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
252750
    pub(super) fn declare_ratio_helpers(&mut self) -> Result<()> {
32
252750
        let ratio_ref = self.ratio_ref();
33
252750
        self.register_function("gcd", &[ValType::I64, ValType::I64], &[ValType::I64])?;
34
252750
        self.register_function("ratio_new", &[ValType::I64, ValType::I64], &[ratio_ref])?;
35
252750
        self.register_function("ratio_add", &[ratio_ref, ratio_ref], &[ratio_ref])?;
36
252750
        self.register_function("ratio_sub", &[ratio_ref, ratio_ref], &[ratio_ref])?;
37
252750
        self.register_function("ratio_mul", &[ratio_ref, ratio_ref], &[ratio_ref])?;
38
252750
        self.register_function("ratio_div", &[ratio_ref, ratio_ref], &[ratio_ref])?;
39
252750
        self.register_function("ratio_eq", &[ratio_ref, ratio_ref], &[ValType::I32])?;
40
252750
        self.register_function("ratio_lt", &[ratio_ref, ratio_ref], &[ValType::I32])?;
41
252750
        self.register_function("ratio_from_i64", &[ValType::I64], &[ratio_ref])?;
42
252750
        self.register_function("ratio_to_i64", &[ratio_ref], &[ValType::I64])?;
43
252750
        Ok(())
44
252750
    }
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
252750
    pub(super) fn build_ratio_helpers(&mut self) -> Result<()> {
51
252750
        let div_trap = self.register_div_by_zero_trap()?;
52
252750
        self.build_gcd_body();
53
252750
        self.build_ratio_new_body(&div_trap);
54
252750
        self.build_ratio_binop_bodies();
55
252750
        self.build_ratio_cmp_bodies();
56
252750
        self.build_ratio_from_i64_body();
57
252750
        self.build_ratio_to_i64_body();
58
252750
        Ok(())
59
252750
    }
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
252750
    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
252750
        let code_data = self.add_data(CODE.as_bytes())?;
69
252750
        let message_data = self.add_data(MESSAGE.as_bytes())?;
70
252750
        Ok(DivByZeroTrap {
71
252750
            i8_array_idx: self.ids.ty_i8_array,
72
252750
            condition_idx: self.condition_type_idx(),
73
252750
            tag: self.nomi_error_tag(),
74
252750
            code_data,
75
252750
            code_len: CODE.len() as u32,
76
252750
            message_data,
77
252750
            message_len: MESSAGE.len() as u32,
78
252750
        })
79
252750
    }
80

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

            
118
252750
    fn build_ratio_new_body(&mut self, div_trap: &DivByZeroTrap) {
119
252750
        let ratio_idx = self.ids.ty_ratio;
120
252750
        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
252750
        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
252750
        f.instruction(&Instruction::LocalGet(1));
130
252750
        f.instruction(&Instruction::I64Eqz);
131
252750
        f.instruction(&Instruction::If(BlockType::Empty));
132
252750
        Self::emit_div_by_zero_throw(&mut f, div_trap);
133
252750
        f.instruction(&Instruction::End);
134
        // g = gcd(num, denom)
135
252750
        f.instruction(&Instruction::LocalGet(0));
136
252750
        f.instruction(&Instruction::LocalGet(1));
137
252750
        f.instruction(&Instruction::Call(gcd_func));
138
252750
        f.instruction(&Instruction::LocalSet(2));
139
        // num = num / g
140
252750
        f.instruction(&Instruction::LocalGet(0));
141
252750
        f.instruction(&Instruction::LocalGet(2));
142
252750
        f.instruction(&Instruction::I64DivS);
143
252750
        f.instruction(&Instruction::LocalSet(0));
144
        // denom = denom / g
145
252750
        f.instruction(&Instruction::LocalGet(1));
146
252750
        f.instruction(&Instruction::LocalGet(2));
147
252750
        f.instruction(&Instruction::I64DivS);
148
252750
        f.instruction(&Instruction::LocalSet(1));
149
        // if denom < 0 { num = -num; denom = -denom }
150
252750
        f.instruction(&Instruction::LocalGet(1));
151
252750
        f.instruction(&Instruction::I64Const(0));
152
252750
        f.instruction(&Instruction::I64LtS);
153
252750
        f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
154
252750
        f.instruction(&Instruction::I64Const(0));
155
252750
        f.instruction(&Instruction::LocalGet(0));
156
252750
        f.instruction(&Instruction::I64Sub);
157
252750
        f.instruction(&Instruction::LocalSet(0));
158
252750
        f.instruction(&Instruction::I64Const(0));
159
252750
        f.instruction(&Instruction::LocalGet(1));
160
252750
        f.instruction(&Instruction::I64Sub);
161
252750
        f.instruction(&Instruction::LocalSet(1));
162
252750
        f.instruction(&Instruction::End);
163
        // struct.new $ratio (num, denom)
164
252750
        f.instruction(&Instruction::LocalGet(0));
165
252750
        f.instruction(&Instruction::LocalGet(1));
166
252750
        f.instruction(&Instruction::StructNew(ratio_idx));
167
252750
        f.instruction(&Instruction::End);
168
252750
        self.pending_helpers.push(f);
169
252750
    }
