Commit 4620dbc0 authored by Marja Hölttä's avatar Marja Hölttä Committed by V8 LUCI CQ

[web snapshots] Add in-place strings

This decreases the snapshot size (we don't need to write the ID for the
string) and speeds up deserialization.

Bug: v8:11525
Change-Id: I8f48d2344a7fd895c746e6a3d26f6dbbdd11a062
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3494539Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Commit-Queue: Marja Hölttä <marja@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79381}
parent daa3ce75
...@@ -211,13 +211,16 @@ WebSnapshotSerializer::WebSnapshotSerializer(Isolate* isolate) ...@@ -211,13 +211,16 @@ WebSnapshotSerializer::WebSnapshotSerializer(Isolate* isolate)
function_ids_(isolate_->heap()), function_ids_(isolate_->heap()),
class_ids_(isolate_->heap()), class_ids_(isolate_->heap()),
array_ids_(isolate_->heap()), array_ids_(isolate_->heap()),
object_ids_(isolate_->heap()) { object_ids_(isolate_->heap()),
all_strings_(isolate_->heap()) {
auto empty_array_list = factory()->empty_array_list(); auto empty_array_list = factory()->empty_array_list();
contexts_ = empty_array_list; contexts_ = empty_array_list;
functions_ = empty_array_list; functions_ = empty_array_list;
classes_ = empty_array_list; classes_ = empty_array_list;
arrays_ = empty_array_list; arrays_ = empty_array_list;
objects_ = empty_array_list; objects_ = empty_array_list;
strings_ = empty_array_list;
maps_ = empty_array_list;
} }
WebSnapshotSerializer::~WebSnapshotSerializer() {} WebSnapshotSerializer::~WebSnapshotSerializer() {}
...@@ -235,7 +238,10 @@ bool WebSnapshotSerializer::TakeSnapshot( ...@@ -235,7 +238,10 @@ bool WebSnapshotSerializer::TakeSnapshot(
if (object->IsHeapObject()) Discover(Handle<HeapObject>::cast(object)); if (object->IsHeapObject()) Discover(Handle<HeapObject>::cast(object));
SerializeSource(); ConstructSource();
// The export is serialized with the empty string as name; we need to
// "discover" the name here.
DiscoverString(factory()->empty_string());
SerializeExport(object, factory()->empty_string()); SerializeExport(object, factory()->empty_string());
WriteSnapshot(data_out.buffer, data_out.buffer_size); WriteSnapshot(data_out.buffer, data_out.buffer_size);
...@@ -264,6 +270,8 @@ bool WebSnapshotSerializer::TakeSnapshot(v8::Local<v8::Context> context, ...@@ -264,6 +270,8 @@ bool WebSnapshotSerializer::TakeSnapshot(v8::Local<v8::Context> context,
if (str->Length() == 0) { if (str->Length() == 0) {
continue; continue;
} }
// Discover the export name.
DiscoverString(Handle<String>::cast(Utils::OpenHandle(*str)));
v8::ScriptCompiler::Source source(str); v8::ScriptCompiler::Source source(str);
auto script = ScriptCompiler::Compile(context, &source).ToLocalChecked(); auto script = ScriptCompiler::Compile(context, &source).ToLocalChecked();
v8::MaybeLocal<v8::Value> script_result = script->Run(context); v8::MaybeLocal<v8::Value> script_result = script->Run(context);
...@@ -278,7 +286,7 @@ bool WebSnapshotSerializer::TakeSnapshot(v8::Local<v8::Context> context, ...@@ -278,7 +286,7 @@ bool WebSnapshotSerializer::TakeSnapshot(v8::Local<v8::Context> context,
Discover(export_objects[i]); Discover(export_objects[i]);
} }
SerializeSource(); ConstructSource();
for (int i = 0, length = exports->Length(); i < length; ++i) { for (int i = 0, length = exports->Length(); i < length; ++i) {
v8::Local<v8::String> str = v8::Local<v8::String> str =
...@@ -300,6 +308,19 @@ bool WebSnapshotSerializer::TakeSnapshot(v8::Local<v8::Context> context, ...@@ -300,6 +308,19 @@ bool WebSnapshotSerializer::TakeSnapshot(v8::Local<v8::Context> context,
} }
void WebSnapshotSerializer::SerializePendingItems() { void WebSnapshotSerializer::SerializePendingItems() {
// The information about string reference counts is now complete. The strings
// in strings_ are not in place and can be serialized now. The in-place
// strings will be serialized as part of their respective objects.
for (int i = 0; i < strings_->Length(); ++i) {
Handle<String> string = handle(String::cast(strings_->Get(i)), isolate_);
SerializeString(string, string_serializer_);
}
for (int i = 0; i < maps_->Length(); ++i) {
Handle<Map> map = handle(Map::cast(maps_->Get(i)), isolate_);
SerializeMap(map);
}
// Serialize the items in the reverse order. The items at the end of the // Serialize the items in the reverse order. The items at the end of the
// contexts_ etc get lower IDs and vice versa. IDs which items use for // contexts_ etc get lower IDs and vice versa. IDs which items use for
// referring to each other are reversed by Get<item>Id functions(). // referring to each other are reversed by Get<item>Id functions().
...@@ -327,8 +348,6 @@ void WebSnapshotSerializer::SerializePendingItems() { ...@@ -327,8 +348,6 @@ void WebSnapshotSerializer::SerializePendingItems() {
handle(JSObject::cast(objects_->Get(i)), isolate_); handle(JSObject::cast(objects_->Get(i)), isolate_);
SerializeObject(object); SerializeObject(object);
} }
// Maps and strings get serialized when they're encountered; we don't need to
// serialize them explicitly.
} }
// Format (full snapshot): // Format (full snapshot):
...@@ -353,6 +372,9 @@ void WebSnapshotSerializer::SerializePendingItems() { ...@@ -353,6 +372,9 @@ void WebSnapshotSerializer::SerializePendingItems() {
// - Serialized export // - Serialized export
void WebSnapshotSerializer::WriteSnapshot(uint8_t*& buffer, void WebSnapshotSerializer::WriteSnapshot(uint8_t*& buffer,
size_t& buffer_size) { size_t& buffer_size) {
if (has_error()) {
return;
}
SerializePendingItems(); SerializePendingItems();
ValueSerializer total_serializer(isolate_, nullptr); ValueSerializer total_serializer(isolate_, nullptr);
...@@ -415,29 +437,22 @@ bool WebSnapshotSerializer::InsertIntoIndexMap(ObjectCacheIndexMap& map, ...@@ -415,29 +437,22 @@ bool WebSnapshotSerializer::InsertIntoIndexMap(ObjectCacheIndexMap& map,
// - Length // - Length
// - Raw bytes (data) // - Raw bytes (data)
void WebSnapshotSerializer::SerializeString(Handle<String> string, void WebSnapshotSerializer::SerializeString(Handle<String> string,
uint32_t& id) { ValueSerializer& serializer) {
if (InsertIntoIndexMap(string_ids_, *string, id)) {
return;
}
// TODO(v8:11525): Always write strings as UTF-8.
string = String::Flatten(isolate_, string);
DisallowGarbageCollection no_gc; DisallowGarbageCollection no_gc;
String::FlatContent flat = string->GetFlatContent(no_gc); String::FlatContent flat = string->GetFlatContent(no_gc);
DCHECK(flat.IsFlat()); DCHECK(flat.IsFlat());
if (flat.IsOneByte()) { if (flat.IsOneByte()) {
base::Vector<const uint8_t> chars = flat.ToOneByteVector(); base::Vector<const uint8_t> chars = flat.ToOneByteVector();
string_serializer_.WriteUint32(chars.length()); serializer.WriteUint32(chars.length());
string_serializer_.WriteRawBytes(chars.begin(), serializer.WriteRawBytes(chars.begin(), chars.length() * sizeof(uint8_t));
chars.length() * sizeof(uint8_t));
} else if (flat.IsTwoByte()) { } else if (flat.IsTwoByte()) {
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_); v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
v8::Local<v8::String> api_string = Utils::ToLocal(string); v8::Local<v8::String> api_string = Utils::ToLocal(string);
int length = api_string->Utf8Length(v8_isolate); int length = api_string->Utf8Length(v8_isolate);
std::unique_ptr<char[]> buffer(new char[length]); std::unique_ptr<char[]> buffer(new char[length]);
api_string->WriteUtf8(v8_isolate, buffer.get(), length); api_string->WriteUtf8(v8_isolate, buffer.get(), length);
string_serializer_.WriteUint32(length); serializer.WriteUint32(length);
string_serializer_.WriteRawBytes(buffer.get(), length * sizeof(uint8_t)); serializer.WriteRawBytes(buffer.get(), length * sizeof(uint8_t));
} else { } else {
UNREACHABLE(); UNREACHABLE();
} }
...@@ -451,23 +466,16 @@ void WebSnapshotSerializer::SerializeString(Handle<String> string, ...@@ -451,23 +466,16 @@ void WebSnapshotSerializer::SerializeString(Handle<String> string,
// - For each property // - For each property
// - String id (name) // - String id (name)
// - If the PropertyAttributesType is CUSTOM: attributes // - If the PropertyAttributesType is CUSTOM: attributes
void WebSnapshotSerializer::SerializeMap(Handle<Map> map, uint32_t& id) { void WebSnapshotSerializer::SerializeMap(Handle<Map> map) {
if (InsertIntoIndexMap(map_ids_, *map, id)) {
return;
}
int first_custom_index = -1; int first_custom_index = -1;
std::vector<uint32_t> string_ids; std::vector<Handle<String>> keys;
std::vector<uint32_t> attributes; std::vector<uint32_t> attributes;
string_ids.reserve(map->NumberOfOwnDescriptors()); keys.reserve(map->NumberOfOwnDescriptors());
attributes.reserve(map->NumberOfOwnDescriptors()); attributes.reserve(map->NumberOfOwnDescriptors());
for (InternalIndex i : map->IterateOwnDescriptors()) { for (InternalIndex i : map->IterateOwnDescriptors()) {
Handle<Name> key(map->instance_descriptors(kRelaxedLoad).GetKey(i), Handle<Name> key(map->instance_descriptors(kRelaxedLoad).GetKey(i),
isolate_); isolate_);
if (!key->IsString()) { keys.push_back(Handle<String>::cast(key));
Throw("Key is not a string");
return;
}
PropertyDetails details = PropertyDetails details =
map->instance_descriptors(kRelaxedLoad).GetDetails(i); map->instance_descriptors(kRelaxedLoad).GetDetails(i);
...@@ -481,10 +489,6 @@ void WebSnapshotSerializer::SerializeMap(Handle<Map> map, uint32_t& id) { ...@@ -481,10 +489,6 @@ void WebSnapshotSerializer::SerializeMap(Handle<Map> map, uint32_t& id) {
if (first_custom_index == -1) first_custom_index = i.as_int(); if (first_custom_index == -1) first_custom_index = i.as_int();
attributes.push_back(AttributesToFlags(details)); attributes.push_back(AttributesToFlags(details));
} }
uint32_t string_id = 0;
SerializeString(Handle<String>::cast(key), string_id);
string_ids.push_back(string_id);
} }
map_serializer_.WriteUint32(first_custom_index == -1 map_serializer_.WriteUint32(first_custom_index == -1
...@@ -505,10 +509,10 @@ void WebSnapshotSerializer::SerializeMap(Handle<Map> map, uint32_t& id) { ...@@ -505,10 +509,10 @@ void WebSnapshotSerializer::SerializeMap(Handle<Map> map, uint32_t& id) {
map_serializer_.WriteUint32(prototype_id + 1); map_serializer_.WriteUint32(prototype_id + 1);
} }
map_serializer_.WriteUint32(static_cast<uint32_t>(string_ids.size())); map_serializer_.WriteUint32(static_cast<uint32_t>(keys.size()));
uint32_t default_flags = GetDefaultAttributeFlags(); uint32_t default_flags = GetDefaultAttributeFlags();
for (size_t i = 0; i < string_ids.size(); ++i) { for (size_t i = 0; i < keys.size(); ++i) {
if (first_custom_index >= 0) { if (first_custom_index >= 0) {
if (static_cast<int>(i) < first_custom_index) { if (static_cast<int>(i) < first_custom_index) {
map_serializer_.WriteUint32(default_flags); map_serializer_.WriteUint32(default_flags);
...@@ -516,7 +520,7 @@ void WebSnapshotSerializer::SerializeMap(Handle<Map> map, uint32_t& id) { ...@@ -516,7 +520,7 @@ void WebSnapshotSerializer::SerializeMap(Handle<Map> map, uint32_t& id) {
map_serializer_.WriteUint32(attributes[i - first_custom_index]); map_serializer_.WriteUint32(attributes[i - first_custom_index]);
} }
} }
map_serializer_.WriteUint32(string_ids[i]); WriteStringId(keys[i], map_serializer_);
} }
} }
...@@ -531,7 +535,7 @@ void WebSnapshotSerializer::SerializeMap(Handle<Map> map, uint32_t& id) { ...@@ -531,7 +535,7 @@ void WebSnapshotSerializer::SerializeMap(Handle<Map> map, uint32_t& id) {
// Constructed source: defghijkstuvwxyzö // Constructed source: defghijkstuvwxyzö
// Functions: 11111111222222223 // Functions: 11111111222222223
// Inner functions 44 55 666 // Inner functions 44 55 666
void WebSnapshotSerializer::SerializeSource() { void WebSnapshotSerializer::ConstructSource() {
if (source_intervals_.empty()) { if (source_intervals_.empty()) {
return; return;
} }
...@@ -565,7 +569,10 @@ void WebSnapshotSerializer::SerializeSource() { ...@@ -565,7 +569,10 @@ void WebSnapshotSerializer::SerializeSource() {
return; return;
} }
} }
SerializeString(source_string, source_id_); DiscoverString(source_string);
bool in_place = false;
source_id_ = GetStringId(source_string, in_place);
DCHECK(!in_place);
} }
void WebSnapshotSerializer::SerializeFunctionInfo(ValueSerializer* serializer, void WebSnapshotSerializer::SerializeFunctionInfo(ValueSerializer* serializer,
...@@ -587,7 +594,6 @@ void WebSnapshotSerializer::SerializeFunctionInfo(ValueSerializer* serializer, ...@@ -587,7 +594,6 @@ void WebSnapshotSerializer::SerializeFunctionInfo(ValueSerializer* serializer,
} }
} }
DCHECK_EQ(source_id_, 0);
serializer->WriteUint32(source_id_); serializer->WriteUint32(source_id_);
int start = function->shared().StartPosition(); int start = function->shared().StartPosition();
int end = function->shared().EndPosition(); int end = function->shared().EndPosition();
...@@ -645,13 +651,31 @@ void WebSnapshotSerializer::Discover(Handle<HeapObject> start_object) { ...@@ -645,13 +651,31 @@ void WebSnapshotSerializer::Discover(Handle<HeapObject> start_object) {
break; break;
case ODDBALL_TYPE: case ODDBALL_TYPE:
case HEAP_NUMBER_TYPE: case HEAP_NUMBER_TYPE:
case JS_PRIMITIVE_WRAPPER_TYPE:
case JS_REG_EXP_TYPE:
// Can't contain references to other objects. // Can't contain references to other objects.
break; break;
case JS_PRIMITIVE_WRAPPER_TYPE: {
Handle<JSPrimitiveWrapper> wrapper =
Handle<JSPrimitiveWrapper>::cast(object);
Handle<Object> value = handle(wrapper->value(), isolate_);
if (value->IsHeapObject()) {
discovery_queue_.push(Handle<HeapObject>::cast(value));
}
break;
}
case JS_REG_EXP_TYPE: {
Handle<JSRegExp> regexp = Handle<JSRegExp>::cast(object);
Handle<String> pattern = handle(regexp->source(), isolate_);
DiscoverString(pattern);
Handle<String> flags_string =
JSRegExp::StringFromFlags(isolate_, regexp->flags());
DiscoverString(flags_string);
break;
}
default: default:
if (object->IsString()) { if (object->IsString()) {
// Can't contain references to other objects. // These are array elements / object properties -> allow in place
// strings.
DiscoverString(Handle<String>::cast(object), AllowInPlace::Yes);
break; break;
} else if (external_objects_ids_.size() > 0) { } else if (external_objects_ids_.size() > 0) {
int unused_id; int unused_id;
...@@ -664,6 +688,50 @@ void WebSnapshotSerializer::Discover(Handle<HeapObject> start_object) { ...@@ -664,6 +688,50 @@ void WebSnapshotSerializer::Discover(Handle<HeapObject> start_object) {
} }
} }
void WebSnapshotSerializer::DiscoverMap(Handle<Map> map) {
uint32_t id;
if (InsertIntoIndexMap(map_ids_, *map, id)) {
return;
}
DCHECK_EQ(id, maps_->Length());
maps_ = ArrayList::Add(isolate_, maps_, map);
for (InternalIndex i : map->IterateOwnDescriptors()) {
Handle<Name> key(map->instance_descriptors(kRelaxedLoad).GetKey(i),
isolate_);
if (!key->IsString()) {
Throw("Key is not a string");
return;
}
DiscoverString(Handle<String>::cast(key));
}
}
void WebSnapshotSerializer::DiscoverString(Handle<String> string,
AllowInPlace can_be_in_place) {
// Can't contain references to other objects. We only log the existence of the
// string itself. Internalize the strings so that we can properly track which
// String objects are the same string.
string = factory()->InternalizeString(string);
auto result = all_strings_.FindOrInsert(string);
if (can_be_in_place == AllowInPlace::Yes && !result.already_exists) {
// This is the only reference to the string so far. Don't generate and
// ID for it yet; only generate it when another reference to the string is
// found.
return;
}
// The string is referred to more than two places, or in-placing not allowed
// -> not a candidate for writing it in-place. Generate an ID for it.
// TODO(v8:11525): Allow in-place strings in more places. Heuristics for
// when to make them in place?
uint32_t id;
if (InsertIntoIndexMap(string_ids_, *string, id)) {
return;
}
DCHECK_EQ(id, strings_->Length());
strings_ = ArrayList::Add(isolate_, strings_, string);
}
void WebSnapshotSerializer::DiscoverFunction(Handle<JSFunction> function) { void WebSnapshotSerializer::DiscoverFunction(Handle<JSFunction> function) {
uint32_t id; uint32_t id;
if (InsertIntoIndexMap(function_ids_, *function, id)) { if (InsertIntoIndexMap(function_ids_, *function, id)) {
...@@ -719,14 +787,15 @@ void WebSnapshotSerializer::DiscoverContext(Handle<Context> context) { ...@@ -719,14 +787,15 @@ void WebSnapshotSerializer::DiscoverContext(Handle<Context> context) {
DCHECK_EQ(id, contexts_->Length()); DCHECK_EQ(id, contexts_->Length());
contexts_ = ArrayList::Add(isolate_, contexts_, context); contexts_ = ArrayList::Add(isolate_, contexts_, context);
DisallowGarbageCollection no_gc; Handle<ScopeInfo> scope_info = handle(context->scope_info(), isolate_);
ScopeInfo scope_info = context->scope_info(); int count = scope_info->ContextLocalCount();
int count = scope_info.ContextLocalCount();
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
// TODO(v8:11525): support parameters // TODO(v8:11525): support parameters
// TODO(v8:11525): distinguish variable modes // TODO(v8:11525): distinguish variable modes
Object value = context->get(scope_info.ContextHeaderLength() + i); Handle<String> name(scope_info->context_local_names(i), isolate_);
DiscoverString(name);
Object value = context->get(scope_info->ContextHeaderLength() + i);
if (!value.IsHeapObject()) continue; if (!value.IsHeapObject()) continue;
discovery_queue_.push(handle(HeapObject::cast(value), isolate_)); discovery_queue_.push(handle(HeapObject::cast(value), isolate_));
} }
...@@ -789,6 +858,7 @@ void WebSnapshotSerializer::DiscoverObject(Handle<JSObject> object) { ...@@ -789,6 +858,7 @@ void WebSnapshotSerializer::DiscoverObject(Handle<JSObject> object) {
} }
Handle<Map> map(object->map(), isolate_); Handle<Map> map(object->map(), isolate_);
DiscoverMap(map);
// Discover __proto__. // Discover __proto__.
if (map->prototype() != if (map->prototype() !=
...@@ -870,9 +940,7 @@ void WebSnapshotSerializer::SerializeContext(Handle<Context> context) { ...@@ -870,9 +940,7 @@ void WebSnapshotSerializer::SerializeContext(Handle<Context> context) {
// TODO(v8:11525): support parameters // TODO(v8:11525): support parameters
// TODO(v8:11525): distinguish variable modes // TODO(v8:11525): distinguish variable modes
Handle<String> name(scope_info->context_local_names(i), isolate_); Handle<String> name(scope_info->context_local_names(i), isolate_);
uint32_t string_id = 0; WriteStringId(name, context_serializer_);
SerializeString(name, string_id);
context_serializer_.WriteUint32(string_id);
Handle<Object> value(context->get(scope_info->ContextHeaderLength() + i), Handle<Object> value(context->get(scope_info->ContextHeaderLength() + i),
isolate_); isolate_);
WriteValue(value, context_serializer_); WriteValue(value, context_serializer_);
...@@ -885,14 +953,7 @@ void WebSnapshotSerializer::SerializeContext(Handle<Context> context) { ...@@ -885,14 +953,7 @@ void WebSnapshotSerializer::SerializeContext(Handle<Context> context) {
// - Serialized value // - Serialized value
void WebSnapshotSerializer::SerializeObject(Handle<JSObject> object) { void WebSnapshotSerializer::SerializeObject(Handle<JSObject> object) {
Handle<Map> map(object->map(), isolate_); Handle<Map> map(object->map(), isolate_);
uint32_t map_id = 0; uint32_t map_id = GetMapId(*map);
SerializeMap(map, map_id);
if (*map != object->map()) {
Throw("Map changed");
return;
}
object_serializer_.WriteUint32(map_id); object_serializer_.WriteUint32(map_id);
for (InternalIndex i : map->IterateOwnDescriptors()) { for (InternalIndex i : map->IterateOwnDescriptors()) {
...@@ -932,14 +993,11 @@ void WebSnapshotSerializer::SerializeArray(Handle<JSArray> array) { ...@@ -932,14 +993,11 @@ void WebSnapshotSerializer::SerializeArray(Handle<JSArray> array) {
void WebSnapshotSerializer::SerializeExport(Handle<Object> object, void WebSnapshotSerializer::SerializeExport(Handle<Object> object,
Handle<String> export_name) { Handle<String> export_name) {
++export_count_; ++export_count_;
uint32_t string_id = 0; WriteStringId(export_name, export_serializer_);
SerializeString(export_name, string_id);
export_serializer_.WriteUint32(string_id);
if (object->IsJSPrimitiveWrapper()) { if (object->IsJSPrimitiveWrapper()) {
Handle<JSPrimitiveWrapper> wrapper = Handle<JSPrimitiveWrapper> wrapper =
Handle<JSPrimitiveWrapper>::cast(object); Handle<JSPrimitiveWrapper>::cast(object);
Handle<Object> export_value = Handle<Object> export_value = handle(wrapper->value(), isolate_);
handle(JSPrimitiveWrapper::cast(*wrapper).value(), isolate_);
WriteValue(export_value, export_serializer_); WriteValue(export_value, export_serializer_);
} else { } else {
WriteValue(object, export_serializer_); WriteValue(object, export_serializer_);
...@@ -951,7 +1009,6 @@ void WebSnapshotSerializer::SerializeExport(Handle<Object> object, ...@@ -951,7 +1009,6 @@ void WebSnapshotSerializer::SerializeExport(Handle<Object> object,
// - Value or id (interpretation depends on the type) // - Value or id (interpretation depends on the type)
void WebSnapshotSerializer::WriteValue(Handle<Object> object, void WebSnapshotSerializer::WriteValue(Handle<Object> object,
ValueSerializer& serializer) { ValueSerializer& serializer) {
uint32_t id = 0;
if (object->IsSmi()) { if (object->IsSmi()) {
serializer.WriteUint32(ValueType::INTEGER); serializer.WriteUint32(ValueType::INTEGER);
serializer.WriteZigZag<int32_t>(Smi::cast(*object).value()); serializer.WriteZigZag<int32_t>(Smi::cast(*object).value());
...@@ -1012,22 +1069,18 @@ void WebSnapshotSerializer::WriteValue(Handle<Object> object, ...@@ -1012,22 +1069,18 @@ void WebSnapshotSerializer::WriteValue(Handle<Object> object,
Throw("Unsupported RegExp map"); Throw("Unsupported RegExp map");
return; return;
} }
uint32_t pattern_id, flags_id; serializer.WriteUint32(ValueType::REGEXP);
Handle<String> pattern = handle(regexp->source(), isolate_); Handle<String> pattern = handle(regexp->source(), isolate_);
WriteStringId(pattern, serializer);
Handle<String> flags_string = Handle<String> flags_string =
JSRegExp::StringFromFlags(isolate_, regexp->flags()); JSRegExp::StringFromFlags(isolate_, regexp->flags());
SerializeString(pattern, pattern_id); WriteStringId(flags_string, serializer);
SerializeString(flags_string, flags_id);
serializer.WriteUint32(ValueType::REGEXP);
serializer.WriteUint32(pattern_id);
serializer.WriteUint32(flags_id);
break; break;
} }
default: default:
if (heap_object->IsString()) { if (heap_object->IsString()) {
SerializeString(Handle<String>::cast(heap_object), id); // Write strings which are referred to only once as in-place strings.
serializer.WriteUint32(ValueType::STRING_ID); WriteStringMaybeInPlace(Handle<String>::cast(heap_object), serializer);
serializer.WriteUint32(id);
} else { } else {
Throw("Unsupported object"); Throw("Unsupported object");
} }
...@@ -1035,6 +1088,52 @@ void WebSnapshotSerializer::WriteValue(Handle<Object> object, ...@@ -1035,6 +1088,52 @@ void WebSnapshotSerializer::WriteValue(Handle<Object> object,
// TODO(v8:11525): Support more types. // TODO(v8:11525): Support more types.
} }
void WebSnapshotSerializer::WriteStringMaybeInPlace(
Handle<String> string, ValueSerializer& serializer) {
// If the string is only referred to by one location, write it in-place.
bool in_place = false;
uint32_t id = GetStringId(string, in_place);
if (in_place) {
serializer.WriteUint32(ValueType::IN_PLACE_STRING_ID);
SerializeString(string, serializer);
} else {
serializer.WriteUint32(ValueType::STRING_ID);
serializer.WriteUint32(id);
}
}
void WebSnapshotSerializer::WriteStringId(Handle<String> string,
ValueSerializer& serializer) {
bool in_place = false;
uint32_t id = GetStringId(string, in_place);
CHECK(!in_place); // The string must have an ID.
serializer.WriteUint32(id);
}
uint32_t WebSnapshotSerializer::GetStringId(Handle<String> string,
bool& in_place) {
// Internalize strings so that they're unique.
string = factory()->InternalizeString(string);
// Strings referred to more than one places are inserted in string_ids_.
// Strings referred to by only one place aren't.
#ifdef DEBUG
auto result = all_strings_.FindOrInsert(string);
DCHECK(result.already_exists);
#endif
int id = 0;
in_place = !string_ids_.Lookup(*string, &id);
return static_cast<uint32_t>(id);
}
uint32_t WebSnapshotSerializer::GetMapId(Map map) {
int id;
bool return_value = map_ids_.Lookup(map, &id);
DCHECK(return_value);
USE(return_value);
return static_cast<uint32_t>(id);
}
uint32_t WebSnapshotSerializer::GetFunctionId(JSFunction function) { uint32_t WebSnapshotSerializer::GetFunctionId(JSFunction function) {
int id; int id;
bool return_value = function_ids_.Lookup(function, &id); bool return_value = function_ids_.Lookup(function, &id);
...@@ -1334,6 +1433,20 @@ String WebSnapshotDeserializer::ReadString(bool internalize) { ...@@ -1334,6 +1433,20 @@ String WebSnapshotDeserializer::ReadString(bool internalize) {
return string; return string;
} }
String WebSnapshotDeserializer::ReadInPlaceString(bool internalize) {
MaybeHandle<String> maybe_string =
deserializer_.ReadUtf8String(AllocationType::kOld);
Handle<String> string;
if (!maybe_string.ToHandle(&string)) {
Throw("Malformed string");
return roots_.empty_string();
}
if (internalize) {
string = factory()->InternalizeString(string);
}
return *string;
}
void WebSnapshotDeserializer::DeserializeMaps() { void WebSnapshotDeserializer::DeserializeMaps() {
RCS_SCOPE(isolate_, RuntimeCallCounterId::kWebSnapshotDeserialize_Maps); RCS_SCOPE(isolate_, RuntimeCallCounterId::kWebSnapshotDeserialize_Maps);
if (!deserializer_.ReadUint32(&map_count_) || map_count_ > kMaxItemCount) { if (!deserializer_.ReadUint32(&map_count_) || map_count_ > kMaxItemCount) {
...@@ -1391,7 +1504,6 @@ void WebSnapshotDeserializer::DeserializeMaps() { ...@@ -1391,7 +1504,6 @@ void WebSnapshotDeserializer::DeserializeMaps() {
Handle<DescriptorArray> descriptors = Handle<DescriptorArray> descriptors =
factory()->NewDescriptorArray(property_count, 0); factory()->NewDescriptorArray(property_count, 0);
// for (uint32_t p = 0; p < property_count; ++p) {
for (InternalIndex i : InternalIndex::Range(property_count)) { for (InternalIndex i : InternalIndex::Range(property_count)) {
PropertyAttributes attributes = PropertyAttributes::NONE; PropertyAttributes attributes = PropertyAttributes::NONE;
if (has_custom_property_attributes) { if (has_custom_property_attributes) {
...@@ -1954,6 +2066,8 @@ Object WebSnapshotDeserializer::ReadValue(Handle<HeapObject> container, ...@@ -1954,6 +2066,8 @@ Object WebSnapshotDeserializer::ReadValue(Handle<HeapObject> container,
return ReadRegexp(); return ReadRegexp();
case ValueType::EXTERNAL_ID: case ValueType::EXTERNAL_ID:
return ReadExternalReference(); return ReadExternalReference();
case ValueType::IN_PLACE_STRING_ID:
return ReadInPlaceString(false);
default: default:
// TODO(v8:11525): Handle other value types. // TODO(v8:11525): Handle other value types.
Throw("Unsupported value type"); Throw("Unsupported value type");
......
...@@ -53,7 +53,8 @@ class WebSnapshotSerializerDeserializer { ...@@ -53,7 +53,8 @@ class WebSnapshotSerializerDeserializer {
FUNCTION_ID, FUNCTION_ID,
CLASS_ID, CLASS_ID,
REGEXP, REGEXP,
EXTERNAL_ID EXTERNAL_ID,
IN_PLACE_STRING_ID
}; };
static constexpr uint8_t kMagicNumber[4] = {'+', '+', '+', ';'}; static constexpr uint8_t kMagicNumber[4] = {'+', '+', '+', ';'};
...@@ -162,6 +163,11 @@ class V8_EXPORT WebSnapshotSerializer ...@@ -162,6 +163,11 @@ class V8_EXPORT WebSnapshotSerializer
WebSnapshotSerializer(const WebSnapshotSerializer&) = delete; WebSnapshotSerializer(const WebSnapshotSerializer&) = delete;
WebSnapshotSerializer& operator=(const WebSnapshotSerializer&) = delete; WebSnapshotSerializer& operator=(const WebSnapshotSerializer&) = delete;
enum class AllowInPlace {
No, // This reference cannot be replace with an in-place item.
Yes, // This reference can be replaced with an in-place item.
};
void SerializePendingItems(); void SerializePendingItems();
void WriteSnapshot(uint8_t*& buffer, size_t& buffer_size); void WriteSnapshot(uint8_t*& buffer, size_t& buffer_size);
void WriteObjects(ValueSerializer& destination, size_t count, void WriteObjects(ValueSerializer& destination, size_t count,
...@@ -173,21 +179,23 @@ class V8_EXPORT WebSnapshotSerializer ...@@ -173,21 +179,23 @@ class V8_EXPORT WebSnapshotSerializer
void ShallowDiscoverExternals(FixedArray externals); void ShallowDiscoverExternals(FixedArray externals);
void Discover(Handle<HeapObject> object); void Discover(Handle<HeapObject> object);
void DiscoverString(Handle<String> string,
AllowInPlace can_be_in_place = AllowInPlace::No);
void DiscoverMap(Handle<Map> map);
void DiscoverFunction(Handle<JSFunction> function); void DiscoverFunction(Handle<JSFunction> function);
void DiscoverClass(Handle<JSFunction> function); void DiscoverClass(Handle<JSFunction> function);
void DiscoverContextAndPrototype(Handle<JSFunction> function); void DiscoverContextAndPrototype(Handle<JSFunction> function);
void DiscoverContext(Handle<Context> context); void DiscoverContext(Handle<Context> context);
void DiscoverSource(Handle<JSFunction> function);
void DiscoverArray(Handle<JSArray> array); void DiscoverArray(Handle<JSArray> array);
void DiscoverObject(Handle<JSObject> object); void DiscoverObject(Handle<JSObject> object);
void DiscoverSource(Handle<JSFunction> function);
void ConstructSource();
void SerializeSource();
void SerializeFunctionInfo(ValueSerializer* serializer, void SerializeFunctionInfo(ValueSerializer* serializer,
Handle<JSFunction> function); Handle<JSFunction> function);
void SerializeString(Handle<String> string, uint32_t& id); void SerializeString(Handle<String> string, ValueSerializer& serializer);
void SerializeMap(Handle<Map> map, uint32_t& id); void SerializeMap(Handle<Map> map);
void SerializeFunction(Handle<JSFunction> function); void SerializeFunction(Handle<JSFunction> function);
void SerializeClass(Handle<JSFunction> function); void SerializeClass(Handle<JSFunction> function);
void SerializeContext(Handle<Context> context); void SerializeContext(Handle<Context> context);
...@@ -196,7 +204,12 @@ class V8_EXPORT WebSnapshotSerializer ...@@ -196,7 +204,12 @@ class V8_EXPORT WebSnapshotSerializer
void SerializeExport(Handle<Object> object, Handle<String> export_name); void SerializeExport(Handle<Object> object, Handle<String> export_name);
void WriteValue(Handle<Object> object, ValueSerializer& serializer); void WriteValue(Handle<Object> object, ValueSerializer& serializer);
void WriteStringMaybeInPlace(Handle<String> string,
ValueSerializer& serializer);
void WriteStringId(Handle<String> string, ValueSerializer& serializer);
uint32_t GetStringId(Handle<String> string, bool& in_place);
uint32_t GetMapId(Map map);
uint32_t GetFunctionId(JSFunction function); uint32_t GetFunctionId(JSFunction function);
uint32_t GetClassId(JSFunction function); uint32_t GetClassId(JSFunction function);
uint32_t GetContextId(Context context); uint32_t GetContextId(Context context);
...@@ -219,6 +232,8 @@ class V8_EXPORT WebSnapshotSerializer ...@@ -219,6 +232,8 @@ class V8_EXPORT WebSnapshotSerializer
Handle<ArrayList> classes_; Handle<ArrayList> classes_;
Handle<ArrayList> arrays_; Handle<ArrayList> arrays_;
Handle<ArrayList> objects_; Handle<ArrayList> objects_;
Handle<ArrayList> strings_;
Handle<ArrayList> maps_;
// IndexMap to keep track of explicitly blocked external objects and // IndexMap to keep track of explicitly blocked external objects and
// non-serializable/not-supported objects (e.g. API Objects). // non-serializable/not-supported objects (e.g. API Objects).
...@@ -239,6 +254,12 @@ class V8_EXPORT WebSnapshotSerializer ...@@ -239,6 +254,12 @@ class V8_EXPORT WebSnapshotSerializer
std::queue<Handle<HeapObject>> discovery_queue_; std::queue<Handle<HeapObject>> discovery_queue_;
// For keeping track of which strings have exactly one reference. Strings are
// inserted here when the first reference is discovered, and never removed.
// Strings which have more than one reference get an ID and are inserted to
// strings_.
IdentityMap<int, base::DefaultAllocationPolicy> all_strings_;
// For constructing the minimal, "compacted", source string to cover all // For constructing the minimal, "compacted", source string to cover all
// function bodies. // function bodies.
Handle<String> full_source_; Handle<String> full_source_;
...@@ -311,6 +332,7 @@ class V8_EXPORT WebSnapshotDeserializer ...@@ -311,6 +332,7 @@ class V8_EXPORT WebSnapshotDeserializer
Object ReadInteger(); Object ReadInteger();
Object ReadNumber(); Object ReadNumber();
String ReadString(bool internalize = false); String ReadString(bool internalize = false);
String ReadInPlaceString(bool internalize = false);
Object ReadArray(Handle<HeapObject> container, uint32_t container_index); Object ReadArray(Handle<HeapObject> container, uint32_t container_index);
Object ReadObject(Handle<HeapObject> container, uint32_t container_index); Object ReadObject(Handle<HeapObject> container, uint32_t container_index);
Object ReadFunction(Handle<HeapObject> container, uint32_t container_index); Object ReadFunction(Handle<HeapObject> container, uint32_t container_index);
......
...@@ -17,7 +17,7 @@ void TestWebSnapshotExtensive( ...@@ -17,7 +17,7 @@ void TestWebSnapshotExtensive(
const char* snapshot_source, const char* test_source, const char* snapshot_source, const char* test_source,
std::function<void(v8::Isolate*, v8::Local<v8::Context>)> tester, std::function<void(v8::Isolate*, v8::Local<v8::Context>)> tester,
uint32_t string_count, uint32_t map_count, uint32_t context_count, uint32_t string_count, uint32_t map_count, uint32_t context_count,
uint32_t function_count, uint32_t object_count) { uint32_t function_count, uint32_t object_count, uint32_t array_count) {
CcTest::InitializeVM(); CcTest::InitializeVM();
v8::Isolate* isolate = CcTest::isolate(); v8::Isolate* isolate = CcTest::isolate();
...@@ -41,6 +41,7 @@ void TestWebSnapshotExtensive( ...@@ -41,6 +41,7 @@ void TestWebSnapshotExtensive(
CHECK_EQ(context_count, serializer.context_count()); CHECK_EQ(context_count, serializer.context_count());
CHECK_EQ(function_count, serializer.function_count()); CHECK_EQ(function_count, serializer.function_count());
CHECK_EQ(object_count, serializer.object_count()); CHECK_EQ(object_count, serializer.object_count());
CHECK_EQ(array_count, serializer.array_count());
} }
{ {
...@@ -63,7 +64,8 @@ void TestWebSnapshotExtensive( ...@@ -63,7 +64,8 @@ void TestWebSnapshotExtensive(
void TestWebSnapshot(const char* snapshot_source, const char* test_source, void TestWebSnapshot(const char* snapshot_source, const char* test_source,
const char* expected_result, uint32_t string_count, const char* expected_result, uint32_t string_count,
uint32_t map_count, uint32_t context_count, uint32_t map_count, uint32_t context_count,
uint32_t function_count, uint32_t object_count) { uint32_t function_count, uint32_t object_count,
uint32_t array_count) {
TestWebSnapshotExtensive( TestWebSnapshotExtensive(
snapshot_source, test_source, snapshot_source, test_source,
[test_source, expected_result](v8::Isolate* isolate, [test_source, expected_result](v8::Isolate* isolate,
...@@ -71,7 +73,8 @@ void TestWebSnapshot(const char* snapshot_source, const char* test_source, ...@@ -71,7 +73,8 @@ void TestWebSnapshot(const char* snapshot_source, const char* test_source,
v8::Local<v8::String> result = CompileRun(test_source).As<v8::String>(); v8::Local<v8::String> result = CompileRun(test_source).As<v8::String>();
CHECK(result->Equals(new_context, v8_str(expected_result)).FromJust()); CHECK(result->Equals(new_context, v8_str(expected_result)).FromJust());
}, },
string_count, map_count, context_count, function_count, object_count); string_count, map_count, context_count, function_count, object_count,
array_count);
} }
} // namespace } // namespace
...@@ -80,13 +83,15 @@ TEST(Minimal) { ...@@ -80,13 +83,15 @@ TEST(Minimal) {
const char* snapshot_source = "var foo = {'key': 'lol'};"; const char* snapshot_source = "var foo = {'key': 'lol'};";
const char* test_source = "foo.key"; const char* test_source = "foo.key";
const char* expected_result = "lol"; const char* expected_result = "lol";
uint32_t kStringCount = 3; // 'foo', 'key', 'lol' uint32_t kStringCount = 2; // 'foo', 'key'
uint32_t kMapCount = 1; uint32_t kMapCount = 1;
uint32_t kContextCount = 0; uint32_t kContextCount = 0;
uint32_t kFunctionCount = 0; uint32_t kFunctionCount = 0;
uint32_t kObjectCount = 1; uint32_t kObjectCount = 1;
uint32_t kArrayCount = 0;
TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount, TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount,
kMapCount, kContextCount, kFunctionCount, kObjectCount); kMapCount, kContextCount, kFunctionCount, kObjectCount,
kArrayCount);
} }
TEST(EmptyObject) { TEST(EmptyObject) {
...@@ -97,6 +102,7 @@ TEST(EmptyObject) { ...@@ -97,6 +102,7 @@ TEST(EmptyObject) {
uint32_t kContextCount = 0; uint32_t kContextCount = 0;
uint32_t kFunctionCount = 0; uint32_t kFunctionCount = 0;
uint32_t kObjectCount = 1; uint32_t kObjectCount = 1;
uint32_t kArrayCount = 0;
std::function<void(v8::Isolate*, v8::Local<v8::Context>)> tester = std::function<void(v8::Isolate*, v8::Local<v8::Context>)> tester =
[test_source](v8::Isolate* isolate, v8::Local<v8::Context> new_context) { [test_source](v8::Isolate* isolate, v8::Local<v8::Context> new_context) {
v8::Local<v8::Object> result = CompileRun(test_source).As<v8::Object>(); v8::Local<v8::Object> result = CompileRun(test_source).As<v8::Object>();
...@@ -107,7 +113,7 @@ TEST(EmptyObject) { ...@@ -107,7 +113,7 @@ TEST(EmptyObject) {
}; };
TestWebSnapshotExtensive(snapshot_source, test_source, tester, kStringCount, TestWebSnapshotExtensive(snapshot_source, test_source, tester, kStringCount,
kMapCount, kContextCount, kFunctionCount, kMapCount, kContextCount, kFunctionCount,
kObjectCount); kObjectCount, kArrayCount);
} }
TEST(Numbers) { TEST(Numbers) {
...@@ -125,6 +131,8 @@ TEST(Numbers) { ...@@ -125,6 +131,8 @@ TEST(Numbers) {
uint32_t kContextCount = 0; uint32_t kContextCount = 0;
uint32_t kFunctionCount = 0; uint32_t kFunctionCount = 0;
uint32_t kObjectCount = 1; uint32_t kObjectCount = 1;
uint32_t kArrayCount = 0;
std::function<void(v8::Isolate*, v8::Local<v8::Context>)> tester = std::function<void(v8::Isolate*, v8::Local<v8::Context>)> tester =
[test_source](v8::Isolate* isolate, v8::Local<v8::Context> new_context) { [test_source](v8::Isolate* isolate, v8::Local<v8::Context> new_context) {
v8::Local<v8::Object> result = CompileRun(test_source).As<v8::Object>(); v8::Local<v8::Object> result = CompileRun(test_source).As<v8::Object>();
...@@ -161,7 +169,7 @@ TEST(Numbers) { ...@@ -161,7 +169,7 @@ TEST(Numbers) {
}; };
TestWebSnapshotExtensive(snapshot_source, test_source, tester, kStringCount, TestWebSnapshotExtensive(snapshot_source, test_source, tester, kStringCount,
kMapCount, kContextCount, kFunctionCount, kMapCount, kContextCount, kFunctionCount,
kObjectCount); kObjectCount, kArrayCount);
} }
TEST(Oddballs) { TEST(Oddballs) {
...@@ -177,6 +185,7 @@ TEST(Oddballs) { ...@@ -177,6 +185,7 @@ TEST(Oddballs) {
uint32_t kContextCount = 0; uint32_t kContextCount = 0;
uint32_t kFunctionCount = 0; uint32_t kFunctionCount = 0;
uint32_t kObjectCount = 1; uint32_t kObjectCount = 1;
uint32_t kArrayCount = 0;
std::function<void(v8::Isolate*, v8::Local<v8::Context>)> tester = std::function<void(v8::Isolate*, v8::Local<v8::Context>)> tester =
[test_source](v8::Isolate* isolate, v8::Local<v8::Context> new_context) { [test_source](v8::Isolate* isolate, v8::Local<v8::Context> new_context) {
v8::Local<v8::Object> result = CompileRun(test_source).As<v8::Object>(); v8::Local<v8::Object> result = CompileRun(test_source).As<v8::Object>();
...@@ -191,7 +200,7 @@ TEST(Oddballs) { ...@@ -191,7 +200,7 @@ TEST(Oddballs) {
}; };
TestWebSnapshotExtensive(snapshot_source, test_source, tester, kStringCount, TestWebSnapshotExtensive(snapshot_source, test_source, tester, kStringCount,
kMapCount, kContextCount, kFunctionCount, kMapCount, kContextCount, kFunctionCount,
kObjectCount); kObjectCount, kArrayCount);
} }
TEST(Function) { TEST(Function) {
...@@ -204,8 +213,10 @@ TEST(Function) { ...@@ -204,8 +213,10 @@ TEST(Function) {
uint32_t kContextCount = 0; uint32_t kContextCount = 0;
uint32_t kFunctionCount = 1; uint32_t kFunctionCount = 1;
uint32_t kObjectCount = 1; uint32_t kObjectCount = 1;
uint32_t kArrayCount = 0;
TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount, TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount,
kMapCount, kContextCount, kFunctionCount, kObjectCount); kMapCount, kContextCount, kFunctionCount, kObjectCount,
kArrayCount);
} }
TEST(InnerFunctionWithContext) { TEST(InnerFunctionWithContext) {
...@@ -217,14 +228,16 @@ TEST(InnerFunctionWithContext) { ...@@ -217,14 +228,16 @@ TEST(InnerFunctionWithContext) {
" })()};"; " })()};";
const char* test_source = "foo.key()"; const char* test_source = "foo.key()";
const char* expected_result = "11525"; const char* expected_result = "11525";
// Strings: 'foo', 'key', function source code (inner), 'result', '11525' // Strings: 'foo', 'key', function source code (inner), 'result'
uint32_t kStringCount = 5; uint32_t kStringCount = 4;
uint32_t kMapCount = 1; uint32_t kMapCount = 1;
uint32_t kContextCount = 1; uint32_t kContextCount = 1;
uint32_t kFunctionCount = 1; uint32_t kFunctionCount = 1;
uint32_t kObjectCount = 1; uint32_t kObjectCount = 1;
uint32_t kArrayCount = 0;
TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount, TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount,
kMapCount, kContextCount, kFunctionCount, kObjectCount); kMapCount, kContextCount, kFunctionCount, kObjectCount,
kArrayCount);
} }
TEST(InnerFunctionWithContextAndParentContext) { TEST(InnerFunctionWithContextAndParentContext) {
...@@ -242,15 +255,16 @@ TEST(InnerFunctionWithContextAndParentContext) { ...@@ -242,15 +255,16 @@ TEST(InnerFunctionWithContextAndParentContext) {
" })()};"; " })()};";
const char* test_source = "foo.key()"; const char* test_source = "foo.key()";
const char* expected_result = "11525"; const char* expected_result = "11525";
// Strings: 'foo', 'key', function source code (innerinner), 'part1', 'part2', // Strings: 'foo', 'key', function source code (innerinner), 'part1', 'part2'.
// '11', '525' uint32_t kStringCount = 5;
uint32_t kStringCount = 7;
uint32_t kMapCount = 1; uint32_t kMapCount = 1;
uint32_t kContextCount = 2; uint32_t kContextCount = 2;
uint32_t kFunctionCount = 1; uint32_t kFunctionCount = 1;
uint32_t kObjectCount = 1; uint32_t kObjectCount = 1;
uint32_t kArrayCount = 0;
TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount, TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount,
kMapCount, kContextCount, kFunctionCount, kObjectCount); kMapCount, kContextCount, kFunctionCount, kObjectCount,
kArrayCount);
} }
TEST(RegExp) { TEST(RegExp) {
...@@ -261,6 +275,7 @@ TEST(RegExp) { ...@@ -261,6 +275,7 @@ TEST(RegExp) {
uint32_t kContextCount = 0; uint32_t kContextCount = 0;
uint32_t kFunctionCount = 0; uint32_t kFunctionCount = 0;
uint32_t kObjectCount = 1; uint32_t kObjectCount = 1;
uint32_t kArrayCount = 0;
std::function<void(v8::Isolate*, v8::Local<v8::Context>)> tester = std::function<void(v8::Isolate*, v8::Local<v8::Context>)> tester =
[test_source](v8::Isolate* isolate, v8::Local<v8::Context> new_context) { [test_source](v8::Isolate* isolate, v8::Local<v8::Context> new_context) {
v8::Local<v8::Object> result = CompileRun(test_source).As<v8::Object>(); v8::Local<v8::Object> result = CompileRun(test_source).As<v8::Object>();
...@@ -279,7 +294,7 @@ TEST(RegExp) { ...@@ -279,7 +294,7 @@ TEST(RegExp) {
}; };
TestWebSnapshotExtensive(snapshot_source, test_source, tester, kStringCount, TestWebSnapshotExtensive(snapshot_source, test_source, tester, kStringCount,
kMapCount, kContextCount, kFunctionCount, kMapCount, kContextCount, kFunctionCount,
kObjectCount); kObjectCount, kArrayCount);
} }
TEST(RegExpNoFlags) { TEST(RegExpNoFlags) {
...@@ -290,6 +305,7 @@ TEST(RegExpNoFlags) { ...@@ -290,6 +305,7 @@ TEST(RegExpNoFlags) {
uint32_t kContextCount = 0; uint32_t kContextCount = 0;
uint32_t kFunctionCount = 0; uint32_t kFunctionCount = 0;
uint32_t kObjectCount = 1; uint32_t kObjectCount = 1;
uint32_t kArrayCount = 0;
std::function<void(v8::Isolate*, v8::Local<v8::Context>)> tester = std::function<void(v8::Isolate*, v8::Local<v8::Context>)> tester =
[test_source](v8::Isolate* isolate, v8::Local<v8::Context> new_context) { [test_source](v8::Isolate* isolate, v8::Local<v8::Context> new_context) {
v8::Local<v8::Object> result = CompileRun(test_source).As<v8::Object>(); v8::Local<v8::Object> result = CompileRun(test_source).As<v8::Object>();
...@@ -308,7 +324,7 @@ TEST(RegExpNoFlags) { ...@@ -308,7 +324,7 @@ TEST(RegExpNoFlags) {
}; };
TestWebSnapshotExtensive(snapshot_source, test_source, tester, kStringCount, TestWebSnapshotExtensive(snapshot_source, test_source, tester, kStringCount,
kMapCount, kContextCount, kFunctionCount, kMapCount, kContextCount, kFunctionCount,
kObjectCount); kObjectCount, kArrayCount);
} }
TEST(SFIDeduplication) { TEST(SFIDeduplication) {
...@@ -678,6 +694,7 @@ TEST(FunctionKinds) { ...@@ -678,6 +694,7 @@ TEST(FunctionKinds) {
uint32_t kContextCount = 0; uint32_t kContextCount = 0;
uint32_t kFunctionCount = 6; uint32_t kFunctionCount = 6;
uint32_t kObjectCount = 1; uint32_t kObjectCount = 1;
uint32_t kArrayCount = 0;
std::function<void(v8::Isolate*, v8::Local<v8::Context>)> tester = std::function<void(v8::Isolate*, v8::Local<v8::Context>)> tester =
[test_source](v8::Isolate* isolate, v8::Local<v8::Context> new_context) { [test_source](v8::Isolate* isolate, v8::Local<v8::Context> new_context) {
v8::Local<v8::Object> result = CompileRun(test_source).As<v8::Object>(); v8::Local<v8::Object> result = CompileRun(test_source).As<v8::Object>();
...@@ -697,7 +714,7 @@ TEST(FunctionKinds) { ...@@ -697,7 +714,7 @@ TEST(FunctionKinds) {
}; };
TestWebSnapshotExtensive(snapshot_source, test_source, tester, kStringCount, TestWebSnapshotExtensive(snapshot_source, test_source, tester, kStringCount,
kMapCount, kContextCount, kFunctionCount, kMapCount, kContextCount, kFunctionCount,
kObjectCount); kObjectCount, kArrayCount);
} }
// Test that concatenating JS code to the snapshot works. // Test that concatenating JS code to the snapshot works.
...@@ -843,5 +860,67 @@ TEST(CompactedSourceCode) { ...@@ -843,5 +860,67 @@ TEST(CompactedSourceCode) {
} }
} }
TEST(InPlaceStringsInArrays) {
const char* snapshot_source = "var foo = ['one', 'two', 'three'];";
const char* test_source = "foo.join('');";
const char* expected_result = "onetwothree";
uint32_t kStringCount = 1; // 'foo'; Other strings are in-place.
uint32_t kMapCount = 0;
uint32_t kContextCount = 0;
uint32_t kFunctionCount = 0;
uint32_t kObjectCount = 0;
uint32_t kArrayCount = 1;
TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount,
kMapCount, kContextCount, kFunctionCount, kObjectCount,
kArrayCount);
}
TEST(RepeatedInPlaceStringsInArrays) {
const char* snapshot_source = "var foo = ['one', 'two', 'one'];";
const char* test_source = "foo.join('');";
const char* expected_result = "onetwoone";
uint32_t kStringCount = 2; // 'foo', 'one'; Other strings are in-place.
uint32_t kMapCount = 0;
uint32_t kContextCount = 0;
uint32_t kFunctionCount = 0;
uint32_t kObjectCount = 0;
uint32_t kArrayCount = 1;
TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount,
kMapCount, kContextCount, kFunctionCount, kObjectCount,
kArrayCount);
}
TEST(InPlaceStringsInObjects) {
const char* snapshot_source = "var foo = {a: 'one', b: 'two', c: 'three'};";
const char* test_source = "foo.a + foo.b + foo.c;";
const char* expected_result = "onetwothree";
// 'foo', 'a', 'b', 'c'. Other strings are in-place.
uint32_t kStringCount = 4;
uint32_t kMapCount = 1;
uint32_t kContextCount = 0;
uint32_t kFunctionCount = 0;
uint32_t kObjectCount = 1;
uint32_t kArrayCount = 0;
TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount,
kMapCount, kContextCount, kFunctionCount, kObjectCount,
kArrayCount);
}
TEST(RepeatedInPlaceStringsInObjects) {
const char* snapshot_source = "var foo = {a: 'one', b: 'two', c: 'one'};";
const char* test_source = "foo.a + foo.b + foo.c;";
const char* expected_result = "onetwoone";
// 'foo', 'a', 'b', 'c', 'one'. Other strings are in-place.
uint32_t kStringCount = 5;
uint32_t kMapCount = 1;
uint32_t kContextCount = 0;
uint32_t kFunctionCount = 0;
uint32_t kObjectCount = 1;
uint32_t kArrayCount = 0;
TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount,
kMapCount, kContextCount, kFunctionCount, kObjectCount,
kArrayCount);
}
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -114,6 +114,50 @@ function takeAndUseWebSnapshot(createObjects, exports) { ...@@ -114,6 +114,50 @@ function takeAndUseWebSnapshot(createObjects, exports) {
assertEquals(5, foo.array[0]()); assertEquals(5, foo.array[0]());
})(); })();
(function TestInPlaceStringsInArray() {
function createObjects() {
globalThis.foo = {
array: ['foo', 'bar', 'baz']
};
}
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
// We cannot test that the strings are really in-place; that's covered by
// cctests.
assertEquals('foobarbaz', foo.array.join(''));
})();
(function TestRepeatedInPlaceStringsInArray() {
function createObjects() {
globalThis.foo = {
array: ['foo', 'bar', 'foo']
};
}
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
// We cannot test that the strings are really in-place; that's covered by
// cctests.
assertEquals('foobarfoo', foo.array.join(''));
})();
(function TestInPlaceStringsInObject() {
function createObjects() {
globalThis.foo = {a: 'foo', b: 'bar', c: 'baz'};
}
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
// We cannot test that the strings are really in-place; that's covered by
// cctests.
assertEquals('foobarbaz', foo.a + foo.b + foo.c);
})();
(function TestRepeatedInPlaceStringsInObject() {
function createObjects() {
globalThis.foo = {a: 'foo', b: 'bar', c: 'foo'};
}
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
// We cannot test that the strings are really in-place; that's covered by
// cctests.
assertEquals('foobarfoo', foo.a + foo.b + foo.c);
})();
(function TestContextReferencingArray() { (function TestContextReferencingArray() {
function createObjects() { function createObjects() {
function outer() { function outer() {
......
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