Skip to main content

shape_vm/bytecode/
content_addressed.rs

1use super::*;
2use crate::type_tracking::{FrameDescriptor, StorageHint};
3
4#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
5pub struct FunctionHash(pub [u8; 32]);
6
7impl std::fmt::Debug for FunctionHash {
8    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
9        write!(f, "FunctionHash({})", self)
10    }
11}
12
13impl std::fmt::Display for FunctionHash {
14    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15        for byte in &self.0 {
16            write!(f, "{:02x}", byte)?;
17        }
18        Ok(())
19    }
20}
21
22impl FunctionHash {
23    /// The zero hash, used as a sentinel/placeholder.
24    pub const ZERO: Self = Self([0u8; 32]);
25}
26
27/// A self-contained, content-addressed function blob.
28///
29/// Each blob carries its own instructions, constants, and strings (no shared
30/// pools). The `content_hash` is the SHA-256 of the serialized content fields,
31/// making deduplication and caching trivial.
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct FunctionBlob {
34    /// SHA-256 hash of the serialized content (everything below).
35    pub content_hash: FunctionHash,
36
37    // -- metadata --
38    pub name: String,
39    pub arity: u16,
40    pub param_names: Vec<String>,
41    pub locals_count: u16,
42    pub is_closure: bool,
43    pub captures_count: u16,
44    pub is_async: bool,
45    #[serde(default)]
46    pub ref_params: Vec<bool>,
47    #[serde(default)]
48    pub ref_mutates: Vec<bool>,
49    #[serde(default)]
50    pub mutable_captures: Vec<bool>,
51    /// Typed frame layout for this function's locals (propagated from compiler).
52    #[serde(default)]
53    pub frame_descriptor: Option<FrameDescriptor>,
54
55    // -- code --
56    /// This function's bytecode instructions.
57    pub instructions: Vec<Instruction>,
58    /// This function's constant pool.
59    pub constants: Vec<Constant>,
60    /// This function's string pool.
61    pub strings: Vec<String>,
62
63    // -- permissions --
64    /// Permissions required by this function (from capability_tags analysis).
65    #[serde(default = "default_permission_set")]
66    pub required_permissions: PermissionSet,
67
68    // -- dependency graph --
69    /// Content hashes of functions this blob references
70    /// (`Operand::Function(idx)` indexes into this vector).
71    pub dependencies: Vec<FunctionHash>,
72
73    /// Callee names corresponding to each dependency entry.
74    /// Used during compilation to resolve forward references; not serialized.
75    #[serde(skip, default)]
76    pub callee_names: Vec<String>,
77
78    // -- type info --
79    /// Type names this function constructs (schema references).
80    pub type_schemas: Vec<String>,
81
82    // -- foreign function dependencies --
83    /// Content hashes of foreign functions referenced by `CallForeign` opcodes.
84    /// Sorted and deduplicated for deterministic hashing.
85    #[serde(default)]
86    pub foreign_dependencies: Vec<[u8; 32]>,
87
88    // -- debug --
89    /// Source mapping entries local to this blob:
90    /// `(local_instruction_offset, file_id, line)`.
91    pub source_map: Vec<(usize, u32, u32)>,
92}
93
94/// Helper struct for deterministic content hashing.
95/// We serialize exactly the fields that define the function's identity.
96#[derive(Serialize)]
97struct FunctionBlobHashInput<'a> {
98    name: &'a str,
99    arity: u16,
100    param_names: &'a [String],
101    locals_count: u16,
102    is_closure: bool,
103    captures_count: u16,
104    is_async: bool,
105    ref_params: &'a [bool],
106    ref_mutates: &'a [bool],
107    mutable_captures: &'a [bool],
108    instructions: &'a [Instruction],
109    constants: &'a [Constant],
110    strings: &'a [String],
111    dependencies: &'a [FunctionHash],
112    type_schemas: &'a [String],
113    /// Permission names sorted deterministically for stable hashing.
114    required_permission_names: Vec<&'a str>,
115    /// Content hashes of foreign functions referenced by this blob.
116    foreign_dependencies: &'a [[u8; 32]],
117}
118
119impl FunctionBlob {
120    /// Compute the content hash from the blob's fields.
121    /// Call this after populating all fields, then assign the result to `content_hash`.
122    pub fn compute_hash(&self) -> FunctionHash {
123        // Convert PermissionSet to sorted permission names for deterministic hashing.
124        let perm_names: Vec<&str> = self.required_permissions.iter().map(|p| p.name()).collect();
125        let input = FunctionBlobHashInput {
126            name: &self.name,
127            arity: self.arity,
128            param_names: &self.param_names,
129            locals_count: self.locals_count,
130            is_closure: self.is_closure,
131            captures_count: self.captures_count,
132            is_async: self.is_async,
133            ref_params: &self.ref_params,
134            ref_mutates: &self.ref_mutates,
135            mutable_captures: &self.mutable_captures,
136            instructions: &self.instructions,
137            constants: &self.constants,
138            strings: &self.strings,
139            dependencies: &self.dependencies,
140            type_schemas: &self.type_schemas,
141            required_permission_names: perm_names,
142            foreign_dependencies: &self.foreign_dependencies,
143        };
144        // Use bincode-compatible MessagePack for deterministic serialization.
145        // rmp_serde::encode::to_vec uses the struct-as-array format which is
146        // order-preserving and deterministic for the types we use here.
147        let bytes = rmp_serde::encode::to_vec(&input)
148            .expect("FunctionBlob content serialization should not fail");
149        let digest = Sha256::digest(&bytes);
150        let mut hash = [0u8; 32];
151        hash.copy_from_slice(&digest);
152        FunctionHash(hash)
153    }
154
155    /// Build a blob with all fields set, then compute and assign its content hash.
156    pub fn finalize(&mut self) {
157        self.content_hash = self.compute_hash();
158    }
159}
160
161/// A content-addressed program: a set of `FunctionBlob`s plus program-level metadata.
162///
163/// This is the **storage / cache** representation. Before execution the linker
164/// flattens it into a `LinkedProgram`.
165#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct Program {
167    /// Hash of the entry-point function.
168    pub entry: FunctionHash,
169
170    /// All function blobs keyed by content hash.
171    pub function_store: HashMap<FunctionHash, FunctionBlob>,
172
173    /// Number of locals used by top-level code.
174    pub top_level_locals_count: u16,
175
176    /// Storage hints for top-level locals.
177    #[serde(default)]
178    pub top_level_local_storage_hints: Vec<StorageHint>,
179
180    /// Module-binding variable names (index -> name).
181    pub module_binding_names: Vec<String>,
182
183    /// Storage hints for module bindings.
184    #[serde(default)]
185    pub module_binding_storage_hints: Vec<StorageHint>,
186
187    /// Per-function local storage hints.
188    #[serde(default)]
189    pub function_local_storage_hints: Vec<Vec<StorageHint>>,
190
191    /// Typed frame layout for top-level locals.
192    #[serde(default)]
193    pub top_level_frame: Option<FrameDescriptor>,
194
195    /// Per-slot fully-resolved `ConcreteType` for top-level locals.
196    ///
197    /// ADR-006 §2.7.5 conduit (content-addressed mirror of
198    /// `BytecodeProgram.top_level_local_concrete_types`). Survives the
199    /// `Program` → `link()` → `LinkedProgram` → `BytecodeProgram`
200    /// round-trip so JIT compilation of in-memory-compiled programs can
201    /// use the typed-array / TypedObject fast paths. Not serialised —
202    /// `ConcreteType` carries opaque registry IDs that aren't a stable
203    /// wire shape; cached-program loads fall through to the legacy
204    /// NaN-boxed path.
205    #[serde(skip, default)]
206    pub top_level_local_concrete_types: Vec<shape_value::v2::ConcreteType>,
207
208    /// Per-user-function per-MIR-slot `ConcreteType` side-table.
209    ///
210    /// ADR-006 §2.7.5 conduit (W12-jit-aggregate-non-array, 2026-05-12):
211    /// content-addressed mirror of
212    /// `BytecodeProgram.function_local_concrete_types`. Survives the
213    /// `Program` → `link()` → `LinkedProgram` → `BytecodeProgram` round-
214    /// trip so JIT compilation of in-memory-compiled programs can use
215    /// the TypedObject Aggregate short-circuit inside user-function
216    /// bodies (Smoke 1.5 `divide`, Smoke 2 `first_positive`, 28 stdlib
217    /// helpers). Not serialised — same rationale as
218    /// `top_level_local_concrete_types`.
219    #[serde(skip, default)]
220    pub function_local_concrete_types: Vec<Vec<shape_value::v2::ConcreteType>>,
221
222    /// Per-user-function declared `ConcreteType` for the return value.
223    ///
224    /// ADR-006 §2.7.5 conduit (W12-jit-call-return-kind close, 2026-05-12):
225    /// content-addressed mirror of
226    /// `BytecodeProgram.function_return_concrete_types`. Survives the
227    /// `Program` → `link()` → `LinkedProgram` → `BytecodeProgram`
228    /// round-trip so the conduit can stamp Call-terminator destination
229    /// slots from the callee's declared return type. Not serialised —
230    /// same rationale as the sibling `*_concrete_types` side-tables.
231    #[serde(skip, default)]
232    pub function_return_concrete_types: Vec<shape_value::v2::ConcreteType>,
233
234    /// ADR-006 §2.7.5 conduit (V3-S6b-jit-method-monomorph-conduit
235    /// close, 2026-05-15): content-addressed mirror of
236    /// `BytecodeProgram.monomorphized_method_call_sites`. Survives the
237    /// `Program` → `link()` → `LinkedProgram` → `BytecodeProgram`
238    /// round-trip so the conduit producer can lift
239    /// `function_return_concrete_types[specialized_idx]` into the
240    /// destination slot's ConcreteType at `MirConstant::Method` Call-
241    /// terminator sites. Not serialised — opaque per-program FunctionId
242    /// indices aren't a stable wire shape.
243    #[serde(skip, default)]
244    pub monomorphized_method_call_sites:
245        HashMap<(shape_ast::ast::span::Span, Option<usize>), usize>,
246
247    /// ADR-006 §2.7.5 conduit (cluster-2-cw-IB-class-b close, 2026-05-16):
248    /// content-addressed mirror of
249    /// `BytecodeProgram.value_call_return_concrete_types`. Survives the
250    /// `Program` → `link()` → `LinkedProgram` → `BytecodeProgram`
251    /// round-trip so the conduit producer can stamp value-call
252    /// `TerminatorKind::Call` destination slots from the closure-bound
253    /// callee's inferred return `ConcreteType`. Not serialised —
254    /// `ConcreteType` carries opaque registry IDs that aren't a stable
255    /// wire shape.
256    #[serde(skip, default)]
257    pub value_call_return_concrete_types:
258        HashMap<
259            (shape_ast::ast::span::Span, Option<usize>),
260            shape_value::v2::ConcreteType,
261        >,
262
263    /// ADR-006 §2.7.5 conduit (W10 jit-call-method-user-trait-fix close,
264    /// 2026-05-17): content-addressed mirror of
265    /// `BytecodeProgram.operator_trait_dispatch_sites`. Survives the
266    /// `Program` → `link()` → `LinkedProgram` → `BytecodeProgram`
267    /// round-trip so the JIT consumer can re-emit user-type binary/unary
268    /// trait-dispatch as method-call IR. Not serialised — Spans carry
269    /// source-position offsets that aren't a stable wire shape.
270    #[serde(skip, default)]
271    pub operator_trait_dispatch_sites:
272        HashMap<shape_ast::ast::span::Span, (String, u16)>,
273
274    /// DataFrame schema for column name resolution.
275    pub data_schema: Option<DataFrameSchema>,
276
277    /// Type schema registry for TypedObject field resolution.
278    #[serde(default)]
279    pub type_schema_registry: shape_runtime::type_schema::TypeSchemaRegistry,
280
281    /// Trait method dispatch registry.
282    pub trait_method_symbols: HashMap<String, String>,
283
284    /// Foreign function metadata table.
285    #[serde(default)]
286    pub foreign_functions: Vec<ForeignFunctionEntry>,
287
288    /// Native `type C` layout metadata table.
289    #[serde(default)]
290    pub native_struct_layouts: Vec<NativeStructLayoutEntry>,
291
292    /// Debug information (source files, variable names).
293    pub debug_info: DebugInfo,
294
295    /// Closure spec §14.6 (H6.5): per-function-name `ClosureLayout`
296    /// side-table threaded through the content-addressed `Program` so it
297    /// survives the `link()` → `LinkedProgram` → `BytecodeProgram` round-
298    /// trip. Keyed by function name because the content-addressed store
299    /// reorders blobs topologically; the linker remaps to post-link
300    /// function-id positions in `LinkedProgram.closure_function_layouts`.
301    /// Not serialised — programs loaded from disk fall back to the
302    /// legacy `HeapValue::Closure` variant.
303    #[serde(skip, default)]
304    pub closure_function_layouts_by_name: std::collections::HashMap<
305        String,
306        std::sync::Arc<shape_value::v2::closure_layout::ClosureLayout>,
307    >,
308
309    /// ADR-006 §2.7.24 Q25.C trait-object vtable registry. Keyed by
310    /// `"Trait::ConcreteType"` strings (matching the existing
311    /// `trait_method_symbols` key prefix). Built at impl-block
312    /// compilation; consumed at `op_box_trait_object` runtime to build
313    /// `Arc<TraitObjectStorage>`. Not serialised because `Arc<VTable>`
314    /// is not a stable wire shape; in cached-program-load mode the
315    /// vtables are rebuilt at link time from `trait_method_symbols`.
316    #[serde(skip, default)]
317    pub trait_vtables: std::collections::HashMap<
318        String,
319        std::sync::Arc<shape_value::value::VTable>,
320    >,
321
322    /// R8 W8 Cluster A surface-and-stop flag (2026-05-25). Mirror of
323    /// `BytecodeProgram::has_imported_const_inline` for the content-
324    /// addressed Program shape; propagated through the linker so the JIT
325    /// executor can deopt the whole program to the bytecode interpreter
326    /// when imported `pub const` identifiers were inlined-at-use by
327    /// `compile_expr_identifier`. v0.4 root-cause fix per close-summary
328    /// §5.16. NOT serialised — compile-time state.
329    #[serde(skip, default)]
330    pub has_imported_const_inline: bool,
331
332    /// R8 W9 B1 W17-marshal-return surface-and-stop flag (2026-05-25).
333    /// Mirror of `BytecodeProgram::has_w17_marshal_residual` for the
334    /// content-addressed Program shape; propagated through the linker so
335    /// the JIT executor can deopt the whole program to the bytecode
336    /// interpreter when the program contains direct calls to imported
337    /// stdlib functions (which route through `Ptr(HeapKind::ModuleFn)`
338    /// callees that the JIT's `jit_call_value` silently returns
339    /// TAG_NULL for). v0.4 root-cause fix per close-summary §5.16.
340    /// NOT serialised — compile-time state.
341    #[serde(skip, default)]
342    pub has_w17_marshal_residual: bool,
343}
344
345/// A linked function ready for execution in a flat instruction array.
346///
347/// Mirrors `Function` but adds `blob_hash` so the runtime can trace back
348/// to the original content-addressed blob.
349#[derive(Debug, Clone, Serialize, Deserialize)]
350pub struct LinkedFunction {
351    /// Content hash of the `FunctionBlob` this was linked from.
352    pub blob_hash: FunctionHash,
353
354    /// Offset into the flat `LinkedProgram::instructions` array.
355    pub entry_point: usize,
356    /// Number of instructions in this function's body.
357    pub body_length: usize,
358
359    // -- metadata (same as Function) --
360    pub name: String,
361    pub arity: u16,
362    pub param_names: Vec<String>,
363    pub locals_count: u16,
364    pub is_closure: bool,
365    pub captures_count: u16,
366    pub is_async: bool,
367    #[serde(default)]
368    pub ref_params: Vec<bool>,
369    #[serde(default)]
370    pub ref_mutates: Vec<bool>,
371    #[serde(default)]
372    pub mutable_captures: Vec<bool>,
373    /// Typed frame layout for this function's locals.
374    #[serde(default)]
375    pub frame_descriptor: Option<FrameDescriptor>,
376}
377
378/// A linked, execution-ready program with flat instruction/constant/string arrays.
379///
380/// This mirrors today's `BytecodeProgram` layout so the executor can run it
381/// with minimal changes. Produced by the linker from a `Program`.
382#[derive(Debug, Clone, Default, Serialize, Deserialize)]
383pub struct LinkedProgram {
384    /// Hash of the entry-point function for execution.
385    #[serde(default)]
386    pub entry: FunctionHash,
387
388    /// Flat instruction array (all functions concatenated).
389    pub instructions: Vec<Instruction>,
390
391    /// Merged constant pool.
392    pub constants: Vec<Constant>,
393
394    /// Merged string pool.
395    pub strings: Vec<String>,
396
397    /// Linked function table (replaces `Vec<Function>`).
398    pub functions: Vec<LinkedFunction>,
399
400    /// Reverse lookup: content hash -> function index in `functions`.
401    pub hash_to_id: HashMap<FunctionHash, usize>,
402
403    /// Debug information.
404    pub debug_info: DebugInfo,
405
406    /// DataFrame schema for column name resolution.
407    pub data_schema: Option<DataFrameSchema>,
408
409    /// Module-binding variable names.
410    pub module_binding_names: Vec<String>,
411
412    /// Number of locals used by top-level code.
413    pub top_level_locals_count: u16,
414
415    /// Storage hints for top-level locals.
416    #[serde(default)]
417    pub top_level_local_storage_hints: Vec<StorageHint>,
418
419    /// Type schema registry for TypedObject field resolution.
420    #[serde(default)]
421    pub type_schema_registry: shape_runtime::type_schema::TypeSchemaRegistry,
422
423    /// Storage hints for module bindings.
424    #[serde(default)]
425    pub module_binding_storage_hints: Vec<StorageHint>,
426
427    /// Per-function local storage hints.
428    #[serde(default)]
429    pub function_local_storage_hints: Vec<Vec<StorageHint>>,
430
431    /// Typed frame layout for top-level locals.
432    #[serde(default)]
433    pub top_level_frame: Option<FrameDescriptor>,
434
435    /// Per-slot fully-resolved `ConcreteType` for top-level locals.
436    ///
437    /// ADR-006 §2.7.5 conduit (top-level concrete-types side-table):
438    /// propagated from `BytecodeProgram.top_level_local_concrete_types`
439    /// through the linker so JIT compilation of linked programs can use
440    /// the typed-array / TypedObject fast paths. Not serialised — the
441    /// embedded `StructLayoutId` / `EnumLayoutId` are compile-time-local
442    /// registry indices; cached-program loads fall through to the legacy
443    /// NaN-boxed path.
444    #[serde(skip, default)]
445    pub top_level_local_concrete_types: Vec<shape_value::v2::ConcreteType>,
446
447    /// Per-user-function per-MIR-slot `ConcreteType` side-table.
448    ///
449    /// ADR-006 §2.7.5 conduit (W12-jit-aggregate-non-array, 2026-05-12):
450    /// LinkedProgram mirror of `Program.function_local_concrete_types`
451    /// — propagated through the linker so JIT compilation of linked
452    /// programs can use the TypedObject Aggregate short-circuit inside
453    /// user-function bodies. Not serialised — same rationale as
454    /// `top_level_local_concrete_types`.
455    #[serde(skip, default)]
456    pub function_local_concrete_types: Vec<Vec<shape_value::v2::ConcreteType>>,
457
458    /// Per-user-function declared `ConcreteType` for the return value.
459    ///
460    /// ADR-006 §2.7.5 conduit (W12-jit-call-return-kind, 2026-05-12):
461    /// LinkedProgram mirror of
462    /// `Program.function_return_concrete_types` — propagated through
463    /// the linker so the conduit can stamp Call-terminator destination
464    /// slots from the callee's declared return type. Not serialised —
465    /// same rationale.
466    #[serde(skip, default)]
467    pub function_return_concrete_types: Vec<shape_value::v2::ConcreteType>,
468
469    /// ADR-006 §2.7.5 conduit (V3-S6b-jit-method-monomorph-conduit
470    /// close, 2026-05-15): LinkedProgram mirror of
471    /// `Program.monomorphized_method_call_sites` — propagated through
472    /// the linker so the conduit producer can lift
473    /// `function_return_concrete_types[specialized_idx]` into the
474    /// destination slot's ConcreteType at `MirConstant::Method` Call-
475    /// terminator sites. Not serialised — same rationale.
476    #[serde(skip, default)]
477    pub monomorphized_method_call_sites:
478        HashMap<(shape_ast::ast::span::Span, Option<usize>), usize>,
479
480    /// ADR-006 §2.7.5 conduit (cluster-2-cw-IB-class-b close, 2026-05-16):
481    /// LinkedProgram mirror of `Program.value_call_return_concrete_types`
482    /// — propagated through the linker so the conduit producer can stamp
483    /// value-call `TerminatorKind::Call` destination slots from the
484    /// closure-bound callee's inferred return `ConcreteType`. Not
485    /// serialised — same rationale.
486    #[serde(skip, default)]
487    pub value_call_return_concrete_types:
488        HashMap<
489            (shape_ast::ast::span::Span, Option<usize>),
490            shape_value::v2::ConcreteType,
491        >,
492
493    /// ADR-006 §2.7.5 conduit (W10 jit-call-method-user-trait-fix close,
494    /// 2026-05-17): LinkedProgram mirror of
495    /// `Program.operator_trait_dispatch_sites` — propagated through the
496    /// linker so the JIT consumer can re-emit user-type binary/unary
497    /// trait-dispatch as method-call IR. Not serialised — same rationale.
498    #[serde(skip, default)]
499    pub operator_trait_dispatch_sites:
500        HashMap<shape_ast::ast::span::Span, (String, u16)>,
501
502    /// Trait method dispatch registry.
503    pub trait_method_symbols: HashMap<String, String>,
504
505    /// Foreign function metadata table.
506    #[serde(default)]
507    pub foreign_functions: Vec<ForeignFunctionEntry>,
508
509    /// Native `type C` layout metadata table.
510    #[serde(default)]
511    pub native_struct_layouts: Vec<NativeStructLayoutEntry>,
512
513    /// Transitive union of all required permissions across all blobs.
514    /// Computed by the linker during `link()`.
515    #[serde(default = "default_permission_set")]
516    pub total_required_permissions: PermissionSet,
517
518    /// Closure spec §14.6 (H6.5): per-function `ClosureLayout` propagated
519    /// from `BytecodeProgram.closure_function_layouts` so the linked
520    /// program carries enough metadata for `op_make_closure`'s raw
521    /// producer path. `None` entries indicate the function is not a
522    /// closure body or the layout wasn't computed. Not serialised —
523    /// programs loaded from disk fall back to the legacy `HeapValue::
524    /// Closure` variant in the VM producer.
525    #[serde(skip, default)]
526    pub closure_function_layouts:
527        Vec<Option<std::sync::Arc<shape_value::v2::closure_layout::ClosureLayout>>>,
528
529    /// ADR-006 §2.7.24 Q25.C trait-object vtable registry. Keyed by
530    /// `"Trait::ConcreteType"`. Threaded through from the
531    /// content-addressed Program at link time so the VM `op_box_trait_object`
532    /// handler can look up the vtable to build `Arc<TraitObjectStorage>`.
533    /// Not serialised (Arc<VTable> is not a stable wire shape).
534    #[serde(skip, default)]
535    pub trait_vtables: std::collections::HashMap<
536        String,
537        std::sync::Arc<shape_value::value::VTable>,
538    >,
539
540    /// R8 W8 Cluster A surface-and-stop flag (2026-05-25).
541    ///
542    /// Mirrors `BytecodeProgram::has_imported_const_inline`; propagated
543    /// through the linker so the JIT executor (which receives the
544    /// post-link `BytecodeProgram`) can refuse to JIT-compile the program
545    /// when imported `pub const` identifiers were inlined-at-use as
546    /// `PushConst(<value>)` by `compile_expr_identifier`. Triggers W12
547    /// `[jit-fallback]` deopt to bytecode interpreter; root-cause fix in
548    /// JIT identifier-eval lowering is v0.4 per
549    /// `docs/v0.3-close-summary.md` §5.16. NOT serialised — compile-time
550    /// state.
551    #[serde(skip, default)]
552    pub has_imported_const_inline: bool,
553
554    /// R8 W9 B1 W17-marshal-return surface-and-stop flag (2026-05-25).
555    /// Mirror of `Program::has_w17_marshal_residual` propagated through
556    /// the linker so the JIT executor can refuse to JIT-compile programs
557    /// containing direct stdlib-imported function calls. Triggers W12
558    /// `[jit-fallback]` deopt to bytecode interpreter; root-cause fix in
559    /// JIT ModuleFn dispatch (`jit_call_value` arm + `dispatch_module_fn_call`
560    /// `todo!()`) is v0.4 per `docs/v0.3-close-summary.md` §5.16. NOT
561    /// serialised — compile-time state.
562    #[serde(skip, default)]
563    pub has_w17_marshal_residual: bool,
564}