Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@

# Reset this number to 0 on major V8 upgrades.
# Increment by one for each non-official patch applied to deps/v8.
'v8_embedder_string': '-node.12',
'v8_embedder_string': '-node.13',

##### V8 defaults for Node.js #####

Expand Down
29 changes: 29 additions & 0 deletions deps/v8/include/v8-initialization.h
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,35 @@ class V8_EXPORT V8 {
static size_t GetSandboxReservationSizeInBytes();
#endif // V8_ENABLE_SANDBOX

enum class WasmMemoryType {
kMemory32,
kMemory64,
};

/**
* Returns the virtual address space reservation size (in bytes) needed
* for one WebAssembly memory instance of the given capacity.
*
* \param type Whether this is a memory32 or memory64 instance.
* \param byte_capacity The maximum size, in bytes, of the WebAssembly
* memory. Values exceeding the engine's maximum allocatable memory
* size for the given type (determined by max_mem32_pages or
* max_mem64_pages) are clamped.
*
* When trap-based bounds checking is enabled by
* EnableWebAssemblyTrapHandler(), the amount of virtual address space
* that V8 needs to reserve for each WebAssembly memory instance can
* be much bigger than the requested size. If the process does
* not have enough virtual memory available, WebAssembly memory allocation
* would fail. During the initialization of V8, embedders can use this method
* to estimate whether the process has enough virtual memory for their
* usage of WebAssembly, and decide whether to enable the trap handler
* via EnableWebAssemblyTrapHandler(), or to skip it and reduce the amount of
* virtual memory required to keep the application running.
*/
static size_t GetWasmMemoryReservationSizeInBytes(WasmMemoryType type,
size_t byte_capacity);

/**
* Activate trap-based bounds checking for WebAssembly.
*
Expand Down
21 changes: 21 additions & 0 deletions deps/v8/src/api/api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@
#include "src/wasm/value-type.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-js.h"
#include "src/wasm/wasm-limits.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-result.h"
#include "src/wasm/wasm-serialization.h"
Expand Down Expand Up @@ -6294,6 +6295,26 @@ bool TryHandleWebAssemblyTrapWindows(EXCEPTION_POINTERS* exception) {
}
#endif

size_t V8::GetWasmMemoryReservationSizeInBytes(WasmMemoryType type,
size_t byte_capacity) {
#if V8_ENABLE_WEBASSEMBLY
bool is_memory64 = type == WasmMemoryType::kMemory64;
uint64_t max_byte_capacity =
is_memory64 ? i::wasm::max_mem64_bytes() : i::wasm::max_mem32_bytes();
if (byte_capacity > max_byte_capacity) {
byte_capacity = static_cast<size_t>(max_byte_capacity);
}
#if V8_TRAP_HANDLER_SUPPORTED
if (!is_memory64 || i::v8_flags.wasm_memory64_trap_handling) {
return i::BackingStore::GetWasmReservationSize(
/* has_guard_regions */ true, byte_capacity,
/* is_wasm_memory64 */ is_memory64);
}
#endif // V8_TRAP_HANDLER_SUPPORTED
#endif // V8_ENABLE_WEBASSEMBLY
return byte_capacity;
}

