1
//! Guest-side helpers for the Ratio type — the integer-fraction
2
//! arithmetic kernel every numeric path bottoms out in.
3
//!
4
//! `ratio_new` reduces by GCD and normalizes the sign so equality
5
//! checks are byte-comparable. The binop bodies use the textbook
6
//! cross-multiply formulas; comparisons compare signed cross-products.
7
//! All helpers are guest-side wasm functions registered up-front so
8
//! the codegen can `(call $ratio_*)` against stable function indices.
9

            
10
use super::CompileContext;
11
use crate::error::Result;
12
use wasm_encoder::{BlockType, Function, Instruction, ValType};
13

            
14
/// Captured indices + interned `division-by-zero` condition strings the
15
/// `ratio_div` guard needs to `throw` a catchable `$nomi_error` from a body
16
/// that only borrows `&self`. Mirrors `commodity::MismatchTrap`.
17
struct DivByZeroTrap {
18
    i8_array_idx: u32,
19
    condition_idx: u32,
20
    tag: u32,
21
    code_data: u32,
22
    code_len: u32,
23
    message_data: u32,
24
    message_len: u32,
25
}
26

            
27
impl CompileContext {
28
    /// Declares the ratio helper signatures (no bodies). Runs before
29
    /// `resolve_ids` so every index exists when `WasmIds` is assembled; the
30
    /// bodies are emitted later by [`Self::build_ratio_helpers`].
31
219161
    pub(super) fn declare_ratio_helpers(&mut self) -> Result<()> {
32
219161
        let ratio_ref = self.ratio_ref();
33
219161
        self.register_function("gcd", &[ValType::I64, ValType::I64], &[ValType::I64])?;
34
219161
        self.register_function("ratio_new", &[ValType::I64, ValType::I64], &[ratio_ref])?;
35
219161
        self.register_function("ratio_add", &[ratio_ref, ratio_ref], &[ratio_ref])?;
36
219161
        self.register_function("ratio_sub", &[ratio_ref, ratio_ref], &[ratio_ref])?;
37
219161
        self.register_function("ratio_mul", &[ratio_ref, ratio_ref], &[ratio_ref])?;
38
219161
        self.register_function("ratio_div", &[ratio_ref, ratio_ref], &[ratio_ref])?;
39
219161
        self.register_function("ratio_eq", &[ratio_ref, ratio_ref], &[ValType::I32])?;
40
219161
        self.register_function("ratio_lt", &[ratio_ref, ratio_ref], &[ValType::I32])?;
41
219161
        self.register_function("ratio_from_i64", &[ValType::I64], &[ratio_ref])?;
42
219161
        self.register_function("ratio_to_i64", &[ratio_ref], &[ValType::I64])?;
43
219161
        Ok(())
44
219161
    }
45

            
46
    /// Emits the ratio helper bodies, in registration order so each maps to its
47
    /// reserved function index. Reads resolved indices from `self.ids`; the
48
    /// `division-by-zero` trap interns its strings here (in build order, so the
49
    /// data-segment index sequence is unchanged from the pre-split layout).
50
219161
    pub(super) fn build_ratio_helpers(&mut self) -> Result<()> {
51
219161
        let div_trap = self.register_div_by_zero_trap()?;
52
219161
        self.build_gcd_body();
53
219161
        self.build_ratio_new_body(&div_trap);
54
219161
        self.build_ratio_binop_bodies();
55
219161
        self.build_ratio_cmp_bodies();
56
219161
        self.build_ratio_from_i64_body();
57
219161
        self.build_ratio_to_i64_body();
58
219161
        Ok(())
59
219161
    }
60

            
61
    /// Interns the `division-by-zero` condition strings as passive data so the
62
    /// `ratio_div` guard can `struct.new` + `throw` against stable indices.
63
219161
    fn register_div_by_zero_trap(&mut self) -> Result<DivByZeroTrap> {
64
        // UPPER-CASE matches the reader's symbol case-folding so a script can
65
        // catch it with `(handler-case … (division-by-zero (e) …))`.
66
        const CODE: &str = "DIVISION-BY-ZERO";
67
        const MESSAGE: &str = "division by zero";
68
219161
        let code_data = self.add_data(CODE.as_bytes())?;
69
219161
        let message_data = self.add_data(MESSAGE.as_bytes())?;
70
219161
        Ok(DivByZeroTrap {
71
219161
            i8_array_idx: self.ids.ty_i8_array,
72
219161
            condition_idx: self.condition_type_idx(),
73
219161
            tag: self.nomi_error_tag(),
74
219161
            code_data,
75
219161
            code_len: CODE.len() as u32,
76
219161
            message_data,
77
219161
            message_len: MESSAGE.len() as u32,
78
219161
        })
79
219161
    }
80

            
81
219161
    fn build_gcd_body(&mut self) {
82
        // Euclidean GCD: gcd(a, b) while b != 0 { t = b; b = a % b; a = t }; abs(a)
83
219161
        let mut f = Function::new([(1, ValType::I64)]); // local $t
84
        // params: $a=0, $b=1, local: $t=2
85
219161
        f.instruction(&Instruction::Block(wasm_encoder::BlockType::Empty));
86
219161
        f.instruction(&Instruction::Loop(wasm_encoder::BlockType::Empty));
87
        // if b == 0, break
88
219161
        f.instruction(&Instruction::LocalGet(1));
89
219161
        f.instruction(&Instruction::I64Eqz);
90
219161
        f.instruction(&Instruction::BrIf(1));
91
        // t = b
92
219161
        f.instruction(&Instruction::LocalGet(1));
93
219161
        f.instruction(&Instruction::LocalSet(2));
94
        // b = a % b
95
219161
        f.instruction(&Instruction::LocalGet(0));
96
219161
        f.instruction(&Instruction::LocalGet(1));
97
219161
        f.instruction(&Instruction::I64RemS);
98
219161
        f.instruction(&Instruction::LocalSet(1));
99
        // a = t
100
219161
        f.instruction(&Instruction::LocalGet(2));
101
219161
        f.instruction(&Instruction::LocalSet(0));
102
219161
        f.instruction(&Instruction::Br(0));
103
219161
        f.instruction(&Instruction::End); // loop
104
219161
        f.instruction(&Instruction::End); // block
105
        // return abs(a)
106
219161
        f.instruction(&Instruction::LocalGet(0));
107
219161
        f.instruction(&Instruction::I64Const(0));
108
219161
        f.instruction(&Instruction::LocalGet(0));
109
219161
        f.instruction(&Instruction::I64Sub);
110
219161
        f.instruction(&Instruction::LocalGet(0));
111
219161
        f.instruction(&Instruction::I64Const(0));
112
219161
        f.instruction(&Instruction::I64LtS);
113
219161
        f.instruction(&Instruction::Select);
114
219161
        f.instruction(&Instruction::End);
115
219161
        self.pending_helpers.push(f);
116
219161
    }
117

            
118
219161
    fn build_ratio_new_body(&mut self, div_trap: &DivByZeroTrap) {
119
219161
        let ratio_idx = self.ids.ty_ratio;
120
219161
        let gcd_func = self.ids.gcd;
121
        // ratio_new(num, denom) -> reduce by GCD, normalize sign
122
        // params: $num=0, $denom=1, locals: $g=2
123
219161
        let mut f = Function::new([(1, ValType::I64)]);
124
        // denom == 0 → throw a catchable `division-by-zero` rather than build a
125
        // degenerate ratio (or trap in `gcd(0,0)` / a later `i64.div_s`). This
126
        // is the single chokepoint for ALL ratio construction: a runtime zero
127
        // divisor (via `ratio_div`) AND an `i64.mul` denominator overflow that
128
        // wraps to zero in add/sub/mul both land here.
129
219161
        f.instruction(&Instruction::LocalGet(1));
130
219161
        f.instruction(&Instruction::I64Eqz);
131
219161
        f.instruction(&Instruction::If(BlockType::Empty));
132
219161
        Self::emit_div_by_zero_throw(&mut f, div_trap);
133
219161
        f.instruction(&Instruction::End);
134
        // g = gcd(num, denom)
135
219161
        f.instruction(&Instruction::LocalGet(0));
136
219161
        f.instruction(&Instruction::LocalGet(1));
137
219161
        f.instruction(&Instruction::Call(gcd_func));
138
219161
        f.instruction(&Instruction::LocalSet(2));
139
        // num = num / g
140
219161
        f.instruction(&Instruction::LocalGet(0));
141
219161
        f.instruction(&Instruction::LocalGet(2));
142
219161
        f.instruction(&Instruction::I64DivS);
143
219161
        f.instruction(&Instruction::LocalSet(0));
144
        // denom = denom / g
145
219161
        f.instruction(&Instruction::LocalGet(1));
146
219161
        f.instruction(&Instruction::LocalGet(2));
147
219161
        f.instruction(&Instruction::I64DivS);
148
219161
        f.instruction(&Instruction::LocalSet(1));
149
        // if denom < 0 { num = -num; denom = -denom }
150
219161
        f.instruction(&Instruction::LocalGet(1));
151
219161
        f.instruction(&Instruction::I64Const(0));
152
219161
        f.instruction(&Instruction::I64LtS);
153
219161
        f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
154
219161
        f.instruction(&Instruction::I64Const(0));
155
219161
        f.instruction(&Instruction::LocalGet(0));
156
219161
        f.instruction(&Instruction::I64Sub);
157
219161
        f.instruction(&Instruction::LocalSet(0));
158
219161
        f.instruction(&Instruction::I64Const(0));
159
219161
        f.instruction(&Instruction::LocalGet(1));
160
219161
        f.instruction(&Instruction::I64Sub);
161
219161
        f.instruction(&Instruction::LocalSet(1));
162
219161
        f.instruction(&Instruction::End);
163
        // struct.new $ratio (num, denom)
164
219161
        f.instruction(&Instruction::LocalGet(0));
165
219161
        f.instruction(&Instruction::LocalGet(1));
166
219161
        f.instruction(&Instruction::StructNew(ratio_idx));
167
219161
        f.instruction(&Instruction::End);
168
219161
        self.pending_helpers.push(f);
169
219161
    }
170

            
171
219161
    fn build_ratio_binop_bodies(&mut self) {
172
219161
        let ratio_idx = self.ids.ty_ratio;
173
219161
        let ratio_new = self.ids.ratio_new;
174

            
175
        // ratio_add(a, b): (a.num * b.denom + b.num * a.denom, a.denom * b.denom)
176
219161
        let mut f = Function::new([]);
177
        // a.num * b.denom
178
219161
        f.instruction(&Instruction::LocalGet(0));
179
219161
        f.instruction(&Instruction::StructGet {
180
219161
            struct_type_index: ratio_idx,
181
219161
            field_index: 0,
182
219161
        });
183
219161
        f.instruction(&Instruction::LocalGet(1));
184
219161
        f.instruction(&Instruction::StructGet {
185
219161
            struct_type_index: ratio_idx,
186
219161
            field_index: 1,
187
219161
        });
188
219161
        f.instruction(&Instruction::I64Mul);
189
        // b.num * a.denom
190
219161
        f.instruction(&Instruction::LocalGet(1));
191
219161
        f.instruction(&Instruction::StructGet {
192
219161
            struct_type_index: ratio_idx,
193
219161
            field_index: 0,
194
219161
        });
195
219161
        f.instruction(&Instruction::LocalGet(0));
196
219161
        f.instruction(&Instruction::StructGet {
197
219161
            struct_type_index: ratio_idx,
198
219161
            field_index: 1,
199
219161
        });
200
219161
        f.instruction(&Instruction::I64Mul);
201
219161
        f.instruction(&Instruction::I64Add);
202
        // a.denom * b.denom
203
219161
        f.instruction(&Instruction::LocalGet(0));
204
219161
        f.instruction(&Instruction::StructGet {
205
219161
            struct_type_index: ratio_idx,
206
219161
            field_index: 1,
207
219161
        });
208
219161
        f.instruction(&Instruction::LocalGet(1));
209
219161
        f.instruction(&Instruction::StructGet {
210
219161
            struct_type_index: ratio_idx,
211
219161
            field_index: 1,
212
219161
        });
213
219161
        f.instruction(&Instruction::I64Mul);
214
219161
        f.instruction(&Instruction::Call(ratio_new));
215
219161
        f.instruction(&Instruction::End);
216
219161
        self.pending_helpers.push(f);
217

            
218
        // ratio_sub(a, b): (a.num * b.denom - b.num * a.denom, a.denom * b.denom)
219
219161
        let mut f = Function::new([]);
220
219161
        f.instruction(&Instruction::LocalGet(0));
221
219161
        f.instruction(&Instruction::StructGet {
222
219161
            struct_type_index: ratio_idx,
223
219161
            field_index: 0,
224
219161
        });
225
219161
        f.instruction(&Instruction::LocalGet(1));
226
219161
        f.instruction(&Instruction::StructGet {
227
219161
            struct_type_index: ratio_idx,
228
219161
            field_index: 1,
229
219161
        });
230
219161
        f.instruction(&Instruction::I64Mul);
231
219161
        f.instruction(&Instruction::LocalGet(1));
232
219161
        f.instruction(&Instruction::StructGet {
233
219161
            struct_type_index: ratio_idx,
234
219161
            field_index: 0,
235
219161
        });
236
219161
        f.instruction(&Instruction::LocalGet(0));
237
219161
        f.instruction(&Instruction::StructGet {
238
219161
            struct_type_index: ratio_idx,
239
219161
            field_index: 1,
240
219161
        });
241
219161
        f.instruction(&Instruction::I64Mul);
242
219161
        f.instruction(&Instruction::I64Sub);
243
219161
        f.instruction(&Instruction::LocalGet(0));
244
219161
        f.instruction(&Instruction::StructGet {
245
219161
            struct_type_index: ratio_idx,
246
219161
            field_index: 1,
247
219161
        });
248
219161
        f.instruction(&Instruction::LocalGet(1));
249
219161
        f.instruction(&Instruction::StructGet {
250
219161
            struct_type_index: ratio_idx,
251
219161
            field_index: 1,
252
219161
        });
253
219161
        f.instruction(&Instruction::I64Mul);
254
219161
        f.instruction(&Instruction::Call(ratio_new));
255
219161
        f.instruction(&Instruction::End);
256
219161
        self.pending_helpers.push(f);
257

            
258
        // ratio_mul(a, b): (a.num * b.num, a.denom * b.denom)
259
219161
        let mut f = Function::new([]);
260
219161
        f.instruction(&Instruction::LocalGet(0));
261
219161
        f.instruction(&Instruction::StructGet {
262
219161
            struct_type_index: ratio_idx,
263
219161
            field_index: 0,
264
219161
        });
265
219161
        f.instruction(&Instruction::LocalGet(1));
266
219161
        f.instruction(&Instruction::StructGet {
267
219161
            struct_type_index: ratio_idx,
268
219161
            field_index: 0,
269
219161
        });
270
219161
        f.instruction(&Instruction::I64Mul);
271
219161
        f.instruction(&Instruction::LocalGet(0));
272
219161
        f.instruction(&Instruction::StructGet {
273
219161
            struct_type_index: ratio_idx,
274
219161
            field_index: 1,
275
219161
        });
276
219161
        f.instruction(&Instruction::LocalGet(1));
277
219161
        f.instruction(&Instruction::StructGet {
278
219161
            struct_type_index: ratio_idx,
279
219161
            field_index: 1,
280
219161
        });
281
219161
        f.instruction(&Instruction::I64Mul);
282
219161
        f.instruction(&Instruction::Call(ratio_new));
283
219161
        f.instruction(&Instruction::End);
284
219161
        self.pending_helpers.push(f);
285

            
286
        // ratio_div(a, b): (a.num * b.denom, a.denom * b.num). A zero divisor
287
        // makes `a.denom * b.num` zero, caught by `ratio_new`'s denom-0 guard.
288
219161
        let mut f = Function::new([]);
289
219161
        f.instruction(&Instruction::LocalGet(0));
290
219161
        f.instruction(&Instruction::StructGet {
291
219161
            struct_type_index: ratio_idx,
292
219161
            field_index: 0,
293
219161
        });
294
219161
        f.instruction(&Instruction::LocalGet(1));
295
219161
        f.instruction(&Instruction::StructGet {
296
219161
            struct_type_index: ratio_idx,
297
219161
            field_index: 1,
298
219161
        });
299
219161
        f.instruction(&Instruction::I64Mul);
300
219161
        f.instruction(&Instruction::LocalGet(0));
301
219161
        f.instruction(&Instruction::StructGet {
302
219161
            struct_type_index: ratio_idx,
303
219161
            field_index: 1,
304
219161
        });
305
219161
        f.instruction(&Instruction::LocalGet(1));
306
219161
        f.instruction(&Instruction::StructGet {
307
219161
            struct_type_index: ratio_idx,
308
219161
            field_index: 0,
309
219161
        });
310
219161
        f.instruction(&Instruction::I64Mul);
311
219161
        f.instruction(&Instruction::Call(ratio_new));
312
219161
        f.instruction(&Instruction::End);
313
219161
        self.pending_helpers.push(f);
314
219161
    }
315

            
316
219161
    fn build_ratio_cmp_bodies(&mut self) {
317
219161
        let ratio_idx = self.ids.ty_ratio;
318

            
319
        // ratio_eq(a, b): a.num * b.denom == b.num * a.denom
320
219161
        let mut f = Function::new([]);
321
219161
        f.instruction(&Instruction::LocalGet(0));
322
219161
        f.instruction(&Instruction::StructGet {
323
219161
            struct_type_index: ratio_idx,
324
219161
            field_index: 0,
325
219161
        });
326
219161
        f.instruction(&Instruction::LocalGet(1));
327
219161
        f.instruction(&Instruction::StructGet {
328
219161
            struct_type_index: ratio_idx,
329
219161
            field_index: 1,
330
219161
        });
331
219161
        f.instruction(&Instruction::I64Mul);
332
219161
        f.instruction(&Instruction::LocalGet(1));
333
219161
        f.instruction(&Instruction::StructGet {
334
219161
            struct_type_index: ratio_idx,
335
219161
            field_index: 0,
336
219161
        });
337
219161
        f.instruction(&Instruction::LocalGet(0));
338
219161
        f.instruction(&Instruction::StructGet {
339
219161
            struct_type_index: ratio_idx,
340
219161
            field_index: 1,
341
219161
        });
342
219161
        f.instruction(&Instruction::I64Mul);
343
219161
        f.instruction(&Instruction::I64Eq);
344
219161
        f.instruction(&Instruction::End);
345
219161
        self.pending_helpers.push(f);
346

            
347
        // ratio_lt(a, b): a.num * b.denom < b.num * a.denom
348
219161
        let mut f = Function::new([]);
349
219161
        f.instruction(&Instruction::LocalGet(0));
350
219161
        f.instruction(&Instruction::StructGet {
351
219161
            struct_type_index: ratio_idx,
352
219161
            field_index: 0,
353
219161
        });
354
219161
        f.instruction(&Instruction::LocalGet(1));
355
219161
        f.instruction(&Instruction::StructGet {
356
219161
            struct_type_index: ratio_idx,
357
219161
            field_index: 1,
358
219161
        });
359
219161
        f.instruction(&Instruction::I64Mul);
360
219161
        f.instruction(&Instruction::LocalGet(1));
361
219161
        f.instruction(&Instruction::StructGet {
362
219161
            struct_type_index: ratio_idx,
363
219161
            field_index: 0,
364
219161
        });
365
219161
        f.instruction(&Instruction::LocalGet(0));
366
219161
        f.instruction(&Instruction::StructGet {
367
219161
            struct_type_index: ratio_idx,
368
219161
            field_index: 1,
369
219161
        });
370
219161
        f.instruction(&Instruction::I64Mul);
371
219161
        f.instruction(&Instruction::I64LtS);
372
219161
        f.instruction(&Instruction::End);
373
219161
        self.pending_helpers.push(f);
374
219161
    }
375

            
376
219161
    fn build_ratio_from_i64_body(&mut self) {
377
219161
        let ratio_idx = self.ids.ty_ratio;
378
        // ratio_from_i64(n) -> struct.new $ratio (n, 1)
379
219161
        let mut f = Function::new([]);
380
219161
        f.instruction(&Instruction::LocalGet(0));
381
219161
        f.instruction(&Instruction::I64Const(1));
382
219161
        f.instruction(&Instruction::StructNew(ratio_idx));
383
219161
        f.instruction(&Instruction::End);
384
219161
        self.pending_helpers.push(f);
385
219161
    }
386

            
387
    /// Builds the `division-by-zero` `$nomi_condition` (interned code + message)
388
    /// and `throw`s `$nomi_error`. `throw` never returns, so the body's declared
389
    /// ratio result is satisfied by wasm stack-polymorphism past the throw.
390
219161
    fn emit_div_by_zero_throw(f: &mut Function, trap: &DivByZeroTrap) {
391
219161
        f.instruction(&Instruction::I32Const(0));
392
219161
        f.instruction(&Instruction::I32Const(trap.code_len as i32));
393
219161
        f.instruction(&Instruction::ArrayNewData {
394
219161
            array_type_index: trap.i8_array_idx,
395
219161
            array_data_index: trap.code_data,
396
219161
        });
397
219161
        f.instruction(&Instruction::I32Const(0));
398
219161
        f.instruction(&Instruction::I32Const(trap.message_len as i32));
399
219161
        f.instruction(&Instruction::ArrayNewData {
400
219161
            array_type_index: trap.i8_array_idx,
401
219161
            array_data_index: trap.message_data,
402
219161
        });
403
219161
        f.instruction(&Instruction::StructNew(trap.condition_idx));
404
219161
        f.instruction(&Instruction::Throw(trap.tag));
405
219161
    }
406

            
407
219161
    fn build_ratio_to_i64_body(&mut self) {
408
219161
        let ratio_idx = self.ids.ty_ratio;
409
        // ratio_to_i64(r) -> r.numer / r.denom  (i64.div_s truncates toward zero,
410
        // matching ADR-0028's scalar->index narrowing). denom is never 0: the
411
        // only path that could build a denom-0 ratio is division by a zero
412
        // divisor, which `ratio_div` now throws on — so no guard is needed here.
413
219161
        let mut f = Function::new([]);
414
219161
        f.instruction(&Instruction::LocalGet(0));
415
219161
        f.instruction(&Instruction::StructGet {
416
219161
            struct_type_index: ratio_idx,
417
219161
            field_index: 0,
418
219161
        });
419
219161
        f.instruction(&Instruction::LocalGet(0));
420
219161
        f.instruction(&Instruction::StructGet {
421
219161
            struct_type_index: ratio_idx,
422
219161
            field_index: 1,
423
219161
        });
424
219161
        f.instruction(&Instruction::I64DivS);
425
219161
        f.instruction(&Instruction::End);
426
219161
        self.pending_helpers.push(f);
427
219161
    }
428
}