
…ntElimination ArgumentPromotion and DeadArgumentElimination passes could change function signatures but the function name remains the same as before the transformation. This makes it hard for tracing with bpf programs where user tends to use function signature in the source. See discussion [1] for details. This patch added suffix to functions whose signatures are changed. The suffix lets users know that function signature has changed and they need to impact the IR or binary to find modified signature before tracing those functions. The suffix for ArgumentPromotion is ".argprom" and the suffixes for DeadArgumentElimination are ".argelim" and ".retelim". The suffix also gives user hints about what kind of transformation has been done. With this patch, I built a recent linux kernel with full LTO enabled. I got 4 functions with only argpromotion like ``` set_track_update.argelim.argprom pmd_trans_huge_lock.argprom ... ``` I got 1058 functions with only deadargelim like ``` process_bit0.argelim pci_io_ecs_init.argelim ... ``` I got 3 functions with both argpromotion and deadargelim ``` set_track_update.argelim.argprom zero_pud_populate.argelim.argprom zero_pmd_populate.argelim.argprom ``` [1] https://github.com/llvm/llvm-project/issues/104678
187 lines
5.4 KiB
LLVM
187 lines
5.4 KiB
LLVM
; RUN: opt -S -passes=deadargelim %s | FileCheck %s
|
|
|
|
; Case 0: the basic example: an entire aggregate use is returned, but it's
|
|
; actually only used in ways we can eliminate. We gain benefit from analysing
|
|
; the "use" and applying its results to all sub-values.
|
|
|
|
; CHECK-LABEL: define internal void @agguse_dead.retelim()
|
|
|
|
define internal { i32, i32 } @agguse_dead() {
|
|
ret { i32, i32 } { i32 0, i32 1 }
|
|
}
|
|
|
|
define internal { i32, i32 } @test_agguse_dead() {
|
|
%val = call { i32, i32 } @agguse_dead()
|
|
ret { i32, i32 } %val
|
|
}
|
|
|
|
|
|
|
|
; Case 1: an opaque use of the aggregate exists (in this case dead). Otherwise
|
|
; only one value is used, so function can be simplified.
|
|
|
|
; CHECK-LABEL: define internal i32 @rets_independent_if_agguse_dead.retelim()
|
|
; CHECK: [[RET:%.*]] = extractvalue { i32, i32 } { i32 0, i32 1 }, 1
|
|
; CHECK: ret i32 [[RET]]
|
|
|
|
define internal { i32, i32 } @rets_independent_if_agguse_dead() {
|
|
ret { i32, i32 } { i32 0, i32 1 }
|
|
}
|
|
|
|
define internal { i32, i32 } @test_rets_independent_if_agguse_dead(i1 %tst) {
|
|
%val = call { i32, i32 } @rets_independent_if_agguse_dead()
|
|
br i1 %tst, label %use_1, label %use_aggregate
|
|
|
|
use_1:
|
|
; This use can be classified as applying only to ret 1.
|
|
%val0 = extractvalue { i32, i32 } %val, 1
|
|
call void @callee(i32 %val0)
|
|
ret { i32, i32 } poison
|
|
|
|
use_aggregate:
|
|
; This use is assumed to apply to both 0 and 1.
|
|
ret { i32, i32 } %val
|
|
}
|
|
|
|
; Case 2: an opaque use of the aggregate exists (in this case *live*). Other
|
|
; uses shouldn't matter.
|
|
|
|
; CHECK-LABEL: define internal { i32, i32 } @rets_live_agguse()
|
|
; CHECK: ret { i32, i32 } { i32 0, i32 1 }
|
|
|
|
define internal { i32, i32 } @rets_live_agguse() {
|
|
ret { i32, i32} { i32 0, i32 1 }
|
|
}
|
|
|
|
define { i32, i32 } @test_rets_live_aggues(i1 %tst) {
|
|
%val = call { i32, i32 } @rets_live_agguse()
|
|
br i1 %tst, label %use_1, label %use_aggregate
|
|
|
|
use_1:
|
|
; This use can be classified as applying only to ret 1.
|
|
%val0 = extractvalue { i32, i32 } %val, 1
|
|
call void @callee(i32 %val0)
|
|
ret { i32, i32 } poison
|
|
|
|
use_aggregate:
|
|
; This use is assumed to apply to both 0 and 1.
|
|
ret { i32, i32 } %val
|
|
}
|
|
|
|
declare void @callee(i32)
|
|
|
|
; Case 3: the insertvalue meant %in was live if ret-slot-1 was, but we were only
|
|
; tracking multiple ret-slots for struct types. So %in was eliminated
|
|
; incorrectly.
|
|
|
|
; CHECK-LABEL: define internal [2 x i32] @array_rets_have_multiple_slots(i32 %in)
|
|
|
|
define internal [2 x i32] @array_rets_have_multiple_slots(i32 %in) {
|
|
%ret = insertvalue [2 x i32] poison, i32 %in, 1
|
|
ret [2 x i32] %ret
|
|
}
|
|
|
|
define [2 x i32] @test_array_rets_have_multiple_slots() {
|
|
%res = call [2 x i32] @array_rets_have_multiple_slots(i32 42)
|
|
ret [2 x i32] %res
|
|
}
|
|
|
|
; Case 4: we can remove some retvals from the array. It's nice to produce an
|
|
; array again having done so (rather than converting it to a struct).
|
|
|
|
; CHECK-LABEL: define internal [2 x i32] @can_shrink_arrays.retelim()
|
|
; CHECK: [[VAL0:%.*]] = extractvalue [3 x i32] [i32 42, i32 43, i32 44], 0
|
|
; CHECK: [[RESTMP:%.*]] = insertvalue [2 x i32] poison, i32 [[VAL0]], 0
|
|
; CHECK: [[VAL2:%.*]] = extractvalue [3 x i32] [i32 42, i32 43, i32 44], 2
|
|
; CHECK: [[RES:%.*]] = insertvalue [2 x i32] [[RESTMP]], i32 [[VAL2]], 1
|
|
; CHECK: ret [2 x i32] [[RES]]
|
|
|
|
; CHECK-LABEL: define void @test_can_shrink_arrays()
|
|
|
|
define internal [3 x i32] @can_shrink_arrays() {
|
|
ret [3 x i32] [i32 42, i32 43, i32 44]
|
|
}
|
|
|
|
define void @test_can_shrink_arrays() {
|
|
%res = call [3 x i32] @can_shrink_arrays()
|
|
|
|
%res.0 = extractvalue [3 x i32] %res, 0
|
|
call void @callee(i32 %res.0)
|
|
|
|
%res.2 = extractvalue [3 x i32] %res, 2
|
|
call void @callee(i32 %res.2)
|
|
|
|
ret void
|
|
}
|
|
|
|
; Case 5: %in gets passed directly to the return. It should mark be marked as
|
|
; used if *any* of the return values are, not just if value 0 is.
|
|
|
|
; CHECK-LABEL: define internal i32 @ret_applies_to_all.retelim({ i32, i32 } %in)
|
|
; CHECK: [[RET:%.*]] = extractvalue { i32, i32 } %in, 1
|
|
; CHECK: ret i32 [[RET]]
|
|
|
|
define internal {i32, i32} @ret_applies_to_all({i32, i32} %in) {
|
|
ret {i32, i32} %in
|
|
}
|
|
|
|
define i32 @test_ret_applies_to_all() {
|
|
%val = call {i32, i32} @ret_applies_to_all({i32, i32} {i32 42, i32 43})
|
|
%ret = extractvalue {i32, i32} %val, 1
|
|
ret i32 %ret
|
|
}
|
|
|
|
; Case 6: When considering @mid, the return instruciton has sub-value 0
|
|
; unconditionally live, but 1 only conditionally live. Since at that level we're
|
|
; applying the results to the whole of %res, this means %res is live and cannot
|
|
; be reduced. There is scope for further optimisation here (though not visible
|
|
; in this test-case).
|
|
|
|
; CHECK-LABEL: define internal { ptr, i32 } @inner()
|
|
|
|
define internal {ptr, i32} @mid() {
|
|
%res = call {ptr, i32} @inner()
|
|
%intval = extractvalue {ptr, i32} %res, 1
|
|
%tst = icmp eq i32 %intval, 42
|
|
br i1 %tst, label %true, label %true
|
|
|
|
true:
|
|
ret {ptr, i32} %res
|
|
}
|
|
|
|
define internal {ptr, i32} @inner() {
|
|
ret {ptr, i32} {ptr null, i32 42}
|
|
}
|
|
|
|
define internal i8 @outer() {
|
|
%res = call {ptr, i32} @mid()
|
|
%resptr = extractvalue {ptr, i32} %res, 0
|
|
|
|
%val = load i8, ptr %resptr
|
|
ret i8 %val
|
|
}
|
|
|
|
define internal { i32 } @agg_ret() {
|
|
entry:
|
|
unreachable
|
|
}
|
|
|
|
; CHECK-LABEL: define void @PR24906
|
|
; CHECK: %[[invoke:.*]] = invoke i32 @agg_ret.retelim()
|
|
; CHECK: %[[oldret:.*]] = insertvalue { i32 } poison, i32 %[[invoke]], 0
|
|
; CHECK: phi { i32 } [ %[[oldret]],
|
|
define void @PR24906() personality ptr poison {
|
|
entry:
|
|
%tmp2 = invoke { i32 } @agg_ret()
|
|
to label %bb3 unwind label %bb4
|
|
|
|
bb3:
|
|
%tmp3 = phi { i32 } [ %tmp2, %entry ]
|
|
unreachable
|
|
|
|
bb4:
|
|
%tmp4 = landingpad { ptr, i32 }
|
|
cleanup
|
|
unreachable
|
|
}
|