bool V8::EnableWebAssemblyTrapHandler(bool use_v8_signal_handler) {
#if V8_ENABLE_WEBASSEMBLY
return i::trap_handler::EnableTrapHandler(use_v8_signal_handler);
Expand Down
42 changes: 22 additions & 20 deletions deps/v8/src/objects/backing-store.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,25 @@ enum class AllocationStatus {
kOtherFailure // Failed for an unknown reason
};

size_t GetReservationSize(bool has_guard_regions, size_t byte_capacity,
bool is_wasm_memory64) {
base::AddressRegion GetReservedRegion(bool has_guard_regions,
bool is_wasm_memory64, void* buffer_start,
size_t byte_capacity) {
return base::AddressRegion(
reinterpret_cast<Address>(buffer_start),
BackingStore::GetWasmReservationSize(has_guard_regions, byte_capacity,
is_wasm_memory64));
}

void RecordStatus(Isolate* isolate, AllocationStatus status) {
isolate->counters()->wasm_memory_allocation_result()->AddSample(
static_cast<int>(status));
}

} // namespace

size_t BackingStore::GetWasmReservationSize(bool has_guard_regions,
size_t byte_capacity,
bool is_wasm_memory64) {
#if V8_TARGET_ARCH_64_BIT && V8_ENABLE_WEBASSEMBLY
DCHECK_IMPLIES(is_wasm_memory64 && has_guard_regions,
v8_flags.wasm_memory64_trap_handling);
Expand All @@ -73,21 +90,6 @@ size_t GetReservationSize(bool has_guard_regions, size_t byte_capacity,
return byte_capacity;
}

base::AddressRegion GetReservedRegion(bool has_guard_regions,
bool is_wasm_memory64, void* buffer_start,
size_t byte_capacity) {
return base::AddressRegion(
reinterpret_cast<Address>(buffer_start),
GetReservationSize(has_guard_regions, byte_capacity, is_wasm_memory64));
}

void RecordStatus(Isolate* isolate, AllocationStatus status) {
isolate->counters()->wasm_memory_allocation_result()->AddSample(
static_cast<int>(status));
}

} // namespace

// The backing store for a Wasm shared memory remembers all the isolates
// with which it has been shared.
struct SharedWasmMemoryData {
Expand Down Expand Up @@ -168,7 +170,7 @@ BackingStore::~BackingStore() {

#if V8_ENABLE_WEBASSEMBLY
if (is_wasm_memory()) {
size_t reservation_size = GetReservationSize(
size_t reservation_size = GetWasmReservationSize(
has_guard_regions(), byte_capacity_, is_wasm_memory64());
TRACE_BS(
"BSw:free bs=%p mem=%p (length=%zu, capacity=%zu, reservation=%zu)\n",
Expand Down Expand Up @@ -324,8 +326,8 @@ std::unique_ptr<BackingStore> BackingStore::TryAllocateAndPartiallyCommitMemory(
};

size_t byte_capacity = maximum_pages * page_size;
size_t reservation_size =
GetReservationSize(has_guard_regions, byte_capacity, is_wasm_memory64);
size_t reservation_size = GetWasmReservationSize(
has_guard_regions, byte_capacity, is_wasm_memory64);

//--------------------------------------------------------------------------
// Allocate pages (inaccessible by default).
Expand Down
5 changes: 5 additions & 0 deletions deps/v8/src/objects/backing-store.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ class V8_EXPORT_PRIVATE BackingStore : public BackingStoreBase {

uint32_t id() const { return id_; }

// Return the size of the reservation needed for a wasm backing store.
static size_t GetWasmReservationSize(bool has_guard_regions,
size_t byte_capacity,
bool is_wasm_memory64);

private:
friend class GlobalBackingStoreRegistry;

Expand Down
16 changes: 16 additions & 0 deletions deps/v8/test/unittests/api/api-wasm-unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -263,4 +263,20 @@ TEST_F(ApiWasmTest, WasmEnableDisableCustomDescriptors) {
}
}

TEST_F(ApiWasmTest, GetWasmMemoryReservationSizeInBytes) {
constexpr size_t kCapacity = 64 * 1024; // 64 KiB
size_t reservation = V8::GetWasmMemoryReservationSizeInBytes(
V8::WasmMemoryType::kMemory32, kCapacity);
size_t reservation64 = V8::GetWasmMemoryReservationSizeInBytes(
V8::WasmMemoryType::kMemory64, kCapacity);

#if V8_TRAP_HANDLER_SUPPORTED
EXPECT_GE(reservation, kCapacity);
EXPECT_GE(reservation64, kCapacity);
#else
EXPECT_EQ(reservation, kCapacity);
EXPECT_EQ(reservation64, kCapacity);
#endif // V8_TRAP_HANDLER_SUPPORTED
}

} // namespace v8
1 change: 1 addition & 0 deletions src/debug_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ void NODE_EXTERN_PRIVATE FWrite(FILE* file, const std::string& str);
// from a provider type to a debug category.
#define DEBUG_CATEGORY_NAMES(V) \
NODE_ASYNC_PROVIDER_TYPES(V) \
V(BOOTSTRAP) \
V(CRYPTO) \
V(COMPILE_CACHE) \
V(DIAGNOSTICS) \
Expand Down
42 changes: 41 additions & 1 deletion src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,44 @@ static ExitCode InitializeNodeWithArgsInternal(
return ExitCode::kNoFailure;
}

#if NODE_USE_V8_WASM_TRAP_HANDLER
bool CanEnableWebAssemblyTrapHandler() {
// On POSIX, the machine may have a limit on the amount of virtual memory
// available, if it's not enough to allocate at least one cage for WASM,
// then the trap-handler-based bound checks cannot be used.
#ifdef __POSIX__
struct rlimit lim;
if (getrlimit(RLIMIT_AS, &lim) != 0 || lim.rlim_cur == RLIM_INFINITY) {
// Can't get the limit or there's no limit, assume trap handler can be
// enabled.
return true;
}
uint64_t virtual_memory_available = static_cast<uint64_t>(lim.rlim_cur);

size_t byte_capacity = 64 * 1024; // 64KB, the minimum size of a WASM memory.
uint64_t cage_size_needed_32 =
V8::GetWasmMemoryReservationSizeInBytes(V8::WasmMemoryType::kMemory32, byte_capacity);
uint64_t cage_size_needed_64 =
V8::GetWasmMemoryReservationSizeInBytes(V8::WasmMemoryType::kMemory64, byte_capacity);
uint64_t cage_size_needed = std::max(cage_size_needed_32, cage_size_needed_64);
bool can_enable = virtual_memory_available >= cage_size_needed;
per_process::Debug(DebugCategory::BOOTSTRAP,
"Virtual memory available: %" PRIu64 " bytes,\n"
"cage size needed for 32-bit: %" PRIu64 " bytes,\n"
"cage size needed for 64-bit: %" PRIu64 " bytes,\n"
"Can%senable WASM trap handler\n",
virtual_memory_available,
cage_size_needed_32,
cage_size_needed_64,
can_enable ? " " : " not ");

return can_enable;
#else
return false;
#endif // __POSIX__
}
#endif // NODE_USE_V8_WASM_TRAP_HANDLER

static std::shared_ptr<InitializationResultImpl>
InitializeOncePerProcessInternal(const std::vector<std::string>& args,
ProcessInitializationFlags::Flags flags =
Expand Down Expand Up @@ -1257,7 +1295,9 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args,
bool use_wasm_trap_handler =
!per_process::cli_options->disable_wasm_trap_handler;
if (!(flags & ProcessInitializationFlags::kNoDefaultSignalHandling) &&
use_wasm_trap_handler) {
use_wasm_trap_handler && CanEnableWebAssemblyTrapHandler()) {
per_process::Debug(DebugCategory::BOOTSTRAP,
"Enabling WebAssembly trap handler for bounds checks\n");
#if defined(_WIN32)
constexpr ULONG first = TRUE;
per_process::old_vectored_exception_handler =
Expand Down
17 changes: 5 additions & 12 deletions test/testpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
LS_RE = re.compile(r'^test-.*\.m?js$')
ENV_PATTERN = re.compile(r"//\s+Env:(.*)")
NODE_TEST_PATTERN = re.compile(r"('|`|\")node:test\1")
RLIMIT_AS_PATTERN = re.compile(r"//\s+RLIMIT_AS:\s*(\d+)")

class SimpleTestCase(test.TestCase):

Expand Down Expand Up @@ -99,6 +100,10 @@ def GetRunConfiguration(self):
else:
result += flags

rlimit_as_match = RLIMIT_AS_PATTERN.search(source)
if rlimit_as_match:
self.max_virtual_memory = int(rlimit_as_match.group(1))

if self.context.use_error_reporter and NODE_TEST_PATTERN.search(source):
result += ['--test-reporter=./test/common/test-error-reporter.js',
'--test-reporter-destination=stdout']
Expand Down Expand Up @@ -189,15 +194,3 @@ def ListTests(self, current_path, path, arch, mode):
for tst in result:
tst.disable_core_files = True
return result

class WasmAllocationTestConfiguration(SimpleTestConfiguration):
def __init__(self, context, root, section, additional=None):
super(WasmAllocationTestConfiguration, self).__init__(context, root, section,
additional)

def ListTests(self, current_path, path, arch, mode):
result = super(WasmAllocationTestConfiguration, self).ListTests(
current_path, path, arch, mode)
for tst in result:
tst.max_virtual_memory = 5 * 1024 * 1024 * 1024 # 5GB
return result
12 changes: 12 additions & 0 deletions test/wasm-allocation/test-wasm-allocation-auto-adapt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// RLIMIT_AS: 3221225472
// When the virtual memory limit is 3GB, there is not enough virtual memory for
// even one wasm cage. In this case Node.js should automatically adapt and
// skip enabling trap-based bounds checks, so that WASM can at least run with
// inline bound checks.
'use strict';

require('../common');
new WebAssembly.Memory({ initial: 10, maximum: 100 });

// Test memory64 works too.
new WebAssembly.Memory({ address: 'i64', initial: 10n, maximum: 100n });
20 changes: 20 additions & 0 deletions test/wasm-allocation/test-wasm-allocation-disable-trap-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Flags: --disable-wasm-trap-handler
// RLIMIT_AS: 34359738368

// 32GB should be enough for at least 2 cages, but not enough for 30.

// Test that with limited virtual memory space, --disable-wasm-trap-handler
// fully disables trap-based bounds checks, and thus allows WASM to run with
// inline bound checks.
'use strict';

require('../common');
const instances = [];
for (let i = 0; i < 30; i++) {
instances.push(new WebAssembly.Memory({ initial: 10, maximum: 100 }));
}

// Test memory64 works too.
for (let i = 0; i < 30; i++) {
instances.push(new WebAssembly.Memory({ initial: 10n, maximum: 100n, address: 'i64' }));
}
22 changes: 22 additions & 0 deletions test/wasm-allocation/test-wasm-allocation-memory64.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// RLIMIT_AS: 21474836480
// With 20GB virtual memory, there's enough space for the first few wasm memory64
// allocation to succeed, but not enough for many subsequent ones since each
// wasm memory32 with guard regions reserves 8GB of virtual address space.
'use strict';

require('../common');
const assert = require('assert');

// The first allocation should succeed.
const first = new WebAssembly.Memory({ address: 'i64', initial: 10n, maximum: 100n });
assert.ok(first);

// Subsequent allocations should eventually fail due to running out of
// virtual address space. memory64 reserves 16GB per allocation (vs 8GB for
// memory32), so the limit is reached even faster.
assert.throws(() => {
const instances = [first];
for (let i = 1; i < 30; i++) {
instances.push(new WebAssembly.Memory({ address: 'i64', initial: 10n, maximum: 100n }));
}
}, /WebAssembly\.Memory/);
22 changes: 18 additions & 4 deletions test/wasm-allocation/test-wasm-allocation.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
// Flags: --disable-wasm-trap-handler
// Test that with limited virtual memory space, --disable-wasm-trap-handler
// allows WASM to at least run with inline bound checks.
// RLIMIT_AS: 21474836480
// With 20GB virtual memory, there's enough space for the first few wasm memory
// allocation to succeed, but not enough for many subsequent ones since each
// wasm memory32 with guard regions reserves 8GB of virtual address space.
'use strict';

require('../common');
new WebAssembly.Memory({ initial: 10, maximum: 100 });
const assert = require('assert');

// The first allocation should succeed.
const first = new WebAssembly.Memory({ initial: 10, maximum: 100 });
assert.ok(first);

// Subsequent allocations should eventually fail due to running out of
// virtual address space.
assert.throws(() => {
const instances = [first];
for (let i = 1; i < 30; i++) {
instances.push(new WebAssembly.Memory({ initial: 10, maximum: 100 }));
}
}, /WebAssembly\.Memory/);
2 changes: 1 addition & 1 deletion test/wasm-allocation/testcfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
import testpy

def GetConfiguration(context, root):
return testpy.WasmAllocationTestConfiguration(context, root, 'wasm-allocation')
return testpy.SimpleTestConfiguration(context, root, 'wasm-allocation')
3 changes: 3 additions & 0 deletions test/wasm-allocation/wasm-allocation.status
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ prefix wasm-allocation

[$system!=linux || $asan==on || $pointer_compression==on]
test-wasm-allocation: SKIP
test-wasm-allocation-auto-adapt: SKIP
test-wasm-allocation-memory64: SKIP
test-wasm-allocation-memory64-auto-adapt: SKIP
Loading