@@ -24,6 +24,7 @@ use self::{
2424 value:: { Tree , Values } ,
2525} ;
2626
27+ use super :: ir:: basic_block:: BasicBlockId ;
2728use super :: ir:: dfg:: GlobalsGraph ;
2829use super :: ir:: instruction:: ErrorType ;
2930use 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 (
0 commit comments