Files
Andreas Haas 401e805dd6 [sandbox] Partition CppHeapPointerTags
The allocation of CppHeapPointerTags caused problems in the past, so
this CL is an attempt to partition CppHeapPointerTags to make it easier
to allocate them correctly. The idea is to split up the tag range into
sub-sections, and then let different components allocate tags in their
subsection freely. All we check is that the components stay within their
subsections.

Within V8, two different tag ranges have to be distinguished: the range
of tags of sub classes of Object::Wrappable, and the range of other
tags. These ranges are then split into the sub-ranges that are used
internally, and those that are used by the embedder.

Furthermore, as a cleanup, this CL moves the enums for tags that are
used V8 internally from v8-sandbox.h to a new header file,
cppheap-pointer-tag.h.

Bug: 563066864
Change-Id: I23de1e86c33da8eefacdc8ca26c2e5f267111a8d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8430419
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Commit-Queue: Andreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/main@{#109979}
2026-09-21 14:37:24 -07:00

429 lines
15 KiB
C++

// Copyright 2024 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "include/cppgc/allocation.h"
#include "include/v8-context.h"
#include "include/v8-internal.h"
#include "include/v8-local-handle.h"
#include "include/v8-persistent-handle.h"
#include "include/v8-sandbox.h"
#include "include/v8-template.h"
#include "src/api/api-inl.h"
#include "src/base/macros.h"
#include "src/objects/js-objects-inl.h"
#include "test/benchmarks/cpp/benchmark-utils.h"
#include "third_party/google_benchmark_chrome/src/include/benchmark/benchmark.h"
namespace {
constexpr v8::EmbedderDataTypeTag kPerContextDataTag = 1;
v8::Local<v8::String> v8_str(const char* x) {
return v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), x).ToLocalChecked();
}
struct WrapperTypeInfo : v8::Object::WrapperTypeInfo {
uint16_t embedder_id;
};
struct PerContextData {
cppgc::AllocationHandle& allocation_handle;
std::map<WrapperTypeInfo*, v8::Global<v8::ObjectTemplate>> object_templates;
};
class ManagedWrappableBase : public v8::Object::Wrappable {
public:
void SetWrapper(v8::Isolate* isolate, v8::Local<v8::Value> value) {
wrapper_.Reset(isolate, value);
}
const char* GetHumanReadableName() const override {
return "ManagedWrappableBase";
}
virtual void Trace(cppgc::Visitor* visitor) const override {
v8::Object::Wrappable::Trace(visitor);
visitor->Trace(wrapper_);
}
private:
v8::TracedReference<v8::Value> wrapper_;
};
class ManagedWrappableValue : public ManagedWrappableBase {
public:
static WrapperTypeInfo wrapper_type_info;
const v8::Object::WrapperTypeInfo* GetWrapperTypeInfo() const override {
return &wrapper_type_info;
}
};
WrapperTypeInfo ManagedWrappableValue::wrapper_type_info{
{}, v8::benchmarking::kEmbedderId};
class ManagedGlobalWrappable : public ManagedWrappableBase {
public:
static WrapperTypeInfo wrapper_type_info;
const WrapperTypeInfo* GetWrapperTypeInfo() const override {
return &wrapper_type_info;
}
ManagedWrappableValue* GetWrappableValue(
cppgc::AllocationHandle& allocation_Handle) {
return cppgc::MakeGarbageCollected<ManagedWrappableValue>(
allocation_Handle);
}
uint16_t GetSmiNumber() { return 17; }
};
WrapperTypeInfo ManagedGlobalWrappable::wrapper_type_info{
{}, v8::benchmarking::kEmbedderId};
class UnmanagedWrappableBase {
public:
virtual ~UnmanagedWrappableBase() = default;
virtual WrapperTypeInfo* GetWrapperTypeInfo() = 0;
void SetWrapper(v8::Isolate* isolate, v8::Local<v8::Value> value) {
wrapper_.Reset(isolate, value);
wrapper_.SetWeak(this, FirstWeakCallback, v8::WeakCallbackType::kParameter);
}
private:
static void FirstWeakCallback(
const v8::WeakCallbackInfo<UnmanagedWrappableBase>& data) {
UnmanagedWrappableBase* wrappable = data.GetParameter();
wrappable->wrapper_.Reset();
data.SetSecondPassCallback(SecondWeakCallback);
}
static void SecondWeakCallback(
const v8::WeakCallbackInfo<UnmanagedWrappableBase>& data) {
UnmanagedWrappableBase* wrappable = data.GetParameter();
delete wrappable;
}
v8::Global<v8::Value> wrapper_;
};
class UnmanagedWrappableValue : public UnmanagedWrappableBase {
public:
static WrapperTypeInfo wrapper_type_info;
WrapperTypeInfo* GetWrapperTypeInfo() override { return &wrapper_type_info; }
};
WrapperTypeInfo UnmanagedWrappableValue::wrapper_type_info{
{}, v8::benchmarking::kEmbedderId};
class UnmanagedGlobalWrappable : public UnmanagedWrappableBase {
public:
static WrapperTypeInfo wrapper_type_info;
WrapperTypeInfo* GetWrapperTypeInfo() override { return &wrapper_type_info; }
UnmanagedWrappableValue* GetWrappableValue() {
return new UnmanagedWrappableValue;
}
uint16_t GetSmiNumber() { return 17; }
};
WrapperTypeInfo UnmanagedGlobalWrappable::wrapper_type_info{
{}, v8::benchmarking::kEmbedderId};
template <typename WrappableValueType>
v8::Local<v8::ObjectTemplate> GetInstanceTemplateForContext(
v8::Isolate* isolate, PerContextData* data,
WrapperTypeInfo* wrapper_type_info, int number_of_internal_fields) {
auto it =
data->object_templates.find((&WrappableValueType::wrapper_type_info));
v8::Local<v8::ObjectTemplate> instance_tpl;
if (it == data->object_templates.end()) {
v8::Local<v8::FunctionTemplate> function_template =
v8::FunctionTemplate::New(isolate);
auto object_template = function_template->InstanceTemplate();
object_template->SetInternalFieldCount(number_of_internal_fields);
data->object_templates.emplace(
&WrappableValueType::wrapper_type_info,
v8::Global<v8::ObjectTemplate>(isolate, object_template));
instance_tpl = object_template;
} else {
instance_tpl = it->second.Get(isolate);
}
return instance_tpl;
}
template <typename ConcreteBindings>
class BindingsBenchmarkBase : public v8::benchmarking::BenchmarkWithIsolate {
public:
static void AccessorReturningWrapper(
const v8::FunctionCallbackInfo<v8::Value>& info) {
// Preamble.
auto* isolate = info.GetIsolate();
auto ctx = isolate->GetCurrentContext();
auto* data = reinterpret_cast<PerContextData*>(
ctx->GetAlignedPointerFromEmbedderData(v8::benchmarking::kEmbedderId,
kPerContextDataTag));
// Unwrap: Get the C++ instance pointer.
typename ConcreteBindings::GlobalWrappable* receiver =
ConcreteBindings::template Unwrap<
typename ConcreteBindings::GlobalWrappable>(isolate, info.This());
// Invoke the actual operation.
typename ConcreteBindings::WrappableValue* return_value =
ConcreteBindings::GetWrappableValue(data, receiver);
// Wrap the C++ value with a JS value.
auto v8_wrapper = ConcreteBindings::Wrap(
isolate, ctx, data, return_value,
&ConcreteBindings::WrappableValue::wrapper_type_info);
// Return the JS value back to V8.
info.GetReturnValue().SetNonEmpty(v8_wrapper);
}
static void AccessorReturningSmi(
const v8::FunctionCallbackInfo<v8::Value>& info) {
// Preamble.
auto* isolate = info.GetIsolate();
// Unwrap: Get the C++ instance pointer.
typename ConcreteBindings::GlobalWrappable* receiver =
ConcreteBindings::template Unwrap<
typename ConcreteBindings::GlobalWrappable>(isolate, info.This());
// Invoke the actual operation.
uint16_t return_value = receiver->GetSmiNumber();
// Return Smi.
info.GetReturnValue().Set(return_value);
}
void SetUp(::benchmark::State& state) override {
auto* isolate = v8_isolate();
v8::HandleScope handle_scope(isolate);
auto proxy_template_function = v8::FunctionTemplate::New(isolate);
auto object_template = proxy_template_function->InstanceTemplate();
ConcreteBindings::SetupContextTemplate(object_template);
object_template->SetAccessorProperty(
v8_str("accessorReturningWrapper"),
v8::FunctionTemplate::New(isolate, &AccessorReturningWrapper));
object_template->SetAccessorProperty(
v8_str("accessorReturningSmi"),
v8::FunctionTemplate::New(isolate, &AccessorReturningSmi));
v8::Local<v8::Context> context =
v8::Context::New(isolate, nullptr, object_template);
auto* per_context_data = new PerContextData{allocation_handle(), {}};
context->SetAlignedPointerInEmbedderData(0, per_context_data,
kPerContextDataTag);
auto* global_wrappable =
ConcreteBindings::CreateGlobalWrappable(per_context_data);
CHECK(context->Global()->IsApiWrapper());
ConcreteBindings::AssociateWithWrapper(
isolate, context->Global(),
&ConcreteBindings::GlobalWrappable::wrapper_type_info,
global_wrappable);
context_.Reset(isolate, context);
context->Enter();
}
void TearDown(::benchmark::State& state) override {
auto* isolate = v8_isolate();
v8::HandleScope handle_scope(isolate);
auto context = context_.Get(isolate);
delete reinterpret_cast<PerContextData*>(
context->GetAlignedPointerFromEmbedderData(
v8::benchmarking::kEmbedderId, kPerContextDataTag));
context->Exit();
context_.Reset();
}
v8::Local<v8::Script> CompileBenchmarkScript(const char* source) {
v8::EscapableHandleScope handle_scope(v8_isolate());
v8::Local<v8::Context> context = v8_context();
v8::Local<v8::String> v8_source = v8_str(source);
v8::Local<v8::Script> script =
v8::Script::Compile(context, v8_source).ToLocalChecked();
return handle_scope.Escape(script);
}
protected:
v8::Local<v8::Context> v8_context() { return context_.Get(v8_isolate()); }
v8::Global<v8::Context> context_;
};
class UnmanagedBindings : public BindingsBenchmarkBase<UnmanagedBindings> {
public:
using WrappableBase = UnmanagedWrappableBase;
using WrappableValue = UnmanagedWrappableValue;
using GlobalWrappable = UnmanagedGlobalWrappable;
static V8_INLINE WrappableValue* GetWrappableValue(
PerContextData* data, GlobalWrappable* receiver) {
return receiver->GetWrappableValue();
}
static V8_INLINE GlobalWrappable* CreateGlobalWrappable(PerContextData*) {
return new GlobalWrappable;
}
static V8_INLINE v8::Local<v8::Object> Wrap(v8::Isolate* isolate,
v8::Local<v8::Context>& context,
PerContextData* data,
WrappableBase* wrappable,
WrapperTypeInfo* info) {
// Allocate a new JS wrapper.
v8::Local<v8::ObjectTemplate> wrapper_instance_tpl =
GetInstanceTemplateForContext<WrappableValue>(
isolate, data, &WrappableValue::wrapper_type_info, 2);
auto v8_wrapper =
wrapper_instance_tpl->NewInstance(context).ToLocalChecked();
AssociateWithWrapper(isolate, v8_wrapper, info, wrappable);
return v8_wrapper;
}
static V8_INLINE void AssociateWithWrapper(v8::Isolate* isolate,
v8::Local<v8::Object> v8_wrapper,
WrapperTypeInfo* info,
WrappableBase* wrappable) {
// Set V8 to C++ reference.
v8_wrapper->SetAlignedPointerInInternalField(
v8::benchmarking::kTypeOffset, info, v8::kEmbedderDataTypeTagDefault);
v8_wrapper->SetAlignedPointerInInternalField(
v8::benchmarking::kInstanceOffset, wrappable,
v8::kEmbedderDataTypeTagDefault);
// Set C++ to V8 reference.
wrappable->SetWrapper(isolate, v8_wrapper);
}
template <typename T>
static V8_INLINE T* Unwrap(v8::Isolate* isolate, v8::Local<v8::Object> thiz) {
return reinterpret_cast<T*>(thiz->GetAlignedPointerFromInternalField(
v8::benchmarking::kInstanceOffset, v8::kEmbedderDataTypeTagDefault));
}
static void SetupContextTemplate(
v8::Local<v8::ObjectTemplate>& object_template) {
object_template->SetInternalFieldCount(2);
}
};
class ManagedBindings : public BindingsBenchmarkBase<ManagedBindings> {
public:
using WrappableBase = ManagedWrappableBase;
using WrappableValue = ManagedWrappableValue;
using GlobalWrappable = ManagedGlobalWrappable;
static V8_INLINE WrappableValue* GetWrappableValue(
PerContextData* data, GlobalWrappable* receiver) {
return receiver->GetWrappableValue(data->allocation_handle);
}
static V8_INLINE GlobalWrappable* CreateGlobalWrappable(
PerContextData* per_context_data) {
return cppgc::MakeGarbageCollected<GlobalWrappable>(
per_context_data->allocation_handle);
}
static V8_INLINE v8::Local<v8::Object> Wrap(v8::Isolate* isolate,
v8::Local<v8::Context>& context,
PerContextData* data,
WrappableBase* wrappable,
WrapperTypeInfo* info) {
// Allocate a new JS wrapper.
v8::Local<v8::ObjectTemplate> wrapper_instance_tpl =
GetInstanceTemplateForContext<WrappableValue>(
isolate, data, &WrappableValue::wrapper_type_info, 0);
auto v8_wrapper =
wrapper_instance_tpl->NewInstance(context).ToLocalChecked();
AssociateWithWrapper(isolate, v8_wrapper, info, wrappable);
return v8_wrapper;
}
static V8_INLINE void AssociateWithWrapper(v8::Isolate* isolate,
v8::Local<v8::Object> v8_wrapper,
WrapperTypeInfo* info,
WrappableBase* wrappable) {
// Set V8 to C++ reference.
v8::Object::Wrap<v8::internal::kTagForTesting>(isolate, v8_wrapper,
wrappable);
// Set C++ to V8 reference.
wrappable->SetWrapper(isolate, v8_wrapper);
}
template <typename T>
static V8_INLINE T* Unwrap(v8::Isolate* isolate, v8::Local<v8::Object> thiz) {
return v8::Object::Unwrap<v8::internal::kTagForTesting, T>(isolate, thiz);
}
static void SetupContextTemplate(
v8::Local<v8::ObjectTemplate>& object_template) {}
};
} // namespace
const char* kScriptInvocingAccessorReturingWrapper =
"function invoke() { globalThis.accessorReturningWrapper; }"
"for (var i =0; i < 1_000; i++) invoke();";
BENCHMARK_F(UnmanagedBindings, AccessorReturningWrapper)(benchmark::State& st) {
v8::HandleScope handle_scope(v8_isolate());
v8::Local<v8::Context> context = v8_context();
v8::Local<v8::Script> script =
CompileBenchmarkScript(kScriptInvocingAccessorReturingWrapper);
v8::HandleScope benchmark_handle_scope(v8_isolate());
for (auto _ : st) {
USE(_);
v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
benchmark::DoNotOptimize(result);
}
}
BENCHMARK_F(ManagedBindings, AccessorReturningWrapper)(benchmark::State& st) {
v8::HandleScope handle_scope(v8_isolate());
v8::Local<v8::Context> context = v8_context();
v8::Local<v8::Script> script =
CompileBenchmarkScript(kScriptInvocingAccessorReturingWrapper);
v8::HandleScope benchmark_handle_scope(v8_isolate());
for (auto _ : st) {
USE(_);
v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
benchmark::DoNotOptimize(result);
}
}
const char* kScriptInvocingAccessorReturingSmi =
"function invoke() { globalThis.accessorReturningSmi; }"
"for (var i =0; i < 1_000; i++) invoke();";
BENCHMARK_F(UnmanagedBindings, AccessorReturningSmi)(benchmark::State& st) {
v8::HandleScope handle_scope(v8_isolate());
v8::Local<v8::Context> context = v8_context();
v8::Local<v8::Script> script =
CompileBenchmarkScript(kScriptInvocingAccessorReturingSmi);
v8::HandleScope benchmark_handle_scope(v8_isolate());
for (auto _ : st) {
USE(_);
v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
benchmark::DoNotOptimize(result);
}
}
BENCHMARK_F(ManagedBindings, AccessorReturningSmi)(benchmark::State& st) {
v8::HandleScope handle_scope(v8_isolate());
v8::Local<v8::Context> context = v8_context();
v8::Local<v8::Script> script =
CompileBenchmarkScript(kScriptInvocingAccessorReturingSmi);
v8::HandleScope benchmark_handle_scope(v8_isolate());
for (auto _ : st) {
USE(_);
v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
benchmark::DoNotOptimize(result);
}
}