1
//! Guest-side i32-repr value helpers: range-checked i31 boxing of an index,
2
//! nil-as-null boxing of a bool, and index division / car-of-nil that throw
3
//! catchable `$nomi_error` conditions (ADR-0026) instead of silently wrapping
4
//! or trapping the engine.
5

            
6
use super::CompileContext;
7
use super::exception::ConditionTrap;
8
use crate::error::Result;
9
use wasm_encoder::{BlockType, Function, HeapType, Instruction, ValType};
10

            
11
impl CompileContext {
12
477238
    pub(super) fn declare_index_helpers(&mut self) -> Result<()> {
13
477238
        let anyref = self.anyref();
14
477238
        let pair = self.pair_ref();
15
477238
        let index = ValType::I32;
16
477238
        self.register_function("box_index", &[index], &[anyref])?;
17
477238
        self.register_function("box_bool", &[index], &[anyref])?;
18
477238
        self.register_function("bool_chain_to_anyref", &[pair], &[pair])?;
19
477238
        self.register_function("index_div", &[index, index], &[index])?;
20
477238
        self.register_function("index_rem", &[index, index], &[index])?;
21
477238
        self.register_function("car_of_nil", &[], &[])?;
22
477238
        Ok(())
23
477238
    }
24

            
25
    /// Emits the helper bodies in [`Self::declare_index_helpers`] order.
26
477238
    pub(super) fn build_index_helpers(&mut self) -> Result<()> {
27
477238
        let beyond_i31 = self.register_condition_trap(
28
477238
            "INDEX-OVERFLOW",
29
477238
            "index value outside the 31-bit range -1073741824..1073741823",
30
        )?;
31
477238
        let quotient_overflow = self.register_condition_trap(
32
477238
            "INDEX-OVERFLOW",
33
477238
            "index quotient -2147483648 / -1 overflows 32 bits",
34
        )?;
35
477238
        let div_by_zero = self.register_condition_trap("DIVISION-BY-ZERO", "division by zero")?;
36
477238
        let car_of_nil = self
37
477238
            .register_condition_trap("CAR-OF-NIL", "car of an empty numeric list has no value")?;
38
477238
        let chain = ChainIds {
39
477238
            pair_idx: self.ids.ty_pair,
40
477238
            pair_ref: self.pair_ref(),
41
477238
            pair_new: self.ids.pair_new,
42
477238
            box_bool: self.ids.box_bool,
43
477238
        };
44
477238
        self.pending_helpers.push(box_index_body(&beyond_i31));
45
477238
        self.pending_helpers.push(box_bool_body(self.anyref()));
46
477238
        self.pending_helpers.push(bool_chain_to_anyref_body(&chain));
47
477238
        self.pending_helpers
48
477238
            .push(index_div_body(&div_by_zero, &quotient_overflow));
49
477238
        self.pending_helpers.push(index_rem_body(&div_by_zero));
50
477238
        self.pending_helpers.push(throw_body(&car_of_nil));
51
477238
        Ok(())
52
477238
    }
53
}
54

            
55
struct ChainIds {
56
    pair_idx: u32,
57
    pair_ref: ValType,
58
    pair_new: u32,
59
    box_bool: u32,
60
}
61

            
62
/// `box_bool(i32) -> anyref`: in an anyref slot a false bool is `nil` as a null
63
/// ref (what `null?` tests) and a true bool is i31 1.
64
477238
fn box_bool_body(anyref: ValType) -> Function {
65
477238
    let mut f = Function::new([]);
66
477238
    f.instruction(&Instruction::LocalGet(0));
67
477238
    f.instruction(&Instruction::If(BlockType::Result(anyref)));
68
477238
    f.instruction(&Instruction::I32Const(1));
69
477238
    f.instruction(&Instruction::RefI31);
70
477238
    f.instruction(&Instruction::Else);
71
477238
    f.instruction(&Instruction::RefNull(HeapType::ANY));
72
477238
    f.instruction(&Instruction::End);
73
477238
    f.instruction(&Instruction::End);
74
477238
    f
75
477238
}
76

            
77
/// `bool_chain_to_anyref(pair) -> pair`: rebuilds a bool chain (i31 0/1 cells)
78
/// with anyref cells so its nils become null refs before it rides a widened
79
/// `AnyRef` chain. Two prepend passes keep the original order.
80
477238
fn bool_chain_to_anyref_body(chain: &ChainIds) -> Function {
81
    const SOURCE: u32 = 0;
82
    const REVERSED: u32 = 1;
83
    const REBUILT: u32 = 2;
84
477238
    let mut f = Function::new([(2, chain.pair_ref)]);
85
477238
    let rebox = [
86
477238
        Instruction::RefCastNonNull(HeapType::I31),
87
477238
        Instruction::I31GetS,
88
477238
        Instruction::Call(chain.box_bool),
89
477238
    ];
90
477238
    prepend_chain(&mut f, chain, SOURCE, REVERSED, &rebox);
91
477238
    prepend_chain(&mut f, chain, REVERSED, REBUILT, &[]);
92
477238
    f.instruction(&Instruction::LocalGet(REBUILT));
93
477238
    f.instruction(&Instruction::End);
94
477238
    f
95
477238
}
96

            
97
/// Prepends every car of the chain in local `src` (passed through `map_car`)
98
/// onto a fresh chain in local `dst`, reversing it.
99
954476
fn prepend_chain(
100
954476
    f: &mut Function,
101
954476
    chain: &ChainIds,
102
954476
    src: u32,
103
954476
    dst: u32,
104
954476
    map_car: &[Instruction<'_>],
105
954476
) {
106
954476
    f.instruction(&Instruction::RefNull(HeapType::Concrete(chain.pair_idx)));
107
954476
    f.instruction(&Instruction::LocalSet(dst));
108
954476
    f.instruction(&Instruction::Block(BlockType::Empty));
109
954476
    f.instruction(&Instruction::Loop(BlockType::Empty));
110
954476
    f.instruction(&Instruction::LocalGet(src));
111
954476
    f.instruction(&Instruction::RefIsNull);
112
954476
    f.instruction(&Instruction::BrIf(1));
113
954476
    f.instruction(&Instruction::LocalGet(src));
114
954476
    f.instruction(&Instruction::StructGet {
115
954476
        struct_type_index: chain.pair_idx,
116
954476
        field_index: 0,
117
954476
    });
118
1431714
    for op in map_car {
119
1431714
        f.instruction(op);
120
1431714
    }
121
954476
    f.instruction(&Instruction::LocalGet(dst));
122
954476
    f.instruction(&Instruction::Call(chain.pair_new));
123
954476
    f.instruction(&Instruction::LocalSet(dst));
124
954476
    f.instruction(&Instruction::LocalGet(src));
125
954476
    f.instruction(&Instruction::StructGet {
126
954476
        struct_type_index: chain.pair_idx,
127
954476
        field_index: 1,
128
954476
    });
129
954476
    f.instruction(&Instruction::LocalSet(src));
130
954476
    f.instruction(&Instruction::Br(0));
131
954476
    f.instruction(&Instruction::End);
132
954476
    f.instruction(&Instruction::End);
133
954476
}
134

            
135
/// A no-result helper that only throws `trap` (e.g. `car_of_nil`); callers
136
/// follow the call with `unreachable`.
137
477238
fn throw_body(trap: &ConditionTrap) -> Function {
138
477238
    let mut f = Function::new([]);
139
477238
    trap.emit_throw(&mut f);
140
477238
    f.instruction(&Instruction::End);
141
477238
    f
142
477238
}
143

            
144
/// `box_index(i32) -> anyref`: `ref.i31` keeps only 31 bits, so a value outside
145
/// [-2^30, 2^30-1] throws rather than wrapping.
146
477238
fn box_index_body(beyond_i31: &ConditionTrap) -> Function {
147
477238
    let mut f = Function::new([]);
148
    // v + 2^30 lands in [0, 2^31) exactly when v is in the i31 range.
149
477238
    f.instruction(&Instruction::LocalGet(0));
150
477238
    f.instruction(&Instruction::I32Const(1 << 30));
151
477238
    f.instruction(&Instruction::I32Add);
152
477238
    f.instruction(&Instruction::I32Const(i32::MIN));
153
477238
    f.instruction(&Instruction::I32GeU);
154
477238
    throw_if(&mut f, beyond_i31);
155
477238
    f.instruction(&Instruction::LocalGet(0));
156
477238
    f.instruction(&Instruction::RefI31);
157
477238
    f.instruction(&Instruction::End);
158
477238
    f
159
477238
}
160

            
161
/// `index_div(a, b) -> i32`: truncating division that throws on a zero divisor
162
/// and on `i32::MIN / -1`, the one quotient that overflows.
163
477238
fn index_div_body(div_by_zero: &ConditionTrap, overflow: &ConditionTrap) -> Function {
164
477238
    let mut f = Function::new([]);
165
477238
    throw_on_zero_divisor(&mut f, div_by_zero);
166
477238
    f.instruction(&Instruction::LocalGet(0));
167
477238
    f.instruction(&Instruction::I32Const(i32::MIN));
168
477238
    f.instruction(&Instruction::I32Eq);
169
477238
    f.instruction(&Instruction::LocalGet(1));
170
477238
    f.instruction(&Instruction::I32Const(-1));
171
477238
    f.instruction(&Instruction::I32Eq);
172
477238
    f.instruction(&Instruction::I32And);
173
477238
    throw_if(&mut f, overflow);
174
477238
    finish_binop(f, &Instruction::I32DivS)
175
477238
}
176

            
177
/// `index_rem(a, b) -> i32`: throws on a zero divisor (`i32.rem_s` defines
178
/// `i32::MIN rem -1` as 0, so no overflow check is needed).
179
477238
fn index_rem_body(div_by_zero: &ConditionTrap) -> Function {
180
477238
    let mut f = Function::new([]);
181
477238
    throw_on_zero_divisor(&mut f, div_by_zero);
182
477238
    finish_binop(f, &Instruction::I32RemS)
183
477238
}
184

            
185
954476
fn throw_on_zero_divisor(f: &mut Function, div_by_zero: &ConditionTrap) {
186
954476
    f.instruction(&Instruction::LocalGet(1));
187
954476
    f.instruction(&Instruction::I32Eqz);
188
954476
    throw_if(f, div_by_zero);
189
954476
}
190

            
191
/// Consumes the i32 condition on the stack, throwing `trap` when it is set.
192
1908952
fn throw_if(f: &mut Function, trap: &ConditionTrap) {
193
1908952
    f.instruction(&Instruction::If(BlockType::Empty));
194
1908952
    trap.emit_throw(f);
195
1908952
    f.instruction(&Instruction::End);
196
1908952
}
197

            
198
954476
fn finish_binop(mut f: Function, op: &Instruction<'_>) -> Function {
199
954476
    f.instruction(&Instruction::LocalGet(0));
200
954476
    f.instruction(&Instruction::LocalGet(1));
201
954476
    f.instruction(op);
202
954476
    f.instruction(&Instruction::End);
203
954476
    f
204
954476
}