Philip Reames 33cbaab141 [funcattrs] Consistently treat calling a function pointer as a non-capturing read
We were being wildly inconsistent about what memory access was implied by an indirect function call. Depending on the call site attributes, you could get anything from a read, to unknown, to none at all. (The last was a miscompile.)

We were also always traversing the uses of a readonly indirect call. This is entirely unneeded as the indirect call does not capture. The callee might capture itself internally, but that has no implications for this caller. (See the nice explanation in the CaptureTracking comments if that case is confusing.)

Note that elsewhere in the same file, we were correctly computing the nocapture attribute for indirect calls. The changed case only resulted in conservatism when computing memory attributes if say the return value was written to.

Differential Revision: https://reviews.llvm.org/D115916
2021-12-17 09:02:03 -08:00

116 lines
3.0 KiB
LLVM

; RUN: opt < %s -function-attrs -S | FileCheck %s
; RUN: opt < %s -passes=function-attrs -S | FileCheck %s
; CHECK: define void @nouses-argworn-funrn(i32* nocapture readnone %.aaa) #0 {
define void @nouses-argworn-funrn(i32* writeonly %.aaa) {
nouses-argworn-funrn_entry:
ret void
}
; CHECK: define void @nouses-argworn-funro(i32* nocapture readnone %.aaa, i32* nocapture readonly %.bbb) #1 {
define void @nouses-argworn-funro(i32* writeonly %.aaa, i32* %.bbb) {
nouses-argworn-funro_entry:
%val = load i32 , i32* %.bbb
ret void
}
%_type_of_d-ccc = type <{ i8*, i8, i8, i8, i8 }>
@d-ccc = internal global %_type_of_d-ccc <{ i8* null, i8 1, i8 13, i8 0, i8 -127 }>, align 8
; CHECK: define void @nouses-argworn-funwo(i32* nocapture readnone %.aaa) #2 {
define void @nouses-argworn-funwo(i32* writeonly %.aaa) {
nouses-argworn-funwo_entry:
store i8 0, i8* getelementptr inbounds (%_type_of_d-ccc, %_type_of_d-ccc* @d-ccc, i32 0, i32 3)
ret void
}
; CHECK: define void @test_store(i8* nocapture writeonly %p)
define void @test_store(i8* %p) {
store i8 0, i8* %p
ret void
}
@G = external global i8*
; CHECK: define i8 @test_store_capture(i8* %p)
define i8 @test_store_capture(i8* %p) {
store i8* %p, i8** @G
%p2 = load i8*, i8** @G
%v = load i8, i8* %p2
ret i8 %v
}
; CHECK: define void @test_addressing(i8* nocapture writeonly %p)
define void @test_addressing(i8* %p) {
%gep = getelementptr i8, i8* %p, i64 8
%bitcast = bitcast i8* %gep to i32*
store i32 0, i32* %bitcast
ret void
}
; CHECK: define void @test_readwrite(i8* nocapture %p)
define void @test_readwrite(i8* %p) {
%v = load i8, i8* %p
store i8 %v, i8* %p
ret void
}
; CHECK: define void @test_volatile(i8* %p)
define void @test_volatile(i8* %p) {
store volatile i8 0, i8* %p
ret void
}
; CHECK: define void @test_atomicrmw(i8* nocapture %p)
define void @test_atomicrmw(i8* %p) {
atomicrmw add i8* %p, i8 0 seq_cst
ret void
}
declare void @direct1_callee(i8* %p)
; CHECK: define void @direct1(i8* %p)
define void @direct1(i8* %p) {
call void @direct1_callee(i8* %p)
ret void
}
declare void @direct2_callee(i8* %p) writeonly
; CHECK: define void @direct2(i8* %p)
define void @direct2(i8* %p) {
call void @direct2_callee(i8* %p)
ret void
}
declare void @direct3_callee(i8* writeonly %p)
; CHECK: define void @direct3(i8* %p)
define void @direct3(i8* %p) {
call void @direct3_callee(i8* %p)
ret void
}
; CHECK: define void @fptr_test1(i8* %p, void (i8*)* nocapture readonly %f)
define void @fptr_test1(i8* %p, void (i8*)* %f) {
call void %f(i8* %p)
ret void
}
; CHECK: define void @fptr_test2(i8* %p, void (i8*)* nocapture readonly %f)
define void @fptr_test2(i8* %p, void (i8*)* %f) {
call void %f(i8* writeonly %p)
ret void
}
; CHECK: define void @fptr_test3(i8* %p, void (i8*)* nocapture readonly %f)
define void @fptr_test3(i8* %p, void (i8*)* %f) {
call void %f(i8* %p) writeonly
ret void
}
; CHECK: attributes #0 = { {{.*}}readnone{{.*}} }
; CHECK: attributes #1 = { {{.*}}readonly{{.*}} }
; CHECK: attributes #2 = { {{.*}}writeonly{{.*}} }