Skip to content

Commit 6aa646a

Browse files
authored
Merge branch 'master' into jl/brillig_entry
2 parents f248e2e + 89c8d39 commit 6aa646a

File tree

72 files changed

+1080
-198
lines changed

Some content is hidden

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

72 files changed

+1080
-198
lines changed

acvm-repo/brillig_vm/src/foreign_call.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ impl<F: AcirField, B: BlackBoxFunctionSolver<F>> VM<'_, F, B> {
366366
self.memory.write(*size_addr, assert_u32(values.len()).into());
367367
self.write_values_to_memory(*pointer, values, value_types)?;
368368
} else {
369-
unimplemented!("deflattening heap vectors from foreign calls");
369+
unreachable!("deflattening heap vectors from foreign calls");
370370
}
371371
}
372372
_ => {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ mod tests {
380380
v1 = lt v0, u32 1
381381
jmpif v1 then: b1, else: b2
382382
b1():
383-
jmp b3(Field 0)
383+
jmp b3(u32 0)
384384
b2():
385385
v3 = call f7(v0) -> u32
386386
call f6()
@@ -454,7 +454,7 @@ mod tests {
454454
v3 = lt v0, u32 1
455455
jmpif v3 then: b1, else: b2
456456
b1():
457-
jmp b3(Field 0)
457+
jmp b3(u32 0)
458458
b2():
459459
v5 = call f7(v0) -> u32
460460
call f6()

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,7 +1032,7 @@ mod tests {
10321032
b1():
10331033
v2 = add Field 1, Field 2
10341034
jmp b2(v2)
1035-
b2():
1035+
b2(v3: Field):
10361036
jmpif u1 0 then: b3, else: b4
10371037
b3():
10381038
constrain u1 0 == u1 1, "Index out of bounds"
@@ -1050,16 +1050,16 @@ mod tests {
10501050
b0():
10511051
jmp b1()
10521052
b1():
1053-
v2 = add Field 1, Field 2
1054-
jmp b2(v2)
1055-
b2():
1053+
v3 = add Field 1, Field 2
1054+
jmp b2(v3)
1055+
b2(v0: Field):
10561056
jmpif u1 0 then: b3, else: b4
10571057
b3():
10581058
constrain u1 0 == u1 1, "Index out of bounds"
10591059
unreachable
10601060
b4():
1061-
v4 = add Field 1, Field 2
1062-
return v4
1061+
v5 = add Field 1, Field 2
1062+
return v5
10631063
}
10641064
"#);
10651065
}

compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ use rustc_hash::FxHashMap as HashMap;
3939
/// is the only part of the context that needs to be shared between threads.
4040
pub(super) struct FunctionContext<'a> {
4141
definitions: HashMap<LocalId, Values>,
42+
pub(super) redefinitions_allowed: bool,
4243

4344
pub(super) builder: FunctionBuilder,
4445
shared_context: &'a SharedContext,
@@ -94,6 +95,18 @@ pub(super) struct Loop {
9495
/// The loop index will be `Some` for a `for` and `None` for a `loop`
9596
pub(super) loop_index: Option<ValueId>,
9697
pub(super) loop_end: BasicBlockId,
98+
/// A variable that tracks whether a `break` was hit or not:
99+
/// `false` if a `break` was hit, `true` if not.
100+
/// This is only `Some` in the case of an inclusive for loop which is
101+
/// generated as an exclusive for loop with an extra iteration for the
102+
/// end of the loop. This extra iteration is only done if no `break` was hit
103+
/// in the exclusive iterations (and if `start <= end`).
104+
/// We track the negated value because we execute the last iteration
105+
/// if we did not hit a break, in the end being `did_not_hit_break && (start <= end)`.
106+
/// If we tracked whether we hit a break or not, the condition to execute
107+
/// the last iteration would be `(not hit_break) && (start <= end)`, which is larger
108+
/// by one instruction.
109+
pub(super) did_not_hit_break_var: Option<ValueId>,
97110
}
98111

99112
/// The queue of functions remaining to compile
@@ -126,7 +139,13 @@ impl<'a> FunctionContext<'a> {
126139
builder.set_runtime(runtime);
127140

128141
let definitions = HashMap::default();
129-
let mut this = Self { definitions, builder, shared_context, loops: Vec::new() };
142+
let mut this = Self {
143+
definitions,
144+
builder,
145+
shared_context,
146+
loops: Vec::new(),
147+
redefinitions_allowed: false,
148+
};
130149
this.add_parameters_to_scope(parameters);
131150
this
132151
}
@@ -555,7 +574,9 @@ impl<'a> FunctionContext<'a> {
555574
/// by calling self.lookup(id)
556575
pub(super) fn define(&mut self, id: LocalId, value: Values) {
557576
let existing = self.definitions.insert(id, value);
558-
assert!(existing.is_none(), "Variable {id:?} was defined twice in ssa-gen pass");
577+
if !self.redefinitions_allowed && existing.is_some() {
578+
panic!("Variable {id:?} was defined twice in ssa-gen pass");
579+
}
559580
}
560581

561582
/// Looks up the value of a given local variable. Expects the variable to have

compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs

Lines changed: 167 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -628,15 +628,22 @@ impl FunctionContext<'_> {
628628
let start_index = self.codegen_non_tuple_expression(&for_expr.start_range)?;
629629

630630
self.builder.set_location(for_expr.end_range_location);
631-
let end_index = self.codegen_non_tuple_expression(&for_expr.end_range)?;
631+
let mut end_index = self.codegen_non_tuple_expression(&for_expr.end_range)?;
632632

633633
let range_bound = |id| self.builder.current_function.dfg.get_integer_constant(id);
634634

635635
if let (Some(start_constant), Some(end_constant)) =
636636
(range_bound(start_index), range_bound(end_index))
637637
{
638-
// If we can determine that the loop contains zero iterations then there's no need to codegen the loop.
639-
if start_constant >= end_constant {
638+
// For inclusive ranges (e.g., `0..=255`), the loop should run if start <= end.
639+
// For exclusive ranges (e.g., `0..256`), the loop should run if start < end.
640+
// If the condition is false, skip the loop entirely.
641+
let should_skip = if for_expr.inclusive {
642+
start_constant > end_constant
643+
} else {
644+
start_constant >= end_constant
645+
};
646+
if should_skip {
640647
return Ok(Self::unit_value());
641648
}
642649
}
@@ -647,11 +654,86 @@ impl FunctionContext<'_> {
647654

648655
// this is the 'i' in `for i in start .. end { block }`
649656
let index_type = Self::convert_non_tuple_type(&for_expr.index_type);
650-
let loop_index = self.builder.add_block_parameter(loop_entry, index_type);
657+
let loop_index = self.builder.add_block_parameter(loop_entry, index_type.clone());
658+
659+
let mut inclusive = for_expr.inclusive;
660+
661+
// If this is an inclusive for loop, check if the end index is not the maximum value for its type.
662+
// In that case we can generate an exclusive for loop up to `end + 1`, which is simpler than
663+
// the code of an inclusive loop.
664+
if inclusive {
665+
if let Some(end_constant) =
666+
self.builder.current_function.dfg.get_integer_constant(end_index)
667+
{
668+
let index_type = index_type.unwrap_numeric();
669+
let bit_size = match index_type {
670+
NumericType::Signed { bit_size } => bit_size - 1,
671+
NumericType::Unsigned { bit_size } => bit_size,
672+
NumericType::NativeField => panic!("Cannot iterate over Field"),
673+
};
674+
let max_value = if bit_size == 128 { u128::MAX } else { (1u128 << bit_size) - 1 };
675+
676+
if end_constant.into_numeric_constant().0.to_u128() < max_value {
677+
let end_constant_plus_one = end_constant.inc();
678+
end_index = self.builder.numeric_constant(
679+
end_constant_plus_one.into_numeric_constant().0,
680+
index_type,
681+
);
682+
inclusive = false;
683+
}
684+
}
685+
}
686+
687+
// For inclusive ranges we could generate a loop like:
688+
//
689+
// ```noir
690+
// let index = start;
691+
// while start <= end {
692+
// body;
693+
// if index == end { break; }
694+
// index += 1;
695+
// }
696+
// ``
697+
//
698+
// We could do that in order to avoid an overflow at `index += 1` when `end` is the maximum
699+
// value for the range type.
700+
//
701+
// However, an SSA like above breaks some assumptions in the unrolling optimization pass.
702+
//
703+
// Instead, we generate something like this:
704+
//
705+
// ```noir
706+
// let index = start;
707+
// // did_not_hit_break is set to false if a break is hit in the for body
708+
// let did_not_hit_break = true;
709+
// for index in start..end {
710+
// body;
711+
// }
712+
// if start <= end && did_not_hit_break {
713+
// index = end;
714+
// body;
715+
// }
716+
// ```
717+
//
718+
// That is, we generate an exclusive for loop and include an extra final iteration that
719+
// is only executed if the start is less than the end, and if no break was hit in the loop body.
720+
let did_not_hit_break_var = if inclusive {
721+
let did_not_hit_break_var = self.builder.insert_allocate(Type::bool());
722+
let zero = self.builder.numeric_constant(true, NumericType::bool());
723+
self.builder.insert_store(did_not_hit_break_var, zero);
724+
Some(did_not_hit_break_var)
725+
} else {
726+
None
727+
};
651728

652729
// Remember the blocks and variable used in case there are break/continue instructions
653730
// within the loop which need to jump to them.
654-
self.enter_loop(Loop { loop_entry, loop_index: Some(loop_index), loop_end });
731+
self.enter_loop(Loop {
732+
loop_entry,
733+
loop_index: Some(loop_index),
734+
loop_end,
735+
did_not_hit_break_var,
736+
});
655737

656738
// Set the location of the initial jmp instruction to the start range. This is the location
657739
// used to issue an error if the start range cannot be determined at compile-time.
@@ -661,7 +743,7 @@ impl FunctionContext<'_> {
661743
// Compile the loop entry block
662744
self.builder.switch_to_block(loop_entry);
663745

664-
// Set the location of the ending Lt instruction and the jmpif back-edge of the loop to the
746+
// Set the location of the ending comparison instruction and the jmpif back-edge of the loop to the
665747
// end range. These are the instructions used to issue an error if the end of the range
666748
// cannot be determined at compile-time.
667749
self.builder.set_location(for_expr.end_range_location);
@@ -673,14 +755,71 @@ impl FunctionContext<'_> {
673755
self.define(for_expr.index_variable, loop_index.into());
674756

675757
let result = self.codegen_expression(&for_expr.block);
676-
self.codegen_unless_break_or_continue(result, |this, _| {
758+
self.codegen_unless_break_or_continue(result.clone(), |this, _| {
677759
let new_loop_index = this.make_offset(loop_index, 1, true);
678760
this.builder.terminate_with_jmp(loop_entry, vec![new_loop_index]);
679761
})?;
680762

681763
// Finish by switching back to the end of the loop
682764
self.builder.switch_to_block(loop_end);
683765
self.exit_loop();
766+
767+
// Generate the final iteration for inclusive ranges
768+
if let Some(did_not_hit_break_var) = did_not_hit_break_var {
769+
let final_iteration = self.builder.insert_block();
770+
let final_iteration_end = self.builder.insert_block();
771+
772+
let did_not_hit_break = self.builder.insert_load(did_not_hit_break_var, Type::bool());
773+
// `start <= end` is equivalent to `!(start > end)`
774+
let end_is_less_than_start =
775+
self.builder.insert_binary(end_index, BinaryOp::Lt, start_index);
776+
let start_is_less_than_or_equal_to_end =
777+
self.builder.insert_not(end_is_less_than_start);
778+
let should_execute_loop_body = self.builder.insert_binary(
779+
did_not_hit_break,
780+
BinaryOp::And,
781+
start_is_less_than_or_equal_to_end,
782+
);
783+
self.builder.terminate_with_jmpif(
784+
should_execute_loop_body,
785+
final_iteration,
786+
final_iteration_end,
787+
);
788+
789+
self.builder.switch_to_block(final_iteration);
790+
791+
// We need to be in the context of a loop because a `break` in the loop, in the final
792+
// iteration, has to jump somewhere.
793+
// We set both `loop_entry` and `loop_end` to `final_iteration_end` because:
794+
// - `break` will jump to `loop_end`
795+
// - `continue` will jump to `loop_entry`, but here we also want to jump to the end
796+
self.enter_loop(Loop {
797+
loop_entry: final_iteration_end,
798+
loop_index: None,
799+
loop_end: final_iteration_end,
800+
did_not_hit_break_var: None,
801+
});
802+
803+
// Temporarily allow redefinitions because:
804+
// 1. We'll override the index variable
805+
// 2. We'll generate the for loop body again
806+
let old_redefinitions_allowed = self.redefinitions_allowed;
807+
self.redefinitions_allowed = true;
808+
809+
self.define(for_expr.index_variable, end_index.into());
810+
811+
let result = self.codegen_expression(&for_expr.block);
812+
self.codegen_unless_break_or_continue(result, |this, _| {
813+
this.builder.terminate_with_jmp(final_iteration_end, vec![]);
814+
})?;
815+
816+
self.redefinitions_allowed = old_redefinitions_allowed;
817+
818+
self.builder.switch_to_block(final_iteration_end);
819+
820+
self.exit_loop();
821+
}
822+
684823
Ok(Self::unit_value())
685824
}
686825

@@ -701,7 +840,12 @@ impl FunctionContext<'_> {
701840
let loop_body = self.builder.insert_block();
702841
let loop_end = self.builder.insert_block();
703842

704-
self.enter_loop(Loop { loop_entry: loop_body, loop_index: None, loop_end });
843+
self.enter_loop(Loop {
844+
loop_entry: loop_body,
845+
loop_index: None,
846+
loop_end,
847+
did_not_hit_break_var: None,
848+
});
705849

706850
self.builder.terminate_with_jmp(loop_body, vec![]);
707851

@@ -746,7 +890,12 @@ impl FunctionContext<'_> {
746890
let condition = self.codegen_non_tuple_expression(&while_.condition)?;
747891
self.builder.terminate_with_jmpif(condition, while_body, while_end);
748892

749-
self.enter_loop(Loop { loop_entry: while_entry, loop_index: None, loop_end: while_end });
893+
self.enter_loop(Loop {
894+
loop_entry: while_entry,
895+
loop_index: None,
896+
loop_end: while_end,
897+
did_not_hit_break_var: None,
898+
});
750899

751900
// Codegen the body
752901
self.builder.switch_to_block(while_body);
@@ -1241,7 +1390,15 @@ impl FunctionContext<'_> {
12411390
}
12421391

12431392
fn codegen_break(&mut self) -> Result<Values, RuntimeError> {
1244-
let loop_end = self.current_loop().loop_end;
1393+
let current_loop = self.current_loop();
1394+
1395+
if let Some(did_not_hit_break_var) = current_loop.did_not_hit_break_var {
1396+
// `did_not_hit_break_var = false` means we hit a break
1397+
let zero = self.builder.numeric_constant(false, NumericType::bool());
1398+
self.builder.insert_store(did_not_hit_break_var, zero);
1399+
}
1400+
1401+
let loop_end = current_loop.loop_end;
12451402
self.builder.terminate_with_jmp(loop_end, Vec::new());
12461403

12471404
Err(RuntimeError::BreakOrContinue { call_stack: CallStack::default() })

0 commit comments

Comments
 (0)