From 6ed729617771d2359a7315663dc83814b1da2b58 Mon Sep 17 00:00:00 2001 From: Maddiaa <47148561+Maddiaa0@users.noreply.github.com> Date: Sat, 3 Feb 2024 00:06:26 +0000 Subject: [PATCH] feat(aztec-nr): initial work for aztec public vm macro (#4400) fixes: https://github.com/AztecProtocol/aztec-packages/issues/4269 --- avm-transpiler/src/instructions.rs | 21 +++++++++---- avm-transpiler/src/main.rs | 11 ++----- avm-transpiler/src/transpile.rs | 31 ++++++++++++------- avm-transpiler/src/transpile_contract.rs | 8 +++-- avm-transpiler/src/utils.rs | 16 +++++----- noir/aztec_macros/src/lib.rs | 18 +++++++++++ .../acir-simulator/src/avm/index.test.ts | 5 +-- .../serialization/bytecode_serialization.ts | 1 - .../contracts/avm_test_contract/src/main.nr | 5 +-- 9 files changed, 74 insertions(+), 42 deletions(-) diff --git a/avm-transpiler/src/instructions.rs b/avm-transpiler/src/instructions.rs index efc195cc5b0..198b327f8e3 100644 --- a/avm-transpiler/src/instructions.rs +++ b/avm-transpiler/src/instructions.rs @@ -1,3 +1,6 @@ +use std::fmt; +use std::fmt::{Debug, Formatter}; + use crate::opcodes::AvmOpcode; /// Common values of the indirect instruction flag @@ -36,7 +39,7 @@ impl AvmInstruction { if let Some(dst_tag) = self.dst_tag { out_str += format!(", dst_tag: {}", dst_tag as u8).as_str(); } - if self.operands.len() > 0 { + if !self.operands.is_empty() { out_str += ", operands: ["; for operand in &self.operands { out_str += format!("{}, ", operand.to_string()).as_str(); @@ -55,8 +58,7 @@ impl AvmInstruction { // TODO(4271): add in_tag alongside its support in TS if let Some(dst_tag) = self.dst_tag { // TODO(4271): make 8 bits when TS supports deserialization of 8 bit flags - //bytes.push(dst_tag as u8); - bytes.extend_from_slice(&(dst_tag as u32).to_be_bytes()); + bytes.extend_from_slice(&(dst_tag as u8).to_be_bytes()); } for operand in &self.operands { bytes.extend_from_slice(&operand.to_be_bytes()); @@ -64,6 +66,13 @@ impl AvmInstruction { bytes } } + +impl Debug for AvmInstruction { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_string()) + } +} + impl Default for AvmInstruction { fn default() -> Self { AvmInstruction { @@ -95,21 +104,21 @@ pub enum AvmTypeTag { pub enum AvmOperand { U32 { value: u32 }, // TODO(4267): Support operands of size other than 32 bits (for SET) - //U128 { value: u128 }, + U128 { value: u128 }, } impl AvmOperand { pub fn to_string(&self) -> String { match self { AvmOperand::U32 { value } => format!(" U32:{}", value), // TODO(4267): Support operands of size other than 32 bits (for SET) - //AvmOperand::U128 { value } => format!("U128:{}", value), + AvmOperand::U128 { value } => format!(" U128:{}", value), } } pub fn to_be_bytes(&self) -> Vec { match self { AvmOperand::U32 { value } => value.to_be_bytes().to_vec(), // TODO(4267): Support operands of size other than 32 bits (for SET) - //AvmOperand::U128 { value } => value.to_be_bytes().to_vec(), + AvmOperand::U128 { value } => value.to_be_bytes().to_vec(), } } } diff --git a/avm-transpiler/src/main.rs b/avm-transpiler/src/main.rs index 064bf343bbf..c81d1e0d682 100644 --- a/avm-transpiler/src/main.rs +++ b/avm-transpiler/src/main.rs @@ -25,14 +25,9 @@ fn main() { serde_json::from_str(&contract_json).expect("Unable to parse json"); // Skip if contract has "transpiled: true" flag! - if let Some(transpiled) = raw_json_obj.get("transpiled") { - match transpiled { - serde_json::Value::Bool(true) => { - warn!("Contract already transpiled. Skipping."); - return; // nothing to transpile - } - _ => (), - } + if let Some(serde_json::Value::Bool(true)) = raw_json_obj.get("transpiled") { + warn!("Contract already transpiled. Skipping."); + return; } // Parse json into contract object let contract: CompiledAcirContract = diff --git a/avm-transpiler/src/transpile.rs b/avm-transpiler/src/transpile.rs index 9785e41c7a1..e4f112f9ee8 100644 --- a/avm-transpiler/src/transpile.rs +++ b/avm-transpiler/src/transpile.rs @@ -11,7 +11,7 @@ use crate::utils::{dbg_print_avm_program, dbg_print_brillig_program}; /// Transpile a Brillig program to AVM bytecode pub fn brillig_to_avm(brillig: &Brillig) -> Vec { - dbg_print_brillig_program(&brillig); + dbg_print_brillig_program(brillig); let mut avm_instrs: Vec = Vec::new(); @@ -38,6 +38,9 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec { // TODO(4268): set in_tag to `field` avm_instrs.push(AvmInstruction { opcode: avm_opcode, + indirect: Some(0), + // TODO(4268): TEMPORARY - typescript wireFormat expects this + dst_tag: Some(AvmTypeTag::UINT32), operands: vec![ AvmOperand::U32 { value: lhs.to_usize() as u32, @@ -49,7 +52,6 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec { value: destination.to_usize() as u32, }, ], - ..Default::default() }); } BrilligOpcode::BinaryIntOp { @@ -80,6 +82,7 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec { // TODO(4268): support u8..u128 and use in_tag avm_instrs.push(AvmInstruction { opcode: avm_opcode, + indirect: Some(0), operands: vec![ AvmOperand::U32 { value: lhs.to_usize() as u32, @@ -97,6 +100,7 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec { BrilligOpcode::CalldataCopy { destination_address, size, offset } => { avm_instrs.push(AvmInstruction { opcode: AvmOpcode::CALLDATACOPY, + indirect: Some(0), operands: vec![ AvmOperand::U32 { value: *offset as u32, // cdOffset (calldata offset) @@ -125,6 +129,7 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec { let avm_loc = brillig_pcs_to_avm_pcs[*location]; avm_instrs.push(AvmInstruction { opcode: AvmOpcode::JUMPI, + indirect: Some(0), operands: vec![ AvmOperand::U32 { value: avm_loc as u32, @@ -139,17 +144,19 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec { BrilligOpcode::Const { destination, value } => { avm_instrs.push(AvmInstruction { opcode: AvmOpcode::SET, - dst_tag: Some(AvmTypeTag::UINT32), + indirect: Some(0), + dst_tag: Some(AvmTypeTag::UINT128), operands: vec![ // TODO(4267): support u8..u128 and use dst_tag - AvmOperand::U32 { - value: value.to_usize() as u32, + // value - temporarily as u128 - matching wireFormat in typescript + AvmOperand::U128 { + value: value.to_usize() as u128, }, + // dest offset AvmOperand::U32 { value: destination.to_usize() as u32, }, ], - ..Default::default() }); } BrilligOpcode::Mov { @@ -158,6 +165,7 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec { } => { avm_instrs.push(AvmInstruction { opcode: AvmOpcode::MOV, + indirect: Some(0), operands: vec![ AvmOperand::U32 { value: source.to_usize() as u32, @@ -223,6 +231,7 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec { BrilligOpcode::Stop { return_data_offset, return_data_size } => { avm_instrs.push(AvmInstruction { opcode: AvmOpcode::RETURN, + indirect: Some(0), operands: vec![ AvmOperand::U32 { value: *return_data_offset as u32}, AvmOperand::U32 { value: *return_data_size as u32}, @@ -234,6 +243,7 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec { // TODO(https://github.com/noir-lang/noir/issues/3113): Trap should support return data avm_instrs.push(AvmInstruction { opcode: AvmOpcode::REVERT, + indirect: Some(0), operands: vec![ //AvmOperand::U32 { value: *return_data_offset as u32}, //AvmOperand::U32 { value: *return_data_size as u32}, @@ -254,9 +264,8 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec { // Constructing bytecode from instructions let mut bytecode = Vec::new(); - for i in 0..avm_instrs.len() { - let instr_bytes = avm_instrs[i].to_bytes(); - bytecode.extend_from_slice(&instr_bytes); + for instruction in avm_instrs { + bytecode.extend_from_slice(&instruction.to_bytes()); } bytecode } @@ -272,8 +281,8 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec { /// returns: an array where each index is a Brillig pc, /// and each value is the corresponding AVM pc. fn map_brillig_pcs_to_avm_pcs(initial_offset: usize, brillig: &Brillig) -> Vec { - let mut pc_map = Vec::with_capacity(brillig.bytecode.len()); - pc_map.resize(brillig.bytecode.len(), 0); + let mut pc_map = vec![0; brillig.bytecode.len()]; + pc_map[0] = initial_offset; for i in 0..brillig.bytecode.len() - 1 { let num_avm_instrs_for_this_brillig_instr = match &brillig.bytecode[i] { diff --git a/avm-transpiler/src/transpile_contract.rs b/avm-transpiler/src/transpile_contract.rs index 2a8b762139f..9b342b4d870 100644 --- a/avm-transpiler/src/transpile_contract.rs +++ b/avm-transpiler/src/transpile_contract.rs @@ -76,10 +76,12 @@ pub enum AvmOrAcirContractFunction { impl From for TranspiledContract { fn from(contract: CompiledAcirContract) -> Self { let mut functions = Vec::new(); + + // Note, in aztec_macros/lib.rs, avm_ prefix is pushed to function names with the #[aztec(public-vm)] tag + let re = Regex::new(r"avm_.*$").unwrap(); for function in contract.functions { // TODO(4269): once functions are tagged for transpilation to AVM, check tag - let re = Regex::new(r"avm_.*$").unwrap(); - if function.function_type == ContractFunctionType::Unconstrained + if function.function_type == ContractFunctionType::Open && re.is_match(function.name.as_str()) { info!( @@ -91,7 +93,7 @@ impl From for TranspiledContract { let brillig = extract_brillig_from_acir(&acir_circuit.opcodes); // Transpile to AVM - let avm_bytecode = brillig_to_avm(&brillig); + let avm_bytecode = brillig_to_avm(brillig); // Push modified function entry to ABI functions.push(AvmOrAcirContractFunction::Avm(AvmContractFunction { diff --git a/avm-transpiler/src/utils.rs b/avm-transpiler/src/utils.rs index 2f31c900450..e7a633e7c15 100644 --- a/avm-transpiler/src/utils.rs +++ b/avm-transpiler/src/utils.rs @@ -14,28 +14,26 @@ pub fn extract_brillig_from_acir(opcodes: &Vec) -> &Brillig { panic!("There should only be one brillig opcode"); } let opcode = &opcodes[0]; - let brillig = match opcode { + match opcode { Opcode::Brillig(brillig) => brillig, _ => panic!("Tried to extract a Brillig program from its ACIR wrapper opcode, but the opcode doesn't contain Brillig!"), - }; - brillig + } } /// Print inputs, outputs, and instructions in a Brillig program pub fn dbg_print_brillig_program(brillig: &Brillig) { debug!("Printing Brillig program..."); debug!("\tInputs: {:?}", brillig.inputs); - for i in 0..brillig.bytecode.len() { - let instr = &brillig.bytecode[i]; - debug!("\tPC:{0} {1:?}", i, instr); + for (i, instruction) in brillig.bytecode.iter().enumerate() { + debug!("\tPC:{0} {1:?}", i, instruction); } debug!("\tOutputs: {:?}", brillig.outputs); } /// Print each instruction in an AVM program -pub fn dbg_print_avm_program(avm_program: &Vec) { +pub fn dbg_print_avm_program(avm_program: &[AvmInstruction]) { debug!("Printing AVM program..."); - for i in 0..avm_program.len() { - debug!("\tPC:{0}: {1}", i, &avm_program[i].to_string()); + for (i, instruction) in avm_program.iter().enumerate() { + debug!("\tPC:{0}: {1}", i, &instruction.to_string()); } } diff --git a/noir/aztec_macros/src/lib.rs b/noir/aztec_macros/src/lib.rs index b8cda78ff34..228664c83fb 100644 --- a/noir/aztec_macros/src/lib.rs +++ b/noir/aztec_macros/src/lib.rs @@ -424,6 +424,10 @@ fn transform_module( transform_function("Public", func, storage_defined) .map_err(|err| (err, crate_graph.root_file_id))?; has_transformed_module = true; + } else if is_custom_attribute(&secondary_attribute, "aztec(public-vm)") { + transform_vm_function(func, storage_defined) + .map_err(|err| (err, crate_graph.root_file_id))?; + has_transformed_module = true; } } // Add the storage struct to the beginning of the function if it is unconstrained in an aztec contract @@ -636,6 +640,20 @@ fn transform_function( Ok(()) } +/// Transform a function to work with AVM bytecode +fn transform_vm_function( + func: &mut NoirFunction, + _storage_defined: bool, +) -> Result<(), AztecMacroError> { + // We want the function to be seen as a public function + func.def.is_open = true; + + // NOTE: the line below is a temporary hack to trigger external transpilation tools + // It will be removed once the transpiler is integrated into the Noir compiler + func.def.name.0.contents = format!("avm_{}", func.def.name.0.contents); + Ok(()) +} + /// Transform Unconstrained /// /// Inserts the following code at the beginning of an unconstrained function diff --git a/yarn-project/acir-simulator/src/avm/index.test.ts b/yarn-project/acir-simulator/src/avm/index.test.ts index 9826a89d8e5..a9e05a97ea2 100644 --- a/yarn-project/acir-simulator/src/avm/index.test.ts +++ b/yarn-project/acir-simulator/src/avm/index.test.ts @@ -39,7 +39,7 @@ describe('avm', () => { describe('testing transpiled Noir contracts', () => { // TODO(https://github.com/AztecProtocol/aztec-packages/issues/4361): sync wire format w/transpiler. - it.skip('Should execute contract function that performs addition', async () => { + it('Should execute contract function that performs addition', async () => { const calldata: Fr[] = [new Fr(1), new Fr(2)]; const journal = mock(); @@ -47,7 +47,8 @@ describe('avm', () => { const addArtifact = AvmTestContractArtifact.functions.find(f => f.name === 'avm_addArgsReturn')!; // Decode bytecode into instructions - const instructions = decodeFromBytecode(Buffer.from(addArtifact.bytecode, 'base64')); + const instructionsBytecode = Buffer.from(addArtifact.bytecode, 'base64'); + const instructions = decodeFromBytecode(instructionsBytecode); // Execute instructions const context = new AvmMachineState(initExecutionEnvironment({ calldata })); diff --git a/yarn-project/acir-simulator/src/avm/serialization/bytecode_serialization.ts b/yarn-project/acir-simulator/src/avm/serialization/bytecode_serialization.ts index b0285018980..8b200e5800c 100644 --- a/yarn-project/acir-simulator/src/avm/serialization/bytecode_serialization.ts +++ b/yarn-project/acir-simulator/src/avm/serialization/bytecode_serialization.ts @@ -56,7 +56,6 @@ const INSTRUCTION_SET: InstructionSet = new Map pub Field { + // Public-vm macro will prefix avm to the function name for transpilation + #[aztec(public-vm)] + fn addArgsReturn(argA: Field, argB: Field) -> pub Field { argA + argB }