Fix LEAVE+LOOP hang, DEPTH off-by-one, division flavor, EVALUATE, WORD, ACCEPT

Six fixes for compliance test regressions introduced in Phases 7-8:

- LEAVE + +LOOP with step=0 caused infinite loop: the XOR termination
  check yields 0 when index=limit and step=0. Added SYSVAR_LEAVE_FLAG
  mechanism — LEAVE sets flag, +LOOP checks it, all loops clear on exit.

- DEPTH was off-by-one: `5440 SP@ -` pushed the literal before SP@
  read the stack pointer, making SP@ see one extra cell. Reordered to
  `SP@ 5440 SWAP -` so SP@ reads dsp before any literal push.

- */ and */MOD used FM/MOD (floored) but WAFER's / uses WASM i32.div_s
  (symmetric). Changed to SM/REM for consistency.

- EVALUATE didn't sync input buffer to WASM memory, breaking SOURCE
  and >IN manipulation inside evaluated strings. Added input-only sync
  (without touching STATE/BASE) and >IN readback after each token.

- WORD didn't skip leading spaces when delimiter != space, causing
  GN' and GS3 tests to read whitespace instead of content.

- Added ACCEPT stub returning 0 for non-interactive mode.

- Added bounds check in refresh_user_here to reject corrupted
  SYSVAR_HERE values beyond WASM memory size.

Core and Facility compliance suites now pass. Other suites have
pre-existing regressions from Phases 1-8 still under investigation.
This commit is contained in:
2026-04-07 20:30:16 +02:00
parent b7256e3130
commit 4bfe6976ee
4 changed files with 142 additions and 20 deletions
+32 -6
View File
@@ -19,7 +19,7 @@ use wasm_encoder::{
use crate::dictionary::WordId;
use crate::error::{WaferError, WaferResult};
use crate::ir::IrOp;
use crate::memory::CELL_SIZE;
use crate::memory::{CELL_SIZE, SYSVAR_LEAVE_FLAG};
// ---------------------------------------------------------------------------
// Import indices (order matters: imports numbered sequentially by kind)
@@ -908,12 +908,22 @@ fn emit_do_loop(f: &mut Function, body: &[IrOp], is_plus_loop: bool, ctx: &EmitC
if is_plus_loop {
// +LOOP: Forth 2012 termination check.
// Exit when (old_index - limit) XOR (new_index - limit) is negative.
// SCRATCH_BASE = old_index (from rpop)
// SCRATCH_BASE+2 = step (from data stack)
// Exit when (old_index - limit) XOR (new_index - limit) is negative,
// or when the LEAVE flag is set (LEAVE sets index=limit, but +LOOP with
// step=0 would loop forever without this flag check).
f.instruction(&Instruction::LocalSet(SCRATCH_BASE));
pop_to(f, SCRATCH_BASE + 2); // step from data stack
// Check leave flag first — if set, clear it and exit immediately
f.instruction(&Instruction::I32Const(SYSVAR_LEAVE_FLAG as i32))
.instruction(&Instruction::I32Load(MEM4))
.instruction(&Instruction::If(BlockType::Empty))
.instruction(&Instruction::I32Const(SYSVAR_LEAVE_FLAG as i32))
.instruction(&Instruction::I32Const(0))
.instruction(&Instruction::I32Store(MEM4))
.instruction(&Instruction::Br(2)) // exit: If(0) → Loop(1) → Block(2)
.instruction(&Instruction::End);
// Peek limit from return stack
rpeek(f);
f.instruction(&Instruction::LocalSet(SCRATCH_BASE + 1));
@@ -973,11 +983,14 @@ fn emit_do_loop(f: &mut Function, body: &[IrOp], is_plus_loop: bool, ctx: &EmitC
.instruction(&Instruction::End); // end block
}
// Clean up: pop index and limit from return stack
// Clean up: pop index and limit from return stack, clear leave flag
rpop(f);
f.instruction(&Instruction::Drop);
rpop(f);
f.instruction(&Instruction::Drop);
f.instruction(&Instruction::I32Const(SYSVAR_LEAVE_FLAG as i32))
.instruction(&Instruction::I32Const(0))
.instruction(&Instruction::I32Store(MEM4));
}
// ---------------------------------------------------------------------------
@@ -2020,6 +2033,16 @@ fn emit_consolidated_do_loop(
f.instruction(&Instruction::LocalSet(SCRATCH_BASE));
pop_to(f, SCRATCH_BASE + 2); // step from data stack
// Check leave flag — if set, clear it and exit immediately
f.instruction(&Instruction::I32Const(SYSVAR_LEAVE_FLAG as i32))
.instruction(&Instruction::I32Load(MEM4))
.instruction(&Instruction::If(BlockType::Empty))
.instruction(&Instruction::I32Const(SYSVAR_LEAVE_FLAG as i32))
.instruction(&Instruction::I32Const(0))
.instruction(&Instruction::I32Store(MEM4))
.instruction(&Instruction::Br(2)) // exit: If(0) → Loop(1) → Block(2)
.instruction(&Instruction::End);
rpeek(f);
f.instruction(&Instruction::LocalSet(SCRATCH_BASE + 1));
@@ -2067,11 +2090,14 @@ fn emit_consolidated_do_loop(
.instruction(&Instruction::End);
}
// Clean up: pop index and limit from return stack
// Clean up: pop index and limit from return stack, clear leave flag
rpop(f);
f.instruction(&Instruction::Drop);
rpop(f);
f.instruction(&Instruction::Drop);
f.instruction(&Instruction::I32Const(SYSVAR_LEAVE_FLAG as i32))
.instruction(&Instruction::I32Const(0))
.instruction(&Instruction::I32Store(MEM4));
}
/// Optional extras for exportable modules (data section, entry point, metadata).