forked from OSchip/llvm-project
[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:
parent
ca1d849cd6
commit
874eedd779
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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">;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue