[lld] [lld][WebAssembly] Add `--max-memory-growth` (PR #82890)

via llvm-commits llvm-commits at lists.llvm.org
Sat Feb 24 09:09:05 PST 2024


https://github.com/SingleAccretion created https://github.com/llvm/llvm-project/pull/82890

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 we need.

However, in the process of implementing support for this in Emscripten (https://github.com/emscripten-core/emscripten/pull/21071), we have realized that `--initial-heap` cannot support the use-case of non-growable memories alone, 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 https://github.com/emscripten-core/emscripten/pull/21071#discussion_r1491755616), one that would allow users to specify they want a non-growable memory.

This change does this by introducing `--max-memory-growth`: 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 `--max-memory-growth` taking precedence). The option specifies how many pages will be dynamically allocatable: in the final WASM file, it will result in a `Maximum` field that is equal to `Minimum + --max-memory-growth`.

Thus, the Emscripten `-sALLOW_MEMORY_GROWTH=0` behavior will be implementable by simply passing `--max-memory-growth=0` (and not passing `--max-memory`).

I have also considered adding a boolean flag (`--no-memory-growth`), but this more general approach looks more consistent with the existing layout options at no real complexity cost.

Closes #81932.

>From 3b115d2d04336f7b5da5d825a3c9d042f2a21f45 Mon Sep 17 00:00:00 2001
From: SingleAccretion <AccretionMail at yandex.ru>
Date: Sat, 24 Feb 2024 19:51:06 +0300
Subject: [PATCH] [lld][WebAssembly] Add "--maximum-memory-growth"

With this option, clients (Emscripten) can implement non-growable
memories without knowing the amount of initial memory upfront.
---
 lld/docs/WebAssembly.rst        |  4 ++++
 lld/test/wasm/data-layout.s     | 32 +++++++++++++++++++++++++
 lld/test/wasm/large-memory.test |  8 +++++++
 lld/wasm/Config.h               |  1 +
 lld/wasm/Driver.cpp             |  1 +
 lld/wasm/Options.td             |  3 +++
 lld/wasm/Writer.cpp             | 42 +++++++++++++++++++++++----------
 7 files changed, 79 insertions(+), 12 deletions(-)

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..0aded3c5bb4831 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -473,6 +473,23 @@ 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 +498,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));
   }
 }



More information about the llvm-commits mailing list