[web snapshots] Add a JS API for deserializing Web snapshots

This enables downloading web snapshots with XMLHttpRequest and
deserializing them.

Bug: v8:11525
Change-Id: I498f1e99795d474a1715fce9aa1d8c1a34651c42
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3585961
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Commit-Queue: Marja Hölttä <marja@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80064}
This commit is contained in:
Marja Hölttä 2022-04-20 15:37:05 +02:00 committed by V8 LUCI CQ
parent 3b1c1cab1a
commit c071cc96b3
9 changed files with 158 additions and 110 deletions

View File

@ -1121,6 +1121,7 @@ filegroup(
"src/builtins/builtins-utils-inl.h",
"src/builtins/builtins-utils.h",
"src/builtins/builtins-weak-refs.cc",
"src/builtins/builtins-web-snapshots.cc",
"src/builtins/builtins.cc",
"src/builtins/builtins.h",
"src/builtins/constants-table-builder.cc",

View File

@ -4133,6 +4133,7 @@ v8_source_set("v8_base_without_compiler") {
"src/builtins/builtins-trace.cc",
"src/builtins/builtins-typed-array.cc",
"src/builtins/builtins-weak-refs.cc",
"src/builtins/builtins-web-snapshots.cc",
"src/builtins/builtins.cc",
"src/builtins/constants-table-builder.cc",
"src/codegen/aligned-slot-allocator.cc",

View File

@ -608,6 +608,10 @@ namespace internal {
CPP(JsonParse) \
CPP(JsonStringify) \
\
/* Web snapshots */ \
CPP(WebSnapshotSerialize) \
CPP(WebSnapshotDeserialize) \
\
/* ICs */ \
TFH(LoadIC, LoadWithVector) \
TFH(LoadIC_Megamorphic, LoadWithVector) \

View File

@ -0,0 +1,118 @@
// Copyright 2022 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/builtins/builtins-utils-inl.h"
#include "src/builtins/builtins.h"
#include "src/logging/counters.h"
#include "src/objects/js-array-buffer-inl.h"
#include "src/objects/js-array-inl.h"
#include "src/objects/objects-inl.h"
#include "src/web-snapshot/web-snapshot.h"
namespace v8 {
namespace internal {
BUILTIN(WebSnapshotSerialize) {
HandleScope scope(isolate);
if (args.length() < 2 || args.length() > 3) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kInvalidArgument));
}
Handle<Object> object = args.at(1);
Handle<FixedArray> block_list = isolate->factory()->empty_fixed_array();
Handle<JSArray> block_list_js_array;
if (args.length() == 3) {
if (!args[2].IsJSArray()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kInvalidArgument));
}
block_list_js_array = args.at<JSArray>(2);
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, block_list,
JSReceiver::GetOwnValues(block_list_js_array,
PropertyFilter::ENUMERABLE_STRINGS));
}
auto snapshot_data = std::make_shared<WebSnapshotData>();
WebSnapshotSerializer serializer(isolate);
if (!serializer.TakeSnapshot(object, block_list, *snapshot_data)) {
DCHECK(isolate->has_pending_exception());
return ReadOnlyRoots(isolate).exception();
}
if (!block_list_js_array.is_null() &&
static_cast<uint32_t>(block_list->length()) <
serializer.external_objects_count()) {
Handle<FixedArray> externals = serializer.GetExternals();
Handle<Map> map = JSObject::GetElementsTransitionMap(block_list_js_array,
PACKED_ELEMENTS);
block_list_js_array->set_elements(*externals);
block_list_js_array->set_length(Smi::FromInt(externals->length()));
block_list_js_array->set_map(*map);
}
MaybeHandle<JSArrayBuffer> maybe_result =
isolate->factory()->NewJSArrayBufferAndBackingStore(
snapshot_data->buffer_size, InitializedFlag::kUninitialized);
Handle<JSArrayBuffer> result;
if (!maybe_result.ToHandle(&result)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kOutOfMemory,
isolate->factory()->NewStringFromAsciiChecked(
"WebSnapshotSerialize")));
}
uint8_t* data =
reinterpret_cast<uint8_t*>(result->GetBackingStore()->buffer_start());
memcpy(data, snapshot_data->buffer, snapshot_data->buffer_size);
return *result;
}
BUILTIN(WebSnapshotDeserialize) {
HandleScope scope(isolate);
if (args.length() < 2 || args.length() > 3) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kInvalidArgument));
}
if (!args[1].IsJSArrayBuffer()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kInvalidArgument));
}
auto buffer = args.at<JSArrayBuffer>(1);
std::shared_ptr<BackingStore> backing_store = buffer->GetBackingStore();
if (backing_store.get() == nullptr) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kInvalidArgument));
}
const uint8_t* data =
reinterpret_cast<uint8_t*>(backing_store->buffer_start());
Handle<FixedArray> injected_references =
isolate->factory()->empty_fixed_array();
if (args.length() == 3) {
if (!args[2].IsJSArray()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kInvalidArgument));
}
auto js_array = args.at<JSArray>(2);
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, injected_references,
JSReceiver::GetOwnValues(js_array, PropertyFilter::ENUMERABLE_STRINGS));
}
WebSnapshotDeserializer deserializer(reinterpret_cast<v8::Isolate*>(isolate),
data, backing_store->byte_length());
if (!deserializer.Deserialize(injected_references)) {
DCHECK(isolate->has_pending_exception());
return ReadOnlyRoots(isolate).exception();
}
Handle<Object> object;
if (!deserializer.value().ToHandle(&object)) {
return ReadOnlyRoots(isolate).undefined_value();
}
return *object;
}
} // namespace internal
} // namespace v8

