Replace 4 mixed-arithmetic Rust host functions with Forth (Phase 3)

Now that the optimizer TailCall/inline bug is fixed, SM/REM, FM/MOD,
*/, and */MOD can be defined in Forth using M* and UM/MOD as primitives.

SM/REM uses DABS (which calls DNEGATE → D+) inside conditional branches
with return-stack items — exactly the pattern that triggered the bug.

Removed ~200 lines of Rust closures. All 426 tests pass.
This commit is contained in:
2026-04-07 13:39:05 +02:00
parent d3b4382440
commit 00efec2cf2
2 changed files with 28 additions and 195 deletions
+1 -192
View File
@@ -2171,12 +2171,7 @@ impl ForthVM {
self.register_m_star()?;
self.register_um_star()?;
self.register_um_div_mod()?;
self.register_fm_div_mod()?;
self.register_sm_div_rem()?;
// */ and */MOD
self.register_star_slash()?;
self.register_star_slash_mod()?;
// FM/MOD, SM/REM, */, */MOD: defined in boot.fth
// U. (unsigned dot)
self.register_u_dot()?;
@@ -4355,192 +4350,6 @@ impl ForthVM {
Ok(())
}
/// FM/MOD ( d n -- rem quot ) floored division.
fn register_fm_div_mod(&mut self) -> anyhow::Result<()> {
let memory = self.memory;
let dsp = self.dsp;
let func = Func::new(
&mut self.store,
FuncType::new(&self.engine, [], []),
move |mut caller, _params, _results| {
let sp = dsp.get(&mut caller).unwrap_i32() as u32;
let data = memory.data(&caller);
// Pop n (divisor)
let b: [u8; 4] = data[sp as usize..sp as usize + 4].try_into().unwrap();
let divisor = i32::from_le_bytes(b) as i64;
// Pop d (double-cell): high at sp+4, low at sp+8
let b: [u8; 4] = data[(sp + 4) as usize..(sp + 8) as usize]
.try_into()
.unwrap();
let hi = i32::from_le_bytes(b) as i64;
let b: [u8; 4] = data[(sp + 8) as usize..(sp + 12) as usize]
.try_into()
.unwrap();
let lo = u32::from_le_bytes(b) as i64;
let dividend = (hi << 32) | (lo & 0xFFFF_FFFF);
if divisor == 0 {
return Err(wasmtime::Error::msg("division by zero"));
}
// Floored division: quotient is floor(dividend/divisor)
let mut quot = dividend / divisor;
let mut rem = dividend % divisor;
// Adjust for floored semantics: if remainder != 0 and signs differ
if rem != 0 && ((rem ^ divisor) < 0) {
quot -= 1;
rem += divisor;
}
let new_sp = sp + 4;
let data = memory.data_mut(&mut caller);
data[(new_sp + 4) as usize..(new_sp + 8) as usize]
.copy_from_slice(&(rem as i32).to_le_bytes());
data[new_sp as usize..new_sp as usize + 4]
.copy_from_slice(&(quot as i32).to_le_bytes());
dsp.set(&mut caller, Val::I32(new_sp as i32))?;
Ok(())
},
);
self.register_host_primitive("FM/MOD", false, func)?;
Ok(())
}
/// SM/REM ( d n -- rem quot ) symmetric division.
fn register_sm_div_rem(&mut self) -> anyhow::Result<()> {
let memory = self.memory;
let dsp = self.dsp;
let func = Func::new(
&mut self.store,
FuncType::new(&self.engine, [], []),
move |mut caller, _params, _results| {
let sp = dsp.get(&mut caller).unwrap_i32() as u32;
let data = memory.data(&caller);
let b: [u8; 4] = data[sp as usize..sp as usize + 4].try_into().unwrap();
let divisor = i32::from_le_bytes(b) as i64;
let b: [u8; 4] = data[(sp + 4) as usize..(sp + 8) as usize]
.try_into()
.unwrap();
let hi = i32::from_le_bytes(b) as i64;
let b: [u8; 4] = data[(sp + 8) as usize..(sp + 12) as usize]
.try_into()
.unwrap();
let lo = u32::from_le_bytes(b) as i64;
let dividend = (hi << 32) | (lo & 0xFFFF_FFFF);
if divisor == 0 {
return Err(wasmtime::Error::msg("division by zero"));
}
// Symmetric (truncated) division -- this is Rust's default
let quot = dividend / divisor;
let rem = dividend % divisor;
let new_sp = sp + 4;
let data = memory.data_mut(&mut caller);
data[(new_sp + 4) as usize..(new_sp + 8) as usize]
.copy_from_slice(&(rem as i32).to_le_bytes());
data[new_sp as usize..new_sp as usize + 4]
.copy_from_slice(&(quot as i32).to_le_bytes());
dsp.set(&mut caller, Val::I32(new_sp as i32))?;
Ok(())
},
);
self.register_host_primitive("SM/REM", false, func)?;
Ok(())
}
/// */ ( n1 n2 n3 -- n4 ) n1*n2/n3 with intermediate double-precision.
fn register_star_slash(&mut self) -> anyhow::Result<()> {
let memory = self.memory;
let dsp = self.dsp;
let func = Func::new(
&mut self.store,
FuncType::new(&self.engine, [], []),
move |mut caller, _params, _results| {
let sp = dsp.get(&mut caller).unwrap_i32() as u32;
let data = memory.data(&caller);
let b: [u8; 4] = data[sp as usize..sp as usize + 4].try_into().unwrap();
let n3 = i32::from_le_bytes(b) as i64;
let b: [u8; 4] = data[(sp + 4) as usize..(sp + 8) as usize]
.try_into()
.unwrap();
let n2 = i32::from_le_bytes(b) as i64;
let b: [u8; 4] = data[(sp + 8) as usize..(sp + 12) as usize]
.try_into()
.unwrap();
let n1 = i32::from_le_bytes(b) as i64;
if n3 == 0 {
return Err(wasmtime::Error::msg("division by zero"));
}
let result = (n1 * n2) / n3;
// Pop 3, push 1: net sp + 8
let new_sp = sp + 8;
let data = memory.data_mut(&mut caller);
data[new_sp as usize..new_sp as usize + 4]
.copy_from_slice(&(result as i32).to_le_bytes());
dsp.set(&mut caller, Val::I32(new_sp as i32))?;
Ok(())
},
);
self.register_host_primitive("*/", false, func)?;
Ok(())
}
/// */MOD ( n1 n2 n3 -- rem quot ) n1*n2/n3 with intermediate double-precision.
fn register_star_slash_mod(&mut self) -> anyhow::Result<()> {
let memory = self.memory;
let dsp = self.dsp;
let func = Func::new(
&mut self.store,
FuncType::new(&self.engine, [], []),
move |mut caller, _params, _results| {
let sp = dsp.get(&mut caller).unwrap_i32() as u32;
let data = memory.data(&caller);
let b: [u8; 4] = data[sp as usize..sp as usize + 4].try_into().unwrap();
let n3 = i32::from_le_bytes(b) as i64;
let b: [u8; 4] = data[(sp + 4) as usize..(sp + 8) as usize]
.try_into()
.unwrap();
let n2 = i32::from_le_bytes(b) as i64;
let b: [u8; 4] = data[(sp + 8) as usize..(sp + 12) as usize]
.try_into()
.unwrap();
let n1 = i32::from_le_bytes(b) as i64;
if n3 == 0 {
return Err(wasmtime::Error::msg("division by zero"));
}
let product = n1 * n2;
let quot = product / n3;
let rem = product % n3;
// Pop 3, push 2: net sp + 4
let new_sp = sp + 4;
let data = memory.data_mut(&mut caller);
data[(new_sp + 4) as usize..(new_sp + 8) as usize]
.copy_from_slice(&(rem as i32).to_le_bytes());
data[new_sp as usize..new_sp as usize + 4]
.copy_from_slice(&(quot as i32).to_le_bytes());
dsp.set(&mut caller, Val::I32(new_sp as i32))?;
Ok(())
},
);
self.register_host_primitive("*/MOD", false, func)?;
Ok(())
}
/// U. ( u -- ) unsigned dot.
fn register_u_dot(&mut self) -> anyhow::Result<()> {
let memory = self.memory;