openvm_deferral_transpiler/
lib.rs

1use eyre::Result;
2use openvm_deferral_guest::{COMMIT_NUM_BYTES, DEFERRAL_FUNCT3, MAX_DEF_CIRCUITS, OPCODE};
3use openvm_instructions::{
4    exe::SparseMemoryImage,
5    instruction::Instruction,
6    riscv::{RV32_MEMORY_AS, RV32_REGISTER_AS, RV32_REGISTER_NUM_LIMBS},
7    LocalOpcode, DEFERRAL_AS,
8};
9use openvm_instructions_derive::LocalOpcode;
10use openvm_transpiler::{TranspilerExtension, TranspilerOutput};
11use p3_field::PrimeField32;
12use rrs_lib::instruction_formats::IType;
13use serde::{Deserialize, Serialize};
14use strum::{EnumCount, EnumIter, FromRepr};
15
16#[derive(
17    Copy,
18    Clone,
19    Debug,
20    PartialEq,
21    Eq,
22    PartialOrd,
23    Ord,
24    EnumCount,
25    EnumIter,
26    FromRepr,
27    LocalOpcode,
28    Serialize,
29    Deserialize,
30)]
31#[opcode_offset = 0x800]
32#[repr(usize)]
33pub enum DeferralOpcode {
34    CALL,
35    OUTPUT,
36}
37
38#[derive(Default)]
39pub struct DeferralTranspilerExtension {
40    def_circuit_commits: Vec<[u8; COMMIT_NUM_BYTES]>,
41}
42
43impl DeferralTranspilerExtension {
44    pub fn new(def_circuit_commits: Vec<[u8; COMMIT_NUM_BYTES]>) -> Self {
45        assert!(def_circuit_commits.len() <= MAX_DEF_CIRCUITS as usize);
46        Self {
47            def_circuit_commits,
48        }
49    }
50}
51
52impl<F: PrimeField32> TranspilerExtension<F> for DeferralTranspilerExtension {
53    fn process_custom(&self, instruction_stream: &[u32]) -> Option<TranspilerOutput<F>> {
54        if instruction_stream.is_empty() {
55            return None;
56        }
57
58        let instruction_u32 = instruction_stream[0];
59        let opcode = (instruction_u32 & 0x7f) as u8;
60        let funct3 = ((instruction_u32 >> 12) & 0b111) as u8;
61
62        if opcode != OPCODE {
63            return None;
64        }
65        if funct3 != DEFERRAL_FUNCT3 {
66            return None;
67        }
68
69        // Deferral immediates are encoded as [def_idx(10 bits) | imm_code(2 bits)],
70        // where imm_code determines which DeferralOpcode is being called. The semantic
71        // def_idx range is further constrained by MAX_DEF_CIRCUITS.
72        let imm12 = ((instruction_u32 >> 20) & 0xfff) as usize;
73        let imm_code = imm12 & 0b11;
74        let def_idx = imm12 >> 2;
75        if def_idx >= MAX_DEF_CIRCUITS as usize {
76            return None;
77        }
78
79        let dec_insn = IType::new(instruction_u32);
80        let def_opcode = DeferralOpcode::from_repr(imm_code)?;
81
82        let instruction = match def_opcode {
83            DeferralOpcode::CALL => Instruction::from_usize(
84                DeferralOpcode::CALL.global_opcode(),
85                [
86                    RV32_REGISTER_NUM_LIMBS * dec_insn.rd,
87                    RV32_REGISTER_NUM_LIMBS * dec_insn.rs1,
88                    def_idx,
89                    RV32_REGISTER_AS as usize,
90                    RV32_MEMORY_AS as usize,
91                ],
92            ),
93            DeferralOpcode::OUTPUT => Instruction::from_usize(
94                DeferralOpcode::OUTPUT.global_opcode(),
95                [
96                    RV32_REGISTER_NUM_LIMBS * dec_insn.rd,
97                    RV32_REGISTER_NUM_LIMBS * dec_insn.rs1,
98                    def_idx,
99                    RV32_REGISTER_AS as usize,
100                    RV32_MEMORY_AS as usize,
101                ],
102            ),
103        };
104
105        Some(TranspilerOutput::one_to_one(instruction))
106    }
107
108    fn modify_initial_memory(&self, init_memory: &mut SparseMemoryImage) -> Result<()> {
109        const F_NUM_BYTES: usize = 4;
110        const COMMIT_SIZE: usize = COMMIT_NUM_BYTES / F_NUM_BYTES;
111
112        // Each input_acc starts at cell 2 * def_idx * COMMIT_SIZE, and each output_acc
113        // immediately follows it. The initial input_acc must be the def_circuit_commit,
114        // and the initial output_acc must be all 0 (i.e. untouched).
115        for (def_idx, commit) in self.def_circuit_commits.iter().enumerate() {
116            let start_cell = 2 * def_idx * COMMIT_SIZE;
117            let start_byte = start_cell * F_NUM_BYTES;
118
119            for (byte_offset, b) in commit.iter().copied().enumerate() {
120                init_memory.insert((DEFERRAL_AS, (start_byte + byte_offset) as u32), b);
121            }
122        }
123
124        Ok(())
125    }
126}