Skip to content

Commit

Permalink
[lld][WebAssembly] Add --no-growable-memory (#82890)
Browse files Browse the repository at this point in the history
We recently added `--initial-heap` - an option that allows one to up the
initial memory size without the burden of having to know exactly how
much is needed.

However, in the process of implementing support for this in Emscripten
(emscripten-core/emscripten#21071), we have
realized that `--initial-heap` cannot support the use-case of
non-growable memories by itself, since with it we don't know what to set
`--max-memory` to.

We have thus agreed to move the above work forward by introducing
another option to the linker (see
emscripten-core/emscripten#21071 (comment)),
one that would allow users to explicitly specify they want a
non-growable memory.

This change does this by introducing `--no-growable-memory`: an option
that is mutally exclusive with `--max-memory` (for simplicity - we can
also decide that it should override or be overridable by `--max-memory`.
In Emscripten a similar mix of options results in `--no-growable-memory`
taking precedence). The option specifies that the maximum memory size
should be set to the initial memory size, effectively disallowing memory
growth.

Closes #81932.
  • Loading branch information
SingleAccretion authored Feb 25, 2024
1 parent 8dfc023 commit cb4f94d
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 12 deletions.
4 changes: 4 additions & 0 deletions lld/docs/WebAssembly.rst
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ WebAssembly-specific options:

Maximum size of the linear memory. Default: unlimited.

.. option:: --no-growable-memory

Set maximum size of the linear memory to its initial size, disallowing memory growth.

By default the function table is neither imported nor exported, but defined
for internal use only.

Expand Down
16 changes: 16 additions & 0 deletions lld/test/wasm/data-layout.s
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,22 @@ local_struct_internal_ptr:
# CHECK-MAX-NEXT: Minimum: 0x2
# CHECK-MAX-NEXT: Maximum: 0x2

# RUN: wasm-ld --no-entry --initial-memory=327680 --no-growable-memory \
# RUN: -o %t_max.wasm %t.hello32.o
# RUN: obj2yaml %t_max.wasm | FileCheck %s -check-prefix=CHECK-NO-GROWTH

# CHECK-NO-GROWTH: - Type: MEMORY
# CHECK-NO-GROWTH-NEXT: Memories:
# CHECK-NO-GROWTH-NEXT: - Flags: [ HAS_MAX ]
# CHECK-NO-GROWTH-NEXT: Minimum: 0x5
# CHECK-NO-GROWTH-NEXT: Maximum: 0x5

# RUN: not wasm-ld --max-memory=262144 --no-growable-memory \
# RUN: --no-entry -o %t_max.wasm %t.hello32.o 2>&1 \
# RUN: | FileCheck %s --check-prefix CHECK-NO-GROWTH-COMPAT-ERROR

# CHECK-NO-GROWTH-COMPAT-ERROR: --max-memory is incompatible with --no-growable-memory

# RUN: wasm-ld -no-gc-sections --allow-undefined --no-entry --shared-memory \
# RUN: --features=atomics,bulk-memory --initial-memory=131072 \
# RUN: --max-memory=131072 -o %t_max.wasm %t32.o %t.hello32.o
Expand Down
1 change: 1 addition & 0 deletions lld/wasm/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ struct Configuration {
uint64_t initialHeap;
uint64_t initialMemory;
uint64_t maxMemory;
bool noGrowableMemory;
// The table offset at which to place function addresses. We reserve zero
// for the null function pointer. This gets set to 1 for executables and 0
// for shared libraries (since they always added to a dynamic offset at
Expand Down
6 changes: 6 additions & 0 deletions lld/wasm/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -542,9 +542,15 @@ static void readConfigs(opt::InputArgList &args) {
config->initialHeap = args::getInteger(args, OPT_initial_heap, 0);
config->initialMemory = args::getInteger(args, OPT_initial_memory, 0);
config->maxMemory = args::getInteger(args, OPT_max_memory, 0);
config->noGrowableMemory = args.hasArg(OPT_no_growable_memory);
config->zStackSize =
args::getZOptionValue(args, OPT_z, "stack-size", WasmPageSize);

if (config->maxMemory != 0 && config->noGrowableMemory) {
// Erroring out here is simpler than defining precedence rules.
error("--max-memory is incompatible with --no-growable-memory");
}

// Default value of exportDynamic depends on `-shared`
config->exportDynamic =
args.hasFlag(OPT_export_dynamic, OPT_no_export_dynamic, config->shared);
Expand Down
3 changes: 3 additions & 0 deletions lld/wasm/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,9 @@ def initial_memory: JJ<"initial-memory=">,
def max_memory: JJ<"max-memory=">,
HelpText<"Maximum size of the linear memory">;

def no_growable_memory: FF<"no-growable-memory">,
HelpText<"Set maximum size of the linear memory to its initial size">;

def no_entry: FF<"no-entry">,
HelpText<"Do not output any entry point">;

Expand Down
28 changes: 16 additions & 12 deletions lld/wasm/Writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ void Writer::layoutMemory() {
WasmSym::heapEnd->setVA(memoryPtr);
}

uint64_t maxMemory = 0;
if (config->maxMemory != 0) {
if (config->maxMemory != alignTo(config->maxMemory, WasmPageSize))
error("maximum memory must be " + Twine(WasmPageSize) + "-byte aligned");
Expand All @@ -481,20 +482,23 @@ void Writer::layoutMemory() {
if (config->maxMemory > maxMemorySetting)
error("maximum memory too large, cannot be greater than " +
Twine(maxMemorySetting));

maxMemory = config->maxMemory;
} else if (config->noGrowableMemory) {
maxMemory = memoryPtr;
}

// Check max if explicitly supplied or required by shared memory
if (config->maxMemory != 0 || config->sharedMemory) {
uint64_t max = config->maxMemory;
if (max == 0) {
// If no maxMemory config was supplied but we are building with
// shared memory, we need to pick a sensible upper limit.
if (ctx.isPic)
max = maxMemorySetting;
else
max = memoryPtr;
}
out.memorySec->maxMemoryPages = max / WasmPageSize;
// If no maxMemory config was supplied but we are building with
// shared memory, we need to pick a sensible upper limit.
if (config->sharedMemory && maxMemory == 0) {
if (ctx.isPic)
maxMemory = maxMemorySetting;
else
maxMemory = memoryPtr;
}

if (maxMemory != 0) {
out.memorySec->maxMemoryPages = maxMemory / WasmPageSize;
log("mem: max pages = " + Twine(out.memorySec->maxMemoryPages));
}
}
Expand Down

0 comments on commit cb4f94d

Please sign in to comment.