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:
+27
-3
@@ -110,6 +110,30 @@
|
|||||||
\ Phase 3: Mixed arithmetic (built on M* and UM/MOD host primitives)
|
\ 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
|
\ SM/REM ( d n -- rem quot ) symmetric (truncated) division
|
||||||
\ for now due to return-stack depth interactions with DABS/DNEGATE.
|
\ Quotient sign: negative if dividend and divisor signs differ.
|
||||||
\ TODO: replace once return-stack nesting issue is resolved.
|
\ 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 ;
|
||||||
|
|||||||
+1
-192
@@ -2171,12 +2171,7 @@ impl ForthVM {
|
|||||||
self.register_m_star()?;
|
self.register_m_star()?;
|
||||||
self.register_um_star()?;
|
self.register_um_star()?;
|
||||||
self.register_um_div_mod()?;
|
self.register_um_div_mod()?;
|
||||||
self.register_fm_div_mod()?;
|
// FM/MOD, SM/REM, */, */MOD: defined in boot.fth
|
||||||
self.register_sm_div_rem()?;
|
|
||||||
|
|
||||||
// */ and */MOD
|
|
||||||
self.register_star_slash()?;
|
|
||||||
self.register_star_slash_mod()?;
|
|
||||||
|
|
||||||
// U. (unsigned dot)
|
// U. (unsigned dot)
|
||||||
self.register_u_dot()?;
|
self.register_u_dot()?;
|
||||||
@@ -4355,192 +4350,6 @@ impl ForthVM {
|
|||||||
Ok(())
|
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.
|
/// U. ( u -- ) unsigned dot.
|
||||||
fn register_u_dot(&mut self) -> anyhow::Result<()> {
|
fn register_u_dot(&mut self) -> anyhow::Result<()> {
|
||||||
let memory = self.memory;
|
let memory = self.memory;
|
||||||
|
|||||||
Reference in New Issue
Block a user