openvm_transpiler/
util.rs

1use std::collections::BTreeMap;
2
3use openvm_instructions::{
4    exe::MemoryImage,
5    instruction::Instruction,
6    riscv::{RV32_MEMORY_AS, RV32_REGISTER_NUM_LIMBS},
7    utils::isize_to_field,
8    LocalOpcode, SystemOpcode, VmOpcode,
9};
10use openvm_stark_backend::p3_field::PrimeField32;
11use rrs_lib::instruction_formats::{BType, IType, ITypeShamt, JType, RType, SType, UType};
12
13fn i12_to_u24(imm: i32) -> u32 {
14    (imm as u32) & 0xffffff
15}
16
17/// Create a new [`Instruction`] from an R-type instruction.
18pub fn from_r_type<F: PrimeField32>(
19    opcode: usize,
20    e_as: usize,
21    dec_insn: &RType,
22    allow_rd_zero: bool,
23) -> Instruction<F> {
24    // If `rd` is not allowed to be zero, we transpile to `NOP` to prevent a write
25    // to `x0`. In the cases where `allow_rd_zero` is true, it is the responsibility of
26    // the caller to guarantee that the resulting instruction does not write to `rd`.
27    if !allow_rd_zero && dec_insn.rd == 0 {
28        return nop();
29    }
30    Instruction::new(
31        VmOpcode::from_usize(opcode),
32        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rd),
33        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs1),
34        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs2),
35        F::ONE,                        // rd and rs1 are registers
36        F::from_canonical_usize(e_as), // rs2 can be mem (eg modular arith)
37        F::ZERO,
38        F::ZERO,
39    )
40}
41
42/// Create a new [`Instruction`] from an I-type instruction. Should only be used for ALU instructions because `imm` is transpiled in a special way.
43pub fn from_i_type<F: PrimeField32>(opcode: usize, dec_insn: &IType) -> Instruction<F> {
44    if dec_insn.rd == 0 {
45        return nop();
46    }
47    Instruction::new(
48        VmOpcode::from_usize(opcode),
49        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rd),
50        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs1),
51        F::from_canonical_u32(i12_to_u24(dec_insn.imm)),
52        F::ONE,  // rd and rs1 are registers
53        F::ZERO, // rs2 is an immediate
54        F::ZERO,
55        F::ZERO,
56    )
57}
58
59/// Create a new [`Instruction`] from a load operation
60pub fn from_load<F: PrimeField32>(opcode: usize, dec_insn: &IType) -> Instruction<F> {
61    Instruction::new(
62        VmOpcode::from_usize(opcode),
63        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rd),
64        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs1),
65        F::from_canonical_u32((dec_insn.imm as u32) & 0xffff),
66        F::ONE,                         // rd is a register
67        F::TWO,                         // we load from memory
68        F::from_bool(dec_insn.rd != 0), // we may need to use this flag in the operation
69        F::from_bool(dec_insn.imm < 0), // flag for sign extension
70    )
71}
72
73/// Create a new [`Instruction`] from an I-type instruction with a shamt.
74/// It seems that shamt can only occur in SLLI, SRLI, SRAI.
75pub fn from_i_type_shamt<F: PrimeField32>(opcode: usize, dec_insn: &ITypeShamt) -> Instruction<F> {
76    if dec_insn.rd == 0 {
77        return nop();
78    }
79    Instruction::new(
80        VmOpcode::from_usize(opcode),
81        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rd),
82        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs1),
83        F::from_canonical_u32(dec_insn.shamt),
84        F::ONE,  // rd and rs1 are registers
85        F::ZERO, // rs2 is an immediate
86        F::ZERO,
87        F::ZERO,
88    )
89}
90
91/// Create a new [`Instruction`] from an S-type instruction.
92pub fn from_s_type<F: PrimeField32>(opcode: usize, dec_insn: &SType) -> Instruction<F> {
93    Instruction::new(
94        VmOpcode::from_usize(opcode),
95        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs2),
96        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs1),
97        F::from_canonical_u32((dec_insn.imm as u32) & 0xffff),
98        F::ONE,
99        F::TWO,
100        F::ONE,
101        F::from_bool(dec_insn.imm < 0),
102    )
103}
104
105/// Create a new [`Instruction`] from a B-type instruction.
106pub fn from_b_type<F: PrimeField32>(opcode: usize, dec_insn: &BType) -> Instruction<F> {
107    Instruction::new(
108        VmOpcode::from_usize(opcode),
109        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs1),
110        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs2),
111        isize_to_field(dec_insn.imm as isize),
112        F::ONE, // rs1 is a register
113        F::ONE, // rs2 is a register
114        F::ZERO,
115        F::ZERO,
116    )
117}
118
119/// Create a new [`Instruction`] from a J-type instruction.
120pub fn from_j_type<F: PrimeField32>(opcode: usize, dec_insn: &JType) -> Instruction<F> {
121    Instruction::new(
122        VmOpcode::from_usize(opcode),
123        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rd),
124        F::ZERO,
125        isize_to_field(dec_insn.imm as isize),
126        F::ONE, // rd is a register
127        F::ZERO,
128        F::from_bool(dec_insn.rd != 0), // we may need to use this flag in the operation
129        F::ZERO,
130    )
131}
132
133/// Create a new [`Instruction`] from a U-type instruction.
134pub fn from_u_type<F: PrimeField32>(opcode: usize, dec_insn: &UType) -> Instruction<F> {
135    if dec_insn.rd == 0 {
136        return nop();
137    }
138    Instruction::new(
139        VmOpcode::from_usize(opcode),
140        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rd),
141        F::ZERO,
142        F::from_canonical_u32((dec_insn.imm as u32 >> 12) & 0xfffff),
143        F::ONE, // rd is a register
144        F::ZERO,
145        F::ZERO,
146        F::ZERO,
147    )
148}
149
150/// Create a new [`Instruction`] that exits with code 2. This is equivalent to program panic but with a special exit code for debugging.
151pub fn unimp<F: PrimeField32>() -> Instruction<F> {
152    Instruction {
153        opcode: SystemOpcode::TERMINATE.global_opcode(),
154        c: F::TWO,
155        ..Default::default()
156    }
157}
158
159pub fn nop<F: PrimeField32>() -> Instruction<F> {
160    Instruction {
161        opcode: SystemOpcode::PHANTOM.global_opcode(),
162        ..Default::default()
163    }
164}
165
166/// Converts our memory image (u32 -> [u8; 4]) into Vm memory image ((as, address) -> word)
167pub fn elf_memory_image_to_openvm_memory_image<F: PrimeField32>(
168    memory_image: BTreeMap<u32, u32>,
169) -> MemoryImage<F> {
170    let mut result = MemoryImage::new();
171    for (addr, word) in memory_image {
172        for (i, byte) in word.to_le_bytes().into_iter().enumerate() {
173            result.insert(
174                (RV32_MEMORY_AS, addr + i as u32),
175                F::from_canonical_u8(byte),
176            );
177        }
178    }
179    result
180}