-
Notifications
You must be signed in to change notification settings - Fork 223
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: Add pass to normalize Ids in SSA (#5909)
# Description ## Problem\* SSA can be difficult to debug manually, particularly for very large files. Even when comparing two sources side by side it can be hard to find exactly where they differ since one optimization difference earlier on can affect where ValueIds start in every function afterward. ## Summary\* This PR adds a pass to normalize ids in an SSA program - restarting from v0 after every SSA pass instead of continuing from the previous end. The goal of this is to be able to take two SSA programs and easily diff them to find out where they start differing. E.g. using this on two files containing the final SSA from #5771 in both failing and passing versions, it is clear that they differ in exactly one ValueId in one instruction. ## Additional Context This new pass is only run when `--show-ssa` is specified, and is run before each printout. ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --------- Co-authored-by: Tom French <[email protected]>
- Loading branch information
1 parent
33bd102
commit 79df6a3
Showing
8 changed files
with
227 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
194 changes: 194 additions & 0 deletions
194
compiler/noirc_evaluator/src/ssa/opt/normalize_value_ids.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
use std::collections::BTreeMap; | ||
|
||
use crate::ssa::{ | ||
ir::{ | ||
basic_block::BasicBlockId, | ||
function::{Function, FunctionId}, | ||
map::SparseMap, | ||
post_order::PostOrder, | ||
value::{Value, ValueId}, | ||
}, | ||
ssa_gen::Ssa, | ||
}; | ||
use fxhash::FxHashMap as HashMap; | ||
use iter_extended::vecmap; | ||
|
||
impl Ssa { | ||
/// This is a debugging pass which re-inserts each instruction | ||
/// and block in a fresh DFG context for each function so that ValueIds, | ||
/// BasicBlockIds, and FunctionIds are always identical for the same SSA code. | ||
/// | ||
/// During normal compilation this is often not the case since prior passes | ||
/// may increase the ID counter so that later passes start at different offsets, | ||
/// even if they contain the same SSA code. | ||
pub(crate) fn normalize_ids(&mut self) { | ||
let mut context = Context::default(); | ||
context.populate_functions(&self.functions); | ||
for function in self.functions.values_mut() { | ||
context.normalize_ids(function); | ||
} | ||
self.functions = context.functions.into_btree(); | ||
} | ||
} | ||
|
||
#[derive(Default)] | ||
struct Context { | ||
functions: SparseMap<Function>, | ||
|
||
new_ids: IdMaps, | ||
} | ||
|
||
/// Maps from old ids to new ones. | ||
/// Separate from the rest of Context so we can call mutable methods on it | ||
/// while Context gives out mutable references to functions within. | ||
#[derive(Default)] | ||
struct IdMaps { | ||
// Maps old function id -> new function id | ||
function_ids: HashMap<FunctionId, FunctionId>, | ||
|
||
// Maps old block id -> new block id | ||
// Cleared in between each function. | ||
blocks: HashMap<BasicBlockId, BasicBlockId>, | ||
|
||
// Maps old value id -> new value id | ||
// Cleared in between each function. | ||
values: HashMap<ValueId, ValueId>, | ||
} | ||
|
||
impl Context { | ||
fn populate_functions(&mut self, functions: &BTreeMap<FunctionId, Function>) { | ||
for (id, function) in functions { | ||
self.functions.insert_with_id(|new_id| { | ||
self.new_ids.function_ids.insert(*id, new_id); | ||
Function::clone_signature(new_id, function) | ||
}); | ||
} | ||
} | ||
|
||
fn normalize_ids(&mut self, old_function: &mut Function) { | ||
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]; | ||
|
||
let mut reachable_blocks = PostOrder::with_function(old_function).into_vec(); | ||
reachable_blocks.reverse(); | ||
|
||
self.new_ids.populate_blocks(&reachable_blocks, old_function, new_function); | ||
|
||
// Map each parameter, instruction, and terminator | ||
for old_block_id in reachable_blocks { | ||
let new_block_id = self.new_ids.blocks[&old_block_id]; | ||
|
||
let old_block = &mut old_function.dfg[old_block_id]; | ||
for old_instruction_id in old_block.take_instructions() { | ||
let instruction = old_function.dfg[old_instruction_id] | ||
.map_values(|value| self.new_ids.map_value(new_function, old_function, value)); | ||
|
||
let call_stack = old_function.dfg.get_call_stack(old_instruction_id); | ||
let old_results = old_function.dfg.instruction_results(old_instruction_id); | ||
|
||
let ctrl_typevars = instruction | ||
.requires_ctrl_typevars() | ||
.then(|| vecmap(old_results, |result| old_function.dfg.type_of_value(*result))); | ||
|
||
let new_results = new_function.dfg.insert_instruction_and_results( | ||
instruction, | ||
new_block_id, | ||
ctrl_typevars, | ||
call_stack, | ||
); | ||
|
||
assert_eq!(old_results.len(), new_results.len()); | ||
for (old_result, new_result) in old_results.iter().zip(new_results.results().iter()) | ||
{ | ||
let old_result = old_function.dfg.resolve(*old_result); | ||
self.new_ids.values.insert(old_result, *new_result); | ||
} | ||
} | ||
|
||
let old_block = &mut old_function.dfg[old_block_id]; | ||
let mut terminator = old_block | ||
.take_terminator() | ||
.map_values(|value| self.new_ids.map_value(new_function, old_function, value)); | ||
terminator.mutate_blocks(|old_block| self.new_ids.blocks[&old_block]); | ||
new_function.dfg.set_block_terminator(new_block_id, terminator); | ||
} | ||
} | ||
} | ||
|
||
impl IdMaps { | ||
fn populate_blocks( | ||
&mut self, | ||
reachable_blocks: &[BasicBlockId], | ||
old_function: &mut Function, | ||
new_function: &mut Function, | ||
) { | ||
let old_entry = old_function.entry_block(); | ||
self.blocks.insert(old_entry, new_function.entry_block()); | ||
|
||
for old_id in reachable_blocks { | ||
if *old_id != old_entry { | ||
let new_id = new_function.dfg.make_block(); | ||
self.blocks.insert(*old_id, new_id); | ||
} | ||
|
||
let new_id = self.blocks[old_id]; | ||
let old_block = &mut old_function.dfg[*old_id]; | ||
for old_parameter in old_block.take_parameters() { | ||
let old_parameter = old_function.dfg.resolve(old_parameter); | ||
let typ = old_function.dfg.type_of_value(old_parameter); | ||
let new_parameter = new_function.dfg.add_block_parameter(new_id, typ); | ||
self.values.insert(old_parameter, new_parameter); | ||
} | ||
} | ||
} | ||
|
||
fn map_value( | ||
&mut self, | ||
new_function: &mut Function, | ||
old_function: &Function, | ||
old_value: ValueId, | ||
) -> ValueId { | ||
let old_value = old_function.dfg.resolve(old_value); | ||
match &old_function.dfg[old_value] { | ||
value @ Value::Instruction { instruction, .. } => { | ||
*self.values.get(&old_value).unwrap_or_else(|| { | ||
let instruction = &old_function.dfg[*instruction]; | ||
unreachable!("Unmapped value with id {old_value}: {value:?}\n from instruction: {instruction:?}, SSA: {old_function}") | ||
}) | ||
} | ||
|
||
value @ Value::Param { .. } => { | ||
*self.values.get(&old_value).unwrap_or_else(|| { | ||
unreachable!("Unmapped value with id {old_value}: {value:?}") | ||
}) | ||
} | ||
|
||
Value::Function(id) => { | ||
let new_id = self.function_ids[id]; | ||
new_function.dfg.import_function(new_id) | ||
} | ||
|
||
Value::NumericConstant { constant, typ } => { | ||
new_function.dfg.make_constant(*constant, typ.clone()) | ||
} | ||
Value::Array { array, typ } => { | ||
if let Some(value) = self.values.get(&old_value) { | ||
return *value; | ||
} | ||
|
||
let array = array | ||
.iter() | ||
.map(|value| self.map_value(new_function, old_function, *value)) | ||
.collect(); | ||
let new_value = new_function.dfg.make_array(array, typ.clone()); | ||
self.values.insert(old_value, new_value); | ||
new_value | ||
} | ||
Value::Intrinsic(intrinsic) => new_function.dfg.import_intrinsic(*intrinsic), | ||
Value::ForeignFunction(name) => new_function.dfg.import_foreign_function(name), | ||
} | ||
} | ||
} |