openvm_rv32im_transpiler/
lib.rs

1use std::marker::PhantomData;
2
3use openvm_instructions::{
4    instruction::Instruction, riscv::RV32_REGISTER_NUM_LIMBS, LocalOpcode, PhantomDiscriminant,
5    SystemOpcode,
6};
7use openvm_rv32im_guest::{
8    PhantomImm, CSRRW_FUNCT3, CSR_OPCODE, HINT_BUFFER_IMM, HINT_FUNCT3, HINT_STOREW_IMM,
9    NATIVE_STOREW_FUNCT3, NATIVE_STOREW_FUNCT7, PHANTOM_FUNCT3, REVEAL_FUNCT3, RV32M_FUNCT7,
10    RV32_ALU_OPCODE, SYSTEM_OPCODE, TERMINATE_FUNCT3,
11};
12use openvm_stark_backend::p3_field::PrimeField32;
13use openvm_transpiler::{
14    util::{nop, unimp},
15    TranspilerExtension, TranspilerOutput,
16};
17use rrs::InstructionTranspiler;
18use rrs_lib::{
19    instruction_formats::{IType, RType},
20    process_instruction,
21};
22
23mod instructions;
24pub mod rrs;
25pub use instructions::*;
26
27#[derive(Default)]
28pub struct Rv32ITranspilerExtension;
29
30#[derive(Default)]
31pub struct Rv32MTranspilerExtension;
32
33#[derive(Default)]
34pub struct Rv32IoTranspilerExtension;
35
36impl<F: PrimeField32> TranspilerExtension<F> for Rv32ITranspilerExtension {
37    fn process_custom(&self, instruction_stream: &[u32]) -> Option<TranspilerOutput<F>> {
38        let mut transpiler = InstructionTranspiler::<F>(PhantomData);
39        if instruction_stream.is_empty() {
40            return None;
41        }
42        let instruction_u32 = instruction_stream[0];
43
44        let opcode = (instruction_u32 & 0x7f) as u8;
45        let funct3 = ((instruction_u32 >> 12) & 0b111) as u8; // All our instructions are R-, I- or B-type
46
47        let instruction = match (opcode, funct3) {
48            (CSR_OPCODE, _) => {
49                let dec_insn = IType::new(instruction_u32);
50                if dec_insn.funct3 as u8 == CSRRW_FUNCT3 {
51                    // CSRRW
52                    if dec_insn.rs1 == 0 && dec_insn.rd == 0 {
53                        // This resets the CSR counter to zero. Since we don't have any CSR
54                        // registers, this is a nop.
55                        return Some(TranspilerOutput::one_to_one(nop()));
56                    }
57                }
58                eprintln!(
59                    "Transpiling system / CSR instruction: {instruction_u32:b} (opcode = {opcode:07b}, funct3 = {funct3:03b}) to unimp"
60                );
61                return Some(TranspilerOutput::one_to_one(unimp()));
62            }
63            (SYSTEM_OPCODE, TERMINATE_FUNCT3) => {
64                let dec_insn = IType::new(instruction_u32);
65                Some(Instruction {
66                    opcode: SystemOpcode::TERMINATE.global_opcode(),
67                    c: F::from_canonical_u8(
68                        dec_insn.imm.try_into().expect("exit code must be byte"),
69                    ),
70                    ..Default::default()
71                })
72            }
73            (SYSTEM_OPCODE, PHANTOM_FUNCT3) => {
74                let dec_insn = IType::new(instruction_u32);
75                PhantomImm::from_repr(dec_insn.imm as u16).map(|phantom| match phantom {
76                    PhantomImm::HintInput => Instruction::phantom(
77                        PhantomDiscriminant(Rv32Phantom::HintInput as u16),
78                        F::ZERO,
79                        F::ZERO,
80                        0,
81                    ),
82                    PhantomImm::HintRandom => Instruction::phantom(
83                        PhantomDiscriminant(Rv32Phantom::HintRandom as u16),
84                        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rd),
85                        F::ZERO,
86                        0,
87                    ),
88                    PhantomImm::PrintStr => Instruction::phantom(
89                        PhantomDiscriminant(Rv32Phantom::PrintStr as u16),
90                        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rd),
91                        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs1),
92                        0,
93                    ),
94                    PhantomImm::HintLoadByKey => Instruction::phantom(
95                        PhantomDiscriminant(Rv32Phantom::HintLoadByKey as u16),
96                        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rd),
97                        F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs1),
98                        0,
99                    ),
100                })
101            }
102            (RV32_ALU_OPCODE, _) => {
103                // Exclude RV32M instructions from this transpiler extension
104                let dec_insn = RType::new(instruction_u32);
105                let funct7 = dec_insn.funct7 as u8;
106                match funct7 {
107                    RV32M_FUNCT7 => None,
108                    _ => process_instruction(&mut transpiler, instruction_u32),
109                }
110            }
111            _ => process_instruction(&mut transpiler, instruction_u32),
112        };
113
114        instruction.map(TranspilerOutput::one_to_one)
115    }
116}
117
118impl<F: PrimeField32> TranspilerExtension<F> for Rv32MTranspilerExtension {
119    fn process_custom(&self, instruction_stream: &[u32]) -> Option<TranspilerOutput<F>> {
120        if instruction_stream.is_empty() {
121            return None;
122        }
123        let instruction_u32 = instruction_stream[0];
124
125        let opcode = (instruction_u32 & 0x7f) as u8;
126        if opcode != RV32_ALU_OPCODE {
127            return None;
128        }
129
130        let dec_insn = RType::new(instruction_u32);
131        let funct7 = dec_insn.funct7 as u8;
132        if funct7 != RV32M_FUNCT7 {
133            return None;
134        }
135
136        let instruction = process_instruction(
137            &mut InstructionTranspiler::<F>(PhantomData),
138            instruction_u32,
139        );
140
141        instruction.map(TranspilerOutput::one_to_one)
142    }
143}
144
145impl<F: PrimeField32> TranspilerExtension<F> for Rv32IoTranspilerExtension {
146    fn process_custom(&self, instruction_stream: &[u32]) -> Option<TranspilerOutput<F>> {
147        if instruction_stream.is_empty() {
148            return None;
149        }
150        let instruction_u32 = instruction_stream[0];
151
152        let opcode = (instruction_u32 & 0x7f) as u8;
153        let funct3 = ((instruction_u32 >> 12) & 0b111) as u8; // All our instructions are R-, I- or B-type
154
155        if opcode != SYSTEM_OPCODE {
156            return None;
157        }
158
159        let instruction = match funct3 {
160            HINT_FUNCT3 => {
161                let dec_insn = IType::new(instruction_u32);
162                let imm_u16 = (dec_insn.imm as u32) & 0xffff;
163                match imm_u16 {
164                    HINT_STOREW_IMM => Some(Instruction::from_isize(
165                        Rv32HintStoreOpcode::HINT_STOREW.global_opcode(),
166                        0,
167                        (RV32_REGISTER_NUM_LIMBS * dec_insn.rd) as isize,
168                        0,
169                        1,
170                        2,
171                    )),
172                    HINT_BUFFER_IMM => Some(Instruction::from_isize(
173                        Rv32HintStoreOpcode::HINT_BUFFER.global_opcode(),
174                        (RV32_REGISTER_NUM_LIMBS * dec_insn.rs1) as isize,
175                        (RV32_REGISTER_NUM_LIMBS * dec_insn.rd) as isize,
176                        0,
177                        1,
178                        2,
179                    )),
180                    _ => None,
181                }
182            }
183            REVEAL_FUNCT3 => {
184                let dec_insn = IType::new(instruction_u32);
185                let imm_u16 = (dec_insn.imm as u32) & 0xffff;
186                // REVEAL_RV32 is a pseudo-instruction for STOREW_RV32 a,b,c,1,3
187                Some(Instruction::large_from_isize(
188                    Rv32LoadStoreOpcode::STOREW.global_opcode(),
189                    (RV32_REGISTER_NUM_LIMBS * dec_insn.rs1) as isize,
190                    (RV32_REGISTER_NUM_LIMBS * dec_insn.rd) as isize,
191                    imm_u16 as isize,
192                    1,
193                    3,
194                    1,
195                    (dec_insn.imm < 0) as isize,
196                ))
197            }
198            NATIVE_STOREW_FUNCT3 => {
199                // NATIVE_STOREW is a pseudo-instruction for STOREW_RV32 a,b,0,1,4
200                let dec_insn = RType::new(instruction_u32);
201                if dec_insn.funct7 != NATIVE_STOREW_FUNCT7 {
202                    return None;
203                }
204                Some(Instruction::large_from_isize(
205                    Rv32LoadStoreOpcode::STOREW.global_opcode(),
206                    (RV32_REGISTER_NUM_LIMBS * dec_insn.rs1) as isize,
207                    (RV32_REGISTER_NUM_LIMBS * dec_insn.rd) as isize,
208                    0,
209                    1,
210                    4,
211                    1,
212                    0,
213                ))
214            }
215            _ => return None,
216        };
217
218        instruction.map(TranspilerOutput::one_to_one)
219    }
220}