From c071cc96b3fa060c264214ce1c0b5d761785e667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marja=20H=C3=B6ltt=C3=A4?= Date: Wed, 20 Apr 2022 15:37:05 +0200 Subject: [PATCH] [web snapshots] Add a JS API for deserializing Web snapshots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Commit-Queue: Marja Hölttä Cr-Commit-Position: refs/heads/main@{#80064} --- BUILD.bazel | 1 + BUILD.gn | 1 + src/builtins/builtins-definitions.h | 4 + src/builtins/builtins-web-snapshots.cc | 118 ++++++++++++++++++ src/flags/flag-definitions.h | 7 +- src/init/bootstrapper.cc | 18 +++ src/runtime/runtime-test.cc | 88 ------------- src/runtime/runtime.h | 2 - .../web-snapshot/web-snapshot-externals.js | 29 ++--- 9 files changed, 158 insertions(+), 110 deletions(-) create mode 100644 src/builtins/builtins-web-snapshots.cc diff --git a/BUILD.bazel b/BUILD.bazel index 0fb6962db7..ce3a95ca75 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -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", diff --git a/BUILD.gn b/BUILD.gn index 2d122b6a5b..1cddc96b48 100644 --- a/BUILD.gn +++ b/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", diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index bf5a145e7c..53c22d712a 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -608,6 +608,10 @@ namespace internal { CPP(JsonParse) \ CPP(JsonStringify) \ \ + /* Web snapshots */ \ + CPP(WebSnapshotSerialize) \ + CPP(WebSnapshotDeserialize) \ + \ /* ICs */ \ TFH(LoadIC, LoadWithVector) \ TFH(LoadIC_Megamorphic, LoadWithVector) \ diff --git a/src/builtins/builtins-web-snapshots.cc b/src/builtins/builtins-web-snapshots.cc new file mode 100644 index 0000000000..8dcfa4fbde --- /dev/null +++ b/src/builtins/builtins-web-snapshots.cc @@ -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 = args.at(1); + Handle block_list = isolate->factory()->empty_fixed_array(); + Handle 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(2); + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( + isolate, block_list, + JSReceiver::GetOwnValues(block_list_js_array, + PropertyFilter::ENUMERABLE_STRINGS)); + } + + auto snapshot_data = std::make_shared(); + 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(block_list->length()) < + serializer.external_objects_count()) { + Handle externals = serializer.GetExternals(); + Handle 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 maybe_result = + isolate->factory()->NewJSArrayBufferAndBackingStore( + snapshot_data->buffer_size, InitializedFlag::kUninitialized); + Handle result; + if (!maybe_result.ToHandle(&result)) { + THROW_NEW_ERROR_RETURN_FAILURE( + isolate, NewTypeError(MessageTemplate::kOutOfMemory, + isolate->factory()->NewStringFromAsciiChecked( + "WebSnapshotSerialize"))); + } + uint8_t* data = + reinterpret_cast(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(1); + std::shared_ptr backing_store = buffer->GetBackingStore(); + if (backing_store.get() == nullptr) { + THROW_NEW_ERROR_RETURN_FAILURE( + isolate, NewTypeError(MessageTemplate::kInvalidArgument)); + } + const uint8_t* data = + reinterpret_cast(backing_store->buffer_start()); + + Handle 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(2); + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( + isolate, injected_references, + JSReceiver::GetOwnValues(js_array, PropertyFilter::ENUMERABLE_STRINGS)); + } + + WebSnapshotDeserializer deserializer(reinterpret_cast(isolate), + data, backing_store->byte_length()); + if (!deserializer.Deserialize(injected_references)) { + DCHECK(isolate->has_pending_exception()); + return ReadOnlyRoots(isolate).exception(); + } + Handle object; + if (!deserializer.value().ToHandle(&object)) { + return ReadOnlyRoots(isolate).undefined_value(); + } + return *object; +} + +} // namespace internal +} // namespace v8 diff --git a/src/flags/flag-definitions.h b/src/flags/flag-definitions.h index b318bdba86..4c5ba01c14 100644 --- a/src/flags/flag-definitions.h +++ b/src/flags/flag-definitions.h @@ -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 diff --git a/src/init/bootstrapper.cc b/src/init/bootstrapper.cc index d4c4122c22..7a71ebd83d 100644 --- a/src/init/bootstrapper.cc +++ b/src/init/bootstrapper.cc @@ -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 CreateArrayBuffer(Handle 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 global(native_context()->global_object(), isolate()); + Handle 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 Genesis::CreateArrayBuffer( Handle name, ArrayBufferKind array_buffer_kind) { // Create the %ArrayBufferPrototype% diff --git a/src/runtime/runtime-test.cc b/src/runtime/runtime-test.cc index 3e0ca4e4dc..c37bf715b1 100644 --- a/src/runtime/runtime-test.cc +++ b/src/runtime/runtime-test.cc @@ -1636,94 +1636,6 @@ RUNTIME_FUNCTION(Runtime_IsSharedString) { Handle::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 = args.at(0); - Handle block_list = isolate->factory()->empty_fixed_array(); - Handle 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(1); - ASSIGN_RETURN_FAILURE_ON_EXCEPTION( - isolate, block_list, - JSReceiver::GetOwnValues(block_list_js_array, - PropertyFilter::ENUMERABLE_STRINGS)); - } - - auto snapshot_data = std::make_shared(); - 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(block_list->length()) < - serializer.external_objects_count()) { - Handle externals = serializer.GetExternals(); - Handle 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 managed_object = Managed::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_data = args.at(0); - Handle 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(1); - ASSIGN_RETURN_FAILURE_ON_EXCEPTION( - isolate, injected_references, - JSReceiver::GetOwnValues(js_array, PropertyFilter::ENUMERABLE_STRINGS)); - } - - auto data = Managed::cast(*foreign_data).get(); - v8::Isolate* v8_isolate = reinterpret_cast(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; - 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); diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index a140f9b526..66117d539d 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -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) \ diff --git a/test/mjsunit/web-snapshot/web-snapshot-externals.js b/test/mjsunit/web-snapshot/web-snapshot-externals.js index c8f0c024c1..17d548a9f1 100644 --- a/test/mjsunit/web-snapshot/web-snapshot-externals.js +++ b/test/mjsunit/web-snapshot/web-snapshot-externals.js @@ -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);