Skip to content

Commit

Permalink
Merge branch 'master' into tf/pull-acirgen-out-of-ssa
Browse files Browse the repository at this point in the history
* master:
  fix: take blackbox function outputs into account when merging expressions (#6532)
  chore: Add `Instruction::MakeArray` to SSA (#6071)
  feat(profiler): Reduce memory in Brillig execution flamegraph (#6538)
  chore: convert some tests to use SSA parser (#6543)
  • Loading branch information
TomAFrench committed Nov 18, 2024
2 parents 81a46d8 + 713df69 commit 3651269
Show file tree
Hide file tree
Showing 41 changed files with 896 additions and 627 deletions.
72 changes: 60 additions & 12 deletions acvm-repo/acvm/src/compiler/optimizers/merge_expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,18 @@ impl MergeExpressionsOptimizer {

// Returns the input witnesses used by the opcode
fn witness_inputs<F: AcirField>(&self, opcode: &Opcode<F>) -> BTreeSet<Witness> {
let mut witnesses = BTreeSet::new();
match opcode {
Opcode::AssertZero(expr) => CircuitSimulator::expr_wit(expr),
Opcode::BlackBoxFuncCall(bb_func) => bb_func.get_input_witnesses(),
Opcode::BlackBoxFuncCall(bb_func) => {
let mut witnesses = bb_func.get_input_witnesses();
witnesses.extend(bb_func.get_outputs_vec());

witnesses
}
Opcode::Directive(Directive::ToLeRadix { a, .. }) => CircuitSimulator::expr_wit(a),
Opcode::MemoryOp { block_id: _, op, predicate } => {
//index et value, et predicate
let mut witnesses = BTreeSet::new();
witnesses.extend(CircuitSimulator::expr_wit(&op.index));
let mut witnesses = CircuitSimulator::expr_wit(&op.index);
witnesses.extend(CircuitSimulator::expr_wit(&op.value));
if let Some(p) = predicate {
witnesses.extend(CircuitSimulator::expr_wit(p));
Expand All @@ -173,6 +176,7 @@ impl MergeExpressionsOptimizer {
init.iter().cloned().collect()
}
Opcode::BrilligCall { inputs, outputs, .. } => {
let mut witnesses = BTreeSet::new();
for i in inputs {
witnesses.extend(self.brillig_input_wit(i));
}
Expand All @@ -182,12 +186,9 @@ impl MergeExpressionsOptimizer {
witnesses
}
Opcode::Call { id: _, inputs, outputs, predicate } => {
for i in inputs {
witnesses.insert(*i);
}
for i in outputs {
witnesses.insert(*i);
}
let mut witnesses: BTreeSet<Witness> = BTreeSet::from_iter(inputs.iter().copied());
witnesses.extend(outputs);

if let Some(p) = predicate {
witnesses.extend(CircuitSimulator::expr_wit(p));
}
Expand Down Expand Up @@ -235,15 +236,15 @@ mod tests {
acir_field::AcirField,
circuit::{
brillig::{BrilligFunctionId, BrilligOutputs},
opcodes::FunctionInput,
opcodes::{BlackBoxFuncCall, FunctionInput},
Circuit, ExpressionWidth, Opcode, PublicInputs,
},
native_types::{Expression, Witness},
FieldElement,
};
use std::collections::BTreeSet;

fn check_circuit(circuit: Circuit<FieldElement>) {
fn check_circuit(circuit: Circuit<FieldElement>) -> Circuit<FieldElement> {
assert!(CircuitSimulator::default().check_circuit(&circuit));
let mut merge_optimizer = MergeExpressionsOptimizer::new();
let acir_opcode_positions = vec![0; 20];
Expand All @@ -253,6 +254,7 @@ mod tests {
optimized_circuit.opcodes = opcodes;
// check that the circuit is still valid after optimization
assert!(CircuitSimulator::default().check_circuit(&optimized_circuit));
optimized_circuit
}

#[test]
Expand Down Expand Up @@ -352,4 +354,50 @@ mod tests {
};
check_circuit(circuit);
}

#[test]
fn takes_blackbox_opcode_outputs_into_account() {
// Regression test for https://github.com/noir-lang/noir/issues/6527
// Previously we would not track the usage of witness 4 in the output of the blackbox function.
// We would then merge the final two opcodes losing the check that the brillig call must match
// with `_0 ^ _1`.

let circuit: Circuit<FieldElement> = Circuit {
current_witness_index: 7,
opcodes: vec![
Opcode::BrilligCall {
id: BrilligFunctionId(0),
inputs: Vec::new(),
outputs: vec![BrilligOutputs::Simple(Witness(3))],
predicate: None,
},
Opcode::BlackBoxFuncCall(BlackBoxFuncCall::AND {
lhs: FunctionInput::witness(Witness(0), 8),
rhs: FunctionInput::witness(Witness(1), 8),
output: Witness(4),
}),
Opcode::AssertZero(Expression {
linear_combinations: vec![
(FieldElement::one(), Witness(3)),
(-FieldElement::one(), Witness(4)),
],
..Default::default()
}),
Opcode::AssertZero(Expression {
linear_combinations: vec![
(-FieldElement::one(), Witness(2)),
(FieldElement::one(), Witness(4)),
],
..Default::default()
}),
],
expression_width: ExpressionWidth::Bounded { width: 4 },
private_parameters: BTreeSet::from([Witness(0), Witness(1)]),
return_values: PublicInputs(BTreeSet::from([Witness(2)])),
..Default::default()
};

let new_circuit = check_circuit(circuit.clone());
assert_eq!(circuit, new_circuit);
}
}
26 changes: 7 additions & 19 deletions compiler/noirc_evaluator/src/acir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,12 @@ impl<'a> Context<'a> {
Instruction::IfElse { .. } => {
unreachable!("IfElse instruction remaining in acir-gen")
}
Instruction::MakeArray { elements, typ: _ } => {
let elements = elements.iter().map(|element| self.convert_value(*element, dfg));
let value = AcirValue::Array(elements.collect());
let result = dfg.instruction_results(instruction_id)[0];
self.ssa_values.insert(result, value);
}
}

self.acir_context.set_call_stack(CallStack::new());
Expand Down Expand Up @@ -1568,7 +1574,7 @@ impl<'a> Context<'a> {
if !already_initialized {
let value = &dfg[array];
match value {
Value::Array { .. } | Value::Instruction { .. } => {
Value::Instruction { .. } => {
let value = self.convert_value(array, dfg);
let array_typ = dfg.type_of_value(array);
let len = if !array_typ.contains_slice_element() {
Expand Down Expand Up @@ -1611,13 +1617,6 @@ impl<'a> Context<'a> {
match array_typ {
Type::Array(_, _) | Type::Slice(_) => {
match &dfg[array_id] {
Value::Array { array, .. } => {
for (i, value) in array.iter().enumerate() {
flat_elem_type_sizes.push(
self.flattened_slice_size(*value, dfg) + flat_elem_type_sizes[i],
);
}
}
Value::Instruction { .. } | Value::Param { .. } => {
// An instruction representing the slice means it has been processed previously during ACIR gen.
// Use the previously defined result of an array operation to fetch the internal type information.
Expand Down Expand Up @@ -1750,13 +1749,6 @@ impl<'a> Context<'a> {
fn flattened_slice_size(&mut self, array_id: ValueId, dfg: &DataFlowGraph) -> usize {
let mut size = 0;
match &dfg[array_id] {
Value::Array { array, .. } => {
// The array is going to be the flattened outer array
// Flattened slice size from SSA value does not need to be multiplied by the len
for value in array {
size += self.flattened_slice_size(*value, dfg);
}
}
Value::NumericConstant { .. } => {
size += 1;
}
Expand Down Expand Up @@ -1920,10 +1912,6 @@ impl<'a> Context<'a> {
Value::NumericConstant { constant, typ } => {
AcirValue::Var(self.acir_context.add_constant(*constant), typ.into())
}
Value::Array { array, .. } => {
let elements = array.iter().map(|element| self.convert_value(*element, dfg));
AcirValue::Array(elements.collect())
}
Value::Intrinsic(..) => todo!(),
Value::Function(function_id) => {
// This conversion is for debugging support only, to allow the
Expand Down
87 changes: 40 additions & 47 deletions compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,9 @@ impl<'block> BrilligBlock<'block> {
);
}
TerminatorInstruction::Return { return_values, .. } => {
let return_registers: Vec<_> = return_values
.iter()
.map(|value_id| {
let return_variable = self.convert_ssa_value(*value_id, dfg);
return_variable.extract_register()
})
.collect();
let return_registers = vecmap(return_values, |value_id| {
self.convert_ssa_value(*value_id, dfg).extract_register()
});
self.brillig_context.codegen_return(&return_registers);
}
}
Expand Down Expand Up @@ -763,6 +759,43 @@ impl<'block> BrilligBlock<'block> {
Instruction::IfElse { .. } => {
unreachable!("IfElse instructions should not be possible in brillig")
}
Instruction::MakeArray { elements: array, typ } => {
let value_id = dfg.instruction_results(instruction_id)[0];
if !self.variables.is_allocated(&value_id) {
let new_variable = self.variables.define_variable(
self.function_context,
self.brillig_context,
value_id,
dfg,
);

// Initialize the variable
match new_variable {
BrilligVariable::BrilligArray(brillig_array) => {
self.brillig_context.codegen_initialize_array(brillig_array);
}
BrilligVariable::BrilligVector(vector) => {
let size = self
.brillig_context
.make_usize_constant_instruction(array.len().into());
self.brillig_context.codegen_initialize_vector(vector, size, None);
self.brillig_context.deallocate_single_addr(size);
}
_ => unreachable!(
"ICE: Cannot initialize array value created as {new_variable:?}"
),
};

// Write the items
let items_pointer = self
.brillig_context
.codegen_make_array_or_vector_items_pointer(new_variable);

self.initialize_constant_array(array, typ, dfg, items_pointer);

self.brillig_context.deallocate_register(items_pointer);
}
}
};

let dead_variables = self
Expand Down Expand Up @@ -1500,46 +1533,6 @@ impl<'block> BrilligBlock<'block> {
new_variable
}
}
Value::Array { array, typ } => {
if self.variables.is_allocated(&value_id) {
self.variables.get_allocation(self.function_context, value_id, dfg)
} else {
let new_variable = self.variables.define_variable(
self.function_context,
self.brillig_context,
value_id,
dfg,
);

// Initialize the variable
match new_variable {
BrilligVariable::BrilligArray(brillig_array) => {
self.brillig_context.codegen_initialize_array(brillig_array);
}
BrilligVariable::BrilligVector(vector) => {
let size = self
.brillig_context
.make_usize_constant_instruction(array.len().into());
self.brillig_context.codegen_initialize_vector(vector, size, None);
self.brillig_context.deallocate_single_addr(size);
}
_ => unreachable!(
"ICE: Cannot initialize array value created as {new_variable:?}"
),
};

// Write the items
let items_pointer = self
.brillig_context
.codegen_make_array_or_vector_items_pointer(new_variable);

self.initialize_constant_array(array, typ, dfg, items_pointer);

self.brillig_context.deallocate_register(items_pointer);

new_variable
}
}
Value::Function(_) => {
// For the debugger instrumentation we want to allow passing
// around values representing function pointers, even though
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,7 @@ impl ConstantAllocation {
}
if let Some(terminator_instruction) = block.terminator() {
terminator_instruction.for_each_value(|value_id| {
let variables = collect_variables_of_value(value_id, &func.dfg);
for variable in variables {
if let Some(variable) = collect_variables_of_value(value_id, &func.dfg) {
record_if_constant(block_id, variable, InstructionLocation::Terminator);
}
});
Expand Down Expand Up @@ -166,7 +165,7 @@ impl ConstantAllocation {
}

pub(crate) fn is_constant_value(id: ValueId, dfg: &DataFlowGraph) -> bool {
matches!(&dfg[dfg.resolve(id)], Value::NumericConstant { .. } | Value::Array { .. })
matches!(&dfg[dfg.resolve(id)], Value::NumericConstant { .. })
}

/// For a given function, finds all the blocks that are within loops
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,32 +45,19 @@ fn find_back_edges(
}

/// Collects the underlying variables inside a value id. It might be more than one, for example in constant arrays that are constructed with multiple vars.
pub(crate) fn collect_variables_of_value(value_id: ValueId, dfg: &DataFlowGraph) -> Vec<ValueId> {
pub(crate) fn collect_variables_of_value(
value_id: ValueId,
dfg: &DataFlowGraph,
) -> Option<ValueId> {
let value_id = dfg.resolve(value_id);
let value = &dfg[value_id];

match value {
Value::Instruction { .. } | Value::Param { .. } => {
vec![value_id]
}
// Literal arrays are constants, but might use variable values to initialize.
Value::Array { array, .. } => {
let mut value_ids = vec![value_id];

array.iter().for_each(|item_id| {
let underlying_ids = collect_variables_of_value(*item_id, dfg);
value_ids.extend(underlying_ids);
});

value_ids
}
Value::NumericConstant { .. } => {
vec![value_id]
Value::Instruction { .. } | Value::Param { .. } | Value::NumericConstant { .. } => {
Some(value_id)
}
// Functions are not variables in a defunctionalized SSA. Only constant function values should appear.
Value::ForeignFunction(_) | Value::Function(_) | Value::Intrinsic(..) => {
vec![]
}
Value::ForeignFunction(_) | Value::Function(_) | Value::Intrinsic(..) => None,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,15 @@ impl<F: AcirField + DebugToString, Registers: RegisterAllocator> BrilligContext<
let destinations_of_temp = movements_map.remove(first_source).unwrap();
movements_map.insert(temp_register, destinations_of_temp);
}

// After removing loops we should have an DAG with each node having only one ancestor (but could have multiple successors)
// Now we should be able to move the registers just by performing a DFS on the movements map
let heads: Vec<_> = movements_map
.keys()
.filter(|source| !destinations_set.contains(source))
.copied()
.collect();

for head in heads {
self.perform_movements(&movements_map, head);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ impl Context {
| Instruction::Load { .. }
| Instruction::Not(..)
| Instruction::Store { .. }
| Instruction::Truncate { .. } => {
| Instruction::Truncate { .. }
| Instruction::MakeArray { .. } => {
self.value_sets.push(instruction_arguments_and_results);
}

Expand Down Expand Up @@ -247,8 +248,7 @@ impl Context {
Value::ForeignFunction(..) => {
panic!("Should not be able to reach foreign function from non-brillig functions, {func_id} in function {}", function.name());
}
Value::Array { .. }
| Value::Instruction { .. }
Value::Instruction { .. }
| Value::NumericConstant { .. }
| Value::Param { .. } => {
panic!("At the point we are running disconnect there shouldn't be any other values as arguments")
Expand Down
Loading

0 comments on commit 3651269

Please sign in to comment.