Commit 8439314d authored by Vicky Kontoura's avatar Vicky Kontoura Committed by V8 LUCI CQ

[web snapshot] Support classes

This CL adds support for classes with methods.

More specifically:
- A new ValueSerializer is added and classes are serialized separetely
from functions, although the common parts are handled in the same way
and abstracted away.
- The function prototype is serialized as an object and any missing
information is set up again during deserialization.
- FunctionFlagsToFunctionKinds() is updated to allow for more function
kinds.
- Context serialization is updated to support serializing BlockContexts
and creating ScopeInfos of type CLASS_SCOPE.
- Map serialization is updated to support properties with custom
attributes.

Bug: v8:11525, v8:11706
Change-Id: I16ca7cbc17b1811721081cda05124ce36073f9be
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3006416
Commit-Queue: Vicky Kontoura <vkont@google.com>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarMarja Hölttä <marja@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75893}
parent 1708ee63
......@@ -477,6 +477,7 @@ class RuntimeCallTimer final {
V(WebSnapshotDeserialize_Contexts) \
V(WebSnapshotDeserialize_Exports) \
V(WebSnapshotDeserialize_Functions) \
V(WebSnapshotDeserialize_Classes) \
V(WebSnapshotDeserialize_Maps) \
V(WebSnapshotDeserialize_Objects) \
V(WebSnapshotDeserialize_Strings)
......
......@@ -444,6 +444,16 @@ void WeakFixedArray::Set(int index, MaybeObject value, WriteBarrierMode mode) {
set_objects(index, value, mode);
}
Handle<WeakFixedArray> WeakFixedArray::EnsureSpace(Isolate* isolate,
Handle<WeakFixedArray> array,
int length) {
if (array->length() < length) {
int grow_by = length - array->length();
array = isolate->factory()->CopyWeakFixedArrayAndGrow(array, grow_by);
}
return array;
}
MaybeObjectSlot WeakFixedArray::data_start() {
return RawMaybeWeakField(kObjectsOffset);
}
......
......@@ -285,6 +285,10 @@ class WeakFixedArray
int index, MaybeObject value,
WriteBarrierMode mode = WriteBarrierMode::UPDATE_WRITE_BARRIER);
static inline Handle<WeakFixedArray> EnsureSpace(Isolate* isolate,
Handle<WeakFixedArray> array,
int length);
// Forward declare the non-atomic (set_)length defined in torque.
using TorqueGeneratedWeakFixedArray::length;
using TorqueGeneratedWeakFixedArray::set_length;
......
This diff is collapsed.
......@@ -52,11 +52,22 @@ class WebSnapshotSerializerDeserializer {
ARRAY_ID,
OBJECT_ID,
FUNCTION_ID,
CLASS_ID,
REGEXP
};
enum ContextType : uint8_t { FUNCTION, BLOCK };
enum PropertyAttributesType : uint8_t { DEFAULT, CUSTOM };
uint32_t FunctionKindToFunctionFlags(FunctionKind kind);
FunctionKind FunctionFlagsToFunctionKind(uint32_t flags);
bool IsFunctionOrMethod(uint32_t flags);
bool IsConstructor(uint32_t flags);
uint32_t GetDefaultAttributeFlags();
uint32_t AttributesToFlags(PropertyDetails details);
PropertyAttributes FlagsToAttributes(uint32_t flags);
// The maximum count of items for each value type (strings, objects etc.)
static constexpr uint32_t kMaxItemCount =
......@@ -81,9 +92,18 @@ class WebSnapshotSerializerDeserializer {
// Keep most common function kinds in the 7 least significant bits to make the
// flags fit in 1 byte.
using ArrowFunctionBitField = base::BitField<bool, 0, 1>;
using AsyncFunctionBitField = ArrowFunctionBitField::Next<bool, 1>;
using AsyncFunctionBitField = base::BitField<bool, 0, 1>;
using GeneratorFunctionBitField = AsyncFunctionBitField::Next<bool, 1>;
using ArrowFunctionBitField = GeneratorFunctionBitField::Next<bool, 1>;
using MethodBitField = ArrowFunctionBitField::Next<bool, 1>;
using StaticBitField = MethodBitField::Next<bool, 1>;
using ClassConstructorBitField = StaticBitField::Next<bool, 1>;
using DefaultConstructorBitField = ClassConstructorBitField::Next<bool, 1>;
using DerivedConstructorBitField = DefaultConstructorBitField::Next<bool, 1>;
using ReadOnlyBitField = base::BitField<bool, 0, 1>;
using ConfigurableBitField = ReadOnlyBitField::Next<bool, 1>;
using EnumerableBitField = ConfigurableBitField::Next<bool, 1>;
};
class V8_EXPORT WebSnapshotSerializer
......@@ -111,6 +131,10 @@ class V8_EXPORT WebSnapshotSerializer
return static_cast<uint32_t>(function_ids_.size());
}
uint32_t class_count() const {
return static_cast<uint32_t>(class_ids_.size());
}
uint32_t array_count() const {
return static_cast<uint32_t>(array_ids_.size());
}
......@@ -129,10 +153,15 @@ class V8_EXPORT WebSnapshotSerializer
// Returns true if the object was already in the map, false if it was added.
bool InsertIntoIndexMap(ObjectCacheIndexMap& map, Handle<HeapObject> object,
uint32_t& id);
void SerializeSource(ValueSerializer* serializer,
Handle<JSFunction> function);
void SerializeFunctionInfo(ValueSerializer* serializer,
Handle<JSFunction> function);
void SerializeString(Handle<String> string, uint32_t& id);
void SerializeMap(Handle<Map> map, uint32_t& id);
void SerializeFunction(Handle<JSFunction> function, uint32_t& id);
void SerializeClass(Handle<JSFunction> function, uint32_t& id);
void SerializeContext(Handle<Context> context, uint32_t& id);
void SerializeArray(Handle<JSArray> array, uint32_t& id);
void SerializePendingArray(Handle<JSArray> array);
......@@ -145,6 +174,7 @@ class V8_EXPORT WebSnapshotSerializer
ValueSerializer map_serializer_;
ValueSerializer context_serializer_;
ValueSerializer function_serializer_;
ValueSerializer class_serializer_;
ValueSerializer array_serializer_;
ValueSerializer object_serializer_;
ValueSerializer export_serializer_;
......@@ -153,6 +183,7 @@ class V8_EXPORT WebSnapshotSerializer
ObjectCacheIndexMap map_ids_;
ObjectCacheIndexMap context_ids_;
ObjectCacheIndexMap function_ids_;
ObjectCacheIndexMap class_ids_;
ObjectCacheIndexMap array_ids_;
ObjectCacheIndexMap object_ids_;
uint32_t export_count_ = 0;
......@@ -173,6 +204,7 @@ class V8_EXPORT WebSnapshotDeserializer
uint32_t map_count() const { return map_count_; }
uint32_t context_count() const { return context_count_; }
uint32_t function_count() const { return function_count_; }
uint32_t class_count() const { return class_count_; }
uint32_t array_count() const { return array_count_; }
uint32_t object_count() const { return object_count_; }
......@@ -184,8 +216,14 @@ class V8_EXPORT WebSnapshotDeserializer
Handle<String> ReadString(bool internalize = false);
void DeserializeMaps();
void DeserializeContexts();
Handle<ScopeInfo> CreateScopeInfo(uint32_t variable_count, bool has_parent);
Handle<ScopeInfo> CreateScopeInfo(uint32_t variable_count, bool has_parent,
ContextType context_type);
Handle<JSFunction> CreateJSFunction(int index, uint32_t start,
uint32_t length, uint32_t flags,
uint32_t context_id);
void DeserializeFunctionData(uint32_t count, uint32_t current_count);
void DeserializeFunctions();
void DeserializeClasses();
void DeserializeArrays();
void DeserializeObjects();
void DeserializeExports();
......@@ -205,15 +243,22 @@ class V8_EXPORT WebSnapshotDeserializer
Handle<FixedArray> maps_;
Handle<FixedArray> contexts_;
Handle<FixedArray> functions_;
Handle<FixedArray> classes_;
Handle<FixedArray> arrays_;
Handle<FixedArray> objects_;
Handle<ArrayList> deferred_references_;
Handle<WeakFixedArray> shared_function_infos_;
Handle<ObjectHashTable> shared_function_info_table_;
Handle<Script> script_;
uint32_t string_count_ = 0;
uint32_t map_count_ = 0;
uint32_t context_count_ = 0;
uint32_t function_count_ = 0;
uint32_t current_function_count_ = 0;
uint32_t class_count_ = 0;
uint32_t current_class_count_ = 0;
uint32_t array_count_ = 0;
uint32_t current_array_count_ = 0;
uint32_t object_count_ = 0;
......
......@@ -366,6 +366,62 @@ TEST(SFIDeduplication) {
}
}
TEST(SFIDeduplicationClasses) {
CcTest::InitializeVM();
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
WebSnapshotData snapshot_data;
{
v8::Local<v8::Context> new_context = CcTest::NewContext();
v8::Context::Scope context_scope(new_context);
const char* snapshot_source =
"let foo = {};\n"
"foo.create = function(a) {\n"
" return class {\n"
" constructor(x) {this.x = x;};\n"
" }\n"
"}\n"
"foo.class = foo.create('hi');";
CompileRun(snapshot_source);
v8::Local<v8::PrimitiveArray> exports = v8::PrimitiveArray::New(isolate, 1);
v8::Local<v8::String> str =
v8::String::NewFromUtf8(isolate, "foo").ToLocalChecked();
exports->Set(isolate, 0, str);
WebSnapshotSerializer serializer(isolate);
CHECK(serializer.TakeSnapshot(new_context, exports, snapshot_data));
CHECK(!serializer.has_error());
CHECK_NOT_NULL(snapshot_data.buffer);
}
{
v8::Local<v8::Context> new_context = CcTest::NewContext();
v8::Context::Scope context_scope(new_context);
WebSnapshotDeserializer deserializer(isolate);
CHECK(deserializer.UseWebSnapshot(snapshot_data.buffer,
snapshot_data.buffer_size));
CHECK(!deserializer.has_error());
const char* get_class = "foo.class";
const char* create_new_class = "foo.create()";
// Verify that foo.inner and the JSFunction which is the result of calling
// foo.outer() after deserialization share the SFI.
v8::Local<v8::Function> v8_class1 =
CompileRun(get_class).As<v8::Function>();
v8::Local<v8::Function> v8_class2 =
CompileRun(create_new_class).As<v8::Function>();
Handle<JSFunction> class1 =
Handle<JSFunction>::cast(Utils::OpenHandle(*v8_class1));
Handle<JSFunction> class2 =
Handle<JSFunction>::cast(Utils::OpenHandle(*v8_class2));
CHECK_EQ(class1->shared(), class2->shared());
}
}
TEST(SFIDeduplicationAfterBytecodeFlushing) {
FLAG_stress_flush_bytecode = true;
CcTest::InitializeVM();
......@@ -451,6 +507,91 @@ TEST(SFIDeduplicationAfterBytecodeFlushing) {
}
}
TEST(SFIDeduplicationAfterBytecodeFlushingClasses) {
FLAG_stress_flush_bytecode = true;
CcTest::InitializeVM();
v8::Isolate* isolate = CcTest::isolate();
WebSnapshotData snapshot_data;
{
v8::HandleScope scope(isolate);
v8::Local<v8::Context> new_context = CcTest::NewContext();
v8::Context::Scope context_scope(new_context);
const char* snapshot_source =
"let foo = {};\n"
"foo.create = function(a) {\n"
" return class {\n"
" constructor(x) {this.x = x;};\n"
" }\n"
"}\n"
"foo.class = foo.create('hi');";
CompileRun(snapshot_source);
v8::Local<v8::PrimitiveArray> exports = v8::PrimitiveArray::New(isolate, 1);
v8::Local<v8::String> str =
v8::String::NewFromUtf8(isolate, "foo").ToLocalChecked();
exports->Set(isolate, 0, str);
WebSnapshotSerializer serializer(isolate);
CHECK(serializer.TakeSnapshot(new_context, exports, snapshot_data));
CHECK(!serializer.has_error());
CHECK_NOT_NULL(snapshot_data.buffer);
}
CcTest::CollectAllGarbage();
CcTest::CollectAllGarbage();
{
v8::HandleScope scope(isolate);
v8::Local<v8::Context> new_context = CcTest::NewContext();
v8::Context::Scope context_scope(new_context);
WebSnapshotDeserializer deserializer(isolate);
CHECK(deserializer.UseWebSnapshot(snapshot_data.buffer,
snapshot_data.buffer_size));
CHECK(!deserializer.has_error());
const char* get_create = "foo.create";
const char* get_class = "foo.class";
const char* create_new_class = "foo.create()";
v8::Local<v8::Function> v8_create =
CompileRun(get_create).As<v8::Function>();
Handle<JSFunction> create =
Handle<JSFunction>::cast(Utils::OpenHandle(*v8_create));
CHECK(!create->shared().is_compiled());
v8::Local<v8::Function> v8_class1 =
CompileRun(get_class).As<v8::Function>();
v8::Local<v8::Function> v8_class2 =
CompileRun(create_new_class).As<v8::Function>();
Handle<JSFunction> class1 =
Handle<JSFunction>::cast(Utils::OpenHandle(*v8_class1));
Handle<JSFunction> class2 =
Handle<JSFunction>::cast(Utils::OpenHandle(*v8_class2));
CHECK(create->shared().is_compiled());
CHECK_EQ(class1->shared(), class2->shared());
// Force bytecode flushing of "foo.outer".
CcTest::CollectAllGarbage();
CcTest::CollectAllGarbage();
CHECK(!create->shared().is_compiled());
// Create another inner function.
v8::Local<v8::Function> v8_class3 =
CompileRun(create_new_class).As<v8::Function>();
Handle<JSFunction> class3 =
Handle<JSFunction>::cast(Utils::OpenHandle(*v8_class3));
// Check that it shares the SFI with the original inner function which is in
// the snapshot.
CHECK_EQ(class1->shared(), class3->shared());
}
}
TEST(SFIDeduplicationOfFunctionsNotInSnapshot) {
CcTest::InitializeVM();
v8::Isolate* isolate = CcTest::isolate();
......
......@@ -13,13 +13,13 @@ function use(exports) {
function takeAndUseWebSnapshot(createObjects, exports) {
// Take a snapshot in Realm r1.
const r1 = Realm.create();
Realm.eval(r1, createObjects, {type: 'function'});
Realm.eval(r1, createObjects, { type: 'function' });
const snapshot = Realm.takeWebSnapshot(r1, exports);
// Use the snapshot in Realm r2.
const r2 = Realm.create();
const success = Realm.useWebSnapshot(r2, snapshot);
assertTrue(success);
return Realm.eval(r2, use, {type: 'function', arguments: [exports]});
return Realm.eval(r2, use, { type: 'function', arguments: [exports] });
}
(function TestMinimal() {
......@@ -191,7 +191,7 @@ function takeAndUseWebSnapshot(createObjects, exports) {
(function TestObjectReferencingObject() {
function createObjects() {
globalThis.foo = {
bar: {baz: 11525}
bar: { baz: 11525 }
};
}
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
......@@ -201,7 +201,7 @@ function takeAndUseWebSnapshot(createObjects, exports) {
(function TestContextReferencingObject() {
function createObjects() {
function outer() {
let o = {value: 11525};
let o = { value: 11525 };
function inner() { return o; }
return inner;
}
......@@ -259,7 +259,7 @@ function takeAndUseWebSnapshot(createObjects, exports) {
(function TestArrayContainingObject() {
function createObjects() {
globalThis.foo = {
array: [{a: 1}, {b: 2}]
array: [{ a: 1 }, { b: 2 }]
};
}
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
......@@ -270,14 +270,13 @@ function takeAndUseWebSnapshot(createObjects, exports) {
(function TestArrayContainingFunction() {
function createObjects() {
globalThis.foo = {
array: [function() { return 5; }]
array: [function () { return 5; }]
};
}
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
assertEquals(5, foo.array[0]());
})();
(function TestContextReferencingArray() {
function createObjects() {
function outer() {
......@@ -292,3 +291,46 @@ function takeAndUseWebSnapshot(createObjects, exports) {
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
assertEquals(11525, foo.func()[0]);
})();
(function TestEmptyClass() {
function createObjects() {
globalThis.Foo = class Foo { };
}
const { Foo } = takeAndUseWebSnapshot(createObjects, ['Foo']);
const x = new Foo();
})();
(function TestClassWithConstructor() {
function createObjects() {
globalThis.Foo = class {
constructor() {
this.n = 42;
}
};
}
const { Foo } = takeAndUseWebSnapshot(createObjects, ['Foo']);
const x = new Foo(2);
assertEquals(42, x.n);
})();
(function TestClassWithMethods() {
function createObjects() {
globalThis.Foo = class {
f() { return 7; };
};
}
const { Foo } = takeAndUseWebSnapshot(createObjects, ['Foo']);
const x = new Foo();
assertEquals(7, x.f());
})();
(async function TestClassWithAsyncMethods() {
function createObjects() {
globalThis.Foo = class {
async g() { return 6; };
};
}
const { Foo } = takeAndUseWebSnapshot(createObjects, ['Foo']);
const x = new Foo();
assertEquals(6, await x.g());
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment