Commit c32ba7e0 authored by Vicky Kontoura's avatar Vicky Kontoura Committed by V8 LUCI CQ

[web snapshot] Support mjsunit tests

This CL adds support for testing web snapshots through mjsunit tests.
To allow for taking and using web snapshots from JavaScript, two
methods, Realm.takeWebSnapshot() and Realm.useWebSnapshot(), are
introduced in d8.

Both of these methods accept a Realm as a parameter, allowing for
mjsunit tests to create and use the snapshot in different realms.

To return the snapshot data, Realm.takeWebSnapshot() creates and
returns a snapshot object with the snapshot data stored as an embedder
field.

Bug: v8:11525, v8:11706
Change-Id: I6e514e10eabf5bdb96d81e2697d4ddc49d92de73
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2905610Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarMarja Hölttä <marja@chromium.org>
Commit-Queue: Vicky Kontoura <vkont@google.com>
Cr-Commit-Position: refs/heads/master@{#74783}
parent c24b5a2b
......@@ -1386,6 +1386,10 @@ PerIsolateData::PerIsolateData(Isolate* isolate)
async_hooks_wrapper_ = new AsyncHooks(isolate);
}
ignore_unhandled_promises_ = false;
// TODO(v8:11525): Use methods on global Snapshot objects with
// signature checks.
HandleScope scope(isolate);
Shell::CreateSnapshotTemplate(isolate);
}
PerIsolateData::~PerIsolateData() {
......@@ -1481,6 +1485,14 @@ void PerIsolateData::SetTestApiObjectCtor(Local<FunctionTemplate> ctor) {
test_api_object_ctor_.Reset(isolate_, ctor);
}
Local<FunctionTemplate> PerIsolateData::GetSnapshotObjectCtor() const {
return snapshot_object_ctor_.Get(isolate_);
}
void PerIsolateData::SetSnapshotObjectCtor(Local<FunctionTemplate> ctor) {
snapshot_object_ctor_.Reset(isolate_, ctor);
}
PerIsolateData::RealmScope::RealmScope(PerIsolateData* data) : data_(data) {
data_->realm_count_ = 1;
data_->realm_current_ = 0;
......@@ -1800,6 +1812,103 @@ void Shell::RealmSharedSet(Local<String> property, Local<Value> value,
data->realm_shared_.Reset(isolate, value);
}
// Realm.takeWebSnapshot(index, exports) takes a snapshot of the list of exports
// in the realm with the specified index and returns the result.
void Shell::RealmTakeWebSnapshot(
const v8::FunctionCallbackInfo<v8::Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 2 || !args[1]->IsArray()) {
isolate->ThrowError("Invalid argument");
return;
}
PerIsolateData* data = PerIsolateData::Get(isolate);
int index = data->RealmIndexOrThrow(args, 0);
if (index == -1) return;
// Create a std::vector<std::string> from the list of exports.
// TODO(v8:11525): Add an API in the serializer that accepts a Local<String>
// Array.
Local<Context> current_context = isolate->GetCurrentContext();
Local<Array> exports_array = args[1].As<Array>();
std::vector<std::string> exports;
for (int i = 0, length = exports_array->Length(); i < length; ++i) {
Local<String> local_str = exports_array->Get(current_context, i)
.ToLocalChecked()
->ToString(current_context)
.ToLocalChecked();
std::string str = ToSTLString(isolate, local_str);
exports.push_back(str);
}
// Enter the realm.
Local<Context> realm = Local<Context>::New(isolate, data->realms_[index]);
realm->Enter();
int previous_index = data->realm_current_;
data->realm_current_ = data->realm_switch_ = index;
// Take the snapshot.
i::WebSnapshotSerializer serializer(isolate);
auto snapshot_data_shared = std::make_shared<i::WebSnapshotData>();
if (!serializer.TakeSnapshot(realm, exports, *snapshot_data_shared)) {
realm->Exit();
data->realm_current_ = data->realm_switch_ = previous_index;
args.GetReturnValue().Set(Undefined(isolate));
return;
}
// Exit the realm.
realm->Exit();
data->realm_current_ = data->realm_switch_ = previous_index;
// Create a snapshot object and store the WebSnapshotData as an embedder
// field. TODO(v8:11525): Use methods on global Snapshot objects with
// signature checks.
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
i::Handle<i::Object> snapshot_data_managed =
i::Managed<i::WebSnapshotData>::FromSharedPtr(
i_isolate, snapshot_data_shared->buffer_size, snapshot_data_shared);
v8::Local<v8::Value> shapshot_data = Utils::ToLocal(snapshot_data_managed);
Local<ObjectTemplate> snapshot_template =
data->GetSnapshotObjectCtor()->InstanceTemplate();
Local<Object> snapshot_instance =
snapshot_template->NewInstance(realm).ToLocalChecked();
snapshot_instance->SetInternalField(0, shapshot_data);
args.GetReturnValue().Set(snapshot_instance);
}
// Realm.useWebSnapshot(index, snapshot) deserializes the snapshot in the realm
// with the specified index.
void Shell::RealmUseWebSnapshot(
const v8::FunctionCallbackInfo<v8::Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 2) {
isolate->ThrowError("Invalid argument");
return;
}
PerIsolateData* data = PerIsolateData::Get(isolate);
int index = data->RealmIndexOrThrow(args, 0);
if (index == -1) return;
// Restore the snapshot data from the snapshot object.
Local<Object> snapshot_instance = args[1].As<Object>();
if (snapshot_instance->InternalFieldCount() != 1) {
isolate->ThrowError("Invalid argument");
return;
}
v8::Local<v8::Value> snapshot_data = snapshot_instance->GetInternalField(0);
i::Handle<i::Object> snapshot_data_handle = Utils::OpenHandle(*snapshot_data);
auto snapshot_data_managed =
i::Handle<i::Managed<i::WebSnapshotData>>::cast(snapshot_data_handle);
std::shared_ptr<i::WebSnapshotData> snapshot_data_shared =
snapshot_data_managed->get();
// Enter the realm.
Local<Context> realm = Local<Context>::New(isolate, data->realms_[index]);
realm->Enter();
int previous_index = data->realm_current_;
data->realm_current_ = data->realm_switch_ = index;
// Deserialize the snapshot.
i::WebSnapshotDeserializer deserializer(isolate);
bool success = deserializer.UseWebSnapshot(snapshot_data_shared->buffer,
snapshot_data_shared->buffer_size);
args.GetReturnValue().Set(success);
realm->Exit();
data->realm_current_ = data->realm_switch_ = previous_index;
}
void Shell::LogGetAndStop(const v8::FunctionCallbackInfo<v8::Value>& args) {
Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
......@@ -2782,9 +2891,21 @@ Local<ObjectTemplate> Shell::CreateRealmTemplate(Isolate* isolate) {
FunctionTemplate::New(isolate, RealmEval));
realm_template->SetAccessor(String::NewFromUtf8Literal(isolate, "shared"),
RealmSharedGet, RealmSharedSet);
if (options.d8_web_snapshot_api) {
realm_template->Set(isolate, "takeWebSnapshot",
FunctionTemplate::New(isolate, RealmTakeWebSnapshot));
realm_template->Set(isolate, "useWebSnapshot",
FunctionTemplate::New(isolate, RealmUseWebSnapshot));
}
return realm_template;
}
Local<FunctionTemplate> Shell::CreateSnapshotTemplate(Isolate* isolate) {
Local<FunctionTemplate> snapshot_template = FunctionTemplate::New(isolate);
snapshot_template->InstanceTemplate()->SetInternalFieldCount(1);
PerIsolateData::Get(isolate)->SetSnapshotObjectCtor(snapshot_template);
return snapshot_template;
}
Local<ObjectTemplate> Shell::CreateD8Template(Isolate* isolate) {
Local<ObjectTemplate> d8_template = ObjectTemplate::New(isolate);
{
......@@ -4134,6 +4255,9 @@ bool Shell::SetOptions(int argc, char* argv[]) {
} else if (strncmp(argv[i], "--web-snapshot-config=", 22) == 0) {
options.web_snapshot_config = argv[i] + 22;
argv[i] = nullptr;
} else if (strcmp(argv[i], "--d8-web-snapshot-api") == 0) {
options.d8_web_snapshot_api = true;
argv[i] = nullptr;
} else if (strcmp(argv[i], "--compile-only") == 0) {
options.compile_only = true;
argv[i] = nullptr;
......@@ -4234,6 +4358,7 @@ int Shell::RunMain(Isolate* isolate, bool last_run) {
}
HandleScope scope(isolate);
Local<Context> context = CreateEvaluationContext(isolate);
CreateSnapshotTemplate(isolate);
bool use_existing_context = last_run && use_interactive_shell();
if (use_existing_context) {
// Keep using the same context in the interactive shell.
......
......@@ -274,6 +274,9 @@ class PerIsolateData {
Local<FunctionTemplate> GetTestApiObjectCtor() const;
void SetTestApiObjectCtor(Local<FunctionTemplate> ctor);
Local<FunctionTemplate> GetSnapshotObjectCtor() const;
void SetSnapshotObjectCtor(Local<FunctionTemplate> ctor);
private:
friend class Shell;
friend class RealmScope;
......@@ -293,6 +296,7 @@ class PerIsolateData {
std::unordered_set<DynamicImportData*> import_data_;
#endif
Global<FunctionTemplate> test_api_object_ctor_;
Global<FunctionTemplate> snapshot_object_ctor_;
int RealmIndexOrThrow(const v8::FunctionCallbackInfo<v8::Value>& args,
int arg_offset);
......@@ -406,6 +410,8 @@ class ShellOptions {
"enable-system-instrumentation", false};
DisallowReassignment<const char*> web_snapshot_config = {
"web-snapshot-config", nullptr};
DisallowReassignment<bool> d8_web_snapshot_api = {"d8-web-snapshot-api",
false};
DisallowReassignment<bool> compile_only = {"compile-only", false};
DisallowReassignment<int> repeat_compile = {"repeat-compile", 1};
};
......@@ -476,6 +482,10 @@ class Shell : public i::AllStatic {
const PropertyCallbackInfo<Value>& info);
static void RealmSharedSet(Local<String> property, Local<Value> value,
const PropertyCallbackInfo<void>& info);
static void RealmTakeWebSnapshot(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void RealmUseWebSnapshot(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void LogGetAndStop(const v8::FunctionCallbackInfo<v8::Value>& args);
static void TestVerifySourcePositions(
......@@ -601,6 +611,8 @@ class Shell : public i::AllStatic {
static void PromiseRejectCallback(v8::PromiseRejectMessage reject_message);
static Local<FunctionTemplate> CreateSnapshotTemplate(Isolate* isolate);
private:
static Global<Context> evaluation_context_;
static base::OnceType quit_once_;
......
......@@ -27,9 +27,12 @@ class Map;
class Object;
class String;
struct WebSnapshotData {
struct WebSnapshotData : public std::enable_shared_from_this<WebSnapshotData> {
uint8_t* buffer = nullptr;
size_t buffer_size = 0;
WebSnapshotData() = default;
WebSnapshotData(const WebSnapshotData&) = delete;
WebSnapshotData& operator=(const WebSnapshotData&) = delete;
~WebSnapshotData() { free(buffer); }
};
......
// 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.
// Flags: --d8-web-snapshot-api
function callString(f) {
return '(' + f.toString() + ')()';
}
(function TestMinimal() {
const r1 = Realm.create();
function initialize() {
globalThis.foo = {
'str': 'hello',
'n': 42,
};
}
Realm.eval(r1, callString(initialize));
const snapshot = Realm.takeWebSnapshot(r1, ['foo']);
const r2 = Realm.create();
function use() {
return globalThis.foo;
}
const success = Realm.useWebSnapshot(r2, snapshot);
assertTrue(success);
const foo = Realm.eval(r2, callString(use));
assertEquals(foo.str, 'hello');
assertEquals(foo.n, 42);
})();
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