Skip to content

Commit fc74c55

Browse files
authored
fix: disable side-effects for no_predicates functions (#6027)
# Description ## Problem\* Brillig output is nullified if the call is with a predicate, however, functions with no_predicates attribute expect valid values from their brillig calls because when they constrain the values , it will not have a predicate. Resolves e-2-e test failing on PR AztecProtocol/aztec-packages#4571 ## Summary\* Disable side-effects when calling a function with no_predicates ## Additional Context The dedicated inlining pass for no_predicates functions can be removed after this fix. ## 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.
1 parent e74b4ae commit fc74c55

4 files changed

Lines changed: 77 additions & 8 deletions

File tree

compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ use crate::ssa::{
142142
basic_block::BasicBlockId,
143143
cfg::ControlFlowGraph,
144144
dfg::{CallStack, InsertInstructionResult},
145-
function::Function,
145+
function::{Function, FunctionId},
146146
function_inserter::FunctionInserter,
147147
instruction::{BinaryOp, Instruction, InstructionId, Intrinsic, TerminatorInstruction},
148148
types::Type,
@@ -164,8 +164,14 @@ impl Ssa {
164164
/// For more information, see the module-level comment at the top of this file.
165165
#[tracing::instrument(level = "trace", skip(self))]
166166
pub(crate) fn flatten_cfg(mut self) -> Ssa {
167+
// Retrieve the 'no_predicates' attribute of the functions in a map, to avoid problems with borrowing
168+
let mut no_predicates = HashMap::default();
169+
for function in self.functions.values() {
170+
no_predicates.insert(function.id(), function.is_no_predicates());
171+
}
172+
167173
for function in self.functions.values_mut() {
168-
flatten_function_cfg(function);
174+
flatten_function_cfg(function, &no_predicates);
169175
}
170176
self
171177
}
@@ -244,7 +250,7 @@ struct ConditionalContext {
244250
call_stack: CallStack,
245251
}
246252

247-
fn flatten_function_cfg(function: &mut Function) {
253+
fn flatten_function_cfg(function: &mut Function, no_predicates: &HashMap<FunctionId, bool>) {
248254
// This pass may run forever on a brillig function.
249255
// Analyze will check if the predecessors have been processed and push the block to the back of
250256
// the queue. This loops forever if there are still any loops present in the program.
@@ -264,18 +270,18 @@ fn flatten_function_cfg(function: &mut Function) {
264270
condition_stack: Vec::new(),
265271
arguments_stack: Vec::new(),
266272
};
267-
context.flatten();
273+
context.flatten(no_predicates);
268274
}
269275

270276
impl<'f> Context<'f> {
271-
fn flatten(&mut self) {
277+
fn flatten(&mut self, no_predicates: &HashMap<FunctionId, bool>) {
272278
// Flatten the CFG by inlining all instructions from the queued blocks
273279
// until all blocks have been flattened.
274280
// We follow the terminator of each block to determine which blocks to
275281
// process next
276282
let mut queue = vec![self.inserter.function.entry_block()];
277283
while let Some(block) = queue.pop() {
278-
self.inline_block(block);
284+
self.inline_block(block, no_predicates);
279285
let to_process = self.handle_terminator(block, &queue);
280286
for incoming_block in to_process {
281287
if !queue.contains(&incoming_block) {
@@ -307,8 +313,23 @@ impl<'f> Context<'f> {
307313
})
308314
}
309315

316+
/// Use the provided map to say if the instruction is a call to a no_predicates function
317+
fn is_no_predicate(
318+
&self,
319+
no_predicates: &HashMap<FunctionId, bool>,
320+
instruction: &InstructionId,
321+
) -> bool {
322+
let mut result = false;
323+
if let Instruction::Call { func, .. } = self.inserter.function.dfg[*instruction] {
324+
if let Value::Function(fid) = self.inserter.function.dfg[func] {
325+
result = *no_predicates.get(&fid).unwrap_or(&false);
326+
}
327+
}
328+
result
329+
}
330+
310331
// Inline all instructions from the given block into the entry block, and track slice capacities
311-
fn inline_block(&mut self, block: BasicBlockId) {
332+
fn inline_block(&mut self, block: BasicBlockId, no_predicates: &HashMap<FunctionId, bool>) {
312333
if self.inserter.function.entry_block() == block {
313334
// we do not inline the entry block into itself
314335
// for the outer block before we start inlining
@@ -322,7 +343,23 @@ impl<'f> Context<'f> {
322343
// unnecessary, when removing it actually causes an aliasing/mutability error.
323344
let instructions = self.inserter.function.dfg[block].instructions().to_vec();
324345
for instruction in instructions.iter() {
325-
self.push_instruction(*instruction);
346+
if self.is_no_predicate(no_predicates, instruction) {
347+
// disable side effect for no_predicate functions
348+
let one = self
349+
.inserter
350+
.function
351+
.dfg
352+
.make_constant(FieldElement::one(), Type::unsigned(1));
353+
self.insert_instruction_with_typevars(
354+
Instruction::EnableSideEffectsIf { condition: one },
355+
None,
356+
im::Vector::new(),
357+
);
358+
self.push_instruction(*instruction);
359+
self.insert_current_side_effects_enabled();
360+
} else {
361+
self.push_instruction(*instruction);
362+
}
326363
}
327364
}
328365

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[package]
2+
name = "regression_unsafe_no_predicates"
3+
type = "bin"
4+
authors = [""]
5+
[dependencies]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
x = 239
2+
nest = false
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
fn main(x: u8, nest: bool) {
2+
if nest {
3+
let foo = unsafe_assert([x]);
4+
assert(foo != 0);
5+
}
6+
}
7+
8+
#[no_predicates]
9+
pub fn unsafe_assert<let N: u32>(msg: [u8; N]) -> u8 {
10+
let block = unsafe {
11+
get_block(msg)
12+
};
13+
verify_block(msg, block);
14+
block[0]
15+
}
16+
17+
unconstrained fn get_block<let N: u32>(msg: [u8; N]) -> [u8; 2] {
18+
let mut block: [u8; 2] = [0; 2];
19+
block[0] = msg[0];
20+
block
21+
}
22+
23+
fn verify_block<let N: u32>(msg: [u8; N], block: [u8; 2]) {
24+
assert_eq(block[0], msg[0]);
25+
}

0 commit comments

Comments
 (0)