1
//! `$nomi_condition` struct + `$nomi_error` exception tag (Tier 3,
2
//! ADR-0026).
3
//!
4
//! A script raise (`(error 'code "msg")`) lowers to `struct.new
5
//! $nomi_condition` + `throw $nomi_error`; `(handler-case)` /
6
//! `(unwind-protect)` catch it via `try_table`, and a compiler-emitted
7
//! boundary wrapper around each host-invoked body catches any uncaught
8
//! raise and bridges it to the `__nomi_raise` host fn. The tag carries a
9
//! single `(ref null $nomi_condition)` payload so a `catch` delivers the
10
//! condition ref straight to the handler block.
11

            
12
use wasm_encoder::{BlockType, Catch, HeapType, RefType, TagKind, TagType, ValType};
13

            
14
use super::CompileContext;
15
use crate::ast::WasmType;
16
use crate::compiler::emit::FunctionEmitter;
17
use crate::error::Result;
18

            
19
impl CompileContext {
20
    /// Registers the `$nomi_condition` struct (`code`, `message` —
21
    /// both `(ref $i8_array)` strings) and the `$nomi_error` tag whose
22
    /// payload is `(ref null $nomi_condition)`. Records the tag index in
23
    /// `nomi_error_tag`. Called once from `new_skeleton`.
24
219162
    pub(super) fn register_exception_support(&mut self) -> Result<()> {
25
219162
        let string = self.string_ref();
26
219162
        let condition_idx = self.register_struct_type(&[string, string])?;
27
219162
        self.ids.ty_nomi_condition = condition_idx;
28
219162
        let payload = ValType::Ref(RefType {
29
219162
            nullable: true,
30
219162
            heap_type: HeapType::Concrete(condition_idx),
31
219162
        });
32
219162
        let sig = self.get_or_create_func_type(&[payload], &[])?;
33
219162
        self.tags.tag(TagType {
34
219162
            kind: TagKind::Exception,
35
219162
            func_type_idx: sig,
36
219162
        });
37
219162
        let tag_idx = self.tag_count;
38
219162
        self.tag_count = tag_idx
39
219162
            .checked_add(1)
40
219162
            .ok_or_else(|| crate::error::Error::Compile("wasm tag index space exhausted".into()))?;
41
219162
        self.nomi_error_tag = Some(tag_idx);
42
219162
        Ok(())
43
219162
    }
44

            
45
    /// Index of the `$nomi_error` exception tag. Panics only if called
46
    /// before `register_exception_support` (a compiler-internal ordering
47
    /// bug, not a script-reachable condition).
48
888029
    pub(in crate::compiler) fn nomi_error_tag(&self) -> u32 {
49
888029
        self.nomi_error_tag
50
888029
            .expect("nomi_error tag registered in new_skeleton")
51
888029
    }
52

            
53
    /// Type index of the `$nomi_condition` struct.
54
888029
    pub(in crate::compiler) fn condition_type_idx(&self) -> u32 {
55
888029
        self.ids.ty_nomi_condition
56
888029
    }
57

            
58
    /// Wraps a host-invoked body in the Tier 3 boundary `try_table` that
59
    /// catches an uncaught `$nomi_error`, reads its `code`/`message`, and
60
    /// bridges to `__nomi_raise` (ADR-0026). `body` emits the actual
61
    /// function body; it runs inside all three wrapper frames, so any
62
    /// `(return-from)` / `(go)` inside it sees the correct depth (the
63
    /// `try_table` helper bumps `block_depth`).
64
    ///
65
    /// Three nested frames (spike-validated, `wasm_exceptions_spike.rs`):
66
    /// ```text
67
    /// block $exit (result T?)
68
    ///   block $handler (result (ref null $nomi_condition))
69
    ///     try_table (result T?) (catch $nomi_error → $handler)
70
    ///       <body>
71
    ///     end
72
    ///     br $exit          ;; normal completion: carry T out, skip handler
73
    ///   end                 ;; catch lands here, condition ref on the stack
74
    ///   <code = struct.get 0; msg = struct.get 1; call __nomi_raise; unreachable>
75
    /// end
76
    /// ```
77
    /// `result_ty` is the body's result: `None` for `process` (void), `Some`
78
    /// for `should_apply` (i32) and `nomi-eval` (anyref). The void form
79
    /// drops the `(result T)` arity from `$exit` and the `try_table`.
80
221230
    pub(in crate::compiler) fn emit_boundary_wrapper(
81
221230
        &mut self,
82
221230
        emit: &mut FunctionEmitter,
83
221230
        result_ty: Option<WasmType>,
84
221230
        body: impl FnOnce(&mut Self, &mut FunctionEmitter) -> Result<()>,
85
221230
    ) -> Result<()> {
86
221230
        let condition_idx = self.condition_type_idx();
87
221230
        let cond_ref = ValType::Ref(RefType {
88
221230
            nullable: true,
89
221230
            heap_type: HeapType::Concrete(condition_idx),
90
221230
        });
91
221230
        let tag = self.nomi_error_tag();
92
221230
        let raise = self.ids.nomi_raise;
93
        // Scratch local holding the caught condition while the handler
94
        // reads both fields. Declared `anyref` (no `WasmType` variant for
95
        // a bare struct type); the handler `ref.cast`s back to
96
        // `$nomi_condition` before each `struct.get`. Allocated before the
97
        // body so the body's own locals stack above it.
98
221230
        let cond_local = self.alloc_local(WasmType::AnyRef)?;
99

            
100
221230
        match result_ty {
101
139137
            Some(ty) => {
102
139137
                let vt = self.wasm_val_type(ty);
103
139137
                emit.block_start_typed(BlockType::Result(vt));
104
139137
            }
105
82093
            None => emit.block_start(),
106
        }
107
221230
        emit.block_start_typed(BlockType::Result(cond_ref));
108
221230
        let try_ty = match result_ty {
109
139137
            Some(ty) => BlockType::Result(self.wasm_val_type(ty)),
110
82093
            None => BlockType::Empty,
111
        };
112
        // catch $nomi_error → `$handler` (the block immediately enclosing
113
        // the try_table, relative depth 0).
114
221230
        emit.try_table(try_ty, &[Catch::One { tag, label: 0 }]);
115

            
116
221230
        body(self, emit)?;
117

            
118
209327
        emit.block_end(); // close try_table
119
        // Normal completion: branch out to `$exit` (now relative depth 1),
120
        // carrying the body result past the handler tail.
121
209327
        emit.br(1);
122
209327
        emit.block_end(); // close $handler — the catch edge lands here
123

            
124
        // Handler: caught `(ref $nomi_condition)` on the stack. Stash it
125
        // (as anyref), then read both fields back via `ref.cast` and
126
        // bridge to `__nomi_raise(code, message)`; the call never returns.
127
209327
        emit.local_set(cond_local);
128
209327
        emit.local_get(cond_local);
129
209327
        emit.ref_cast(condition_idx);
130
209327
        emit.struct_get(condition_idx, 0); // code
131
209327
        emit.local_get(cond_local);
132
209327
        emit.ref_cast(condition_idx);
133
209327
        emit.struct_get(condition_idx, 1); // message
134
209327
        emit.call(raise);
135
209327
        emit.unreachable();
136

            
137
209327
        emit.block_end(); // close $exit
138
209327
        Ok(())
139
221230
    }
140
}