1
//! Snapshot/restore round-trip tests.
2
//!
3
//! Lock-in for the Tier 1.5 monomorphisation fixpoint: a trial body
4
//! emit must leave no observable trace if the snapshot is restored.
5
//! Each test mutates a different surface (types, function-section,
6
//! data, closures, declared funcrefs, pending helpers, locals) and
7
//! confirms that `restore` undoes the mutation.
8

            
9
use wasm_encoder::{Function, Instruction, ValType};
10

            
11
use super::CompileContext;
12
use crate::ast::{Expr, LambdaParams, WasmType};
13

            
14
#[test]
15
1
fn snapshot_restore_round_trips_struct_type_registration() {
16
1
    let mut ctx = CompileContext::new().unwrap();
17
1
    let snap = ctx.snapshot();
18
1
    let pre_count = ctx.type_count;
19

            
20
1
    let idx = ctx.register_struct_type(&[ValType::I32]).unwrap();
21
1
    assert_eq!(ctx.type_count, pre_count + 1);
22
1
    assert_eq!(idx, pre_count, "new struct gets the next type index");
23

            
24
1
    ctx.restore(snap);
25
1
    assert_eq!(ctx.type_count, pre_count);
26
1
}
27

            
28
#[test]
29
1
fn snapshot_restore_round_trips_function_registration() {
30
1
    let mut ctx = CompileContext::new().unwrap();
31
1
    let snap = ctx.snapshot();
32
1
    let pre_local_func_count = ctx.local_func_count;
33
1
    let pre_func_names = ctx.func_names.clone();
34

            
35
1
    ctx.register_function("trial_fn", &[ValType::I32], &[ValType::I32])
36
1
        .unwrap();
37
1
    assert_eq!(ctx.local_func_count, pre_local_func_count + 1);
38
1
    assert!(ctx.func_names.contains_key("trial_fn"));
39

            
40
1
    ctx.restore(snap);
41
1
    assert_eq!(ctx.local_func_count, pre_local_func_count);
42
1
    assert_eq!(ctx.func_names, pre_func_names);
43
1
}
44

            
45
#[test]
46
1
fn snapshot_restore_round_trips_data_segment() {
47
1
    let mut ctx = CompileContext::new().unwrap();
48
1
    let snap = ctx.snapshot();
49
1
    let pre_count = ctx.data_count;
50

            
51
1
    let _ = ctx.add_data(b"trial").unwrap();
52
1
    assert_eq!(ctx.data_count, pre_count + 1);
53

            
54
1
    ctx.restore(snap);
55
1
    assert_eq!(ctx.data_count, pre_count);
56
1
}
57

            
58
#[test]
59
1
fn snapshot_restore_round_trips_closure_signature() {
60
1
    let mut ctx = CompileContext::new().unwrap();
61
1
    let snap = ctx.snapshot();
62
1
    let pre_type_count = ctx.type_count;
63

            
64
1
    let _ = ctx
65
1
        .intern_closure_signature(&[WasmType::Ratio], WasmType::Ratio)
66
1
        .unwrap();
67
1
    assert!(ctx.type_count > pre_type_count);
68

            
69
1
    ctx.restore(snap);
70
1
    assert_eq!(ctx.type_count, pre_type_count);
71
1
    assert!(
72
1
        ctx.intern_closure_signature(&[WasmType::Ratio], WasmType::Ratio)
73
1
            .is_ok(),
74
        "interner must work post-restore"
75
    );
76
1
}
77

            
78
#[test]
79
1
fn snapshot_restore_truncates_pending_helpers() {
80
1
    let mut ctx = CompileContext::new().unwrap();
81
1
    let baseline = ctx.pending_helper_count();
82
1
    let snap = ctx.snapshot();
83

            
84
1
    let mut f = Function::new([]);
85
1
    f.instruction(&Instruction::I32Const(0));
86
1
    f.instruction(&Instruction::End);
87
1
    ctx.queue_helper(f);
88
1
    assert_eq!(ctx.pending_helper_count(), baseline + 1);
89

            
90
1
    ctx.restore(snap);
91
1
    assert_eq!(ctx.pending_helper_count(), baseline);
92
1
}
93

            
94
#[test]
95
1
fn snapshot_restore_round_trips_declared_funcrefs() {
96
1
    let mut ctx = CompileContext::new().unwrap();
97
1
    let snap = ctx.snapshot();
98
1
    let pre_len = ctx.declared_funcs.len();
99

            
100
1
    ctx.declare_funcref(42);
101
1
    ctx.declare_funcref(43);
102
1
    assert_eq!(ctx.declared_funcs.len(), pre_len + 2);
103

            
104
1
    ctx.restore(snap);
105
1
    assert_eq!(ctx.declared_funcs.len(), pre_len);
106
1
}
107

            
108
#[test]
109
1
fn snapshot_restore_round_trips_local_pool() {
110
1
    let mut ctx = CompileContext::new().unwrap();
111
1
    let snap = ctx.snapshot();
112
1
    let pre_next_local = ctx.next_local;
113

            
114
1
    let _ = ctx.alloc_local(WasmType::Ratio).unwrap();
115
1
    let _ = ctx.alloc_local(WasmType::I32).unwrap();
116
1
    assert_eq!(ctx.next_local, pre_next_local + 2);
117

            
118
1
    ctx.restore(snap);
119
1
    assert_eq!(ctx.next_local, pre_next_local);
120
1
    assert!(ctx.local_types.is_empty());
121
1
}
122

            
123
#[test]
124
1
fn snapshot_restore_drops_closure_bodies_recorded_in_trial() {
125
1
    let mut ctx = CompileContext::new().unwrap();
126
1
    let snap = ctx.snapshot();
127

            
128
1
    let idx = ctx.alloc_local(WasmType::I32).unwrap();
129
1
    ctx.record_closure_body(idx, LambdaParams::simple(vec!["x".to_string()]), Expr::Nil);
130
1
    assert!(ctx.closure_body(idx).is_some());
131

            
132
1
    ctx.restore(snap);
133
1
    assert!(
134
1
        ctx.closure_body(idx).is_none(),
135
        "a closure body recorded during a rolled-back trial emit must not survive"
136
    );
137
1
}
138

            
139
#[test]
140
1
fn finish_after_restore_produces_valid_wasm() {
141
    // The whole point of the round-trip: after a trial emit + restore
142
    // the context must still produce a valid wasm module — no leaked
143
    // type-section bytes, no orphaned function-section slots, no half-
144
    // queued helper.
145
1
    let mut ctx = CompileContext::new().unwrap();
146
1
    let snap = ctx.snapshot();
147

            
148
1
    ctx.register_struct_type(&[ValType::I32]).unwrap();
149
1
    ctx.register_function("doomed_fn", &[], &[ValType::I32])
150
1
        .unwrap();
151
1
    let _ = ctx.add_data(b"doomed").unwrap();
152
1
    let mut f = Function::new([]);
153
1
    f.instruction(&Instruction::I32Const(7));
154
1
    f.instruction(&Instruction::End);
155
1
    ctx.queue_helper(f);
156

            
157
1
    ctx.restore(snap);
158

            
159
1
    ctx.add_should_apply(CompileContext::default_should_apply(), usize::MAX);
160
1
    let mut process = Function::new([]);
161
1
    process.instruction(&Instruction::End);
162
1
    ctx.add_process(process);
163

            
164
1
    let bytes = ctx.finish();
165
1
    wasmparser::validate(&bytes).expect("module must validate after restore");
166
1
}