1
// Skipped under Miri: these tests compile+run wasm via wasmtime, whose
2
// Cranelift backend refuses to run under Miri.
3
#![cfg(not(miri))]
4

            
5
//! Runtime exercise of BLOCK / RETURN-FROM and TAGBODY / GO **inside the
6
//! Tier 3.2 boundary wrapper**. The wrapper adds three control frames
7
//! (`block $exit` / `block $handler` / `try_table`) around every
8
//! host-invoked body, so every lexical `br` target a labelled-exit form
9
//! computes off `block_depth()` must still resolve correctly through the
10
//! +3 offset. Codegen tests (`block_forms.rs`, `tagbody_forms.rs`) prove
11
//! the modules *validate*; these prove they *evaluate to the right value*
12
//! at runtime — an off-by-one `br` target that still type-checks would be
13
//! caught here but not by validation alone.
14
//!
15
//! `Interpreter::eval` runs through the wrapped `nomi-eval` (anyref) body;
16
//! `compile_to_wasm` + `run_wasm` runs through the wrapped `process` /
17
//! `should_apply` (void / i32) entry bodies.
18

            
19
use nms::interpreter::Interpreter;
20
use scripting::nomiscript::{Fraction, Value};
21

            
22
9
fn eval_one(src: &str) -> Value {
23
9
    let mut interp = Interpreter::new(false).unwrap();
24
9
    let results = interp
25
9
        .eval(src)
26
9
        .unwrap_or_else(|e| panic!("eval {src:?}: {e}"));
27
9
    results
28
9
        .into_iter()
29
9
        .next_back()
30
9
        .unwrap_or_else(|| panic!("eval {src:?} produced no value"))
31
9
}
32

            
33
11
fn n(v: i64) -> Value {
34
11
    Value::Number(Fraction::from_integer(v))
35
11
}
36

            
37
#[test]
38
1
fn return_from_resolves_through_eval_boundary_wrapper() {
39
1
    assert_eq!(eval_one("(block done (return-from done 7) 99)"), n(7));
40
1
}
41

            
42
#[test]
43
1
fn return_from_inside_if_resolves_through_wrapper() {
44
1
    assert_eq!(
45
1
        eval_one("(block done (if (= 1 1) (return-from done 7) 0))"),
46
1
        n(7)
47
    );
48
1
}
49

            
50
#[test]
51
1
fn nested_block_return_from_outer_skips_inner_remainder() {
52
1
    assert_eq!(
53
1
        eval_one("(block outer (block inner (return-from outer 7)) 99)"),
54
1
        n(7)
55
    );
56
1
}
57

            
58
#[test]
59
1
fn block_falls_through_to_tail_when_no_return_from() {
60
1
    assert_eq!(eval_one("(block done 1 2 3)"), n(3));
61
1
}
62

            
63
#[test]
64
1
fn block_dead_tail_after_return_from_yields_the_exit_value() {
65
    // The `99` tail is dead code after an unconditional `(return-from)`.
66
    // The block must return the exit value `7`, and the dead tail's
67
    // `unreachable`-sealed value must not corrupt the result at runtime.
68
1
    assert_eq!(eval_one("(block done (return-from done 7) 99)"), n(7));
69
1
}
70

            
71
#[test]
72
1
fn block_effectful_if_test_runs_exactly_once() {
73
    // BLOCK's emit-time exit discovery classifies divergence on a CLONED
74
    // symbol table, so the `(setf n (+ n 1))` in the IF test is NOT
75
    // double-evaluated. Result must be 1 (one increment), not 2.
76
1
    assert_eq!(
77
1
        eval_one("(let* ((n 0)) (block b (if (begin (setf n (+ n 1)) #t) n 0)))"),
78
1
        n(1)
79
    );
80
1
}
81

            
82
#[test]
83
1
fn block_effectful_test_in_non_tail_diverging_form_runs_once() {
84
    // The non-tail `(if … (return-from b n) 0)` diverges conditionally;
85
    // its effectful test must run once even though BLOCK queries
86
    // divergence before compiling. The `99` tail is dead.
87
1
    assert_eq!(
88
1
        eval_one(
89
1
            "(let* ((n 0)) (block b (if (begin (setf n (+ n 1)) #t) (return-from b n) 0) 99))"
90
        ),
91
1
        n(1)
92
    );
93
1
}
94

            
95
#[test]
96
1
fn tagbody_go_loop_resolves_through_wrapper() {
97
    // GO emits `br $tagbody_loop`; its relative depth is computed off
98
    // block_depth() and must clear the +3 wrapper frames.
99
1
    assert_eq!(
100
1
        eval_one("(let* ((x 0)) (tagbody loop (setf x (+ x 1)) (when (< x 3) (go loop))) x)"),
101
1
        n(3)
102
    );
103
1
}
104

            
105
#[test]
106
1
fn tagbody_forward_go_skips_intervening_body() {
107
1
    assert_eq!(
108
1
        eval_one("(let* ((x 1)) (tagbody (go skip) (setf x 99) skip) x)"),
109
1
        n(1)
110
    );
111
1
}
112

            
113
#[test]
114
1
fn return_from_resolves_through_process_entry_wrapper() {
115
    // compile_to_wasm + run_wasm drives the wrapped `process` (void) and
116
    // `should_apply` (i32) bodies, a different wrapper-arity pair than the
117
    // eval path above.
118
1
    let mut interp = Interpreter::new(false).unwrap();
119
1
    let wasm = interp
120
1
        .compile_to_wasm("(block done (return-from done 7) 99)")
121
1
        .unwrap();
122
1
    assert_eq!(interp.run_wasm(&wasm).unwrap(), n(7));
123
1
}
124

            
125
#[test]
126
1
fn tagbody_go_resolves_through_process_entry_wrapper() {
127
1
    let mut interp = Interpreter::new(false).unwrap();
128
1
    let wasm = interp
129
1
        .compile_to_wasm(
130
1
            "(let* ((x 0)) (tagbody loop (setf x (+ x 1)) (when (< x 3) (go loop))) x)",
131
        )
132
1
        .unwrap();
133
1
    assert_eq!(interp.run_wasm(&wasm).unwrap(), n(3));
134
1
}