Skip to content

Commit 473fa73

Browse files
joyeecheungmarco-ippolito
authored andcommittedJun 19, 2024··
cli: allow running wasm in limited vmem with --disable-wasm-trap-handler
By default, Node.js enables trap-handler-based WebAssembly bound checks. As a result, V8 does not need to insert inline bound checks int the code compiled from WebAssembly which may speedup WebAssembly execution significantly, but this optimization requires allocating a big virtual memory cage (currently 10GB). If the Node.js process does not have access to a large enough virtual memory address space due to system configurations or hardware limitations, users won't be able to run any WebAssembly that involves allocation in this virtual memory cage and will see an out-of-memory error. ```console $ ulimit -v 5000000 $ node -p "new WebAssembly.Memory({ initial: 10, maximum: 100 });" [eval]:1 new WebAssembly.Memory({ initial: 10, maximum: 100 }); ^ RangeError: WebAssembly.Memory(): could not allocate memory at [eval]:1:1 at runScriptInThisContext (node:internal/vm:209:10) at node:internal/process/execution:118:14 at [eval]-wrapper:6:24 at runScript (node:internal/process/execution:101:62) at evalScript (node:internal/process/execution:136:3) at node:internal/main/eval_string:49:3 ``` `--disable-wasm-trap-handler` disables this optimization so that users can at least run WebAssembly (with a less optimial performance) when the virtual memory address space available to their Node.js process is lower than what the V8 WebAssembly memory cage needs. PR-URL: #52766 Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
1 parent d54aa47 commit 473fa73

File tree

9 files changed

+128
-31
lines changed

9 files changed

+128
-31
lines changed
 

‎doc/api/cli.md