170

            
171
252750
    fn build_ratio_binop_bodies(&mut self) {
172
252750
        let ratio_idx = self.ids.ty_ratio;
173
252750
        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
252750
        let mut f = Function::new([]);
177
        // a.num * b.denom
178
252750
        f.instruction(&Instruction::LocalGet(0));
179
252750
        f.instruction(&Instruction::StructGet {
180
252750
            struct_type_index: ratio_idx,
181
252750
            field_index: 0,
182
252750
        });
183
252750
        f.instruction(&Instruction::LocalGet(1));
184
252750
        f.instruction(&Instruction::StructGet {
185
252750
            struct_type_index: ratio_idx,
186
252750
            field_index: 1,
187
252750
        });
188
252750
        f.instruction(&Instruction::I64Mul);
189
        // b.num * a.denom
190
252750
        f.instruction(&Instruction::LocalGet(1));
191
252750
        f.instruction(&Instruction::StructGet {
192
252750
            struct_type_index: ratio_idx,
193
252750
            field_index: 0,
194
252750
        });
195
252750
        f.instruction(&Instruction::LocalGet(0));
196
252750
        f.instruction(&Instruction::StructGet {
197
252750
            struct_type_index: ratio_idx,
198
252750
            field_index: 1,
199
252750
        });
200
252750
        f.instruction(&Instruction::I64Mul);
201
252750
        f.instruction(&Instruction::I64Add);
202
        // a.denom * b.denom
203
252750
        f.instruction(&Instruction::LocalGet(0));
204
252750
        f.instruction(&Instruction::StructGet {
205
252750
            struct_type_index: ratio_idx,
206
252750
            field_index: 1,
207
252750
        });
208
252750
        f.instruction(&Instruction::LocalGet(1));
209
252750
        f.instruction(&Instruction::StructGet {
210
252750
            struct_type_index: ratio_idx,
211
252750
            field_index: 1,
212
252750
        });
213
252750
        f.instruction(&Instruction::I64Mul);
214
252750
        f.instruction(&Instruction::Call(ratio_new));
215
252750
        f.instruction(&Instruction::End);
216
252750
        self.pending_helpers.push(f);
217

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

            
258
        // ratio_mul(a, b): (a.num * b.num, a.denom * b.denom)
259
252750
        let mut f = Function::new([]);
260
252750
        f.instruction(&Instruction::LocalGet(0));
261
252750
        f.instruction(&Instruction::StructGet {
262
252750
            struct_type_index: ratio_idx,
263
252750
            field_index: 0,
264
252750
        });
265
252750
        f.instruction(&Instruction::LocalGet(1));
266
252750
        f.instruction(&Instruction::StructGet {
267
252750
            struct_type_index: ratio_idx,
268
252750
            field_index: 0,
269
252750
        });
270
252750
        f.instruction(&Instruction::I64Mul);
271
252750
        f.instruction(&Instruction::LocalGet(0));
272
252750
        f.instruction(&Instruction::StructGet {
273
252750
            struct_type_index: ratio_idx,
274
252750
            field_index: 1,
275
252750
        });
276
252750
        f.instruction(&Instruction::LocalGet(1));
277
252750
        f.instruction(&Instruction::StructGet {
278
252750
            struct_type_index: ratio_idx,
279
252750
            field_index: 1,
280
252750
        });
281
252750
        f.instruction(&Instruction::I64Mul);
282
252750
        f.instruction(&Instruction::Call(ratio_new));
283
252750
        f.instruction(&Instruction::End);
284
252750
        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
252750
        let mut f = Function::new([]);
289
252750
        f.instruction(&Instruction::LocalGet(0));
290
252750
        f.instruction(&Instruction::StructGet {
291
252750
            struct_type_index: ratio_idx,
292
252750
            field_index: 0,
293
252750
        });
294
252750
        f.instruction(&Instruction::LocalGet(1));
295
252750
        f.instruction(&Instruction::StructGet {
296
252750
            struct_type_index: ratio_idx,
297
252750
            field_index: 1,
298
252750
        });
299
252750
        f.instruction(&Instruction::I64Mul);
300
252750
        f.instruction(&Instruction::LocalGet(0));
301
252750
        f.instruction(&Instruction::StructGet {
302
252750
            struct_type_index: ratio_idx,
303
252750
            field_index: 1,
304
252750
        });
305
252750
        f.instruction(&Instruction::LocalGet(1));
306
252750
        f.instruction(&Instruction::StructGet {
307
252750
            struct_type_index: ratio_idx,
308
252750
            field_index: 0,
309
252750
        });
310
252750
        f.instruction(&Instruction::I64Mul);
311
252750
        f.instruction(&Instruction::Call(ratio_new));
312
252750
        f.instruction(&Instruction::End);
313
252750
        self.pending_helpers.push(f);
314
252750
    }
