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

feat: Variable liveness analysis for brillig #2715

Merged
merged 14 commits into from
Sep 19, 2023
Prev Previous commit
Next Next commit
added defined_variables
sirasistant committed Sep 14, 2023
commit 617558590789efaf37bea3e667464215f64eb646
9 changes: 7 additions & 2 deletions compiler/noirc_evaluator/src/brillig/brillig_gen.rs
Original file line number Diff line number Diff line change
@@ -7,15 +7,20 @@ mod variable_liveness;

use self::{brillig_block::BrilligBlock, brillig_fn::FunctionContext};
use super::brillig_ir::{artifact::BrilligArtifact, BrilligContext};
use crate::ssa::ir::function::Function;
use crate::ssa::ir::{function::Function, post_order::PostOrder};

/// Converting an SSA function into Brillig bytecode.
pub(crate) fn convert_ssa_function(func: &Function, enable_debug_trace: bool) -> BrilligArtifact {
let mut reverse_post_order = Vec::new();
reverse_post_order.extend_from_slice(PostOrder::with_function(func).as_slice());
reverse_post_order.reverse();

let mut function_context = FunctionContext::new(func);

let mut brillig_context = BrilligContext::new(enable_debug_trace);

brillig_context.enter_context(FunctionContext::function_id_to_function_label(func.id()));
for block in function_context.blocks.clone() {
for block in reverse_post_order {
BrilligBlock::compile(&mut function_context, &mut brillig_context, block, &func.dfg);
}

28 changes: 13 additions & 15 deletions compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::collections::HashSet;

use crate::brillig::brillig_ir::{
BrilligBinaryOp, BrilligContext, BRILLIG_INTEGER_ARITHMETIC_BIT_SIZE,
extract_heap_array, extract_register, extract_registers, BrilligBinaryOp, BrilligContext,
BRILLIG_INTEGER_ARITHMETIC_BIT_SIZE,
};
use crate::ssa::ir::dfg::CallStack;
use crate::ssa::ir::{
@@ -31,7 +32,7 @@ pub(crate) struct BrilligBlock<'block> {
/// Context for creating brillig opcodes
pub(crate) brillig_context: &'block mut BrilligContext,

available_variables: HashSet<ValueId>,
pub(crate) available_variables: HashSet<ValueId>,
}

impl<'block> BrilligBlock<'block> {
@@ -144,7 +145,7 @@ impl<'block> BrilligBlock<'block> {
.iter()
.flat_map(|value_id| {
let return_variable = self.convert_ssa_value(*value_id, dfg);
self.function_context.extract_registers(return_variable)
extract_registers(return_variable)
})
.collect();
self.brillig_context.return_instruction(&return_registers);
@@ -368,8 +369,7 @@ impl<'block> BrilligBlock<'block> {
// or an array in the case of an array.
if let Type::Numeric(_) = dfg.type_of_value(param_id) {
let len_variable = self.convert_ssa_value(arguments[0], dfg);
let len_register_index =
self.function_context.extract_register(len_variable);
let len_register_index = extract_register(len_variable);
self.brillig_context.mov_instruction(result_register, len_register_index);
} else {
self.convert_ssa_array_len(arguments[0], result_register, dfg);
@@ -402,7 +402,7 @@ impl<'block> BrilligBlock<'block> {
results[0],
dfg,
);
let target_len = self.function_context.extract_register(target_len_variable);
let target_len = extract_register(target_len_variable);

let target_slice = self.function_context.create_variable(
self.brillig_context,
@@ -434,7 +434,7 @@ impl<'block> BrilligBlock<'block> {
results[0],
dfg,
);
let target_len = self.function_context.extract_register(target_len_variable);
let target_len = extract_register(target_len_variable);

let target_slice = self.function_context.create_variable(
self.brillig_context,
@@ -540,7 +540,7 @@ impl<'block> BrilligBlock<'block> {
.iter()
.flat_map(|argument_id| {
let variable_to_pass = self.convert_ssa_value(*argument_id, dfg);
self.function_context.extract_registers(variable_to_pass)
extract_registers(variable_to_pass)
})
.collect();

@@ -566,9 +566,7 @@ impl<'block> BrilligBlock<'block> {
// Collect the registers that should have been returned
let returned_registers: Vec<RegisterIndex> = variables_assigned_to
.iter()
.flat_map(|returned_variable| {
self.function_context.extract_registers(*returned_variable)
})
.flat_map(|returned_variable| extract_registers(*returned_variable))
.collect();

assert!(
@@ -888,7 +886,7 @@ impl<'block> BrilligBlock<'block> {
) {
let source_len_variable =
self.function_context.get_or_create_variable(self.brillig_context, source_value, dfg);
let source_len = self.function_context.extract_register(source_len_variable);
let source_len = extract_register(source_len_variable);

self.brillig_context.usize_op(source_len, target_len, binary_op, 1);
}
@@ -998,7 +996,7 @@ impl<'block> BrilligBlock<'block> {
value_id,
dfg,
);
let register_index = self.function_context.extract_register(new_variable);
let register_index = extract_register(new_variable);

self.brillig_context.const_instruction(register_index, (*constant).into());
new_variable
@@ -1061,7 +1059,7 @@ impl<'block> BrilligBlock<'block> {
dfg: &DataFlowGraph,
) -> RegisterIndex {
let variable = self.convert_ssa_value(value_id, dfg);
self.function_context.extract_register(variable)
extract_register(variable)
}

fn allocate_external_call_result(
@@ -1078,7 +1076,7 @@ impl<'block> BrilligBlock<'block> {
Type::Array(..) => {
let variable =
self.function_context.create_variable(self.brillig_context, result, dfg);
let array = self.function_context.extract_heap_array(variable);
let array = extract_heap_array(variable);
self.brillig_context.allocate_fixed_length_array(array.pointer, array.size);
variable
}
40 changes: 2 additions & 38 deletions compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_fn.rs
Original file line number Diff line number Diff line change
@@ -4,13 +4,11 @@ use iter_extended::vecmap;
use crate::{
brillig::brillig_ir::{
artifact::{BrilligParameter, Label},
BrilligContext,
extract_register, BrilligContext,
},
ssa::ir::{
basic_block::BasicBlockId,
dfg::DataFlowGraph,
function::{Function, FunctionId},
post_order::PostOrder,
types::{CompositeType, Type},
value::ValueId,
},
@@ -24,22 +22,15 @@ pub(crate) struct FunctionContext {
/// Map from SSA values to register or memory.
pub(crate) ssa_value_to_brillig_variable: HashMap<ValueId, RegisterOrMemory>,

pub(crate) blocks: Vec<BasicBlockId>,

pub(crate) liveness: VariableLiveness,
}

impl FunctionContext {
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();

Self {
function_id: id,
ssa_value_to_brillig_variable: HashMap::default(),
blocks: reverse_post_order,
liveness: VariableLiveness::from_function(function),
}
}
@@ -122,34 +113,7 @@ impl FunctionContext {
dfg: &DataFlowGraph,
) -> RegisterIndex {
let variable = self.create_variable(brillig_context, value, dfg);
self.extract_register(variable)
}

pub(crate) fn extract_register(&self, variable: RegisterOrMemory) -> RegisterIndex {
match variable {
RegisterOrMemory::RegisterIndex(register_index) => register_index,
_ => unreachable!("ICE: Expected register, got {variable:?}"),
}
}

pub(crate) fn extract_heap_array(&self, variable: RegisterOrMemory) -> HeapArray {
match variable {
RegisterOrMemory::HeapArray(array) => array,
_ => unreachable!("ICE: Expected array, got {variable:?}"),
}
}

/// Collects the registers that a given variable is stored in.
pub(crate) fn extract_registers(&self, variable: RegisterOrMemory) -> Vec<RegisterIndex> {
match variable {
RegisterOrMemory::RegisterIndex(register_index) => vec![register_index],
RegisterOrMemory::HeapArray(array) => {
vec![array.pointer]
}
RegisterOrMemory::HeapVector(vector) => {
vec![vector.pointer, vector.size]
}
}
extract_register(variable)
}

/// Creates a function label from a given SSA function id.
Original file line number Diff line number Diff line change
@@ -311,6 +311,7 @@ impl<'block> BrilligBlock<'block> {

#[cfg(test)]
mod tests {
use std::collections::HashSet;
use std::vec;

use acvm::acir::brillig::{HeapVector, Value};
@@ -321,24 +322,30 @@ mod tests {
use crate::brillig::brillig_ir::artifact::BrilligParameter;
use crate::brillig::brillig_ir::tests::{create_and_run_vm, create_context};
use crate::brillig::brillig_ir::BrilligContext;
use crate::ssa::function_builder::FunctionBuilder;
use crate::ssa::ir::function::RuntimeType;
use crate::ssa::ir::map::Id;
use fxhash::FxHashMap as HashMap;

fn create_test_environment() -> (FunctionContext, BrilligContext) {
let function_context = FunctionContext {
function_id: Id::test_new(0),
ssa_value_to_brillig_variable: HashMap::default(),
blocks: vec![],
};
use crate::ssa::ssa_gen::Ssa;

fn create_test_environment() -> (Ssa, FunctionContext, BrilligContext) {
let builder =
FunctionBuilder::new("main".to_string(), Id::test_new(0), RuntimeType::Brillig);
let ssa = builder.finish();
let function_context = FunctionContext::new(ssa.main());
let brillig_context = create_context();
(function_context, brillig_context)
(ssa, function_context, brillig_context)
}

fn create_brillig_block<'a>(
function_context: &'a mut FunctionContext,
brillig_context: &'a mut BrilligContext,
) -> BrilligBlock<'a> {
BrilligBlock { function_context, block_id: Id::test_new(0), brillig_context }
BrilligBlock {
function_context,
block_id: Id::test_new(0),
brillig_context,
available_variables: HashSet::new(),
}
}

#[test]
@@ -358,7 +365,7 @@ mod tests {
BrilligParameter::Simple,
];

let (mut function_context, mut context) = create_test_environment();
let (_, mut function_context, mut context) = create_test_environment();

// Allocate the parameters
let array_pointer = context.allocate_register();
@@ -451,7 +458,7 @@ mod tests {
BrilligParameter::Simple,
];

let (mut function_context, mut context) = create_test_environment();
let (_, mut function_context, mut context) = create_test_environment();

// Allocate the parameters
let array_pointer = context.allocate_register();
@@ -548,7 +555,7 @@ mod tests {
BrilligParameter::Simple,
];

let (mut function_context, mut context) = create_test_environment();
let (_, mut function_context, mut context) = create_test_environment();

// Allocate the parameters
let array_pointer = context.allocate_register();
@@ -671,7 +678,7 @@ mod tests {
BrilligParameter::Simple,
];

let (mut function_context, mut context) = create_test_environment();
let (_, mut function_context, mut context) = create_test_environment();

// Allocate the parameters
let array_pointer = context.allocate_register();
119 changes: 87 additions & 32 deletions compiler/noirc_evaluator/src/brillig/brillig_gen/variable_liveness.rs
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ use crate::ssa::ir::{
dfg::DataFlowGraph,
dom::DominatorTree,
function::Function,
instruction::{Instruction, InstructionId},
post_order::PostOrder,
value::{Value, ValueId},
};
@@ -59,74 +60,97 @@ pub(crate) fn compute_defined_variables(
defined_vars
}

fn compute_before_def(
block: &BasicBlock,
dfg: &DataFlowGraph,
defined_in_block: &HashSet<ValueId>,
) -> HashSet<ValueId> {
let mut before_def = HashSet::new();
fn collect_variables(value_id: ValueId, dfg: &DataFlowGraph) -> Vec<ValueId> {
let value_id = dfg.resolve(value_id);
let value = &dfg[value_id];

fn process_value(
value_id: ValueId,
dfg: &DataFlowGraph,
before_def: &mut HashSet<ValueId>,
defined_in_this_block: &HashSet<ValueId>,
) {
let value_id = dfg.resolve(value_id);
let value = &dfg[value_id];
match value {
Value::Instruction { .. } | Value::Param { .. } => {
if !defined_in_this_block.contains(&value_id) {
before_def.insert(value_id);
}
}
Value::Array { array, .. } => {
array.iter().for_each(|item_id| {
process_value(*item_id, dfg, before_def, defined_in_this_block);
});
}
_ => {}
match value {
Value::Instruction { .. } | Value::Param { .. } => {
vec![value_id]
}
Value::Array { array, .. } => {
let mut value_ids = Vec::new();

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

value_ids
}
_ => {
vec![]
}
}
}

pub(crate) fn variables_used(instruction: &Instruction, dfg: &DataFlowGraph) -> Vec<ValueId> {
let mut used = Vec::new();

instruction.for_each_value(|value_id| {
let underlying_ids = collect_variables(value_id, dfg);
used.extend(underlying_ids);
});

used
}

pub(crate) fn variables_used_in_block(block: &BasicBlock, dfg: &DataFlowGraph) -> HashSet<ValueId> {
let mut used = HashSet::new();

for instruction_id in block.instructions() {
let instruction = &dfg[*instruction_id];
instruction.for_each_value(|value_id| {
process_value(value_id, dfg, &mut before_def, defined_in_block);
});
used.extend(variables_used(instruction, dfg));
}

if let Some(terminator) = block.terminator() {
terminator.for_each_value(|value_id| {
process_value(value_id, dfg, &mut before_def, defined_in_block);
used.extend(collect_variables(value_id, dfg));
});
}

before_def
used
}

fn compute_before_def(
block: &BasicBlock,
dfg: &DataFlowGraph,
defined_in_block: &HashSet<ValueId>,
) -> HashSet<ValueId> {
variables_used_in_block(block, dfg)
.into_iter()
.filter(|id| !defined_in_block.contains(id))
.collect()
}

pub(crate) struct VariableLiveness {
cfg: ControlFlowGraph,
post_order: PostOrder,
live_in: HashMap<BasicBlockId, HashSet<ValueId>>,
last_uses: HashMap<BasicBlockId, HashMap<InstructionId, HashSet<ValueId>>>,
}

impl VariableLiveness {
pub(crate) fn from_function(func: &Function) -> Self {
let cfg = ControlFlowGraph::with_function(func);
let post_order = PostOrder::with_function(func);

let mut instance = Self { cfg, post_order, live_in: HashMap::new() };
let mut instance =
Self { cfg, post_order, live_in: HashMap::new(), last_uses: HashMap::new() };

instance.compute_live_in_of_blocks(func);

instance.compute_last_uses(func);

instance
}

/// The set of values that are live before the block starts executing
pub(crate) fn get_live_in(&self, block_id: &BasicBlockId) -> &HashSet<ValueId> {
self.live_in.get(block_id).expect("Live ins should have been calculated")
}

/// The set of values that are live after the block has finished executed
pub(crate) fn get_live_out(&self, block_id: &BasicBlockId) -> HashSet<ValueId> {
let mut live_out = HashSet::new();
for successor_id in self.cfg.successors(*block_id) {
@@ -135,6 +159,14 @@ impl VariableLiveness {
live_out
}

/// A map of instruction id to the set of values that die after the instruction has executed
pub(crate) fn get_last_uses(
&self,
block_id: &BasicBlockId,
) -> &HashMap<InstructionId, HashSet<ValueId>> {
self.last_uses.get(block_id).expect("Last uses should have been calculated")
}

fn compute_live_in_recursive(
&mut self,
func: &Function,
@@ -211,6 +243,29 @@ impl VariableLiveness {
self.update_live_ins_within_loop(back_edge);
}
}

fn compute_last_uses(&mut self, func: &Function) {
for block_id in func.reachable_blocks() {
let block = &func.dfg[block_id];
let live_out = self.get_live_out(&block_id);

let mut used_after: HashSet<ValueId> = HashSet::new();
let mut block_last_uses: HashMap<InstructionId, HashSet<ValueId>> = HashMap::new();

for instruction_id in block.instructions().iter().rev() {
let instruction = &func.dfg[*instruction_id];
let instruction_last_uses = variables_used(instruction, &func.dfg)
.into_iter()
.filter(|id| !used_after.contains(id) && !live_out.contains(id))
.collect();

used_after.extend(&instruction_last_uses);
block_last_uses.insert(*instruction_id, instruction_last_uses);
}

self.last_uses.insert(block_id, block_last_uses);
}
}
}

#[cfg(test)]
27 changes: 27 additions & 0 deletions compiler/noirc_evaluator/src/brillig/brillig_ir.rs
Original file line number Diff line number Diff line change
@@ -961,6 +961,33 @@ impl BrilligContext {
}
}

pub(crate) fn extract_register(variable: RegisterOrMemory) -> RegisterIndex {
match variable {
RegisterOrMemory::RegisterIndex(register_index) => register_index,
_ => unreachable!("ICE: Expected register, got {variable:?}"),
}
}

pub(crate) fn extract_heap_array(variable: RegisterOrMemory) -> HeapArray {
match variable {
RegisterOrMemory::HeapArray(array) => array,
_ => unreachable!("ICE: Expected array, got {variable:?}"),
}
}

/// Collects the registers that a given variable is stored in.
pub(crate) fn extract_registers(variable: RegisterOrMemory) -> Vec<RegisterIndex> {
match variable {
RegisterOrMemory::RegisterIndex(register_index) => vec![register_index],
RegisterOrMemory::HeapArray(array) => {
vec![array.pointer]
}
RegisterOrMemory::HeapVector(vector) => {
vec![vector.pointer, vector.size]
}
}
}

/// Type to encapsulate the binary operation types in Brillig
#[derive(Clone)]
pub(crate) enum BrilligBinaryOp {