Skip to main content

wave_compiler/emit/
wave_emit.rs

1// Copyright 2026 Ojima Abraham
2// SPDX-License-Identifier: Apache-2.0
3
4//! LIR to WAVE instruction emission.
5//!
6//! Converts LIR instructions with physical registers to WAVE encoded
7//! instruction words. Uses wave-decode's opcode and field definitions
8//! for bit-accurate encoding.
9
10use wave_decode::opcodes::{
11    CmpOp, ControlOp, FUnaryOp, MemWidth as DecodeMemWidth, MiscOp, Opcode, SyncOp, MODIFIER_MASK,
12    MODIFIER_SHIFT, OPCODE_SHIFT, RD_SHIFT, RS1_SHIFT, RS2_SHIFT,
13};
14
15use crate::lir::instruction::LirInst;
16use crate::lir::operand::{MemWidth, PhysReg, VReg};
17
18const SYNC_OP_FLAG: u32 = 0x01;
19const MISC_OP_FLAG: u32 = 0x02;
20
21/// An encoded WAVE instruction (4 or 8 bytes).
22#[derive(Debug, Clone)]
23pub struct EncodedInst {
24    /// First instruction word.
25    pub word0: u32,
26    /// Optional second word for extended instructions.
27    pub word1: Option<u32>,
28}
29
30impl EncodedInst {
31    /// Returns the size in bytes.
32    #[must_use]
33    pub fn size(&self) -> usize {
34        if self.word1.is_some() {
35            8
36        } else {
37            4
38        }
39    }
40
41    /// Encode to bytes (little-endian).
42    #[must_use]
43    pub fn to_bytes(&self) -> Vec<u8> {
44        let mut bytes = self.word0.to_le_bytes().to_vec();
45        if let Some(w1) = self.word1 {
46            bytes.extend_from_slice(&w1.to_le_bytes());
47        }
48        bytes
49    }
50}
51
52/// Mapping from `VReg` to physical register index.
53pub type RegMap = std::collections::HashMap<VReg, PhysReg>;
54
55/// Emit a LIR instruction as encoded WAVE bytes using the register mapping.
56///
57/// For instructions that still use `VReg`s (before register allocation),
58/// the `VReg` number is used directly as the physical register index.
59#[must_use]
60pub fn emit_instruction(inst: &LirInst, reg_map: &RegMap) -> EncodedInst {
61    match inst {
62        LirInst::Iadd { .. }
63        | LirInst::Isub { .. }
64        | LirInst::Imul { .. }
65        | LirInst::Idiv { .. }
66        | LirInst::Imod { .. }
67        | LirInst::Ineg { .. }
68        | LirInst::Fadd { .. }
69        | LirInst::Fsub { .. }
70        | LirInst::Fmul { .. }
71        | LirInst::Fdiv { .. }
72        | LirInst::Fma { .. }
73        | LirInst::Fneg { .. }
74        | LirInst::Fabs { .. }
75        | LirInst::Fsqrt { .. }
76        | LirInst::Fsin { .. }
77        | LirInst::Fcos { .. }
78        | LirInst::Fexp2 { .. }
79        | LirInst::Flog2 { .. }
80        | LirInst::Fmin { .. }
81        | LirInst::Fmax { .. }
82        | LirInst::And { .. }
83        | LirInst::Or { .. }
84        | LirInst::Xor { .. }
85        | LirInst::Not { .. }
86        | LirInst::Shl { .. }
87        | LirInst::Shr { .. }
88        | LirInst::Sar { .. }
89        | LirInst::CvtF32I32 { .. }
90        | LirInst::CvtI32F32 { .. } => emit_alu(inst, reg_map),
91        LirInst::MovImm { .. } | LirInst::MovReg { .. } | LirInst::MovSr { .. } => {
92            emit_mov(inst, reg_map)
93        }
94        LirInst::LocalLoad { .. }
95        | LirInst::LocalStore { .. }
96        | LirInst::DeviceLoad { .. }
97        | LirInst::DeviceStore { .. } => emit_memory(inst, reg_map),
98        LirInst::IcmpEq { .. }
99        | LirInst::IcmpNe { .. }
100        | LirInst::IcmpLt { .. }
101        | LirInst::IcmpLe { .. }
102        | LirInst::IcmpGt { .. }
103        | LirInst::IcmpGe { .. }
104        | LirInst::UcmpLt { .. }
105        | LirInst::FcmpEq { .. }
106        | LirInst::FcmpLt { .. }
107        | LirInst::FcmpGt { .. } => emit_compare(inst, reg_map),
108        LirInst::If { .. }
109        | LirInst::Else
110        | LirInst::Endif
111        | LirInst::Loop
112        | LirInst::Break { .. }
113        | LirInst::Continue { .. }
114        | LirInst::Endloop
115        | LirInst::Barrier
116        | LirInst::Halt => emit_control(inst, reg_map),
117    }
118}
119
120fn emit_alu(inst: &LirInst, reg_map: &RegMap) -> EncodedInst {
121    match inst {
122        LirInst::Iadd { .. }
123        | LirInst::Isub { .. }
124        | LirInst::Imul { .. }
125        | LirInst::Idiv { .. }
126        | LirInst::Imod { .. }
127        | LirInst::Ineg { .. } => emit_int_alu(inst, reg_map),
128        LirInst::Fadd { .. }
129        | LirInst::Fsub { .. }
130        | LirInst::Fmul { .. }
131        | LirInst::Fdiv { .. }
132        | LirInst::Fma { .. }
133        | LirInst::Fneg { .. }
134        | LirInst::Fabs { .. }
135        | LirInst::Fsqrt { .. }
136        | LirInst::Fsin { .. }
137        | LirInst::Fcos { .. }
138        | LirInst::Fexp2 { .. }
139        | LirInst::Flog2 { .. }
140        | LirInst::Fmin { .. }
141        | LirInst::Fmax { .. } => emit_float_alu(inst, reg_map),
142        LirInst::And { .. }
143        | LirInst::Or { .. }
144        | LirInst::Xor { .. }
145        | LirInst::Not { .. }
146        | LirInst::Shl { .. }
147        | LirInst::Shr { .. }
148        | LirInst::Sar { .. } => emit_bitwise_alu(inst, reg_map),
149        LirInst::CvtF32I32 { dest, src } => {
150            let word0 =
151                encode_base_word(Opcode::Cvt, reg(*dest, reg_map), reg(*src, reg_map), 0, 0);
152            EncodedInst { word0, word1: None }
153        }
154        LirInst::CvtI32F32 { dest, src } => {
155            let word0 =
156                encode_base_word(Opcode::Cvt, reg(*dest, reg_map), reg(*src, reg_map), 0, 2);
157            EncodedInst { word0, word1: None }
158        }
159        _ => unreachable!(),
160    }
161}
162
163fn emit_int_alu(inst: &LirInst, reg_map: &RegMap) -> EncodedInst {
164    match inst {
165        LirInst::Iadd { dest, src1, src2 } => encode_alu3(
166            Opcode::Iadd,
167            reg(*dest, reg_map),
168            reg(*src1, reg_map),
169            reg(*src2, reg_map),
170        ),
171        LirInst::Isub { dest, src1, src2 } => encode_alu3(
172            Opcode::Isub,
173            reg(*dest, reg_map),
174            reg(*src1, reg_map),
175            reg(*src2, reg_map),
176        ),
177        LirInst::Imul { dest, src1, src2 } => encode_alu3(
178            Opcode::Imul,
179            reg(*dest, reg_map),
180            reg(*src1, reg_map),
181            reg(*src2, reg_map),
182        ),
183        LirInst::Idiv { dest, src1, src2 } => encode_alu3(
184            Opcode::Idiv,
185            reg(*dest, reg_map),
186            reg(*src1, reg_map),
187            reg(*src2, reg_map),
188        ),
189        LirInst::Imod { dest, src1, src2 } => encode_alu3(
190            Opcode::Imod,
191            reg(*dest, reg_map),
192            reg(*src1, reg_map),
193            reg(*src2, reg_map),
194        ),
195        LirInst::Ineg { dest, src } => {
196            encode_alu2(Opcode::Ineg, reg(*dest, reg_map), reg(*src, reg_map))
197        }
198        _ => unreachable!(),
199    }
200}
201
202fn emit_float_alu(inst: &LirInst, reg_map: &RegMap) -> EncodedInst {
203    match inst {
204        LirInst::Fadd { dest, src1, src2 } => encode_alu3(
205            Opcode::Fadd,
206            reg(*dest, reg_map),
207            reg(*src1, reg_map),
208            reg(*src2, reg_map),
209        ),
210        LirInst::Fsub { dest, src1, src2 } => encode_alu3(
211            Opcode::Fsub,
212            reg(*dest, reg_map),
213            reg(*src1, reg_map),
214            reg(*src2, reg_map),
215        ),
216        LirInst::Fmul { dest, src1, src2 } => encode_alu3(
217            Opcode::Fmul,
218            reg(*dest, reg_map),
219            reg(*src1, reg_map),
220            reg(*src2, reg_map),
221        ),
222        LirInst::Fdiv { dest, src1, src2 } => encode_alu3(
223            Opcode::Fdiv,
224            reg(*dest, reg_map),
225            reg(*src1, reg_map),
226            reg(*src2, reg_map),
227        ),
228        LirInst::Fma {
229            dest,
230            src1,
231            src2,
232            src3,
233        } => {
234            let word0 = encode_base_word(
235                Opcode::Fma,
236                reg(*dest, reg_map),
237                reg(*src1, reg_map),
238                reg(*src2, reg_map),
239                0,
240            );
241            let word1 = u32::from(reg(*src3, reg_map)) << 27;
242            EncodedInst {
243                word0,
244                word1: Some(word1),
245            }
246        }
247        LirInst::Fneg { dest, src } => {
248            encode_alu2(Opcode::Fneg, reg(*dest, reg_map), reg(*src, reg_map))
249        }
250        LirInst::Fabs { dest, src } => {
251            encode_alu2(Opcode::Fabs, reg(*dest, reg_map), reg(*src, reg_map))
252        }
253        LirInst::Fsqrt { dest, src } => {
254            encode_alu2(Opcode::Fsqrt, reg(*dest, reg_map), reg(*src, reg_map))
255        }
256        LirInst::Fsin { dest, src } => {
257            encode_funary(FUnaryOp::Fsin, reg(*dest, reg_map), reg(*src, reg_map))
258        }
259        LirInst::Fcos { dest, src } => {
260            encode_funary(FUnaryOp::Fcos, reg(*dest, reg_map), reg(*src, reg_map))
261        }
262        LirInst::Fexp2 { dest, src } => {
263            encode_funary(FUnaryOp::Fexp2, reg(*dest, reg_map), reg(*src, reg_map))
264        }
265        LirInst::Flog2 { dest, src } => {
266            encode_funary(FUnaryOp::Flog2, reg(*dest, reg_map), reg(*src, reg_map))
267        }
268        LirInst::Fmin { dest, src1, src2 } => encode_alu3(
269            Opcode::Fmin,
270            reg(*dest, reg_map),
271            reg(*src1, reg_map),
272            reg(*src2, reg_map),
273        ),
274        LirInst::Fmax { dest, src1, src2 } => encode_alu3(
275            Opcode::Fmax,
276            reg(*dest, reg_map),
277            reg(*src1, reg_map),
278            reg(*src2, reg_map),
279        ),
280        _ => unreachable!(),
281    }
282}
283
284fn emit_bitwise_alu(inst: &LirInst, reg_map: &RegMap) -> EncodedInst {
285    match inst {
286        LirInst::And { dest, src1, src2 } => encode_alu3(
287            Opcode::And,
288            reg(*dest, reg_map),
289            reg(*src1, reg_map),
290            reg(*src2, reg_map),
291        ),
292        LirInst::Or { dest, src1, src2 } => encode_alu3(
293            Opcode::Or,
294            reg(*dest, reg_map),
295            reg(*src1, reg_map),
296            reg(*src2, reg_map),
297        ),
298        LirInst::Xor { dest, src1, src2 } => encode_alu3(
299            Opcode::Xor,
300            reg(*dest, reg_map),
301            reg(*src1, reg_map),
302            reg(*src2, reg_map),
303        ),
304        LirInst::Not { dest, src } => {
305            encode_alu2(Opcode::Not, reg(*dest, reg_map), reg(*src, reg_map))
306        }
307        LirInst::Shl { dest, src1, src2 } => encode_alu3(
308            Opcode::Shl,
309            reg(*dest, reg_map),
310            reg(*src1, reg_map),
311            reg(*src2, reg_map),
312        ),
313        LirInst::Shr { dest, src1, src2 } => encode_alu3(
314            Opcode::Shr,
315            reg(*dest, reg_map),
316            reg(*src1, reg_map),
317            reg(*src2, reg_map),
318        ),
319        LirInst::Sar { dest, src1, src2 } => encode_alu3(
320            Opcode::Sar,
321            reg(*dest, reg_map),
322            reg(*src1, reg_map),
323            reg(*src2, reg_map),
324        ),
325        _ => unreachable!(),
326    }
327}
328
329fn emit_mov(inst: &LirInst, reg_map: &RegMap) -> EncodedInst {
330    match inst {
331        LirInst::MovImm { dest, value } => {
332            let word0 =
333                encode_control_word(MiscOp::MovImm as u8, reg(*dest, reg_map), 0, 0) | MISC_OP_FLAG;
334            EncodedInst {
335                word0,
336                word1: Some(*value),
337            }
338        }
339        LirInst::MovReg { dest, src } => {
340            let word0 = encode_control_word(
341                MiscOp::Mov as u8,
342                reg(*dest, reg_map),
343                reg(*src, reg_map),
344                0,
345            ) | MISC_OP_FLAG;
346            EncodedInst { word0, word1: None }
347        }
348        LirInst::MovSr { dest, sr } => {
349            let word0 =
350                encode_control_word(MiscOp::MovSr as u8, reg(*dest, reg_map), sr.index(), 0)
351                    | MISC_OP_FLAG;
352            EncodedInst { word0, word1: None }
353        }
354        _ => unreachable!(),
355    }
356}
357
358fn emit_memory(inst: &LirInst, reg_map: &RegMap) -> EncodedInst {
359    match inst {
360        LirInst::LocalLoad { dest, addr, width } => encode_mem(
361            Opcode::LocalLoad,
362            reg(*dest, reg_map),
363            reg(*addr, reg_map),
364            0,
365            lir_width_to_decode(*width),
366        ),
367        LirInst::LocalStore { addr, value, width } => encode_mem(
368            Opcode::LocalStore,
369            0,
370            reg(*addr, reg_map),
371            reg(*value, reg_map),
372            lir_width_to_decode(*width),
373        ),
374        LirInst::DeviceLoad { dest, addr, width } => encode_mem(
375            Opcode::DeviceLoad,
376            reg(*dest, reg_map),
377            reg(*addr, reg_map),
378            0,
379            lir_width_to_decode(*width),
380        ),
381        LirInst::DeviceStore { addr, value, width } => encode_mem(
382            Opcode::DeviceStore,
383            0,
384            reg(*addr, reg_map),
385            reg(*value, reg_map),
386            lir_width_to_decode(*width),
387        ),
388        _ => unreachable!(),
389    }
390}
391
392fn emit_compare(inst: &LirInst, reg_map: &RegMap) -> EncodedInst {
393    match inst {
394        LirInst::IcmpEq { dest, src1, src2 } => encode_cmp(
395            Opcode::Icmp,
396            CmpOp::Eq,
397            dest.0,
398            reg(*src1, reg_map),
399            reg(*src2, reg_map),
400        ),
401        LirInst::IcmpNe { dest, src1, src2 } => encode_cmp(
402            Opcode::Icmp,
403            CmpOp::Ne,
404            dest.0,
405            reg(*src1, reg_map),
406            reg(*src2, reg_map),
407        ),
408        LirInst::IcmpLt { dest, src1, src2 } => encode_cmp(
409            Opcode::Icmp,
410            CmpOp::Lt,
411            dest.0,
412            reg(*src1, reg_map),
413            reg(*src2, reg_map),
414        ),
415        LirInst::IcmpLe { dest, src1, src2 } => encode_cmp(
416            Opcode::Icmp,
417            CmpOp::Le,
418            dest.0,
419            reg(*src1, reg_map),
420            reg(*src2, reg_map),
421        ),
422        LirInst::IcmpGt { dest, src1, src2 } => encode_cmp(
423            Opcode::Icmp,
424            CmpOp::Gt,
425            dest.0,
426            reg(*src1, reg_map),
427            reg(*src2, reg_map),
428        ),
429        LirInst::IcmpGe { dest, src1, src2 } => encode_cmp(
430            Opcode::Icmp,
431            CmpOp::Ge,
432            dest.0,
433            reg(*src1, reg_map),
434            reg(*src2, reg_map),
435        ),
436        LirInst::UcmpLt { dest, src1, src2 } => encode_cmp(
437            Opcode::Ucmp,
438            CmpOp::Lt,
439            dest.0,
440            reg(*src1, reg_map),
441            reg(*src2, reg_map),
442        ),
443        LirInst::FcmpEq { dest, src1, src2 } => encode_cmp(
444            Opcode::Fcmp,
445            CmpOp::Eq,
446            dest.0,
447            reg(*src1, reg_map),
448            reg(*src2, reg_map),
449        ),
450        LirInst::FcmpLt { dest, src1, src2 } => encode_cmp(
451            Opcode::Fcmp,
452            CmpOp::Lt,
453            dest.0,
454            reg(*src1, reg_map),
455            reg(*src2, reg_map),
456        ),
457        LirInst::FcmpGt { dest, src1, src2 } => encode_cmp(
458            Opcode::Fcmp,
459            CmpOp::Gt,
460            dest.0,
461            reg(*src1, reg_map),
462            reg(*src2, reg_map),
463        ),
464        _ => unreachable!(),
465    }
466}
467
468fn emit_control(inst: &LirInst, _reg_map: &RegMap) -> EncodedInst {
469    match inst {
470        LirInst::If { pred } => {
471            let word0 = encode_control_word(ControlOp::If as u8, 0, pred.0, 0);
472            EncodedInst { word0, word1: None }
473        }
474        LirInst::Else => {
475            let word0 = encode_control_word(ControlOp::Else as u8, 0, 0, 0);
476            EncodedInst { word0, word1: None }
477        }
478        LirInst::Endif => {
479            let word0 = encode_control_word(ControlOp::Endif as u8, 0, 0, 0);
480            EncodedInst { word0, word1: None }
481        }
482        LirInst::Loop => {
483            let word0 = encode_control_word(ControlOp::Loop as u8, 0, 0, 0);
484            EncodedInst { word0, word1: None }
485        }
486        LirInst::Break { pred } => {
487            let word0 = encode_control_word(ControlOp::Break as u8, 0, pred.0, 0);
488            EncodedInst { word0, word1: None }
489        }
490        LirInst::Continue { pred } => {
491            let word0 = encode_control_word(ControlOp::Continue as u8, 0, pred.0, 0);
492            EncodedInst { word0, word1: None }
493        }
494        LirInst::Endloop => {
495            let word0 = encode_control_word(ControlOp::Endloop as u8, 0, 0, 0);
496            EncodedInst { word0, word1: None }
497        }
498        LirInst::Barrier => {
499            let word0 = encode_control_word(SyncOp::Barrier as u8, 0, 0, 0) | SYNC_OP_FLAG;
500            EncodedInst { word0, word1: None }
501        }
502        LirInst::Halt => {
503            let word0 = encode_control_word(SyncOp::Halt as u8, 0, 0, 0) | SYNC_OP_FLAG;
504            EncodedInst { word0, word1: None }
505        }
506        _ => unreachable!(),
507    }
508}
509
510fn reg(vreg: VReg, map: &RegMap) -> u8 {
511    map.get(&vreg)
512        .map_or(u8::try_from(vreg.0).expect("vreg index exceeds u8"), |p| {
513            p.0
514        })
515}
516
517fn encode_base_word(opcode: Opcode, rd: u8, rs1: u8, rs2: u8, modifier: u8) -> u32 {
518    (u32::from(opcode as u8) << OPCODE_SHIFT)
519        | (u32::from(rd & 0x1F) << RD_SHIFT)
520        | (u32::from(rs1 & 0x1F) << RS1_SHIFT)
521        | (u32::from(rs2 & 0x1F) << RS2_SHIFT)
522        | ((u32::from(modifier) & MODIFIER_MASK) << MODIFIER_SHIFT)
523}
524
525fn encode_alu3(opcode: Opcode, rd: u8, rs1: u8, rs2: u8) -> EncodedInst {
526    EncodedInst {
527        word0: encode_base_word(opcode, rd, rs1, rs2, 0),
528        word1: None,
529    }
530}
531
532fn encode_alu2(opcode: Opcode, rd: u8, rs1: u8) -> EncodedInst {
533    EncodedInst {
534        word0: encode_base_word(opcode, rd, rs1, 0, 0),
535        word1: None,
536    }
537}
538
539fn encode_funary(op: FUnaryOp, rd: u8, rs1: u8) -> EncodedInst {
540    EncodedInst {
541        word0: encode_base_word(Opcode::FUnaryOps, rd, rs1, 0, op as u8),
542        word1: None,
543    }
544}
545
546fn encode_mem(opcode: Opcode, rd: u8, rs1: u8, rs2: u8, width: DecodeMemWidth) -> EncodedInst {
547    EncodedInst {
548        word0: encode_base_word(opcode, rd, rs1, rs2, width as u8),
549        word1: None,
550    }
551}
552
553fn encode_cmp(opcode: Opcode, cmp_op: CmpOp, pd: u8, rs1: u8, rs2: u8) -> EncodedInst {
554    EncodedInst {
555        word0: encode_base_word(opcode, pd, rs1, rs2, cmp_op as u8),
556        word1: None,
557    }
558}
559
560fn encode_control_word(modifier: u8, rd: u8, rs1: u8, rs2: u8) -> u32 {
561    encode_base_word(Opcode::Control, rd, rs1, rs2, modifier)
562}
563
564fn lir_width_to_decode(w: MemWidth) -> DecodeMemWidth {
565    match w {
566        MemWidth::W8 => DecodeMemWidth::U8,
567        MemWidth::W16 => DecodeMemWidth::U16,
568        MemWidth::W32 => DecodeMemWidth::U32,
569        MemWidth::W64 => DecodeMemWidth::U64,
570    }
571}
572
573#[cfg(test)]
574mod tests {
575    use super::*;
576    use std::collections::HashMap;
577
578    fn empty_map() -> RegMap {
579        HashMap::new()
580    }
581
582    #[test]
583    fn test_emit_iadd() {
584        let inst = LirInst::Iadd {
585            dest: VReg(5),
586            src1: VReg(3),
587            src2: VReg(4),
588        };
589        let encoded = emit_instruction(&inst, &empty_map());
590        assert!(encoded.word1.is_none());
591        let opcode = (encoded.word0 >> OPCODE_SHIFT) & 0x3F;
592        assert_eq!(opcode, Opcode::Iadd as u32);
593        let rd = (encoded.word0 >> RD_SHIFT) & 0x1F;
594        assert_eq!(rd, 5);
595    }
596
597    #[test]
598    fn test_emit_device_store_encoding() {
599        let inst = LirInst::DeviceStore {
600            addr: VReg(3),
601            value: VReg(5),
602            width: MemWidth::W32,
603        };
604        let encoded = emit_instruction(&inst, &empty_map());
605        let rs1 = (encoded.word0 >> RS1_SHIFT) & 0x1F;
606        let rs2 = (encoded.word0 >> RS2_SHIFT) & 0x1F;
607        assert_eq!(rs1, 3);
608        assert_eq!(rs2, 5);
609    }
610
611    #[test]
612    fn test_emit_mov_imm() {
613        let inst = LirInst::MovImm {
614            dest: VReg(5),
615            value: 0x1234_5678,
616        };
617        let encoded = emit_instruction(&inst, &empty_map());
618        assert!(encoded.word1.is_some());
619        assert_eq!(encoded.word1.unwrap(), 0x1234_5678);
620    }
621
622    #[test]
623    fn test_emit_halt() {
624        let inst = LirInst::Halt;
625        let encoded = emit_instruction(&inst, &empty_map());
626        let opcode = (encoded.word0 >> OPCODE_SHIFT) & 0x3F;
627        assert_eq!(opcode, Opcode::Control as u32);
628        assert_ne!(encoded.word0 & SYNC_OP_FLAG, 0);
629    }
630
631    #[test]
632    fn test_emit_if_endif() {
633        let if_inst = LirInst::If {
634            pred: crate::lir::operand::PReg(1),
635        };
636        let encoded_if = emit_instruction(&if_inst, &empty_map());
637        let modifier = (encoded_if.word0 >> MODIFIER_SHIFT) & 0x0F;
638        assert_eq!(modifier, ControlOp::If as u32);
639
640        let endif_inst = LirInst::Endif;
641        let encoded_endif = emit_instruction(&endif_inst, &empty_map());
642        let modifier = (encoded_endif.word0 >> MODIFIER_SHIFT) & 0x0F;
643        assert_eq!(modifier, ControlOp::Endif as u32);
644    }
645
646    #[test]
647    fn test_emit_with_reg_map() {
648        let mut map = RegMap::new();
649        map.insert(VReg(0), PhysReg(10));
650        map.insert(VReg(1), PhysReg(11));
651        map.insert(VReg(2), PhysReg(12));
652
653        let inst = LirInst::Iadd {
654            dest: VReg(2),
655            src1: VReg(0),
656            src2: VReg(1),
657        };
658        let encoded = emit_instruction(&inst, &map);
659        let rd = (encoded.word0 >> RD_SHIFT) & 0x1F;
660        let rs1 = (encoded.word0 >> RS1_SHIFT) & 0x1F;
661        let rs2 = (encoded.word0 >> RS2_SHIFT) & 0x1F;
662        assert_eq!(rd, 12);
663        assert_eq!(rs1, 10);
664        assert_eq!(rs2, 11);
665    }
666}