Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 79262ff

Browse filesBrowse files
joyeecheungRafaelGSS
authored andcommitted
src: do not enable wasm trap handler if there's not enough vmem
PR-URL: #62132 Refs: microsoft/vscode#251777 Refs: https://chromium-review.googlesource.com/c/v8/v8/+/7638233 Reviewed-By: Aditi Singh <aditisingh1400@gmail.com>
1 parent ebfaf25 commit 79262ff
Copy full SHA for 79262ff

10 files changed

+143-51Lines changed: 143 additions & 51 deletions
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎doc/api/cli.md‎

Copy file name to clipboardExpand all lines: doc/api/cli.md
+30-34Lines changed: 30 additions & 34 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -754,40 +754,36 @@ vm.measureMemory();
754754
added:
755755
- v22.2.0
756756
- v20.15.0
757-
-->
758-
759-
By default, Node.js enables trap-handler-based WebAssembly bound
760-
checks. As a result, V8 does not need to insert inline bound checks
761-
in the code compiled from WebAssembly which may speed up WebAssembly
762-
execution significantly, but this optimization requires allocating
763-
a big virtual memory cage (currently 10GB). If the Node.js process
764-
does not have access to a large enough virtual memory address space
765-
due to system configurations or hardware limitations, users won't
766-
be able to run any WebAssembly that involves allocation in this
767-
virtual memory cage and will see an out-of-memory error.
768-
769-
```console
770-
$ ulimit -v 5000000
771-
$ node -p "new WebAssembly.Memory({ initial: 10, maximum: 100 });"
772-
[eval]:1
773-
new WebAssembly.Memory({ initial: 10, maximum: 100 });
774-
^
775-
776-
RangeError: WebAssembly.Memory(): could not allocate memory
777-
at [eval]:1:1
778-
at runScriptInThisContext (node:internal/vm:209:10)
779-
at node:internal/process/execution:118:14
780-
at [eval]-wrapper:6:24
781-
at runScript (node:internal/process/execution:101:62)
782-
at evalScript (node:internal/process/execution:136:3)
783-
at node:internal/main/eval_string:49:3
784-
785-
```
786-
787-
`--disable-wasm-trap-handler` disables this optimization so that
788-
users can at least run WebAssembly (with less optimal performance)
789-
when the virtual memory address space available to their Node.js
790-
process is lower than what the V8 WebAssembly memory cage needs.
757+
changes:
758+
- version:
759+
- REPLACEME
760+
pr-url: https://github.com/nodejs/node/pull/62132
761+
description: Node.js now automatically disables the trap handler when there is not
762+
enough virtual memory available at startup to allocate one cage.
763+
-->
764+
765+
Node.js enables V8's trap-handler-based WebAssembly bound checks on 64-bit platforms,
766+
which significantly improves WebAssembly performance by eliminating the need for
767+
inline bound checks. This optimization requires allocating a large virtual memory
768+
cage per WebAssembly memory instance (currently typically 8GB for 32-bit WebAssembly memory,
769+
16GB for 64-bit WebAssembly memory) to trap out-of-bound accesses. On most 64-bit
770+
platforms, the virtual memory address space is usually large enough (around 128TB)
771+
to accommodate typical WebAssembly usages, but if the machine has manual limits
772+
on virtual memory (e.g. through `ulimit -v`), WebAssembly memory allocation is
773+
more likely to fail with `WebAssembly.Memory(): could not allocate memory`.
774+
775+
At startup, Node.js automatically checks whether there is enough virtual memory
776+
available to allocate at least one cage, and if not, the trap-handler optimization
777+
is automatically disabled so that WebAssembly can still run using inline
778+
bound checks (with less optimal performance). But if the application needs to create
779+
many WebAssembly memory instances and the machine still configures a relatively high
780+
limit on virtual memory, allocation of WebAssembly memory instances may still fail
781+
more quickly than expected due to the raised virtual memory usage.
782+
783+
`--disable-wasm-trap-handler` fully disables this optimization so that WebAssembly memory
784+
instances always use inline bound checks instead of reserving large virtual memory cages.
785+
This allows more instances to be created when the virtual memory address space available
786+
to the Node.js process is limited.
791787

792788
### `--disallow-code-generation-from-strings`
793789

Collapse file

‎src/debug_utils.h‎

Copy file name to clipboardExpand all lines: src/debug_utils.h
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ void NODE_EXTERN_PRIVATE FWrite(FILE* file, const std::string& str);
4444
// from a provider type to a debug category.
4545
#define DEBUG_CATEGORY_NAMES(V) \
4646
NODE_ASYNC_PROVIDER_TYPES(V) \
47+
V(BOOTSTRAP) \
4748
V(CRYPTO) \
4849
V(COMPILE_CACHE) \
4950
V(DIAGNOSTICS) \
Collapse file

