
lifetime.start and lifetime.end are primarily intended for use on allocas, to enable stack coloring and other liveness optimizations. This is necessary because all (static) allocas are hoisted into the entry block, so lifetime markers are the only way to convey the actual lifetimes. However, lifetime.start and lifetime.end are currently *allowed* to be used on non-alloca pointers. We don't actually do this in practice, but just the mere fact that this is possible breaks the core purpose of the lifetime markers, which is stack coloring of allocas. Stack coloring can only work correctly if all lifetime markers for an alloca are analyzable. * If a lifetime marker may operate on multiple allocas via a select/phi, we don't know which lifetime actually starts/ends and handle it incorrectly (https://github.com/llvm/llvm-project/issues/104776). * Stack coloring operates on the assumption that all lifetime markers are visible, and not, for example, hidden behind a function call or escaped pointer. It's not possible to change this, as part of the purpose of lifetime markers is that they work even in the presence of escaped pointers, where simple use analysis is insufficient. I don't think there is any way to have coherent semantics for lifetime markers on allocas, while also permitting them on arbitrary pointer values. This PR restricts lifetimes to operate on allocas only. As a followup, I will also drop the size argument, which is superfluous if we always operate on an alloca. (This change also renders various code handling lifetime markers on non-alloca dead. I plan to clean up that kind of code after dropping the size argument as well.) In practice, I've only found a few places that currently produce lifetimes on non-allocas: * CoroEarly replaces the promise alloca with the result of an intrinsic, which will later be replaced back with an alloca. I think this is the only place where there is some legitimate loss of functionality, but I don't think this is particularly important (I don't think we'd expect the promise in a coroutine to admit useful lifetime optimization.) * SafeStack moves unsafe allocas onto a separate frame. We can safely drop lifetimes here, as SafeStack performs its own stack coloring. * Similar for AddressSanitizer, it also moves allocas into separate memory. * LSR sometimes replaces the lifetime argument with a GEP chain of the alloca (where the offsets ultimately cancel out). This is just unnecessary. (Fixed separately in https://github.com/llvm/llvm-project/pull/149492.) * InferAddrSpaces sometimes makes lifetimes operate on an addrspacecast of an alloca. I don't think this is necessary.
477 lines
18 KiB
LLVM
477 lines
18 KiB
LLVM
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
|
|
; RUN: opt < %s -S -aa-pipeline=basic-aa -passes=inferattrs,dse | FileCheck %s
|
|
|
|
target triple = "x86_64-unknown-linux-gnu"
|
|
|
|
declare ptr @strcpy(ptr %dest, ptr %src) nounwind
|
|
define void @test1(ptr %src) {
|
|
; CHECK-LABEL: @test1(
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
%B = alloca [16 x i8]
|
|
%call = call ptr @strcpy(ptr %B, ptr %src)
|
|
ret void
|
|
}
|
|
|
|
define void @strcpy_reads_after(ptr noalias %dest, ptr %src) {
|
|
; CHECK-LABEL: @strcpy_reads_after(
|
|
; CHECK-NEXT: [[SRC_2:%.*]] = getelementptr inbounds i8, ptr [[SRC:%.*]], i64 1
|
|
; CHECK-NEXT: store i8 99, ptr [[SRC_2]], align 1
|
|
; CHECK-NEXT: [[SRC_1:%.*]] = getelementptr inbounds i8, ptr [[SRC]], i64 1
|
|
; CHECK-NEXT: [[CALL:%.*]] = call ptr @strcpy(ptr [[DEST:%.*]], ptr [[SRC_1]])
|
|
; CHECK-NEXT: store i8 2, ptr [[SRC]], align 1
|
|
; CHECK-NEXT: store i8 2, ptr [[SRC_2]], align 1
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
%src.2 = getelementptr inbounds i8, ptr %src, i64 1
|
|
store i8 1, ptr %src
|
|
store i8 99, ptr %src.2
|
|
%src.1 = getelementptr inbounds i8, ptr %src, i64 1
|
|
%call = call ptr @strcpy(ptr %dest, ptr %src.1)
|
|
store i8 2, ptr %src
|
|
store i8 2, ptr %src.2
|
|
ret void
|
|
}
|
|
|
|
declare ptr @strncpy(ptr %dest, ptr %src, i64 %n) nounwind
|
|
define void @test2(ptr %src) {
|
|
; CHECK-LABEL: @test2(
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
%B = alloca [16 x i8]
|
|
%call = call ptr @strncpy(ptr %B, ptr %src, i64 12)
|
|
ret void
|
|
}
|
|
|
|
declare ptr @strcat(ptr %B, ptr %src) nounwind
|
|
define void @test3(ptr %src) {
|
|
; CHECK-LABEL: @test3(
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
%B = alloca [16 x i8]
|
|
%call = call ptr @strcat(ptr %B, ptr %src)
|
|
ret void
|
|
}
|
|
|
|
define void @test_strcat_with_lifetime(ptr %src) {
|
|
; CHECK-LABEL: @test_strcat_with_lifetime(
|
|
; CHECK-NEXT: [[B:%.*]] = alloca [16 x i8], align 1
|
|
; CHECK-NEXT: call void @llvm.lifetime.start.p0(i64 16, ptr nonnull [[B]])
|
|
; CHECK-NEXT: call void @llvm.lifetime.end.p0(i64 16, ptr nonnull [[B]])
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
%B = alloca [16 x i8]
|
|
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %B)
|
|
%call = call ptr @strcat(ptr %B, ptr %src)
|
|
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %B)
|
|
ret void
|
|
}
|
|
|
|
declare ptr @strncat(ptr %dest, ptr %src, i64 %n) nounwind
|
|
define void @test4(ptr %src) {
|
|
; CHECK-LABEL: @test4(
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
%B = alloca [16 x i8]
|
|
%call = call ptr @strncat(ptr %B, ptr %src, i64 12)
|
|
ret void
|
|
}
|
|
|
|
define void @test5(ptr nocapture %src) {
|
|
; CHECK-LABEL: @test5(
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
%dest = alloca [100 x i8], align 16
|
|
%call = call ptr @strcpy(ptr %dest, ptr %src)
|
|
%arrayidx = getelementptr inbounds i8, ptr %call, i64 10
|
|
store i8 97, ptr %arrayidx, align 1
|
|
ret void
|
|
}
|
|
|
|
declare void @user(ptr %p)
|
|
define void @test6(ptr %src) {
|
|
; CHECK-LABEL: @test6(
|
|
; CHECK-NEXT: [[B:%.*]] = alloca [16 x i8], align 1
|
|
; CHECK-NEXT: [[CALL:%.*]] = call ptr @strcpy(ptr [[B]], ptr [[SRC:%.*]])
|
|
; CHECK-NEXT: call void @user(ptr [[B]])
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
%B = alloca [16 x i8]
|
|
%call = call ptr @strcpy(ptr %B, ptr %src)
|
|
call void @user(ptr %B)
|
|
ret void
|
|
}
|
|
|
|
declare i32 @memcmp(ptr, ptr, i64)
|
|
|
|
define i32 @test_memcmp_const_size(ptr noalias %foo) {
|
|
; CHECK-LABEL: @test_memcmp_const_size(
|
|
; CHECK-NEXT: entry:
|
|
; CHECK-NEXT: [[STACK:%.*]] = alloca [10 x i8], align 1
|
|
; CHECK-NEXT: store i8 49, ptr [[STACK]], align 1
|
|
; CHECK-NEXT: [[GEP_1:%.*]] = getelementptr i8, ptr [[STACK]], i64 1
|
|
; CHECK-NEXT: store i8 50, ptr [[GEP_1]], align 1
|
|
; CHECK-NEXT: [[RES:%.*]] = call i32 @memcmp(ptr nonnull dereferenceable(2) [[FOO:%.*]], ptr nonnull dereferenceable(2) [[STACK]], i64 2)
|
|
; CHECK-NEXT: ret i32 [[RES]]
|
|
;
|
|
entry:
|
|
%stack = alloca [10 x i8]
|
|
store i8 49, ptr %stack, align 1
|
|
%gep.1 = getelementptr i8, ptr %stack, i64 1
|
|
store i8 50, ptr %gep.1, align 1
|
|
%gep.2 = getelementptr i8, ptr %stack, i64 2
|
|
store i8 51, ptr %gep.2, align 1
|
|
%gep.3 = getelementptr i8, ptr %stack, i64 3
|
|
store i8 52, ptr %gep.3, align 1
|
|
%res = call i32 @memcmp(ptr nonnull dereferenceable(2) %foo, ptr nonnull dereferenceable(2) %stack, i64 2)
|
|
ret i32 %res
|
|
}
|
|
|
|
define i32 @test_memcmp_variable_size(ptr noalias %foo, i64 %n) {
|
|
; CHECK-LABEL: @test_memcmp_variable_size(
|
|
; CHECK-NEXT: entry:
|
|
; CHECK-NEXT: [[STACK:%.*]] = alloca [10 x i8], align 1
|
|
; CHECK-NEXT: store i8 49, ptr [[STACK]], align 1
|
|
; CHECK-NEXT: [[GEP_1:%.*]] = getelementptr i8, ptr [[STACK]], i64 1
|
|
; CHECK-NEXT: store i8 50, ptr [[GEP_1]], align 1
|
|
; CHECK-NEXT: [[GEP_2:%.*]] = getelementptr i8, ptr [[STACK]], i64 2
|
|
; CHECK-NEXT: store i8 51, ptr [[GEP_2]], align 1
|
|
; CHECK-NEXT: [[GEP_3:%.*]] = getelementptr i8, ptr [[STACK]], i64 3
|
|
; CHECK-NEXT: store i8 52, ptr [[GEP_3]], align 1
|
|
; CHECK-NEXT: [[RES:%.*]] = call i32 @memcmp(ptr nonnull [[FOO:%.*]], ptr nonnull [[STACK]], i64 [[N:%.*]])
|
|
; CHECK-NEXT: ret i32 [[RES]]
|
|
;
|
|
entry:
|
|
%stack = alloca [10 x i8]
|
|
store i8 49, ptr %stack, align 1
|
|
%gep.1 = getelementptr i8, ptr %stack, i64 1
|
|
store i8 50, ptr %gep.1, align 1
|
|
%gep.2 = getelementptr i8, ptr %stack, i64 2
|
|
store i8 51, ptr %gep.2, align 1
|
|
%gep.3 = getelementptr i8, ptr %stack, i64 3
|
|
store i8 52, ptr %gep.3, align 1
|
|
%res = call i32 @memcmp(ptr nonnull %foo, ptr nonnull %stack, i64 %n)
|
|
ret i32 %res
|
|
}
|
|
|
|
declare i32 @bcmp(ptr, ptr, i64)
|
|
|
|
define i1 @test_bcmp_const_size(ptr noalias %foo) {
|
|
; CHECK-LABEL: @test_bcmp_const_size(
|
|
; CHECK-NEXT: entry:
|
|
; CHECK-NEXT: [[STACK:%.*]] = alloca [10 x i8], align 1
|
|
; CHECK-NEXT: store i8 49, ptr [[STACK]], align 1
|
|
; CHECK-NEXT: [[GEP_1:%.*]] = getelementptr i8, ptr [[STACK]], i64 1
|
|
; CHECK-NEXT: store i8 50, ptr [[GEP_1]], align 1
|
|
; CHECK-NEXT: [[CALL:%.*]] = call i32 @bcmp(ptr nonnull dereferenceable(2) [[FOO:%.*]], ptr nonnull dereferenceable(2) [[STACK]], i64 2)
|
|
; CHECK-NEXT: [[RES:%.*]] = icmp eq i32 [[CALL]], 0
|
|
; CHECK-NEXT: ret i1 [[RES]]
|
|
;
|
|
entry:
|
|
%stack = alloca [10 x i8]
|
|
store i8 49, ptr %stack, align 1
|
|
%gep.1 = getelementptr i8, ptr %stack, i64 1
|
|
store i8 50, ptr %gep.1, align 1
|
|
%gep.2 = getelementptr i8, ptr %stack, i64 2
|
|
store i8 51, ptr %gep.2, align 1
|
|
%gep.3 = getelementptr i8, ptr %stack, i64 3
|
|
store i8 52, ptr %gep.3, align 1
|
|
%call = call i32 @bcmp(ptr nonnull dereferenceable(2) %foo, ptr nonnull dereferenceable(2) %stack, i64 2)
|
|
%res = icmp eq i32 %call, 0
|
|
ret i1 %res
|
|
}
|
|
|
|
define i1 @test_bcmp_variable_size(ptr noalias %foo, i64 %n) {
|
|
; CHECK-LABEL: @test_bcmp_variable_size(
|
|
; CHECK-NEXT: entry:
|
|
; CHECK-NEXT: [[STACK:%.*]] = alloca [10 x i8], align 1
|
|
; CHECK-NEXT: store i8 49, ptr [[STACK]], align 1
|
|
; CHECK-NEXT: [[GEP_1:%.*]] = getelementptr i8, ptr [[STACK]], i64 1
|
|
; CHECK-NEXT: store i8 50, ptr [[GEP_1]], align 1
|
|
; CHECK-NEXT: [[GEP_2:%.*]] = getelementptr i8, ptr [[STACK]], i64 2
|
|
; CHECK-NEXT: store i8 51, ptr [[GEP_2]], align 1
|
|
; CHECK-NEXT: [[GEP_3:%.*]] = getelementptr i8, ptr [[STACK]], i64 3
|
|
; CHECK-NEXT: store i8 52, ptr [[GEP_3]], align 1
|
|
; CHECK-NEXT: [[CALL:%.*]] = call i32 @bcmp(ptr nonnull [[FOO:%.*]], ptr nonnull [[STACK]], i64 [[N:%.*]])
|
|
; CHECK-NEXT: [[RES:%.*]] = icmp eq i32 [[CALL]], 0
|
|
; CHECK-NEXT: ret i1 [[RES]]
|
|
;
|
|
entry:
|
|
%stack = alloca [10 x i8]
|
|
store i8 49, ptr %stack, align 1
|
|
%gep.1 = getelementptr i8, ptr %stack, i64 1
|
|
store i8 50, ptr %gep.1, align 1
|
|
%gep.2 = getelementptr i8, ptr %stack, i64 2
|
|
store i8 51, ptr %gep.2, align 1
|
|
%gep.3 = getelementptr i8, ptr %stack, i64 3
|
|
store i8 52, ptr %gep.3, align 1
|
|
%call = call i32 @bcmp(ptr nonnull %foo, ptr nonnull %stack, i64 %n)
|
|
%res = icmp eq i32 %call, 0
|
|
ret i1 %res
|
|
}
|
|
|
|
declare ptr @memchr(ptr, i32, i64)
|
|
|
|
define ptr @test_memchr_const_size() {
|
|
; CHECK-LABEL: @test_memchr_const_size(
|
|
; CHECK-NEXT: entry:
|
|
; CHECK-NEXT: [[STACK:%.*]] = alloca [10 x i8], align 1
|
|
; CHECK-NEXT: store i8 49, ptr [[STACK]], align 1
|
|
; CHECK-NEXT: [[GEP_1:%.*]] = getelementptr i8, ptr [[STACK]], i64 1
|
|
; CHECK-NEXT: store i8 50, ptr [[GEP_1]], align 1
|
|
; CHECK-NEXT: [[CALL:%.*]] = call ptr @memchr(ptr [[STACK]], i32 42, i64 2)
|
|
; CHECK-NEXT: ret ptr [[CALL]]
|
|
;
|
|
entry:
|
|
%stack = alloca [10 x i8]
|
|
store i8 49, ptr %stack, align 1
|
|
%gep.1 = getelementptr i8, ptr %stack, i64 1
|
|
store i8 50, ptr %gep.1, align 1
|
|
%gep.2 = getelementptr i8, ptr %stack, i64 2
|
|
store i8 51, ptr %gep.2, align 1
|
|
%gep.3 = getelementptr i8, ptr %stack, i64 3
|
|
store i8 52, ptr %gep.3, align 1
|
|
%call = call ptr @memchr(ptr %stack, i32 42, i64 2)
|
|
ret ptr %call
|
|
}
|
|
|
|
define ptr @test_memchr_variable_size(i64 %n) {
|
|
; CHECK-LABEL: @test_memchr_variable_size(
|
|
; CHECK-NEXT: entry:
|
|
; CHECK-NEXT: [[STACK:%.*]] = alloca [10 x i8], align 1
|
|
; CHECK-NEXT: store i8 49, ptr [[STACK]], align 1
|
|
; CHECK-NEXT: [[GEP_1:%.*]] = getelementptr i8, ptr [[STACK]], i64 1
|
|
; CHECK-NEXT: store i8 50, ptr [[GEP_1]], align 1
|
|
; CHECK-NEXT: [[GEP_2:%.*]] = getelementptr i8, ptr [[STACK]], i64 2
|
|
; CHECK-NEXT: store i8 51, ptr [[GEP_2]], align 1
|
|
; CHECK-NEXT: [[GEP:%.*]] = getelementptr i8, ptr [[STACK]], i64 4
|
|
; CHECK-NEXT: store i8 52, ptr [[GEP]], align 1
|
|
; CHECK-NEXT: [[CALL:%.*]] = call ptr @memchr(ptr [[STACK]], i32 42, i64 [[N:%.*]])
|
|
; CHECK-NEXT: ret ptr [[CALL]]
|
|
;
|
|
entry:
|
|
%stack = alloca [10 x i8]
|
|
store i8 49, ptr %stack, align 1
|
|
%gep.1 = getelementptr i8, ptr %stack, i64 1
|
|
store i8 50, ptr %gep.1, align 1
|
|
%gep.2 = getelementptr i8, ptr %stack, i64 2
|
|
store i8 51, ptr %gep.2, align 1
|
|
%gep = getelementptr i8, ptr %stack, i64 4
|
|
store i8 52, ptr %gep, align 1
|
|
%call = call ptr @memchr(ptr %stack, i32 42, i64 %n)
|
|
ret ptr %call
|
|
}
|
|
|
|
declare ptr @memccpy(ptr, ptr, i32, i64)
|
|
|
|
define ptr @test_memccpy_const_size(ptr %foo) {
|
|
; CHECK-LABEL: @test_memccpy_const_size(
|
|
; CHECK-NEXT: entry:
|
|
; CHECK-NEXT: [[STACK:%.*]] = alloca [10 x i8], align 1
|
|
; CHECK-NEXT: store i8 49, ptr [[STACK]], align 1
|
|
; CHECK-NEXT: [[GEP_1:%.*]] = getelementptr i8, ptr [[STACK]], i64 1
|
|
; CHECK-NEXT: store i8 50, ptr [[GEP_1]], align 1
|
|
; CHECK-NEXT: [[RES:%.*]] = call ptr @memccpy(ptr [[FOO:%.*]], ptr [[STACK]], i32 42, i64 2)
|
|
; CHECK-NEXT: ret ptr [[RES]]
|
|
;
|
|
entry:
|
|
%stack = alloca [10 x i8]
|
|
store i8 49, ptr %stack, align 1
|
|
%gep.1 = getelementptr i8, ptr %stack, i64 1
|
|
store i8 50, ptr %gep.1, align 1
|
|
%gep.2 = getelementptr i8, ptr %stack, i64 2
|
|
store i8 51, ptr %gep.2, align 1
|
|
%gep.3 = getelementptr i8, ptr %stack, i64 3
|
|
store i8 52, ptr %gep.3, align 1
|
|
%res = call ptr @memccpy(ptr %foo, ptr %stack, i32 42, i64 2)
|
|
ret ptr %res
|
|
}
|
|
|
|
define ptr @test_memccpy_variable_size(ptr %foo, i64 %n) {
|
|
; CHECK-LABEL: @test_memccpy_variable_size(
|
|
; CHECK-NEXT: entry:
|
|
; CHECK-NEXT: [[STACK:%.*]] = alloca [10 x i8], align 1
|
|
; CHECK-NEXT: store i8 49, ptr [[STACK]], align 1
|
|
; CHECK-NEXT: [[GEP_1:%.*]] = getelementptr i8, ptr [[STACK]], i64 1
|
|
; CHECK-NEXT: store i8 50, ptr [[GEP_1]], align 1
|
|
; CHECK-NEXT: [[GEP_2:%.*]] = getelementptr i8, ptr [[STACK]], i64 2
|
|
; CHECK-NEXT: store i8 51, ptr [[GEP_2]], align 1
|
|
; CHECK-NEXT: [[GEP_3:%.*]] = getelementptr i8, ptr [[STACK]], i64 3
|
|
; CHECK-NEXT: store i8 52, ptr [[GEP_3]], align 1
|
|
; CHECK-NEXT: [[RES:%.*]] = call ptr @memccpy(ptr [[FOO:%.*]], ptr [[STACK]], i32 42, i64 [[N:%.*]])
|
|
; CHECK-NEXT: ret ptr [[RES]]
|
|
;
|
|
entry:
|
|
%stack = alloca [10 x i8]
|
|
store i8 49, ptr %stack, align 1
|
|
%gep.1 = getelementptr i8, ptr %stack, i64 1
|
|
store i8 50, ptr %gep.1, align 1
|
|
%gep.2 = getelementptr i8, ptr %stack, i64 2
|
|
store i8 51, ptr %gep.2, align 1
|
|
%gep.3 = getelementptr i8, ptr %stack, i64 3
|
|
store i8 52, ptr %gep.3, align 1
|
|
%res = call ptr @memccpy(ptr %foo, ptr %stack, i32 42, i64 %n)
|
|
ret ptr %res
|
|
}
|
|
|
|
; Make sure memccpy does not kill any stores, because it is not known how many
|
|
; bytes are written.
|
|
define ptr @test_memccpy_const_size_does_not_kill_stores(ptr noalias %dest, ptr noalias %foo) {
|
|
; CHECK-LABEL: @test_memccpy_const_size_does_not_kill_stores(
|
|
; CHECK-NEXT: entry:
|
|
; CHECK-NEXT: store i8 49, ptr [[DEST:%.*]], align 1
|
|
; CHECK-NEXT: [[GEP_1:%.*]] = getelementptr i8, ptr [[DEST]], i64 1
|
|
; CHECK-NEXT: store i8 50, ptr [[GEP_1]], align 1
|
|
; CHECK-NEXT: [[GEP_2:%.*]] = getelementptr i8, ptr [[DEST]], i64 2
|
|
; CHECK-NEXT: store i8 51, ptr [[GEP_2]], align 1
|
|
; CHECK-NEXT: [[GEP_3:%.*]] = getelementptr i8, ptr [[DEST]], i64 3
|
|
; CHECK-NEXT: store i8 52, ptr [[GEP_3]], align 1
|
|
; CHECK-NEXT: [[RES:%.*]] = call ptr @memccpy(ptr [[DEST]], ptr [[FOO:%.*]], i32 42, i64 2)
|
|
; CHECK-NEXT: ret ptr [[RES]]
|
|
;
|
|
entry:
|
|
store i8 49, ptr %dest, align 1
|
|
%gep.1 = getelementptr i8, ptr %dest, i64 1
|
|
store i8 50, ptr %gep.1, align 1
|
|
%gep.2 = getelementptr i8, ptr %dest, i64 2
|
|
store i8 51, ptr %gep.2, align 1
|
|
%gep.3 = getelementptr i8, ptr %dest, i64 3
|
|
store i8 52, ptr %gep.3, align 1
|
|
%res = call ptr @memccpy(ptr %dest, ptr %foo, i32 42, i64 2)
|
|
ret ptr %res
|
|
}
|
|
|
|
define void @dse_strcpy(ptr nocapture readonly %src) {
|
|
; CHECK-LABEL: @dse_strcpy(
|
|
; CHECK-NEXT: [[A:%.*]] = alloca [256 x i8], align 16
|
|
; CHECK-NEXT: call void @llvm.lifetime.start.p0(i64 256, ptr nonnull [[A]])
|
|
; CHECK-NEXT: call void @llvm.lifetime.end.p0(i64 256, ptr nonnull [[A]])
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
%a = alloca [256 x i8], align 16
|
|
call void @llvm.lifetime.start.p0(i64 256, ptr nonnull %a)
|
|
call ptr @strcpy(ptr nonnull %a, ptr nonnull dereferenceable(1) %src)
|
|
call void @llvm.lifetime.end.p0(i64 256, ptr nonnull %a)
|
|
ret void
|
|
}
|
|
|
|
define void @dse_strncpy(ptr nocapture readonly %src) {
|
|
; CHECK-LABEL: @dse_strncpy(
|
|
; CHECK-NEXT: [[A:%.*]] = alloca [256 x i8], align 16
|
|
; CHECK-NEXT: call void @llvm.lifetime.start.p0(i64 256, ptr nonnull [[A]])
|
|
; CHECK-NEXT: call void @llvm.lifetime.end.p0(i64 256, ptr nonnull [[A]])
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
%a = alloca [256 x i8], align 16
|
|
call void @llvm.lifetime.start.p0(i64 256, ptr nonnull %a)
|
|
call ptr @strncpy(ptr nonnull %a, ptr nonnull dereferenceable(1) %src, i64 6)
|
|
call void @llvm.lifetime.end.p0(i64 256, ptr nonnull %a)
|
|
ret void
|
|
}
|
|
|
|
define void @dse_strcat(ptr nocapture readonly %src) {
|
|
; CHECK-LABEL: @dse_strcat(
|
|
; CHECK-NEXT: [[A:%.*]] = alloca [256 x i8], align 16
|
|
; CHECK-NEXT: call void @llvm.lifetime.start.p0(i64 256, ptr nonnull [[A]])
|
|
; CHECK-NEXT: call void @llvm.lifetime.end.p0(i64 256, ptr nonnull [[A]])
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
%a = alloca [256 x i8], align 16
|
|
call void @llvm.lifetime.start.p0(i64 256, ptr nonnull %a)
|
|
call ptr @strcat(ptr nonnull %a, ptr nonnull dereferenceable(1) %src)
|
|
call void @llvm.lifetime.end.p0(i64 256, ptr nonnull %a)
|
|
ret void
|
|
}
|
|
|
|
define void @dse_strncat(ptr nocapture readonly %src) {
|
|
; CHECK-LABEL: @dse_strncat(
|
|
; CHECK-NEXT: [[A:%.*]] = alloca [256 x i8], align 16
|
|
; CHECK-NEXT: call void @llvm.lifetime.start.p0(i64 256, ptr nonnull [[A]])
|
|
; CHECK-NEXT: call void @llvm.lifetime.end.p0(i64 256, ptr nonnull [[A]])
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
%a = alloca [256 x i8], align 16
|
|
call void @llvm.lifetime.start.p0(i64 256, ptr nonnull %a)
|
|
call ptr @strncat(ptr nonnull %a, ptr nonnull dereferenceable(1) %src, i64 6)
|
|
call void @llvm.lifetime.end.p0(i64 256, ptr nonnull %a)
|
|
ret void
|
|
}
|
|
|
|
declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture)
|
|
declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture)
|
|
|
|
declare void @llvm.memset.p0.i64(ptr nocapture, i8, i64, i1) nounwind
|
|
|
|
; Test that strncpy/memset overwriting each other is optimized out
|
|
|
|
; strncpy -> memset, full overwrite
|
|
define void @dse_strncpy_test1(ptr noalias %out, ptr noalias %in) {
|
|
; CHECK-LABEL: @dse_strncpy_test1(
|
|
; CHECK-NEXT: tail call void @llvm.memset.p0.i64(ptr [[OUT:%.*]], i8 42, i64 100, i1 false)
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
%call = tail call ptr @strncpy(ptr %out, ptr %in, i64 100)
|
|
tail call void @llvm.memset.p0.i64(ptr %out, i8 42, i64 100, i1 false)
|
|
ret void
|
|
}
|
|
|
|
; strncpy -> memset, partial overwrite
|
|
define void @dse_strncpy_test2(ptr noalias %out, ptr noalias %in) {
|
|
; CHECK-LABEL: @dse_strncpy_test2(
|
|
; CHECK-NEXT: [[CALL:%.*]] = tail call ptr @strncpy(ptr [[OUT:%.*]], ptr [[IN:%.*]], i64 100)
|
|
; CHECK-NEXT: tail call void @llvm.memset.p0.i64(ptr [[OUT]], i8 42, i64 99, i1 false)
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
%call = tail call ptr @strncpy(ptr %out, ptr %in, i64 100)
|
|
tail call void @llvm.memset.p0.i64(ptr %out, i8 42, i64 99, i1 false)
|
|
ret void
|
|
}
|
|
|
|
; strncpy -> memset, different destination
|
|
define void @dse_strncpy_test3(ptr noalias %out1, ptr noalias %out2, ptr noalias %in) {
|
|
; CHECK-LABEL: @dse_strncpy_test3(
|
|
; CHECK-NEXT: [[CALL:%.*]] = tail call ptr @strncpy(ptr [[OUT1:%.*]], ptr [[IN:%.*]], i64 100)
|
|
; CHECK-NEXT: tail call void @llvm.memset.p0.i64(ptr [[OUT2:%.*]], i8 42, i64 100, i1 false)
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
%call = tail call ptr @strncpy(ptr %out1, ptr %in, i64 100)
|
|
tail call void @llvm.memset.p0.i64(ptr %out2, i8 42, i64 100, i1 false)
|
|
ret void
|
|
}
|
|
|
|
; memset -> strncpy, full overwrite
|
|
define void @dse_strncpy_test4(ptr noalias %out, ptr noalias %in) {
|
|
; CHECK-LABEL: @dse_strncpy_test4(
|
|
; CHECK-NEXT: [[CALL:%.*]] = tail call ptr @strncpy(ptr [[OUT:%.*]], ptr [[IN:%.*]], i64 100)
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
tail call void @llvm.memset.p0.i64(ptr %out, i8 42, i64 100, i1 false)
|
|
%call = tail call ptr @strncpy(ptr %out, ptr %in, i64 100)
|
|
ret void
|
|
}
|
|
|
|
; memset -> strncpy, partial overwrite
|
|
define void @dse_strncpy_test5(ptr noalias %out, ptr noalias %in) {
|
|
; CHECK-LABEL: @dse_strncpy_test5(
|
|
; CHECK-NEXT: [[TMP1:%.*]] = getelementptr inbounds i8, ptr [[OUT:%.*]], i64 99
|
|
; CHECK-NEXT: tail call void @llvm.memset.p0.i64(ptr align 1 [[TMP1]], i8 42, i64 1, i1 false)
|
|
; CHECK-NEXT: [[CALL:%.*]] = tail call ptr @strncpy(ptr [[OUT]], ptr [[IN:%.*]], i64 99)
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
tail call void @llvm.memset.p0.i64(ptr %out, i8 42, i64 100, i1 false)
|
|
%call = tail call ptr @strncpy(ptr %out, ptr %in, i64 99)
|
|
ret void
|
|
}
|
|
|
|
; memset -> strncpy, different destination
|
|
define void @dse_strncpy_test6(ptr noalias %out1, ptr noalias %out2, ptr noalias %in) {
|
|
; CHECK-LABEL: @dse_strncpy_test6(
|
|
; CHECK-NEXT: tail call void @llvm.memset.p0.i64(ptr [[OUT1:%.*]], i8 42, i64 100, i1 false)
|
|
; CHECK-NEXT: [[CALL:%.*]] = tail call ptr @strncpy(ptr [[OUT2:%.*]], ptr [[IN:%.*]], i64 100)
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
tail call void @llvm.memset.p0.i64(ptr %out1, i8 42, i64 100, i1 false)
|
|
%call = tail call ptr @strncpy(ptr %out2, ptr %in, i64 100)
|
|
ret void
|
|
}
|