Skip to main content

presolve_compiler/
intermediate_representation.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::path::{Path, PathBuf};
3
4use crate::component_graph::{
5    ArithmeticOperator, ComparisonOperator, LogicalOperator, UnaryOperator,
6};
7use crate::{
8    ApplicationSemanticModel, CapabilityOperationId, CapabilityOperationKind, ComponentNode,
9    ComputedPurity, ComputedValue, ConsumerId, ContextConsumerAvailabilityStatus,
10    ContextEvaluationBatchId, ContextSourcePlanStatus, ContextValueSourceId, Effect,
11    EffectCompatibility, EffectStatementKind, EffectValidation, ExpressionNode, ExpressionNodeKind,
12    ResourceId, SemanticId, SemanticReference, SemanticReferenceKind, SemanticType, SemanticTypeId,
13    SerializableValue, SourceProvenance, EFFECT_CAPABILITY_REGISTRY,
14};
15
16/// Compiler-owned intermediate representation, independent of backend output.
17#[derive(Debug, Clone, PartialEq, Eq, Default)]
18pub struct IntermediateRepresentation {
19    pub modules: Vec<IrModule>,
20    pub context_ir: ContextIrReport,
21}
22
23/// One source module represented in the canonical IR.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct IrModule {
26    pub path: PathBuf,
27    pub components: Vec<SemanticId>,
28    pub storages: Vec<IrStorage>,
29    pub storage_initializers: Vec<IrInstruction>,
30    pub template_entrypoints: Vec<IrTemplateEntrypoint>,
31    pub functions: Vec<IrFunction>,
32    pub computed_evaluations: Vec<IrComputedEvaluation>,
33    pub effect_executions: Vec<IrEffectExecution>,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct IrTemplateEntrypoint {
38    pub template: SemanticId,
39    pub render_method: SemanticId,
40    pub provenance: SourceProvenance,
41}
42
43/// Canonical IR evaluation result for one computed semantic entity.
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct IrComputedEvaluation {
46    pub computed: SemanticId,
47    pub function: SemanticId,
48    pub result: IrValueId,
49    pub provenance: SourceProvenance,
50}
51
52/// Compiler-owned executable record for one schedulable effect entity.
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct IrEffectExecution {
55    pub effect: SemanticId,
56    pub function: SemanticId,
57    pub entry_block: IrBlockId,
58    pub completion: IrEffectCompletion,
59    pub capability_operations: Vec<CapabilityOperationId>,
60    pub cleanup_function: Option<SemanticId>,
61    pub cleanup_entry_block: Option<IrBlockId>,
62    pub cleanup_capability_operations: Vec<CapabilityOperationId>,
63    pub provenance: SourceProvenance,
64}
65
66/// Stable compiler-owned Context slot identity. It is not a runtime lookup key.
67#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
68pub struct ContextValueSlotId(String);
69
70impl ContextValueSlotId {
71    #[must_use]
72    pub fn for_source(source: &ContextValueSourceId) -> Self {
73        match source {
74            ContextValueSourceId::Provider(provider) => Self(format!("{provider}/context-slot")),
75            ContextValueSourceId::ContextDefault(context) => {
76                Self(format!("{context}/default-context-slot"))
77            }
78        }
79    }
80
81    #[must_use]
82    pub fn as_str(&self) -> &str {
83        &self.0
84    }
85}
86
87impl std::fmt::Display for ContextValueSlotId {
88    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        formatter.write_str(&self.0)
90    }
91}
92
93/// Typed generated-function identity for one planned Context value source.
94#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
95pub struct ContextSourceFunctionId(SemanticId);
96
97impl ContextSourceFunctionId {
98    #[must_use]
99    pub fn for_source(source: &ContextValueSourceId) -> Self {
100        match source {
101            ContextValueSourceId::Provider(provider) => {
102                Self(provider.as_semantic_id().context_provider_function())
103            }
104            ContextValueSourceId::ContextDefault(context) => {
105                Self(context.as_semantic_id().context_default_function())
106            }
107        }
108    }
109
110    #[must_use]
111    pub const fn as_semantic_id(&self) -> &SemanticId {
112        &self.0
113    }
114}
115
116impl std::fmt::Display for ContextSourceFunctionId {
117    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        self.0.fmt(formatter)
119    }
120}
121
122/// Typed canonical operation identity for one available Consumer Context load.
123#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
124pub struct ContextConsumerLoadId(SemanticId);
125
126impl ContextConsumerLoadId {
127    #[must_use]
128    pub fn for_consumer(consumer: &ConsumerId) -> Self {
129        Self(consumer.as_semantic_id().context_consumer_load())
130    }
131
132    #[must_use]
133    pub const fn as_semantic_id(&self) -> &SemanticId {
134        &self.0
135    }
136}
137
138impl std::fmt::Display for ContextConsumerLoadId {
139    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        self.0.fmt(formatter)
141    }
142}
143
144/// One compiler-owned source function and Context-slot initialization plan.
145#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct IrContextSourceEvaluation {
147    pub source: ContextValueSourceId,
148    pub context: crate::ContextId,
149    pub function: ContextSourceFunctionId,
150    pub entry_block: IrBlockId,
151    pub result: IrValueId,
152    pub slot: ContextValueSlotId,
153    pub evaluation_batch: ContextEvaluationBatchId,
154    pub prerequisite_computed_batches: Vec<u32>,
155    pub provenance: SourceProvenance,
156}
157
158/// One retained, compiler-owned Context-slot load operation.
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub struct IrContextLoad {
161    pub id: ContextConsumerLoadId,
162    pub slot: ContextValueSlotId,
163    pub result: IrValueId,
164}
165
166impl IrContextLoad {
167    /// The generic canonical instruction form represented by this retained
168    /// binding load. Loads have no generated Consumer function in G10.
169    #[must_use]
170    pub fn kind(&self) -> IrInstructionKind {
171        IrInstructionKind::LoadContextSlot {
172            slot: self.slot.clone(),
173        }
174    }
175}
176
177/// One available Consumer's exact compiler-selected Context-slot binding.
178#[derive(Debug, Clone, PartialEq, Eq)]
179pub struct IrContextConsumerBinding {
180    pub consumer: ConsumerId,
181    pub context: crate::ContextId,
182    pub source: ContextValueSourceId,
183    pub slot: ContextValueSlotId,
184    pub load: IrContextLoad,
185    pub semantic_type: SemanticTypeId,
186    pub provenance: SourceProvenance,
187}
188
189/// Immutable G10 Context IR products, ordered by G9 batches and Consumer ID.
190#[derive(Debug, Clone, PartialEq, Eq, Default)]
191pub struct ContextIrReport {
192    pub source_evaluations: Vec<IrContextSourceEvaluation>,
193    pub consumer_bindings: Vec<IrContextConsumerBinding>,
194}
195
196/// One G10 source evaluation retained after immutable Context-only optimization.
197#[derive(Debug, Clone, PartialEq, Eq)]
198pub struct OptimizedIrContextSourceEvaluation {
199    pub source: ContextValueSourceId,
200    pub context: crate::ContextId,
201    pub function: ContextSourceFunctionId,
202    pub entry_block: IrBlockId,
203    pub result: IrValueId,
204    pub slot: ContextValueSlotId,
205    pub evaluation_batch: ContextEvaluationBatchId,
206    pub prerequisite_computed_batches: Vec<u32>,
207    pub provenance: SourceProvenance,
208}
209
210impl From<&IrContextSourceEvaluation> for OptimizedIrContextSourceEvaluation {
211    fn from(evaluation: &IrContextSourceEvaluation) -> Self {
212        Self {
213            source: evaluation.source.clone(),
214            context: evaluation.context.clone(),
215            function: evaluation.function.clone(),
216            entry_block: evaluation.entry_block.clone(),
217            result: evaluation.result.clone(),
218            slot: evaluation.slot.clone(),
219            evaluation_batch: evaluation.evaluation_batch.clone(),
220            prerequisite_computed_batches: evaluation.prerequisite_computed_batches.clone(),
221            provenance: evaluation.provenance.clone(),
222        }
223    }
224}
225
226/// Immutable G11 optimization product for G10-generated Context source IR.
227#[derive(Debug, Clone, PartialEq, Eq)]
228pub struct OptimizedContextIrReport {
229    pub source_report: ContextIrReport,
230    pub optimized_module: IntermediateRepresentation,
231    pub source_evaluations: Vec<OptimizedIrContextSourceEvaluation>,
232    pub pass_metrics: Vec<IrOptimizationPassReport>,
233}
234
235impl ContextIrReport {
236    #[must_use]
237    pub fn context_source_evaluation(
238        &self,
239        source: &ContextValueSourceId,
240    ) -> Option<&IrContextSourceEvaluation> {
241        self.source_evaluations
242            .iter()
243            .find(|evaluation| evaluation.source == *source)
244    }
245
246    #[must_use]
247    pub fn context_consumer_binding(
248        &self,
249        consumer: &ConsumerId,
250    ) -> Option<&IrContextConsumerBinding> {
251        self.consumer_bindings
252            .iter()
253            .find(|binding| binding.consumer == *consumer)
254    }
255}
256
257/// F10 effects complete normally and produce no semantic result value.
258#[derive(Debug, Clone, Copy, PartialEq, Eq)]
259pub enum IrEffectCompletion {
260    Normal,
261}
262
263impl IntermediateRepresentation {
264    /// Resolves the canonical F10 function identity for one lowered effect.
265    #[must_use]
266    pub fn effect_ir_function(&self, effect: &SemanticId) -> Option<&SemanticId> {
267        self.modules
268            .iter()
269            .flat_map(|module| &module.effect_executions)
270            .find(|execution| execution.effect == *effect)
271            .map(|execution| &execution.function)
272    }
273
274    #[must_use]
275    pub fn context_source_evaluation(
276        &self,
277        source: &ContextValueSourceId,
278    ) -> Option<&IrContextSourceEvaluation> {
279        self.context_ir.context_source_evaluation(source)
280    }
281
282    #[must_use]
283    pub fn context_consumer_binding(
284        &self,
285        consumer: &ConsumerId,
286    ) -> Option<&IrContextConsumerBinding> {
287        self.context_ir.context_consumer_binding(consumer)
288    }
289}
290
291/// A stable compiler-owned DOM node identity within a template entrypoint.
292#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
293pub struct IrDomNodeId(String);
294
295impl IrDomNodeId {
296    #[must_use]
297    pub fn for_template(template: &SemanticId, path: &str) -> Self {
298        Self(format!("{template}/dom:{path}"))
299    }
300
301    #[must_use]
302    pub fn as_str(&self) -> &str {
303        &self.0
304    }
305}
306
307/// Backend-neutral DOM node semantics.
308#[derive(Debug, Clone, PartialEq, Eq)]
309pub struct IrDomNode {
310    pub id: IrDomNodeId,
311    pub kind: IrDomNodeKind,
312    pub provenance: SourceProvenance,
313}
314
315/// Structural DOM node forms before text, bindings, attributes, and events are lowered.
316#[derive(Debug, Clone, PartialEq, Eq)]
317pub enum IrDomNodeKind {
318    Element {
319        tag: String,
320        children: Vec<IrDomNodeId>,
321    },
322    Fragment {
323        children: Vec<IrDomNodeId>,
324    },
325}
326
327/// Text semantics owned by canonical DOM IR rather than a backend renderer.
328#[derive(Debug, Clone, PartialEq, Eq)]
329pub struct IrDomText {
330    pub node: IrDomNodeId,
331    pub value: String,
332    pub provenance: SourceProvenance,
333}
334
335/// A value-driven DOM update target, independent of backend rendering syntax.
336#[derive(Debug, Clone, PartialEq, Eq)]
337pub struct IrDomBinding {
338    pub node: IrDomNodeId,
339    pub value: IrValueId,
340    pub provenance: SourceProvenance,
341}
342
343/// A static or value-driven DOM attribute independent of backend serialization.
344#[derive(Debug, Clone, PartialEq, Eq)]
345pub struct IrDomAttribute {
346    pub node: IrDomNodeId,
347    pub name: String,
348    pub value: IrDomAttributeValue,
349    pub provenance: SourceProvenance,
350}
351
352#[derive(Debug, Clone, PartialEq, Eq)]
353pub enum IrDomAttributeValue {
354    Static(String),
355    Binding(IrValueId),
356}
357
358/// A DOM event bound to an authored handler semantic identity.
359#[derive(Debug, Clone, PartialEq, Eq)]
360pub struct IrDomEvent {
361    pub node: IrDomNodeId,
362    pub event: String,
363    pub handler: SemanticId,
364    pub provenance: SourceProvenance,
365}
366
367/// Conditional DOM output driven by a canonical IR value.
368#[derive(Debug, Clone, PartialEq, Eq)]
369pub struct IrDomConditional {
370    pub condition: IrValueId,
371    pub when_true: IrDomNodeId,
372    pub when_false: Option<IrDomNodeId>,
373    pub provenance: SourceProvenance,
374}
375
376/// Repeated DOM output driven by one canonical iterable value.
377#[derive(Debug, Clone, PartialEq, Eq)]
378pub struct IrDomList {
379    pub iterable: IrValueId,
380    pub item: IrValueId,
381    pub index: Option<IrValueId>,
382    pub body: IrDomNodeId,
383    pub provenance: SourceProvenance,
384}
385
386/// Deterministic read-only lookup surface for canonical DOM nodes.
387#[derive(Debug, Clone, PartialEq, Eq)]
388pub struct IrDomInspection {
389    pub nodes: BTreeMap<IrDomNodeId, IrDomNode>,
390}
391
392/// Compiler-owned reactive dependency topology.
393#[derive(Debug, Clone, PartialEq, Eq, Default)]
394pub struct IrReactiveGraph {
395    pub nodes: BTreeMap<String, IrReactiveNode>,
396    pub edges: Vec<IrReactiveEdge>,
397}
398
399/// Immutable transitive dependency and dependent topology derived from a
400/// canonical reactive graph.
401#[derive(Debug, Clone, PartialEq, Eq, Default)]
402pub struct IrReactiveTransitiveAnalysis {
403    pub dependencies: BTreeMap<String, Vec<String>>,
404    pub dependents: BTreeMap<String, Vec<String>>,
405}
406
407/// One strongly connected group of computed reactive nodes.
408#[derive(Debug, Clone, PartialEq, Eq)]
409pub struct IrReactiveCycle {
410    pub nodes: Vec<String>,
411}
412
413/// Immutable computed dependency-cycle analysis derived from a canonical
414/// reactive graph.
415#[derive(Debug, Clone, PartialEq, Eq, Default)]
416pub struct IrReactiveCycleAnalysis {
417    pub cycles: Vec<IrReactiveCycle>,
418}
419
420/// Compiler-generated evaluation order and update batches for computed values.
421#[derive(Debug, Clone, PartialEq, Eq, Default)]
422pub struct IrComputedEvaluationPlan {
423    pub evaluation_order: Vec<String>,
424    pub update_batches: Vec<Vec<String>>,
425    pub unplanned: Vec<String>,
426}
427
428/// Derive deterministic transitive reactive dependency and dependent maps.
429///
430/// This analysis preserves direct graph topology and deliberately makes no
431/// cycle diagnosis or scheduling decision.
432#[must_use]
433pub fn analyze_reactive_transitive_graph(graph: &IrReactiveGraph) -> IrReactiveTransitiveAnalysis {
434    let dependencies = graph
435        .nodes
436        .keys()
437        .map(|id| {
438            (
439                id.clone(),
440                graph.transitive_targets(id, IrReactiveEdgeKind::Reads),
441            )
442        })
443        .collect();
444    let dependents = graph
445        .nodes
446        .keys()
447        .map(|id| {
448            (
449                id.clone(),
450                graph.transitive_targets(id, IrReactiveEdgeKind::Invalidates),
451            )
452        })
453        .collect();
454
455    IrReactiveTransitiveAnalysis {
456        dependencies,
457        dependents,
458    }
459}
460
461/// Detect deterministic computed dependency cycles from direct reactive reads.
462#[must_use]
463pub fn analyze_reactive_cycles(graph: &IrReactiveGraph) -> IrReactiveCycleAnalysis {
464    let computed = graph
465        .nodes
466        .iter()
467        .filter(|(_, node)| node.kind == IrReactiveNodeKind::Computed)
468        .map(|(id, _)| id.clone())
469        .collect::<BTreeSet<_>>();
470    let adjacency = computed
471        .iter()
472        .map(|id| {
473            let targets = graph
474                .edges
475                .iter()
476                .filter(|edge| {
477                    edge.source == *id
478                        && edge.kind == IrReactiveEdgeKind::Reads
479                        && computed.contains(&edge.target)
480                })
481                .map(|edge| edge.target.clone())
482                .collect();
483            (id.clone(), targets)
484        })
485        .collect::<BTreeMap<String, BTreeSet<String>>>();
486    let reverse_adjacency = reverse_reactive_adjacency(&adjacency);
487    let mut visited = BTreeSet::new();
488    let mut finish_order = Vec::new();
489    for node in &computed {
490        visit_reactive_node(node, &adjacency, &mut visited, &mut finish_order);
491    }
492
493    let mut cycles = Vec::new();
494    visited.clear();
495    for node in finish_order.into_iter().rev() {
496        if !visited.insert(node.clone()) {
497            continue;
498        }
499        let mut members = BTreeSet::new();
500        collect_reactive_component(&node, &reverse_adjacency, &mut visited, &mut members);
501        let is_self_cycle = members.len() == 1
502            && adjacency
503                .get(&node)
504                .is_some_and(|targets| targets.contains(&node));
505        if members.len() > 1 || is_self_cycle {
506            cycles.push(IrReactiveCycle {
507                nodes: members.into_iter().collect(),
508            });
509        }
510    }
511    cycles.sort_by(|left, right| left.nodes.cmp(&right.nodes));
512
513    IrReactiveCycleAnalysis { cycles }
514}
515
516/// Build a deterministic computed evaluation plan through the canonical update
517/// scheduler.
518#[must_use]
519pub fn plan_computed_evaluation(graph: &IrReactiveGraph) -> IrComputedEvaluationPlan {
520    let nodes = graph
521        .nodes
522        .iter()
523        .filter(|(_, node)| node.kind == IrReactiveNodeKind::Computed)
524        .map(|(id, node)| (id.clone(), node.clone()))
525        .collect::<BTreeMap<_, _>>();
526    let edges = graph
527        .edges
528        .iter()
529        .filter(|edge| {
530            edge.kind == IrReactiveEdgeKind::Invalidates
531                && nodes.contains_key(&edge.source)
532                && nodes.contains_key(&edge.target)
533        })
534        .cloned()
535        .collect();
536    let inspection = IrUpdateScheduler::new(IrReactiveGraph { nodes, edges }).inspect();
537
538    IrComputedEvaluationPlan {
539        evaluation_order: inspection.order,
540        update_batches: inspection.batches,
541        unplanned: inspection.cycles,
542    }
543}
544
545fn reverse_reactive_adjacency(
546    adjacency: &BTreeMap<String, BTreeSet<String>>,
547) -> BTreeMap<String, BTreeSet<String>> {
548    let mut reversed = adjacency
549        .keys()
550        .cloned()
551        .map(|node| (node, BTreeSet::new()))
552        .collect::<BTreeMap<_, _>>();
553    for (source, targets) in adjacency {
554        for target in targets {
555            reversed
556                .get_mut(target)
557                .expect("computed dependency target should be a reactive node")
558                .insert(source.clone());
559        }
560    }
561    reversed
562}
563
564fn visit_reactive_node(
565    node: &str,
566    adjacency: &BTreeMap<String, BTreeSet<String>>,
567    visited: &mut BTreeSet<String>,
568    finish_order: &mut Vec<String>,
569) {
570    if !visited.insert(node.to_string()) {
571        return;
572    }
573    for target in adjacency
574        .get(node)
575        .expect("computed reactive node should have adjacency")
576    {
577        visit_reactive_node(target, adjacency, visited, finish_order);
578    }
579    finish_order.push(node.to_string());
580}
581
582fn collect_reactive_component(
583    node: &str,
584    adjacency: &BTreeMap<String, BTreeSet<String>>,
585    visited: &mut BTreeSet<String>,
586    members: &mut BTreeSet<String>,
587) {
588    members.insert(node.to_string());
589    for target in adjacency
590        .get(node)
591        .expect("computed reactive node should have reverse adjacency")
592    {
593        if visited.insert(target.clone()) {
594            collect_reactive_component(target, adjacency, visited, members);
595        }
596    }
597}
598
599/// Build compiler-owned reactive topology from canonical state, computed, and
600/// valid effect-reference products.
601///
602/// # Panics
603///
604/// Panics when a state field has no canonical source provenance.
605#[must_use]
606pub fn build_reactive_graph(
607    components: &[ComponentNode],
608    computed_values: &BTreeMap<SemanticId, ComputedValue>,
609    effects: &BTreeMap<SemanticId, Effect>,
610    resource_declarations: &BTreeMap<ResourceId, crate::ResourceDeclaration>,
611    references: &[SemanticReference],
612    provenance: &BTreeMap<SemanticId, SourceProvenance>,
613) -> IrReactiveGraph {
614    let mut nodes = BTreeMap::new();
615    for component in components {
616        for field in &component.state_fields {
617            let id = field.id.as_str().to_string();
618            nodes.insert(
619                id.clone(),
620                IrReactiveNode {
621                    id,
622                    kind: IrReactiveNodeKind::State,
623                    provenance: provenance
624                        .get(&field.id)
625                        .expect("state field should have canonical provenance")
626                        .clone(),
627                },
628            );
629        }
630    }
631    for resource in resource_declarations.values() {
632        let id = resource.id.as_str().to_string();
633        nodes.insert(
634            id.clone(),
635            IrReactiveNode {
636                id,
637                kind: IrReactiveNodeKind::Resource,
638                provenance: resource.provenance.clone(),
639            },
640        );
641    }
642    for computed in computed_values.values() {
643        let id = computed.id.as_str().to_string();
644        nodes.insert(
645            id.clone(),
646            IrReactiveNode {
647                id,
648                kind: IrReactiveNodeKind::Computed,
649                provenance: computed.provenance.clone(),
650            },
651        );
652    }
653    for effect in effects
654        .values()
655        .filter(|effect| effect.validation == EffectValidation::Valid)
656    {
657        let id = effect.id.as_str().to_string();
658        nodes.insert(
659            id.clone(),
660            IrReactiveNode {
661                id,
662                kind: IrReactiveNodeKind::Effect,
663                provenance: effect.provenance.clone(),
664            },
665        );
666    }
667
668    let mut edges = Vec::new();
669    for reference in references.iter().filter(|reference| {
670        matches!(
671            reference.kind,
672            SemanticReferenceKind::ComputedState
673                | SemanticReferenceKind::ComputedComputed
674                | SemanticReferenceKind::ComputedResource
675                | SemanticReferenceKind::EffectState
676                | SemanticReferenceKind::EffectComputed
677        )
678    }) {
679        let source = reference.source.as_str().to_string();
680        let target = reference.target.as_str().to_string();
681        if !nodes.contains_key(&source) || !nodes.contains_key(&target) {
682            continue;
683        }
684        edges.push(IrReactiveEdge {
685            source: source.clone(),
686            target: target.clone(),
687            kind: IrReactiveEdgeKind::Reads,
688            provenance: reference.provenance.clone(),
689        });
690        edges.push(IrReactiveEdge {
691            source: target,
692            target: source,
693            kind: IrReactiveEdgeKind::Invalidates,
694            provenance: reference.provenance.clone(),
695        });
696    }
697    edges.sort_by(|left, right| {
698        (left.kind, left.source.as_str(), left.target.as_str()).cmp(&(
699            right.kind,
700            right.source.as_str(),
701            right.target.as_str(),
702        ))
703    });
704    edges.dedup_by(|left, right| {
705        left.kind == right.kind && left.source == right.source && left.target == right.target
706    });
707
708    IrReactiveGraph { nodes, edges }
709}
710
711#[derive(Debug, Clone, PartialEq, Eq)]
712pub struct IrReactiveNode {
713    pub id: String,
714    pub kind: IrReactiveNodeKind,
715    pub provenance: SourceProvenance,
716}
717
718#[derive(Debug, Clone, Copy, PartialEq, Eq)]
719pub enum IrReactiveNodeKind {
720    State,
721    Resource,
722    Computed,
723    Effect,
724    Action,
725    Template,
726}
727
728#[derive(Debug, Clone, PartialEq, Eq)]
729pub struct IrReactiveEdge {
730    pub source: String,
731    pub target: String,
732    pub kind: IrReactiveEdgeKind,
733    pub provenance: SourceProvenance,
734}
735
736/// Compiler-owned foundation for planning reactive updates from dependency topology.
737#[derive(Debug, Clone, PartialEq, Eq)]
738pub struct IrUpdateScheduler {
739    pub graph: IrReactiveGraph,
740}
741
742/// Read-only summary of one compiler-generated update plan.
743#[derive(Debug, Clone, PartialEq, Eq)]
744pub struct IrSchedulerInspection {
745    pub order: Vec<String>,
746    pub batches: Vec<Vec<String>>,
747    pub cycles: Vec<String>,
748}
749
750impl IrUpdateScheduler {
751    #[must_use]
752    pub fn new(graph: IrReactiveGraph) -> Self {
753        Self { graph }
754    }
755
756    #[must_use]
757    pub fn dependency_order(&self) -> Vec<String> {
758        let mut incoming = self
759            .graph
760            .nodes
761            .keys()
762            .cloned()
763            .map(|id| (id, 0_usize))
764            .collect::<BTreeMap<_, _>>();
765        for edge in &self.graph.edges {
766            if let Some(count) = incoming.get_mut(&edge.target) {
767                *count += 1;
768            }
769        }
770        let mut ready = incoming
771            .iter()
772            .filter(|(_, count)| **count == 0)
773            .map(|(id, _)| id.clone())
774            .collect::<BTreeSet<_>>();
775        let mut order = Vec::new();
776        while let Some(id) = ready.pop_first() {
777            order.push(id.clone());
778            for edge in self.graph.dependents_of(&id) {
779                if let Some(count) = incoming.get_mut(&edge.target) {
780                    *count -= 1;
781                    if *count == 0 {
782                        ready.insert(edge.target.clone());
783                    }
784                }
785            }
786        }
787        order
788    }
789
790    #[must_use]
791    pub fn update_batches(&self) -> Vec<Vec<String>> {
792        let mut incoming = self
793            .graph
794            .nodes
795            .keys()
796            .cloned()
797            .map(|id| (id, 0_usize))
798            .collect::<BTreeMap<_, _>>();
799        for edge in &self.graph.edges {
800            if let Some(count) = incoming.get_mut(&edge.target) {
801                *count += 1;
802            }
803        }
804        let mut ready = incoming
805            .iter()
806            .filter(|(_, count)| **count == 0)
807            .map(|(id, _)| id.clone())
808            .collect::<BTreeSet<_>>();
809        let mut batches = Vec::new();
810        while !ready.is_empty() {
811            let batch = std::mem::take(&mut ready).into_iter().collect::<Vec<_>>();
812            for id in &batch {
813                for edge in self.graph.dependents_of(id) {
814                    if let Some(count) = incoming.get_mut(&edge.target) {
815                        *count -= 1;
816                        if *count == 0 {
817                            ready.insert(edge.target.clone());
818                        }
819                    }
820                }
821            }
822            batches.push(batch);
823        }
824        batches
825    }
826
827    #[must_use]
828    pub fn cyclic_nodes(&self) -> Vec<String> {
829        let ordered = self.dependency_order().into_iter().collect::<BTreeSet<_>>();
830        self.graph
831            .nodes
832            .keys()
833            .filter(|id| !ordered.contains(*id))
834            .cloned()
835            .collect()
836    }
837
838    #[must_use]
839    pub fn inspect(&self) -> IrSchedulerInspection {
840        IrSchedulerInspection {
841            order: self.dependency_order(),
842            batches: self.update_batches(),
843            cycles: self.cyclic_nodes(),
844        }
845    }
846}
847
848#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
849pub enum IrReactiveEdgeKind {
850    Reads,
851    Invalidates,
852}
853
854impl IrReactiveGraph {
855    fn transitive_targets(&self, source: &str, kind: IrReactiveEdgeKind) -> Vec<String> {
856        let mut discovered = BTreeSet::new();
857        let mut pending = BTreeSet::from([source.to_string()]);
858        while let Some(current) = pending.pop_first() {
859            for edge in self
860                .edges
861                .iter()
862                .filter(|edge| edge.source == current && edge.kind == kind)
863            {
864                if discovered.insert(edge.target.clone()) {
865                    pending.insert(edge.target.clone());
866                }
867            }
868        }
869        discovered.into_iter().collect()
870    }
871
872    #[must_use]
873    pub fn computed_dependencies(&self, computed: &str) -> Vec<&IrReactiveEdge> {
874        matches!(
875            self.nodes.get(computed).map(|node| node.kind),
876            Some(IrReactiveNodeKind::Computed)
877        )
878        .then(|| {
879            self.edges
880                .iter()
881                .filter(|edge| edge.source == computed && edge.kind == IrReactiveEdgeKind::Reads)
882                .collect()
883        })
884        .unwrap_or_default()
885    }
886
887    #[must_use]
888    pub fn action_dependencies(&self, action: &str) -> Vec<&IrReactiveEdge> {
889        matches!(
890            self.nodes.get(action).map(|node| node.kind),
891            Some(IrReactiveNodeKind::Action)
892        )
893        .then(|| {
894            self.edges
895                .iter()
896                .filter(|edge| edge.source == action && edge.kind == IrReactiveEdgeKind::Reads)
897                .collect()
898        })
899        .unwrap_or_default()
900    }
901
902    #[must_use]
903    pub fn invalidations_from(&self, source: &str) -> Vec<&IrReactiveEdge> {
904        self.edges
905            .iter()
906            .filter(|edge| edge.source == source && edge.kind == IrReactiveEdgeKind::Invalidates)
907            .collect()
908    }
909
910    #[must_use]
911    pub fn dependencies_of(&self, target: &str) -> Vec<&IrReactiveEdge> {
912        self.edges
913            .iter()
914            .filter(|edge| edge.target == target)
915            .collect()
916    }
917
918    #[must_use]
919    pub fn dependents_of(&self, source: &str) -> Vec<&IrReactiveEdge> {
920        self.edges
921            .iter()
922            .filter(|edge| edge.source == source)
923            .collect()
924    }
925}
926
927impl IrReactiveTransitiveAnalysis {
928    #[must_use]
929    pub fn dependencies_of(&self, node: &str) -> &[String] {
930        self.dependencies
931            .get(node)
932            .map(Vec::as_slice)
933            .unwrap_or_default()
934    }
935
936    #[must_use]
937    pub fn dependents_of(&self, node: &str) -> &[String] {
938        self.dependents
939            .get(node)
940            .map(Vec::as_slice)
941            .unwrap_or_default()
942    }
943}
944
945#[must_use]
946pub fn inspect_dom_nodes(nodes: Vec<IrDomNode>) -> IrDomInspection {
947    IrDomInspection {
948        nodes: nodes
949            .into_iter()
950            .map(|node| (node.id.clone(), node))
951            .collect(),
952    }
953}
954
955/// Lowers application component ownership into deterministic IR module structure.
956#[must_use]
957pub fn lower_components_to_ir(model: &ApplicationSemanticModel) -> IntermediateRepresentation {
958    let mut modules = std::collections::BTreeMap::<PathBuf, IrModule>::new();
959    for component in &model.components {
960        let Some(provenance) = model.provenance(&component.id) else {
961            continue;
962        };
963        let module = modules
964            .entry(provenance.path.clone())
965            .or_insert_with(|| IrModule {
966                path: provenance.path.clone(),
967                components: Vec::new(),
968                storages: Vec::new(),
969                storage_initializers: Vec::new(),
970                template_entrypoints: Vec::new(),
971                functions: Vec::new(),
972                computed_evaluations: Vec::new(),
973                effect_executions: Vec::new(),
974            });
975        lower_component_to_ir(model, component, module);
976    }
977    let context_ir = lower_context_ir(model, &mut modules);
978    IntermediateRepresentation {
979        modules: modules.into_values().collect(),
980        context_ir,
981    }
982}
983
984#[allow(clippy::too_many_lines)]
985fn lower_component_to_ir(
986    model: &ApplicationSemanticModel,
987    component: &ComponentNode,
988    module: &mut IrModule,
989) {
990    module.components.push(component.id.clone());
991    module
992        .storages
993        .extend(component.state_fields.iter().filter_map(|field| {
994            model.provenance(&field.id).map(|provenance| IrStorage {
995                id: IrStorageId::for_semantic_origin(&field.id),
996                semantic_origin: field.id.clone(),
997                value_type: model
998                    .semantic_types
999                    .assignments
1000                    .get(&field.id)
1001                    .map_or(SemanticType::Unknown, |assignment| {
1002                        assignment.semantic_type.clone()
1003                    }),
1004                initial_value: field.initial_value.clone(),
1005                provenance: provenance.clone(),
1006            })
1007        }));
1008    let storage_offset = module.storage_initializers.len();
1009    module
1010        .storage_initializers
1011        .extend(
1012            component
1013                .state_fields
1014                .iter()
1015                .enumerate()
1016                .filter_map(|(index, field)| {
1017                    model.provenance(&field.id).map(|provenance| IrInstruction {
1018                        id: IrInstructionId::for_module(&module.path, storage_offset + index),
1019                        provenance: provenance.clone(),
1020                        result: None,
1021                        semantic_origin: Some(field.id.clone()),
1022                        kind: IrInstructionKind::InitializeStorage {
1023                            storage: IrStorageId::for_semantic_origin(&field.id),
1024                        },
1025                    })
1026                }),
1027        );
1028    if let (Some(template), Some(render)) = (
1029        model
1030            .templates
1031            .iter()
1032            .find(|template| template.component_name == component.class_name),
1033        component
1034            .methods
1035            .iter()
1036            .find(|method| method.name == "render"),
1037    ) {
1038        module.template_entrypoints.push(IrTemplateEntrypoint {
1039            template: template.id.clone(),
1040            render_method: render.id.clone(),
1041            provenance: template.provenance.clone(),
1042        });
1043    }
1044    module
1045        .functions
1046        .extend(component.methods.iter().filter_map(|method| {
1047            model.provenance(&method.id).map(|provenance| IrFunction {
1048                id: method.id.clone(),
1049                name: method.name.clone(),
1050                provenance: provenance.clone(),
1051                entry_block: IrBlockId::entry_for(&method.id),
1052                blocks: vec![IrBlock {
1053                    id: IrBlockId::entry_for(&method.id),
1054                    provenance: provenance.clone(),
1055                    instructions: Vec::new(),
1056                }],
1057                branch_edges: Vec::new(),
1058                values: BTreeMap::new(),
1059                loops: Vec::new(),
1060            })
1061        }));
1062    for computed in model
1063        .computed_evaluation_plan
1064        .evaluation_order
1065        .iter()
1066        .filter_map(|id| {
1067            model
1068                .computed_values
1069                .values()
1070                .find(|computed| computed.id.as_str() == id)
1071        })
1072    {
1073        if computed.owner.entity_id() != Some(&component.id)
1074            || computed.purity != ComputedPurity::Pure
1075        {
1076            continue;
1077        }
1078        if let Some((function, evaluation)) = lower_computed_evaluation(model, component, computed)
1079        {
1080            module.functions.push(function);
1081            module.computed_evaluations.push(evaluation);
1082        }
1083    }
1084    let executable_computed = module
1085        .computed_evaluations
1086        .iter()
1087        .map(|evaluation| evaluation.computed.clone())
1088        .collect::<BTreeSet<_>>();
1089    for effect in model.effects.values().filter(|effect| {
1090        effect.owner.entity_id() == Some(&component.id)
1091            && effect.validation == EffectValidation::Valid
1092            && effect_is_scheduled(model, &effect.id)
1093    }) {
1094        if let Some((functions, execution)) =
1095            lower_effect_execution(model, component, effect, &executable_computed)
1096        {
1097            module.functions.extend(functions);
1098            module.effect_executions.push(execution);
1099        }
1100    }
1101}
1102
1103fn lower_context_ir(
1104    model: &ApplicationSemanticModel,
1105    modules: &mut BTreeMap<PathBuf, IrModule>,
1106) -> ContextIrReport {
1107    let executable_computed = modules
1108        .values()
1109        .flat_map(|module| &module.computed_evaluations)
1110        .map(|evaluation| evaluation.computed.clone())
1111        .collect::<BTreeSet<_>>();
1112    let mut report = ContextIrReport::default();
1113    let mut slots = BTreeMap::new();
1114
1115    for batch in &model.context_evaluation.evaluation_batches {
1116        for source in &batch.sources {
1117            let Some(entry) = model.context_evaluation.context_source_plan(source) else {
1118                continue;
1119            };
1120            if entry.status != ContextSourcePlanStatus::Planned {
1121                continue;
1122            }
1123            let Some(component) = model
1124                .components
1125                .iter()
1126                .find(|component| component.id == entry.owner_component)
1127            else {
1128                continue;
1129            };
1130            let Some((function, evaluation)) = lower_context_source(
1131                model,
1132                component,
1133                entry,
1134                batch.id.clone(),
1135                &executable_computed,
1136            ) else {
1137                continue;
1138            };
1139            let Some(module) = modules.get_mut(&entry.provenance.path) else {
1140                continue;
1141            };
1142            slots.insert(source.clone(), evaluation.slot.clone());
1143            module.functions.push(function);
1144            report.source_evaluations.push(evaluation);
1145        }
1146    }
1147
1148    for (consumer, entry) in &model.context_evaluation.consumer_entries {
1149        if entry.status != ContextConsumerAvailabilityStatus::Available {
1150            continue;
1151        }
1152        let (Some(source), Some(slot), Some(entity), Some(context)) = (
1153            entry.selected_source.as_ref(),
1154            entry
1155                .selected_source
1156                .as_ref()
1157                .and_then(|source| slots.get(source)),
1158            model.consumers.get(consumer),
1159            model
1160                .consumers
1161                .get(consumer)
1162                .and_then(crate::ConsumerEntity::context),
1163        ) else {
1164            continue;
1165        };
1166        let load = IrContextLoad {
1167            id: ContextConsumerLoadId::for_consumer(consumer),
1168            slot: slot.clone(),
1169            result: IrValueId::for_function(
1170                ContextConsumerLoadId::for_consumer(consumer).as_semantic_id(),
1171                0,
1172            ),
1173        };
1174        report.consumer_bindings.push(IrContextConsumerBinding {
1175            consumer: consumer.clone(),
1176            context: context.clone(),
1177            source: source.clone(),
1178            slot: slot.clone(),
1179            load,
1180            semantic_type: entity.requested_type_id.clone(),
1181            provenance: entry.provenance.clone(),
1182        });
1183    }
1184    report
1185}
1186
1187fn lower_context_source(
1188    model: &ApplicationSemanticModel,
1189    component: &ComponentNode,
1190    entry: &crate::ContextSourcePlanEntry,
1191    evaluation_batch: ContextEvaluationBatchId,
1192    executable_computed: &BTreeSet<SemanticId>,
1193) -> Option<(IrFunction, IrContextSourceEvaluation)> {
1194    let function_id = ContextSourceFunctionId::for_source(&entry.source);
1195    let entry_block = IrBlockId::entry_for(function_id.as_semantic_id());
1196    let dependencies = entry
1197        .required_state
1198        .iter()
1199        .chain(&entry.required_computed)
1200        .cloned()
1201        .collect::<BTreeSet<_>>();
1202    let mut lowering = ExpressionIrLowering {
1203        model,
1204        component,
1205        function: function_id.as_semantic_id(),
1206        reference_owner: match &entry.source {
1207            ContextValueSourceId::Provider(provider) => provider.as_semantic_id(),
1208            ContextValueSourceId::ContextDefault(context) => context.as_semantic_id(),
1209        },
1210        entry_block: entry_block.clone(),
1211        instructions: Vec::new(),
1212        values: BTreeMap::new(),
1213        executable_computed: Some(executable_computed),
1214        allowed_dependencies: Some(&dependencies),
1215    };
1216    let result = lowering.lower_node(&entry.expression_root)?;
1217    let slot = ContextValueSlotId::for_source(&entry.source);
1218    lowering.instructions.push(IrInstruction {
1219        id: IrInstructionId::for_block(&entry_block, lowering.instructions.len()),
1220        provenance: entry.provenance.clone(),
1221        result: None,
1222        semantic_origin: Some(entry.expression_root.clone()),
1223        kind: IrInstructionKind::InitializeContextSlot {
1224            slot: slot.clone(),
1225            value: result.clone(),
1226        },
1227    });
1228    let function = IrFunction {
1229        id: function_id.as_semantic_id().clone(),
1230        name: format!("context-evaluate:{:?}", entry.source),
1231        provenance: entry.provenance.clone(),
1232        entry_block: entry_block.clone(),
1233        blocks: vec![IrBlock {
1234            id: entry_block.clone(),
1235            provenance: entry.provenance.clone(),
1236            instructions: lowering.instructions,
1237        }],
1238        branch_edges: Vec::new(),
1239        values: lowering.values,
1240        loops: Vec::new(),
1241    };
1242    let evaluation = IrContextSourceEvaluation {
1243        source: entry.source.clone(),
1244        context: entry.context.clone(),
1245        function: function_id,
1246        entry_block,
1247        result,
1248        slot,
1249        evaluation_batch,
1250        prerequisite_computed_batches: entry.prerequisite_computed_batches.clone(),
1251        provenance: entry.provenance.clone(),
1252    };
1253    Some((function, evaluation))
1254}
1255
1256fn lower_computed_evaluation(
1257    model: &ApplicationSemanticModel,
1258    component: &ComponentNode,
1259    computed: &ComputedValue,
1260) -> Option<(IrFunction, IrComputedEvaluation)> {
1261    let root = model.expression_graph.root_for(&computed.id)?.clone();
1262    let entry_block = IrBlockId::entry_for(&computed.id);
1263    let mut lowering = ExpressionIrLowering {
1264        model,
1265        component,
1266        function: &computed.id,
1267        reference_owner: &computed.id,
1268        entry_block: entry_block.clone(),
1269        instructions: Vec::new(),
1270        values: BTreeMap::new(),
1271        executable_computed: None,
1272        allowed_dependencies: None,
1273    };
1274    let result = lowering.lower_node(&root)?;
1275    let function = IrFunction {
1276        id: computed.id.clone(),
1277        name: computed.name.clone(),
1278        provenance: computed.provenance.clone(),
1279        entry_block: entry_block.clone(),
1280        blocks: vec![IrBlock {
1281            id: entry_block,
1282            provenance: computed.provenance.clone(),
1283            instructions: lowering.instructions,
1284        }],
1285        branch_edges: Vec::new(),
1286        values: lowering.values,
1287        loops: Vec::new(),
1288    };
1289    let evaluation = IrComputedEvaluation {
1290        computed: computed.id.clone(),
1291        function: function.id.clone(),
1292        result,
1293        provenance: computed.provenance.clone(),
1294    };
1295    Some((function, evaluation))
1296}
1297
1298fn lower_effect_execution(
1299    model: &ApplicationSemanticModel,
1300    component: &ComponentNode,
1301    effect: &Effect,
1302    executable_computed: &BTreeSet<SemanticId>,
1303) -> Option<(Vec<IrFunction>, IrEffectExecution)> {
1304    let body = model.effect_body(&effect.id)?;
1305    let (function, capability_operations) = lower_effect_program(
1306        model,
1307        component,
1308        effect,
1309        &effect.id,
1310        &effect.name,
1311        &body.statements,
1312        &effect.provenance,
1313        executable_computed,
1314    )?;
1315    let cleanup = if let Some(cleanup) = &body.cleanup {
1316        let id = effect.id.effect_cleanup_program();
1317        let (function, operations) = lower_effect_program(
1318            model,
1319            component,
1320            effect,
1321            &id,
1322            &format!("{} cleanup", effect.name),
1323            &cleanup.statements,
1324            &cleanup.provenance,
1325            executable_computed,
1326        )?;
1327        Some((id, function, operations))
1328    } else {
1329        None
1330    };
1331    let entry_block = function.entry_block.clone();
1332    let (cleanup_function, cleanup_entry_block, cleanup_capability_operations, functions) =
1333        match cleanup {
1334            Some((id, cleanup_function, operations)) => {
1335                let entry = cleanup_function.entry_block.clone();
1336                (
1337                    Some(id),
1338                    Some(entry),
1339                    operations,
1340                    vec![function, cleanup_function],
1341                )
1342            }
1343            None => (None, None, Vec::new(), vec![function]),
1344        };
1345    Some((
1346        functions,
1347        IrEffectExecution {
1348            effect: effect.id.clone(),
1349            function: effect.id.clone(),
1350            entry_block,
1351            completion: IrEffectCompletion::Normal,
1352            capability_operations,
1353            cleanup_function,
1354            cleanup_entry_block,
1355            cleanup_capability_operations,
1356            provenance: effect.provenance.clone(),
1357        },
1358    ))
1359}
1360
1361#[allow(clippy::too_many_arguments)]
1362fn lower_effect_program(
1363    model: &ApplicationSemanticModel,
1364    component: &ComponentNode,
1365    effect: &Effect,
1366    function_id: &SemanticId,
1367    name: &str,
1368    statements: &[SemanticId],
1369    provenance: &SourceProvenance,
1370    executable_computed: &BTreeSet<SemanticId>,
1371) -> Option<(IrFunction, Vec<CapabilityOperationId>)> {
1372    let entry_block = IrBlockId::entry_for(function_id);
1373    let mut lowering = ExpressionIrLowering {
1374        model,
1375        component,
1376        function: function_id,
1377        reference_owner: &effect.id,
1378        entry_block: entry_block.clone(),
1379        instructions: Vec::new(),
1380        values: BTreeMap::new(),
1381        executable_computed: Some(executable_computed),
1382        allowed_dependencies: None,
1383    };
1384    let mut capability_operations = Vec::new();
1385    for statement_id in statements {
1386        let statement = model.effect_statement(statement_id)?;
1387        let record = model.effect_statement_type(statement_id)?;
1388        match &statement.kind {
1389            EffectStatementKind::ExternalMemberAssignment { value, .. } => {
1390                let operation =
1391                    effect_capability_operation(record, CapabilityOperationKind::MemberAssignment)?;
1392                let value = lowering.lower_node(value)?;
1393                lowering.emit_effect(
1394                    statement,
1395                    IrInstructionKind::CapabilityAssign {
1396                        operation: operation.id,
1397                        value,
1398                    },
1399                );
1400                capability_operations.push(operation.id);
1401            }
1402            EffectStatementKind::CapabilityCall { arguments, .. } => {
1403                let operation =
1404                    effect_capability_operation(record, CapabilityOperationKind::MethodCall)?;
1405                let arguments = arguments
1406                    .iter()
1407                    .map(|argument| lowering.lower_node(argument))
1408                    .collect::<Option<Vec<_>>>()?;
1409                lowering.emit_effect(
1410                    statement,
1411                    IrInstructionKind::CapabilityCall {
1412                        operation: operation.id,
1413                        arguments,
1414                    },
1415                );
1416                capability_operations.push(operation.id);
1417            }
1418            EffectStatementKind::EffectReturn { value: None } | EffectStatementKind::Empty => {}
1419            EffectStatementKind::EffectReturn { value: Some(_) }
1420            | EffectStatementKind::Unsupported(_) => return None,
1421        }
1422    }
1423    let function = IrFunction {
1424        id: function_id.clone(),
1425        name: name.to_owned(),
1426        provenance: provenance.clone(),
1427        entry_block: entry_block.clone(),
1428        blocks: vec![IrBlock {
1429            id: entry_block.clone(),
1430            provenance: provenance.clone(),
1431            instructions: lowering.instructions,
1432        }],
1433        branch_edges: Vec::new(),
1434        values: lowering.values,
1435        loops: Vec::new(),
1436    };
1437    Some((function, capability_operations))
1438}
1439
1440fn effect_is_scheduled(model: &ApplicationSemanticModel, effect: &SemanticId) -> bool {
1441    model
1442        .effect_execution_plan
1443        .initial
1444        .effect_batches
1445        .iter()
1446        .chain(
1447            model
1448                .effect_execution_plan
1449                .actions
1450                .iter()
1451                .flat_map(|action| action.effect_batches.iter()),
1452        )
1453        .any(|batch| batch.effects.contains(effect))
1454}
1455
1456fn effect_capability_operation(
1457    record: &crate::EffectStatementTypeRecord,
1458    kind: CapabilityOperationKind,
1459) -> Option<&'static crate::CapabilityOperation> {
1460    let operation = EFFECT_CAPABILITY_REGISTRY.operation(record.capability_operation?)?;
1461    (operation.kind == kind).then_some(operation)
1462}
1463
1464struct ExpressionIrLowering<'a> {
1465    model: &'a ApplicationSemanticModel,
1466    component: &'a ComponentNode,
1467    function: &'a SemanticId,
1468    reference_owner: &'a SemanticId,
1469    entry_block: IrBlockId,
1470    instructions: Vec<IrInstruction>,
1471    values: BTreeMap<IrValueId, IrValue>,
1472    executable_computed: Option<&'a BTreeSet<SemanticId>>,
1473    allowed_dependencies: Option<&'a BTreeSet<SemanticId>>,
1474}
1475
1476impl ExpressionIrLowering<'_> {
1477    fn lower_node(&mut self, id: &SemanticId) -> Option<IrValueId> {
1478        let node = self.model.expression_graph.node(id)?.clone();
1479        let kind = match node.kind.clone() {
1480            ExpressionNodeKind::Literal(value) => IrInstructionKind::Constant {
1481                value: ir_constant(value),
1482            },
1483            ExpressionNodeKind::Boolean(value) => IrInstructionKind::Constant {
1484                value: IrConstant::Boolean(value),
1485            },
1486            ExpressionNodeKind::Identifier(_) => {
1487                return None;
1488            }
1489            ExpressionNodeKind::Call { .. } => {
1490                return None;
1491            }
1492            ExpressionNodeKind::BuiltinPureCall {
1493                operation,
1494                arguments,
1495            } => match operation {
1496                crate::component_graph::BuiltinPureOperation::MathAbs
1497                | crate::component_graph::BuiltinPureOperation::MathFloor
1498                | crate::component_graph::BuiltinPureOperation::MathCeil
1499                | crate::component_graph::BuiltinPureOperation::MathRound => {
1500                    let [argument] = arguments.as_slice() else {
1501                        return None;
1502                    };
1503                    IrInstructionKind::Unary {
1504                        operation: match operation {
1505                            crate::component_graph::BuiltinPureOperation::MathAbs => {
1506                                IrUnaryOperation::Abs
1507                            }
1508                            crate::component_graph::BuiltinPureOperation::MathFloor => {
1509                                IrUnaryOperation::Floor
1510                            }
1511                            crate::component_graph::BuiltinPureOperation::MathCeil => {
1512                                IrUnaryOperation::Ceil
1513                            }
1514                            crate::component_graph::BuiltinPureOperation::MathRound => {
1515                                IrUnaryOperation::Round
1516                            }
1517                            crate::component_graph::BuiltinPureOperation::MathMin
1518                            | crate::component_graph::BuiltinPureOperation::MathMax => {
1519                                unreachable!()
1520                            }
1521                        },
1522                        operand: IrOperand::Value(self.lower_node(argument)?),
1523                    }
1524                }
1525                crate::component_graph::BuiltinPureOperation::MathMin
1526                | crate::component_graph::BuiltinPureOperation::MathMax => {
1527                    let [left, right] = arguments.as_slice() else {
1528                        return None;
1529                    };
1530                    IrInstructionKind::Binary {
1531                        operation: match operation {
1532                            crate::component_graph::BuiltinPureOperation::MathMin => {
1533                                IrBinaryOperation::Min
1534                            }
1535                            crate::component_graph::BuiltinPureOperation::MathMax => {
1536                                IrBinaryOperation::Max
1537                            }
1538                            crate::component_graph::BuiltinPureOperation::MathAbs
1539                            | crate::component_graph::BuiltinPureOperation::MathFloor
1540                            | crate::component_graph::BuiltinPureOperation::MathCeil
1541                            | crate::component_graph::BuiltinPureOperation::MathRound => {
1542                                unreachable!()
1543                            }
1544                        },
1545                        left: IrOperand::Value(self.lower_node(left)?),
1546                        right: IrOperand::Value(self.lower_node(right)?),
1547                    }
1548                }
1549            },
1550            ExpressionNodeKind::Template {
1551                quasis,
1552                expressions,
1553            } => IrInstructionKind::Template {
1554                quasis,
1555                expressions: expressions
1556                    .iter()
1557                    .map(|expression| self.lower_node(expression))
1558                    .collect::<Option<Vec<_>>>()?,
1559            },
1560            ExpressionNodeKind::SemanticPackagePureCall {
1561                package,
1562                version,
1563                integrity,
1564                export,
1565                runtime_module,
1566                resume_policy,
1567                operation,
1568                arguments,
1569            } => IrInstructionKind::PurePackageCall {
1570                package,
1571                version,
1572                integrity,
1573                export,
1574                runtime_module,
1575                resume_policy,
1576                operation,
1577                arguments: arguments
1578                    .iter()
1579                    .map(|argument| self.lower_node(argument))
1580                    .collect::<Option<Vec<_>>>()?,
1581            },
1582            ExpressionNodeKind::ThisMember { name } => self.lower_this_member(&name)?,
1583            ExpressionNodeKind::MemberAccess {
1584                object,
1585                property,
1586                optional,
1587            } => IrInstructionKind::GetMember {
1588                object: IrOperand::Value(self.lower_node(&object)?),
1589                property,
1590                optional,
1591            },
1592            ExpressionNodeKind::IndexAccess { object, index } => IrInstructionKind::GetIndex {
1593                object: IrOperand::Value(self.lower_node(&object)?),
1594                index: IrOperand::Value(self.lower_node(&index)?),
1595            },
1596            ExpressionNodeKind::Conditional {
1597                condition,
1598                when_true,
1599                when_false,
1600            } => IrInstructionKind::Select {
1601                condition: IrOperand::Value(self.lower_node(&condition)?),
1602                when_true: IrOperand::Value(self.lower_node(&when_true)?),
1603                when_false: IrOperand::Value(self.lower_node(&when_false)?),
1604            },
1605            ExpressionNodeKind::Arithmetic {
1606                left,
1607                right,
1608                operator,
1609            } => IrInstructionKind::Binary {
1610                operation: ir_arithmetic_operation(operator),
1611                left: IrOperand::Value(self.lower_node(&left)?),
1612                right: IrOperand::Value(self.lower_node(&right)?),
1613            },
1614            ExpressionNodeKind::Comparison {
1615                left,
1616                right,
1617                operator,
1618            } => IrInstructionKind::Binary {
1619                operation: ir_comparison_operation(operator),
1620                left: IrOperand::Value(self.lower_node(&left)?),
1621                right: IrOperand::Value(self.lower_node(&right)?),
1622            },
1623            ExpressionNodeKind::Logical {
1624                left,
1625                right,
1626                operator,
1627            } => IrInstructionKind::Binary {
1628                operation: ir_logical_operation(operator),
1629                left: IrOperand::Value(self.lower_node(&left)?),
1630                right: IrOperand::Value(self.lower_node(&right)?),
1631            },
1632            ExpressionNodeKind::NullishCoalescing { left, right } => IrInstructionKind::Binary {
1633                operation: IrBinaryOperation::NullishCoalesce,
1634                left: IrOperand::Value(self.lower_node(&left)?),
1635                right: IrOperand::Value(self.lower_node(&right)?),
1636            },
1637            ExpressionNodeKind::Unary { operand, operator } => IrInstructionKind::Unary {
1638                operation: ir_unary_operation(operator),
1639                operand: IrOperand::Value(self.lower_node(&operand)?),
1640            },
1641        };
1642        Some(self.emit(node, kind))
1643    }
1644
1645    fn lower_this_member(&self, name: &str) -> Option<IrInstructionKind> {
1646        let target = self
1647            .component
1648            .state_fields
1649            .iter()
1650            .find(|field| field.name == name)
1651            .map(|field| field.id.clone())
1652            .or_else(|| {
1653                self.model
1654                    .computed_values
1655                    .get(&self.component.id.computed(name))
1656                    .map(|computed| computed.id.clone())
1657            })
1658            .or_else(|| {
1659                let resource = ResourceId::for_owner(&self.component.id, name);
1660                self.model
1661                    .resource_declarations
1662                    .contains_key(&resource)
1663                    .then_some(resource.as_semantic_id().clone())
1664            })?;
1665        let has_reference = self.model.references.iter().any(|reference| {
1666            reference.source == *self.reference_owner && reference.target == target
1667        });
1668        if !has_reference
1669            && !self
1670                .allowed_dependencies
1671                .is_some_and(|dependencies| dependencies.contains(&target))
1672        {
1673            return None;
1674        }
1675        if self
1676            .component
1677            .state_fields
1678            .iter()
1679            .any(|field| field.id == target)
1680        {
1681            Some(IrInstructionKind::LoadStorage {
1682                storage: IrStorageId::for_semantic_origin(&target),
1683            })
1684        } else {
1685            if self
1686                .model
1687                .resource_declarations
1688                .contains_key(&ResourceId::for_owner(&self.component.id, name))
1689            {
1690                return Some(IrInstructionKind::LoadResource {
1691                    declaration: target,
1692                });
1693            }
1694            if self
1695                .executable_computed
1696                .is_some_and(|computed| !computed.contains(&target))
1697            {
1698                return None;
1699            }
1700            Some(IrInstructionKind::LoadComputed { computed: target })
1701        }
1702    }
1703
1704    fn emit(&mut self, node: ExpressionNode, kind: IrInstructionKind) -> IrValueId {
1705        let value = IrValueId::for_function(self.function, self.values.len());
1706        let instruction = IrInstructionId::for_block(&self.entry_block, self.instructions.len());
1707        self.values.insert(
1708            value.clone(),
1709            IrValue {
1710                id: value.clone(),
1711                definition: IrValueDefinition::Instruction(instruction.clone()),
1712                semantic_type: self
1713                    .model
1714                    .semantic_type_of(&node.id)
1715                    .cloned()
1716                    .unwrap_or(SemanticType::Unknown),
1717                provenance: node.provenance.clone(),
1718                semantic_origin: Some(node.id.clone()),
1719            },
1720        );
1721        self.instructions.push(IrInstruction {
1722            id: instruction,
1723            provenance: node.provenance,
1724            result: Some(value.clone()),
1725            semantic_origin: Some(node.id),
1726            kind,
1727        });
1728        value
1729    }
1730
1731    fn emit_effect(&mut self, statement: &crate::EffectStatement, kind: IrInstructionKind) {
1732        let instruction = IrInstructionId::for_block(&self.entry_block, self.instructions.len());
1733        self.instructions.push(IrInstruction {
1734            id: instruction,
1735            provenance: statement.provenance.clone(),
1736            result: None,
1737            semantic_origin: Some(statement.id.clone()),
1738            kind,
1739        });
1740    }
1741}
1742
1743fn ir_constant(value: SerializableValue) -> IrConstant {
1744    match value {
1745        SerializableValue::Null => IrConstant::Null,
1746        SerializableValue::Boolean(value) => IrConstant::Boolean(value),
1747        SerializableValue::Number(value) => IrConstant::Number(value),
1748        SerializableValue::String(value) => IrConstant::String(value),
1749        SerializableValue::Array(value) => IrConstant::Array(value),
1750        SerializableValue::Object(value) => IrConstant::Object(value),
1751    }
1752}
1753
1754const fn ir_arithmetic_operation(operator: ArithmeticOperator) -> IrBinaryOperation {
1755    match operator {
1756        ArithmeticOperator::Add => IrBinaryOperation::Add,
1757        ArithmeticOperator::Subtract => IrBinaryOperation::Subtract,
1758        ArithmeticOperator::Multiply => IrBinaryOperation::Multiply,
1759        ArithmeticOperator::Divide => IrBinaryOperation::Divide,
1760        ArithmeticOperator::Remainder => IrBinaryOperation::Remainder,
1761    }
1762}
1763
1764const fn ir_comparison_operation(operator: ComparisonOperator) -> IrBinaryOperation {
1765    match operator {
1766        ComparisonOperator::Equal => IrBinaryOperation::Equal,
1767        ComparisonOperator::NotEqual => IrBinaryOperation::NotEqual,
1768        ComparisonOperator::LessThan => IrBinaryOperation::LessThan,
1769        ComparisonOperator::LessThanOrEqual => IrBinaryOperation::LessThanOrEqual,
1770        ComparisonOperator::GreaterThan => IrBinaryOperation::GreaterThan,
1771        ComparisonOperator::GreaterThanOrEqual => IrBinaryOperation::GreaterThanOrEqual,
1772    }
1773}
1774
1775const fn ir_logical_operation(operator: LogicalOperator) -> IrBinaryOperation {
1776    match operator {
1777        LogicalOperator::And => IrBinaryOperation::And,
1778        LogicalOperator::Or => IrBinaryOperation::Or,
1779    }
1780}
1781
1782const fn ir_unary_operation(operator: UnaryOperator) -> IrUnaryOperation {
1783    match operator {
1784        UnaryOperator::Not => IrUnaryOperation::Not,
1785        UnaryOperator::Plus => IrUnaryOperation::Identity,
1786        UnaryOperator::Minus => IrUnaryOperation::Negate,
1787    }
1788}
1789
1790/// One compiler-owned executable function.
1791#[derive(Debug, Clone, PartialEq, Eq)]
1792pub struct IrFunction {
1793    pub id: SemanticId,
1794    pub name: String,
1795    pub provenance: SourceProvenance,
1796    pub entry_block: IrBlockId,
1797    pub blocks: Vec<IrBlock>,
1798    pub branch_edges: Vec<IrBranchEdge>,
1799    pub values: BTreeMap<IrValueId, IrValue>,
1800    pub loops: Vec<IrLoop>,
1801}
1802
1803impl IrFunction {
1804    #[must_use]
1805    pub fn block(&self, id: &IrBlockId) -> Option<&IrBlock> {
1806        self.blocks.iter().find(|block| block.id == *id)
1807    }
1808
1809    #[must_use]
1810    pub fn successor_blocks(&self, id: &IrBlockId) -> Vec<IrBlockId> {
1811        self.branch_edges
1812            .iter()
1813            .filter(|edge| edge.from == *id)
1814            .map(|edge| edge.to.clone())
1815            .collect::<BTreeSet<_>>()
1816            .into_iter()
1817            .collect()
1818    }
1819
1820    #[must_use]
1821    pub fn predecessor_blocks(&self, id: &IrBlockId) -> Vec<IrBlockId> {
1822        self.branch_edges
1823            .iter()
1824            .filter(|edge| edge.to == *id)
1825            .map(|edge| edge.from.clone())
1826            .collect::<BTreeSet<_>>()
1827            .into_iter()
1828            .collect()
1829    }
1830
1831    #[must_use]
1832    pub fn is_exit_block(&self, id: &IrBlockId) -> bool {
1833        self.block(id).is_some() && self.successor_blocks(id).is_empty()
1834    }
1835
1836    #[must_use]
1837    pub fn value(&self, id: &IrValueId) -> Option<&IrValue> {
1838        self.values.get(id)
1839    }
1840}
1841
1842/// A stable compiler-owned basic-block identity within an IR function.
1843#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1844pub struct IrBlockId(String);
1845
1846impl IrBlockId {
1847    #[must_use]
1848    pub fn entry_for(function: &SemanticId) -> Self {
1849        Self::for_function(function, "entry")
1850    }
1851
1852    #[must_use]
1853    pub fn for_function(function: &SemanticId, name: &str) -> Self {
1854        Self(format!("{function}/block:{name}"))
1855    }
1856
1857    #[must_use]
1858    pub fn as_str(&self) -> &str {
1859        &self.0
1860    }
1861}
1862
1863impl std::fmt::Display for IrBlockId {
1864    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1865        formatter.write_str(&self.0)
1866    }
1867}
1868
1869/// A stable compiler-owned operation identity within an IR block.
1870#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1871pub struct IrInstructionId(String);
1872
1873impl IrInstructionId {
1874    #[must_use]
1875    pub fn for_block(block: &IrBlockId, index: usize) -> Self {
1876        Self(format!("{block}/instruction:{index}"))
1877    }
1878
1879    #[must_use]
1880    pub fn for_module(path: &Path, index: usize) -> Self {
1881        Self(format!("module:{}/instruction:{index}", path.display()))
1882    }
1883
1884    #[must_use]
1885    pub fn as_str(&self) -> &str {
1886        &self.0
1887    }
1888}
1889
1890impl std::fmt::Display for IrInstructionId {
1891    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1892        formatter.write_str(&self.0)
1893    }
1894}
1895
1896/// A stable compiler-owned transient value identity within an IR function.
1897#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1898pub struct IrValueId(String);
1899
1900impl IrValueId {
1901    #[must_use]
1902    pub fn for_function(function: &SemanticId, index: usize) -> Self {
1903        Self(format!("{function}/value:{index}"))
1904    }
1905
1906    #[must_use]
1907    pub fn as_str(&self) -> &str {
1908        &self.0
1909    }
1910}
1911
1912impl std::fmt::Display for IrValueId {
1913    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1914        formatter.write_str(&self.0)
1915    }
1916}
1917
1918/// A stable compiler-owned storage-slot identity.
1919#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1920pub struct IrStorageId(String);
1921
1922impl IrStorageId {
1923    #[must_use]
1924    pub fn for_semantic_origin(origin: &SemanticId) -> Self {
1925        Self(format!("storage:{origin}"))
1926    }
1927
1928    #[must_use]
1929    pub fn as_str(&self) -> &str {
1930        &self.0
1931    }
1932}
1933
1934/// One mutable runtime storage slot lowered from an authored semantic entity.
1935#[derive(Debug, Clone, PartialEq, Eq)]
1936pub struct IrStorage {
1937    pub id: IrStorageId,
1938    pub semantic_origin: SemanticId,
1939    pub value_type: SemanticType,
1940    pub initial_value: Option<crate::SerializableValue>,
1941    pub provenance: SourceProvenance,
1942}
1943
1944impl std::fmt::Display for IrStorageId {
1945    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1946        formatter.write_str(&self.0)
1947    }
1948}
1949
1950/// An immutable primitive constant embedded directly in an IR operand.
1951#[derive(Debug, Clone, PartialEq, Eq)]
1952pub enum IrConstant {
1953    Null,
1954    Boolean(bool),
1955    Number(String),
1956    String(String),
1957    Array(Vec<SerializableValue>),
1958    Object(BTreeMap<String, SerializableValue>),
1959}
1960
1961/// A closed set of executable inputs supported by the canonical IR.
1962#[derive(Debug, Clone, PartialEq, Eq)]
1963pub enum IrOperand {
1964    Value(IrValueId),
1965    Constant(IrConstant),
1966    Storage(IrStorageId),
1967}
1968
1969/// The canonical origin of an IR value.
1970#[derive(Debug, Clone, PartialEq, Eq)]
1971pub enum IrValueDefinition {
1972    Instruction(IrInstructionId),
1973    Parameter { function: SemanticId, index: usize },
1974    BlockParameter { block: IrBlockId, index: usize },
1975}
1976
1977/// One function-scoped transient value and its canonical definition metadata.
1978#[derive(Debug, Clone, PartialEq, Eq)]
1979pub struct IrValue {
1980    pub id: IrValueId,
1981    pub definition: IrValueDefinition,
1982    pub semantic_type: SemanticType,
1983    pub provenance: SourceProvenance,
1984    pub semantic_origin: Option<SemanticId>,
1985}
1986
1987/// A structural integrity failure in canonical IR value and operand metadata.
1988#[derive(Debug, Clone, PartialEq, Eq)]
1989pub struct IrValidationDiagnostic {
1990    pub code: &'static str,
1991    pub message: String,
1992}
1993
1994/// One exact value-consuming operand position in a canonical instruction.
1995#[derive(Debug, Clone, PartialEq, Eq)]
1996pub struct IrUse {
1997    pub instruction: IrInstructionId,
1998    pub operand_index: usize,
1999}
2000
2001/// Canonical definition and use relations for one IR function.
2002#[derive(Debug, Clone, PartialEq, Eq)]
2003pub struct IrDefinitionUseAnalysis {
2004    pub definitions: BTreeMap<IrValueId, IrValueDefinition>,
2005    pub uses: BTreeMap<IrValueId, Vec<IrUse>>,
2006}
2007
2008/// A resolved use-to-definition relation for one value-consuming operand.
2009#[derive(Debug, Clone, PartialEq, Eq)]
2010pub struct IrUseDefinition {
2011    pub value: IrValueId,
2012    pub instruction: IrInstructionId,
2013    pub operand_index: usize,
2014    pub definition: IrValueDefinition,
2015}
2016
2017/// Block-level live-in and live-out sets for one canonical IR function.
2018#[derive(Debug, Clone, PartialEq, Eq)]
2019pub struct IrLivenessAnalysis {
2020    pub live_in: BTreeMap<IrBlockId, Vec<IrValueId>>,
2021    pub live_out: BTreeMap<IrBlockId, Vec<IrValueId>>,
2022}
2023
2024/// The entry-reachable and unreachable block partition for one IR function.
2025#[derive(Debug, Clone, PartialEq, Eq)]
2026pub struct IrReachabilityAnalysis {
2027    pub reachable: Vec<IrBlockId>,
2028    pub unreachable: Vec<IrBlockId>,
2029}
2030
2031/// Statically known transient values derived by canonical IR constant propagation.
2032#[derive(Debug, Clone, PartialEq, Eq)]
2033pub struct IrConstantPropagationAnalysis {
2034    pub constants: BTreeMap<IrValueId, IrConstant>,
2035}
2036
2037/// Side-effect-free instructions whose produced values have no canonical uses.
2038#[derive(Debug, Clone, PartialEq, Eq)]
2039pub struct IrDeadAssignmentAnalysis {
2040    pub instructions: Vec<IrInstructionId>,
2041}
2042
2043/// A compiler-owned transformation over canonical IR.
2044pub trait IrOptimizationPass {
2045    fn name(&self) -> &'static str;
2046    fn run(&self, input: &IntermediateRepresentation) -> IntermediateRepresentation;
2047}
2048
2049/// Ordered owner and executor for canonical IR optimization passes.
2050#[derive(Default)]
2051pub struct IrPassManager {
2052    passes: Vec<Box<dyn IrOptimizationPass>>,
2053}
2054
2055impl IrPassManager {
2056    #[must_use]
2057    pub fn new() -> Self {
2058        Self::default()
2059    }
2060
2061    pub fn register(&mut self, pass: Box<dyn IrOptimizationPass>) {
2062        self.passes.push(pass);
2063    }
2064
2065    #[must_use]
2066    pub fn pass_names(&self) -> Vec<&'static str> {
2067        self.passes.iter().map(|pass| pass.name()).collect()
2068    }
2069
2070    #[must_use]
2071    pub fn run(&self, input: &IntermediateRepresentation) -> IntermediateRepresentation {
2072        self.passes
2073            .iter()
2074            .fold(input.clone(), |current, pass| pass.run(&current))
2075    }
2076}
2077
2078/// An immutable ordered optimization pipeline.
2079pub struct IrOptimizationPipeline {
2080    passes: Vec<Box<dyn IrOptimizationPass>>,
2081}
2082
2083impl IrOptimizationPipeline {
2084    #[must_use]
2085    pub fn new(passes: Vec<Box<dyn IrOptimizationPass>>) -> Self {
2086        Self { passes }
2087    }
2088
2089    #[must_use]
2090    pub fn pass_names(&self) -> Vec<&'static str> {
2091        self.passes.iter().map(|pass| pass.name()).collect()
2092    }
2093
2094    #[must_use]
2095    pub fn run(&self, input: &IntermediateRepresentation) -> IntermediateRepresentation {
2096        self.passes
2097            .iter()
2098            .fold(input.clone(), |current, pass| pass.run(&current))
2099    }
2100
2101    #[must_use]
2102    pub fn run_with_report(&self, input: &IntermediateRepresentation) -> IrOptimizationReport {
2103        let mut current = input.clone();
2104        let mut passes = Vec::new();
2105        for pass in &self.passes {
2106            let before = optimization_metrics(&current);
2107            current = pass.run(&current);
2108            passes.push(IrOptimizationPassReport {
2109                name: pass.name(),
2110                before,
2111                after: optimization_metrics(&current),
2112            });
2113        }
2114        IrOptimizationReport {
2115            output: current,
2116            passes,
2117        }
2118    }
2119}
2120
2121/// Build the immutable optimization pipeline applied to canonical computed IR.
2122#[must_use]
2123pub fn computed_optimization_pipeline() -> IrOptimizationPipeline {
2124    IrOptimizationPipeline::new(vec![
2125        Box::new(IrCommonSubexpressionEliminationPass),
2126        Box::new(IrCopyPropagationPass),
2127        Box::new(IrConstantFoldingPass),
2128        Box::new(IrInstructionSimplificationPass),
2129        Box::new(IrDeadCodeEliminationPass),
2130        Box::new(IrCfgCleanupPass),
2131    ])
2132}
2133
2134/// Run the immutable canonical optimization pipeline over computed IR.
2135#[must_use]
2136pub fn optimize_computed_ir(input: &IntermediateRepresentation) -> IrOptimizationReport {
2137    computed_optimization_pipeline().run_with_report(input)
2138}
2139
2140/// Run the existing immutable optimization pipeline over F10 effect functions
2141/// only, preserving every non-effect IR product verbatim.
2142#[must_use]
2143pub fn optimize_effect_ir(input: &IntermediateRepresentation) -> IrOptimizationReport {
2144    let mut effect_input = input.clone();
2145    for module in &mut effect_input.modules {
2146        let effect_functions = module
2147            .effect_executions
2148            .iter()
2149            .flat_map(|execution| {
2150                std::iter::once(execution.function.clone())
2151                    .chain(execution.cleanup_function.clone())
2152            })
2153            .collect::<BTreeSet<_>>();
2154        module
2155            .functions
2156            .retain(|function| effect_functions.contains(&function.id));
2157        module.computed_evaluations.clear();
2158    }
2159    let mut report = computed_optimization_pipeline().run_with_report(&effect_input);
2160    let mut output = input.clone();
2161    for (module, optimized) in output.modules.iter_mut().zip(&report.output.modules) {
2162        let optimized_functions = optimized
2163            .functions
2164            .iter()
2165            .map(|function| (function.id.clone(), function.clone()))
2166            .collect::<BTreeMap<_, _>>();
2167        for function in &mut module.functions {
2168            if let Some(optimized) = optimized_functions.get(&function.id) {
2169                *function = optimized.clone();
2170            }
2171        }
2172    }
2173    report.output = output;
2174    report
2175}
2176
2177/// Run the existing immutable optimization pipeline over G10-generated Context
2178/// source functions only. The source-to-slot bindings remain compiler-owned
2179/// G9/G10 facts; optimization can simplify a source value producer but cannot
2180/// select, replace, merge, or remove a Context slot.
2181#[must_use]
2182pub fn optimize_context_ir(input: &IntermediateRepresentation) -> OptimizedContextIrReport {
2183    let context_functions = input
2184        .context_ir
2185        .source_evaluations
2186        .iter()
2187        .map(|evaluation| evaluation.function.as_semantic_id().clone())
2188        .collect::<BTreeSet<_>>();
2189    let mut context_input = input.clone();
2190    for module in &mut context_input.modules {
2191        module
2192            .functions
2193            .retain(|function| context_functions.contains(&function.id));
2194        module.computed_evaluations.clear();
2195        module.effect_executions.clear();
2196    }
2197
2198    let projection = computed_optimization_pipeline().run_with_report(&context_input);
2199    let mut optimized_module = input.clone();
2200    for (module, optimized) in optimized_module
2201        .modules
2202        .iter_mut()
2203        .zip(&projection.output.modules)
2204    {
2205        let optimized_functions = optimized
2206            .functions
2207            .iter()
2208            .map(|function| (function.id.clone(), function.clone()))
2209            .collect::<BTreeMap<_, _>>();
2210        for function in &mut module.functions {
2211            if context_functions.contains(&function.id) {
2212                if let Some(optimized) = optimized_functions.get(&function.id) {
2213                    *function = optimized.clone();
2214                }
2215            }
2216        }
2217    }
2218
2219    OptimizedContextIrReport {
2220        source_report: input.context_ir.clone(),
2221        optimized_module,
2222        source_evaluations: input
2223            .context_ir
2224            .source_evaluations
2225            .iter()
2226            .map(OptimizedIrContextSourceEvaluation::from)
2227            .collect(),
2228        pass_metrics: projection.passes,
2229    }
2230}
2231
2232/// Compact structural metrics for one canonical IR snapshot.
2233#[derive(Debug, Clone, PartialEq, Eq)]
2234pub struct IrOptimizationMetrics {
2235    pub blocks: usize,
2236    pub instructions: usize,
2237    pub values: usize,
2238}
2239
2240/// One pass's observable before/after optimization result.
2241#[derive(Debug, Clone, PartialEq, Eq)]
2242pub struct IrOptimizationPassReport {
2243    pub name: &'static str,
2244    pub before: IrOptimizationMetrics,
2245    pub after: IrOptimizationMetrics,
2246}
2247
2248/// The output IR and ordered reports from one immutable pipeline run.
2249#[derive(Debug, Clone, PartialEq, Eq)]
2250pub struct IrOptimizationReport {
2251    pub output: IntermediateRepresentation,
2252    pub passes: Vec<IrOptimizationPassReport>,
2253}
2254
2255fn optimization_metrics(representation: &IntermediateRepresentation) -> IrOptimizationMetrics {
2256    IrOptimizationMetrics {
2257        blocks: representation
2258            .modules
2259            .iter()
2260            .flat_map(|module| &module.functions)
2261            .map(|function| function.blocks.len())
2262            .sum(),
2263        instructions: representation
2264            .modules
2265            .iter()
2266            .flat_map(|module| &module.functions)
2267            .flat_map(|function| &function.blocks)
2268            .map(|block| block.instructions.len())
2269            .sum(),
2270        values: representation
2271            .modules
2272            .iter()
2273            .flat_map(|module| &module.functions)
2274            .map(|function| function.values.len())
2275            .sum(),
2276    }
2277}
2278
2279/// Detects dead result assignments without treating storage effects as removable.
2280#[must_use]
2281pub fn analyze_dead_assignments(function: &IrFunction) -> IrDeadAssignmentAnalysis {
2282    analyze_dead_assignments_preserving(function, &BTreeSet::new())
2283}
2284
2285fn analyze_dead_assignments_preserving(
2286    function: &IrFunction,
2287    preserved_values: &BTreeSet<IrValueId>,
2288) -> IrDeadAssignmentAnalysis {
2289    let uses = analyze_definition_uses(function).uses;
2290    let instructions = function
2291        .blocks
2292        .iter()
2293        .flat_map(|block| block.instructions.iter())
2294        .filter(|instruction| {
2295            instruction.result.as_ref().is_some_and(|result| {
2296                !preserved_values.contains(result)
2297                    && uses.get(result).is_some_and(Vec::is_empty)
2298                    && matches!(
2299                        instruction.kind,
2300                        IrInstructionKind::Constant { .. }
2301                            | IrInstructionKind::LoadStorage { .. }
2302                            | IrInstructionKind::LoadComputed { .. }
2303                            | IrInstructionKind::GetMember { .. }
2304                            | IrInstructionKind::Copy { .. }
2305                            | IrInstructionKind::Binary { .. }
2306                            | IrInstructionKind::Unary { .. }
2307                    )
2308            })
2309        })
2310        .map(|instruction| instruction.id.clone())
2311        .collect();
2312    IrDeadAssignmentAnalysis { instructions }
2313}
2314
2315/// Propagates inline primitive constants through the current unary and binary IR operations.
2316#[must_use]
2317pub fn analyze_constant_propagation(function: &IrFunction) -> IrConstantPropagationAnalysis {
2318    let mut constants = BTreeMap::new();
2319    for block in &function.blocks {
2320        for instruction in &block.instructions {
2321            let Some(result) = &instruction.result else {
2322                continue;
2323            };
2324            let constant = match &instruction.kind {
2325                IrInstructionKind::Constant { value } => Some(value.clone()),
2326                IrInstructionKind::Unary { operation, operand } => {
2327                    resolve_constant(operand, &constants).and_then(|operand| {
2328                        match (operation, operand) {
2329                            (IrUnaryOperation::Not, IrConstant::Boolean(value)) => {
2330                                Some(IrConstant::Boolean(!value))
2331                            }
2332                            (IrUnaryOperation::Identity, value) => Some(value),
2333                            (IrUnaryOperation::Negate, IrConstant::Number(value)) => {
2334                                negate_number(&value).map(IrConstant::Number)
2335                            }
2336                            _ => None,
2337                        }
2338                    })
2339                }
2340                IrInstructionKind::Binary {
2341                    operation,
2342                    left,
2343                    right,
2344                } => {
2345                    let (Some(IrConstant::Number(left)), Some(IrConstant::Number(right))) = (
2346                        resolve_constant(left, &constants),
2347                        resolve_constant(right, &constants),
2348                    ) else {
2349                        continue;
2350                    };
2351                    evaluate_numeric_binary(*operation, &left, &right).map(IrConstant::Number)
2352                }
2353                _ => None,
2354            };
2355            if let Some(constant) = constant {
2356                constants.insert(result.clone(), constant);
2357            }
2358        }
2359    }
2360    IrConstantPropagationAnalysis { constants }
2361}
2362
2363fn resolve_constant(
2364    operand: &IrOperand,
2365    constants: &BTreeMap<IrValueId, IrConstant>,
2366) -> Option<IrConstant> {
2367    match operand {
2368        IrOperand::Constant(constant) => Some(constant.clone()),
2369        IrOperand::Value(value) => constants.get(value).cloned(),
2370        IrOperand::Storage(_) => None,
2371    }
2372}
2373
2374fn negate_number(value: &str) -> Option<String> {
2375    value.parse::<f64>().ok().map(|value| format_number(-value))
2376}
2377fn evaluate_numeric_binary(
2378    operation: IrBinaryOperation,
2379    left: &str,
2380    right: &str,
2381) -> Option<String> {
2382    let left = left.parse::<f64>().ok()?;
2383    let right = right.parse::<f64>().ok()?;
2384    let value = match operation {
2385        IrBinaryOperation::Add => left + right,
2386        IrBinaryOperation::Subtract => left - right,
2387        IrBinaryOperation::Multiply => left * right,
2388        IrBinaryOperation::Divide if right != 0.0 => left / right,
2389        IrBinaryOperation::Remainder if right != 0.0 => left % right,
2390        IrBinaryOperation::Min => left.min(right),
2391        IrBinaryOperation::Max => left.max(right),
2392        _ => return None,
2393    };
2394    Some(format_number(value))
2395}
2396fn format_number(value: f64) -> String {
2397    if value.fract() == 0.0 {
2398        format!("{value:.0}")
2399    } else {
2400        value.to_string()
2401    }
2402}
2403
2404/// Computes canonical reachability from a function's entry block.
2405#[must_use]
2406pub fn analyze_reachability(function: &IrFunction) -> IrReachabilityAnalysis {
2407    let mut reachable = BTreeSet::from([function.entry_block.clone()]);
2408    let mut pending = vec![function.entry_block.clone()];
2409    while let Some(block) = pending.pop() {
2410        for successor in function.successor_blocks(&block) {
2411            if reachable.insert(successor.clone()) {
2412                pending.push(successor);
2413            }
2414        }
2415    }
2416    let all_blocks = function
2417        .blocks
2418        .iter()
2419        .map(|block| block.id.clone())
2420        .collect::<BTreeSet<_>>();
2421    IrReachabilityAnalysis {
2422        reachable: reachable
2423            .iter()
2424            .filter(|block| all_blocks.contains(*block))
2425            .cloned()
2426            .collect(),
2427        unreachable: all_blocks.difference(&reachable).cloned().collect(),
2428    }
2429}
2430
2431/// Computes immutable block liveness from value uses, definitions, and CFG successors.
2432#[must_use]
2433pub fn analyze_liveness(function: &IrFunction) -> IrLivenessAnalysis {
2434    let block_ids = function
2435        .blocks
2436        .iter()
2437        .map(|block| block.id.clone())
2438        .collect::<BTreeSet<_>>();
2439    let mut block_uses = BTreeMap::new();
2440    let mut block_definitions = BTreeMap::new();
2441    for block in &function.blocks {
2442        let mut uses = BTreeSet::new();
2443        let mut definitions = BTreeSet::new();
2444        for instruction in &block.instructions {
2445            for operand in ir_instruction_operands(&instruction.kind) {
2446                if let IrOperand::Value(value) = operand {
2447                    if !definitions.contains(&value) {
2448                        uses.insert(value);
2449                    }
2450                }
2451            }
2452            if let Some(result) = &instruction.result {
2453                definitions.insert(result.clone());
2454            }
2455        }
2456        block_uses.insert(block.id.clone(), uses);
2457        block_definitions.insert(block.id.clone(), definitions);
2458    }
2459    let mut live_in = block_ids
2460        .iter()
2461        .cloned()
2462        .map(|id| (id, BTreeSet::new()))
2463        .collect::<BTreeMap<_, _>>();
2464    let mut live_out = live_in.clone();
2465    let mut changed = true;
2466    while changed {
2467        changed = false;
2468        for block in block_ids.iter().rev() {
2469            let next_out = function
2470                .successor_blocks(block)
2471                .into_iter()
2472                .flat_map(|successor| live_in[&successor].clone())
2473                .collect::<BTreeSet<_>>();
2474            let mut next_in = block_uses[block].clone();
2475            next_in.extend(next_out.difference(&block_definitions[block]).cloned());
2476            if live_out.get(block) != Some(&next_out) || live_in.get(block) != Some(&next_in) {
2477                live_out.insert(block.clone(), next_out);
2478                live_in.insert(block.clone(), next_in);
2479                changed = true;
2480            }
2481        }
2482    }
2483    IrLivenessAnalysis {
2484        live_in: live_in
2485            .into_iter()
2486            .map(|(block, values)| (block, values.into_iter().collect()))
2487            .collect(),
2488        live_out: live_out
2489            .into_iter()
2490            .map(|(block, values)| (block, values.into_iter().collect()))
2491            .collect(),
2492    }
2493}
2494
2495/// Resolves each canonical value use to its registered definition.
2496#[must_use]
2497pub fn analyze_use_definitions(function: &IrFunction) -> Vec<IrUseDefinition> {
2498    let mut relations = Vec::new();
2499    for block in &function.blocks {
2500        for instruction in &block.instructions {
2501            for (operand_index, operand) in ir_instruction_operands(&instruction.kind)
2502                .into_iter()
2503                .enumerate()
2504            {
2505                let IrOperand::Value(value) = operand else {
2506                    continue;
2507                };
2508                let Some(definition) = function
2509                    .values
2510                    .get(&value)
2511                    .map(|value| value.definition.clone())
2512                else {
2513                    continue;
2514                };
2515                relations.push(IrUseDefinition {
2516                    value,
2517                    instruction: instruction.id.clone(),
2518                    operand_index,
2519                    definition,
2520                });
2521            }
2522        }
2523    }
2524    relations
2525}
2526
2527/// Computes definition and use chains from one function's value registry and instruction operands.
2528#[must_use]
2529pub fn analyze_definition_uses(function: &IrFunction) -> IrDefinitionUseAnalysis {
2530    let definitions = function
2531        .values
2532        .iter()
2533        .map(|(id, value)| (id.clone(), value.definition.clone()))
2534        .collect::<BTreeMap<_, _>>();
2535    let mut uses = function
2536        .values
2537        .keys()
2538        .cloned()
2539        .map(|id| (id, Vec::new()))
2540        .collect::<BTreeMap<_, _>>();
2541    for block in &function.blocks {
2542        for instruction in &block.instructions {
2543            for (operand_index, operand) in ir_instruction_operands(&instruction.kind)
2544                .into_iter()
2545                .enumerate()
2546            {
2547                if let IrOperand::Value(value) = operand {
2548                    uses.entry(value).or_default().push(IrUse {
2549                        instruction: instruction.id.clone(),
2550                        operand_index,
2551                    });
2552                }
2553            }
2554        }
2555    }
2556    IrDefinitionUseAnalysis { definitions, uses }
2557}
2558
2559/// Validates identity, definition, operand, and storage-reference integrity for canonical IR.
2560#[must_use]
2561pub fn validate_intermediate_representation(
2562    representation: &IntermediateRepresentation,
2563) -> Vec<IrValidationDiagnostic> {
2564    let storage_ids = representation
2565        .modules
2566        .iter()
2567        .flat_map(|module| module.storages.iter().map(|storage| storage.id.clone()))
2568        .collect::<BTreeSet<_>>();
2569    let computed_evaluation_ids = representation
2570        .modules
2571        .iter()
2572        .flat_map(|module| {
2573            module
2574                .computed_evaluations
2575                .iter()
2576                .map(|evaluation| evaluation.computed.clone())
2577        })
2578        .collect::<BTreeSet<_>>();
2579    let mut diagnostics = Vec::new();
2580    let mut instruction_ids = BTreeSet::new();
2581    let mut effect_ids = BTreeSet::new();
2582
2583    for module in &representation.modules {
2584        for instruction in &module.storage_initializers {
2585            validate_instruction(
2586                instruction,
2587                None,
2588                &BTreeSet::new(),
2589                &storage_ids,
2590                &computed_evaluation_ids,
2591                &mut instruction_ids,
2592                &mut diagnostics,
2593            );
2594        }
2595        for function in &module.functions {
2596            validate_function(
2597                function,
2598                &storage_ids,
2599                &computed_evaluation_ids,
2600                &mut instruction_ids,
2601                &mut diagnostics,
2602            );
2603        }
2604        validate_computed_evaluations(module, &mut diagnostics);
2605        validate_effect_executions(module, &mut effect_ids, &mut diagnostics);
2606    }
2607    diagnostics
2608}
2609
2610/// Validates F10 effect-IR records against the canonical ASM products without
2611/// re-running capability matching or semantic validation.
2612#[allow(clippy::too_many_lines)]
2613#[must_use]
2614pub fn validate_effect_ir(
2615    model: &ApplicationSemanticModel,
2616    representation: &IntermediateRepresentation,
2617) -> Vec<IrValidationDiagnostic> {
2618    let mut diagnostics = validate_intermediate_representation(representation);
2619    let lowered = representation
2620        .modules
2621        .iter()
2622        .flat_map(|module| &module.effect_executions)
2623        .map(|execution| execution.effect.clone())
2624        .collect::<BTreeSet<_>>();
2625    for execution in representation.modules.iter().flat_map(|module| {
2626        module
2627            .effect_executions
2628            .iter()
2629            .map(move |execution| (module, execution))
2630    }) {
2631        let (module, execution) = execution;
2632        let Some(effect) = model.effects.get(&execution.effect) else {
2633            diagnostics.push(IrValidationDiagnostic {
2634                code: "PSIR1020",
2635                message: format!(
2636                    "effect execution references missing effect {}",
2637                    execution.effect
2638                ),
2639            });
2640            continue;
2641        };
2642        if effect.validation != EffectValidation::Valid || !effect_is_scheduled(model, &effect.id) {
2643            diagnostics.push(IrValidationDiagnostic {
2644                code: "PSIR1021",
2645                message: format!(
2646                    "effect execution {} is invalid or F9-unplanned",
2647                    execution.effect
2648                ),
2649            });
2650        }
2651        let Some(body) = model.effect_body(&effect.id) else {
2652            diagnostics.push(IrValidationDiagnostic {
2653                code: "PSIR1022",
2654                message: format!(
2655                    "effect execution {} has no canonical body",
2656                    execution.effect
2657                ),
2658            });
2659            continue;
2660        };
2661        let Some(function) = module
2662            .functions
2663            .iter()
2664            .find(|function| function.id == execution.function)
2665        else {
2666            continue;
2667        };
2668        let statement_positions = body
2669            .statements
2670            .iter()
2671            .enumerate()
2672            .map(|(index, statement)| (statement, index))
2673            .collect::<BTreeMap<_, _>>();
2674        let mut previous_statement = None;
2675        for instruction in function.blocks.iter().flat_map(|block| &block.instructions) {
2676            let (operation, expected_kind) = match &instruction.kind {
2677                IrInstructionKind::CapabilityCall { operation, .. } => {
2678                    (*operation, CapabilityOperationKind::MethodCall)
2679                }
2680                IrInstructionKind::CapabilityAssign { operation, .. } => {
2681                    (*operation, CapabilityOperationKind::MemberAssignment)
2682                }
2683                _ => continue,
2684            };
2685            let Some(statement) = instruction.semantic_origin.as_ref() else {
2686                diagnostics.push(IrValidationDiagnostic {
2687                    code: "PSIR1023",
2688                    message: format!(
2689                        "capability instruction {} lacks an effect statement origin",
2690                        instruction.id
2691                    ),
2692                });
2693                continue;
2694            };
2695            let Some(record) = model.effect_statement_type(statement) else {
2696                diagnostics.push(IrValidationDiagnostic {
2697                    code: "PSIR1024",
2698                    message: format!(
2699                        "capability instruction {} has unknown statement {statement}",
2700                        instruction.id
2701                    ),
2702                });
2703                continue;
2704            };
2705            let registry_operation = EFFECT_CAPABILITY_REGISTRY.operation(operation);
2706            if record.capability_operation != Some(operation)
2707                || registry_operation.is_none_or(|candidate| candidate.kind != expected_kind)
2708                || record.signature_compatibility != EffectCompatibility::Compatible
2709                || record.boundary_compatibility != EffectCompatibility::Compatible
2710                || record.serialization_compatibility != EffectCompatibility::Compatible
2711                || instruction.result.is_some()
2712                || instruction.provenance != record.provenance
2713            {
2714                diagnostics.push(IrValidationDiagnostic {
2715                    code: "PSIR1025",
2716                    message: format!(
2717                        "capability instruction {} conflicts with canonical F4 facts",
2718                        instruction.id
2719                    ),
2720                });
2721            }
2722            let position = statement_positions.get(statement).copied();
2723            if position.is_none() || previous_statement.is_some_and(|prior| position <= Some(prior))
2724            {
2725                diagnostics.push(IrValidationDiagnostic {
2726                    code: "PSIR1026",
2727                    message: format!(
2728                        "effect instruction {} does not preserve statement order",
2729                        instruction.id
2730                    ),
2731                });
2732            }
2733            previous_statement = position;
2734        }
2735    }
2736    for effect in model.effects.values() {
2737        if (effect.validation != EffectValidation::Valid || !effect_is_scheduled(model, &effect.id))
2738            && lowered.contains(&effect.id)
2739        {
2740            diagnostics.push(IrValidationDiagnostic {
2741                code: "PSIR1027",
2742                message: format!("invalid or unplanned effect {} has IR", effect.id),
2743            });
2744        }
2745    }
2746    diagnostics
2747}
2748
2749/// Validates G10 Context IR against retained G9 and existing canonical products.
2750/// This consumes plan facts only and does not rerun Context resolution, typing,
2751/// lifetime, or evaluation planning.
2752#[allow(clippy::too_many_lines)]
2753#[must_use]
2754pub fn validate_context_ir(
2755    model: &ApplicationSemanticModel,
2756    representation: &IntermediateRepresentation,
2757) -> Vec<IrValidationDiagnostic> {
2758    let mut diagnostics = validate_intermediate_representation(representation);
2759    let planned = model
2760        .context_evaluation
2761        .source_entries
2762        .iter()
2763        .filter(|(_, entry)| entry.status == ContextSourcePlanStatus::Planned)
2764        .collect::<BTreeMap<_, _>>();
2765    let batches = model
2766        .context_evaluation
2767        .evaluation_batches
2768        .iter()
2769        .flat_map(|batch| {
2770            batch
2771                .sources
2772                .iter()
2773                .cloned()
2774                .map(move |source| (source, batch.id.clone()))
2775        })
2776        .collect::<BTreeMap<_, _>>();
2777    let mut source_records = BTreeMap::new();
2778    let mut slots = BTreeSet::new();
2779    let mut functions = BTreeSet::new();
2780
2781    for evaluation in &representation.context_ir.source_evaluations {
2782        let Some(entry) = planned.get(&evaluation.source) else {
2783            diagnostics.push(IrValidationDiagnostic {
2784                code: "PSIR1030",
2785                message: format!("Context source {:?} is not G9-planned", evaluation.source),
2786            });
2787            continue;
2788        };
2789        if source_records
2790            .insert(evaluation.source.clone(), evaluation)
2791            .is_some()
2792            || !slots.insert(evaluation.slot.clone())
2793            || !functions.insert(evaluation.function.clone())
2794        {
2795            diagnostics.push(IrValidationDiagnostic {
2796                code: "PSIR1031",
2797                message: format!(
2798                    "Context source {:?} has non-unique IR identities",
2799                    evaluation.source
2800                ),
2801            });
2802        }
2803        if evaluation.context != entry.context
2804            || evaluation.function != ContextSourceFunctionId::for_source(&evaluation.source)
2805            || evaluation.slot != ContextValueSlotId::for_source(&evaluation.source)
2806            || evaluation.prerequisite_computed_batches != entry.prerequisite_computed_batches
2807            || batches.get(&evaluation.source) != Some(&evaluation.evaluation_batch)
2808        {
2809            diagnostics.push(IrValidationDiagnostic {
2810                code: "PSIR1032",
2811                message: format!(
2812                    "Context source {:?} conflicts with retained G9 facts",
2813                    evaluation.source
2814                ),
2815            });
2816        }
2817        let function = representation
2818            .modules
2819            .iter()
2820            .flat_map(|module| &module.functions)
2821            .find(|function| function.id == *evaluation.function.as_semantic_id());
2822        let Some(function) = function else {
2823            diagnostics.push(IrValidationDiagnostic {
2824                code: "PSIR1033",
2825                message: format!(
2826                    "Context source {:?} is missing its IR function",
2827                    evaluation.source
2828                ),
2829            });
2830            continue;
2831        };
2832        let initializes_slot = function
2833            .blocks
2834            .iter()
2835            .flat_map(|block| &block.instructions)
2836            .any(|instruction| {
2837                matches!(
2838                    &instruction.kind,
2839                    IrInstructionKind::InitializeContextSlot { slot, value }
2840                        if slot == &evaluation.slot && value == &evaluation.result
2841                )
2842            });
2843        if function.entry_block != evaluation.entry_block
2844            || !function.values.contains_key(&evaluation.result)
2845            || !initializes_slot
2846        {
2847            diagnostics.push(IrValidationDiagnostic {
2848                code: "PSIR1034",
2849                message: format!(
2850                    "Context source {:?} lacks its exact result or slot initialization",
2851                    evaluation.source
2852                ),
2853            });
2854        }
2855        let actual_state = function
2856            .blocks
2857            .iter()
2858            .flat_map(|block| &block.instructions)
2859            .filter_map(|instruction| match &instruction.kind {
2860                IrInstructionKind::LoadStorage { storage } => Some(storage.clone()),
2861                _ => None,
2862            })
2863            .collect::<BTreeSet<_>>();
2864        let expected_state = entry
2865            .required_state
2866            .iter()
2867            .map(IrStorageId::for_semantic_origin)
2868            .collect::<BTreeSet<_>>();
2869        let actual_computed = function
2870            .blocks
2871            .iter()
2872            .flat_map(|block| &block.instructions)
2873            .filter_map(|instruction| match &instruction.kind {
2874                IrInstructionKind::LoadComputed { computed } => Some(computed.clone()),
2875                _ => None,
2876            })
2877            .collect::<BTreeSet<_>>();
2878        let expected_computed = entry
2879            .required_computed
2880            .iter()
2881            .cloned()
2882            .collect::<BTreeSet<_>>();
2883        if !expected_state.is_subset(&actual_state)
2884            || !expected_computed.is_subset(&actual_computed)
2885        {
2886            diagnostics.push(IrValidationDiagnostic {
2887                code: "PSIR1035",
2888                message: format!(
2889                    "Context source {:?} has incomplete canonical dependencies",
2890                    evaluation.source
2891                ),
2892            });
2893        }
2894    }
2895    for source in planned.keys() {
2896        if !source_records.contains_key(*source) {
2897            diagnostics.push(IrValidationDiagnostic {
2898                code: "PSIR1036",
2899                message: format!("G9-planned Context source {source:?} has no IR"),
2900            });
2901        }
2902    }
2903
2904    let available = model
2905        .context_evaluation
2906        .consumer_entries
2907        .iter()
2908        .filter(|(_, entry)| entry.status == ContextConsumerAvailabilityStatus::Available)
2909        .collect::<BTreeMap<_, _>>();
2910    let mut consumer_records = BTreeSet::new();
2911    for binding in &representation.context_ir.consumer_bindings {
2912        let Some(entry) = available.get(&binding.consumer) else {
2913            diagnostics.push(IrValidationDiagnostic {
2914                code: "PSIR1037",
2915                message: format!(
2916                    "unavailable Context Consumer {} has executable IR",
2917                    binding.consumer
2918                ),
2919            });
2920            continue;
2921        };
2922        if !consumer_records.insert(binding.consumer.clone()) {
2923            diagnostics.push(IrValidationDiagnostic {
2924                code: "PSIR1038",
2925                message: format!(
2926                    "Context Consumer {} has duplicate load IR",
2927                    binding.consumer
2928                ),
2929            });
2930        }
2931        let Some(consumer) = model.consumers.get(&binding.consumer) else {
2932            diagnostics.push(IrValidationDiagnostic {
2933                code: "PSIR1039",
2934                message: format!("Context Consumer {} is not canonical", binding.consumer),
2935            });
2936            continue;
2937        };
2938        let Some(source) = entry.selected_source.as_ref() else {
2939            diagnostics.push(IrValidationDiagnostic {
2940                code: "PSIR1040",
2941                message: format!(
2942                    "available Context Consumer {} has no selected source",
2943                    binding.consumer
2944                ),
2945            });
2946            continue;
2947        };
2948        let source_slot = source_records
2949            .get(source)
2950            .map(|evaluation| &evaluation.slot);
2951        if consumer.context() != Some(&binding.context)
2952            || binding.source != *source
2953            || source_slot != Some(&binding.slot)
2954            || binding.load.id != ContextConsumerLoadId::for_consumer(&binding.consumer)
2955            || binding.load.slot != binding.slot
2956            || binding.load.result != IrValueId::for_function(binding.load.id.as_semantic_id(), 0)
2957            || binding.semantic_type != consumer.requested_type_id
2958        {
2959            diagnostics.push(IrValidationDiagnostic {
2960                code: "PSIR1041",
2961                message: format!(
2962                    "Context Consumer {} conflicts with G4/G9 IR facts",
2963                    binding.consumer
2964                ),
2965            });
2966        }
2967    }
2968    for consumer in available.keys() {
2969        if !consumer_records.contains(*consumer) {
2970            diagnostics.push(IrValidationDiagnostic {
2971                code: "PSIR1042",
2972                message: format!("available Context Consumer {consumer} has no load IR"),
2973            });
2974        }
2975    }
2976    diagnostics
2977}
2978
2979/// Validates the immutable G11 optimization product against its frozen G9/G10
2980/// inputs. This deliberately consumes retained compiler products and never
2981/// re-runs Provider selection, Context typing, lifetime analysis, or planning.
2982#[allow(clippy::too_many_lines)]
2983#[must_use]
2984pub fn validate_optimized_context_ir(
2985    model: &ApplicationSemanticModel,
2986    input: &IntermediateRepresentation,
2987    report: &OptimizedContextIrReport,
2988) -> Vec<IrValidationDiagnostic> {
2989    let mut diagnostics = validate_context_ir(model, &report.optimized_module);
2990    if report.source_report != input.context_ir
2991        || report.optimized_module.context_ir != report.source_report
2992    {
2993        diagnostics.push(IrValidationDiagnostic {
2994            code: "PSIR1043",
2995            message: "optimized Context IR does not retain its exact G10 source report".to_string(),
2996        });
2997    }
2998
2999    let expected_evaluations = input
3000        .context_ir
3001        .source_evaluations
3002        .iter()
3003        .map(OptimizedIrContextSourceEvaluation::from)
3004        .collect::<Vec<_>>();
3005    if report.source_evaluations != expected_evaluations {
3006        diagnostics.push(IrValidationDiagnostic {
3007            code: "PSIR1044",
3008            message: "optimized Context source evaluations changed frozen identities or order"
3009                .to_string(),
3010        });
3011    }
3012    if report.optimized_module.context_ir.consumer_bindings != input.context_ir.consumer_bindings {
3013        diagnostics.push(IrValidationDiagnostic {
3014            code: "PSIR1045",
3015            message: "optimized Context IR changed compiler-selected Consumer slot bindings"
3016                .to_string(),
3017        });
3018    }
3019
3020    let source_functions = input
3021        .context_ir
3022        .source_evaluations
3023        .iter()
3024        .map(|evaluation| evaluation.function.as_semantic_id().clone())
3025        .collect::<BTreeSet<_>>();
3026    for evaluation in &report.source_evaluations {
3027        let functions = report
3028            .optimized_module
3029            .modules
3030            .iter()
3031            .flat_map(|module| &module.functions)
3032            .filter(|function| function.id == *evaluation.function.as_semantic_id())
3033            .collect::<Vec<_>>();
3034        if functions.len() != 1 {
3035            diagnostics.push(IrValidationDiagnostic {
3036                code: "PSIR1046",
3037                message: format!(
3038                    "optimized Context source {:?} has {} IR functions instead of one",
3039                    evaluation.source,
3040                    functions.len()
3041                ),
3042            });
3043            continue;
3044        }
3045        let function = functions[0];
3046        let initializations = report
3047            .optimized_module
3048            .modules
3049            .iter()
3050            .flat_map(|module| &module.functions)
3051            .flat_map(|function| &function.blocks)
3052            .flat_map(|block| &block.instructions)
3053            .filter(|instruction| {
3054                matches!(
3055                    &instruction.kind,
3056                    IrInstructionKind::InitializeContextSlot { slot, .. } if slot == &evaluation.slot
3057                )
3058            })
3059            .collect::<Vec<_>>();
3060        let exact_initializations = function
3061            .blocks
3062            .iter()
3063            .flat_map(|block| &block.instructions)
3064            .filter(|instruction| {
3065                matches!(
3066                    &instruction.kind,
3067                    IrInstructionKind::InitializeContextSlot { slot, value }
3068                        if slot == &evaluation.slot && value == &evaluation.result
3069                )
3070            })
3071            .count();
3072        if initializations.len() != 1
3073            || exact_initializations != 1
3074            || function.entry_block != evaluation.entry_block
3075            || !function.values.contains_key(&evaluation.result)
3076        {
3077            diagnostics.push(IrValidationDiagnostic {
3078                code: "PSIR1047",
3079                message: format!(
3080                    "optimized Context source {:?} does not retain one exact observable slot initialization",
3081                    evaluation.source
3082                ),
3083            });
3084        }
3085    }
3086
3087    let optimized_modules = report
3088        .optimized_module
3089        .modules
3090        .iter()
3091        .map(|module| (&module.path, module))
3092        .collect::<BTreeMap<_, _>>();
3093    for module in &input.modules {
3094        let Some(optimized) = optimized_modules.get(&module.path) else {
3095            diagnostics.push(IrValidationDiagnostic {
3096                code: "PSIR1048",
3097                message: format!(
3098                    "optimized Context IR is missing module {}",
3099                    module.path.display()
3100                ),
3101            });
3102            continue;
3103        };
3104        if module.components != optimized.components
3105            || module.storages != optimized.storages
3106            || module.storage_initializers != optimized.storage_initializers
3107            || module.template_entrypoints != optimized.template_entrypoints
3108            || module.computed_evaluations != optimized.computed_evaluations
3109            || module.effect_executions != optimized.effect_executions
3110            || module.functions.len() != optimized.functions.len()
3111        {
3112            diagnostics.push(IrValidationDiagnostic {
3113                code: "PSIR1049",
3114                message: format!(
3115                    "optimized Context IR changed a non-Context module product in {}",
3116                    module.path.display()
3117                ),
3118            });
3119        }
3120        for function in &module.functions {
3121            if source_functions.contains(&function.id) {
3122                continue;
3123            }
3124            if optimized
3125                .functions
3126                .iter()
3127                .find(|candidate| candidate.id == function.id)
3128                != Some(function)
3129            {
3130                diagnostics.push(IrValidationDiagnostic {
3131                    code: "PSIR1050",
3132                    message: format!(
3133                        "optimized Context IR changed unrelated function {}",
3134                        function.id
3135                    ),
3136                });
3137            }
3138        }
3139    }
3140    diagnostics
3141}
3142
3143fn validate_function(
3144    function: &IrFunction,
3145    storage_ids: &BTreeSet<IrStorageId>,
3146    computed_evaluation_ids: &BTreeSet<SemanticId>,
3147    instruction_ids: &mut BTreeSet<IrInstructionId>,
3148    diagnostics: &mut Vec<IrValidationDiagnostic>,
3149) {
3150    let function_instruction_ids = function
3151        .blocks
3152        .iter()
3153        .flat_map(|block| {
3154            block
3155                .instructions
3156                .iter()
3157                .map(|instruction| instruction.id.clone())
3158        })
3159        .collect::<BTreeSet<_>>();
3160    for block in &function.blocks {
3161        for instruction in &block.instructions {
3162            validate_instruction(
3163                instruction,
3164                Some(function),
3165                &function_instruction_ids,
3166                storage_ids,
3167                computed_evaluation_ids,
3168                instruction_ids,
3169                diagnostics,
3170            );
3171        }
3172    }
3173    for (id, value) in &function.values {
3174        validate_value(function, id, value, &function_instruction_ids, diagnostics);
3175    }
3176}
3177
3178fn validate_value(
3179    function: &IrFunction,
3180    id: &IrValueId,
3181    value: &IrValue,
3182    instruction_ids: &BTreeSet<IrInstructionId>,
3183    diagnostics: &mut Vec<IrValidationDiagnostic>,
3184) {
3185    if id != &value.id {
3186        diagnostics.push(IrValidationDiagnostic {
3187            code: "PSIR1001",
3188            message: format!(
3189                "value registry key {id} does not match value ID {}",
3190                value.id
3191            ),
3192        });
3193    }
3194    match &value.definition {
3195        IrValueDefinition::Instruction(instruction) if !instruction_ids.contains(instruction) => {
3196            diagnostics.push(IrValidationDiagnostic {
3197                code: "PSIR1002",
3198                message: format!(
3199                    "value {id} references missing defining instruction {instruction}"
3200                ),
3201            });
3202        }
3203        IrValueDefinition::Parameter {
3204            function: owner, ..
3205        } if owner != &function.id => {
3206            diagnostics.push(IrValidationDiagnostic {
3207                code: "PSIR1003",
3208                message: format!(
3209                    "value {id} belongs to parameter function {owner}, not {}",
3210                    function.id
3211                ),
3212            });
3213        }
3214        IrValueDefinition::BlockParameter { block, .. } if function.block(block).is_none() => {
3215            diagnostics.push(IrValidationDiagnostic {
3216                code: "PSIR1004",
3217                message: format!("value {id} references missing block parameter owner {block}"),
3218            });
3219        }
3220        _ => {}
3221    }
3222}
3223
3224fn validate_computed_evaluations(module: &IrModule, diagnostics: &mut Vec<IrValidationDiagnostic>) {
3225    for evaluation in &module.computed_evaluations {
3226        if evaluation.computed != evaluation.function {
3227            diagnostics.push(IrValidationDiagnostic {
3228                code: "PSIR1010",
3229                message: format!(
3230                    "computed evaluation {} must use its computed ID as function {}",
3231                    evaluation.computed, evaluation.function
3232                ),
3233            });
3234            continue;
3235        }
3236        let Some(function) = module
3237            .functions
3238            .iter()
3239            .find(|function| function.id == evaluation.function)
3240        else {
3241            diagnostics.push(IrValidationDiagnostic {
3242                code: "PSIR1010",
3243                message: format!(
3244                    "computed evaluation {} references missing function {}",
3245                    evaluation.computed, evaluation.function
3246                ),
3247            });
3248            continue;
3249        };
3250        if !function.values.contains_key(&evaluation.result) {
3251            diagnostics.push(IrValidationDiagnostic {
3252                code: "PSIR1011",
3253                message: format!(
3254                    "computed evaluation {} references missing result {}",
3255                    evaluation.computed, evaluation.result
3256                ),
3257            });
3258        }
3259    }
3260}
3261
3262fn validate_effect_executions(
3263    module: &IrModule,
3264    effect_ids: &mut BTreeSet<SemanticId>,
3265    diagnostics: &mut Vec<IrValidationDiagnostic>,
3266) {
3267    for execution in &module.effect_executions {
3268        if !effect_ids.insert(execution.effect.clone()) || execution.effect != execution.function {
3269            diagnostics.push(IrValidationDiagnostic {
3270                code: "PSIR1013",
3271                message: format!(
3272                    "effect execution {} has a non-unique function identity",
3273                    execution.effect
3274                ),
3275            });
3276            continue;
3277        }
3278        let Some(function) = module
3279            .functions
3280            .iter()
3281            .find(|function| function.id == execution.function)
3282        else {
3283            diagnostics.push(IrValidationDiagnostic {
3284                code: "PSIR1014",
3285                message: format!(
3286                    "effect execution {} references a missing function",
3287                    execution.effect
3288                ),
3289            });
3290            continue;
3291        };
3292        if function.entry_block != execution.entry_block {
3293            diagnostics.push(IrValidationDiagnostic {
3294                code: "PSIR1015",
3295                message: format!(
3296                    "effect execution {} has an inconsistent entry block",
3297                    execution.effect
3298                ),
3299            });
3300        }
3301        let operations = function
3302            .blocks
3303            .iter()
3304            .flat_map(|block| &block.instructions)
3305            .filter_map(|instruction| match instruction.kind {
3306                IrInstructionKind::CapabilityCall { operation, .. }
3307                | IrInstructionKind::CapabilityAssign { operation, .. } => Some(operation),
3308                _ => None,
3309            })
3310            .collect::<Vec<_>>();
3311        if operations != execution.capability_operations {
3312            diagnostics.push(IrValidationDiagnostic {
3313                code: "PSIR1016",
3314                message: format!(
3315                    "effect execution {} has inconsistent capability operations",
3316                    execution.effect
3317                ),
3318            });
3319        }
3320        match (&execution.cleanup_function, &execution.cleanup_entry_block) {
3321            (Some(cleanup_id), Some(cleanup_entry)) if cleanup_id != &execution.effect => {
3322                let Some(cleanup) = module
3323                    .functions
3324                    .iter()
3325                    .find(|function| function.id == *cleanup_id)
3326                else {
3327                    diagnostics.push(IrValidationDiagnostic {
3328                        code: "PSIR1023",
3329                        message: format!(
3330                            "effect execution {} references a missing cleanup function",
3331                            execution.effect
3332                        ),
3333                    });
3334                    continue;
3335                };
3336                if cleanup.entry_block != *cleanup_entry {
3337                    diagnostics.push(IrValidationDiagnostic {
3338                        code: "PSIR1024",
3339                        message: format!(
3340                            "effect execution {} has an inconsistent cleanup entry block",
3341                            execution.effect
3342                        ),
3343                    });
3344                }
3345                let operations = cleanup
3346                    .blocks
3347                    .iter()
3348                    .flat_map(|block| &block.instructions)
3349                    .filter_map(|instruction| match instruction.kind {
3350                        IrInstructionKind::CapabilityCall { operation, .. }
3351                        | IrInstructionKind::CapabilityAssign { operation, .. } => Some(operation),
3352                        _ => None,
3353                    })
3354                    .collect::<Vec<_>>();
3355                if operations != execution.cleanup_capability_operations {
3356                    diagnostics.push(IrValidationDiagnostic {
3357                        code: "PSIR1025",
3358                        message: format!(
3359                            "effect execution {} has inconsistent cleanup capability operations",
3360                            execution.effect
3361                        ),
3362                    });
3363                }
3364            }
3365            (None, None) if execution.cleanup_capability_operations.is_empty() => {}
3366            _ => diagnostics.push(IrValidationDiagnostic {
3367                code: "PSIR1026",
3368                message: format!(
3369                    "effect execution {} has malformed cleanup program metadata",
3370                    execution.effect
3371                ),
3372            }),
3373        }
3374    }
3375}
3376
3377fn validate_instruction(
3378    instruction: &IrInstruction,
3379    function: Option<&IrFunction>,
3380    function_instruction_ids: &BTreeSet<IrInstructionId>,
3381    storage_ids: &BTreeSet<IrStorageId>,
3382    computed_evaluation_ids: &BTreeSet<SemanticId>,
3383    instruction_ids: &mut BTreeSet<IrInstructionId>,
3384    diagnostics: &mut Vec<IrValidationDiagnostic>,
3385) {
3386    if !instruction_ids.insert(instruction.id.clone()) {
3387        diagnostics.push(IrValidationDiagnostic {
3388            code: "PSIR1005",
3389            message: format!("duplicate instruction ID {}", instruction.id),
3390        });
3391    }
3392    if let (Some(function), Some(result)) = (function, &instruction.result) {
3393        match function.values.get(result) {
3394            Some(value)
3395                if value.definition == IrValueDefinition::Instruction(instruction.id.clone()) => {}
3396            _ => diagnostics.push(IrValidationDiagnostic {
3397                code: "PSIR1006",
3398                message: format!(
3399                    "instruction {} result {result} lacks a matching value definition",
3400                    instruction.id
3401                ),
3402            }),
3403        }
3404    }
3405    for operand in ir_instruction_operands(&instruction.kind) {
3406        if let IrOperand::Value(value) = operand {
3407            if function.is_none_or(|function| !function.values.contains_key(&value)) {
3408                diagnostics.push(IrValidationDiagnostic {
3409                    code: "PSIR1007",
3410                    message: format!(
3411                        "instruction {} references unknown value {value}",
3412                        instruction.id
3413                    ),
3414                });
3415            }
3416        }
3417    }
3418    for storage in instruction_storages(&instruction.kind) {
3419        if !storage_ids.contains(storage) {
3420            diagnostics.push(IrValidationDiagnostic {
3421                code: "PSIR1008",
3422                message: format!(
3423                    "instruction {} references unknown storage {storage}",
3424                    instruction.id
3425                ),
3426            });
3427        }
3428    }
3429    if let IrInstructionKind::LoadComputed { computed } = &instruction.kind {
3430        if !computed_evaluation_ids.contains(computed) {
3431            diagnostics.push(IrValidationDiagnostic {
3432                code: "PSIR1012",
3433                message: format!(
3434                    "instruction {} references unknown computed evaluation {computed}",
3435                    instruction.id
3436                ),
3437            });
3438        }
3439    }
3440    if let IrInstructionKind::CapabilityCall { operation, .. }
3441    | IrInstructionKind::CapabilityAssign { operation, .. } = &instruction.kind
3442    {
3443        if EFFECT_CAPABILITY_REGISTRY.operation(*operation).is_none() {
3444            diagnostics.push(IrValidationDiagnostic {
3445                code: "PSIR1017",
3446                message: format!(
3447                    "instruction {} references unknown capability operation {}",
3448                    instruction.id, operation.0
3449                ),
3450            });
3451        }
3452    }
3453    if let Some(result) = &instruction.result {
3454        if !function_instruction_ids.contains(&instruction.id) {
3455            diagnostics.push(IrValidationDiagnostic {
3456                code: "PSIR1009",
3457                message: format!(
3458                    "module instruction {} must not produce value {result}",
3459                    instruction.id
3460                ),
3461            });
3462        }
3463    }
3464}
3465
3466/// Returns the exact operand reads encoded by one canonical IR instruction.
3467///
3468/// This is the closed IR operand authority shared by data-flow projections;
3469/// callers must not infer additional reads from instruction spelling.
3470#[must_use]
3471pub fn ir_instruction_operands(kind: &IrInstructionKind) -> Vec<IrOperand> {
3472    match kind {
3473        IrInstructionKind::StoreStorage { value, .. }
3474        | IrInstructionKind::Unary { operand: value, .. }
3475        | IrInstructionKind::Copy { source: value }
3476        | IrInstructionKind::GetMember { object: value, .. } => vec![value.clone()],
3477        IrInstructionKind::GetIndex { object, index } => vec![object.clone(), index.clone()],
3478        IrInstructionKind::Select {
3479            condition,
3480            when_true,
3481            when_false,
3482        } => vec![condition.clone(), when_true.clone(), when_false.clone()],
3483        IrInstructionKind::Binary { left, right, .. } => vec![left.clone(), right.clone()],
3484        IrInstructionKind::CapabilityCall { arguments, .. } => {
3485            arguments.iter().cloned().map(IrOperand::Value).collect()
3486        }
3487        IrInstructionKind::PurePackageCall { arguments, .. } => {
3488            arguments.iter().cloned().map(IrOperand::Value).collect()
3489        }
3490        IrInstructionKind::Template { expressions, .. } => {
3491            expressions.iter().cloned().map(IrOperand::Value).collect()
3492        }
3493        IrInstructionKind::CapabilityAssign { value, .. } => vec![IrOperand::Value(value.clone())],
3494        IrInstructionKind::InitializeContextSlot { value, .. } => {
3495            vec![IrOperand::Value(value.clone())]
3496        }
3497        IrInstructionKind::Nop
3498        | IrInstructionKind::Constant { .. }
3499        | IrInstructionKind::InitializeStorage { .. }
3500        | IrInstructionKind::LoadStorage { .. }
3501        | IrInstructionKind::LoadContextSlot { .. }
3502        | IrInstructionKind::LoadComputed { .. }
3503        | IrInstructionKind::LoadResource { .. } => Vec::new(),
3504    }
3505}
3506
3507fn instruction_storages(kind: &IrInstructionKind) -> Vec<&IrStorageId> {
3508    match kind {
3509        IrInstructionKind::InitializeStorage { storage }
3510        | IrInstructionKind::LoadStorage { storage }
3511        | IrInstructionKind::StoreStorage { storage, .. } => vec![storage],
3512        IrInstructionKind::Nop
3513        | IrInstructionKind::Constant { .. }
3514        | IrInstructionKind::Copy { .. }
3515        | IrInstructionKind::InitializeContextSlot { .. }
3516        | IrInstructionKind::LoadComputed { .. }
3517        | IrInstructionKind::LoadResource { .. }
3518        | IrInstructionKind::LoadContextSlot { .. }
3519        | IrInstructionKind::GetMember { .. }
3520        | IrInstructionKind::GetIndex { .. }
3521        | IrInstructionKind::Select { .. }
3522        | IrInstructionKind::Template { .. }
3523        | IrInstructionKind::PurePackageCall { .. }
3524        | IrInstructionKind::CapabilityCall { .. }
3525        | IrInstructionKind::CapabilityAssign { .. }
3526        | IrInstructionKind::Binary { .. }
3527        | IrInstructionKind::Unary { .. } => Vec::new(),
3528    }
3529}
3530
3531/// A stable compiler-owned loop identity within an IR function.
3532#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3533pub struct IrLoopId(String);
3534
3535impl IrLoopId {
3536    #[must_use]
3537    pub fn for_function(function: &SemanticId, name: &str) -> Self {
3538        Self(format!("{function}/loop:{name}"))
3539    }
3540
3541    #[must_use]
3542    pub fn as_str(&self) -> &str {
3543        &self.0
3544    }
3545}
3546
3547impl std::fmt::Display for IrLoopId {
3548    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3549        formatter.write_str(&self.0)
3550    }
3551}
3552
3553/// One ordered instruction region in an IR function.
3554#[derive(Debug, Clone, PartialEq, Eq)]
3555pub struct IrBlock {
3556    pub id: IrBlockId,
3557    pub provenance: SourceProvenance,
3558    pub instructions: Vec<IrInstruction>,
3559}
3560
3561/// A directed conditional branch between compiler-owned basic blocks.
3562#[derive(Debug, Clone, PartialEq, Eq)]
3563pub struct IrBranchEdge {
3564    pub from: IrBlockId,
3565    pub to: IrBlockId,
3566    pub arm: IrBranchArm,
3567    pub provenance: SourceProvenance,
3568}
3569
3570/// The outcome of a conditional branch represented by an IR edge.
3571#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3572pub enum IrBranchArm {
3573    True,
3574    False,
3575}
3576
3577/// A compiler-owned natural loop whose body includes its header and latches.
3578#[derive(Debug, Clone, PartialEq, Eq)]
3579pub struct IrLoop {
3580    pub id: IrLoopId,
3581    pub header: IrBlockId,
3582    pub body: Vec<IrBlockId>,
3583    pub latches: Vec<IrBlockId>,
3584    pub exits: Vec<IrBlockId>,
3585    pub provenance: SourceProvenance,
3586}
3587
3588/// The immutable dominator relation derived from one canonical IR function.
3589#[derive(Debug, Clone, PartialEq, Eq)]
3590pub struct IrDominatorTree {
3591    pub function: SemanticId,
3592    pub dominators: BTreeMap<IrBlockId, Vec<IrBlockId>>,
3593}
3594
3595impl IrDominatorTree {
3596    #[must_use]
3597    pub fn dominators_of(&self, block: &IrBlockId) -> Option<&[IrBlockId]> {
3598        self.dominators.get(block).map(Vec::as_slice)
3599    }
3600
3601    #[must_use]
3602    pub fn dominates(&self, dominator: &IrBlockId, block: &IrBlockId) -> bool {
3603        self.dominators_of(block)
3604            .is_some_and(|dominators| dominators.contains(dominator))
3605    }
3606}
3607
3608/// The immutable post-dominator relation derived from one canonical IR function.
3609#[derive(Debug, Clone, PartialEq, Eq)]
3610pub struct IrPostDominatorTree {
3611    pub function: SemanticId,
3612    pub post_dominators: BTreeMap<IrBlockId, Vec<IrBlockId>>,
3613}
3614
3615impl IrPostDominatorTree {
3616    #[must_use]
3617    pub fn post_dominators_of(&self, block: &IrBlockId) -> Option<&[IrBlockId]> {
3618        self.post_dominators.get(block).map(Vec::as_slice)
3619    }
3620
3621    #[must_use]
3622    pub fn post_dominates(&self, post_dominator: &IrBlockId, block: &IrBlockId) -> bool {
3623        self.post_dominators_of(block)
3624            .is_some_and(|post_dominators| post_dominators.contains(post_dominator))
3625    }
3626}
3627
3628/// Computes dominators from the function's entry block and canonical branch edges.
3629#[must_use]
3630pub fn compute_dominators(function: &IrFunction) -> IrDominatorTree {
3631    let block_ids = function
3632        .blocks
3633        .iter()
3634        .map(|block| block.id.clone())
3635        .collect::<BTreeSet<_>>();
3636    let mut predecessors = block_ids
3637        .iter()
3638        .cloned()
3639        .map(|block| (block, BTreeSet::new()))
3640        .collect::<BTreeMap<_, _>>();
3641    for edge in &function.branch_edges {
3642        if block_ids.contains(&edge.from) && block_ids.contains(&edge.to) {
3643            predecessors
3644                .entry(edge.to.clone())
3645                .or_default()
3646                .insert(edge.from.clone());
3647        }
3648    }
3649
3650    let mut dominators = block_ids
3651        .iter()
3652        .cloned()
3653        .map(|block| {
3654            let initial = if block == function.entry_block {
3655                BTreeSet::from([block.clone()])
3656            } else {
3657                block_ids.clone()
3658            };
3659            (block, initial)
3660        })
3661        .collect::<BTreeMap<_, _>>();
3662    let mut changed = true;
3663    while changed {
3664        changed = false;
3665        for block in &block_ids {
3666            if *block == function.entry_block {
3667                continue;
3668            }
3669            let mut next = predecessors[block]
3670                .iter()
3671                .filter_map(|predecessor| dominators.get(predecessor).cloned())
3672                .reduce(|mut shared, predecessor_dominators| {
3673                    shared.retain(|candidate| predecessor_dominators.contains(candidate));
3674                    shared
3675                })
3676                .unwrap_or_default();
3677            next.insert(block.clone());
3678            if dominators.get(block) != Some(&next) {
3679                dominators.insert(block.clone(), next);
3680                changed = true;
3681            }
3682        }
3683    }
3684
3685    IrDominatorTree {
3686        function: function.id.clone(),
3687        dominators: dominators
3688            .into_iter()
3689            .map(|(block, dominators)| (block, dominators.into_iter().collect()))
3690            .collect(),
3691    }
3692}
3693
3694/// Computes post-dominators from canonical branch edges and their CFG exit blocks.
3695#[must_use]
3696pub fn compute_post_dominators(function: &IrFunction) -> IrPostDominatorTree {
3697    let block_ids = function
3698        .blocks
3699        .iter()
3700        .map(|block| block.id.clone())
3701        .collect::<BTreeSet<_>>();
3702    let mut successors = block_ids
3703        .iter()
3704        .cloned()
3705        .map(|block| (block, BTreeSet::new()))
3706        .collect::<BTreeMap<_, _>>();
3707    for edge in &function.branch_edges {
3708        if block_ids.contains(&edge.from) && block_ids.contains(&edge.to) {
3709            successors
3710                .entry(edge.from.clone())
3711                .or_default()
3712                .insert(edge.to.clone());
3713        }
3714    }
3715    let exits = successors
3716        .iter()
3717        .filter_map(|(block, successors)| successors.is_empty().then_some(block.clone()))
3718        .collect::<BTreeSet<_>>();
3719
3720    let mut post_dominators = block_ids
3721        .iter()
3722        .cloned()
3723        .map(|block| {
3724            let initial = if exits.contains(&block) {
3725                BTreeSet::from([block.clone()])
3726            } else {
3727                block_ids.clone()
3728            };
3729            (block, initial)
3730        })
3731        .collect::<BTreeMap<_, _>>();
3732    let mut changed = true;
3733    while changed {
3734        changed = false;
3735        for block in &block_ids {
3736            if exits.contains(block) {
3737                continue;
3738            }
3739            let mut next = successors[block]
3740                .iter()
3741                .filter_map(|successor| post_dominators.get(successor).cloned())
3742                .reduce(|mut shared, successor_post_dominators| {
3743                    shared.retain(|candidate| successor_post_dominators.contains(candidate));
3744                    shared
3745                })
3746                .unwrap_or_default();
3747            next.insert(block.clone());
3748            if post_dominators.get(block) != Some(&next) {
3749                post_dominators.insert(block.clone(), next);
3750                changed = true;
3751            }
3752        }
3753    }
3754
3755    IrPostDominatorTree {
3756        function: function.id.clone(),
3757        post_dominators: post_dominators
3758            .into_iter()
3759            .map(|(block, post_dominators)| (block, post_dominators.into_iter().collect()))
3760            .collect(),
3761    }
3762}
3763
3764/// One backend-neutral instruction with stable source provenance.
3765#[derive(Debug, Clone, PartialEq, Eq)]
3766pub struct IrInstruction {
3767    pub id: IrInstructionId,
3768    pub provenance: SourceProvenance,
3769    pub result: Option<IrValueId>,
3770    pub semantic_origin: Option<SemanticId>,
3771    pub kind: IrInstructionKind,
3772}
3773
3774/// Instruction forms available to canonical IR lowering.
3775#[derive(Debug, Clone, PartialEq, Eq)]
3776pub enum IrInstructionKind {
3777    Nop,
3778    Constant {
3779        value: IrConstant,
3780    },
3781    Copy {
3782        source: IrOperand,
3783    },
3784    InitializeStorage {
3785        storage: IrStorageId,
3786    },
3787    /// Observable initialization of one compiler-owned Context value slot.
3788    InitializeContextSlot {
3789        slot: ContextValueSlotId,
3790        value: IrValueId,
3791    },
3792    LoadStorage {
3793        storage: IrStorageId,
3794    },
3795    /// Typed read from one exact compiler-selected Context value slot.
3796    LoadContextSlot {
3797        slot: ContextValueSlotId,
3798    },
3799    LoadComputed {
3800        computed: SemanticId,
3801    },
3802    /// Direct load of one compiler-selected Resource declaration record. The
3803    /// generated runtime resolves the concrete activation from the current
3804    /// component instance; there is no authored dynamic Resource lookup.
3805    LoadResource {
3806        declaration: SemanticId,
3807    },
3808    GetMember {
3809        object: IrOperand,
3810        property: String,
3811        optional: bool,
3812    },
3813    GetIndex {
3814        object: IrOperand,
3815        index: IrOperand,
3816    },
3817    Select {
3818        condition: IrOperand,
3819        when_true: IrOperand,
3820        when_false: IrOperand,
3821    },
3822    /// Compiler-lowered interpolation with source-retained cooked literal segments.
3823    Template {
3824        quasis: Vec<String>,
3825        expressions: Vec<IrValueId>,
3826    },
3827    /// A deterministic pure operation declared by an integrity-checked package contract.
3828    PurePackageCall {
3829        package: String,
3830        version: String,
3831        integrity: String,
3832        export: String,
3833        runtime_module: String,
3834        resume_policy: String,
3835        operation: crate::semantic_package::SemanticPackagePureOperation,
3836        arguments: Vec<IrValueId>,
3837    },
3838    StoreStorage {
3839        storage: IrStorageId,
3840        value: IrOperand,
3841    },
3842    /// One compiler-recognized, observable void capability invocation.
3843    CapabilityCall {
3844        operation: CapabilityOperationId,
3845        arguments: Vec<IrValueId>,
3846    },
3847    /// One compiler-recognized, observable void capability-member assignment.
3848    CapabilityAssign {
3849        operation: CapabilityOperationId,
3850        value: IrValueId,
3851    },
3852    Binary {
3853        operation: IrBinaryOperation,
3854        left: IrOperand,
3855        right: IrOperand,
3856    },
3857    Unary {
3858        operation: IrUnaryOperation,
3859        operand: IrOperand,
3860    },
3861}
3862
3863impl IrInstructionKind {
3864    /// Whether this instruction is an observable operation that optimization
3865    /// passes must preserve and keep ordered.
3866    #[must_use]
3867    pub const fn is_observable_side_effect(&self) -> bool {
3868        matches!(
3869            self,
3870            Self::CapabilityCall { .. }
3871                | Self::CapabilityAssign { .. }
3872                | Self::InitializeContextSlot { .. }
3873        )
3874    }
3875}
3876
3877/// Immutable copy-propagation pass for canonical IR operands.
3878pub struct IrCopyPropagationPass;
3879
3880impl IrOptimizationPass for IrCopyPropagationPass {
3881    fn name(&self) -> &'static str {
3882        "copy-propagation"
3883    }
3884
3885    fn run(&self, input: &IntermediateRepresentation) -> IntermediateRepresentation {
3886        let mut output = input.clone();
3887        for module in &mut output.modules {
3888            for function in &mut module.functions {
3889                let mut copies = BTreeMap::new();
3890                for block in &mut function.blocks {
3891                    for instruction in &mut block.instructions {
3892                        replace_copy_operands(&mut instruction.kind, &copies);
3893                        if let (Some(result), IrInstructionKind::Copy { source }) =
3894                            (&instruction.result, &instruction.kind)
3895                        {
3896                            copies.insert(result.clone(), source.clone());
3897                        }
3898                    }
3899                }
3900            }
3901        }
3902        output
3903    }
3904}
3905
3906/// Immutable common-subexpression-elimination pass for current pure expressions.
3907pub struct IrCommonSubexpressionEliminationPass;
3908
3909impl IrOptimizationPass for IrCommonSubexpressionEliminationPass {
3910    fn name(&self) -> &'static str {
3911        "common-subexpression-elimination"
3912    }
3913
3914    fn run(&self, input: &IntermediateRepresentation) -> IntermediateRepresentation {
3915        let mut output = input.clone();
3916        for module in &mut output.modules {
3917            for function in &mut module.functions {
3918                let mut expressions = BTreeMap::<String, IrValueId>::new();
3919                for block in &mut function.blocks {
3920                    for instruction in &mut block.instructions {
3921                        let Some(result) = instruction.result.clone() else {
3922                            continue;
3923                        };
3924                        let key = match &instruction.kind {
3925                            IrInstructionKind::Unary { .. } | IrInstructionKind::Binary { .. } => {
3926                                format!("{:?}", instruction.kind)
3927                            }
3928                            _ => continue,
3929                        };
3930                        if let Some(existing) = expressions.get(&key) {
3931                            instruction.kind = IrInstructionKind::Copy {
3932                                source: IrOperand::Value(existing.clone()),
3933                            };
3934                        } else {
3935                            expressions.insert(key, result);
3936                        }
3937                    }
3938                }
3939            }
3940        }
3941        output
3942    }
3943}
3944
3945/// Immutable instruction-simplification pass for current canonical IR forms.
3946pub struct IrInstructionSimplificationPass;
3947
3948impl IrOptimizationPass for IrInstructionSimplificationPass {
3949    fn name(&self) -> &'static str {
3950        "instruction-simplification"
3951    }
3952
3953    fn run(&self, input: &IntermediateRepresentation) -> IntermediateRepresentation {
3954        let mut output = input.clone();
3955        for module in &mut output.modules {
3956            for function in &mut module.functions {
3957                let constants = analyze_constant_propagation(function).constants;
3958                for block in &mut function.blocks {
3959                    for instruction in &mut block.instructions {
3960                        if let IrInstructionKind::Copy { source } = &instruction.kind {
3961                            if let Some(value) = resolve_constant(source, &constants) {
3962                                instruction.kind = IrInstructionKind::Constant { value };
3963                            }
3964                        }
3965                    }
3966                }
3967            }
3968        }
3969        output
3970    }
3971}
3972
3973/// Immutable cleanup pass that removes unreachable canonical CFG artifacts.
3974pub struct IrCfgCleanupPass;
3975
3976impl IrOptimizationPass for IrCfgCleanupPass {
3977    fn name(&self) -> &'static str {
3978        "cfg-cleanup"
3979    }
3980
3981    fn run(&self, input: &IntermediateRepresentation) -> IntermediateRepresentation {
3982        let mut output = input.clone();
3983        for module in &mut output.modules {
3984            for function in &mut module.functions {
3985                let reachable = analyze_reachability(function)
3986                    .reachable
3987                    .into_iter()
3988                    .collect::<BTreeSet<_>>();
3989                function
3990                    .blocks
3991                    .retain(|block| reachable.contains(&block.id));
3992                function
3993                    .branch_edges
3994                    .retain(|edge| reachable.contains(&edge.from) && reachable.contains(&edge.to));
3995                function
3996                    .loops
3997                    .retain(|loop_region| reachable.contains(&loop_region.header));
3998                let instructions = function
3999                    .blocks
4000                    .iter()
4001                    .flat_map(|block| {
4002                        block
4003                            .instructions
4004                            .iter()
4005                            .map(|instruction| instruction.id.clone())
4006                    })
4007                    .collect::<BTreeSet<_>>();
4008                function.values.retain(|_, value| !matches!(&value.definition, IrValueDefinition::Instruction(instruction) if !instructions.contains(instruction)));
4009            }
4010        }
4011        output
4012    }
4013}
4014
4015fn replace_copy_operands(kind: &mut IrInstructionKind, copies: &BTreeMap<IrValueId, IrOperand>) {
4016    let resolve = |operand: &mut IrOperand| {
4017        while let IrOperand::Value(value) = operand {
4018            let Some(replacement) = copies.get(value) else {
4019                break;
4020            };
4021            *operand = replacement.clone();
4022        }
4023    };
4024    match kind {
4025        IrInstructionKind::Copy { source }
4026        | IrInstructionKind::StoreStorage { value: source, .. }
4027        | IrInstructionKind::Unary {
4028            operand: source, ..
4029        }
4030        | IrInstructionKind::GetMember { object: source, .. } => resolve(source),
4031        IrInstructionKind::GetIndex { object, index } => {
4032            resolve(object);
4033            resolve(index);
4034        }
4035        IrInstructionKind::Select {
4036            condition,
4037            when_true,
4038            when_false,
4039        } => {
4040            resolve(condition);
4041            resolve(when_true);
4042            resolve(when_false);
4043        }
4044        IrInstructionKind::Binary { left, right, .. } => {
4045            resolve(left);
4046            resolve(right);
4047        }
4048        _ => {}
4049    }
4050}
4051
4052/// Immutable primitive constant-folding pass for canonical IR.
4053pub struct IrConstantFoldingPass;
4054
4055impl IrOptimizationPass for IrConstantFoldingPass {
4056    fn name(&self) -> &'static str {
4057        "constant-folding"
4058    }
4059
4060    fn run(&self, input: &IntermediateRepresentation) -> IntermediateRepresentation {
4061        let mut output = input.clone();
4062        for module in &mut output.modules {
4063            for function in &mut module.functions {
4064                let constants = analyze_constant_propagation(function).constants;
4065                for block in &mut function.blocks {
4066                    for instruction in &mut block.instructions {
4067                        if let Some(result) = &instruction.result {
4068                            if let Some(value) = constants.get(result) {
4069                                instruction.kind = IrInstructionKind::Constant {
4070                                    value: value.clone(),
4071                                };
4072                            }
4073                        }
4074                    }
4075                }
4076            }
4077        }
4078        output
4079    }
4080}
4081
4082/// Immutable dead-code-elimination pass for unused pure instruction results.
4083pub struct IrDeadCodeEliminationPass;
4084
4085impl IrOptimizationPass for IrDeadCodeEliminationPass {
4086    fn name(&self) -> &'static str {
4087        "dead-code-elimination"
4088    }
4089
4090    fn run(&self, input: &IntermediateRepresentation) -> IntermediateRepresentation {
4091        let mut output = input.clone();
4092        for module in &mut output.modules {
4093            for function in &mut module.functions {
4094                let preserved_values = module
4095                    .computed_evaluations
4096                    .iter()
4097                    .filter(|evaluation| evaluation.function == function.id)
4098                    .map(|evaluation| evaluation.result.clone())
4099                    .collect::<BTreeSet<_>>();
4100                loop {
4101                    let dead = analyze_dead_assignments_preserving(function, &preserved_values)
4102                        .instructions
4103                        .into_iter()
4104                        .collect::<BTreeSet<_>>();
4105                    if dead.is_empty() {
4106                        break;
4107                    }
4108                    for block in &mut function.blocks {
4109                        block
4110                            .instructions
4111                            .retain(|instruction| !dead.contains(&instruction.id));
4112                    }
4113                    function.values.retain(|_, value| {
4114                        !matches!(
4115                            &value.definition,
4116                            IrValueDefinition::Instruction(instruction) if dead.contains(instruction)
4117                        )
4118                    });
4119                }
4120            }
4121        }
4122        output
4123    }
4124}
4125
4126/// A binary operation with value-producing IR semantics.
4127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4128pub enum IrBinaryOperation {
4129    Add,
4130    Subtract,
4131    Multiply,
4132    Divide,
4133    Remainder,
4134    Equal,
4135    NotEqual,
4136    LessThan,
4137    LessThanOrEqual,
4138    GreaterThan,
4139    GreaterThanOrEqual,
4140    And,
4141    Or,
4142    NullishCoalesce,
4143    Min,
4144    Max,
4145}
4146
4147/// A unary operation with value-producing IR semantics.
4148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4149pub enum IrUnaryOperation {
4150    Not,
4151    Identity,
4152    Negate,
4153    Abs,
4154    Floor,
4155    Ceil,
4156    Round,
4157}
4158
4159#[cfg(test)]
4160mod tests {
4161    use super::{
4162        compute_dominators, compute_post_dominators, lower_components_to_ir, optimize_computed_ir,
4163        optimize_context_ir, optimize_effect_ir, validate_context_ir, validate_effect_ir,
4164        validate_intermediate_representation, validate_optimized_context_ir, ContextIrReport,
4165        IntermediateRepresentation, IrBinaryOperation, IrBlock, IrBlockId, IrBranchArm,
4166        IrBranchEdge, IrConstant, IrFunction, IrInstruction, IrInstructionId, IrInstructionKind,
4167        IrLoop, IrLoopId, IrModule, IrOperand, IrStorageId, IrUnaryOperation, IrValue,
4168        IrValueDefinition, IrValueId,
4169    };
4170    use crate::{
4171        build_application_semantic_model, CapabilityOperationId, ConsumerId, ContextId,
4172        ContextValueSourceId, ProviderId, SemanticId, SemanticType, SourceProvenance,
4173    };
4174    use std::collections::BTreeMap;
4175
4176    #[test]
4177    fn represents_backend_neutral_ir_structure_with_provenance() {
4178        let provenance = SourceProvenance::new(
4179            "src/Counter.tsx",
4180            presolve_parser::SourceSpan {
4181                start: 0,
4182                end: 1,
4183                line: 1,
4184                column: 1,
4185            },
4186        );
4187        let function = IrFunction {
4188            id: SemanticId::component(Some("x-counter"), "Counter").method("increment"),
4189            name: "increment".to_string(),
4190            provenance: provenance.clone(),
4191            entry_block: IrBlockId::entry_for(
4192                &SemanticId::component(Some("x-counter"), "Counter").method("increment"),
4193            ),
4194            blocks: vec![IrBlock {
4195                id: IrBlockId::entry_for(
4196                    &SemanticId::component(Some("x-counter"), "Counter").method("increment"),
4197                ),
4198                provenance: provenance.clone(),
4199                instructions: vec![IrInstruction {
4200                    id: IrInstructionId::for_block(
4201                        &IrBlockId::entry_for(
4202                            &SemanticId::component(Some("x-counter"), "Counter")
4203                                .method("increment"),
4204                        ),
4205                        0,
4206                    ),
4207                    provenance,
4208                    result: None,
4209                    semantic_origin: None,
4210                    kind: IrInstructionKind::Nop,
4211                }],
4212            }],
4213            branch_edges: Vec::new(),
4214            values: BTreeMap::new(),
4215            loops: Vec::new(),
4216        };
4217        let ir = IntermediateRepresentation {
4218            modules: vec![IrModule {
4219                path: "src/Counter.tsx".into(),
4220                components: vec![SemanticId::component(Some("x-counter"), "Counter")],
4221                storages: Vec::new(),
4222                storage_initializers: Vec::new(),
4223                template_entrypoints: Vec::new(),
4224                functions: vec![function],
4225                computed_evaluations: Vec::new(),
4226                effect_executions: Vec::new(),
4227            }],
4228            context_ir: ContextIrReport::default(),
4229        };
4230
4231        assert_eq!(ir.modules[0].functions[0].blocks[0].instructions.len(), 1);
4232    }
4233
4234    #[test]
4235    fn represents_provenanced_conditional_branch_edges() {
4236        let provenance = SourceProvenance::new(
4237            "src/Counter.tsx",
4238            presolve_parser::SourceSpan {
4239                start: 0,
4240                end: 1,
4241                line: 1,
4242                column: 1,
4243            },
4244        );
4245        let function = SemanticId::component(Some("x-counter"), "Counter").method("render");
4246        let entry = IrBlockId::entry_for(&function);
4247        let when_true = IrBlockId::for_function(&function, "when-true");
4248        let branch = IrBranchEdge {
4249            from: entry,
4250            to: when_true,
4251            arm: IrBranchArm::True,
4252            provenance,
4253        };
4254
4255        assert_eq!(
4256            branch.from.as_str(),
4257            "component:x-counter/method:render/block:entry"
4258        );
4259        assert_eq!(
4260            branch.to.as_str(),
4261            "component:x-counter/method:render/block:when-true"
4262        );
4263        assert_eq!(branch.arm, IrBranchArm::True);
4264    }
4265
4266    #[test]
4267    fn keeps_ir_identity_domains_distinct_and_deterministic() {
4268        let function = SemanticId::component(Some("x-counter"), "Counter").method("increment");
4269        let block = IrBlockId::entry_for(&function);
4270        let instruction = IrInstructionId::for_block(&block, 0);
4271        let value = IrValueId::for_function(&function, 0);
4272        let storage = IrStorageId::for_semantic_origin(
4273            &SemanticId::component(Some("x-counter"), "Counter").state_field("count"),
4274        );
4275
4276        assert_eq!(
4277            instruction.as_str(),
4278            "component:x-counter/method:increment/block:entry/instruction:0"
4279        );
4280        assert_eq!(
4281            value.as_str(),
4282            "component:x-counter/method:increment/value:0"
4283        );
4284        assert_eq!(storage.as_str(), "storage:component:x-counter/state:count");
4285        assert_ne!(instruction.as_str(), value.as_str());
4286        assert_ne!(value.as_str(), storage.as_str());
4287    }
4288
4289    #[test]
4290    fn represents_closed_ir_operands_without_semantic_identity_operands() {
4291        let function = SemanticId::component(Some("x-counter"), "Counter").method("increment");
4292        let value = IrOperand::Value(IrValueId::for_function(&function, 0));
4293        let constant = IrOperand::Constant(IrConstant::Number("1".to_string()));
4294        let storage = IrOperand::Storage(IrStorageId::for_semantic_origin(
4295            &SemanticId::component(Some("x-counter"), "Counter").state_field("count"),
4296        ));
4297
4298        assert!(matches!(value, IrOperand::Value(_)));
4299        assert!(
4300            matches!(constant, IrOperand::Constant(IrConstant::Number(number)) if number == "1")
4301        );
4302        assert!(matches!(storage, IrOperand::Storage(_)));
4303    }
4304
4305    #[test]
4306    fn records_instruction_results_separately_from_operation_identity() {
4307        let provenance = SourceProvenance::new(
4308            "src/Counter.tsx",
4309            presolve_parser::SourceSpan {
4310                start: 0,
4311                end: 1,
4312                line: 1,
4313                column: 1,
4314            },
4315        );
4316        let function = SemanticId::component(Some("x-counter"), "Counter").method("increment");
4317        let block = IrBlockId::entry_for(&function);
4318        let storage = IrStorageId::for_semantic_origin(
4319            &SemanticId::component(Some("x-counter"), "Counter").state_field("count"),
4320        );
4321        let instruction = IrInstruction {
4322            id: IrInstructionId::for_block(&block, 0),
4323            provenance,
4324            result: Some(IrValueId::for_function(&function, 0)),
4325            semantic_origin: Some(
4326                SemanticId::component(Some("x-counter"), "Counter").state_field("count"),
4327            ),
4328            kind: IrInstructionKind::LoadStorage { storage },
4329        };
4330
4331        assert_eq!(
4332            instruction.id.as_str(),
4333            "component:x-counter/method:increment/block:entry/instruction:0"
4334        );
4335        assert_eq!(
4336            instruction.result.expect("load result").as_str(),
4337            "component:x-counter/method:increment/value:0"
4338        );
4339        assert!(matches!(
4340            instruction.kind,
4341            IrInstructionKind::LoadStorage { .. }
4342        ));
4343    }
4344
4345    #[test]
4346    fn indexes_values_by_function_scoped_value_identity() {
4347        let provenance = SourceProvenance::new(
4348            "src/Counter.tsx",
4349            presolve_parser::SourceSpan {
4350                start: 0,
4351                end: 1,
4352                line: 1,
4353                column: 1,
4354            },
4355        );
4356        let id = SemanticId::component(Some("x-counter"), "Counter").method("increment");
4357        let entry = IrBlockId::entry_for(&id);
4358        let value_id = IrValueId::for_function(&id, 0);
4359        let value = IrValue {
4360            id: value_id.clone(),
4361            definition: IrValueDefinition::Instruction(IrInstructionId::for_block(&entry, 0)),
4362            semantic_type: SemanticType::Number,
4363            provenance: provenance.clone(),
4364            semantic_origin: Some(
4365                SemanticId::component(Some("x-counter"), "Counter").state_field("count"),
4366            ),
4367        };
4368        let function = IrFunction {
4369            id,
4370            name: "increment".to_string(),
4371            provenance: provenance.clone(),
4372            entry_block: entry.clone(),
4373            blocks: vec![IrBlock {
4374                id: entry,
4375                provenance,
4376                instructions: Vec::new(),
4377            }],
4378            branch_edges: Vec::new(),
4379            values: BTreeMap::from([(value_id.clone(), value)]),
4380            loops: Vec::new(),
4381        };
4382
4383        assert_eq!(
4384            function.value(&value_id).expect("value").semantic_type,
4385            SemanticType::Number
4386        );
4387        assert!(matches!(
4388            function.value(&value_id).expect("value").definition,
4389            IrValueDefinition::Instruction(_)
4390        ));
4391    }
4392
4393    #[test]
4394    fn represents_natural_loops_with_header_latches_and_exits() {
4395        let provenance = SourceProvenance::new(
4396            "src/Counter.tsx",
4397            presolve_parser::SourceSpan {
4398                start: 0,
4399                end: 1,
4400                line: 1,
4401                column: 1,
4402            },
4403        );
4404        let function = SemanticId::component(Some("x-counter"), "Counter").method("render");
4405        let header = IrBlockId::for_function(&function, "loop-header");
4406        let latch = IrBlockId::for_function(&function, "loop-latch");
4407        let exit = IrBlockId::for_function(&function, "loop-exit");
4408        let loop_region = IrLoop {
4409            id: IrLoopId::for_function(&function, "items"),
4410            header: header.clone(),
4411            body: vec![header, latch.clone()],
4412            latches: vec![latch],
4413            exits: vec![exit],
4414            provenance,
4415        };
4416
4417        assert_eq!(
4418            loop_region.id.as_str(),
4419            "component:x-counter/method:render/loop:items"
4420        );
4421        assert_eq!(loop_region.body[0], loop_region.header);
4422        assert_eq!(loop_region.body[1], loop_region.latches[0]);
4423        assert_eq!(
4424            loop_region.exits[0].as_str(),
4425            "component:x-counter/method:render/block:loop-exit"
4426        );
4427    }
4428
4429    #[test]
4430    fn computes_dominators_from_canonical_branch_edges() {
4431        let provenance = SourceProvenance::new(
4432            "src/Counter.tsx",
4433            presolve_parser::SourceSpan {
4434                start: 0,
4435                end: 1,
4436                line: 1,
4437                column: 1,
4438            },
4439        );
4440        let id = SemanticId::component(Some("x-counter"), "Counter").method("render");
4441        let entry = IrBlockId::entry_for(&id);
4442        let when_true = IrBlockId::for_function(&id, "when-true");
4443        let when_false = IrBlockId::for_function(&id, "when-false");
4444        let merge = IrBlockId::for_function(&id, "merge");
4445        let function = IrFunction {
4446            id: id.clone(),
4447            name: "render".to_string(),
4448            provenance: provenance.clone(),
4449            entry_block: entry.clone(),
4450            blocks: vec![
4451                IrBlock {
4452                    id: entry.clone(),
4453                    provenance: provenance.clone(),
4454                    instructions: Vec::new(),
4455                },
4456                IrBlock {
4457                    id: when_true.clone(),
4458                    provenance: provenance.clone(),
4459                    instructions: Vec::new(),
4460                },
4461                IrBlock {
4462                    id: when_false.clone(),
4463                    provenance: provenance.clone(),
4464                    instructions: Vec::new(),
4465                },
4466                IrBlock {
4467                    id: merge.clone(),
4468                    provenance: provenance.clone(),
4469                    instructions: Vec::new(),
4470                },
4471            ],
4472            branch_edges: vec![
4473                IrBranchEdge {
4474                    from: entry.clone(),
4475                    to: when_true.clone(),
4476                    arm: IrBranchArm::True,
4477                    provenance: provenance.clone(),
4478                },
4479                IrBranchEdge {
4480                    from: entry.clone(),
4481                    to: when_false.clone(),
4482                    arm: IrBranchArm::False,
4483                    provenance: provenance.clone(),
4484                },
4485                IrBranchEdge {
4486                    from: when_true,
4487                    to: merge.clone(),
4488                    arm: IrBranchArm::True,
4489                    provenance: provenance.clone(),
4490                },
4491                IrBranchEdge {
4492                    from: when_false,
4493                    to: merge.clone(),
4494                    arm: IrBranchArm::False,
4495                    provenance,
4496                },
4497            ],
4498            values: BTreeMap::new(),
4499            loops: Vec::new(),
4500        };
4501
4502        let tree = compute_dominators(&function);
4503
4504        assert_eq!(tree.function, id);
4505        assert_eq!(tree.dominators[&entry], vec![entry.clone()]);
4506        assert_eq!(tree.dominators[&merge], vec![entry, merge]);
4507    }
4508
4509    #[test]
4510    fn computes_post_dominators_from_canonical_branch_edges() {
4511        let provenance = SourceProvenance::new(
4512            "src/Counter.tsx",
4513            presolve_parser::SourceSpan {
4514                start: 0,
4515                end: 1,
4516                line: 1,
4517                column: 1,
4518            },
4519        );
4520        let id = SemanticId::component(Some("x-counter"), "Counter").method("render");
4521        let entry = IrBlockId::entry_for(&id);
4522        let when_true = IrBlockId::for_function(&id, "when-true");
4523        let when_false = IrBlockId::for_function(&id, "when-false");
4524        let merge = IrBlockId::for_function(&id, "merge");
4525        let function = IrFunction {
4526            id: id.clone(),
4527            name: "render".to_string(),
4528            provenance: provenance.clone(),
4529            entry_block: entry.clone(),
4530            blocks: [
4531                entry.clone(),
4532                when_true.clone(),
4533                when_false.clone(),
4534                merge.clone(),
4535            ]
4536            .into_iter()
4537            .map(|id| IrBlock {
4538                id,
4539                provenance: provenance.clone(),
4540                instructions: Vec::new(),
4541            })
4542            .collect(),
4543            branch_edges: vec![
4544                IrBranchEdge {
4545                    from: entry.clone(),
4546                    to: when_true.clone(),
4547                    arm: IrBranchArm::True,
4548                    provenance: provenance.clone(),
4549                },
4550                IrBranchEdge {
4551                    from: entry.clone(),
4552                    to: when_false.clone(),
4553                    arm: IrBranchArm::False,
4554                    provenance: provenance.clone(),
4555                },
4556                IrBranchEdge {
4557                    from: when_true,
4558                    to: merge.clone(),
4559                    arm: IrBranchArm::True,
4560                    provenance: provenance.clone(),
4561                },
4562                IrBranchEdge {
4563                    from: when_false,
4564                    to: merge.clone(),
4565                    arm: IrBranchArm::False,
4566                    provenance,
4567                },
4568            ],
4569            values: BTreeMap::new(),
4570            loops: Vec::new(),
4571        };
4572
4573        let tree = compute_post_dominators(&function);
4574
4575        assert_eq!(tree.function, id);
4576        assert_eq!(tree.post_dominators[&merge], vec![merge.clone()]);
4577        assert_eq!(tree.post_dominators[&entry], vec![entry, merge]);
4578    }
4579
4580    #[test]
4581    fn queries_canonical_cfg_connectivity_and_dominance() {
4582        let provenance = SourceProvenance::new(
4583            "src/Counter.tsx",
4584            presolve_parser::SourceSpan {
4585                start: 0,
4586                end: 1,
4587                line: 1,
4588                column: 1,
4589            },
4590        );
4591        let id = SemanticId::component(Some("x-counter"), "Counter").method("render");
4592        let entry = IrBlockId::entry_for(&id);
4593        let exit = IrBlockId::for_function(&id, "exit");
4594        let function = IrFunction {
4595            id,
4596            name: "render".to_string(),
4597            provenance: provenance.clone(),
4598            entry_block: entry.clone(),
4599            blocks: [entry.clone(), exit.clone()]
4600                .into_iter()
4601                .map(|id| IrBlock {
4602                    id,
4603                    provenance: provenance.clone(),
4604                    instructions: Vec::new(),
4605                })
4606                .collect(),
4607            branch_edges: vec![IrBranchEdge {
4608                from: entry.clone(),
4609                to: exit.clone(),
4610                arm: IrBranchArm::True,
4611                provenance,
4612            }],
4613            values: BTreeMap::new(),
4614            loops: Vec::new(),
4615        };
4616        let dominators = compute_dominators(&function);
4617        let post_dominators = compute_post_dominators(&function);
4618
4619        assert_eq!(function.block(&entry).expect("entry").id, entry);
4620        assert_eq!(
4621            function.successor_blocks(&function.entry_block),
4622            vec![exit.clone()]
4623        );
4624        assert_eq!(
4625            function.predecessor_blocks(&exit),
4626            vec![function.entry_block.clone()]
4627        );
4628        assert!(function.is_exit_block(&exit));
4629        assert!(dominators.dominates(&function.entry_block, &exit));
4630        assert!(post_dominators.post_dominates(&exit, &function.entry_block));
4631    }
4632
4633    #[test]
4634    fn lowers_components_into_modules_with_entry_blocks() {
4635        let parsed = presolve_parser::parse_file(
4636            "src/Counter.tsx",
4637            "@component(\"x-counter\") class Counter extends Component { count = state(0); increment() {} render() { return <p>{this.count}</p>; } }",
4638        );
4639        let model = crate::build_application_semantic_model(&parsed);
4640        let ir = lower_components_to_ir(&model);
4641
4642        assert_eq!(ir.modules.len(), 1);
4643        assert_eq!(
4644            ir.modules[0].components,
4645            vec![model.components[0].id.clone()]
4646        );
4647        assert_eq!(ir.modules[0].functions[0].name, "increment");
4648        assert_eq!(ir.modules[0].functions[0].blocks.len(), 1);
4649        assert_eq!(
4650            ir.modules[0].functions[0].entry_block,
4651            ir.modules[0].functions[0].blocks[0].id
4652        );
4653        assert_eq!(
4654            ir.modules[0].functions[0].entry_block.as_str(),
4655            format!("{}/block:entry", ir.modules[0].functions[0].id).as_str()
4656        );
4657        assert!(ir.modules[0].functions[0].blocks[0].instructions.is_empty());
4658        assert!(ir.modules[0].functions[0].branch_edges.is_empty());
4659        assert!(ir.modules[0].functions[0].loops.is_empty());
4660        assert!(matches!(
4661            ir.modules[0].storage_initializers[0].kind,
4662            IrInstructionKind::InitializeStorage { .. }
4663        ));
4664        assert_eq!(ir.modules[0].storages.len(), 1);
4665        assert_eq!(
4666            ir.modules[0].storages[0].id.as_str(),
4667            format!("storage:{}", model.components[0].state_fields[0].id).as_str()
4668        );
4669        assert_eq!(
4670            ir.modules[0].storage_initializers[0].semantic_origin,
4671            Some(model.components[0].state_fields[0].id.clone())
4672        );
4673        assert_eq!(ir.modules[0].template_entrypoints.len(), 1);
4674        assert_eq!(
4675            ir.modules[0].template_entrypoints[0].render_method,
4676            model.components[0]
4677                .methods
4678                .iter()
4679                .find(|method| method.name == "render")
4680                .expect("render")
4681                .id
4682        );
4683    }
4684
4685    fn computed_ir_fixture() -> (
4686        IntermediateRepresentation,
4687        SemanticId,
4688        SemanticId,
4689        SemanticId,
4690    ) {
4691        let parsed = presolve_parser::parse_file(
4692            "src/ComputedIr.tsx",
4693            r#"
4694@component("x-computed-ir")
4695class ComputedIr extends Component {
4696  count = state(1);
4697  profile = state({ hidden: false });
4698
4699  @computed()
4700  get doubled() { return this.count * 2; }
4701
4702  @computed()
4703  get visible() { return ((this.doubled >= 2 && !this.profile.hidden) ?? false) || true; }
4704}
4705"#,
4706        );
4707        let model = crate::build_application_semantic_model(&parsed);
4708        let component = &model.components[0];
4709        let count = component.id.state_field("count");
4710        let doubled = component.id.computed("doubled");
4711        let visible = component.id.computed("visible");
4712        (lower_components_to_ir(&model), count, doubled, visible)
4713    }
4714
4715    #[test]
4716    fn lowers_planned_computed_evaluations_into_canonical_ir_functions() {
4717        let (ir, count, doubled, visible) = computed_ir_fixture();
4718        let module = &ir.modules[0];
4719        let doubled_function = module
4720            .functions
4721            .iter()
4722            .find(|function| function.id == doubled)
4723            .expect("computed doubled function");
4724        let visible_function = module
4725            .functions
4726            .iter()
4727            .find(|function| function.id == visible)
4728            .expect("computed visible function");
4729
4730        assert_eq!(module.computed_evaluations.len(), 2);
4731        assert_eq!(doubled_function.name, "doubled");
4732        assert!(doubled_function.blocks[0]
4733            .instructions
4734            .iter()
4735            .any(|instruction| {
4736                matches!(
4737                    instruction.kind,
4738                    IrInstructionKind::LoadStorage { ref storage }
4739                        if storage == &IrStorageId::for_semantic_origin(&count)
4740                )
4741            }));
4742        assert!(doubled_function.blocks[0]
4743            .instructions
4744            .iter()
4745            .any(|instruction| {
4746                matches!(
4747                    instruction.kind,
4748                    IrInstructionKind::Binary {
4749                        operation: IrBinaryOperation::Multiply,
4750                        ..
4751                    }
4752                )
4753            }));
4754        assert!(visible_function.blocks[0]
4755            .instructions
4756            .iter()
4757            .any(|instruction| {
4758                matches!(
4759                    instruction.kind,
4760                    IrInstructionKind::LoadComputed { ref computed } if computed == &doubled
4761                )
4762            }));
4763        assert!(visible_function.blocks[0]
4764            .instructions
4765            .iter()
4766            .any(|instruction| {
4767                matches!(instruction.kind, IrInstructionKind::GetMember { .. })
4768            }));
4769        for operation in [
4770            IrBinaryOperation::GreaterThanOrEqual,
4771            IrBinaryOperation::And,
4772            IrBinaryOperation::NullishCoalesce,
4773            IrBinaryOperation::Or,
4774        ] {
4775            assert!(visible_function.blocks[0]
4776                .instructions
4777                .iter()
4778                .any(|instruction| {
4779                    matches!(
4780                        instruction.kind,
4781                        IrInstructionKind::Binary {
4782                            operation: candidate,
4783                            ..
4784                        } if candidate == operation
4785                    )
4786                }));
4787        }
4788        assert!(visible_function.blocks[0]
4789            .instructions
4790            .iter()
4791            .any(|instruction| {
4792                matches!(
4793                    instruction.kind,
4794                    IrInstructionKind::Unary {
4795                        operation: IrUnaryOperation::Not,
4796                        ..
4797                    }
4798                )
4799            }));
4800        assert!(module.computed_evaluations.iter().all(|evaluation| {
4801            module
4802                .functions
4803                .iter()
4804                .find(|function| function.id == evaluation.function)
4805                .is_some_and(|function| function.values.contains_key(&evaluation.result))
4806        }));
4807        assert!(validate_intermediate_representation(&ir).is_empty());
4808    }
4809
4810    #[test]
4811    #[allow(clippy::too_many_lines)]
4812    fn lowers_one_effect_function_with_generic_capability_instructions() {
4813        let parsed = presolve_parser::parse_file(
4814            "src/EffectIr.tsx",
4815            r#"
4816@component("x-effect-ir")
4817class EffectIr extends Component {
4818  title = state("Presolve");
4819  theme = state("light");
4820  count = state(1);
4821
4822  @computed()
4823  get total() { return this.count * 2; }
4824
4825  @action()
4826  refreshTitle() { this.title = "Refreshed"; }
4827
4828  @action()
4829  refreshTheme() { this.theme = "dark"; }
4830
4831  @effect()
4832  syncAndReport() {
4833    document.title = this.title;
4834    console.log("total", this.total);
4835    localStorage.setItem("theme", this.theme);
4836  }
4837
4838  @effect()
4839  logReady() { console.log("ready"); }
4840
4841  @effect()
4842  invalidMutation() { this.title = "invalid"; }
4843
4844  render() { return <p />; }
4845}
4846"#,
4847        );
4848        let model = crate::build_application_semantic_model(&parsed);
4849        let component = &model.components[0];
4850        let sync = component.id.effect("syncAndReport");
4851        let ready = component.id.effect("logReady");
4852        let invalid = component.id.effect("invalidMutation");
4853        let ir = lower_components_to_ir(&model);
4854        let module = &ir.modules[0];
4855        let execution = module
4856            .effect_executions
4857            .iter()
4858            .find(|execution| execution.effect == sync)
4859            .expect("sync effect execution");
4860        let function = module
4861            .functions
4862            .iter()
4863            .find(|function| function.id == sync)
4864            .expect("sync effect function");
4865
4866        assert_eq!(module.effect_executions.len(), 2);
4867        assert_eq!(execution.function, sync);
4868        assert_ne!(execution.function, component.id.method("syncAndReport"));
4869        assert_eq!(
4870            execution.capability_operations,
4871            vec![
4872                CapabilityOperationId("builtin.browser.document.title.assign"),
4873                CapabilityOperationId("builtin.browser.console.log"),
4874                CapabilityOperationId("builtin.browser.local_storage.set_item"),
4875            ]
4876        );
4877        let capability_instructions = function.blocks[0]
4878            .instructions
4879            .iter()
4880            .filter(|instruction| {
4881                matches!(
4882                    instruction.kind,
4883                    IrInstructionKind::CapabilityCall { .. }
4884                        | IrInstructionKind::CapabilityAssign { .. }
4885                )
4886            })
4887            .collect::<Vec<_>>();
4888        assert_eq!(capability_instructions.len(), 3);
4889        assert!(matches!(
4890            capability_instructions[0].kind,
4891            IrInstructionKind::CapabilityAssign { ref operation, .. }
4892                if operation == &CapabilityOperationId("builtin.browser.document.title.assign")
4893        ));
4894        assert!(matches!(
4895            capability_instructions[1].kind,
4896            IrInstructionKind::CapabilityCall { ref operation, ref arguments }
4897                if operation == &CapabilityOperationId("builtin.browser.console.log")
4898                    && arguments.len() == 2
4899        ));
4900        assert!(matches!(
4901            capability_instructions[2].kind,
4902            IrInstructionKind::CapabilityCall { ref operation, ref arguments }
4903                if operation == &CapabilityOperationId("builtin.browser.local_storage.set_item")
4904                    && arguments.len() == 2
4905        ));
4906        assert!(capability_instructions
4907            .iter()
4908            .all(|instruction| instruction.result.is_none()
4909                && instruction.kind.is_observable_side_effect()));
4910        assert!(function.blocks[0].instructions.iter().any(|instruction| {
4911            matches!(
4912                instruction.kind,
4913                IrInstructionKind::LoadStorage { ref storage }
4914                    if storage == &IrStorageId::for_semantic_origin(&component.id.state_field("title"))
4915            )
4916        }));
4917        assert!(function.blocks[0].instructions.iter().any(|instruction| {
4918            matches!(
4919                instruction.kind,
4920                IrInstructionKind::LoadComputed { ref computed }
4921                    if computed == &component.id.computed("total")
4922            )
4923        }));
4924        let ready_function = module
4925            .functions
4926            .iter()
4927            .find(|function| function.id == ready)
4928            .expect("dependency-free effect function");
4929        assert!(ready_function.blocks[0]
4930            .instructions
4931            .iter()
4932            .all(|instruction| {
4933                !matches!(
4934                    instruction.kind,
4935                    IrInstructionKind::LoadStorage { .. } | IrInstructionKind::LoadComputed { .. }
4936                )
4937            }));
4938        assert!(!module
4939            .effect_executions
4940            .iter()
4941            .any(|execution| execution.effect == invalid));
4942        assert_eq!(ir.effect_ir_function(&sync), Some(&sync));
4943        assert!(validate_intermediate_representation(&ir).is_empty());
4944        assert!(validate_effect_ir(&model, &ir).is_empty());
4945    }
4946
4947    #[test]
4948    fn optimizes_effect_operands_without_removing_or_reordering_capabilities() {
4949        let parsed = presolve_parser::parse_file(
4950            "src/OptimizedEffectIr.tsx",
4951            r#"
4952@component("x-optimized-effect-ir")
4953class OptimizedEffectIr extends Component {
4954  title = state("Presolve");
4955
4956  @computed()
4957  get unrelated() { return 4 + 5; }
4958
4959  @effect()
4960  report() {
4961    console.log(1 + 2);
4962    document.title = this.title;
4963    console.log("after");
4964  }
4965
4966  render() { return <p />; }
4967}
4968"#,
4969        );
4970        let model = crate::build_application_semantic_model(&parsed);
4971        let component = &model.components[0];
4972        let effect = component.id.effect("report");
4973        let computed = component.id.computed("unrelated");
4974        let input = lower_components_to_ir(&model);
4975        let original_computed = input.modules[0]
4976            .functions
4977            .iter()
4978            .find(|function| function.id == computed)
4979            .expect("original computed function")
4980            .clone();
4981        let report = optimize_effect_ir(&input);
4982        let output = &report.output;
4983        let optimized_effect = output.modules[0]
4984            .functions
4985            .iter()
4986            .find(|function| function.id == effect)
4987            .expect("optimized effect function");
4988        let optimized_computed = output.modules[0]
4989            .functions
4990            .iter()
4991            .find(|function| function.id == computed)
4992            .expect("preserved computed function");
4993        let operations = optimized_effect.blocks[0]
4994            .instructions
4995            .iter()
4996            .filter_map(|instruction| match instruction.kind {
4997                IrInstructionKind::CapabilityCall { operation, .. }
4998                | IrInstructionKind::CapabilityAssign { operation, .. } => Some(operation),
4999                _ => None,
5000            })
5001            .collect::<Vec<_>>();
5002
5003        assert_eq!(input, lower_components_to_ir(&model));
5004        assert_eq!(optimized_computed, &original_computed);
5005        assert_eq!(
5006            operations,
5007            vec![
5008                CapabilityOperationId("builtin.browser.console.log"),
5009                CapabilityOperationId("builtin.browser.document.title.assign"),
5010                CapabilityOperationId("builtin.browser.console.log"),
5011            ]
5012        );
5013        assert!(optimized_effect.blocks[0]
5014            .instructions
5015            .iter()
5016            .any(|instruction| {
5017                matches!(
5018                    instruction.kind,
5019                    IrInstructionKind::Constant {
5020                        value: IrConstant::Number(ref value)
5021                    } if value == "3"
5022                )
5023            }));
5024        assert!(optimized_effect.blocks[0]
5025            .instructions
5026            .iter()
5027            .all(|instruction| {
5028                !instruction.kind.is_observable_side_effect() || instruction.result.is_none()
5029            }));
5030        assert_eq!(
5031            report
5032                .passes
5033                .iter()
5034                .map(|pass| pass.name)
5035                .collect::<Vec<_>>(),
5036            vec![
5037                "common-subexpression-elimination",
5038                "copy-propagation",
5039                "constant-folding",
5040                "instruction-simplification",
5041                "dead-code-elimination",
5042                "cfg-cleanup",
5043            ]
5044        );
5045        assert!(validate_intermediate_representation(output).is_empty());
5046        assert!(validate_effect_ir(&model, output).is_empty());
5047    }
5048
5049    #[test]
5050    fn lowers_state_backed_provider_once_for_shared_consumers() {
5051        let model = build_application_semantic_model(&presolve_parser::parse_file(
5052            "src/App.tsx",
5053            r#"
5054@component("x-app")
5055class App extends Component {
5056  selected: string = state("dark");
5057  @context()
5058  theme!: string;
5059  @provide(App.theme)
5060  providedTheme: string = this.selected;
5061  @consume(App.theme)
5062  first!: string;
5063  @consume(App.theme)
5064  second!: string;
5065  render() { return <main />; }
5066}
5067"#,
5068        ));
5069        let component = &model.components[0].id;
5070        let provider = ProviderId::for_component(component, "providedTheme");
5071        let first = ConsumerId::for_component(component, "first");
5072        let second = ConsumerId::for_component(component, "second");
5073        let ir = lower_components_to_ir(&model);
5074        let evaluation = ir
5075            .context_source_evaluation(&ContextValueSourceId::Provider(provider))
5076            .expect("planned Provider source evaluation");
5077        let function = ir
5078            .modules
5079            .iter()
5080            .flat_map(|module| &module.functions)
5081            .find(|function| function.id == *evaluation.function.as_semantic_id())
5082            .expect("Provider source function");
5083        assert!(function.blocks[0].instructions.iter().any(|instruction| {
5084            matches!(
5085                instruction.kind,
5086                IrInstructionKind::LoadStorage { ref storage }
5087                    if storage == &IrStorageId::for_semantic_origin(&component.state_field("selected"))
5088            )
5089        }));
5090        assert!(function.blocks[0].instructions.iter().any(|instruction| {
5091            matches!(
5092                instruction.kind,
5093                IrInstructionKind::InitializeContextSlot { ref slot, ref value }
5094                    if slot == &evaluation.slot && value == &evaluation.result
5095            )
5096        }));
5097        let first_binding = ir.context_consumer_binding(&first).expect("first load");
5098        let second_binding = ir.context_consumer_binding(&second).expect("second load");
5099        assert_eq!(first_binding.slot, evaluation.slot);
5100        assert_eq!(second_binding.slot, evaluation.slot);
5101        assert_ne!(first_binding.load.id, second_binding.load.id);
5102        assert!(matches!(
5103            first_binding.load.kind(),
5104            IrInstructionKind::LoadContextSlot { slot } if slot == evaluation.slot
5105        ));
5106        assert!(validate_context_ir(&model, &ir).is_empty());
5107    }
5108
5109    #[test]
5110    fn lowers_default_and_computed_provider_with_distinct_source_slots() {
5111        let model = build_application_semantic_model(&presolve_parser::parse_file(
5112            "src/App.tsx",
5113            r#"
5114@component("x-app")
5115class App extends Component {
5116  selected: string = state("dark");
5117  @computed()
5118  get derivedTheme(): string { return this.selected; }
5119  @context()
5120  theme!: string;
5121  @provide(App.theme)
5122  providedTheme: string = this.derivedTheme;
5123  @consume(App.theme)
5124  theme!: string;
5125  @context()
5126  locale: string = "en";
5127  @consume(App.locale)
5128  locale!: string;
5129  render() { return <main />; }
5130}
5131"#,
5132        ));
5133        let component = &model.components[0].id;
5134        let provider =
5135            ContextValueSourceId::Provider(ProviderId::for_component(component, "providedTheme"));
5136        let locale =
5137            ContextValueSourceId::ContextDefault(ContextId::for_component(component, "locale"));
5138        let ir = lower_components_to_ir(&model);
5139        let provider_evaluation = ir
5140            .context_source_evaluation(&provider)
5141            .expect("Provider IR");
5142        let default_evaluation = ir.context_source_evaluation(&locale).expect("default IR");
5143        assert_ne!(provider_evaluation.function, default_evaluation.function);
5144        assert_ne!(provider_evaluation.slot, default_evaluation.slot);
5145        assert!(!provider_evaluation.prerequisite_computed_batches.is_empty());
5146        let provider_function = ir
5147            .modules
5148            .iter()
5149            .flat_map(|module| &module.functions)
5150            .find(|function| function.id == *provider_evaluation.function.as_semantic_id())
5151            .expect("Provider function");
5152        assert!(provider_function.blocks[0]
5153            .instructions
5154            .iter()
5155            .any(|instruction| {
5156                matches!(instruction.kind, IrInstructionKind::LoadComputed { .. })
5157            }));
5158        let locale_consumer = ConsumerId::for_component(component, "locale");
5159        assert_eq!(
5160            ir.context_consumer_binding(&locale_consumer)
5161                .expect("default Consumer load")
5162                .slot,
5163            default_evaluation.slot
5164        );
5165        assert!(validate_context_ir(&model, &ir).is_empty());
5166    }
5167
5168    #[test]
5169    fn omits_unused_blocked_and_unavailable_context_ir() {
5170        let model = build_application_semantic_model(&presolve_parser::parse_file(
5171            "src/App.tsx",
5172            r#"
5173@component("x-app")
5174class App extends Component {
5175  @context()
5176  unused!: string;
5177  @provide(App.unused)
5178  unusedProvider: string = "unused";
5179  @context()
5180  broken!: number;
5181  @provide(App.broken)
5182  brokenProvider: string = "wrong";
5183  @consume(App.broken)
5184  brokenValue!: number;
5185  @context()
5186  missing!: string;
5187  @consume(App.missing)
5188  missingValue!: string;
5189  render() { return <main />; }
5190}
5191"#,
5192        ));
5193        let component = &model.components[0].id;
5194        let unused =
5195            ContextValueSourceId::Provider(ProviderId::for_component(component, "unusedProvider"));
5196        let broken =
5197            ContextValueSourceId::Provider(ProviderId::for_component(component, "brokenProvider"));
5198        let broken_consumer = ConsumerId::for_component(component, "brokenValue");
5199        assert!(model
5200            .context_evaluation_plan()
5201            .context_source_plan(&unused)
5202            .is_some());
5203        assert!(model
5204            .context_evaluation_plan()
5205            .context_source_plan(&broken)
5206            .is_some());
5207        let ir = lower_components_to_ir(&model);
5208        assert!(ir.context_source_evaluation(&unused).is_none());
5209        assert!(ir.context_source_evaluation(&broken).is_none());
5210        assert!(ir.context_consumer_binding(&broken_consumer).is_none());
5211        assert!(validate_context_ir(&model, &ir).is_empty());
5212    }
5213
5214    #[test]
5215    fn optimizes_context_sources_without_changing_slots_or_unrelated_ir() {
5216        let model = build_application_semantic_model(&presolve_parser::parse_file(
5217            "src/App.tsx",
5218            r#"
5219@component("x-app")
5220class App extends Component {
5221  @computed()
5222  get unrelated(): number { return 4 + 5; }
5223  @context()
5224  count!: number;
5225  @provide(App.count)
5226  providedCount: number = 1 + 2;
5227  @consume(App.count)
5228  first!: number;
5229  @consume(App.count)
5230  second!: number;
5231  render() { return <main />; }
5232}
5233"#,
5234        ));
5235        let component = &model.components[0].id;
5236        let source =
5237            ContextValueSourceId::Provider(ProviderId::for_component(component, "providedCount"));
5238        let unrelated = component.computed("unrelated");
5239        let input = lower_components_to_ir(&model);
5240        let original_unrelated = input.modules[0]
5241            .functions
5242            .iter()
5243            .find(|function| function.id == unrelated)
5244            .expect("unrelated computed function")
5245            .clone();
5246        let original_source = input
5247            .context_source_evaluation(&source)
5248            .expect("planned Context source")
5249            .clone();
5250
5251        let report = optimize_context_ir(&input);
5252        let optimized_source = report
5253            .optimized_module
5254            .modules
5255            .iter()
5256            .flat_map(|module| &module.functions)
5257            .find(|function| function.id == *original_source.function.as_semantic_id())
5258            .expect("optimized Context source function");
5259        let optimized_unrelated = report.optimized_module.modules[0]
5260            .functions
5261            .iter()
5262            .find(|function| function.id == unrelated)
5263            .expect("preserved unrelated computed function");
5264
5265        assert_eq!(input, lower_components_to_ir(&model));
5266        assert_eq!(optimized_unrelated, &original_unrelated);
5267        assert_eq!(
5268            report.source_evaluations,
5269            vec![super::OptimizedIrContextSourceEvaluation::from(
5270                &original_source
5271            )]
5272        );
5273        assert_eq!(
5274            report.optimized_module.context_ir.consumer_bindings,
5275            input.context_ir.consumer_bindings
5276        );
5277        assert_eq!(optimized_source.blocks[0].instructions.len(), 2);
5278        assert!(matches!(
5279            optimized_source.blocks[0].instructions[0].kind,
5280            IrInstructionKind::Constant {
5281                value: IrConstant::Number(ref value)
5282            } if value == "3"
5283        ));
5284        assert!(matches!(
5285            optimized_source.blocks[0].instructions[1].kind,
5286            IrInstructionKind::InitializeContextSlot { ref slot, ref value }
5287                if slot == &original_source.slot && value == &original_source.result
5288        ));
5289        assert_eq!(
5290            report
5291                .pass_metrics
5292                .iter()
5293                .map(|pass| pass.name)
5294                .collect::<Vec<_>>(),
5295            vec![
5296                "common-subexpression-elimination",
5297                "copy-propagation",
5298                "constant-folding",
5299                "instruction-simplification",
5300                "dead-code-elimination",
5301                "cfg-cleanup",
5302            ]
5303        );
5304        assert!(validate_optimized_context_ir(&model, &input, &report).is_empty());
5305    }
5306
5307    #[test]
5308    fn optimizes_computed_ir_immutably_and_preserves_evaluation_results() {
5309        let parsed = presolve_parser::parse_file(
5310            "src/OptimizedComputedIr.tsx",
5311            r#"
5312@component("x-optimized-computed-ir")
5313class OptimizedComputedIr extends Component {
5314  @computed()
5315  get total() { return 1 + 2; }
5316}
5317"#,
5318        );
5319        let model = crate::build_application_semantic_model(&parsed);
5320        let total = model.components[0].id.computed("total");
5321        let ir = lower_components_to_ir(&model);
5322        let original = ir.modules[0]
5323            .functions
5324            .iter()
5325            .find(|function| function.id == total)
5326            .expect("original computed function");
5327        assert_eq!(original.blocks[0].instructions.len(), 3);
5328        assert!(original.blocks[0].instructions.iter().any(|instruction| {
5329            matches!(
5330                instruction.kind,
5331                IrInstructionKind::Binary {
5332                    operation: IrBinaryOperation::Add,
5333                    ..
5334                }
5335            )
5336        }));
5337
5338        let report = optimize_computed_ir(&ir);
5339        let optimized_module = &report.output.modules[0];
5340        let evaluation = optimized_module
5341            .computed_evaluations
5342            .iter()
5343            .find(|evaluation| evaluation.computed == total)
5344            .expect("computed evaluation");
5345        let optimized = optimized_module
5346            .functions
5347            .iter()
5348            .find(|function| function.id == total)
5349            .expect("optimized computed function");
5350
5351        assert_eq!(
5352            report
5353                .passes
5354                .iter()
5355                .map(|pass| pass.name)
5356                .collect::<Vec<_>>(),
5357            vec![
5358                "common-subexpression-elimination",
5359                "copy-propagation",
5360                "constant-folding",
5361                "instruction-simplification",
5362                "dead-code-elimination",
5363                "cfg-cleanup",
5364            ]
5365        );
5366        assert_eq!(optimized.blocks[0].instructions.len(), 1);
5367        assert!(matches!(
5368            optimized.blocks[0].instructions[0].kind,
5369            IrInstructionKind::Constant {
5370                value: IrConstant::Number(ref value)
5371            } if value == "3"
5372        ));
5373        assert_eq!(optimized.values.len(), 1);
5374        assert!(optimized.values.contains_key(&evaluation.result));
5375        assert!(validate_intermediate_representation(&ir).is_empty());
5376        assert!(validate_intermediate_representation(&report.output).is_empty());
5377    }
5378
5379    #[test]
5380    fn validates_result_value_definitions_and_storage_references() {
5381        let parsed = presolve_parser::parse_file(
5382            "src/Counter.tsx",
5383            "@component(\"x-counter\") class Counter extends Component { count = state(0); increment() {} }",
5384        );
5385        let model = crate::build_application_semantic_model(&parsed);
5386        let mut representation = lower_components_to_ir(&model);
5387        assert!(validate_intermediate_representation(&representation).is_empty());
5388
5389        let storage = representation.modules[0].storages[0].id.clone();
5390        let function = &mut representation.modules[0].functions[0];
5391        let result = IrValueId::for_function(&function.id, 0);
5392        function.blocks[0].instructions.push(IrInstruction {
5393            id: IrInstructionId::for_block(&function.entry_block, 0),
5394            provenance: function.provenance.clone(),
5395            result: Some(result),
5396            semantic_origin: None,
5397            kind: IrInstructionKind::LoadStorage { storage },
5398        });
5399
5400        assert!(validate_intermediate_representation(&representation)
5401            .iter()
5402            .any(|diagnostic| diagnostic.code == "PSIR1006"));
5403    }
5404}