| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent ebfaf25 commit 79262ff
10 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -754,40 +754,36 @@ vm.measureMemory(); | |||
| 754 | 754 | added: | |
| 755 | 755 | - v22.2.0 | |
| 756 | 756 | - 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. | ||
| 791 | 787 | ||
| 792 | 788 | ### `--disallow-code-generation-from-strings` | |
| 793 | 789 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -44,6 +44,7 @@ void NODE_EXTERN_PRIVATE FWrite(FILE* file, const std::string& str); | |||
| 44 | 44 | // from a provider type to a debug category. | |
| 45 | 45 | #define DEBUG_CATEGORY_NAMES(V) \ | |
| 46 | 46 | NODE_ASYNC_PROVIDER_TYPES(V) \ | |
| 47 | + V(BOOTSTRAP) \ | ||
| 47 | 48 | V(CRYPTO) \ | |
| 48 | 49 | V(COMPILE_CACHE) \ | |
| 49 | 50 | V(DIAGNOSTICS) \ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1046,6 +1046,45 @@ static ExitCode InitializeNodeWithArgsInternal( | |||
| 1046 | 1046 | return ExitCode::kNoFailure; | |
| 1047 | 1047 | } | |
| 1048 | 1048 | ||
| 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 | + | ||
| 1049 | 1088 | static std::shared_ptr<InitializationResultImpl> | |
| 1050 | 1089 | InitializeOncePerProcessInternal(const std::vector<std::string>& args, | |
| 1051 | 1090 | ProcessInitializationFlags::Flags flags = | |
@@ -1248,7 +1287,9 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args, | |||
| 1248 | 1287 | bool use_wasm_trap_handler = | |
| 1249 | 1288 | !per_process::cli_options->disable_wasm_trap_handler; | |
| 1250 | 1289 | 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"); | ||
| 1252 | 1293 | #if defined(_WIN32) | |
| 1253 | 1294 | constexpr ULONG first = TRUE; | |
| 1254 | 1295 | per_process::old_vectored_exception_handler = | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -36,6 +36,7 @@ | |||
| 36 | 36 | LS_RE = re.compile(r'^test-.*\.m?js$') | |
| 37 | 37 | ENV_PATTERN = re.compile(r"//\s+Env:(.*)") | |
| 38 | 38 | NODE_TEST_PATTERN = re.compile(r"('|`|\")node:test\1") | |
| 39 | + RLIMIT_AS_PATTERN = re.compile(r"//\s+RLIMIT_AS:\s*(\d+)") | ||
| 39 | 40 | ||
| 40 | 41 | class SimpleTestCase(test.TestCase): | |
| 41 | 42 | ||
@@ -99,6 +100,10 @@ def GetRunConfiguration(self): | |||
| 99 | 100 | else: | |
| 100 | 101 | result += flags | |
| 101 | 102 | ||
| 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 | + | ||
| 102 | 107 | if self.context.use_error_reporter and NODE_TEST_PATTERN.search(source): | |
| 103 | 108 | result += ['--test-reporter=./test/common/test-error-reporter.js', | |
| 104 | 109 | '--test-reporter-destination=stdout'] | |
@@ -189,15 +194,3 @@ def ListTests(self, current_path, path, arch, mode): | |||
| 189 | 194 | for tst in result: | |
| 190 | 195 | tst.disable_core_files = True | |
| 191 | 196 | 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 | ||
| Original file line number | Diff line number | Diff 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 }); | ||
| Original file line number | Diff line number | Diff 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 | + } | ||
| Original file line number | Diff line number | Diff 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/); | ||
| Original file line number | Diff line number | Diff 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. | ||
| 4 | 5 | 'use strict'; | |
| 5 | 6 | ||
| 6 | 7 | require('../common'); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,4 +3,4 @@ | |||
| 3 | 3 | import testpy | |
| 4 | 4 | ||
| 5 | 5 | def GetConfiguration(context, root): | |
| 6 | - return testpy.WasmAllocationTestConfiguration(context, root, 'wasm-allocation') | ||
| 6 | + return testpy.SimpleTestConfiguration(context, root, 'wasm-allocation') | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,3 +8,9 @@ prefix wasm-allocation | |||
| 8 | 8 | ||
| 9 | 9 | [$system!=linux || $asan==on || $pointer_compression==on] | |
| 10 | 10 | 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 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments