-
Notifications
You must be signed in to change notification settings - Fork 13.5k
[lld][WebAssembly] Add --no-growable-memory
#82890
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
✅ With the latest revision this PR passed the C/C++ code formatter. |
With this option, clients (Emscripten) can implement non-growable memories without knowing the amount of initial memory upfront.
3b115d2
to
5f52f34
Compare
@llvm/pr-subscribers-lld @llvm/pr-subscribers-lld-wasm Author: None (SingleAccretion) ChangesWe recently added However, in the process of implementing support for this in Emscripten (emscripten-core/emscripten#21071), we have realized that 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 Thus, the Emscripten I have also considered adding a boolean flag ( Closes #81932. Full diff: https://github.com/llvm/llvm-project/pull/82890.diff 7 Files Affected:
diff --git a/lld/docs/WebAssembly.rst b/lld/docs/WebAssembly.rst
index 3f554de46d38a7..ede78309ecfb1e 100644
--- a/lld/docs/WebAssembly.rst
+++ b/lld/docs/WebAssembly.rst
@@ -131,6 +131,10 @@ WebAssembly-specific options:
Initial size of the linear memory. Default: the sum of stack, static data and heap sizes.
+.. option:: --max-memory-growth=<value>
+
+ Maximum size of memory that can be allocated dynamically. Default: unlimited.
+
.. option:: --max-memory=<value>
Maximum size of the linear memory. Default: unlimited.
diff --git a/lld/test/wasm/data-layout.s b/lld/test/wasm/data-layout.s
index 2a447aad622167..6e6a6cde747e2f 100644
--- a/lld/test/wasm/data-layout.s
+++ b/lld/test/wasm/data-layout.s
@@ -103,6 +103,38 @@ local_struct_internal_ptr:
# CHECK-MAX-NEXT: Minimum: 0x2
# CHECK-MAX-NEXT: Maximum: 0x2
+# RUN: wasm-ld --no-entry --initial-memory=327680 --max-memory-growth=0 \
+# RUN: -o %t_max.wasm %t.hello32.o
+# RUN: obj2yaml %t_max.wasm | FileCheck %s -check-prefix=CHECK-MAX-GROWTH-ZERO
+
+# CHECK-MAX-GROWTH-ZERO: - Type: MEMORY
+# CHECK-MAX-GROWTH-ZERO-NEXT: Memories:
+# CHECK-MAX-GROWTH-ZERO: - Flags: [ HAS_MAX ]
+# CHECK-MAX-GROWTH-ZERO: Minimum: 0x5
+# CHECK-MAX-GROWTH-ZERO: Maximum: 0x5
+
+# RUN: wasm-ld --initial-memory=196608 --max-memory-growth=262144 \
+# RUN: --no-entry -o %t_max.wasm %t.hello32.o
+# RUN: obj2yaml %t_max.wasm | FileCheck %s -check-prefix=CHECK-MAX-GROWTH-SOME
+
+# CHECK-MAX-GROWTH-SOME: - Type: MEMORY
+# CHECK-MAX-GROWTH-SOME-NEXT: Memories:
+# CHECK-MAX-GROWTH-SOME: - Flags: [ HAS_MAX ]
+# CHECK-MAX-GROWTH-SOME: Minimum: 0x3
+# CHECK-MAX-GROWTH-SOME: Maximum: 0x7
+
+# RUN: not wasm-ld --max-memory-growth=131073 \
+# RUN: --no-entry -o %t_max.wasm %t.hello32.o 2>&1 \
+# RUN: | FileCheck %s --check-prefix CHECK-MAX-GROWTH-ALIGN-ERROR
+
+# CHECK-MAX-GROWTH-ALIGN-ERROR: maximum memory growth must be 65536-byte aligned
+
+# RUN: not wasm-ld --initial-memory=131072 --max-memory=262144 --max-memory-growth=131072 \
+# RUN: --no-entry -o %t_max.wasm %t.hello32.o 2>&1 \
+# RUN: | FileCheck %s --check-prefix CHECK-MAX-GROWTH-COMPAT-ERROR
+
+# CHECK-MAX-GROWTH-COMPAT-ERROR: --max-memory-growth and --max-memory are mutually exclusive
+
# 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
diff --git a/lld/test/wasm/large-memory.test b/lld/test/wasm/large-memory.test
index 5b737e41549630..bc39a65bb3311b 100644
--- a/lld/test/wasm/large-memory.test
+++ b/lld/test/wasm/large-memory.test
@@ -5,10 +5,16 @@ RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown %p/Inputs/start.s -o %
RUN: wasm-ld %t.o -o %t1.wasm --max-memory=2147483648
RUN: obj2yaml %t1.wasm | FileCheck %s --check-prefixes=CHECK,CHECK-2G
+RUN: wasm-ld %t.o -o %t1.wasm --initial-memory=131072 --max-memory-growth=2147352576
+RUN: obj2yaml %t1.wasm | FileCheck %s --check-prefixes=CHECK,CHECK-2G
+
; And also 4G of total memory
RUN: wasm-ld %t.o -o %t2.wasm --max-memory=4294967296
RUN: obj2yaml %t2.wasm | FileCheck %s --check-prefixes=CHECK,CHECK-4G
+RUN: wasm-ld %t.o -o %t2.wasm --initial-memory=131072 --max-memory-growth=4294836224
+RUN: obj2yaml %t2.wasm | FileCheck %s --check-prefixes=CHECK,CHECK-4G
+
CHECK: - Type: MEMORY
CHECK-NEXT: Memories:
CHECK-NEXT: - Flags: [ HAS_MAX ]
@@ -19,6 +25,8 @@ CHECK-4G-NEXT: Maximum: 0x10000
; Test error for more than 4G of memory
RUN: not wasm-ld %t.o -o %t3.wasm --initial-memory=4295032832 2>&1 | FileCheck %s --check-prefix INIT-ERROR
RUN: not wasm-ld %t.o -o %t4.wasm --max-memory=4295032832 2>&1 | FileCheck %s --check-prefix MAX-ERROR
+RUN: not wasm-ld %t.o -o %t4.wasm --initial-memory=131072 --max-memory-growth=4294901760 2>&1 | FileCheck %s --check-prefix MAX-GROWTH-ERROR
INIT-ERROR: initial memory too large, cannot be greater than 4294967296
MAX-ERROR: maximum memory too large, cannot be greater than 4294967296
+MAX-GROWTH-ERROR: maximum memory growth too large, cannot be greater than 4294836224
diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h
index 97c508bda6a1c3..70872685b40120 100644
--- a/lld/wasm/Config.h
+++ b/lld/wasm/Config.h
@@ -77,6 +77,7 @@ struct Configuration {
uint64_t globalBase;
uint64_t initialHeap;
uint64_t initialMemory;
+ uint64_t maxMemoryGrowth;
uint64_t maxMemory;
// 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
diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 635f19f78b15e6..87afdd223a707a 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -541,6 +541,7 @@ static void readConfigs(opt::InputArgList &args) {
config->globalBase = args::getInteger(args, OPT_global_base, 0);
config->initialHeap = args::getInteger(args, OPT_initial_heap, 0);
config->initialMemory = args::getInteger(args, OPT_initial_memory, 0);
+ config->maxMemoryGrowth = args::getInteger(args, OPT_max_memory_growth, -1);
config->maxMemory = args::getInteger(args, OPT_max_memory, 0);
config->zStackSize =
args::getZOptionValue(args, OPT_z, "stack-size", WasmPageSize);
diff --git a/lld/wasm/Options.td b/lld/wasm/Options.td
index 8190717cef63bb..ed3e014850278a 100644
--- a/lld/wasm/Options.td
+++ b/lld/wasm/Options.td
@@ -227,6 +227,9 @@ def initial_heap: JJ<"initial-heap=">,
def initial_memory: JJ<"initial-memory=">,
HelpText<"Initial size of the linear memory">;
+def max_memory_growth: JJ<"max-memory-growth=">,
+ HelpText<"Maximum size of dynamically allocatable linear memory">;
+
def max_memory: JJ<"max-memory=">,
HelpText<"Maximum size of the linear memory">;
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index d1a06c9ac9c2ae..261d633f11b745 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -473,6 +473,25 @@ void Writer::layoutMemory() {
WasmSym::heapEnd->setVA(memoryPtr);
}
+ if (config->maxMemory != 0 && config->maxMemoryGrowth != -1) {
+ // Erroring out here is simpler than defining precedence rules.
+ error("--max-memory-growth and --max-memory are mutually exclusive");
+ }
+
+ uint64_t maxMemory = 0;
+ if (config->maxMemoryGrowth != -1) {
+ if (config->maxMemoryGrowth !=
+ alignTo(config->maxMemoryGrowth, WasmPageSize))
+ error("maximum memory growth must be " + Twine(WasmPageSize) +
+ "-byte aligned");
+ uint64_t maxMaxMemoryGrowth = maxMemorySetting - memoryPtr;
+ if (config->maxMemoryGrowth > maxMaxMemoryGrowth)
+ error("maximum memory growth too large, cannot be greater than " +
+ Twine(maxMaxMemoryGrowth));
+
+ maxMemory = memoryPtr + config->maxMemoryGrowth;
+ }
+
if (config->maxMemory != 0) {
if (config->maxMemory != alignTo(config->maxMemory, WasmPageSize))
error("maximum memory must be " + Twine(WasmPageSize) + "-byte aligned");
@@ -481,20 +500,21 @@ void Writer::layoutMemory() {
if (config->maxMemory > maxMemorySetting)
error("maximum memory too large, cannot be greater than " +
Twine(maxMemorySetting));
+
+ maxMemory = config->maxMemory;
}
- // 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));
}
}
|
How about adding |
I'll make the change. To be honest, I just wanted an option name that suggests it being related to Edit: done. |
--max-memory-growth
--no-growable-memory
f6e6499
to
9bf7075
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm with one more comment
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.