315

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

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

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

            
376
252750
    fn build_ratio_from_i64_body(&mut self) {
377
252750
        let ratio_idx = self.ids.ty_ratio;
378
        // ratio_from_i64(n) -> struct.new $ratio (n, 1)
379
252750
        let mut f = Function::new([]);
380
252750
        f.instruction(&Instruction::LocalGet(0));
381
252750
        f.instruction(&Instruction::I64Const(1));
382
252750
        f.instruction(&Instruction::StructNew(ratio_idx));
383
252750
        f.instruction(&Instruction::End);
384
252750
        self.pending_helpers.push(f);
385
252750
    }
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
252750
    fn emit_div_by_zero_throw(f: &mut Function, trap: &DivByZeroTrap) {
391
252750
        f.instruction(&Instruction::I32Const(0));
392
252750
        f.instruction(&Instruction::I32Const(trap.code_len as i32));
393
252750
        f.instruction(&Instruction::ArrayNewData {
394
252750
            array_type_index: trap.i8_array_idx,
395
252750
            array_data_index: trap.code_data,
396
252750
        });
397
252750
        f.instruction(&Instruction::I32Const(0));
398
252750
        f.instruction(&Instruction::I32Const(trap.message_len as i32));
399
252750
        f.instruction(&Instruction::ArrayNewData {
400
252750
            array_type_index: trap.i8_array_idx,
401
252750
            array_data_index: trap.message_data,
402
252750
        });
403
252750
        f.instruction(&Instruction::StructNew(trap.condition_idx));
404
252750
        f.instruction(&Instruction::Throw(trap.tag));
405
252750
    }
406

            
407
252750
    fn build_ratio_to_i64_body(&mut self) {
408
252750
        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
252750
        let mut f = Function::new([]);
414
252750
        f.instruction(&Instruction::LocalGet(0));
415
252750
        f.instruction(&Instruction::StructGet {
416
252750
            struct_type_index: ratio_idx,
417
252750
            field_index: 0,
418
252750
        });
419
252750
        f.instruction(&Instruction::LocalGet(0));
420
252750
        f.instruction(&Instruction::StructGet {
421
252750
            struct_type_index: ratio_idx,
422
252750
            field_index: 1,
423
252750
        });
424
252750
        f.instruction(&Instruction::I64DivS);
425
252750
        f.instruction(&Instruction::End);
426
252750
        self.pending_helpers.push(f);
427
252750
    }
428
}