Skip to content

Commit 657adc4

Browse files
author
AztecBot
committed
chore!: remove ec module from stdlib (noir-lang/noir#6612)
fix(LSP): use generic self type to narrow down methods to complete (noir-lang/noir#6617) fix!: Disallow `#[export]` on associated methods (noir-lang/noir#6626) chore: redo typo PR by donatik27 (noir-lang/noir#6575) chore: redo typo PR by Dimitrolito (noir-lang/noir#6614) feat: simplify `jmpif`s by reversing branches if condition is negated (noir-lang/noir#5891) fix: Do not warn on unused functions marked with #[export] (noir-lang/noir#6625) chore: Add panic for compiler error described in #6620 (noir-lang/noir#6621) feat(perf): Track last loads per block in mem2reg and remove them if possible (noir-lang/noir#6088) fix(ssa): Track all local allocations during flattening (noir-lang/noir#6619) feat(comptime): Implement blackbox functions in comptime interpreter (noir-lang/noir#6551) chore: derive PartialEq and Hash for FieldElement (noir-lang/noir#6610) chore: ignore almost-empty directories in nargo_cli tests (noir-lang/noir#6611) chore: remove temporary allocations from `num_bits` (noir-lang/noir#6600) chore: Release Noir(1.0.0-beta.0) (noir-lang/noir#6562) feat: Add `array_refcount` and `slice_refcount` builtins for debugging (noir-lang/noir#6584) chore!: Require types of globals to be specified (noir-lang/noir#6592) fix: don't report visibility errors when elaborating comptime value (noir-lang/noir#6498) fix: preserve newlines between comments when formatting statements (noir-lang/noir#6601) fix: parse a bit more SSA stuff (noir-lang/noir#6599) chore!: remove eddsa from stdlib (noir-lang/noir#6591) chore: Typo in oracles how to (noir-lang/noir#6598) feat(ssa): Loop invariant code motion (noir-lang/noir#6563) fix: remove `compiler_version` from new `Nargo.toml` (noir-lang/noir#6590) feat: Avoid incrementing reference counts in some cases (noir-lang/noir#6568) chore: fix typo in test name (noir-lang/noir#6589) fix: consider prereleases to be compatible with pre-1.0.0 releases (noir-lang/noir#6580) feat: try to inline brillig calls with all constant arguments (noir-lang/noir#6548) fix: correct type when simplifying `derive_pedersen_generators` (noir-lang/noir#6579) feat: Sync from aztec-packages (noir-lang/noir#6576)
2 parents a5d4de0 + cc17c3c commit 657adc4

53 files changed

Lines changed: 647 additions & 2028 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.noir-sync-commit

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
649117570b95b26776150e337c458d478eb48c2e
1+
1e965bc8b9c4222c7b2ad7502df415781308de7f

noir/noir-repo/.github/ACVM_NOT_PUBLISHABLE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ assignees: TomAFrench, Savio-Sou
55

66
The ACVM crates are currently unpublishable, making a release will NOT push our crates to crates.io.
77

8-
This is likely due to a crate we depend on bumping its MSRV above our own. Our lockfile is not taken into account when publishing to crates.io (as people downloading our crate don't use it) so we need to be able to use the most up to date versions of our dependencies (including transient dependencies) specified.
8+
This is likely due to a crate we depend on bumping its MSRV above our own. Our lockfile is not taken into account when publishing to crates.io (as people downloading our crate don't use it) so we need to be able to use the most up-to-date versions of our dependencies (including transient dependencies) specified.
99

1010
Check the [MSRV check]({{env.WORKFLOW_URL}}) workflow for details.
1111

noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,16 @@ impl<'f> Context<'f> {
432432
};
433433
self.condition_stack.push(cond_context);
434434
self.insert_current_side_effects_enabled();
435+
436+
// We disallow this case as it results in the `else_destination` block
437+
// being inlined before the `then_destination` block due to block deduplication in the work queue.
438+
//
439+
// The `else_destination` block then gets treated as if it were the `then_destination` block
440+
// and has the incorrect condition applied to it.
441+
assert_ne!(
442+
self.branch_ends[if_entry], *then_destination,
443+
"ICE: branches merge inside of `then` branch"
444+
);
435445
vec![self.branch_ends[if_entry], *else_destination, *then_destination]
436446
}
437447

@@ -1475,4 +1485,23 @@ mod test {
14751485
_ => unreachable!("Should have terminator instruction"),
14761486
}
14771487
}
1488+
1489+
#[test]
1490+
#[should_panic = "ICE: branches merge inside of `then` branch"]
1491+
fn panics_if_branches_merge_within_then_branch() {
1492+
//! This is a regression test for https://github.com/noir-lang/noir/issues/6620
1493+
1494+
let src = "
1495+
acir(inline) fn main f0 {
1496+
b0(v0: u1):
1497+
jmpif v0 then: b2, else: b1
1498+
b2():
1499+
return
1500+
b1():
1501+
jmp b2()
1502+
}
1503+
";
1504+
let merged_ssa = Ssa::from_str(src).unwrap();
1505+
let _ = merged_ssa.flatten_cfg();
1506+
}
14781507
}

noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs

Lines changed: 221 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
//! - A reference with 0 aliases means we were unable to find which reference this reference
1919
//! refers to. If such a reference is stored to, we must conservatively invalidate every
2020
//! reference in the current block.
21+
//! - We also track the last load instruction to each address per block.
2122
//!
2223
//! From there, to figure out the value of each reference at the end of block, iterate each instruction:
2324
//! - On `Instruction::Allocate`:
@@ -28,6 +29,13 @@
2829
//! - Furthermore, if the result of the load is a reference, mark the result as an alias
2930
//! of the reference it dereferences to (if known).
3031
//! - If which reference it dereferences to is not known, this load result has no aliases.
32+
//! - We also track the last instance of a load instruction to each address in a block.
33+
//! If we see that the last load instruction was from the same address as the current load instruction,
34+
//! we move to replace the result of the current load with the result of the previous load.
35+
//! This removal requires a couple conditions:
36+
//! - No store occurs to that address before the next load,
37+
//! - The address is not used as an argument to a call
38+
//! This optimization helps us remove repeated loads for which there are not known values.
3139
//! - On `Instruction::Store { address, value }`:
3240
//! - If the address of the store is known:
3341
//! - If the address has exactly 1 alias:
@@ -40,11 +48,13 @@
4048
//! - Conservatively mark every alias in the block to `Unknown`.
4149
//! - Additionally, if there were no Loads to any alias of the address between this Store and
4250
//! the previous Store to the same address, the previous store can be removed.
51+
//! - Remove the instance of the last load instruction to the address and its aliases
4352
//! - On `Instruction::Call { arguments }`:
4453
//! - If any argument of the call is a reference, set the value of each alias of that
4554
//! reference to `Unknown`
4655
//! - Any builtin functions that may return aliases if their input also contains a
4756
//! reference should be tracked. Examples: `slice_push_back`, `slice_insert`, `slice_remove`, etc.
57+
//! - Remove the instance of the last load instruction for any reference arguments and their aliases
4858
//!
4959
//! On a terminator instruction:
5060
//! - If the terminator is a `Jmp`:
@@ -274,6 +284,9 @@ impl<'f> PerFunctionContext<'f> {
274284
if let Some(first_predecessor) = predecessors.next() {
275285
let mut first = self.blocks.get(&first_predecessor).cloned().unwrap_or_default();
276286
first.last_stores.clear();
287+
// Last loads are tracked per block. During unification we are creating a new block from the current one,
288+
// so we must clear the last loads of the current block before we return the new block.
289+
first.last_loads.clear();
277290

278291
// Note that we have to start folding with the first block as the accumulator.
279292
// If we started with an empty block, an empty block union'd with any other block
@@ -410,6 +423,28 @@ impl<'f> PerFunctionContext<'f> {
410423

411424
self.last_loads.insert(address, (instruction, block_id));
412425
}
426+
427+
// Check whether the block has a repeat load from the same address (w/ no calls or stores in between the loads).
428+
// If we do have a repeat load, we can remove the current load and map its result to the previous load's result.
429+
if let Some(last_load) = references.last_loads.get(&address) {
430+
let Instruction::Load { address: previous_address } =
431+
&self.inserter.function.dfg[*last_load]
432+
else {
433+
panic!("Expected a Load instruction here");
434+
};
435+
let result = self.inserter.function.dfg.instruction_results(instruction)[0];
436+
let previous_result =
437+
self.inserter.function.dfg.instruction_results(*last_load)[0];
438+
if *previous_address == address {
439+
self.inserter.map_value(result, previous_result);
440+
self.instructions_to_remove.insert(instruction);
441+
}
442+
}
443+
// We want to set the load for every load even if the address has a known value
444+
// and the previous load instruction was removed.
445+
// We are safe to still remove a repeat load in this case as we are mapping from the current load's
446+
// result to the previous load, which if it was removed should already have a mapping to the known value.
447+
references.set_last_load(address, instruction);
413448
}
414449
Instruction::Store { address, value } => {
415450
let address = self.inserter.function.dfg.resolve(*address);
@@ -437,6 +472,8 @@ impl<'f> PerFunctionContext<'f> {
437472
}
438473

439474
references.set_known_value(address, value);
475+
// If we see a store to an address, the last load to that address needs to remain.
476+
references.keep_last_load_for(address, self.inserter.function);
440477
references.last_stores.insert(address, instruction);
441478
}
442479
Instruction::Allocate => {
@@ -544,6 +581,9 @@ impl<'f> PerFunctionContext<'f> {
544581
let value = self.inserter.function.dfg.resolve(*value);
545582
references.set_unknown(value);
546583
references.mark_value_used(value, self.inserter.function);
584+
585+
// If a reference is an argument to a call, the last load to that address and its aliases needs to remain.
586+
references.keep_last_load_for(value, self.inserter.function);
547587
}
548588
}
549589
}
@@ -574,6 +614,12 @@ impl<'f> PerFunctionContext<'f> {
574614
let destination_parameters = self.inserter.function.dfg[*destination].parameters();
575615
assert_eq!(destination_parameters.len(), arguments.len());
576616

617+
// If we have multiple parameters that alias that same argument value,
618+
// then those parameters also alias each other.
619+
// We save parameters with repeat arguments to later mark those
620+
// parameters as aliasing one another.
621+
let mut arg_set: HashMap<ValueId, BTreeSet<ValueId>> = HashMap::default();
622+
577623
// Add an alias for each reference parameter
578624
for (parameter, argument) in destination_parameters.iter().zip(arguments) {
579625
if self.inserter.function.dfg.value_is_reference(*parameter) {
@@ -583,10 +629,27 @@ impl<'f> PerFunctionContext<'f> {
583629
if let Some(aliases) = references.aliases.get_mut(expression) {
584630
// The argument reference is possibly aliased by this block parameter
585631
aliases.insert(*parameter);
632+
633+
// Check if we have seen the same argument
634+
let seen_parameters = arg_set.entry(argument).or_default();
635+
// Add the current parameter to the parameters we have seen for this argument.
636+
// The previous parameters and the current one alias one another.
637+
seen_parameters.insert(*parameter);
586638
}
587639
}
588640
}
589641
}
642+
643+
// Set the aliases of the parameters
644+
for (_, aliased_params) in arg_set {
645+
for param in aliased_params.iter() {
646+
self.set_aliases(
647+
references,
648+
*param,
649+
AliasSet::known_multiple(aliased_params.clone()),
650+
);
651+
}
652+
}
590653
}
591654
TerminatorInstruction::Return { return_values, .. } => {
592655
// Removing all `last_stores` for each returned reference is more important here
@@ -902,7 +965,7 @@ mod tests {
902965
// v10 = eq v9, Field 2
903966
// constrain v9 == Field 2
904967
// v11 = load v2
905-
// v12 = load v10
968+
// v12 = load v11
906969
// v13 = eq v12, Field 2
907970
// constrain v11 == Field 2
908971
// return
@@ -961,7 +1024,7 @@ mod tests {
9611024
let main = ssa.main();
9621025
assert_eq!(main.reachable_blocks().len(), 4);
9631026

964-
// The store from the original SSA should remain
1027+
// The stores from the original SSA should remain
9651028
assert_eq!(count_stores(main.entry_block(), &main.dfg), 2);
9661029
assert_eq!(count_stores(b2, &main.dfg), 1);
9671030

@@ -1008,4 +1071,160 @@ mod tests {
10081071
let main = ssa.main();
10091072
assert_eq!(count_loads(main.entry_block(), &main.dfg), 1);
10101073
}
1074+
1075+
#[test]
1076+
fn remove_repeat_loads() {
1077+
// This tests starts with two loads from the same unknown load.
1078+
// Specifically you should look for `load v2` in `b3`.
1079+
// We should be able to remove the second repeated load.
1080+
let src = "
1081+
acir(inline) fn main f0 {
1082+
b0():
1083+
v0 = allocate -> &mut Field
1084+
store Field 0 at v0
1085+
v2 = allocate -> &mut &mut Field
1086+
store v0 at v2
1087+
jmp b1(Field 0)
1088+
b1(v3: Field):
1089+
v4 = eq v3, Field 0
1090+
jmpif v4 then: b2, else: b3
1091+
b2():
1092+
v5 = load v2 -> &mut Field
1093+
store Field 2 at v5
1094+
v8 = add v3, Field 1
1095+
jmp b1(v8)
1096+
b3():
1097+
v9 = load v0 -> Field
1098+
v10 = eq v9, Field 2
1099+
constrain v9 == Field 2
1100+
v11 = load v2 -> &mut Field
1101+
v12 = load v2 -> &mut Field
1102+
v13 = load v12 -> Field
1103+
v14 = eq v13, Field 2
1104+
constrain v13 == Field 2
1105+
return
1106+
}
1107+
";
1108+
1109+
let ssa = Ssa::from_str(src).unwrap();
1110+
1111+
// The repeated load from v3 should be removed
1112+
// b3 should only have three loads now rather than four previously
1113+
//
1114+
// All stores are expected to remain.
1115+
let expected = "
1116+
acir(inline) fn main f0 {
1117+
b0():
1118+
v1 = allocate -> &mut Field
1119+
store Field 0 at v1
1120+
v3 = allocate -> &mut &mut Field
1121+
store v1 at v3
1122+
jmp b1(Field 0)
1123+
b1(v0: Field):
1124+
v4 = eq v0, Field 0
1125+
jmpif v4 then: b3, else: b2
1126+
b3():
1127+
v11 = load v3 -> &mut Field
1128+
store Field 2 at v11
1129+
v13 = add v0, Field 1
1130+
jmp b1(v13)
1131+
b2():
1132+
v5 = load v1 -> Field
1133+
v7 = eq v5, Field 2
1134+
constrain v5 == Field 2
1135+
v8 = load v3 -> &mut Field
1136+
v9 = load v8 -> Field
1137+
v10 = eq v9, Field 2
1138+
constrain v9 == Field 2
1139+
return
1140+
}
1141+
";
1142+
1143+
let ssa = ssa.mem2reg();
1144+
assert_normalized_ssa_equals(ssa, expected);
1145+
}
1146+
1147+
#[test]
1148+
fn keep_repeat_loads_passed_to_a_call() {
1149+
// The test is the exact same as `remove_repeat_loads` above except with the call
1150+
// to `f1` between the repeated loads.
1151+
let src = "
1152+
acir(inline) fn main f0 {
1153+
b0():
1154+
v1 = allocate -> &mut Field
1155+
store Field 0 at v1
1156+
v3 = allocate -> &mut &mut Field
1157+
store v1 at v3
1158+
jmp b1(Field 0)
1159+
b1(v0: Field):
1160+
v4 = eq v0, Field 0
1161+
jmpif v4 then: b3, else: b2
1162+
b3():
1163+
v13 = load v3 -> &mut Field
1164+
store Field 2 at v13
1165+
v15 = add v0, Field 1
1166+
jmp b1(v15)
1167+
b2():
1168+
v5 = load v1 -> Field
1169+
v7 = eq v5, Field 2
1170+
constrain v5 == Field 2
1171+
v8 = load v3 -> &mut Field
1172+
call f1(v3)
1173+
v10 = load v3 -> &mut Field
1174+
v11 = load v10 -> Field
1175+
v12 = eq v11, Field 2
1176+
constrain v11 == Field 2
1177+
return
1178+
}
1179+
acir(inline) fn foo f1 {
1180+
b0(v0: &mut Field):
1181+
return
1182+
}
1183+
";
1184+
1185+
let ssa = Ssa::from_str(src).unwrap();
1186+
1187+
let ssa = ssa.mem2reg();
1188+
// We expect the program to be unchanged
1189+
assert_normalized_ssa_equals(ssa, src);
1190+
}
1191+
1192+
#[test]
1193+
fn keep_repeat_loads_with_alias_store() {
1194+
// v7, v8, and v9 alias one another. We want to make sure that a repeat load to v7 with a store
1195+
// to its aliases in between the repeat loads does not remove those loads.
1196+
let src = "
1197+
acir(inline) fn main f0 {
1198+
b0(v0: u1):
1199+
jmpif v0 then: b2, else: b1
1200+
b2():
1201+
v6 = allocate -> &mut Field
1202+
store Field 0 at v6
1203+
jmp b3(v6, v6, v6)
1204+
b3(v1: &mut Field, v2: &mut Field, v3: &mut Field):
1205+
v8 = load v1 -> Field
1206+
store Field 2 at v2
1207+
v10 = load v1 -> Field
1208+
store Field 1 at v3
1209+
v11 = load v1 -> Field
1210+
store Field 3 at v3
1211+
v13 = load v1 -> Field
1212+
constrain v8 == Field 0
1213+
constrain v10 == Field 2
1214+
constrain v11 == Field 1
1215+
constrain v13 == Field 3
1216+
return
1217+
b1():
1218+
v4 = allocate -> &mut Field
1219+
store Field 1 at v4
1220+
jmp b3(v4, v4, v4)
1221+
}
1222+
";
1223+
1224+
let ssa = Ssa::from_str(src).unwrap();
1225+
1226+
let ssa = ssa.mem2reg();
1227+
// We expect the program to be unchanged
1228+
assert_normalized_ssa_equals(ssa, src);
1229+
}
10111230
}

noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg/alias_set.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ impl AliasSet {
2424
Self { aliases: Some(aliases) }
2525
}
2626

27+
pub(super) fn known_multiple(values: BTreeSet<ValueId>) -> AliasSet {
28+
Self { aliases: Some(values) }
29+
}
30+
2731
/// In rare cases, such as when creating an empty array of references, the set of aliases for a
2832
/// particular value will be known to be zero, which is distinct from being unknown and
2933
/// possibly referring to any alias.

0 commit comments

Comments
 (0)