src: initial enablement of IsolateGroups

This lays the initial groundwork for enabling the
use of IsolateGroups.

Every isolate in V8 is created within a group. When pointer
compression is enabled, all isolates within a single group
are limited to a 4 GB shared pointer cage. By default, all
isolates in the process share the same group, which means
that when running with pointer compression, the entire
process would be limited to a single 4 GB shared pointer
cage. But, we can create as many IsolateGroups as we want,
limited only by the amount of virtual memory available on
the machine.

PR-URL: https://github.com/nodejs/node/pull/60254
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
This commit is contained in:
James M Snell 2025-10-17 06:22:19 -04:00 committed by GitHub
parent e6d94ef106
commit c2f3c2131d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 62 additions and 4 deletions

View File

@ -443,6 +443,9 @@
['v8_enable_pointer_compression == 1', { ['v8_enable_pointer_compression == 1', {
'defines': ['V8_COMPRESS_POINTERS'], 'defines': ['V8_COMPRESS_POINTERS'],
}], }],
['v8_enable_pointer_compression == 1 and v8_enable_pointer_compression_shared_cage != 1', {
'defines': ['V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES'],
}],
['v8_enable_pointer_compression_shared_cage == 1', { ['v8_enable_pointer_compression_shared_cage == 1', {
'defines': ['V8_COMPRESS_POINTERS_IN_SHARED_CAGE'], 'defines': ['V8_COMPRESS_POINTERS_IN_SHARED_CAGE'],
}], }],

View File

