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