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 parser #6489

Merged
merged 68 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
a5c4c5f
WIP
asterite Nov 7, 2024
d610616
SSA printer: don't leave extra space after return without values
asterite Nov 7, 2024
0876084
SSA parser: parse an empty function
asterite Nov 7, 2024
c2f9164
Also test brillig function
asterite Nov 7, 2024
70ab9b0
return Field
asterite Nov 8, 2024
6bb0bcc
Parse all integer types
asterite Nov 8, 2024
a69145f
Arrays
asterite Nov 8, 2024
b7dba51
Block parameters and variables
asterite Nov 8, 2024
30b49ef
Carry spans in identifiers
asterite Nov 8, 2024
994dc38
Blocks and jmp
asterite Nov 8, 2024
fb5c8b6
jmpif
asterite Nov 8, 2024
3861d82
Handle ambiguity with return
asterite Nov 8, 2024
cead105
Functions and calls
asterite Nov 8, 2024
3744974
Multiple return values
asterite Nov 8, 2024
1d7c2a2
cast
asterite Nov 8, 2024
af954ec
Helper to define variables
asterite Nov 8, 2024
e179d10
constrain
asterite Nov 8, 2024
989fc0e
Simplify one test
asterite Nov 8, 2024
3907e7f
enable_side_effects
asterite Nov 8, 2024
7ba73ff
array_get
asterite Nov 8, 2024
61c0880
Simplify another test
asterite Nov 8, 2024
3601c77
Slightly better tests
asterite Nov 8, 2024
4a190ca
Better place for test helper
asterite Nov 8, 2024
cf4e560
binary
asterite Nov 8, 2024
733ae28
Simplify another test
asterite Nov 8, 2024
4aac831
truncate
asterite Nov 8, 2024
d048cb1
Simplify another test
asterite Nov 8, 2024
3ae58b2
Fix serialization test
asterite Nov 8, 2024
3885151
array_set
asterite Nov 8, 2024
eb9c706
Fix bug in array set
asterite Nov 8, 2024
1182893
Simplify another test
asterite Nov 8, 2024
85ccc2a
More uniform way to specify types
asterite Nov 9, 2024
241f0ed
not
asterite Nov 9, 2024
18ba63e
range_check
asterite Nov 9, 2024
19341b6
Fix serialization roundtrip again
asterite Nov 9, 2024
5a27b52
allocate
asterite Nov 9, 2024
253897f
load
asterite Nov 9, 2024
7b2870b
store
asterite Nov 9, 2024
af832a5
inc_rc
asterite Nov 9, 2024
eb512ae
dec_rc
asterite Nov 9, 2024
962b80e
call without returns
asterite Nov 9, 2024
2d06011
Intrinsic
asterite Nov 9, 2024
543acad
Trim leading whitespace from lines for better-looking tests
asterite Nov 10, 2024
f720d39
Update more examples
asterite Nov 10, 2024
08e8f33
Fix test
asterite Nov 10, 2024
95b65ae
Merge branch 'master' into ab/ssa-parser
asterite Nov 10, 2024
2e13c59
Another example
asterite Nov 10, 2024
53d3027
Align src
asterite Nov 10, 2024
457a20b
More examples
asterite Nov 10, 2024
f75ca44
mutable array_set
asterite Nov 10, 2024
a049769
Some die tests
asterite Nov 10, 2024
9bb740f
More die test s
asterite Nov 11, 2024
9415de2
Parse &mut type
asterite Nov 11, 2024
0254be9
A few more tests
asterite Nov 11, 2024
bacca20
Allow comments in SSA
asterite Nov 11, 2024
5da0cc0
Simplify a test that has a comment
asterite Nov 11, 2024
8c53a41
Refactor
asterite Nov 11, 2024
6cec6cb
Extract `parse_assignment`
asterite Nov 11, 2024
8854b7d
Simplify `eat_binary_op`
asterite Nov 11, 2024
ed10260
Error in parse_assignment, not outside
asterite Nov 11, 2024
51af914
Make `allocate` return `-> &mut ...`
asterite Nov 12, 2024
52dc715
Merge branch 'master' into ab/ssa-parser
TomAFrench Nov 12, 2024
13e55b1
Merge branch 'ab/ssa-parser' of github.com:noir-lang/noir into ab/ssa…
asterite Nov 12, 2024
280b522
Show parse errors embedded in the original code
asterite Nov 12, 2024
8825b25
Swap arguments order in assert_ssa_equals
asterite Nov 12, 2024
757c8bd
Error early if expected SSA is not valid SSA
asterite Nov 12, 2024
cdfb505
assert_ssa_equals -> assert_normalized_ssa_equals
asterite Nov 12, 2024
9e6dc2d
Fix test
asterite Nov 12, 2024
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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion compiler/noirc_evaluator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ fxhash.workspace = true
iter-extended.workspace = true
thiserror.workspace = true
num-bigint = "0.4"
num-traits.workspace = true
im.workspace = true
serde.workspace = true
serde_json.workspace = true
Expand All @@ -31,7 +32,8 @@ cfg-if.workspace = true

