[WebAssembly] Add export/import for function pointer table

This enables callback-style programming where the JavaScript environment
can call back into the Wasm environment using a function pointer
received from the module.

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

llvm-svn: 328643
This commit is contained in:
Nicholas Wilson 2018-03-27 17:38:51 +00:00
parent ca1d849cd6
commit 874eedd779
8 changed files with 95 additions and 7 deletions

View File

@ -0,0 +1,19 @@
# RUN: llc -filetype=obj %p/Inputs/start.ll -o %t.start.o
# RUN: wasm-ld --check-signatures --export-table -o %t.wasm %t.start.o
# RUN: obj2yaml %t.wasm | FileCheck %s
# Verify the --export-table flag creates a table export
# CHECK: - Type: TABLE
# CHECK-NEXT: Tables:
# CHECK-NEXT: - ElemType: ANYFUNC
# CHECK-NEXT: Limits:
# CHECK-NEXT: Flags: [ HAS_MAX ]
# CHECK-NEXT: Initial: 0x00000001
# CHECK-NEXT: Maximum: 0x00000001
# CHECK-NEXT: - Type:
# CHECK: - Type: EXPORT
# CHECK-NEXT: Exports:
# CHECK: - Name: __indirect_function_table
# CHECK-NEXT: Kind: TABLE
# CHECK-NEXT: Index: 0

View File

@ -0,0 +1,18 @@
# RUN: llc -filetype=obj %p/Inputs/start.ll -o %t.start.o
# RUN: wasm-ld --check-signatures --import-table -o %t.wasm %t.start.o
# RUN: obj2yaml %t.wasm | FileCheck %s
# Verify the --import-table flag creates a table import
# CHECK: - Type: IMPORT
# CHECK-NEXT: Imports:
# CHECK-NEXT: - Module: env
# CHECK-NEXT: Field: __indirect_function_table
# CHECK-NEXT: Kind: TABLE
# CHECK-NEXT: Table:
# CHECK-NEXT: ElemType: ANYFUNC
# CHECK-NEXT: Limits:
# CHECK-NEXT: Flags: [ HAS_MAX ]
# CHECK-NEXT: Initial: 0x00000001
# CHECK-NEXT: Maximum: 0x00000001

View File

