Skip to content

Commit 670063c

Browse files
authored
fix: Fix if/match tracking in last uses pass (#8935)
1 parent b891901 commit 670063c

19 files changed

Lines changed: 634 additions & 56 deletions

compiler/noirc_frontend/src/ownership/last_uses.rs

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub(super) enum Branches {
5656
/// No use in this branch or there is no branch
5757
None,
5858
Direct(IdentId),
59-
IfOrMatch(Vec<Branches>),
59+
IfOrMatch(IfOrMatchId, HashMap<BranchId, Branches>),
6060
}
6161

6262
impl Branches {
@@ -65,28 +65,49 @@ impl Branches {
6565
match self {
6666
Branches::None => Vec::new(),
6767
Branches::Direct(ident_id) => vec![ident_id],
68-
Branches::IfOrMatch(cases) => cases.into_iter().flat_map(Self::flatten_uses).collect(),
68+
Branches::IfOrMatch(_, cases) => {
69+
cases.into_values().flat_map(Self::flatten_uses).collect()
70+
}
71+
}
72+
}
73+
74+
fn get_if_or_match_id(&self) -> Option<IfOrMatchId> {
75+
match self {
76+
Branches::IfOrMatch(id, _) => Some(*id),
77+
_ => None,
78+
}
79+
}
80+
81+
fn get_branches_map(&mut self) -> Option<&mut HashMap<BranchId, Branches>> {
82+
match self {
83+
Branches::IfOrMatch(_, map) => Some(map),
84+
_ => None,
6985
}
7086
}
7187
}
7288

7389
/// A single path through a `Branches` enum.
7490
///
7591
/// This is used by the context to keep track of where we currently are within a function.
76-
#[derive(Debug)]
77-
enum BranchesPath {
78-
/// We've reached our destination
79-
Here,
80-
/// We're in a fork in the road, take the branch at the given index
81-
IfOrMatch { branch_index: usize, rest: Box<BranchesPath> },
82-
}
92+
type BranchesPath = Vec<(IfOrMatchId, BranchId)>;
93+
94+
/// The Id of an `if` or `match`, used to distinguish multiple sequential ifs/matches
95+
/// from each other.
96+
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
97+
pub(super) struct IfOrMatchId(u32);
98+
99+
/// The Id for a single branch of an if or match
100+
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
101+
pub(super) struct BranchId(u32);
83102

84103
struct LastUseContext {
85104
/// The outer `Vec` is each loop we're currently in, while the `BranchPath` contains
86105
/// the path to overwrite the last use in any `Branches` enums of the variables we find.
87106
/// As a whole, this tracks the current control-flow of the function we're in.
88107
current_loop_and_branch: Vec<BranchesPath>,
89108

109+
next_id: u32,
110+
90111
/// Stores the location of each variable's last use
91112
///
92113
/// Map from each local variable to the last instance of that variable. Separate uses of
@@ -132,8 +153,12 @@ impl Context {
132153
pub(super) fn find_last_uses_of_variables(
133154
function: &Function,
134155
) -> HashMap<LocalId, Vec<IdentId>> {
135-
let mut context =
136-
LastUseContext { current_loop_and_branch: Vec::new(), last_uses: HashMap::default() };
156+
let mut context = LastUseContext {
157+
current_loop_and_branch: Vec::new(),
158+
last_uses: HashMap::default(),
159+
next_id: 0,
160+
};
161+
137162
context.push_loop_scope();
138163
for (parameter, ..) in &function.parameters {
139164
context.declare_variable(*parameter);
@@ -145,29 +170,30 @@ impl Context {
145170

146171
impl LastUseContext {
147172
fn push_loop_scope(&mut self) {
148-
self.current_loop_and_branch.push(BranchesPath::Here);
173+
self.current_loop_and_branch.push(BranchesPath::new());
149174
}
150175

151176
fn pop_loop_scope(&mut self) {
152-
self.current_loop_and_branch.pop();
177+
self.current_loop_and_branch.pop().expect("No loop to pop");
153178
}
154179

155-
fn branch(&mut self, branch_index: usize) {
180+
fn branch(&mut self, id: IfOrMatchId, branch_id: u32) {
156181
let path =
157182
self.current_loop_and_branch.last_mut().expect("We should always have at least 1 path");
158-
let rest = Box::new(std::mem::replace(path, BranchesPath::Here));
159-
*path = BranchesPath::IfOrMatch { branch_index, rest };
183+
184+
path.push((id, BranchId(branch_id)));
185+
}
186+
187+
fn next_if_or_match_id(&mut self) -> IfOrMatchId {
188+
let id = self.next_id;
189+
self.next_id += 1;
190+
IfOrMatchId(id)
160191
}
161192

162193
fn unbranch(&mut self) {
163194
let path =
164195
self.current_loop_and_branch.last_mut().expect("We should always have at least 1 path");
165-
let rest = std::mem::replace(path, BranchesPath::Here);
166-
167-
match rest {
168-
BranchesPath::Here => panic!("unbranch called without any branches"),
169-
BranchesPath::IfOrMatch { branch_index: _, rest } => *path = *rest,
170-
}
196+
path.pop().expect("No branch to unbranch");
171197
}
172198

173199
fn loop_index(&self) -> usize {
@@ -203,37 +229,38 @@ impl LastUseContext {
203229

204230
fn remember_use_of_variable_rec(
205231
branches: &mut Branches,
206-
path: &BranchesPath,
232+
path: &[(IfOrMatchId, BranchId)],
207233
variable: IdentId,
208234
) {
235+
let reset_branch_and_recur = |branch: &mut Branches, if_or_match_id, branch_id| {
236+
let empty_branch = [(branch_id, Branches::None)].into_iter().collect();
237+
*branch = Branches::IfOrMatch(if_or_match_id, empty_branch);
238+
Self::remember_use_of_variable_rec(branch, path, variable);
239+
};
240+
209241
match (branches, path) {
210242
// Path is direct, overwrite the last use entirely
211-
(branch, BranchesPath::Here) => {
243+
(branch, []) => {
212244
*branch = Branches::Direct(variable);
213245
}
214246
// Our path says we need to enter an if or match but the variable's last
215247
// use was direct. So we need to overwrite the last use with an empty IfOrMatch
216248
// and write to the relevant branch of that
217249
(
218250
branch @ (Branches::None | Branches::Direct { .. }),
219-
BranchesPath::IfOrMatch { branch_index, rest: _ },
251+
[(if_or_match_id, branch_id), ..],
220252
) => {
221253
// The branch doesn't exist for this variable; create it
222-
let empty_branches =
223-
std::iter::repeat_n(Branches::None, *branch_index + 1).collect();
224-
*branch = Branches::IfOrMatch(empty_branches);
225-
Self::remember_use_of_variable_rec(branch, path, variable);
254+
reset_branch_and_recur(branch, *if_or_match_id, *branch_id);
226255
}
227-
// The variable's last use was in an if or match, and our current path is in one
228-
// as well so just follow to the relevant branch in both. We just need to possibly
229-
// resize the variable's branches count in case it was created with fewer than we
230-
// know we currently have.
231-
(Branches::IfOrMatch(branches), BranchesPath::IfOrMatch { branch_index, rest }) => {
232-
let required_len = *branch_index + 1;
233-
if branches.len() < required_len {
234-
branches.resize(required_len, Branches::None);
256+
(branches @ Branches::IfOrMatch(..), [(new_if_id, branch_id), rest @ ..]) => {
257+
if branches.get_if_or_match_id() == Some(*new_if_id) {
258+
let nested = branches.get_branches_map().expect("We know this is a IfOrMatch");
259+
let entry = nested.entry(*branch_id).or_insert(Branches::None);
260+
Self::remember_use_of_variable_rec(entry, rest, variable);
261+
} else {
262+
reset_branch_and_recur(branches, *new_if_id, *branch_id);
235263
}
236-
Self::remember_use_of_variable_rec(&mut branches[*branch_index], rest, variable);
237264
}
238265
}
239266
}
@@ -338,30 +365,33 @@ impl LastUseContext {
338365
fn track_variables_in_if(&mut self, if_expr: &ast::If) {
339366
self.track_variables_in_expression(&if_expr.condition);
340367

341-
self.branch(0);
368+
let if_id = self.next_if_or_match_id();
369+
self.branch(if_id, 0);
342370
self.track_variables_in_expression(&if_expr.consequence);
343371
self.unbranch();
344372

345373
if let Some(alt) = &if_expr.alternative {
346-
self.branch(1);
374+
self.branch(if_id, 1);
347375
self.track_variables_in_expression(alt);
348376
self.unbranch();
349377
}
350378
}
351379

352380
fn track_variables_in_match(&mut self, match_expr: &ast::Match) {
381+
let match_id = self.next_if_or_match_id();
382+
353383
for (i, case) in match_expr.cases.iter().enumerate() {
354384
for argument in &case.arguments {
355385
self.declare_variable(*argument);
356386
}
357387

358-
self.branch(i);
388+
self.branch(match_id, i as u32);
359389
self.track_variables_in_expression(&case.branch);
360390
self.unbranch();
361391
}
362392

363393
if let Some(default_case) = &match_expr.default_case {
364-
self.branch(match_expr.cases.len());
394+
self.branch(match_id, match_expr.cases.len() as u32);
365395
self.track_variables_in_expression(default_case);
366396
self.unbranch();
367397
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "last_uses_regression_8935"
3+
type = "bin"
4+
authors = [""]
5+
compiler_version = ">=0.32.0"
6+
7+
[dependencies]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
a = [42]
2+
b = [42]
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
unconstrained fn main(a: [u16; 1], b: [u16; 1]) {
2+
case1(a);
3+
case2(b);
4+
}
5+
6+
unconstrained fn case1(array: [u16; 1]) {
7+
if false {} else {
8+
let mut d = array;
9+
if (true) {
10+
d[0] = 2;
11+
assert(array[0] != 2);
12+
13+
// This println is needed to ensure d is not optimized out
14+
println(d);
15+
} else {
16+
()
17+
}
18+
}
19+
}
20+
21+
unconstrained fn case2(array: [u16; 1]) {
22+
if true {
23+
if true {
24+
let mut foo = array;
25+
foo[0] = 0;
26+
27+
// This println is needed to ensure foo is not optimized out
28+
println(foo);
29+
}
30+
}
31+
32+
if true {
33+
if false {
34+
()
35+
} else {
36+
assert(array[0] != 0);
37+
}
38+
}
39+
}

tooling/nargo_cli/tests/snapshots/execution_success/last_uses_regression_8935/execute__tests__expanded.snap

Lines changed: 38 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tooling/nargo_cli/tests/snapshots/execution_success/last_uses_regression_8935/execute__tests__force_brillig_false_inliner_-9223372036854775808.snap

Lines changed: 76 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tooling/nargo_cli/tests/snapshots/execution_success/last_uses_regression_8935/execute__tests__force_brillig_false_inliner_0.snap

Lines changed: 76 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tooling/nargo_cli/tests/snapshots/execution_success/last_uses_regression_8935/execute__tests__force_brillig_false_inliner_9223372036854775807.snap

Lines changed: 76 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tooling/nargo_cli/tests/snapshots/execution_success/last_uses_regression_8935/execute__tests__force_brillig_true_inliner_-9223372036854775808.snap

Lines changed: 76 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tooling/nargo_cli/tests/snapshots/execution_success/last_uses_regression_8935/execute__tests__force_brillig_true_inliner_0.snap

Lines changed: 76 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)