cda296aab5
Compiled code could silently move dsp/rsp/fsp out of their stack regions (e.g. DROP on an empty stack), corrupting later pushes with no diagnostic -- the addresses stay inside valid linear memory, so nothing could trap. Host-side checks cannot catch it. - Guards are emitted at the sp-adjustment choke points (dsp_inc/ dsp_dec, fsp_inc/fsp_dec, rpush/rpop/rpeek, peek, TwoDup/TwoDrop, promoted prologue/epilogue -- DROP never loads its value, so guarding pop() alone is not enough). On fault: write the code to SYSVAR_FAULT_CODE, call _STACK_FAULT_, which THROWs it -- so guards are CATCHable and print standard messages (-3/-4/-5/-6/ -44/-45). - The batch/consolidated compile path (all boot primitives) and the export path are wired too; a thread-local carries the fault index into the shared emission helpers. - Config: codegen.stack_guards, default ON. `wafer build` output defaults OFF (production artifact); WAFER_STACK_GUARDS=0|1 overrides either. Perf comparison lanes run unguarded. - Measured overhead in release loops: within noise (never-taken branches). - toolstest.fth baseline 37 -> 38: line 368's bare interpreted `R>` used to underflow silently and count as passing; the guard now correctly reports -6.
170 lines
5.7 KiB
Rust
170 lines
5.7 KiB
Rust
//! Consolidation recompiler: merge all JIT-compiled words into a single WASM module.
|
|
//!
|
|
//! After interactive development, `CONSOLIDATE` recompiles everything:
|
|
//! - All `call_indirect` replaced with direct `call`
|
|
//! - Single WASM module output for maximum performance
|
|
//!
|
|
//! The implementation lives across two places:
|
|
//! - `codegen::compile_consolidated_module()` generates the multi-function WASM module
|
|
//! - `outer::ForthVM::consolidate()` orchestrates collection, compilation, and table update
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::collections::HashMap;
|
|
|
|
use crate::codegen::compile_consolidated_module;
|
|
use crate::dictionary::WordId;
|
|
use crate::ir::IrOp;
|
|
|
|
#[test]
|
|
fn consolidated_module_validates_empty() {
|
|
// Empty word list should produce nothing (but we guard against this at call site)
|
|
let words = vec![];
|
|
let map = HashMap::new();
|
|
let result = compile_consolidated_module(&words, &map, 16, None);
|
|
// Empty is valid -- should produce a valid module with no functions
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn consolidated_module_validates_single_word() {
|
|
let words = vec![(WordId(1), vec![IrOp::PushI32(42)])];
|
|
let mut map = HashMap::new();
|
|
map.insert(WordId(1), 1u32); // function index 1 (after emit import)
|
|
let result = compile_consolidated_module(&words, &map, 16, None);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn consolidated_module_validates_multiple_words() {
|
|
let words = vec![
|
|
(WordId(1), vec![IrOp::PushI32(1)]),
|
|
(WordId(2), vec![IrOp::PushI32(2), IrOp::Add]),
|
|
(
|
|
WordId(3),
|
|
vec![IrOp::Call(WordId(1)), IrOp::Call(WordId(2))],
|
|
),
|
|
];
|
|
let mut map = HashMap::new();
|
|
map.insert(WordId(1), 1u32);
|
|
map.insert(WordId(2), 2u32);
|
|
map.insert(WordId(3), 3u32);
|
|
let result = compile_consolidated_module(&words, &map, 16, None);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn consolidated_module_validates_external_call() {
|
|
// Word 3 calls WordId(99) which is NOT in the module -- should use call_indirect
|
|
let words = vec![(WordId(3), vec![IrOp::Call(WordId(99))])];
|
|
let mut map = HashMap::new();
|
|
map.insert(WordId(3), 1u32);
|
|
let result = compile_consolidated_module(&words, &map, 256, None);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn consolidated_module_validates_tail_call() {
|
|
let words = vec![
|
|
(WordId(1), vec![IrOp::PushI32(1)]),
|
|
(WordId(2), vec![IrOp::TailCall(WordId(1))]),
|
|
];
|
|
let mut map = HashMap::new();
|
|
map.insert(WordId(1), 1u32);
|
|
map.insert(WordId(2), 2u32);
|
|
let result = compile_consolidated_module(&words, &map, 16, None);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn consolidated_module_validates_control_flow_with_calls() {
|
|
// IF body contains a call to a consolidated word
|
|
let words = vec![
|
|
(WordId(1), vec![IrOp::PushI32(1)]),
|
|
(
|
|
WordId(2),
|
|
vec![
|
|
IrOp::PushI32(1),
|
|
IrOp::If {
|
|
then_body: vec![IrOp::Call(WordId(1))],
|
|
else_body: Some(vec![IrOp::PushI32(0)]),
|
|
},
|
|
],
|
|
),
|
|
];
|
|
let mut map = HashMap::new();
|
|
map.insert(WordId(1), 1u32);
|
|
map.insert(WordId(2), 2u32);
|
|
let result = compile_consolidated_module(&words, &map, 16, None);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn consolidated_module_validates_loop_with_calls() {
|
|
// DO LOOP body contains a call to a consolidated word
|
|
let words = vec![
|
|
(WordId(1), vec![IrOp::PushI32(1), IrOp::Add]),
|
|
(
|
|
WordId(2),
|
|
vec![
|
|
IrOp::PushI32(0),
|
|
IrOp::PushI32(3),
|
|
IrOp::PushI32(0),
|
|
IrOp::DoLoop {
|
|
body: vec![IrOp::Call(WordId(1))],
|
|
is_plus_loop: false,
|
|
},
|
|
],
|
|
),
|
|
];
|
|
let mut map = HashMap::new();
|
|
map.insert(WordId(1), 1u32);
|
|
map.insert(WordId(2), 2u32);
|
|
let result = compile_consolidated_module(&words, &map, 16, None);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn consolidated_module_validates_begin_until_with_calls() {
|
|
let words = vec![
|
|
(WordId(1), vec![IrOp::PushI32(1), IrOp::Sub]),
|
|
(
|
|
WordId(2),
|
|
vec![
|
|
IrOp::PushI32(5),
|
|
IrOp::BeginUntil {
|
|
body: vec![IrOp::Call(WordId(1)), IrOp::Dup, IrOp::ZeroEq],
|
|
},
|
|
],
|
|
),
|
|
];
|
|
let mut map = HashMap::new();
|
|
map.insert(WordId(1), 1u32);
|
|
map.insert(WordId(2), 2u32);
|
|
let result = compile_consolidated_module(&words, &map, 16, None);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn consolidated_module_validates_begin_while_repeat_with_calls() {
|
|
let words = vec![
|
|
(WordId(1), vec![IrOp::PushI32(1), IrOp::Sub]),
|
|
(
|
|
WordId(2),
|
|
vec![
|
|
IrOp::PushI32(3),
|
|
IrOp::BeginWhileRepeat {
|
|
test: vec![IrOp::Dup],
|
|
body: vec![IrOp::Call(WordId(1))],
|
|
},
|
|
],
|
|
),
|
|
];
|
|
let mut map = HashMap::new();
|
|
map.insert(WordId(1), 1u32);
|
|
map.insert(WordId(2), 2u32);
|
|
let result = compile_consolidated_module(&words, &map, 16, None);
|
|
assert!(result.is_ok());
|
|
}
|
|
}
|