Skip to main content

presolve_compiler/
runtime_context_artifact.rs

1use std::collections::BTreeMap;
2
3use serde::Serialize;
4
5use crate::runtime_computed_artifact::{
6    runtime_instruction, RuntimeComputedArtifactInstruction, RuntimeComputedArtifactOperand,
7};
8use crate::{
9    build_context_update_plan, build_runtime_context_registry, ContextEvaluationBatchId,
10    ExecutionBoundary, OptimizedContextIrReport, RuntimeContextConsumerRecord,
11    RuntimeContextEvaluationBatch, RuntimeContextSourceKind, RuntimeContextSourceRecord,
12};
13
14pub const RUNTIME_CONTEXT_ARTIFACT_SCHEMA_VERSION: u32 = 2;
15
16/// Separate schema-v1 artifact for compiler-owned Context runtime metadata.
17#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
18pub struct RuntimeContextArtifact {
19    pub schema_version: u32,
20    pub sources: Vec<SerializedContextSource>,
21    pub consumers: Vec<SerializedContextConsumerBinding>,
22    pub initial_batches: Vec<SerializedContextEvaluationBatch>,
23    pub action_updates: Vec<SerializedContextActionUpdatePlan>,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
27pub struct SerializedContextSource {
28    pub source: String,
29    pub context: String,
30    pub slot: String,
31    pub source_function: String,
32    pub source_kind: SerializedContextSourceKind,
33    pub program: SerializedContextProgram,
34    pub required_state: Vec<String>,
35    pub required_computed: Vec<String>,
36    pub prerequisite_computed_batches: Vec<u32>,
37    pub evaluation_batch: SerializedContextBatchId,
38    pub semantic_type: String,
39    pub execution_boundary: SerializedContextExecutionBoundary,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
43pub struct SerializedContextProgram {
44    pub result: String,
45    pub instructions: Vec<SerializedContextInstruction>,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
49#[serde(untagged)]
50pub enum SerializedContextInstruction {
51    Evaluate(RuntimeComputedArtifactInstruction),
52    InitializeContextSlot {
53        #[serde(rename = "kind")]
54        kind: SerializedContextInstructionKind,
55        slot: String,
56        value: RuntimeComputedArtifactOperand,
57    },
58    LoadContextSlot {
59        #[serde(rename = "kind")]
60        kind: SerializedContextInstructionKind,
61        result: String,
62        slot: String,
63    },
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
67#[serde(rename_all = "snake_case")]
68pub enum SerializedContextInstructionKind {
69    InitializeContextSlot,
70    LoadContextSlot,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
74#[serde(rename_all = "kebab-case")]
75pub enum SerializedContextSourceKind {
76    Provider,
77    ContextDefault,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
81pub struct SerializedContextConsumerBinding {
82    pub consumer: String,
83    pub context: String,
84    pub selected_source: String,
85    pub slot: String,
86    pub load_identity: String,
87    pub semantic_type: String,
88    pub source_batch: SerializedContextBatchId,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
92pub struct SerializedContextEvaluationBatch {
93    pub id: SerializedContextBatchId,
94    pub sources: Vec<String>,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
98pub struct SerializedContextActionUpdatePlan {
99    pub action_batch: String,
100    pub invalidated_sources: Vec<String>,
101    pub prerequisite_computed_batches: Vec<u32>,
102    pub source_evaluation_batches: Vec<SerializedContextBatchId>,
103    pub affected_consumers: Vec<String>,
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
107pub struct SerializedContextBatchId {
108    pub plan: &'static str,
109    pub index: u32,
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
113#[serde(rename_all = "kebab-case")]
114pub enum SerializedContextExecutionBoundary {
115    Client,
116    Server,
117}
118
119/// Emit a deterministic Context artifact from G12 registry and optimized G11
120/// programs. It serializes instructions only; it performs no Context execution.
121#[must_use]
122pub fn build_runtime_context_artifact(
123    model: &crate::ApplicationSemanticModel,
124    optimized: &OptimizedContextIrReport,
125) -> RuntimeContextArtifact {
126    let registry = build_runtime_context_registry(model, optimized);
127    let updates = build_context_update_plan(model, optimized);
128    let programs = context_programs(optimized);
129    RuntimeContextArtifact {
130        schema_version: RUNTIME_CONTEXT_ARTIFACT_SCHEMA_VERSION,
131        sources: registry
132            .sources
133            .iter()
134            .filter_map(|record| {
135                programs
136                    .get(&record.source)
137                    .cloned()
138                    .map(|program| serialized_source(record, program))
139            })
140            .collect(),
141        consumers: registry.consumers.iter().map(serialized_consumer).collect(),
142        initial_batches: registry
143            .initial_batches
144            .iter()
145            .map(serialized_batch)
146            .collect(),
147        action_updates: updates.actions.iter().map(serialized_update).collect(),
148    }
149}
150
151/// Serialize the Context artifact as deterministic, pretty JSON.
152///
153/// # Panics
154///
155/// Panics when compiler-owned Context runtime metadata cannot serialize.
156#[must_use]
157pub fn runtime_context_artifact_json(artifact: &RuntimeContextArtifact) -> String {
158    serde_json::to_string_pretty(artifact).expect("Context runtime artifact should serialize")
159        + "\n"
160}
161
162fn context_programs(
163    optimized: &OptimizedContextIrReport,
164) -> BTreeMap<crate::ContextValueSourceId, SerializedContextProgram> {
165    optimized
166        .source_evaluations
167        .iter()
168        .filter_map(|evaluation| {
169            let function = optimized
170                .optimized_module
171                .modules
172                .iter()
173                .flat_map(|module| &module.functions)
174                .find(|function| function.id == *evaluation.function.as_semantic_id())?;
175            let instructions = function
176                .blocks
177                .iter()
178                .flat_map(|block| &block.instructions)
179                .map(serialized_instruction)
180                .collect::<Option<Vec<_>>>()?;
181            Some((
182                evaluation.source.clone(),
183                SerializedContextProgram {
184                    result: evaluation.result.as_str().to_string(),
185                    instructions,
186                },
187            ))
188        })
189        .collect()
190}
191
192fn serialized_instruction(
193    instruction: &crate::IrInstruction,
194) -> Option<SerializedContextInstruction> {
195    match &instruction.kind {
196        crate::IrInstructionKind::InitializeContextSlot { slot, value } => {
197            Some(SerializedContextInstruction::InitializeContextSlot {
198                kind: SerializedContextInstructionKind::InitializeContextSlot,
199                slot: slot.as_str().to_string(),
200                value: RuntimeComputedArtifactOperand::Value {
201                    value: value.as_str().to_string(),
202                },
203            })
204        }
205        crate::IrInstructionKind::LoadContextSlot { slot } => {
206            Some(SerializedContextInstruction::LoadContextSlot {
207                kind: SerializedContextInstructionKind::LoadContextSlot,
208                result: instruction.result.as_ref()?.as_str().to_string(),
209                slot: slot.as_str().to_string(),
210            })
211        }
212        _ => runtime_instruction(instruction).map(SerializedContextInstruction::Evaluate),
213    }
214}
215
216fn serialized_source(
217    record: &RuntimeContextSourceRecord,
218    program: SerializedContextProgram,
219) -> SerializedContextSource {
220    SerializedContextSource {
221        source: source_id(&record.source),
222        context: record.context.as_str().to_string(),
223        slot: record.slot.as_str().to_string(),
224        source_function: record.function.as_semantic_id().as_str().to_string(),
225        source_kind: source_kind(record.source_kind),
226        program,
227        required_state: semantic_ids(&record.required_state),
228        required_computed: semantic_ids(&record.required_computed),
229        prerequisite_computed_batches: record.prerequisite_computed_batches.clone(),
230        evaluation_batch: batch_id(&record.evaluation_batch),
231        semantic_type: record.semantic_type.to_string(),
232        execution_boundary: boundary(record.boundary),
233    }
234}
235
236fn serialized_consumer(record: &RuntimeContextConsumerRecord) -> SerializedContextConsumerBinding {
237    SerializedContextConsumerBinding {
238        consumer: record.consumer.as_str().to_string(),
239        context: record.context.as_str().to_string(),
240        selected_source: source_id(&record.selected_source),
241        slot: record.slot.as_str().to_string(),
242        load_identity: record.load_identity.as_semantic_id().as_str().to_string(),
243        semantic_type: record.semantic_type.to_string(),
244        source_batch: batch_id(&record.source_batch),
245    }
246}
247
248fn serialized_batch(batch: &RuntimeContextEvaluationBatch) -> SerializedContextEvaluationBatch {
249    SerializedContextEvaluationBatch {
250        id: batch_id(&batch.id),
251        sources: batch.sources.iter().map(source_id).collect(),
252    }
253}
254
255fn serialized_update(update: &crate::ContextActionUpdatePlan) -> SerializedContextActionUpdatePlan {
256    SerializedContextActionUpdatePlan {
257        action_batch: update.action_batch.as_str().to_string(),
258        invalidated_sources: update.invalidated_sources.iter().map(source_id).collect(),
259        prerequisite_computed_batches: update.prerequisite_computed_batches.clone(),
260        source_evaluation_batches: update
261            .source_evaluation_batches
262            .iter()
263            .map(batch_id)
264            .collect(),
265        affected_consumers: update
266            .affected_consumers
267            .iter()
268            .map(|consumer| consumer.as_str().to_string())
269            .collect(),
270    }
271}
272
273fn batch_id(batch: &ContextEvaluationBatchId) -> SerializedContextBatchId {
274    SerializedContextBatchId {
275        plan: "initial",
276        index: batch.index,
277    }
278}
279
280fn semantic_ids(values: &[crate::SemanticId]) -> Vec<String> {
281    values
282        .iter()
283        .map(|value| value.as_str().to_string())
284        .collect()
285}
286
287fn source_id(source: &crate::ContextValueSourceId) -> String {
288    match source {
289        crate::ContextValueSourceId::Provider(provider) => provider.as_str().to_string(),
290        crate::ContextValueSourceId::ContextDefault(context) => {
291            format!("{}/default", context.as_str())
292        }
293    }
294}
295
296const fn source_kind(kind: RuntimeContextSourceKind) -> SerializedContextSourceKind {
297    match kind {
298        RuntimeContextSourceKind::Provider => SerializedContextSourceKind::Provider,
299        RuntimeContextSourceKind::ContextDefault => SerializedContextSourceKind::ContextDefault,
300    }
301}
302
303const fn boundary(boundary: ExecutionBoundary) -> SerializedContextExecutionBoundary {
304    match boundary {
305        ExecutionBoundary::Client => SerializedContextExecutionBoundary::Client,
306        ExecutionBoundary::Server => SerializedContextExecutionBoundary::Server,
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use crate::{
313        build_application_semantic_model, build_runtime_context_artifact, lower_components_to_ir,
314        optimize_context_ir, runtime_context_artifact_json,
315        RUNTIME_CONTEXT_ARTIFACT_SCHEMA_VERSION,
316    };
317
318    #[test]
319    fn emits_deterministic_context_slot_programs_without_runtime_lookup_operations() {
320        let model = build_application_semantic_model(&presolve_parser::parse_file(
321            "src/App.tsx",
322            r#"
323@component("x-app")
324class App extends Component {
325  count = state(1);
326  @context()
327  total!: number;
328  @provide(App.total)
329  providedTotal: number = this.count + 2;
330  @consume(App.total)
331  total!: number;
332  render() { return <main />; }
333}
334"#,
335        ));
336        let optimized = optimize_context_ir(&lower_components_to_ir(&model));
337        let artifact = build_runtime_context_artifact(&model, &optimized);
338        let json = runtime_context_artifact_json(&artifact);
339
340        assert_eq!(
341            artifact.schema_version,
342            RUNTIME_CONTEXT_ARTIFACT_SCHEMA_VERSION
343        );
344        assert_eq!(artifact.sources.len(), 1);
345        assert_eq!(artifact.consumers.len(), 1);
346        assert_eq!(artifact.sources[0].program.instructions.len(), 4);
347        assert!(json.contains("initialize_context_slot"));
348        assert!(json.contains("load-state"));
349        assert!(!json.contains("find_provider"));
350        assert!(!json.contains("resolve_context"));
351        assert!(!json.contains("walk_parent"));
352        assert!(!json.contains("lookup_by_name"));
353        assert_eq!(
354            json,
355            runtime_context_artifact_json(&build_runtime_context_artifact(&model, &optimized))
356        );
357    }
358}