Skip to content

Commit

Permalink
based on david's
Browse files Browse the repository at this point in the history
  • Loading branch information
fcarreiro committed Mar 7, 2024
1 parent 480161f commit 4102e8d
Show file tree
Hide file tree
Showing 20 changed files with 459 additions and 214 deletions.
22 changes: 0 additions & 22 deletions avm-transpiler/src/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,20 @@
/// Keep updated with TS and yellow paper!
#[derive(Copy, Clone)]
pub enum AvmOpcode {
// Compute
// Compute - Arithmetic
ADD,
SUB,
MUL,
DIV,
// Compute - Comparators
EQ,
LT,
LTE,
// Compute - Bitwise
AND,
OR,
XOR,
NOT,
SHL,
SHR,
// Compute - Type Conversions
CAST,

// Execution Environment
ADDRESS,
STORAGEADDRESS,
ORIGIN,
Expand All @@ -32,7 +25,6 @@ pub enum AvmOpcode {
FEEPERL2GAS,
FEEPERDAGAS,
CONTRACTCALLDEPTH,
// Execution Environment - Globals
CHAINID,
VERSION,
BLOCKNUMBER,
Expand All @@ -41,25 +33,17 @@ pub enum AvmOpcode {
BLOCKL1GASLIMIT,
BLOCKL2GASLIMIT,
BLOCKDAGASLIMIT,
// Execution Environment - Calldata
CALLDATACOPY,

// Machine State
// Machine State - Gas
L1GASLEFT,
L2GASLEFT,
DAGASLEFT,
// Machine State - Internal Control Flow
JUMP,
JUMPI,
INTERNALCALL,
INTERNALRETURN,
// Machine State - Memory
SET,
MOV,
CMOV,

// World State
SLOAD, // Public Storage
SSTORE, // Public Storage
NOTEHASHEXISTS, // Notes & Nullifiers
Expand All @@ -68,19 +52,13 @@ pub enum AvmOpcode {
EMITNULLIFIER, // Notes & Nullifiers
L1TOL2MSGEXISTS, // Messages
HEADERMEMBER, // Archive tree & Headers

// Accrued Substate
EMITUNENCRYPTEDLOG,
SENDL2TOL1MSG,

// Control Flow - Contract Calls
CALL,
STATICCALL,
DELEGATECALL,
RETURN,
REVERT,

// Gadgets
KECCAK,
POSEIDON,
SHA256,
Expand Down
86 changes: 85 additions & 1 deletion avm-transpiler/src/transpile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec<u8> {
lhs,
rhs,
} => {
assert!(is_integral_bit_size(*bit_size), "BinaryIntOp::{:?} bit_size must be integral, got {:?}", op, bit_size);
let avm_opcode = match op {
BinaryIntOp::Add => AvmOpcode::ADD,
BinaryIntOp::Sub => AvmOpcode::SUB,
Expand Down Expand Up @@ -179,7 +180,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(ALL_DIRECT),
indirect: Some(ZEROTH_OPERAND_INDIRECT),
operands: vec![
AvmOperand::U32 { value: *return_data_offset as u32 },
AvmOperand::U32 { value: *return_data_size as u32 },
Expand Down Expand Up @@ -238,6 +239,7 @@ fn handle_foreign_call(
inputs: &Vec<ValueOrArray>,
) {
match function {
"avmOpcodeCall" => handle_external_call(avm_instrs, destinations, inputs),
"amvOpcodeEmitUnencryptedLog" => {
handle_emit_unencrypted_log(avm_instrs, destinations, inputs)
},
Expand Down Expand Up @@ -271,6 +273,81 @@ fn handle_foreign_call(
}
}

/// Handle an AVM CALL
/// (an external 'call' brillig foreign call was encountered)
/// Adds the new instruction to the avm instructions list.
fn handle_external_call(
avm_instrs: &mut Vec<AvmInstruction>,
destinations: &Vec<ValueOrArray>,
inputs: &Vec<ValueOrArray>,
) {
if destinations.len() != 2 || inputs.len() != 4 {
panic!(
"Transpiler expects ForeignCall::CALL to have 2 destinations and 4 inputs, got {} and {}.
Make sure your call instructions's input/return arrays have static length (`[Field; <size>]`)!",
destinations.len(),
inputs.len()
);
}
let gas_offset_maybe = inputs[0];
let gas_offset = match gas_offset_maybe {
ValueOrArray::HeapArray(HeapArray { pointer, size }) => {
assert!(size == 3, "Call instruction's gas input should be a HeapArray of size 3 (`[l1Gas, l2Gas, daGas]`)");
pointer.0 as u32
}
ValueOrArray::HeapVector(_) => panic!("Call instruction's gas input must be a HeapArray, not a HeapVector. Make sure you are explicitly defining its size as 3 (`[l1Gas, l2Gas, daGas]`)!"),
_ => panic!("Call instruction's gas input should be a HeapArray"),
};
let address_offset = match &inputs[1] {
ValueOrArray::MemoryAddress(offset) => offset.to_usize() as u32,
_ => panic!("Call instruction's target address input should be a basic MemoryAddress",),
};
let args_offset_maybe = inputs[2];
let (args_offset, args_size) = match args_offset_maybe {
ValueOrArray::HeapArray(HeapArray { pointer, size }) => (pointer.0 as u32, size as u32),
ValueOrArray::HeapVector(_) => panic!("Call instruction's args must be a HeapArray, not a HeapVector. Make sure you are explicitly defining its size (`[arg0, arg1, ... argN]`)!"),
_ => panic!("Call instruction's args input should be a HeapArray input"),
};
let temporary_function_selector_offset = match &inputs[3] {
ValueOrArray::MemoryAddress(offset) => offset.to_usize() as u32,
_ => panic!(
"Call instruction's temporary function selector input should be a basic MemoryAddress",
),
};

let ret_offset_maybe = destinations[0];
let (ret_offset, ret_size) = match ret_offset_maybe {
ValueOrArray::HeapArray(HeapArray { pointer, size }) => (pointer.0 as u32, size as u32),
ValueOrArray::HeapVector(_) => panic!("Call instruction's return data must be a HeapArray, not a HeapVector. Make sure you are explicitly defining its size (`let returnData: [Field; <size>] = ...`)!"),
_ => panic!("Call instruction's returnData destination should be a HeapArray input"),
};
let success_offset = match &destinations[1] {
ValueOrArray::MemoryAddress(offset) => offset.to_usize() as u32,
_ => panic!("Call instruction's success destination should be a basic MemoryAddress",),
};
avm_instrs.push(AvmInstruction {
opcode: AvmOpcode::CALL,
indirect: Some(0b01101), // (left to right) selector direct, ret offset INDIRECT, args offset INDIRECT, address offset direct, gas offset INDIRECT
operands: vec![
AvmOperand::U32 { value: gas_offset },
AvmOperand::U32 {
value: address_offset,
},
AvmOperand::U32 { value: args_offset },
AvmOperand::U32 { value: args_size },
AvmOperand::U32 { value: ret_offset },
AvmOperand::U32 { value: ret_size },
AvmOperand::U32 {
value: success_offset,
},
AvmOperand::U32 {
value: temporary_function_selector_offset,
},
],
..Default::default()
});
}

/// Handle an AVM NOTEHASHEXISTS instruction
/// Adds the new instruction to the avm instructions list.
fn handle_note_hash_exists(
Expand Down Expand Up @@ -913,6 +990,13 @@ fn map_brillig_pcs_to_avm_pcs(initial_offset: usize, brillig: &Brillig) -> Vec<u
pc_map
}

fn is_integral_bit_size(bit_size: u32) -> bool {
match bit_size {
1 | 8 | 16 | 32 | 64 | 128 => true,
_ => false,
}
}

fn tag_from_bit_size(bit_size: u32) -> AvmTypeTag {
match bit_size {
1 => AvmTypeTag::UINT8, // temp workaround
Expand Down
38 changes: 35 additions & 3 deletions noir-projects/aztec-nr/aztec/src/context/avm.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use dep::protocol_types::{address::{AztecAddress, EthAddress}, constants::L1_TO_L2_MESSAGE_LENGTH};
use dep::protocol_types::traits::{Serialize};
use dep::protocol_types::abis::function_selector::FunctionSelector;

// Getters that will be converted by the transpiler into their
// own opcodes
Expand Down Expand Up @@ -80,9 +81,19 @@ impl AVMContext {
#[oracle(avmOpcodeSendL2ToL1Msg)]
pub fn send_l2_to_l1_msg(self, recipient: EthAddress, content: Field) {}

///////////////////////////////////////////////////////////////////////////
// The functions below allow interface-equivalence with PrivateContext
///////////////////////////////////////////////////////////////////////////
#[oracle(avmOpcodeCall)]
fn call<ARGS_COUNT, RET_SIZE>(
self,
gas: [Field; 3], // gas allocation: [l1Gas, l2Gas, daGas]
address: AztecAddress,
args: [Field; ARGS_COUNT],
temporary_function_selector: Field
) -> ([Field; RET_SIZE], u8) {}
// ^ return data ^ success

////////////////////////////////////////////////////////////////////////////////
// The functions below allow interface-equivalence with current public/private
////////////////////////////////////////////////////////////////////////////////
pub fn this_address(self) -> AztecAddress {
self.address()
}
Expand All @@ -107,4 +118,25 @@ impl AVMContext {
// Cannot nullify pending commitments in AVM, so `nullified_commitment` is not used
self.emit_nullifier(nullifier);
}

pub fn call_public_function<ARGS_COUNT, RET_SIZE>(
self: Self,
contract_address: AztecAddress,
temporary_function_selector: FunctionSelector,
args: [Field; ARGS_COUNT]
) -> [Field; RET_SIZE] {
let gas = [/*l1Gas*/42, /*l2Gas*/24, /*daGas*/420];

let results = self.call(
gas,
contract_address,
args,
temporary_function_selector.to_field()
);
let returnData: [Field; RET_SIZE] = results.0;
let success: u8 = results.1;
assert(success == 1, "Nested call failed!");

returnData
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ contract AvmTest {
// Libs
use dep::aztec::state_vars::PublicMutable;
use dep::aztec::protocol_types::{address::{AztecAddress, EthAddress}, constants::L1_TO_L2_MESSAGE_LENGTH};
use dep::aztec::protocol_types::abis::function_selector::FunctionSelector;
use dep::compressed_string::CompressedString;

// avm lib
Expand Down Expand Up @@ -214,4 +215,38 @@ contract AvmTest {
fn send_l2_to_l1_msg(recipient: EthAddress, content: Field) {
context.message_portal(recipient, content)
}

// Directly call the external call opcode to initiate a nested call to the add function
#[aztec(public-vm)]
fn raw_nested_call_to_add(argA: Field, argB: Field) -> pub Field {
let selector = FunctionSelector::from_signature("avm_addArgsReturn(Field,Field)").to_field();
let gas = [/*l1Gas*/42, /*l2Gas*/24, /*daGas*/420];

// Nested call
let results = context.call(gas, context.address(), [argA, argB], selector);
let returnData: [Field; 1] = results.0;
// this explicit size ^ is necessary to ensure that retSize is compile-time
// (ensure the returnData is in a HeapArray not a HeapVector)
let success: u8 = results.1;

assert(success == 1, "Call failed");

let addResult = returnData[0];
// assert(addResult == argA + argB, "Return value from nested 'add' call incorrect");
addResult
}

// Use the `call_public_function` wrapper to initiate a nested call to the add function
#[aztec(public-vm)]
fn nested_call_to_add(argA: Field, argB: Field) -> pub Field {
let selector = FunctionSelector::from_signature("avm_addArgsReturn(Field,Field)");

// Nested call using standard context interface function
let returnData: [Field; 1] = context.call_public_function(context.address(), selector, [argA, argB]);
// this explicit size ^ is necessary to ensure that retSize is compile-time
// (ensure the returnData is in a HeapArray not a HeapVector)

let addResult = returnData[0];
addResult
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -831,14 +831,15 @@ impl<'block> BrilligBlock<'block> {
_ => unreachable!("ICE: array set on non-array"),
};

let one = self.brillig_context.make_usize_constant(1_usize.into());
let bit_size: u32 = 64; // REFERENCE_COUNT_SIZE?
let one = self.brillig_context.make_constant(1_usize.into(), bit_size);
let condition = self.brillig_context.allocate_register();

self.brillig_context.binary_instruction(
reference_count,
one,
condition,
BrilligBinaryOp::Field { op: BinaryFieldOp::Equals },
BrilligBinaryOp::Integer { op: BinaryIntOp::Equals, bit_size }
);

self.brillig_context.branch_instruction(condition, |ctx, cond| {
Expand Down
25 changes: 8 additions & 17 deletions noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ use acvm::{
FieldElement,
};
use debug_show::DebugShow;
use num_bigint::BigUint;

/// The Brillig VM does not apply a limit to the memory address space,
/// As a convention, we take use 64 bits. This means that we assume that
Expand Down Expand Up @@ -217,7 +216,7 @@ impl BrilligContext {
array_ptr,
index,
index_of_element_in_memory,
BrilligBinaryOp::Field { op: BinaryFieldOp::Add },
BrilligBinaryOp::Integer { op: BinaryIntOp::Add, bit_size: BRILLIG_MEMORY_ADDRESSING_BIT_SIZE },
);

self.load_instruction(result, index_of_element_in_memory);
Expand All @@ -239,7 +238,7 @@ impl BrilligContext {
array_ptr,
index,
index_of_element_in_memory,
BrilligBinaryOp::Field { op: BinaryFieldOp::Add },
BrilligBinaryOp::Integer { op: BinaryIntOp::Add, bit_size: BRILLIG_MEMORY_ADDRESSING_BIT_SIZE },
);

self.store_instruction(index_of_element_in_memory, value);
Expand Down Expand Up @@ -744,20 +743,12 @@ impl BrilligContext {
value_to_truncate.bit_size
);

let mask = BigUint::from(2_u32).pow(bit_size) - BigUint::from(1_u32);
let mask_constant = self.make_constant(
FieldElement::from_be_bytes_reduce(&mask.to_bytes_be()).into(),
value_to_truncate.bit_size,
);

self.binary_instruction(
value_to_truncate.address,
mask_constant,
destination_of_truncated_value.address,
BrilligBinaryOp::Integer { op: BinaryIntOp::And, bit_size: value_to_truncate.bit_size },
);

self.deallocate_register(mask_constant);
// We cast back and forth to ensure that the value is truncated.
let intermediate_register =
SingleAddrVariable { address: self.allocate_register(), bit_size };
self.cast_instruction(intermediate_register, value_to_truncate);
self.cast_instruction(destination_of_truncated_value, intermediate_register);
self.deallocate_register(intermediate_register.address);
}

/// Emits a stop instruction
Expand Down
Loading

0 comments on commit 4102e8d

Please sign in to comment.