ruka_codegen_wasm/lower_instr_dispatch/
numeric.rs

1use super::*;
2
3pub(crate) fn lower_numeric_instr(
4    instr: &ruka_mir::MirInstr,
5    body: &mut walrus::InstrSeqBuilder,
6    ctx: &LowerCtx<'_>,
7) -> Result<bool, LowerError> {
8    match instr {
9        ruka_mir::MirInstr::IntAdd { lhs, rhs, dst }
10        | ruka_mir::MirInstr::IntSub { lhs, rhs, dst }
11        | ruka_mir::MirInstr::IntMul { lhs, rhs, dst }
12        | ruka_mir::MirInstr::IntDiv { lhs, rhs, dst }
13        | ruka_mir::MirInstr::IntMod { lhs, rhs, dst } => {
14            let lhs_id = lhs.as_u32();
15            let rhs_id = rhs.as_u32();
16            let dst_id = dst.as_u32();
17            let lhs_local = runtime_local_index(ctx.local_indices, lhs_id, "int binary lhs")?;
18            let rhs_local = runtime_local_index(ctx.local_indices, rhs_id, "int binary rhs")?;
19            let dst_local = runtime_local_index(ctx.local_indices, dst_id, "int binary dst")?;
20            let lhs_ty = local_ty(ctx.local_tys, lhs_id, "int binary lhs ty")?;
21            let dst_ty_semantic = local_ty(ctx.local_tys, dst_id, "int binary dst ty")?;
22            let lhs_valtype =
23                runtime_local_valtype(ctx.local_runtime_types, lhs_id, "int binary lhs runtime")?;
24            let dst_valtype =
25                runtime_local_valtype(ctx.local_runtime_types, dst_id, "int binary dst runtime")?;
26            body.local_get(lhs_local).local_get(rhs_local);
27            match (lhs_valtype, instr) {
28                (ValType::I32, ruka_mir::MirInstr::IntAdd { .. }) => {
29                    body.binop(BinaryOp::I32Add);
30                }
31                (ValType::I32, ruka_mir::MirInstr::IntSub { .. }) => {
32                    body.binop(BinaryOp::I32Sub);
33                }
34                (ValType::I32, ruka_mir::MirInstr::IntMul { .. }) => {
35                    body.binop(BinaryOp::I32Mul);
36                }
37                (ValType::I32, ruka_mir::MirInstr::IntDiv { .. }) => {
38                    if is_unsigned_integer_ty(lhs_ty) {
39                        body.binop(BinaryOp::I32DivU);
40                    } else {
41                        body.binop(BinaryOp::I32DivS);
42                    }
43                }
44                (ValType::I32, ruka_mir::MirInstr::IntMod { .. }) => {
45                    if is_unsigned_integer_ty(lhs_ty) {
46                        body.binop(BinaryOp::I32RemU);
47                    } else {
48                        body.binop(BinaryOp::I32RemS);
49                    }
50                }
51                (ValType::I64, ruka_mir::MirInstr::IntAdd { .. }) => {
52                    body.binop(BinaryOp::I64Add);
53                }
54                (ValType::I64, ruka_mir::MirInstr::IntSub { .. }) => {
55                    body.binop(BinaryOp::I64Sub);
56                }
57                (ValType::I64, ruka_mir::MirInstr::IntMul { .. }) => {
58                    body.binop(BinaryOp::I64Mul);
59                }
60                (ValType::I64, ruka_mir::MirInstr::IntDiv { .. }) => {
61                    if is_unsigned_integer_ty(lhs_ty) {
62                        body.binop(BinaryOp::I64DivU);
63                    } else {
64                        body.binop(BinaryOp::I64DivS);
65                    }
66                }
67                (ValType::I64, ruka_mir::MirInstr::IntMod { .. }) => {
68                    if is_unsigned_integer_ty(lhs_ty) {
69                        body.binop(BinaryOp::I64RemU);
70                    } else {
71                        body.binop(BinaryOp::I64RemS);
72                    }
73                }
74                (ValType::F32, ruka_mir::MirInstr::IntAdd { .. }) => {
75                    body.binop(BinaryOp::F32Add);
76                }
77                (ValType::F32, ruka_mir::MirInstr::IntSub { .. }) => {
78                    body.binop(BinaryOp::F32Sub);
79                }
80                (ValType::F32, ruka_mir::MirInstr::IntMul { .. }) => {
81                    body.binop(BinaryOp::F32Mul);
82                }
83                (ValType::F32, ruka_mir::MirInstr::IntDiv { .. }) => {
84                    body.binop(BinaryOp::F32Div);
85                }
86                (ValType::F32, ruka_mir::MirInstr::IntMod { .. }) => {
87                    return Err(LowerError::UnsupportedInstruction(
88                        "f32 modulo is unsupported",
89                    ));
90                }
91                (ValType::F64, ruka_mir::MirInstr::IntAdd { .. }) => {
92                    body.binop(BinaryOp::F64Add);
93                }
94                (ValType::F64, ruka_mir::MirInstr::IntSub { .. }) => {
95                    body.binop(BinaryOp::F64Sub);
96                }
97                (ValType::F64, ruka_mir::MirInstr::IntMul { .. }) => {
98                    body.binop(BinaryOp::F64Mul);
99                }
100                (ValType::F64, ruka_mir::MirInstr::IntDiv { .. }) => {
101                    body.binop(BinaryOp::F64Div);
102                }
103                (ValType::F64, ruka_mir::MirInstr::IntMod { .. }) => {
104                    return Err(LowerError::UnsupportedInstruction(
105                        "f64 modulo is unsupported",
106                    ));
107                }
108                _ => {
109                    return Err(LowerError::UnsupportedInstruction(
110                        "unsupported numeric binary op",
111                    ));
112                }
113            }
114            emit_normalize_integer_top_of_stack(body, dst_ty_semantic, dst_valtype)?;
115            body.local_set(dst_local);
116            Ok(true)
117        }
118        ruka_mir::MirInstr::IntLt { lhs, rhs, dst }
119        | ruka_mir::MirInstr::IntEq { lhs, rhs, dst }
120        | ruka_mir::MirInstr::IntGt { lhs, rhs, dst }
121        | ruka_mir::MirInstr::IntGtEq { lhs, rhs, dst }
122        | ruka_mir::MirInstr::IntNeq { lhs, rhs, dst }
123        | ruka_mir::MirInstr::IntLtEq { lhs, rhs, dst } => {
124            let lhs_id = lhs.as_u32();
125            let rhs_id = rhs.as_u32();
126            let dst_id = dst.as_u32();
127            let lhs_local = runtime_local_index(ctx.local_indices, lhs_id, "int relational lhs")?;
128            let rhs_local = runtime_local_index(ctx.local_indices, rhs_id, "int relational rhs")?;
129            let dst_local = runtime_local_index(ctx.local_indices, dst_id, "bool relational dst")?;
130            let lhs_ty = local_ty(ctx.local_tys, lhs_id, "int relational lhs ty")?;
131            let lhs_valtype = runtime_local_valtype(
132                ctx.local_runtime_types,
133                lhs_id,
134                "int relational lhs runtime",
135            )?;
136            body.local_get(lhs_local).local_get(rhs_local);
137            match (lhs_valtype, instr) {
138                (ValType::I32, ruka_mir::MirInstr::IntLt { .. }) => {
139                    body.binop(if is_unsigned_integer_ty(lhs_ty) {
140                        BinaryOp::I32LtU
141                    } else {
142                        BinaryOp::I32LtS
143                    });
144                }
145                (ValType::I32, ruka_mir::MirInstr::IntEq { .. }) => {
146                    body.binop(BinaryOp::I32Eq);
147                }
148                (ValType::I32, ruka_mir::MirInstr::IntGt { .. }) => {
149                    body.binop(if is_unsigned_integer_ty(lhs_ty) {
150                        BinaryOp::I32GtU
151                    } else {
152                        BinaryOp::I32GtS
153                    });
154                }
155                (ValType::I32, ruka_mir::MirInstr::IntGtEq { .. }) => {
156                    body.binop(if is_unsigned_integer_ty(lhs_ty) {
157                        BinaryOp::I32GeU
158                    } else {
159                        BinaryOp::I32GeS
160                    });
161                }
162                (ValType::I32, ruka_mir::MirInstr::IntNeq { .. }) => {
163                    body.binop(BinaryOp::I32Ne);
164                }
165                (ValType::I32, ruka_mir::MirInstr::IntLtEq { .. }) => {
166                    body.binop(if is_unsigned_integer_ty(lhs_ty) {
167                        BinaryOp::I32LeU
168                    } else {
169                        BinaryOp::I32LeS
170                    });
171                }
172                (ValType::I64, ruka_mir::MirInstr::IntLt { .. }) => {
173                    body.binop(if is_unsigned_integer_ty(lhs_ty) {
174                        BinaryOp::I64LtU
175                    } else {
176                        BinaryOp::I64LtS
177                    });
178                }
179                (ValType::I64, ruka_mir::MirInstr::IntEq { .. }) => {
180                    body.binop(BinaryOp::I64Eq);
181                }
182                (ValType::I64, ruka_mir::MirInstr::IntGt { .. }) => {
183                    body.binop(if is_unsigned_integer_ty(lhs_ty) {
184                        BinaryOp::I64GtU
185                    } else {
186                        BinaryOp::I64GtS
187                    });
188                }
189                (ValType::I64, ruka_mir::MirInstr::IntGtEq { .. }) => {
190                    body.binop(if is_unsigned_integer_ty(lhs_ty) {
191                        BinaryOp::I64GeU
192                    } else {
193                        BinaryOp::I64GeS
194                    });
195                }
196                (ValType::I64, ruka_mir::MirInstr::IntNeq { .. }) => {
197                    body.binop(BinaryOp::I64Ne);
198                }
199                (ValType::I64, ruka_mir::MirInstr::IntLtEq { .. }) => {
200                    body.binop(if is_unsigned_integer_ty(lhs_ty) {
201                        BinaryOp::I64LeU
202                    } else {
203                        BinaryOp::I64LeS
204                    });
205                }
206                (ValType::F32, ruka_mir::MirInstr::IntLt { .. }) => {
207                    body.binop(BinaryOp::F32Lt);
208                }
209                (ValType::F32, ruka_mir::MirInstr::IntEq { .. }) => {
210                    body.binop(BinaryOp::F32Eq);
211                }
212                (ValType::F32, ruka_mir::MirInstr::IntGt { .. }) => {
213                    body.binop(BinaryOp::F32Gt);
214                }
215                (ValType::F32, ruka_mir::MirInstr::IntGtEq { .. }) => {
216                    body.binop(BinaryOp::F32Ge);
217                }
218                (ValType::F32, ruka_mir::MirInstr::IntNeq { .. }) => {
219                    body.binop(BinaryOp::F32Ne);
220                }
221                (ValType::F32, ruka_mir::MirInstr::IntLtEq { .. }) => {
222                    body.binop(BinaryOp::F32Le);
223                }
224                (ValType::F64, ruka_mir::MirInstr::IntLt { .. }) => {
225                    body.binop(BinaryOp::F64Lt);
226                }
227                (ValType::F64, ruka_mir::MirInstr::IntEq { .. }) => {
228                    body.binop(BinaryOp::F64Eq);
229                }
230                (ValType::F64, ruka_mir::MirInstr::IntGt { .. }) => {
231                    body.binop(BinaryOp::F64Gt);
232                }
233                (ValType::F64, ruka_mir::MirInstr::IntGtEq { .. }) => {
234                    body.binop(BinaryOp::F64Ge);
235                }
236                (ValType::F64, ruka_mir::MirInstr::IntNeq { .. }) => {
237                    body.binop(BinaryOp::F64Ne);
238                }
239                (ValType::F64, ruka_mir::MirInstr::IntLtEq { .. }) => {
240                    body.binop(BinaryOp::F64Le);
241                }
242                _ => {
243                    return Err(LowerError::UnsupportedInstruction(
244                        "unsupported numeric relational op",
245                    ));
246                }
247            }
248            body.local_set(dst_local);
249            Ok(true)
250        }
251        _ => Ok(false),
252    }
253}
254
255pub(crate) fn is_unsigned_integer_ty(ty: &Ty) -> bool {
256    matches!(ty, Ty::U8 | Ty::U16 | Ty::U32 | Ty::U64)
257}
258
259pub(crate) fn signed_integer_min_i64(ty: &Ty) -> Option<i64> {
260    match ty {
261        Ty::I8 => Some(i8::MIN as i64),
262        Ty::I16 => Some(i16::MIN as i64),
263        Ty::I32 => Some(i32::MIN as i64),
264        Ty::I64 => Some(i64::MIN),
265        _ => None,
266    }
267}
268
269pub(crate) fn signed_integer_max_i64(ty: &Ty) -> Option<i64> {
270    match ty {
271        Ty::I8 => Some(i8::MAX as i64),
272        Ty::I16 => Some(i16::MAX as i64),
273        Ty::I32 => Some(i32::MAX as i64),
274        Ty::I64 => Some(i64::MAX),
275        _ => None,
276    }
277}
278
279pub(crate) fn unsigned_integer_max_u64(ty: &Ty) -> Option<u64> {
280    match ty {
281        Ty::U8 => Some(u8::MAX as u64),
282        Ty::U16 => Some(u16::MAX as u64),
283        Ty::U32 => Some(u32::MAX as u64),
284        Ty::U64 => Some(u64::MAX),
285        _ => None,
286    }
287}
288
289pub(crate) fn emit_i64_check_signed_gte(
290    body: &mut walrus::InstrSeqBuilder,
291    value_local: LocalId,
292    min: i64,
293) {
294    body.block(None, |ok: &mut walrus::InstrSeqBuilder| {
295        let ok_id = ok.id();
296        ok.local_get(value_local)
297            .i64_const(min)
298            .binop(BinaryOp::I64GeS)
299            .br_if(ok_id)
300            .unreachable();
301    });
302}
303
304pub(crate) fn emit_i64_check_signed_lte(
305    body: &mut walrus::InstrSeqBuilder,
306    value_local: LocalId,
307    max: i64,
308) {
309    body.block(None, |ok: &mut walrus::InstrSeqBuilder| {
310        let ok_id = ok.id();
311        ok.local_get(value_local)
312            .i64_const(max)
313            .binop(BinaryOp::I64LeS)
314            .br_if(ok_id)
315            .unreachable();
316    });
317}
318
319pub(crate) fn emit_i64_check_unsigned_lte(
320    body: &mut walrus::InstrSeqBuilder,
321    value_local: LocalId,
322    max_bits: i64,
323) {
324    body.block(None, |ok: &mut walrus::InstrSeqBuilder| {
325        let ok_id = ok.id();
326        ok.local_get(value_local)
327            .i64_const(max_bits)
328            .binop(BinaryOp::I64LeU)
329            .br_if(ok_id)
330            .unreachable();
331    });
332}
333
334pub(crate) fn emit_normalize_integer_top_of_stack(
335    body: &mut walrus::InstrSeqBuilder,
336    semantic_ty: &Ty,
337    runtime_ty: ValType,
338) -> Result<(), LowerError> {
339    if !semantic_ty.is_integer() {
340        return Ok(());
341    }
342    let slot_bits = match runtime_ty {
343        ValType::I32 => 32,
344        ValType::I64 => 64,
345        _ => {
346            return Err(LowerError::UnsupportedInstruction(
347                "integer value stored in non-integer runtime slot",
348            ));
349        }
350    };
351    let Some(width_bits) = integer_width_bits(semantic_ty) else {
352        return Err(LowerError::UnsupportedInstruction(
353            "missing integer bit-width metadata",
354        ));
355    };
356    if width_bits >= slot_bits {
357        return Ok(());
358    }
359
360    if is_unsigned_integer_ty(semantic_ty) {
361        let mask = ((1u128 << width_bits) - 1) as u64;
362        match runtime_ty {
363            ValType::I32 => {
364                body.i32_const(mask as i32).binop(BinaryOp::I32And);
365            }
366            ValType::I64 => {
367                body.i64_const(mask as i64).binop(BinaryOp::I64And);
368            }
369            _ => unreachable!("handled above"),
370        }
371    } else {
372        let shift = (slot_bits - width_bits) as i32;
373        match runtime_ty {
374            ValType::I32 => {
375                body.i32_const(shift)
376                    .binop(BinaryOp::I32Shl)
377                    .i32_const(shift)
378                    .binop(BinaryOp::I32ShrS);
379            }
380            ValType::I64 => {
381                body.i64_const(i64::from(shift))
382                    .binop(BinaryOp::I64Shl)
383                    .i64_const(i64::from(shift))
384                    .binop(BinaryOp::I64ShrS);
385            }
386            _ => unreachable!("handled above"),
387        }
388    }
389
390    Ok(())
391}
392
393pub(crate) fn integer_width_bits(ty: &Ty) -> Option<u8> {
394    match ty {
395        Ty::U8 | Ty::I8 => Some(8),
396        Ty::U16 | Ty::I16 => Some(16),
397        Ty::U32 | Ty::I32 => Some(32),
398        Ty::U64 | Ty::I64 => Some(64),
399        _ => None,
400    }
401}