Skip to main content

lifter/
lib.rs

1//! The lifter converts executable guest memory to NNIL so that the JIT stays architecture-neutral.
2//!
3//! A lifter decodes guest instructions but does not run them. It converts each RISC-V operation
4//! to a small sequence of NNIL operations. The backend can then handle NNIL without knowledge of
5//! RISC-V bit fields.
6
7use jit::GuestMemory;
8use liil::nnil::Nnil;
9
10mod riscv;
11
12// These derived traits make the small architecture selector easy to copy, compare, and diagnose.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14/// Selects the register width because decoding and word-result rules depend on XLEN.
15///
16/// XLEN is the width of an integer register and a guest address calculation. RV32 uses 32 bits.
17/// RV64 uses 64 bits, and RV128 uses 128 bits. Some instruction encodings have a
18/// different meaning at different XLEN values, especially compressed and `*W` instructions.
19/// The runtime currently requests RV64 only. RV32 and the draft RV128 path expose incomplete
20/// decoder work, and NNIL values and addresses remain u64.
21pub enum RiscVArch {
22    /// Uses 32-bit integer registers and address calculations.
23    Rv32,
24    /// Uses 64-bit integer registers and address calculations.
25    Rv64,
26    /// Selects the draft 128-bit decode rules, which the current NNIL cannot represent fully.
27    Rv128,
28}
29
30/// Restricts instruction fetches to the MMU execute-permission checks.
31///
32/// The lifetime `'a` prevents this view from living longer than the borrowed guest memory.
33/// The view does not copy memory, so the JIT and the lifter use the same mapped bytes.
34pub struct MemoryView<'a> {
35    /// Borrows the MMU so instruction fetches enforce execute permission.
36    pub memory: &'a GuestMemory,
37}
38
39impl MemoryView<'_> {
40    // Return `None` when two executable bytes are not available. The caller can reject the block
41    // instead of decoding bytes without execute permission.
42    fn fetch_u16(&self, pc: u64) -> Option<u16> {
43        self.memory.fetch_u16(pc)
44    }
45
46    // A 32-bit instruction needs four executable bytes. Keep this check in `GuestMemory` so the
47    // same page-boundary and permission rules apply to all instruction fetches.
48    fn fetch_u32(&self, pc: u64) -> Option<u32> {
49        self.memory.fetch_u32(pc)
50    }
51}
52
53// todo -- kann eig. weg
54//         nur noch von alter lifter cli benutzt
55/// Lifts a complete diagnostic byte slice from guest address zero.
56///
57/// This path does not use MMU permission checks. The runtime uses [`lift_block`] instead.
58pub fn lift(arch: RiscVArch, data: &[u8]) -> Nnil {
59    riscv::lift_riscv(data, None, arch)
60}
61
62/// Lifts one control-flow region so that the runtime can compile code only when it is needed.
63///
64/// `start_pc` is a guest virtual address. The result ends at a branch, jump, system call, or
65/// breakpoint. `None` means that instruction bytes were not executable or were not present.
66pub fn lift_block(arch: RiscVArch, view: &MemoryView, start_pc: u64) -> Option<Nnil> {
67    riscv::lift_block_riscv(view, start_pc, arch)
68}