From 00efec2cf22b44c1befda601cdf24455bb43936c Mon Sep 17 00:00:00 2001 From: Oleksandr Kozachuk Date: Tue, 7 Apr 2026 13:39:05 +0200 Subject: [PATCH] Replace 4 mixed-arithmetic Rust host functions with Forth (Phase 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- crates/core/boot.fth | 30 +++++- crates/core/src/outer.rs | 193 +-------------------------------------- 2 files changed, 28 insertions(+), 195 deletions(-) diff --git a/crates/core/boot.fth b/crates/core/boot.fth index d400713..53f5449 100644 --- a/crates/core/boot.fth +++ b/crates/core/boot.fth @@ -110,6 +110,30 @@ \ Phase 3: Mixed arithmetic (built on M* and UM/MOD host primitives) \ --------------------------------------------------------------- -\ Phase 3 words (SM/REM, FM/MOD, */, */MOD) kept as Rust host functions -\ for now due to return-stack depth interactions with DABS/DNEGATE. -\ TODO: replace once return-stack nesting issue is resolved. +\ SM/REM ( d n -- rem quot ) symmetric (truncated) division +\ Quotient sign: negative if dividend and divisor signs differ. +\ Remainder sign: same as dividend. +: SM/REM + OVER >R + 2DUP XOR >R + ABS >R DABS R> + UM/MOD + R> 0< IF NEGATE THEN + SWAP R> 0< IF NEGATE THEN + SWAP ; + +\ FM/MOD ( d n -- rem quot ) floored division +: FM/MOD + DUP >R + SM/REM + OVER 0<> OVER 0< AND IF + 1- SWAP R> + SWAP + ELSE + R> DROP + THEN ; + +\ */ ( n1 n2 n3 -- n4 ) n1*n2/n3 with double intermediate +: */ >R M* R> FM/MOD SWAP DROP ; + +\ */MOD ( n1 n2 n3 -- rem quot ) +: */MOD >R M* R> FM/MOD ; diff --git a/crates/core/src/outer.rs b/crates/core/src/outer.rs index 4dddd3a..6dba4c7 100644 --- a/crates/core/src/outer.rs +++ b/crates/core/src/outer.rs @@ -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;