Nikita Popov c23b4fbdbb
[IR] Remove size argument from lifetime intrinsics (#150248)
Now that #149310 has restricted lifetime intrinsics to only work on
allocas, we can also drop the explicit size argument. Instead, the size
is implied by the alloca.

This removes the ability to only mark a prefix of an alloca alive/dead.
We never used that capability, so we should remove the need to handle
that possibility everywhere (though many key places, including stack
coloring, did not actually respect this).
2025-08-08 11:09:34 +02:00

1141 lines
30 KiB
LLVM

; RUN: opt -S -passes="print<stack-safety-local>" -disable-output < %s 2>&1 | FileCheck %s --check-prefixes=CHECK,LOCAL
; RUN: opt -S -passes="print-stack-safety" -disable-output < %s 2>&1 | FileCheck %s --check-prefixes=CHECK,GLOBAL
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
@sink = global ptr null, align 8
declare void @llvm.memset.p0.i32(ptr %dest, i8 %val, i32 %len, i1 %isvolatile)
declare void @llvm.memcpy.p0.p0.i32(ptr %dest, ptr %src, i32 %len, i1 %isvolatile)
declare void @llvm.memmove.p0.p0.i32(ptr %dest, ptr %src, i32 %len, i1 %isvolatile)
declare void @llvm.memset.p0.i64(ptr %dest, i8 %val, i64 %len, i1 %isvolatile)
declare void @unknown_call(ptr %dest)
declare void @unknown_call_int(i64 %i)
declare ptr @retptr(ptr returned)
; Address leaked.
define void @LeakAddress() {
; CHECK-LABEL: @LeakAddress dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[4]: full-set{{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
store ptr %x, ptr @sink, align 8
ret void
}
define void @StoreInBounds() {
; CHECK-LABEL: @StoreInBounds dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[4]: [0,1){{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: store i8 0, ptr %x, align 1
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
store i8 0, ptr %x, align 1
ret void
}
define void @StoreInBoundsCond(i64 %i) {
; CHECK-LABEL: @StoreInBoundsCond dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[4]: full-set{{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: store i8 0, ptr %x2, align 1
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
%c1 = icmp sge i64 %i, 0
%c2 = icmp slt i64 %i, 4
br i1 %c1, label %c1.true, label %false
c1.true:
br i1 %c2, label %c2.true, label %false
c2.true:
%x2 = getelementptr i8, ptr %x, i64 %i
store i8 0, ptr %x2, align 1
br label %false
false:
ret void
}
define void @StoreInBoundsMinMax(i64 %i) {
; CHECK-LABEL: @StoreInBoundsMinMax dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[4]: [0,4){{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: store i8 0, ptr %x2, align 1
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
%c1 = icmp sge i64 %i, 0
%i1 = select i1 %c1, i64 %i, i64 0
%c2 = icmp slt i64 %i1, 3
%i2 = select i1 %c2, i64 %i1, i64 3
%x2 = getelementptr i8, ptr %x, i64 %i2
store i8 0, ptr %x2, align 1
ret void
}
define void @StoreInBounds2() {
; CHECK-LABEL: @StoreInBounds2 dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[4]: [0,4){{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: store i32 0, ptr %x, align 4
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
store i32 0, ptr %x, align 4
ret void
}
define void @StoreInBounds3() {
; CHECK-LABEL: @StoreInBounds3 dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[4]: [2,3){{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: store i8 0, ptr %x2, align 1
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
%x2 = getelementptr i8, ptr %x, i64 2
store i8 0, ptr %x2, align 1
ret void
}
; FIXME: ScalarEvolution does not look through ptrtoint/inttoptr.
define void @StoreInBounds4() {
; CHECK-LABEL: @StoreInBounds4 dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[4]: full-set{{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
%x1 = ptrtoint ptr %x to i64
%x2 = add i64 %x1, 2
%x3 = inttoptr i64 %x2 to ptr
store i8 0, ptr %x3, align 1
ret void
}
define void @StoreInBounds6() {
; CHECK-LABEL: @StoreInBounds6 dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; GLOBAL-NEXT: x[4]: full-set, @retptr(arg0, [0,1)){{$}}
; LOCAL-NEXT: x[4]: [0,1), @retptr(arg0, [0,1)){{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: store i8 0, ptr %x2, align 1
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
%x2 = call ptr @retptr(ptr %x)
store i8 0, ptr %x2, align 1
ret void
}
define dso_local void @WriteMinMax(ptr %p) {
; CHECK-LABEL: @WriteMinMax{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: p[]: full-set
; CHECK-NEXT: allocas uses:
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: store i8 0, ptr %p1, align 1
; GLOBAL-NEXT: store i8 0, ptr %p2, align 1
; CHECK-EMPTY:
entry:
%p1 = getelementptr i8, ptr %p, i64 9223372036854775805
store i8 0, ptr %p1, align 1
%p2 = getelementptr i8, ptr %p, i64 -9223372036854775805
store i8 0, ptr %p2, align 1
ret void
}
define dso_local void @WriteMax(ptr %p) {
; CHECK-LABEL: @WriteMax{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: p[]: [-9223372036854775807,9223372036854775806)
; CHECK-NEXT: allocas uses:
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: call void @llvm.memset.p0.i64(ptr %p, i8 1, i64 9223372036854775806, i1 false)
; GLOBAL-NEXT: call void @llvm.memset.p0.i64(ptr %p2, i8 1, i64 9223372036854775806, i1 false)
; CHECK-EMPTY:
entry:
call void @llvm.memset.p0.i64(ptr %p, i8 1, i64 9223372036854775806, i1 0)
%p2 = getelementptr i8, ptr %p, i64 -9223372036854775807
call void @llvm.memset.p0.i64(ptr %p2, i8 1, i64 9223372036854775806, i1 0)
ret void
}
define void @StoreOutOfBounds() {
; CHECK-LABEL: @StoreOutOfBounds dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[4]: [2,6){{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
%x2 = getelementptr i8, ptr %x, i64 2
store i32 0, ptr %x2, align 1
ret void
}
define void @StoreOutOfBoundsCond(i64 %i) {
; CHECK-LABEL: @StoreOutOfBoundsCond dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[4]: full-set{{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
%c1 = icmp sge i64 %i, 0
%c2 = icmp slt i64 %i, 5
br i1 %c1, label %c1.true, label %false
c1.true:
br i1 %c2, label %c2.true, label %false
c2.true:
%x2 = getelementptr i8, ptr %x, i64 %i
store i8 0, ptr %x2, align 1
br label %false
false:
ret void
}
define void @StoreOutOfBoundsCond2(i64 %i) {
; CHECK-LABEL: @StoreOutOfBoundsCond2 dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[4]: full-set{{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
%c2 = icmp slt i64 %i, 5
br i1 %c2, label %c2.true, label %false
c2.true:
%x2 = getelementptr i8, ptr %x, i64 %i
store i8 0, ptr %x2, align 1
br label %false
false:
ret void
}
define void @StoreOutOfBounds2() {
; CHECK-LABEL: @StoreOutOfBounds2 dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; GLOBAL-NEXT: x[4]: full-set, @retptr(arg0, [2,3)){{$}}
; LOCAL-NEXT: x[4]: [2,6), @retptr(arg0, [2,3)){{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
%x2 = getelementptr i8, ptr %x, i64 2
%x3 = call ptr @retptr(ptr %x2)
store i32 0, ptr %x3, align 1
ret void
}
; There is no difference in load vs store handling.
define void @LoadInBounds() {
; CHECK-LABEL: @LoadInBounds dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[4]: [0,1){{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: %v = load i8, ptr %x, align 1
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
%v = load i8, ptr %x, align 1
ret void
}
define void @LoadOutOfBounds() {
; CHECK-LABEL: @LoadOutOfBounds dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[4]: [2,6){{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
%x2 = getelementptr i8, ptr %x, i64 2
%v = load i32, ptr %x2, align 1
ret void
}
; Leak through ret.
define ptr @Ret() {
; CHECK-LABEL: @Ret dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[4]: full-set{{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
%x2 = getelementptr i8, ptr %x, i64 2
ret ptr %x2
}
declare void @Foo(ptr %p)
define void @DirectCall() {
; CHECK-LABEL: @DirectCall dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; LOCAL-NEXT: x[8]: empty-set, @Foo(arg0, [2,3)){{$}}
; GLOBAL-NEXT: x[8]: full-set, @Foo(arg0, [2,3)){{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i64, align 4
%x2 = getelementptr i16, ptr %x, i64 1
call void @Foo(ptr %x2);
ret void
}
; Indirect calls can not be analyzed (yet).
; FIXME: %p[]: full-set looks invalid
define void @IndirectCall(ptr %p) {
; CHECK-LABEL: @IndirectCall dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: p[]: full-set{{$}}
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[4]: full-set{{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
call void %p(ptr %x);
ret void
}
define void @NonConstantOffset(i1 zeroext %z) {
; CHECK-LABEL: @NonConstantOffset dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; FIXME: SCEV can't look through selects.
; CHECK-NEXT: x[4]: [0,4){{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: store i8 0, ptr %x2, align 1
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
%idx = select i1 %z, i64 1, i64 2
%x2 = getelementptr i8, ptr %x, i64 %idx
store i8 0, ptr %x2, align 1
ret void
}
define void @NegativeOffset() {
; CHECK-LABEL: @NegativeOffset dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[40]: [-1600000000000,-1599999999996){{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i32, i32 10, align 4
%x2 = getelementptr i32, ptr %x, i64 -400000000000
store i32 0, ptr %x2, align 1
ret void
}
define void @PossiblyNegativeOffset(i16 %z) {
; CHECK-LABEL: @PossiblyNegativeOffset dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[40]: [-131072,131072){{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i32, i32 10, align 4
%x2 = getelementptr i32, ptr %x, i16 %z
store i32 0, ptr %x2, align 1
ret void
}
define void @NonConstantOffsetOOB(i1 zeroext %z) {
; CHECK-LABEL: @NonConstantOffsetOOB dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[4]: [0,6){{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
%idx = select i1 %z, i64 1, i64 4
%x2 = getelementptr i8, ptr %x, i64 %idx
store i8 0, ptr %x2, align 1
ret void
}
define void @ArrayAlloca() {
; CHECK-LABEL: @ArrayAlloca dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[40]: [36,40){{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: store i32 0, ptr %x2, align 1
; CHECK-EMPTY:
entry:
%x = alloca i32, i32 10, align 4
%x2 = getelementptr i8, ptr %x, i64 36
store i32 0, ptr %x2, align 1
ret void
}
define void @ArrayAllocaOOB() {
; CHECK-LABEL: @ArrayAllocaOOB dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[40]: [37,41){{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i32, i32 10, align 4
%x2 = getelementptr i8, ptr %x, i64 37
store i32 0, ptr %x2, align 1
ret void
}
define void @DynamicAllocaUnused(i64 %size) {
; CHECK-LABEL: @DynamicAllocaUnused dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[0]: empty-set{{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i32, i64 %size, align 16
ret void
}
; Dynamic alloca with unknown size.
define void @DynamicAlloca(i64 %size) {
; CHECK-LABEL: @DynamicAlloca dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[0]: [0,4){{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i32, i64 %size, align 16
store i32 0, ptr %x, align 1
ret void
}
; Dynamic alloca with limited size.
; FIXME: could be proved safe. Implement.
define void @DynamicAllocaFiniteSizeRange(i1 zeroext %z) {
; CHECK-LABEL: @DynamicAllocaFiniteSizeRange dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[0]: [0,4){{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%size = select i1 %z, i64 3, i64 5
%x = alloca i32, i64 %size, align 16
store i32 0, ptr %x, align 1
ret void
}
define signext i8 @SimpleLoop() {
; CHECK-LABEL: @SimpleLoop dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[10]: [0,10){{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: %load = load volatile i8, ptr %p.09, align 1
; CHECK-EMPTY:
entry:
%x = alloca [10 x i8], align 1
%lftr.limit = getelementptr inbounds [10 x i8], ptr %x, i64 0, i64 10
br label %for.body
for.body:
%sum.010 = phi i8 [ 0, %entry ], [ %add, %for.body ]
%p.09 = phi ptr [ %x, %entry ], [ %incdec.ptr, %for.body ]
%incdec.ptr = getelementptr inbounds i8, ptr %p.09, i64 1
%load = load volatile i8, ptr %p.09, align 1
%add = add i8 %load, %sum.010
%exitcond = icmp eq ptr %incdec.ptr, %lftr.limit
br i1 %exitcond, label %for.cond.cleanup, label %for.body
for.cond.cleanup:
ret i8 %add
}
; OOB in a loop.
define signext i8 @SimpleLoopOOB() {
; CHECK-LABEL: @SimpleLoopOOB dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[10]: [0,11){{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca [10 x i8], align 1
; 11 iterations
%lftr.limit = getelementptr inbounds [10 x i8], ptr %x, i64 0, i64 11
br label %for.body
for.body:
%sum.010 = phi i8 [ 0, %entry ], [ %add, %for.body ]
%p.09 = phi ptr [ %x, %entry ], [ %incdec.ptr, %for.body ]
%incdec.ptr = getelementptr inbounds i8, ptr %p.09, i64 1
%load = load volatile i8, ptr %p.09, align 1
%add = add i8 %load, %sum.010
%exitcond = icmp eq ptr %incdec.ptr, %lftr.limit
br i1 %exitcond, label %for.cond.cleanup, label %for.body
for.cond.cleanup:
ret i8 %add
}
define dso_local void @SizeCheck(i32 %sz) {
; CHECK-LABEL: @SizeCheck{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x1[128]: [0,4294967295){{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x1 = alloca [128 x i8], align 16
%cmp = icmp slt i32 %sz, 129
br i1 %cmp, label %if.then, label %if.end
if.then:
call void @llvm.memset.p0.i32(ptr nonnull align 16 %x1, i8 0, i32 %sz, i1 false)
br label %if.end
if.end:
ret void
}
; FIXME: scalable allocas are considered to be of size zero, and scalable accesses to be full-range.
; This effectively disables safety analysis for scalable allocations.
define void @Scalable(ptr %p, ptr %unused, <vscale x 4 x i32> %v) {
; CHECK-LABEL: @Scalable dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: p[]: full-set
; CHECK-NEXT: unused[]: empty-set
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[0]: [0,1){{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: store <vscale x 4 x i32> %v, ptr %p, align 4
; CHECK-EMPTY:
entry:
%x = alloca <vscale x 4 x i32>, align 4
store i8 0, ptr %x, align 1
store <vscale x 4 x i32> %v, ptr %p, align 4
ret void
}
%zerosize_type = type {}
define void @ZeroSize(ptr %p) {
; CHECK-LABEL: @ZeroSize dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: p[]: empty-set
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[0]: empty-set
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: store %zerosize_type undef, ptr %x, align 4
; GLOBAL-NEXT: store %zerosize_type undef, ptr undef, align 4
; GLOBAL-NEXT: load %zerosize_type, ptr %p, align
; CHECK-EMPTY:
entry:
%x = alloca %zerosize_type, align 4
store %zerosize_type undef, ptr %x, align 4
store %zerosize_type undef, ptr undef, align 4
%val = load %zerosize_type, ptr %p, align 4
ret void
}
define void @OperandBundle() {
; CHECK-LABEL: @OperandBundle dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: a[4]: full-set
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%a = alloca i32, align 4
call void @LeakAddress() ["unknown"(ptr %a)]
ret void
}
define void @ByVal(ptr byval(i16) %p) {
; CHECK-LABEL: @ByVal dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
ret void
}
define void @TestByVal() {
; CHECK-LABEL: @TestByVal dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[2]: [0,2)
; CHECK-NEXT: y[8]: [0,2)
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: call void @ByVal(ptr byval(i16) %x)
; GLOBAL-NEXT: call void @ByVal(ptr byval(i16) %y)
; CHECK-EMPTY:
entry:
%x = alloca i16, align 4
call void @ByVal(ptr byval(i16) %x)
%y = alloca i64, align 4
call void @ByVal(ptr byval(i16) %y)
ret void
}
declare void @ByValArray(ptr byval([100000 x i64]) %p)
define void @TestByValArray() {
; CHECK-LABEL: @TestByValArray dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: z[800000]: [500000,1300000)
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%z = alloca [100000 x i64], align 4
%z2 = getelementptr i8, ptr %z, i64 500000
call void @ByValArray(ptr byval([100000 x i64]) %z2)
ret void
}
define dso_local i8 @LoadMinInt64(ptr %p) {
; CHECK-LABEL: @LoadMinInt64{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: p[]: [-9223372036854775808,-9223372036854775807){{$}}
; CHECK-NEXT: allocas uses:
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: load i8, ptr %p2, align 1
; CHECK-EMPTY:
%p2 = getelementptr i8, ptr %p, i64 -9223372036854775808
%v = load i8, ptr %p2, align 1
ret i8 %v
}
define void @Overflow() {
; CHECK-LABEL: @Overflow dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; LOCAL-NEXT: x[1]: empty-set, @LoadMinInt64(arg0, [-9223372036854775808,-9223372036854775807)){{$}}
; GLOBAL-NEXT: x[1]: full-set, @LoadMinInt64(arg0, [-9223372036854775808,-9223372036854775807)){{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i8, align 4
%x2 = getelementptr i8, ptr %x, i64 -9223372036854775808
%v = call i8 @LoadMinInt64(ptr %x2)
ret void
}
define void @DeadBlock(ptr %p) {
; CHECK-LABEL: @DeadBlock dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: p[]: empty-set{{$}}
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[1]: empty-set{{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: store i8 5, ptr %x
; GLOBAL-NEXT: store i64 -5, ptr %p
; CHECK-EMPTY:
entry:
%x = alloca i8, align 4
br label %end
dead:
store i8 5, ptr %x
store i64 -5, ptr %p
br label %end
end:
ret void
}
define void @LifeNotStarted() {
; CHECK-LABEL: @LifeNotStarted dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK: x[1]: full-set{{$}}
; CHECK: y[1]: full-set{{$}}
; CHECK: z[1]: full-set{{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i8, align 4
%y = alloca i8, align 4
%z = alloca i8, align 4
store i8 5, ptr %x
%n = load i8, ptr %y
call void @llvm.memset.p0.i32(ptr nonnull %z, i8 0, i32 1, i1 false)
call void @llvm.lifetime.start.p0(ptr %x)
call void @llvm.lifetime.start.p0(ptr %y)
call void @llvm.lifetime.start.p0(ptr %z)
ret void
}
define void @LifeOK() {
; CHECK-LABEL: @LifeOK dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK: x[1]: [0,1){{$}}
; CHECK: y[1]: [0,1){{$}}
; CHECK: z[1]: [0,1){{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: store i8 5, ptr %x
; GLOBAL-NEXT: %n = load i8, ptr %y
; GLOBAL-NEXT: call void @llvm.memset.p0.i32(ptr nonnull %z, i8 0, i32 1, i1 false)
; CHECK-EMPTY:
entry:
%x = alloca i8, align 4
%y = alloca i8, align 4
%z = alloca i8, align 4
call void @llvm.lifetime.start.p0(ptr %x)
call void @llvm.lifetime.start.p0(ptr %y)
call void @llvm.lifetime.start.p0(ptr %z)
store i8 5, ptr %x
%n = load i8, ptr %y
call void @llvm.memset.p0.i32(ptr nonnull %z, i8 0, i32 1, i1 false)
ret void
}
define void @LifeEnded() {
; CHECK-LABEL: @LifeEnded dso_preemptable{{$}}
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK: x[1]: full-set{{$}}
; CHECK: y[1]: full-set{{$}}
; CHECK: z[1]: full-set{{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%x = alloca i8, align 4
%y = alloca i8, align 4
%z = alloca i8, align 4
call void @llvm.lifetime.start.p0(ptr %x)
call void @llvm.lifetime.start.p0(ptr %y)
call void @llvm.lifetime.start.p0(ptr %z)
call void @llvm.lifetime.end.p0(ptr %x)
call void @llvm.lifetime.end.p0(ptr %y)
call void @llvm.lifetime.end.p0(ptr %z)
store i8 5, ptr %x
%n = load i8, ptr %y
call void @llvm.memset.p0.i32(ptr nonnull %z, i8 0, i32 1, i1 false)
ret void
}
define void @TwoAllocasOK() {
; CHECK-LABEL: @TwoAllocasOK
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK: a[4]: [0,1){{$}}
; CHECK: y[1]: [0,1){{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr %y, ptr %a, i32 1, i1 false)
; CHECK-EMPTY:
entry:
%a = alloca i32, align 4
%y = alloca i8, align 4
call void @llvm.memcpy.p0.p0.i32(ptr %y, ptr %a, i32 1, i1 false)
ret void
}
define void @TwoAllocasOOBDest() {
; CHECK-LABEL: @TwoAllocasOOBDest
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK: a[4]: [0,4){{$}}
; CHECK: y[1]: [0,4){{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%a = alloca i32, align 4
%y = alloca i8, align 4
call void @llvm.memcpy.p0.p0.i32(ptr %y, ptr %a, i32 4, i1 false)
ret void
}
define void @TwoAllocasOOBSource() {
; CHECK-LABEL: @TwoAllocasOOBSource
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK: a[4]: [0,4){{$}}
; CHECK: y[1]: [0,4){{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%a = alloca i32, align 4
%y = alloca i8, align 4
call void @llvm.memcpy.p0.p0.i32(ptr %a, ptr %y, i32 4, i1 false)
ret void
}
define void @TwoAllocasOOBBoth() {
; CHECK-LABEL: @TwoAllocasOOBBoth
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK: a[4]: [0,5){{$}}
; CHECK: y[1]: [0,5){{$}}
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%a = alloca i32, align 4
%y = alloca i8, align 4
call void @llvm.memcpy.p0.p0.i32(ptr %y, ptr %a, i32 5, i1 false)
ret void
}
define void @MixedAccesses() {
; CHECK-LABEL: @MixedAccesses
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK: a[4]: [0,5){{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: call void @llvm.memset.p0.i32(ptr %a, i8 1, i32 4, i1 false)
; CHECK-EMPTY:
entry:
%a = alloca i32, align 4
call void @llvm.memset.p0.i32(ptr %a, i8 1, i32 5, i1 false)
call void @llvm.memset.p0.i32(ptr %a, i8 1, i32 4, i1 false)
ret void
}
define void @MixedAccesses2() {
; CHECK-LABEL: @MixedAccesses2
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK: a[4]: [0,8){{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: load i32, ptr %a, align 4
; CHECK-EMPTY:
entry:
%a = alloca i32, align 4
%n1 = load i64, ptr %a, align 4
%n2 = load i32, ptr %a, align 4
ret void
}
define void @MixedAccesses3(ptr %func) {
; CHECK-LABEL: @MixedAccesses3
; CHECK-NEXT: args uses:
; CHECK-NEXT: func[]: full-set
; CHECK-NEXT: allocas uses:
; CHECK: a[4]: full-set{{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: load i32, ptr %a, align 4
; CHECK-EMPTY:
entry:
%a = alloca i32, align 4
%n2 = load i32, ptr %a, align 4
call void %func(ptr %a)
ret void
}
define void @MixedAccesses4() {
; CHECK-LABEL: @MixedAccesses4
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK: a[4]: full-set{{$}}
; CHECK: a1[8]: [0,8){{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: load i32, ptr %a, align 4
; CHECK-EMPTY:
entry:
%a = alloca i32, align 4
%a1 = alloca ptr, align 4
%n2 = load i32, ptr %a, align 4
store ptr %a, ptr %a1
ret void
}
define ptr @MixedAccesses5(i1 %x, ptr %y) {
; CHECK-LABEL: @MixedAccesses5
; CHECK-NEXT: args uses:
; CHECK: y[]: full-set
; CHECK-NEXT: allocas uses:
; CHECK: a[4]: full-set{{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: load i32, ptr %a, align 4
; CHECK-EMPTY:
entry:
%a = alloca i32, align 4
br i1 %x, label %tlabel, label %flabel
flabel:
%n = load i32, ptr %a, align 4
ret ptr %y
tlabel:
ret ptr %a
}
define void @MixedAccesses6(ptr %arg) {
; CHECK-LABEL: @MixedAccesses6
; CHECK-NEXT: args uses:
; CHECK-NEXT: arg[]: [0,4)
; CHECK-NEXT: allocas uses:
; CHECK: a[4]: [0,4)
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr %a, ptr %arg, i32 4, i1 false)
; CHECK-EMPTY:
entry:
%a = alloca i32, align 4
call void @llvm.memcpy.p0.p0.i32(ptr %a, ptr %arg, i32 4, i1 false)
ret void
}
define void @MixedAccesses7(i1 %cond, ptr %arg) {
; SECV doesn't support select, so we consider this non-stack-safe, even through
; it is.
;
; CHECK-LABEL: @MixedAccesses7
; CHECK-NEXT: args uses:
; CHECK-NEXT: arg[]: full-set
; CHECK-NEXT: allocas uses:
; CHECK: a[4]: full-set
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
entry:
%a = alloca i32, align 4
%x1 = select i1 %cond, ptr %arg, ptr %a
call void @llvm.memcpy.p0.p0.i32(ptr %x1, ptr %arg, i32 4, i1 false)
ret void
}
define void @NoStackAccess(ptr %arg1, ptr %arg2) {
; CHECK-LABEL: @NoStackAccess
; CHECK-NEXT: args uses:
; CHECK-NEXT: arg1[]: [0,4)
; CHECK-NEXT: arg2[]: [0,4)
; CHECK-NEXT: allocas uses:
; CHECK: a[4]: empty-set{{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr %arg1, ptr %arg2, i32 4, i1 false)
; CHECK-EMPTY:
entry:
%a = alloca i32, align 4
call void @llvm.memcpy.p0.p0.i32(ptr %arg1, ptr %arg2, i32 4, i1 false)
ret void
}
define void @DoubleLifetime() {
; CHECK-LABEL: @DoubleLifetime
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK: a[4]: full-set{{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: call void @llvm.memset.p0.i32(ptr %a, i8 1, i32 4, i1 false)
; CHECK-EMPTY:
entry:
%a = alloca i32, align 4
call void @llvm.lifetime.start.p0(ptr %a)
call void @llvm.lifetime.end.p0(ptr %a)
call void @llvm.memset.p0.i32(ptr %a, i8 1, i32 4, i1 true)
call void @llvm.lifetime.start.p0(ptr %a)
call void @llvm.memset.p0.i32(ptr %a, i8 1, i32 4, i1 false)
call void @llvm.lifetime.end.p0(ptr %a)
ret void
}
define void @DoubleLifetime2() {
; CHECK-LABEL: @DoubleLifetime2
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK: a[4]: full-set{{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: call void @llvm.memset.p0.i32(ptr %a, i8 1, i32 4, i1 false)
; CHECK-EMPTY:
entry:
%a = alloca i32, align 4
call void @llvm.lifetime.start.p0(ptr %a)
call void @llvm.lifetime.end.p0(ptr %a)
%n = load i32, ptr %a
call void @llvm.lifetime.start.p0(ptr %a)
call void @llvm.memset.p0.i32(ptr %a, i8 1, i32 4, i1 false)
call void @llvm.lifetime.end.p0(ptr %a)
ret void
}
define void @DoubleLifetime3() {
; CHECK-LABEL: @DoubleLifetime3
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK: a[4]: full-set{{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: call void @llvm.memset.p0.i32(ptr %a, i8 1, i32 4, i1 false)
; CHECK-EMPTY:
entry:
%a = alloca i32, align 4
call void @llvm.lifetime.start.p0(ptr %a)
call void @llvm.lifetime.end.p0(ptr %a)
store i32 5, ptr %a
call void @llvm.lifetime.start.p0(ptr %a)
call void @llvm.memset.p0.i32(ptr %a, i8 1, i32 4, i1 false)
call void @llvm.lifetime.end.p0(ptr %a)
ret void
}
define void @DoubleLifetime4() {
; CHECK-LABEL: @DoubleLifetime4
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK: a[4]: full-set{{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: call void @llvm.memset.p0.i32(ptr %a, i8 1, i32 4, i1 false)
; CHECK-EMPTY:
entry:
%a = alloca i32, align 4
call void @llvm.lifetime.start.p0(ptr %a)
call void @llvm.memset.p0.i32(ptr %a, i8 1, i32 4, i1 false)
call void @llvm.lifetime.end.p0(ptr %a)
call void @unknown_call(ptr %a)
ret void
}
define void @Cmpxchg4Arg(ptr %p) {
; CHECK-LABEL: @Cmpxchg4Arg
; CHECK-NEXT: args uses:
; CHECK-NEXT: p[]: [0,4){{$}}
; CHECK-NEXT: allocas uses:
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: cmpxchg ptr %p, i32 0, i32 1 monotonic monotonic, align 1
; CHECK-EMPTY:
entry:
cmpxchg ptr %p, i32 0, i32 1 monotonic monotonic, align 1
ret void
}
define void @AtomicRMW4Arg(ptr %p) {
; CHECK-LABEL: @AtomicRMW4Arg
; CHECK-NEXT: args uses:
; CHECK-NEXT: p[]: [0,4){{$}}
; CHECK-NEXT: allocas uses:
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: atomicrmw add ptr %p, i32 1 monotonic, align 1
; CHECK-EMPTY:
entry:
atomicrmw add ptr %p, i32 1 monotonic, align 1
ret void
}
define void @Cmpxchg4Alloca() {
; CHECK-LABEL: @Cmpxchg4Alloca
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[4]: [0,4){{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: cmpxchg ptr %x, i32 0, i32 1 monotonic monotonic, align 1
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
cmpxchg ptr %x, i32 0, i32 1 monotonic monotonic, align 1
ret void
}
define void @AtomicRMW4Alloca() {
; CHECK-LABEL: @AtomicRMW4Alloca
; CHECK-NEXT: args uses:
; CHECK-NEXT: allocas uses:
; CHECK-NEXT: x[4]: [0,4){{$}}
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: atomicrmw add ptr %x, i32 1 monotonic, align 1
; CHECK-EMPTY:
entry:
%x = alloca i32, align 4
atomicrmw add ptr %x, i32 1 monotonic, align 1
ret void
}
define void @StoreArg(ptr %p) {
; CHECK-LABEL: @StoreArg
; CHECK-NEXT: args uses:
; CHECK-NEXT: p[]: [0,4){{$}}
; CHECK-NEXT: allocas uses:
; GLOBAL-NEXT: safe accesses:
; GLOBAL-NEXT: store i32 1, ptr %p
; CHECK-EMPTY:
entry:
store i32 1, ptr %p
ret void
}
define void @NonPointer(ptr %p) {
; CHECK-LABEL: @NonPointer
; CHECK-NEXT: args uses:
; LOCAL-NEXT: p[]: empty-set, @unknown_call_int(arg0, full-set)
; GLOBAL-NEXT: p[]: full-set, @unknown_call_int(arg0, full-set)
; CHECK-NEXT: allocas uses:
; GLOBAL-NEXT: safe accesses:
; CHECK-EMPTY:
%int = ptrtoint ptr %p to i64
call void @unknown_call_int(i64 %int)
ret void
}
@ifunc = dso_local ifunc i64 (ptr), ptr @ifunc_resolver
define dso_local void @CallIfunc(ptr noundef %uaddr) local_unnamed_addr {
; CHECK-LABEL: @CallIfunc
; CHECK-NEXT: args uses:
; CHECK-NEXT: uaddr[]: full-set
entry:
tail call i64 @ifunc(ptr noundef %uaddr)
ret void
}
define dso_local ptr @ifunc_resolver() {
entry:
ret ptr null
}
declare void @llvm.lifetime.start.p0(ptr nocapture)
declare void @llvm.lifetime.end.p0(ptr nocapture)