@ -17,6 +17,8 @@
namespace lld {
namespace wasm {
enum class ExposeAs { IMPORT, EXPORT, NONE };
struct Configuration {
bool AllowUndefined;
bool CheckSignatures;
@ -27,6 +29,7 @@ struct Configuration {
bool Relocatable;
bool StripAll;
bool StripDebug;
ExposeAs Table;
uint32_t GlobalBase;
uint32_t InitialMemory;
uint32_t MaxMemory;

View File

@ -216,6 +216,15 @@ static StringRef getEntry(opt::InputArgList &Args, StringRef Default) {
return Arg->getValue();
}
static ExposeAs getExpose(opt::InputArgList &Args, unsigned Import,
unsigned Export) {
auto *Arg = Args.getLastArg(Import, Export);
if (!Arg)
return ExposeAs::NONE;
return Arg->getOption().getID() == Import ? ExposeAs::IMPORT
: ExposeAs::EXPORT;
}
static const uint8_t UnreachableFn[] = {
0x03 /* ULEB length */, 0x00 /* ULEB num locals */,
0x00 /* opcode unreachable */, 0x0b /* opcode end */
@ -296,6 +305,7 @@ void LinkerDriver::link(ArrayRef<const char *> ArgsArr) {
Config->SearchPaths = args::getStrings(Args, OPT_L);
Config->StripAll = Args.hasArg(OPT_strip_all);
Config->StripDebug = Args.hasArg(OPT_strip_debug);
Config->Table = getExpose(Args, OPT_import_table, OPT_export_table);
errorHandler().Verbose = Args.hasArg(OPT_verbose);
ThreadsEnabled = Args.hasFlag(OPT_threads, OPT_no_threads, true);

View File

@ -103,12 +103,18 @@ defm check_signatures: B<"check-signatures",
defm export: Eq<"export">,
HelpText<"Force a symbol to be exported">;
def export_table: F<"export-table">,
HelpText<"Export function table to the environment">;
def global_base: J<"global-base=">,
HelpText<"Where to start to place global data">;
def import_memory: F<"import-memory">,
HelpText<"Import memory from the environment">;
def import_table: F<"import-table">,
HelpText<"Import function table from the environment">;
def initial_memory: J<"initial-memory=">,
HelpText<"Initial size of the linear memory">;

View File

@ -39,6 +39,7 @@ using namespace lld::wasm;
static constexpr int kStackAlignment = 16;
static constexpr int kInitialTableOffset = 1;
static constexpr const char *kFunctionTableName = "__indirect_function_table";
namespace {
@ -126,6 +127,8 @@ void Writer::createImportSection() {
uint32_t NumImports = ImportedSymbols.size();
if (Config->ImportMemory)
++NumImports;
if (Config->Table == ExposeAs::IMPORT)
++NumImports;
if (NumImports == 0)
return;
@ -149,6 +152,17 @@ void Writer::createImportSection() {
writeImport(OS, Import);
}
if (Config->Table == ExposeAs::IMPORT) {
uint32_t TableSize = kInitialTableOffset + IndirectFunctions.size();
WasmImport Import;
Import.Module = "env";
Import.Field = kFunctionTableName;
Import.Kind = WASM_EXTERNAL_TABLE;
Import.Table.ElemType = WASM_TYPE_ANYFUNC;
Import.Table.Limits = {WASM_LIMITS_FLAG_HAS_MAX, TableSize, TableSize};
writeImport(OS, Import);
}
for (const Symbol *Sym : ImportedSymbols) {
WasmImport Import;
Import.Module = "env";
@ -222,8 +236,11 @@ void Writer::createGlobalSection() {
}
void Writer::createTableSection() {
// Always output a table section, even if there are no indirect calls.
// There are two reasons for this:
if (Config->Table == ExposeAs::IMPORT)
return;
// Always output a table section (or table import), even if there are no
// indirect calls. There are two reasons for this:
// 1. For executables it is useful to have an empty table slot at 0
// which can be filled with a null function call handler.
// 2. If we don't do this, any program that contains a call_indirect but
@ -236,16 +253,16 @@ void Writer::createTableSection() {
raw_ostream &OS = Section->getStream();
writeUleb128(OS, 1, "table count");
writeU8(OS, WASM_TYPE_ANYFUNC, "table type");
writeUleb128(OS, WASM_LIMITS_FLAG_HAS_MAX, "table flags");
writeUleb128(OS, TableSize, "table initial size");
writeUleb128(OS, TableSize, "table max size");
WasmLimits Limits = {WASM_LIMITS_FLAG_HAS_MAX, TableSize, TableSize};
writeTableType(OS, WasmTable{WASM_TYPE_ANYFUNC, Limits});
}
void Writer::createExportSection() {
bool ExportMemory = !Config->Relocatable && !Config->ImportMemory;
bool ExportTable = !Config->Relocatable && Config->Table == ExposeAs::EXPORT;
uint32_t NumExports = (ExportMemory ? 1 : 0) + ExportedSymbols.size();
uint32_t NumExports =
(ExportMemory ? 1 : 0) + (ExportTable ? 1 : 0) + ExportedSymbols.size();
if (!NumExports)
return;
@ -256,6 +273,8 @@ void Writer::createExportSection() {
if (ExportMemory)
writeExport(OS, {"memory", WASM_EXTERNAL_MEMORY, 0});
if (ExportTable)
writeExport(OS, {kFunctionTableName, WASM_EXTERNAL_TABLE, 0});
unsigned FakeGlobalIndex = NumImportedGlobals + InputGlobals.size();

View File

@ -126,6 +126,11 @@ void wasm::writeGlobal(raw_ostream &OS, const WasmGlobal &Global) {
writeInitExpr(OS, Global.InitExpr);
}
void wasm::writeTableType(raw_ostream &OS, const llvm::wasm::WasmTable &Type) {
writeU8(OS, WASM_TYPE_ANYFUNC, "table type");
writeLimits(OS, Type.Limits);
}
void wasm::writeImport(raw_ostream &OS, const WasmImport &Import) {
writeStr(OS, Import.Module, "import module name");
writeStr(OS, Import.Field, "import field name");
@ -140,6 +145,9 @@ void wasm::writeImport(raw_ostream &OS, const WasmImport &Import) {
case WASM_EXTERNAL_MEMORY:
writeLimits(OS, Import.Memory);
break;
case WASM_EXTERNAL_TABLE:
writeTableType(OS, Import.Table);
break;
default:
fatal("unsupported import type: " + Twine(Import.Kind));
}
@ -158,6 +166,9 @@ void wasm::writeExport(raw_ostream &OS, const WasmExport &Export) {
case WASM_EXTERNAL_MEMORY:
writeUleb128(OS, Export.Index, "memory index");
break;
case WASM_EXTERNAL_TABLE:
writeUleb128(OS, Export.Index, "table index");
break;
default:
fatal("unsupported export type: " + Twine(Export.Kind));
}

View File

@ -47,6 +47,8 @@ void writeGlobalType(raw_ostream &OS, const llvm::wasm::WasmGlobalType &Type);
void writeGlobal(raw_ostream &OS, const llvm::wasm::WasmGlobal &Global);
void writeTableType(raw_ostream &OS, const llvm::wasm::WasmTable &Type);
void writeImport(raw_ostream &OS, const llvm::wasm::WasmImport &Import);
void writeExport(raw_ostream &OS, const llvm::wasm::WasmExport &Export);