[WebAssembly] Emit type signatures for declared functions

Under emscripten, C code can take the address of a function implemented
in Javascript (which is exposed via an import in wasm). Because imports
do not have linear memory address in wasm, we need to generate a thunk
to be the target of the indirect call; it call the import directly.

To make this possible, LLVM needs to emit the type signatures for these
functions, because they may not be called directly or referred to other
than where the address is taken.

This uses s new .s directive (.functype) which specifies the signature.

Differential Revision: http://reviews.llvm.org/D20891

Re-apply r271599 but instead of bailing with an error when a declared
function has multiple returns, replace it with a pointer argument. Also
add the test case I forgot to 'git add' last time around.

llvm-svn: 271703
This commit is contained in:
Derek Schuff 2016-06-03 18:34:36 +00:00
parent 96938d67b6
commit 5859a9ed80
4 changed files with 129 additions and 10 deletions

View File

@ -64,6 +64,16 @@ void WebAssemblyTargetAsmStreamer::emitLocal(ArrayRef<MVT> Types) {
void WebAssemblyTargetAsmStreamer::emitEndFunc() { OS << "\t.endfunc\n"; }
void WebAssemblyTargetAsmStreamer::emitIndirectFunctionType(
StringRef name, SmallVectorImpl<MVT> &SignatureVTs, size_t NumResults) {
OS << "\t.functype\t" << name;
if (NumResults == 0) OS << ", void";
for (auto Ty : SignatureVTs) {
OS << ", " << WebAssembly::TypeToString(Ty);
}
OS << "\n";
}
// FIXME: What follows is not the real binary encoding.
static void EncodeTypes(MCStreamer &Streamer, ArrayRef<MVT> Types) {

View File

@ -37,6 +37,12 @@ public:
virtual void emitLocal(ArrayRef<MVT> Types) = 0;
/// .endfunc
virtual void emitEndFunc() = 0;
/// .functype
virtual void emitIndirectFunctionType(StringRef name,
SmallVectorImpl<MVT> &SignatureVTs,
size_t NumResults) {
llvm_unreachable("emitIndirectFunctionType not implemented");
}
};
/// This part is for ascii assembly output
@ -50,6 +56,9 @@ public:
void emitResult(ArrayRef<MVT> Types) override;
void emitLocal(ArrayRef<MVT> Types) override;
void emitEndFunc() override;
void emitIndirectFunctionType(StringRef name,
SmallVectorImpl<MVT> &SignatureVTs,
size_t NumResults) override;
};
/// This part is for ELF object output

View File

