[web snapshot] Support properties in function

This CL adds the serialization and deserialization
for properties in function. And we only support fast
properties in property array now.

Bug: v8:11525
Change-Id: If0bb3fee400ca957009d046ed74b92d8192c2514
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3650675
Commit-Queue: 王澳 <wangao.james@bytedance.com>
Reviewed-by: Marja Hölttä <marja@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80858}
This commit is contained in:
jameslahm 2022-05-31 20:45:14 +08:00 committed by V8 LUCI CQ
parent cbdc545dcb
commit 16fa2f28b7
3 changed files with 329 additions and 61 deletions

View File

@ -590,17 +590,18 @@ void WebSnapshotSerializer::SerializeMap(Handle<Map> map) {
keys.reserve(map->NumberOfOwnDescriptors());
attributes.reserve(map->NumberOfOwnDescriptors());
for (InternalIndex i : map->IterateOwnDescriptors()) {
Handle<Name> key(map->instance_descriptors(kRelaxedLoad).GetKey(i),
isolate_);
keys.push_back(key);
PropertyDetails details =
map->instance_descriptors(kRelaxedLoad).GetDetails(i);
// If there are non-field properties in a map that doesn't allow them, i.e.,
// a non-function map, DiscoverMap has already thrown.
if (details.location() != PropertyLocation::kField) {
Throw("Properties which are not fields not supported");
return;
continue;
}
Handle<Name> key(map->instance_descriptors(kRelaxedLoad).GetKey(i),
isolate_);
keys.push_back(key);
if (first_custom_index >= 0 || details.IsReadOnly() ||
!details.IsConfigurable() || details.IsDontEnum()) {
if (first_custom_index == -1) first_custom_index = i.as_int();
@ -692,6 +693,29 @@ void WebSnapshotSerializer::ConstructSource() {
DCHECK(!in_place);
}
void WebSnapshotSerializer::SerializeFunctionProperties(
Handle<JSFunction> function) {
Handle<Map> map(function->map(), isolate_);
if (function->map() ==
isolate_->context().get(function->shared().function_map_index())) {
function_serializer_.WriteUint32(0);
return;
} else {
function_serializer_.WriteUint32(GetMapId(function->map()) + 1);
}
for (InternalIndex i : map->IterateOwnDescriptors()) {
PropertyDetails details =
map->instance_descriptors(kRelaxedLoad).GetDetails(i);
if (details.location() == PropertyLocation::kDescriptor) {
continue;
}
FieldIndex field_index = FieldIndex::ForDescriptor(*map, i);
Handle<Object> value = JSObject::FastPropertyAt(
isolate_, function, details.representation(), field_index);
WriteValue(value, function_serializer_);
}
}
void WebSnapshotSerializer::SerializeFunctionInfo(Handle<JSFunction> function,
ValueSerializer& serializer) {
if (!function->shared().HasSourceCode()) {
@ -735,7 +759,6 @@ void WebSnapshotSerializer::SerializeFunctionInfo(Handle<JSFunction> function,
} else {
serializer.WriteUint32(0);
}
WriteValue(handle(function->map().prototype(), isolate_), serializer);
}
void WebSnapshotSerializer::ShallowDiscoverExternals(FixedArray externals) {
@ -836,7 +859,19 @@ void WebSnapshotSerializer::Discover(Handle<HeapObject> start_object) {
}
}
void WebSnapshotSerializer::DiscoverMap(Handle<Map> map) {
void WebSnapshotSerializer::DiscoverPropertyKey(Handle<Name> key) {
if (key->IsString()) {
DiscoverString(Handle<String>::cast(key), AllowInPlace::Yes);
} else if (key->IsSymbol()) {
DiscoverSymbol(Handle<Symbol>::cast(key));
} else {
Throw("Property key is not a String / Symbol");
return;
}
}
void WebSnapshotSerializer::DiscoverMap(Handle<Map> map,
bool allow_property_in_descriptor) {
// Dictionary map object names get discovered in DiscoverObject.
if (map->is_dictionary_map()) {
return;
@ -849,16 +884,19 @@ void WebSnapshotSerializer::DiscoverMap(Handle<Map> map) {
DCHECK_EQ(id, maps_->Length());
maps_ = ArrayList::Add(isolate_, maps_, map);
for (InternalIndex i : map->IterateOwnDescriptors()) {
PropertyDetails details =
map->instance_descriptors(kRelaxedLoad).GetDetails(i);
if (details.location() != PropertyLocation::kField) {
if (!allow_property_in_descriptor) {
Throw("Properties which are not fields not supported");
return;
} else {
continue;
}
}
Handle<Name> key(map->instance_descriptors(kRelaxedLoad).GetKey(i),
isolate_);
if (key->IsString()) {
DiscoverString(Handle<String>::cast(key), AllowInPlace::Yes);
} else if (key->IsSymbol()) {
DiscoverSymbol(Handle<Symbol>::cast(key));
} else {
Throw("Map key is not a String / Symbol");
return;
}
DiscoverPropertyKey(key);
}
}
@ -900,8 +938,37 @@ void WebSnapshotSerializer::DiscoverFunction(Handle<JSFunction> function) {
DCHECK_EQ(id, functions_->Length());
functions_ = ArrayList::Add(isolate_, functions_, function);
JSObject::MigrateSlowToFast(function, 0, "Web snapshot");
// TODO(v8:11525): Support functions with so many properties that they can't
// be in fast mode.
if (!function->HasFastProperties()) {
Throw("Unsupported function with dictionary map");
return;
}
DiscoverContextAndPrototype(function);
// TODO(v8:11525): Support properties in functions.
if (function->map() !=
isolate_->context().get(function->shared().function_map_index())) {
Handle<Map> map(function->map(), isolate_);
// We only serialize properties which are fields in function. And properties
// which are descriptors will be setup in CreateJSFunction.
DiscoverMap(map, true);
discovery_queue_.push(handle(map->prototype(), isolate_));
// Discover property values.
for (InternalIndex i : map->IterateOwnDescriptors()) {
PropertyDetails details =
map->instance_descriptors(kRelaxedLoad).GetDetails(i);
if (details.location() == PropertyLocation::kDescriptor) {
continue;
}
FieldIndex field_index = FieldIndex::ForDescriptor(*map, i);
Handle<Object> value = JSObject::FastPropertyAt(
isolate_, function, details.representation(), field_index);
if (!value->IsHeapObject()) continue;
discovery_queue_.push(Handle<HeapObject>::cast(value));
}
}
DiscoverSource(function);
}
@ -1095,14 +1162,7 @@ void WebSnapshotSerializer::DiscoverObjectPropertiesWithDictionaryMap(T dict) {
// Ignore deleted entries.
continue;
}
if (key->IsString()) {
DiscoverString(Handle<String>::cast(key), AllowInPlace::Yes);
} else if (key->IsSymbol()) {
DiscoverSymbol(Handle<Symbol>::cast(key));
} else {
Throw("Object property is not a String / Symbol");
return;
}
DiscoverPropertyKey(Handle<Name>::cast(key));
Handle<Object> value = handle(dict->ValueAt(index), isolate_);
if (!value->IsHeapObject()) {
continue;
@ -1126,8 +1186,12 @@ 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.
// TODO(v8:11525): After we allow "non-map" objects which are small
// enough to have a fast map, we should remove this. Although we support
// objects with dictionary map now, we still check the property count is
// bigger than kMaxNumberOfDescriptors when deserializing dictionary map and
// then removing this will break deserializing prototype objects having a
// dictionary map with few properties.
JSObject::MigrateSlowToFast(object, 0, "Web snapshot");
Handle<Map> map(object->map(), isolate_);
@ -1216,12 +1280,16 @@ void WebSnapshotSerializer::DiscoverSymbol(Handle<Symbol> symbol) {
// - 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
// - 0 if there's no map, 1 + map id otherwise
// - For each function property
// - Serialized value
// - Function prototype
// TODO(v8:11525): Investigate whether the length is really needed.
void WebSnapshotSerializer::SerializeFunction(Handle<JSFunction> function) {
SerializeFunctionInfo(function, function_serializer_);
// TODO(v8:11525): Support properties in functions.
SerializeFunctionProperties(function);
WriteValue(handle(function->map().prototype(), isolate_),
function_serializer_);
}
// Format (serialized class):
@ -1234,6 +1302,7 @@ void WebSnapshotSerializer::SerializeFunction(Handle<JSFunction> function) {
// - 1 + object id for the function prototype
void WebSnapshotSerializer::SerializeClass(Handle<JSFunction> function) {
SerializeFunctionInfo(function, class_serializer_);
WriteValue(handle(function->map().prototype(), isolate_), class_serializer_);
// TODO(v8:11525): Support properties in classes.
// TODO(v8:11525): Support class members.
}
@ -1320,9 +1389,9 @@ void WebSnapshotSerializer::SerializeObjectPropertiesWithDictionaryMap(T dict) {
WriteValue(handle(dict->ValueAt(index), isolate_), object_serializer_);
if (first_custom_index >= 0) {
if (index.as_int() < first_custom_index) {
map_serializer_.WriteUint32(default_flags);
object_serializer_.WriteUint32(default_flags);
} else {
map_serializer_.WriteUint32(
object_serializer_.WriteUint32(
attributes[index.as_int() - first_custom_index]);
}
}
@ -2457,6 +2526,54 @@ void WebSnapshotDeserializer::DeserializeFunctions() {
functions_.set(current_function_count_, *function);
ReadFunctionPrototype(function);
uint32_t map_id;
if (!deserializer_->ReadUint32(&map_id) || map_id >= map_count_ + 1) {
Throw("Malformed function");
return;
}
if (map_id > 0) {
map_id--; // Subtract 1 to get the real map_id.
Handle<Map> map(Map::cast(maps_.get(map_id)), isolate_);
int no_properties = map->NumberOfOwnDescriptors();
Handle<DescriptorArray> descriptors =
handle(map->instance_descriptors(kRelaxedLoad), isolate_);
Handle<PropertyArray> property_array =
DeserializePropertyArray(descriptors, no_properties);
// This function map was already deserialized completely and can be
// directly used.
auto iter = deserialized_function_maps_.find(map_id);
if (iter != deserialized_function_maps_.end()) {
function->set_map(*iter->second, kReleaseStore);
function->set_raw_properties_or_hash(*property_array);
} else {
// TODO(v8:11525): In-object properties.
Handle<Map> function_map = Map::Copy(
isolate_, handle(function->map(), isolate_), "Web Snapshot");
Map::EnsureDescriptorSlack(isolate_, function_map,
descriptors->number_of_descriptors());
{
for (InternalIndex i : map->IterateOwnDescriptors()) {
Descriptor d = Descriptor::DataField(
isolate_, handle(descriptors->GetKey(i), isolate_),
descriptors->GetDetails(i).field_index(),
descriptors->GetDetails(i).attributes(),
descriptors->GetDetails(i).representation());
function_map->instance_descriptors().Append(&d);
if (d.GetKey()->IsInterestingSymbol()) {
function_map->set_may_have_interesting_symbols(true);
}
}
function_map->SetNumberOfOwnDescriptors(
function_map->NumberOfOwnDescriptors() +
descriptors->number_of_descriptors());
function->set_map(*function_map, kReleaseStore);
function->set_raw_properties_or_hash(*property_array);
}
deserialized_function_maps_.insert(
std::make_pair(map_id, function_map));
}
}
DeserializeObjectPrototypeForFunction(function);
}
}
@ -2531,13 +2648,33 @@ void WebSnapshotDeserializer::DeserializeObjectPrototype(Handle<Map> map) {
}
}
bool WebSnapshotDeserializer::IsInitialFunctionPrototype(Object prototype) {
return prototype == isolate_->context().function_prototype() ||
// Asyncfunction prototype.
prototype == isolate_->context()
.async_function_constructor()
.instance_prototype() ||
// GeneratorFunction prototype.
prototype == JSFunction::cast(isolate_->context()
.generator_function_map()
.constructor_or_back_pointer())
.instance_prototype() ||
// AsyncGeneratorFunction prototype
prototype == JSFunction::cast(isolate_->context()
.async_generator_function_map()
.constructor_or_back_pointer())
.instance_prototype();
}
void WebSnapshotDeserializer::DeserializeObjectPrototypeForFunction(
Handle<JSFunction> function) {
Handle<Map> map(function->map(), isolate_);
// Copy the map so that we don't end up modifying the canonical maps.
// If the function prototype is not the initial function prototype, then the
// map must not be the canonical maps because we already copy the map when
// deserializaing the map for the function. And so we don't need to copy the
// map.
// TODO(v8:11525): Ensure we create the same map tree as for non-websnapshot
// functions + add a test.
map = Map::Copy(isolate_, map, "Web Snapshot");
auto result = ReadValue(map, 0, InternalizeStrings::kNo);
Object prototype = std::get<0>(result);
bool was_deferred = std::get<1>(result);
@ -2546,16 +2683,13 @@ void WebSnapshotDeserializer::DeserializeObjectPrototypeForFunction(
// TODO(v8:11525): if the object order is relaxed, it's possible to have a
// deferred reference to Function.prototype, and we'll need to recognize and
// handle that case.
if (prototype == isolate_->context().function_prototype()) {
DCHECK_EQ(function->map().prototype(),
isolate_->context().function_prototype());
// TODO(v8:11525): Avoid map creation (above) in this case.
if (IsInitialFunctionPrototype(prototype)) {
DCHECK(IsInitialFunctionPrototype(function->map().prototype()));
return;
}
if (!was_deferred) {
SetPrototype(map, handle(prototype, isolate_));
}
function->set_map(*map, kReleaseStore);
}
Handle<Map>
@ -2647,6 +2781,32 @@ bool WebSnapshotDeserializer::ReadMapType() {
}
}
Handle<PropertyArray> WebSnapshotDeserializer::DeserializePropertyArray(
Handle<DescriptorArray> descriptors, int no_properties) {
Handle<PropertyArray> property_array =
factory()->NewPropertyArray(no_properties);
for (int i = 0; i < no_properties; ++i) {
Object value = std::get<0>(ReadValue(property_array, i));
DisallowGarbageCollection no_gc;
// Read the representation from the map.
DescriptorArray raw_descriptors = *descriptors;
PropertyDetails details = raw_descriptors.GetDetails(InternalIndex(i));
CHECK_EQ(details.location(), PropertyLocation::kField);
CHECK_EQ(PropertyKind::kData, details.kind());
Representation r = details.representation();
if (r.IsNone()) {
// Switch over to wanted_representation.
details = details.CopyWithRepresentation(Representation::Tagged());
raw_descriptors.SetDetails(InternalIndex(i), details);
} else if (!r.Equals(Representation::Tagged())) {
// TODO(v8:11525): Support this case too.
UNREACHABLE();
}
property_array->set(i, value);
}
return property_array;
}
void WebSnapshotDeserializer::DeserializeObjects() {
RCS_SCOPE(isolate_, RuntimeCallCounterId::kWebSnapshotDeserialize_Objects);
if (!deserializer_->ReadUint32(&object_count_) ||
@ -2675,26 +2835,7 @@ void WebSnapshotDeserializer::DeserializeObjects() {
// TODO(v8:11525): In-object properties.
Handle<Map> map(raw_map, isolate_);
Handle<PropertyArray> property_array =
factory()->NewPropertyArray(no_properties);
for (int i = 0; i < no_properties; ++i) {
Object value = std::get<0>(ReadValue(property_array, i));
DisallowGarbageCollection no_gc;
// Read the representation from the map.
DescriptorArray raw_descriptors = *descriptors;
PropertyDetails details = raw_descriptors.GetDetails(InternalIndex(i));
CHECK_EQ(details.location(), PropertyLocation::kField);
CHECK_EQ(PropertyKind::kData, details.kind());
Representation r = details.representation();
if (r.IsNone()) {
// Switch over to wanted_representation.
details = details.CopyWithRepresentation(Representation::Tagged());
raw_descriptors.SetDetails(InternalIndex(i), details);
} else if (!r.Equals(Representation::Tagged())) {
// TODO(v8:11525): Support this case too.
UNREACHABLE();
}
property_array->set(i, value);
}
DeserializePropertyArray(descriptors, no_properties);
object = factory()->NewJSObjectFromMap(map);
object->set_raw_properties_or_hash(*property_array, kRelaxedStore);
} else {

View File

@ -208,7 +208,8 @@ class V8_EXPORT WebSnapshotSerializer
void DiscoverString(Handle<String> string,
AllowInPlace can_be_in_place = AllowInPlace::No);
void DiscoverSymbol(Handle<Symbol> symbol);
void DiscoverMap(Handle<Map> map);
void DiscoverMap(Handle<Map> map, bool allow_property_in_descriptor = false);
void DiscoverPropertyKey(Handle<Name> key);
void DiscoverFunction(Handle<JSFunction> function);
void DiscoverClass(Handle<JSFunction> function);
void DiscoverContextAndPrototype(Handle<JSFunction> function);
@ -224,7 +225,7 @@ class V8_EXPORT WebSnapshotSerializer
void SerializeFunctionInfo(Handle<JSFunction> function,
ValueSerializer& serializer);
void SerializeFunctionProperties(Handle<JSFunction> function);
void SerializeString(Handle<String> string, ValueSerializer& serializer);
void SerializeSymbol(Handle<Symbol> symbol);
void SerializeMap(Handle<Map> map);
@ -412,10 +413,15 @@ class V8_EXPORT WebSnapshotDeserializer
void DeserializeObjectPrototypeForFunction(Handle<JSFunction> function);
void SetPrototype(Handle<Map> map, Handle<Object> prototype);
bool IsInitialFunctionPrototype(Object prototype);
template <typename T>
void DeserializeObjectPropertiesWithDictionaryMap(
T dict, uint32_t property_count, bool has_custom_property_attributes);
Handle<PropertyArray> DeserializePropertyArray(
Handle<DescriptorArray> descriptors, int no_properties);
// Return value: (object, was_deferred)
std::tuple<Object, bool> ReadValue(
Handle<HeapObject> object_for_deferred_reference = Handle<HeapObject>(),
@ -470,6 +476,7 @@ class V8_EXPORT WebSnapshotDeserializer
Handle<FixedArray> maps_handle_;
FixedArray maps_;
std::map<int, Handle<Map>> deserialized_function_maps_;
Handle<FixedArray> contexts_handle_;
FixedArray contexts_;

View File

@ -147,3 +147,123 @@ d8.file.execute('test/mjsunit/web-snapshot/web-snapshot-helpers.js');
assertSame(newAsyncGeneratorFunction.prototype.__proto__,
asyncGeneratorFunction.prototype.__proto__);
})();
(function TestFunctionWithProperties() {
function createObjects() {
function bar() { return 'bar'; };
bar.key1 = "value1";
bar.key2 = 1;
bar.key3 = 2.2;
bar.key4 = function key4() {
return "key4";
}
bar.key5 = [1, 2];
bar.key6 = {"key":"value"}
globalThis.foo = {
bar: bar,
};
}
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
assertEquals('bar', foo.bar());
assertEquals('value1', foo.bar.key1);
assertEquals(1, foo.bar.key2);
assertEquals(2.2, foo.bar.key3);
assertEquals('key4', foo.bar.key4());
assertEquals([1, 2], foo.bar.key5);
assertEquals({ "key": "value" }, foo.bar.key6 );
})();
(function TestAsyncFunctionWithProperties() {
function createObjects() {
async function bar() { return 'bar'; };
bar.key1 = "value1";
bar.key2 = 1;
bar.key3 = 2.2;
bar.key4 = function key4() {
return "key4";
}
bar.key5 = [1, 2];
bar.key6 = {"key":"value"}
globalThis.foo = {
bar: bar,
};
}
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
assertEquals('value1', foo.bar.key1);
assertEquals(1, foo.bar.key2);
assertEquals(2.2, foo.bar.key3);
assertEquals('key4', foo.bar.key4());
assertEquals([1, 2], foo.bar.key5);
assertEquals({ "key": "value" }, foo.bar.key6 );
})();
(function TestGeneratorFunctionWithProperties() {
function createObjects() {
function *bar() { return 'bar'; };
bar.key1 = "value1";
bar.key2 = 1;
bar.key3 = 2.2;
bar.key4 = function key4() {
return "key4";
}
bar.key5 = [1, 2];
bar.key6 = {"key":"value"}
globalThis.foo = {
bar: bar,
};
}
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
assertEquals('value1', foo.bar.key1);
assertEquals(1, foo.bar.key2);
assertEquals(2.2, foo.bar.key3);
assertEquals('key4', foo.bar.key4());
assertEquals([1, 2], foo.bar.key5);
assertEquals({ "key": "value" }, foo.bar.key6 );
})();
(function TestAsyncGeneratorFunctionWithProperties() {
function createObjects() {
async function *bar() { return 'bar'; };
bar.key1 = "value1";
bar.key2 = 1;
bar.key3 = 2.2;
bar.key4 = function key4() {
return "key4";
}
bar.key5 = [1, 2];
bar.key6 = {"key":"value"}
globalThis.foo = {
bar: bar,
};
}
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
assertEquals('value1', foo.bar.key1);
assertEquals(1, foo.bar.key2);
assertEquals(2.2, foo.bar.key3);
assertEquals('key4', foo.bar.key4());
assertEquals([1, 2], foo.bar.key5);
assertEquals({ "key": "value" }, foo.bar.key6 );
})();
(function TestFunctionsWithSameMap() {
function createObjects() {
function bar1() { return 'bar1'; };
bar1.key = "value";
function bar2() {
return "bar2";
}
bar2.key = "value";
globalThis.foo = {
bar1: bar1,
bar2: bar2
};
}
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
assertEquals('bar1', foo.bar1());
assertEquals('value', foo.bar1.key);
assertEquals('bar2', foo.bar2());
assertEquals('value', foo.bar2.key);
assertTrue(%HaveSameMap(foo.bar1, foo.bar2))
})();