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}