+40
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,45 @@ const vm = require('node:vm');
559559
vm.measureMemory();
560560
```
561561

562+
### `--disable-wasm-trap-handler`
563+
564+
<!-- YAML
565+
added: REPLACEME
566+
-->
567+
568+
By default, Node.js enables trap-handler-based WebAssembly bound
569+
checks. As a result, V8 does not need to insert inline bound checks
570+
int the code compiled from WebAssembly which may speedup WebAssembly
571+
execution significantly, but this optimization requires allocating
572+
a big virtual memory cage (currently 10GB). If the Node.js process
573+
does not have access to a large enough virtual memory address space
574+
due to system configurations or hardware limitations, users won't
575+
be able to run any WebAssembly that involves allocation in this
576+
virtual memory cage and will see an out-of-memory error.
577+
578+
```console
579+
$ ulimit -v 5000000
580+
$ node -p "new WebAssembly.Memory({ initial: 10, maximum: 100 });"
581+
[eval]:1
582+
new WebAssembly.Memory({ initial: 10, maximum: 100 });
583+
^
584+
585+
RangeError: WebAssembly.Memory(): could not allocate memory
586+
at [eval]:1:1
587+
at runScriptInThisContext (node:internal/vm:209:10)
588+
at node:internal/process/execution:118:14
589+
at [eval]-wrapper:6:24
590+
at runScript (node:internal/process/execution:101:62)
591+
at evalScript (node:internal/process/execution:136:3)
592+
at node:internal/main/eval_string:49:3
593+
594+
```
595+
596+
`--disable-wasm-trap-handler` disables this optimization so that
597+
users can at least run WebAssembly (with less optimal performance)
598+
when the virtual memory address space available to their Node.js
599+
process is lower than what the V8 WebAssembly memory cage needs.
600+
562601
### `--disable-proto=mode`
563602

564603
<!-- YAML
@@ -2499,6 +2538,7 @@ one is included in the list below.
24992538
* `--diagnostic-dir`
25002539
* `--disable-proto`
25012540
* `--disable-warning`
2541+
* `--disable-wasm-trap-handler`
25022542
* `--dns-result-order`
25032543
* `--enable-fips`
25042544
* `--enable-network-family-autoselection`

‎doc/node.1

+5
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ is `delete`, the property will be removed entirely. If
142142
is `throw`, accesses to the property will throw an exception with the code
143143
`ERR_PROTO_ACCESS`.
144144
.
145+
.It Fl -disable-wasm-trap-handler Ns = Ns Ar mode
146+
Disable trap-handler-based WebAssembly bound checks and fall back to
147+
inline bound checks so that WebAssembly can be run with limited virtual
148+
memory.
149+
.
145150
.It Fl -disallow-code-generation-from-strings
146151
Make built-in language features like `eval` and `new Function` that generate
147152
code from strings throw an exception instead. This does not affect the Node.js

‎src/node.cc

+38-31
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
420420
typedef void (*sigaction_cb)(int signo, siginfo_t* info, void* ucontext);
421421
#endif
422422
#if NODE_USE_V8_WASM_TRAP_HANDLER
423+
static std::atomic<bool> is_wasm_trap_handler_configured{false};
423424
#if defined(_WIN32)
424425
static LONG WINAPI TrapWebAssemblyOrContinue(EXCEPTION_POINTERS* exception) {
425426
if (v8::TryHandleWebAssemblyTrapWindows(exception)) {
@@ -465,15 +466,17 @@ void RegisterSignalHandler(int signal,
465466
bool reset_handler) {
466467
CHECK_NOT_NULL(handler);
467468
#if NODE_USE_V8_WASM_TRAP_HANDLER
468-
if (signal == SIGSEGV) {
469+
// Stash the user-registered handlers for TrapWebAssemblyOrContinue
470+
// to call out to when the signal is not coming from a WASM OOM.
471+
if (signal == SIGSEGV && is_wasm_trap_handler_configured.load()) {
469472
CHECK(previous_sigsegv_action.is_lock_free());
470473
CHECK(!reset_handler);
471474
previous_sigsegv_action.store(handler);
472475
return;
473476
}
474-
// TODO(align behavior between macos and other in next major version)
477+
// TODO(align behavior between macos and other in next major version)
475478
#if defined(__APPLE__)
476-
if (signal == SIGBUS) {
479+
if (signal == SIGBUS && is_wasm_trap_handler_configured.load()) {
477480
CHECK(previous_sigbus_action.is_lock_free());
478481
CHECK(!reset_handler);
479482
previous_sigbus_action.store(handler);
@@ -625,25 +628,6 @@ static void PlatformInit(ProcessInitializationFlags::Flags flags) {
625628
if (!(flags & ProcessInitializationFlags::kNoDefaultSignalHandling)) {
626629
RegisterSignalHandler(SIGINT, SignalExit, true);
627630
RegisterSignalHandler(SIGTERM, SignalExit, true);
628-
629-
#if NODE_USE_V8_WASM_TRAP_HANDLER
630-
// Tell V8 to disable emitting WebAssembly
631-
// memory bounds checks. This means that we have
632-
// to catch the SIGSEGV/SIGBUS in TrapWebAssemblyOrContinue
633-
// and pass the signal context to V8.
634-
{
635-
struct sigaction sa;
636-
memset(&sa, 0, sizeof(sa));
637-
sa.sa_sigaction = TrapWebAssemblyOrContinue;
638-
sa.sa_flags = SA_SIGINFO;
639-
CHECK_EQ(sigaction(SIGSEGV, &sa, nullptr), 0);
640-
// TODO(align behavior between macos and other in next major version)
641-
#if defined(__APPLE__)
642-
CHECK_EQ(sigaction(SIGBUS, &sa, nullptr), 0);
643-
#endif
644-
}
645-
V8::EnableWebAssemblyTrapHandler(false);
646-
#endif // NODE_USE_V8_WASM_TRAP_HANDLER
647631
}
648632

649633
if (!(flags & ProcessInitializationFlags::kNoAdjustResourceLimits)) {
@@ -670,14 +654,6 @@ static void PlatformInit(ProcessInitializationFlags::Flags flags) {
670654
}
671655
#endif // __POSIX__
672656
#ifdef _WIN32
673-
#ifdef NODE_USE_V8_WASM_TRAP_HANDLER
674-
{
675-
constexpr ULONG first = TRUE;
676-
per_process::old_vectored_exception_handler =
677-
AddVectoredExceptionHandler(first, TrapWebAssemblyOrContinue);
678-
}
679-
V8::EnableWebAssemblyTrapHandler(false);
680-
#endif // NODE_USE_V8_WASM_TRAP_HANDLER
681657
if (!(flags & ProcessInitializationFlags::kNoStdioInitialization)) {
682658
for (int fd = 0; fd <= 2; ++fd) {
683659
auto handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
@@ -1207,6 +1183,37 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args,
12071183
cppgc::InitializeProcess(allocator);
12081184
}
12091185

1186+
#if NODE_USE_V8_WASM_TRAP_HANDLER
1187+
bool use_wasm_trap_handler =
1188+
!per_process::cli_options->disable_wasm_trap_handler;
1189+
if (!(flags & ProcessInitializationFlags::kNoDefaultSignalHandling) &&
1190+
use_wasm_trap_handler) {
1191+
#if defined(_WIN32)
1192+
constexpr ULONG first = TRUE;
1193+
per_process::old_vectored_exception_handler =
1194+
AddVectoredExceptionHandler(first, TrapWebAssemblyOrContinue);
1195+
#else
1196+
// Tell V8 to disable emitting WebAssembly
1197+
// memory bounds checks. This means that we have
1198+
// to catch the SIGSEGV/SIGBUS in TrapWebAssemblyOrContinue
1199+
// and pass the signal context to V8.
1200+
{
1201+
struct sigaction sa;
1202+
memset(&sa, 0, sizeof(sa));
1203+
sa.sa_sigaction = TrapWebAssemblyOrContinue;
1204+
sa.sa_flags = SA_SIGINFO;
1205+
CHECK_EQ(sigaction(SIGSEGV, &sa, nullptr), 0);
1206+
// TODO(align behavior between macos and other in next major version)
1207+
#if defined(__APPLE__)
1208+
CHECK_EQ(sigaction(SIGBUS, &sa, nullptr), 0);
1209+
#endif
1210+
}
1211+
#endif // defined(_WIN32)
1212+
is_wasm_trap_handler_configured.store(true);
1213+
V8::EnableWebAssemblyTrapHandler(false);
1214+
}
1215+
#endif // NODE_USE_V8_WASM_TRAP_HANDLER
1216+
12101217
performance::performance_v8_start = PERFORMANCE_NOW();
12111218
per_process::v8_initialized = true;
12121219

@@ -1236,7 +1243,7 @@ void TearDownOncePerProcess() {
12361243
}
12371244

12381245
#if NODE_USE_V8_WASM_TRAP_HANDLER && defined(_WIN32)
1239-
if (!(flags & ProcessInitializationFlags::kNoDefaultSignalHandling)) {
1246+
if (is_wasm_trap_handler_configured.load()) {
12401247
RemoveVectoredExceptionHandler(per_process::old_vectored_exception_handler);
12411248
}
12421249
#endif

‎src/node_options.cc

+8
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,14 @@ PerProcessOptionsParser::PerProcessOptionsParser(
10711071
"Generate a blob that can be embedded into the single executable "
10721072
"application",
10731073
&PerProcessOptions::experimental_sea_config);
1074+
1075+
AddOption(
1076+
"--disable-wasm-trap-handler",
1077+
"Disable trap-handler-based WebAssembly bound checks. V8 will insert "
1078+
"inline bound checks when compiling WebAssembly which may slow down "
1079+
"performance.",
1080+
&PerProcessOptions::disable_wasm_trap_handler,
1081+
kAllowedInEnvvar);
10741082
}
10751083

10761084
inline std::string RemoveBrackets(const std::string& host) {

‎src/node_options.h

+2
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,8 @@ class PerProcessOptions : public Options {
310310
bool openssl_shared_config = false;
311311
#endif
312312

313+
bool disable_wasm_trap_handler = false;
314+
313315
// Per-process because reports can be triggered outside a known V8 context.
314316
bool report_on_fatalerror = false;
315317
bool report_compact = false;

‎test/testpy/__init__.py

+12
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,15 @@ def ListTests(self, current_path, path, arch, mode):
167167
for tst in result:
168168
tst.disable_core_files = True
169169
return result
170+
171+
class WasmAllocationTestConfiguration(SimpleTestConfiguration):
172+
def __init__(self, context, root, section, additional=None):
173+
super(WasmAllocationTestConfiguration, self).__init__(context, root, section,
174+
additional)
175+
176+
def ListTests(self, current_path, path, arch, mode):
177+
result = super(WasmAllocationTestConfiguration, self).ListTests(
178+
current_path, path, arch, mode)
179+
for tst in result:
180+
tst.max_virtual_memory = 5 * 1024 * 1024 * 1024 # 5GB
181+
return result
Original file line numberDiff line numberDiff line change
@@ -0,0 +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.
4+
'use strict';
5+
6+
require('../common');
7+
new WebAssembly.Memory({ initial: 10, maximum: 100 });

‎test/wasm-allocation/testcfg.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import sys, os
2+
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
3+
import testpy
4+
5+
def GetConfiguration(context, root):
6+
return testpy.WasmAllocationTestConfiguration(context, root, 'wasm-allocation')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
prefix wasm-allocation
2+
3+
# To mark a test as flaky, list the test name in the appropriate section
4+
# below, without ".js", followed by ": PASS,FLAKY". Example:
5+
# sample-test : PASS,FLAKY
6+
7+
[true] # This section applies to all platforms
8+
9+
[$system!=linux || $asan==on]
10+
test-wasm-allocation: SKIP

0 commit comments

Comments
 (0)
Please sign in to comment.