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

chore: Use BrilligCall for unconstrained main and update AVM transpiler #5797

Merged
merged 7 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
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
Loading