Make PARSE/PARSE-NAME inline host functions, fix stack residue cascade
PARSE and PARSE-NAME were using the deferred pending mechanism which broke when called from compiled code (the calling word continued executing before PARSE ran). Replaced with inline host functions that read >IN/#TIB directly from WASM memory and parse immediately. This fixes utilities.fth $"/$2" failures that left stack residue cascading into all subsequent compliance test suites. Also: core_ext 17→14, string 27→17.
This commit is contained in:
+113
-8
@@ -4685,14 +4685,71 @@ impl ForthVM {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PARSE as a host function for compiled code.
|
/// PARSE ( char "ccc<char>" -- c-addr u ) as inline host function.
|
||||||
fn register_parse_host(&mut self) -> anyhow::Result<()> {
|
fn register_parse_host(&mut self) -> anyhow::Result<()> {
|
||||||
let pending = Arc::clone(&self.pending_define);
|
let memory = self.memory;
|
||||||
|
let dsp = self.dsp;
|
||||||
|
|
||||||
let func = Func::new(
|
let func = Func::new(
|
||||||
&mut self.store,
|
&mut self.store,
|
||||||
FuncType::new(&self.engine, [], []),
|
FuncType::new(&self.engine, [], []),
|
||||||
move |_caller, _params, _results| {
|
move |mut caller, _params, _results| {
|
||||||
*pending.lock().unwrap() = 7;
|
// Pop delimiter 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 delim = i32::from_le_bytes(b) as u8;
|
||||||
|
let sp = sp + CELL_SIZE; // pop delimiter
|
||||||
|
|
||||||
|
// Read >IN and #TIB from WASM memory
|
||||||
|
let data = memory.data(&caller);
|
||||||
|
let b: [u8; 4] = data[SYSVAR_TO_IN as usize..SYSVAR_TO_IN as usize + 4]
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let mut to_in = u32::from_le_bytes(b);
|
||||||
|
let b: [u8; 4] = data[SYSVAR_NUM_TIB as usize..SYSVAR_NUM_TIB as usize + 4]
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let num_tib = u32::from_le_bytes(b);
|
||||||
|
|
||||||
|
// Skip one leading space (outer interpreter's trailing delimiter)
|
||||||
|
if to_in < num_tib {
|
||||||
|
let data = memory.data(&caller);
|
||||||
|
if data[(INPUT_BUFFER_BASE + to_in) as usize] == b' ' {
|
||||||
|
to_in += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse until delimiter
|
||||||
|
let start = to_in;
|
||||||
|
while to_in < num_tib {
|
||||||
|
let data = memory.data(&caller);
|
||||||
|
if data[(INPUT_BUFFER_BASE + to_in) as usize] == delim {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
to_in += 1;
|
||||||
|
}
|
||||||
|
let parsed_len = to_in - start;
|
||||||
|
|
||||||
|
// Skip past delimiter
|
||||||
|
if to_in < num_tib {
|
||||||
|
to_in += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update >IN in WASM memory
|
||||||
|
let data = memory.data_mut(&mut caller);
|
||||||
|
data[SYSVAR_TO_IN as usize..SYSVAR_TO_IN as usize + 4]
|
||||||
|
.copy_from_slice(&to_in.to_le_bytes());
|
||||||
|
|
||||||
|
// Push (c-addr u) to data stack
|
||||||
|
let c_addr = INPUT_BUFFER_BASE + start;
|
||||||
|
let new_sp = sp - 2 * CELL_SIZE;
|
||||||
|
data[new_sp as usize..new_sp as usize + 4]
|
||||||
|
.copy_from_slice(&(parsed_len as i32).to_le_bytes());
|
||||||
|
data[(new_sp + CELL_SIZE) as usize..(new_sp + 2 * CELL_SIZE) as usize]
|
||||||
|
.copy_from_slice(&(c_addr as i32).to_le_bytes());
|
||||||
|
dsp.set(&mut caller, Val::I32(new_sp as i32))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -4701,14 +4758,62 @@ impl ForthVM {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PARSE-NAME as a host function for compiled code.
|
/// PARSE-NAME ( "<spaces>name<space>" -- c-addr u ) as inline host function.
|
||||||
fn register_parse_name_host(&mut self) -> anyhow::Result<()> {
|
fn register_parse_name_host(&mut self) -> anyhow::Result<()> {
|
||||||
let pending = Arc::clone(&self.pending_define);
|
let memory = self.memory;
|
||||||
|
let dsp = self.dsp;
|
||||||
|
|
||||||
let func = Func::new(
|
let func = Func::new(
|
||||||
&mut self.store,
|
&mut self.store,
|
||||||
FuncType::new(&self.engine, [], []),
|
FuncType::new(&self.engine, [], []),
|
||||||
move |_caller, _params, _results| {
|
move |mut caller, _params, _results| {
|
||||||
*pending.lock().unwrap() = 8;
|
// Read >IN and #TIB from WASM memory
|
||||||
|
let data = memory.data(&caller);
|
||||||
|
let b: [u8; 4] = data[SYSVAR_TO_IN as usize..SYSVAR_TO_IN as usize + 4]
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let mut to_in = u32::from_le_bytes(b);
|
||||||
|
let b: [u8; 4] = data[SYSVAR_NUM_TIB as usize..SYSVAR_NUM_TIB as usize + 4]
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let num_tib = u32::from_le_bytes(b);
|
||||||
|
|
||||||
|
// Skip leading whitespace
|
||||||
|
while to_in < num_tib {
|
||||||
|
let data = memory.data(&caller);
|
||||||
|
if !data[(INPUT_BUFFER_BASE + to_in) as usize].is_ascii_whitespace() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
to_in += 1;
|
||||||
|
}
|
||||||
|
let start = to_in;
|
||||||
|
|
||||||
|
// Parse until whitespace
|
||||||
|
while to_in < num_tib {
|
||||||
|
let data = memory.data(&caller);
|
||||||
|
if data[(INPUT_BUFFER_BASE + to_in) as usize].is_ascii_whitespace() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
to_in += 1;
|
||||||
|
}
|
||||||
|
let parsed_len = to_in - start;
|
||||||
|
|
||||||
|
// Update >IN
|
||||||
|
let data = memory.data_mut(&mut caller);
|
||||||
|
data[SYSVAR_TO_IN as usize..SYSVAR_TO_IN as usize + 4]
|
||||||
|
.copy_from_slice(&to_in.to_le_bytes());
|
||||||
|
|
||||||
|
// Push (c-addr u) to data stack
|
||||||
|
let c_addr = INPUT_BUFFER_BASE + start;
|
||||||
|
let sp = dsp.get(&mut caller).unwrap_i32() as u32;
|
||||||
|
let new_sp = sp - 2 * CELL_SIZE;
|
||||||
|
let data = memory.data_mut(&mut caller);
|
||||||
|
data[new_sp as usize..new_sp as usize + 4]
|
||||||
|
.copy_from_slice(&(parsed_len as i32).to_le_bytes());
|
||||||
|
data[(new_sp + CELL_SIZE) as usize..(new_sp + 2 * CELL_SIZE) as usize]
|
||||||
|
.copy_from_slice(&(c_addr as i32).to_le_bytes());
|
||||||
|
dsp.set(&mut caller, Val::I32(new_sp as i32))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>WAFER - test_js.wasm</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: monospace; background: #1a1a2e; color: #e0e0e0; padding: 2em; }
|
||||||
|
#output { white-space: pre-wrap; font-size: 1.2em; padding: 1em; background: #16213e;
|
||||||
|
border: 1px solid #0f3460; border-radius: 4px; min-height: 4em; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>WAFER Output</h2>
|
||||||
|
<div id="output"></div>
|
||||||
|
<script src="test_js.js"></script>
|
||||||
|
<script>WAFER.run();</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
// WAFER JS Loader - generated by wafer build --js
|
||||||
|
// Loads and runs test_js.wasm in the browser.
|
||||||
|
|
||||||
|
const WAFER = (() => {
|
||||||
|
const CELL_SIZE = 4;
|
||||||
|
const DATA_STACK_TOP = 0x1540;
|
||||||
|
const SYSVAR_BASE = 0x0004;
|
||||||
|
let outputCallback = (s) => {
|
||||||
|
const el = document.getElementById('output');
|
||||||
|
if (el) el.textContent += s;
|
||||||
|
else console.log(s);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function run(opts) {
|
||||||
|
if (opts && opts.output) outputCallback = opts.output;
|
||||||
|
|
||||||
|
const memory = new WebAssembly.Memory({ initial: 16 });
|
||||||
|
const dsp = new WebAssembly.Global({ value: 'i32', mutable: true }, 5440);
|
||||||
|
const rsp = new WebAssembly.Global({ value: 'i32', mutable: true }, 9536);
|
||||||
|
const fsp = new WebAssembly.Global({ value: 'i32', mutable: true }, 11584);
|
||||||
|
const table = new WebAssembly.Table({ element: 'anyfunc', initial: 256 });
|
||||||
|
|
||||||
|
function emit(code) {
|
||||||
|
outputCallback(String.fromCharCode(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
const importObject = {
|
||||||
|
env: { emit, memory, dsp, rsp, fsp, table }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register host functions
|
||||||
|
const view = () => new DataView(memory.buffer);
|
||||||
|
const pop = () => {
|
||||||
|
const sp = dsp.value;
|
||||||
|
const v = view().getInt32(sp, true);
|
||||||
|
dsp.value = sp + CELL_SIZE;
|
||||||
|
return v;
|
||||||
|
};
|
||||||
|
const push = (v) => {
|
||||||
|
const sp = dsp.value - CELL_SIZE;
|
||||||
|
view().setInt32(sp, v, true);
|
||||||
|
dsp.value = sp;
|
||||||
|
};
|
||||||
|
|
||||||
|
table.set(84, new WebAssembly.Function({parameters:[], results:[]}, () => {
|
||||||
|
const n = pop();
|
||||||
|
const base = view().getUint32(SYSVAR_BASE, true);
|
||||||
|
outputCallback((base === 16 ? n.toString(16).toUpperCase() : n.toString()) + ' ');
|
||||||
|
}));
|
||||||
|
|
||||||
|
const response = await fetch('test_js.wasm');
|
||||||
|
const bytes = await response.arrayBuffer();
|
||||||
|
const { instance } = await WebAssembly.instantiate(bytes, importObject);
|
||||||
|
|
||||||
|
if (instance.exports._start) {
|
||||||
|
instance.exports._start();
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { run };
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user