[dev-dependencies]
proptest.workspace = true
similar-asserts.workspace = true

[features]
bn254 = ["noirc_frontend/bn254"]
bls12_381= []
bls12_381 = []
18 changes: 18 additions & 0 deletions compiler/noirc_evaluator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,21 @@ pub mod ssa;
pub mod brillig;

pub use ssa::create_program;

/// Trims leading whitespace from each line of the input string, according to
/// how much leading whitespace there is on the first non-empty line.
#[cfg(test)]
pub(crate) fn trim_leading_whitespace_from_lines(src: &str) -> String {
let mut lines = src.trim_end().lines();
let mut first_line = lines.next().unwrap();
while first_line.is_empty() {
first_line = lines.next().unwrap();
}
let indent = first_line.len() - first_line.trim_start().len();
let mut result = first_line.trim_start().to_string();
for line in lines {
result.push('\n');
result.push_str(&line[indent..]);
}
result
}
1 change: 1 addition & 0 deletions compiler/noirc_evaluator/src/ssa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ mod checks;
pub(super) mod function_builder;
pub mod ir;
mod opt;
mod parser;
pub mod ssa_gen;

pub struct SsaEvaluatorOptions {
Expand Down
10 changes: 10 additions & 0 deletions compiler/noirc_evaluator/src/ssa/function_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,16 @@ impl FunctionBuilder {
.first()
}

pub(crate) fn insert_mutable_array_set(
&mut self,
array: ValueId,
index: ValueId,
value: ValueId,
) -> ValueId {
self.insert_instruction(Instruction::ArraySet { array, index, value, mutable: true }, None)
.first()
}

/// Insert an instruction to increment an array's reference count. This only has an effect
/// in unconstrained code where arrays are reference counted and copy on write.
pub(crate) fn insert_inc_rc(&mut self, value: ValueId) {
Expand Down
50 changes: 42 additions & 8 deletions compiler/noirc_evaluator/src/ssa/ir/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,16 @@ fn value(function: &Function, id: ValueId) -> String {
}
Value::Function(id) => id.to_string(),
Value::Intrinsic(intrinsic) => intrinsic.to_string(),
Value::Array { array, .. } => {
Value::Array { array, typ } => {
let elements = vecmap(array, |element| value(function, *element));
format!("[{}]", elements.join(", "))
let element_types = &typ.clone().element_types();
let element_types_str =
element_types.iter().map(|typ| typ.to_string()).collect::<Vec<String>>().join(", ");
if element_types.len() == 1 {
format!("[{}] of {}", elements.join(", "), element_types_str)
} else {
format!("[{}] of ({})", elements.join(", "), element_types_str)
}
}
Value::Param { .. } | Value::Instruction { .. } | Value::ForeignFunction(_) => {
id.to_string()
Expand Down Expand Up @@ -120,7 +127,11 @@ pub(crate) fn display_terminator(
)
}
Some(TerminatorInstruction::Return { return_values, .. }) => {
writeln!(f, " return {}", value_list(function, return_values))
if return_values.is_empty() {
writeln!(f, " return")
} else {
writeln!(f, " return {}", value_list(function, return_values))
}
}
None => writeln!(f, " (no terminator instruction)"),
}
Expand All @@ -140,12 +151,13 @@ pub(crate) fn display_instruction(
write!(f, "{} = ", value_list(function, results))?;
}

display_instruction_inner(function, &function.dfg[instruction], f)
display_instruction_inner(function, &function.dfg[instruction], results, f)
}

fn display_instruction_inner(
function: &Function,
instruction: &Instruction,
results: &[ValueId],
f: &mut Formatter,
) -> Result {
let show = |id| value(function, id);
Expand All @@ -169,18 +181,29 @@ fn display_instruction_inner(
}
}
Instruction::Call { func, arguments } => {
writeln!(f, "call {}({})", show(*func), value_list(function, arguments))
let arguments = value_list(function, arguments);
writeln!(f, "call {}({}){}", show(*func), arguments, result_types(function, results))
}
Instruction::Allocate => {
writeln!(f, "allocate{}", result_types(function, results))
}
Instruction::Load { address } => {
writeln!(f, "load {}{}", show(*address), result_types(function, results))
}
Instruction::Allocate => writeln!(f, "allocate"),
Instruction::Load { address } => writeln!(f, "load {}", show(*address)),
Instruction::Store { address, value } => {
writeln!(f, "store {} at {}", show(*value), show(*address))
}
Instruction::EnableSideEffectsIf { condition } => {
writeln!(f, "enable_side_effects {}", show(*condition))
}
Instruction::ArrayGet { array, index } => {
writeln!(f, "array_get {}, index {}", show(*array), show(*index))
writeln!(
f,
"array_get {}, index {}{}",
show(*array),
show(*index),
result_types(function, results)
)
}
Instruction::ArraySet { array, index, value, mutable } => {
let array = show(*array);
Expand Down Expand Up @@ -211,6 +234,17 @@ fn display_instruction_inner(
}
}

fn result_types(function: &Function, results: &[ValueId]) -> String {
let types = vecmap(results, |result| function.dfg.type_of_value(*result).to_string());
if types.is_empty() {
String::new()
} else if types.len() == 1 {
format!(" -> {}", types[0])
} else {
format!(" -> ({})", types.join(", "))
}
}

/// Tries to extract a constant string from an error payload.
pub(crate) fn try_to_extract_string_from_error_payload(
error_selector: ErrorSelector,
Expand Down
6 changes: 5 additions & 1 deletion compiler/noirc_evaluator/src/ssa/ir/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,11 @@ impl std::fmt::Display for Type {
Type::Reference(element) => write!(f, "&mut {element}"),
Type::Array(element, length) => {
let elements = vecmap(element.iter(), |element| element.to_string());
write!(f, "[{}; {length}]", elements.join(", "))
if elements.len() == 1 {
write!(f, "[{}; {length}]", elements.join(", "))
} else {
write!(f, "[({}); {length}]", elements.join(", "))
}
}
Type::Slice(element) => {
let elements = vecmap(element.iter(), |element| element.to_string());
Expand Down
154 changes: 33 additions & 121 deletions compiler/noirc_evaluator/src/ssa/opt/array_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,135 +206,47 @@ fn make_mutable(

#[cfg(test)]
mod tests {
use std::sync::Arc;

use im::vector;
use noirc_frontend::monomorphization::ast::InlineType;

use crate::ssa::{
function_builder::FunctionBuilder,
ir::{
function::RuntimeType,
instruction::{BinaryOp, Instruction},
map::Id,
types::Type,
},
};
use crate::ssa::{opt::assert_normalized_ssa_equals, Ssa};

#[test]
fn array_set_in_loop_with_conditional_clone() {
// We want to make sure that we do not mark a single array set mutable which is loaded
// from and cloned in a loop. If the array is inadvertently marked mutable, and is cloned in a previous iteration
// of the loop, its clone will also be altered.
//
// brillig fn main f0 {
// b0():
// v3 = allocate
// store [[Field 0, Field 0, Field 0, Field 0, Field 0], [Field 0, Field 0, Field 0, Field 0, Field 0]] at v3
// v4 = allocate
// store [[Field 0, Field 0, Field 0, Field 0, Field 0], [Field 0, Field 0, Field 0, Field 0, Field 0]] at v4
// jmp b1(u32 0)
// b1(v6: u32):
// v8 = lt v6, u32 5
// jmpif v8 then: b3, else: b2
// b3():
// v9 = eq v6, u32 5
// jmpif v9 then: b4, else: b5
// b4():
// v10 = load v3
// store v10 at v4
// jmp b5()
// b5():
// v11 = load v3
// v13 = array_get v11, index Field 0
// v14 = array_set v13, index v6, value Field 20
// v15 = array_set v11, index v6, value v14
// store v15 at v3
// v17 = add v6, u32 1
// jmp b1(v17)
// b2():
// return
// }
let main_id = Id::test_new(0);
let mut builder = FunctionBuilder::new("main".into(), main_id);
builder.set_runtime(RuntimeType::Brillig(InlineType::default()));

let array_type = Type::Array(Arc::new(vec![Type::field()]), 5);
let zero = builder.field_constant(0u128);
let array_constant =
builder.array_constant(vector![zero, zero, zero, zero, zero], array_type.clone());
let nested_array_type = Type::Array(Arc::new(vec![array_type.clone()]), 2);
let nested_array_constant = builder
.array_constant(vector![array_constant, array_constant], nested_array_type.clone());

let v3 = builder.insert_allocate(array_type.clone());

builder.insert_store(v3, nested_array_constant);

let v4 = builder.insert_allocate(array_type.clone());
builder.insert_store(v4, nested_array_constant);

let b1 = builder.insert_block();
let zero_u32 = builder.numeric_constant(0u128, Type::unsigned(32));
builder.terminate_with_jmp(b1, vec![zero_u32]);

// Loop header
builder.switch_to_block(b1);
let v5 = builder.add_block_parameter(b1, Type::unsigned(32));
let five = builder.numeric_constant(5u128, Type::unsigned(32));
let v8 = builder.insert_binary(v5, BinaryOp::Lt, five);

let b2 = builder.insert_block();
let b3 = builder.insert_block();
let b4 = builder.insert_block();
let b5 = builder.insert_block();
builder.terminate_with_jmpif(v8, b3, b2);

// Loop body
// b3 is the if statement conditional
builder.switch_to_block(b3);
let two = builder.numeric_constant(5u128, Type::unsigned(32));
let v9 = builder.insert_binary(v5, BinaryOp::Eq, two);
builder.terminate_with_jmpif(v9, b4, b5);

// b4 is the rest of the loop after the if statement
builder.switch_to_block(b4);
let v10 = builder.insert_load(v3, nested_array_type.clone());
builder.insert_store(v4, v10);
builder.terminate_with_jmp(b5, vec![]);

builder.switch_to_block(b5);
let v11 = builder.insert_load(v3, nested_array_type.clone());
let twenty = builder.field_constant(20u128);
let v13 = builder.insert_array_get(v11, zero, array_type.clone());
let v14 = builder.insert_array_set(v13, v5, twenty);
let v15 = builder.insert_array_set(v11, v5, v14);

builder.insert_store(v3, v15);
let one = builder.numeric_constant(1u128, Type::unsigned(32));
let v17 = builder.insert_binary(v5, BinaryOp::Add, one);
builder.terminate_with_jmp(b1, vec![v17]);

builder.switch_to_block(b2);
builder.terminate_with_return(vec![]);
let src = "
brillig(inline) fn main f0 {
b0():
v1 = allocate -> &mut [Field; 5]
store [[Field 0, Field 0, Field 0, Field 0, Field 0] of Field, [Field 0, Field 0, Field 0, Field 0, Field 0] of Field] of [Field; 5] at v1
v6 = allocate -> &mut [Field; 5]
store [[Field 0, Field 0, Field 0, Field 0, Field 0] of Field, [Field 0, Field 0, Field 0, Field 0, Field 0] of Field] of [Field; 5] at v6
jmp b1(u32 0)
b1(v0: u32):
v12 = lt v0, u32 5
jmpif v12 then: b3, else: b2
b3():
v13 = eq v0, u32 5
jmpif v13 then: b4, else: b5
b4():
v14 = load v1 -> [[Field; 5]; 2]
store v14 at v6
jmp b5()
b5():
v15 = load v1 -> [[Field; 5]; 2]
v16 = array_get v15, index Field 0 -> [Field; 5]
v18 = array_set v16, index v0, value Field 20
v19 = array_set v15, index v0, value v18
store v19 at v1
v21 = add v0, u32 1
jmp b1(v21)
b2():
return
}
";
let ssa = Ssa::from_str(src).unwrap();

let ssa = builder.finish();
// We expect the same result as above
let ssa = ssa.array_set_optimization();

let main = ssa.main();
assert_eq!(main.reachable_blocks().len(), 6);

let array_set_instructions = main.dfg[b5]
.instructions()
.iter()
.filter(|instruction| matches!(&main.dfg[**instruction], Instruction::ArraySet { .. }))
.collect::<Vec<_>>();

assert_eq!(array_set_instructions.len(), 2);
if let Instruction::ArraySet { mutable, .. } = &main.dfg[*array_set_instructions[0]] {
// The single array set should not be marked mutable
assert!(!mutable);
}
assert_normalized_ssa_equals(ssa, src);
}
}
Loading
Loading