Skip to content

Commit

Permalink
[WebAssembly] Ensure bitcasts that would result in invalid wasm are r…
Browse files Browse the repository at this point in the history
…emoved by FixFunctionBitcasts

Rather than allowing invalid bitcasts to be lowered to wasm
call instructions that won't validate, generate wrappers that
contain unreachable thereby delaying the error until runtime.

Differential Revision: https://reviews.llvm.org/D49517

llvm-svn: 338744
  • Loading branch information
sbc100 committed Aug 2, 2018
1 parent b4b1f59 commit 41d7047
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 61 deletions.
107 changes: 82 additions & 25 deletions llvm/lib/Target/WebAssembly/WebAssemblyFixFunctionBitcasts.cpp
Expand Up @@ -103,49 +103,106 @@ static void FindUses(Value *V, Function &F,
// - Return value is not needed: drop it
// - Return value needed but not present: supply an undef
//
// For now, return nullptr without creating a wrapper if the wrapper cannot
// be generated due to incompatible types.
// If the all the argument types of trivially castable to one another (i.e.
// I32 vs pointer type) then we don't create a wrapper at all (return nullptr
// instead).
//
// If there is a type mismatch that would result in an invalid wasm module
// being written then generate wrapper that contains unreachable (i.e. abort
// at runtime). Such programs are deep into undefined behaviour territory,
// but we choose to fail at runtime rather than generate and invalid module
// or fail at compiler time. The reason we delay the error is that we want
// to support the CMake which expects to be able to compile and link programs
// that refer to functions with entirely incorrect signatures (this is how
// CMake detects the existence of a function in a toolchain).
static Function *CreateWrapper(Function *F, FunctionType *Ty) {
Module *M = F->getParent();

Function *Wrapper =
Function::Create(Ty, Function::PrivateLinkage, "bitcast", M);
Function *Wrapper = Function::Create(Ty, Function::PrivateLinkage,
F->getName() + "_bitcast", M);
BasicBlock *BB = BasicBlock::Create(M->getContext(), "body", Wrapper);
const DataLayout &DL = BB->getModule()->getDataLayout();

// Determine what arguments to pass.
SmallVector<Value *, 4> Args;
Function::arg_iterator AI = Wrapper->arg_begin();
Function::arg_iterator AE = Wrapper->arg_end();
FunctionType::param_iterator PI = F->getFunctionType()->param_begin();
FunctionType::param_iterator PE = F->getFunctionType()->param_end();
bool TypeMismatch = false;
bool WrapperNeeded = false;

if ((F->getFunctionType()->getNumParams() != Ty->getNumParams()) ||
(F->getFunctionType()->isVarArg() != Ty->isVarArg()))
WrapperNeeded = true;

for (; AI != AE && PI != PE; ++AI, ++PI) {
if (AI->getType() != *PI) {
Wrapper->eraseFromParent();
return nullptr;
Type *ArgType = AI->getType();
Type *ParamType = *PI;

if (ArgType == ParamType) {
Args.push_back(&*AI);
} else {
if (CastInst::isBitOrNoopPointerCastable(ArgType, ParamType, DL)) {
Instruction *PtrCast =
CastInst::CreateBitOrPointerCast(AI, ParamType, "cast");
BB->getInstList().push_back(PtrCast);
Args.push_back(PtrCast);
} else {
LLVM_DEBUG(dbgs() << "CreateWrapper: arg type mismatch calling: "
<< F->getName() << "\n");
LLVM_DEBUG(dbgs() << "Arg[" << Args.size() << "] Expected: "
<< *ParamType << " Got: " << *ArgType << "\n");
TypeMismatch = true;
break;
}
}
Args.push_back(&*AI);
}
for (; PI != PE; ++PI)
Args.push_back(UndefValue::get(*PI));
if (F->isVarArg())
for (; AI != AE; ++AI)
Args.push_back(&*AI);

CallInst *Call = CallInst::Create(F, Args, "", BB);

// Determine what value to return.
if (Ty->getReturnType()->isVoidTy())
ReturnInst::Create(M->getContext(), BB);
else if (F->getFunctionType()->getReturnType()->isVoidTy())
ReturnInst::Create(M->getContext(), UndefValue::get(Ty->getReturnType()),
BB);
else if (F->getFunctionType()->getReturnType() == Ty->getReturnType())
ReturnInst::Create(M->getContext(), Call, BB);
else {
if (!TypeMismatch) {
for (; PI != PE; ++PI)
Args.push_back(UndefValue::get(*PI));
if (F->isVarArg())
for (; AI != AE; ++AI)
Args.push_back(&*AI);

CallInst *Call = CallInst::Create(F, Args, "", BB);

Type *ExpectedRtnType = F->getFunctionType()->getReturnType();
Type *RtnType = Ty->getReturnType();
// Determine what value to return.
if (RtnType->isVoidTy()) {
ReturnInst::Create(M->getContext(), BB);
WrapperNeeded = true;
} else if (ExpectedRtnType->isVoidTy()) {
ReturnInst::Create(M->getContext(), UndefValue::get(RtnType), BB);
WrapperNeeded = true;
} else if (RtnType == ExpectedRtnType) {
ReturnInst::Create(M->getContext(), Call, BB);
} else if (CastInst::isBitOrNoopPointerCastable(ExpectedRtnType, RtnType,
DL)) {
Instruction *Cast =
CastInst::CreateBitOrPointerCast(Call, RtnType, "cast");
BB->getInstList().push_back(Cast);
ReturnInst::Create(M->getContext(), Cast, BB);
} else {
LLVM_DEBUG(dbgs() << "CreateWrapper: return type mismatch calling: "
<< F->getName() << "\n");
LLVM_DEBUG(dbgs() << "Expected: " << *ExpectedRtnType
<< " Got: " << *RtnType << "\n");
TypeMismatch = true;
}
}

if (TypeMismatch) {
new UnreachableInst(M->getContext(), BB);
Wrapper->setName(F->getName() + "_bitcast_invalid");
} else if (!WrapperNeeded) {
LLVM_DEBUG(dbgs() << "CreateWrapper: no wrapper needed: " << F->getName()
<< "\n");
Wrapper->eraseFromParent();
return nullptr;
}

return Wrapper;
}

Expand Down
2 changes: 1 addition & 1 deletion llvm/test/CodeGen/WebAssembly/call.ll
Expand Up @@ -153,7 +153,7 @@ define void @coldcc_tail_call_void_nullary() {
; CHECK-LABEL: call_constexpr:
; CHECK-NEXT: i32.const $push[[L0:[0-9]+]]=, 2{{$}}
; CHECK-NEXT: i32.const $push[[L1:[0-9]+]]=, 3{{$}}
; CHECK-NEXT: call .Lbitcast@FUNCTION, $pop[[L0]], $pop[[L1]]{{$}}
; CHECK-NEXT: call .Lvararg_func_bitcast@FUNCTION, $pop[[L0]], $pop[[L1]]{{$}}
; CHECK-NEXT: call other_void_nullary@FUNCTION{{$}}
; CHECK-NEXT: call void_nullary@FUNCTION{{$}}
; CHECK-NEXT: return{{$}}
Expand Down
8 changes: 4 additions & 4 deletions llvm/test/CodeGen/WebAssembly/function-bitcasts-varargs.ll
Expand Up @@ -19,13 +19,13 @@ declare void @specified(i32, i32)
; CHECK: callWithArgs:
; CHECK: i32.const $push1=, 0
; CHECK-NEXT: i32.const $push0=, 1
; CHECK-NEXT: call .Lbitcast@FUNCTION, $pop1, $pop0
; CHECK: call .Lbitcast.1@FUNCTION, $pop{{[0-9]+$}}
; CHECK-NEXT: call .Lunderspecified_bitcast@FUNCTION, $pop1, $pop0
; CHECK: call .Lspecified_bitcast@FUNCTION, $pop{{[0-9]+$}}

; CHECK: .Lbitcast:
; CHECK: .Lunderspecified_bitcast:
; CHECK-NEXT: .param i32, i32{{$}}
; CHECK: call underspecified@FUNCTION, $pop{{[0-9]+$}}

; CHECK: .Lbitcast.1:
; CHECK: .Lspecified_bitcast:
; CHECK-NEXT: .param i32{{$}}
; CHECK: call specified@FUNCTION, $pop{{[0-9]+}}, $pop{{[0-9]+$}}
32 changes: 16 additions & 16 deletions llvm/test/CodeGen/WebAssembly/function-bitcasts.ll
Expand Up @@ -16,17 +16,17 @@ declare void @foo2()
declare void @foo3()

; CHECK-LABEL: test:
; CHECK-NEXT: call .Lbitcast@FUNCTION{{$}}
; CHECK-NEXT: call .Lbitcast@FUNCTION{{$}}
; CHECK-NEXT: call .Lbitcast.1@FUNCTION{{$}}
; CHECK-NEXT: call .Lhas_i32_arg_bitcast@FUNCTION{{$}}
; CHECK-NEXT: call .Lhas_i32_arg_bitcast@FUNCTION{{$}}
; CHECK-NEXT: call .Lhas_i32_ret_bitcast@FUNCTION{{$}}
; CHECK-NEXT: i32.const $push[[L0:[0-9]+]]=, 0
; CHECK-NEXT: call .Lbitcast.4@FUNCTION, $pop[[L0]]{{$}}
; CHECK-NEXT: call .Lfoo0_bitcast@FUNCTION, $pop[[L0]]{{$}}
; CHECK-NEXT: i32.const $push[[L1:[0-9]+]]=, 0
; CHECK-NEXT: call .Lbitcast.4@FUNCTION, $pop[[L1]]{{$}}
; CHECK-NEXT: call .Lfoo0_bitcast@FUNCTION, $pop[[L1]]{{$}}
; CHECK-NEXT: i32.const $push[[L2:[0-9]+]]=, 0
; CHECK-NEXT: call .Lbitcast.4@FUNCTION, $pop[[L2]]{{$}}
; CHECK-NEXT: call .Lfoo0_bitcast@FUNCTION, $pop[[L2]]{{$}}
; CHECK-NEXT: call foo0@FUNCTION
; CHECK-NEXT: i32.call $drop=, .Lbitcast.5@FUNCTION{{$}}
; CHECK-NEXT: i32.call $drop=, .Lfoo1_bitcast@FUNCTION{{$}}
; CHECK-NEXT: call foo2@FUNCTION{{$}}
; CHECK-NEXT: call foo1@FUNCTION{{$}}
; CHECK-NEXT: call foo3@FUNCTION{{$}}
Expand Down Expand Up @@ -54,10 +54,10 @@ entry:
; CHECK-LABEL: test_varargs:
; CHECK: set_global
; CHECK: i32.const $push[[L3:[0-9]+]]=, 0{{$}}
; CHECK-NEXT: call .Lbitcast.2@FUNCTION, $pop[[L3]]{{$}}
; CHECK-NEXT: call .Lvararg_bitcast@FUNCTION, $pop[[L3]]{{$}}
; CHECK-NEXT: i32.const $push[[L4:[0-9]+]]=, 0{{$}}
; CHECK-NEXT: i32.store 0($[[L5:[0-9]+]]), $pop[[L4]]{{$}}
; CHECK-NEXT: call .Lbitcast.3@FUNCTION, $[[L5]]{{$}}
; CHECK-NEXT: call .Lplain_bitcast@FUNCTION, $[[L5]]{{$}}
define void @test_varargs() {
call void bitcast (void (...)* @vararg to void (i32)*)(i32 0)
call void (...) bitcast (void (i32)* @plain to void (...)*)(i32 0)
Expand Down Expand Up @@ -113,7 +113,7 @@ define void @test_argument() {
; CHECK: i32.const $push[[L3:[0-9]+]]=, call_func@FUNCTION{{$}}
; CHECK-NEXT: i32.const $push[[L2:[0-9]+]]=, has_i32_arg@FUNCTION{{$}}
; CHECK-NEXT: call "__invoke_void_i32()*"@FUNCTION, $pop[[L3]], $pop[[L2]]{{$}}
; CHECK: i32.const $push[[L4:[0-9]+]]=, .Lbitcast@FUNCTION{{$}}
; CHECK: i32.const $push[[L4:[0-9]+]]=, .Lhas_i32_arg_bitcast@FUNCTION{{$}}
; CHECK-NEXT: call __invoke_void@FUNCTION, $pop[[L4]]{{$}}
declare i32 @personality(...)
define void @test_invoke() personality i32 (...)* @personality {
Expand All @@ -138,28 +138,28 @@ end:
ret void
}

; CHECK-LABEL: .Lbitcast:
; CHECK-LABEL: .Lhas_i32_arg_bitcast:
; CHECK-NEXT: call has_i32_arg@FUNCTION, $0{{$}}
; CHECK-NEXT: end_function

; CHECK-LABEL: .Lbitcast.1:
; CHECK-LABEL: .Lhas_i32_ret_bitcast:
; CHECK-NEXT: call $drop=, has_i32_ret@FUNCTION{{$}}
; CHECK-NEXT: end_function

; CHECK-LABEL: .Lbitcast.2:
; CHECK-LABEL: .Lvararg_bitcast:
; CHECK: call vararg@FUNCTION, $1{{$}}
; CHECK: end_function

; CHECK-LABEL: .Lbitcast.3:
; CHECK-LABEL: .Lplain_bitcast:
; CHECK: call plain@FUNCTION, $1{{$}}
; CHECK: end_function

; CHECK-LABEL: .Lbitcast.4:
; CHECK-LABEL: .Lfoo0_bitcast:
; CHECK-NEXT: .param i32
; CHECK-NEXT: call foo0@FUNCTION{{$}}
; CHECK-NEXT: end_function

; CHECK-LABEL: .Lbitcast.5:
; CHECK-LABEL: .Lfoo1_bitcast:
; CHECK-NEXT: .result i32
; CHECK-NEXT: call foo1@FUNCTION{{$}}
; CHECK-NEXT: copy_local $push0=, $0
Expand Down
62 changes: 47 additions & 15 deletions llvm/test/CodeGen/WebAssembly/unsupported-function-bitcasts.ll
@@ -1,27 +1,59 @@
; RUN: llc < %s -asm-verbose=false | FileCheck %s

; Test that function pointer casts that require conversions are not converted
; to wrappers. In theory some conversions could be supported, but currently no
; conversions are implemented.
; Test that function pointer casts that require conversions of arguments or
; return types are converted to unreachable.

target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-unknown"

; CHECK-LABEL: test:
declare i32 @has_i64_arg(i64)
declare i32 @has_ptr_arg(i8*)

define void @test_invalid_rtn() {
entry:
call i32 bitcast (i32 (i64)* @has_i64_arg to i32 (i32)*)(i32 0)
ret void
}
; CHECK-LABEL: test_invalid_rtn:
; CHECK-NEXT: i32.const $push[[L0:[0-9]+]]=, 0{{$}}
; CHECK-NEXT: call has_i64_arg@FUNCTION, $pop[[L0]]{{$}}
; CHECK-NEXT: i32.call $push{{[0-9]+}}=, has_i64_ret@FUNCTION{{$}}
; CHECK-NEXT: drop
; CHECK-NEXT: i32.call $push1=, .Lhas_i64_arg_bitcast_invalid@FUNCTION, $pop[[L0]]{{$}}
; CHECK-NEXT: drop $pop1
; CHECK-NEXT: end_function

; CHECK-NOT: .Lbitcast

declare void @has_i64_arg(i64)
declare i64 @has_i64_ret()

define void @test() {
define void @test_invalid_arg() {
entry:
call void bitcast (void (i64)* @has_i64_arg to void (i32)*)(i32 0)
%t = call i32 bitcast (i64 ()* @has_i64_ret to i32 ()*)()
call i32 bitcast (i32 (i8*)* @has_ptr_arg to i32 (i8)*)(i8 2)
call i32 bitcast (i32 (i8*)* @has_ptr_arg to i32 (i32)*)(i32 2)
call i32 bitcast (i32 (i8*)* @has_ptr_arg to i32 (i64)*)(i64 3)
ret void
}

; CHECK-LABEL: test_invalid_arg:
; CHECK-NEXT: i32.const $push[[L0:[0-9]+]]=, 2{{$}}
; CHECK-NEXT: i32.call $push[[L1:[0-9]+]]=, .Lhas_ptr_arg_bitcast_invalid.1@FUNCTION, $pop[[L0]]{{$}}
; CHECK-NEXT: drop $pop[[L1]]{{$}}
; CHECK-NEXT: i32.const $push[[L0:[0-9]+]]=, 2{{$}}
; CHECK-NEXT: i32.call $push[[L1:[0-9]+]]=, has_ptr_arg@FUNCTION, $pop[[L0]]{{$}}
; CHECK-NEXT: drop $pop[[L1]]{{$}}
; CHECK-NEXT: i64.const $push[[L0:[0-9]+]]=, 3{{$}}
; CHECK-NEXT: i32.call $push[[L1:[0-9]+]]=, .Lhas_ptr_arg_bitcast_invalid@FUNCTION, $pop[[L0]]{{$}}
; CHECK-NEXT: drop $pop[[L1]]{{$}}
; CHECK-NEXT: end_function

; CHECK-LABEL: .Lhas_i64_arg_bitcast_invalid:
; CHECK-NEXT: .param i32
; CHECK-NEXT: .result i32
; CHECK-NEXT: unreachable
; CHECK-NEXT: end_function

; CHECK-LABEL: .Lhas_ptr_arg_bitcast_invalid:
; CHECK-NEXT: .param i64
; CHECK-NEXT: .result i32
; CHECK-NEXT: unreachable
; CHECK-NEXT: end_function

; CHECK-LABEL: .Lhas_ptr_arg_bitcast_invalid.1:
; CHECK-NEXT: .param i32
; CHECK-NEXT: .result i32
; CHECK-NEXT: unreachable
; CHECK-NEXT: end_function

0 comments on commit 41d7047

Please sign in to comment.