diff --git a/compiler/noirc_evaluator/src/acir/mod.rs b/compiler/noirc_evaluator/src/acir/mod.rs index fe46fbe72a..f83a2095f1 100644 --- a/compiler/noirc_evaluator/src/acir/mod.rs +++ b/compiler/noirc_evaluator/src/acir/mod.rs @@ -2893,7 +2893,6 @@ mod test { }, FieldElement, }; - use im::vector; use noirc_errors::Location; use noirc_frontend::monomorphization::ast::InlineType; use std::collections::BTreeMap; @@ -2904,6 +2903,7 @@ mod test { ssa::{ function_builder::FunctionBuilder, ir::{ + dfg::CallStack, function::FunctionId, instruction::BinaryOp, map::Id, @@ -2930,7 +2930,9 @@ mod test { builder.new_function("foo".into(), foo_id, inline_type); } // Set a call stack for testing whether `brillig_locations` in the `GeneratedAcir` was accurately set. - builder.set_call_stack(vector![Location::dummy(), Location::dummy()]); + let mut stack = CallStack::unit(Location::dummy()); + stack.push_back(Location::dummy()); + builder.set_call_stack(stack); let foo_v0 = builder.add_parameter(Type::field()); let foo_v1 = builder.add_parameter(Type::field()); diff --git a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index 80329ea448..fe654912ad 100644 --- a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -196,7 +196,7 @@ impl FunctionBuilder { } pub(crate) fn set_location(&mut self, location: Location) -> &mut FunctionBuilder { - self.call_stack = im::Vector::unit(location); + self.call_stack = CallStack::unit(location); self } diff --git a/compiler/noirc_evaluator/src/ssa/ir/cfg.rs b/compiler/noirc_evaluator/src/ssa/ir/cfg.rs index 38e6efa5b9..2268e6b219 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/cfg.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/cfg.rs @@ -231,7 +231,7 @@ mod tests { func.dfg[block2_id].set_terminator(TerminatorInstruction::Jmp { destination: ret_block_id, arguments: vec![], - call_stack: im::Vector::new(), + call_stack: CallStack::new(), }); func.dfg[block0_id].set_terminator(TerminatorInstruction::JmpIf { condition: cond, diff --git a/compiler/noirc_evaluator/src/ssa/ir/dfg.rs b/compiler/noirc_evaluator/src/ssa/ir/dfg.rs index 94074eb385..7546cba19d 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/dfg.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/dfg.rs @@ -97,7 +97,7 @@ pub(crate) struct DataFlowGraph { pub(crate) data_bus: DataBus, } -pub(crate) type CallStack = im::Vector; +pub(crate) type CallStack = super::list::List; impl DataFlowGraph { /// Creates a new basic block with no parameters. @@ -497,7 +497,7 @@ impl DataFlowGraph { pub(crate) fn get_value_call_stack(&self, value: ValueId) -> CallStack { match &self.values[self.resolve(value)] { Value::Instruction { instruction, .. } => self.get_call_stack(*instruction), - _ => im::Vector::new(), + _ => CallStack::new(), } } diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 0e9f6e31a0..fb35978d90 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -1,4 +1,3 @@ -use noirc_errors::Location; use serde::{Deserialize, Serialize}; use std::hash::{Hash, Hasher}; @@ -1248,7 +1247,7 @@ impl TerminatorInstruction { } } - pub(crate) fn call_stack(&self) -> im::Vector { + pub(crate) fn call_stack(&self) -> CallStack { match self { TerminatorInstruction::JmpIf { call_stack, .. } | TerminatorInstruction::Jmp { call_stack, .. } diff --git a/compiler/noirc_evaluator/src/ssa/ir/list.rs b/compiler/noirc_evaluator/src/ssa/ir/list.rs new file mode 100644 index 0000000000..9a84d30444 --- /dev/null +++ b/compiler/noirc_evaluator/src/ssa/ir/list.rs @@ -0,0 +1,187 @@ +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +/// A shared linked list type intended to be cloned +#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct List { + head: Arc>, + len: usize, +} + +#[derive(Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +enum Node { + #[default] + Nil, + Cons(T, Arc>), +} + +impl Default for List { + fn default() -> Self { + List { head: Arc::new(Node::Nil), len: 0 } + } +} + +impl List { + pub fn new() -> Self { + Self::default() + } + + /// This is actually a push_front since we just create a new head for the + /// list. This is done so that the tail of the list can still be shared. + /// In the case of call stacks, the last node will be main, while the top + /// of the call stack will be the head of this list. + pub fn push_back(&mut self, value: T) { + self.len += 1; + self.head = Arc::new(Node::Cons(value, self.head.clone())); + } + + /// It is more efficient to iterate from the head of the list towards the tail. + /// For callstacks this means from the top of the call stack towards main. + fn iter_rev(&self) -> IterRev { + IterRev { head: &self.head, len: self.len } + } + + pub fn clear(&mut self) { + *self = Self::default(); + } + + pub fn append(&mut self, other: Self) + where + T: Copy + std::fmt::Debug, + { + let other = other.into_iter().collect::>(); + + for item in other { + self.push_back(item); + } + } + + pub fn len(&self) -> usize { + self.len + } + + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + fn pop_front(&mut self) -> Option + where + T: Copy, + { + match self.head.as_ref() { + Node::Nil => None, + Node::Cons(value, rest) => { + let value = *value; + self.head = rest.clone(); + self.len -= 1; + Some(value) + } + } + } + + pub fn truncate(&mut self, len: usize) + where + T: Copy, + { + if self.len > len { + for _ in 0..self.len - len { + self.pop_front(); + } + } + } + + pub fn unit(item: T) -> Self { + let mut this = Self::default(); + this.push_back(item); + this + } + + pub fn back(&self) -> Option<&T> { + match self.head.as_ref() { + Node::Nil => None, + Node::Cons(item, _) => Some(item), + } + } +} + +pub struct IterRev<'a, T> { + head: &'a Node, + len: usize, +} + +impl IntoIterator for List +where + T: Copy + std::fmt::Debug, +{ + type Item = T; + + type IntoIter = std::iter::Rev>; + + fn into_iter(self) -> Self::IntoIter { + let items: Vec<_> = self.iter_rev().copied().collect(); + items.into_iter().rev() + } +} + +impl<'a, T> IntoIterator for &'a List { + type Item = &'a T; + + type IntoIter = std::iter::Rev< as IntoIterator>::IntoIter>; + + fn into_iter(self) -> Self::IntoIter { + let items: Vec<_> = self.iter_rev().collect(); + items.into_iter().rev() + } +} + +impl<'a, T> Iterator for IterRev<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + match self.head { + Node::Nil => None, + Node::Cons(value, rest) => { + self.head = rest; + Some(value) + } + } + } + + fn size_hint(&self) -> (usize, Option) { + (0, Some(self.len)) + } +} + +impl<'a, T> ExactSizeIterator for IterRev<'a, T> {} + +impl std::fmt::Debug for List +where + T: std::fmt::Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "[")?; + for (i, item) in self.iter_rev().enumerate() { + if i != 0 { + write!(f, ", ")?; + } + write!(f, "{item:?}")?; + } + write!(f, "]") + } +} + +impl std::fmt::Display for List +where + T: std::fmt::Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "[")?; + for (i, item) in self.iter_rev().enumerate() { + if i != 0 { + write!(f, ", ")?; + } + write!(f, "{item}")?; + } + write!(f, "]") + } +} diff --git a/compiler/noirc_evaluator/src/ssa/ir/mod.rs b/compiler/noirc_evaluator/src/ssa/ir/mod.rs index 3ef680dda0..89ba22e8b7 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/mod.rs @@ -5,6 +5,7 @@ pub(crate) mod dom; pub(crate) mod function; pub(crate) mod function_inserter; pub(crate) mod instruction; +pub mod list; pub(crate) mod map; pub(crate) mod post_order; pub(crate) mod printer; diff --git a/compiler/noirc_evaluator/src/ssa/opt/die.rs b/compiler/noirc_evaluator/src/ssa/opt/die.rs index de3ac44652..b0843f327c 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/die.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/die.rs @@ -1,14 +1,12 @@ //! Dead Instruction Elimination (DIE) pass: Removes any instruction without side-effects for //! which the results are unused. use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; -use im::Vector; -use noirc_errors::Location; use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; use crate::ssa::{ ir::{ basic_block::{BasicBlock, BasicBlockId}, - dfg::DataFlowGraph, + dfg::{CallStack, DataFlowGraph}, function::Function, instruction::{BinaryOp, Instruction, InstructionId, Intrinsic}, post_order::PostOrder, @@ -484,7 +482,7 @@ fn apply_side_effects( rhs: ValueId, function: &mut Function, block_id: BasicBlockId, - call_stack: Vector, + call_stack: CallStack, ) -> (ValueId, ValueId) { // See if there's an active "enable side effects" condition let Some(condition) = side_effects_condition else { diff --git a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index dc7952979e..9c3fd72f28 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -337,7 +337,7 @@ impl<'f> Context<'f> { self.insert_instruction_with_typevars( Instruction::EnableSideEffectsIf { condition: one }, None, - im::Vector::new(), + CallStack::new(), ); self.push_instruction(*instruction); self.insert_current_side_effects_enabled();