Skip to content

Commit d148df6

Browse files
jfechervezenovm
andauthored
fix(experimental): Fix execution of match expressions with multiple branches (#7570)
Co-authored-by: Maxim Vezenov <mvezenov@gmail.com>
1 parent a6e5526 commit d148df6

7 files changed

Lines changed: 89 additions & 10 deletions

File tree

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

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use self::{
2424
value::{Tree, Values},
2525
};
2626

27+
use super::ir::basic_block::BasicBlockId;
2728
use super::ir::dfg::GlobalsGraph;
2829
use super::ir::instruction::ErrorType;
2930
use super::ir::types::NumericType;
@@ -767,7 +768,15 @@ impl FunctionContext<'_> {
767768
let tag = self.enum_tag(&variable);
768769
let tag_type = self.builder.type_of_value(tag).unwrap_numeric();
769770

770-
let end_block = self.builder.insert_block();
771+
let make_end_block = |this: &mut Self| -> (BasicBlockId, Values) {
772+
let block = this.builder.insert_block();
773+
let results = Self::map_type(&match_expr.typ, |typ| {
774+
this.builder.add_block_parameter(block, typ).into()
775+
});
776+
(block, results)
777+
};
778+
779+
let (end_block, end_results) = make_end_block(self);
771780

772781
// Optimization: if there is no default case we can jump directly to the last case
773782
// when finished with the previous case instead of using a jmpif with an unreachable
@@ -778,6 +787,8 @@ impl FunctionContext<'_> {
778787
match_expr.cases.len() - 1
779788
};
780789

790+
let mut blocks_to_merge = Vec::with_capacity(last_case);
791+
781792
for i in 0..last_case {
782793
let case = &match_expr.cases[i];
783794
let variant_tag = self.variant_index_value(&case.constructor, tag_type)?;
@@ -790,28 +801,70 @@ impl FunctionContext<'_> {
790801
self.builder.switch_to_block(case_block);
791802
self.bind_case_arguments(variable.clone(), case);
792803
let results = self.codegen_expression(&case.branch)?.into_value_list(self);
793-
self.builder.terminate_with_jmp(end_block, results);
804+
805+
// Each branch will jump to a different end block for now. We have to merge them all
806+
// later since SSA doesn't support more than two blocks jumping to the same end block.
807+
let local_end_block = make_end_block(self);
808+
self.builder.terminate_with_jmp(local_end_block.0, results);
809+
blocks_to_merge.push(local_end_block);
794810

795811
self.builder.switch_to_block(else_block);
796812
}
797813

814+
let (last_local_end_block, last_results) = make_end_block(self);
815+
blocks_to_merge.push((last_local_end_block, last_results));
816+
798817
if let Some(branch) = &match_expr.default_case {
799818
let results = self.codegen_expression(branch)?.into_value_list(self);
800-
self.builder.terminate_with_jmp(end_block, results);
819+
self.builder.terminate_with_jmp(last_local_end_block, results);
801820
} else {
802821
// If there is no default case, assume we saved the last case from the
803822
// last_case optimization above
804823
let case = match_expr.cases.last().unwrap();
805824
self.bind_case_arguments(variable, case);
806825
let results = self.codegen_expression(&case.branch)?.into_value_list(self);
807-
self.builder.terminate_with_jmp(end_block, results);
826+
self.builder.terminate_with_jmp(last_local_end_block, results);
827+
}
828+
829+
// Merge blocks as last-in first-out:
830+
//
831+
// local_end_block0-----------------------------------------\
832+
// end block
833+
// /
834+
// local_end_block1---------------------\ /
835+
// new merge block2-/
836+
// local_end_block2--\ /
837+
// new merge block1-/
838+
// local_end_block3--/
839+
//
840+
// This is necessary since SSA panics during flattening if we immediately
841+
// try to jump directly to end block instead: https://github.com/noir-lang/noir/issues/7323.
842+
//
843+
// It'd also be more efficient to merge them tournament-bracket style but that
844+
// also leads to panics during flattening for similar reasons.
845+
while let Some((block, results)) = blocks_to_merge.pop() {
846+
self.builder.switch_to_block(block);
847+
848+
if let Some((block2, results2)) = blocks_to_merge.pop() {
849+
// Merge two blocks in the queue together
850+
let (new_merge, new_merge_results) = make_end_block(self);
851+
blocks_to_merge.push((new_merge, new_merge_results));
852+
853+
let results = results.into_value_list(self);
854+
self.builder.terminate_with_jmp(new_merge, results);
855+
856+
self.builder.switch_to_block(block2);
857+
let results2 = results2.into_value_list(self);
858+
self.builder.terminate_with_jmp(new_merge, results2);
859+
} else {
860+
// Finally done, jump to the end
861+
let results = results.into_value_list(self);
862+
self.builder.terminate_with_jmp(end_block, results);
863+
}
808864
}
809865

810866
self.builder.switch_to_block(end_block);
811-
let result = Self::map_type(&match_expr.typ, |typ| {
812-
self.builder.add_block_parameter(end_block, typ).into()
813-
});
814-
Ok(result)
867+
Ok(end_results)
815868
}
816869

817870
fn variant_index_value(
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[package]
2+
name = "regression_7323"
3+
type = "bin"
4+
authors = [""]
5+
6+
[dependencies]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
x = 5
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// This program lead to panics previously due to the compiler lowering it to multiple blocks
2+
// which all jumped to the same end block. It runs now due to the compiler lowering to the
3+
// equivalent of a nested series of if-else instead.
4+
fn main(x: Field) {
5+
match x {
6+
1 => panic(f"Branch 1 should not be taken"),
7+
2 => panic(f"Branch 2 should not be taken"),
8+
3 => panic(f"Branch 3 should not be taken"),
9+
_ => (),
10+
}
11+
}

test_programs/rebuild.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ process_dir() {
4747

4848
export -f process_dir
4949

50-
excluded_dirs=("workspace" "workspace_default_member")
50+
# Reactive `regression_7323` once enums are ready
51+
excluded_dirs=("workspace" "workspace_default_member" "regression_7323")
5152
current_dir=$(pwd)
5253
base_path="$current_dir/execution_success"
5354
dirs_to_process=()

tooling/debugger/ignored-tests.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ macros
55
reference_counts
66
references
77
regression_4709
8+
regression_7323
89
reference_only_used_as_alias
910
brillig_rc_regression_6123

tooling/nargo_cli/src/cli/compile_cmd.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ mod tests {
325325
use nargo::ops::compile_program;
326326
use nargo_toml::PackageSelection;
327327
use noirc_driver::{CompileOptions, CrateName};
328+
use noirc_frontend::elaborator::UnstableFeature;
328329
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
329330

330331
use crate::cli::test_cmd::formatters::diagnostic_to_string;
@@ -403,12 +404,17 @@ mod tests {
403404
let binary_packages = workspace.into_iter().filter(|package| package.is_binary());
404405

405406
for package in binary_packages {
407+
let options = CompileOptions {
408+
unstable_features: vec![UnstableFeature::Enums],
409+
..Default::default()
410+
};
411+
406412
let (program_0, _warnings) = compile_program(
407413
&file_manager,
408414
&parsed_files,
409415
workspace,
410416
package,
411-
&CompileOptions::default(),
417+
&options,
412418
None,
413419
)
414420
.unwrap_or_else(|err| {

0 commit comments

Comments
 (0)