View File

@ -2225,11 +2225,10 @@ DEFINE_NEG_IMPLICATION(single_threaded_gc, parallel_scavenge)
DEFINE_NEG_IMPLICATION(single_threaded_gc, concurrent_array_buffer_sweeping)
DEFINE_NEG_IMPLICATION(single_threaded_gc, stress_concurrent_allocation)
// Web snapshots
// Web snapshots: 1) expose WebSnapshot.* API 2) interpret scripts as web
// snapshots if they start with a magic number.
// TODO(v8:11525): Remove this flag once proper embedder integration is done.
DEFINE_BOOL(
experimental_web_snapshots, false,
"interpret scripts as web snapshots if they start with a magic number")
DEFINE_BOOL(experimental_web_snapshots, false, "enable Web Snapshots")
DEFINE_NEG_IMPLICATION(experimental_web_snapshots, script_streaming)
#undef FLAG

View File

@ -239,6 +239,8 @@ class Genesis {
#undef DECLARE_FEATURE_INITIALIZATION
void InitializeGlobal_regexp_linear_flag();
void InitializeGlobal_experimental_web_snapshots();
enum ArrayBufferKind { ARRAY_BUFFER, SHARED_ARRAY_BUFFER };
Handle<JSFunction> CreateArrayBuffer(Handle<String> name,
ArrayBufferKind array_buffer_kind);
@ -4117,6 +4119,7 @@ void Genesis::InitializeExperimentalGlobal() {
HARMONY_INPROGRESS(FEATURE_INITIALIZE_GLOBAL)
#undef FEATURE_INITIALIZE_GLOBAL
InitializeGlobal_regexp_linear_flag();
InitializeGlobal_experimental_web_snapshots();
}
bool Genesis::CompileExtension(Isolate* isolate, v8::Extension* extension) {
@ -5449,6 +5452,21 @@ void Genesis::InitializeGlobal_harmony_intl_number_format_v3() {
#endif // V8_INTL_SUPPORT
void Genesis::InitializeGlobal_experimental_web_snapshots() {
if (!FLAG_experimental_web_snapshots) return;
Handle<JSGlobalObject> global(native_context()->global_object(), isolate());
Handle<JSObject> web_snapshot_object =
factory()->NewJSObject(isolate_->object_function(), AllocationType::kOld);
JSObject::AddProperty(isolate_, global, "WebSnapshot", web_snapshot_object,
DONT_ENUM);
InstallToStringTag(isolate_, web_snapshot_object, "WebSnapshot");
SimpleInstallFunction(isolate_, web_snapshot_object, "serialize",
Builtin::kWebSnapshotSerialize, 2, false);
SimpleInstallFunction(isolate_, web_snapshot_object, "deserialize",
Builtin::kWebSnapshotDeserialize, 2, false);
}
Handle<JSFunction> Genesis::CreateArrayBuffer(
Handle<String> name, ArrayBufferKind array_buffer_kind) {
// Create the %ArrayBufferPrototype%

View File

@ -1636,94 +1636,6 @@ RUNTIME_FUNCTION(Runtime_IsSharedString) {
Handle<String>::cast(obj)->IsShared());
}
RUNTIME_FUNCTION(Runtime_WebSnapshotSerialize) {
if (!FLAG_allow_natives_syntax) {
return ReadOnlyRoots(isolate).undefined_value();
}
HandleScope scope(isolate);
if (args.length() < 1 || args.length() > 2) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kRuntimeWrongNumArgs));
}
Handle<Object> object = args.at(0);
Handle<FixedArray> block_list = isolate->factory()->empty_fixed_array();
Handle<JSArray> block_list_js_array;
if (args.length() == 2) {
if (!args[1].IsJSArray()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kInvalidArgument));
}
block_list_js_array = args.at<JSArray>(1);
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, block_list,
JSReceiver::GetOwnValues(block_list_js_array,
PropertyFilter::ENUMERABLE_STRINGS));
}
auto snapshot_data = std::make_shared<WebSnapshotData>();
WebSnapshotSerializer serializer(isolate);
if (!serializer.TakeSnapshot(object, block_list, *snapshot_data)) {
DCHECK(isolate->has_pending_exception());
return ReadOnlyRoots(isolate).exception();
}
if (!block_list_js_array.is_null() &&
static_cast<uint32_t>(block_list->length()) <
serializer.external_objects_count()) {
Handle<FixedArray> externals = serializer.GetExternals();
Handle<Map> map = JSObject::GetElementsTransitionMap(block_list_js_array,
PACKED_ELEMENTS);
block_list_js_array->set_elements(*externals);
block_list_js_array->set_length(Smi::FromInt(externals->length()));
block_list_js_array->set_map(*map);
}
i::Handle<i::Object> managed_object = Managed<WebSnapshotData>::FromSharedPtr(
isolate, snapshot_data->buffer_size, snapshot_data);
return *managed_object;
}
RUNTIME_FUNCTION(Runtime_WebSnapshotDeserialize) {
if (!FLAG_allow_natives_syntax) {
return ReadOnlyRoots(isolate).undefined_value();
}
HandleScope scope(isolate);
if (args.length() == 0 || args.length() > 2) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kRuntimeWrongNumArgs));
}
if (!args[0].IsForeign()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kInvalidArgument));
}
Handle<Foreign> foreign_data = args.at<Foreign>(0);
Handle<FixedArray> injected_references =
isolate->factory()->empty_fixed_array();
if (args.length() == 2) {
if (!args[1].IsJSArray()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kInvalidArgument));
}
auto js_array = args.at<JSArray>(1);
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, injected_references,
JSReceiver::GetOwnValues(js_array, PropertyFilter::ENUMERABLE_STRINGS));
}
auto data = Managed<WebSnapshotData>::cast(*foreign_data).get();
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
WebSnapshotDeserializer deserializer(v8_isolate, data->buffer,
data->buffer_size);
if (!deserializer.Deserialize(injected_references)) {
DCHECK(isolate->has_pending_exception());
return ReadOnlyRoots(isolate).exception();
}
Handle<Object> object;
if (!deserializer.value().ToHandle(&object)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kWebSnapshotError));
}
return *object;
}
RUNTIME_FUNCTION(Runtime_SharedGC) {
SealHandleScope scope(isolate);
isolate->heap()->CollectSharedGarbage(GarbageCollectionReason::kTesting);

View File

@ -568,8 +568,6 @@ namespace internal {
F(TurbofanStaticAssert, 1, 1) \
F(TypedArraySpeciesProtector, 0, 1) \
F(WaitForBackgroundOptimization, 0, 1) \
F(WebSnapshotDeserialize, -1, 1) \
F(WebSnapshotSerialize, -1, 1) \
I(DeoptimizeNow, 0, 1)
#define FOR_EACH_INTRINSIC_TYPEDARRAY(F, I) \

View File

@ -2,9 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --experimental-d8-web-snapshot-api --allow-natives-syntax
// Flags: --experimental-web-snapshots --allow-natives-syntax
const external_1 = {external: 1};
const external_2 = {external: 2};
@ -16,8 +14,8 @@ const object = {
};
(function testNoExternals() {
const snapshot = %WebSnapshotSerialize(object);
const deserialized = %WebSnapshotDeserialize(snapshot);
const snapshot = WebSnapshot.serialize(object);
const deserialized = WebSnapshot.deserialize(snapshot);
%HeapObjectVerify(deserialized);
assertEquals(deserialized, object);
assertEquals(deserialized.b, external_1);
@ -28,9 +26,9 @@ const object = {
(function testOneExternals() {
const externals = [ external_1];
const snapshot = %WebSnapshotSerialize(object, externals);
const snapshot = WebSnapshot.serialize(object, externals);
const replaced_externals = [{replacement:1}]
const deserialized = %WebSnapshotDeserialize(snapshot, replaced_externals);
const deserialized = WebSnapshot.deserialize(snapshot, replaced_externals);
%HeapObjectVerify(deserialized);
assertEquals(deserialized.a, object.a);
assertSame(deserialized.b, replaced_externals[0]);
@ -43,9 +41,9 @@ const object = {
(function testTwoExternals() {
const externals = [external_1, external_2];
const snapshot = %WebSnapshotSerialize(object, externals);
const snapshot = WebSnapshot.serialize(object, externals);
const replaced_externals = [{replacement:1}, {replacement:2}]
const deserialized = %WebSnapshotDeserialize(snapshot, replaced_externals);
const deserialized = WebSnapshot.deserialize(snapshot, replaced_externals);
%HeapObjectVerify(deserialized);
assertEquals(deserialized.a, object.a);
assertSame(deserialized.b, replaced_externals[0]);
@ -55,24 +53,23 @@ const object = {
assertSame(deserialized.d.d_a, replaced_externals[1]);
})();
(function testApiObject() {
const api_object = new d8.dom.Div();
const source_1 = [{}, api_object];
assertThrows(() => %WebSnapshotSerialize(source_1));
assertThrows(() => WebSnapshot.serialize(source_1));
let externals = [external_1]
const source_2 = [{}, external_1, api_object, api_object];
const snapshot_2 = %WebSnapshotSerialize(source_2, externals);
const snapshot_2 = WebSnapshot.serialize(source_2, externals);
%HeapObjectVerify(externals);
// Check that the unhandled api object is added to the externals.
assertArrayEquals(externals, [external_1, api_object]);
assertThrows(() => %WebSnapshotDeserialize(snapshot_2));
assertThrows(() => %WebSnapshotDeserialize(snapshot_2, []));
assertThrows(() => %WebSnapshotDeserialize(snapshot_2, [external_1]));
assertThrows(() => WebSnapshot.deserialize(snapshot_2));
assertThrows(() => WebSnapshot.deserialize(snapshot_2, []));
assertThrows(() => WebSnapshot.deserialize(snapshot_2, [external_1]));
const result_2 = %WebSnapshotDeserialize(snapshot_2, [external_1, api_object]);
const result_2 = WebSnapshot.deserialize(snapshot_2, [external_1, api_object]);
%HeapObjectVerify(externals);
%HeapObjectVerify(result_2);
assertArrayEquals(result_2, source_2);