ruka_codegen_wasm/
lower_instr_dispatch.rs

1use super::*;
2
3mod call_family;
4mod numeric;
5
6pub(crate) use call_family::lower_call_family_instr;
7pub(crate) use numeric::*;
8
9pub(crate) fn lower_instr(
10    instr: &ruka_mir::MirInstr,
11    body: &mut walrus::InstrSeqBuilder,
12    ctx: &LowerCtx<'_>,
13) -> Result<(), LowerError> {
14    if lower_call_family_instr(instr, body, ctx)? {
15        return Ok(());
16    }
17    if lower_numeric_instr(instr, body, ctx)? {
18        return Ok(());
19    }
20    if lower_aggregate_instr(instr, body, ctx)? {
21        return Ok(());
22    }
23
24    match instr {
25        ruka_mir::MirInstr::ConstUnit { .. } => Ok(()),
26        ruka_mir::MirInstr::ReleaseHeap { local } => {
27            let ownership = local_heap_ownership(
28                ctx.local_heap_ownership,
29                local.as_u32(),
30                "release heap ownership",
31            )?;
32            if !ownership.uses_heap_ops() {
33                return Err(LowerError::UnsupportedInstruction(
34                    "release heap for non-owned local",
35                ));
36            }
37            let value_ty = local_ty(ctx.local_tys, local.as_u32(), "release heap ty")?;
38            let value_local =
39                runtime_local_index(ctx.local_indices, local.as_u32(), "release heap")?;
40            match value_ty {
41                Ty::Pointer(item) => emit_pointer_release(
42                    body,
43                    ctx.runtime,
44                    ctx.memory_id,
45                    value_local,
46                    item.as_ref(),
47                    ctx.structs,
48                    ctx.enums,
49                    ctx.pointer_drop_functions,
50                    ctx.scratch_i32_local,
51                    ctx.scratch_i32_local_b,
52                    ctx.scratch_i32_local_d,
53                    ctx.scratch_i32_local_c,
54                    ctx.scratch_i64_local,
55                ),
56                Ty::String => emit_string_release(
57                    body,
58                    ctx.runtime,
59                    ctx.memory_id,
60                    value_local,
61                    ctx.scratch_i32_local,
62                    ctx.scratch_i32_local_b,
63                    ctx.scratch_i32_local_c,
64                ),
65                Ty::Array { item, .. } | Ty::Slice(item) => {
66                    if matches!(ownership, ruka_mir::MirHeapOwnership::OwnedShallow) {
67                        emit_array_release_shallow(
68                            body,
69                            ctx.runtime,
70                            ctx.memory_id,
71                            value_local,
72                            ctx.scratch_i32_local,
73                            ctx.scratch_i32_local_b,
74                            ctx.scratch_i32_local_c,
75                            ctx.scratch_i32_local_d,
76                            ctx.scratch_i64_local,
77                        )
78                    } else {
79                        emit_array_release(
80                            body,
81                            ctx.runtime,
82                            ctx.memory_id,
83                            value_local,
84                            item.as_ref(),
85                            ctx.structs,
86                            ctx.enums,
87                            ctx.pointer_drop_functions,
88                            ctx.scratch_i32_local,
89                            ctx.scratch_i32_local_b,
90                            ctx.scratch_i32_local_c,
91                            ctx.scratch_i32_local_d,
92                            ctx.scratch_i64_local,
93                        )
94                    }
95                }
96                Ty::Enum { .. } => emit_enum_release(
97                    body,
98                    ctx.runtime,
99                    ctx.memory_id,
100                    value_local,
101                    value_ty,
102                    ctx.structs,
103                    ctx.enums,
104                    ctx.pointer_drop_functions,
105                    ctx.scratch_i32_local,
106                    ctx.scratch_i32_local_b,
107                    ctx.scratch_i32_local_c,
108                    ctx.scratch_i32_local_d,
109                    ctx.scratch_i64_local,
110                ),
111                _ => Err(LowerError::UnsupportedInstruction(
112                    "release heap for unsupported type",
113                )),
114            }
115        }
116        ruka_mir::MirInstr::ConstInt { dst, value } => {
117            let dst_id = dst.as_u32();
118            if let Some(dst) = runtime_local(ctx.local_indices, dst_id)? {
119                let dst_ty =
120                    runtime_local_valtype(ctx.local_runtime_types, dst_id, "const int dst")?;
121                match dst_ty {
122                    ValType::I32 => {
123                        body.i32_const(*value as i32).local_set(dst);
124                    }
125                    ValType::I64 => {
126                        body.i64_const(*value).local_set(dst);
127                    }
128                    ValType::F32 => {
129                        body.f32_const(*value as f32).local_set(dst);
130                    }
131                    ValType::F64 => {
132                        body.f64_const(*value as f64).local_set(dst);
133                    }
134                    _ => {
135                        return Err(LowerError::UnsupportedInstruction(
136                            "unsupported const int dst type",
137                        ));
138                    }
139                }
140            }
141            Ok(())
142        }
143        ruka_mir::MirInstr::ConstFloat { dst, value } => {
144            let dst_id = dst.as_u32();
145            if let Some(dst) = runtime_local(ctx.local_indices, dst_id)? {
146                let dst_ty =
147                    runtime_local_valtype(ctx.local_runtime_types, dst_id, "const float dst")?;
148                match dst_ty {
149                    ValType::F32 => {
150                        body.f32_const(*value as f32).local_set(dst);
151                    }
152                    ValType::F64 => {
153                        body.f64_const(*value).local_set(dst);
154                    }
155                    _ => {
156                        return Err(LowerError::UnsupportedInstruction(
157                            "unsupported const float dst type",
158                        ));
159                    }
160                }
161            }
162            Ok(())
163        }
164        ruka_mir::MirInstr::ConstString { dst, value } => {
165            if let Some(dst) = runtime_local(ctx.local_indices, dst.as_u32())? {
166                let string_ptr = *ctx
167                    .string_literal_offsets
168                    .get(value)
169                    .ok_or_else(|| LowerError::UnknownStringLiteral(value.clone()))?;
170                body.i32_const(int32_from_u32(string_ptr, "string literal offset")?)
171                    .local_set(dst);
172            }
173            Ok(())
174        }
175        ruka_mir::MirInstr::ConstBool { dst, value } => {
176            if let Some(dst) = runtime_local(ctx.local_indices, dst.as_u32())? {
177                body.i32_const(if *value { 1 } else { 0 }).local_set(dst);
178            }
179            Ok(())
180        }
181        ruka_mir::MirInstr::ConstNull { dst } => {
182            if let Some(dst) = runtime_local(ctx.local_indices, dst.as_u32())? {
183                body.i32_const(0).local_set(dst);
184            }
185            Ok(())
186        }
187        ruka_mir::MirInstr::PointerNew { src, dst } => {
188            let src_local = runtime_local_index(ctx.local_indices, src.as_u32(), "pointer src")?;
189            let src_ty =
190                runtime_local_valtype(ctx.local_runtime_types, src.as_u32(), "pointer src ty")?;
191            let dst_local = runtime_local_index(ctx.local_indices, dst.as_u32(), "pointer dst")?;
192            emit_pointer_alloc(
193                body,
194                ctx.runtime,
195                ctx.memory_id,
196                None,
197                ctx.function_line,
198                src_local,
199                src_ty,
200                dst_local,
201            )?;
202            Ok(())
203        }
204        ruka_mir::MirInstr::PointerIsSome { pointer, dst } => {
205            let pointer_local =
206                runtime_local_index(ctx.local_indices, pointer.as_u32(), "pointer is some")?;
207            let dst_local = runtime_local_index(ctx.local_indices, dst.as_u32(), "pointer bool")?;
208            body.local_get(pointer_local)
209                .i32_const(0)
210                .binop(BinaryOp::I32Ne)
211                .local_set(dst_local);
212            Ok(())
213        }
214        ruka_mir::MirInstr::PointerBorrowRo { pointer, dst }
215        | ruka_mir::MirInstr::PointerBorrowMut { pointer, dst } => {
216            let pointer_local =
217                runtime_local_index(ctx.local_indices, pointer.as_u32(), "pointer borrow src")?;
218            let dst_local =
219                runtime_local_index(ctx.local_indices, dst.as_u32(), "pointer borrow dst")?;
220            body.local_get(pointer_local)
221                .i32_const(POINTER_VALUE_OFFSET as i32)
222                .binop(BinaryOp::I32Add)
223                .local_set(dst_local);
224            Ok(())
225        }
226        ruka_mir::MirInstr::Copy { src, dst }
227        | ruka_mir::MirInstr::Move { src, dst }
228        | ruka_mir::MirInstr::AssignLocal { src, dst } => {
229            match (
230                runtime_local(ctx.local_indices, src.as_u32())?,
231                runtime_local(ctx.local_indices, dst.as_u32())?,
232            ) {
233                (Some(src_local), Some(dst_local)) => {
234                    let src_mir_ty = local_ty(ctx.local_tys, src.as_u32(), "copy src mir ty")?;
235                    let dst_mir_ty = local_ty(ctx.local_tys, dst.as_u32(), "copy dst mir ty")?;
236                    if matches!(instr, ruka_mir::MirInstr::Copy { .. }) {
237                        let ownership = local_heap_ownership(
238                            ctx.local_heap_ownership,
239                            src.as_u32(),
240                            "copy src ownership",
241                        )?;
242                        if ownership.uses_heap_ops() {
243                            emit_clone_for_type(
244                                body,
245                                ctx.runtime,
246                                ctx.memory_id,
247                                None,
248                                ctx.function_line,
249                                src_local,
250                                src_mir_ty,
251                                ctx.structs,
252                                ctx.enums,
253                                ctx.scratch_i32_local,
254                                ctx.scratch_i32_local_b,
255                                ctx.scratch_i32_local_c,
256                                ctx.scratch_i32_local_d,
257                                ctx.scratch_i32_local_e,
258                                ctx.scratch_i64_local,
259                                dst_local,
260                            )?;
261                            return Ok(());
262                        }
263                    }
264                    let src_ty = runtime_local_valtype(
265                        ctx.local_runtime_types,
266                        src.as_u32(),
267                        "copy src ty",
268                    )?;
269                    let dst_ty = runtime_local_valtype(
270                        ctx.local_runtime_types,
271                        dst.as_u32(),
272                        "copy dst ty",
273                    )?;
274                    body.local_get(src_local);
275                    match (src_ty, dst_ty) {
276                        (ValType::I64, ValType::I64)
277                        | (ValType::I32, ValType::I32)
278                        | (ValType::F32, ValType::F32)
279                        | (ValType::F64, ValType::F64) => {}
280                        (ValType::I32, ValType::I64) => {
281                            body.unop(UnaryOp::I64ExtendUI32);
282                        }
283                        (ValType::I64, ValType::I32) => {
284                            body.unop(UnaryOp::I32WrapI64);
285                        }
286                        (ValType::F32, ValType::F64) => {
287                            body.unop(UnaryOp::F64PromoteF32);
288                        }
289                        (ValType::F64, ValType::F32) => {
290                            body.unop(UnaryOp::F32DemoteF64);
291                        }
292                        _ => {
293                            return Err(LowerError::UnsupportedInstruction(
294                                "unsupported copy runtime type conversion",
295                            ));
296                        }
297                    }
298                    emit_normalize_integer_top_of_stack(body, dst_mir_ty, dst_ty)?;
299                    body.local_set(dst_local);
300                    if matches!(instr, ruka_mir::MirInstr::Move { .. }) {
301                        let ownership = local_heap_ownership(
302                            ctx.local_heap_ownership,
303                            src.as_u32(),
304                            "move src ownership",
305                        )?;
306                        if ownership.uses_heap_ops() {
307                            body.i32_const(0).local_set(src_local);
308                        }
309                    }
310                }
311                (None, None) => {}
312                (Some(_), None) => {}
313                (None, Some(_)) => {
314                    return Err(LowerError::UnsupportedInstruction("copy from unit local"));
315                }
316            }
317            Ok(())
318        }
319        ruka_mir::MirInstr::NumCast { src, dst } => {
320            let src_local = runtime_local_index(ctx.local_indices, src.as_u32(), "num cast src")?;
321            let dst_local = runtime_local_index(ctx.local_indices, dst.as_u32(), "num cast dst")?;
322            let src_ty =
323                runtime_local_valtype(ctx.local_runtime_types, src.as_u32(), "num cast src ty")?;
324            let dst_ty =
325                runtime_local_valtype(ctx.local_runtime_types, dst.as_u32(), "num cast dst ty")?;
326            let semantic_src_ty = local_ty(ctx.local_tys, src.as_u32(), "num cast src semantic")?;
327            let semantic_dst_ty = local_ty(ctx.local_tys, dst.as_u32(), "num cast dst semantic")?;
328            body.local_get(src_local);
329            match (src_ty, dst_ty) {
330                (ValType::I32, ValType::I64) => {
331                    if is_unsigned_integer_ty(semantic_src_ty) {
332                        body.unop(UnaryOp::I64ExtendUI32);
333                    } else {
334                        body.unop(UnaryOp::I64ExtendSI32);
335                    }
336                }
337                (ValType::I64, ValType::I32) => {
338                    body.unop(UnaryOp::I32WrapI64);
339                }
340                (ValType::I32, ValType::F32) => {
341                    if is_unsigned_integer_ty(semantic_src_ty) {
342                        body.unop(UnaryOp::F32ConvertUI32);
343                    } else {
344                        body.unop(UnaryOp::F32ConvertSI32);
345                    }
346                }
347                (ValType::I32, ValType::F64) => {
348                    if is_unsigned_integer_ty(semantic_src_ty) {
349                        body.unop(UnaryOp::F64ConvertUI32);
350                    } else {
351                        body.unop(UnaryOp::F64ConvertSI32);
352                    }
353                }
354                (ValType::I64, ValType::F32) => {
355                    if is_unsigned_integer_ty(semantic_src_ty) {
356                        body.unop(UnaryOp::F32ConvertUI64);
357                    } else {
358                        body.unop(UnaryOp::F32ConvertSI64);
359                    }
360                }
361                (ValType::I64, ValType::F64) => {
362                    if is_unsigned_integer_ty(semantic_src_ty) {
363                        body.unop(UnaryOp::F64ConvertUI64);
364                    } else {
365                        body.unop(UnaryOp::F64ConvertSI64);
366                    }
367                }
368                (ValType::F32, ValType::F64) => {
369                    body.unop(UnaryOp::F64PromoteF32);
370                }
371                (ValType::F64, ValType::F32) => {
372                    body.unop(UnaryOp::F32DemoteF64);
373                }
374                (ValType::F32, ValType::I32) => {
375                    if is_unsigned_integer_ty(semantic_dst_ty) {
376                        body.unop(UnaryOp::I32TruncUF32);
377                    } else {
378                        body.unop(UnaryOp::I32TruncSF32);
379                    }
380                }
381                (ValType::F32, ValType::I64) => {
382                    if is_unsigned_integer_ty(semantic_dst_ty) {
383                        body.unop(UnaryOp::I64TruncUF32);
384                    } else {
385                        body.unop(UnaryOp::I64TruncSF32);
386                    }
387                }
388                (ValType::F64, ValType::I32) => {
389                    if is_unsigned_integer_ty(semantic_dst_ty) {
390                        body.unop(UnaryOp::I32TruncUF64);
391                    } else {
392                        body.unop(UnaryOp::I32TruncSF64);
393                    }
394                }
395                (ValType::F64, ValType::I64) => {
396                    if is_unsigned_integer_ty(semantic_dst_ty) {
397                        body.unop(UnaryOp::I64TruncUF64);
398                    } else {
399                        body.unop(UnaryOp::I64TruncSF64);
400                    }
401                }
402                _ => {}
403            }
404            emit_normalize_integer_top_of_stack(body, semantic_dst_ty, dst_ty)?;
405            body.local_set(dst_local);
406            Ok(())
407        }
408        ruka_mir::MirInstr::CheckedIntCast { src, dst } => {
409            let src_local =
410                runtime_local_index(ctx.local_indices, src.as_u32(), "checked cast src")?;
411            let dst_local =
412                runtime_local_index(ctx.local_indices, dst.as_u32(), "checked cast dst")?;
413            let src_ty = runtime_local_valtype(
414                ctx.local_runtime_types,
415                src.as_u32(),
416                "checked cast src ty",
417            )?;
418            let dst_ty = runtime_local_valtype(
419                ctx.local_runtime_types,
420                dst.as_u32(),
421                "checked cast dst ty",
422            )?;
423            let semantic_src_ty =
424                local_ty(ctx.local_tys, src.as_u32(), "checked cast src semantic")?;
425            let semantic_dst_ty =
426                local_ty(ctx.local_tys, dst.as_u32(), "checked cast dst semantic")?;
427            if !(semantic_src_ty.is_integer() && semantic_dst_ty.is_integer()) {
428                return Err(LowerError::UnsupportedInstruction(
429                    "checked int cast requires integer locals",
430                ));
431            }
432
433            body.local_get(src_local);
434            match src_ty {
435                ValType::I32 => {
436                    if is_unsigned_integer_ty(semantic_src_ty) {
437                        body.unop(UnaryOp::I64ExtendUI32);
438                    } else {
439                        body.unop(UnaryOp::I64ExtendSI32);
440                    }
441                }
442                ValType::I64 => {}
443                _ => {
444                    return Err(LowerError::UnsupportedInstruction(
445                        "checked int cast only supports i32/i64 runtime values",
446                    ));
447                }
448            }
449            body.local_set(ctx.scratch_i64_local);
450
451            if is_unsigned_integer_ty(semantic_src_ty) {
452                if is_unsigned_integer_ty(semantic_dst_ty) {
453                    if let Some(max) = unsigned_integer_max_u64(semantic_dst_ty)
454                        && max != u64::MAX
455                    {
456                        emit_i64_check_unsigned_lte(body, ctx.scratch_i64_local, max as i64);
457                    }
458                } else if let Some(max) = signed_integer_max_i64(semantic_dst_ty) {
459                    emit_i64_check_unsigned_lte(body, ctx.scratch_i64_local, max);
460                }
461            } else if is_unsigned_integer_ty(semantic_dst_ty) {
462                emit_i64_check_signed_gte(body, ctx.scratch_i64_local, 0);
463                if let Some(max) = unsigned_integer_max_u64(semantic_dst_ty)
464                    && max != u64::MAX
465                {
466                    emit_i64_check_signed_lte(body, ctx.scratch_i64_local, max as i64);
467                }
468            } else {
469                let min = signed_integer_min_i64(semantic_dst_ty)
470                    .ok_or(LowerError::UnsupportedInstruction("checked cast dst min"))?;
471                let max = signed_integer_max_i64(semantic_dst_ty)
472                    .ok_or(LowerError::UnsupportedInstruction("checked cast dst max"))?;
473                emit_i64_check_signed_gte(body, ctx.scratch_i64_local, min);
474                emit_i64_check_signed_lte(body, ctx.scratch_i64_local, max);
475            }
476
477            body.local_get(ctx.scratch_i64_local);
478            match dst_ty {
479                ValType::I64 => {}
480                ValType::I32 => {
481                    body.unop(UnaryOp::I32WrapI64);
482                }
483                _ => {
484                    return Err(LowerError::UnsupportedInstruction(
485                        "checked int cast runtime type mismatch",
486                    ));
487                }
488            }
489            body.local_set(dst_local);
490            Ok(())
491        }
492        ruka_mir::MirInstr::DerefCopy { src, dst } => {
493            let src_ref = runtime_local_index(ctx.local_indices, src.as_u32(), "deref src")?;
494            let dst_local = runtime_local_index(ctx.local_indices, dst.as_u32(), "deref dst")?;
495            let src_ty =
496                runtime_local_valtype(ctx.local_runtime_types, src.as_u32(), "deref src ty")?;
497            let dst_ty =
498                runtime_local_valtype(ctx.local_runtime_types, dst.as_u32(), "deref dst ty")?;
499            if is_passthrough_place_local(ctx, *src) {
500                body.local_get(src_ref);
501                match (src_ty, dst_ty) {
502                    (ValType::I64, ValType::I64) | (ValType::I32, ValType::I32) => {}
503                    (ValType::I32, ValType::I64) => {
504                        body.unop(UnaryOp::I64ExtendUI32);
505                    }
506                    (ValType::I64, ValType::I32) => {
507                        body.unop(UnaryOp::I32WrapI64);
508                    }
509                    _ => {
510                        return Err(LowerError::UnsupportedInstruction(
511                            "unsupported passthrough deref runtime type conversion",
512                        ));
513                    }
514                }
515            } else {
516                body.local_get(src_ref).instr(Load {
517                    memory: ctx.memory_id,
518                    kind: LoadKind::I64 { atomic: false },
519                    arg: MemArg {
520                        align: 8,
521                        offset: 0,
522                    },
523                });
524                match dst_ty {
525                    ValType::I64 => {}
526                    ValType::I32 => {
527                        body.unop(UnaryOp::I32WrapI64);
528                    }
529                    _ => {
530                        return Err(LowerError::UnsupportedInstruction(
531                            "unsupported deref destination type",
532                        ));
533                    }
534                }
535            }
536            body.local_set(dst_local);
537            Ok(())
538        }
539        ruka_mir::MirInstr::StoreRef { src, dst_ref } => {
540            let src_local = runtime_local_index(ctx.local_indices, src.as_u32(), "store ref src")?;
541            let src_ty =
542                runtime_local_valtype(ctx.local_runtime_types, src.as_u32(), "store ref src ty")?;
543            let dst_ref_id = *dst_ref;
544            let dst_ref =
545                runtime_local_index(ctx.local_indices, dst_ref_id.as_u32(), "store ref dst")?;
546            if is_passthrough_place_local(ctx, dst_ref_id) {
547                let dst_ty = runtime_local_valtype(
548                    ctx.local_runtime_types,
549                    dst_ref_id.as_u32(),
550                    "store ref dst ty",
551                )?;
552                body.local_get(src_local);
553                match (src_ty, dst_ty) {
554                    (ValType::I64, ValType::I64)
555                    | (ValType::I32, ValType::I32)
556                    | (ValType::F32, ValType::F32)
557                    | (ValType::F64, ValType::F64) => {}
558                    (ValType::I32, ValType::I64) => {
559                        body.unop(UnaryOp::I64ExtendUI32);
560                    }
561                    (ValType::I64, ValType::I32) => {
562                        body.unop(UnaryOp::I32WrapI64);
563                    }
564                    (ValType::F32, ValType::F64) => {
565                        body.unop(UnaryOp::F64PromoteF32);
566                    }
567                    (ValType::F64, ValType::F32) => {
568                        body.unop(UnaryOp::F32DemoteF64);
569                    }
570                    _ => {
571                        return Err(LowerError::UnsupportedInstruction(
572                            "unsupported store ref passthrough conversion",
573                        ));
574                    }
575                }
576                body.local_set(dst_ref);
577                return Ok(());
578            }
579            body.local_get(dst_ref).local_get(src_local);
580            match src_ty {
581                ValType::I64 => {}
582                ValType::I32 => {
583                    body.unop(UnaryOp::I64ExtendUI32);
584                }
585                _ => {
586                    return Err(LowerError::UnsupportedInstruction(
587                        "unsupported store ref source type",
588                    ));
589                }
590            }
591            body.instr(Store {
592                memory: ctx.memory_id,
593                kind: StoreKind::I64 { atomic: false },
594                arg: MemArg {
595                    align: 8,
596                    offset: 0,
597                },
598            });
599            Ok(())
600        }
601        ruka_mir::MirInstr::Call { .. }
602        | ruka_mir::MirInstr::CallIntrinsic { .. }
603        | ruka_mir::MirInstr::CallExtern { .. }
604        | ruka_mir::MirInstr::IntAdd { .. }
605        | ruka_mir::MirInstr::IntSub { .. }
606        | ruka_mir::MirInstr::IntMul { .. }
607        | ruka_mir::MirInstr::IntDiv { .. }
608        | ruka_mir::MirInstr::IntMod { .. }
609        | ruka_mir::MirInstr::IntLt { .. }
610        | ruka_mir::MirInstr::IntGt { .. }
611        | ruka_mir::MirInstr::IntLtEq { .. }
612        | ruka_mir::MirInstr::IntGtEq { .. }
613        | ruka_mir::MirInstr::IntEq { .. }
614        | ruka_mir::MirInstr::IntNeq { .. }
615        | ruka_mir::MirInstr::MakeArray { .. }
616        | ruka_mir::MirInstr::CollectionLen { .. }
617        | ruka_mir::MirInstr::IndexBorrowRo { .. }
618        | ruka_mir::MirInstr::IndexBorrowMut { .. }
619        | ruka_mir::MirInstr::MakeStruct { .. }
620        | ruka_mir::MirInstr::ReadField { .. }
621        | ruka_mir::MirInstr::FieldBorrowRo { .. }
622        | ruka_mir::MirInstr::FieldBorrowMut { .. }
623        | ruka_mir::MirInstr::MakeEnum { .. }
624        | ruka_mir::MirInstr::EnumIsVariant { .. }
625        | ruka_mir::MirInstr::EnumGetField { .. } => {
626            unreachable!("instruction should be lowered by helper before main match")
627        }
628        _ => Err(LowerError::UnsupportedInstruction(instr_name(instr))),
629    }
630}