Skip to content

Commit

Permalink
feat(aztec-nr): initial work for aztec public vm macro (#4400)
Browse files Browse the repository at this point in the history
fixes: #4269
  • Loading branch information
Maddiaa0 authored Feb 3, 2024
1 parent 14e57cb commit 0024590
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 42 deletions.
21 changes: 15 additions & 6 deletions avm-transpiler/src/instructions.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::fmt;
use std::fmt::{Debug, Formatter};

use crate::opcodes::AvmOpcode;

/// Common values of the indirect instruction flag
Expand Down Expand Up @@ -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();
Expand All @@ -55,15 +58,21 @@ 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());
}
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 {
Expand Down Expand Up @@ -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<u8> {
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(),
}
}
}
11 changes: 3 additions & 8 deletions avm-transpiler/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
31 changes: 20 additions & 11 deletions avm-transpiler/src/transpile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8> {
dbg_print_brillig_program(&brillig);
dbg_print_brillig_program(brillig);

let mut avm_instrs: Vec<AvmInstruction> = Vec::new();

Expand All @@ -38,6 +38,9 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec<u8> {
// 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,
Expand All @@ -49,7 +52,6 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec<u8> {
value: destination.to_usize() as u32,
},
],
..Default::default()
});
}
BrilligOpcode::BinaryIntOp {
Expand Down Expand Up @@ -80,6 +82,7 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec<u8> {
// 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,
Expand All @@ -97,6 +100,7 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec<u8> {
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)
Expand Down Expand Up @@ -125,6 +129,7 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec<u8> {
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,
Expand All @@ -139,17 +144,19 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec<u8> {
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 {
Expand All @@ -158,6 +165,7 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec<u8> {
} => {
avm_instrs.push(AvmInstruction {
opcode: AvmOpcode::MOV,
indirect: Some(0),
operands: vec![
AvmOperand::U32 {
value: source.to_usize() as u32,
Expand Down Expand Up @@ -223,6 +231,7 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec<u8> {
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},
Expand All @@ -234,6 +243,7 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec<u8> {
// 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},
Expand All @@ -254,9 +264,8 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec<u8> {

// 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
}
Expand All @@ -272,8 +281,8 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec<u8> {
/// 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<usize> {
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] {
Expand Down
8 changes: 5 additions & 3 deletions avm-transpiler/src/transpile_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,12 @@ pub enum AvmOrAcirContractFunction {
impl From<CompiledAcirContract> 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!(
Expand All @@ -91,7 +93,7 @@ impl From<CompiledAcirContract> 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 {
Expand Down
16 changes: 7 additions & 9 deletions avm-transpiler/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,26 @@ pub fn extract_brillig_from_acir(opcodes: &Vec<Opcode>) -> &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<AvmInstruction>) {
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());
}
}
18 changes: 18 additions & 0 deletions noir/aztec_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions yarn-project/acir-simulator/src/avm/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,16 @@ 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<AvmJournal>();

// Get contract function artifact
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 }));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ const INSTRUCTION_SET: InstructionSet = new Map<Opcode, DeserializableInstructio
[
[Add.opcode, Add],
[Sub.opcode, Sub],
[Sub.opcode, Sub],
[Mul.opcode, Mul],
[Div.opcode, Div],
[Eq.opcode, Eq],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ contract AvmTest {
#[aztec(private)]
fn constructor() {}

// Function name prefix "avm_" flags it for transpilation
unconstrained fn avm_addArgsReturn(argA: Field, argB: Field) -> 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
}

Expand Down

0 comments on commit 0024590

Please sign in to comment.