Skip to content

Commit

Permalink
chore: Use BrilligCall for unconstrained main and update AVM transpil…
Browse files Browse the repository at this point in the history
…er (#5797)

A follow-up to #5737

Separate PR as this touches mainly the AVM transpiler and not Noir
codegen itself. Look in PR #5737 for full details about the switch, but
basically we are moving away from the `Brillig` opcode to a
`BrilligCall` opcode that contains a Brillig call opcode. The AVM needs
to be updated to account for this change.
  • Loading branch information
vezenovm authored Apr 16, 2024
1 parent 86d8176 commit 3fb94c0
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 32 deletions.
20 changes: 11 additions & 9 deletions avm-transpiler/src/transpile.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use acvm::acir::brillig::Opcode as BrilligOpcode;
use acvm::acir::circuit::brillig::Brillig;

use acvm::brillig_vm::brillig::{
BinaryFieldOp, BinaryIntOp, BlackBoxOp, HeapArray, HeapVector, MemoryAddress, ValueOrArray,
Expand All @@ -14,17 +13,17 @@ use crate::opcodes::AvmOpcode;
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);
pub fn brillig_to_avm(brillig_bytecode: &[BrilligOpcode]) -> Vec<u8> {
dbg_print_brillig_program(brillig_bytecode);

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

// Map Brillig pcs to AVM pcs
// (some Brillig instructions map to >1 AVM instruction)
let brillig_pcs_to_avm_pcs = map_brillig_pcs_to_avm_pcs(avm_instrs.len(), brillig);
let brillig_pcs_to_avm_pcs = map_brillig_pcs_to_avm_pcs(avm_instrs.len(), brillig_bytecode);

// Transpile a Brillig instruction to one or more AVM instructions
for brillig_instr in &brillig.bytecode {
for brillig_instr in brillig_bytecode {
match brillig_instr {
BrilligOpcode::BinaryFieldOp {
destination,
Expand Down Expand Up @@ -1090,12 +1089,15 @@ fn handle_storage_read(
/// brillig: the Brillig program
/// 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![0; brillig.bytecode.len()];
fn map_brillig_pcs_to_avm_pcs(
initial_offset: usize,
brillig_bytecode: &[BrilligOpcode],
) -> Vec<usize> {
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] {
for i in 0..brillig_bytecode.len() - 1 {
let num_avm_instrs_for_this_brillig_instr = match &brillig_bytecode[i] {
BrilligOpcode::Const { bit_size: 254, .. } => 2,
_ => 1,
};
Expand Down
6 changes: 3 additions & 3 deletions avm-transpiler/src/transpile_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use acvm::acir::circuit::Program;

use crate::transpile::brillig_to_avm;
use crate::utils::extract_brillig_from_acir;
use crate::utils::extract_brillig_from_acir_program;

/// Representation of a contract with some transpiled functions
#[derive(Debug, Serialize, Deserialize)]
Expand Down Expand Up @@ -88,10 +88,10 @@ impl From<CompiledAcirContract> for TranspiledContract {
);
// Extract Brillig Opcodes from acir
let acir_program = function.bytecode;
let brillig = extract_brillig_from_acir(&acir_program.functions[0].opcodes);
let brillig_bytecode = extract_brillig_from_acir_program(&acir_program);

// Transpile to AVM
let avm_bytecode = brillig_to_avm(brillig);
let avm_bytecode = brillig_to_avm(brillig_bytecode);

// Push modified function entry to ABI
functions.push(AvmOrAcirContractFunction::Avm(AvmContractFunction {
Expand Down
47 changes: 30 additions & 17 deletions avm-transpiler/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,46 @@
use log::debug;

use acvm::acir::circuit::brillig::Brillig;
use acvm::acir::circuit::Opcode;
use acvm::acir::brillig::Opcode as BrilligOpcode;
use acvm::acir::circuit::{Opcode, Program};

use crate::instructions::AvmInstruction;

/// Extract the Brillig program from its ACIR wrapper instruction.
/// An Noir unconstrained function compiles to one ACIR instruction
/// wrapping a Brillig program. This function just extracts that Brillig
/// assuming the 0th ACIR opcode is the wrapper.
pub fn extract_brillig_from_acir(opcodes: &Vec<Opcode>) -> &Brillig {
if opcodes.len() != 1 {
panic!("An AVM program should be contained entirely in only a single ACIR opcode flagged as 'Brillig'");
}
let opcode = &opcodes[0];
match opcode {
Opcode::Brillig(brillig) => brillig,
/// Extract the Brillig program from its `Program` wrapper.
/// Noir entry point unconstrained functions are compiled to their own list contained
/// as part of a full program. Function calls are then accessed through a function
/// pointer opcode in ACIR that fetches those unconstrained functions from the main list.
/// This function just extracts Brillig bytecode, with the assumption that the
/// 0th unconstrained function in the full `Program` structure.
pub fn extract_brillig_from_acir_program(program: &Program) -> &[BrilligOpcode] {
assert_eq!(
program.functions.len(),
1,
"An AVM program should have only a single ACIR function flagged as 'BrilligCall'"
);
let opcodes = &program.functions[0].opcodes;
assert_eq!(
opcodes.len(),
1,
"An AVM program should have only a single ACIR function flagged as 'BrilligCall'"
);
match opcodes[0] {
Opcode::BrilligCall { .. } => {}
_ => panic!("Tried to extract a Brillig program from its ACIR wrapper opcode, but the opcode doesn't contain Brillig!"),
}
assert_eq!(
program.unconstrained_functions.len(),
1,
"An AVM program should be contained entirely in only a single `Brillig` function"
);
&program.unconstrained_functions[0].bytecode
}

/// Print inputs, outputs, and instructions in a Brillig program
pub fn dbg_print_brillig_program(brillig: &Brillig) {
pub fn dbg_print_brillig_program(brillig_bytecode: &[BrilligOpcode]) {
debug!("Printing Brillig program...");
debug!("\tInputs: {:?}", brillig.inputs);
for (i, instruction) in brillig.bytecode.iter().enumerate() {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,14 +360,18 @@ impl<'a> Context<'a> {

// We specifically do not attempt execution of the brillig code being generated as this can result in it being
// replaced with constraints on witnesses to the program outputs.
let output_values = self.acir_context.brillig(
let output_values = self.acir_context.brillig_call(
self.current_side_effects_enabled_var,
code,
&code,
inputs,
outputs,
false,
true,
// We are guaranteed to have a Brillig function pointer of `0` as main itself is marked as unconstrained
0,
)?;
self.shared_context.insert_generated_brillig(main_func.id(), 0, code);

let output_vars: Vec<_> = output_values
.iter()
.flat_map(|value| value.clone().flatten())
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/circuits.js/src/constants.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_MAGIC_VALUE =
0xe7af816635466f128568edb04c9fa024f6c87fb9010fdbffa68b3d99n;
export const DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE =
0x85864497636cf755ae7bde03f267ce01a520981c21c3682aaf82a631n;
export const DEPLOYER_CONTRACT_ADDRESS = 0x2b9c3612fc04623512ab47f68537c7b63a90b3efcc39f622f42d1820bfeb93acn;
export const DEPLOYER_CONTRACT_ADDRESS = 0x0b98aeb0111208b95d8d71f484f849d7ab44b3e34c545d13736a707ce3cb0839n;
export const L1_TO_L2_MESSAGE_ORACLE_CALL_LENGTH = 17;
export const MAX_NOTE_FIELDS_LENGTH = 20;
export const GET_NOTE_ORACLE_RETURN_LENGTH = 23;
Expand Down

0 comments on commit 3fb94c0

Please sign in to comment.