[wasm-ld] Allow importing/exporting the output module's memory with arbitrary names

This adds an `--export-memory` option to wasm-ld which allows passing
a name to give to the exported memory, and extends `--import-memory` to
allow passing a <module>,<name> pair specifying where the memory should
be imported from.

This is based on https://reviews.llvm.org/D131376, with the main
difference being that it only supports exporting memory by one name.

Differential Revision: https://reviews.llvm.org/D135898
This commit is contained in:
Dan Gohman 2022-10-04 12:50:10 -07:00
parent a3a9fffea1
commit d4c8a0edca
9 changed files with 147 additions and 13 deletions

View File

@ -0,0 +1,88 @@
# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown %p/Inputs/start.s -o %t.start.o
# RUN: wasm-ld --export-memory=foo -o %t.wasm %t.start.o
# RUN: obj2yaml %t.wasm | FileCheck %s
# Verify that the --export-memory=<name> option changes the exported name of the module's memory
# CHECK: - Type: EXPORT
# CHECK-NEXT: Exports:
# CHECK-NEXT: - Name: foo
# CHECK-NEXT: Kind: MEMORY
# CHECK-NEXT: Index: 0
# CHECK-NEXT: - Name: _start
# CHECK-NEXT: Kind: FUNCTION
# CHECK-NEXT: Index: 0
# CHECK-NEXT: - Type:
# RUN:wasm-ld --export-memory --export-memory -o %t.unnamed.wasm %t.start.o
# RUN: obj2yaml %t.unnamed.wasm | FileCheck -check-prefix=CHECK-UNNAMED %s
# Verify that the --export-memory option without a parameter exports the memory
# as "memory"
# CHECK-UNNAMED: - Type: EXPORT
# CHECK-UNNAMED-NEXT: Exports:
# CHECK-UNNAMED-NEXT: - Name: memory
# CHECK-UNNAMED-NEXT: Kind: MEMORY
# CHECK-UNNAMED-NEXT: Index: 0
# CHECK-UNNAMED-NEXT: - Name: _start
# CHECK-UNNAMED-NEXT: Kind: FUNCTION
# CHECK-UNNAMED-NEXT: Index: 0
# CHECK-UNNAMED-NEXT: - Type:
# RUN:wasm-ld --export-memory=foo --export-memory=foo -o %t.duplicate.wasm %t.start.o
# RUN: obj2yaml %t.duplicate.wasm | FileCheck -check-prefix=CHECK-DUPLICATE %s
# Verify that passing --export-memory with the same name twice works
# CHECK-DUPLICATE: - Type: EXPORT
# CHECK-DUPLICATE-NEXT: Exports:
# CHECK-DUPLICATE-NEXT: - Name: foo
# CHECK-DUPLICATE-NEXT: Kind: MEMORY
# CHECK-DUPLICATE-NEXT: Index: 0
# CHECK-DUPLICATE-NEXT: - Name: _start
# CHECK-DUPLICATE-NEXT: Kind: FUNCTION
# CHECK-DUPLICATE-NEXT: Index: 0
# CHECK-DUPLICATE-NEXT: - Type:
# RUN:wasm-ld --import-memory=foo,bar -o %t.import.wasm %t.start.o
# RUN: obj2yaml %t.import.wasm | FileCheck -check-prefix=CHECK-IMPORT %s
# Verify that memory imports can be renamed, and that no memory is exported by
# default when memory is being imported
# CHECK-IMPORT: - Type: IMPORT
# CHECK-IMPORT-NEXT: Imports:
# CHECK-IMPORT-NEXT: - Module: foo
# CHECK-IMPORT-NEXT: Field: bar
# CHECK-IMPORT-NEXT: Kind: MEMORY
# CHECK-IMPORT-NEXT: Memory:
# CHECK-IMPORT-NEXT: Minimum: 0x2
# CHECK-IMPORT: - Type: EXPORT
# CHECK-IMPORT-NEXT: Exports:
# CHECK-IMPORT-NEXT: - Name: _start
# CHECK-IMPORT-NEXT: Kind: FUNCTION
# CHECK-IMPORT-NEXT: Index: 0
# CHECK-IMPORT-NEXT: - Type:
# RUN:wasm-ld --import-memory=foo,bar --export-memory=qux -o %t.both.wasm %t.start.o
# RUN: obj2yaml %t.both.wasm | FileCheck -check-prefix=CHECK-BOTH %s
# Verify that memory can be both imported and exported from a module
# CHECK-BOTH: - Type: IMPORT
# CHECK-BOTH-NEXT: Imports:
# CHECK-BOTH-NEXT: - Module: foo
# CHECK-BOTH-NEXT: Field: bar
# CHECK-BOTH-NEXT: Kind: MEMORY
# CHECK-BOTH-NEXT: Memory:
# CHECK-BOTH-NEXT: Minimum: 0x2
# CHECK-BOTH: - Type: EXPORT
# CHECK-BOTH-NEXT: Exports:
# CHECK-BOTH-NEXT: - Name: qux
# CHECK-BOTH-NEXT: Kind: MEMORY
# CHECK-BOTH-NEXT: Index: 0
# CHECK-BOTH-NEXT: - Name: _start
# CHECK-BOTH-NEXT: Kind: FUNCTION
# CHECK-BOTH-NEXT: Index: 0
# CHECK-BOTH-NEXT: - Type:

