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: SSA globals in monomorphization and SSA gen #6985

Merged
merged 27 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c7d8009
cleanup global generation with GlobalsBuilder
vezenovm Jan 8, 2025
4b19a7b
all tests paassing
vezenovm Jan 8, 2025
c8e08d3
cargo fmt
vezenovm Jan 8, 2025
8a24746
fmt and clippy
vezenovm Jan 8, 2025
e3358e3
some comments for context
vezenovm Jan 8, 2025
c3948fe
nargo fmt
vezenovm Jan 8, 2025
328004e
merge master and conflicts w/ printer
vezenovm Jan 8, 2025
40c50fc
remove type from Value::Global and cleanup
vezenovm Jan 8, 2025
7744179
bring back global type
vezenovm Jan 8, 2025
68ddd14
Update compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs
vezenovm Jan 8, 2025
6823019
only print globals when printing ssa if globals exist
vezenovm Jan 8, 2025
73ceea5
Merge remote-tracking branch 'origin/mv/ssa-globals-1' into mv/ssa-gl…
vezenovm Jan 8, 2025
cbe377e
make an eval method
vezenovm Jan 8, 2025
2d750fb
Update compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs
vezenovm Jan 8, 2025
faeb30f
Update compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs
vezenovm Jan 8, 2025
2322cb6
Update test_programs/execution_success/global_var_regression_simple/s…
vezenovm Jan 8, 2025
08a2aba
Merge branch 'master' into mv/ssa-globals-1
vezenovm Jan 8, 2025
fa6d9cf
cargo mft
vezenovm Jan 8, 2025
200964f
Merge branch 'master' into mv/ssa-globals-1
vezenovm Jan 8, 2025
f98c713
merge conflicts w/ master
vezenovm Jan 9, 2025
ad19fc3
Merge branch 'master' into mv/ssa-globals-1
vezenovm Jan 9, 2025
bb5a9ea
Update compiler/noirc_frontend/src/hir/comptime/value.rs
vezenovm Jan 9, 2025
74a7358
do ot duplicate codegen
vezenovm Jan 9, 2025
df5eef0
Merge branch 'master' into mv/ssa-globals-1
vezenovm Jan 9, 2025
584cc92
Merge branch 'master' into mv/ssa-globals-1
vezenovm Jan 10, 2025
8ff2934
switch to Function from DataFlowGraph to represent globals
vezenovm Jan 10, 2025
aae17de
remove unnecessary lifetime on InlineContext
vezenovm Jan 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions compiler/noirc_evaluator/src/ssa/ir/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pub(crate) struct Function {
/// Name of the function for debugging only
name: String,

id: FunctionId,
id: Option<FunctionId>,

/// The DataFlowGraph holds the majority of data pertaining to the function
/// including its blocks, instructions, and values.
Expand All @@ -90,14 +90,22 @@ impl Function {
pub(crate) fn new(name: String, id: FunctionId) -> Self {
let mut dfg = DataFlowGraph::default();
let entry_block = dfg.make_block();
Self { name, id, entry_block, dfg }
Self { name, id: Some(id), entry_block, dfg }
}

/// Globals are generated using the same codegen process as functions.
/// To avoid a recursive global context we should create a pseudo function to mock a globals context.
pub(crate) fn new_for_globals() -> Self {
let mut dfg = DataFlowGraph::default();
let entry_block = dfg.make_block();
Self { name: "globals".to_owned(), id: None, entry_block, dfg }
}

/// Creates a new function as a clone of the one passed in with the passed in id.
pub(crate) fn clone_with_id(id: FunctionId, another: &Function) -> Self {
let dfg = another.dfg.clone();
let entry_block = another.entry_block;
Self { name: another.name.clone(), id, entry_block, dfg }
Self { name: another.name.clone(), id: Some(id), entry_block, dfg }
}

/// Takes the signature (function name & runtime) from a function but does not copy the body.
Expand All @@ -115,7 +123,7 @@ impl Function {

/// The id of the function.
pub(crate) fn id(&self) -> FunctionId {
self.id
self.id.expect("FunctionId should be initialized")
}

/// Runtime type of the function.
Expand Down Expand Up @@ -228,12 +236,6 @@ pub(crate) struct Signature {
pub(crate) returns: Vec<Type>,
}

impl std::fmt::Display for Function {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
super::printer::display_function(self, f)
}
}

#[test]
fn sign_smoke() {
let mut signature = Signature::default();
Expand Down
67 changes: 52 additions & 15 deletions compiler/noirc_evaluator/src/ssa/ir/printer.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
//! This file is for pretty-printing the SSA IR in a human-readable form for debugging.
use std::fmt::{Formatter, Result};
use std::fmt::{Display, Formatter, Result};

use acvm::acir::AcirField;
use im::Vector;
use iter_extended::vecmap;

use crate::ssa::ir::types::{NumericType, Type};
use crate::ssa::{
ir::types::{NumericType, Type},
Ssa,
};

use super::{
basic_block::BasicBlockId,
Expand All @@ -15,8 +18,42 @@ use super::{
value::{Value, ValueId},
};

impl Display for Ssa {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
for (id, global_value) in self.globals.dfg.values_iter() {
match global_value {
Value::NumericConstant { constant, typ } => {
writeln!(f, "g{} = {typ} {constant}", id.to_u32())?;
}
Value::Instruction { instruction, .. } => {
display_instruction(&self.globals.dfg, *instruction, true, f)?;
}
Value::Global(_) => {
panic!("Value::Global should only be in the function dfg");
}
_ => panic!("Expected only numeric constant or instruction"),
};
}

if self.globals.dfg.values_iter().len() > 0 {
writeln!(f)?;
}

for function in self.functions.values() {
writeln!(f, "{function}")?;
}
Ok(())
}
}

impl Display for Function {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
display_function(self, f)
}
}

/// Helper function for Function's Display impl to pretty-print the function with the given formatter.
pub(crate) fn display_function(function: &Function, f: &mut Formatter) -> Result {
fn display_function(function: &Function, f: &mut Formatter) -> Result {
writeln!(f, "{} fn {} {} {{", function.runtime(), function.name(), function.id())?;
for block_id in function.reachable_blocks() {
display_block(&function.dfg, block_id, f)?;
Expand All @@ -25,17 +62,13 @@ pub(crate) fn display_function(function: &Function, f: &mut Formatter) -> Result
}

/// Display a single block. This will not display the block's successors.
pub(crate) fn display_block(
dfg: &DataFlowGraph,
block_id: BasicBlockId,
f: &mut Formatter,
) -> Result {
fn display_block(dfg: &DataFlowGraph, block_id: BasicBlockId, f: &mut Formatter) -> Result {
let block = &dfg[block_id];

writeln!(f, " {}({}):", block_id, value_list_with_types(dfg, block.parameters()))?;

for instruction in block.instructions() {
display_instruction(dfg, *instruction, true, f)?;
display_instruction(dfg, *instruction, false, f)?;
}

display_terminator(dfg, block.terminator(), f)
Expand All @@ -54,7 +87,7 @@ fn value(dfg: &DataFlowGraph, id: ValueId) -> String {
Value::ForeignFunction(function) => function.clone(),
Value::Param { .. } | Value::Instruction { .. } => id.to_string(),
Value::Global(_) => {
format!("@{id}")
format!("g{}", id.to_u32())
}
}
}
Expand All @@ -75,7 +108,7 @@ fn value_list(dfg: &DataFlowGraph, values: &[ValueId]) -> String {
}

/// Display a terminator instruction
pub(crate) fn display_terminator(
fn display_terminator(
dfg: &DataFlowGraph,
terminator: Option<&TerminatorInstruction>,
f: &mut Formatter,
Expand Down Expand Up @@ -110,20 +143,24 @@ pub(crate) fn display_terminator(
}

/// Display an arbitrary instruction
pub(crate) fn display_instruction(
fn display_instruction(
dfg: &DataFlowGraph,
instruction: InstructionId,
indent: bool,
in_global_space: bool,
f: &mut Formatter,
) -> Result {
if indent {
if !in_global_space {
// instructions are always indented within a function
write!(f, " ")?;
}

let results = dfg.instruction_results(instruction);
if !results.is_empty() {
write!(f, "{} = ", value_list(dfg, results))?;
let mut value_list = value_list(dfg, results);
if in_global_space {
value_list = value_list.replace('v', "g");
}
write!(f, "{} = ", value_list)?;
}

display_instruction_inner(dfg, &dfg[instruction], results, f)
Expand Down
18 changes: 4 additions & 14 deletions compiler/noirc_evaluator/src/ssa/ir/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,16 @@ pub(crate) enum Value {
/// Example, if you add two numbers together, then the resulting
/// value would have position `0`, the typ would be the type
/// of the operands, and the instruction would map to an add instruction.
Instruction {
instruction: InstructionId,
position: usize,
typ: Type,
},
Instruction { instruction: InstructionId, position: usize, typ: Type },

/// This Value originates from a block parameter. Since function parameters
/// are also represented as block parameters, this includes function parameters as well.
///
/// position -- the index of this Value in the block parameters list
Param {
block: BasicBlockId,
position: usize,
typ: Type,
},
Param { block: BasicBlockId, position: usize, typ: Type },

/// This Value originates from a numeric constant
NumericConstant {
constant: FieldElement,
typ: NumericType,
},
NumericConstant { constant: FieldElement, typ: NumericType },

/// This Value refers to a function in the IR.
/// Functions always have the type Type::Function.
Expand All @@ -65,6 +54,7 @@ pub(crate) enum Value {
/// other than generating different backend operations and being only accessible through Brillig.
ForeignFunction(String),

/// This Value indicates we have a reserved slot that needs to be accessed in a separate global context
Global(Type),
}

Expand Down
32 changes: 17 additions & 15 deletions compiler/noirc_evaluator/src/ssa/opt/inlining.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::ssa::{
instruction::{Instruction, InstructionId, TerminatorInstruction},
value::{Value, ValueId},
},
ssa_gen::{context::GlobalsContext, Ssa},
ssa_gen::Ssa,
};
use fxhash::FxHashMap as HashMap;

Expand Down Expand Up @@ -80,7 +80,7 @@ impl Ssa {
/// This works using an internal FunctionBuilder to build a new main function from scratch.
/// Doing it this way properly handles importing instructions between functions and lets us
/// reuse the existing API at the cost of essentially cloning each of main's instructions.
struct InlineContext<'global> {
struct InlineContext {
recursion_level: u32,
builder: FunctionBuilder,

Expand All @@ -98,22 +98,20 @@ struct InlineContext<'global> {

// These are the functions of the program that we shouldn't inline.
functions_not_to_inline: BTreeSet<FunctionId>,

globals: &'global GlobalsContext,
}

/// The per-function inlining context contains information that is only valid for one function.
/// For example, each function has its own DataFlowGraph, and thus each function needs a translation
/// layer to translate between BlockId to BlockId for the current function and the function to
/// inline into. The same goes for ValueIds, InstructionIds, and for storing other data like
/// parameter to argument mappings.
struct PerFunctionContext<'function, 'global> {
struct PerFunctionContext<'function> {
/// The source function is the function we're currently inlining into the function being built.
source_function: &'function Function,

/// The shared inlining context for all functions. This notably contains the FunctionBuilder used
/// to build the function we're inlining into.
context: &'function mut InlineContext<'global>,
context: &'function mut InlineContext,

/// Maps ValueIds in the function being inlined to the new ValueIds to use in the function
/// being inlined into. This mapping also contains the mapping from parameter values to
Expand All @@ -131,6 +129,8 @@ struct PerFunctionContext<'function, 'global> {

/// True if we're currently working on the entry point function.
inlining_entry: bool,

globals: &'function Function,
}

/// Utility function to find out the direct calls of a function.
Expand Down Expand Up @@ -349,14 +349,14 @@ fn compute_function_interface_cost(func: &Function) -> usize {
func.parameters().len() + func.returns().len()
}

impl<'global> InlineContext<'global> {
impl InlineContext {
/// Create a new context object for the function inlining pass.
/// This starts off with an empty mapping of instructions for main's parameters.
/// The function being inlined into will always be the main function, although it is
/// actually a copy that is created in case the original main is still needed from a function
/// that could not be inlined calling it.
fn new(
ssa: &'global Ssa,
ssa: &Ssa,
entry_point: FunctionId,
inline_no_predicates_functions: bool,
functions_not_to_inline: BTreeSet<FunctionId>,
Expand All @@ -371,15 +371,15 @@ impl<'global> InlineContext<'global> {
call_stack: CallStackId::root(),
inline_no_predicates_functions,
functions_not_to_inline,
globals: &ssa.globals,
}
}

/// Start inlining the entry point function and all functions reachable from it.
fn inline_all(mut self, ssa: &Ssa) -> Function {
let entry_point = &ssa.functions[&self.entry_point];

let mut context = PerFunctionContext::new(&mut self, entry_point);
// let globals = self.globals;
let mut context = PerFunctionContext::new(&mut self, entry_point, &ssa.globals);
context.inlining_entry = true;

for (_, value) in ssa.globals.dfg.values_iter() {
Expand Down Expand Up @@ -429,7 +429,7 @@ impl<'global> InlineContext<'global> {
);
}

let mut context = PerFunctionContext::new(self, source_function);
let mut context = PerFunctionContext::new(self, source_function, &ssa.globals);

let parameters = source_function.parameters();
assert_eq!(parameters.len(), arguments.len());
Expand All @@ -444,21 +444,23 @@ impl<'global> InlineContext<'global> {
}
}

impl<'function, 'global> PerFunctionContext<'function, 'global> {
impl<'function> PerFunctionContext<'function> {
/// Create a new PerFunctionContext from the source function.
/// The value and block mappings for this context are initially empty except
/// for containing the mapping between parameters in the source_function and
/// the arguments of the destination function.
fn new(
context: &'function mut InlineContext<'global>,
context: &'function mut InlineContext,
source_function: &'function Function,
globals: &'function Function,
) -> Self {
Self {
context,
source_function,
blocks: HashMap::default(),
values: HashMap::default(),
inlining_entry: false,
globals,
}
}

Expand Down Expand Up @@ -491,10 +493,10 @@ impl<'function, 'global> PerFunctionContext<'function, 'global> {
Value::Global(_) => {
// TODO: Inlining the global into the function is only a temporary measure
// until Brillig gen with globals is working end to end
match &self.context.globals.dfg[id] {
match &self.globals.dfg[id] {
Value::Instruction { instruction, .. } => {
let Instruction::MakeArray { elements, typ } =
&self.context.globals.dfg[*instruction]
&self.globals.dfg[*instruction]
else {
panic!("Only expect Instruction::MakeArray for a global");
};
Expand Down
9 changes: 5 additions & 4 deletions compiler/noirc_evaluator/src/ssa/opt/normalize_value_ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ use std::collections::BTreeMap;
use crate::ssa::{
ir::{
basic_block::BasicBlockId,
dfg::DataFlowGraph,
function::{Function, FunctionId},
map::SparseMap,
post_order::PostOrder,
value::{Value, ValueId},
},
ssa_gen::{context::GlobalsContext, Ssa},
ssa_gen::Ssa,
};
use fxhash::FxHashMap as HashMap;
use iter_extended::vecmap;
Expand All @@ -25,7 +26,7 @@ impl Ssa {
let mut context = Context::default();
context.populate_functions(&self.functions);
for function in self.functions.values_mut() {
context.normalize_ids(function, &self.globals);
context.normalize_ids(function, &self.globals.dfg);
}
self.functions = context.functions.into_btree();
}
Expand Down Expand Up @@ -65,14 +66,14 @@ impl Context {
}
}

fn normalize_ids(&mut self, old_function: &mut Function, globals: &GlobalsContext) {
fn normalize_ids(&mut self, old_function: &mut Function, globals: &DataFlowGraph) {
self.new_ids.blocks.clear();
self.new_ids.values.clear();

let new_function_id = self.new_ids.function_ids[&old_function.id()];
let new_function = &mut self.functions[new_function_id];

for (_, value) in globals.dfg.values_iter() {
for (_, value) in globals.values_iter() {
new_function.dfg.make_global(value.get_type().into_owned());
}

Expand Down
Loading
Loading