[web snapshots] Web Snapshots Version 0.01

The minimal implementation which does something useful. Initial
machinery for serializing / deserializing objects and functions (only
the very simple cases are supported).

For more info, see https://docs.google.com/document/d/1Qierkg3b3klIwCQt-oZCHqhcc1_9DXNIErBwvdpD4wU/edit?usp=sharing

Bug: v8:11525

Change-Id: I73c4de11285c7912bf9870868d203d4b3d2b4e5f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2716288
Reviewed-by: Hannes Payer <hpayer@chromium.org>
Reviewed-by: Shu-yu Guo <syg@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Commit-Queue: Marja Hölttä <marja@chromium.org>
Cr-Commit-Position: refs/heads/master@{#73371}
This commit is contained in:
Marja Hölttä 2021-03-12 12:08:44 +01:00 committed by Commit Bot
parent 6d8e8ab315
commit fb03b88ed1
13 changed files with 986 additions and 7 deletions

View File

@ -3926,6 +3926,8 @@ v8_source_set("v8_base_without_compiler") {
"src/utils/ostreams.cc",
"src/utils/utils.cc",
"src/utils/version.cc",
"src/web-snapshot/web-snapshot.cc",
"src/web-snapshot/web-snapshot.h",
"src/zone/accounting-allocator.cc",
"src/zone/type-stats.cc",
"src/zone/zone-segment.cc",

View File

@ -59,6 +59,7 @@
#include "src/trap-handler/trap-handler.h"
#include "src/utils/ostreams.h"
#include "src/utils/utils.h"
#include "src/web-snapshot/web-snapshot.h"
#ifdef V8_FUZZILLI
#include "src/d8/cov.h"
@ -723,6 +724,27 @@ bool Shell::ExecuteString(Isolate* isolate, Local<String> source,
if (!HandleUnhandledPromiseRejections(isolate)) success = false;
}
data->realm_current_ = data->realm_switch_;
if (options.web_snapshot_config) {
std::vector<std::string> exports;
if (!ReadLines(options.web_snapshot_config, exports)) {
Throw(isolate, "Web snapshots: unable to read config");
CHECK(try_catch.HasCaught());
ReportException(isolate, &try_catch);
return false;
}
i::WebSnapshotSerializer serializer(isolate);
i::WebSnapshotData snapshot_data;
if (serializer.TakeSnapshot(context, exports, snapshot_data)) {
DCHECK_NOT_NULL(snapshot_data.buffer);
WriteChars("web.snap", snapshot_data.buffer, snapshot_data.buffer_size);
} else {
CHECK(try_catch.HasCaught());
ReportException(isolate, &try_catch);
return false;
}
}
}
Local<Value> result;
if (!maybe_result.ToLocal(&result)) {
@ -1325,6 +1347,37 @@ bool Shell::ExecuteModule(Isolate* isolate, const char* file_name) {
return true;
}
bool Shell::ExecuteWebSnapshot(Isolate* isolate, const char* file_name) {
HandleScope handle_scope(isolate);
PerIsolateData* data = PerIsolateData::Get(isolate);
Local<Context> realm = data->realms_[data->realm_current_].Get(isolate);
Context::Scope context_scope(realm);
std::string absolute_path = NormalizePath(file_name, GetWorkingDirectory());
TryCatch try_catch(isolate);
try_catch.SetVerbose(true);
int length = 0;
std::unique_ptr<uint8_t[]> snapshot_data(
reinterpret_cast<uint8_t*>(ReadChars(absolute_path.c_str(), &length)));
if (length == 0) {
Throw(isolate, "Error reading the web snapshot");
DCHECK(try_catch.HasCaught());
ReportException(isolate, &try_catch);
return false;
}
i::WebSnapshotDeserializer deserializer(isolate);
if (!deserializer.UseWebSnapshot(snapshot_data.get(),
static_cast<size_t>(length))) {
DCHECK(try_catch.HasCaught());
ReportException(isolate, &try_catch);
return false;
}
DCHECK(!try_catch.HasCaught());
return true;
}
PerIsolateData::PerIsolateData(Isolate* isolate)
: isolate_(isolate), realms_(nullptr) {
isolate->SetData(0, this);
@ -3059,9 +3112,9 @@ static FILE* FOpen(const char* path, const char* mode) {
#endif
}
static char* ReadChars(const char* name, int* size_out) {
if (Shell::options.read_from_tcp_port >= 0) {
return Shell::ReadCharsFromTcpPort(name, size_out);
char* Shell::ReadChars(const char* name, int* size_out) {
if (options.read_from_tcp_port >= 0) {
return ReadCharsFromTcpPort(name, size_out);
}
FILE* file = FOpen(name, "rb");
@ -3086,6 +3139,20 @@ static char* ReadChars(const char* name, int* size_out) {
return chars;
}
bool Shell::ReadLines(const char* name, std::vector<std::string>& lines) {
int length;
const char* data = reinterpret_cast<const char*>(ReadChars(name, &length));
if (data == nullptr) {
return false;
}
std::stringstream stream(data);
std::string line;
while (std::getline(stream, line, '\n')) {
lines.emplace_back(line);
}
return true;
}
void Shell::ReadBuffer(const v8::FunctionCallbackInfo<v8::Value>& args) {
static_assert(sizeof(char) == sizeof(uint8_t),
"char and uint8_t should both have 1 byte");
@ -3136,6 +3203,13 @@ Local<String> Shell::ReadFile(Isolate* isolate, const char* name) {
return result;
}
void Shell::WriteChars(const char* name, uint8_t* buffer, size_t buffer_size) {
FILE* file = base::Fopen(name, "w");
if (file == nullptr) return;
fwrite(buffer, 1, buffer_size, file);
base::Fclose(file);
}
void Shell::RunShell(Isolate* isolate) {
HandleScope outer_scope(isolate);
v8::Local<v8::Context> context =
@ -3393,6 +3467,15 @@ bool SourceGroup::Execute(Isolate* isolate) {
break;
}
continue;
} else if (strcmp(arg, "--web-snapshot") == 0 && i + 1 < end_offset_) {
// Treat the next file as a web snapshot.
arg = argv_[++i];
Shell::set_script_executed();
if (!Shell::ExecuteWebSnapshot(isolate, arg)) {
success = false;
break;
}
continue;
} else if (arg[0] == '-') {
// Ignore other options. They have been parsed already.
continue;
@ -3945,6 +4028,9 @@ bool Shell::SetOptions(int argc, char* argv[]) {
options.cpu_profiler = true;
options.cpu_profiler_print = true;
argv[i] = nullptr;
} else if (strncmp(argv[i], "--web-snapshot-config=", 22) == 0) {
options.web_snapshot_config = argv[i] + 22;
argv[i] = nullptr;
#ifdef V8_FUZZILLI
} else if (strcmp(argv[i], "--no-fuzzilli-enable-builtins-coverage") == 0) {
options.fuzzilli_enable_builtins_coverage = false;
@ -3972,10 +4058,12 @@ bool Shell::SetOptions(int argc, char* argv[]) {
const char* usage =
"Synopsis:\n"
" shell [options] [--shell] [<file>...]\n"
" d8 [options] [-e <string>] [--shell] [[--module] <file>...]\n\n"
" d8 [options] [-e <string>] [--shell] [[--module|--web-snapshot]"
" <file>...]\n\n"
" -e execute a string in V8\n"
" --shell run an interactive JavaScript shell\n"
" --module execute a file as a JavaScript module\n\n";
" --module execute a file as a JavaScript module\n"
" --web-snapshot execute a file as a web snapshot\n\n";
using HelpOptions = i::FlagList::HelpOptions;
i::FLAG_abort_on_contradictory_flags = true;
i::FlagList::SetFlagsFromCommandLine(&argc, argv, true,
@ -3997,8 +4085,9 @@ bool Shell::SetOptions(int argc, char* argv[]) {
current->End(i);
current++;
current->Begin(argv, i + 1);
} else if (strcmp(str, "--module") == 0) {
// Pass on to SourceGroup, which understands this option.
} else if (strcmp(str, "--module") == 0 ||
strcmp(str, "--web-snapshot") == 0) {
// Pass on to SourceGroup, which understands these options.
} else if (strncmp(str, "--", 2) == 0) {
if (!i::FLAG_correctness_fuzzer_suppressions) {
printf("Warning: unknown flag %s.\nTry --help for options\n", str);

View File

@ -401,6 +401,8 @@ class ShellOptions {
"fuzzy-module-file-extensions", true};
DisallowReassignment<bool> enable_system_instrumentation = {
"enable-system-instrumentation", false};
DisallowReassignment<const char*> web_snapshot_config = {
"web-snapshot-config", nullptr};
};
class Shell : public i::AllStatic {
@ -420,6 +422,7 @@ class Shell : public i::AllStatic {
ReportExceptions report_exceptions,
ProcessMessageQueue process_message_queue);
static bool ExecuteModule(Isolate* isolate, const char* file_name);
static bool ExecuteWebSnapshot(Isolate* isolate, const char* file_name);
static void ReportException(Isolate* isolate, Local<Message> message,
Local<Value> exception);
static void ReportException(Isolate* isolate, TryCatch* try_catch);
@ -489,11 +492,14 @@ class Shell : public i::AllStatic {
static void Quit(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Version(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Read(const v8::FunctionCallbackInfo<v8::Value>& args);
static char* ReadChars(const char* name, int* size_out);
static bool ReadLines(const char* name, std::vector<std::string>& lines);
static void ReadBuffer(const v8::FunctionCallbackInfo<v8::Value>& args);
static Local<String> ReadFromStdin(Isolate* isolate);
static void ReadLine(const v8::FunctionCallbackInfo<v8::Value>& args) {
args.GetReturnValue().Set(ReadFromStdin(args.GetIsolate()));
}
static void WriteChars(const char* name, uint8_t* buffer, size_t buffer_size);
static void Load(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetTimeout(const v8::FunctionCallbackInfo<v8::Value>& args);
static void WorkerNew(const v8::FunctionCallbackInfo<v8::Value>& args);

View File

@ -490,6 +490,8 @@ DEFINE_BOOL(trace_block_coverage, false,
"trace collected block coverage information")
DEFINE_BOOL(trace_protector_invalidation, false,
"trace protector cell invalidations")
DEFINE_BOOL(trace_web_snapshot, false, "trace web snapshot deserialization")
DEFINE_BOOL(feedback_normalization, false,
"feed back normalization to constructors")
// TODO(jkummerow): This currently adds too much load on the stub cache.

View File

@ -217,6 +217,7 @@ class DescriptorArray
using EntryValueField = TaggedField<MaybeObject, kEntryValueOffset>;
private:
friend class WebSnapshotDeserializer;
DECL_INT16_ACCESSORS(filler16bits)
inline void SetKey(InternalIndex descriptor_number, Name key);

View File

@ -1103,6 +1103,15 @@ ValueDeserializer::ValueDeserializer(Isolate* isolate,
id_map_(isolate->global_handles()->Create(
ReadOnlyRoots(isolate_).empty_fixed_array())) {}
ValueDeserializer::ValueDeserializer(Isolate* isolate, const uint8_t* data,
size_t size)
: isolate_(isolate),
delegate_(nullptr),
position_(data),
end_(data + size),
id_map_(isolate->global_handles()->Create(
ReadOnlyRoots(isolate_).empty_fixed_array())) {}
ValueDeserializer::~ValueDeserializer() {
GlobalHandles::Destroy(id_map_.location());

View File

@ -94,6 +94,8 @@ class ValueSerializer {
void SetTreatArrayBufferViewsAsHostObjects(bool mode);
private:
friend class WebSnapshotSerializer;
// Managing allocations of the internal buffer.
Maybe<bool> ExpandBuffer(size_t required_capacity);
@ -182,6 +184,7 @@ class ValueDeserializer {
public:
ValueDeserializer(Isolate* isolate, Vector<const uint8_t> data,
v8::ValueDeserializer::Delegate* delegate);
ValueDeserializer(Isolate* isolate, const uint8_t* data, size_t size);
~ValueDeserializer();
ValueDeserializer(const ValueDeserializer&) = delete;
ValueDeserializer& operator=(const ValueDeserializer&) = delete;
@ -230,6 +233,8 @@ class ValueDeserializer {
bool ReadRawBytes(size_t length, const void** data) V8_WARN_UNUSED_RESULT;
private:
friend class WebSnapshotDeserializer;
// Reading the wire format.
Maybe<SerializationTag> PeekTag() const V8_WARN_UNUSED_RESULT;
void ConsumeTag(SerializationTag peeked_tag);

View File

@ -152,6 +152,8 @@ class ObjectCacheIndexMap {
return find_result.already_exists;
}
int size() const { return next_index_; }
private:
IdentityMap<int, base::DefaultAllocationPolicy> map_;
int next_index_;

4
src/web-snapshot/OWNERS Normal file
View File

@ -0,0 +1,4 @@
marja@chromium.org
leszeks@chromium.org
syg@chromium.org
verwaest@chromium.org

View File

@ -0,0 +1,629 @@
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/web-snapshot/web-snapshot.h"
#include <limits>
#include "include/v8.h"
#include "src/api/api-inl.h"
#include "src/base/platform/wrappers.h"
#include "src/handles/handles.h"
#include "src/objects/contexts.h"
#include "src/objects/script.h"
namespace v8 {
namespace internal {
void WebSnapshotSerializerDeserializer::Throw(const char* message) {
if (error_message_ != nullptr) {
return;
}
error_message_ = message;
if (!isolate_->has_pending_exception()) {
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
v8_isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(v8_isolate, message).ToLocalChecked()));
}
}
WebSnapshotSerializer::WebSnapshotSerializer(v8::Isolate* isolate)
: WebSnapshotSerializerDeserializer(
reinterpret_cast<v8::internal::Isolate*>(isolate)),
string_serializer_(isolate_, nullptr),
map_serializer_(isolate_, nullptr),
function_serializer_(isolate_, nullptr),
object_serializer_(isolate_, nullptr),
export_serializer_(isolate_, nullptr),
string_ids_(isolate_->heap()),
map_ids_(isolate_->heap()),
function_ids_(isolate_->heap()),
object_ids_(isolate_->heap()) {}
WebSnapshotSerializer::~WebSnapshotSerializer() {}
bool WebSnapshotSerializer::TakeSnapshot(
v8::Local<v8::Context> context, const std::vector<std::string>& exports,
WebSnapshotData& data_out) {
if (string_ids_.size() > 0) {
Throw("Web snapshot: Can't reuse WebSnapshotSerializer");
return false;
}
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
for (const std::string& export_name : exports) {
v8::ScriptCompiler::Source source(
v8::String::NewFromUtf8(v8_isolate, export_name.c_str(),
NewStringType::kNormal,
static_cast<int>(export_name.length()))
.ToLocalChecked());
auto script = ScriptCompiler::Compile(context, &source).ToLocalChecked();
v8::MaybeLocal<v8::Value> script_result = script->Run(context);
v8::Local<v8::Object> v8_object;
if (script_result.IsEmpty() ||
!script_result.ToLocalChecked()->ToObject(context).ToLocal(
&v8_object)) {
Throw("Web snapshot: Exported object not found");
return false;
}
auto object = Handle<JSObject>::cast(Utils::OpenHandle(*v8_object));
SerializeExport(object, export_name);
}
WriteSnapshot(data_out.buffer, data_out.buffer_size);
return !has_error();
}
uint32_t WebSnapshotSerializer::string_count() const {
return static_cast<uint32_t>(string_ids_.size());
}
uint32_t WebSnapshotSerializer::map_count() const {
return static_cast<uint32_t>(map_ids_.size());
}
uint32_t WebSnapshotSerializer::function_count() const {
return static_cast<uint32_t>(function_ids_.size());
}
uint32_t WebSnapshotSerializer::object_count() const {
return static_cast<uint32_t>(object_ids_.size());
}
// Format (full snapshot):
// - String count
// - For each string:
// - Serialized string
// - Shape count
// - For each shape:
// - Serialized shape
// - Function count
// - For each function:
// - Serialized function
// - Object count
// - For each object:
// - Serialized object
// - Export count
// - For each export:
// - Serialized export
void WebSnapshotSerializer::WriteSnapshot(uint8_t*& buffer,
size_t& buffer_size) {
while (!pending_objects_.empty()) {
const Handle<JSObject>& object = pending_objects_.front();
SerializePendingJSObject(object);
pending_objects_.pop();
}
ValueSerializer total_serializer(isolate_, nullptr);
size_t needed_size =
string_serializer_.buffer_size_ + map_serializer_.buffer_size_ +
function_serializer_.buffer_size_ + object_serializer_.buffer_size_ +
export_serializer_.buffer_size_ + 4 * sizeof(uint32_t);
if (total_serializer.ExpandBuffer(needed_size).IsNothing()) {
Throw("Web snapshot: Out of memory");
return;
}
total_serializer.WriteUint32(static_cast<uint32_t>(string_ids_.size()));
total_serializer.WriteRawBytes(string_serializer_.buffer_,
string_serializer_.buffer_size_);
total_serializer.WriteUint32(static_cast<uint32_t>(map_ids_.size()));
total_serializer.WriteRawBytes(map_serializer_.buffer_,
map_serializer_.buffer_size_);
total_serializer.WriteUint32(static_cast<uint32_t>(function_ids_.size()));
total_serializer.WriteRawBytes(function_serializer_.buffer_,
function_serializer_.buffer_size_);
total_serializer.WriteUint32(static_cast<uint32_t>(object_ids_.size()));
total_serializer.WriteRawBytes(object_serializer_.buffer_,
object_serializer_.buffer_size_);
total_serializer.WriteUint32(export_count_);
total_serializer.WriteRawBytes(export_serializer_.buffer_,
export_serializer_.buffer_size_);
if (has_error()) {
return;
}
auto result = total_serializer.Release();
buffer = result.first;
buffer_size = result.second;
}
bool WebSnapshotSerializer::InsertIntoIndexMap(ObjectCacheIndexMap& map,
Handle<HeapObject> object,
uint32_t& id) {
if (static_cast<uint32_t>(map.size()) >=
std::numeric_limits<uint32_t>::max()) {
Throw("Web snapshot: Too many objects");
return true;
}
int index_out;
bool found = map.LookupOrInsert(object, &index_out);
id = static_cast<uint32_t>(index_out);
return found;
}
// Format:
// - Length
// - Raw bytes (data)
void WebSnapshotSerializer::SerializeString(Handle<String> string,
uint32_t& id) {
if (InsertIntoIndexMap(string_ids_, string, id)) {
return;
}
// TODO(v8:11525): Always write strings as UTF-8.
string = String::Flatten(isolate_, string);
DisallowGarbageCollection no_gc;
String::FlatContent flat = string->GetFlatContent(no_gc);
DCHECK(flat.IsFlat());
if (flat.IsOneByte()) {
Vector<const uint8_t> chars = flat.ToOneByteVector();
string_serializer_.WriteUint32(chars.length());
string_serializer_.WriteRawBytes(chars.begin(),
chars.length() * sizeof(uint8_t));
} else if (flat.IsTwoByte()) {
// TODO(v8:11525): Support two-byte strings.
UNREACHABLE();
} else {
UNREACHABLE();
}
}
// Format (serialized shape):
// - Property count
// - For each property
// - String id (name)
void WebSnapshotSerializer::SerializeMap(Handle<Map> map, uint32_t& id) {
if (InsertIntoIndexMap(map_ids_, map, id)) {
return;
}
std::vector<uint32_t> string_ids;
for (InternalIndex i : map->IterateOwnDescriptors()) {
Handle<Name> key(map->instance_descriptors(kRelaxedLoad).GetKey(i),
isolate_);
if (!key->IsString()) {
Throw("Web snapshot: Key is not a string");
return;
}
PropertyDetails details =
map->instance_descriptors(kRelaxedLoad).GetDetails(i);
if (details.IsDontEnum()) {
Throw("Web snapshot: Non-enumerable properties not supported");
return;
}
if (details.location() != kField) {
Throw("Web snapshot: Properties which are not fields not supported");
return;
}
uint32_t string_id = 0;
SerializeString(Handle<String>::cast(key), string_id);
string_ids.push_back(string_id);
// TODO(v8:11525): Support property attributes.
}
map_serializer_.WriteUint32(static_cast<uint32_t>(string_ids.size()));
for (auto i : string_ids) {
map_serializer_.WriteUint32(i);
}
}
// Format (serialized function):
// - String id (source string)
void WebSnapshotSerializer::SerializeJSFunction(Handle<JSFunction> function,
uint32_t& id) {
if (InsertIntoIndexMap(function_ids_, function, id)) {
return;
}
if (!function->shared().HasSourceCode()) {
Throw("Web snapshot: Function without source code");
return;
}
// TODO(v8:11525): For inner functions, create a "substring" type, so that we
// don't need to serialize the same content twice.
Handle<String> full_source(
String::cast(Script::cast(function->shared().script()).source()),
isolate_);
int start = function->shared().StartPosition();
int end = function->shared().EndPosition();
Handle<String> source =
isolate_->factory()->NewSubString(full_source, start, end);
uint32_t source_id = 0;
SerializeString(source, source_id);
function_serializer_.WriteUint32(source_id);
// TODO(v8:11525): Serialize .prototype.
// TODO(v8:11525): Support properties in functions.
}
void WebSnapshotSerializer::SerializeJSObject(Handle<JSObject> object,
uint32_t& id) {
DCHECK(!object->IsJSFunction());
if (InsertIntoIndexMap(object_ids_, object, id)) {
return;
}
pending_objects_.push(object);
}
// Format (serialized object):
// - Shape id
// - For each property:
// - Serialized value
void WebSnapshotSerializer::SerializePendingJSObject(Handle<JSObject> object) {
Handle<Map> map(object->map(), isolate_);
uint32_t map_id = 0;
SerializeMap(map, map_id);
if (*map != object->map()) {
Throw("Web snapshot: Map changed");
return;
}
object_serializer_.WriteUint32(map_id);
for (InternalIndex i : map->IterateOwnDescriptors()) {
PropertyDetails details =
map->instance_descriptors(kRelaxedLoad).GetDetails(i);
FieldIndex field_index = FieldIndex::ForDescriptor(*map, i);
Handle<Object> value =
JSObject::FastPropertyAt(object, details.representation(), field_index);
WriteValue(value, object_serializer_);
}
}
// Format (serialized export):
// - String id (export name)
// - Object id (exported object)
void WebSnapshotSerializer::SerializeExport(Handle<JSObject> object,
const std::string& export_name) {
// TODO(v8:11525): Support exporting functions.
++export_count_;
Handle<String> export_name_string =
isolate_->factory()
->NewStringFromOneByte(Vector<const uint8_t>(
reinterpret_cast<const uint8_t*>(export_name.c_str()),
static_cast<int>(export_name.length())))
.ToHandleChecked();
uint32_t string_id = 0;
SerializeString(export_name_string, string_id);
uint32_t object_id = 0;
SerializeJSObject(object, object_id);
export_serializer_.WriteUint32(string_id);
export_serializer_.WriteUint32(object_id);
}
// Format (serialized value):
// - Type id (ValueType enum)
// - Value or id (interpretation depends on the type)
void WebSnapshotSerializer::WriteValue(Handle<Object> object,
ValueSerializer& serializer) {
uint32_t id = 0;
if (object->IsSmi()) {
// TODO(v8:11525): Implement.
UNREACHABLE();
}
DCHECK(object->IsHeapObject());
switch (HeapObject::cast(*object).map().instance_type()) {
case ODDBALL_TYPE:
// TODO(v8:11525): Implement.
UNREACHABLE();
case HEAP_NUMBER_TYPE:
// TODO(v8:11525): Implement.
UNREACHABLE();
case JS_FUNCTION_TYPE:
SerializeJSFunction(Handle<JSFunction>::cast(object), id);
serializer.WriteUint32(ValueType::FUNCTION_ID);
serializer.WriteUint32(id);
break;
case JS_OBJECT_TYPE:
SerializeJSObject(Handle<JSObject>::cast(object), id);
serializer.WriteUint32(ValueType::OBJECT_ID);
serializer.WriteUint32(id);
break;
default:
if (object->IsString()) {
SerializeString(Handle<String>::cast(object), id);
serializer.WriteUint32(ValueType::STRING_ID);
serializer.WriteUint32(id);
} else {
Throw("Web snapshot: Unsupported object");
}
}
// TODO(v8:11525): Support more types.
}
WebSnapshotDeserializer::WebSnapshotDeserializer(v8::Isolate* isolate)
: WebSnapshotSerializerDeserializer(
reinterpret_cast<v8::internal::Isolate*>(isolate)) {}
bool WebSnapshotDeserializer::UseWebSnapshot(const uint8_t* data,
size_t buffer_size) {
if (strings_.size() > 0) {
Throw("Web snapshot: Can't reuse WebSnapshotDeserializer");
return false;
}
base::ElapsedTimer timer;
if (FLAG_trace_web_snapshot) {
timer.Start();
}
HandleScope scope(isolate_);
size_t ix = 0;
DeserializeStrings(data, ix, buffer_size);
DeserializeMaps(data, ix, buffer_size);
DeserializeFunctions(data, ix, buffer_size);
DeserializeObjects(data, ix, buffer_size);
DeserializeExports(data, ix, buffer_size);
if (ix != buffer_size) {
Throw("Web snapshot: Snapshot length mismatch");
return false;
}
if (FLAG_trace_web_snapshot) {
double ms = timer.Elapsed().InMillisecondsF();
PrintF("[Deserializing snapshot (%zu bytes) took %0.3f ms]\n", buffer_size,
ms);
}
// TODO(v8:11525): Add verification mode; verify the objects we just produced.
return !has_error();
}
void WebSnapshotDeserializer::DeserializeStrings(const uint8_t* data,
size_t& ix, size_t size) {
ValueDeserializer deserializer(isolate_, &data[ix], size - ix);
uint32_t count;
if (!deserializer.ReadUint32(&count)) {
Throw("Web snapshot: Malformed string table");
return;
}
for (uint32_t i = 0; i < count; ++i) {
// TODO(v8:11525): Read strings as UTF-8.
MaybeHandle<String> maybe_string = deserializer.ReadOneByteString();
Handle<String> string;
if (!maybe_string.ToHandle(&string)) {
Throw("Web snapshot: Malformed string");
return;
}
strings_.emplace_back(string);
}
ix = deserializer.position_ - data;
}
void WebSnapshotDeserializer::DeserializeMaps(const uint8_t* data, size_t& ix,
size_t size) {
ValueDeserializer deserializer(isolate_, &data[ix], size - ix);
uint32_t map_count;
if (!deserializer.ReadUint32(&map_count)) {
Throw("Web snapshot: Malformed shape table");
return;
}
for (uint32_t i = 0; i < map_count; ++i) {
uint32_t property_count;
if (!deserializer.ReadUint32(&property_count)) {
Throw("Web snapshot: Malformed shape");
return;
}
if (property_count > kMaxNumberOfDescriptors) {
Throw("Web snapshot: Malformed shape: too many properties");
return;
}
Handle<DescriptorArray> descriptors =
isolate_->factory()->NewDescriptorArray(0, property_count);
for (uint32_t p = 0; p < property_count; ++p) {
uint32_t string_id;
if (!deserializer.ReadUint32(&string_id) ||
string_id >= strings_.size()) {
Throw("Web snapshot: Malformed shape");
return;
}
Handle<String> key = strings_[string_id];
if (!key->IsInternalizedString()) {
key = isolate_->factory()->InternalizeString(key);
strings_[string_id] = key;
}
// Use the "none" representation until we see the first object having this
// map. At that point, modify the representation.
Descriptor desc = Descriptor::DataField(
isolate_, key, static_cast<int>(p), PropertyAttributes::NONE,
Representation::None());
descriptors->Append(&desc);
}
Handle<Map> map = isolate_->factory()->NewMap(
JS_OBJECT_TYPE, JSObject::kHeaderSize * kTaggedSize, HOLEY_ELEMENTS, 0);
map->InitializeDescriptors(isolate_, *descriptors);
maps_.emplace_back(map);
}
ix = deserializer.position_ - data;
}
void WebSnapshotDeserializer::DeserializeFunctions(const uint8_t* data,
size_t& ix, size_t size) {
ValueDeserializer deserializer(isolate_, &data[ix], size - ix);
uint32_t count;
if (!deserializer.ReadUint32(&count)) {
Throw("Web snapshot: Malformed function table");
return;
}
for (uint32_t i = 0; i < count; ++i) {
uint32_t source_id;
if (!deserializer.ReadUint32(&source_id) || source_id >= strings_.size()) {
Throw("Web snapshot: Malformed function");
return;
}
Handle<String> source = strings_[source_id];
// See CreateDynamicFunction which builds the function in a similar way.
IncrementalStringBuilder builder(isolate_);
builder.AppendCString("(function anonymous");
builder.AppendString(source);
builder.AppendCString(")");
MaybeHandle<String> maybe_source = builder.Finish();
if (!maybe_source.ToHandle(&source)) {
Throw("Web snapshot: Error when creating function");
return;
}
Handle<JSFunction> function_from_string;
if (!Compiler::GetFunctionFromString(
handle(isolate_->context().native_context(), isolate_), source,
ONLY_SINGLE_FUNCTION_LITERAL, kNoSourcePosition, false)
.ToHandle(&function_from_string)) {
Throw("Web snapshot: Invalid function source code");
return;
}
Handle<Object> result;
if (!Execution::Call(isolate_, function_from_string,
isolate_->factory()->undefined_value(), 0, nullptr)
.ToHandle(&result)) {
Throw("Web snapshot: Error when creating function");
return;
}
Handle<JSFunction> function = Handle<JSFunction>::cast(result);
functions_.emplace_back(function);
}
ix = deserializer.position_ - data;
}
void WebSnapshotDeserializer::DeserializeObjects(const uint8_t* data,
size_t& ix, size_t size) {
ValueDeserializer deserializer(isolate_, &data[ix], size - ix);
uint32_t object_count;
if (!deserializer.ReadUint32(&object_count)) {
Throw("Web snapshot: Malformed objects table");
return;
}
for (size_t i = 0; i < object_count; ++i) {
uint32_t map_id;
if (!deserializer.ReadUint32(&map_id) || map_id >= maps_.size()) {
Throw("Web snapshot: Malformed object");
return;
}
Handle<Map> map = maps_[map_id];
DescriptorArray descriptors = map->instance_descriptors(kRelaxedLoad);
int no_properties = map->NumberOfOwnDescriptors();
Handle<PropertyArray> property_array =
isolate_->factory()->NewPropertyArray(no_properties);
for (int i = 0; i < no_properties; ++i) {
Handle<Object> value;
uint32_t value_type;
if (!deserializer.ReadUint32(&value_type)) {
Throw("Web snapshot: Malformed object property");
return;
}
Representation wanted_representation;
switch (value_type) {
case ValueType::STRING_ID: {
uint32_t string_id;
if (!deserializer.ReadUint32(&string_id) ||
string_id >= strings_.size()) {
Throw("Web snapshot: Malformed object property");
return;
}
value = strings_[string_id];
wanted_representation = Representation::Tagged();
break;
}
case ValueType::OBJECT_ID:
// TODO(v8:11525): Handle circular references.
UNREACHABLE();
break;
case ValueType::FUNCTION_ID: {
// Functions have been deserialized already.
uint32_t function_id;
if (!deserializer.ReadUint32(&function_id) ||
function_id >= functions_.size()) {
Throw("Web snapshot: Malformed object property");
return;
}
value = functions_[function_id];
wanted_representation = Representation::Tagged();
break;
}
default:
Throw("Web snapshot: Unsupported value type");
return;
}
// Read the representation from the map.
PropertyDetails details = descriptors.GetDetails(InternalIndex(i));
CHECK_EQ(details.location(), kField);
CHECK_EQ(kData, details.kind());
Representation r = details.representation();
if (r.IsNone()) {
// Switch over to wanted_representation.
details = details.CopyWithRepresentation(wanted_representation);
descriptors.SetDetails(InternalIndex(i), details);
} else if (!r.Equals(wanted_representation)) {
// TODO(v8:11525): Support this case too.
UNREACHABLE();
}
property_array->set(i, *value);
}
Handle<JSObject> object = isolate_->factory()->NewJSObjectFromMap(map);
object->set_raw_properties_or_hash(*property_array);
objects_.emplace_back(object);
}
ix = deserializer.position_ - data;
}
void WebSnapshotDeserializer::DeserializeExports(const uint8_t* data,
size_t& ix, size_t size) {
ValueDeserializer deserializer(isolate_, &data[ix], size - ix);
uint32_t count;
if (!deserializer.ReadUint32(&count)) {
Throw("Web snapshot: Malformed export table");
return;
}
for (uint32_t i = 0; i < count; ++i) {
uint32_t string_id = 0, object_id = 0;
if (!deserializer.ReadUint32(&string_id) || string_id >= strings_.size() ||
!deserializer.ReadUint32(&object_id) || object_id >= objects_.size()) {
Throw("Web snapshot: Malformed export");
return;
}
Handle<String> export_name = strings_[string_id];
Handle<Object> exported_object = objects_[object_id];
auto result = Object::SetProperty(isolate_, isolate_->global_object(),
export_name, exported_object);
if (result.is_null()) {
Throw("Web snapshot: Setting global property failed");
return;
}
}
ix = deserializer.position_ - data;
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,137 @@
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_WEB_SNAPSHOT_WEB_SNAPSHOT_H_
#define V8_WEB_SNAPSHOT_WEB_SNAPSHOT_H_
#include <queue>
#include <vector>
#include "src/handles/handles.h"
#include "src/objects/value-serializer.h"
#include "src/snapshot/serializer.h" // For ObjectCacheIndexMap
namespace v8 {
class Context;
class Isolate;
template <typename T>
class Local;
namespace internal {
class Context;
class Map;
class Object;
class String;
struct WebSnapshotData {
uint8_t* buffer = nullptr;
size_t buffer_size = 0;
~WebSnapshotData() { free(buffer); }
};
class WebSnapshotSerializerDeserializer {
public:
bool has_error() const { return error_message_ != nullptr; }
const char* error_message() const { return error_message_; }
enum ValueType : uint8_t { STRING_ID, OBJECT_ID, FUNCTION_ID };
protected:
explicit WebSnapshotSerializerDeserializer(Isolate* isolate)
: isolate_(isolate) {}
void Throw(const char* message);
Isolate* isolate_;
const char* error_message_ = nullptr;
private:
WebSnapshotSerializerDeserializer(const WebSnapshotSerializerDeserializer&) =
delete;
WebSnapshotSerializerDeserializer& operator=(
const WebSnapshotSerializerDeserializer&) = delete;
};
class V8_EXPORT WebSnapshotSerializer
: public WebSnapshotSerializerDeserializer {
public:
explicit WebSnapshotSerializer(v8::Isolate* isolate);
~WebSnapshotSerializer();
bool TakeSnapshot(v8::Local<v8::Context> context,
const std::vector<std::string>& exports,
WebSnapshotData& data_out);
// For inspecting the state after taking a snapshot.
uint32_t string_count() const;
uint32_t map_count() const;
uint32_t function_count() const;
uint32_t object_count() const;
private:
WebSnapshotSerializer(const WebSnapshotSerializer&) = delete;
WebSnapshotSerializer& operator=(const WebSnapshotSerializer&) = delete;
void WriteSnapshot(uint8_t*& buffer, size_t& buffer_size);
// 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 SerializeString(Handle<String> string, uint32_t& id);
void SerializeMap(Handle<Map> map, uint32_t& id);
void SerializeJSFunction(Handle<JSFunction> function, uint32_t& id);
void SerializeJSObject(Handle<JSObject> object, uint32_t& id);
void SerializePendingJSObject(Handle<JSObject> object);
void SerializeExport(Handle<JSObject> object, const std::string& export_name);
void WriteValue(Handle<Object> object, ValueSerializer& serializer);
ValueSerializer string_serializer_;
ValueSerializer map_serializer_;
ValueSerializer function_serializer_;
ValueSerializer object_serializer_;
ValueSerializer export_serializer_;
ObjectCacheIndexMap string_ids_;
ObjectCacheIndexMap map_ids_;
ObjectCacheIndexMap function_ids_;
ObjectCacheIndexMap object_ids_;
uint32_t export_count_ = 0;
std::queue<Handle<JSObject>> pending_objects_;
};
class V8_EXPORT WebSnapshotDeserializer
: public WebSnapshotSerializerDeserializer {
public:
explicit WebSnapshotDeserializer(v8::Isolate* v8_isolate);
bool UseWebSnapshot(const uint8_t* data, size_t buffer_size);
// For inspecting the state after taking a snapshot.
size_t string_count() const { return strings_.size(); }
size_t map_count() const { return maps_.size(); }
size_t function_count() const { return functions_.size(); }
size_t object_count() const { return objects_.size(); }
private:
WebSnapshotDeserializer(const WebSnapshotDeserializer&) = delete;
WebSnapshotDeserializer& operator=(const WebSnapshotDeserializer&) = delete;
void DeserializeStrings(const uint8_t* data, size_t& ix, size_t size);
void DeserializeMaps(const uint8_t* data, size_t& ix, size_t size);
void DeserializeFunctions(const uint8_t* data, size_t& ix, size_t size);
void DeserializeObjects(const uint8_t* data, size_t& ix, size_t size);
void DeserializeExports(const uint8_t* data, size_t& ix, size_t size);
std::vector<Handle<String>> strings_;
std::vector<Handle<Map>> maps_;
std::vector<Handle<JSFunction>> functions_;
std::vector<Handle<JSObject>> objects_;
};
} // namespace internal
} // namespace v8
#endif // V8_WEB_SNAPSHOT_WEB_SNAPSHOT_H_

View File

@ -287,6 +287,7 @@ v8_source_set("cctest_sources") {
"test-version.cc",
"test-weakmaps.cc",
"test-weaksets.cc",
"test-web-snapshots.cc",
"torque/test-torque.cc",
"trace-extension.cc",
"trace-extension.h",

View File

@ -0,0 +1,92 @@
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/web-snapshot/web-snapshot.h"
#include "test/cctest/cctest-utils.h"
#include "test/cctest/cctest.h"
namespace v8 {
namespace internal {
TEST(Minimal) {
CcTest::InitializeVM();
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = isolate->GetCurrentContext();
CompileRun("var foo = {'key': 'lol'}");
WebSnapshotData snapshot_data;
{
std::vector<std::string> exports;
exports.push_back("foo");
WebSnapshotSerializer serializer(isolate);
CHECK(serializer.TakeSnapshot(context, exports, snapshot_data));
CHECK(!serializer.has_error());
CHECK_NOT_NULL(snapshot_data.buffer);
// Strings: 'foo', 'key', 'lol'
CHECK_EQ(3, serializer.string_count());
CHECK_EQ(1, serializer.map_count());
CHECK_EQ(1, serializer.object_count());
CHECK_EQ(0, serializer.function_count());
}
{
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());
v8::Local<v8::String> result = CompileRun("foo.key").As<v8::String>();
CHECK(result->Equals(new_context, v8_str("lol")).FromJust());
CHECK_EQ(3, deserializer.string_count());
CHECK_EQ(1, deserializer.map_count());
CHECK_EQ(1, deserializer.object_count());
CHECK_EQ(0, deserializer.function_count());
}
}
TEST(Function) {
CcTest::InitializeVM();
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = isolate->GetCurrentContext();
CompileRun("var foo = {'key': function() { return '11525'; }}");
WebSnapshotData snapshot_data;
{
std::vector<std::string> exports;
exports.push_back("foo");
WebSnapshotSerializer serializer(isolate);
CHECK(serializer.TakeSnapshot(context, exports, snapshot_data));
CHECK(!serializer.has_error());
CHECK_NOT_NULL(snapshot_data.buffer);
// Strings: 'foo', 'key', function source code
CHECK_EQ(3, serializer.string_count());
CHECK_EQ(1, serializer.map_count());
CHECK_EQ(1, serializer.object_count());
CHECK_EQ(1, serializer.function_count());
}
{
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());
v8::Local<v8::Function> function = CompileRun("foo.key").As<v8::Function>();
v8::Local<v8::Value> result =
function->Call(new_context, new_context->Global(), 0, nullptr)
.ToLocalChecked();
CHECK(result->Equals(new_context, v8_str("11525")).FromJust());
CHECK_EQ(3, deserializer.string_count());
CHECK_EQ(1, deserializer.map_count());
CHECK_EQ(1, deserializer.object_count());
CHECK_EQ(1, deserializer.function_count());
}
}
} // namespace internal
} // namespace v8