feat(codegen): stack under/overflow guards in compiled words (WS-007)
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.
This commit is contained in:
@@ -93,6 +93,8 @@ fn find_sf64() -> Option<&'static str> {
|
||||
/// Spawn `binary`, write `input` to its stdin, and collect the output.
|
||||
fn run_via_stdin(binary: &str, input: &str) -> Option<std::process::Output> {
|
||||
Command::new(binary)
|
||||
// Perf lanes measure unguarded code (only the wafer binary reads this)
|
||||
.env("WAFER_STACK_GUARDS", "0")
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
|
||||
@@ -105,8 +105,13 @@ fn expected_load_failures(path: &str) -> u32 {
|
||||
// TRAVERSE-WORDLIST / NAME>COMPILE / NAME>INTERPRET blocks leak as
|
||||
// unknown-word errors. Fix the SOURCE/`>IN` interaction with
|
||||
// line-mode input and drop this to 0.
|
||||
//
|
||||
// The 38th: line 368 `R> DROP TRUE` runs interpreted (its enclosing
|
||||
// definition aborted on the missing NAME?), and the bare `R>` used
|
||||
// to underflow the return stack silently; stack guards now report
|
||||
// it as "Return stack underflow (throw -6)".
|
||||
if path.ends_with("/toolstest.fth") {
|
||||
return 37;
|
||||
return 38;
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user