[web snapshots] Support function prototype

Bug: v8:11525

Change-Id: Iacdbc486de4aac3df6792f760ee216a5b6e62a27
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3312276
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Commit-Queue: Marja Hölttä <marja@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78272}
This commit is contained in:
Marja Hölttä 2021-12-06 14:30:40 +01:00 committed by V8 LUCI CQ
parent 9b7c14bb33
commit 5f4a7052e6
3 changed files with 114 additions and 59 deletions

View File

@ -147,7 +147,7 @@ FunctionKind WebSnapshotSerializerDeserializer::FunctionFlagsToFunctionKind(
kind = FunctionKind::kInvalid;
}
if (kind == FunctionKind::kInvalid) {
Throw("Web Snapshots: Invalid function flags\n");
Throw("Invalid function flags\n");
}
return kind;
}
@ -501,6 +501,15 @@ void WebSnapshotSerializer::SerializeFunctionInfo(ValueSerializer* serializer,
function->shared().internal_formal_parameter_count_without_receiver());
serializer->WriteUint32(
FunctionKindToFunctionFlags(function->shared().kind()));
if (function->has_prototype_slot() && function->has_instance_prototype()) {
Handle<JSObject> prototype = Handle<JSObject>::cast(
handle(function->instance_prototype(), isolate_));
uint32_t prototype_id = GetObjectId(prototype);
serializer->WriteUint32(prototype_id + 1);
} else {
serializer->WriteUint32(0);
}
}
void WebSnapshotSerializer::Discovery(Handle<Object> start_object) {
@ -556,13 +565,7 @@ void WebSnapshotSerializer::DiscoverFunction(Handle<JSFunction> function) {
DCHECK_EQ(id, functions_->Length());
functions_ = ArrayList::Add(isolate_, functions_, function);
Handle<Context> context(function->context(), isolate_);
if (context->IsFunctionContext() || context->IsBlockContext()) {
DiscoverContext(context);
}
// TODO(v8:11525): Serialize .prototype.
DiscoverContextAndPrototype(function);
// TODO(v8:11525): Support properties in functions.
}
@ -575,16 +578,29 @@ void WebSnapshotSerializer::DiscoverClass(Handle<JSFunction> function) {
DCHECK_EQ(id, classes_->Length());
classes_ = ArrayList::Add(isolate_, classes_, function);
DiscoverContextAndPrototype(function);
// TODO(v8:11525): Support properties in classes.
// TODO(v8:11525): Support class members.
}
void WebSnapshotSerializer::DiscoverContextAndPrototype(
Handle<JSFunction> function) {
Handle<Context> context(function->context(), isolate_);
if (context->IsFunctionContext() || context->IsBlockContext()) {
DiscoverContext(context);
}
Handle<JSObject> prototype =
Handle<JSObject>::cast(handle(function->prototype(), isolate_));
discovery_queue_.push(prototype);
// TODO(v8:11525): Support properties in classes.
// TODO(v8:11525): Support class members.
if (function->has_prototype_slot() &&
function->map().has_non_instance_prototype()) {
Throw("Functions with non-instance prototypes not supported");
return;
}
if (function->has_prototype_slot() && function->has_instance_prototype()) {
Handle<JSObject> prototype = Handle<JSObject>::cast(
handle(function->instance_prototype(), isolate_));
discovery_queue_.push(prototype);
}
}
void WebSnapshotSerializer::DiscoverContext(Handle<Context> context) {
@ -647,6 +663,10 @@ void WebSnapshotSerializer::DiscoverObject(Handle<JSObject> object) {
DCHECK_EQ(id, objects_->Length());
objects_ = ArrayList::Add(isolate_, objects_, object);
// TODO(v8:11525): Support objects with so many properties that they can't be
// in fast mode.
JSObject::MigrateSlowToFast(object, 0, "Web snapshot");
Handle<Map> map(object->map(), isolate_);
for (InternalIndex i : map->IterateOwnDescriptors()) {
PropertyDetails details =
@ -665,14 +685,12 @@ void WebSnapshotSerializer::DiscoverObject(Handle<JSObject> object) {
// - Length in the source snippet
// - Formal parameter count
// - Flags (see FunctionFlags)
// - 0 if there's no function prototype, 1 + object id for the function
// prototype otherwise
// TODO(v8:11525): Investigate whether the length is really needed.
void WebSnapshotSerializer::SerializeFunction(Handle<JSFunction> function) {
SerializeFunctionInfo(&function_serializer_, function);
// TODO(v8:11525): Serialize .prototype.
// TODO(v8:11525): Support properties in functions.
// TODO(v8:11525): Support function referencing a function indirectly (e.g.,
// function -> context -> array -> function).
}
// Format (serialized class):
@ -682,18 +700,10 @@ void WebSnapshotSerializer::SerializeFunction(Handle<JSFunction> function) {
// - Length in the source snippet
// - Formal parameter count
// - Flags (see FunctionFlags)
// - Object id (function prototype)
// - 1 + object id for the function prototype
void WebSnapshotSerializer::SerializeClass(Handle<JSFunction> function) {
SerializeFunctionInfo(&class_serializer_, function);
Handle<JSObject> prototype =
Handle<JSObject>::cast(handle(function->prototype(), isolate_));
uint32_t prototype_id = GetObjectId(prototype);
class_serializer_.WriteUint32(prototype_id);
// TODO(v8:11525): Support properties in classes.
// TODO(v8:11525): Support class referencing a class indirectly (e.g.,
// class -> context -> array -> class).
// TODO(v8:11525): Support class members.
}
@ -940,6 +950,8 @@ void WebSnapshotDeserializer::Throw(const char* message) {
class_count_ = 0;
function_count_ = 0;
object_count_ = 0;
deferred_references_->SetLength(0);
// Make sure we don't read any more data
deserializer_->position_ = deserializer_->end_;
@ -1056,13 +1068,10 @@ bool WebSnapshotDeserializer::DeserializeSnapshot() {
DeserializeFunctions();
DeserializeArrays();
DeserializeObjects();
// It comes in handy to deserialize objects before classes. This
// way, we already have the function prototype for a class deserialized when
// processing the class and it's easier to adjust it as needed.
DeserializeClasses();
ProcessDeferredReferences();
DeserializeExports();
DCHECK_EQ(deferred_references_->Length(), 0);
DCHECK_EQ(0, deferred_references_->Length());
return !has_error();
}
@ -1480,6 +1489,8 @@ void WebSnapshotDeserializer::DeserializeFunctions() {
CreateJSFunction(current_function_count_ + 1, start_position, length,
parameter_count, flags, context_id);
functions_->set(current_function_count_, *function);
ReadFunctionPrototype(function);
}
}
@ -1535,28 +1546,7 @@ void WebSnapshotDeserializer::DeserializeClasses() {
parameter_count, flags, context_id);
classes_->set(current_class_count_, *function);
uint32_t function_prototype;
if (!deserializer_->ReadUint32(&function_prototype) ||
function_prototype >= object_count_) {
Throw("Malformed class");
return;
}
Handle<JSObject> prototype = Handle<JSObject>::cast(
handle(Object::cast(objects_->get(function_prototype)), isolate_));
// TODO(v8:11525): Enforce the invariant that no two prototypes share a map.
Map map = prototype->map();
map.set_is_prototype_map(true);
if (!map.constructor_or_back_pointer().IsNullOrUndefined()) {
Throw("Map already has a constructor or back pointer set");
return;
}
map.set_constructor_or_back_pointer(*function);
function->set_prototype_or_initial_map(*prototype, kReleaseStore);
classes_->set(current_class_count_, *function);
ReadFunctionPrototype(function);
}
}
@ -1842,12 +1832,50 @@ void WebSnapshotDeserializer::ReadValue(
}
}
void WebSnapshotDeserializer::ReadFunctionPrototype(
Handle<JSFunction> function) {
uint32_t object_id;
if (!deserializer_->ReadUint32(&object_id) || object_id > kMaxItemCount + 1) {
Throw("Malformed class / function");
return;
}
if (object_id == 0) {
// No prototype.
return;
}
--object_id;
if (object_id < current_object_count_) {
if (!SetFunctionPrototype(*function,
JSReceiver::cast(objects_->get(object_id)))) {
Throw("Can't reuse function prototype");
return;
}
} else {
// The object hasn't been deserialized yet.
AddDeferredReference(function, 0, OBJECT_ID, object_id);
}
}
bool WebSnapshotDeserializer::SetFunctionPrototype(JSFunction function,
JSReceiver prototype) {
// TODO(v8:11525): Enforce the invariant that no two prototypes share a map.
Map map = prototype.map();
map.set_is_prototype_map(true);
if (!map.constructor_or_back_pointer().IsNullOrUndefined()) {
return false;
}
map.set_constructor_or_back_pointer(function);
function.set_prototype_or_initial_map(prototype, kReleaseStore);
return true;
}
void WebSnapshotDeserializer::AddDeferredReference(Handle<Object> container,
uint32_t index,
ValueType target_type,
uint32_t target_index) {
DCHECK(container->IsPropertyArray() || container->IsContext() ||
container->IsFixedArray());
container->IsFixedArray() || container->IsJSFunction());
deferred_references_ = ArrayList::Add(
isolate_, deferred_references_, container, Smi::FromInt(index),
Smi::FromInt(target_type), Smi::FromInt(target_index));
@ -1882,17 +1910,15 @@ void WebSnapshotDeserializer::ProcessDeferredReferences() {
// Throw can allocate, but it's ok, since we're not using the raw
// pointers after that.
AllowGarbageCollection allow_gc;
Throw("Web Snapshots: Invalid function reference");
Throw("Invalid function reference");
return;
}
target = raw_functions.get(target_index);
break;
case CLASS_ID:
if (static_cast<uint32_t>(target_index) >= class_count_) {
// Throw can allocate, but it's ok, since we're not using the raw
// pointers after that.
AllowGarbageCollection allow_gc;
Throw("Web Snapshots: Invalid class reference");
Throw("Invalid class reference");
return;
}
target = raw_classes.get(target_index);
@ -1900,7 +1926,7 @@ void WebSnapshotDeserializer::ProcessDeferredReferences() {
case ARRAY_ID:
if (static_cast<uint32_t>(target_index) >= array_count_) {
AllowGarbageCollection allow_gc;
Throw("Web Snapshots: Invalid array reference");
Throw("Invalid array reference");
return;
}
target = raw_arrays.get(target_index);
@ -1908,7 +1934,7 @@ void WebSnapshotDeserializer::ProcessDeferredReferences() {
case OBJECT_ID:
if (static_cast<uint32_t>(target_index) >= object_count_) {
AllowGarbageCollection allow_gc;
Throw("Web Snapshots: Invalid object reference");
Throw("Invalid object reference");
return;
}
target = raw_objects.get(target_index);
@ -1922,6 +1948,17 @@ void WebSnapshotDeserializer::ProcessDeferredReferences() {
Context::cast(container).set(index, target);
} else if (container.IsFixedArray()) {
FixedArray::cast(container).set(index, target);
} else if (container.IsJSFunction()) {
// The only deferred reference allowed for a JSFunction is the function
// prototype.
DCHECK_EQ(index, 0);
DCHECK(target.IsJSReceiver());
if (!SetFunctionPrototype(JSFunction::cast(container),
JSReceiver::cast(target))) {
AllowGarbageCollection allow_gc;
Throw("Can't reuse function prototype");
return;
}
} else {
UNREACHABLE();
}

View File

@ -159,6 +159,7 @@ class V8_EXPORT WebSnapshotSerializer
void Discovery(Handle<Object> object);
void DiscoverFunction(Handle<JSFunction> function);
void DiscoverClass(Handle<JSFunction> function);
void DiscoverContextAndPrototype(Handle<JSFunction> function);
void DiscoverContext(Handle<Context> context);
void DiscoverArray(Handle<JSArray> array);
void DiscoverObject(Handle<JSObject> object);
@ -259,6 +260,8 @@ class V8_EXPORT WebSnapshotDeserializer
Handle<Object>& value, Representation& representation,
Handle<Object> object_for_deferred_reference = Handle<Object>(),
uint32_t index_for_deferred_reference = 0);
void ReadFunctionPrototype(Handle<JSFunction> function);
bool SetFunctionPrototype(JSFunction function, JSReceiver prototype);
void AddDeferredReference(Handle<Object> container, uint32_t index,
ValueType target_type,

View File

@ -370,3 +370,18 @@ function takeAndUseWebSnapshot(createObjects, exports) {
%OptimizeFunctionOnNextCall(C);
assertEquals(4, new C(1, 3).x);
})();
(function TestFunctionPrototype() {
function createObjects() {
globalThis.F = function(p1, p2) {
this.x = p1 + p2;
}
globalThis.F.prototype.m = function(p1, p2) {
return this.x + p1 + p2;
}
}
const { F } = takeAndUseWebSnapshot(createObjects, ['F']);
const o = new F(1, 2);
assertEquals(3, o.x);
assertEquals(10, o.m(3, 4));
})();