Index: test/wasm/export-table.test =================================================================== --- test/wasm/export-table.test +++ test/wasm/export-table.test @@ -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 Index: test/wasm/import-table.test =================================================================== --- test/wasm/import-table.test +++ test/wasm/import-table.test @@ -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 + Index: wasm/Config.h =================================================================== --- wasm/Config.h +++ wasm/Config.h @@ -17,6 +17,8 @@ namespace lld { namespace wasm { +enum class ExposeAs { IMPORT, EXPORT, NONE }; + struct Configuration { bool AllowUndefined; bool CheckSignatures; @@ -27,6 +29,7 @@ bool Relocatable; bool StripAll; bool StripDebug; + ExposeAs Table; uint32_t GlobalBase; uint32_t InitialMemory; uint32_t MaxMemory; Index: wasm/Driver.cpp =================================================================== --- wasm/Driver.cpp +++ wasm/Driver.cpp @@ -216,6 +216,15 @@ 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 @@ 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); Index: wasm/Options.td =================================================================== --- wasm/Options.td +++ wasm/Options.td @@ -103,12 +103,18 @@ 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">; Index: wasm/Writer.cpp =================================================================== --- wasm/Writer.cpp +++ wasm/Writer.cpp @@ -39,6 +39,7 @@ static constexpr int kStackAlignment = 16; static constexpr int kInitialTableOffset = 1; +static constexpr const char *kFunctionTableName = "__indirect_function_table"; namespace { @@ -126,6 +127,8 @@ uint32_t NumImports = ImportedSymbols.size(); if (Config->ImportMemory) ++NumImports; + if (Config->Table == ExposeAs::IMPORT) + ++NumImports; if (NumImports == 0) return; @@ -149,6 +152,17 @@ 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::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 @@ 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 @@ if (ExportMemory) writeExport(OS, {"memory", WASM_EXTERNAL_MEMORY, 0}); + if (ExportTable) + writeExport(OS, {kFunctionTableName, WASM_EXTERNAL_TABLE, 0}); unsigned FakeGlobalIndex = NumImportedGlobals + InputGlobals.size(); Index: wasm/WriterUtils.h =================================================================== --- wasm/WriterUtils.h +++ wasm/WriterUtils.h @@ -47,6 +47,8 @@ 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); Index: wasm/WriterUtils.cpp =================================================================== --- wasm/WriterUtils.cpp +++ wasm/WriterUtils.cpp @@ -126,6 +126,11 @@ 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 @@ 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 @@ 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)); }