diff --git a/Cargo.lock b/Cargo.lock index a33b8bae3a..28d1b6d67f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3508,6 +3508,7 @@ dependencies = [ "openvm-native-circuit", "openvm-native-compiler", "openvm-native-recursion", + "openvm-native-transpiler", "openvm-rv32im-circuit", "openvm-rv32im-transpiler", "openvm-sdk", @@ -3709,7 +3710,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -3964,7 +3965,6 @@ dependencies = [ "openvm-stark-backend", "openvm-stark-sdk", "rand", - "rayon", "serde", "serde-big-array", "serde_with", @@ -3979,17 +3979,13 @@ name = "openvm-native-compiler" version = "0.2.0-alpha" dependencies = [ "backtrace", - "cfg-if", "hex", "itertools 0.13.0", "lazy_static", "metrics", "num-bigint 0.4.6", - "num-bigint-dig", "num-integer", - "num-traits", "openvm-circuit", - "openvm-circuit-primitives", "openvm-instructions", "openvm-instructions-derive", "openvm-native-circuit", @@ -4002,13 +3998,11 @@ dependencies = [ "p3-symmetric", "rand", "serde", - "serde_json", "snark-verifier-sdk", "strum", "strum_macros", "test-case", "test-log", - "tracing", "zkhash", ] @@ -4024,6 +4018,37 @@ dependencies = [ "syn 2.0.95", ] +[[package]] +name = "openvm-native-guest-macro" +version = "0.2.0-alpha" +dependencies = [ + "openvm-instructions", + "openvm-native-compiler", + "openvm-native-transpiler", + "p3-baby-bear", + "p3-field", + "proc-macro2", + "quote", + "strum", + "strum_macros", +] + +[[package]] +name = "openvm-native-integration-tests" +version = "0.2.0-alpha" +dependencies = [ + "eyre", + "openvm-build", + "openvm-circuit", + "openvm-native-circuit", + "openvm-native-transpiler", + "openvm-rv32im-transpiler", + "openvm-sdk", + "openvm-toolchain-tests", + "openvm-transpiler", + "p3-baby-bear", +] + [[package]] name = "openvm-native-recursion" version = "0.2.0-alpha" @@ -4052,6 +4077,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "openvm-native-transpiler" +version = "0.2.0-alpha" +dependencies = [ + "openvm-instructions", + "openvm-transpiler", + "p3-field", +] + [[package]] name = "openvm-pairing-circuit" version = "0.2.0-alpha" diff --git a/Cargo.toml b/Cargo.toml index ee5ebc186d..746b574a5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,9 @@ members = [ "extensions/native/compiler", "extensions/native/compiler/derive", "extensions/native/recursion", + "extensions/native/transpiler", + "extensions/native/guest-macro", + "extensions/native/tests", "extensions/algebra/circuit", "extensions/algebra/transpiler", "extensions/algebra/guest", @@ -135,6 +138,9 @@ openvm-native-circuit = { path = "extensions/native/circuit", default-features = openvm-native-compiler = { path = "extensions/native/compiler", default-features = false } openvm-native-compiler-derive = { path = "extensions/native/compiler/derive", default-features = false } openvm-native-recursion = { path = "extensions/native/recursion", default-features = false } +openvm-native-guest-macro = { path = "extensions/native/guest-macro", default-features = false } +openvm-native-integration-test = { path = "extensions/native/guest-macro/test", default-features = false } +openvm-native-transpiler = { path = "extensions/native/transpiler", default-features = false } openvm-keccak256-circuit = { path = "extensions/keccak256/circuit", default-features = false } openvm-keccak256-transpiler = { path = "extensions/keccak256/transpiler", default-features = false } openvm-keccak256-guest = { path = "extensions/keccak256/guest", default-features = false } diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml index bce3002295..5c6c4e30ec 100644 --- a/benchmarks/Cargo.toml +++ b/benchmarks/Cargo.toml @@ -15,6 +15,7 @@ openvm-sdk.workspace = true openvm-stark-backend.workspace = true openvm-stark-sdk.workspace = true openvm-transpiler.workspace = true +openvm-native-transpiler.workspace = true openvm-algebra-circuit.workspace = true openvm-algebra-transpiler.workspace = true diff --git a/crates/toolchain/build/src/lib.rs b/crates/toolchain/build/src/lib.rs index adaf5d5b99..f84a379b46 100644 --- a/crates/toolchain/build/src/lib.rs +++ b/crates/toolchain/build/src/lib.rs @@ -34,7 +34,12 @@ pub fn get_package(manifest_dir: impl AsRef) -> Package { .manifest_path(&manifest_path) .no_deps() .exec() - .expect("cargo metadata command failed"); + .unwrap_or_else(|e| { + panic!( + "cargo metadata command failed for manifest path: {}: {e:?}", + manifest_path.display() + ) + }); let mut matching: Vec = manifest_meta .packages .into_iter() diff --git a/crates/toolchain/instructions/src/instruction.rs b/crates/toolchain/instructions/src/instruction.rs index 7faed64fc1..a4f0f2e443 100644 --- a/crates/toolchain/instructions/src/instruction.rs +++ b/crates/toolchain/instructions/src/instruction.rs @@ -90,6 +90,10 @@ impl Instruction { ..Default::default() } } + + pub fn operands(&self) -> Vec { + vec![self.a, self.b, self.c, self.d, self.e, self.f, self.g] + } } impl Default for Instruction { diff --git a/crates/toolchain/transpiler/src/extension.rs b/crates/toolchain/transpiler/src/extension.rs index 805c50a33f..3ed4d054fd 100644 --- a/crates/toolchain/transpiler/src/extension.rs +++ b/crates/toolchain/transpiler/src/extension.rs @@ -8,7 +8,35 @@ pub trait TranspilerExtension { /// The `instruction_stream` provides a view of the remaining RISC-V instructions to be processed, /// presented as 32-bit chunks. The [process_custom](Self::process_custom) should determine if it knows how to transpile /// the next contiguous section of RISC-V instructions into an [`Instruction`]. - /// It returns `None` if it cannot transpile. Otherwise it returns `(instruction, how_many_u32s)` to indicate that - /// `instruction_stream[..how_many_u32s]` should be transpiled into `instruction`. - fn process_custom(&self, instruction_stream: &[u32]) -> Option<(Instruction, usize)>; + /// It returns `None` if it cannot transpile. Otherwise it returns `TranspilerOutput { instructions, used_u32s }` to indicate that + /// `instruction_stream[..used_u32s]` should be transpiled into `instructions`. + fn process_custom(&self, instruction_stream: &[u32]) -> Option>; +} + +pub struct TranspilerOutput { + pub instructions: Vec>>, + pub used_u32s: usize, +} + +impl TranspilerOutput { + pub fn one_to_one(instruction: Instruction) -> Self { + Self { + instructions: vec![Some(instruction)], + used_u32s: 1, + } + } + + pub fn many_to_one(instruction: Instruction, used_u32s: usize) -> Self { + Self { + instructions: vec![Some(instruction)], + used_u32s, + } + } + + pub fn gap(gap_length: usize, used_u32s: usize) -> Self { + Self { + instructions: (0..gap_length).map(|_| None).collect(), + used_u32s, + } + } } diff --git a/crates/toolchain/transpiler/src/lib.rs b/crates/toolchain/transpiler/src/lib.rs index 8305437595..bf88d25a8e 100644 --- a/crates/toolchain/transpiler/src/lib.rs +++ b/crates/toolchain/transpiler/src/lib.rs @@ -16,7 +16,7 @@ pub mod transpiler; pub mod util; mod extension; -pub use extension::TranspilerExtension; +pub use extension::{TranspilerExtension, TranspilerOutput}; pub trait FromElf { type ElfContext; @@ -29,7 +29,7 @@ impl FromElf for VmExe { type ElfContext = Transpiler; fn from_elf(elf: Elf, transpiler: Self::ElfContext) -> Result { let instructions = transpiler.transpile(&elf.instructions)?; - let program = Program::new_without_debug_infos( + let program = Program::new_without_debug_infos_with_option( &instructions, DEFAULT_PC_STEP, elf.pc_base, diff --git a/crates/toolchain/transpiler/src/transpiler.rs b/crates/toolchain/transpiler/src/transpiler.rs index 5102e3ec39..7a15247c55 100644 --- a/crates/toolchain/transpiler/src/transpiler.rs +++ b/crates/toolchain/transpiler/src/transpiler.rs @@ -50,7 +50,7 @@ impl Transpiler { pub fn transpile( &self, instructions_u32: &[u32], - ) -> Result>, TranspilerError> { + ) -> Result>>, TranspilerError> { let mut instructions = Vec::new(); let mut ptr = 0; while ptr < instructions_u32.len() { @@ -66,9 +66,9 @@ impl Transpiler { if options.len() > 1 { return Err(TranspilerError::AmbiguousNextInstruction); } - let (instruction, advance) = options.pop().unwrap().unwrap(); - instructions.push(instruction); - ptr += advance; + let transpiler_output = options.pop().unwrap().unwrap(); + instructions.extend(transpiler_output.instructions); + ptr += transpiler_output.used_u32s; } Ok(instructions) } diff --git a/extensions/algebra/transpiler/src/lib.rs b/extensions/algebra/transpiler/src/lib.rs index f85bf8f672..a5f82d9f18 100644 --- a/extensions/algebra/transpiler/src/lib.rs +++ b/extensions/algebra/transpiler/src/lib.rs @@ -7,7 +7,7 @@ use openvm_instructions::{ }; use openvm_instructions_derive::UsizeOpcode; use openvm_stark_backend::p3_field::PrimeField32; -use openvm_transpiler::{util::from_r_type, TranspilerExtension}; +use openvm_transpiler::{util::from_r_type, TranspilerExtension, TranspilerOutput}; use rrs_lib::instruction_formats::RType; use strum::{EnumCount, EnumIter, FromRepr}; @@ -50,7 +50,7 @@ pub struct ModularTranspilerExtension; pub struct Fp2TranspilerExtension; impl TranspilerExtension for ModularTranspilerExtension { - fn process_custom(&self, instruction_stream: &[u32]) -> Option<(Instruction, usize)> { + fn process_custom(&self, instruction_stream: &[u32]) -> Option> { if instruction_stream.is_empty() { return None; } @@ -122,12 +122,12 @@ impl TranspilerExtension for ModularTranspilerExtension { Some(from_r_type(global_opcode, 2, &dec_insn)) } }; - instruction.map(|instruction| (instruction, 1)) + instruction.map(TranspilerOutput::one_to_one) } } impl TranspilerExtension for Fp2TranspilerExtension { - fn process_custom(&self, instruction_stream: &[u32]) -> Option<(Instruction, usize)> { + fn process_custom(&self, instruction_stream: &[u32]) -> Option> { if instruction_stream.is_empty() { return None; } @@ -190,6 +190,6 @@ impl TranspilerExtension for Fp2TranspilerExtension { Some(from_r_type(global_opcode, 2, &dec_insn)) } }; - instruction.map(|instruction| (instruction, 1)) + instruction.map(TranspilerOutput::one_to_one) } } diff --git a/extensions/bigint/transpiler/src/lib.rs b/extensions/bigint/transpiler/src/lib.rs index 472e54ab49..964d1e1843 100644 --- a/extensions/bigint/transpiler/src/lib.rs +++ b/extensions/bigint/transpiler/src/lib.rs @@ -8,7 +8,7 @@ use openvm_rv32im_transpiler::{ BaseAluOpcode, BranchEqualOpcode, BranchLessThanOpcode, LessThanOpcode, MulOpcode, ShiftOpcode, }; use openvm_stark_backend::p3_field::PrimeField32; -use openvm_transpiler::{util::from_r_type, TranspilerExtension}; +use openvm_transpiler::{util::from_r_type, TranspilerExtension, TranspilerOutput}; use rrs_lib::instruction_formats::{BType, RType}; use strum::IntoEnumIterator; @@ -80,7 +80,7 @@ impl Rv32Mul256Opcode { pub struct Int256TranspilerExtension; impl TranspilerExtension for Int256TranspilerExtension { - fn process_custom(&self, instruction_stream: &[u32]) -> Option<(Instruction, usize)> { + fn process_custom(&self, instruction_stream: &[u32]) -> Option> { if instruction_stream.is_empty() { return None; } @@ -154,6 +154,6 @@ impl TranspilerExtension for Int256TranspilerExtension { } _ => None, }; - instruction.map(|instruction| (instruction, 1)) + instruction.map(TranspilerOutput::one_to_one) } } diff --git a/extensions/ecc/transpiler/src/lib.rs b/extensions/ecc/transpiler/src/lib.rs index 956fb1ad0e..25a2ddaef5 100644 --- a/extensions/ecc/transpiler/src/lib.rs +++ b/extensions/ecc/transpiler/src/lib.rs @@ -5,7 +5,7 @@ use openvm_instructions::{ }; use openvm_instructions_derive::UsizeOpcode; use openvm_stark_backend::p3_field::PrimeField32; -use openvm_transpiler::{util::from_r_type, TranspilerExtension}; +use openvm_transpiler::{util::from_r_type, TranspilerExtension, TranspilerOutput}; use rrs_lib::instruction_formats::RType; use strum::{EnumCount, EnumIter, FromRepr}; @@ -32,7 +32,7 @@ pub enum EccPhantom { pub struct EccTranspilerExtension; impl TranspilerExtension for EccTranspilerExtension { - fn process_custom(&self, instruction_stream: &[u32]) -> Option<(Instruction, usize)> { + fn process_custom(&self, instruction_stream: &[u32]) -> Option> { if instruction_stream.is_empty() { return None; } @@ -59,15 +59,12 @@ impl TranspilerExtension for EccTranspilerExtension { let curve_idx_shift = curve_idx * Rv32WeierstrassOpcode::COUNT; if let Some(SwBaseFunct7::HintDecompress) = SwBaseFunct7::from_repr(base_funct7) { assert_eq!(dec_insn.rd, 0); - return Some(( - Instruction::phantom( - PhantomDiscriminant(EccPhantom::HintDecompress as u16), - F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs1), - F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs2), - curve_idx as u16, - ), - 1, - )); + return Some(TranspilerOutput::one_to_one(Instruction::phantom( + PhantomDiscriminant(EccPhantom::HintDecompress as u16), + F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs1), + F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs2), + curve_idx as u16, + ))); } if base_funct7 == SwBaseFunct7::SwSetup as u8 { let local_opcode = match dec_insn.rs2 { @@ -101,6 +98,6 @@ impl TranspilerExtension for EccTranspilerExtension { Some(from_r_type(global_opcode, 2, &dec_insn)) } }; - instruction.map(|instruction| (instruction, 1)) + instruction.map(TranspilerOutput::one_to_one) } } diff --git a/extensions/keccak256/transpiler/src/lib.rs b/extensions/keccak256/transpiler/src/lib.rs index 733a1596a0..c048a03766 100644 --- a/extensions/keccak256/transpiler/src/lib.rs +++ b/extensions/keccak256/transpiler/src/lib.rs @@ -1,8 +1,8 @@ -use openvm_instructions::{instruction::Instruction, UsizeOpcode}; +use openvm_instructions::UsizeOpcode; use openvm_instructions_derive::UsizeOpcode; use openvm_keccak256_guest::{KECCAK256_FUNCT3, KECCAK256_FUNCT7, OPCODE}; use openvm_stark_backend::p3_field::PrimeField32; -use openvm_transpiler::{util::from_r_type, TranspilerExtension}; +use openvm_transpiler::{util::from_r_type, TranspilerExtension, TranspilerOutput}; use rrs_lib::instruction_formats::RType; use strum::{EnumCount, EnumIter, FromRepr}; @@ -19,7 +19,7 @@ pub enum Rv32KeccakOpcode { pub struct Keccak256TranspilerExtension; impl TranspilerExtension for Keccak256TranspilerExtension { - fn process_custom(&self, instruction_stream: &[u32]) -> Option<(Instruction, usize)> { + fn process_custom(&self, instruction_stream: &[u32]) -> Option> { if instruction_stream.is_empty() { return None; } @@ -39,6 +39,6 @@ impl TranspilerExtension for Keccak256TranspilerExtension { 2, &dec_insn, ); - Some((instruction, 1)) + Some(TranspilerOutput::one_to_one(instruction)) } } diff --git a/extensions/native/circuit/Cargo.toml b/extensions/native/circuit/Cargo.toml index 8fe5e29ad3..d4564c486d 100644 --- a/extensions/native/circuit/Cargo.toml +++ b/extensions/native/circuit/Cargo.toml @@ -29,7 +29,6 @@ eyre.workspace = true serde.workspace = true serde-big-array.workspace = true serde_with.workspace = true -rayon.workspace = true [dev-dependencies] openvm-stark-sdk = { workspace = true } diff --git a/extensions/native/circuit/src/extension.rs b/extensions/native/circuit/src/extension.rs index b6314bb8b3..cd73998f1d 100644 --- a/extensions/native/circuit/src/extension.rs +++ b/extensions/native/circuit/src/extension.rs @@ -16,16 +16,23 @@ use openvm_instructions::{ program::DEFAULT_PC_STEP, PhantomDiscriminant, Poseidon2Opcode, UsizeOpcode, VmOpcode, }; use openvm_native_compiler::{ - FieldArithmeticOpcode, FieldExtensionOpcode, FriOpcode, NativeBranchEqualOpcode, + CastfOpcode, FieldArithmeticOpcode, FieldExtensionOpcode, FriOpcode, NativeBranchEqualOpcode, NativeJalOpcode, NativeLoadStoreOpcode, NativePhantom, }; use openvm_poseidon2_air::Poseidon2Config; -use openvm_rv32im_circuit::BranchEqualCoreChip; +use openvm_rv32im_circuit::{ + BranchEqualCoreChip, Rv32I, Rv32IExecutor, Rv32IPeriphery, Rv32Io, Rv32IoExecutor, + Rv32IoPeriphery, Rv32M, Rv32MExecutor, Rv32MPeriphery, +}; use openvm_stark_backend::p3_field::PrimeField32; use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; -use crate::{adapters::*, phantom::*, *}; +use crate::{ + adapters::{convert_adapter::ConvertAdapterChip, *}, + phantom::*, + *, +}; #[derive(Clone, Debug, Serialize, Deserialize, VmConfig, derive_new::new)] pub struct NativeConfig { @@ -284,3 +291,76 @@ pub(crate) mod phantom { } } } + +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] +pub struct CastFExtension; + +#[derive(ChipUsageGetter, Chip, InstructionExecutor, From, AnyEnum)] +pub enum CastFExtensionExecutor { + CastF(CastFChip), +} + +#[derive(From, ChipUsageGetter, Chip, AnyEnum)] +pub enum CastFExtensionPeriphery { + Placeholder(CastFChip), +} + +impl VmExtension for CastFExtension { + type Executor = CastFExtensionExecutor; + type Periphery = CastFExtensionPeriphery; + + fn build( + &self, + builder: &mut VmInventoryBuilder, + ) -> Result, VmInventoryError> { + let mut inventory = VmInventory::new(); + let SystemPort { + execution_bus, + program_bus, + memory_bridge, + } = builder.system_port(); + let offline_memory = builder.system_base().offline_memory(); + let range_checker = builder.system_base().range_checker_chip.clone(); + + let castf_chip = CastFChip::new( + ConvertAdapterChip::new(execution_bus, program_bus, memory_bridge), + CastFCoreChip::new(range_checker.clone(), CastfOpcode::default_offset()), + offline_memory.clone(), + ); + inventory.add_executor( + castf_chip, + [VmOpcode::with_default_offset(CastfOpcode::CASTF)], + )?; + + Ok(inventory) + } +} + +#[derive(Clone, Debug, VmConfig, derive_new::new, Serialize, Deserialize)] +pub struct Rv32WithKernelsConfig { + #[system] + pub system: SystemConfig, + #[extension] + pub rv32i: Rv32I, + #[extension] + pub rv32m: Rv32M, + #[extension] + pub io: Rv32Io, + #[extension] + pub native: Native, + #[extension] + pub castf: CastFExtension, +} + +impl Default for Rv32WithKernelsConfig { + fn default() -> Self { + Self { + system: SystemConfig::default().with_continuations(), + rv32i: Rv32I, + rv32m: Rv32M::default(), + io: Rv32Io, + native: Native, + castf: CastFExtension, + } + } +} diff --git a/extensions/native/compiler/Cargo.toml b/extensions/native/compiler/Cargo.toml index 569e36cf90..173830b76e 100644 --- a/extensions/native/compiler/Cargo.toml +++ b/extensions/native/compiler/Cargo.toml @@ -16,25 +16,19 @@ openvm-stark-backend = { workspace = true } openvm-native-compiler-derive = { workspace = true } openvm-instructions = { workspace = true } openvm-instructions-derive = { workspace = true } -openvm-circuit-primitives = { workspace = true } openvm-stark-sdk = { workspace = true } openvm-circuit = { workspace = true } openvm-rv32im-transpiler = { workspace = true } # disable jemalloc to be compatible with stark-backend snark-verifier-sdk = { workspace = true, optional = true } -tracing.workspace = true itertools.workspace = true serde.workspace = true -serde_json.workspace = true backtrace = { workspace = true, features = ["serde"] } strum_macros = "0.26.4" -num-bigint-dig.workspace = true num-bigint.workspace = true num-integer.workspace = true -num-traits.workspace = true metrics = { workspace = true, optional = true } -cfg-if = { workspace = true } strum = { workspace = true } [dev-dependencies] diff --git a/extensions/native/compiler/src/asm/compiler.rs b/extensions/native/compiler/src/asm/compiler.rs index e5778eda9d..cd10ec784a 100644 --- a/extensions/native/compiler/src/asm/compiler.rs +++ b/extensions/native/compiler/src/asm/compiler.rs @@ -20,7 +20,7 @@ pub(crate) const HEAP_START_ADDRESS: i32 = 1 << 24; /// The heap pointer address. pub(crate) const HEAP_PTR: i32 = HEAP_START_ADDRESS - 4; /// Utility register. -pub(crate) const A0: i32 = HEAP_START_ADDRESS - 8; +pub const A0: i32 = HEAP_START_ADDRESS - 8; /// The memory location for the top of the stack. pub(crate) const STACK_TOP: i32 = HEAP_START_ADDRESS - 64; diff --git a/extensions/native/compiler/src/conversion/mod.rs b/extensions/native/compiler/src/conversion/mod.rs index e92e6c3b66..1ec1e3b4f9 100644 --- a/extensions/native/compiler/src/conversion/mod.rs +++ b/extensions/native/compiler/src/conversion/mod.rs @@ -108,7 +108,7 @@ fn inst_large( #[derive(Clone, Copy)] #[repr(u8)] -enum AS { +pub enum AS { Immediate = 0, Native = 5, } diff --git a/extensions/native/guest-macro/Cargo.toml b/extensions/native/guest-macro/Cargo.toml new file mode 100644 index 0000000000..090e4122a9 --- /dev/null +++ b/extensions/native/guest-macro/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "openvm-native-guest-macro" +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0" +proc-macro2 = "1.0" +openvm-instructions = { workspace = true } +openvm-native-compiler = { workspace = true } +openvm-native-transpiler = { workspace = true } +p3-field = { workspace = true } +p3-baby-bear = { workspace = true } + +[dev-dependencies] +strum.workspace = true +strum_macros.workspace = true +openvm-instructions.workspace = true diff --git a/extensions/native/guest-macro/src/lib.rs b/extensions/native/guest-macro/src/lib.rs new file mode 100644 index 0000000000..5a4f0ae58d --- /dev/null +++ b/extensions/native/guest-macro/src/lib.rs @@ -0,0 +1,20 @@ +use p3_baby_bear::BabyBear; + +use crate::parse_compiler_output::CompiledKernel; + +mod parse_compiler_output; +mod parse_kernel; +mod transportation; + +#[proc_macro] +pub fn native_kernel(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = proc_macro2::TokenStream::from(input); + + let parsed_kernel = parse_kernel::parse_raw_kernel(input); + let compiler_output = std::fs::read_to_string(parsed_kernel.file_path.clone()).unwrap(); + let compiled_kernel: CompiledKernel = + parse_compiler_output::parse_compiled_kernel(parsed_kernel, compiler_output); + let rust_function = transportation::compiled_kernel_to_function(compiled_kernel); + + proc_macro::TokenStream::from(rust_function) +} diff --git a/extensions/native/guest-macro/src/parse_compiler_output.rs b/extensions/native/guest-macro/src/parse_compiler_output.rs new file mode 100644 index 0000000000..2349eeeeae --- /dev/null +++ b/extensions/native/guest-macro/src/parse_compiler_output.rs @@ -0,0 +1,65 @@ +use openvm_instructions::instruction::Instruction; +use openvm_native_transpiler::deserialize_defined_instructions; +use p3_field::{Field, PrimeField32}; + +use crate::parse_kernel::ParsedKernel; + +#[derive(Debug)] +pub struct CompiledKernelArgument { + pub name: String, + pub rust_type: String, + pub edsl_type: String, + pub fp: usize, +} + +#[derive(Debug)] +pub struct CompiledKernel { + pub function_name: String, + pub arguments: Vec, + pub body: Vec>, + pub rust_return_type: String, + pub edsl_return_type: String, + pub return_fp: usize, +} + +pub fn parse_compiled_kernel( + parsed_kernel: ParsedKernel, + compiler_output: String, +) -> CompiledKernel { + let words: Vec = compiler_output + .lines() + .map(|line| line.parse().unwrap()) + .collect(); + let mut index = 0; + + let arguments = parsed_kernel + .arguments + .into_iter() + .map(|argument| { + let name = argument.name; + let rust_type = argument.rust_type; + let edsl_type = argument.edsl_type; + let fp = words[index] as usize; + index += 1; + CompiledKernelArgument { + name, + rust_type, + edsl_type, + fp, + } + }) + .collect::>(); + let return_fp = words[index] as usize; + index += 1; + + let instructions = deserialize_defined_instructions(&words[index..]); + + CompiledKernel { + function_name: parsed_kernel.function_name, + arguments, + body: instructions, + rust_return_type: parsed_kernel.rust_return_type, + edsl_return_type: parsed_kernel.edsl_return_type, + return_fp, + } +} diff --git a/extensions/native/guest-macro/src/parse_kernel.rs b/extensions/native/guest-macro/src/parse_kernel.rs new file mode 100644 index 0000000000..1a863d3813 --- /dev/null +++ b/extensions/native/guest-macro/src/parse_kernel.rs @@ -0,0 +1,77 @@ +use proc_macro2::{TokenStream, TokenTree}; + +#[derive(Debug)] +pub struct ParsedKernelArgument { + pub name: String, + pub rust_type: String, + pub edsl_type: String, +} + +#[derive(Debug)] +pub struct ParsedKernel { + pub function_name: String, + pub arguments: Vec, + pub file_path: String, + pub rust_return_type: String, + pub edsl_return_type: String, +} + +pub fn parse_raw_kernel(source: TokenStream) -> ParsedKernel { + let token_trees = source.into_iter().collect::>(); + + let function_name = match token_trees[1].clone() { + TokenTree::Ident(ident) => ident.to_string(), + _ => panic!("First token must be the function name"), + }; + + let arguments = match token_trees[2].clone() { + TokenTree::Group(group) => { + assert_eq!(group.delimiter(), proc_macro2::Delimiter::Parenthesis); + + let as_string = group.stream().to_string(); + let argument_strings = as_string + .split(',') + .map(|argument| argument.trim()) + .collect::>(); + + argument_strings + .into_iter() + .map(|argument_string| { + let colon_index = argument_string.find(':').unwrap(); + let bar_index = argument_string.find('|').unwrap(); + let name = argument_string[..colon_index].trim().to_string(); + let rust_type = argument_string[colon_index + 1..bar_index] + .trim() + .to_string(); + let edsl_type = argument_string[bar_index + 1..].trim().to_string(); + ParsedKernelArgument { + name, + rust_type, + edsl_type, + } + }) + .collect::>() + } + _ => panic!("Second token must be the list of arguments"), + }; + + let return_type_token_trees = token_trees[5..token_trees.len() - 1].to_vec(); + let return_type_stream = TokenStream::from_iter(return_type_token_trees); + let return_type_string = return_type_stream.to_string(); + let bar_index = return_type_string.find('|').unwrap(); + let rust_return_type = return_type_string[..bar_index].trim().to_string(); + let edsl_return_type = return_type_string[bar_index + 1..].trim().to_string(); + + let file_path = match token_trees[token_trees.len() - 1].clone() { + TokenTree::Group(group) => group.stream().to_string(), + _ => panic!("Last token must be the function body"), + }; + + ParsedKernel { + function_name, + arguments, + file_path, + rust_return_type, + edsl_return_type, + } +} diff --git a/extensions/native/guest-macro/src/transportation.rs b/extensions/native/guest-macro/src/transportation.rs new file mode 100644 index 0000000000..72aeccd054 --- /dev/null +++ b/extensions/native/guest-macro/src/transportation.rs @@ -0,0 +1,331 @@ +use openvm_instructions::{ + instruction::Instruction, + program::DEFAULT_PC_STEP, + riscv::{RV32_CELL_BITS, RV32_IMM_AS, RV32_REGISTER_AS, RV32_REGISTER_NUM_LIMBS}, + VmOpcode, +}; +use openvm_native_compiler::{ + asm::A0, conversion::AS, CastfOpcode, FieldArithmeticOpcode, NativeJalOpcode, +}; +use openvm_native_transpiler::{ + GAP_INDICATOR, LONG_FORM_INSTRUCTION_INDICATOR, VARIABLE_REGISTER_INDICATOR, +}; +use p3_field::{Field, PrimeField32}; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +use crate::{ + parse_compiler_output::CompiledKernel, + transportation::Operand::{Literal, Variable}, +}; + +#[derive(Clone, Debug)] +pub enum Operand { + Literal(F), + Variable(String, usize), +} + +impl From for Operand { + fn from(val: usize) -> Self { + Literal(F::from_canonical_usize(val)) + } +} + +impl From for Operand { + fn from(val: u32) -> Self { + Literal(F::from_canonical_u32(val)) + } +} + +impl From for Operand { + fn from(val: i32) -> Self { + let sign = if val >= 0 { F::ONE } else { F::NEG_ONE }; + Literal(sign * F::from_canonical_u32(val.unsigned_abs())) + } +} + +impl From for Operand { + fn from(val: AS) -> Self { + Literal(F::from_canonical_u32(val as u32)) + } +} + +impl Operand { + pub fn arbitrary() -> Self { + Literal(F::ZERO) + } +} + +#[derive(Clone, Debug)] +pub struct MacroInstruction { + pub opcode: VmOpcode, + pub operands: Vec>, +} + +impl MacroInstruction { + fn literal(instruction: Instruction) -> Self { + Self { + opcode: instruction.opcode, + operands: instruction.operands().into_iter().map(Literal).collect(), + } + } + + fn new(opcode: VmOpcode, operands: [Operand; N]) -> Self { + Self { + opcode, + operands: operands.to_vec(), + } + } +} + +/* + +how things are going to go: + +transportation will provide some MacroInstructions + +the body will be converted to MacroInstructions + +MacroInstructions are converted to an asm! call + */ + +pub fn compiled_kernel_to_function( + compiled_kernel: CompiledKernel, +) -> TokenStream { + let mut instructions = vec![]; + let mut input_vars = vec![]; + let return_name = "result".to_string(); + + let function_name_token = format_ident!("{}", compiled_kernel.function_name); + let return_type_token = format_ident!("{}", compiled_kernel.rust_return_type); + let return_name_token = format_ident!("{}", return_name); + + let mut arguments = vec![]; + + for argument in compiled_kernel.arguments { + let var_name = "var_".to_string() + &argument.name; + let var_name_token = format_ident!("{}", var_name); + let rust_type_token = format_ident!("{}", argument.rust_type); + arguments.push(quote! { + #var_name_token: #rust_type_token + }); + + input_vars.push(var_name); + instructions.extend(transport_rust_to_edsl( + argument.rust_type, + argument.edsl_type, + "var_".to_string() + &argument.name, + argument.fp, + )); + } + + instructions.extend( + compiled_kernel + .body + .into_iter() + .map(MacroInstruction::literal), + ); + instructions.extend(transport_edsl_to_rust( + compiled_kernel.rust_return_type, + compiled_kernel.edsl_return_type, + return_name.clone(), + compiled_kernel.return_fp, + )); + + let asm_call: TokenStream = + instructions_to_asm_call(instructions, input_vars, vec![return_name.clone()]) + .parse() + .unwrap(); + + quote! { + fn #function_name_token(#(#arguments),*) -> #return_type_token { + let #return_name_token: #return_type_token; + #asm_call + #return_name_token + } + } +} + +fn u32_to_directive(x: u32) -> String { + let opcode = x & 0b1111111; + let funct3 = (x >> 12) & 0b111; + let rd = (x >> 7) & 0b11111; + let rs1 = (x >> 15) & 0b11111; + let mut simm12 = (x >> 20) as i32; + if simm12 >= 1 << 11 { + simm12 -= 1 << 12; + } + format!( + ".insn i {}, {}, x{}, x{}, {}", + opcode, funct3, rd, rs1, simm12 + ) +} + +fn operand_to_directives(operand: Operand) -> Vec { + match operand { + Literal(x) => vec![u32_to_directive(x.as_canonical_u32())], + Variable(var, offset) => vec![ + u32_to_directive(VARIABLE_REGISTER_INDICATOR), + format!(".insn i 0, 0, {{{}}}, x0, {}", var, offset), + ], + } +} + +fn instruction_to_directives(instruction: MacroInstruction) -> Vec { + let mut directives = vec![]; + + directives.push(u32_to_directive(LONG_FORM_INSTRUCTION_INDICATOR)); + directives.push(u32_to_directive(instruction.operands.len() as u32)); + directives.push(u32_to_directive(instruction.opcode.as_usize() as u32)); + for operand in instruction.operands { + directives.extend(operand_to_directives(operand)); + } + + directives +} + +pub fn instructions_to_asm_call( + instructions: Vec>, + input_vars: Vec, + output_vars: Vec, +) -> String { + let mut result = String::new(); + result.push_str("\tunsafe {\n"); + result.push_str("\t\tcore::arch::asm!(\n"); + + let mut add_directives = |directives: Vec| { + for directive in directives { + result.push_str(&format!("\t\t\t\"{}\",\n", directive)); + } + result.push('\n'); + }; + + let mut pc_diff = 2; + for instruction in instructions { + let directives = instruction_to_directives(instruction); + pc_diff += directives.len() - 1; + add_directives(directives); + } + + let mut jal_instruction: MacroInstruction = MacroInstruction::new( + VmOpcode::with_default_offset(NativeJalOpcode::JAL), + [ + Operand::from(A0), + Operand::arbitrary(), + Operand::arbitrary(), + Operand::from(AS::Native), + ], + ); + let jal_example_directives = instruction_to_directives(jal_instruction.clone()); + pc_diff += jal_example_directives.len() - 1; + + jal_instruction.operands[1] = Operand::from(DEFAULT_PC_STEP as usize * (pc_diff + 1)); + add_directives(instruction_to_directives(jal_instruction)); + + add_directives(vec![ + u32_to_directive(GAP_INDICATOR), + u32_to_directive(pc_diff as u32), + ]); + + for input_var in input_vars { + result.push_str(&format!("\t\t\t{} = in(reg) {},\n", input_var, input_var)); + } + for output_var in output_vars { + result.push_str(&format!( + "\t\t\t{} = out(reg) {},\n", + output_var, output_var + )); + } + result.push_str("\t\t)\n"); + result.push_str("\t}\n"); + result +} + +pub fn transport_rust_to_edsl( + rust_type: String, + edsl_type: String, + rust_name: String, + edsl_fp: usize, +) -> Vec> { + match (rust_type.as_str(), edsl_type.as_str()) { + ("usize", "Felt") => transport_usize_to_felt(rust_name, edsl_fp), + _ => panic!( + "Unsupported conversion from rust type {:?} to edsl type {:?}", + rust_type, edsl_type + ), + } +} + +fn transport_usize_to_felt( + rust_name: String, + edsl_fp: usize, +) -> Vec> { + let mut result = vec![]; + for i in (0..RV32_REGISTER_NUM_LIMBS).rev() { + // add [{rust_name} + i] to [edsl_fp] + result.push(MacroInstruction::new( + VmOpcode::with_default_offset(FieldArithmeticOpcode::ADD), + [ + Operand::from(edsl_fp), + Operand::from(if i == RV32_REGISTER_NUM_LIMBS - 1 { + 0 + } else { + edsl_fp + }), + Variable(rust_name.clone(), i), + Operand::from(AS::Native), + if i == RV32_REGISTER_NUM_LIMBS - 1 { + Operand::from(RV32_IMM_AS) + } else { + Operand::from(AS::Native) + }, + Operand::from(RV32_REGISTER_AS), + ], + )); + if i > 0 { + result.push(MacroInstruction::new( + VmOpcode::with_default_offset(FieldArithmeticOpcode::MUL), + [ + Operand::from(edsl_fp), + Operand::from(edsl_fp), + Operand::from(1 << RV32_CELL_BITS), + Operand::from(AS::Native), + Operand::from(AS::Native), + Operand::from(RV32_IMM_AS), + ], + )); + } + } + result +} + +pub fn transport_edsl_to_rust( + rust_type: String, + edsl_type: String, + rust_name: String, + edsl_fp: usize, +) -> Vec> { + match (rust_type.as_str(), edsl_type.as_str()) { + ("usize", "Felt < F >") => transport_felt_to_usize(rust_name, edsl_fp), + _ => panic!( + "Unsupported conversion from edsl type {:?} to rust type {:?}", + edsl_type, rust_type, + ), + } +} + +fn transport_felt_to_usize( + rust_name: String, + edsl_fp: usize, +) -> Vec> { + vec![MacroInstruction::new( + VmOpcode::with_default_offset(CastfOpcode::CASTF), + [ + Variable(rust_name, 0), + Operand::from(edsl_fp), + Operand::from(0), + Operand::from(RV32_REGISTER_AS), + Operand::from(AS::Native), + ], + )] +} diff --git a/extensions/native/tests/Cargo.toml b/extensions/native/tests/Cargo.toml new file mode 100644 index 0000000000..857db630a6 --- /dev/null +++ b/extensions/native/tests/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "openvm-native-integration-tests" +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +openvm-circuit.workspace = true +openvm-sdk.workspace = true +openvm-build.workspace = true +openvm-transpiler.workspace = true +openvm-native-transpiler.workspace = true +openvm-native-circuit.workspace = true +openvm-rv32im-transpiler.workspace = true +openvm-toolchain-tests.workspace = true +p3-baby-bear.workspace = true + +eyre.workspace = true + +[features] +default = ["parallel"] +parallel = ["openvm-circuit/parallel"] diff --git a/extensions/native/tests/programs/Cargo.toml b/extensions/native/tests/programs/Cargo.toml new file mode 100644 index 0000000000..fb95321cf7 --- /dev/null +++ b/extensions/native/tests/programs/Cargo.toml @@ -0,0 +1,20 @@ +[workspace] +[package] +name = "openvm-native-integration-test-program" +version = "0.0.0" +edition = "2021" + +[dependencies] +openvm = { path = "../../../../crates/toolchain/openvm" } +openvm-native-guest-macro = { path = "../../guest-macro", default-features = false } + +[features] +default = [] +std = ["openvm/std"] + +[build-dependencies] +openvm-native-compiler = { path = "../../compiler", default-features = false } +openvm-native-transpiler = { path = "../../transpiler", default-features = false } +openvm-instructions = { path = "../../../../crates/toolchain/instructions", default-features = false } +openvm-stark-sdk = { git = "https://github.com/openvm-org/stark-backend.git", rev = "c785515", default-features = false } # FIXME +openvm-stark-backend = { git = "https://github.com/openvm-org/stark-backend.git", rev = "c785515", default-features = false } diff --git a/extensions/native/tests/programs/build.rs b/extensions/native/tests/programs/build.rs new file mode 100644 index 0000000000..d3d518425c --- /dev/null +++ b/extensions/native/tests/programs/build.rs @@ -0,0 +1,47 @@ +use std::fs::File; +use openvm_stark_sdk::p3_baby_bear::BabyBear; +use openvm_stark_backend::p3_field::extension::BinomialExtensionField; +use std::io::Write; +use openvm_stark_backend::p3_field::FieldAlgebra; +use openvm_native_compiler::{ + asm::{AsmBuilder, AsmCompiler}, + conversion::{CompilerOptions, convert_program} + , +}; +use openvm_native_compiler::ir::{Felt, Var}; +use openvm_native_transpiler::serialize_defined_instructions; + +type F = BabyBear; +type EF = BinomialExtensionField; + +fn function_name(builder: &mut AsmBuilder, n: Var) -> Felt { + let a: Felt<_> = builder.constant(F::ZERO); + let b: Felt<_> = builder.constant(F::ONE); + let zero: Var<_> = builder.eval(F::ZERO); + builder.range(zero, n).for_each(|_, builder| { + builder.assign(&b, a + b); + builder.assign(&a, b - a); + }); + a +} + +fn main() { + let mut file = File::create("compiler_output.txt").unwrap(); + + let mut builder = AsmBuilder::::default(); + + let var_n = builder.uninit(); + let result = function_name(&mut builder, var_n); + + writeln!(file, "{}", var_n.fp()).unwrap(); + writeln!(file, "{}", result.fp()).unwrap(); + + let mut compiler = AsmCompiler::new(1); + compiler.build(builder.operations); + let asm_code = compiler.code(); + let program = convert_program::(asm_code, CompilerOptions::default()); + let serialized = serialize_defined_instructions(&program.defined_instructions()); + for word in serialized { + writeln!(file, "{}", word).unwrap(); + } +} \ No newline at end of file diff --git a/extensions/native/tests/programs/compiler_output.txt b/extensions/native/tests/programs/compiler_output.txt new file mode 100644 index 0000000000..6ddb2d7792 --- /dev/null +++ b/extensions/native/tests/programs/compiler_output.txt @@ -0,0 +1,132 @@ +16777150 +16777148 +28683 +7 +257 +0 +0 +0 +0 +5 +0 +0 +28683 +7 +257 +16777216 +0 +16777212 +0 +5 +0 +0 +28683 +7 +277 +16777208 +8 +0 +5 +0 +0 +0 +28683 +7 +1 +0 +0 +1 +0 +0 +0 +0 +28683 +7 +257 +0 +0 +16777148 +0 +5 +0 +0 +28683 +7 +257 +1 +0 +16777141 +0 +5 +0 +0 +28683 +7 +257 +0 +0 +16777143 +0 +5 +0 +0 +28683 +7 +256 +16777142 +0 +16777143 +5 +0 +0 +0 +28683 +7 +277 +16777208 +16 +0 +5 +0 +0 +0 +28683 +7 +304 +16777141 +16777148 +16777141 +5 +5 +5 +0 +28683 +7 +305 +16777148 +16777141 +16777148 +5 +5 +5 +0 +28683 +7 +304 +16777142 +16777142 +1 +5 +5 +0 +0 +28683 +7 +273 +16777142 +16777150 +2013265909 +5 +5 +0 +0 diff --git a/extensions/native/tests/programs/src/main.rs b/extensions/native/tests/programs/src/main.rs new file mode 100644 index 0000000000..befe07b480 --- /dev/null +++ b/extensions/native/tests/programs/src/main.rs @@ -0,0 +1,21 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +openvm_native_guest_macro::native_kernel! { + fn function_name(n: usize | Felt) -> usize | Felt { + compiler_output.txt + } +} + +openvm::entry!(main); + +const N: usize = 8; + +fn main() { + let answers: [usize; N] = [0, 1, 1, 2, 3, 5, 8, 13]; + for i in 0..N { + if function_name(i) != answers[i] { + panic!(); + } + } +} diff --git a/extensions/native/tests/src/lib.rs b/extensions/native/tests/src/lib.rs new file mode 100644 index 0000000000..cee6a6954b --- /dev/null +++ b/extensions/native/tests/src/lib.rs @@ -0,0 +1,40 @@ +#[cfg(test)] +mod tests { + use eyre::Result; + use openvm_build::{GuestOptions, TargetFilter}; + use openvm_circuit::{arch::instructions::exe::VmExe, utils::air_test}; + use openvm_native_circuit::Rv32WithKernelsConfig; + use openvm_native_transpiler::LongFormTranspilerExtension; + use openvm_rv32im_transpiler::{ + Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, + }; + use openvm_sdk::Sdk; + use openvm_toolchain_tests::get_programs_dir; + use openvm_transpiler::{transpiler::Transpiler, FromElf}; + use p3_baby_bear::BabyBear; + + #[test] + fn test_native_kernel() -> Result<()> { + let sdk = Sdk; + + let elf = sdk.build( + GuestOptions::default(), + get_programs_dir!(), + &Some(TargetFilter { + kind: "bin".to_string(), + name: "openvm-native-integration-test-program".to_string(), + }), + )?; + let exe = VmExe::from_elf( + elf.clone(), + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(LongFormTranspilerExtension), + )?; + + air_test(Rv32WithKernelsConfig::default(), exe); + Ok(()) + } +} diff --git a/extensions/native/transpiler/Cargo.toml b/extensions/native/transpiler/Cargo.toml new file mode 100644 index 0000000000..45c5e27bf3 --- /dev/null +++ b/extensions/native/transpiler/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "openvm-native-transpiler" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +openvm-instructions = { workspace = true } +openvm-transpiler = { workspace = true } +p3-field = { workspace = true } diff --git a/extensions/native/transpiler/src/lib.rs b/extensions/native/transpiler/src/lib.rs new file mode 100644 index 0000000000..241160b563 --- /dev/null +++ b/extensions/native/transpiler/src/lib.rs @@ -0,0 +1,95 @@ +use openvm_instructions::{instruction::Instruction, riscv::RV32_REGISTER_NUM_LIMBS, VmOpcode}; +use openvm_transpiler::{TranspilerExtension, TranspilerOutput}; +use p3_field::PrimeField32; + +/* + * The indicators use + * - opcode = 0x0b (custom-0 as defined in RISC-V spec document) + * - funct3 = 0b111 + * + * `LONG_FORM_INSTRUCTION_INDICATOR` has funct7 = 0b0. + * `GAP_INDICATOR` has funct7 = 0b1. + * + * `VARIABLE_REGISTER_INDICATOR` does not need to conform to RISC_V format, + * because it occurs only within a block already prefixed with `LONG_FORM_INSTRUCTION_INDICATOR`. + * Thus, we make its value larger than 2^31 to ensure that it is not equal to a possible field element. + */ +const OPCODE: u32 = 0x0b; +const FUNCT3: u32 = 0b111; +pub const LONG_FORM_INSTRUCTION_INDICATOR: u32 = (FUNCT3 << 12) + OPCODE; +pub const GAP_INDICATOR: u32 = (1 << 25) + (FUNCT3 << 12) + OPCODE; +pub const VARIABLE_REGISTER_INDICATOR: u32 = (1 << 31) + 116; + +pub struct LongFormTranspilerExtension; + +impl TranspilerExtension for LongFormTranspilerExtension { + fn process_custom(&self, instruction_stream: &[u32]) -> Option> { + if instruction_stream[0] == LONG_FORM_INSTRUCTION_INDICATOR { + let num_operands = instruction_stream[1] as usize; + let opcode = VmOpcode::from_usize(instruction_stream[2] as usize); + let mut operands = vec![]; + let mut j = 3; + for _ in 0..num_operands { + if instruction_stream[j] == VARIABLE_REGISTER_INDICATOR { + let register = (instruction_stream[j + 1] >> 7) & 0x1f; + let offset = instruction_stream[j + 1] >> 20; + let mut operand = (RV32_REGISTER_NUM_LIMBS as u32 * register) + offset; + if offset >= 1 << 12 { + operand -= 1 << 12; + } + operands.push(F::from_canonical_u32(operand)); + j += 2; + } else { + operands.push(F::from_canonical_u32(instruction_stream[j])); + j += 1; + } + } + while operands.len() < 7 { + operands.push(F::ZERO); + } + let instruction = Instruction { + opcode, + a: operands[0], + b: operands[1], + c: operands[2], + d: operands[3], + e: operands[4], + f: operands[5], + g: operands[6], + }; + Some(TranspilerOutput::many_to_one(instruction, j)) + } else if instruction_stream[0] == GAP_INDICATOR { + Some(TranspilerOutput::gap(instruction_stream[1] as usize, 2)) + } else { + None + } + } +} + +pub fn serialize_defined_instructions( + instructions: &[Instruction], +) -> Vec { + let mut words = vec![]; + for instruction in instructions { + words.push(LONG_FORM_INSTRUCTION_INDICATOR); + let operands = instruction.operands(); + words.push(operands.len() as u32); + words.push(instruction.opcode.as_usize() as u32); + words.extend(operands.iter().map(F::as_canonical_u32)) + } + words +} + +// panics if deserialization fails or results in gaps +pub fn deserialize_defined_instructions(words: &[u32]) -> Vec> { + let mut index = 0; + let mut instructions = vec![]; + while index < words.len() { + let next = LongFormTranspilerExtension + .process_custom(&words[index..]) + .unwrap(); + instructions.extend(next.instructions.into_iter().map(Option::unwrap)); + index += next.used_u32s; + } + instructions +} diff --git a/extensions/pairing/transpiler/src/lib.rs b/extensions/pairing/transpiler/src/lib.rs index 846b9e6526..b268b13be5 100644 --- a/extensions/pairing/transpiler/src/lib.rs +++ b/extensions/pairing/transpiler/src/lib.rs @@ -4,7 +4,7 @@ use openvm_instructions::{ use openvm_instructions_derive::UsizeOpcode; use openvm_pairing_guest::{PairingBaseFunct7, OPCODE, PAIRING_FUNCT3}; use openvm_stark_backend::p3_field::PrimeField32; -use openvm_transpiler::{util::from_r_type, TranspilerExtension}; +use openvm_transpiler::{util::from_r_type, TranspilerExtension, TranspilerOutput}; use rrs_lib::instruction_formats::RType; use strum::{EnumCount, EnumIter, FromRepr}; @@ -81,7 +81,7 @@ pub enum PairingPhantom { pub struct PairingTranspilerExtension; impl TranspilerExtension for PairingTranspilerExtension { - fn process_custom(&self, instruction_stream: &[u32]) -> Option<(Instruction, usize)> { + fn process_custom(&self, instruction_stream: &[u32]) -> Option> { if instruction_stream.is_empty() { return None; } @@ -102,15 +102,12 @@ impl TranspilerExtension for PairingTranspilerExtension { if let Some(PairingBaseFunct7::HintFinalExp) = PairingBaseFunct7::from_repr(base_funct7) { assert_eq!(dec_insn.rd, 0); // Return exits the outermost function - return Some(( - Instruction::phantom( - PhantomDiscriminant(PairingPhantom::HintFinalExp as u16), - F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs1), - F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs2), - pairing_idx as u16, - ), - 1, - )); + return Some(TranspilerOutput::one_to_one(Instruction::phantom( + PhantomDiscriminant(PairingPhantom::HintFinalExp as u16), + F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs1), + F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs2), + pairing_idx as u16, + ))); } let global_opcode = match PairingBaseFunct7::from_repr(base_funct7) { Some(PairingBaseFunct7::MillerDoubleStep) => { @@ -151,6 +148,10 @@ impl TranspilerExtension for PairingTranspilerExtension { }; let global_opcode = global_opcode + pairing_idx_shift; - Some((from_r_type(global_opcode, 2, &dec_insn), 1)) + Some(TranspilerOutput::one_to_one(from_r_type( + global_opcode, + 2, + &dec_insn, + ))) } } diff --git a/extensions/rv32im/transpiler/src/lib.rs b/extensions/rv32im/transpiler/src/lib.rs index bbecccb443..69ae849fc3 100644 --- a/extensions/rv32im/transpiler/src/lib.rs +++ b/extensions/rv32im/transpiler/src/lib.rs @@ -11,7 +11,7 @@ use openvm_rv32im_guest::{ use openvm_stark_backend::p3_field::PrimeField32; use openvm_transpiler::{ util::{nop, unimp}, - TranspilerExtension, + TranspilerExtension, TranspilerOutput, }; use rrs::InstructionTranspiler; use rrs_lib::{ @@ -33,7 +33,7 @@ pub struct Rv32MTranspilerExtension; pub struct Rv32IoTranspilerExtension; impl TranspilerExtension for Rv32ITranspilerExtension { - fn process_custom(&self, instruction_stream: &[u32]) -> Option<(Instruction, usize)> { + fn process_custom(&self, instruction_stream: &[u32]) -> Option> { let mut transpiler = InstructionTranspiler::(PhantomData); if instruction_stream.is_empty() { return None; @@ -50,14 +50,14 @@ impl TranspilerExtension for Rv32ITranspilerExtension { // CSRRW if dec_insn.rs1 == 0 && dec_insn.rd == 0 { // This resets the CSR counter to zero. Since we don't have any CSR registers, this is a nop. - return Some((nop(), 1)); + return Some(TranspilerOutput::one_to_one(nop())); } } eprintln!( "Transpiling system / CSR instruction: {:b} (opcode = {:07b}, funct3 = {:03b}) to unimp", instruction_u32, opcode, funct3 ); - return Some((unimp(), 1)); + return Some(TranspilerOutput::one_to_one(unimp())); } (SYSTEM_OPCODE, TERMINATE_FUNCT3) => { let dec_insn = IType::new(instruction_u32); @@ -98,12 +98,12 @@ impl TranspilerExtension for Rv32ITranspilerExtension { _ => process_instruction(&mut transpiler, instruction_u32), }; - instruction.map(|ret| (ret, 1)) + instruction.map(TranspilerOutput::one_to_one) } } impl TranspilerExtension for Rv32MTranspilerExtension { - fn process_custom(&self, instruction_stream: &[u32]) -> Option<(Instruction, usize)> { + fn process_custom(&self, instruction_stream: &[u32]) -> Option> { if instruction_stream.is_empty() { return None; } @@ -125,12 +125,12 @@ impl TranspilerExtension for Rv32MTranspilerExtension { instruction_u32, ); - instruction.map(|instruction| (instruction, 1)) + instruction.map(TranspilerOutput::one_to_one) } } impl TranspilerExtension for Rv32IoTranspilerExtension { - fn process_custom(&self, instruction_stream: &[u32]) -> Option<(Instruction, usize)> { + fn process_custom(&self, instruction_stream: &[u32]) -> Option> { if instruction_stream.is_empty() { return None; } @@ -175,6 +175,6 @@ impl TranspilerExtension for Rv32IoTranspilerExtension { _ => return None, }; - instruction.map(|instruction| (instruction, 1)) + instruction.map(TranspilerOutput::one_to_one) } } diff --git a/extensions/sha256/transpiler/src/lib.rs b/extensions/sha256/transpiler/src/lib.rs index b39bb4f3c6..a3ed174c93 100644 --- a/extensions/sha256/transpiler/src/lib.rs +++ b/extensions/sha256/transpiler/src/lib.rs @@ -1,8 +1,8 @@ -use openvm_instructions::{instruction::Instruction, riscv::RV32_MEMORY_AS, UsizeOpcode}; +use openvm_instructions::{riscv::RV32_MEMORY_AS, UsizeOpcode}; use openvm_instructions_derive::UsizeOpcode; use openvm_sha256_guest::{OPCODE, SHA256_FUNCT3, SHA256_FUNCT7}; use openvm_stark_backend::p3_field::PrimeField32; -use openvm_transpiler::{util::from_r_type, TranspilerExtension}; +use openvm_transpiler::{util::from_r_type, TranspilerExtension, TranspilerOutput}; use rrs_lib::instruction_formats::RType; use strum::{EnumCount, EnumIter, FromRepr}; @@ -19,7 +19,7 @@ pub enum Rv32Sha256Opcode { pub struct Sha256TranspilerExtension; impl TranspilerExtension for Sha256TranspilerExtension { - fn process_custom(&self, instruction_stream: &[u32]) -> Option<(Instruction, usize)> { + fn process_custom(&self, instruction_stream: &[u32]) -> Option> { if instruction_stream.is_empty() { return None; } @@ -40,6 +40,6 @@ impl TranspilerExtension for Sha256TranspilerExtension { RV32_MEMORY_AS as usize, &dec_insn, ); - Some((instruction, 1)) + Some(TranspilerOutput::one_to_one(instruction)) } }