diff --git a/compiler/noirc_evaluator/src/acir/mod.rs b/compiler/noirc_evaluator/src/acir/mod.rs index 137d0f3c28e..b573b7136f3 100644 --- a/compiler/noirc_evaluator/src/acir/mod.rs +++ b/compiler/noirc_evaluator/src/acir/mod.rs @@ -2909,7 +2909,7 @@ mod test { use std::collections::BTreeMap; use crate::{ - acir::BrilligStdlibFunc, + acir::{BrilligStdlibFunc, Function}, brillig::Brillig, ssa::{ function_builder::FunctionBuilder, @@ -3341,7 +3341,8 @@ mod test { build_basic_foo_with_return(&mut builder, foo_id, true, InlineType::default()); build_basic_foo_with_return(&mut builder, bar_id, true, InlineType::default()); - let ssa = builder.finish(); + let mut ssa = builder.finish(); + ssa.globals = Function::new("globals".to_owned(), ssa.main_id); let brillig = ssa.to_brillig(false); let (acir_functions, brillig_functions, _, _) = ssa @@ -3479,7 +3480,8 @@ mod test { build_basic_foo_with_return(&mut builder, foo_id, true, InlineType::default()); - let ssa = builder.finish(); + let mut ssa = builder.finish(); + ssa.globals = Function::new("globals".to_owned(), ssa.main_id); // We need to generate Brillig artifacts for the regular Brillig function and pass them to the ACIR generation pass. let brillig = ssa.to_brillig(false); println!("{}", ssa); @@ -3568,7 +3570,8 @@ mod test { // Build an ACIR function which has the same logic as the Brillig function above build_basic_foo_with_return(&mut builder, bar_id, false, InlineType::Fold); - let ssa = builder.finish(); + let mut ssa = builder.finish(); + ssa.globals = Function::new("globals".to_owned(), ssa.main_id); // We need to generate Brillig artifacts for the regular Brillig function and pass them to the ACIR generation pass. let brillig = ssa.to_brillig(false); println!("{}", ssa); diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen.rs index 5a81c79ae0d..b51a3445a1b 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen.rs @@ -2,11 +2,13 @@ pub(crate) mod brillig_black_box; pub(crate) mod brillig_block; pub(crate) mod brillig_block_variables; pub(crate) mod brillig_fn; +pub(crate) mod brillig_globals; pub(crate) mod brillig_slice_ops; mod constant_allocation; mod variable_liveness; use acvm::FieldElement; +use fxhash::FxHashMap as HashMap; use self::{brillig_block::BrilligBlock, brillig_fn::FunctionContext}; use super::{ @@ -14,7 +16,7 @@ use super::{ artifact::{BrilligArtifact, BrilligParameter, GeneratedBrillig, Label}, BrilligContext, }, - Brillig, + Brillig, BrilligVariable, ValueId, }; use crate::{ errors::InternalError, @@ -25,6 +27,7 @@ use crate::{ pub(crate) fn convert_ssa_function( func: &Function, enable_debug_trace: bool, + globals: &HashMap, ) -> BrilligArtifact { let mut brillig_context = BrilligContext::new(enable_debug_trace); @@ -35,7 +38,13 @@ pub(crate) fn convert_ssa_function( brillig_context.call_check_max_stack_depth_procedure(); for block in function_context.blocks.clone() { - BrilligBlock::compile(&mut function_context, &mut brillig_context, block, &func.dfg); + BrilligBlock::compile( + &mut function_context, + &mut brillig_context, + block, + &func.dfg, + globals, + ); } let mut artifact = brillig_context.artifact(); @@ -53,6 +62,7 @@ pub(crate) fn gen_brillig_for( arguments, FunctionContext::return_values(func), func.id(), + true, ); entry_point.name = func.name().to_string(); diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index ec918c51ff1..698d4cd05be 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -3,7 +3,7 @@ use crate::brillig::brillig_ir::brillig_variable::{ type_to_heap_value_type, BrilligArray, BrilligVariable, SingleAddrVariable, }; -use crate::brillig::brillig_ir::registers::Stack; +use crate::brillig::brillig_ir::registers::RegisterAllocator; use crate::brillig::brillig_ir::{ BrilligBinaryOp, BrilligContext, ReservedRegisters, BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, }; @@ -32,28 +32,41 @@ use super::brillig_fn::FunctionContext; use super::constant_allocation::InstructionLocation; /// Generate the compilation artifacts for compiling a function into brillig bytecode. -pub(crate) struct BrilligBlock<'block> { +pub(crate) struct BrilligBlock<'block, Registers: RegisterAllocator> { pub(crate) function_context: &'block mut FunctionContext, /// The basic block that is being converted pub(crate) block_id: BasicBlockId, /// Context for creating brillig opcodes - pub(crate) brillig_context: &'block mut BrilligContext, + pub(crate) brillig_context: &'block mut BrilligContext, /// Tracks the available variable during the codegen of the block pub(crate) variables: BlockVariables, /// For each instruction, the set of values that are not used anymore after it. pub(crate) last_uses: HashMap>, + + pub(crate) globals: &'block HashMap, + + pub(crate) building_globals: bool, } -impl<'block> BrilligBlock<'block> { +impl<'block, Registers: RegisterAllocator> BrilligBlock<'block, Registers> { /// Converts an SSA Basic block into a sequence of Brillig opcodes pub(crate) fn compile( function_context: &'block mut FunctionContext, - brillig_context: &'block mut BrilligContext, + brillig_context: &'block mut BrilligContext, block_id: BasicBlockId, dfg: &DataFlowGraph, + globals: &'block HashMap, ) { let live_in = function_context.liveness.get_live_in(&block_id); - let variables = BlockVariables::new(live_in.clone()); + + let mut live_in_no_globals = HashSet::default(); + for value in live_in { + if !dfg.is_global(*value) { + live_in_no_globals.insert(*value); + } + } + + let variables = BlockVariables::new(live_in_no_globals); brillig_context.set_allocated_registers( variables @@ -64,12 +77,44 @@ impl<'block> BrilligBlock<'block> { ); let last_uses = function_context.liveness.get_last_uses(&block_id).clone(); - let mut brillig_block = - BrilligBlock { function_context, block_id, brillig_context, variables, last_uses }; + let mut brillig_block = BrilligBlock { + function_context, + block_id, + brillig_context, + variables, + last_uses, + globals, + building_globals: false, + }; brillig_block.convert_block(dfg); } + pub(crate) fn compile_globals( + &mut self, + globals: &DataFlowGraph, + used_globals: &HashSet, + ) { + for (id, value) in globals.values_iter() { + if !used_globals.contains(&id) { + continue; + } + match value { + Value::NumericConstant { .. } => { + self.convert_ssa_value(id, globals); + } + Value::Instruction { instruction, .. } => { + self.convert_ssa_instruction(*instruction, globals); + } + _ => { + panic!( + "Expected either an instruction or a numeric constant for a global value" + ) + } + } + } + } + fn convert_block(&mut self, dfg: &DataFlowGraph) { // Add a label for this block let block_label = self.create_block_label_for_current_function(self.block_id); @@ -199,7 +244,11 @@ impl<'block> BrilligBlock<'block> { } /// Converts an SSA instruction into a sequence of Brillig opcodes. - fn convert_ssa_instruction(&mut self, instruction_id: InstructionId, dfg: &DataFlowGraph) { + pub(crate) fn convert_ssa_instruction( + &mut self, + instruction_id: InstructionId, + dfg: &DataFlowGraph, + ) { let instruction = &dfg[instruction_id]; self.brillig_context.set_call_stack(dfg.get_instruction_call_stack(instruction_id)); @@ -847,18 +896,24 @@ impl<'block> BrilligBlock<'block> { Instruction::Noop => (), }; - let dead_variables = self - .last_uses - .get(&instruction_id) - .expect("Last uses for instruction should have been computed"); - - for dead_variable in dead_variables { - self.variables.remove_variable( - dead_variable, - self.function_context, - self.brillig_context, - ); + if !self.building_globals { + let dead_variables = self + .last_uses + .get(&instruction_id) + .expect("Last uses for instruction should have been computed"); + + for dead_variable in dead_variables { + // Globals are reserved throughout the entirety of the program + if !dfg.is_global(*dead_variable) { + self.variables.remove_variable( + dead_variable, + self.function_context, + self.brillig_context, + ); + } + } } + self.brillig_context.set_call_stack(CallStack::new()); } @@ -1559,25 +1614,38 @@ impl<'block> BrilligBlock<'block> { } /// Converts an SSA `ValueId` into a `RegisterOrMemory`. Initializes if necessary. - fn convert_ssa_value(&mut self, value_id: ValueId, dfg: &DataFlowGraph) -> BrilligVariable { + pub(crate) fn convert_ssa_value( + &mut self, + value_id: ValueId, + dfg: &DataFlowGraph, + ) -> BrilligVariable { let value_id = dfg.resolve(value_id); let value = &dfg[value_id]; match value { Value::Global(_) => { - unreachable!("ICE: All globals should have been inlined"); + unreachable!("Expected global value to be resolve to its inner value"); } Value::Param { .. } | Value::Instruction { .. } => { // All block parameters and instruction results should have already been // converted to registers so we fetch from the cache. - - self.variables.get_allocation(self.function_context, value_id, dfg) + if dfg.is_global(value_id) { + *self.globals.get(&value_id).unwrap_or_else(|| { + panic!("ICE: Global value not found in cache {value_id}") + }) + } else { + self.variables.get_allocation(self.function_context, value_id, dfg) + } } Value::NumericConstant { constant, .. } => { // Constants might have been converted previously or not, so we get or create and // (re)initialize the value inside. if self.variables.is_allocated(&value_id) { self.variables.get_allocation(self.function_context, value_id, dfg) + } else if dfg.is_global(value_id) { + *self.globals.get(&value_id).unwrap_or_else(|| { + panic!("ICE: Global value not found in cache {value_id}") + }) } else { let new_variable = self.variables.define_variable( self.function_context, diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block_variables.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block_variables.rs index bf0a1bc7347..4cf8e921483 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block_variables.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block_variables.rs @@ -7,7 +7,7 @@ use crate::{ get_bit_size_from_ssa_type, BrilligArray, BrilligVariable, BrilligVector, SingleAddrVariable, }, - registers::{RegisterAllocator, Stack}, + registers::RegisterAllocator, BrilligContext, }, ssa::ir::{ @@ -48,10 +48,10 @@ impl BlockVariables { } /// For a given SSA value id, define the variable and return the corresponding cached allocation. - pub(crate) fn define_variable( + pub(crate) fn define_variable( &mut self, function_context: &mut FunctionContext, - brillig_context: &mut BrilligContext, + brillig_context: &mut BrilligContext, value_id: ValueId, dfg: &DataFlowGraph, ) -> BrilligVariable { @@ -68,10 +68,10 @@ impl BlockVariables { } /// Defines a variable that fits in a single register and returns the allocated register. - pub(crate) fn define_single_addr_variable( + pub(crate) fn define_single_addr_variable( &mut self, function_context: &mut FunctionContext, - brillig_context: &mut BrilligContext, + brillig_context: &mut BrilligContext, value: ValueId, dfg: &DataFlowGraph, ) -> SingleAddrVariable { @@ -80,11 +80,11 @@ impl BlockVariables { } /// Removes a variable so it's not used anymore within this block. - pub(crate) fn remove_variable( + pub(crate) fn remove_variable( &mut self, value_id: &ValueId, function_context: &mut FunctionContext, - brillig_context: &mut BrilligContext, + brillig_context: &mut BrilligContext, ) { assert!(self.available_variables.remove(value_id), "ICE: Variable is not available"); let variable = function_context @@ -133,6 +133,14 @@ pub(crate) fn allocate_value( ) -> BrilligVariable { let typ = dfg.type_of_value(value_id); + allocate_value_with_type(brillig_context, typ) +} + +/// For a given value_id, allocates the necessary registers to hold it. +pub(crate) fn allocate_value_with_type( + brillig_context: &mut BrilligContext, + typ: Type, +) -> BrilligVariable { match typ { Type::Numeric(_) | Type::Reference(_) | Type::Function => { BrilligVariable::SingleAddr(SingleAddrVariable { diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs new file mode 100644 index 00000000000..99c8ee0fded --- /dev/null +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs @@ -0,0 +1,39 @@ +use acvm::FieldElement; +use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; + +use super::{ + BrilligArtifact, BrilligBlock, BrilligVariable, Function, FunctionContext, Label, ValueId, +}; +use crate::brillig::{brillig_ir::BrilligContext, DataFlowGraph}; + +pub(crate) fn convert_ssa_globals( + enable_debug_trace: bool, + globals: &Function, + used_globals: &HashSet, +) -> (BrilligArtifact, HashMap) { + let mut brillig_context = BrilligContext::new_for_global_init(enable_debug_trace); + // The global space does not have globals itself + let empty_globals = HashMap::default(); + // We can use any ID here as this context is only going to be used for globals which does not differentiate + // by functions and blocks. The only Label that should be used in the globals context is `Label::globals_init()` + let mut function_context = FunctionContext::new(globals); + brillig_context.enter_context(Label::globals_init()); + + let block_id = DataFlowGraph::default().make_block(); + let mut brillig_block = BrilligBlock { + function_context: &mut function_context, + block_id, + brillig_context: &mut brillig_context, + variables: Default::default(), + last_uses: HashMap::default(), + globals: &empty_globals, + building_globals: true, + }; + + brillig_block.compile_globals(&globals.dfg, used_globals); + + brillig_context.return_instruction(); + + let artifact = brillig_context.artifact(); + (artifact, function_context.ssa_value_allocations) +} diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_slice_ops.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_slice_ops.rs index 26c7151bf07..1ec2d165b12 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_slice_ops.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_slice_ops.rs @@ -2,12 +2,13 @@ use acvm::acir::brillig::MemoryAddress; use crate::brillig::brillig_ir::{ brillig_variable::{BrilligVariable, BrilligVector, SingleAddrVariable}, + registers::RegisterAllocator, BrilligBinaryOp, }; use super::brillig_block::BrilligBlock; -impl<'block> BrilligBlock<'block> { +impl<'block, Registers: RegisterAllocator> BrilligBlock<'block, Registers> { fn write_variables(&mut self, write_pointer: MemoryAddress, variables: &[BrilligVariable]) { for (index, variable) in variables.iter().enumerate() { self.brillig_context.store_instruction(write_pointer, variable.extract_register()); @@ -159,6 +160,7 @@ mod tests { use std::vec; use acvm::FieldElement; + use fxhash::FxHashMap as HashMap; use noirc_frontend::monomorphization::ast::InlineType; use crate::brillig::brillig_gen::brillig_block::BrilligBlock; @@ -173,6 +175,7 @@ mod tests { create_and_run_vm, create_context, create_entry_point_bytecode, }; use crate::brillig::brillig_ir::{BrilligContext, BRILLIG_MEMORY_ADDRESSING_BIT_SIZE}; + use crate::brillig::ValueId; use crate::ssa::function_builder::FunctionBuilder; use crate::ssa::ir::function::RuntimeType; use crate::ssa::ir::map::Id; @@ -193,7 +196,8 @@ mod tests { fn create_brillig_block<'a>( function_context: &'a mut FunctionContext, brillig_context: &'a mut BrilligContext, - ) -> BrilligBlock<'a> { + globals: &'a HashMap, + ) -> BrilligBlock<'a, Stack> { let variables = BlockVariables::default(); BrilligBlock { function_context, @@ -201,6 +205,8 @@ mod tests { brillig_context, variables, last_uses: Default::default(), + globals, + building_globals: false, } } @@ -242,7 +248,9 @@ mod tests { // Allocate the results let target_vector = BrilligVector { pointer: context.allocate_register() }; - let mut block = create_brillig_block(&mut function_context, &mut context); + let brillig_globals = HashMap::default(); + let mut block = + create_brillig_block(&mut function_context, &mut context, &brillig_globals); if push_back { block.slice_push_back_operation( @@ -358,7 +366,9 @@ mod tests { bit_size: BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, }; - let mut block = create_brillig_block(&mut function_context, &mut context); + let brillig_globals = HashMap::default(); + let mut block = + create_brillig_block(&mut function_context, &mut context, &brillig_globals); if pop_back { block.slice_pop_back_operation( @@ -464,7 +474,9 @@ mod tests { // Allocate the results let target_vector = BrilligVector { pointer: context.allocate_register() }; - let mut block = create_brillig_block(&mut function_context, &mut context); + let brillig_globals = HashMap::default(); + let mut block = + create_brillig_block(&mut function_context, &mut context, &brillig_globals); block.slice_insert_operation( target_vector, @@ -604,7 +616,9 @@ mod tests { bit_size: BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, }; - let mut block = create_brillig_block(&mut function_context, &mut context); + let brillig_globals = HashMap::default(); + let mut block = + create_brillig_block(&mut function_context, &mut context, &brillig_globals); block.slice_remove_operation( target_vector, diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/constant_allocation.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/constant_allocation.rs index 61ca20be2f5..64741393dd7 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/constant_allocation.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/constant_allocation.rs @@ -22,6 +22,7 @@ pub(crate) enum InstructionLocation { Terminator, } +#[derive(Default)] pub(crate) struct ConstantAllocation { constant_usage: HashMap>>, allocation_points: HashMap>>, diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/variable_liveness.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/variable_liveness.rs index 6bcadc3910d..37a63466119 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/variable_liveness.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/variable_liveness.rs @@ -114,6 +114,7 @@ fn compute_used_before_def( type LastUses = HashMap; /// A struct representing the liveness of variables throughout a function. +#[derive(Default)] pub(crate) struct VariableLiveness { cfg: ControlFlowGraph, post_order: PostOrder, diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs index 55e12c993fa..06f61948337 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs @@ -37,7 +37,7 @@ use acvm::{ }; use debug_show::DebugShow; -use super::ProcedureId; +use super::{GlobalSpace, ProcedureId}; /// The Brillig VM does not apply a limit to the memory address space, /// As a convention, we take use 32 bits. This means that we assume that @@ -110,7 +110,9 @@ impl BrilligContext { can_call_procedures: true, } } +} +impl BrilligContext { /// Splits a two's complement signed integer in the sign bit and the absolute value. /// For example, -6 i8 (11111010) is split to 00000110 (6, absolute value) and 1 (is_negative). pub(crate) fn absolute_value( @@ -213,6 +215,21 @@ impl BrilligContext { } } +/// Special brillig context to codegen global values initialization +impl BrilligContext { + pub(crate) fn new_for_global_init(enable_debug_trace: bool) -> BrilligContext { + BrilligContext { + obj: BrilligArtifact::default(), + registers: GlobalSpace::new(), + context_label: Label::globals_init(), + current_section: 0, + next_section: 1, + debug_show: DebugShow::new(enable_debug_trace), + can_call_procedures: false, + } + } +} + impl BrilligContext { /// Adds a brillig instruction to the brillig byte code fn push_opcode(&mut self, opcode: BrilligOpcode) { @@ -299,8 +316,12 @@ pub(crate) mod tests { returns: Vec, ) -> GeneratedBrillig { let artifact = context.artifact(); - let mut entry_point_artifact = - BrilligContext::new_entry_point_artifact(arguments, returns, FunctionId::test_new(0)); + let mut entry_point_artifact = BrilligContext::new_entry_point_artifact( + arguments, + returns, + FunctionId::test_new(0), + false, + ); entry_point_artifact.link_with(&artifact); while let Some(unresolved_fn_label) = entry_point_artifact.first_unresolved_function_call() { diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs index 3654a95a03f..4c48675d1e7 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs @@ -75,6 +75,8 @@ pub(crate) enum LabelType { Function(FunctionId, Option), /// Labels for intrinsic procedures Procedure(ProcedureId), + /// Label for initialization of globals + GlobalInit, } impl std::fmt::Display for LabelType { @@ -89,6 +91,7 @@ impl std::fmt::Display for LabelType { } LabelType::Entrypoint => write!(f, "Entrypoint"), LabelType::Procedure(procedure_id) => write!(f, "Procedure({:?})", procedure_id), + LabelType::GlobalInit => write!(f, "Globals Initialization"), } } } @@ -123,6 +126,10 @@ impl Label { pub(crate) fn procedure(procedure_id: ProcedureId) -> Self { Label { label_type: LabelType::Procedure(procedure_id), section: None } } + + pub(crate) fn globals_init() -> Self { + Label { label_type: LabelType::GlobalInit, section: None } + } } impl std::fmt::Display for Label { diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_calls.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_calls.rs index da310873cff..4da3aa4d6d2 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_calls.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_calls.rs @@ -9,7 +9,8 @@ use super::{ BrilligBinaryOp, BrilligContext, ReservedRegisters, }; -impl BrilligContext { +impl BrilligContext { + // impl BrilligContext { pub(crate) fn codegen_call( &mut self, func_id: FunctionId, @@ -17,7 +18,7 @@ impl BrilligContext { returns: &[BrilligVariable], ) { let stack_size_register = SingleAddrVariable::new_usize(self.allocate_register()); - let previous_stack_pointer = self.registers.empty_stack_start(); + let previous_stack_pointer = self.registers.empty_registers_start(); let stack_size = previous_stack_pointer.unwrap_relative(); // Write the stack size self.const_instruction(stack_size_register, stack_size.into()); diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs index 2dbee48b277..b84a15db4ad 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs @@ -15,6 +15,7 @@ use acvm::acir::{ pub(crate) const MAX_STACK_SIZE: usize = 16 * MAX_STACK_FRAME_SIZE; pub(crate) const MAX_STACK_FRAME_SIZE: usize = 2048; pub(crate) const MAX_SCRATCH_SPACE: usize = 64; +pub(crate) const MAX_GLOBAL_SPACE: usize = 16384; impl BrilligContext { /// Creates an entry point artifact that will jump to the function label provided. @@ -22,11 +23,16 @@ impl BrilligContext { arguments: Vec, return_parameters: Vec, target_function: FunctionId, + globals_init: bool, ) -> BrilligArtifact { let mut context = BrilligContext::new(false); context.codegen_entry_point(&arguments, &return_parameters); + if globals_init { + context.add_globals_init_instruction(); + } + context.add_external_call_instruction(target_function); context.codegen_exit_point(&arguments, &return_parameters); @@ -34,7 +40,7 @@ impl BrilligContext { } fn calldata_start_offset() -> usize { - ReservedRegisters::len() + MAX_STACK_SIZE + MAX_SCRATCH_SPACE + ReservedRegisters::len() + MAX_STACK_SIZE + MAX_SCRATCH_SPACE + MAX_GLOBAL_SPACE } fn return_data_start_offset(calldata_size: usize) -> usize { diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs index 2bf5364414c..d67da423d44 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs @@ -200,6 +200,13 @@ impl BrilligContext< self.obj.add_unresolved_external_call(BrilligOpcode::Call { location: 0 }, proc_label); } + pub(super) fn add_globals_init_instruction(&mut self) { + let globals_init_label = Label::globals_init(); + self.debug_show.add_external_call_instruction(globals_init_label.to_string()); + self.obj + .add_unresolved_external_call(BrilligOpcode::Call { location: 0 }, globals_init_label); + } + /// Adds a unresolved `Jump` instruction to the bytecode. pub(crate) fn jump_instruction(&mut self, target_label: Label) { self.debug_show.jump_instruction(target_label.to_string()); diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/registers.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/registers.rs index dd7766f40aa..b83c03b297a 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/registers.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/registers.rs @@ -7,7 +7,7 @@ use crate::brillig::brillig_ir::entry_point::MAX_STACK_SIZE; use super::{ brillig_variable::SingleAddrVariable, - entry_point::{MAX_SCRATCH_SPACE, MAX_STACK_FRAME_SIZE}, + entry_point::{MAX_GLOBAL_SPACE, MAX_SCRATCH_SPACE, MAX_STACK_FRAME_SIZE}, BrilligContext, ReservedRegisters, }; @@ -24,6 +24,8 @@ pub(crate) trait RegisterAllocator { fn ensure_register_is_allocated(&mut self, register: MemoryAddress); /// Creates a new register context from a set of registers allocated previously. fn from_preallocated_registers(preallocated_registers: Vec) -> Self; + /// Finds the first register that is available based upon the deallocation list + fn empty_registers_start(&self) -> MemoryAddress; } /// Every brillig stack frame/call context has its own view of register space. @@ -41,10 +43,6 @@ impl Stack { let offset = register.unwrap_relative(); offset >= Self::start() && offset < Self::end() } - - pub(crate) fn empty_stack_start(&self) -> MemoryAddress { - MemoryAddress::relative(self.storage.empty_registers_start(Self::start())) - } } impl RegisterAllocator for Stack { @@ -83,6 +81,10 @@ impl RegisterAllocator for Stack { ), } } + + fn empty_registers_start(&self) -> MemoryAddress { + MemoryAddress::relative(self.storage.empty_registers_start(Self::start())) + } } /// Procedure arguments and returns are passed through scratch space. @@ -109,7 +111,7 @@ impl RegisterAllocator for ScratchSpace { } fn end() -> usize { - ReservedRegisters::len() + MAX_STACK_SIZE + MAX_SCRATCH_SPACE + Self::start() + MAX_SCRATCH_SPACE } fn ensure_register_is_allocated(&mut self, register: MemoryAddress) { @@ -139,6 +141,70 @@ impl RegisterAllocator for ScratchSpace { ), } } + + fn empty_registers_start(&self) -> MemoryAddress { + MemoryAddress::direct(self.storage.empty_registers_start(Self::start())) + } +} + +/// Globals have a separate memory space +/// This memory space is initialized once at the beginning of a program +/// and is read-only. +pub(crate) struct GlobalSpace { + storage: DeallocationListAllocator, +} + +impl GlobalSpace { + pub(crate) fn new() -> Self { + Self { storage: DeallocationListAllocator::new(Self::start()) } + } + + fn is_within_bounds(register: MemoryAddress) -> bool { + let index = register.unwrap_direct(); + index >= Self::start() && index < Self::end() + } +} + +impl RegisterAllocator for GlobalSpace { + fn start() -> usize { + ScratchSpace::end() + } + + fn end() -> usize { + Self::start() + MAX_GLOBAL_SPACE + } + + fn allocate_register(&mut self) -> MemoryAddress { + let allocated = MemoryAddress::direct(self.storage.allocate_register()); + assert!(Self::is_within_bounds(allocated), "Global space too deep"); + allocated + } + + fn deallocate_register(&mut self, register_index: MemoryAddress) { + self.storage.deallocate_register(register_index.unwrap_direct()); + } + + fn ensure_register_is_allocated(&mut self, register: MemoryAddress) { + assert!(Self::is_within_bounds(register), "Register out of global space bounds"); + self.storage.ensure_register_is_allocated(register.unwrap_direct()); + } + + fn from_preallocated_registers(preallocated_registers: Vec) -> Self { + for register in &preallocated_registers { + assert!(Self::is_within_bounds(*register), "Register out of global space bounds"); + } + + Self { + storage: DeallocationListAllocator::from_preallocated_registers( + Self::start(), + vecmap(preallocated_registers, |r| r.unwrap_direct()), + ), + } + } + + fn empty_registers_start(&self) -> MemoryAddress { + MemoryAddress::direct(self.storage.empty_registers_start(Self::start())) + } } struct DeallocationListAllocator { diff --git a/compiler/noirc_evaluator/src/brillig/mod.rs b/compiler/noirc_evaluator/src/brillig/mod.rs index cb8c35cd8e0..3d96a855aa0 100644 --- a/compiler/noirc_evaluator/src/brillig/mod.rs +++ b/compiler/noirc_evaluator/src/brillig/mod.rs @@ -2,7 +2,8 @@ pub(crate) mod brillig_gen; pub(crate) mod brillig_ir; use acvm::FieldElement; -use brillig_ir::artifact::LabelType; +use brillig_gen::brillig_globals::convert_ssa_globals; +use brillig_ir::{artifact::LabelType, brillig_variable::BrilligVariable, registers::GlobalSpace}; use self::{ brillig_gen::convert_ssa_function, @@ -12,7 +13,11 @@ use self::{ }, }; use crate::ssa::{ - ir::function::{Function, FunctionId}, + ir::{ + dfg::DataFlowGraph, + function::{Function, FunctionId}, + value::ValueId, + }, ssa_gen::Ssa, }; use fxhash::FxHashMap as HashMap; @@ -26,12 +31,18 @@ pub use self::brillig_ir::procedures::ProcedureId; pub struct Brillig { /// Maps SSA function labels to their brillig artifact ssa_function_to_brillig: HashMap>, + globals: BrilligArtifact, } impl Brillig { /// Compiles a function into brillig and store the compilation artifacts - pub(crate) fn compile(&mut self, func: &Function, enable_debug_trace: bool) { - let obj = convert_ssa_function(func, enable_debug_trace); + pub(crate) fn compile( + &mut self, + func: &Function, + enable_debug_trace: bool, + globals: &HashMap, + ) { + let obj = convert_ssa_function(func, enable_debug_trace, globals); self.ssa_function_to_brillig.insert(func.id(), obj); } @@ -46,6 +57,7 @@ impl Brillig { } // Procedures are compiled as needed LabelType::Procedure(procedure_id) => Some(Cow::Owned(compile_procedure(procedure_id))), + LabelType::GlobalInit => Some(Cow::Borrowed(&self.globals)), _ => unreachable!("ICE: Expected a function or procedure label"), } } @@ -71,9 +83,14 @@ impl Ssa { .collect::>(); let mut brillig = Brillig::default(); + + let (artifact, brillig_globals) = + convert_ssa_globals(enable_debug_trace, &self.globals, &self.used_global_values); + brillig.globals = artifact; + for brillig_function_id in brillig_reachable_function_ids { let func = &self.functions[&brillig_function_id]; - brillig.compile(func, enable_debug_trace); + brillig.compile(func, enable_debug_trace, &brillig_globals); } brillig diff --git a/compiler/noirc_evaluator/src/ssa/ir/cfg.rs b/compiler/noirc_evaluator/src/ssa/ir/cfg.rs index 788b1a7d302..5253c68c72c 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/cfg.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/cfg.rs @@ -18,7 +18,7 @@ struct CfgNode { pub(crate) successors: BTreeSet, } -#[derive(Clone)] +#[derive(Clone, Default)] /// The Control Flow Graph maintains a mapping of blocks to their predecessors /// and successors where predecessors are basic blocks and successors are /// basic blocks. diff --git a/compiler/noirc_evaluator/src/ssa/ir/dfg.rs b/compiler/noirc_evaluator/src/ssa/ir/dfg.rs index 28242b223ac..83b8f2a57ff 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/dfg.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/dfg.rs @@ -124,7 +124,7 @@ impl GlobalsGraph { } /// Iterate over every Value in this DFG in no particular order, including unused Values - pub(crate) fn values_iter(&self) -> impl ExactSizeIterator { + pub(crate) fn values_iter(&self) -> impl DoubleEndedIterator { self.values.iter() } } @@ -173,12 +173,12 @@ impl DataFlowGraph { /// The pairs are order by id, which is not guaranteed to be meaningful. pub(crate) fn basic_blocks_iter( &self, - ) -> impl ExactSizeIterator { + ) -> impl DoubleEndedIterator { self.blocks.iter() } /// Iterate over every Value in this DFG in no particular order, including unused Values - pub(crate) fn values_iter(&self) -> impl ExactSizeIterator { + pub(crate) fn values_iter(&self) -> impl DoubleEndedIterator { self.values.iter() } diff --git a/compiler/noirc_evaluator/src/ssa/ir/dom.rs b/compiler/noirc_evaluator/src/ssa/ir/dom.rs index ff54bf3b6ed..3dde6240e18 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/dom.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/dom.rs @@ -39,6 +39,7 @@ impl DominatorTreeNode { } /// The dominator tree for a single function. +#[derive(Default)] pub(crate) struct DominatorTree { /// The nodes of the dominator tree /// diff --git a/compiler/noirc_evaluator/src/ssa/ir/map.rs b/compiler/noirc_evaluator/src/ssa/ir/map.rs index 1d637309191..b6da107957c 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/map.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/map.rs @@ -190,7 +190,7 @@ impl DenseMap { /// Gets an iterator to a reference to each element in the dense map paired with its id. /// /// The id-element pairs are ordered by the numeric values of the ids. - pub(crate) fn iter(&self) -> impl ExactSizeIterator, &T)> { + pub(crate) fn iter(&self) -> impl DoubleEndedIterator, &T)> { let ids_iter = (0..self.storage.len() as u32).map(|idx| Id::new(idx)); ids_iter.zip(self.storage.iter()) } diff --git a/compiler/noirc_evaluator/src/ssa/ir/post_order.rs b/compiler/noirc_evaluator/src/ssa/ir/post_order.rs index 398ce887b96..08f195e53d1 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/post_order.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/post_order.rs @@ -13,6 +13,7 @@ enum Visit { Last, } +#[derive(Default)] pub(crate) struct PostOrder(Vec); impl PostOrder { diff --git a/compiler/noirc_evaluator/src/ssa/ir/printer.rs b/compiler/noirc_evaluator/src/ssa/ir/printer.rs index 85f8dcaba48..88bee0799a3 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/printer.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/printer.rs @@ -35,7 +35,7 @@ impl Display for Ssa { }; } - if self.globals.dfg.values_iter().len() > 0 { + if self.globals.dfg.values_iter().next().is_some() { writeln!(f)?; } diff --git a/compiler/noirc_evaluator/src/ssa/opt/die.rs b/compiler/noirc_evaluator/src/ssa/opt/die.rs index 48e55bc49e5..3b5537aceb4 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/die.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/die.rs @@ -30,9 +30,25 @@ impl Ssa { } fn dead_instruction_elimination_inner(mut self, flattened: bool) -> Ssa { - self.functions + let mut used_global_values: HashSet<_> = self + .functions .par_iter_mut() - .for_each(|(_, func)| func.dead_instruction_elimination(true, flattened)); + .flat_map(|(_, func)| func.dead_instruction_elimination(true, flattened)) + .collect(); + + // Check which globals are used across all functions + for (id, value) in self.globals.dfg.values_iter().rev() { + if used_global_values.contains(&id) { + if let Value::Instruction { instruction, .. } = &value { + let instruction = &self.globals.dfg[*instruction]; + instruction.for_each_value(|value_id| { + used_global_values.insert(value_id); + }); + } + } + } + + self.used_global_values = used_global_values; self } @@ -45,12 +61,16 @@ impl Function { /// instructions that reference results from an instruction in another block are evaluated first. /// If we did not iterate blocks in this order we could not safely say whether or not the results /// of its instructions are needed elsewhere. + /// + /// Returns the set of globals that were used in this function. + /// After processing all functions, the union of these sets enables determining the unused globals. pub(crate) fn dead_instruction_elimination( &mut self, insert_out_of_bounds_checks: bool, flattened: bool, - ) { + ) -> HashSet { let mut context = Context { flattened, ..Default::default() }; + for call_data in &self.dfg.data_bus.call_data { context.mark_used_instruction_results(&self.dfg, call_data.array_id); } @@ -70,11 +90,12 @@ impl Function { // instructions (we don't want to remove those checks, or instructions that are // dependencies of those checks) if inserted_out_of_bounds_checks { - self.dead_instruction_elimination(false, flattened); - return; + return self.dead_instruction_elimination(false, flattened); } context.remove_rc_instructions(&mut self.dfg); + + context.used_values.into_iter().filter(|value| self.dfg.is_global(*value)).collect() } } @@ -212,15 +233,17 @@ impl Context { /// Inspects a value and marks all instruction results as used. fn mark_used_instruction_results(&mut self, dfg: &DataFlowGraph, value_id: ValueId) { let value_id = dfg.resolve(value_id); - if matches!(&dfg[value_id], Value::Instruction { .. } | Value::Param { .. }) { + if matches!(&dfg[value_id], Value::Instruction { .. } | Value::Param { .. }) + || dfg.is_global(value_id) + { self.used_values.insert(value_id); } } - fn remove_rc_instructions(self, dfg: &mut DataFlowGraph) { + fn remove_rc_instructions(&self, dfg: &mut DataFlowGraph) { let unused_rc_values_by_block: HashMap> = - self.rc_instructions.into_iter().fold(HashMap::default(), |mut acc, (rc, block)| { - let value = match &dfg[rc] { + self.rc_instructions.iter().fold(HashMap::default(), |mut acc, (rc, block)| { + let value = match &dfg[*rc] { Instruction::IncrementRc { value } => *value, Instruction::DecrementRc { value } => *value, other => { @@ -231,7 +254,7 @@ impl Context { }; if !self.used_values.contains(&value) { - acc.entry(block).or_default().insert(rc); + acc.entry(*block).or_default().insert(*rc); } acc }); @@ -373,15 +396,16 @@ impl Context { ) -> bool { use Instruction::*; if let IncrementRc { value } | DecrementRc { value } = instruction { - if let Value::Instruction { instruction, .. } = &dfg[*value] { - return match &dfg[*instruction] { - MakeArray { .. } => true, - Call { func, .. } => { - matches!(&dfg[*func], Value::Intrinsic(_) | Value::ForeignFunction(_)) - } - _ => false, - }; - } + let Some(instruction) = dfg.get_local_or_global_instruction(*value) else { + return false; + }; + return match instruction { + MakeArray { .. } => true, + Call { func, .. } => { + matches!(&dfg[*func], Value::Intrinsic(_) | Value::ForeignFunction(_)) + } + _ => false, + }; } false } diff --git a/compiler/noirc_evaluator/src/ssa/opt/inlining.rs b/compiler/noirc_evaluator/src/ssa/opt/inlining.rs index b5cbc90e30d..88cf70e13ce 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/inlining.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/inlining.rs @@ -480,18 +480,21 @@ impl<'function> PerFunctionContext<'function> { let new_value = match &self.source_function.dfg[id] { value @ Value::Instruction { instruction, .. } => { - // TODO: Inlining the global into the function is only a temporary measure - // until Brillig gen with globals is working end to end if self.source_function.dfg.is_global(id) { - let Instruction::MakeArray { elements, typ } = &self.globals.dfg[*instruction] - else { - panic!("Only expect Instruction::MakeArray for a global"); - }; - let elements = elements - .iter() - .map(|element| self.translate_value(*element)) - .collect::>(); - return self.context.builder.insert_make_array(elements, typ.clone()); + if self.context.builder.current_function.dfg.runtime().is_acir() { + let Instruction::MakeArray { elements, typ } = + &self.globals.dfg[*instruction] + else { + panic!("Only expect Instruction::MakeArray for a global"); + }; + let elements = elements + .iter() + .map(|element| self.translate_value(*element)) + .collect::>(); + return self.context.builder.insert_make_array(elements, typ.clone()); + } else { + return id; + } } unreachable!("All Value::Instructions should already be known during inlining after creating the original inlined instruction. Unknown value {id} = {value:?}") } @@ -499,11 +502,16 @@ impl<'function> PerFunctionContext<'function> { unreachable!("All Value::Params should already be known from previous calls to translate_block. Unknown value {id} = {value:?}") } Value::NumericConstant { constant, typ } => { - // TODO: Inlining the global into the function is only a temporary measure - // until Brillig gen with globals is working end to end. - // The dfg indexes a global's inner value directly, so we will need to check here + // The dfg indexes a global's inner value directly, so we need to check here // whether we have a global. - self.context.builder.numeric_constant(*constant, *typ) + // We also only keep a global and do not inline it in a Brillig runtime. + if self.source_function.dfg.is_global(id) + && self.context.builder.current_function.dfg.runtime().is_brillig() + { + id + } else { + self.context.builder.numeric_constant(*constant, *typ) + } } Value::Function(function) => self.context.builder.import_function(*function), Value::Intrinsic(intrinsic) => self.context.builder.import_intrinsic_id(*intrinsic), diff --git a/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs b/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs index 4645a3ba26d..cae7735b2c3 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs @@ -24,7 +24,10 @@ use acvm::{acir::AcirField, FieldElement}; use im::HashSet; use crate::{ - brillig::brillig_gen::convert_ssa_function, + brillig::{ + brillig_gen::{brillig_globals::convert_ssa_globals, convert_ssa_function}, + brillig_ir::brillig_variable::BrilligVariable, + }, errors::RuntimeError, ssa::{ ir::{ @@ -87,8 +90,13 @@ impl Ssa { if has_unrolled { if let Some((orig_function, max_incr_pct)) = orig_func_and_max_incr_pct { - let new_size = brillig_bytecode_size(function); - let orig_size = brillig_bytecode_size(&orig_function); + // DIE is run at the end of our SSA optimizations, so we mark all globals as in use here. + let used_globals = &self.globals.dfg.values_iter().map(|(id, _)| id).collect(); + let (_, brillig_globals) = + convert_ssa_globals(false, &self.globals, used_globals); + + let new_size = brillig_bytecode_size(function, &brillig_globals); + let orig_size = brillig_bytecode_size(&orig_function, &brillig_globals); if !is_new_size_ok(orig_size, new_size, max_incr_pct) { *function = orig_function; } @@ -988,7 +996,10 @@ fn simplify_between_unrolls(function: &mut Function) { } /// Convert the function to Brillig bytecode and return the resulting size. -fn brillig_bytecode_size(function: &Function) -> usize { +fn brillig_bytecode_size( + function: &Function, + globals: &HashMap, +) -> usize { // We need to do some SSA passes in order for the conversion to be able to go ahead, // otherwise we can hit `unreachable!()` instructions in `convert_ssa_instruction`. // Creating a clone so as not to modify the originals. @@ -1000,7 +1011,7 @@ fn brillig_bytecode_size(function: &Function) -> usize { // This is to try to prevent hitting ICE. temp.dead_instruction_elimination(false, true); - convert_ssa_function(&temp, false).byte_code.len() + convert_ssa_function(&temp, false, globals).byte_code.len() } /// Decide if the new bytecode size is acceptable, compared to the original. diff --git a/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs b/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs index fcaaf74f533..98e7586cab3 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs @@ -5,7 +5,9 @@ use acvm::acir::circuit::ErrorSelector; use crate::ssa::{ function_builder::FunctionBuilder, ir::{ - basic_block::BasicBlockId, function::FunctionId, instruction::ConstrainError, + basic_block::BasicBlockId, + function::{Function, FunctionId}, + instruction::ConstrainError, value::ValueId, }, }; @@ -345,6 +347,8 @@ impl Translator { // that the SSA we parsed was printed by the `SsaBuilder`, which normalizes // before each print. ssa.normalize_ids(); + // Does not matter what ID we use here. + ssa.globals = Function::new("globals".to_owned(), ssa.main_id); ssa } diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs index 305ee16446d..7cd5c5c3990 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs @@ -1,6 +1,7 @@ use std::collections::BTreeMap; use acvm::acir::circuit::ErrorSelector; +use fxhash::FxHashSet as HashSet; use iter_extended::btree_map; use serde::{Deserialize, Serialize}; use serde_with::serde_as; @@ -11,6 +12,8 @@ use crate::ssa::ir::{ }; use noirc_frontend::hir_def::types::Type as HirType; +use super::ValueId; + /// Contains the entire SSA representation of the program. #[serde_as] #[derive(Serialize, Deserialize)] @@ -18,6 +21,7 @@ pub(crate) struct Ssa { #[serde_as(as = "Vec<(_, _)>")] pub(crate) functions: BTreeMap, pub(crate) globals: Function, + pub(crate) used_global_values: HashSet, pub(crate) main_id: FunctionId, #[serde(skip)] pub(crate) next_id: AtomicCounter, @@ -54,9 +58,12 @@ impl Ssa { next_id: AtomicCounter::starting_after(max_id), entry_point_to_generated_index: BTreeMap::new(), error_selector_to_type: error_types, - // This field should be set afterwards as globals are generated + // These fields should be set afterwards as globals are generated // outside of the FunctionBuilder, which is where the `Ssa` is instantiated. globals: Function::new_for_globals(), + // This field is set only after running DIE and is utilized + // for optimizing implementation of globals post-SSA. + used_global_values: HashSet::default(), } }