Skip to content

Commit

Permalink
feat: Consider block parameters in variable liveness (noir-lang/noir#…
Browse files Browse the repository at this point in the history
…5097)

chore(docs): Link to evmdiff for supported EVM chains (noir-lang/noir#5107)
chore: Avoid creating witness for simple multiplications (noir-lang/noir#5100)
feat: Add intrinsic to get if running inside an unconstrained context (noir-lang/noir#5098)
chore: Move turbofish changes to the elaborator (noir-lang/noir#5094)
fix: Apply self type from generic trait constraint before instantiating identifiers (noir-lang/noir#5087)
chore: reactivate gates report (noir-lang/noir#5084)
chore: automatically clear any unwanted directories in `test_programs` (noir-lang/noir#5081)
chore: update `nargo info` table to remove circuit size column
feat: Activate return_data in ACIR opcodes (noir-lang/noir#5080)
fix: Use plain integer addresses for opcodes in DAP disassembly view (noir-lang/noir#4941)
chore(experimental): Elaborate globals (noir-lang/noir#5069)
chore(experimental): Add types and traits to the elaborator (noir-lang/noir#5066)
feat: remove conditional compilation of `bn254_blackbox_solver` (noir-lang/noir#5058)
chore: use `bbup` to install `bb` (noir-lang/noir#5073)
feat: Sync from aztec-packages (noir-lang/noir#5070)
chore: update docs to represent zksync supporting necessary precompiles (noir-lang/noir#5071)
feat(stdlib)!: eddsa function using turbofish (noir-lang/noir#5050)
fix(frontend): Correctly monomorphize turbofish functions (noir-lang/noir#5049)
  • Loading branch information
AztecBot committed May 27, 2024
2 parents 5321892 + 1d99554 commit e76651e
Show file tree
Hide file tree
Showing 35 changed files with 706 additions and 227 deletions.
2 changes: 1 addition & 1 deletion .noir-sync-commit
Original file line number Diff line number Diff line change
@@ -1 +1 @@
23a64779c7a7384b852bf1c51295f3eb77573130
e4eb5f539f377fd3c2e1a874707ffce62a5bc10a
Original file line number Diff line number Diff line change
Expand Up @@ -210,16 +210,16 @@ impl CSatTransformer {
}
} else {
// No more usable elements left in the old opcode
opcode.linear_combinations = remaining_linear_terms;
break;
}
}
opcode.linear_combinations.extend(remaining_linear_terms);

// Constraint this intermediate_opcode to be equal to the temp variable by adding it into the IndexMap
// We need a unique name for our intermediate variable
// XXX: Another optimization, which could be applied in another algorithm
// If two opcodes have a large fan-in/out and they share a few common terms, then we should create intermediate variables for them
// Do some sort of subset matching algorithm for this on the terms of the polynomial

let inter_var = Self::get_or_create_intermediate_vars(
intermediate_variables,
intermediate_opcode,
Expand Down
61 changes: 53 additions & 8 deletions noir/noir-repo/acvm-repo/acvm_js/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ use crate::{
JsExecutionError, JsSolvedAndReturnWitness, JsWitnessMap, JsWitnessStack,
};

#[wasm_bindgen]
pub struct WasmBlackBoxFunctionSolver;

impl WasmBlackBoxFunctionSolver {
async fn initialize() -> WasmBlackBoxFunctionSolver {
WasmBlackBoxFunctionSolver
}
}

#[wasm_bindgen(js_name = "createBlackBoxSolver")]
pub async fn create_black_box_solver() -> WasmBlackBoxFunctionSolver {
WasmBlackBoxFunctionSolver::initialize().await
}

/// Executes an ACIR circuit to generate the solved witness from the initial witness.
///
/// @param {Uint8Array} circuit - A serialized representation of an ACIR circuit
Expand Down Expand Up @@ -50,6 +64,7 @@ pub async fn execute_circuit(
/// @returns {SolvedAndReturnWitness} The solved witness calculated by executing the circuit on the provided inputs, as well as the return witness indices as specified by the circuit.
#[wasm_bindgen(js_name = executeCircuitWithReturnWitness, skip_jsdoc)]
pub async fn execute_circuit_with_return_witness(
_solver: &WasmBlackBoxFunctionSolver,
program: Vec<u8>,
initial_witness: JsWitnessMap,
foreign_call_handler: ForeignCallHandler,
Expand Down Expand Up @@ -78,10 +93,28 @@ pub async fn execute_circuit_with_return_witness(

/// Executes an ACIR circuit to generate the solved witness from the initial witness.
///
/// @param {Uint8Array} program - A serialized representation of an ACIR program
/// @param {WitnessMap} initial_witness - The initial witness map defining all of the inputs to `program`.
/// @param {ForeignCallHandler} foreign_call_handler - A callback to process any foreign calls from the program.
/// @returns {WitnessStack} The solved witness calculated by executing the program on the provided inputs.
/// @param {&WasmBlackBoxFunctionSolver} solver - A black box solver.
/// @param {Uint8Array} circuit - A serialized representation of an ACIR circuit
/// @param {WitnessMap} initial_witness - The initial witness map defining all of the inputs to `circuit`..
/// @param {ForeignCallHandler} foreign_call_handler - A callback to process any foreign calls from the circuit.
/// @returns {WitnessMap} The solved witness calculated by executing the circuit on the provided inputs.
#[wasm_bindgen(js_name = executeCircuitWithBlackBoxSolver, skip_jsdoc)]
pub async fn execute_circuit_with_black_box_solver(
_solver: &WasmBlackBoxFunctionSolver,
program: Vec<u8>,
initial_witness: JsWitnessMap,
foreign_call_handler: ForeignCallHandler,
) -> Result<JsWitnessMap, Error> {
console_error_panic_hook::set_once();

let mut witness_stack =
execute_program_with_native_type_return(program, initial_witness, &foreign_call_handler)
.await?;
let witness_map =
witness_stack.pop().expect("Should have at least one witness on the stack").witness;
Ok(witness_map.into())
}

#[wasm_bindgen(js_name = executeProgram, skip_jsdoc)]
pub async fn execute_program(
program: Vec<u8>,
Expand All @@ -90,8 +123,21 @@ pub async fn execute_program(
) -> Result<JsWitnessStack, Error> {
console_error_panic_hook::set_once();

let solver = WasmBlackBoxFunctionSolver::initialize().await;

execute_program_with_black_box_solver(&solver, program, initial_witness, &foreign_call_handler)
.await
}

#[wasm_bindgen(js_name = executeProgramWithBlackBoxSolver, skip_jsdoc)]
pub async fn execute_program_with_black_box_solver(
_solver: &WasmBlackBoxFunctionSolver,
program: Vec<u8>,
initial_witness: JsWitnessMap,
foreign_call_executor: &ForeignCallHandler,
) -> Result<JsWitnessStack, Error> {
let witness_stack =
execute_program_with_native_type_return(program, initial_witness, &foreign_call_handler)
execute_program_with_native_type_return(program, initial_witness, foreign_call_executor)
.await?;

Ok(witness_stack.into())
Expand All @@ -117,12 +163,11 @@ async fn execute_program_with_native_program_and_return(
initial_witness: JsWitnessMap,
foreign_call_executor: &ForeignCallHandler,
) -> Result<WitnessStack, Error> {

let solver = Bn254BlackBoxSolver::initialize().await;
let blackbox_solver = Bn254BlackBoxSolver;
let executor = ProgramExecutor::new(
&program.functions,
&program.unconstrained_functions,
&Bn254BlackBoxSolver,
&blackbox_solver,
foreign_call_executor,
);
let witness_stack = executor.execute(initial_witness.into()).await?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::ssa::ir::function::Function;
pub(crate) fn convert_ssa_function(func: &Function, enable_debug_trace: bool) -> BrilligArtifact {
let mut brillig_context = BrilligContext::new(enable_debug_trace);

let mut function_context = FunctionContext::new(func, &mut brillig_context);
let mut function_context = FunctionContext::new(func);

brillig_context.enter_context(FunctionContext::function_id_to_function_label(func.id()));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::brillig::brillig_ir::{
use crate::ssa::ir::dfg::CallStack;
use crate::ssa::ir::instruction::ConstrainError;
use crate::ssa::ir::{
basic_block::{BasicBlock, BasicBlockId},
basic_block::BasicBlockId,
dfg::DataFlowGraph,
function::FunctionId,
instruction::{
Expand Down Expand Up @@ -49,8 +49,7 @@ impl<'block> BrilligBlock<'block> {
dfg: &DataFlowGraph,
) {
let live_in = function_context.liveness.get_live_in(&block_id);
let variables =
BlockVariables::new(live_in.clone(), function_context.all_block_parameters());
let variables = BlockVariables::new(live_in.clone());

brillig_context.set_allocated_registers(
variables
Expand All @@ -72,9 +71,9 @@ impl<'block> BrilligBlock<'block> {
let block_label = self.create_block_label_for_current_function(self.block_id);
self.brillig_context.enter_context(block_label);

// Convert the block parameters
self.convert_block_params(dfg);

let block = &dfg[self.block_id];
self.convert_block_params(block, dfg);

// Convert all of the instructions into the block
for instruction_id in block.instructions() {
Expand Down Expand Up @@ -134,12 +133,8 @@ impl<'block> BrilligBlock<'block> {
let target_block = &dfg[*destination_block];
for (src, dest) in arguments.iter().zip(target_block.parameters()) {
// Destinations are block parameters so they should have been allocated previously.
let destination = self.variables.get_block_param(
self.function_context,
*destination_block,
*dest,
dfg,
);
let destination =
self.variables.get_allocation(self.function_context, *dest, dfg);
let source = self.convert_ssa_value(*src, dfg);
self.pass_variable(source, destination);
}
Expand Down Expand Up @@ -206,10 +201,14 @@ impl<'block> BrilligBlock<'block> {
}
}

/// Converts SSA Block parameters into Brillig Registers.
fn convert_block_params(&mut self, block: &BasicBlock, dfg: &DataFlowGraph) {
for param_id in block.parameters() {
let value = &dfg[*param_id];
/// Allocates the block parameters that the given block is defining
fn convert_block_params(&mut self, dfg: &DataFlowGraph) {
// We don't allocate the block parameters here, we allocate the parameters the block is defining.
// Since predecessors to a block have to know where the parameters of the block are allocated to pass data to it,
// the block parameters need to be defined/allocated before the given block. Variable liveness provides when the block parameters are defined.
// For the entry block, the defined block params will be the params of the function + any extra params of blocks it's the immediate dominator of.
for param_id in self.function_context.liveness.defined_block_params(&self.block_id) {
let value = &dfg[param_id];
let param_type = match value {
Value::Param { typ, .. } => typ,
_ => unreachable!("ICE: Only Param type values should appear in block parameters"),
Expand All @@ -220,10 +219,10 @@ impl<'block> BrilligBlock<'block> {
// Be a valid pointer to the array.
// For slices, two registers are passed, the pointer to the data and a register holding the size of the slice.
Type::Numeric(_) | Type::Array(..) | Type::Slice(..) | Type::Reference(_) => {
self.variables.get_block_param(
self.variables.define_variable(
self.function_context,
self.block_id,
*param_id,
self.brillig_context,
param_id,
dfg,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use crate::{
BrilligContext,
},
ssa::ir::{
basic_block::BasicBlockId,
dfg::DataFlowGraph,
types::{CompositeType, Type},
value::ValueId,
Expand All @@ -21,18 +20,13 @@ use super::brillig_fn::FunctionContext;
#[derive(Debug, Default)]
pub(crate) struct BlockVariables {
available_variables: HashSet<ValueId>,
block_parameters: HashSet<ValueId>,
available_constants: HashMap<ValueId, BrilligVariable>,
}

impl BlockVariables {
/// Creates a BlockVariables instance. It uses the variables that are live in to the block and the global available variables (block parameters)
pub(crate) fn new(live_in: HashSet<ValueId>, all_block_parameters: HashSet<ValueId>) -> Self {
BlockVariables {
available_variables: live_in.into_iter().chain(all_block_parameters.clone()).collect(),
block_parameters: all_block_parameters,
..Default::default()
}
pub(crate) fn new(live_in: HashSet<ValueId>) -> Self {
BlockVariables { available_variables: live_in, ..Default::default() }
}

/// Returns all non-constant variables that have not been removed at this point.
Expand Down Expand Up @@ -92,16 +86,13 @@ impl BlockVariables {
brillig_context: &mut BrilligContext,
) {
assert!(self.available_variables.remove(value_id), "ICE: Variable is not available");
// Block parameters should not be deallocated
if !self.block_parameters.contains(value_id) {
let variable = function_context
.ssa_value_allocations
.get(value_id)
.expect("ICE: Variable allocation not found");
variable.extract_registers().iter().for_each(|register| {
brillig_context.deallocate_register(*register);
});
}
let variable = function_context
.ssa_value_allocations
.get(value_id)
.expect("ICE: Variable allocation not found");
variable.extract_registers().iter().for_each(|register| {
brillig_context.deallocate_register(*register);
});
}

/// For a given SSA value id, return the corresponding cached allocation.
Expand Down Expand Up @@ -155,27 +146,6 @@ impl BlockVariables {
pub(crate) fn dump_constants(&mut self) {
self.available_constants.clear();
}

/// For a given block parameter, return the allocation that was done globally to the function.
pub(crate) fn get_block_param(
&mut self,
function_context: &FunctionContext,
block_id: BasicBlockId,
value_id: ValueId,
dfg: &DataFlowGraph,
) -> BrilligVariable {
let value_id = dfg.resolve(value_id);
assert!(
function_context
.block_parameters
.get(&block_id)
.expect("Block not found")
.contains(&value_id),
"Value is not a block parameter"
);

*function_context.ssa_value_allocations.get(&value_id).expect("Block param not found")
}
}

/// Computes the length of an array. This will match with the indexes that SSA will issue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use crate::{
brillig::brillig_ir::{
artifact::{BrilligParameter, Label},
brillig_variable::{get_bit_size_from_ssa_type, BrilligVariable},
BrilligContext,
},
ssa::ir::{
basic_block::BasicBlockId,
Expand All @@ -14,57 +13,37 @@ use crate::{
value::ValueId,
},
};
use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet};
use fxhash::FxHashMap as HashMap;

use super::{brillig_block_variables::allocate_value, variable_liveness::VariableLiveness};
use super::variable_liveness::VariableLiveness;

pub(crate) struct FunctionContext {
pub(crate) function_id: FunctionId,
/// Map from SSA values its allocation. Since values can be only defined once in SSA form, we insert them here on when we allocate them at their definition.
pub(crate) ssa_value_allocations: HashMap<ValueId, BrilligVariable>,
/// Block parameters are pre allocated at the function level.
pub(crate) block_parameters: HashMap<BasicBlockId, Vec<ValueId>>,
/// The block ids of the function in reverse post order.
pub(crate) blocks: Vec<BasicBlockId>,
/// Liveness information for each variable in the function.
pub(crate) liveness: VariableLiveness,
}

impl FunctionContext {
/// Creates a new function context. It will allocate parameters for all blocks and compute the liveness of every variable.
pub(crate) fn new(function: &Function, brillig_context: &mut BrilligContext) -> Self {
/// Creates a new function context. It will compute the liveness of every variable.
pub(crate) fn new(function: &Function) -> Self {
let id = function.id();

let mut reverse_post_order = Vec::new();
reverse_post_order.extend_from_slice(PostOrder::with_function(function).as_slice());
reverse_post_order.reverse();

let mut block_parameters = HashMap::default();
let mut ssa_variable_to_register_or_memory = HashMap::default();

for &block_id in &reverse_post_order {
let block = &function.dfg[block_id];
let parameters = block.parameters().to_vec();
parameters.iter().for_each(|&value_id| {
let variable = allocate_value(value_id, brillig_context, &function.dfg);
ssa_variable_to_register_or_memory.insert(value_id, variable);
});
block_parameters.insert(block_id, parameters);
}

Self {
function_id: id,
ssa_value_allocations: ssa_variable_to_register_or_memory,
block_parameters,
ssa_value_allocations: HashMap::default(),
blocks: reverse_post_order,
liveness: VariableLiveness::from_function(function),
}
}

pub(crate) fn all_block_parameters(&self) -> HashSet<ValueId> {
self.block_parameters.values().flat_map(|parameters| parameters.iter()).cloned().collect()
}

/// Creates a function label from a given SSA function id.
pub(crate) fn function_id_to_function_label(function_id: FunctionId) -> Label {
function_id.to_string()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,9 +377,9 @@ mod tests {
builder.set_runtime(RuntimeType::Brillig);

let ssa = builder.finish();
let mut brillig_context = create_context();
let brillig_context = create_context();

let function_context = FunctionContext::new(ssa.main(), &mut brillig_context);
let function_context = FunctionContext::new(ssa.main());
(ssa, function_context, brillig_context)
}

Expand Down
Loading

0 comments on commit e76651e

Please sign in to comment.