openvm_sdk/keygen/
asm.rs

1use openvm_circuit::arch::instructions::{
2    instruction::{Instruction, NUM_OPERANDS},
3    program::Program,
4    LocalOpcode,
5};
6use openvm_continuations::F;
7use openvm_native_compiler::{asm::A0, conversion::AS, NativeJalOpcode};
8use openvm_stark_backend::p3_field::{FieldAlgebra, PrimeField32};
9use rrs_lib::instruction_formats::IType;
10
11const OPCODE: u32 = 0x0b;
12const FUNCT3: u32 = 0b111;
13const LONG_FORM_INSTRUCTION_INDICATOR: u32 = (FUNCT3 << 12) + OPCODE;
14const GAP_INDICATOR: u32 = (1 << 25) + (FUNCT3 << 12) + OPCODE;
15
16pub fn program_to_asm(mut program: Program<F>) -> String {
17    let pc_diff = handle_pc_diff(&mut program);
18    let assembly_and_comments = convert_program_to_u32s_and_comments(&program, pc_diff);
19    let mut asm_output = String::new();
20    for (u32s, comment) in &assembly_and_comments {
21        for (idx, x) in u32s.iter().enumerate() {
22            asm_output.push_str(&u32_to_directive(*x));
23            if idx == 0 {
24                asm_output.push_str(" // ");
25                asm_output.push_str(comment);
26            }
27            asm_output.push('\n');
28        }
29    }
30    asm_output
31}
32
33fn u32_to_directive(x: u32) -> String {
34    let opcode = x & 0b1111111;
35    let dec_insn = IType::new(x);
36    format!(
37        ".insn i {}, {}, x{}, x{}, {}",
38        opcode, dec_insn.funct3, dec_insn.rd, dec_insn.rs1, dec_insn.imm
39    )
40}
41
42/// In order to use native instructions in kernel functions, native instructions need to be
43/// converted to RISC-V machine code(long form instructions) first. Then Rust compiler compiles the
44/// whole program into an ELF. Finally, the ELF is transpiled into an OpenVm Exe.
45/// In the perspective of the native compiler and the transpiler, the PC step between 2 native
46/// instructions is 4. However, in the ELF, each native instruction takes longer than 4 bytes, so
47/// the instructions after the code blocks use the actual lengths of the native instructions to
48/// compute PC offsets. To solve this problem, we need the gap indicator to pad the native code
49/// block in order to align the PC of the following instructions.
50/// More details about long form instructions and gap indicators can be found in
51/// `docs/specs/transpiler.md`.
52fn handle_pc_diff(program: &mut Program<F>) -> usize {
53    const GAP_INDICATOR_WIDTH: usize = 2;
54    const LONG_FORM_NATIVE_INSTRUCTION_WIDTH: usize = 10;
55    const PC_STEP: usize = 4;
56    // For GAP_INDICATOR, whose width is 2.
57    let mut pc_diff = GAP_INDICATOR_WIDTH;
58    // For each native instruction
59    pc_diff += program.num_defined_instructions() * (LONG_FORM_NATIVE_INSTRUCTION_WIDTH - 1);
60    // For next jal
61    pc_diff += LONG_FORM_NATIVE_INSTRUCTION_WIDTH - 1;
62    let jal = Instruction::<F> {
63        opcode: NativeJalOpcode::JAL.global_opcode(),
64        a: F::from_canonical_usize(A0 as usize), // A0
65        // +1 means the next instruction after the gap
66        b: F::from_canonical_usize(PC_STEP * (pc_diff + 1)),
67        c: F::from_canonical_usize(0),
68        d: F::from_canonical_u32(AS::Native as u32),
69        e: F::from_canonical_usize(0),
70        f: F::from_canonical_usize(0),
71        g: F::from_canonical_usize(0),
72    };
73    program.push_instruction(jal);
74    pc_diff
75}
76
77fn convert_program_to_u32s_and_comments(
78    program: &Program<F>,
79    pc_diff: usize,
80) -> Vec<(Vec<u32>, String)> {
81    program
82        .defined_instructions()
83        .iter()
84        .map(|ins| {
85            (
86                vec![
87                    LONG_FORM_INSTRUCTION_INDICATOR,
88                    NUM_OPERANDS as u32,
89                    ins.opcode.as_usize() as u32,
90                    ins.a.as_canonical_u32(),
91                    ins.b.as_canonical_u32(),
92                    ins.c.as_canonical_u32(),
93                    ins.d.as_canonical_u32(),
94                    ins.e.as_canonical_u32(),
95                    ins.f.as_canonical_u32(),
96                    ins.g.as_canonical_u32(),
97                ],
98                format!("{:?}", ins.opcode),
99            )
100        })
101        .chain(std::iter::once((
102            vec![GAP_INDICATOR, pc_diff as u32],
103            "GAP_INDICATOR".to_string(),
104        )))
105        .collect()
106}