ruka_codegen_wasm/lower_instr_dispatch/
numeric.rs1use 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}