@ -646,6 +646,12 @@ parser.add_argument('--experimental-enable-pointer-compression',
default=None, default=None,
help='[Experimental] Enable V8 pointer compression (limits max heap to 4GB and breaks ABI compatibility)') help='[Experimental] Enable V8 pointer compression (limits max heap to 4GB and breaks ABI compatibility)')
parser.add_argument('--experimental-pointer-compression-shared-cage',
action='store_true',
dest='pointer_compression_shared_cage',
default=None,
help='[Experimental] Use V8 pointer compression with shared cage (requires --experimental-enable-pointer-compression)')
parser.add_argument('--v8-options', parser.add_argument('--v8-options',
action='store', action='store',
dest='v8_options', dest='v8_options',
@ -1789,7 +1795,10 @@ def configure_v8(o, configs):
# Note that enabling pointer compression without enabling sandbox is unsupported by V8, # Note that enabling pointer compression without enabling sandbox is unsupported by V8,
# so this can be broken at any time. # so this can be broken at any time.
o['variables']['v8_enable_sandbox'] = 0 o['variables']['v8_enable_sandbox'] = 0
o['variables']['v8_enable_pointer_compression_shared_cage'] = 1 if options.enable_pointer_compression else 0 # We set v8_enable_pointer_compression_shared_cage to 0 always, even when
# pointer compression is enabled so that we don't accidentally enable shared
# cage mode when pointer compression is on.
o['variables']['v8_enable_pointer_compression_shared_cage'] = 1 if options.pointer_compression_shared_cage else 0
o['variables']['v8_enable_external_code_space'] = 1 if options.enable_pointer_compression else 0 o['variables']['v8_enable_external_code_space'] = 1 if options.enable_pointer_compression else 0
o['variables']['v8_enable_31bit_smis_on_64bit_arch'] = 1 if options.enable_pointer_compression else 0 o['variables']['v8_enable_31bit_smis_on_64bit_arch'] = 1 if options.enable_pointer_compression else 0
o['variables']['v8_enable_extensible_ro_snapshot'] = 0 o['variables']['v8_enable_extensible_ro_snapshot'] = 0

View File

@ -1,6 +1,7 @@
#include "debug_utils-inl.h" #include "debug_utils-inl.h"
#include "env-inl.h" #include "env-inl.h"
#include "node.h" #include "node.h"
#include "node_internals.h"
#include "node_snapshot_builder.h" #include "node_snapshot_builder.h"
using v8::Context; using v8::Context;
@ -127,7 +128,7 @@ CommonEnvironmentSetup::CommonEnvironmentSetup(
if (flags & Flags::kIsForSnapshotting) { if (flags & Flags::kIsForSnapshotting) {
// The isolate must be registered before the SnapshotCreator initializes the // The isolate must be registered before the SnapshotCreator initializes the
// isolate, so that the memory reducer can be initialized. // isolate, so that the memory reducer can be initialized.
isolate = impl_->isolate = Isolate::Allocate(); isolate = impl_->isolate = Isolate::Allocate(GetOrCreateIsolateGroup());
platform->RegisterIsolate(isolate, loop); platform->RegisterIsolate(isolate, loop);
impl_->snapshot_creator.emplace(isolate, params); impl_->snapshot_creator.emplace(isolate, params);

View File

@ -37,6 +37,7 @@ using v8::Function;
using v8::FunctionCallbackInfo; using v8::FunctionCallbackInfo;
using v8::HandleScope; using v8::HandleScope;
using v8::Isolate; using v8::Isolate;
using v8::IsolateGroup;
using v8::Just; using v8::Just;
using v8::JustVoid; using v8::JustVoid;
using v8::Local; using v8::Local;
@ -304,6 +305,17 @@ void SetIsolateUpForNode(v8::Isolate* isolate) {
SetIsolateUpForNode(isolate, settings); SetIsolateUpForNode(isolate, settings);
} }
//
IsolateGroup GetOrCreateIsolateGroup() {
// When pointer compression is disabled, we cannot create new groups,
// in which case we'll always return the default.
if (IsolateGroup::CanCreateNewGroups()) {
return IsolateGroup::Create();
}
return IsolateGroup::GetDefault();
}
// TODO(joyeecheung): we may want to expose this, but then we need to be // TODO(joyeecheung): we may want to expose this, but then we need to be
// careful about what we override in the params. // careful about what we override in the params.
Isolate* NewIsolate(Isolate::CreateParams* params, Isolate* NewIsolate(Isolate::CreateParams* params,
@ -311,7 +323,8 @@ Isolate* NewIsolate(Isolate::CreateParams* params,
MultiIsolatePlatform* platform, MultiIsolatePlatform* platform,
const SnapshotData* snapshot_data, const SnapshotData* snapshot_data,
const IsolateSettings& settings) { const IsolateSettings& settings) {
Isolate* isolate = Isolate::Allocate(); IsolateGroup group = GetOrCreateIsolateGroup();
Isolate* isolate = Isolate::Allocate(group);
if (isolate == nullptr) return nullptr; if (isolate == nullptr) return nullptr;
if (snapshot_data != nullptr) { if (snapshot_data != nullptr) {

View File

@ -56,6 +56,9 @@ extern uint64_t node_start_time;
// Forward declaration // Forward declaration
class Environment; class Environment;
static constexpr uint64_t kMaxPointerCompressionHeap = uint64_t{1}
<< 32; // 4 GiB
// Convert a struct sockaddr to a { address: '1.2.3.4', port: 1234 } JS object. // Convert a struct sockaddr to a { address: '1.2.3.4', port: 1234 } JS object.
// Sets address and port properties on the info object and returns it. // Sets address and port properties on the info object and returns it.
// If |info| is omitted, a new object is returned. // If |info| is omitted, a new object is returned.
@ -342,6 +345,20 @@ void TraceEnvVar(Environment* env,
v8::Local<v8::String> key); v8::Local<v8::String> key);
void DefineZlibConstants(v8::Local<v8::Object> target); void DefineZlibConstants(v8::Local<v8::Object> target);
// If creating new v8::IsolateGroup instance is supported, this returns a
// new instance. Otherwise, it returns the default instance.
//
// An IsolateGroup is a collection of Isolates that share the same underlying
// pointer cage when pointer compression is enabled. When pointer compression is
// disabled, there is a default IsolateGroup that is used for all isolates, and
// when pointer compression is enabled, all isolates in the app share the
// same pointer cage by default that is limited a maximum of 4GB, not counting
// array buffers and off-heap storage. Multiple IsolateGroups can be used to
// work around the 4GB limit, but each group reserves a range of virtual memory
// addresses, so this should be used with care.
v8::IsolateGroup GetOrCreateIsolateGroup();
v8::Isolate* NewIsolate(v8::Isolate::CreateParams* params, v8::Isolate* NewIsolate(v8::Isolate::CreateParams* params,
uv_loop_t* event_loop, uv_loop_t* event_loop,
MultiIsolatePlatform* platform, MultiIsolatePlatform* platform,

View File

@ -128,7 +128,15 @@ void PerIsolateOptions::HandleMaxOldSpaceSizePercentage(
} }
// Get available memory in bytes // Get available memory in bytes
#ifdef V8_COMPRESS_POINTERS
// When pointer compression is enabled, V8 uses a 4 GiB heap limit.
// We'll use the smaller of that or the total system memory as
// reported by uv.
uint64_t total_memory =
std::min(uv_get_total_memory(), kMaxPointerCompressionHeap); // 4 GiB
#else
uint64_t total_memory = uv_get_total_memory(); uint64_t total_memory = uv_get_total_memory();
#endif
uint64_t constrained_memory = uv_get_constrained_memory(); uint64_t constrained_memory = uv_get_constrained_memory();
// Use constrained memory if available, otherwise use total memory // Use constrained memory if available, otherwise use total memory

View File

@ -122,7 +122,11 @@ assert(
); );
// Validate heap sizes against system memory // Validate heap sizes against system memory
const totalMemoryMB = Math.floor(os.totalmem() / 1024 / 1024); // When pointer compression is enabled, the maximum total memory is 4 GB
const totalmem = Math.floor(os.totalmem() / 1024 / 1024);
const totalMemoryMB = process.config.variables.v8_enable_pointer_compression ?
Math.min(4096, totalmem) :
totalmem;
const uint64Max = 2 ** 64 - 1; const uint64Max = 2 ** 64 - 1;
const constrainedMemory = process.constrainedMemory(); const constrainedMemory = process.constrainedMemory();
const constrainedMemoryMB = Math.floor(constrainedMemory / 1024 / 1024); const constrainedMemoryMB = Math.floor(constrainedMemory / 1024 / 1024);

View File

@ -355,6 +355,9 @@
['v8_enable_pointer_compression==1', { ['v8_enable_pointer_compression==1', {
'defines': ['V8_COMPRESS_POINTERS'], 'defines': ['V8_COMPRESS_POINTERS'],
}], }],
['v8_enable_pointer_compression==1 and v8_enable_pointer_compression_shared_cage!=1', {
'defines': ['V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES'],
}],
['v8_enable_pointer_compression_shared_cage==1', { ['v8_enable_pointer_compression_shared_cage==1', {
'defines': ['V8_COMPRESS_POINTERS_IN_SHARED_CAGE'], 'defines': ['V8_COMPRESS_POINTERS_IN_SHARED_CAGE'],
}], }],