That transformation is lossy, as discussed in https://github.com/llvm/llvm-project/issues/53853 and https://github.com/rust-lang/rust/issues/85133#issuecomment-904185574 This is an alternative to D119839, which would add a limited IPSCCP into SimplifyCFG. Unlike lowering switch to lookup, we still want this transformation to happen relatively early, but after giving a chance for the things like CVP to do their thing. It seems like deferring it just until the IPSCCP is enough for the tests at hand, but perhaps we need to be more aggressive and disable it until CVP. Fixes https://github.com/llvm/llvm-project/issues/53853 Refs. https://github.com/rust-lang/rust/issues/85133 Reviewed By: nikic Differential Revision: https://reviews.llvm.org/D119854
263 lines
7.0 KiB
LLVM
263 lines
7.0 KiB
LLVM
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
|
|
; RUN: opt %s -S -passes='simplifycfg<switch-to-lookup>' -simplifycfg-require-and-preserve-domtree=1 -switch-range-to-icmp | FileCheck %s
|
|
|
|
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
|
declare void @foo(i32)
|
|
|
|
define void @test(i1 %a) {
|
|
; CHECK-LABEL: @test(
|
|
; CHECK-NEXT: [[A_OFF:%.*]] = add i1 [[A:%.*]], true
|
|
; CHECK-NEXT: [[SWITCH:%.*]] = icmp ult i1 [[A_OFF]], true
|
|
; CHECK-NEXT: br i1 [[SWITCH]], label [[TRUE:%.*]], label [[FALSE:%.*]]
|
|
; CHECK: common.ret:
|
|
; CHECK-NEXT: ret void
|
|
; CHECK: true:
|
|
; CHECK-NEXT: call void @foo(i32 1)
|
|
; CHECK-NEXT: br label [[COMMON_RET:%.*]]
|
|
; CHECK: false:
|
|
; CHECK-NEXT: call void @foo(i32 3)
|
|
; CHECK-NEXT: br label [[COMMON_RET]]
|
|
;
|
|
switch i1 %a, label %default [i1 1, label %true
|
|
i1 0, label %false]
|
|
true:
|
|
call void @foo(i32 1)
|
|
ret void
|
|
false:
|
|
call void @foo(i32 3)
|
|
ret void
|
|
default:
|
|
call void @foo(i32 2)
|
|
ret void
|
|
}
|
|
|
|
define void @test2(i2 %a) {
|
|
; CHECK-LABEL: @test2(
|
|
; CHECK-NEXT: switch i2 [[A:%.*]], label [[DOTUNREACHABLEDEFAULT:%.*]] [
|
|
; CHECK-NEXT: i2 0, label [[CASE0:%.*]]
|
|
; CHECK-NEXT: i2 1, label [[CASE1:%.*]]
|
|
; CHECK-NEXT: i2 -2, label [[CASE2:%.*]]
|
|
; CHECK-NEXT: i2 -1, label [[CASE3:%.*]]
|
|
; CHECK-NEXT: ]
|
|
; CHECK: common.ret:
|
|
; CHECK-NEXT: ret void
|
|
; CHECK: case0:
|
|
; CHECK-NEXT: call void @foo(i32 0)
|
|
; CHECK-NEXT: br label [[COMMON_RET:%.*]]
|
|
; CHECK: case1:
|
|
; CHECK-NEXT: call void @foo(i32 1)
|
|
; CHECK-NEXT: br label [[COMMON_RET]]
|
|
; CHECK: case2:
|
|
; CHECK-NEXT: call void @foo(i32 2)
|
|
; CHECK-NEXT: br label [[COMMON_RET]]
|
|
; CHECK: case3:
|
|
; CHECK-NEXT: call void @foo(i32 3)
|
|
; CHECK-NEXT: br label [[COMMON_RET]]
|
|
; CHECK: .unreachabledefault:
|
|
; CHECK-NEXT: unreachable
|
|
;
|
|
switch i2 %a, label %default [i2 0, label %case0
|
|
i2 1, label %case1
|
|
i2 2, label %case2
|
|
i2 3, label %case3]
|
|
case0:
|
|
call void @foo(i32 0)
|
|
ret void
|
|
case1:
|
|
call void @foo(i32 1)
|
|
ret void
|
|
case2:
|
|
call void @foo(i32 2)
|
|
ret void
|
|
case3:
|
|
call void @foo(i32 3)
|
|
ret void
|
|
default:
|
|
call void @foo(i32 4)
|
|
ret void
|
|
}
|
|
|
|
; This one is a negative test - we know the value of the default,
|
|
; but that's about it
|
|
define void @test3(i2 %a) {
|
|
; CHECK-LABEL: @test3(
|
|
; CHECK-NEXT: switch i2 [[A:%.*]], label [[DEFAULT:%.*]] [
|
|
; CHECK-NEXT: i2 0, label [[CASE0:%.*]]
|
|
; CHECK-NEXT: i2 1, label [[CASE1:%.*]]
|
|
; CHECK-NEXT: i2 -2, label [[CASE2:%.*]]
|
|
; CHECK-NEXT: ]
|
|
; CHECK: common.ret:
|
|
; CHECK-NEXT: ret void
|
|
; CHECK: case0:
|
|
; CHECK-NEXT: call void @foo(i32 0)
|
|
; CHECK-NEXT: br label [[COMMON_RET:%.*]]
|
|
; CHECK: case1:
|
|
; CHECK-NEXT: call void @foo(i32 1)
|
|
; CHECK-NEXT: br label [[COMMON_RET]]
|
|
; CHECK: case2:
|
|
; CHECK-NEXT: call void @foo(i32 2)
|
|
; CHECK-NEXT: br label [[COMMON_RET]]
|
|
; CHECK: default:
|
|
; CHECK-NEXT: call void @foo(i32 3)
|
|
; CHECK-NEXT: br label [[COMMON_RET]]
|
|
;
|
|
switch i2 %a, label %default [i2 0, label %case0
|
|
i2 1, label %case1
|
|
i2 2, label %case2]
|
|
|
|
case0:
|
|
call void @foo(i32 0)
|
|
ret void
|
|
case1:
|
|
call void @foo(i32 1)
|
|
ret void
|
|
case2:
|
|
call void @foo(i32 2)
|
|
ret void
|
|
default:
|
|
call void @foo(i32 3)
|
|
ret void
|
|
}
|
|
|
|
; Negative test - check for possible overflow when computing
|
|
; number of possible cases.
|
|
define void @test4(i128 %a) {
|
|
; CHECK-LABEL: @test4(
|
|
; CHECK-NEXT: switch i128 [[A:%.*]], label [[DEFAULT:%.*]] [
|
|
; CHECK-NEXT: i128 0, label [[CASE0:%.*]]
|
|
; CHECK-NEXT: i128 1, label [[CASE1:%.*]]
|
|
; CHECK-NEXT: ]
|
|
; CHECK: common.ret:
|
|
; CHECK-NEXT: ret void
|
|
; CHECK: case0:
|
|
; CHECK-NEXT: call void @foo(i32 0)
|
|
; CHECK-NEXT: br label [[COMMON_RET:%.*]]
|
|
; CHECK: case1:
|
|
; CHECK-NEXT: call void @foo(i32 1)
|
|
; CHECK-NEXT: br label [[COMMON_RET]]
|
|
; CHECK: default:
|
|
; CHECK-NEXT: call void @foo(i32 2)
|
|
; CHECK-NEXT: br label [[COMMON_RET]]
|
|
;
|
|
switch i128 %a, label %default [i128 0, label %case0
|
|
i128 1, label %case1]
|
|
|
|
case0:
|
|
call void @foo(i32 0)
|
|
ret void
|
|
case1:
|
|
call void @foo(i32 1)
|
|
ret void
|
|
default:
|
|
call void @foo(i32 2)
|
|
ret void
|
|
}
|
|
|
|
; All but one bit known zero
|
|
define void @test5(i8 %a) {
|
|
; CHECK-LABEL: @test5(
|
|
; CHECK-NEXT: [[CMP:%.*]] = icmp ult i8 [[A:%.*]], 2
|
|
; CHECK-NEXT: call void @llvm.assume(i1 [[CMP]])
|
|
; CHECK-NEXT: [[A_OFF:%.*]] = add i8 [[A]], -1
|
|
; CHECK-NEXT: [[SWITCH:%.*]] = icmp ult i8 [[A_OFF]], 1
|
|
; CHECK-NEXT: br i1 [[SWITCH]], label [[TRUE:%.*]], label [[FALSE:%.*]]
|
|
; CHECK: common.ret:
|
|
; CHECK-NEXT: ret void
|
|
; CHECK: true:
|
|
; CHECK-NEXT: call void @foo(i32 1)
|
|
; CHECK-NEXT: br label [[COMMON_RET:%.*]]
|
|
; CHECK: false:
|
|
; CHECK-NEXT: call void @foo(i32 3)
|
|
; CHECK-NEXT: br label [[COMMON_RET]]
|
|
;
|
|
%cmp = icmp ult i8 %a, 2
|
|
call void @llvm.assume(i1 %cmp)
|
|
switch i8 %a, label %default [i8 1, label %true
|
|
i8 0, label %false]
|
|
true:
|
|
call void @foo(i32 1)
|
|
ret void
|
|
false:
|
|
call void @foo(i32 3)
|
|
ret void
|
|
default:
|
|
call void @foo(i32 2)
|
|
ret void
|
|
}
|
|
|
|
;; All but one bit known one
|
|
define void @test6(i8 %a) {
|
|
; CHECK-LABEL: @test6(
|
|
; CHECK-NEXT: [[AND:%.*]] = and i8 [[A:%.*]], -2
|
|
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[AND]], -2
|
|
; CHECK-NEXT: call void @llvm.assume(i1 [[CMP]])
|
|
; CHECK-NEXT: [[A_OFF:%.*]] = add i8 [[A]], 1
|
|
; CHECK-NEXT: [[SWITCH:%.*]] = icmp ult i8 [[A_OFF]], 1
|
|
; CHECK-NEXT: br i1 [[SWITCH]], label [[TRUE:%.*]], label [[FALSE:%.*]]
|
|
; CHECK: common.ret:
|
|
; CHECK-NEXT: ret void
|
|
; CHECK: true:
|
|
; CHECK-NEXT: call void @foo(i32 1)
|
|
; CHECK-NEXT: br label [[COMMON_RET:%.*]]
|
|
; CHECK: false:
|
|
; CHECK-NEXT: call void @foo(i32 3)
|
|
; CHECK-NEXT: br label [[COMMON_RET]]
|
|
;
|
|
%and = and i8 %a, 254
|
|
%cmp = icmp eq i8 %and, 254
|
|
call void @llvm.assume(i1 %cmp)
|
|
switch i8 %a, label %default [i8 255, label %true
|
|
i8 254, label %false]
|
|
true:
|
|
call void @foo(i32 1)
|
|
ret void
|
|
false:
|
|
call void @foo(i32 3)
|
|
ret void
|
|
default:
|
|
call void @foo(i32 2)
|
|
ret void
|
|
}
|
|
|
|
; Check that we can eliminate both dead cases and dead defaults
|
|
; within a single run of simplifycfg
|
|
define void @test7(i8 %a) {
|
|
; CHECK-LABEL: @test7(
|
|
; CHECK-NEXT: [[AND:%.*]] = and i8 [[A:%.*]], -2
|
|
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[AND]], -2
|
|
; CHECK-NEXT: call void @llvm.assume(i1 [[CMP]])
|
|
; CHECK-NEXT: [[A_OFF:%.*]] = add i8 [[A]], 1
|
|
; CHECK-NEXT: [[SWITCH:%.*]] = icmp ult i8 [[A_OFF]], 1
|
|
; CHECK-NEXT: br i1 [[SWITCH]], label [[TRUE:%.*]], label [[FALSE:%.*]]
|
|
; CHECK: common.ret:
|
|
; CHECK-NEXT: ret void
|
|
; CHECK: true:
|
|
; CHECK-NEXT: call void @foo(i32 1)
|
|
; CHECK-NEXT: br label [[COMMON_RET:%.*]]
|
|
; CHECK: false:
|
|
; CHECK-NEXT: call void @foo(i32 3)
|
|
; CHECK-NEXT: br label [[COMMON_RET]]
|
|
;
|
|
%and = and i8 %a, 254
|
|
%cmp = icmp eq i8 %and, 254
|
|
call void @llvm.assume(i1 %cmp)
|
|
switch i8 %a, label %default [i8 255, label %true
|
|
i8 254, label %false
|
|
i8 0, label %also_dead]
|
|
true:
|
|
call void @foo(i32 1)
|
|
ret void
|
|
false:
|
|
call void @foo(i32 3)
|
|
ret void
|
|
also_dead:
|
|
call void @foo(i32 5)
|
|
ret void
|
|
default:
|
|
call void @foo(i32 2)
|
|
ret void
|
|
}
|
|
|
|
declare void @llvm.assume(i1)
|
|
|