
I noticed that the two C functions emitted different IR: ``` int switch_duplicate_arms(int switch_val, int v, int w) { switch (switch_val) { default: break; case 0: w = v; break; case 1: w = v; break; } return w; } int if_duplicate_arms(int switch_val, int v, int w) { if (switch_val == 0) w = v; else if (switch_val == 1) w = v; return v0; } ``` We generate IR that looks like this: ``` define i32 @switch_duplicate_arms(i32 %0, i32 %1, i32 %2, i32 %3) { switch i32 %1, label %7 [ i32 0, label %5 i32 1, label %6 ] 5: br label %7 6: br label %7 7: %8 = phi i32 [ %3, %4 ], [ %2, %6 ], [ %2, %5 ] ret i32 %8 } define i32 @if_duplicate_arms(i32 %0, i32 %1, i32 %2, i32 %3) { %5 = icmp ult i32 %1, 2 %6 = select i1 %5, i32 %2, i32 %3 ret i32 %6 } ``` For `switch_duplicate_arms`, taking case 0 and 1 are the same since %5 and %6 branch to the same location and the incoming values for %8 are the same from those blocks. We could remove one on the duplicate switch targets and update the switch with the single target. On RISC-V, prior to this patch, we generate the following code: ``` switch_duplicate_arms: li a4, 1 beq a1, a4, .LBB0_2 mv a0, a3 bnez a1, .LBB0_3 .LBB0_2: mv a0, a2 .LBB0_3: ret if_duplicate_arms: li a4, 2 mv a0, a2 bltu a1, a4, .LBB1_2 mv a0, a3 .LBB1_2: ret ``` After this patch, the O3 code is optimized to the icmp + select pair, which gives us the same code gen as `if_duplicate_arms`, as desired. This results is one less branch instruction in the final assembly. This may help with both code size and further switch simplification. I found that this patch causes no significant impact to spec2006/int/ref and spec2017/intrate/ref. --------- Co-authored-by: Min Hsu <min@myhsu.dev>
312 lines
8.7 KiB
LLVM
312 lines
8.7 KiB
LLVM
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
|
|
; RUN: opt < %s -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 -S | FileCheck %s
|
|
|
|
; int foo1_with_default(int a) {
|
|
; switch(a) {
|
|
; case 10:
|
|
; return 10;
|
|
; case 20:
|
|
; return 2;
|
|
; }
|
|
; return 4;
|
|
; }
|
|
|
|
define i32 @foo1_with_default(i32 %a) {
|
|
; CHECK-LABEL: @foo1_with_default(
|
|
; CHECK-NEXT: entry:
|
|
; CHECK-NEXT: [[SWITCH_SELECTCMP:%.*]] = icmp eq i32 [[A:%.*]], 20
|
|
; CHECK-NEXT: [[SWITCH_SELECT:%.*]] = select i1 [[SWITCH_SELECTCMP]], i32 2, i32 4
|
|
; CHECK-NEXT: [[SWITCH_SELECTCMP1:%.*]] = icmp eq i32 [[A]], 10
|
|
; CHECK-NEXT: [[SWITCH_SELECT2:%.*]] = select i1 [[SWITCH_SELECTCMP1]], i32 10, i32 [[SWITCH_SELECT]]
|
|
; CHECK-NEXT: ret i32 [[SWITCH_SELECT2]]
|
|
;
|
|
entry:
|
|
switch i32 %a, label %sw.epilog [
|
|
i32 10, label %sw.bb
|
|
i32 20, label %sw.bb1
|
|
]
|
|
|
|
sw.bb:
|
|
br label %return
|
|
|
|
sw.bb1:
|
|
br label %return
|
|
|
|
sw.epilog:
|
|
br label %return
|
|
|
|
return:
|
|
%retval.0 = phi i32 [ 4, %sw.epilog ], [ 2, %sw.bb1 ], [ 10, %sw.bb ]
|
|
ret i32 %retval.0
|
|
}
|
|
|
|
; Same as above, but both cases have the same value.
|
|
define i32 @same_value(i32 %a) {
|
|
; CHECK-LABEL: @same_value(
|
|
; CHECK-NEXT: entry:
|
|
; CHECK-NEXT: [[SWITCH_SELECTCMP_CASE1:%.*]] = icmp eq i32 [[A:%.*]], 10
|
|
; CHECK-NEXT: [[SWITCH_SELECTCMP_CASE2:%.*]] = icmp eq i32 [[A]], 20
|
|
; CHECK-NEXT: [[SWITCH_SELECTCMP:%.*]] = or i1 [[SWITCH_SELECTCMP_CASE1]], [[SWITCH_SELECTCMP_CASE2]]
|
|
; CHECK-NEXT: [[TMP0:%.*]] = select i1 [[SWITCH_SELECTCMP]], i32 10, i32 4
|
|
; CHECK-NEXT: ret i32 [[TMP0]]
|
|
;
|
|
entry:
|
|
switch i32 %a, label %sw.epilog [
|
|
i32 10, label %sw.bb
|
|
i32 20, label %sw.bb
|
|
]
|
|
|
|
sw.bb:
|
|
br label %return
|
|
|
|
sw.epilog:
|
|
br label %return
|
|
|
|
return:
|
|
%retval.0 = phi i32 [ 4, %sw.epilog ], [ 10, %sw.bb ]
|
|
ret i32 %retval.0
|
|
}
|
|
|
|
define i1 @switch_to_select_same2_case_results_different_default(i8 %0) {
|
|
; CHECK-LABEL: @switch_to_select_same2_case_results_different_default(
|
|
; CHECK-NEXT: [[SWITCH_AND:%.*]] = and i8 [[TMP0:%.*]], -5
|
|
; CHECK-NEXT: [[SWITCH_SELECTCMP:%.*]] = icmp eq i8 [[SWITCH_AND]], 0
|
|
; CHECK-NEXT: [[TMP2:%.*]] = select i1 [[SWITCH_SELECTCMP]], i1 true, i1 false
|
|
; CHECK-NEXT: ret i1 [[TMP2]]
|
|
;
|
|
switch i8 %0, label %2 [
|
|
i8 4, label %3
|
|
i8 0, label %3
|
|
]
|
|
|
|
2:
|
|
br label %3
|
|
|
|
3:
|
|
%4 = phi i1 [ false, %2 ], [ true, %1 ], [ true, %1 ]
|
|
ret i1 %4
|
|
}
|
|
|
|
define i1 @switch_to_select_same2_case_results_different_default_and_positive_offset_for_case(i8 %0) {
|
|
; CHECK-LABEL: @switch_to_select_same2_case_results_different_default_and_positive_offset_for_case(
|
|
; CHECK-NEXT: [[TMP2:%.*]] = sub i8 [[TMP0:%.*]], 43
|
|
; CHECK-NEXT: [[SWITCH_AND:%.*]] = and i8 [[TMP2]], -3
|
|
; CHECK-NEXT: [[SWITCH_SELECTCMP:%.*]] = icmp eq i8 [[SWITCH_AND]], 0
|
|
; CHECK-NEXT: [[TMP3:%.*]] = select i1 [[SWITCH_SELECTCMP]], i1 true, i1 false
|
|
; CHECK-NEXT: ret i1 [[TMP3]]
|
|
;
|
|
switch i8 %0, label %2 [
|
|
i8 43, label %3
|
|
i8 45, label %3
|
|
]
|
|
|
|
2:
|
|
br label %3
|
|
|
|
3:
|
|
%4 = phi i1 [ false, %2 ], [ true, %1 ], [ true, %1 ]
|
|
ret i1 %4
|
|
}
|
|
|
|
define i8 @switch_to_select_same2_case_results_different_default_and_negative_offset_for_case(i32 %i) {
|
|
; CHECK-LABEL: @switch_to_select_same2_case_results_different_default_and_negative_offset_for_case(
|
|
; CHECK-NEXT: entry:
|
|
; CHECK-NEXT: [[TMP0:%.*]] = sub i32 [[I:%.*]], -5
|
|
; CHECK-NEXT: [[SWITCH_AND:%.*]] = and i32 [[TMP0]], -3
|
|
; CHECK-NEXT: [[SWITCH_SELECTCMP:%.*]] = icmp eq i32 [[SWITCH_AND]], 0
|
|
; CHECK-NEXT: [[TMP1:%.*]] = select i1 [[SWITCH_SELECTCMP]], i8 3, i8 42
|
|
; CHECK-NEXT: ret i8 [[TMP1]]
|
|
;
|
|
entry:
|
|
switch i32 %i, label %default [
|
|
i32 -3, label %end
|
|
i32 -5, label %end
|
|
]
|
|
|
|
default:
|
|
br label %end
|
|
|
|
end:
|
|
%t0 = phi i8 [ 42, %default ], [ 3, %entry ], [ 3, %entry ]
|
|
ret i8 %t0
|
|
}
|
|
|
|
define i1 @switch_to_select_same4_case_results_different_default(i32 %i) {
|
|
; CHECK-LABEL: @switch_to_select_same4_case_results_different_default(
|
|
; CHECK-NEXT: entry:
|
|
; CHECK-NEXT: [[SWITCH_AND:%.*]] = and i32 [[I:%.*]], -7
|
|
; CHECK-NEXT: [[SWITCH_SELECTCMP:%.*]] = icmp eq i32 [[SWITCH_AND]], 0
|
|
; CHECK-NEXT: [[TMP0:%.*]] = select i1 [[SWITCH_SELECTCMP]], i1 true, i1 false
|
|
; CHECK-NEXT: ret i1 [[TMP0]]
|
|
;
|
|
entry:
|
|
switch i32 %i, label %lor.rhs [
|
|
i32 0, label %lor.end
|
|
i32 2, label %lor.end
|
|
i32 4, label %lor.end
|
|
i32 6, label %lor.end
|
|
]
|
|
|
|
lor.rhs:
|
|
br label %lor.end
|
|
|
|
lor.end:
|
|
%0 = phi i1 [ true, %entry ], [ false, %lor.rhs ], [ true, %entry ], [ true, %entry ], [ true, %entry ]
|
|
ret i1 %0
|
|
}
|
|
|
|
define i1 @switch_to_select_same4_case_results_different_default_alt_bitmask(i32 %i) {
|
|
; CHECK-LABEL: @switch_to_select_same4_case_results_different_default_alt_bitmask(
|
|
; CHECK-NEXT: entry:
|
|
; CHECK-NEXT: [[SWITCH_AND:%.*]] = and i32 [[I:%.*]], -11
|
|
; CHECK-NEXT: [[SWITCH_SELECTCMP:%.*]] = icmp eq i32 [[SWITCH_AND]], 0
|
|
; CHECK-NEXT: [[TMP0:%.*]] = select i1 [[SWITCH_SELECTCMP]], i1 true, i1 false
|
|
; CHECK-NEXT: ret i1 [[TMP0]]
|
|
;
|
|
entry:
|
|
switch i32 %i, label %lor.rhs [
|
|
i32 0, label %lor.end
|
|
i32 2, label %lor.end
|
|
i32 8, label %lor.end
|
|
i32 10, label %lor.end
|
|
]
|
|
|
|
lor.rhs:
|
|
br label %lor.end
|
|
|
|
lor.end:
|
|
%0 = phi i1 [ true, %entry ], [ false, %lor.rhs ], [ true, %entry ], [ true, %entry ], [ true, %entry ]
|
|
ret i1 %0
|
|
}
|
|
|
|
define i1 @switch_to_select_same4_case_results_different_default_positive_offset(i32 %i) {
|
|
; CHECK-LABEL: @switch_to_select_same4_case_results_different_default_positive_offset(
|
|
; CHECK-NEXT: entry:
|
|
; CHECK-NEXT: [[TMP0:%.*]] = sub i32 [[I:%.*]], 2
|
|
; CHECK-NEXT: [[SWITCH_AND:%.*]] = and i32 [[TMP0]], -11
|
|
; CHECK-NEXT: [[SWITCH_SELECTCMP:%.*]] = icmp eq i32 [[SWITCH_AND]], 0
|
|
; CHECK-NEXT: [[TMP1:%.*]] = select i1 [[SWITCH_SELECTCMP]], i1 true, i1 false
|
|
; CHECK-NEXT: ret i1 [[TMP1]]
|
|
;
|
|
entry:
|
|
switch i32 %i, label %lor.rhs [
|
|
i32 2, label %lor.end
|
|
i32 4, label %lor.end
|
|
i32 10, label %lor.end
|
|
i32 12, label %lor.end
|
|
]
|
|
|
|
lor.rhs:
|
|
br label %lor.end
|
|
|
|
lor.end:
|
|
%0 = phi i1 [ true, %entry ], [ false, %lor.rhs ], [ true, %entry ], [ true, %entry ], [ true, %entry ]
|
|
ret i1 %0
|
|
}
|
|
|
|
define i1 @switch_to_select_invalid_mask(i32 %i) {
|
|
; CHECK-LABEL: @switch_to_select_invalid_mask(
|
|
; CHECK-NEXT: entry:
|
|
; CHECK-NEXT: switch i32 [[I:%.*]], label [[LOR_RHS:%.*]] [
|
|
; CHECK-NEXT: i32 1, label [[LOR_END:%.*]]
|
|
; CHECK-NEXT: i32 4, label [[LOR_END]]
|
|
; CHECK-NEXT: i32 10, label [[LOR_END]]
|
|
; CHECK-NEXT: i32 12, label [[LOR_END]]
|
|
; CHECK-NEXT: ]
|
|
; CHECK: lor.rhs:
|
|
; CHECK-NEXT: br label [[LOR_END]]
|
|
; CHECK: lor.end:
|
|
; CHECK-NEXT: [[TMP0:%.*]] = phi i1 [ true, [[ENTRY:%.*]] ], [ false, [[LOR_RHS]] ], [ true, [[ENTRY]] ], [ true, [[ENTRY]] ], [ true, [[ENTRY]] ]
|
|
; CHECK-NEXT: ret i1 [[TMP0]]
|
|
;
|
|
entry:
|
|
switch i32 %i, label %lor.rhs [
|
|
i32 1, label %lor.end
|
|
i32 4, label %lor.end
|
|
i32 10, label %lor.end
|
|
i32 12, label %lor.end
|
|
]
|
|
|
|
lor.rhs:
|
|
br label %lor.end
|
|
|
|
lor.end:
|
|
%0 = phi i1 [ true, %entry ], [ false, %lor.rhs ], [ true, %entry ], [ true, %entry ], [ true, %entry ]
|
|
ret i1 %0
|
|
}
|
|
|
|
define i1 @switch_to_select_nonpow2_cases(i32 %i) {
|
|
; CHECK-LABEL: @switch_to_select_nonpow2_cases(
|
|
; CHECK-NEXT: entry:
|
|
; CHECK-NEXT: switch i32 [[I:%.*]], label [[LOR_RHS:%.*]] [
|
|
; CHECK-NEXT: i32 0, label [[LOR_END:%.*]]
|
|
; CHECK-NEXT: i32 2, label [[LOR_END]]
|
|
; CHECK-NEXT: i32 4, label [[LOR_END]]
|
|
; CHECK-NEXT: ]
|
|
; CHECK: lor.rhs:
|
|
; CHECK-NEXT: br label [[LOR_END]]
|
|
; CHECK: lor.end:
|
|
; CHECK-NEXT: [[TMP0:%.*]] = phi i1 [ true, [[ENTRY:%.*]] ], [ false, [[LOR_RHS]] ], [ true, [[ENTRY]] ], [ true, [[ENTRY]] ]
|
|
; CHECK-NEXT: ret i1 [[TMP0]]
|
|
;
|
|
entry:
|
|
switch i32 %i, label %lor.rhs [
|
|
i32 0, label %lor.end
|
|
i32 2, label %lor.end
|
|
i32 4, label %lor.end
|
|
]
|
|
|
|
lor.rhs:
|
|
br label %lor.end
|
|
|
|
lor.end:
|
|
%0 = phi i1 [ true, %entry ], [ false, %lor.rhs ], [ true, %entry ], [ true, %entry ]
|
|
ret i1 %0
|
|
}
|
|
|
|
; TODO: we can produce the optimal code when there is no default also
|
|
define i8 @switch_to_select_two_case_results_no_default(i32 %i) {
|
|
; CHECK-LABEL: @switch_to_select_two_case_results_no_default(
|
|
; CHECK-NEXT: entry:
|
|
; CHECK-NEXT: switch i32 [[I:%.*]], label [[DEFAULT:%.*]] [
|
|
; CHECK-NEXT: i32 0, label [[END:%.*]]
|
|
; CHECK-NEXT: i32 2, label [[END]]
|
|
; CHECK-NEXT: i32 4, label [[CASE3:%.*]]
|
|
; CHECK-NEXT: i32 6, label [[CASE3]]
|
|
; CHECK-NEXT: ]
|
|
; CHECK: case3:
|
|
; CHECK-NEXT: br label [[END]]
|
|
; CHECK: default:
|
|
; CHECK-NEXT: unreachable
|
|
; CHECK: end:
|
|
; CHECK-NEXT: [[T0:%.*]] = phi i8 [ 44, [[CASE3]] ], [ 42, [[ENTRY:%.*]] ], [ 42, [[ENTRY]] ]
|
|
; CHECK-NEXT: ret i8 [[T0]]
|
|
;
|
|
entry:
|
|
switch i32 %i, label %default [
|
|
i32 0, label %case1
|
|
i32 2, label %case2
|
|
i32 4, label %case3
|
|
i32 6, label %case4
|
|
]
|
|
|
|
case1:
|
|
br label %end
|
|
|
|
case2:
|
|
br label %end
|
|
|
|
case3:
|
|
br label %end
|
|
|
|
case4:
|
|
br label %end
|
|
|
|
default:
|
|
unreachable
|
|
|
|
end:
|
|
%t0 = phi i8 [ 42, %case1 ], [ 42, %case2 ], [ 44, %case3 ], [ 44, %case4 ]
|
|
ret i8 %t0
|
|
}
|