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 super::exception::ConditionTrap;
12
use crate::error::Result;
13
use wasm_encoder::{BlockType, Function, Instruction, ValType};
14

            
15
impl CompileContext {
16
    /// Declares the ratio helper signatures (no bodies). Runs before
17
    /// `resolve_ids` so every index exists when `WasmIds` is assembled; the
18
    /// bodies are emitted later by [`Self::build_ratio_helpers`].
19
477238
    pub(super) fn declare_ratio_helpers(&mut self) -> Result<()> {
20
477238
        let ratio_ref = self.ratio_ref();
21
477238
        self.register_function("gcd", &[ValType::I64, ValType::I64], &[ValType::I64])?;
22
477238
        self.register_function("ratio_new", &[ValType::I64, ValType::I64], &[ratio_ref])?;
23
477238
        self.register_function("ratio_add", &[ratio_ref, ratio_ref], &[ratio_ref])?;
24
477238
        self.register_function("ratio_sub", &[ratio_ref, ratio_ref], &[ratio_ref])?;
25
477238
        self.register_function("ratio_mul", &[ratio_ref, ratio_ref], &[ratio_ref])?;
26
477238
        self.register_function("ratio_div", &[ratio_ref, ratio_ref], &[ratio_ref])?;
27
477238
        self.register_function("ratio_eq", &[ratio_ref, ratio_ref], &[ValType::I32])?;
28
477238
        self.register_function("ratio_lt", &[ratio_ref, ratio_ref], &[ValType::I32])?;
29
477238
        self.register_function("ratio_from_i64", &[ValType::I64], &[ratio_ref])?;
30
477238
        self.register_function("ratio_to_i64", &[ratio_ref], &[ValType::I64])?;
31
477238
        Ok(())
32
477238
    }
33

            
34
    /// Emits the ratio helper bodies, in registration order so each maps to its
35
    /// reserved function index. Reads resolved indices from `self.ids`; the
36
    /// `division-by-zero` trap interns its strings here (in build order, so the
37
    /// data-segment index sequence is unchanged from the pre-split layout).
38
477238
    pub(super) fn build_ratio_helpers(&mut self) -> Result<()> {
39
        // UPPER-CASE matches the reader's symbol case-folding so a script can
40
        // catch it with `(handler-case … (division-by-zero (e) …))`.
41
477238
        let div_trap = self.register_condition_trap("DIVISION-BY-ZERO", "division by zero")?;
42
477238
        self.build_gcd_body();
43
477238
        self.build_ratio_new_body(&div_trap);
44
477238
        self.build_ratio_binop_bodies();
45
477238
        self.build_ratio_cmp_bodies();
46
477238
        self.build_ratio_from_i64_body();
47
477238
        self.build_ratio_to_i64_body();
48
477238
        Ok(())
49
477238
    }
50

            
51
477238
    fn build_gcd_body(&mut self) {
52
        // Euclidean GCD: gcd(a, b) while b != 0 { t = b; b = a % b; a = t }; abs(a)
53
477238
        let mut f = Function::new([(1, ValType::I64)]); // local $t
54
        // params: $a=0, $b=1, local: $t=2
55
477238
        f.instruction(&Instruction::Block(wasm_encoder::BlockType::Empty));
56
477238
        f.instruction(&Instruction::Loop(wasm_encoder::BlockType::Empty));
57
        // if b == 0, break
58
477238
        f.instruction(&Instruction::LocalGet(1));
59
477238
        f.instruction(&Instruction::I64Eqz);
60
477238
        f.instruction(&Instruction::BrIf(1));
61
        // t = b
62
477238
        f.instruction(&Instruction::LocalGet(1));
63
477238
        f.instruction(&Instruction::LocalSet(2));
64
        // b = a % b
65
477238
        f.instruction(&Instruction::LocalGet(0));
66
477238
        f.instruction(&Instruction::LocalGet(1));
67
477238
        f.instruction(&Instruction::I64RemS);
68
477238
        f.instruction(&Instruction::LocalSet(1));
69
        // a = t
70
477238
        f.instruction(&Instruction::LocalGet(2));
71
477238
        f.instruction(&Instruction::LocalSet(0));
72
477238
        f.instruction(&Instruction::Br(0));
73
477238
        f.instruction(&Instruction::End); // loop
74
477238
        f.instruction(&Instruction::End); // block
75
        // return abs(a)
76
477238
        f.instruction(&Instruction::LocalGet(0));
77
477238
        f.instruction(&Instruction::I64Const(0));
78
477238
        f.instruction(&Instruction::LocalGet(0));
79
477238
        f.instruction(&Instruction::I64Sub);
80
477238
        f.instruction(&Instruction::LocalGet(0));
81
477238
        f.instruction(&Instruction::I64Const(0));
82
477238
        f.instruction(&Instruction::I64LtS);
83
477238
        f.instruction(&Instruction::Select);
84
477238
        f.instruction(&Instruction::End);
85
477238
        self.pending_helpers.push(f);
86
477238
    }
87

            
88
477238
    fn build_ratio_new_body(&mut self, div_trap: &ConditionTrap) {
89
477238
        let ratio_idx = self.ids.ty_ratio;
90
477238
        let gcd_func = self.ids.gcd;
91
        // ratio_new(num, denom) -> reduce by GCD, normalize sign
92
        // params: $num=0, $denom=1, locals: $g=2
93
477238
        let mut f = Function::new([(1, ValType::I64)]);
94
        // denom == 0 → throw a catchable `division-by-zero` rather than build a
95
        // degenerate ratio (or trap in `gcd(0,0)` / a later `i64.div_s`). This
96
        // is the single chokepoint for ALL ratio construction: a runtime zero
97
        // divisor (via `ratio_div`) AND an `i64.mul` denominator overflow that
98
        // wraps to zero in add/sub/mul both land here.
99
477238
        f.instruction(&Instruction::LocalGet(1));
100
477238
        f.instruction(&Instruction::I64Eqz);
101
477238
        f.instruction(&Instruction::If(BlockType::Empty));
102
477238
        div_trap.emit_throw(&mut f);
103
477238
        f.instruction(&Instruction::End);
104
        // g = gcd(num, denom)
105
477238
        f.instruction(&Instruction::LocalGet(0));
106
477238
        f.instruction(&Instruction::LocalGet(1));
107
477238
        f.instruction(&Instruction::Call(gcd_func));
108
477238
        f.instruction(&Instruction::LocalSet(2));
109
        // num = num / g
110
477238
        f.instruction(&Instruction::LocalGet(0));
111
477238
        f.instruction(&Instruction::LocalGet(2));
112
477238
        f.instruction(&Instruction::I64DivS);
113
477238
        f.instruction(&Instruction::LocalSet(0));
114
        // denom = denom / g
115
477238
        f.instruction(&Instruction::LocalGet(1));
116
477238
        f.instruction(&Instruction::LocalGet(2));
117
477238
        f.instruction(&Instruction::I64DivS);
118
477238
        f.instruction(&Instruction::LocalSet(1));
119
        // if denom < 0 { num = -num; denom = -denom }
120
477238
        f.instruction(&Instruction::LocalGet(1));
121
477238
        f.instruction(&Instruction::I64Const(0));
122
477238
        f.instruction(&Instruction::I64LtS);
123
477238
        f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
124
477238
        f.instruction(&Instruction::I64Const(0));
125
477238
        f.instruction(&Instruction::LocalGet(0));
126
477238
        f.instruction(&Instruction::I64Sub);
127
477238
        f.instruction(&Instruction::LocalSet(0));
128
477238
        f.instruction(&Instruction::I64Const(0));
129
477238
        f.instruction(&Instruction::LocalGet(1));
130
477238
        f.instruction(&Instruction::I64Sub);
131
477238
        f.instruction(&Instruction::LocalSet(1));
132
477238
        f.instruction(&Instruction::End);
133
        // struct.new $ratio (num, denom)
134
477238
        f.instruction(&Instruction::LocalGet(0));
135
477238
        f.instruction(&Instruction::LocalGet(1));
136
477238
        f.instruction(&Instruction::StructNew(ratio_idx));
137
477238
        f.instruction(&Instruction::End);
138
477238
        self.pending_helpers.push(f);
139
477238
    }
140

            
141
477238
    fn build_ratio_binop_bodies(&mut self) {
142
477238
        let ratio_idx = self.ids.ty_ratio;
143
477238
        let ratio_new = self.ids.ratio_new;
144

            
145
        // ratio_add(a, b): (a.num * b.denom + b.num * a.denom, a.denom * b.denom)
146
477238
        let mut f = Function::new([]);
147
        // a.num * b.denom
148
477238
        f.instruction(&Instruction::LocalGet(0));
149
477238
        f.instruction(&Instruction::StructGet {
150
477238
            struct_type_index: ratio_idx,
151
477238
            field_index: 0,
152
477238
        });
153
477238
        f.instruction(&Instruction::LocalGet(1));
154
477238
        f.instruction(&Instruction::StructGet {
155
477238
            struct_type_index: ratio_idx,
156
477238
            field_index: 1,
157
477238
        });
158
477238
        f.instruction(&Instruction::I64Mul);
159
        // b.num * a.denom
160
477238
        f.instruction(&Instruction::LocalGet(1));
161
477238
        f.instruction(&Instruction::StructGet {
162
477238
            struct_type_index: ratio_idx,
163
477238
            field_index: 0,
164
477238
        });
165
477238
        f.instruction(&Instruction::LocalGet(0));
166
477238
        f.instruction(&Instruction::StructGet {
167
477238
            struct_type_index: ratio_idx,
168
477238
            field_index: 1,
169
477238
        });
170
477238
        f.instruction(&Instruction::I64Mul);
171
477238
        f.instruction(&Instruction::I64Add);
172
        // a.denom * b.denom
173
477238
        f.instruction(&Instruction::LocalGet(0));
174
477238
        f.instruction(&Instruction::StructGet {
175
477238
            struct_type_index: ratio_idx,
176
477238
            field_index: 1,
177
477238
        });
178
477238
        f.instruction(&Instruction::LocalGet(1));
179
477238
        f.instruction(&Instruction::StructGet {
180
477238
            struct_type_index: ratio_idx,
181
477238
            field_index: 1,
182
477238
        });
183
477238
        f.instruction(&Instruction::I64Mul);
184
477238
        f.instruction(&Instruction::Call(ratio_new));
185
477238
        f.instruction(&Instruction::End);
186
477238
        self.pending_helpers.push(f);
187

            
188
        // ratio_sub(a, b): (a.num * b.denom - b.num * a.denom, a.denom * b.denom)
189
477238
        let mut f = Function::new([]);
190
477238
        f.instruction(&Instruction::LocalGet(0));
191
477238
        f.instruction(&Instruction::StructGet {
192
477238
            struct_type_index: ratio_idx,
193
477238
            field_index: 0,
194
477238
        });
195
477238
        f.instruction(&Instruction::LocalGet(1));
196
477238
        f.instruction(&Instruction::StructGet {
197
477238
            struct_type_index: ratio_idx,
198
477238
            field_index: 1,
199
477238
        });
200
477238
        f.instruction(&Instruction::I64Mul);
201
477238
        f.instruction(&Instruction::LocalGet(1));
202
477238
        f.instruction(&Instruction::StructGet {
203
477238
            struct_type_index: ratio_idx,
204
477238
            field_index: 0,
205
477238
        });
206
477238
        f.instruction(&Instruction::LocalGet(0));
207
477238
        f.instruction(&Instruction::StructGet {
208
477238
            struct_type_index: ratio_idx,
209
477238
            field_index: 1,
210
477238
        });
211
477238
        f.instruction(&Instruction::I64Mul);
212
477238
        f.instruction(&Instruction::I64Sub);
213
477238
        f.instruction(&Instruction::LocalGet(0));
214
477238
        f.instruction(&Instruction::StructGet {
215
477238
            struct_type_index: ratio_idx,
216
477238
            field_index: 1,
217
477238
        });
218
477238
        f.instruction(&Instruction::LocalGet(1));
219
477238
        f.instruction(&Instruction::StructGet {
220
477238
            struct_type_index: ratio_idx,
221
477238
            field_index: 1,
222
477238
        });
223
477238
        f.instruction(&Instruction::I64Mul);
224
477238
        f.instruction(&Instruction::Call(ratio_new));
225
477238
        f.instruction(&Instruction::End);
226
477238
        self.pending_helpers.push(f);
227

            
228
        // ratio_mul(a, b): (a.num * b.num, a.denom * b.denom)
229
477238
        let mut f = Function::new([]);
230
477238
        f.instruction(&Instruction::LocalGet(0));
231
477238
        f.instruction(&Instruction::StructGet {
232
477238
            struct_type_index: ratio_idx,
233
477238
            field_index: 0,
234
477238
        });
235
477238
        f.instruction(&Instruction::LocalGet(1));
236
477238
        f.instruction(&Instruction::StructGet {
237
477238
            struct_type_index: ratio_idx,
238
477238
            field_index: 0,
239
477238
        });
240
477238
        f.instruction(&Instruction::I64Mul);
241
477238
        f.instruction(&Instruction::LocalGet(0));
242
477238
        f.instruction(&Instruction::StructGet {
243
477238
            struct_type_index: ratio_idx,
244
477238
            field_index: 1,
245
477238
        });
246
477238
        f.instruction(&Instruction::LocalGet(1));
247
477238
        f.instruction(&Instruction::StructGet {
248
477238
            struct_type_index: ratio_idx,
249
477238
            field_index: 1,
250
477238
        });
251
477238
        f.instruction(&Instruction::I64Mul);
252
477238
        f.instruction(&Instruction::Call(ratio_new));
253
477238
        f.instruction(&Instruction::End);
254
477238
        self.pending_helpers.push(f);
255

            
256
        // ratio_div(a, b): (a.num * b.denom, a.denom * b.num). A zero divisor
257
        // makes `a.denom * b.num` zero, caught by `ratio_new`'s denom-0 guard.
258
477238
        let mut f = Function::new([]);
259
477238
        f.instruction(&Instruction::LocalGet(0));
260
477238
        f.instruction(&Instruction::StructGet {
261
477238
            struct_type_index: ratio_idx,
262
477238
            field_index: 0,
263
477238
        });
264
477238
        f.instruction(&Instruction::LocalGet(1));
265
477238
        f.instruction(&Instruction::StructGet {
266
477238
            struct_type_index: ratio_idx,
267
477238
            field_index: 1,
268
477238
        });
269
477238
        f.instruction(&Instruction::I64Mul);
270
477238
        f.instruction(&Instruction::LocalGet(0));
271
477238
        f.instruction(&Instruction::StructGet {
272
477238
            struct_type_index: ratio_idx,
273
477238
            field_index: 1,
274
477238
        });
275
477238
        f.instruction(&Instruction::LocalGet(1));
276
477238
        f.instruction(&Instruction::StructGet {
277
477238
            struct_type_index: ratio_idx,
278
477238
            field_index: 0,
279
477238
        });
280
477238
        f.instruction(&Instruction::I64Mul);
281
477238
        f.instruction(&Instruction::Call(ratio_new));
282
477238
        f.instruction(&Instruction::End);
283
477238
        self.pending_helpers.push(f);
284
477238
    }
285

            
286
477238
    fn build_ratio_cmp_bodies(&mut self) {
287
477238
        let ratio_idx = self.ids.ty_ratio;
288

            
289
        // ratio_eq(a, b): a.num * b.denom == b.num * a.denom
290
477238
        let mut f = Function::new([]);
291
477238
        f.instruction(&Instruction::LocalGet(0));
292
477238
        f.instruction(&Instruction::StructGet {
293
477238
            struct_type_index: ratio_idx,
294
477238
            field_index: 0,
295
477238
        });
296
477238
        f.instruction(&Instruction::LocalGet(1));
297
477238
        f.instruction(&Instruction::StructGet {
298
477238
            struct_type_index: ratio_idx,
299
477238
            field_index: 1,
300
477238
        });
301
477238
        f.instruction(&Instruction::I64Mul);
302
477238
        f.instruction(&Instruction::LocalGet(1));
303
477238
        f.instruction(&Instruction::StructGet {
304
477238
            struct_type_index: ratio_idx,
305
477238
            field_index: 0,
306
477238
        });
307
477238
        f.instruction(&Instruction::LocalGet(0));
308
477238
        f.instruction(&Instruction::StructGet {
309
477238
            struct_type_index: ratio_idx,
310
477238
            field_index: 1,
311
477238
        });
312
477238
        f.instruction(&Instruction::I64Mul);
313
477238
        f.instruction(&Instruction::I64Eq);
314
477238
        f.instruction(&Instruction::End);
315
477238
        self.pending_helpers.push(f);
316

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

            
346
477238
    fn build_ratio_from_i64_body(&mut self) {
347
477238
        let ratio_idx = self.ids.ty_ratio;
348
        // ratio_from_i64(n) -> struct.new $ratio (n, 1)
349
477238
        let mut f = Function::new([]);
350
477238
        f.instruction(&Instruction::LocalGet(0));
351
477238
        f.instruction(&Instruction::I64Const(1));
352
477238
        f.instruction(&Instruction::StructNew(ratio_idx));
353
477238
        f.instruction(&Instruction::End);
354
477238
        self.pending_helpers.push(f);
355
477238
    }
356

            
357
477238
    fn build_ratio_to_i64_body(&mut self) {
358
477238
        let ratio_idx = self.ids.ty_ratio;
359
        // ratio_to_i64(r) -> r.numer / r.denom  (i64.div_s truncates toward zero,
360
        // matching ADR-0028's scalar->index narrowing). denom is never 0: the
361
        // only path that could build a denom-0 ratio is division by a zero
362
        // divisor, which `ratio_div` now throws on — so no guard is needed here.
363
477238
        let mut f = Function::new([]);
364
477238
        f.instruction(&Instruction::LocalGet(0));
365
477238
        f.instruction(&Instruction::StructGet {
366
477238
            struct_type_index: ratio_idx,
367
477238
            field_index: 0,
368
477238
        });
369
477238
        f.instruction(&Instruction::LocalGet(0));
370
477238
        f.instruction(&Instruction::StructGet {
371
477238
            struct_type_index: ratio_idx,
372
477238
            field_index: 1,
373
477238
        });
374
477238
        f.instruction(&Instruction::I64DivS);
375
477238
        f.instruction(&Instruction::End);
376
477238
        self.pending_helpers.push(f);
377
477238
    }
378
}