View File

@ -39,7 +39,8 @@ struct Configuration {
bool extendedConst;
bool growableTable;
bool gcSections;
bool importMemory;
llvm::Optional<std::pair<llvm::StringRef, llvm::StringRef>> memoryImport;
llvm::Optional<llvm::StringRef> memoryExport;
bool sharedMemory;
bool importTable;
bool importUndefined;

View File

@ -393,7 +393,27 @@ static void readConfigs(opt::InputArgList &args) {
config->exportAll = args.hasArg(OPT_export_all);
config->exportTable = args.hasArg(OPT_export_table);
config->growableTable = args.hasArg(OPT_growable_table);
config->importMemory = args.hasArg(OPT_import_memory);
if (args.hasArg(OPT_import_memory_with_name)) {
config->memoryImport =
args.getLastArgValue(OPT_import_memory_with_name).split(",");
} else if (args.hasArg(OPT_import_memory)) {
config->memoryImport =
std::pair<llvm::StringRef, llvm::StringRef>(defaultModule, memoryName);
} else {
config->memoryImport =
llvm::Optional<std::pair<llvm::StringRef, llvm::StringRef>>();
}
if (args.hasArg(OPT_export_memory_with_name)) {
config->memoryExport =
args.getLastArgValue(OPT_export_memory_with_name);
} else if (args.hasArg(OPT_export_memory)) {
config->memoryExport = memoryName;
} else {
config->memoryExport = llvm::Optional<llvm::StringRef>();
}
config->sharedMemory = args.hasArg(OPT_shared_memory);
config->importTable = args.hasArg(OPT_import_table);
config->importUndefined = args.hasArg(OPT_import_undefined);
@ -510,9 +530,21 @@ static void setConfigs() {
}
if (config->shared) {
config->importMemory = true;
if (config->memoryExport.hasValue()) {
error("--export-memory is incompatible with --shared");
}
if (!config->memoryImport.hasValue()) {
config->memoryImport =
std::pair<llvm::StringRef, llvm::StringRef>(defaultModule, memoryName);
}
config->importUndefined = true;
}
// If neither export-memory nor import-memory is specified, default to
// exporting memory under its default name.
if (!config->memoryExport.hasValue() && !config->memoryImport.hasValue()) {
config->memoryExport = memoryName;
}
}
// Some command line options or some combinations of them are not allowed.

View File

@ -189,7 +189,15 @@ def global_base: JJ<"global-base=">,
HelpText<"Where to start to place global data">;
def import_memory: FF<"import-memory">,
HelpText<"Import memory from the environment">;
HelpText<"Import the module's memory from the default module of \"env\" with the name \"memory\".">;
def import_memory_with_name: JJ<"import-memory=">,
HelpText<"Import the module's memory from the passed module with the passed name.">,
MetaVarName<"<module>,<name>">;
def export_memory: FF<"export-memory">,
HelpText<"Export the module's memory with the default name of \"memory\"">;
def export_memory_with_name: JJ<"export-memory=">,
HelpText<"Export the module's memory with the passed name">;
def shared_memory: FF<"shared-memory">,
HelpText<"Use shared linear memory">;

View File

@ -457,6 +457,7 @@ void printTraceSymbol(Symbol *sym) {
const char *defaultModule = "env";
const char *functionTableName = "__indirect_function_table";
const char *memoryName = "memory";
} // namespace wasm
} // namespace lld

