| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 8b37af8 commit 818b43d
10 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -695,40 +695,36 @@ vm.measureMemory(); | |||
| 695 | 695 | added: | |
| 696 | 696 | - v22.2.0 | |
| 697 | 697 | - v20.15.0 | |
| 698 | - --> | ||
| 699 | - | ||
| 700 | - By default, Node.js enables trap-handler-based WebAssembly bound | ||
| 701 | - checks. As a result, V8 does not need to insert inline bound checks | ||
| 702 | - in the code compiled from WebAssembly which may speed up WebAssembly | ||
| 703 | - execution significantly, but this optimization requires allocating | ||
| 704 | - a big virtual memory cage (currently 10GB). If the Node.js process | ||
| 705 | - does not have access to a large enough virtual memory address space | ||
| 706 | - due to system configurations or hardware limitations, users won't | ||
| 707 | - be able to run any WebAssembly that involves allocation in this | ||
| 708 | - virtual memory cage and will see an out-of-memory error. | ||
| 709 | - | ||
| 710 | - ```console | ||
| 711 | - $ ulimit -v 5000000 | ||
| 712 | - $ node -p "new WebAssembly.Memory({ initial: 10, maximum: 100 });" | ||
| 713 | - [eval]:1 | ||
| 714 | - new WebAssembly.Memory({ initial: 10, maximum: 100 }); | ||
| 715 | - ^ | ||
| 716 | - | ||
| 717 | - RangeError: WebAssembly.Memory(): could not allocate memory | ||
| 718 | - at [eval]:1:1 | ||
| 719 | - at runScriptInThisContext (node:internal/vm:209:10) | ||
| 720 | - at node:internal/process/execution:118:14 | ||
| 721 | - at [eval]-wrapper:6:24 | ||
| 722 | - at runScript (node:internal/process/execution:101:62) | ||
| 723 | - at evalScript (node:internal/process/execution:136:3) | ||
| 724 | - at node:internal/main/eval_string:49:3 | ||
| 725 | - | ||
| 726 | - ``` | ||
| 727 | - | ||
| 728 | - `--disable-wasm-trap-handler` disables this optimization so that | ||
| 729 | - users can at least run WebAssembly (with less optimal performance) | ||
| 730 | - when the virtual memory address space available to their Node.js | ||
| 731 | - process is lower than what the V8 WebAssembly memory cage needs. | ||
| 698 | + changes: | ||
| 699 | + - version: | ||
| 700 | + - REPLACEME | ||
| 701 | + pr-url: https://github.com/nodejs/node/pull/62132 | ||
| 702 | + description: Node.js now automatically disables the trap handler when there is not | ||
| 703 | + enough virtual memory available at startup to allocate one cage. | ||
| 704 | + --> | ||
| 705 | + | ||
| 706 | + Node.js enables V8's trap-handler-based WebAssembly bound checks on 64-bit platforms, | ||
| 707 | + which significantly improves WebAssembly performance by eliminating the need for | ||
| 708 | + inline bound checks. This optimization requires allocating a large virtual memory | ||
| 709 | + cage per WebAssembly memory instance (currently typically 8GB for 32-bit WebAssembly memory, | ||
| 710 | + 16GB for 64-bit WebAssembly memory) to trap out-of-bound accesses. On most 64-bit | ||
| 711 | + platforms, the virtual memory address space is usually large enough (around 128TB) | ||
| 712 | + to accommodate typical WebAssembly usages, but if the machine has manual limits | ||
| 713 | + on virtual memory (e.g. through `ulimit -v`), WebAssembly memory allocation is | ||
| 714 | + more likely to fail with `WebAssembly.Memory(): could not allocate memory`. | ||
| 715 | + | ||
| 716 | + At startup, Node.js automatically checks whether there is enough virtual memory | ||
| 717 | + available to allocate at least one cage, and if not, the trap-handler optimization | ||
| 718 | + is automatically disabled so that WebAssembly can still run using inline | ||
| 719 | + bound checks (with less optimal performance). But if the application needs to create | ||
| 720 | + many WebAssembly memory instances and the machine still configures a relatively high | ||
| 721 | + limit on virtual memory, allocation of WebAssembly memory instances may still fail | ||
| 722 | + more quickly than expected due to the raised virtual memory usage. | ||
| 723 | + | ||
| 724 | + `--disable-wasm-trap-handler` fully disables this optimization so that WebAssembly memory | ||
| 725 | + instances always use inline bound checks instead of reserving large virtual memory cages. | ||
| 726 | + This allows more instances to be created when the virtual memory address space available | ||
| 727 | + to the Node.js process is limited. | ||
| 732 | 728 | ||
| 733 | 729 | ### `--disallow-code-generation-from-strings` | |
| 734 | 730 | ||
| 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(CONTEXTIFY) \ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1066,6 +1066,45 @@ int InitializeNodeWithArgs(std::vector<std::string>* argv, | |||
| 1066 | 1066 | InitializeNodeWithArgsInternal(argv, exec_argv, errors, flags)); | |
| 1067 | 1067 | } | |
| 1068 | 1068 | ||
| 1069 | + #if NODE_USE_V8_WASM_TRAP_HANDLER | ||
| 1070 | + bool CanEnableWebAssemblyTrapHandler() { | ||
| 1071 | + // On POSIX, the machine may have a limit on the amount of virtual memory | ||
| 1072 | + // available, if it's not enough to allocate at least one cage for WASM, | ||
| 1073 | + // then the trap-handler-based bound checks cannot be used. | ||
| 1074 | + #ifdef __POSIX__ | ||
| 1075 | + struct rlimit lim; | ||
| 1076 | + if (getrlimit(RLIMIT_AS, &lim) != 0 || lim.rlim_cur == RLIM_INFINITY) { | ||
| 1077 | + // Can't get the limit or there's no limit, assume trap handler can be | ||
| 1078 | + // enabled. | ||
| 1079 | + return true; | ||
| 1080 | + } | ||
| 1081 | + uint64_t virtual_memory_available = static_cast<uint64_t>(lim.rlim_cur); | ||
| 1082 | + | ||
| 1083 | + size_t byte_capacity = 64 * 1024; // 64KB, the minimum size of a WASM memory. | ||
| 1084 | + uint64_t cage_size_needed_32 = V8::GetWasmMemoryReservationSizeInBytes( | ||
| 1085 | + V8::WasmMemoryType::kMemory32, byte_capacity); | ||
| 1086 | + uint64_t cage_size_needed_64 = V8::GetWasmMemoryReservationSizeInBytes( | ||
| 1087 | + V8::WasmMemoryType::kMemory64, byte_capacity); | ||
| 1088 | + uint64_t cage_size_needed = | ||
| 1089 | + std::max(cage_size_needed_32, cage_size_needed_64); | ||
| 1090 | + bool can_enable = virtual_memory_available >= cage_size_needed; | ||
| 1091 | + per_process::Debug(DebugCategory::BOOTSTRAP, | ||
| 1092 | + "Virtual memory available: %" PRIu64 " bytes,\n" | ||
| 1093 | + "cage size needed for 32-bit: %" PRIu64 " bytes,\n" | ||
| 1094 | + "cage size needed for 64-bit: %" PRIu64 " bytes,\n" | ||
| 1095 | + "Can%senable WASM trap handler\n", | ||
| 1096 | + virtual_memory_available, | ||
| 1097 | + cage_size_needed_32, | ||
| 1098 | + cage_size_needed_64, | ||
| 1099 | + can_enable ? " " : " not "); | ||
| 1100 | + | ||
| 1101 | + return can_enable; | ||
| 1102 | + #else | ||
| 1103 | + return true; | ||
| 1104 | + #endif // __POSIX__ | ||
| 1105 | + } | ||
| 1106 | + #endif // NODE_USE_V8_WASM_TRAP_HANDLER | ||
| 1107 | + | ||
| 1069 | 1108 | static std::shared_ptr<InitializationResultImpl> | |
| 1070 | 1109 | InitializeOncePerProcessInternal(const std::vector<std::string>& args, | |
| 1071 | 1110 | ProcessInitializationFlags::Flags flags = | |
@@ -1268,7 +1307,9 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args, | |||
| 1268 | 1307 | bool use_wasm_trap_handler = | |
| 1269 | 1308 | !per_process::cli_options->disable_wasm_trap_handler; | |
| 1270 | 1309 | if (!(flags & ProcessInitializationFlags::kNoDefaultSignalHandling) && | |
| 1271 | - use_wasm_trap_handler) { | ||
| 1310 | + use_wasm_trap_handler && CanEnableWebAssemblyTrapHandler()) { | ||
| 1311 | + per_process::Debug(DebugCategory::BOOTSTRAP, | ||
| 1312 | + "Enabling WebAssembly trap handler for bounds checks\n"); | ||
| 1272 | 1313 | #if defined(_WIN32) | |
| 1273 | 1314 | constexpr ULONG first = TRUE; | |
| 1274 | 1315 | 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,7 +1,21 @@ | |||
| 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'); | |
| 7 | - new WebAssembly.Memory({ initial: 10, maximum: 100 }); | ||
| 8 | + const assert = require('assert'); | ||
| 9 | + | ||
| 10 | + // The first allocation should succeed. | ||
| 11 | + const first = new WebAssembly.Memory({ initial: 10, maximum: 100 }); | ||
| 12 | + assert(first); | ||
| 13 | + | ||
| 14 | + // Subsequent allocations should eventually fail due to running out of | ||
| 15 | + // virtual address space. | ||
| 16 | + assert.throws(() => { | ||
| 17 | + const instances = [first]; | ||
| 18 | + for (let i = 1; i < 30; i++) { | ||
| 19 | + instances.push(new WebAssembly.Memory({ initial: 10, maximum: 100 })); | ||
| 20 | + } | ||
| 21 | + }, /WebAssembly\.Memory/); | ||
| 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