shape_vm/execution.rs
1//! Program compilation and execution logic.
2//!
3//! Contains the VM execution loop, module_binding variable synchronization,
4//! snapshot resume, compilation pipeline, and trait implementations
5//! for `ProgramExecutor` and `ExpressionEvaluator`.
6//!
7//! W12-host-boundary (ADR-006 §2.7.4 / §2.7.5): the program-completion
8//! host boundary now flows the VM's `KindedSlot` completion value
9//! through the kind-threaded `wire_conversion::slot_to_envelope` /
10//! `slot_to_wire` / `slot_extract_content` helpers. The deleted
11//! `nb_to_wire` / `nb_to_envelope` / `nb_extract_content` /
12//! `synthesize_value_word_from_raw` ValueWord-shape host-API surface
13//! does not return; the kinded helpers take `(bits, kind)` directly per
14//! ADR-006 §2.7.5 and the slot's kind is sourced from `KindedSlot::kind`
15//! (compiler-proven via `BytecodeProgram::top_level_frame.return_kind`).
16//!
17//! Snapshot resume / `eval_statements` remain Phase-2c stubs — the
18//! suspend/resume marker rebuild (kinded `Snapshot::Resumed`
19//! constructor + push) and the REPL-binding round-trip
20//! (`save_module_bindings_to_context` / `load_module_bindings_from_context`)
21//! are independent host-boundary workstreams.
22
23use std::sync::Arc;
24
25use crate::bytecode::BytecodeProgram;
26use crate::compiler::BytecodeCompiler;
27use crate::configuration::BytecodeExecutor;
28use crate::executor::{ForeignFunctionHandle, VMConfig, VirtualMachine};
29
30use shape_ast::Program;
31use shape_runtime::context::ExecutionContext;
32use shape_runtime::engine::{ExecutionType, ProgramExecutor, ShapeEngine};
33use shape_runtime::error::Result;
34use shape_runtime::wire_conversion;
35use shape_value::KindedSlot;
36
37impl BytecodeExecutor {
38 /// Compile a program to bytecode without executing it.
39 ///
40 /// This performs the same compilation pipeline as `execute_program`
41 /// (merging core stdlib, extensions, virtual modules) but stops
42 /// before creating a VM or executing. Compilation does not depend on
43 /// the deleted `ValueWord` carrier — it returns `BytecodeProgram`
44 /// directly.
45 pub(crate) fn compile_program_impl(
46 &mut self,
47 engine: &mut ShapeEngine,
48 program: &Program,
49 ) -> Result<BytecodeProgram> {
50 let source_for_compilation = engine.current_source().map(|s| s.to_string());
51
52 // Check bytecode cache before expensive compilation
53 if let (Some(cache), Some(source)) = (&self.bytecode_cache, &source_for_compilation) {
54 if let Some(cached) = cache.get(source) {
55 return Ok(cached);
56 }
57 }
58
59 // Install this engine's runtime-scoped TypeSchemaRegistry as the
60 // ambient handle for the duration of compilation.
61 let _schema_scope = engine.runtime.enter_schema_scope();
62
63 // REPL cross-cell persistence (WS-11): the persisted user type
64 // schemas (struct / enum) carrying their session-stable
65 // `SchemaId`s, and a `schema_id -> name` reverse map for
66 // resolving the type name of a persisted `TypedObject` binding.
67 let persistent_schemas: Vec<shape_runtime::type_schema::TypeSchema> =
68 engine.repl_user_schemas().values().cloned().collect();
69 let schema_id_to_name: std::collections::HashMap<u32, String> = persistent_schemas
70 .iter()
71 .map(|s| (s.id, s.name.clone()))
72 .collect();
73
74 let runtime = engine.get_runtime_mut();
75
76 let known_bindings: Vec<String> = if let Some(ctx) = runtime.persistent_context() {
77 ctx.root_scope_binding_names()
78 } else {
79 Vec::new()
80 };
81
82 // REPL cross-cell persistence (WS-11): derive a compiler-facing
83 // type name for each persisted binding from the value stored in
84 // the context. Without this the next cell's `a + b` (where `a`,
85 // `b` were `let`-bound in earlier cells) falls into the
86 // strict-typing `unknown + unknown` reject path. The kind is read
87 // off the persisted `KindedSlot` — no fabrication, the producer
88 // stamped it (ADR-006 §2.7.5).
89 let known_binding_types: Vec<(String, String)> =
90 if let Some(ctx) = runtime.persistent_context() {
91 known_bindings
92 .iter()
93 .filter_map(|name| {
94 let value = ctx.get_variable(name).ok().flatten()?;
95 let type_name = binding_type_name_for_kind(
96 value.kind(),
97 value.raw(),
98 &schema_id_to_name,
99 )?;
100 Some((name.clone(), type_name))
101 })
102 .collect()
103 } else {
104 Vec::new()
105 };
106
107 let mut root_program = program.clone();
108 crate::module_resolution::annotate_program_native_abi_package_key(
109 &mut root_program,
110 self.root_package_key.as_deref(),
111 );
112
113 let mut loader = self.module_loader.take().unwrap_or_else(
114 shape_runtime::module_loader::ModuleLoader::new,
115 );
116 let (graph, stdlib_names, prelude_imports) =
117 crate::module_resolution::build_graph_and_stdlib_names(
118 &root_program,
119 &mut loader,
120 &self.extensions,
121 )?;
122 self.module_loader = Some(loader);
123
124 let mut compiler = BytecodeCompiler::new();
125 compiler.stdlib_function_names = stdlib_names;
126
127 // REPL cross-cell persistence (WS-11): seed the compiler's
128 // schema registry with user `type` / `enum` schemas from prior
129 // cells so each keeps a stable `SchemaId` for the whole session
130 // — a `TypedObject` persisted across cells carries the id
131 // stamped at construction, and `GetFieldTyped` resolves it
132 // against this cell's program registry. Seeding before
133 // `register_known_bindings` (which can itself touch schemas via
134 // `register_extension_module_schema`) keeps the user ids stable.
135 if !persistent_schemas.is_empty() {
136 compiler.seed_persistent_schemas(&persistent_schemas);
137 }
138
139 compiler.register_known_bindings(&known_bindings);
140 for (name, type_name) in &known_binding_types {
141 compiler.register_known_binding_type(name, type_name);
142 }
143
144 if !self.extensions.is_empty() {
145 compiler.extension_registry = Some(Arc::new(self.extensions.clone()));
146 }
147
148 if let Ok(cwd) = std::env::current_dir() {
149 compiler.set_source_dir(cwd);
150 }
151
152 compiler.native_resolution_context = self.native_resolution_context.clone();
153
154 if let Some(source) = &source_for_compilation {
155 compiler.set_source(source);
156 }
157
158 let bytecode =
159 compiler.compile_with_graph_and_prelude(&root_program, graph, &prelude_imports)?;
160
161 // Store in bytecode cache (best-effort, ignore errors)
162 if let (Some(cache), Some(source)) = (&self.bytecode_cache, &source_for_compilation) {
163 let _ = cache.put(source, &bytecode);
164 }
165
166 Ok(bytecode)
167 }
168
169 /// Compile a program with the same pipeline as execution, but do not run it.
170 pub fn compile_program_for_inspection(
171 &mut self,
172 engine: &mut ShapeEngine,
173 program: &Program,
174 ) -> Result<BytecodeProgram> {
175 self.compile_program_impl(engine, program)
176 }
177
178 /// Resume execution from a snapshot — Phase-2c stub.
179 ///
180 /// The legacy body built a `Snapshot::Resumed` marker via the deleted
181 /// `create_typed_enum_nb` returning a `ValueWord`, pushed it via the
182 /// deleted raw-bits stack push, then ran the suspend/resume loop —
183 /// every step of which depended on `ValueWord` / `EnumValue` /
184 /// `nb_to_wire`. Phase-2c (ADR-006 §2.7.4) rebuilds the marker as a
185 /// kinded `Arc<TypedObjectStorage>` payload + parallel-kind track,
186 /// pushed via `push_kinded(bits, NativeKind::Ptr(HeapKind::TypedObject))`.
187 pub fn resume_snapshot(
188 &self,
189 _engine: &mut ShapeEngine,
190 _vm_snapshot: shape_runtime::snapshot::VmSnapshot,
191 _bytecode: BytecodeProgram,
192 ) -> Result<shape_runtime::engine::ProgramExecutorResult> {
193 Err(shape_runtime::error::ShapeError::RuntimeError {
194 message: "resume_snapshot: snapshot rebuild depends on the deleted \
195 ValueWord carrier and the deleted `create_typed_enum_nb` / \
196 `nb_to_wire` host-API surface — Phase-2c, see ADR-006 §2.7.4."
197 .to_string(),
198 location: None,
199 })
200 }
201
202 /// Recompile source and resume from a snapshot — Phase-2c stub.
203 ///
204 /// Same surface as `resume_snapshot`: the snapshot-to-host marker
205 /// hop depends on the deleted `ValueWord` carrier (ADR-006 §2.7.4).
206 pub fn recompile_and_resume(
207 &mut self,
208 _engine: &mut ShapeEngine,
209 _vm_snapshot: shape_runtime::snapshot::VmSnapshot,
210 _old_bytecode: BytecodeProgram,
211 _program: &Program,
212 ) -> Result<shape_runtime::engine::ProgramExecutorResult> {
213 Err(shape_runtime::error::ShapeError::RuntimeError {
214 message: "recompile_and_resume: snapshot resume depends on the \
215 deleted ValueWord carrier and the kinded suspend/resume \
216 marker rebuild is Phase-2c (ADR-006 §2.7.4)."
217 .to_string(),
218 location: None,
219 })
220 }
221}
222
223impl shape_runtime::engine::ExpressionEvaluator for BytecodeExecutor {
224 fn eval_statements(
225 &self,
226 _stmts: &[shape_ast::Statement],
227 _ctx: &mut ExecutionContext,
228 ) -> Result<KindedSlot> {
229 // Phase-2c surface (ADR-006 §2.7.4): the legacy implementation
230 // round-tripped the result through `vm.execute()` (which returned
231 // `ValueWord`) and persisted module bindings via
232 // `save_module_bindings_to_context` (which called the deleted
233 // `synthesize_value_word_from_raw`). The kinded rebuild returns
234 // `KindedSlot` directly from a `vm.execute_kinded()` shape and
235 // persists bindings via per-slot `(bits, NativeKind)` writes —
236 // both Phase-2c.
237 Err(shape_runtime::error::ShapeError::RuntimeError {
238 message: "eval_statements: depends on `vm.execute() -> ValueWord` \
239 and the deleted `synthesize_value_word_from_raw` \
240 host-boundary path; the kinded `vm.execute_kinded() \
241 -> KindedSlot` rebuild is Phase-2c (ADR-006 §2.7.4)."
242 .to_string(),
243 location: None,
244 })
245 }
246
247 fn eval_expr(&self, expr: &shape_ast::Expr, ctx: &mut ExecutionContext) -> Result<KindedSlot> {
248 let stmt = shape_ast::Statement::Expression(expr.clone(), shape_ast::Span::DUMMY);
249 self.eval_statements(&[stmt], ctx)
250 }
251}
252
253/// Derive the compiler-facing type-name string for a persisted REPL
254/// binding from its runtime `NativeKind` (WS-11).
255///
256/// The bytecode compiler's type tracker recognises canonical type names
257/// (`"int"`, `"number"`, `"bool"`, `"string"`, and registered struct /
258/// enum names) via `register_known_binding_type`. Mapping the persisted
259/// `KindedSlot`'s kind to one of those names lets the next cell's
260/// expressions referencing the binding (`a + b`, `p.x`) compile through
261/// the strict-typing path instead of the `unknown` reject.
262///
263/// The kind was stamped by the original producer at compile time
264/// (ADR-006 §2.7.5) — this reads it, it does not fabricate it. Kinds
265/// without a stable surface type name (the no-op `Bool`/`Null` sentinel
266/// for never-written slots, container kinds the compiler resolves
267/// structurally) return `None`; the binding then carries no type info
268/// and falls back to the polymorphic path, which is correct — it is not
269/// a wrong-result, just a missed specialization.
270fn binding_type_name_for_kind(
271 kind: shape_value::NativeKind,
272 bits: u64,
273 schema_id_to_name: &std::collections::HashMap<u32, String>,
274) -> Option<String> {
275 use shape_value::{HeapKind, NativeKind};
276 match kind {
277 NativeKind::Int64 | NativeKind::UInt64 | NativeKind::IntSize | NativeKind::UIntSize => {
278 Some("int".to_string())
279 }
280 NativeKind::Int8
281 | NativeKind::UInt8
282 | NativeKind::Int16
283 | NativeKind::UInt16
284 | NativeKind::Int32
285 | NativeKind::UInt32 => Some("int".to_string()),
286 NativeKind::Float64 | NativeKind::Float32 => Some("number".to_string()),
287 NativeKind::Bool => Some("bool".to_string()),
288 NativeKind::String | NativeKind::StringV2 => Some("string".to_string()),
289 NativeKind::Ptr(HeapKind::TypedObject) => {
290 // The slot bits are a live `*const TypedObjectStorage`
291 // (ADR-006 §2.4 v2-raw carrier). Read its `schema_id` and
292 // resolve the registered struct name via the persisted
293 // user-schema id map so the next cell's `binding.field`
294 // access compiles through the typed-field path.
295 if bits == 0 {
296 return None;
297 }
298 let ptr = bits as *const shape_value::heap_value::TypedObjectStorage;
299 // SAFETY: a `Ptr(HeapKind::TypedObject)`-kinded slot whose
300 // bits are non-zero points at a live `TypedObjectStorage`
301 // (the context's `KindedSlot` holds an owning share for the
302 // duration of this read). `schema_id` is a POD `u64` field.
303 let schema_id = unsafe { (*ptr).schema_id };
304 schema_id_to_name.get(&(schema_id as u32)).cloned()
305 }
306 // Other heap kinds (arrays, maps, options, results, …) are
307 // resolved structurally by the compiler; no flat type name to
308 // register. Bool/Null sentinel and remaining scalar kinds carry
309 // no useful binding-type info.
310 _ => None,
311 }
312}
313
314/// Collect the names introduced by top-level `let` / `var` / `const`
315/// declarations in a program (REPL cross-cell persistence, WS-11).
316///
317/// A top-level binding can appear either as `Item::VariableDecl` or as
318/// `Item::Statement(Statement::VariableDecl(..))` depending on how the
319/// parser bucketed the line; both shapes are walked. Destructuring
320/// patterns contribute every bound identifier.
321fn collect_top_level_binding_names(program: &Program) -> std::collections::HashSet<String> {
322 use shape_ast::ast::{Item, Statement};
323 let mut names = std::collections::HashSet::new();
324 let mut absorb = |decl: &shape_ast::ast::VariableDecl| {
325 for ident in decl.pattern.get_identifiers() {
326 names.insert(ident);
327 }
328 };
329 for item in &program.items {
330 match item {
331 Item::VariableDecl(decl, _) => absorb(decl),
332 Item::Statement(Statement::VariableDecl(decl, _), _) => absorb(decl),
333 _ => {}
334 }
335 }
336 names
337}
338
339impl BytecodeExecutor {
340 /// REPL load-side binding round-trip (WS-11).
341 ///
342 /// For every VM module-binding slot whose name matches a value
343 /// binding live in the persistent `ExecutionContext`'s root scope,
344 /// copy the context's `KindedSlot` into the slot. This is what makes
345 /// a variable defined in a prior cell resolvable from a later one:
346 /// the compiler reserved the slot via `register_known_bindings`, and
347 /// this fills it with the persisted value before execution.
348 ///
349 /// The copy retains an independent strong-count share — the
350 /// context's variable keeps its own ownership; the VM slot owns the
351 /// clone, and `module_binding_write_kinded` releases whatever
352 /// occupied the slot before. No tag synthesis: `KindedSlot` already
353 /// carries the `NativeKind`, so the slot's bits and kind transfer
354 /// directly per ADR-006 §2.7.8 / Q10.
355 fn load_module_bindings_from_context(
356 vm: &mut VirtualMachine,
357 ctx: &ExecutionContext,
358 ) {
359 let names = vm.program.module_binding_names.clone();
360 for (idx, name) in names.iter().enumerate() {
361 if name.is_empty() {
362 continue;
363 }
364 // Only root-scope context variables are user value bindings;
365 // `get_variable` searches inner-to-outer but the REPL context
366 // has a single root scope between cells.
367 let Ok(Some(value)) = ctx.get_variable(name) else {
368 continue;
369 };
370 // `value` is a fresh clone with its own share (KindedSlot's
371 // Clone retains). Hand that share to the binding slot.
372 let bits = value.raw();
373 let kind = value.kind();
374 std::mem::forget(value);
375 vm.module_binding_write_kinded(idx, bits, kind);
376 }
377 }
378
379 /// REPL save-side binding round-trip (WS-11).
380 ///
381 /// After a cell executes, copy every VM module-binding slot whose
382 /// name is a user value binding (`user_binding_names`) back into the
383 /// persistent `ExecutionContext` so the next cell sees it. The name
384 /// filter excludes module-namespace objects and stdlib/prelude
385 /// function bindings — those are not user values and must not leak
386 /// into the context's variable scope.
387 ///
388 /// `module_binding_read_owned_kinded` bumps the strong-count so the
389 /// VM's own slot (dropped with the VM at end of cell) and the
390 /// context's stored copy each hold an independent share.
391 fn save_module_bindings_to_context(
392 vm: &VirtualMachine,
393 ctx: &mut ExecutionContext,
394 user_binding_names: &std::collections::HashSet<String>,
395 ) {
396 let names = vm.program.module_binding_names.clone();
397 for (idx, name) in names.iter().enumerate() {
398 if name.is_empty() || !user_binding_names.contains(name) {
399 continue;
400 }
401 if idx >= vm.module_bindings_len() {
402 continue;
403 }
404 let value = vm.module_binding_read_owned_kinded(idx);
405 // `set_variable` updates an existing variable or creates a
406 // fresh `var`; either way the persisted slot now holds this
407 // cell's final value for `name`.
408 let _ = ctx.set_variable(name, value);
409 }
410 }
411}
412
413impl ProgramExecutor for BytecodeExecutor {
414 fn execute_program(
415 &mut self,
416 engine: &mut ShapeEngine,
417 program: &Program,
418 ) -> Result<shape_runtime::engine::ProgramExecutorResult> {
419 // Phase 1 — compile (does not depend on the deleted ValueWord).
420 let _schema_scope = engine.runtime.enter_schema_scope();
421
422 // REPL cross-cell persistence (WS-11): re-prepend `fn` / `type` /
423 // `enum` / `trait` / `impl` / type-alias / annotation definitions
424 // from prior cells so the bytecode compiler resolves names
425 // declared in earlier lines. `execute_program` owns the only
426 // cross-cell-stable handle (`ShapeEngine`) and is the single
427 // path every executor caller routes through (real `execute_repl`,
428 // notebook, test helpers), so the injection lives here rather
429 // than in any one entry point.
430 let augmented_program: Program;
431 let compile_target: &Program = if engine.has_repl_definitions() {
432 let priors = engine.repl_definitions();
433 let mut items = Vec::with_capacity(priors.len() + program.items.len());
434 items.extend(priors.iter().cloned());
435 items.extend(program.items.iter().cloned());
436 augmented_program = Program {
437 items,
438 docs: program.docs.clone(),
439 };
440 &augmented_program
441 } else {
442 program
443 };
444
445 let bytecode = self.compile_program_impl(engine, compile_target)?;
446
447 // Build a VM and prime extensions / foreign-function links.
448 // These steps don't reach into the deleted ValueWord carrier
449 // themselves; the host-boundary persistence + completion-value
450 // synthesis is what's deferred to Phase-2c.
451 let mut vm = VirtualMachine::new(VMConfig::default());
452 vm.set_interrupt(self.interrupt.clone());
453 vm.load_program(bytecode);
454 for ext in &self.extensions {
455 vm.register_extension(ext.clone());
456 }
457 // populate_module_objects is itself a Phase-2c stub (see
458 // vm_impl/modules.rs) — calling it is a no-op until the kinded
459 // module-binding cell-storage rebuild lands per ADR-006 §2.7.8 / Q10.
460 vm.populate_module_objects();
461 vm.foreign_fn_handles.clear();
462 if !vm.program.foreign_functions.is_empty() {
463 let entries = vm.program.foreign_functions.clone();
464 let mut handles: Vec<Option<ForeignFunctionHandle>> = Vec::with_capacity(entries.len());
465 let mut native_library_cache: std::collections::HashMap<
466 String,
467 std::sync::Arc<libloading::Library>,
468 > = std::collections::HashMap::new();
469 for (idx, entry) in entries.iter().enumerate() {
470 if let Some(native_spec) = &entry.native_abi {
471 let linked = crate::executor::native_abi::link_native_function(
472 native_spec,
473 &vm.program.native_struct_layouts,
474 &mut native_library_cache,
475 )
476 .map_err(|e| {
477 shape_runtime::error::ShapeError::RuntimeError {
478 message: format!(
479 "Failed to link native function '{}': {}",
480 entry.name, e
481 ),
482 location: None,
483 }
484 })?;
485 vm.program.foreign_functions[idx].dynamic_errors = false;
486 handles.push(Some(ForeignFunctionHandle::Native(std::sync::Arc::new(
487 linked,
488 ))));
489 continue;
490 }
491 handles.push(None);
492 }
493 vm.foreign_fn_handles = handles;
494 }
495
496 // Phase 2 — execute. `vm.execute(ctx)` returns
497 // `Result<KindedSlot, VMError>` (dispatch.rs:25). The slot's
498 // kind is sourced from `BytecodeProgram::top_level_frame.
499 // return_kind` for typed-producer programs and from the
500 // §2.7.7 stack parallel-kind track when the producer pushed a
501 // post-resolution kind directly. No tag-bit decode, no
502 // ValueWord round-trip.
503 // REPL cross-cell persistence (WS-11): the set of names the user
504 // can reference as value bindings across cells — every name
505 // already live in the persistent context's root scope plus every
506 // top-level `let`/`var` declared in this cell. Module-namespace
507 // objects, stdlib functions, and prelude builtins are never in
508 // this set (they are not `VariableDecl`s and the prior cell never
509 // `set_variable`d them), so the round-trip touches user variables
510 // only.
511 let repl_persistence = engine.repl_persistence();
512 let user_binding_names: std::collections::HashSet<String> = if repl_persistence {
513 let mut names = collect_top_level_binding_names(program);
514 if let Some(ctx) = engine.runtime.persistent_context() {
515 names.extend(ctx.root_scope_binding_names());
516 }
517 names
518 } else {
519 std::collections::HashSet::new()
520 };
521
522 let runtime = engine.get_runtime_mut();
523 let mut owned_ctx_fallback;
524 let ctx_borrow: &mut ExecutionContext = match runtime.persistent_context_mut() {
525 Some(ctx) => ctx,
526 None => {
527 // Programs without a persistent ExecutionContext (the
528 // non-REPL `shape run` path) still need a live context
529 // for stdlib I/O dispatch + wire-conversion lookups.
530 // An empty context exposes no host data but satisfies
531 // the borrow.
532 owned_ctx_fallback = ExecutionContext::new_empty();
533 &mut owned_ctx_fallback
534 }
535 };
536
537 // REPL load-side: copy every persisted value binding from the
538 // context into its VM module-binding slot before execution, so a
539 // reference to a variable defined in a prior cell resolves.
540 if repl_persistence {
541 Self::load_module_bindings_from_context(&mut vm, ctx_borrow);
542 }
543
544 let completion: KindedSlot = vm.execute(Some(ctx_borrow)).map_err(|e| {
545 shape_runtime::error::ShapeError::RuntimeError {
546 message: e.to_string(),
547 location: None,
548 }
549 })?;
550
551 // REPL save-side: copy this cell's value bindings back into the
552 // context so the next cell can reference them.
553 if repl_persistence {
554 Self::save_module_bindings_to_context(&vm, ctx_borrow, &user_binding_names);
555 }
556
557 // Phase 3 — host-boundary projection. Pull `(bits, kind)` off
558 // the `KindedSlot` once and feed the kinded
559 // `wire_conversion::slot_*` helpers (ADR-006 §2.7.5). The
560 // KindedSlot owns the strong-count share for the duration of
561 // this scope; the helpers read by-pointer and do not consume
562 // the share.
563 let bits = completion.raw();
564 let kind = completion.kind();
565
566 let envelope = wire_conversion::slot_to_envelope(bits, kind, "", ctx_borrow);
567 let (content_json, content_html, content_terminal) =
568 wire_conversion::slot_extract_content(bits, kind);
569
570 // The `ctx_borrow` reborrow of `engine.runtime` ends at its last
571 // use above (NLL), freeing `engine` for the persistence
572 // bookkeeping below.
573 //
574 // REPL cross-cell persistence (WS-11): the cell executed cleanly,
575 // so fold its definition items into the accumulator for the next
576 // cell. Harvest from the ORIGINAL `program` (not the augmented
577 // one) — prior definitions are already in the accumulator and
578 // re-absorbing them would be redundant (the identity-dedup makes
579 // it harmless either way, but harvesting the cell's own items is
580 // the precise intent).
581 if repl_persistence {
582 // Record each user `type` / `enum` schema under its
583 // first-assigned id. `remember_repl_user_schema` is
584 // first-write-wins, so a type compiled in an earlier cell
585 // keeps that cell's id — exactly the id every already
586 // persisted instance of the type carries. Harvest from the
587 // cell's own definition items; types declared in this cell
588 // for the first time are captured here, and re-injected
589 // prior types resolve to their seeded (already-recorded)
590 // schema so the `or_insert` is a no-op.
591 for type_name in ShapeEngine::repl_user_type_names(program) {
592 if let Some(schema) = vm.program.type_schema_registry.get(&type_name) {
593 engine.remember_repl_user_schema(schema.clone());
594 }
595 }
596 engine.absorb_repl_cell_definitions(program);
597 }
598
599 Ok(shape_runtime::engine::ProgramExecutorResult {
600 wire_value: envelope.value,
601 type_info: Some(envelope.type_info),
602 execution_type: ExecutionType::Script,
603 content_json,
604 content_html,
605 content_terminal,
606 })
607 }
608}
609
610#[cfg(test)]
611mod tests {
612 // The snapshot-resume integration tests (snapshot_resume_keeps_…,
613 // snapshot_resumed_variant_matches_without_resume_flow,
614 // stdlib_json_value_methods_can_use_internal_json_builtins,
615 // snapshot_resume_direct_vm_from_snapshot_with_marker) all asserted
616 // on `WireValue::as_number()` / `as_str()` / `as_bool()` round-trips
617 // through the deleted ValueWord host boundary, plus called the
618 // deleted `vm.create_typed_enum_nb` / `synthesize_value_word_from_raw`
619 // helpers directly. They land in the Phase-2c snapshot rebuild
620 // session along with their host-API counterparts (ADR-006 §2.7.4).
621 //
622 // No tests are kept in this module for the duration of the surface;
623 // the integration coverage lives in
624 // `crates/shape-vm/src/lib_tests_parts/` once the kinded host-API
625 // returns.
626}