‎src/node.cc‎

Copy file name to clipboardExpand all lines: src/node.cc
+42-1Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,45 @@ static ExitCode InitializeNodeWithArgsInternal(
10461046
return ExitCode::kNoFailure;
10471047
}
10481048

1049+
#if NODE_USE_V8_WASM_TRAP_HANDLER
1050+
bool CanEnableWebAssemblyTrapHandler() {
1051+
// On POSIX, the machine may have a limit on the amount of virtual memory
1052+
// available, if it's not enough to allocate at least one cage for WASM,
1053+
// then the trap-handler-based bound checks cannot be used.
1054+
#ifdef __POSIX__
1055+
struct rlimit lim;
1056+
if (getrlimit(RLIMIT_AS, &lim) != 0 || lim.rlim_cur == RLIM_INFINITY) {
1057+
// Can't get the limit or there's no limit, assume trap handler can be
1058+
// enabled.
1059+
return true;
1060+
}
1061+
uint64_t virtual_memory_available = static_cast<uint64_t>(lim.rlim_cur);
1062+
1063+
size_t byte_capacity = 64 * 1024; // 64KB, the minimum size of a WASM memory.
1064+
uint64_t cage_size_needed_32 = V8::GetWasmMemoryReservationSizeInBytes(
1065+
V8::WasmMemoryType::kMemory32, byte_capacity);
1066+
uint64_t cage_size_needed_64 = V8::GetWasmMemoryReservationSizeInBytes(
1067+
V8::WasmMemoryType::kMemory64, byte_capacity);
1068+
uint64_t cage_size_needed =
1069+
std::max(cage_size_needed_32, cage_size_needed_64);
1070+
bool can_enable = virtual_memory_available >= cage_size_needed;
1071+
per_process::Debug(DebugCategory::BOOTSTRAP,
1072+
"Virtual memory available: %" PRIu64 " bytes,\n"
1073+
"cage size needed for 32-bit: %" PRIu64 " bytes,\n"
1074+
"cage size needed for 64-bit: %" PRIu64 " bytes,\n"
1075+
"Can%senable WASM trap handler\n",
1076+
virtual_memory_available,
1077+
cage_size_needed_32,
1078+
cage_size_needed_64,
1079+
can_enable ? " " : " not ");
1080+
1081+
return can_enable;
1082+
#else
1083+
return true;
1084+
#endif // __POSIX__
1085+
}
1086+
#endif // NODE_USE_V8_WASM_TRAP_HANDLER
1087+
10491088
static std::shared_ptr<InitializationResultImpl>
10501089
InitializeOncePerProcessInternal(const std::vector<std::string>& args,
10511090
ProcessInitializationFlags::Flags flags =
@@ -1248,7 +1287,9 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args,
12481287
bool use_wasm_trap_handler =
12491288
!per_process::cli_options->disable_wasm_trap_handler;
12501289
if (!(flags & ProcessInitializationFlags::kNoDefaultSignalHandling) &&
1251-
use_wasm_trap_handler) {
1290+
use_wasm_trap_handler && CanEnableWebAssemblyTrapHandler()) {
1291+
per_process::Debug(DebugCategory::BOOTSTRAP,
1292+
"Enabling WebAssembly trap handler for bounds checks\n");
12521293
#if defined(_WIN32)
12531294
constexpr ULONG first = TRUE;
12541295
per_process::old_vectored_exception_handler =
Collapse file

‎test/testpy/__init__.py‎

Copy file name to clipboardExpand all lines: test/testpy/__init__.py
+5-12Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
LS_RE = re.compile(r'^test-.*\.m?js$')
3737
ENV_PATTERN = re.compile(r"//\s+Env:(.*)")
3838
NODE_TEST_PATTERN = re.compile(r"('|`|\")node:test\1")
39+
RLIMIT_AS_PATTERN = re.compile(r"//\s+RLIMIT_AS:\s*(\d+)")
3940

4041
class SimpleTestCase(test.TestCase):
4142

@@ -99,6 +100,10 @@ def GetRunConfiguration(self):
99100
else:
100101
result += flags
101102

103+
rlimit_as_match = RLIMIT_AS_PATTERN.search(source)
104+
if rlimit_as_match:
105+
self.max_virtual_memory = int(rlimit_as_match.group(1))
106+
102107
if self.context.use_error_reporter and NODE_TEST_PATTERN.search(source):
103108
result += ['--test-reporter=./test/common/test-error-reporter.js',
104109
'--test-reporter-destination=stdout']
@@ -189,15 +194,3 @@ def ListTests(self, current_path, path, arch, mode):
189194
for tst in result:
190195
tst.disable_core_files = True
191196
return result
192-
193-
class WasmAllocationTestConfiguration(SimpleTestConfiguration):
194-
def __init__(self, context, root, section, additional=None):
195-
super(WasmAllocationTestConfiguration, self).__init__(context, root, section,
196-
additional)
197-
198-
def ListTests(self, current_path, path, arch, mode):
199-
result = super(WasmAllocationTestConfiguration, self).ListTests(
200-
current_path, path, arch, mode)
201-
for tst in result:
202-
tst.max_virtual_memory = 5 * 1024 * 1024 * 1024 # 5GB
203-
return result
Collapse file
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// RLIMIT_AS: 3221225472
2+
// When the virtual memory limit is 3GB, there is not enough virtual memory for
3+
// even one wasm cage. In this case Node.js should automatically adapt and
4+
// skip enabling trap-based bounds checks, so that WASM can at least run with
5+
// inline bound checks.
6+
'use strict';
7+
8+
require('../common');
9+
new WebAssembly.Memory({ initial: 10, maximum: 100 });
10+
11+
// Test memory64 works too.
12+
new WebAssembly.Memory({ address: 'i64', initial: 10n, maximum: 100n });
Collapse file
+20Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Flags: --disable-wasm-trap-handler
2+
// RLIMIT_AS: 34359738368
3+
4+
// 32GB should be enough for at least 2 cages, but not enough for 30.
5+
6+
// Test that with limited virtual memory space, --disable-wasm-trap-handler
7+
// fully disables trap-based bounds checks, and thus allows WASM to run with
8+
// inline bound checks.
9+
'use strict';
10+
11+
require('../common');
12+
const instances = [];
13+
for (let i = 0; i < 30; i++) {
14+
instances.push(new WebAssembly.Memory({ initial: 10, maximum: 100 }));
15+
}
16+
17+
// Test memory64 works too.
18+
for (let i = 0; i < 30; i++) {
19+
instances.push(new WebAssembly.Memory({ initial: 10n, maximum: 100n, address: 'i64' }));
20+
}
Collapse file
+22Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RLIMIT_AS: 21474836480
2+
// With 20GB virtual memory, there's enough space for the first wasm memory64
3+
// allocation to succeed, but not enough for subsequent ones since each
4+
// wasm memory64 with guard regions reserves 16GB of virtual address space.
5+
'use strict';
6+
7+
require('../common');
8+
const assert = require('assert');
9+
10+
// The first allocation should succeed.
11+
const first = new WebAssembly.Memory({ address: 'i64', initial: 10n, maximum: 100n });
12+
assert(first);
13+
14+
// Subsequent allocations should eventually fail due to running out of
15+
// virtual address space. memory64 reserves 16GB per allocation (vs 8GB for
16+
// memory32), so the limit is reached even faster.
17+
assert.throws(() => {
18+
const instances = [first];
19+
for (let i = 1; i < 30; i++) {
20+
instances.push(new WebAssembly.Memory({ address: 'i64', initial: 10n, maximum: 100n }));
21+
}
22+
}, /WebAssembly\.Memory/);
Collapse file

‎test/wasm-allocation/test-wasm-allocation.js‎

Copy file name to clipboardExpand all lines: test/wasm-allocation/test-wasm-allocation.js
+4-3Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
// Flags: --disable-wasm-trap-handler
2-
// Test that with limited virtual memory space, --disable-wasm-trap-handler
3-
// allows WASM to at least run with inline bound checks.
1+
// RLIMIT_AS: 21474836480
2+
// With 20GB virtual memory, there's enough space for the first few wasm memory
3+
// allocation to succeed, but not enough for many subsequent ones since each
4+
// wasm memory32 with guard regions reserves 8GB of virtual address space.
45
'use strict';
56

67
require('../common');
Collapse file

‎test/wasm-allocation/testcfg.py‎

Copy file name to clipboardExpand all lines: test/wasm-allocation/testcfg.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
import testpy
44

55
def GetConfiguration(context, root):
6-
return testpy.WasmAllocationTestConfiguration(context, root, 'wasm-allocation')
6+
return testpy.SimpleTestConfiguration(context, root, 'wasm-allocation')
Collapse file

‎test/wasm-allocation/wasm-allocation.status‎

Copy file name to clipboardExpand all lines: test/wasm-allocation/wasm-allocation.status
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,9 @@ prefix wasm-allocation
88

99
[$system!=linux || $asan==on || $pointer_compression==on]
1010
test-wasm-allocation: SKIP
11+
test-wasm-allocation-auto-adapt: SKIP
12+
test-wasm-allocation-memory64: SKIP
13+
14+
[$arch!=x64 && $arch!=arm64]
15+
test-wasm-allocation: SKIP
16+
test-wasm-allocation-memory64: SKIP

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.