Skip to main content

liil/
nnil.rs

1//! The lifter stores guest operations here so that backends do not have to decode RISC-V.
2//!
3//! NNIL is a linear, append-only intermediate representation. Each operation gets an [`Insn`]
4//! handle. A later operation uses that handle to refer to the earlier result. This design is
5//! similar to a value graph, but explicit `Reg`, `Set`, load, store, and control-flow operations
6//! keep guest state visible.
7//!
8//! NNIL stores integer and address bits in one backend value width. `Signed` and `Unsigned`
9//! operations add interpretation information when the same bits need different arithmetic.
10//! Memory and extension sizes are byte counts. For example, size 4 means 32 bits.
11
12use std::collections::BTreeMap;
13
14// These macros keep all simple emitters consistent with the append-only instruction model.
15// A Rust macro writes repeated source code at compile time. It does not add a run-time call.
16// `$func` is the generated method name. `$insn` is the matching enum variant name.
17macro_rules! emit_alu {
18    ($func:ident, $insn:ident) => {
19        /// Appends this two-input value operation and returns its stable result handle.
20        pub fn $func(&mut self, lhs: Insn, rhs: Insn) -> Insn {
21            // The next vector index is also the stable handle for the new result.
22            let ret = self.insns.len();
23            // Store operand handles instead of operand copies so the backend can follow values.
24            self.insns.push(NnilInstruction::$insn { lhs, rhs });
25            // Use `u64` for handles so the public IR does not depend on the host `usize` width.
26            Insn(ret as u64)
27        }
28    };
29}
30
31macro_rules! emit_branch {
32    ($func:ident, $insn:ident) => {
33        /// Appends this comparison branch with explicit true and false targets.
34        pub fn $func(&mut self, lhs: Insn, rhs: Insn, ttgt: Label, ftgt: Label) -> Insn {
35            let ret = self.insns.len();
36            // Store both targets. A basic block must state where both comparison results go.
37            self.insns.push(NnilInstruction::$insn {
38                lhs,
39                rhs,
40                ttgt,
41                ftgt,
42            });
43            Insn(ret as u64)
44        }
45    };
46}
47
48macro_rules! emit_mem {
49    ($func:ident, $insn:ident) => {
50        /// Appends this memory operation with an explicit byte width.
51        pub fn $func(&mut self, dst: Insn, src: Insn, size: u64) -> Insn {
52            let ret = self.insns.len();
53            // The same shape supports load and store. Their variants define operand direction.
54            self.insns.push(NnilInstruction::$insn { dst, src, size });
55            Insn(ret as u64)
56        }
57    };
58}
59
60macro_rules! emit_ext {
61    ($func:ident, $insn:ident) => {
62        /// Appends this width extension so the backend does not infer a guest width.
63        pub fn $func(&mut self, size: usize, dst: Insn) -> Insn {
64            let ret = self.insns.len();
65            // Keep extension width explicit because guest XLEN can differ from host width.
66            self.insns.push(NnilInstruction::$insn { size, dst });
67            Insn(ret as u64)
68        }
69    };
70}
71
72macro_rules! emit_sys {
73    ($func:ident, $insn:ident) => {
74        /// Appends this runtime exit and returns its stable operation handle.
75        pub fn $func(&mut self) -> Insn {
76            let ret = self.insns.len();
77            // System exits have no value operands, but they still need an index and boundary.
78            self.insns.push(NnilInstruction::$insn {});
79            Insn(ret as u64)
80        }
81    };
82}
83
84macro_rules! emit_sign {
85    ($func:ident, $insn:ident) => {
86        /// Appends this arithmetic interpretation marker without changing the input bits.
87        pub fn $func(&mut self, dst: Insn) -> Insn {
88            let ret = self.insns.len();
89            // Signedness is a backend marker. It preserves the input bits without conversion.
90            self.insns.push(NnilInstruction::$insn { dst });
91            Insn(ret as u64)
92        }
93    };
94}
95
96#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
97/// Identifies an NNIL value by its stable position in the instruction list.
98///
99/// The tuple field is public so a backend can use the number directly. The wrapper prevents a
100/// value handle from being used by mistake as a guest address or label.
101pub struct Insn(pub u64);
102impl std::fmt::Debug for Insn {
103    // Keep the type name in debug output because a bare number could mean an address or register.
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        write!(f, "Insn({})", self.0)
106    }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
110/// Identifies a branch target without storing a temporary instruction index.
111///
112/// A label number indexes `Nnil::label_addrs`. It stays valid when more NNIL operations are added.
113pub struct Label(pub u64);
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
116/// Keeps guest addresses distinct from instruction and label identifiers.
117///
118/// Guest addresses use `u64` because the current runtime stores guest PCs in 64 bits.
119pub struct Address(pub u64);
120
121#[derive(Clone, Copy, PartialEq, Eq)]
122/// Represents guest semantics in a form that the JIT can emit directly.
123///
124/// Most operands are [`Insn`] handles. `lhs` and `rhs` are value inputs. A `dst` can be a value
125/// input or a write target, depending on the operation. Load, `Mov`, `Set`, LR, SC, and AMO use
126/// a register-like `dst` as a write target. The operation index is also its result handle.
127pub enum NnilInstruction {
128    /// Adds two bit patterns with wrapping semantics because guest overflow does not panic.
129    Add {
130        /// Supplies the left input value.
131        lhs: Insn,
132        /// Supplies the right input value.
133        rhs: Insn,
134    },
135    /// Subtracts with wrapping semantics for the same guest-overflow rule.
136    Sub {
137        /// Supplies the value to subtract from.
138        lhs: Insn,
139        /// Supplies the value to subtract.
140        rhs: Insn,
141    },
142    /// Produces the low backend-width part of an integer product.
143    Mul {
144        /// Supplies the left factor.
145        lhs: Insn,
146        /// Supplies the right factor.
147        rhs: Insn,
148    },
149    /// Produces the high backend-width part of a double-width product.
150    /// Signedness markers on the operands select MULH, MULHSU, or MULHU behavior.
151    Mulh {
152        /// Supplies the left factor and its signedness marker.
153        lhs: Insn,
154        /// Supplies the right factor and its signedness marker.
155        rhs: Insn,
156    },
157    /// Divides integer values. Operand markers select signed or unsigned behavior.
158    Div {
159        /// Supplies the dividend and its signedness marker.
160        lhs: Insn,
161        /// Supplies the divisor and its signedness marker.
162        rhs: Insn,
163    },
164    /// Produces an integer remainder. Operand markers select signed or unsigned behavior.
165    Mod {
166        /// Supplies the dividend and its signedness marker.
167        lhs: Insn,
168        /// Supplies the divisor and its signedness marker.
169        rhs: Insn,
170    },
171    /// Divides IEEE floating-point bit patterns at the selected byte width.
172    Fdiv {
173        /// Supplies the floating-point dividend bits.
174        lhs: Insn,
175        /// Supplies the floating-point divisor bits.
176        rhs: Insn,
177        // Four bytes select f32 and eight bytes select f64 in the current backend.
178        /// Selects the floating-point width as a byte count.
179        size: usize,
180    },
181    /// Performs bitwise AND without a signedness distinction.
182    And {
183        /// Supplies the left input bits.
184        lhs: Insn,
185        /// Supplies the right input bits.
186        rhs: Insn,
187    },
188    /// Performs bitwise OR without a signedness distinction.
189    Or {
190        /// Supplies the left input bits.
191        lhs: Insn,
192        /// Supplies the right input bits.
193        rhs: Insn,
194    },
195    /// Performs bitwise exclusive OR without a signedness distinction.
196    Xor {
197        /// Supplies the left input bits.
198        lhs: Insn,
199        /// Supplies the right input bits.
200        rhs: Insn,
201    },
202    /// Shifts left and fills low bits with zero.
203    Sll {
204        /// Supplies the value to shift.
205        lhs: Insn,
206        /// Supplies the shift count.
207        rhs: Insn,
208    },
209    /// Shifts right and fills high bits with zero.
210    Srl {
211        /// Supplies the value to shift.
212        lhs: Insn,
213        /// Supplies the shift count.
214        rhs: Insn,
215    },
216    /// Shifts right and copies the sign bit into high bits.
217    Sra {
218        /// Supplies the signed value to shift.
219        lhs: Insn,
220        /// Supplies the shift count.
221        rhs: Insn,
222    },
223    /// Returns one or zero for a less-than comparison.
224    /// Operand markers tell the backend whether it must compare signed or unsigned values.
225    Slt {
226        /// Supplies the left comparison value.
227        lhs: Insn,
228        /// Supplies the right comparison value.
229        rhs: Insn,
230    },
231
232    /// Copies a value to a write target.
233    Mov {
234        /// Selects the register-like write target.
235        dst: Insn,
236        /// Supplies the value to copy.
237        src: Insn,
238    },
239    /// Writes a computed value to a guest register or temporary target.
240    Set {
241        /// Selects the guest register or temporary write target.
242        dst: Insn,
243        /// Supplies the value to write.
244        src: Insn,
245    },
246
247    /// Transfers control to a computed address value and ends the current basic block.
248    Jr {
249        /// Supplies the computed guest target address.
250        dst: Insn,
251    },
252    /// Transfers control to a direct or computed value and ends the current basic block.
253    Jmp {
254        /// Supplies the direct or computed guest target address.
255        dst: Insn,
256    },
257
258    // Both labels are explicit because emitted basic blocks cannot depend on list fallthrough.
259    /// Branches to `ttgt` when two values are equal. Otherwise it branches to `ftgt`.
260    Beq {
261        /// Supplies the left comparison value.
262        lhs: Insn,
263        /// Supplies the right comparison value.
264        rhs: Insn,
265        /// Selects the target when the values are equal.
266        ttgt: Label,
267        /// Selects the target when the values differ.
268        ftgt: Label,
269    },
270    /// Branches to `ttgt` when two values differ. Otherwise it branches to `ftgt`.
271    Bne {
272        /// Supplies the left comparison value.
273        lhs: Insn,
274        /// Supplies the right comparison value.
275        rhs: Insn,
276        /// Selects the target when the values differ.
277        ttgt: Label,
278        /// Selects the target when the values are equal.
279        ftgt: Label,
280    },
281    /// Branches on less-than. Operand markers select signed or unsigned comparison.
282    Blt {
283        /// Supplies the left comparison value.
284        lhs: Insn,
285        /// Supplies the right comparison value.
286        rhs: Insn,
287        /// Selects the target when `lhs` is less than `rhs`.
288        ttgt: Label,
289        /// Selects the other target.
290        ftgt: Label,
291    },
292    /// Branches on greater-than. Operand markers select signed or unsigned comparison.
293    Bgt {
294        /// Supplies the left comparison value.
295        lhs: Insn,
296        /// Supplies the right comparison value.
297        rhs: Insn,
298        /// Selects the target when `lhs` is greater than `rhs`.
299        ttgt: Label,
300        /// Selects the other target.
301        ftgt: Label,
302    },
303
304    /// Reads `size` bytes at address `src` and writes the result through `dst`.
305    /// A later `Unsigned` marker requests zero extension. Integer loads otherwise sign-extend.
306    Load {
307        /// Selects the register-like target for the loaded value.
308        dst: Insn,
309        /// Supplies the guest source address.
310        src: Insn,
311        /// Selects the memory width as a byte count.
312        size: u64,
313    },
314    /// Writes the low `size` bytes of `src` to address `dst`.
315    Store {
316        /// Supplies the guest destination address.
317        dst: Insn,
318        /// Supplies the value whose low bytes are stored.
319        src: Insn,
320        /// Selects the memory width as a byte count.
321        size: u64,
322    },
323
324    /// Sign-extends the low `size` bytes to the backend value width.
325    Sext {
326        /// Selects the input width as a byte count.
327        size: usize,
328        /// Supplies the value to extend.
329        dst: Insn,
330    },
331    /// Zero-extends the low `size` bytes to the backend value width.
332    Zext {
333        /// Selects the input width as a byte count.
334        size: usize,
335        /// Supplies the value to extend.
336        dst: Insn,
337    },
338    /// Marks a value for signed arithmetic without changing its bits.
339    Signed {
340        /// Supplies the value that later arithmetic must interpret as signed.
341        dst: Insn,
342    },
343    /// Marks a value for unsigned arithmetic without changing its bits.
344    Unsigned {
345        /// Supplies the value that later arithmetic must interpret as unsigned.
346        dst: Insn,
347    },
348    /// Converts an integer value to an IEEE floating-point bit pattern.
349    IntToFloat {
350        /// Supplies the integer bits to convert.
351        src: Insn,
352        // Sizes are byte counts, so 4 and 8 select 32-bit and 64-bit source values.
353        /// Selects the integer source width as a byte count.
354        src_size: usize,
355        // A Boolean keeps signedness explicit without two conversion variants.
356        /// Selects signed or unsigned integer interpretation.
357        signed: bool,
358        // Destination sizes are byte counts: 4 selects f32 and 8 selects f64.
359        // The current Rust backend emits only the eight-byte form.
360        /// Selects the floating-point result width as a byte count.
361        dst_size: usize,
362    },
363
364    /// Creates a constant. Signed storage keeps negative RISC-V immediates direct.
365    Imm {
366        /// Stores the signed constant directly.
367        imm: i64,
368    },
369    /// Reads one architectural register. The backend enforces special rules such as x0.
370    Reg {
371        /// Selects a register in the combined integer and floating-point namespace.
372        reg: u64,
373    },
374    /// Reads one translator-private temporary register without using a guest register number.
375    TmpReg {
376        /// Selects a translator-private temporary index.
377        reg: u64,
378    },
379
380    /// Returns control to the runtime for an execution-environment call.
381    Syscall {},
382    /// Returns control to the runtime for a guest breakpoint.
383    Sysbreak {},
384
385    /// Loads a value and starts an atomic reservation for the addressed memory.
386    /// The current Rust backend does not keep reservation state, which is an implementation limit.
387    Lr {
388        /// Selects the register-like target for the loaded value.
389        dst: Insn,
390        /// Supplies the guest source address.
391        src: Insn,
392        /// Selects the access width as a byte count.
393        size: u64,
394    },
395    
396    /// Stores through a reservation and writes the success code through `dst`.
397    /// The current Rust backend always reports success because it does not model reservations.
398    Sc {
399        /// Selects the target for the architectural success code.
400        dst: Insn,
401        /// Supplies the value to store.
402        src: Insn,
403        /// Supplies the reserved guest address.
404        addr: Insn,
405        /// Selects the access width as a byte count.
406        size: u64,
407    },
408    /// Performs one atomic read-modify-write operation and returns the old memory value.
409    Amo {
410        /// Selects how the loaded and source values form the stored value.
411        op: AmoOp,
412        /// Selects the target for the old memory value.
413        dst: Insn,
414        /// Supplies the value combined with memory.
415        src: Insn,
416        /// Supplies the guest memory address.
417        addr: Insn,
418        /// Selects the access width as a byte count.
419        size: u64,
420    },
421
422    /// Carries guest memory-order information even when a single-threaded backend needs no code.
423    Fence {
424        // Four mode bits permit FENCE extensions such as total-store-order encodings.
425        /// Keeps extension-specific fence mode bits.
426        mode: u8,
427        // The four predecessor and successor bits select I/O, memory read, and memory write classes.
428        /// Selects predecessor access classes that must complete first.
429        pred: u8,
430        // Keep the successor mask separate because FENCE orders selected earlier and later classes.
431        /// Selects successor access classes that must start later.
432        succ: u8,
433    },
434}
435
436#[derive(Clone, Copy, PartialEq, Eq)]
437/// Selects an atomic operation while one instruction form supplies the common operands.
438///
439/// The A extension gives all AMOs the same address, source, destination, and width fields.
440/// A small enum avoids duplicate NNIL variants for that common structure.
441pub enum AmoOp {
442    /// Stores the source value without combining it with the loaded value.
443    Swap,
444    /// Stores the wrapping sum of the loaded and source values.
445    Add,
446    /// Stores the bitwise exclusive OR of both values.
447    Xor,
448    /// Stores the bitwise AND of both values.
449    And,
450    /// Stores the bitwise OR of both values.
451    Or,
452    /// Stores the smaller signed value.
453    Min,
454    /// Stores the larger signed value.
455    Max,
456    /// Stores the smaller unsigned value.
457    Minu,
458    /// Stores the larger unsigned value.
459    Maxu,
460}
461
462impl std::fmt::Debug for AmoOp {
463    // Use RISC-V mnemonic suffixes so a developer can compare output with a disassembler.
464    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
465        match self {
466            Self::Swap => write!(f, "swap"),
467            Self::Add => write!(f, "add"),
468            Self::Xor => write!(f, "xor"),
469            Self::And => write!(f, "and"),
470            Self::Or => write!(f, "or"),
471            Self::Min => write!(f, "min"),
472            Self::Max => write!(f, "max"),
473            Self::Minu => write!(f, "minu"),
474            Self::Maxu => write!(f, "maxu"),
475        }
476    }
477}
478
479impl std::fmt::Debug for NnilInstruction {
480    // Print a compact assembly-like form so lifted output is easy to compare by eye.
481    // This output is diagnostic text. It does not control execution semantics.
482    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
483        match self {
484            Self::Add { lhs, rhs } => write!(f, "add {lhs:?}, {rhs:?}"),
485            Self::Sub { lhs, rhs } => write!(f, "sub {lhs:?}, {rhs:?}"),
486            Self::Mul { lhs, rhs } => write!(f, "mul {lhs:?}, {rhs:?}"),
487            Self::Mulh { lhs, rhs } => write!(f, "mulh {lhs:?}, {rhs:?}"),
488            Self::Div { lhs, rhs } => write!(f, "div {lhs:?}, {rhs:?}"),
489            Self::Mod { lhs, rhs } => write!(f, "mod {lhs:?}, {rhs:?}"),
490            Self::Fdiv { lhs, rhs, size } => match size {
491                // Floating-point sizes are bytes: 4, 8, and 16 mean 32, 64, and 128 bits.
492                4 => write!(f, "fdiv.s {lhs:?}, {rhs:?}"),
493                8 => write!(f, "fdiv.d {lhs:?}, {rhs:?}"),
494                16 => write!(f, "fdiv.q {lhs:?}, {rhs:?}"),
495                _ => write!(f, "fdiv.{size} {lhs:?}, {rhs:?}"),
496            },
497            Self::And { lhs, rhs } => write!(f, "and {lhs:?}, {rhs:?}"),
498            Self::Or { lhs, rhs } => write!(f, "or {lhs:?}, {rhs:?}"),
499            Self::Xor { lhs, rhs } => write!(f, "xor {lhs:?}, {rhs:?}"),
500            Self::Sll { lhs, rhs } => write!(f, "sll {lhs:?}, {rhs:?}"),
501            Self::Srl { lhs, rhs } => write!(f, "srl {lhs:?}, {rhs:?}"),
502            Self::Sra { lhs, rhs } => write!(f, "sra {lhs:?}, {rhs:?}"),
503            Self::Slt { lhs, rhs } => write!(f, "slt {lhs:?}, {rhs:?}"),
504
505            Self::Mov { dst, src } => write!(f, "mov {dst:?}, {src:?}"),
506            Self::Set { dst, src } => write!(f, "set {dst:?}, {src:?}"),
507
508            Self::Jr { dst } => write!(f, "jr {dst:?}"),
509            Self::Jmp { dst } => write!(f, "jmp {dst:?}"),
510
511            Self::Beq {
512                lhs,
513                rhs,
514                ttgt,
515                ftgt,
516            } => write!(f, "beq {lhs:?}, {rhs:?} => {ttgt:?} ({ftgt:?})"),
517            Self::Bne {
518                lhs,
519                rhs,
520                ttgt,
521                ftgt,
522            } => write!(f, "bne {lhs:?}, {rhs:?} => {ttgt:?} ({ftgt:?})"),
523            Self::Blt {
524                lhs,
525                rhs,
526                ttgt,
527                ftgt,
528            } => write!(f, "blt {lhs:?}, {rhs:?} => {ttgt:?} ({ftgt:?})"),
529            Self::Bgt {
530                lhs,
531                rhs,
532                ttgt,
533                ftgt,
534            } => write!(f, "bgt {lhs:?}, {rhs:?} => {ttgt:?} ({ftgt:?})"),
535
536            Self::Load { dst, src, size } => {
537                let size = match size {
538                    // These are internal debug suffixes. `q` means eight bytes here; it is not
539                    // RISC-V Q, which means a 16-byte value.
540                    1 => "b",
541                    2 => "h",
542                    4 => "w",
543                    8 => "q",
544                    _ => unreachable!("unknown size for load {}", size),
545                };
546                write!(f, "l{size} {dst:?}, ({src:?})")
547            }
548            Self::Store { dst, src, size } => {
549                let size = match size {
550                    // Use the same internal byte-count suffixes as loads. Thus, `q` is eight
551                    // bytes here and does not mean the 16-byte RISC-V Q format.
552                    1 => "b",
553                    2 => "h",
554                    4 => "w",
555                    8 => "q",
556                    _ => unreachable!("unknown size for store {}", size),
557                };
558                write!(f, "s{size} {src:?}, ({dst:?})")
559            }
560
561            Self::Sext { size, dst } => {
562                let size = match size {
563                    // Extension width is also a byte count. The suffix states the source width;
564                    // internal `q` means eight bytes.
565                    1 => "b",
566                    2 => "h",
567                    4 => "w",
568                    8 => "q",
569                    _ => unreachable!("unknown size for store {}", size),
570                };
571                write!(f, "sext.{size} {dst:?}")
572            }
573            Self::Zext { size, dst } => {
574                let size = match size {
575                    // Zero extension uses the same 1, 2, 4, and 8 byte vocabulary.
576                    1 => "b",
577                    2 => "h",
578                    4 => "w",
579                    8 => "q",
580                    _ => unreachable!("unknown size for store {}", size),
581                };
582                write!(f, "zext.{size} {dst:?}")
583            }
584            Self::Signed { dst } => write!(f, "signed {dst:?}"),
585            Self::Unsigned { dst } => write!(f, "unsigned {dst:?}"),
586            Self::IntToFloat {
587                src,
588                src_size,
589                signed,
590                dst_size,
591            } => {
592                // A leading `i` or `u` makes integer signedness visible in diagnostic output.
593                let src_ty = if *signed { "i" } else { "u" };
594                let dst_ty = match dst_size {
595                    // Destination byte counts 4, 8, and 16 map to IEEE precision names.
596                    4 => "f32",
597                    8 => "f64",
598                    16 => "f128",
599                    _ => "f?",
600                };
601                // Multiply bytes by eight because Rust type names state a width in bits.
602                write!(f, "{src_ty}{}to{dst_ty} {src:?}", src_size * 8)
603            }
604
605            Self::Imm { imm } => write!(f, "imm({imm:?})"),
606            Self::Reg { reg } => write!(f, "reg({reg:?})"),
607            Self::TmpReg { reg } => write!(f, "tmpreg {reg:?}"),
608
609            Self::Syscall {} => write!(f, "syscall"),
610            Self::Sysbreak {} => write!(f, "sysbreak"),
611
612            Self::Lr { dst, src, size } => {
613                write!(f, "lr.{size} {dst:?}, ({src:?})")
614            }
615            Self::Sc {
616                dst,
617                src,
618                addr,
619                size,
620            } => {
621                write!(f, "sc.{size} {dst:?}, {src:?}, ({addr:?})")
622            }
623            Self::Amo {
624                op,
625                dst,
626                src,
627                addr,
628                size,
629            } => {
630                write!(f, "amo{op:?}.{size} {dst:?}, {src:?}, ({addr:?})")
631            }
632
633            Self::Fence { .. } => write!(f, "fence"),
634        }
635    }
636}
637
638/// Owns one lifted region so its value handles and address maps stay valid together.
639pub struct Nnil {
640    /// Base address of the lifted image
641    /// This value lets tools relate NNIL output to the source image.
642    /// It does not change while the lifter advances `pc`.
643    base: u64,
644
645    /// Current address of the lifter
646    /// This value avoids a second address counter in each architecture lifter.
647    /// It always points at the guest instruction that the lifter will decode next.
648    pc: u64,
649
650    /// List of all lifted instructions
651    /// Append-only storage keeps every `Insn` identifier valid.
652    /// The vector position is the operation result number used by later operands.
653    insns: Vec<NnilInstruction>,
654
655    /// Mapping of jump targets to stable label ids
656    /// A sorted map makes generated output reproducible.
657    /// It also makes repeated requests for one guest address return one label.
658    labels_by_addr: BTreeMap<Address, Label>,
659
660    /// Reverse lookup for branch target labels
661    /// A vector makes label resolution a constant-time operation for the emitter.
662    /// The label number is the index into this vector.
663    label_addrs: Vec<Address>,
664
665    /// Mapping of address to corresponding NNIL instruction
666    /// The emitter needs this boundary map to recover guest instructions and basic blocks.
667    /// One guest instruction can map to several NNIL operations, so only the first index is stored.
668    mapping: BTreeMap<Address, Insn>,
669}
670
671impl std::fmt::Debug for Nnil {
672    // Group NNIL operations under their source guest addresses for manual decoder checks.
673    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
674        // Eight hexadecimal digits keep common 32-bit guest addresses vertically aligned.
675        writeln!(f, "Base: 0x{:08x}", self.base)?;
676        writeln!(f, "PC: 0x{:08x}", self.pc)?;
677        writeln!(f, "\nInstructions:")?;
678
679        // An empty mapping has no first or last key. Return before later unwrap operations.
680        if self.mapping.is_empty() {
681            return Ok(());
682        }
683
684        // Each adjacent pair gives the start index of one guest instruction and the start index
685        // of the next. Their difference is the NNIL range for the first guest instruction.
686        for (addr, i1, i2) in self
687            .mapping
688            .iter()
689            .map_windows(|&[(&Address(addr1), &Insn(insn1)), (_, &Insn(insn2))]| {
690                (addr1, insn1, insn2)
691            })
692            // Collect copied boundaries before formatting so the loop no longer holds the map iterator.
693            .collect::<Vec<_>>()
694        {
695            if self.labels_by_addr.contains_key(&Address(addr)) {
696                // `:03` zero-pads a small decimal guest address to a minimum width of three.
697                writeln!(f, "\nLabel_{addr:03}:").unwrap();
698            }
699            writeln!(f, "Address: {addr:08x}").unwrap();
700            for x in i1..i2 {
701                // Width eight aligns NNIL instruction indices for easier diagnostic scanning.
702                writeln!(f, "{:8}: {:?}", x, self.insns[x as usize]).unwrap();
703            }
704        }
705
706        // No adjacent pair contains the final guest instruction. Print its range separately.
707        let (&Address(addr), &Insn(insn)) = self.mapping.last_key_value().unwrap();
708        if self.labels_by_addr.contains_key(&Address(addr)) {
709            writeln!(f, "\nLabel_{addr:03}:").unwrap();
710        }
711        writeln!(f, "Address: {addr:08x}").unwrap();
712        for x in insn..self.insns.len() as u64 {
713            // Keep the final NNIL range aligned with the earlier diagnostic rows.
714            writeln!(f, "{:8}: {:?}", x, self.insns[x as usize]).unwrap();
715        }
716
717        Ok(())
718    }
719}
720
721impl Default for Nnil {
722    // Keep generic Rust construction equal to the explicit address-neutral constructor.
723    fn default() -> Self {
724        Self::new()
725    }
726}
727
728impl Nnil {
729    /// Creates address-neutral NNIL for callers that lift a complete byte slice.
730    pub fn new() -> Self {
731        Self {
732            // Zero makes addresses equal to byte offsets for raw-slice diagnostics.
733            base: 0,
734            pc: 0,
735            insns: Vec::new(),
736            labels_by_addr: BTreeMap::new(),
737            label_addrs: Vec::new(),
738            mapping: BTreeMap::new(),
739        }
740    }
741
742    /// Starts at a guest address so that branches and fault reports use process addresses.
743    pub fn with_base(address: u64) -> Self {
744        Self {
745            // Start both values together. `base` stays fixed while `pc` advances.
746            base: address,
747            pc: address,
748            insns: Vec::new(),
749            labels_by_addr: BTreeMap::new(),
750            label_addrs: Vec::new(),
751            mapping: BTreeMap::new(),
752        }
753    }
754
755    /// Reuses a label for each address so that all incoming branches have one identity.
756    pub fn create_label(&mut self, addr: u64) -> Label {
757        let addr = Address(addr);
758        if let Some(label) = self.labels_by_addr.get(&addr) {
759            // Reuse the existing identity so all incoming edges agree on one target.
760            *label
761        } else {
762            // The next reverse-map index is a new stable label number.
763            let label = Label(self.label_addrs.len() as u64);
764            self.label_addrs.push(addr);
765            self.labels_by_addr.insert(addr, label);
766            label
767        }
768    }
769
770    /// Records a guest-instruction boundary before its NNIL operations are appended.
771    pub fn new_insn(&mut self) {
772        // Store the current vector length before lowering appends the first NNIL operation.
773        self.mapping
774            .insert(Address(self.pc), Insn(self.insns.len() as u64));
775    }
776
777    /// Advances the guest PC separately because one guest instruction can emit many operations.
778    pub fn advance_by(&mut self, amount: u64) {
779        // The architecture decoder supplies two for compressed RISC-V and four for base RISC-V.
780        self.pc += amount;
781    }
782
783    /// Returns the first guest address that belongs to this NNIL region.
784    pub fn base(&self) -> u64 {
785        self.base
786    }
787    /// Returns the next guest address that the lifter will assign.
788    pub fn pc(&self) -> u64 {
789        self.pc
790    }
791    /// Returns the lowest mapped guest address, or `None` for an empty region.
792    pub fn entrypoint(&self) -> Option<Address> {
793        // BTreeMap order selects the lowest address, which need not be the first one decoded.
794        self.mapping.first_key_value().map(|(&addr, _)| addr)
795    }
796    /// Returns operations in handle order so a backend can resolve operands by index.
797    pub fn insns(&self) -> &[NnilInstruction] {
798        &self.insns
799    }
800    /// Returns guest-instruction boundaries for basic-block collection and diagnostics.
801    pub fn instruction_starts(&self) -> &BTreeMap<Address, Insn> {
802        &self.mapping
803    }
804    /// Returns the address-to-label map for direct control-flow edges.
805    pub fn labels(&self) -> &BTreeMap<Address, Label> {
806        &self.labels_by_addr
807    }
808    /// Finds the stable label for a guest address when one was created.
809    pub fn label_for_address(&self, addr: Address) -> Option<Label> {
810        self.labels_by_addr.get(&addr).copied()
811    }
812    /// Resolves a label back to its guest address.
813    pub fn label_address(&self, label: Label) -> Option<Address> {
814        // Invalid or foreign label numbers return `None` instead of indexing past the vector.
815        self.label_addrs.get(label.0 as usize).copied()
816    }
817
818    // Each emitted value uses its vector position so later operations can refer to it directly.
819    /// Appends a signed immediate bit pattern.
820    ///
821    /// Signed storage represents negative sign-extended immediates without a separate flag.
822    pub fn imm(&mut self, imm: i64) -> Insn {
823        let ret = self.insns.len();
824        self.insns.push(NnilInstruction::Imm { imm });
825        Insn(ret as u64)
826    }
827    /// Appends a read of one register in the combined NNIL register namespace.
828    ///
829    /// RISC-V integer registers use indices 0 through 31. The lifter adds 32 for floating-point
830    /// registers so integer and floating-point state cannot have the same NNIL index.
831    pub fn reg(&mut self, reg: u64) -> Insn {
832        let ret = self.insns.len();
833        self.insns.push(NnilInstruction::Reg { reg });
834        Insn(ret as u64)
835    }
836
837    /// Appends a read of a translator-private temporary.
838    ///
839    /// A separate variant prevents a temporary index from aliasing guest architectural state.
840    pub fn tmp_reg(&mut self, reg: u64) -> Insn {
841        let ret = self.insns.len();
842        self.insns.push(NnilInstruction::TmpReg { reg });
843        Insn(ret as u64)
844    }
845
846    // These methods append pure value operations. They do not write guest state by themselves.
847    emit_alu!(add, Add);
848    emit_alu!(sub, Sub);
849    emit_alu!(mul, Mul);
850    emit_alu!(mulh, Mulh);
851    emit_alu!(div, Div);
852    // The `r#` prefix permits the Rust keyword `mod` to be used as a method name.
853    emit_alu!(r#mod, Mod);
854    emit_alu!(and, And);
855    emit_alu!(or, Or);
856    emit_alu!(xor, Xor);
857    emit_alu!(sll, Sll);
858    emit_alu!(srl, Srl);
859    emit_alu!(sra, Sra);
860    emit_alu!(slt, Slt);
861
862    /// Appends a value move and writes its result through `dst`.
863    pub fn mov(&mut self, dst: Insn, src: Insn) -> Insn {
864        let ret = self.insns.len();
865        self.insns.push(NnilInstruction::Mov { dst, src });
866        Insn(ret as u64)
867    }
868    /// Appends a guest-state write after a pure NNIL calculation.
869    pub fn set(&mut self, dst: Insn, src: Insn) -> Insn {
870        let ret = self.insns.len();
871        self.insns.push(NnilInstruction::Set { dst, src });
872        Insn(ret as u64)
873    }
874
875    /// Appends an indirect transfer and ends linear execution.
876    pub fn jr(&mut self, dst: Insn) -> Insn {
877        let ret = self.insns.len();
878        self.insns.push(NnilInstruction::Jr { dst });
879        Insn(ret as u64)
880    }
881    /// Appends a transfer to the address value in `dst`.
882    pub fn jmp(&mut self, dst: Insn) -> Insn {
883        let ret = self.insns.len();
884        self.insns.push(NnilInstruction::Jmp { dst });
885        Insn(ret as u64)
886    }
887
888    // Branch methods require true and false labels so the backend never guesses fallthrough.
889    emit_branch!(beq, Beq);
890    emit_branch!(bne, Bne);
891    emit_branch!(blt, Blt);
892    emit_branch!(bgt, Bgt);
893
894    // Memory methods use byte counts because guest widths need not match Rust host types.
895    emit_mem!(load, Load);
896    emit_mem!(store, Store);
897
898    // Extension methods make the intended source width explicit at an XLEN boundary.
899    emit_ext!(sext, Sext);
900    emit_ext!(zext, Zext);
901    // Sign markers guide comparisons, division, remainder, and high multiplication.
902    emit_sign!(signed, Signed);
903    emit_sign!(unsigned, Unsigned);
904
905    /// Appends floating-point division for a value width in bytes.
906    pub fn fdiv(&mut self, size: usize, lhs: Insn, rhs: Insn) -> Insn {
907        let ret = self.insns.len();
908        self.insns.push(NnilInstruction::Fdiv { lhs, rhs, size });
909        Insn(ret as u64)
910    }
911
912    // todo
913    /// Appends an integer-to-floating-point conversion with explicit source and result widths.
914    ///
915    /// Explicit widths prevent the backend host type from changing guest conversion behavior.
916    pub fn int_to_float(
917        &mut self,
918        src: Insn,
919        src_size: usize,
920        signed: bool,
921        dst_size: usize,
922    ) -> Insn {
923        let ret = self.insns.len();
924        self.insns.push(NnilInstruction::IntToFloat {
925            src,
926            src_size,
927            signed,
928            dst_size,
929        });
930
931        Insn(ret as u64)
932    }
933
934    // todo
935    // Both system operations exit generated code so the runtime can provide process services.
936    emit_sys!(syscall, Syscall);
937    emit_sys!(sysbreak, Sysbreak);
938
939    /// Appends an atomic load-reserved of `size` bytes from address `src`.
940    pub fn lr(&mut self, dst: Insn, src: Insn, size: u64) -> Insn {
941        let ret = self.insns.len();
942        self.insns.push(NnilInstruction::Lr { dst, src, size });
943        Insn(ret as u64)
944    }
945    /// Appends an atomic store-conditional and its architectural success result.
946    pub fn sc(&mut self, dst: Insn, src: Insn, addr: Insn, size: u64) -> Insn {
947        let ret = self.insns.len();
948        self.insns.push(NnilInstruction::Sc {
949            dst,
950            src,
951            addr,
952            size,
953        });
954        Insn(ret as u64)
955    }
956    /// Appends an atomic read-modify-write operation.
957    ///
958    /// `dst` receives the old memory value. `src` contributes to the new memory value.
959    pub fn amo(&mut self, op: AmoOp, dst: Insn, src: Insn, addr: Insn, size: u64) -> Insn {
960        let ret = self.insns.len();
961        self.insns.push(NnilInstruction::Amo {
962            op,
963            dst,
964            src,
965            addr,
966            size,
967        });
968        Insn(ret as u64)
969    }
970
971    /// Appends a memory-order fence with the encoded four-bit mode and access masks.
972    pub fn fence(&mut self, mode: u8, pred: u8, succ: u8) -> Insn {
973        let ret = self.insns.len();
974        self.insns.push(NnilInstruction::Fence { mode, pred, succ });
975        Insn(ret as u64)
976    }
977}