1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
use super::super::*;
impl VirtualMachine {
/// Load a program into the VM
pub fn load_program(&mut self, program: BytecodeProgram) {
// Content-addressed bytecode is the canonical runtime format.
// Do not silently fall back to the flat instruction stream if linking fails.
if let Some(ref ca_program) = program.content_addressed {
let linked = crate::linker::link(ca_program).unwrap_or_else(|e| {
panic!(
"content-addressed linker failed ({} function blobs): {}",
ca_program.function_store.len(),
e
)
});
self.load_linked_program(linked);
return;
}
self.program = program;
if shape_runtime::type_schema::builtin_schemas::resolve_builtin_schema_ids(
&self.program.type_schema_registry,
)
.is_none()
{
// Programs built manually in tests may omit builtin schemas.
// Merge the static stdlib registry (includes builtin fixed schemas)
// without synthesizing any dynamic runtime schemas.
let (stdlib_registry, _) =
shape_runtime::type_schema::TypeSchemaRegistry::with_stdlib_types_and_builtin_ids();
self.program.type_schema_registry.merge(stdlib_registry);
}
self.builtin_schemas =
shape_runtime::type_schema::builtin_schemas::resolve_builtin_schema_ids(
&self.program.type_schema_registry,
)
.expect(
"compiled program is missing builtin schemas (__AnyError, __TraceFrame, ...); \
schema registry must include static builtin schemas",
);
// Reserve schema IDs above the compiled program registry on the
// ambient per-Runtime registry. Since B1.7 the ambient registry
// is always available — scopeless callers share a process-wide
// default — so no legacy-counter fallback is needed.
let max_program_id = self
.program
.type_schema_registry
.max_schema_id()
.unwrap_or(0);
shape_runtime::type_schema::current_registry().ensure_next_id_above(max_program_id);
self.rebuild_function_name_index();
self.populate_content_addressed_metadata();
self.program_entry_ip = 0;
self.module_init_done = false;
self.feedback_vectors
.resize_with(self.program.functions.len(), || None);
self.reset();
// Bytecode verification: ensure trusted opcodes have valid FrameDescriptors.
//
// R8 W7 G.5 (v0.3 divergence-elimination, ADR-006 §2.7.14 SURFACE):
// V2 typed-opcode verifier failures here are warning-only — the
// bytecode interpreter handles missing FrameDescriptor opcodes fine
// (smoke s2's `Vec.map::i64_*` closure runs cleanly through the VM
// even with verifier warnings). The JIT path is the actual
// divergence surface: native code emitted for unverified opcodes
// skipped the runtime string-key check in `as_string_key` and
// returned garbage where the VM cleanly errored (audit
// `docs/cluster-audits/v0.3-r8w6-hashmap-key-kind-audit.md` §4 —
// `set::from_array([1,2,3])` ec=0 with `{"Integer": -1407...}`
// garbage vs VM ec=1 "HashMap key must be a string"). The fatal-
// surface lives in `JITExecutor::execute_with_jit` (Option B per
// audit §5): the JIT compile step refuses unverified bytecode and
// the existing `[jit-fallback]` path routes the program through
// the interpreter, which agrees with the VM error surface. Full V2
// type soundness for every JIT-emitted opcode is v0.4 follow-up.
#[cfg(debug_assertions)]
{
if let Err(errors) = crate::bytecode::verifier::verify_trusted_opcodes(&self.program) {
eprintln!(
"Bytecode verification warning: {} violation(s) found",
errors.len()
);
for e in &errors {
eprintln!(" - {}", e);
}
}
if let Err(errors) =
crate::bytecode::verifier::verify_v2_typed_opcodes(&self.program)
{
eprintln!(
"V2 bytecode verification warning: {} violation(s) found",
errors.len()
);
for e in &errors {
eprintln!(" - {}", e);
}
}
}
#[cfg(not(debug_assertions))]
{
if let Err(errors) = crate::bytecode::verifier::verify_trusted_opcodes(&self.program) {
eprintln!(
"Bytecode verification failed: {} violation(s)",
errors.len()
);
for e in &errors {
eprintln!(" - {}", e);
}
}
if let Err(errors) =
crate::bytecode::verifier::verify_v2_typed_opcodes(&self.program)
{
eprintln!(
"V2 bytecode verification failed: {} violation(s)",
errors.len()
);
for e in &errors {
eprintln!(" - {}", e);
}
}
}
}
/// Load a `LinkedProgram` into the VM, extracting content-addressed metadata
/// directly from the linked function table.
///
/// This converts the `LinkedProgram` into the flat `BytecodeProgram` layout that
/// the executor expects, then populates `function_hashes` and `function_entry_points`
/// from the linked function metadata.
pub fn load_linked_program(&mut self, linked: crate::bytecode::LinkedProgram) {
let entry_function_id = linked
.hash_to_id
.get(&linked.entry)
.copied()
.or_else(|| linked.functions.iter().position(|f| f.name == "__main__"))
.unwrap_or(0);
let entry_ip = linked
.functions
.get(entry_function_id)
.map(|f| f.entry_point)
.unwrap_or(0);
// Extract hash metadata before converting
let hashes: Vec<Option<FunctionHash>> = linked
.functions
.iter()
.map(|lf| {
if lf.blob_hash == FunctionHash::ZERO {
None
} else {
Some(lf.blob_hash)
}
})
.collect();
let entry_points: Vec<usize> = linked.functions.iter().map(|lf| lf.entry_point).collect();
// Convert LinkedProgram functions to BytecodeProgram functions
let functions: Vec<crate::bytecode::Function> = linked
.functions
.iter()
.map(|lf| crate::bytecode::Function {
name: lf.name.clone(),
arity: lf.arity,
param_names: lf.param_names.clone(),
locals_count: lf.locals_count,
entry_point: lf.entry_point,
body_length: lf.body_length,
is_closure: lf.is_closure,
captures_count: lf.captures_count,
is_async: lf.is_async,
ref_params: lf.ref_params.clone(),
ref_mutates: lf.ref_mutates.clone(),
mutable_captures: lf.mutable_captures.clone(),
frame_descriptor: lf.frame_descriptor.clone(),
osr_entry_points: Vec::new(),
mir_data: None,
})
.collect();
let program = BytecodeProgram {
instructions: linked.instructions,
constants: linked.constants,
strings: linked.strings,
functions,
debug_info: linked.debug_info,
data_schema: linked.data_schema,
module_binding_names: linked.module_binding_names,
top_level_locals_count: linked.top_level_locals_count,
top_level_local_storage_hints: linked.top_level_local_storage_hints,
type_schema_registry: linked.type_schema_registry,
module_binding_storage_hints: linked.module_binding_storage_hints,
function_local_storage_hints: linked.function_local_storage_hints,
trait_method_symbols: linked.trait_method_symbols,
foreign_functions: linked.foreign_functions,
native_struct_layouts: linked.native_struct_layouts,
function_blob_hashes: entry_points
.iter()
.enumerate()
.map(|(idx, _)| hashes.get(idx).copied().flatten())
.collect(),
// Closure spec §14.6 (H6.5): propagate the linker's
// per-function `ClosureLayout` side-table so the VM
// `op_make_closure` producer can emit `HeapValue::ClosureRaw`.
closure_function_layouts: linked.closure_function_layouts.clone(),
// E+5.5 Unit C step 2: propagate the typed top-level frame so
// `vm.execute()` synthesises a tagged ValueWord at the host
// boundary per the program's declared return kind.
top_level_frame: linked.top_level_frame,
// ADR-006 §2.7.24 Q25.C: propagate the linker's trait-object
// vtable registry so `op_box_trait_object` can resolve
// `(concrete_type, trait)` → `Arc<VTable>` at runtime.
trait_vtables: linked.trait_vtables.clone(),
..BytecodeProgram::default()
};
// Load the program normally (handles schema resolution, function name index, etc.)
self.load_program(program);
// Override the content-addressed metadata with the linked data
// (load_program calls populate_content_addressed_metadata which won't find
// content_addressed since we didn't set it — override here)
self.function_hashes = hashes;
self.function_hash_raw = self
.function_hashes
.iter()
.map(|opt| opt.map(|fh| fh.0))
.collect();
self.function_id_by_hash.clear();
for (idx, maybe_hash) in self.function_hashes.iter().enumerate() {
if let Some(hash) = maybe_hash {
self.function_id_by_hash.entry(*hash).or_insert(idx as u16);
}
}
self.function_entry_points = entry_points;
self.program_entry_ip = entry_ip;
self.reset();
}
/// Hot-patch a single function in the loaded program with a new blob.
///
/// The new blob's instructions, constants, and strings replace the existing
/// function's bytecode in-place. The function's metadata (arity, param names,
/// locals count, etc.) is also updated. The content hash is recorded so that
/// in-flight frames referencing the old hash remain valid (they execute from
/// their saved IP which is now stale, but callers that resolve by function ID
/// will pick up the new code on the next call).
///
/// Returns `Ok(old_hash)` on success (the previous content hash, if any),
/// or `Err(msg)` if the function ID is out of range.
pub fn patch_function(
&mut self,
fn_id: u16,
new_blob: FunctionBlob,
) -> Result<Option<FunctionHash>, String> {
let idx = fn_id as usize;
if idx >= self.program.functions.len() {
return Err(format!(
"patch_function: fn_id {} out of range (program has {} functions)",
fn_id,
self.program.functions.len()
));
}
// Capture the old hash before overwriting.
let old_hash = self.function_hashes.get(idx).copied().flatten();
let func = &mut self.program.functions[idx];
let old_entry = func.entry_point;
// Compute instruction splice range: from this function's entry point
// to the next function's entry point (or end of instructions).
let next_entry = self
.program
.functions
.get(idx + 1)
.map(|f| f.entry_point)
.unwrap_or(self.program.instructions.len());
let old_len = next_entry - old_entry;
let new_len = new_blob.instructions.len();
// Splice instructions.
self.program.instructions.splice(
old_entry..old_entry + old_len,
new_blob.instructions.iter().cloned(),
);
// If the new function has a different instruction count, shift all
// subsequent function entry points.
if new_len != old_len {
let delta = new_len as isize - old_len as isize;
for subsequent in self.program.functions.iter_mut().skip(idx + 1) {
subsequent.entry_point = (subsequent.entry_point as isize + delta) as usize;
}
// Also update function_entry_points mirror.
for ep in self.function_entry_points.iter_mut().skip(idx + 1) {
*ep = (*ep as isize + delta) as usize;
}
}
// Append new constants and strings to the program pools.
// The blob's Operand indices reference its local pools, so we need to
// remap them to the global pool offsets.
let const_offset = self.program.constants.len();
let string_offset = self.program.strings.len();
self.program
.constants
.extend(new_blob.constants.iter().cloned());
self.program
.strings
.extend(new_blob.strings.iter().cloned());
// Remap operands in the spliced instructions to use global pool offsets.
let instr_slice = &mut self.program.instructions[old_entry..old_entry + new_len];
for instr in instr_slice.iter_mut() {
remap_operand(&mut instr.operand, const_offset, string_offset);
}
// Update function metadata.
let func = &mut self.program.functions[idx];
func.name = new_blob.name;
func.arity = new_blob.arity;
func.param_names = new_blob.param_names;
func.locals_count = new_blob.locals_count;
func.is_closure = new_blob.is_closure;
func.captures_count = new_blob.captures_count;
func.is_async = new_blob.is_async;
func.ref_params = new_blob.ref_params;
func.ref_mutates = new_blob.ref_mutates;
func.mutable_captures = new_blob.mutable_captures;
// Update content hash metadata.
let new_hash = new_blob.content_hash;
if idx < self.function_hashes.len() {
self.function_hashes[idx] = Some(new_hash);
}
if idx < self.function_hash_raw.len() {
self.function_hash_raw[idx] = Some(new_hash.0);
}
self.function_id_by_hash.entry(new_hash).or_insert(fn_id);
// Update function_entry_points for this function.
if idx < self.function_entry_points.len() {
self.function_entry_points[idx] = old_entry;
}
// Rebuild function name index so UFCS dispatch picks up renames.
self.rebuild_function_name_index();
Ok(old_hash)
}
/// Load a content-addressed `Program` with permission checking.
///
/// Links the program, checks that `total_required_permissions` is a subset of
/// `granted`, and loads normally if the check passes. Returns an error listing
/// the missing permissions if the check fails.
pub fn load_program_with_permissions(
&mut self,
program: crate::bytecode::Program,
granted: &shape_abi_v1::PermissionSet,
) -> Result<(), PermissionError> {
let linked =
crate::linker::link(&program).map_err(|e| PermissionError::LinkError(e.to_string()))?;
if !linked.total_required_permissions.is_subset(granted) {
let missing = linked.total_required_permissions.difference(granted);
return Err(PermissionError::InsufficientPermissions {
required: linked.total_required_permissions.clone(),
granted: granted.clone(),
missing,
});
}
self.load_linked_program(linked);
Ok(())
}
/// Load a `LinkedProgram` with permission checking.
///
/// Checks that `total_required_permissions` is a subset of `granted`, then
/// loads normally. Returns an error listing the missing permissions if the
/// check fails.
pub fn load_linked_program_with_permissions(
&mut self,
linked: crate::bytecode::LinkedProgram,
granted: &shape_abi_v1::PermissionSet,
) -> Result<(), PermissionError> {
if !linked.total_required_permissions.is_subset(granted) {
let missing = linked.total_required_permissions.difference(granted);
return Err(PermissionError::InsufficientPermissions {
required: linked.total_required_permissions.clone(),
granted: granted.clone(),
missing,
});
}
self.load_linked_program(linked);
Ok(())
}
/// Populate `function_hashes` and `function_entry_points` from the loaded program.
///
/// If the program was compiled with content-addressed metadata (`content_addressed`
/// is `Some`), we extract blob hashes by matching function names/entry points.
/// Otherwise both vectors remain empty and `CallFrame::blob_hash` will be `None`.
pub(crate) fn populate_content_addressed_metadata(&mut self) {
let func_count = self.program.functions.len();
self.function_entry_points = self
.program
.functions
.iter()
.map(|f| f.entry_point)
.collect();
if self.program.function_blob_hashes.len() == func_count {
self.function_hashes = self.program.function_blob_hashes.clone();
} else if let Some(ref ca_program) = self.program.content_addressed {
// Build a lookup from function name -> blob hash from the Program's function_store
let mut name_to_hash: HashMap<String, FunctionHash> =
HashMap::with_capacity(ca_program.function_store.len());
for (hash, blob) in &ca_program.function_store {
name_to_hash.insert(blob.name.clone(), *hash);
}
self.function_hashes = Vec::with_capacity(func_count);
for func in &self.program.functions {
self.function_hashes
.push(name_to_hash.get(&func.name).copied());
}
} else {
self.function_hashes = vec![None; func_count];
}
// Build the raw byte mirror for ModuleContext.
self.function_hash_raw = self
.function_hashes
.iter()
.map(|opt| opt.map(|fh| fh.0))
.collect();
self.function_id_by_hash.clear();
for (idx, maybe_hash) in self.function_hashes.iter().enumerate() {
if let Some(hash) = maybe_hash {
self.function_id_by_hash.entry(*hash).or_insert(idx as u16);
}
}
}
/// Build the function name → index map for runtime UFCS dispatch.
/// Called after program load or merge to enable type-scoped method resolution
/// (e.g., "DbTable::filter" looked up when calling .filter() on an Object with __type "DbTable").
pub(crate) fn rebuild_function_name_index(&mut self) {
self.function_name_index.clear();
for (i, func) in self.program.functions.iter().enumerate() {
self.function_name_index.insert(func.name.clone(), i as u16);
}
}
/// Reset VM state
/// Get a snapshot of all module binding values.
///
/// Phase-1b-vm Wave-ε E-vm-impl-tail: the legacy `Vec<ValueWord>`
/// return type referenced the deleted runtime carrier (CLAUDE.md
/// "Forbidden Patterns"). The signature is flipped to the kinded
/// carrier `Vec<shape_value::KindedSlot>` per ADR-006 §2.7 / Q7.
/// The §2.7.8 / Q10 parallel-kind track is now live on
/// `module_binding_kinds`, and `module_binding_read_owned_kinded`
/// returns `KindedSlot` shares per binding — the kinded read
/// backbone is in place. The body remains a `todo!()` until the
/// Phase-2c snapshot revival lands the host-API surface (§2.7.4) —
/// at that point the body is one map+collect over
/// `(0..self.module_bindings_len()).map(|i|
/// self.module_binding_read_owned_kinded(i)).collect()`.
pub fn module_bindings_snapshot(&self) -> Vec<shape_value::KindedSlot> {
todo!(
"phase-2c — see ADR-006 §2.7.4: module_bindings_snapshot \
body deferred to the Phase-2c snapshot revival. The §2.7.8 \
parallel-kind track is live, so the body is one map+collect \
over `module_binding_read_owned_kinded` per index."
);
}
/// Reset VM execution state for trampoline use.
/// Clears stack, call frames, error state, and exception handlers but
/// preserves the loaded program, module bindings, module_fn_table,
/// and registered extensions.
pub fn reset_for_trampoline(&mut self) {
// WB2.6 Phase 3: release each live slot's owning share before
// clearing to NONE_BITS. The retain-on-read contract (WB2.1–WB2.5)
// guarantees no caller aliases these slots without its own retain.
// Kind-aware drop walks the parallel `kinds` track in lockstep
// with `stack` (ADR-006 §2.7.7).
for i in 0..self.sp {
super::stack::drop_with_kind(self.stack[i], self.kinds[i]);
self.stack[i] = Self::NONE_BITS;
self.kinds[i] = shape_value::NativeKind::Bool;
}
self.sp = 0;
self.ip = 0;
self.call_stack.clear();
self.loop_stack.clear();
self.timeframe_stack.clear();
self.exception_handlers.clear();
self.instruction_count = 0;
self.last_error_line = None;
self.last_error_file = None;
self.last_uncaught_exception = None;
self.module_init_done = true; // Skip re-init on next call
}
pub fn reset(&mut self) {
self.ip = self.program_entry_ip;
// WB2.6 Phase 3: release each live slot's owning share.
// Kind-aware drop walks the parallel `kinds` track in lockstep
// with `stack` (ADR-006 §2.7.7).
for i in 0..self.sp {
super::stack::drop_with_kind(self.stack[i], self.kinds[i]);
self.stack[i] = Self::NONE_BITS;
self.kinds[i] = shape_value::NativeKind::Bool;
}
// Advance sp past top-level locals so expression evaluation
// doesn't overlap with local variable storage in register windows.
let tl = self.program.top_level_locals_count as usize;
self.sp = tl;
self.call_stack.clear();
self.loop_stack.clear();
self.timeframe_stack.clear();
self.exception_handlers.clear();
self.instruction_count = 0;
self.last_error_line = None;
self.last_error_file = None;
self.last_uncaught_exception = None;
}
/// Reset stack only (for reusing compiled program across iterations)
/// Keeps program, module_bindings, and GC state intact - only clears execution state
pub fn reset_stack(&mut self) {
self.ip = self.program_entry_ip;
// WB2.6 Phase 3: release each live slot's owning share.
// Kind-aware drop walks the parallel `kinds` track in lockstep
// with `stack` (ADR-006 §2.7.7).
for i in 0..self.sp {
super::stack::drop_with_kind(self.stack[i], self.kinds[i]);
self.stack[i] = Self::NONE_BITS;
self.kinds[i] = shape_value::NativeKind::Bool;
}
let tl = self.program.top_level_locals_count as usize;
self.sp = tl;
self.call_stack.clear();
self.loop_stack.clear();
self.timeframe_stack.clear();
self.exception_handlers.clear();
self.last_error_line = None;
self.last_error_file = None;
self.last_uncaught_exception = None;
}
/// Minimal reset for hot loops - only clears essential state
/// Use this when you know the function doesn't create GC objects or use exceptions
#[inline]
pub fn reset_minimal(&mut self) {
self.ip = self.program_entry_ip;
// WB2.6 Phase 3: release each live slot's owning share.
// Kind-aware drop walks the parallel `kinds` track in lockstep
// with `stack` (ADR-006 §2.7.7).
for i in 0..self.sp {
super::stack::drop_with_kind(self.stack[i], self.kinds[i]);
self.stack[i] = Self::NONE_BITS;
self.kinds[i] = shape_value::NativeKind::Bool;
}
let tl = self.program.top_level_locals_count as usize;
self.sp = tl;
self.call_stack.clear();
self.last_error_line = None;
self.last_error_file = None;
self.last_uncaught_exception = None;
}
/// Push a value onto the stack (public, for testing and host integration).
///
/// Phase-1b-vm Wave-ε E-vm-impl-tail: the legacy `ValueWord`
/// parameter type referenced the deleted runtime carrier (CLAUDE.md
/// "Forbidden Patterns"). The signature is flipped to the kinded
/// carrier `shape_value::KindedSlot` per ADR-006 §2.7 / Q7. The
/// kinded direct-write path on the VM is `push_kinded(bits, kind)`;
/// host-integration / test helpers that still build values via the
/// legacy carrier need a Phase-2c host-API rebuild (see ADR-006
/// §2.7.4 — output-adapter cluster). The body is preserved as a
/// `todo!()` so callers fail loudly rather than silently dropping
/// the value.
pub fn push_value(&mut self, _value: shape_value::KindedSlot) {
todo!(
"phase-2c — see ADR-006 §2.7.4: push_value(KindedSlot) is a \
host-boundary helper; the in-VM surface uses \
push_kinded(bits, kind) sourced directly from the producer."
);
}
}