[WebAssembly] Support opaque pointers in AddMissingPrototypes

The change here is basically the same as in D108880: Rather than
looking at bitcasts, look at calls and their function type. We
still need to look through bitcasts to find those calls.

The change in llvm/test/CodeGen/WebAssembly/add-prototypes-conflict.ll
is due to different visitation order. add-prototypes-opaque-ptrs.ll
is a copy of add-prototypes.ll with -force-opaque-pointers.

Differential Revision: https://reviews.llvm.org/D109256
This commit is contained in:
Nikita Popov 2021-09-03 21:53:01 +02:00
parent 9e06c767a4
commit 66a54af967
3 changed files with 112 additions and 23 deletions

View file

@ -86,27 +86,37 @@ bool WebAssemblyAddMissingPrototypes::runOnModule(Module &M) {
F.getName());
}
// Create a function prototype based on the first call site (first bitcast)
// that we find.
// Find calls of this function, looking through bitcasts.
SmallVector<CallBase *> Calls;
SmallVector<Value *> Worklist;
Worklist.push_back(&F);
while (!Worklist.empty()) {
Value *V = Worklist.pop_back_val();
for (User *U : V->users()) {
if (auto *BC = dyn_cast<BitCastOperator>(U))
Worklist.push_back(BC);
else if (auto *CB = dyn_cast<CallBase>(U))
if (CB->getCalledOperand() == V)
Calls.push_back(CB);
}
}
// Create a function prototype based on the first call site that we find.
FunctionType *NewType = nullptr;
for (Use &U : F.uses()) {
LLVM_DEBUG(dbgs() << "prototype-less use: " << F.getName() << "\n");
LLVM_DEBUG(dbgs() << *U.getUser() << "\n");
if (auto *BC = dyn_cast<BitCastOperator>(U.getUser())) {
if (auto *DestType = dyn_cast<FunctionType>(
BC->getDestTy()->getPointerElementType())) {
if (!NewType) {
// Create a new function with the correct type
NewType = DestType;
LLVM_DEBUG(dbgs() << "found function type: " << *NewType << "\n");
} else if (NewType != DestType) {
errs() << "warning: prototype-less function used with "
"conflicting signatures: "
<< F.getName() << "\n";
LLVM_DEBUG(dbgs() << " " << *DestType << "\n");
LLVM_DEBUG(dbgs() << " "<< *NewType << "\n");
}
}
for (CallBase *CB : Calls) {
LLVM_DEBUG(dbgs() << "prototype-less call of " << F.getName() << ":\n");
LLVM_DEBUG(dbgs() << *CB << "\n");
FunctionType *DestType = CB->getFunctionType();
if (!NewType) {
// Create a new function with the correct type
NewType = DestType;
LLVM_DEBUG(dbgs() << "found function type: " << *NewType << "\n");
} else if (NewType != DestType) {
errs() << "warning: prototype-less function used with "
"conflicting signatures: "
<< F.getName() << "\n";
LLVM_DEBUG(dbgs() << " " << *DestType << "\n");
LLVM_DEBUG(dbgs() << " " << *NewType << "\n");
}
}

View file

@ -7,15 +7,15 @@ target triple = "wasm32-unknown-unknown"
; WARNING: warning: prototype-less function used with conflicting signatures: foo
; CHECK-LABEL: @call_with_conflicting_prototypes
; CHECK: %call1 = call i64 @foo(i32 42)
; CHECK: %call2 = call i64 bitcast (i64 (i32)* @foo to i64 (i32, i32)*)(i32 42, i32 43)
; CHECK: %call1 = call i64 bitcast (i64 (i32, i32)* @foo to i64 (i32)*)(i32 42)
; CHECK: %call2 = call i64 @foo(i32 42, i32 43)
define void @call_with_conflicting_prototypes() {
%call1 = call i64 bitcast (i64 (...)* @foo to i64 (i32)*)(i32 42)
%call2 = call i64 bitcast (i64 (...)* @foo to i64 (i32, i32)*)(i32 42, i32 43)
ret void
}
; CHECK: declare extern_weak i64 @foo(i32)
; CHECK: declare extern_weak i64 @foo(i32, i32)
declare extern_weak i64 @foo(...) #1
; CHECK-NOT: attributes {{.*}} = { {{.*}}"no-prototype"{{.*}} }

View file

@ -0,0 +1,79 @@
; RUN: opt -S -wasm-add-missing-prototypes -force-opaque-pointers %s | FileCheck %s
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-unknown"
; CHECK: @foo_addr = global ptr @foo, align 8
@foo_addr = global i64 (i32)* bitcast (i64 (...)* @foo to i64 (i32)*), align 8
; CHECK: @foo_addr_i8 = global ptr @foo, align 8
@foo_addr_i8 = global i8* bitcast (i64 (...)* @foo to i8*), align 8
; CHECK-LABEL: @call_foo
; CHECK: %call = call i64 @foo(i32 42)
define void @call_foo(i32 %a) {
%call = call i64 bitcast (i64 (...)* @foo to i64 (i32)*)(i32 42)
ret void
}
; CHECK-LABEL: @call_foo_ptr
; CHECK: %1 = bitcast ptr @foo to ptr
; CHECK-NEXT: %call = call i64 %1(i32 43)
define i64 @call_foo_ptr(i32 %a) {
%1 = bitcast i64 (...)* @foo to i64 (i32)*
%call = call i64 (i32) %1(i32 43)
ret i64 %call
}
; CHECK-LABEL: @to_intptr_inst
; CHECK: %1 = bitcast ptr @foo to ptr
; CHECK-NEXT: ret ptr %1
define i8* @to_intptr_inst() {
%1 = bitcast i64 (...)* @foo to i8*
ret i8* %1
}
; CHECK-LABEL: @to_intptr_constexpr
; CHECK: ret ptr @foo
define i8* @to_intptr_constexpr() {
ret i8* bitcast (i64 (...)* @foo to i8*)
}
; CHECK-LABEL: @null_compare
; CHECK: br i1 icmp eq (ptr @foo, ptr null), label %if.then, label %if.end
define i8 @null_compare() {
br i1 icmp eq (i64 (...)* @foo, i64 (...)* null), label %if.then, label %if.end
if.then:
ret i8 0
if.end:
ret i8 1
}
; CHECK-LABEL: @as_paramater
; CHECK: call void @func_param(ptr @foo)
define void @as_paramater() {
call void @func_param(i64 (...)* @foo)
ret void
}
; Check if a sret parameter works in a no-prototype function.
; CHECK-LABEL: @sret_param
; CHECK: call void @make_struct_foo(ptr sret(%struct.foo) %foo)
%struct.foo = type { i32, i32 }
declare void @make_struct_foo(%struct.foo* sret(%struct.foo), ...) #1
define void @sret_param() {
%foo = alloca %struct.foo, align 4
call void bitcast (void (%struct.foo*, ...)* @make_struct_foo to void (%struct.foo*)*)(%struct.foo* sret(%struct.foo) %foo)
ret void
}
declare void @func_param(i64 (...)*)
; CHECK: declare void @func_not_called()
declare void @func_not_called(...) #1
; CHECK: declare extern_weak i64 @foo(i32)
declare extern_weak i64 @foo(...) #1
; CHECK-NOT: attributes {{.*}} = { {{.*}}"no-prototype"{{.*}} }
attributes #1 = { "no-prototype" }