[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:
parent
6d8e8ab315
commit
fb03b88ed1
2
BUILD.gn
2
BUILD.gn
@ -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",
|
||||
|
103
src/d8/d8.cc
103
src/d8/d8.cc
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
4
src/web-snapshot/OWNERS
Normal file
@ -0,0 +1,4 @@
|
||||
marja@chromium.org
|
||||
leszeks@chromium.org
|
||||
syg@chromium.org
|
||||
verwaest@chromium.org
|
629
src/web-snapshot/web-snapshot.cc
Normal file
629
src/web-snapshot/web-snapshot.cc
Normal 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
|
137
src/web-snapshot/web-snapshot.h
Normal file
137
src/web-snapshot/web-snapshot.h
Normal 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_
|
@ -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",
|
||||
|
92
test/cctest/test-web-snapshots.cc
Normal file
92
test/cctest/test-web-snapshots.cc
Normal 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
|
Loading…
Reference in New Issue
Block a user