View File

@ -26,6 +26,9 @@ extern const char *defaultModule;
// The name under which to import or export the wasm table.
extern const char *functionTableName;
// The name under which to import or export the wasm memory.
extern const char *memoryName;
using llvm::wasm::WasmSymbolType;
class InputFile;

View File

@ -162,7 +162,7 @@ void TypeSection::writeBody() {
uint32_t ImportSection::getNumImports() const {
assert(isSealed);
uint32_t numImports = importedSymbols.size() + gotSymbols.size();
if (config->importMemory)
if (config->memoryImport.hasValue())
++numImports;
return numImports;
}
@ -234,10 +234,10 @@ void ImportSection::writeBody() {
bool is64 = config->is64.value_or(false);
if (config->importMemory) {
if (config->memoryImport) {
WasmImport import;
import.Module = defaultModule;
import.Field = "memory";
import.Module = config->memoryImport.getValue().first;
import.Field = config->memoryImport.getValue().second;
import.Kind = WASM_EXTERNAL_MEMORY;
import.Memory.Flags = 0;
import.Memory.Minimum = out.memorySec->numMemoryPages;

View File

@ -230,7 +230,7 @@ class MemorySection : public SyntheticSection {
public:
MemorySection() : SyntheticSection(llvm::wasm::WASM_SEC_MEMORY) {}
bool isNeeded() const override { return !config->importMemory; }
bool isNeeded() const override { return !config->memoryImport.hasValue(); }
void writeBody() override;
uint64_t numMemoryPages = 0;

View File

@ -577,7 +577,7 @@ done:
// memory is not being imported then we can assume its zero initialized.
// In the case the memory is imported, and we can use the memory.fill
// instruction, then we can also avoid including the segments.
if (config->importMemory && !allowed.count("bulk-memory"))
if (config->memoryImport.hasValue() && !allowed.count("bulk-memory"))
config->emitBssSegments = true;
if (allowed.count("extended-const"))
@ -671,9 +671,10 @@ void Writer::calculateExports() {
if (config->relocatable)
return;
if (!config->relocatable && !config->importMemory)
if (!config->relocatable && config->memoryExport.hasValue()) {
out.exportSec->exports.push_back(
WasmExport{"memory", WASM_EXTERNAL_MEMORY, 0});
WasmExport{*config->memoryExport, WASM_EXTERNAL_MEMORY, 0});
}
unsigned globalIndex =
out.importSec->getNumImportedGlobals() + out.globalSec->numGlobals();
@ -1007,7 +1008,7 @@ static void createFunction(DefinedFunction *func, StringRef bodyContent) {
bool Writer::needsPassiveInitialization(const OutputSegment *segment) {
// If bulk memory features is supported then we can perform bss initialization
// (via memory.fill) during `__wasm_init_memory`.
if (config->importMemory && !segment->requiredInBinary())
if (config->memoryImport.hasValue() && !segment->requiredInBinary())
return true;
return segment->initFlags & WASM_DATA_SEGMENT_IS_PASSIVE;
}