Update all dependencies to latest versions

wasmtime 31→43, wasm-encoder/wasmparser 0.228→0.246, rustyline 15→18.

API migrations: F64Const now takes Ieee64 wrapper, wasmtime has own
Error type (wasmtime::bail! in host closures), cache_config_load_default
removed. Add performance regression limits to benchmark tests.
This commit is contained in:
2026-04-12 18:36:48 +02:00
parent 22a4372c45
commit d24fa59e43
7 changed files with 558 additions and 496 deletions
Generated
+502 -468
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -41,9 +41,9 @@ needless_collect = "warn"
or_fun_call = "warn" or_fun_call = "warn"
[workspace.dependencies] [workspace.dependencies]
wasm-encoder = "0.228" wasm-encoder = "0.246"
wasmparser = "0.228" wasmparser = "0.246"
wasmtime = "31" wasmtime = "43"
anyhow = "1" anyhow = "1"
thiserror = "2" thiserror = "2"
proptest = "1" proptest = "1"
+1 -1
View File
@@ -13,4 +13,4 @@ wafer-core = { path = "../core", version = "0.1.0" }
wasmtime = { workspace = true } wasmtime = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
rustyline = "15" rustyline = "18"
+3 -3
View File
@@ -349,7 +349,7 @@ fn emit_op(f: &mut Function, op: &IrOp, ctx: &mut EmitCtx) {
IrOp::PushF64(val) => { IrOp::PushF64(val) => {
fsp_dec(f); fsp_dec(f);
f.instruction(&Instruction::GlobalGet(FSP)) f.instruction(&Instruction::GlobalGet(FSP))
.instruction(&Instruction::F64Const(*val)) .instruction(&Instruction::F64Const((*val).into()))
.instruction(&Instruction::F64Store(MEM8)); .instruction(&Instruction::F64Store(MEM8));
} }
@@ -855,14 +855,14 @@ fn emit_op(f: &mut Function, op: &IrOp, ctx: &mut EmitCtx) {
// -- Float comparisons (cross-stack) -------------------------------- // -- Float comparisons (cross-stack) --------------------------------
IrOp::FZeroEq => { IrOp::FZeroEq => {
fpop(f); fpop(f);
f.instruction(&Instruction::F64Const(0.0)) f.instruction(&Instruction::F64Const(0.0.into()))
.instruction(&Instruction::F64Eq); .instruction(&Instruction::F64Eq);
bool_to_forth_flag(f, SCRATCH_BASE); bool_to_forth_flag(f, SCRATCH_BASE);
push_via_local(f, SCRATCH_BASE + 1); push_via_local(f, SCRATCH_BASE + 1);
} }
IrOp::FZeroLt => { IrOp::FZeroLt => {
fpop(f); fpop(f);
f.instruction(&Instruction::F64Const(0.0)) f.instruction(&Instruction::F64Const(0.0.into()))
.instruction(&Instruction::F64Lt); .instruction(&Instruction::F64Lt);
bool_to_forth_flag(f, SCRATCH_BASE); bool_to_forth_flag(f, SCRATCH_BASE);
push_via_local(f, SCRATCH_BASE + 1); push_via_local(f, SCRATCH_BASE + 1);
-2
View File
@@ -282,8 +282,6 @@ impl ForthVM {
pub fn new_with_config(wafer_config: WaferConfig) -> anyhow::Result<Self> { pub fn new_with_config(wafer_config: WaferConfig) -> anyhow::Result<Self> {
let mut config = wasmtime::Config::new(); let mut config = wasmtime::Config::new();
config.cranelift_nan_canonicalization(false); config.cranelift_nan_canonicalization(false);
// Best-effort module caching
let _ = config.cache_config_load_default();
let engine = Engine::new(&config)?; let engine = Engine::new(&config)?;
let output = Arc::new(Mutex::new(String::new())); let output = Arc::new(Mutex::new(String::new()));
+3 -3
View File
@@ -21,7 +21,7 @@ struct RunnerHost {}
fn make_engine() -> anyhow::Result<Engine> { fn make_engine() -> anyhow::Result<Engine> {
let mut config = wasmtime::Config::new(); let mut config = wasmtime::Config::new();
config.cranelift_nan_canonicalization(false); config.cranelift_nan_canonicalization(false);
Engine::new(&config) Ok(Engine::new(&config)?)
} }
/// Execute a pre-compiled `.wasm` module from a file path. /// Execute a pre-compiled `.wasm` module from a file path.
@@ -336,7 +336,7 @@ fn create_host_func(
((hi << 32) | lo, divisor) ((hi << 32) | lo, divisor)
}; };
if divisor == 0 { if divisor == 0 {
anyhow::bail!("division by zero"); wasmtime::bail!("division by zero");
} }
let quot = (dividend / divisor) as u32; let quot = (dividend / divisor) as u32;
let rem = (dividend % divisor) as u32; let rem = (dividend % divisor) as u32;
@@ -368,7 +368,7 @@ fn create_host_func(
// Unimplemented host function: trap with a clear message. // Unimplemented host function: trap with a clear message.
let name_owned = name.to_string(); let name_owned = name.to_string();
Func::new(store, void_type, move |_caller, _params, _results| { Func::new(store, void_type, move |_caller, _params, _results| {
anyhow::bail!("host function '{name_owned}' is not available in standalone mode") wasmtime::bail!("host function '{name_owned}' is not available in standalone mode")
}) })
} }
} }
+43 -13
View File
@@ -592,6 +592,9 @@ struct PerfBenchmark {
verify: &'static str, verify: &'static str,
expected: i32, expected: i32,
samples: u32, // Number of runs for WAFER median samples: u32, // Number of runs for WAFER median
/// Maximum acceptable WAFER/gforth ratio (< 1.0 = WAFER faster).
/// Test fails if ratio exceeds this. Set ~40-50% above measured baseline.
max_ratio: f64,
} }
fn perf_benchmarks() -> Vec<PerfBenchmark> { fn perf_benchmarks() -> Vec<PerfBenchmark> {
@@ -603,6 +606,7 @@ fn perf_benchmarks() -> Vec<PerfBenchmark> {
verify: "25 FIB", verify: "25 FIB",
expected: 75025, expected: 75025,
samples: 5, samples: 5,
max_ratio: 0.65,
}, },
PerfBenchmark { PerfBenchmark {
name: "Factorial(12)x10K", name: "Factorial(12)x10K",
@@ -612,6 +616,7 @@ fn perf_benchmarks() -> Vec<PerfBenchmark> {
verify: "12 FACT", verify: "12 FACT",
expected: 479001600, expected: 479001600,
samples: 5, samples: 5,
max_ratio: 0.75,
}, },
PerfBenchmark { PerfBenchmark {
name: "GCD-bench(500)", name: "GCD-bench(500)",
@@ -621,6 +626,7 @@ fn perf_benchmarks() -> Vec<PerfBenchmark> {
verify: "48 36 GCD", verify: "48 36 GCD",
expected: 12, expected: 12,
samples: 5, samples: 5,
max_ratio: 0.70,
}, },
PerfBenchmark { PerfBenchmark {
name: "NestedLoops(50)", name: "NestedLoops(50)",
@@ -630,6 +636,7 @@ fn perf_benchmarks() -> Vec<PerfBenchmark> {
verify: "5 NESTED", verify: "5 NESTED",
expected: 0, expected: 0,
samples: 3, samples: 3,
max_ratio: 0.20,
}, },
PerfBenchmark { PerfBenchmark {
name: "Collatz(2K)", name: "Collatz(2K)",
@@ -641,6 +648,7 @@ fn perf_benchmarks() -> Vec<PerfBenchmark> {
verify: "27 COLLATZ", verify: "27 COLLATZ",
expected: 111, expected: 111,
samples: 3, samples: 3,
max_ratio: 0.45,
}, },
] ]
} }
@@ -827,15 +835,17 @@ fn performance_report() {
println!(" WAFER vs Gforth Performance Comparison (release mode)"); println!(" WAFER vs Gforth Performance Comparison (release mode)");
println!("{sep}\n"); println!("{sep}\n");
println!( println!(
"{:<22} {:>10} {:>10} {:>10} {:>10} {:>10}", "{:<22} {:>10} {:>10} {:>10} {:>10} {:>10} {:>10}",
"Benchmark", "WAFER", "CONSOL", "gforth", "gf-fast", "WAFER/gf" "Benchmark", "WAFER", "CONSOL", "gforth", "gf-fast", "WAFER/gf", "limit"
); );
println!( println!(
"{:<22} {:>10} {:>10} {:>10} {:>10} {:>10}", "{:<22} {:>10} {:>10} {:>10} {:>10} {:>10} {:>10}",
"", "(us)", "(us)", "(us)", "(us)", "" "", "(us)", "(us)", "(us)", "(us)", "", ""
); );
println!("{thin}"); println!("{thin}");
let mut regressions: Vec<String> = Vec::new();
for bench in &benchmarks { for bench in &benchmarks {
let wafer = wafer_release let wafer = wafer_release
.and_then(|w| measure_wafer_release(w, bench)) .and_then(|w| measure_wafer_release(w, bench))
@@ -853,25 +863,45 @@ fn performance_report() {
} else { } else {
wafer wafer
}; };
let ratio = gf.map_or_else( let ratio_val = gf.and_then(|g| {
|| "-".to_string(),
|g| {
if g > 0 { if g > 0 {
format!("{:.2}x", best_wafer as f64 / g as f64) Some(best_wafer as f64 / g as f64)
} else { } else {
"-".to_string() None
} }
}, });
); let ratio = ratio_val.map_or_else(|| "-".to_string(), |r| format!("{r:.2}x"));
let limit_str = format!("{:.2}x", bench.max_ratio);
println!( println!(
"{:<22} {:>10} {:>10} {:>10} {:>10} {:>10}", "{:<22} {:>10} {:>10} {:>10} {:>10} {:>10} {:>10}",
bench.name, wafer, consol, gf_str, gf_fast_str, ratio bench.name, wafer, consol, gf_str, gf_fast_str, ratio, limit_str
); );
// Check regression limits
if let Some(r) = ratio_val {
if r > bench.max_ratio {
regressions.push(format!(
" {} ratio {:.2}x exceeds limit {:.2}x (REGRESSION)",
bench.name, r, bench.max_ratio
));
}
if r < 0.02 {
regressions.push(format!(
" {} ratio {:.4}x suspiciously low (measurement error?)",
bench.name, r
));
}
}
} }
println!("{thin}"); println!("{thin}");
println!(" WAFER = all optimizations, CONSOL = after CONSOLIDATE"); println!(" WAFER = all optimizations, CONSOL = after CONSOLIDATE");
println!(" WAFER/gf = best(WAFER,CONSOL) vs gforth, < 1.0 means WAFER faster"); println!(" WAFER/gf = best(WAFER,CONSOL) vs gforth, < 1.0 means WAFER faster");
println!("{sep}\n"); println!("{sep}\n");
if !regressions.is_empty() {
let msg = regressions.join("\n");
panic!("Performance regression detected:\n{msg}");
}
} }