Lines
100 %
Functions
Branches
//! Snapshot/restore round-trip tests.
//!
//! Lock-in for the Tier 1.5 monomorphisation fixpoint: a trial body
//! emit must leave no observable trace if the snapshot is restored.
//! Each test mutates a different surface (types, function-section,
//! data, closures, declared funcrefs, pending helpers, locals) and
//! confirms that `restore` undoes the mutation.
use wasm_encoder::{Function, Instruction, ValType};
use super::CompileContext;
use crate::ast::{Expr, LambdaParams, WasmType};
#[test]
fn snapshot_restore_round_trips_struct_type_registration() {
let mut ctx = CompileContext::new().unwrap();
let snap = ctx.snapshot();
let pre_count = ctx.type_count;
let idx = ctx.register_struct_type(&[ValType::I32]).unwrap();
assert_eq!(ctx.type_count, pre_count + 1);
assert_eq!(idx, pre_count, "new struct gets the next type index");
ctx.restore(snap);
assert_eq!(ctx.type_count, pre_count);
}
fn snapshot_restore_round_trips_function_registration() {
let pre_local_func_count = ctx.local_func_count;
let pre_func_names = ctx.func_names.clone();
ctx.register_function("trial_fn", &[ValType::I32], &[ValType::I32])
.unwrap();
assert_eq!(ctx.local_func_count, pre_local_func_count + 1);
assert!(ctx.func_names.contains_key("trial_fn"));
assert_eq!(ctx.local_func_count, pre_local_func_count);
assert_eq!(ctx.func_names, pre_func_names);
fn snapshot_restore_round_trips_data_segment() {
let pre_count = ctx.data_count;
let _ = ctx.add_data(b"trial").unwrap();
assert_eq!(ctx.data_count, pre_count + 1);
assert_eq!(ctx.data_count, pre_count);
fn snapshot_restore_round_trips_closure_signature() {
let pre_type_count = ctx.type_count;
let _ = ctx
.intern_closure_signature(&[WasmType::Ratio], WasmType::Ratio)
assert!(ctx.type_count > pre_type_count);
assert_eq!(ctx.type_count, pre_type_count);
assert!(
ctx.intern_closure_signature(&[WasmType::Ratio], WasmType::Ratio)
.is_ok(),
"interner must work post-restore"
);
fn snapshot_restore_truncates_pending_helpers() {
let baseline = ctx.pending_helper_count();
let mut f = Function::new([]);
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::End);
ctx.queue_helper(f);
assert_eq!(ctx.pending_helper_count(), baseline + 1);
assert_eq!(ctx.pending_helper_count(), baseline);
fn snapshot_restore_round_trips_declared_funcrefs() {
let pre_len = ctx.declared_funcs.len();
ctx.declare_funcref(42);
ctx.declare_funcref(43);
assert_eq!(ctx.declared_funcs.len(), pre_len + 2);
assert_eq!(ctx.declared_funcs.len(), pre_len);
fn snapshot_restore_round_trips_local_pool() {
let pre_next_local = ctx.next_local;
let _ = ctx.alloc_local(WasmType::Ratio).unwrap();
let _ = ctx.alloc_local(WasmType::I32).unwrap();
assert_eq!(ctx.next_local, pre_next_local + 2);
assert_eq!(ctx.next_local, pre_next_local);
assert!(ctx.local_types.is_empty());
fn snapshot_restore_drops_closure_bodies_recorded_in_trial() {
let idx = ctx.alloc_local(WasmType::I32).unwrap();
ctx.record_closure_body(idx, LambdaParams::simple(vec!["x".to_string()]), Expr::Nil);
assert!(ctx.closure_body(idx).is_some());
ctx.closure_body(idx).is_none(),
"a closure body recorded during a rolled-back trial emit must not survive"
fn finish_after_restore_produces_valid_wasm() {
// The whole point of the round-trip: after a trial emit + restore
// the context must still produce a valid wasm module — no leaked
// type-section bytes, no orphaned function-section slots, no half-
// queued helper.
ctx.register_struct_type(&[ValType::I32]).unwrap();
ctx.register_function("doomed_fn", &[], &[ValType::I32])
let _ = ctx.add_data(b"doomed").unwrap();
f.instruction(&Instruction::I32Const(7));
ctx.add_should_apply(CompileContext::default_should_apply(), usize::MAX);
let mut process = Function::new([]);
process.instruction(&Instruction::End);
ctx.add_process(process);
let bytes = ctx.finish();
wasmparser::validate(&bytes).expect("module must validate after restore");