4f59ffa19e
Stack-to-local promotion (Phase 1: straight-line code): - Words with no control flow/calls use WASM locals instead of memory stack - Stack manipulation (Swap, Rot, Nip, Tuck, Dup, Drop) emits ZERO instructions - ~7x instruction reduction for arithmetic-heavy words like DUP * - Pre-loads consumed items from memory, writes results back at exit Consolidation recompiler (CONSOLIDATE word): - Recompiles all IR-based words into single WASM module - Direct call instructions instead of call_indirect through function table - Cranelift can inline and optimize across word boundaries - All control flow variants support consolidated calls 342 unit tests + 11 compliance, all passing.
170 lines
5.7 KiB
Rust
170 lines
5.7 KiB
Rust
//! Consolidation recompiler: merge all JIT-compiled words into a single WASM module.
|
|
//!
|
|
//! After interactive development, `CONSOLIDATE` recompiles everything:
|
|
//! - All `call_indirect` replaced with direct `call`
|
|
//! - Single WASM module output for maximum performance
|
|
//!
|
|
//! The implementation lives across two places:
|
|
//! - `codegen::compile_consolidated_module()` generates the multi-function WASM module
|
|
//! - `outer::ForthVM::consolidate()` orchestrates collection, compilation, and table update
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::collections::HashMap;
|
|
|
|
use crate::codegen::compile_consolidated_module;
|
|
use crate::dictionary::WordId;
|
|
use crate::ir::IrOp;
|
|
|
|
#[test]
|
|
fn consolidated_module_validates_empty() {
|
|
// Empty word list should produce nothing (but we guard against this at call site)
|
|
let words = vec![];
|
|
let map = HashMap::new();
|
|
let result = compile_consolidated_module(&words, &map, 16);
|
|
// Empty is valid -- should produce a valid module with no functions
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn consolidated_module_validates_single_word() {
|
|
let words = vec![(WordId(1), vec![IrOp::PushI32(42)])];
|
|
let mut map = HashMap::new();
|
|
map.insert(WordId(1), 1u32); // function index 1 (after emit import)
|
|
let result = compile_consolidated_module(&words, &map, 16);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn consolidated_module_validates_multiple_words() {
|
|
let words = vec![
|
|
(WordId(1), vec![IrOp::PushI32(1)]),
|
|
(WordId(2), vec![IrOp::PushI32(2), IrOp::Add]),
|
|
(
|
|
WordId(3),
|
|
vec![IrOp::Call(WordId(1)), IrOp::Call(WordId(2))],
|
|
),
|
|
];
|
|
let mut map = HashMap::new();
|
|
map.insert(WordId(1), 1u32);
|
|
map.insert(WordId(2), 2u32);
|
|
map.insert(WordId(3), 3u32);
|
|
let result = compile_consolidated_module(&words, &map, 16);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn consolidated_module_validates_external_call() {
|
|
// Word 3 calls WordId(99) which is NOT in the module -- should use call_indirect
|
|
let words = vec![(WordId(3), vec![IrOp::Call(WordId(99))])];
|
|
let mut map = HashMap::new();
|
|
map.insert(WordId(3), 1u32);
|
|
let result = compile_consolidated_module(&words, &map, 256);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn consolidated_module_validates_tail_call() {
|
|
let words = vec![
|
|
(WordId(1), vec![IrOp::PushI32(1)]),
|
|
(WordId(2), vec![IrOp::TailCall(WordId(1))]),
|
|
];
|
|
let mut map = HashMap::new();
|
|
map.insert(WordId(1), 1u32);
|
|
map.insert(WordId(2), 2u32);
|
|
let result = compile_consolidated_module(&words, &map, 16);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn consolidated_module_validates_control_flow_with_calls() {
|
|
// IF body contains a call to a consolidated word
|
|
let words = vec![
|
|
(WordId(1), vec![IrOp::PushI32(1)]),
|
|
(
|
|
WordId(2),
|
|
vec![
|
|
IrOp::PushI32(1),
|
|
IrOp::If {
|
|
then_body: vec![IrOp::Call(WordId(1))],
|
|
else_body: Some(vec![IrOp::PushI32(0)]),
|
|
},
|
|
],
|
|
),
|
|
];
|
|
let mut map = HashMap::new();
|
|
map.insert(WordId(1), 1u32);
|
|
map.insert(WordId(2), 2u32);
|
|
let result = compile_consolidated_module(&words, &map, 16);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn consolidated_module_validates_loop_with_calls() {
|
|
// DO LOOP body contains a call to a consolidated word
|
|
let words = vec![
|
|
(WordId(1), vec![IrOp::PushI32(1), IrOp::Add]),
|
|
(
|
|
WordId(2),
|
|
vec![
|
|
IrOp::PushI32(0),
|
|
IrOp::PushI32(3),
|
|
IrOp::PushI32(0),
|
|
IrOp::DoLoop {
|
|
body: vec![IrOp::Call(WordId(1))],
|
|
is_plus_loop: false,
|
|
},
|
|
],
|
|
),
|
|
];
|
|
let mut map = HashMap::new();
|
|
map.insert(WordId(1), 1u32);
|
|
map.insert(WordId(2), 2u32);
|
|
let result = compile_consolidated_module(&words, &map, 16);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn consolidated_module_validates_begin_until_with_calls() {
|
|
let words = vec![
|
|
(WordId(1), vec![IrOp::PushI32(1), IrOp::Sub]),
|
|
(
|
|
WordId(2),
|
|
vec![
|
|
IrOp::PushI32(5),
|
|
IrOp::BeginUntil {
|
|
body: vec![IrOp::Call(WordId(1)), IrOp::Dup, IrOp::ZeroEq],
|
|
},
|
|
],
|
|
),
|
|
];
|
|
let mut map = HashMap::new();
|
|
map.insert(WordId(1), 1u32);
|
|
map.insert(WordId(2), 2u32);
|
|
let result = compile_consolidated_module(&words, &map, 16);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn consolidated_module_validates_begin_while_repeat_with_calls() {
|
|
let words = vec![
|
|
(WordId(1), vec![IrOp::PushI32(1), IrOp::Sub]),
|
|
(
|
|
WordId(2),
|
|
vec![
|
|
IrOp::PushI32(3),
|
|
IrOp::BeginWhileRepeat {
|
|
test: vec![IrOp::Dup],
|
|
body: vec![IrOp::Call(WordId(1))],
|
|
},
|
|
],
|
|
),
|
|
];
|
|
let mut map = HashMap::new();
|
|
map.insert(WordId(1), 1u32);
|
|
map.insert(WordId(2), 2u32);
|
|
let result = compile_consolidated_module(&words, &map, 16);
|
|
assert!(result.is_ok());
|
|
}
|
|
}
|