@@ -1076,3 +1076,80 @@ fn test_cross_group_reorder() {
10761076 op_annotations. insert ( r, root. clone ( ) ) ;
10771077 draw_ir_html ( & ir, op_annotations, "test33.html" ) ;
10781078}
1079+
1080+ /// Bug reproducer: GroupInput shares layer 1 with an IntInput inside the group.
1081+ /// This triggers the indexing bug in place_once_bottom_up (line 203) where
1082+ /// the layer position is used as an index into args_original_places.
1083+ ///
1084+ /// Forces crossing: two external inputs A, B go into the group. An internal source C
1085+ /// also exists on layer 1. The median heuristic will reorder based on downstream users,
1086+ /// potentially placing C between the GroupInputs.
1087+ #[ test]
1088+ fn test_mixed_first_layer_in_group ( ) {
1089+ // Structure:
1090+ // A, B (root) -> [group: gi_A, gi_B, internal_C (all layer 1) -> use them -> outputs] -> ret
1091+ // With careful wiring, reordering can place internal_C at position 1, pushing gi_B to position
1092+ // 2. args_original_places has 2 elements, so index 2 is out of bounds.
1093+ let mut ir: IR < TestLang > = IR :: empty ( ) ;
1094+ let ( op_a, a) = ir. add_op ( TestInstructionSet :: IntInput { pos : 0 } , svec ! [ ] ) ;
1095+ let ( op_b, b) = ir. add_op ( TestInstructionSet :: IntInput { pos : 1 } , svec ! [ ] ) ;
1096+ // Inside group_a:
1097+ let ( op_c, c) = ir. add_op ( TestInstructionSet :: IntInput { pos : 2 } , svec ! [ ] ) ; // internal source
1098+ // Wire so that C is used first (lower position), then B, then A - forces reorder
1099+ let ( op_add1, add1) = ir. add_op ( TestInstructionSet :: Add , svec ! [ c[ 0 ] , b[ 0 ] ] ) ; // uses C and B
1100+ let ( op_add2, add2) = ir. add_op ( TestInstructionSet :: Add , svec ! [ add1[ 0 ] , a[ 0 ] ] ) ; // uses result and A
1101+ // Back at root:
1102+ let ( op_ret, _) = ir. add_op ( TestInstructionSet :: Return , svec ! [ add2[ 0 ] ] ) ;
1103+
1104+ let root = Hierarchy :: new ( ) ;
1105+ let mut group_a = root. clone ( ) ;
1106+ group_a. push ( "group_a" ) ;
1107+
1108+ let mut op_annotations = ir. empty_opmap ( ) ;
1109+ op_annotations. insert ( op_a, root. clone ( ) ) ;
1110+ op_annotations. insert ( op_b, root. clone ( ) ) ;
1111+ op_annotations. insert ( op_c, group_a. clone ( ) ) ; // internal source, layer 1
1112+ op_annotations. insert ( op_add1, group_a. clone ( ) ) ;
1113+ op_annotations. insert ( op_add2, group_a. clone ( ) ) ;
1114+ op_annotations. insert ( op_ret, root. clone ( ) ) ;
1115+ draw_ir_html ( & ir, op_annotations, "test34.html" ) ;
1116+ }
1117+
1118+ /// Bug reproducer: GroupOutput shares last layer with a Return inside the group.
1119+ /// This triggers the indexing bug in place_once_top_down (line 100) where
1120+ /// the layer position is used as an index into rets_original_places.
1121+ ///
1122+ /// Strategy: Single GroupOutput + Return on last layer. If Return ends up at
1123+ /// position 0 and GroupOutput at position 1, indexing with 1 into a 1-element
1124+ /// rets_original_places array causes out of bounds.
1125+ /// We use two inputs where the one feeding the internal Return has lower position.
1126+ #[ test]
1127+ fn test_mixed_last_layer_in_group ( ) {
1128+ let mut ir: IR < TestLang > = IR :: empty ( ) ;
1129+ // inp0 at position 0, inp1 at position 1
1130+ let ( op_inp0, inp0) = ir. add_op ( TestInstructionSet :: IntInput { pos : 0 } , svec ! [ ] ) ;
1131+ let ( op_inp1, inp1) = ir. add_op ( TestInstructionSet :: IntInput { pos : 1 } , svec ! [ ] ) ;
1132+ // Inside group_a:
1133+ // inc1 from inp0 (position 0) -> feeds internal Return (will want position 0 on last layer)
1134+ let ( op_inc1, inc1) = ir. add_op ( TestInstructionSet :: Inc , svec ! [ inp0[ 0 ] ] ) ;
1135+ // inc2 from inp1 (position 1) -> exits group (will want position 1 on last layer)
1136+ let ( op_inc2, inc2) = ir. add_op ( TestInstructionSet :: Inc , svec ! [ inp1[ 0 ] ] ) ;
1137+ // Internal Return fed by inc1 - should get position 0 on last layer
1138+ let ( op_internal_ret, _) = ir. add_op ( TestInstructionSet :: Return , svec ! [ inc1[ 0 ] ] ) ;
1139+ // inc2 exits the group - GroupOutput should get position 1 on last layer
1140+ // But rets_original_places has only 1 element!
1141+ let ( op_ret, _) = ir. add_op ( TestInstructionSet :: Return , svec ! [ inc2[ 0 ] ] ) ;
1142+
1143+ let root = Hierarchy :: new ( ) ;
1144+ let mut group_a = root. clone ( ) ;
1145+ group_a. push ( "group_a" ) ;
1146+
1147+ let mut op_annotations = ir. empty_opmap ( ) ;
1148+ op_annotations. insert ( op_inp0, root. clone ( ) ) ;
1149+ op_annotations. insert ( op_inp1, root. clone ( ) ) ;
1150+ op_annotations. insert ( op_inc1, group_a. clone ( ) ) ;
1151+ op_annotations. insert ( op_inc2, group_a. clone ( ) ) ;
1152+ op_annotations. insert ( op_internal_ret, group_a. clone ( ) ) ;
1153+ op_annotations. insert ( op_ret, root. clone ( ) ) ;
1154+ draw_ir_html ( & ir, op_annotations, "test35.html" ) ;
1155+ }
0 commit comments