@ -67,6 +67,7 @@ private:
// AsmPrinter Implementation.
//===------------------------------------------------------------------===//
void EmitEndOfAsmFile(Module &M) override;
void EmitJumpTableInfo() override;
void EmitConstantPool() override;
void EmitFunctionBodyStart() override;
@ -124,16 +125,6 @@ WebAssemblyTargetStreamer *WebAssemblyAsmPrinter::getTargetStreamer() {
//===----------------------------------------------------------------------===//
// WebAssemblyAsmPrinter Implementation.
//===----------------------------------------------------------------------===//
void WebAssemblyAsmPrinter::EmitConstantPool() {
assert(MF->getConstantPool()->getConstants().empty() &&
"WebAssembly disables constant pools");
}
void WebAssemblyAsmPrinter::EmitJumpTableInfo() {
// Nothing to do; jump tables are incorporated into the instruction stream.
}
static void ComputeLegalValueVTs(const Function &F, const TargetMachine &TM,
Type *Ty, SmallVectorImpl<MVT> &ValueVTs) {
const DataLayout &DL(F.getParent()->getDataLayout());
@ -150,6 +141,42 @@ static void ComputeLegalValueVTs(const Function &F, const TargetMachine &TM,
}
}
void WebAssemblyAsmPrinter::EmitEndOfAsmFile(Module &M) {
for (const auto &F : M) {
// Emit function type info for all undefined functions
if (F.isDeclarationForLinker() && !F.isIntrinsic()) {
SmallVector<MVT, 4> SignatureVTs;
ComputeLegalValueVTs(F, TM, F.getReturnType(), SignatureVTs);
size_t NumResults = SignatureVTs.size();
if (SignatureVTs.size() > 1) {
// WebAssembly currently can't lower returns of multiple values without
// demoting to sret (see WebAssemblyTargetLowering::CanLowerReturn). So
// replace multiple return values with a pointer parameter.
SignatureVTs.clear();
SignatureVTs.push_back(
MVT::getIntegerVT(M.getDataLayout().getPointerSizeInBits()));
NumResults = 0;
}
for (auto &Arg : F.args()) {
ComputeLegalValueVTs(F, TM, Arg.getType(), SignatureVTs);
}
getTargetStreamer()->emitIndirectFunctionType(F.getName(), SignatureVTs,
NumResults);
}
}
}
void WebAssemblyAsmPrinter::EmitConstantPool() {
assert(MF->getConstantPool()->getConstants().empty() &&
"WebAssembly disables constant pools");
}
void WebAssemblyAsmPrinter::EmitJumpTableInfo() {
// Nothing to do; jump tables are incorporated into the instruction stream.
}
void WebAssemblyAsmPrinter::EmitFunctionBodyStart() {
if (!MFI->getParams().empty())
getTargetStreamer()->emitParam(MFI->getParams());

View File

@ -0,0 +1,73 @@
; RUN: llc < %s -asm-verbose=false -disable-wasm-fallthrough-return-opt -verify-machineinstrs | FileCheck %s
; RUN: llc < %s -asm-verbose=false -disable-wasm-fallthrough-return-opt -verify-machineinstrs -fast-isel | FileCheck %s
; ModuleID = 'test/dot_s/indirect-import.c'
source_filename = "test/dot_s/indirect-import.c"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32"
%struct.big = type { float, double, i32 }
; Function Attrs: nounwind
; CHECK: bar:
define hidden i32 @bar() #0 {
entry:
%fd = alloca float (double)*, align 4
%vj = alloca void (i64)*, align 4
%v = alloca void ()*, align 4
%ijidf = alloca i32 (i64, i32, double, float)*, align 4
%vs = alloca void (%struct.big*)*, align 4
%s = alloca void (%struct.big*)*, align 4
; CHECK: i32.const {{.+}}=, extern_fd@FUNCTION
store float (double)* @extern_fd, float (double)** %fd, align 4
; CHECK: i32.const {{.+}}=, extern_vj@FUNCTION
store void (i64)* @extern_vj, void (i64)** %vj, align 4
%0 = load void (i64)*, void (i64)** %vj, align 4
call void %0(i64 1)
; CHECK: i32.const {{.+}}=, extern_v@FUNCTION
store void ()* @extern_v, void ()** %v, align 4
%1 = load void ()*, void ()** %v, align 4
call void %1()
; CHECK: i32.const {{.+}}=, extern_ijidf@FUNCTION
store i32 (i64, i32, double, float)* @extern_ijidf, i32 (i64, i32, double, float)** %ijidf, align 4
%2 = load i32 (i64, i32, double, float)*, i32 (i64, i32, double, float)** %ijidf, align 4
%call = call i32 %2(i64 1, i32 2, double 3.000000e+00, float 4.000000e+00)
; CHECK: i32.const {{.+}}=, extern_struct@FUNCTION
store void (%struct.big*)* @extern_struct, void (%struct.big*)** %vs, align 4
; CHECK: i32.const {{.+}}=, extern_sret@FUNCTION
store void (%struct.big*)* @extern_sret, void (%struct.big*)** %s, align 4
%3 = load float (double)*, float (double)** %fd, align 4
%4 = ptrtoint float (double)* %3 to i32
ret i32 %4
}
declare float @extern_fd(double) #1
declare void @extern_vj(i64) #1
declare void @extern_v() #1
declare i32 @extern_ijidf(i64, i32, double, float) #1
declare void @extern_struct(%struct.big* byval align 8) #1
declare void @extern_sret(%struct.big* sret) #1
declare i128 @extern_i128ret(i64) #1
attributes #0 = { nounwind "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="generic" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="generic" "unsafe-fp-math"="false" "use-soft-float"="false" }
; CHECK: .functype extern_fd, f32, f64
; CHECK: .functype extern_vj, void, i64
; CHECK: .functype extern_v, void
; CHECK: .functype extern_ijidf, i32, i64, i32, f64, f32
; CHECK: .functype extern_struct, void, i32
; CHECK: .functype extern_sret, void, i32
; CHECK: .functype extern_i128ret, void, i32, i64