Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(aztec-nr): initial work for aztec public vm macro #4400

Merged
merged 5 commits into from
Feb 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

[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
}
Comment on lines -12 to 16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!


Expand Down
Loading