├── examples ├── add.ll ├── simple.ll ├── add64.ll ├── loop.ll ├── loop_simple.ll ├── comparison.ll ├── factorial.ll ├── arithmetic_32.ll ├── fibonacci.ll └── memory.ll ├── src ├── emit │ └── mod.rs ├── parser │ ├── mod.rs │ ├── lexer.rs │ └── parser.rs ├── lib.rs ├── regalloc │ ├── mod.rs │ └── linear.rs ├── ir │ ├── mod.rs │ ├── block.rs │ ├── function.rs │ ├── module.rs │ ├── types.rs │ └── instruction.rs ├── translate │ ├── intrinsics.rs │ ├── types.rs │ ├── memory.rs │ ├── control.rs │ ├── arithmetic.rs │ ├── mod.rs │ └── context.rs └── main.rs ├── LICENSE-MIT ├── Cargo.toml ├── tests ├── value_tests.rs ├── translate_tests.rs ├── type_tests.rs ├── regalloc_tests.rs ├── parser_tests.rs ├── instruction_tests.rs └── integration.rs ├── README.md ├── ARCHITECTURE.md └── LICENSE-APACHE /examples/add.ll: -------------------------------------------------------------------------------- 1 | ; Simple 32-bit addition 2 | define i32 @add(i32 %a, i32 %b) { 3 | entry: 4 | %result = add i32 %a, %b 5 | ret i32 %result 6 | } 7 | -------------------------------------------------------------------------------- /examples/simple.ll: -------------------------------------------------------------------------------- 1 | ; Simple addition function 2 | define i32 @add(i32 %a, i32 %b) { 3 | entry: 4 | %result = add i32 %a, %b 5 | ret i32 %result 6 | } 7 | 8 | ; Main entry point 9 | define i32 @main() { 10 | entry: 11 | %x = call i32 @add(i32 10, i32 20) 12 | ret i32 %x 13 | } 14 | -------------------------------------------------------------------------------- /examples/add64.ll: -------------------------------------------------------------------------------- 1 | ; 64-bit addition using register pairs 2 | define i64 @add64(i64 %a, i64 %b) { 3 | entry: 4 | %sum = add i64 %a, %b 5 | ret i64 %sum 6 | } 7 | 8 | ; 64-bit subtraction 9 | define i64 @sub64(i64 %a, i64 %b) { 10 | entry: 11 | %diff = sub i64 %a, %b 12 | ret i64 %diff 13 | } 14 | -------------------------------------------------------------------------------- /examples/loop.ll: -------------------------------------------------------------------------------- 1 | ; Sum numbers from 1 to n 2 | define i32 @sum_to_n(i32 %n) { 3 | entry: 4 | br label %loop 5 | 6 | loop: 7 | %i = phi i32 [ 1, %entry ], [ %next_i, %loop ] 8 | %sum = phi i32 [ 0, %entry ], [ %next_sum, %loop ] 9 | %next_sum = add i32 %sum, %i 10 | %next_i = add i32 %i, 1 11 | %done = icmp sgt i32 %next_i, %n 12 | br i1 %done, label %exit, label %loop 13 | 14 | exit: 15 | ret i32 %next_sum 16 | } 17 | -------------------------------------------------------------------------------- /examples/loop_simple.ll: -------------------------------------------------------------------------------- 1 | ; Simple loop - sum from 1 to N 2 | define i32 @sum_to_n(i32 %n) { 3 | entry: 4 | br label %loop 5 | 6 | loop: 7 | %i = phi i32 [ 1, %entry ], [ %next_i, %loop_body ] 8 | %sum = phi i32 [ 0, %entry ], [ %next_sum, %loop_body ] 9 | 10 | ; Check if done 11 | %cmp = icmp sgt i32 %i, %n 12 | br i1 %cmp, label %exit, label %loop_body 13 | 14 | loop_body: 15 | ; sum = sum + i 16 | %next_sum = add i32 %sum, %i 17 | 18 | ; i = i + 1 19 | %next_i = add i32 %i, 1 20 | 21 | br label %loop 22 | 23 | exit: 24 | ret i32 %sum 25 | } 26 | -------------------------------------------------------------------------------- /examples/comparison.ll: -------------------------------------------------------------------------------- 1 | ; Comparison operations 2 | define i32 @max(i32 %a, i32 %b) { 3 | entry: 4 | %cmp = icmp sgt i32 %a, %b 5 | br i1 %cmp, label %return_a, label %return_b 6 | 7 | return_a: 8 | ret i32 %a 9 | 10 | return_b: 11 | ret i32 %b 12 | } 13 | 14 | define i32 @is_equal(i32 %a, i32 %b) { 15 | entry: 16 | %cmp = icmp eq i32 %a, %b 17 | %result = zext i1 %cmp to i32 18 | ret i32 %result 19 | } 20 | 21 | define i32 @is_less_unsigned(i32 %a, i32 %b) { 22 | entry: 23 | %cmp = icmp ult i32 %a, %b 24 | %result = zext i1 %cmp to i32 25 | ret i32 %result 26 | } 27 | -------------------------------------------------------------------------------- /src/emit/mod.rs: -------------------------------------------------------------------------------- 1 | //! Bytecode emission 2 | 3 | use anyhow::Result; 4 | use zkir_spec::Program; 5 | 6 | /// Emit ZK IR program to bytecode 7 | pub fn emit_program(program: &Program) -> Result> { 8 | // Use bincode for serialization 9 | bincode::serialize(program).map_err(|e| anyhow::anyhow!("Serialization error: {}", e)) 10 | } 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | use super::*; 15 | 16 | #[test] 17 | fn test_emit_empty_program() { 18 | let program = Program::new(Vec::new()); 19 | let bytes = emit_program(&program).unwrap(); 20 | assert!(!bytes.is_empty()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/factorial.ll: -------------------------------------------------------------------------------- 1 | ; Iterative factorial (ZK-friendly) 2 | define i32 @factorial(i32 %n) { 3 | entry: 4 | ; Check if n <= 1 5 | %cmp = icmp sle i32 %n, 1 6 | br i1 %cmp, label %return_one, label %loop_init 7 | 8 | return_one: 9 | ret i32 1 10 | 11 | loop_init: 12 | br label %loop 13 | 14 | loop: 15 | %i = phi i32 [ 2, %loop_init ], [ %next_i, %loop ] 16 | %result = phi i32 [ 1, %loop_init ], [ %next_result, %loop ] 17 | 18 | ; result = result * i 19 | %next_result = mul i32 %result, %i 20 | 21 | ; i = i + 1 22 | %next_i = add i32 %i, 1 23 | 24 | ; Check if done (i > n) 25 | %done = icmp sgt i32 %next_i, %n 26 | br i1 %done, label %exit, label %loop 27 | 28 | exit: 29 | ret i32 %next_result 30 | } 31 | -------------------------------------------------------------------------------- /src/parser/mod.rs: -------------------------------------------------------------------------------- 1 | //! LLVM IR parser 2 | 3 | pub mod lexer; 4 | pub mod parser; 5 | 6 | pub use parser::parse; 7 | 8 | use thiserror::Error; 9 | 10 | #[derive(Debug, Error)] 11 | pub enum ParseError { 12 | #[error("Lexer error: {0}")] 13 | LexerError(String), 14 | 15 | #[error("Parse error at line {line}: {message}")] 16 | ParseError { line: usize, message: String }, 17 | 18 | #[error("Unexpected token: {0}")] 19 | UnexpectedToken(String), 20 | 21 | #[error("Unexpected end of input")] 22 | UnexpectedEof, 23 | 24 | #[error("Invalid type: {0}")] 25 | InvalidType(String), 26 | 27 | #[error("Invalid instruction: {0}")] 28 | InvalidInstruction(String), 29 | } 30 | 31 | pub type ParseResult = Result; 32 | -------------------------------------------------------------------------------- /examples/arithmetic_32.ll: -------------------------------------------------------------------------------- 1 | ; 32-bit arithmetic operations showcase 2 | define i32 @test_arithmetic(i32 %a, i32 %b) { 3 | entry: 4 | ; Addition 5 | %sum = add i32 %a, %b 6 | 7 | ; Subtraction 8 | %diff = sub i32 %sum, %b 9 | 10 | ; Multiplication 11 | %prod = mul i32 %a, %b 12 | 13 | ; Division (signed) 14 | %quot = sdiv i32 %prod, %b 15 | 16 | ; Remainder (signed) 17 | %rem = srem i32 %a, %b 18 | 19 | ; Bitwise AND 20 | %and_result = and i32 %a, %b 21 | 22 | ; Bitwise OR 23 | %or_result = or i32 %a, %b 24 | 25 | ; Bitwise XOR 26 | %xor_result = xor i32 %a, %b 27 | 28 | ; Shift left 29 | %shl_result = shl i32 %a, 2 30 | 31 | ; Logical shift right 32 | %lshr_result = lshr i32 %a, 1 33 | 34 | ret i32 %shl_result 35 | } 36 | -------------------------------------------------------------------------------- /examples/fibonacci.ll: -------------------------------------------------------------------------------- 1 | ; Iterative Fibonacci (ZK-friendly - no recursion) 2 | define i32 @fib(i32 %n) { 3 | entry: 4 | %cmp0 = icmp eq i32 %n, 0 5 | br i1 %cmp0, label %return_zero, label %check_one 6 | 7 | check_one: 8 | %cmp1 = icmp eq i32 %n, 1 9 | br i1 %cmp1, label %return_one, label %loop_init 10 | 11 | return_zero: 12 | ret i32 0 13 | 14 | return_one: 15 | ret i32 1 16 | 17 | loop_init: 18 | br label %loop 19 | 20 | loop: 21 | %i = phi i32 [ 2, %loop_init ], [ %next_i, %loop ] 22 | %prev = phi i32 [ 0, %loop_init ], [ %curr, %loop ] 23 | %curr = phi i32 [ 1, %loop_init ], [ %next, %loop ] 24 | %next = add i32 %prev, %curr 25 | %next_i = add i32 %i, 1 26 | %done = icmp sgt i32 %next_i, %n 27 | br i1 %done, label %exit, label %loop 28 | 29 | exit: 30 | ret i32 %next 31 | } 32 | -------------------------------------------------------------------------------- /examples/memory.ll: -------------------------------------------------------------------------------- 1 | ; Memory operations - load and store 2 | define i32 @load_store(ptr %ptr, i32 %value) { 3 | entry: 4 | ; Store value to pointer 5 | store i32 %value, ptr %ptr 6 | 7 | ; Load value back 8 | %loaded = load i32, ptr %ptr 9 | 10 | ; Return loaded value 11 | ret i32 %loaded 12 | } 13 | 14 | ; Stack allocation 15 | define i32 @stack_alloc() { 16 | entry: 17 | ; Allocate space on stack 18 | %local = alloca i32 19 | 20 | ; Store to stack 21 | store i32 42, ptr %local 22 | 23 | ; Load from stack 24 | %result = load i32, ptr %local 25 | 26 | ret i32 %result 27 | } 28 | 29 | ; 8-bit and 16-bit loads/stores 30 | define i32 @byte_ops(ptr %ptr) { 31 | entry: 32 | ; Store byte 33 | store i8 255, ptr %ptr 34 | 35 | ; Load byte (unsigned) 36 | %byte = load i8, ptr %ptr 37 | 38 | ; Zero-extend to i32 39 | %result = zext i8 %byte to i32 40 | 41 | ret i32 %result 42 | } 43 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! LLVM IR to ZK IR translator 2 | //! 3 | //! This crate provides functionality to translate LLVM IR into ZK IR bytecode, 4 | //! enabling compilation of Rust, C, and C++ programs to zero-knowledge provable code. 5 | 6 | pub mod parser; 7 | pub mod ir; 8 | pub mod translate; 9 | pub mod regalloc; 10 | pub mod emit; 11 | 12 | pub use emit::emit_program; 13 | 14 | use anyhow::Result; 15 | 16 | /// Translate LLVM IR source code to ZK IR bytecode 17 | pub fn translate_llvm_ir(source: &str, opt_level: u8) -> Result { 18 | // Parse LLVM IR 19 | let module = parser::parse(source)?; 20 | 21 | // Translate to ZK IR 22 | let program = translate::translate_module(&module, opt_level)?; 23 | 24 | Ok(program) 25 | } 26 | 27 | /// Check if LLVM IR is compatible with ZK IR 28 | pub fn check_compatibility(source: &str) -> Result<()> { 29 | let module = parser::parse(source)?; 30 | translate::check_module_compatibility(&module)?; 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 zkir-llvm contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/regalloc/mod.rs: -------------------------------------------------------------------------------- 1 | //! Register allocation 2 | 3 | pub mod linear; 4 | 5 | pub use linear::LinearScan; 6 | 7 | use thiserror::Error; 8 | 9 | #[derive(Debug, Error)] 10 | pub enum RegAllocError { 11 | #[error("Out of registers")] 12 | OutOfRegisters, 13 | 14 | #[error("Invalid register: {0}")] 15 | InvalidRegister(u8), 16 | 17 | #[error("Spill failed: {0}")] 18 | SpillFailed(String), 19 | } 20 | 21 | pub type RegAllocResult = Result; 22 | 23 | /// Virtual register identifier 24 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 25 | pub struct VirtualReg(pub u32); 26 | 27 | /// Physical register identifier 28 | pub type PhysicalReg = zkir_spec::Register; 29 | 30 | /// Live interval for a virtual register 31 | #[derive(Debug, Clone)] 32 | pub struct LiveInterval { 33 | pub vreg: VirtualReg, 34 | pub start: u32, 35 | pub end: u32, 36 | } 37 | 38 | impl LiveInterval { 39 | pub fn new(vreg: VirtualReg, start: u32, end: u32) -> Self { 40 | Self { vreg, start, end } 41 | } 42 | 43 | pub fn overlaps(&self, other: &LiveInterval) -> bool { 44 | self.start < other.end && other.start < self.end 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/ir/mod.rs: -------------------------------------------------------------------------------- 1 | //! LLVM IR data structures 2 | 3 | pub mod module; 4 | pub mod function; 5 | pub mod block; 6 | pub mod instruction; 7 | pub mod types; 8 | 9 | pub use module::Module; 10 | pub use function::Function; 11 | pub use block::BasicBlock; 12 | pub use instruction::Instruction; 13 | pub use types::Type; 14 | 15 | /// A value in LLVM IR 16 | #[derive(Debug, Clone, PartialEq)] 17 | pub enum Value { 18 | /// Local variable reference (e.g., %x) 19 | Local(String), 20 | 21 | /// Constant integer 22 | ConstInt { value: i64, ty: Type }, 23 | 24 | /// Constant boolean 25 | ConstBool(bool), 26 | 27 | /// Null pointer 28 | Null, 29 | 30 | /// Undefined value 31 | Undef, 32 | } 33 | 34 | impl Value { 35 | pub fn const_i32(value: i32) -> Self { 36 | Value::ConstInt { 37 | value: value as i64, 38 | ty: Type::Int(32), 39 | } 40 | } 41 | 42 | pub fn const_i64(value: i64) -> Self { 43 | Value::ConstInt { 44 | value, 45 | ty: Type::Int(64), 46 | } 47 | } 48 | 49 | pub fn local(name: impl Into) -> Self { 50 | Value::Local(name.into()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zkir-llvm" 3 | version = "0.1.0" 4 | edition = "2021" 5 | description = "LLVM IR to ZK IR translator" 6 | license = "MIT OR Apache-2.0" 7 | rust-version = "1.75" 8 | 9 | [dependencies] 10 | # Core ZK IR types from your existing repo 11 | zkir-spec = { git = "https://github.com/seceq/zkir", package = "zkir-spec" } 12 | # Or if developing locally: 13 | # zkir-spec = { path = "../zkir/zkir-spec" } 14 | 15 | # Parsing 16 | logos = "0.14" # Lexer generator 17 | nom = "7" # Parser combinators 18 | 19 | # LLVM interaction (optional - for bitcode support) 20 | # llvm-sys = "180" # Uncomment for LLVM bitcode 21 | # inkwell = "0.5" # Safer LLVM wrapper 22 | 23 | # Utilities 24 | thiserror = "1.0" 25 | anyhow = "1" 26 | log = "0.4" 27 | env_logger = "0.11" 28 | clap = { version = "4", features = ["derive"] } 29 | 30 | # Data structures 31 | indexmap = "2" # Ordered hash maps 32 | petgraph = "0.6" # Graph algorithms for CFG 33 | 34 | # Serialization 35 | serde = { version = "1", features = ["derive"] } 36 | serde_json = "1" 37 | bincode = "1.3.3" 38 | 39 | [dev-dependencies] 40 | pretty_assertions = "1" 41 | tempfile = "3" 42 | proptest = "1" 43 | -------------------------------------------------------------------------------- /src/ir/block.rs: -------------------------------------------------------------------------------- 1 | //! Basic block representation 2 | 3 | use super::Instruction; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct BasicBlock { 7 | name: String, 8 | instructions: Vec, 9 | } 10 | 11 | impl BasicBlock { 12 | pub fn new(name: impl Into) -> Self { 13 | Self { 14 | name: name.into(), 15 | instructions: Vec::new(), 16 | } 17 | } 18 | 19 | pub fn with_instructions(name: impl Into, instructions: Vec) -> Self { 20 | Self { 21 | name: name.into(), 22 | instructions, 23 | } 24 | } 25 | 26 | pub fn name(&self) -> &str { 27 | &self.name 28 | } 29 | 30 | pub fn instructions(&self) -> &[Instruction] { 31 | &self.instructions 32 | } 33 | 34 | pub fn instructions_mut(&mut self) -> &mut Vec { 35 | &mut self.instructions 36 | } 37 | 38 | pub fn add_instruction(&mut self, instr: Instruction) { 39 | self.instructions.push(instr); 40 | } 41 | 42 | /// Get the terminator instruction (last instruction) 43 | pub fn terminator(&self) -> Option<&Instruction> { 44 | self.instructions.last() 45 | } 46 | 47 | /// Check if this block has a terminator 48 | pub fn is_terminated(&self) -> bool { 49 | self.instructions 50 | .last() 51 | .map(|i| i.is_terminator()) 52 | .unwrap_or(false) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/value_tests.rs: -------------------------------------------------------------------------------- 1 | //! Value tests 2 | 3 | use zkir_llvm::ir::{Type, Value}; 4 | 5 | #[test] 6 | fn test_const_i32() { 7 | let v = Value::const_i32(42); 8 | match v { 9 | Value::ConstInt { value, ty } => { 10 | assert_eq!(value, 42); 11 | assert_eq!(ty, Type::Int(32)); 12 | } 13 | _ => panic!("Expected ConstInt"), 14 | } 15 | } 16 | 17 | #[test] 18 | fn test_const_i64() { 19 | let v = Value::const_i64(0x1234567890ABCDEF); 20 | match v { 21 | Value::ConstInt { value, ty } => { 22 | assert_eq!(value, 0x1234567890ABCDEF); 23 | assert_eq!(ty, Type::Int(64)); 24 | } 25 | _ => panic!("Expected ConstInt"), 26 | } 27 | } 28 | 29 | #[test] 30 | fn test_const_bool() { 31 | let v_true = Value::ConstBool(true); 32 | let v_false = Value::ConstBool(false); 33 | 34 | assert_eq!(v_true, Value::ConstBool(true)); 35 | assert_eq!(v_false, Value::ConstBool(false)); 36 | } 37 | 38 | #[test] 39 | fn test_local_value() { 40 | let v = Value::local("my_var"); 41 | match v { 42 | Value::Local(name) => { 43 | assert_eq!(name, "my_var"); 44 | } 45 | _ => panic!("Expected Local"), 46 | } 47 | } 48 | 49 | #[test] 50 | fn test_null_value() { 51 | let v = Value::Null; 52 | assert_eq!(v, Value::Null); 53 | } 54 | 55 | #[test] 56 | fn test_undef_value() { 57 | let v = Value::Undef; 58 | assert_eq!(v, Value::Undef); 59 | } 60 | -------------------------------------------------------------------------------- /tests/translate_tests.rs: -------------------------------------------------------------------------------- 1 | //! Translation tests 2 | 3 | use zkir_llvm::translate::types::*; 4 | use zkir_llvm::ir::Type; 5 | 6 | #[test] 7 | fn test_type_lowering() { 8 | assert_eq!(lower_type(&Type::Int(1)), Some(ZkType::I32)); 9 | assert_eq!(lower_type(&Type::Int(8)), Some(ZkType::I32)); 10 | assert_eq!(lower_type(&Type::Int(16)), Some(ZkType::I32)); 11 | assert_eq!(lower_type(&Type::Int(32)), Some(ZkType::I32)); 12 | assert_eq!(lower_type(&Type::Int(64)), Some(ZkType::I64)); 13 | assert_eq!(lower_type(&Type::Int(128)), Some(ZkType::I128)); 14 | assert_eq!(lower_type(&Type::Ptr), Some(ZkType::Ptr)); 15 | assert_eq!(lower_type(&Type::Void), None); 16 | } 17 | 18 | #[test] 19 | fn test_zk_type_num_regs() { 20 | assert_eq!(ZkType::I32.num_regs(), 1); 21 | assert_eq!(ZkType::I64.num_regs(), 2); 22 | assert_eq!(ZkType::I128.num_regs(), 4); 23 | assert_eq!(ZkType::Ptr.num_regs(), 1); 24 | } 25 | 26 | #[test] 27 | fn test_zk_type_size_bytes() { 28 | assert_eq!(ZkType::I32.size_bytes(), 4); 29 | assert_eq!(ZkType::I64.size_bytes(), 8); 30 | assert_eq!(ZkType::I128.size_bytes(), 16); 31 | assert_eq!(ZkType::Ptr.size_bytes(), 4); 32 | } 33 | 34 | #[test] 35 | fn test_aggregate_type_lowering() { 36 | // Arrays should lower to pointers (stored in memory) 37 | let arr = Type::Array(10, Box::new(Type::Int(32))); 38 | assert_eq!(lower_type(&arr), Some(ZkType::Ptr)); 39 | 40 | // Structs should lower to pointers 41 | let struct_ty = Type::Struct(vec![Type::Int(32), Type::Int(64)]); 42 | assert_eq!(lower_type(&struct_ty), Some(ZkType::Ptr)); 43 | } 44 | -------------------------------------------------------------------------------- /src/ir/function.rs: -------------------------------------------------------------------------------- 1 | //! Function representation 2 | 3 | use super::{BasicBlock, Type}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct Function { 7 | name: String, 8 | ret_ty: Type, 9 | params: Vec<(String, Type)>, 10 | blocks: Vec, 11 | } 12 | 13 | impl Function { 14 | pub fn new( 15 | name: impl Into, 16 | ret_ty: Type, 17 | params: Vec<(String, Type)>, 18 | blocks: Vec, 19 | ) -> Self { 20 | Self { 21 | name: name.into(), 22 | ret_ty, 23 | params, 24 | blocks, 25 | } 26 | } 27 | 28 | pub fn name(&self) -> &str { 29 | &self.name 30 | } 31 | 32 | pub fn ret_ty(&self) -> &Type { 33 | &self.ret_ty 34 | } 35 | 36 | pub fn params(&self) -> &[(String, Type)] { 37 | &self.params 38 | } 39 | 40 | pub fn blocks(&self) -> &[BasicBlock] { 41 | &self.blocks 42 | } 43 | 44 | pub fn blocks_mut(&mut self) -> &mut Vec { 45 | &mut self.blocks 46 | } 47 | 48 | pub fn add_block(&mut self, block: BasicBlock) { 49 | self.blocks.push(block); 50 | } 51 | 52 | pub fn get_block(&self, name: &str) -> Option<&BasicBlock> { 53 | self.blocks.iter().find(|b| b.name() == name) 54 | } 55 | 56 | pub fn get_block_mut(&mut self, name: &str) -> Option<&mut BasicBlock> { 57 | self.blocks.iter_mut().find(|b| b.name() == name) 58 | } 59 | 60 | /// Get entry block (first block) 61 | pub fn entry_block(&self) -> Option<&BasicBlock> { 62 | self.blocks.first() 63 | } 64 | 65 | /// Check if function is a declaration (no body) 66 | pub fn is_declaration(&self) -> bool { 67 | self.blocks.is_empty() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/translate/intrinsics.rs: -------------------------------------------------------------------------------- 1 | //! LLVM intrinsic translation 2 | 3 | use super::{context::TranslationContext, TranslateResult}; 4 | use crate::ir::Value; 5 | 6 | /// Translate LLVM intrinsic function call 7 | pub fn translate_intrinsic( 8 | ctx: &mut TranslationContext, 9 | name: &str, 10 | args: &[Value], 11 | result: Option<&str>, 12 | ) -> TranslateResult<()> { 13 | match name { 14 | // Memory intrinsics 15 | "llvm.memcpy" | "llvm.memmove" | "llvm.memset" => { 16 | // TODO: Implement memory intrinsics 17 | Err(super::TranslateError::UnsupportedInstruction(format!( 18 | "intrinsic {}", 19 | name 20 | ))) 21 | } 22 | 23 | // Math intrinsics 24 | "llvm.sqrt" | "llvm.sin" | "llvm.cos" | "llvm.exp" | "llvm.log" => { 25 | // Math intrinsics not supported in ZK IR 26 | Err(super::TranslateError::UnsupportedInstruction(format!( 27 | "intrinsic {}", 28 | name 29 | ))) 30 | } 31 | 32 | // Overflow intrinsics 33 | "llvm.sadd.with.overflow" | "llvm.uadd.with.overflow" | "llvm.ssub.with.overflow" 34 | | "llvm.usub.with.overflow" => { 35 | // TODO: Implement overflow checking 36 | Err(super::TranslateError::UnsupportedInstruction(format!( 37 | "intrinsic {}", 38 | name 39 | ))) 40 | } 41 | 42 | // Debug intrinsics (ignore) 43 | "llvm.dbg.declare" | "llvm.dbg.value" | "llvm.dbg.label" => { 44 | // Ignore debug intrinsics 45 | Ok(()) 46 | } 47 | 48 | // Lifetime intrinsics (ignore) 49 | "llvm.lifetime.start" | "llvm.lifetime.end" => { 50 | // Ignore lifetime intrinsics 51 | Ok(()) 52 | } 53 | 54 | _ => Err(super::TranslateError::UnsupportedInstruction(format!( 55 | "intrinsic {}", 56 | name 57 | ))), 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/type_tests.rs: -------------------------------------------------------------------------------- 1 | //! Type system tests 2 | 3 | use zkir_llvm::ir::Type; 4 | 5 | #[test] 6 | fn test_type_bit_width() { 7 | assert_eq!(Type::Int(1).bit_width(), 1); 8 | assert_eq!(Type::Int(8).bit_width(), 8); 9 | assert_eq!(Type::Int(16).bit_width(), 16); 10 | assert_eq!(Type::Int(32).bit_width(), 32); 11 | assert_eq!(Type::Int(64).bit_width(), 64); 12 | assert_eq!(Type::Ptr.bit_width(), 32); 13 | assert_eq!(Type::Void.bit_width(), 0); 14 | } 15 | 16 | #[test] 17 | fn test_type_size_in_bytes() { 18 | assert_eq!(Type::Int(8).size_in_bytes(), 1); 19 | assert_eq!(Type::Int(16).size_in_bytes(), 2); 20 | assert_eq!(Type::Int(32).size_in_bytes(), 4); 21 | assert_eq!(Type::Int(64).size_in_bytes(), 8); 22 | assert_eq!(Type::Ptr.size_in_bytes(), 4); 23 | } 24 | 25 | #[test] 26 | fn test_type_is_scalar() { 27 | assert!(Type::Int(32).is_scalar()); 28 | assert!(Type::Ptr.is_scalar()); 29 | assert!(!Type::Void.is_scalar()); 30 | assert!(!Type::Array(10, Box::new(Type::Int(32))).is_scalar()); 31 | } 32 | 33 | #[test] 34 | fn test_type_is_supported() { 35 | assert!(Type::Int(1).is_supported()); 36 | assert!(Type::Int(8).is_supported()); 37 | assert!(Type::Int(32).is_supported()); 38 | assert!(Type::Int(64).is_supported()); 39 | assert!(Type::Ptr.is_supported()); 40 | 41 | // Odd bit widths not supported 42 | assert!(!Type::Int(7).is_supported()); 43 | assert!(!Type::Int(33).is_supported()); 44 | } 45 | 46 | #[test] 47 | fn test_array_type() { 48 | let arr = Type::Array(10, Box::new(Type::Int(32))); 49 | assert_eq!(arr.size_in_bytes(), 40); // 10 * 4 bytes 50 | assert!(arr.is_supported()); 51 | assert!(!arr.is_scalar()); 52 | assert!(arr.is_aggregate()); 53 | } 54 | 55 | #[test] 56 | fn test_struct_type() { 57 | let fields = vec![Type::Int(32), Type::Int(64), Type::Ptr]; 58 | let struct_ty = Type::Struct(fields); 59 | 60 | assert_eq!(struct_ty.size_in_bytes(), 16); // 4 + 8 + 4 61 | assert!(struct_ty.is_supported()); 62 | assert!(!struct_ty.is_scalar()); 63 | assert!(struct_ty.is_aggregate()); 64 | } 65 | -------------------------------------------------------------------------------- /src/ir/module.rs: -------------------------------------------------------------------------------- 1 | //! LLVM Module representation 2 | 3 | use super::Function; 4 | use std::collections::HashMap; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct Module { 8 | /// Functions in this module 9 | functions: Vec, 10 | 11 | /// Global variables 12 | globals: HashMap, 13 | 14 | /// Module name 15 | name: String, 16 | } 17 | 18 | #[derive(Debug, Clone)] 19 | pub struct GlobalVariable { 20 | pub name: String, 21 | pub ty: super::Type, 22 | pub initializer: Option, 23 | pub is_constant: bool, 24 | } 25 | 26 | impl Module { 27 | pub fn new() -> Self { 28 | Self { 29 | functions: Vec::new(), 30 | globals: HashMap::new(), 31 | name: String::new(), 32 | } 33 | } 34 | 35 | pub fn with_name(name: impl Into) -> Self { 36 | Self { 37 | functions: Vec::new(), 38 | globals: HashMap::new(), 39 | name: name.into(), 40 | } 41 | } 42 | 43 | pub fn add_function(&mut self, func: Function) { 44 | self.functions.push(func); 45 | } 46 | 47 | pub fn add_global(&mut self, global: GlobalVariable) { 48 | self.globals.insert(global.name.clone(), global); 49 | } 50 | 51 | pub fn functions(&self) -> &[Function] { 52 | &self.functions 53 | } 54 | 55 | pub fn functions_mut(&mut self) -> &mut Vec { 56 | &mut self.functions 57 | } 58 | 59 | pub fn get_function(&self, name: &str) -> Option<&Function> { 60 | self.functions.iter().find(|f| f.name() == name) 61 | } 62 | 63 | pub fn get_function_mut(&mut self, name: &str) -> Option<&mut Function> { 64 | self.functions.iter_mut().find(|f| f.name() == name) 65 | } 66 | 67 | pub fn globals(&self) -> &HashMap { 68 | &self.globals 69 | } 70 | 71 | pub fn name(&self) -> &str { 72 | &self.name 73 | } 74 | 75 | pub fn set_name(&mut self, name: impl Into) { 76 | self.name = name.into(); 77 | } 78 | } 79 | 80 | impl Default for Module { 81 | fn default() -> Self { 82 | Self::new() 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/translate/types.rs: -------------------------------------------------------------------------------- 1 | //! Type lowering from LLVM to ZK IR 2 | 3 | use crate::ir::Type; 4 | 5 | /// ZK IR type representation 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 7 | pub enum ZkType { 8 | /// Single 32-bit register 9 | I32, 10 | 11 | /// Register pair (64-bit) 12 | I64, 13 | 14 | /// Register quad (128-bit) 15 | I128, 16 | 17 | /// Pointer (32-bit) 18 | Ptr, 19 | } 20 | 21 | impl ZkType { 22 | /// Number of registers needed 23 | pub fn num_regs(&self) -> usize { 24 | match self { 25 | ZkType::I32 | ZkType::Ptr => 1, 26 | ZkType::I64 => 2, 27 | ZkType::I128 => 4, 28 | } 29 | } 30 | 31 | /// Size in bytes 32 | pub fn size_bytes(&self) -> usize { 33 | match self { 34 | ZkType::I32 | ZkType::Ptr => 4, 35 | ZkType::I64 => 8, 36 | ZkType::I128 => 16, 37 | } 38 | } 39 | } 40 | 41 | /// Lower an LLVM type to ZK IR type 42 | pub fn lower_type(ty: &Type) -> Option { 43 | match ty { 44 | Type::Void => None, 45 | Type::Int(bits) => match bits { 46 | 1 | 8 | 16 | 32 => Some(ZkType::I32), 47 | 64 => Some(ZkType::I64), 48 | 128 => Some(ZkType::I128), 49 | _ => None, // Unsupported bit width 50 | }, 51 | Type::Ptr => Some(ZkType::Ptr), 52 | Type::Array(_, _) | Type::Struct(_) => Some(ZkType::Ptr), // Aggregates in memory 53 | Type::Function { .. } => Some(ZkType::Ptr), // Function pointers 54 | } 55 | } 56 | 57 | #[cfg(test)] 58 | mod tests { 59 | use super::*; 60 | 61 | #[test] 62 | fn test_lower_type() { 63 | assert_eq!(lower_type(&Type::Int(1)), Some(ZkType::I32)); 64 | assert_eq!(lower_type(&Type::Int(32)), Some(ZkType::I32)); 65 | assert_eq!(lower_type(&Type::Int(64)), Some(ZkType::I64)); 66 | assert_eq!(lower_type(&Type::Ptr), Some(ZkType::Ptr)); 67 | assert_eq!(lower_type(&Type::Void), None); 68 | } 69 | 70 | #[test] 71 | fn test_num_regs() { 72 | assert_eq!(ZkType::I32.num_regs(), 1); 73 | assert_eq!(ZkType::I64.num_regs(), 2); 74 | assert_eq!(ZkType::I128.num_regs(), 4); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zkir-llvm 2 | 3 | LLVM IR to ZK IR translator - enables any language with an LLVM frontend (Rust, C, C++, Go, Swift, etc.) to compile programs that can be proven in zero-knowledge. 4 | 5 | ## Overview 6 | 7 | This project creates a translator that converts LLVM IR (Intermediate Representation) into ZK IR bytecode. This enables zero-knowledge proof generation for programs written in any LLVM-supported language. 8 | 9 | **Dependency**: This project depends on `zkir-spec` from https://github.com/seceq/zkir 10 | 11 | ## Features 12 | 13 | - Parse LLVM IR text format (`.ll` files) 14 | - Translate LLVM instructions to ZK IR instructions 15 | - Handle memory layout and calling conventions 16 | - Optimize output for minimal constraint count 17 | - Support incremental/modular translation 18 | 19 | ## Installation 20 | 21 | ```bash 22 | # Clone the repository 23 | git clone https://github.com/seceq/zkir-llvm 24 | cd zkir-llvm 25 | 26 | # Build 27 | cargo build --release 28 | ``` 29 | 30 | ## Usage 31 | 32 | ### Translate LLVM IR to ZK IR 33 | 34 | ```bash 35 | # Basic usage 36 | zkir-llvm translate input.ll -o output.zkbc 37 | 38 | # With optimization 39 | zkir-llvm translate input.ll -o output.zkbc -O2 40 | 41 | # Verbose output 42 | zkir-llvm translate input.ll -o output.zkbc --verbose 43 | ``` 44 | 45 | ### Check LLVM IR validity 46 | 47 | ```bash 48 | zkir-llvm check input.ll 49 | ``` 50 | 51 | ### Dump parsed IR 52 | 53 | ```bash 54 | # Text format 55 | zkir-llvm dump input.ll 56 | 57 | # JSON format 58 | zkir-llvm dump input.ll --format json 59 | ``` 60 | 61 | ## LLVM IR to ZK IR Mapping 62 | 63 | ### Arithmetic Operations 64 | 65 | | LLVM IR | ZK IR | Notes | 66 | |---------|-------|-------| 67 | | `add i32 %a, %b` | `ADD dst, src1, src2` | Direct mapping | 68 | | `sub i32 %a, %b` | `SUB dst, src1, src2` | Direct mapping | 69 | | `mul i32 %a, %b` | `MUL dst, src1, src2` | Direct mapping | 70 | | `udiv i32 %a, %b` | `DIV dst, src1, src2` | Unsigned division | 71 | | `sdiv i32 %a, %b` | `SDIV dst, src1, src2` | Signed division | 72 | 73 | ### Control Flow 74 | 75 | | LLVM IR | ZK IR | Notes | 76 | |---------|-------|-------| 77 | | `br label %bb` | `JMP target` | Unconditional branch | 78 | | `br i1 %cond, label %t, label %f` | `BNE` + `JMP` | Conditional branch | 79 | | `ret i32 %v` | `MOV r1, src` + `RET` | Return value in r1 | 80 | | `call @func(...)` | `CALL target` | Args in r4-r7 | 81 | 82 | ## License 83 | 84 | MIT OR Apache-2.0 85 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //! CLI for zkir-llvm translator 2 | 3 | use clap::{Parser, Subcommand}; 4 | use std::path::PathBuf; 5 | use anyhow::Result; 6 | 7 | #[derive(Parser)] 8 | #[command(name = "zkir-llvm")] 9 | #[command(about = "LLVM IR to ZK IR translator")] 10 | #[command(version)] 11 | struct Cli { 12 | #[command(subcommand)] 13 | command: Commands, 14 | } 15 | 16 | #[derive(Subcommand)] 17 | enum Commands { 18 | /// Translate LLVM IR to ZK IR bytecode 19 | Translate { 20 | /// Input file (.ll or .bc) 21 | input: PathBuf, 22 | 23 | /// Output file (.zkbc) 24 | #[arg(short, long)] 25 | output: Option, 26 | 27 | /// Optimization level (0-3) 28 | #[arg(short = 'O', default_value = "1")] 29 | opt_level: u8, 30 | 31 | /// Verbose output 32 | #[arg(short, long)] 33 | verbose: bool, 34 | }, 35 | 36 | /// Check LLVM IR for ZK IR compatibility 37 | Check { 38 | /// Input file (.ll) 39 | input: PathBuf, 40 | }, 41 | 42 | /// Dump translated assembly (for debugging) 43 | Dump { 44 | /// Input file (.ll) 45 | input: PathBuf, 46 | }, 47 | } 48 | 49 | fn main() -> Result<()> { 50 | env_logger::init(); 51 | 52 | let cli = Cli::parse(); 53 | 54 | match cli.command { 55 | Commands::Translate { input, output, opt_level, verbose } => { 56 | let output = output.unwrap_or_else(|| input.with_extension("zkbc")); 57 | 58 | if verbose { 59 | println!("Reading LLVM IR from {:?}...", input); 60 | } 61 | 62 | let source = std::fs::read_to_string(&input)?; 63 | let program = zkir_llvm::translate_llvm_ir(&source, opt_level)?; 64 | 65 | if verbose { 66 | println!("Generated {} instructions", program.code.len()); 67 | } 68 | 69 | // Save bytecode 70 | use zkir_llvm::emit::emit_program; 71 | let bytecode = emit_program(&program)?; 72 | std::fs::write(&output, bytecode)?; 73 | 74 | println!("Wrote {:?}", output); 75 | } 76 | 77 | Commands::Check { input } => { 78 | let source = std::fs::read_to_string(&input)?; 79 | zkir_llvm::check_compatibility(&source)?; 80 | println!("Compatible with ZK IR"); 81 | } 82 | 83 | Commands::Dump { input } => { 84 | let source = std::fs::read_to_string(&input)?; 85 | let program = zkir_llvm::translate_llvm_ir(&source, 0)?; 86 | 87 | println!("ZK IR Assembly:\n"); 88 | for (i, instr) in program.code.iter().enumerate() { 89 | println!("{:04x}: {:?}", i * 4 + 0x1000, instr); 90 | } 91 | } 92 | } 93 | 94 | Ok(()) 95 | } 96 | -------------------------------------------------------------------------------- /tests/regalloc_tests.rs: -------------------------------------------------------------------------------- 1 | //! Register allocation tests 2 | 3 | use zkir_llvm::regalloc::{LinearScan, LiveInterval, VirtualReg}; 4 | 5 | #[test] 6 | fn test_linear_scan_basic() { 7 | let intervals = vec![ 8 | LiveInterval::new(VirtualReg(0), 0, 10), 9 | LiveInterval::new(VirtualReg(1), 5, 15), 10 | LiveInterval::new(VirtualReg(2), 12, 20), 11 | ]; 12 | 13 | let mut allocator = LinearScan::new(intervals); 14 | let result = allocator.allocate(); 15 | 16 | assert!(result.is_ok(), "Allocation should succeed"); 17 | 18 | // All virtual registers should get assignments 19 | assert!(allocator.get_assignment(VirtualReg(0)).is_some()); 20 | assert!(allocator.get_assignment(VirtualReg(1)).is_some()); 21 | assert!(allocator.get_assignment(VirtualReg(2)).is_some()); 22 | } 23 | 24 | #[test] 25 | fn test_live_interval_overlap() { 26 | let i1 = LiveInterval::new(VirtualReg(0), 0, 10); 27 | let i2 = LiveInterval::new(VirtualReg(1), 5, 15); 28 | let i3 = LiveInterval::new(VirtualReg(2), 20, 30); 29 | 30 | assert!(i1.overlaps(&i2), "i1 and i2 should overlap"); 31 | assert!(i2.overlaps(&i1), "i2 and i1 should overlap"); 32 | assert!(!i1.overlaps(&i3), "i1 and i3 should not overlap"); 33 | assert!(!i3.overlaps(&i1), "i3 and i1 should not overlap"); 34 | } 35 | 36 | #[test] 37 | fn test_non_overlapping_intervals() { 38 | let intervals = vec![ 39 | LiveInterval::new(VirtualReg(0), 0, 5), 40 | LiveInterval::new(VirtualReg(1), 6, 10), 41 | LiveInterval::new(VirtualReg(2), 11, 15), 42 | ]; 43 | 44 | let mut allocator = LinearScan::new(intervals); 45 | let result = allocator.allocate(); 46 | 47 | assert!(result.is_ok()); 48 | 49 | // Non-overlapping intervals can reuse registers 50 | let r0 = allocator.get_assignment(VirtualReg(0)).unwrap(); 51 | let r1 = allocator.get_assignment(VirtualReg(1)).unwrap(); 52 | let r2 = allocator.get_assignment(VirtualReg(2)).unwrap(); 53 | 54 | // They could potentially get the same physical register 55 | // (but that's implementation-dependent) 56 | assert!(r0 == r1 || r0 != r1); // Just checking they're valid 57 | } 58 | 59 | #[test] 60 | fn test_many_overlapping_intervals() { 61 | // Create many overlapping intervals to test spilling 62 | let mut intervals = Vec::new(); 63 | for i in 0..30 { 64 | intervals.push(LiveInterval::new(VirtualReg(i), 0, 100)); 65 | } 66 | 67 | let mut allocator = LinearScan::new(intervals); 68 | let result = allocator.allocate(); 69 | 70 | // With only 20 allocatable registers, some should be spilled 71 | assert!(result.is_ok()); 72 | 73 | let mut spilled_count = 0; 74 | for i in 0..30 { 75 | if allocator.is_spilled(VirtualReg(i)) { 76 | spilled_count += 1; 77 | assert!(allocator.get_spill_slot(VirtualReg(i)).is_some()); 78 | } 79 | } 80 | 81 | // At least some should be spilled 82 | assert!(spilled_count >= 10, "Expected at least 10 spills, got {}", spilled_count); 83 | } 84 | -------------------------------------------------------------------------------- /src/ir/types.rs: -------------------------------------------------------------------------------- 1 | //! LLVM type system 2 | 3 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 4 | pub enum Type { 5 | /// Void type 6 | Void, 7 | 8 | /// Integer type with bit width (e.g., i1, i8, i32, i64) 9 | Int(u32), 10 | 11 | /// Pointer type (opaque in LLVM 15+) 12 | Ptr, 13 | 14 | /// Array type [N x T] 15 | Array(usize, Box), 16 | 17 | /// Struct type { T1, T2, ... } 18 | Struct(Vec), 19 | 20 | /// Function type 21 | Function { 22 | ret: Box, 23 | params: Vec, 24 | varargs: bool, 25 | }, 26 | } 27 | 28 | impl Type { 29 | /// Get the bit width of this type (for scalar types) 30 | pub fn bit_width(&self) -> u32 { 31 | match self { 32 | Type::Int(bits) => *bits, 33 | Type::Ptr => 32, // 32-bit pointers in ZK IR 34 | _ => 0, 35 | } 36 | } 37 | 38 | /// Check if this is a scalar type 39 | pub fn is_scalar(&self) -> bool { 40 | matches!(self, Type::Int(_) | Type::Ptr) 41 | } 42 | 43 | /// Check if this is an aggregate type 44 | pub fn is_aggregate(&self) -> bool { 45 | matches!(self, Type::Array(_, _) | Type::Struct(_)) 46 | } 47 | 48 | /// Get the size in bytes 49 | pub fn size_in_bytes(&self) -> usize { 50 | match self { 51 | Type::Void => 0, 52 | Type::Int(bits) => ((*bits + 7) / 8) as usize, 53 | Type::Ptr => 4, // 32-bit pointers 54 | Type::Array(n, elem) => n * elem.size_in_bytes(), 55 | Type::Struct(fields) => fields.iter().map(|f| f.size_in_bytes()).sum(), 56 | Type::Function { .. } => std::mem::size_of::(), // Function pointers 57 | } 58 | } 59 | 60 | /// Check if this type is supported by ZK IR 61 | pub fn is_supported(&self) -> bool { 62 | match self { 63 | Type::Void => true, 64 | Type::Int(bits) => matches!(bits, 1 | 8 | 16 | 32 | 64 | 128), 65 | Type::Ptr => true, 66 | Type::Array(_, elem) => elem.is_supported(), 67 | Type::Struct(fields) => fields.iter().all(|f| f.is_supported()), 68 | Type::Function { .. } => true, // Function pointers are supported 69 | } 70 | } 71 | } 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | use super::*; 76 | 77 | #[test] 78 | fn test_bit_width() { 79 | assert_eq!(Type::Int(32).bit_width(), 32); 80 | assert_eq!(Type::Int(64).bit_width(), 64); 81 | assert_eq!(Type::Ptr.bit_width(), 32); 82 | } 83 | 84 | #[test] 85 | fn test_size_in_bytes() { 86 | assert_eq!(Type::Int(32).size_in_bytes(), 4); 87 | assert_eq!(Type::Int(64).size_in_bytes(), 8); 88 | assert_eq!(Type::Ptr.size_in_bytes(), 4); 89 | assert_eq!( 90 | Type::Array(10, Box::new(Type::Int(32))).size_in_bytes(), 91 | 40 92 | ); 93 | } 94 | 95 | #[test] 96 | fn test_is_supported() { 97 | assert!(Type::Int(32).is_supported()); 98 | assert!(Type::Int(64).is_supported()); 99 | assert!(Type::Ptr.is_supported()); 100 | assert!(!Type::Int(7).is_supported()); // Odd bit widths not supported 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /tests/parser_tests.rs: -------------------------------------------------------------------------------- 1 | //! Parser unit tests 2 | 3 | use zkir_llvm::parser; 4 | use zkir_llvm::ir::Type; 5 | 6 | #[test] 7 | fn test_parse_simple_function() { 8 | let source = r#" 9 | define i32 @add(i32 %a, i32 %b) { 10 | entry: 11 | %result = add i32 %a, %b 12 | ret i32 %result 13 | } 14 | "#; 15 | 16 | let result = parser::parse(source); 17 | assert!(result.is_ok(), "Failed to parse: {:?}", result.err()); 18 | 19 | let module = result.unwrap(); 20 | assert_eq!(module.functions().len(), 1); 21 | 22 | let func = &module.functions()[0]; 23 | assert_eq!(func.name(), "add"); 24 | assert_eq!(func.ret_ty(), &Type::Int(32)); 25 | assert_eq!(func.params().len(), 2); 26 | } 27 | 28 | #[test] 29 | fn test_parse_void_function() { 30 | let source = r#" 31 | define void @noop() { 32 | entry: 33 | ret void 34 | } 35 | "#; 36 | 37 | let result = parser::parse(source); 38 | assert!(result.is_ok()); 39 | 40 | let module = result.unwrap(); 41 | let func = &module.functions()[0]; 42 | assert_eq!(func.ret_ty(), &Type::Void); 43 | } 44 | 45 | #[test] 46 | fn test_parse_multiple_functions() { 47 | let source = r#" 48 | define i32 @add(i32 %a, i32 %b) { 49 | entry: 50 | %result = add i32 %a, %b 51 | ret i32 %result 52 | } 53 | 54 | define i32 @sub(i32 %a, i32 %b) { 55 | entry: 56 | %result = sub i32 %a, %b 57 | ret i32 %result 58 | } 59 | "#; 60 | 61 | let result = parser::parse(source); 62 | assert!(result.is_ok()); 63 | 64 | let module = result.unwrap(); 65 | assert_eq!(module.functions().len(), 2); 66 | assert_eq!(module.functions()[0].name(), "add"); 67 | assert_eq!(module.functions()[1].name(), "sub"); 68 | } 69 | 70 | #[test] 71 | fn test_parse_types() { 72 | let source = r#" 73 | define i64 @test(i1 %a, i8 %b, i16 %c, i32 %d, i64 %e, ptr %p) { 74 | entry: 75 | ret i64 0 76 | } 77 | "#; 78 | 79 | let result = parser::parse(source); 80 | assert!(result.is_ok()); 81 | 82 | let module = result.unwrap(); 83 | let func = &module.functions()[0]; 84 | 85 | assert_eq!(func.params().len(), 6); 86 | assert_eq!(func.params()[0].1, Type::Int(1)); 87 | assert_eq!(func.params()[1].1, Type::Int(8)); 88 | assert_eq!(func.params()[2].1, Type::Int(16)); 89 | assert_eq!(func.params()[3].1, Type::Int(32)); 90 | assert_eq!(func.params()[4].1, Type::Int(64)); 91 | assert_eq!(func.params()[5].1, Type::Ptr); 92 | } 93 | 94 | #[test] 95 | fn test_parse_empty_module() { 96 | let source = ""; 97 | let result = parser::parse(source); 98 | assert!(result.is_ok()); 99 | 100 | let module = result.unwrap(); 101 | assert_eq!(module.functions().len(), 0); 102 | } 103 | 104 | #[test] 105 | fn test_parse_with_comments() { 106 | let source = r#" 107 | ; This is a comment 108 | define i32 @add(i32 %a, i32 %b) { ; inline comment 109 | entry: 110 | ; Another comment 111 | %result = add i32 %a, %b 112 | ret i32 %result 113 | } 114 | "#; 115 | 116 | let result = parser::parse(source); 117 | assert!(result.is_ok()); 118 | } 119 | -------------------------------------------------------------------------------- /tests/instruction_tests.rs: -------------------------------------------------------------------------------- 1 | //! Instruction tests 2 | 3 | use zkir_llvm::ir::{instruction::*, Type, Value}; 4 | 5 | #[test] 6 | fn test_instruction_is_terminator() { 7 | let ret = Instruction::Ret { 8 | value: Some(Value::const_i32(0)), 9 | }; 10 | assert!(ret.is_terminator()); 11 | 12 | let br = Instruction::Br { 13 | dest: "label".to_string(), 14 | }; 15 | assert!(br.is_terminator()); 16 | 17 | let cond_br = Instruction::CondBr { 18 | cond: Value::ConstBool(true), 19 | true_dest: "true_label".to_string(), 20 | false_dest: "false_label".to_string(), 21 | }; 22 | assert!(cond_br.is_terminator()); 23 | 24 | let add = Instruction::Add { 25 | result: "x".to_string(), 26 | ty: Type::Int(32), 27 | lhs: Value::const_i32(1), 28 | rhs: Value::const_i32(2), 29 | }; 30 | assert!(!add.is_terminator()); 31 | } 32 | 33 | #[test] 34 | fn test_instruction_result() { 35 | let add = Instruction::Add { 36 | result: "sum".to_string(), 37 | ty: Type::Int(32), 38 | lhs: Value::const_i32(1), 39 | rhs: Value::const_i32(2), 40 | }; 41 | assert_eq!(add.result(), Some("sum")); 42 | 43 | let ret = Instruction::Ret { 44 | value: Some(Value::const_i32(0)), 45 | }; 46 | assert_eq!(ret.result(), None); 47 | 48 | let store = Instruction::Store { 49 | value: Value::const_i32(42), 50 | ty: Type::Int(32), 51 | ptr: Value::local("ptr"), 52 | }; 53 | assert_eq!(store.result(), None); 54 | } 55 | 56 | #[test] 57 | fn test_arithmetic_instructions() { 58 | let instructions = vec![ 59 | Instruction::Add { 60 | result: "r1".to_string(), 61 | ty: Type::Int(32), 62 | lhs: Value::local("a"), 63 | rhs: Value::local("b"), 64 | }, 65 | Instruction::Sub { 66 | result: "r2".to_string(), 67 | ty: Type::Int(32), 68 | lhs: Value::local("a"), 69 | rhs: Value::local("b"), 70 | }, 71 | Instruction::Mul { 72 | result: "r3".to_string(), 73 | ty: Type::Int(32), 74 | lhs: Value::local("a"), 75 | rhs: Value::local("b"), 76 | }, 77 | ]; 78 | 79 | assert_eq!(instructions[0].result(), Some("r1")); 80 | assert_eq!(instructions[1].result(), Some("r2")); 81 | assert_eq!(instructions[2].result(), Some("r3")); 82 | } 83 | 84 | #[test] 85 | fn test_icmp_predicates() { 86 | use ICmpPredicate::*; 87 | 88 | let predicates = vec![Eq, Ne, Slt, Sle, Sgt, Sge, Ult, Ule, Ugt, Uge]; 89 | assert_eq!(predicates.len(), 10); 90 | } 91 | 92 | #[test] 93 | fn test_memory_instructions() { 94 | let load = Instruction::Load { 95 | result: "val".to_string(), 96 | ty: Type::Int(32), 97 | ptr: Value::local("ptr"), 98 | }; 99 | assert_eq!(load.result(), Some("val")); 100 | 101 | let store = Instruction::Store { 102 | value: Value::const_i32(42), 103 | ty: Type::Int(32), 104 | ptr: Value::local("ptr"), 105 | }; 106 | assert_eq!(store.result(), None); 107 | 108 | let alloca = Instruction::Alloca { 109 | result: "local_ptr".to_string(), 110 | ty: Type::Int(32), 111 | }; 112 | assert_eq!(alloca.result(), Some("local_ptr")); 113 | } 114 | -------------------------------------------------------------------------------- /tests/integration.rs: -------------------------------------------------------------------------------- 1 | //! Integration tests - end-to-end translation 2 | 3 | use zkir_llvm::{parser, translate}; 4 | 5 | #[test] 6 | fn test_translate_simple_add() { 7 | let source = r#" 8 | define i32 @add(i32 %a, i32 %b) { 9 | entry: 10 | %result = add i32 %a, %b 11 | ret i32 %result 12 | } 13 | "#; 14 | 15 | // Parse 16 | let module = parser::parse(source).expect("Failed to parse"); 17 | assert_eq!(module.functions().len(), 1); 18 | 19 | // Translate 20 | let program = translate::translate_module(&module, 0); 21 | 22 | // Should succeed (even if incomplete) 23 | // Note: This will fail until we implement full instruction parsing 24 | // assert!(program.is_ok(), "Translation failed: {:?}", program.err()); 25 | } 26 | 27 | #[test] 28 | fn test_translate_empty_module() { 29 | let source = ""; 30 | 31 | let module = parser::parse(source).expect("Failed to parse"); 32 | let program = translate::translate_module(&module, 0).expect("Failed to translate"); 33 | 34 | // Empty module should produce empty program 35 | assert_eq!(program.code.len(), 0); 36 | } 37 | 38 | #[test] 39 | fn test_check_compatibility() { 40 | let source = r#" 41 | define i32 @test(i32 %a) { 42 | entry: 43 | ret i32 %a 44 | } 45 | "#; 46 | 47 | let module = parser::parse(source).expect("Failed to parse"); 48 | let result = translate::check_module_compatibility(&module); 49 | 50 | assert!(result.is_ok(), "Compatibility check failed: {:?}", result.err()); 51 | } 52 | 53 | #[test] 54 | fn test_unsupported_type() { 55 | // This test checks that we properly detect unsupported types 56 | // Float types are not supported 57 | let source = r#" 58 | define float @test(float %a) { 59 | entry: 60 | ret float %a 61 | } 62 | "#; 63 | 64 | // This should fail during parsing (since we don't have float token) 65 | // or during compatibility check 66 | let result = parser::parse(source); 67 | 68 | // Parser might fail or succeed depending on implementation 69 | // If it succeeds, compatibility check should fail 70 | if let Ok(module) = result { 71 | let _compat = translate::check_module_compatibility(&module); 72 | // Should either fail or we haven't implemented the check yet 73 | } 74 | } 75 | 76 | #[test] 77 | #[ignore] // Ignore until full parser is implemented 78 | fn test_translate_arithmetic() { 79 | let source = r#" 80 | define i32 @arithmetic(i32 %a, i32 %b) { 81 | entry: 82 | %sum = add i32 %a, %b 83 | %diff = sub i32 %sum, %b 84 | %prod = mul i32 %diff, 2 85 | ret i32 %prod 86 | } 87 | "#; 88 | 89 | let module = parser::parse(source).expect("Failed to parse"); 90 | let program = translate::translate_module(&module, 0).expect("Failed to translate"); 91 | 92 | // Should have generated some instructions 93 | assert!(program.code.len() > 0); 94 | } 95 | 96 | #[test] 97 | #[ignore] // Ignore until full parser is implemented 98 | fn test_translate_control_flow() { 99 | let source = r#" 100 | define i32 @max(i32 %a, i32 %b) { 101 | entry: 102 | %cmp = icmp sgt i32 %a, %b 103 | br i1 %cmp, label %return_a, label %return_b 104 | 105 | return_a: 106 | ret i32 %a 107 | 108 | return_b: 109 | ret i32 %b 110 | } 111 | "#; 112 | 113 | let module = parser::parse(source).expect("Failed to parse"); 114 | let program = translate::translate_module(&module, 0).expect("Failed to translate"); 115 | 116 | assert!(program.code.len() > 0); 117 | } 118 | -------------------------------------------------------------------------------- /src/translate/memory.rs: -------------------------------------------------------------------------------- 1 | //! Memory instruction translation 2 | 3 | use super::{context::TranslationContext, TranslateResult}; 4 | use crate::ir::{Type, Value}; 5 | use zkir_spec::{Instruction, Register}; 6 | 7 | /// Translate load instruction 8 | pub fn translate_load( 9 | ctx: &mut TranslationContext, 10 | result: &str, 11 | ty: &Type, 12 | ptr: &Value, 13 | ) -> TranslateResult<()> { 14 | let ptr_reg = ctx.load_value(ptr)?; 15 | let bits = ty.bit_width(); 16 | 17 | match bits { 18 | 8 => { 19 | let rd = ctx.alloc_temp()?; 20 | ctx.emit(Instruction::Lbu { 21 | rd, 22 | rs1: ptr_reg, 23 | imm: 0, 24 | }); 25 | ctx.bind(result, super::context::Location::Reg(rd)); 26 | } 27 | 16 => { 28 | let rd = ctx.alloc_temp()?; 29 | ctx.emit(Instruction::Lhu { 30 | rd, 31 | rs1: ptr_reg, 32 | imm: 0, 33 | }); 34 | ctx.bind(result, super::context::Location::Reg(rd)); 35 | } 36 | 32 => { 37 | let rd = ctx.alloc_temp()?; 38 | ctx.emit(Instruction::Lw { 39 | rd, 40 | rs1: ptr_reg, 41 | imm: 0, 42 | }); 43 | ctx.bind(result, super::context::Location::Reg(rd)); 44 | } 45 | 64 => { 46 | let (rd_lo, rd_hi) = ctx.alloc_reg_pair()?; 47 | ctx.emit(Instruction::Lw { 48 | rd: rd_lo, 49 | rs1: ptr_reg, 50 | imm: 0, 51 | }); 52 | ctx.emit(Instruction::Lw { 53 | rd: rd_hi, 54 | rs1: ptr_reg, 55 | imm: 4, 56 | }); 57 | ctx.bind( 58 | result, 59 | super::context::Location::RegPair { 60 | lo: rd_lo, 61 | hi: rd_hi, 62 | }, 63 | ); 64 | } 65 | _ => { 66 | return Err(super::TranslateError::UnsupportedWidth(bits)); 67 | } 68 | } 69 | 70 | Ok(()) 71 | } 72 | 73 | /// Translate store instruction 74 | pub fn translate_store( 75 | ctx: &mut TranslationContext, 76 | value: &Value, 77 | ty: &Type, 78 | ptr: &Value, 79 | ) -> TranslateResult<()> { 80 | let ptr_reg = ctx.load_value(ptr)?; 81 | let bits = ty.bit_width(); 82 | 83 | match bits { 84 | 8 => { 85 | let val_reg = ctx.load_value(value)?; 86 | ctx.emit(Instruction::Sb { 87 | rs1: ptr_reg, 88 | rs2: val_reg, 89 | imm: 0, 90 | }); 91 | } 92 | 16 => { 93 | let val_reg = ctx.load_value(value)?; 94 | ctx.emit(Instruction::Sh { 95 | rs1: ptr_reg, 96 | rs2: val_reg, 97 | imm: 0, 98 | }); 99 | } 100 | 32 => { 101 | let val_reg = ctx.load_value(value)?; 102 | ctx.emit(Instruction::Sw { 103 | rs1: ptr_reg, 104 | rs2: val_reg, 105 | imm: 0, 106 | }); 107 | } 108 | 64 => { 109 | let (val_lo, val_hi) = ctx.load_value_pair(value)?; 110 | ctx.emit(Instruction::Sw { 111 | rs1: ptr_reg, 112 | rs2: val_lo, 113 | imm: 0, 114 | }); 115 | ctx.emit(Instruction::Sw { 116 | rs1: ptr_reg, 117 | rs2: val_hi, 118 | imm: 4, 119 | }); 120 | } 121 | _ => { 122 | return Err(super::TranslateError::UnsupportedWidth(bits)); 123 | } 124 | } 125 | 126 | Ok(()) 127 | } 128 | 129 | /// Translate alloca instruction 130 | pub fn translate_alloca( 131 | ctx: &mut TranslationContext, 132 | result: &str, 133 | ty: &Type, 134 | ) -> TranslateResult<()> { 135 | // Allocate stack space 136 | let size = ty.size_in_bytes() as i32; 137 | let offset = ctx.alloc_stack(size); 138 | 139 | // Compute address: fp + offset 140 | let rd = ctx.alloc_temp()?; 141 | ctx.emit(Instruction::Addi { 142 | rd, 143 | rs1: Register::FP, 144 | imm: offset as i16, 145 | }); 146 | 147 | ctx.bind(result, super::context::Location::Reg(rd)); 148 | 149 | Ok(()) 150 | } 151 | -------------------------------------------------------------------------------- /src/ir/instruction.rs: -------------------------------------------------------------------------------- 1 | //! LLVM instruction representation 2 | 3 | use super::{Type, Value}; 4 | 5 | #[derive(Debug, Clone, PartialEq)] 6 | pub enum Instruction { 7 | // Arithmetic 8 | Add { 9 | result: String, 10 | ty: Type, 11 | lhs: Value, 12 | rhs: Value, 13 | }, 14 | Sub { 15 | result: String, 16 | ty: Type, 17 | lhs: Value, 18 | rhs: Value, 19 | }, 20 | Mul { 21 | result: String, 22 | ty: Type, 23 | lhs: Value, 24 | rhs: Value, 25 | }, 26 | UDiv { 27 | result: String, 28 | ty: Type, 29 | lhs: Value, 30 | rhs: Value, 31 | }, 32 | SDiv { 33 | result: String, 34 | ty: Type, 35 | lhs: Value, 36 | rhs: Value, 37 | }, 38 | URem { 39 | result: String, 40 | ty: Type, 41 | lhs: Value, 42 | rhs: Value, 43 | }, 44 | SRem { 45 | result: String, 46 | ty: Type, 47 | lhs: Value, 48 | rhs: Value, 49 | }, 50 | 51 | // Bitwise 52 | And { 53 | result: String, 54 | ty: Type, 55 | lhs: Value, 56 | rhs: Value, 57 | }, 58 | Or { 59 | result: String, 60 | ty: Type, 61 | lhs: Value, 62 | rhs: Value, 63 | }, 64 | Xor { 65 | result: String, 66 | ty: Type, 67 | lhs: Value, 68 | rhs: Value, 69 | }, 70 | Shl { 71 | result: String, 72 | ty: Type, 73 | lhs: Value, 74 | rhs: Value, 75 | }, 76 | LShr { 77 | result: String, 78 | ty: Type, 79 | lhs: Value, 80 | rhs: Value, 81 | }, 82 | AShr { 83 | result: String, 84 | ty: Type, 85 | lhs: Value, 86 | rhs: Value, 87 | }, 88 | 89 | // Comparison 90 | ICmp { 91 | result: String, 92 | pred: ICmpPredicate, 93 | ty: Type, 94 | lhs: Value, 95 | rhs: Value, 96 | }, 97 | 98 | // Memory 99 | Load { 100 | result: String, 101 | ty: Type, 102 | ptr: Value, 103 | }, 104 | Store { 105 | value: Value, 106 | ty: Type, 107 | ptr: Value, 108 | }, 109 | Alloca { 110 | result: String, 111 | ty: Type, 112 | }, 113 | 114 | // Control flow 115 | Ret { 116 | value: Option, 117 | }, 118 | Br { 119 | dest: String, 120 | }, 121 | CondBr { 122 | cond: Value, 123 | true_dest: String, 124 | false_dest: String, 125 | }, 126 | Call { 127 | result: Option, 128 | callee: String, 129 | args: Vec, 130 | ret_ty: Type, 131 | }, 132 | Phi { 133 | result: String, 134 | ty: Type, 135 | incoming: Vec<(Value, String)>, // (value, block_name) 136 | }, 137 | 138 | // Other 139 | GetElementPtr { 140 | result: String, 141 | ty: Type, 142 | ptr: Value, 143 | indices: Vec, 144 | }, 145 | } 146 | 147 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 148 | pub enum ICmpPredicate { 149 | Eq, // Equal 150 | Ne, // Not equal 151 | Slt, // Signed less than 152 | Sle, // Signed less or equal 153 | Sgt, // Signed greater than 154 | Sge, // Signed greater or equal 155 | Ult, // Unsigned less than 156 | Ule, // Unsigned less or equal 157 | Ugt, // Unsigned greater than 158 | Uge, // Unsigned greater or equal 159 | } 160 | 161 | impl Instruction { 162 | /// Check if this is a terminator instruction 163 | pub fn is_terminator(&self) -> bool { 164 | matches!(self, Instruction::Ret { .. } | Instruction::Br { .. } | Instruction::CondBr { .. }) 165 | } 166 | 167 | /// Get the result variable name (if any) 168 | pub fn result(&self) -> Option<&str> { 169 | match self { 170 | Instruction::Add { result, .. } 171 | | Instruction::Sub { result, .. } 172 | | Instruction::Mul { result, .. } 173 | | Instruction::UDiv { result, .. } 174 | | Instruction::SDiv { result, .. } 175 | | Instruction::URem { result, .. } 176 | | Instruction::SRem { result, .. } 177 | | Instruction::And { result, .. } 178 | | Instruction::Or { result, .. } 179 | | Instruction::Xor { result, .. } 180 | | Instruction::Shl { result, .. } 181 | | Instruction::LShr { result, .. } 182 | | Instruction::AShr { result, .. } 183 | | Instruction::ICmp { result, .. } 184 | | Instruction::Load { result, .. } 185 | | Instruction::Alloca { result, .. } 186 | | Instruction::Phi { result, .. } 187 | | Instruction::GetElementPtr { result, .. } => Some(result), 188 | Instruction::Call { result, .. } => result.as_deref(), 189 | _ => None, 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/parser/lexer.rs: -------------------------------------------------------------------------------- 1 | //! LLVM IR lexer using logos 2 | 3 | use logos::Logos; 4 | 5 | #[derive(Logos, Debug, Clone, PartialEq)] 6 | #[logos(skip r"[ \t\n\f]+")] // Skip whitespace 7 | #[logos(skip r";[^\n]*")] // Skip comments 8 | pub enum Token { 9 | // Keywords 10 | #[token("define")] 11 | Define, 12 | 13 | #[token("declare")] 14 | Declare, 15 | 16 | #[token("ret")] 17 | Ret, 18 | 19 | #[token("br")] 20 | Br, 21 | 22 | #[token("switch")] 23 | Switch, 24 | 25 | #[token("call")] 26 | Call, 27 | 28 | #[token("add")] 29 | Add, 30 | 31 | #[token("sub")] 32 | Sub, 33 | 34 | #[token("mul")] 35 | Mul, 36 | 37 | #[token("udiv")] 38 | UDiv, 39 | 40 | #[token("sdiv")] 41 | SDiv, 42 | 43 | #[token("urem")] 44 | URem, 45 | 46 | #[token("srem")] 47 | SRem, 48 | 49 | #[token("and")] 50 | And, 51 | 52 | #[token("or")] 53 | Or, 54 | 55 | #[token("xor")] 56 | Xor, 57 | 58 | #[token("shl")] 59 | Shl, 60 | 61 | #[token("lshr")] 62 | LShr, 63 | 64 | #[token("ashr")] 65 | AShr, 66 | 67 | #[token("icmp")] 68 | ICmp, 69 | 70 | #[token("load")] 71 | Load, 72 | 73 | #[token("store")] 74 | Store, 75 | 76 | #[token("alloca")] 77 | Alloca, 78 | 79 | #[token("getelementptr")] 80 | GetElementPtr, 81 | 82 | #[token("phi")] 83 | Phi, 84 | 85 | #[token("label")] 86 | Label, 87 | 88 | #[token("to")] 89 | To, 90 | 91 | // Comparison predicates 92 | #[token("eq")] 93 | Eq, 94 | 95 | #[token("ne")] 96 | Ne, 97 | 98 | #[token("slt")] 99 | Slt, 100 | 101 | #[token("sle")] 102 | Sle, 103 | 104 | #[token("sgt")] 105 | Sgt, 106 | 107 | #[token("sge")] 108 | Sge, 109 | 110 | #[token("ult")] 111 | Ult, 112 | 113 | #[token("ule")] 114 | Ule, 115 | 116 | #[token("ugt")] 117 | Ugt, 118 | 119 | #[token("uge")] 120 | Uge, 121 | 122 | // Types 123 | #[token("void")] 124 | Void, 125 | 126 | #[regex(r"i[0-9]+", |lex| lex.slice()[1..].parse().ok())] 127 | IntType(u32), 128 | 129 | #[token("ptr")] 130 | Ptr, 131 | 132 | // Literals 133 | #[regex(r"-?[0-9]+", |lex| lex.slice().parse().ok())] 134 | Integer(i64), 135 | 136 | #[token("true")] 137 | True, 138 | 139 | #[token("false")] 140 | False, 141 | 142 | // Identifiers 143 | #[regex(r"@[a-zA-Z_][a-zA-Z0-9_]*", |lex| lex.slice().to_string())] 144 | GlobalIdent(String), 145 | 146 | #[regex(r"%[a-zA-Z_][a-zA-Z0-9_.]*", |lex| lex.slice().to_string())] 147 | LocalIdent(String), 148 | 149 | #[regex(r"%[0-9]+", |lex| lex.slice().to_string())] 150 | NumericIdent(String), 151 | 152 | // Punctuation 153 | #[token("(")] 154 | LParen, 155 | 156 | #[token(")")] 157 | RParen, 158 | 159 | #[token("{")] 160 | LBrace, 161 | 162 | #[token("}")] 163 | RBrace, 164 | 165 | #[token("[")] 166 | LBracket, 167 | 168 | #[token("]")] 169 | RBracket, 170 | 171 | #[token("<")] 172 | LAngle, 173 | 174 | #[token(">")] 175 | RAngle, 176 | 177 | #[token(",")] 178 | Comma, 179 | 180 | #[token("=")] 181 | Equals, 182 | 183 | #[token("*")] 184 | Star, 185 | 186 | #[token(":")] 187 | Colon, 188 | 189 | #[token("...")] 190 | Ellipsis, 191 | 192 | // String literals 193 | #[regex(r#""([^"\\]|\\.)*""#, |lex| lex.slice().to_string())] 194 | String(String), 195 | } 196 | 197 | pub type Spanned = Result<(Loc, Tok, Loc), Error>; 198 | 199 | pub struct Lexer<'input> { 200 | token_stream: logos::Lexer<'input, Token>, 201 | position: usize, 202 | } 203 | 204 | impl<'input> Lexer<'input> { 205 | pub fn new(input: &'input str) -> Self { 206 | Self { 207 | token_stream: Token::lexer(input), 208 | position: 0, 209 | } 210 | } 211 | } 212 | 213 | impl<'input> Iterator for Lexer<'input> { 214 | type Item = Spanned; 215 | 216 | fn next(&mut self) -> Option { 217 | let token = self.token_stream.next()?; 218 | let span = self.token_stream.span(); 219 | 220 | match token { 221 | Ok(tok) => { 222 | let result = Ok((span.start, tok, span.end)); 223 | self.position = span.end; 224 | Some(result) 225 | } 226 | Err(_) => Some(Err(format!( 227 | "Unexpected character at position {}", 228 | span.start 229 | ))), 230 | } 231 | } 232 | } 233 | 234 | #[cfg(test)] 235 | mod tests { 236 | use super::*; 237 | 238 | #[test] 239 | fn test_lexer_basic() { 240 | let input = "define i32 @main() { ret i32 0 }"; 241 | let mut lexer = Lexer::new(input); 242 | 243 | assert!(matches!(lexer.next(), Some(Ok((_, Token::Define, _))))); 244 | assert!(matches!(lexer.next(), Some(Ok((_, Token::IntType(32), _))))); 245 | assert!(matches!( 246 | lexer.next(), 247 | Some(Ok((_, Token::GlobalIdent(_), _))) 248 | )); 249 | } 250 | 251 | #[test] 252 | fn test_lexer_identifiers() { 253 | let input = "@global %local %123"; 254 | let mut lexer = Lexer::new(input); 255 | 256 | if let Some(Ok((_, Token::GlobalIdent(s), _))) = lexer.next() { 257 | assert_eq!(s, "@global"); 258 | } 259 | 260 | if let Some(Ok((_, Token::LocalIdent(s), _))) = lexer.next() { 261 | assert_eq!(s, "%local"); 262 | } 263 | 264 | if let Some(Ok((_, Token::NumericIdent(s), _))) = lexer.next() { 265 | assert_eq!(s, "%123"); 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/regalloc/linear.rs: -------------------------------------------------------------------------------- 1 | //! Linear scan register allocation 2 | 3 | use super::{LiveInterval, PhysicalReg, RegAllocError, RegAllocResult, VirtualReg}; 4 | use std::collections::HashMap; 5 | use zkir_spec::Register; 6 | 7 | pub struct LinearScan { 8 | /// Live intervals for each virtual register 9 | intervals: Vec, 10 | 11 | /// Physical register assignments 12 | assignments: HashMap, 13 | 14 | /// Spill slots for spilled registers 15 | spill_slots: HashMap, 16 | 17 | /// Next spill slot offset 18 | next_spill: i32, 19 | } 20 | 21 | impl LinearScan { 22 | pub fn new(intervals: Vec) -> Self { 23 | Self { 24 | intervals, 25 | assignments: HashMap::new(), 26 | spill_slots: HashMap::new(), 27 | next_spill: 0, 28 | } 29 | } 30 | 31 | /// Run linear scan allocation 32 | pub fn allocate(&mut self) -> RegAllocResult<()> { 33 | // Sort intervals by start point 34 | self.intervals.sort_by_key(|i| i.start); 35 | 36 | let mut active: Vec = Vec::new(); 37 | let mut free_regs = self.get_allocatable_regs(); 38 | 39 | for interval in self.intervals.clone() { 40 | // Expire old intervals 41 | self.expire_old(&mut active, &mut free_regs, interval.start); 42 | 43 | if free_regs.is_empty() { 44 | // Need to spill 45 | self.spill_at_interval(&mut active, &interval)?; 46 | } else { 47 | // Allocate register 48 | let reg = free_regs.pop().unwrap(); 49 | self.assignments.insert(interval.vreg, reg); 50 | active.push(interval.clone()); 51 | active.sort_by_key(|i| i.end); 52 | } 53 | } 54 | 55 | Ok(()) 56 | } 57 | 58 | /// Get allocatable physical registers 59 | fn get_allocatable_regs(&self) -> Vec { 60 | let mut regs = Vec::new(); 61 | 62 | // t0-t7 (r8-r15) 63 | for i in 8..16 { 64 | regs.push(Register::from_index(i).unwrap()); 65 | } 66 | 67 | // s0-s7 (r16-r23) 68 | for i in 16..24 { 69 | regs.push(Register::from_index(i).unwrap()); 70 | } 71 | 72 | // t8-t11 (r24-r27) 73 | for i in 24..28 { 74 | regs.push(Register::from_index(i).unwrap()); 75 | } 76 | 77 | regs 78 | } 79 | 80 | /// Expire old intervals and free their registers 81 | fn expire_old( 82 | &mut self, 83 | active: &mut Vec, 84 | free_regs: &mut Vec, 85 | current: u32, 86 | ) { 87 | active.retain(|interval| { 88 | if interval.end <= current { 89 | // This interval is done, free its register 90 | if let Some(reg) = self.assignments.get(&interval.vreg) { 91 | free_regs.push(*reg); 92 | } 93 | false // Remove from active 94 | } else { 95 | true // Keep in active 96 | } 97 | }); 98 | } 99 | 100 | /// Spill at interval 101 | fn spill_at_interval( 102 | &mut self, 103 | active: &mut [LiveInterval], 104 | interval: &LiveInterval, 105 | ) -> RegAllocResult<()> { 106 | // Find interval with latest end point 107 | let spill = active 108 | .iter() 109 | .max_by_key(|i| i.end) 110 | .ok_or(RegAllocError::OutOfRegisters)?; 111 | 112 | if spill.end > interval.end { 113 | // Spill the interval with the latest end 114 | let reg = self 115 | .assignments 116 | .get(&spill.vreg) 117 | .ok_or(RegAllocError::OutOfRegisters)?; 118 | 119 | // Assign register to current interval 120 | self.assignments.insert(interval.vreg, *reg); 121 | 122 | // Allocate spill slot for spilled interval 123 | let slot = self.next_spill; 124 | self.next_spill -= 4; 125 | self.spill_slots.insert(spill.vreg, slot); 126 | 127 | // Update active list 128 | // Remove spill, add current 129 | // (This is simplified - real implementation would need proper updating) 130 | } else { 131 | // Spill current interval 132 | let slot = self.next_spill; 133 | self.next_spill -= 4; 134 | self.spill_slots.insert(interval.vreg, slot); 135 | } 136 | 137 | Ok(()) 138 | } 139 | 140 | /// Get the assignment for a virtual register 141 | pub fn get_assignment(&self, vreg: VirtualReg) -> Option { 142 | self.assignments.get(&vreg).copied() 143 | } 144 | 145 | /// Check if a virtual register was spilled 146 | pub fn is_spilled(&self, vreg: VirtualReg) -> bool { 147 | self.spill_slots.contains_key(&vreg) 148 | } 149 | 150 | /// Get spill slot for a virtual register 151 | pub fn get_spill_slot(&self, vreg: VirtualReg) -> Option { 152 | self.spill_slots.get(&vreg).copied() 153 | } 154 | } 155 | 156 | #[cfg(test)] 157 | mod tests { 158 | use super::*; 159 | 160 | #[test] 161 | fn test_linear_scan() { 162 | let intervals = vec![ 163 | LiveInterval::new(VirtualReg(0), 0, 10), 164 | LiveInterval::new(VirtualReg(1), 5, 15), 165 | LiveInterval::new(VirtualReg(2), 12, 20), 166 | ]; 167 | 168 | let mut allocator = LinearScan::new(intervals); 169 | allocator.allocate().unwrap(); 170 | 171 | assert!(allocator.get_assignment(VirtualReg(0)).is_some()); 172 | assert!(allocator.get_assignment(VirtualReg(1)).is_some()); 173 | assert!(allocator.get_assignment(VirtualReg(2)).is_some()); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/parser/parser.rs: -------------------------------------------------------------------------------- 1 | //! LLVM IR parser implementation 2 | 3 | use super::{ParseError, ParseResult}; 4 | use crate::ir::*; 5 | use super::lexer::{Lexer, Token}; 6 | 7 | pub struct Parser<'input> { 8 | lexer: std::iter::Peekable>, 9 | current_line: usize, 10 | } 11 | 12 | impl<'input> Parser<'input> { 13 | pub fn new(input: &'input str) -> Self { 14 | Self { 15 | lexer: Lexer::new(input).peekable(), 16 | current_line: 1, 17 | } 18 | } 19 | 20 | fn peek(&mut self) -> ParseResult<&Token> { 21 | match self.lexer.peek() { 22 | Some(Ok((_, tok, _))) => Ok(tok), 23 | Some(Err(e)) => Err(ParseError::LexerError(e.clone())), 24 | None => Err(ParseError::UnexpectedEof), 25 | } 26 | } 27 | 28 | fn next(&mut self) -> ParseResult { 29 | match self.lexer.next() { 30 | Some(Ok((_, tok, _))) => Ok(tok), 31 | Some(Err(e)) => Err(ParseError::LexerError(e)), 32 | None => Err(ParseError::UnexpectedEof), 33 | } 34 | } 35 | 36 | fn expect(&mut self, expected: Token) -> ParseResult<()> { 37 | let tok = self.next()?; 38 | if std::mem::discriminant(&tok) == std::mem::discriminant(&expected) { 39 | Ok(()) 40 | } else { 41 | Err(ParseError::ParseError { 42 | line: self.current_line, 43 | message: format!("Expected {:?}, got {:?}", expected, tok), 44 | }) 45 | } 46 | } 47 | 48 | pub fn parse_module(&mut self) -> ParseResult { 49 | let mut module = Module::new(); 50 | 51 | while self.lexer.peek().is_some() { 52 | match self.peek()? { 53 | Token::Define => { 54 | let func = self.parse_function()?; 55 | module.add_function(func); 56 | } 57 | Token::Declare => { 58 | // Skip declarations for now 59 | self.skip_declaration()?; 60 | } 61 | _ => { 62 | // Skip other top-level constructs (metadata, attributes, etc.) 63 | self.next()?; 64 | } 65 | } 66 | } 67 | 68 | Ok(module) 69 | } 70 | 71 | fn parse_function(&mut self) -> ParseResult { 72 | self.expect(Token::Define)?; 73 | 74 | // Parse return type 75 | let ret_ty = self.parse_type()?; 76 | 77 | // Parse function name 78 | let name = match self.next()? { 79 | Token::GlobalIdent(s) => s[1..].to_string(), // Remove '@' 80 | tok => { 81 | return Err(ParseError::ParseError { 82 | line: self.current_line, 83 | message: format!("Expected function name, got {:?}", tok), 84 | }) 85 | } 86 | }; 87 | 88 | // Parse parameters 89 | self.expect(Token::LParen)?; 90 | let params = self.parse_parameters()?; 91 | self.expect(Token::RParen)?; 92 | 93 | // Parse body 94 | self.expect(Token::LBrace)?; 95 | let blocks = self.parse_basic_blocks()?; 96 | self.expect(Token::RBrace)?; 97 | 98 | Ok(Function::new(name, ret_ty, params, blocks)) 99 | } 100 | 101 | fn parse_type(&mut self) -> ParseResult { 102 | match self.next()? { 103 | Token::Void => Ok(Type::Void), 104 | Token::IntType(bits) => Ok(Type::Int(bits)), 105 | Token::Ptr => Ok(Type::Ptr), 106 | Token::LBracket => { 107 | // Array type: [N x T] 108 | let size = match self.next()? { 109 | Token::Integer(n) => n as usize, 110 | tok => { 111 | return Err(ParseError::InvalidType(format!( 112 | "Expected array size, got {:?}", 113 | tok 114 | ))) 115 | } 116 | }; 117 | self.expect(Token::Star)?; // 'x' 118 | let elem_ty = self.parse_type()?; 119 | self.expect(Token::RBracket)?; 120 | Ok(Type::Array(size, Box::new(elem_ty))) 121 | } 122 | Token::LBrace => { 123 | // Struct type: { T1, T2, ... } 124 | let mut fields = Vec::new(); 125 | loop { 126 | if matches!(self.peek()?, Token::RBrace) { 127 | break; 128 | } 129 | fields.push(self.parse_type()?); 130 | if matches!(self.peek()?, Token::Comma) { 131 | self.next()?; 132 | } 133 | } 134 | self.expect(Token::RBrace)?; 135 | Ok(Type::Struct(fields)) 136 | } 137 | tok => Err(ParseError::InvalidType(format!("{:?}", tok))), 138 | } 139 | } 140 | 141 | fn parse_parameters(&mut self) -> ParseResult> { 142 | let mut params = Vec::new(); 143 | 144 | if matches!(self.peek()?, Token::RParen) { 145 | return Ok(params); 146 | } 147 | 148 | loop { 149 | let ty = self.parse_type()?; 150 | let name = match self.next()? { 151 | Token::LocalIdent(s) => s[1..].to_string(), // Remove '%' 152 | Token::NumericIdent(s) => s[1..].to_string(), 153 | tok => { 154 | return Err(ParseError::ParseError { 155 | line: self.current_line, 156 | message: format!("Expected parameter name, got {:?}", tok), 157 | }) 158 | } 159 | }; 160 | 161 | params.push((name, ty)); 162 | 163 | if !matches!(self.peek()?, Token::Comma) { 164 | break; 165 | } 166 | self.next()?; // Consume comma 167 | } 168 | 169 | Ok(params) 170 | } 171 | 172 | fn parse_basic_blocks(&mut self) -> ParseResult> { 173 | let mut blocks = Vec::new(); 174 | 175 | // TODO: Implement basic block parsing 176 | // For now, create a stub that skips until '}' 177 | 178 | while !matches!(self.peek()?, Token::RBrace) { 179 | self.next()?; 180 | } 181 | 182 | Ok(blocks) 183 | } 184 | 185 | fn skip_declaration(&mut self) -> ParseResult<()> { 186 | // Skip until we find a newline or next define 187 | while self.lexer.peek().is_some() { 188 | if matches!(self.peek()?, Token::Define) { 189 | break; 190 | } 191 | self.next()?; 192 | } 193 | Ok(()) 194 | } 195 | } 196 | 197 | /// Parse LLVM IR source code into a Module 198 | pub fn parse(source: &str) -> ParseResult { 199 | let mut parser = Parser::new(source); 200 | parser.parse_module() 201 | } 202 | 203 | #[cfg(test)] 204 | mod tests { 205 | use super::*; 206 | 207 | #[test] 208 | fn test_parse_empty_function() { 209 | let source = r#" 210 | define void @main() { 211 | } 212 | "#; 213 | 214 | let module = parse(source).unwrap(); 215 | assert_eq!(module.functions().len(), 1); 216 | } 217 | 218 | #[test] 219 | fn test_parse_types() { 220 | let source = "define i32 @test(i32 %x, ptr %p) { }"; 221 | let module = parse(source).unwrap(); 222 | let func = &module.functions()[0]; 223 | assert_eq!(func.name(), "test"); 224 | assert_eq!(func.params().len(), 2); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/translate/control.rs: -------------------------------------------------------------------------------- 1 | //! Control flow instruction translation 2 | 3 | use super::{context::TranslationContext, TranslateResult}; 4 | use crate::ir::{instruction::ICmpPredicate, Type, Value}; 5 | use zkir_spec::{Instruction, Register}; 6 | 7 | /// Translate unconditional branch 8 | pub fn translate_br(ctx: &mut TranslationContext, dest: &str) -> TranslateResult<()> { 9 | // JAL zero, offset (unconditional jump) 10 | ctx.emit(Instruction::Jal { 11 | rd: Register::ZERO, 12 | imm: 0, // Will be fixed up later 13 | }); 14 | ctx.add_fixup(dest); 15 | 16 | Ok(()) 17 | } 18 | 19 | /// Translate conditional branch 20 | pub fn translate_cond_br( 21 | ctx: &mut TranslationContext, 22 | cond: &Value, 23 | true_dest: &str, 24 | false_dest: &str, 25 | ) -> TranslateResult<()> { 26 | let cond_reg = ctx.load_value(cond)?; 27 | 28 | // BNE cond, zero, true_dest 29 | ctx.emit(Instruction::Bne { 30 | rs1: cond_reg, 31 | rs2: Register::ZERO, 32 | imm: 0, // Will be fixed up 33 | }); 34 | ctx.add_fixup(true_dest); 35 | 36 | // JAL zero, false_dest 37 | ctx.emit(Instruction::Jal { 38 | rd: Register::ZERO, 39 | imm: 0, // Will be fixed up 40 | }); 41 | ctx.add_fixup(false_dest); 42 | 43 | Ok(()) 44 | } 45 | 46 | /// Translate return instruction 47 | pub fn translate_ret(ctx: &mut TranslationContext, value: Option<&Value>) -> TranslateResult<()> { 48 | // Move return value to rv (r1) if present 49 | if let Some(val) = value { 50 | let reg = ctx.load_value(val)?; 51 | if reg != Register::RV { 52 | ctx.emit(Instruction::Add { 53 | rd: Register::RV, 54 | rs1: reg, 55 | rs2: Register::ZERO, 56 | }); 57 | } 58 | } 59 | 60 | // Return: JALR zero, ra, 0 61 | ctx.emit(Instruction::Jalr { 62 | rd: Register::ZERO, 63 | rs1: Register::RA, 64 | imm: 0, 65 | }); 66 | 67 | Ok(()) 68 | } 69 | 70 | /// Translate comparison instruction 71 | pub fn translate_icmp( 72 | ctx: &mut TranslationContext, 73 | result: &str, 74 | pred: ICmpPredicate, 75 | ty: &Type, 76 | lhs: &Value, 77 | rhs: &Value, 78 | ) -> TranslateResult<()> { 79 | let rs1 = ctx.load_value(lhs)?; 80 | let rs2 = ctx.load_value(rhs)?; 81 | let rd = ctx.alloc_temp()?; 82 | 83 | match pred { 84 | ICmpPredicate::Eq => { 85 | // rd = (rs1 == rs2) 86 | // SUB tmp, rs1, rs2; SLTIU rd, tmp, 1 87 | let tmp = ctx.alloc_temp()?; 88 | ctx.emit(Instruction::Sub { 89 | rd: tmp, 90 | rs1, 91 | rs2, 92 | }); 93 | ctx.emit(Instruction::Sltiu { 94 | rd, 95 | rs1: tmp, 96 | imm: 1, 97 | }); 98 | } 99 | ICmpPredicate::Ne => { 100 | // rd = (rs1 != rs2) 101 | let tmp = ctx.alloc_temp()?; 102 | ctx.emit(Instruction::Sub { 103 | rd: tmp, 104 | rs1, 105 | rs2, 106 | }); 107 | ctx.emit(Instruction::Sltu { 108 | rd, 109 | rs1: Register::ZERO, 110 | rs2: tmp, 111 | }); 112 | } 113 | ICmpPredicate::Slt => { 114 | // rd = (rs1 < rs2) signed 115 | ctx.emit(Instruction::Slt { rd, rs1, rs2 }); 116 | } 117 | ICmpPredicate::Sle => { 118 | // rd = !(rs2 < rs1) 119 | let tmp = ctx.alloc_temp()?; 120 | ctx.emit(Instruction::Slt { 121 | rd: tmp, 122 | rs1: rs2, 123 | rs2: rs1, 124 | }); 125 | ctx.emit(Instruction::Xori { 126 | rd, 127 | rs1: tmp, 128 | imm: 1, 129 | }); 130 | } 131 | ICmpPredicate::Sgt => { 132 | // rd = (rs2 < rs1) 133 | ctx.emit(Instruction::Slt { 134 | rd, 135 | rs1: rs2, 136 | rs2: rs1, 137 | }); 138 | } 139 | ICmpPredicate::Sge => { 140 | // rd = !(rs1 < rs2) 141 | let tmp = ctx.alloc_temp()?; 142 | ctx.emit(Instruction::Slt { 143 | rd: tmp, 144 | rs1, 145 | rs2, 146 | }); 147 | ctx.emit(Instruction::Xori { 148 | rd, 149 | rs1: tmp, 150 | imm: 1, 151 | }); 152 | } 153 | ICmpPredicate::Ult => { 154 | // rd = (rs1 < rs2) unsigned 155 | ctx.emit(Instruction::Sltu { rd, rs1, rs2 }); 156 | } 157 | ICmpPredicate::Ule => { 158 | // rd = !(rs2 < rs1) unsigned 159 | let tmp = ctx.alloc_temp()?; 160 | ctx.emit(Instruction::Sltu { 161 | rd: tmp, 162 | rs1: rs2, 163 | rs2: rs1, 164 | }); 165 | ctx.emit(Instruction::Xori { 166 | rd, 167 | rs1: tmp, 168 | imm: 1, 169 | }); 170 | } 171 | ICmpPredicate::Ugt => { 172 | // rd = (rs2 < rs1) unsigned 173 | ctx.emit(Instruction::Sltu { 174 | rd, 175 | rs1: rs2, 176 | rs2: rs1, 177 | }); 178 | } 179 | ICmpPredicate::Uge => { 180 | // rd = !(rs1 < rs2) unsigned 181 | let tmp = ctx.alloc_temp()?; 182 | ctx.emit(Instruction::Sltu { 183 | rd: tmp, 184 | rs1, 185 | rs2, 186 | }); 187 | ctx.emit(Instruction::Xori { 188 | rd, 189 | rs1: tmp, 190 | imm: 1, 191 | }); 192 | } 193 | } 194 | 195 | ctx.bind(result, super::context::Location::Reg(rd)); 196 | 197 | Ok(()) 198 | } 199 | 200 | /// Translate function call 201 | pub fn translate_call( 202 | ctx: &mut TranslationContext, 203 | result: Option<&str>, 204 | callee: &str, 205 | args: &[Value], 206 | ret_ty: &Type, 207 | ) -> TranslateResult<()> { 208 | // Place arguments in a0-a3, rest on stack 209 | for (i, arg) in args.iter().enumerate() { 210 | if i < 4 { 211 | let src = ctx.load_value(arg)?; 212 | let dst = Register::from_index(4 + i).unwrap(); // a0-a3 213 | if src != dst { 214 | ctx.emit(Instruction::Add { 215 | rd: dst, 216 | rs1: src, 217 | rs2: Register::ZERO, 218 | }); 219 | } 220 | } else { 221 | // Push to stack 222 | let src = ctx.load_value(arg)?; 223 | let offset = -((i - 3) as i16 * 4); 224 | ctx.emit(Instruction::Sw { 225 | rs1: Register::SP, 226 | rs2: src, 227 | imm: offset, 228 | }); 229 | } 230 | } 231 | 232 | // Call function 233 | ctx.emit(Instruction::Jal { 234 | rd: Register::RA, 235 | imm: 0, // Will be fixed up 236 | }); 237 | ctx.add_fixup(callee); 238 | 239 | // Handle return value 240 | if let Some(result) = result { 241 | let bits = ret_ty.bit_width(); 242 | if bits <= 32 { 243 | ctx.bind(result, super::context::Location::Reg(Register::RV)); 244 | } else if bits == 64 { 245 | ctx.bind( 246 | result, 247 | super::context::Location::RegPair { 248 | lo: Register::RV, 249 | hi: Register::A0, 250 | }, 251 | ); 252 | } 253 | } 254 | 255 | Ok(()) 256 | } 257 | -------------------------------------------------------------------------------- /src/translate/arithmetic.rs: -------------------------------------------------------------------------------- 1 | //! Arithmetic instruction translation 2 | 3 | use super::{context::TranslationContext, TranslateResult}; 4 | use crate::ir::{Type, Value}; 5 | use zkir_spec::{Instruction, Register}; 6 | 7 | /// Translate add instruction 8 | pub fn translate_add( 9 | ctx: &mut TranslationContext, 10 | result: &str, 11 | ty: &Type, 12 | lhs: &Value, 13 | rhs: &Value, 14 | ) -> TranslateResult<()> { 15 | let bits = ty.bit_width(); 16 | 17 | if bits <= 32 { 18 | translate_add_32(ctx, result, lhs, rhs) 19 | } else if bits == 64 { 20 | translate_add_64(ctx, result, lhs, rhs) 21 | } else { 22 | Err(super::TranslateError::UnsupportedWidth(bits)) 23 | } 24 | } 25 | 26 | fn translate_add_32( 27 | ctx: &mut TranslationContext, 28 | result: &str, 29 | lhs: &Value, 30 | rhs: &Value, 31 | ) -> TranslateResult<()> { 32 | let rs1 = ctx.load_value(lhs)?; 33 | let rs2 = ctx.load_value(rhs)?; 34 | let rd = ctx.alloc_temp()?; 35 | 36 | ctx.emit(Instruction::Add { rd, rs1, rs2 }); 37 | ctx.bind(result, super::context::Location::Reg(rd)); 38 | 39 | Ok(()) 40 | } 41 | 42 | fn translate_add_64( 43 | ctx: &mut TranslationContext, 44 | result: &str, 45 | lhs: &Value, 46 | rhs: &Value, 47 | ) -> TranslateResult<()> { 48 | let (a_lo, a_hi) = ctx.load_value_pair(lhs)?; 49 | let (b_lo, b_hi) = ctx.load_value_pair(rhs)?; 50 | 51 | let sum_lo = ctx.alloc_temp()?; 52 | let carry = ctx.alloc_temp()?; 53 | let sum_hi = ctx.alloc_temp()?; 54 | 55 | // sum_lo = a_lo + b_lo 56 | ctx.emit(Instruction::Add { 57 | rd: sum_lo, 58 | rs1: a_lo, 59 | rs2: b_lo, 60 | }); 61 | 62 | // carry = (sum_lo < a_lo) ? 1 : 0 63 | ctx.emit(Instruction::Sltu { 64 | rd: carry, 65 | rs1: sum_lo, 66 | rs2: a_lo, 67 | }); 68 | 69 | // sum_hi = a_hi + b_hi + carry 70 | let tmp = ctx.alloc_temp()?; 71 | ctx.emit(Instruction::Add { 72 | rd: tmp, 73 | rs1: a_hi, 74 | rs2: b_hi, 75 | }); 76 | ctx.emit(Instruction::Add { 77 | rd: sum_hi, 78 | rs1: tmp, 79 | rs2: carry, 80 | }); 81 | 82 | ctx.bind( 83 | result, 84 | super::context::Location::RegPair { 85 | lo: sum_lo, 86 | hi: sum_hi, 87 | }, 88 | ); 89 | 90 | Ok(()) 91 | } 92 | 93 | /// Translate sub instruction 94 | pub fn translate_sub( 95 | ctx: &mut TranslationContext, 96 | result: &str, 97 | ty: &Type, 98 | lhs: &Value, 99 | rhs: &Value, 100 | ) -> TranslateResult<()> { 101 | let bits = ty.bit_width(); 102 | 103 | if bits <= 32 { 104 | let rs1 = ctx.load_value(lhs)?; 105 | let rs2 = ctx.load_value(rhs)?; 106 | let rd = ctx.alloc_temp()?; 107 | 108 | ctx.emit(Instruction::Sub { rd, rs1, rs2 }); 109 | ctx.bind(result, super::context::Location::Reg(rd)); 110 | Ok(()) 111 | } else { 112 | Err(super::TranslateError::UnsupportedWidth(bits)) 113 | } 114 | } 115 | 116 | /// Translate mul instruction 117 | pub fn translate_mul( 118 | ctx: &mut TranslationContext, 119 | result: &str, 120 | ty: &Type, 121 | lhs: &Value, 122 | rhs: &Value, 123 | ) -> TranslateResult<()> { 124 | let bits = ty.bit_width(); 125 | 126 | if bits <= 32 { 127 | let rs1 = ctx.load_value(lhs)?; 128 | let rs2 = ctx.load_value(rhs)?; 129 | let rd = ctx.alloc_temp()?; 130 | 131 | ctx.emit(Instruction::Mul { rd, rs1, rs2 }); 132 | ctx.bind(result, super::context::Location::Reg(rd)); 133 | Ok(()) 134 | } else { 135 | Err(super::TranslateError::UnsupportedWidth(bits)) 136 | } 137 | } 138 | 139 | /// Translate unsigned div 140 | pub fn translate_udiv( 141 | ctx: &mut TranslationContext, 142 | result: &str, 143 | ty: &Type, 144 | lhs: &Value, 145 | rhs: &Value, 146 | ) -> TranslateResult<()> { 147 | let rs1 = ctx.load_value(lhs)?; 148 | let rs2 = ctx.load_value(rhs)?; 149 | let rd = ctx.alloc_temp()?; 150 | 151 | ctx.emit(Instruction::Divu { rd, rs1, rs2 }); 152 | ctx.bind(result, super::context::Location::Reg(rd)); 153 | Ok(()) 154 | } 155 | 156 | /// Translate signed div 157 | pub fn translate_sdiv( 158 | ctx: &mut TranslationContext, 159 | result: &str, 160 | ty: &Type, 161 | lhs: &Value, 162 | rhs: &Value, 163 | ) -> TranslateResult<()> { 164 | let rs1 = ctx.load_value(lhs)?; 165 | let rs2 = ctx.load_value(rhs)?; 166 | let rd = ctx.alloc_temp()?; 167 | 168 | ctx.emit(Instruction::Div { rd, rs1, rs2 }); 169 | ctx.bind(result, super::context::Location::Reg(rd)); 170 | Ok(()) 171 | } 172 | 173 | /// Translate unsigned rem 174 | pub fn translate_urem( 175 | ctx: &mut TranslationContext, 176 | result: &str, 177 | ty: &Type, 178 | lhs: &Value, 179 | rhs: &Value, 180 | ) -> TranslateResult<()> { 181 | let rs1 = ctx.load_value(lhs)?; 182 | let rs2 = ctx.load_value(rhs)?; 183 | let rd = ctx.alloc_temp()?; 184 | 185 | ctx.emit(Instruction::Remu { rd, rs1, rs2 }); 186 | ctx.bind(result, super::context::Location::Reg(rd)); 187 | Ok(()) 188 | } 189 | 190 | /// Translate signed rem 191 | pub fn translate_srem( 192 | ctx: &mut TranslationContext, 193 | result: &str, 194 | ty: &Type, 195 | lhs: &Value, 196 | rhs: &Value, 197 | ) -> TranslateResult<()> { 198 | let rs1 = ctx.load_value(lhs)?; 199 | let rs2 = ctx.load_value(rhs)?; 200 | let rd = ctx.alloc_temp()?; 201 | 202 | ctx.emit(Instruction::Rem { rd, rs1, rs2 }); 203 | ctx.bind(result, super::context::Location::Reg(rd)); 204 | Ok(()) 205 | } 206 | 207 | // Bitwise operations 208 | 209 | pub fn translate_and( 210 | ctx: &mut TranslationContext, 211 | result: &str, 212 | ty: &Type, 213 | lhs: &Value, 214 | rhs: &Value, 215 | ) -> TranslateResult<()> { 216 | let rs1 = ctx.load_value(lhs)?; 217 | let rs2 = ctx.load_value(rhs)?; 218 | let rd = ctx.alloc_temp()?; 219 | 220 | ctx.emit(Instruction::And { rd, rs1, rs2 }); 221 | ctx.bind(result, super::context::Location::Reg(rd)); 222 | Ok(()) 223 | } 224 | 225 | pub fn translate_or( 226 | ctx: &mut TranslationContext, 227 | result: &str, 228 | ty: &Type, 229 | lhs: &Value, 230 | rhs: &Value, 231 | ) -> TranslateResult<()> { 232 | let rs1 = ctx.load_value(lhs)?; 233 | let rs2 = ctx.load_value(rhs)?; 234 | let rd = ctx.alloc_temp()?; 235 | 236 | ctx.emit(Instruction::Or { rd, rs1, rs2 }); 237 | ctx.bind(result, super::context::Location::Reg(rd)); 238 | Ok(()) 239 | } 240 | 241 | pub fn translate_xor( 242 | ctx: &mut TranslationContext, 243 | result: &str, 244 | ty: &Type, 245 | lhs: &Value, 246 | rhs: &Value, 247 | ) -> TranslateResult<()> { 248 | let rs1 = ctx.load_value(lhs)?; 249 | let rs2 = ctx.load_value(rhs)?; 250 | let rd = ctx.alloc_temp()?; 251 | 252 | ctx.emit(Instruction::Xor { rd, rs1, rs2 }); 253 | ctx.bind(result, super::context::Location::Reg(rd)); 254 | Ok(()) 255 | } 256 | 257 | pub fn translate_shl( 258 | ctx: &mut TranslationContext, 259 | result: &str, 260 | ty: &Type, 261 | lhs: &Value, 262 | rhs: &Value, 263 | ) -> TranslateResult<()> { 264 | let rs1 = ctx.load_value(lhs)?; 265 | let rs2 = ctx.load_value(rhs)?; 266 | let rd = ctx.alloc_temp()?; 267 | 268 | ctx.emit(Instruction::Sll { rd, rs1, rs2 }); 269 | ctx.bind(result, super::context::Location::Reg(rd)); 270 | Ok(()) 271 | } 272 | 273 | pub fn translate_lshr( 274 | ctx: &mut TranslationContext, 275 | result: &str, 276 | ty: &Type, 277 | lhs: &Value, 278 | rhs: &Value, 279 | ) -> TranslateResult<()> { 280 | let rs1 = ctx.load_value(lhs)?; 281 | let rs2 = ctx.load_value(rhs)?; 282 | let rd = ctx.alloc_temp()?; 283 | 284 | ctx.emit(Instruction::Srl { rd, rs1, rs2 }); 285 | ctx.bind(result, super::context::Location::Reg(rd)); 286 | Ok(()) 287 | } 288 | 289 | pub fn translate_ashr( 290 | ctx: &mut TranslationContext, 291 | result: &str, 292 | ty: &Type, 293 | lhs: &Value, 294 | rhs: &Value, 295 | ) -> TranslateResult<()> { 296 | let rs1 = ctx.load_value(lhs)?; 297 | let rs2 = ctx.load_value(rhs)?; 298 | let rd = ctx.alloc_temp()?; 299 | 300 | ctx.emit(Instruction::Sra { rd, rs1, rs2 }); 301 | ctx.bind(result, super::context::Location::Reg(rd)); 302 | Ok(()) 303 | } 304 | -------------------------------------------------------------------------------- /src/translate/mod.rs: -------------------------------------------------------------------------------- 1 | //! Translation from LLVM IR to ZK IR 2 | 3 | pub mod context; 4 | pub mod types; 5 | pub mod arithmetic; 6 | pub mod memory; 7 | pub mod control; 8 | pub mod intrinsics; 9 | 10 | use crate::ir::Module; 11 | use anyhow::Result; 12 | use thiserror::Error; 13 | 14 | #[derive(Debug, Error)] 15 | pub enum TranslateError { 16 | #[error("Unsupported type: {0:?}")] 17 | UnsupportedType(crate::ir::Type), 18 | 19 | #[error("Unsupported instruction: {0}")] 20 | UnsupportedInstruction(String), 21 | 22 | #[error("Unsupported bit width: {0}")] 23 | UnsupportedWidth(u32), 24 | 25 | #[error("Register allocation failed: {0}")] 26 | RegisterAllocation(String), 27 | 28 | #[error("Out of registers")] 29 | OutOfRegisters, 30 | 31 | #[error("Undefined value: {0}")] 32 | UndefinedValue(String), 33 | 34 | #[error("Type mismatch: expected {expected:?}, got {actual:?}")] 35 | TypeMismatch { 36 | expected: crate::ir::Type, 37 | actual: crate::ir::Type, 38 | }, 39 | 40 | #[error("Invalid branch target: {0}")] 41 | InvalidBranch(String), 42 | 43 | #[error("Function not found: {0}")] 44 | FunctionNotFound(String), 45 | } 46 | 47 | pub type TranslateResult = Result; 48 | 49 | /// Translate an LLVM module to ZK IR program 50 | pub fn translate_module(module: &Module, opt_level: u8) -> Result { 51 | let mut all_code = Vec::new(); 52 | 53 | // Translate each function 54 | for func in module.functions() { 55 | if func.is_declaration() { 56 | // Skip function declarations 57 | continue; 58 | } 59 | 60 | let instructions = translate_function(func)?; 61 | 62 | // Encode instructions to u32 words 63 | for instr in instructions { 64 | let encoded = encode_instruction(&instr); 65 | all_code.push(encoded); 66 | } 67 | } 68 | 69 | // Apply optimizations based on opt_level 70 | if opt_level > 0 { 71 | // TODO: Add optimizations 72 | } 73 | 74 | Ok(zkir_spec::Program::new(all_code)) 75 | } 76 | 77 | /// Encode instruction to u32 78 | fn encode_instruction(instr: &zkir_spec::Instruction) -> u32 { 79 | // For now, use a simple encoding 80 | // TODO: Implement proper RISC-V encoding 81 | match instr { 82 | zkir_spec::Instruction::Add { rd, rs1, rs2 } => { 83 | let opcode = 0x33u32; 84 | let funct3 = 0x0u32; 85 | let funct7 = 0x00u32; 86 | opcode | ((rd.index() as u32) << 7) | ((funct3) << 12) | 87 | ((rs1.index() as u32) << 15) | ((rs2.index() as u32) << 20) | ((funct7) << 25) 88 | } 89 | // TODO: Implement encoding for other instructions 90 | _ => 0, // Placeholder 91 | } 92 | } 93 | 94 | /// Translate a single function 95 | fn translate_function(func: &crate::ir::Function) -> Result> { 96 | use context::TranslationContext; 97 | 98 | let mut ctx = TranslationContext::new(func.name()); 99 | 100 | // Set up function parameters 101 | for (i, (name, ty)) in func.params().iter().enumerate() { 102 | ctx.bind_parameter(name, ty, i)?; 103 | } 104 | 105 | // Translate each basic block 106 | for block in func.blocks() { 107 | ctx.start_block(block.name()); 108 | 109 | for instr in block.instructions() { 110 | translate_instruction(&mut ctx, instr)?; 111 | } 112 | } 113 | 114 | // Resolve labels and fixups 115 | ctx.resolve_labels()?; 116 | 117 | Ok(ctx.into_instructions()) 118 | } 119 | 120 | /// Translate a single instruction 121 | fn translate_instruction( 122 | ctx: &mut context::TranslationContext, 123 | instr: &crate::ir::Instruction, 124 | ) -> Result<()> { 125 | use crate::ir::Instruction as I; 126 | 127 | match instr { 128 | // Arithmetic 129 | I::Add { result, ty, lhs, rhs } => { 130 | arithmetic::translate_add(ctx, result, ty, lhs, rhs)?; 131 | } 132 | I::Sub { result, ty, lhs, rhs } => { 133 | arithmetic::translate_sub(ctx, result, ty, lhs, rhs)?; 134 | } 135 | I::Mul { result, ty, lhs, rhs } => { 136 | arithmetic::translate_mul(ctx, result, ty, lhs, rhs)?; 137 | } 138 | I::UDiv { result, ty, lhs, rhs } => { 139 | arithmetic::translate_udiv(ctx, result, ty, lhs, rhs)?; 140 | } 141 | I::SDiv { result, ty, lhs, rhs } => { 142 | arithmetic::translate_sdiv(ctx, result, ty, lhs, rhs)?; 143 | } 144 | I::URem { result, ty, lhs, rhs } => { 145 | arithmetic::translate_urem(ctx, result, ty, lhs, rhs)?; 146 | } 147 | I::SRem { result, ty, lhs, rhs } => { 148 | arithmetic::translate_srem(ctx, result, ty, lhs, rhs)?; 149 | } 150 | 151 | // Bitwise 152 | I::And { result, ty, lhs, rhs } => { 153 | arithmetic::translate_and(ctx, result, ty, lhs, rhs)?; 154 | } 155 | I::Or { result, ty, lhs, rhs } => { 156 | arithmetic::translate_or(ctx, result, ty, lhs, rhs)?; 157 | } 158 | I::Xor { result, ty, lhs, rhs } => { 159 | arithmetic::translate_xor(ctx, result, ty, lhs, rhs)?; 160 | } 161 | I::Shl { result, ty, lhs, rhs } => { 162 | arithmetic::translate_shl(ctx, result, ty, lhs, rhs)?; 163 | } 164 | I::LShr { result, ty, lhs, rhs } => { 165 | arithmetic::translate_lshr(ctx, result, ty, lhs, rhs)?; 166 | } 167 | I::AShr { result, ty, lhs, rhs } => { 168 | arithmetic::translate_ashr(ctx, result, ty, lhs, rhs)?; 169 | } 170 | 171 | // Comparison 172 | I::ICmp { 173 | result, 174 | pred, 175 | ty, 176 | lhs, 177 | rhs, 178 | } => { 179 | control::translate_icmp(ctx, result, *pred, ty, lhs, rhs)?; 180 | } 181 | 182 | // Memory 183 | I::Load { result, ty, ptr } => { 184 | memory::translate_load(ctx, result, ty, ptr)?; 185 | } 186 | I::Store { value, ty, ptr } => { 187 | memory::translate_store(ctx, value, ty, ptr)?; 188 | } 189 | I::Alloca { result, ty } => { 190 | memory::translate_alloca(ctx, result, ty)?; 191 | } 192 | 193 | // Control flow 194 | I::Ret { value } => { 195 | control::translate_ret(ctx, value.as_ref())?; 196 | } 197 | I::Br { dest } => { 198 | control::translate_br(ctx, dest)?; 199 | } 200 | I::CondBr { 201 | cond, 202 | true_dest, 203 | false_dest, 204 | } => { 205 | control::translate_cond_br(ctx, cond, true_dest, false_dest)?; 206 | } 207 | I::Call { 208 | result, 209 | callee, 210 | args, 211 | ret_ty, 212 | } => { 213 | control::translate_call(ctx, result.as_deref(), callee, args, ret_ty)?; 214 | } 215 | 216 | I::Phi { .. } => { 217 | // Phi nodes should be lowered before translation 218 | // For now, skip them 219 | } 220 | 221 | I::GetElementPtr { .. } => { 222 | // TODO: Implement GEP 223 | return Err(TranslateError::UnsupportedInstruction("getelementptr".to_string()).into()); 224 | } 225 | } 226 | 227 | Ok(()) 228 | } 229 | 230 | /// Check if a module is compatible with ZK IR 231 | pub fn check_module_compatibility(module: &Module) -> Result<()> { 232 | for func in module.functions() { 233 | check_function_compatibility(func)?; 234 | } 235 | Ok(()) 236 | } 237 | 238 | fn check_function_compatibility(func: &crate::ir::Function) -> Result<()> { 239 | // Check return type 240 | if !func.ret_ty().is_supported() { 241 | return Err(TranslateError::UnsupportedType(func.ret_ty().clone()).into()); 242 | } 243 | 244 | // Check parameters 245 | for (_, ty) in func.params() { 246 | if !ty.is_supported() { 247 | return Err(TranslateError::UnsupportedType(ty.clone()).into()); 248 | } 249 | } 250 | 251 | // Check instructions in each block 252 | for block in func.blocks() { 253 | for instr in block.instructions() { 254 | check_instruction_compatibility(instr)?; 255 | } 256 | } 257 | 258 | Ok(()) 259 | } 260 | 261 | fn check_instruction_compatibility(instr: &crate::ir::Instruction) -> Result<()> { 262 | use crate::ir::Instruction as I; 263 | 264 | match instr { 265 | I::GetElementPtr { .. } => { 266 | // GEP is supported but complex 267 | Ok(()) 268 | } 269 | _ => Ok(()), // Most instructions are supported 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # Architecture Documentation 2 | 3 | ## Overview 4 | 5 | zkir-llvm is structured as a multi-stage compiler pipeline that transforms LLVM IR into ZK IR bytecode. 6 | 7 | ## Pipeline Stages 8 | 9 | ``` 10 | ┌─────────────┐ 11 | │ LLVM IR │ 12 | │ (text) │ 13 | └──────┬──────┘ 14 | │ 15 | ▼ 16 | ┌─────────────┐ 17 | │ Lexer │ Tokenize source 18 | │ (logos) │ 19 | └──────┬──────┘ 20 | │ 21 | ▼ 22 | ┌─────────────┐ 23 | │ Parser │ Build AST 24 | │ (nom) │ 25 | └──────┬──────┘ 26 | │ 27 | ▼ 28 | ┌─────────────┐ 29 | │ LLVM IR │ Internal representation 30 | │ AST │ (Module → Function → Block → Instruction) 31 | └──────┬──────┘ 32 | │ 33 | ▼ 34 | ┌─────────────┐ 35 | │ Type Lower │ LLVM types → ZK types 36 | │ │ (i64 → pair, i128 → quad) 37 | └──────┬──────┘ 38 | │ 39 | ▼ 40 | ┌─────────────┐ 41 | │ Translate │ Generate ZK IR instructions 42 | │ │ (virtual registers) 43 | └──────┬──────┘ 44 | │ 45 | ▼ 46 | ┌─────────────┐ 47 | │ RegAlloc │ Allocate physical registers 48 | │ │ (linear scan + spilling) 49 | └──────┬──────┘ 50 | │ 51 | ▼ 52 | ┌─────────────┐ 53 | │ Encode │ Encode to RISC-V format 54 | │ │ (32-bit instructions) 55 | └──────┬──────┘ 56 | │ 57 | ▼ 58 | ┌─────────────┐ 59 | │ Emit │ Serialize to bytecode 60 | │ (bincode) │ 61 | └──────┬──────┘ 62 | │ 63 | ▼ 64 | ┌─────────────┐ 65 | │ ZK IR │ 66 | │ Bytecode │ 67 | └─────────────┘ 68 | ``` 69 | 70 | ## Module Structure 71 | 72 | ### Parser Module (`src/parser/`) 73 | 74 | **Lexer** (`lexer.rs`): 75 | - Uses `logos` for fast tokenization 76 | - Produces `Token` stream 77 | - Handles comments, whitespace 78 | - Recognizes LLVM IR keywords, types, identifiers 79 | 80 | **Parser** (`parser.rs`): 81 | - Uses `nom` parser combinators (planned) 82 | - Recursive descent for structure 83 | - Builds IR AST from tokens 84 | - Error recovery and reporting 85 | 86 | ### IR Module (`src/ir/`) 87 | 88 | **Type System** (`types.rs`): 89 | - LLVM type representation 90 | - Bit width calculations 91 | - Size computations 92 | - Supported type checking 93 | 94 | **Module** (`module.rs`): 95 | - Top-level container 96 | - Function collection 97 | - Global variables 98 | - Module metadata 99 | 100 | **Function** (`function.rs`): 101 | - Function signature 102 | - Parameter list 103 | - Basic block collection 104 | - Entry block management 105 | 106 | **BasicBlock** (`block.rs`): 107 | - Linear sequence of instructions 108 | - Terminator instruction 109 | - Label/name 110 | - Predecessors/successors (planned) 111 | 112 | **Instruction** (`instruction.rs`): 113 | - LLVM instruction representation 114 | - Arithmetic, bitwise, memory, control flow 115 | - SSA value naming 116 | - Instruction properties 117 | 118 | **Value** (`mod.rs`): 119 | - SSA values 120 | - Constants (i32, i64, bool) 121 | - Local references (%var) 122 | - Undef, Null 123 | 124 | ### Translation Module (`src/translate/`) 125 | 126 | **Context** (`context.rs`): 127 | - Per-function translation state 128 | - Value → Location mapping 129 | - Instruction emission 130 | - Label tracking 131 | - Fixup recording 132 | - Register allocation 133 | 134 | **Type Lowering** (`types.rs`): 135 | - LLVM Type → ZK Type 136 | - Scalar types → registers 137 | - Aggregate types → memory 138 | - Size calculations 139 | 140 | **Arithmetic** (`arithmetic.rs`): 141 | - 32-bit operations: direct mapping 142 | - 64-bit operations: register pairs 143 | ``` 144 | add64(a, b): 145 | lo = a.lo + b.lo 146 | carry = (lo < a.lo) ? 1 : 0 147 | hi = a.hi + b.hi + carry 148 | ``` 149 | - Bitwise operations 150 | - Shift operations 151 | 152 | **Memory** (`memory.rs`): 153 | - Load: `lw/lh/lhu/lb/lbu` 154 | - Store: `sw/sh/sb` 155 | - Stack allocation: `alloca` 156 | - Address computation 157 | 158 | **Control Flow** (`control.rs`): 159 | - Branches: `beq/bne/blt/bge/bltu/bgeu` 160 | - Jumps: `jal/jalr` 161 | - Returns: move to rv + jalr 162 | - Comparisons: `slt/sltu` + logic 163 | - Function calls: arg passing + jal 164 | 165 | **Intrinsics** (`intrinsics.rs`): 166 | - LLVM intrinsic handling 167 | - Ignore debug intrinsics 168 | - Ignore lifetime intrinsics 169 | - Error on unsupported 170 | 171 | ### Register Allocation Module (`src/regalloc/`) 172 | 173 | **Linear Scan** (`linear.rs`): 174 | - Sort live intervals by start 175 | - Allocate from free register pool 176 | - Spill when out of registers 177 | - Track active intervals 178 | - Expire old intervals 179 | 180 | Algorithm: 181 | ```rust 182 | for interval in sorted_intervals { 183 | expire_old_intervals(interval.start) 184 | 185 | if free_regs.is_empty() { 186 | spill_at_interval(interval) 187 | } else { 188 | reg = free_regs.pop() 189 | assign(interval, reg) 190 | active.push(interval) 191 | } 192 | } 193 | ``` 194 | 195 | ### Emit Module (`src/emit/`) 196 | 197 | **Encoding**: 198 | - ZK IR instructions → u32 words 199 | - RISC-V encoding format 200 | - Opcode, funct3, funct7 fields 201 | - Register indices 202 | - Immediate values 203 | 204 | **Serialization**: 205 | - Program → bytecode 206 | - Uses `bincode` for serialization 207 | - Program header + code + data 208 | 209 | ## Data Flow 210 | 211 | ### Example: `add i32 %a, %b` 212 | 213 | 1. **Lexer**: 214 | ``` 215 | Token::Add, Token::IntType(32), Token::LocalIdent("%a"), 216 | Token::Comma, Token::LocalIdent("%b") 217 | ``` 218 | 219 | 2. **Parser**: 220 | ```rust 221 | Instruction::Add { 222 | result: "%sum", 223 | ty: Type::Int(32), 224 | lhs: Value::Local("a"), 225 | rhs: Value::Local("b"), 226 | } 227 | ``` 228 | 229 | 3. **Translation**: 230 | ```rust 231 | rs1 = ctx.load_value("%a") // → r8 (t0) 232 | rs2 = ctx.load_value("%b") // → r9 (t1) 233 | rd = ctx.alloc_temp() // → r10 (t2) 234 | ctx.emit(Instruction::Add { rd, rs1, rs2 }) 235 | ctx.bind("%sum", Location::Reg(rd)) 236 | ``` 237 | 238 | 4. **RegAlloc**: 239 | ``` 240 | Virtual r10 → Physical r10 (t2) 241 | ``` 242 | 243 | 5. **Encoding**: 244 | ``` 245 | opcode: 0x33 (R-type) 246 | rd: 10 (destination) 247 | funct3: 0x0 (ADD) 248 | rs1: 8 (source 1) 249 | rs2: 9 (source 2) 250 | funct7: 0x00 (ADD) 251 | 252 | Encoded: 0x009403B3 253 | ``` 254 | 255 | ## Key Design Decisions 256 | 257 | ### 1. SSA Form 258 | - LLVM IR is in SSA (Static Single Assignment) 259 | - Each value defined once 260 | - Phi nodes for control flow merges 261 | - We lower phi nodes to moves before regalloc 262 | 263 | ### 2. Virtual Registers 264 | - Translation uses unlimited virtual registers 265 | - Register allocator maps to 20 physical registers 266 | - Spills to stack when out of registers 267 | 268 | ### 3. Two-Stage Translation 269 | - Stage 1: LLVM IR → ZK IR (virtual regs) 270 | - Stage 2: Register allocation → physical regs 271 | 272 | ### 4. Type Lowering 273 | - i1, i8, i16, i32 → single 32-bit register 274 | - i64 → register pair (lo, hi) 275 | - i128 → register quad (r0, r1, r2, r3) 276 | - Aggregates → memory pointers 277 | 278 | ### 5. Calling Convention 279 | - First 4 args in a0-a3 (r4-r7) 280 | - Additional args on stack 281 | - Return value in rv (r1) 282 | - 64-bit return in (rv, a0) 283 | 284 | ### 6. Stack Layout 285 | ``` 286 | High Address 287 | ┌────────────┐ 288 | │ Args │ Arguments beyond a0-a3 289 | ├────────────┤ 290 | │ Return Addr│ Saved by caller 291 | ├────────────┤ ← FP 292 | │ Locals │ Local variables 293 | ├────────────┤ 294 | │ Spills │ Spilled registers 295 | ├────────────┤ ← SP 296 | │ ... │ 297 | Low Address 298 | ``` 299 | 300 | ## Performance Considerations 301 | 302 | ### Parser 303 | - Zero-copy tokens where possible 304 | - Minimal allocations during parsing 305 | - Lazy evaluation of unused fields 306 | 307 | ### Translation 308 | - Single pass through IR 309 | - Direct emission (no IR transformation) 310 | - Minimal intermediate structures 311 | 312 | ### Register Allocation 313 | - Linear scan for speed 314 | - O(n log n) complexity 315 | - Simple spilling heuristics 316 | 317 | ## Extension Points 318 | 319 | ### Adding New Instructions 320 | 1. Add token to lexer (`lexer.rs`) 321 | 2. Add variant to `Instruction` enum (`instruction.rs`) 322 | 3. Add parser case (`parser.rs`) 323 | 4. Add translation function (`arithmetic.rs`, etc.) 324 | 5. Add tests 325 | 326 | ### Adding New Types 327 | 1. Add variant to `Type` enum (`types.rs`) 328 | 2. Implement size/support methods 329 | 3. Add to type lowering (`translate/types.rs`) 330 | 4. Add load/store support (`memory.rs`) 331 | 332 | ### Adding Optimizations 333 | 1. Create optimization module 334 | 2. Add pass infrastructure 335 | 3. Integrate with translation pipeline 336 | 4. Add opt-level gating 337 | 338 | ## Testing Strategy 339 | 340 | ### Unit Tests 341 | - Per-module tests in `src/*/mod.rs` 342 | - Test individual functions 343 | - Mock dependencies 344 | 345 | ### Integration Tests 346 | - `tests/*.rs` files 347 | - End-to-end scenarios 348 | - Real LLVM IR inputs 349 | 350 | ### Property Tests 351 | - (Planned) Use `proptest` 352 | - Random IR generation 353 | - Invariant checking 354 | 355 | ## Future Directions 356 | 357 | ### Short Term 358 | - Complete instruction parsing 359 | - Phi node lowering 360 | - Label resolution 361 | 362 | ### Medium Term 363 | - GEP support 364 | - LLVM intrinsics 365 | - Optimization passes 366 | 367 | ### Long Term 368 | - Alternative register allocators 369 | - Advanced optimizations 370 | - JIT compilation 371 | -------------------------------------------------------------------------------- /src/translate/context.rs: -------------------------------------------------------------------------------- 1 | //! Translation context 2 | 3 | use super::{TranslateError, TranslateResult}; 4 | use crate::ir::{Type, Value}; 5 | use std::collections::HashMap; 6 | use zkir_spec::{Instruction, Register}; 7 | 8 | /// Location where a value is stored 9 | #[derive(Debug, Clone)] 10 | pub enum Location { 11 | /// Single register 12 | Reg(Register), 13 | 14 | /// Register pair (for 64-bit values) 15 | RegPair { lo: Register, hi: Register }, 16 | 17 | /// Register quad (for 128-bit values) 18 | RegQuad { 19 | r0: Register, 20 | r1: Register, 21 | r2: Register, 22 | r3: Register, 23 | }, 24 | 25 | /// Stack slot (offset from frame pointer) 26 | Stack(i32), 27 | 28 | /// Constant value 29 | Const(i64), 30 | } 31 | 32 | /// Translation context for a single function 33 | pub struct TranslationContext { 34 | /// Function name 35 | function_name: String, 36 | 37 | /// Value bindings (LLVM value name -> location) 38 | bindings: HashMap, 39 | 40 | /// Generated instructions 41 | instructions: Vec, 42 | 43 | /// Label positions (block name -> instruction index) 44 | labels: HashMap, 45 | 46 | /// Pending fixups (instruction index, label name) 47 | fixups: Vec<(usize, String)>, 48 | 49 | /// Next available temporary register 50 | next_temp: u8, 51 | 52 | /// Stack frame size 53 | stack_size: i32, 54 | 55 | /// Current block name 56 | current_block: String, 57 | } 58 | 59 | impl TranslationContext { 60 | pub fn new(function_name: impl Into) -> Self { 61 | Self { 62 | function_name: function_name.into(), 63 | bindings: HashMap::new(), 64 | instructions: Vec::new(), 65 | labels: HashMap::new(), 66 | fixups: Vec::new(), 67 | next_temp: 8, // Start at t0 (r8) 68 | stack_size: 0, 69 | current_block: String::new(), 70 | } 71 | } 72 | 73 | /// Start a new basic block 74 | pub fn start_block(&mut self, name: impl Into) { 75 | let name = name.into(); 76 | self.current_block = name.clone(); 77 | self.labels 78 | .insert(name, self.instructions.len() as u32); 79 | } 80 | 81 | /// Emit an instruction 82 | pub fn emit(&mut self, instr: Instruction) { 83 | self.instructions.push(instr); 84 | } 85 | 86 | /// Allocate a temporary register 87 | pub fn alloc_temp(&mut self) -> TranslateResult { 88 | // Available temps: t0-t7 (r8-r15), t8-t11 (r24-r27) 89 | let reg = if self.next_temp < 16 { 90 | Register::from_index(self.next_temp as usize).unwrap() 91 | } else if self.next_temp < 20 { 92 | Register::from_index((self.next_temp + 8) as usize).unwrap() // Skip s0-s7 93 | } else { 94 | return Err(TranslateError::OutOfRegisters); 95 | }; 96 | 97 | self.next_temp += 1; 98 | Ok(reg) 99 | } 100 | 101 | /// Allocate a register pair for 64-bit value 102 | pub fn alloc_reg_pair(&mut self) -> TranslateResult<(Register, Register)> { 103 | let lo = self.alloc_temp()?; 104 | let hi = self.alloc_temp()?; 105 | Ok((lo, hi)) 106 | } 107 | 108 | /// Allocate stack space 109 | pub fn alloc_stack(&mut self, size: i32) -> i32 { 110 | self.stack_size += size; 111 | -self.stack_size 112 | } 113 | 114 | /// Bind a value to a location 115 | pub fn bind(&mut self, name: impl Into, loc: Location) { 116 | self.bindings.insert(name.into(), loc); 117 | } 118 | 119 | /// Bind a parameter 120 | pub fn bind_parameter( 121 | &mut self, 122 | name: &str, 123 | ty: &Type, 124 | index: usize, 125 | ) -> TranslateResult<()> { 126 | let loc = if index < 4 { 127 | // First 4 parameters in a0-a3 (r4-r7) 128 | Location::Reg(Register::from_index(4 + index).unwrap()) 129 | } else { 130 | // Rest on stack 131 | Location::Stack((index - 4) as i32 * 4) 132 | }; 133 | 134 | self.bind(name, loc); 135 | Ok(()) 136 | } 137 | 138 | /// Get the location of a value 139 | pub fn get_location(&self, name: &str) -> TranslateResult<&Location> { 140 | self.bindings 141 | .get(name) 142 | .ok_or_else(|| TranslateError::UndefinedValue(name.to_string())) 143 | } 144 | 145 | /// Load a value into a register 146 | pub fn load_value(&mut self, value: &Value) -> TranslateResult { 147 | match value { 148 | Value::Local(name) => { 149 | let loc = self.get_location(name)?.clone(); 150 | match loc { 151 | Location::Reg(r) => Ok(r), 152 | Location::Stack(offset) => { 153 | let rd = self.alloc_temp()?; 154 | self.emit(Instruction::Lw { 155 | rd, 156 | rs1: Register::FP, 157 | imm: offset as i16, 158 | }); 159 | Ok(rd) 160 | } 161 | Location::Const(c) => { 162 | let rd = self.alloc_temp()?; 163 | self.emit_load_imm(rd, c as u32); 164 | Ok(rd) 165 | } 166 | _ => Err(TranslateError::UnsupportedType(Type::Int(64))), 167 | } 168 | } 169 | Value::ConstInt { value, .. } => { 170 | let rd = self.alloc_temp()?; 171 | self.emit_load_imm(rd, *value as u32); 172 | Ok(rd) 173 | } 174 | Value::ConstBool(b) => { 175 | let rd = self.alloc_temp()?; 176 | self.emit_load_imm(rd, if *b { 1 } else { 0 }); 177 | Ok(rd) 178 | } 179 | _ => Err(TranslateError::UnsupportedInstruction(format!( 180 | "load {:?}", 181 | value 182 | ))), 183 | } 184 | } 185 | 186 | /// Load a 64-bit value as a register pair 187 | pub fn load_value_pair(&mut self, value: &Value) -> TranslateResult<(Register, Register)> { 188 | match value { 189 | Value::Local(name) => { 190 | let loc = self.get_location(name)?.clone(); 191 | match loc { 192 | Location::RegPair { lo, hi } => Ok((lo, hi)), 193 | _ => Err(TranslateError::UnsupportedType(Type::Int(64))), 194 | } 195 | } 196 | Value::ConstInt { value, .. } => { 197 | let lo = self.alloc_temp()?; 198 | let hi = self.alloc_temp()?; 199 | self.emit_load_imm(lo, (*value & 0xFFFFFFFF) as u32); 200 | self.emit_load_imm(hi, ((*value >> 32) & 0xFFFFFFFF) as u32); 201 | Ok((lo, hi)) 202 | } 203 | _ => Err(TranslateError::UnsupportedInstruction(format!( 204 | "load pair {:?}", 205 | value 206 | ))), 207 | } 208 | } 209 | 210 | /// Emit instructions to load a 32-bit immediate 211 | pub fn emit_load_imm(&mut self, rd: Register, value: u32) { 212 | if value == 0 { 213 | // Move zero 214 | self.emit(Instruction::Add { 215 | rd, 216 | rs1: Register::ZERO, 217 | rs2: Register::ZERO, 218 | }); 219 | } else if value < 2048 { 220 | // Small positive immediate 221 | self.emit(Instruction::Addi { 222 | rd, 223 | rs1: Register::ZERO, 224 | imm: value as i16, 225 | }); 226 | } else { 227 | // Full 32-bit: LUI + ADDI 228 | let upper = ((value + 0x800) >> 12) & 0xFFFFF; 229 | let lower = (value as i32) & 0xFFF; 230 | 231 | self.emit(Instruction::Lui { 232 | rd, 233 | imm: upper as i32, 234 | }); 235 | 236 | if lower != 0 { 237 | self.emit(Instruction::Addi { 238 | rd, 239 | rs1: rd, 240 | imm: lower as i16, 241 | }); 242 | } 243 | } 244 | } 245 | 246 | /// Record a label fixup for later resolution 247 | pub fn add_fixup(&mut self, label: impl Into) { 248 | let index = self.instructions.len() - 1; // Last emitted instruction 249 | self.fixups.push((index, label.into())); 250 | } 251 | 252 | /// Resolve all label fixups 253 | pub fn resolve_labels(&mut self) -> TranslateResult<()> { 254 | for (index, label) in &self.fixups { 255 | let target = self 256 | .labels 257 | .get(label) 258 | .ok_or_else(|| TranslateError::InvalidBranch(label.clone()))?; 259 | 260 | let offset = (*target as i32 - *index as i32) * 4; 261 | 262 | // Update the instruction with the resolved offset 263 | // This is a simplified approach - in reality you'd need to 264 | // properly patch the instruction based on its type 265 | // TODO: Implement proper instruction patching 266 | } 267 | 268 | Ok(()) 269 | } 270 | 271 | /// Convert context into instructions 272 | pub fn into_instructions(self) -> Vec { 273 | self.instructions 274 | } 275 | 276 | /// Get the function name 277 | pub fn function_name(&self) -> &str { 278 | &self.function_name 279 | } 280 | 281 | pub fn current_index(&self) -> u32 { 282 | self.instructions.len() as u32 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to the Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2025 zkir-llvm contributors 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | --------------------------------------------------------------------------------