Replace 13 Rust host functions with Forth bootstrap (Phase 1)

Create boot.fth loaded at startup after IR primitives are compiled.
Forth-compiled WASM with direct calls outperforms host function dispatch
(no call_indirect overhead, Cranelift can inline across word boundaries).

Words moved to Forth: 2OVER, 2ROT, WITHIN, 2@, 2!, FILL, CMOVE, CMOVE>,
MOVE, ERASE, BLANK, /STRING, -TRAILING.

Removed 547 lines of Rust closures, replaced by 48 lines of Forth.
All 425 tests pass.
This commit is contained in:
2026-04-04 13:47:47 +02:00
parent af42820163
commit b5d06b8e32
2 changed files with 76 additions and 547 deletions
+61
View File
@@ -0,0 +1,61 @@
\ WAFER Bootstrap -- Forth definitions replacing Rust host functions.
\ Loaded at startup after IR primitives are compiled.
\ Compiled WASM with direct calls outperforms host function dispatch.
\ ---------------------------------------------------------------
\ Phase 1: Pure stack and memory operations
\ ---------------------------------------------------------------
\ 2OVER ( x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2 )
: 2OVER 3 PICK 3 PICK ;
\ 2ROT ( x1 x2 x3 x4 x5 x6 -- x3 x4 x5 x6 x1 x2 )
: 2ROT 2>R 2SWAP 2R> 2SWAP ;
\ WITHIN ( n lo hi -- flag ) true if lo <= n < hi (unsigned)
: WITHIN OVER - >R - R> U< ;
\ 2@ ( addr -- x1 x2 ) fetch double-cell, low addr = deeper stack
: 2@ DUP CELL+ @ SWAP @ ;
\ 2! ( x1 x2 addr -- ) store double-cell
: 2! SWAP OVER ! CELL+ ! ;
\ 2R@ stays as host function (needs direct return-stack access)
\ FILL ( addr u char -- ) fill u bytes with char
: FILL ROT ROT 0 ?DO 2DUP I + C! LOOP 2DROP ;
\ CMOVE ( src dst u -- ) forward byte copy
: CMOVE 0 ?DO OVER I + C@ OVER I + C! LOOP 2DROP ;
\ CMOVE> ( src dst u -- ) backward byte copy (for overlap)
: CMOVE>
DUP 0= IF DROP 2DROP EXIT THEN
1- >R
BEGIN R@ 0< INVERT WHILE
OVER R@ + C@ OVER R@ + C!
R> 1- >R
REPEAT
R> DROP 2DROP ;
\ MOVE ( src dst u -- ) smart copy (handles overlap)
: MOVE
DUP 0= IF DROP 2DROP EXIT THEN
>R 2DUP U< IF R> CMOVE> ELSE R> CMOVE THEN ;
\ ERASE ( addr u -- ) zero fill
: ERASE 0 FILL ;
\ BLANK ( addr u -- ) space fill
: BLANK BL FILL ;
\ /STRING ( addr u n -- addr+n u-n )
: /STRING ROT OVER + ROT ROT - ;
\ -TRAILING ( addr u -- addr u' ) remove trailing spaces
: -TRAILING
BEGIN DUP 0> WHILE
2DUP + 1- C@ BL <> IF EXIT THEN
1-
REPEAT ;
+15 -547
View File
@@ -2119,8 +2119,7 @@ impl ForthVM {
self.register_primitive("CHAR+", false, vec![IrOp::PushI32(1), IrOp::Add])?; self.register_primitive("CHAR+", false, vec![IrOp::PushI32(1), IrOp::Add])?;
self.register_align()?; self.register_align()?;
self.register_aligned()?; self.register_aligned()?;
self.register_move()?; // MOVE, FILL: defined in boot.fth
self.register_fill()?;
// -- Priority 4: Stack/arithmetic -- // -- Priority 4: Stack/arithmetic --
self.register_primitive("2DUP", false, vec![IrOp::Over, IrOp::Over])?; self.register_primitive("2DUP", false, vec![IrOp::Over, IrOp::Over])?;
@@ -2130,12 +2129,12 @@ impl ForthVM {
false, false,
vec![IrOp::Rot, IrOp::ToR, IrOp::Rot, IrOp::FromR], vec![IrOp::Rot, IrOp::ToR, IrOp::Rot, IrOp::FromR],
)?; )?;
self.register_2over()?; // 2OVER: defined in boot.fth
self.register_qdup()?; self.register_qdup()?;
self.register_pick()?; self.register_pick()?;
self.register_min()?; self.register_min()?;
self.register_max()?; self.register_max()?;
self.register_within()?; // WITHIN: defined in boot.fth
// -- Priority 5: Comparison -- // -- Priority 5: Comparison --
self.register_primitive("0<>", false, vec![IrOp::ZeroEq, IrOp::ZeroEq])?; self.register_primitive("0<>", false, vec![IrOp::ZeroEq, IrOp::ZeroEq])?;
@@ -2162,8 +2161,7 @@ impl ForthVM {
// -- Priority 7: New core words -- // -- Priority 7: New core words --
self.register_count()?; self.register_count()?;
self.register_s_to_d()?; self.register_s_to_d()?;
self.register_cmove()?; // CMOVE, CMOVE>: defined in boot.fth
self.register_cmove_up()?;
self.register_find()?; self.register_find()?;
self.register_to_in()?; self.register_to_in()?;
self.register_state_var()?; self.register_state_var()?;
@@ -2202,9 +2200,7 @@ impl ForthVM {
self.register_evaluate_word()?; self.register_evaluate_word()?;
self.register_word_word()?; self.register_word_word()?;
// 2@ and 2! // 2@, 2!: defined in boot.fth
self.register_two_fetch()?;
self.register_two_store()?;
// Pictured numeric output // Pictured numeric output
self.register_pictured_numeric()?; self.register_pictured_numeric()?;
@@ -2238,8 +2234,7 @@ impl ForthVM {
vec![IrOp::PushI32(crate::memory::PAD_BASE as i32)], vec![IrOp::PushI32(crate::memory::PAD_BASE as i32)],
)?; )?;
// ERASE ( addr u -- ) fill memory with zeros // ERASE: defined in boot.fth
self.register_erase()?;
// .R and U.R // .R and U.R
self.register_dot_r()?; self.register_dot_r()?;
@@ -2298,15 +2293,13 @@ impl ForthVM {
self.register_m_star_slash()?; self.register_m_star_slash()?;
self.register_d_dot()?; self.register_d_dot()?;
self.register_d_dot_r()?; self.register_d_dot_r()?;
self.register_2rot()?; // 2ROT: defined in boot.fth
self.register_du_lt()?; self.register_du_lt()?;
// -- String word set -- // -- String word set --
self.register_compare()?; self.register_compare()?;
self.register_search()?; self.register_search()?;
self.register_slash_string()?; // /STRING, BLANK, -TRAILING: defined in boot.fth
self.register_blank()?;
self.register_minus_trailing()?;
// -- Floating-Point word set -- // -- Floating-Point word set --
self.register_float_words()?; self.register_float_words()?;
@@ -2315,6 +2308,13 @@ impl ForthVM {
self.batch_mode = false; self.batch_mode = false;
self.batch_compile_deferred()?; self.batch_compile_deferred()?;
// Load Forth bootstrap definitions (replaces many host functions).
// Evaluate line-by-line so `\` comments work correctly.
let boot = include_str!("../boot.fth");
for line in boot.lines() {
self.evaluate(line)?;
}
Ok(()) Ok(())
} }
@@ -3191,151 +3191,10 @@ impl ForthVM {
Ok(()) Ok(())
} }
/// MOVE -- ( src dst n -- ) memory move.
fn register_move(&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 mem_len = data.len();
// Pop n
let b: [u8; 4] = data[sp as usize..sp as usize + 4].try_into().unwrap();
let n_raw = i32::from_le_bytes(b);
// Pop dst
let b: [u8; 4] = data[(sp + 4) as usize..(sp + 8) as usize]
.try_into()
.unwrap();
let dst_raw = i32::from_le_bytes(b);
// Pop src
let b: [u8; 4] = data[(sp + 8) as usize..(sp + 12) as usize]
.try_into()
.unwrap();
let src_raw = i32::from_le_bytes(b);
dsp.set(&mut caller, Val::I32((sp + 12) as i32))?;
// If n <= 0, nothing to do
if n_raw <= 0 {
return Ok(());
}
let n = n_raw as usize;
let src = src_raw as u32 as usize;
let dst = dst_raw as u32 as usize;
// Bounds check
if src.saturating_add(n) > mem_len || dst.saturating_add(n) > mem_len {
return Err(wasmtime::Error::msg("MOVE: address out of range"));
}
// Perform copy (handle overlapping regions)
let data = memory.data_mut(&mut caller);
if src < dst && src + n > dst {
// Overlapping, copy backwards
for i in (0..n).rev() {
data[dst + i] = data[src + i];
}
} else {
for i in 0..n {
data[dst + i] = data[src + i];
}
}
Ok(())
},
);
self.register_host_primitive("MOVE", false, func)?;
Ok(())
}
/// FILL -- ( addr n char -- ) fill memory.
fn register_fill(&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 ch = i32::from_le_bytes(b) as u8;
let b: [u8; 4] = data[(sp + 4) as usize..(sp + 8) as usize]
.try_into()
.unwrap();
let n_raw = i32::from_le_bytes(b);
let b: [u8; 4] = data[(sp + 8) as usize..(sp + 12) as usize]
.try_into()
.unwrap();
let addr_raw = i32::from_le_bytes(b);
dsp.set(&mut caller, Val::I32((sp + 12) as i32))?;
if n_raw <= 0 {
return Ok(());
}
let n = n_raw as usize;
let addr = addr_raw as u32 as usize;
let mem_len = memory.data(&caller).len();
if addr.saturating_add(n) > mem_len {
return Err(wasmtime::Error::msg("FILL: address out of range"));
}
let data = memory.data_mut(&mut caller);
for i in 0..n {
data[addr + i] = ch;
}
Ok(())
},
);
self.register_host_primitive("FILL", false, func)?;
Ok(())
}
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Priority 4: Stack/arithmetic host functions // Priority 4: Stack/arithmetic host functions
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
/// 2OVER -- ( a b c d -- a b c d a b ) copy second pair over top pair.
fn register_2over(&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);
// Stack (top first): d at sp, c at sp+4, b at sp+8, a at sp+12
// We want to copy a and b on top
let b: [u8; 4] = data[(sp + 8) as usize..(sp + 12) as usize]
.try_into()
.unwrap();
let val_b = i32::from_le_bytes(b);
let b: [u8; 4] = data[(sp + 12) as usize..(sp + 16) as usize]
.try_into()
.unwrap();
let val_a = i32::from_le_bytes(b);
// Push a then b (a goes deeper, b on top)
let mem_len = memory.data(&caller).len() as u32;
if sp < 8 || sp > mem_len {
return Err(wasmtime::Error::msg("data stack overflow in 2OVER"));
}
let new_sp = sp - 8;
let data = memory.data_mut(&mut caller);
// Write a at new_sp+4 (deeper), b at new_sp (top)
data[(new_sp + 4) as usize..(new_sp + 8) as usize]
.copy_from_slice(&val_a.to_le_bytes());
data[new_sp as usize..(new_sp + 4) as usize].copy_from_slice(&val_b.to_le_bytes());
dsp.set(&mut caller, Val::I32(new_sp as i32))?;
Ok(())
},
);
self.register_host_primitive("2OVER", false, func)?;
Ok(())
}
/// ?DUP -- ( x -- 0 | x x ) duplicate if non-zero. /// ?DUP -- ( x -- 0 | x x ) duplicate if non-zero.
fn register_qdup(&mut self) -> anyhow::Result<()> { fn register_qdup(&mut self) -> anyhow::Result<()> {
self.register_primitive( self.register_primitive(
@@ -3422,44 +3281,6 @@ impl ForthVM {
Ok(()) Ok(())
} }
/// WITHIN -- ( n lo hi -- flag )
fn register_within(&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 hi = i32::from_le_bytes(b);
let b: [u8; 4] = data[(sp + 4) as usize..(sp + 8) as usize]
.try_into()
.unwrap();
let lo = i32::from_le_bytes(b);
let b: [u8; 4] = data[(sp + 8) as usize..(sp + 12) as usize]
.try_into()
.unwrap();
let n = i32::from_le_bytes(b);
// WITHIN: true if lo <= n < hi (unsigned subtraction trick)
let result = ((n.wrapping_sub(lo)) as u32) < ((hi.wrapping_sub(lo)) as u32);
let flag: i32 = if result { -1 } else { 0 };
// Pop 3, push 1: net = sp + 8
let new_sp = sp + 8;
let data = memory.data_mut(&mut caller);
let bytes = flag.to_le_bytes();
data[new_sp as usize..new_sp as usize + 4].copy_from_slice(&bytes);
dsp.set(&mut caller, Val::I32(new_sp as i32))?;
Ok(())
},
);
self.register_host_primitive("WITHIN", false, func)?;
Ok(())
}
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Priority 6: System/compiler host functions // Priority 6: System/compiler host functions
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
@@ -4337,86 +4158,6 @@ impl ForthVM {
Ok(()) Ok(())
} }
/// CMOVE ( src dst u -- ) copy u bytes from src to dst, low-to-high.
fn register_cmove(&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 u = i32::from_le_bytes(b) as usize;
let b: [u8; 4] = data[(sp + 4) as usize..(sp + 8) as usize]
.try_into()
.unwrap();
let dst = i32::from_le_bytes(b) as usize;
let b: [u8; 4] = data[(sp + 8) as usize..(sp + 12) as usize]
.try_into()
.unwrap();
let src = i32::from_le_bytes(b) as u32 as usize;
dsp.set(&mut caller, Val::I32((sp + 12) as i32))?;
if u > 0 {
let mem_len = memory.data(&caller).len();
if src.saturating_add(u) > mem_len || dst.saturating_add(u) > mem_len {
return Err(wasmtime::Error::msg("CMOVE: address out of range"));
}
let data = memory.data_mut(&mut caller);
for i in 0..u {
data[dst + i] = data[src + i];
}
}
Ok(())
},
);
self.register_host_primitive("CMOVE", false, func)?;
Ok(())
}
/// CMOVE> ( src dst u -- ) copy u bytes from src to dst, high-to-low.
fn register_cmove_up(&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 u = i32::from_le_bytes(b) as usize;
let b: [u8; 4] = data[(sp + 4) as usize..(sp + 8) as usize]
.try_into()
.unwrap();
let dst = i32::from_le_bytes(b) as usize;
let b: [u8; 4] = data[(sp + 8) as usize..(sp + 12) as usize]
.try_into()
.unwrap();
let src = i32::from_le_bytes(b) as u32 as usize;
dsp.set(&mut caller, Val::I32((sp + 12) as i32))?;
if u > 0 {
let mem_len = memory.data(&caller).len();
if src.saturating_add(u) > mem_len || dst.saturating_add(u) > mem_len {
return Err(wasmtime::Error::msg("CMOVE>: address out of range"));
}
let data = memory.data_mut(&mut caller);
for i in (0..u).rev() {
data[dst + i] = data[src + i];
}
}
Ok(())
},
);
self.register_host_primitive("CMOVE>", false, func)?;
Ok(())
}
/// FIND ( c-addr -- c-addr 0 | xt 1 | xt -1 ) look up counted string. /// FIND ( c-addr -- c-addr 0 | xt 1 | xt -1 ) look up counted string.
fn register_find(&mut self) -> anyhow::Result<()> { fn register_find(&mut self) -> anyhow::Result<()> {
let memory = self.memory; let memory = self.memory;
@@ -5258,92 +4999,6 @@ impl ForthVM {
Ok(()) Ok(())
} }
// -----------------------------------------------------------------------
// 2@ and 2!
// -----------------------------------------------------------------------
/// 2@ ( addr -- x1 x2 ) Fetch two cells. x2 from addr, x1 from addr+CELL.
fn register_two_fetch(&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 addr = u32::from_le_bytes(b);
// x2 is at addr, x1 is at addr+4
let mem_len = data.len() as u32;
if addr.wrapping_add(8) > mem_len || addr > mem_len {
return Err(wasmtime::Error::msg("2@: address out of range"));
}
let b: [u8; 4] = data[addr as usize..addr as usize + 4].try_into().unwrap();
let x2 = i32::from_le_bytes(b);
let b: [u8; 4] = data[(addr + 4) as usize..(addr + 8) as usize]
.try_into()
.unwrap();
let x1 = i32::from_le_bytes(b);
// Replace addr with x1, push x2
if sp < 4 || sp > mem_len {
return Err(wasmtime::Error::msg("data stack overflow in 2@"));
}
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(&x1.to_le_bytes());
data[new_sp as usize..new_sp as usize + 4].copy_from_slice(&x2.to_le_bytes());
dsp.set(&mut caller, Val::I32(new_sp as i32))?;
Ok(())
},
);
self.register_host_primitive("2@", false, func)?;
Ok(())
}
/// 2! ( x1 x2 addr -- ) Store x2 at addr, x1 at addr+CELL.
fn register_two_store(&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 addr = u32::from_le_bytes(b);
let b: [u8; 4] = data[(sp + 4) as usize..(sp + 8) as usize]
.try_into()
.unwrap();
let x2 = i32::from_le_bytes(b);
let b: [u8; 4] = data[(sp + 8) as usize..(sp + 12) as usize]
.try_into()
.unwrap();
let x1 = i32::from_le_bytes(b);
// Store x2 at addr, x1 at addr+4
let mem_len = memory.data(&caller).len() as u32;
if addr.wrapping_add(8) > mem_len || addr > mem_len {
dsp.set(&mut caller, Val::I32((sp + 12) as i32))?;
return Err(wasmtime::Error::msg("2!: address out of range"));
}
let data = memory.data_mut(&mut caller);
data[addr as usize..addr as usize + 4].copy_from_slice(&x2.to_le_bytes());
data[(addr + 4) as usize..(addr + 8) as usize].copy_from_slice(&x1.to_le_bytes());
// Pop 3 cells
dsp.set(&mut caller, Val::I32((sp + 12) as i32))?;
Ok(())
},
);
self.register_host_primitive("2!", false, func)?;
Ok(())
}
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Pictured numeric output: <# # #S #> HOLD SIGN // Pictured numeric output: <# # #S #> HOLD SIGN
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
@@ -5707,42 +5362,6 @@ impl ForthVM {
Ok(()) Ok(())
} }
/// ERASE ( addr u -- ) fill memory with zeros.
fn register_erase(&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 u = i32::from_le_bytes(b) as usize;
let b: [u8; 4] = data[(sp + 4) as usize..(sp + 8) as usize]
.try_into()
.unwrap();
let addr = i32::from_le_bytes(b) as u32 as usize;
dsp.set(&mut caller, Val::I32((sp + 8) as i32))?;
if u > 0 {
let mem_len = memory.data(&caller).len();
if addr.saturating_add(u) > mem_len {
return Err(wasmtime::Error::msg("ERASE: address out of range"));
}
let data = memory.data_mut(&mut caller);
for i in 0..u {
data[addr + i] = 0;
}
}
Ok(())
},
);
self.register_host_primitive("ERASE", false, func)?;
Ok(())
}
/// .R ( n width -- ) right-justified signed number output. /// .R ( n width -- ) right-justified signed number output.
fn register_dot_r(&mut self) -> anyhow::Result<()> { fn register_dot_r(&mut self) -> anyhow::Result<()> {
let memory = self.memory; let memory = self.memory;
@@ -6665,42 +6284,6 @@ impl ForthVM {
Ok(()) Ok(())
} }
/// 2ROT ( x1 x2 x3 x4 x5 x6 -- x3 x4 x5 x6 x1 x2 ) rotate three pairs.
fn register_2rot(&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);
// Stack: x6(sp), x5(sp+4), x4(sp+8), x3(sp+12), x2(sp+16), x1(sp+20)
let mut vals = [0i32; 6];
for (i, val) in vals.iter_mut().enumerate() {
let off = (sp + i as u32 * 4) as usize;
let b: [u8; 4] = data[off..off + 4].try_into().unwrap();
*val = i32::from_le_bytes(b);
}
// Want: x6(sp), x5(sp+4), x4(sp+8), x3(sp+12) stay as x4,x3,x2,x1
// Actually: ( x1 x2 x3 x4 x5 x6 -- x3 x4 x5 x6 x1 x2 )
// Stack top-first: [x6, x5, x4, x3, x2, x1]
// Result top-first: [x2, x1, x6, x5, x4, x3]
let new_vals = [vals[4], vals[5], vals[0], vals[1], vals[2], vals[3]];
let data = memory.data_mut(&mut caller);
for (i, new_val) in new_vals.iter().enumerate() {
let off = (sp + i as u32 * 4) as usize;
data[off..off + 4].copy_from_slice(&new_val.to_le_bytes());
}
Ok(())
},
);
self.register_host_primitive("2ROT", false, func)?;
Ok(())
}
/// DU< ( ud1 ud2 -- flag ) unsigned double-cell comparison. /// DU< ( ud1 ud2 -- flag ) unsigned double-cell comparison.
fn register_du_lt(&mut self) -> anyhow::Result<()> { fn register_du_lt(&mut self) -> anyhow::Result<()> {
let memory = self.memory; let memory = self.memory;
@@ -7038,121 +6621,6 @@ impl ForthVM {
Ok(()) Ok(())
} }
/// /STRING ( c-addr u n -- c-addr+n u-n ) adjust string.
fn register_slash_string(&mut self) -> anyhow::Result<()> {
// ( c-addr u n -- c-addr+n u-n )
// ROT ROT + SWAP ROT - -- hmm, simpler with host fn
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);
// Stack: n(sp), u(sp+4), c-addr(sp+8)
let b: [u8; 4] = data[sp as usize..sp as usize + 4].try_into().unwrap();
let n = i32::from_le_bytes(b);
let b: [u8; 4] = data[(sp + 4) as usize..(sp + 8) as usize]
.try_into()
.unwrap();
let u = i32::from_le_bytes(b);
let b: [u8; 4] = data[(sp + 8) as usize..(sp + 12) as usize]
.try_into()
.unwrap();
let addr = i32::from_le_bytes(b);
let new_addr = addr.wrapping_add(n);
let new_u = u.wrapping_sub(n);
// 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(&new_addr.to_le_bytes());
data[new_sp as usize..new_sp as usize + 4].copy_from_slice(&new_u.to_le_bytes());
dsp.set(&mut caller, Val::I32(new_sp as i32))?;
Ok(())
},
);
self.register_host_primitive("/STRING", false, func)?;
Ok(())
}
/// BLANK ( c-addr u -- ) fill with spaces.
fn register_blank(&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 u = i32::from_le_bytes(b);
let b: [u8; 4] = data[(sp + 4) as usize..(sp + 8) as usize]
.try_into()
.unwrap();
let addr = i32::from_le_bytes(b) as u32 as usize;
dsp.set(&mut caller, Val::I32((sp + 8) as i32))?;
if u > 0 {
let n = u as usize;
let mem_len = memory.data(&caller).len();
if addr.saturating_add(n) <= mem_len {
let data = memory.data_mut(&mut caller);
for i in 0..n {
data[addr + i] = b' ';
}
}
}
Ok(())
},
);
self.register_host_primitive("BLANK", false, func)?;
Ok(())
}
/// -TRAILING ( c-addr u -- c-addr u' ) remove trailing spaces.
fn register_minus_trailing(&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 mut u = i32::from_le_bytes(b) as usize;
let b: [u8; 4] = data[(sp + 4) as usize..(sp + 8) as usize]
.try_into()
.unwrap();
let addr = u32::from_le_bytes(b) as usize;
let mem_len = data.len();
while u > 0 {
let idx = addr + u - 1;
if idx < mem_len && data[idx] == b' ' {
u -= 1;
} else {
break;
}
}
let data = memory.data_mut(&mut caller);
data[sp as usize..sp as usize + 4].copy_from_slice(&(u as i32).to_le_bytes());
Ok(())
},
);
self.register_host_primitive("-TRAILING", false, func)?;
Ok(())
}
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Floating-Point word set // Floating-Point word set
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------