[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:
parent
3b1c1cab1a
commit
c071cc96b3
@ -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",
|
||||
|
1
BUILD.gn
1
BUILD.gn
@ -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",
|
||||
|
@ -608,6 +608,10 @@ namespace internal {
|
||||
CPP(JsonParse) \
|
||||
CPP(JsonStringify) \
|
||||
\
|
||||
/* Web snapshots */ \
|
||||
CPP(WebSnapshotSerialize) \
|
||||
CPP(WebSnapshotDeserialize) \
|
||||
\
|
||||
/* ICs */ \
|
||||
TFH(LoadIC, LoadWithVector) \
|
||||
TFH(LoadIC_Megamorphic, LoadWithVector) \
|
||||
|
118
src/builtins/builtins-web-snapshots.cc
Normal file
118
src/builtins/builtins-web-snapshots.cc
Normal 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
|
@ -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
|
||||
|
@ -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%
|
||||
|
@ -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);
|
||||
|
@ -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) \
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user