Replace ALLOT/comma/C-comma/ALIGN + float alignment with Forth (Phase 8)

Move memory allocation words to boot.fth:
- ALLOT: `: ALLOT HERE + 12 ! ;`
- , (comma): `: , HERE ! 1 CELLS ALLOT ;`
- C, : `: C, HERE C! 1 ALLOT ;`
- ALIGN: `: ALIGN HERE ALIGNED 12 ! ;`
- FALIGN, SFALIGN, DFALIGN: float-aligned variants

These write directly to WASM memory[SYSVAR_HERE]. The Rust side picks up
Forth-side HERE changes via refresh_user_here() which now reads both
here_cell (for Rust host functions) and memory[12] (for Forth words),
taking the maximum to ensure no allocation is lost.

Removed 222 lines of Rust. All 426 tests pass.
This commit is contained in:
2026-04-07 15:59:16 +02:00
parent b2378e34be
commit d0991c58f6
2 changed files with 40 additions and 222 deletions
+21
View File
@@ -160,6 +160,18 @@
\ ALIGNED is already an IR primitive in the compiler.
\ ALLOT ( n -- ) advance HERE by n bytes
: ALLOT HERE + 12 ! ;
\ , ( x -- ) store cell at HERE, advance by cell
: , HERE ! 1 CELLS ALLOT ;
\ C, ( char -- ) store byte at HERE, advance by 1
: C, HERE C! 1 ALLOT ;
\ ALIGN ( -- ) align HERE to cell boundary
: ALIGN HERE ALIGNED 12 ! ;
\ ---------------------------------------------------------------
\ Phase 5: I/O, pictured numeric output, formatted output
\ ---------------------------------------------------------------
@@ -275,4 +287,13 @@
\ DFALIGNED ( addr -- addr ) align to 8-byte double-float boundary
: DFALIGNED 7 + -8 AND ;
\ FALIGN ( -- ) align HERE to 8-byte float boundary
: FALIGN HERE FALIGNED 12 ! ;
\ SFALIGN ( -- ) align HERE to 4-byte single-float boundary
: SFALIGN ALIGN ;
\ DFALIGN ( -- ) align HERE to 8-byte double-float boundary
: DFALIGN FALIGN ;
\ .S keeps its Rust host function (complex stack introspection).
+19 -222
View File
@@ -2067,15 +2067,13 @@ impl ForthVM {
// HERE: defined in boot.fth (reads SYSVAR_HERE from WASM memory).
// Initialize the here_cell for host functions that still need it.
self.here_cell = Some(Arc::new(Mutex::new(self.user_here)));
self.register_allot()?;
self.register_comma()?;
self.register_c_comma()?;
// ALLOT, comma, C-comma: defined in boot.fth
self.register_primitive("CELLS", false, vec![IrOp::PushI32(4), IrOp::Mul])?;
self.register_primitive("CELL+", false, vec![IrOp::PushI32(4), IrOp::Add])?;
// CHARS is a no-op (byte addressed)
self.register_primitive("CHARS", false, vec![])?;
self.register_primitive("CHAR+", false, vec![IrOp::PushI32(1), IrOp::Add])?;
self.register_align()?;
// ALIGN: defined in boot.fth
self.register_aligned()?;
// MOVE, FILL: defined in boot.fth
@@ -2899,11 +2897,27 @@ impl ForthVM {
}
}
/// Update `user_here` from the shared cell and then write back.
/// Update `user_here` from the shared cell and WASM memory.
///
/// Reads both `here_cell` (modified by Rust host functions) and
/// `memory[SYSVAR_HERE]` (modified by Forth ALLOT/`,`/`C,`/ALIGN).
/// Takes the maximum to ensure no allocation is lost.
fn refresh_user_here(&mut self) {
if let Some(ref cell) = self.here_cell {
self.user_here = *cell.lock().unwrap();
}
let data = self.memory.data(&self.store);
let mem_here = u32::from_le_bytes(
data[SYSVAR_HERE as usize..SYSVAR_HERE as usize + 4]
.try_into()
.unwrap(),
);
if mem_here > self.user_here {
self.user_here = mem_here;
if let Some(ref cell) = self.here_cell {
*cell.lock().unwrap() = mem_here;
}
}
}
/// Write `user_here` to WASM `memory[SYSVAR_HERE]` so Forth code can read it.
@@ -2915,132 +2929,6 @@ impl ForthVM {
.copy_from_slice(&self.user_here.to_le_bytes());
}
/// ALLOT -- ( n -- ) advance HERE by n bytes.
fn register_allot(&mut self) -> anyhow::Result<()> {
let memory = self.memory;
let dsp = self.dsp;
let here_cell = self.here_cell.clone();
let func = Func::new(
&mut self.store,
FuncType::new(&self.engine, [], []),
move |mut caller, _params, _results| {
// Pop n from data stack
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 n = i32::from_le_bytes(b);
dsp.set(&mut caller, Val::I32((sp + CELL_SIZE) as i32))?;
// Advance HERE
if let Some(ref cell) = here_cell {
let mut h = cell.lock().unwrap();
*h = (*h as i32 + n) as u32;
let new_here = *h;
let data = memory.data_mut(&mut caller);
data[SYSVAR_HERE as usize..SYSVAR_HERE as usize + 4]
.copy_from_slice(&new_here.to_le_bytes());
}
Ok(())
},
);
self.register_host_primitive("ALLOT", false, func)?;
Ok(())
}
/// , (comma) -- ( x -- ) store x at HERE, advance HERE by cell.
fn register_comma(&mut self) -> anyhow::Result<()> {
let memory = self.memory;
let dsp = self.dsp;
let here_cell = self.here_cell.clone();
let func = Func::new(
&mut self.store,
FuncType::new(&self.engine, [], []),
move |mut caller, _params, _results| {
// Pop value from data stack
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 value = i32::from_le_bytes(b);
dsp.set(&mut caller, Val::I32((sp + CELL_SIZE) as i32))?;
// Store at HERE and advance
if let Some(ref cell) = here_cell {
let mut h = cell.lock().unwrap();
let addr = *h as usize;
*h += CELL_SIZE;
let new_here = *h;
let data = memory.data_mut(&mut caller);
data[addr..addr + 4].copy_from_slice(&value.to_le_bytes());
data[SYSVAR_HERE as usize..SYSVAR_HERE as usize + 4]
.copy_from_slice(&new_here.to_le_bytes());
}
Ok(())
},
);
self.register_host_primitive(",", false, func)?;
Ok(())
}
/// C, -- ( char -- ) store byte at HERE, advance HERE by 1.
fn register_c_comma(&mut self) -> anyhow::Result<()> {
let memory = self.memory;
let dsp = self.dsp;
let here_cell = self.here_cell.clone();
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 value = i32::from_le_bytes(b) as u8;
dsp.set(&mut caller, Val::I32((sp + CELL_SIZE) as i32))?;
if let Some(ref cell) = here_cell {
let mut h = cell.lock().unwrap();
let addr = *h as usize;
*h += 1;
let new_here = *h;
let data = memory.data_mut(&mut caller);
data[addr] = value;
data[SYSVAR_HERE as usize..SYSVAR_HERE as usize + 4]
.copy_from_slice(&new_here.to_le_bytes());
}
Ok(())
},
);
self.register_host_primitive("C,", false, func)?;
Ok(())
}
/// ALIGN -- align HERE to cell boundary.
fn register_align(&mut self) -> anyhow::Result<()> {
let memory = self.memory;
let here_cell = self.here_cell.clone();
let func = Func::new(
&mut self.store,
FuncType::new(&self.engine, [], []),
move |mut caller, _params, _results| {
if let Some(ref cell) = here_cell {
let mut h = cell.lock().unwrap();
*h = (*h + 3) & !3;
let new_here = *h;
let data = memory.data_mut(&mut caller);
data[SYSVAR_HERE as usize..SYSVAR_HERE as usize + 4]
.copy_from_slice(&new_here.to_le_bytes());
}
Ok(())
},
);
self.register_host_primitive("ALIGN", false, func)?;
Ok(())
}
/// ALIGNED -- ( addr -- aligned-addr ) align address to cell boundary.
fn register_aligned(&mut self) -> anyhow::Result<()> {
// Can be done purely in IR: (addr + 3) AND NOT(3)
@@ -5270,36 +5158,6 @@ impl ForthVM {
self.register_host_primitive("FALIGNED", false, func)?;
}
// FALIGN ( -- ) align HERE to float boundary
{
let memory = self.memory;
let here_cell = self.here_cell.clone();
let func = Func::new(
&mut self.store,
FuncType::new(&self.engine, [], []),
move |mut caller, _, _| {
let here_val = if let Some(ref cell) = here_cell {
*cell.lock().unwrap()
} else {
let mem = memory.data(&caller);
let b: [u8; 4] = mem[SYSVAR_HERE as usize..SYSVAR_HERE as usize + 4]
.try_into()
.unwrap();
u32::from_le_bytes(b)
};
let aligned = (here_val + 7) & !7;
if let Some(ref cell) = here_cell {
*cell.lock().unwrap() = aligned;
}
let mem = memory.data_mut(&mut caller);
mem[SYSVAR_HERE as usize..SYSVAR_HERE as usize + 4]
.copy_from_slice(&aligned.to_le_bytes());
Ok(())
},
);
self.register_host_primitive("FALIGN", false, func)?;
}
// SFLOATS ( n -- n*sfloat_size ) single-float size (same as FLOATS for us)
self.register_primitive(
"SFLOATS",
@@ -5885,67 +5743,6 @@ impl ForthVM {
self.register_host_primitive("DFALIGNED", false, func)?;
}
// SFALIGN, DFALIGN (align HERE)
// Not commonly needed but let's register stubs
// SFALIGN aligns to 4, DFALIGN aligns to 8
{
let memory = self.memory;
let here_cell = self.here_cell.clone();
let func = Func::new(
&mut self.store,
FuncType::new(&self.engine, [], []),
move |mut caller, _, _| {
let here_val = if let Some(ref cell) = here_cell {
*cell.lock().unwrap()
} else {
let mem = memory.data(&caller);
let b: [u8; 4] = mem[SYSVAR_HERE as usize..SYSVAR_HERE as usize + 4]
.try_into()
.unwrap();
u32::from_le_bytes(b)
};
let aligned = (here_val + 3) & !3;
if let Some(ref cell) = here_cell {
*cell.lock().unwrap() = aligned;
}
let mem = memory.data_mut(&mut caller);
mem[SYSVAR_HERE as usize..SYSVAR_HERE as usize + 4]
.copy_from_slice(&aligned.to_le_bytes());
Ok(())
},
);
self.register_host_primitive("SFALIGN", false, func)?;
}
{
let memory = self.memory;
let here_cell = self.here_cell.clone();
let func = Func::new(
&mut self.store,
FuncType::new(&self.engine, [], []),
move |mut caller, _, _| {
let here_val = if let Some(ref cell) = here_cell {
*cell.lock().unwrap()
} else {
let mem = memory.data(&caller);
let b: [u8; 4] = mem[SYSVAR_HERE as usize..SYSVAR_HERE as usize + 4]
.try_into()
.unwrap();
u32::from_le_bytes(b)
};
let aligned = (here_val + 7) & !7;
if let Some(ref cell) = here_cell {
*cell.lock().unwrap() = aligned;
}
let mem = memory.data_mut(&mut caller);
mem[SYSVAR_HERE as usize..SYSVAR_HERE as usize + 4]
.copy_from_slice(&aligned.to_le_bytes());
Ok(())
},
);
self.register_host_primitive("DFALIGN", false, func)?;
}
Ok(())
}