[web snapshots] Support elements in objects

Bug: v8:11525
Change-Id: I0580787252ab235222e9b9fb2d677015794207eb
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3506485
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Commit-Queue: Marja Hölttä <marja@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79441}
This commit is contained in:
Marja Hölttä 2022-03-10 13:09:05 +01:00 committed by V8 LUCI CQ
parent 14331ec537
commit 4f3dd3db80
7 changed files with 198 additions and 40 deletions

View File

@ -876,6 +876,15 @@ void WebSnapshotSerializer::DiscoverObject(Handle<JSObject> object) {
if (!value->IsHeapObject()) continue;
discovery_queue_.push(Handle<HeapObject>::cast(value));
}
// Discover elements.
Handle<FixedArray> elements =
handle(FixedArray::cast(object->elements()), isolate_);
for (int i = 0; i < elements->length(); ++i) {
Object object = elements->get(i);
if (!object.IsHeapObject()) continue;
discovery_queue_.push(handle(HeapObject::cast(object), isolate_));
}
}
// Format (serialized function):
@ -951,11 +960,17 @@ void WebSnapshotSerializer::SerializeContext(Handle<Context> context) {
// - Shape id
// - For each property:
// - Serialized value
// - Max element index + 1 (or 0 if there are no elements)
// - For each element:
// - Index
// - Serialized value
// TODO(v8:11525): Support packed elements with a denser format.
void WebSnapshotSerializer::SerializeObject(Handle<JSObject> object) {
Handle<Map> map(object->map(), isolate_);
uint32_t map_id = GetMapId(*map);
object_serializer_.WriteUint32(map_id);
// Properties.
for (InternalIndex i : map->IterateOwnDescriptors()) {
PropertyDetails details =
map->instance_descriptors(kRelaxedLoad).GetDetails(i);
@ -964,6 +979,34 @@ void WebSnapshotSerializer::SerializeObject(Handle<JSObject> object) {
isolate_, object, details.representation(), field_index);
WriteValue(value, object_serializer_);
}
// Elements.
ReadOnlyRoots roots(isolate_);
Handle<FixedArray> elements =
handle(FixedArray::cast(object->elements()), isolate_);
uint32_t max_element_index = 0;
for (int i = 0; i < elements->length(); ++i) {
DisallowGarbageCollection no_gc;
Object value = elements->get(i);
if (value != roots.the_hole_value()) {
if (i > static_cast<int>(max_element_index)) {
max_element_index = i;
}
}
}
if (max_element_index == 0) {
object_serializer_.WriteUint32(0);
} else {
object_serializer_.WriteUint32(max_element_index + 1);
}
for (int i = 0; i < elements->length(); ++i) {
Handle<Object> value = handle(elements->get(i), isolate_);
if (*value != roots.the_hole_value()) {
DCHECK_LE(i, max_element_index);
object_serializer_.WriteUint32(i);
WriteValue(value, object_serializer_);
}
}
}
// Format (serialized array):
@ -1931,6 +1974,37 @@ void WebSnapshotDeserializer::DeserializeObjects() {
}
Handle<JSObject> object = factory()->NewJSObjectFromMap(map);
object->set_raw_properties_or_hash(*property_array, kRelaxedStore);
uint32_t max_element_index = 0;
if (!deserializer_.ReadUint32(&max_element_index) ||
max_element_index > kMaxItemCount + 1) {
Throw("Malformed object");
return;
}
if (max_element_index > 0) {
--max_element_index; // Subtract 1 to get the real max_element_index.
Handle<FixedArray> elements =
factory()->NewFixedArray(max_element_index + 1);
// Read (index, value) pairs until we encounter one where index ==
// max_element_index.
while (true) {
uint32_t index;
if (!deserializer_.ReadUint32(&index) || index > max_element_index) {
Throw("Malformed object");
return;
}
Object value = ReadValue(elements, index);
elements->set(index, value);
if (index == max_element_index) {
break;
}
}
object->set_elements(*elements);
// Objects always get HOLEY_ELEMENTS.
DCHECK(!IsSmiElementsKind(object->map().elements_kind()));
DCHECK(!IsDoubleElementsKind(object->map().elements_kind()));
DCHECK(IsHoleyElementsKind(object->map().elements_kind()));
}
objects_.set(static_cast<int>(current_object_count_), *object);
}
}

View File

@ -36,6 +36,7 @@
'wasm/wasm-module-builder': [SKIP],
'compiler/fast-api-helpers': [SKIP],
'typedarray-helpers': [SKIP],
'web-snapshot/web-snapshot-helpers': [SKIP],
# All tests in the bug directory are expected to fail.
'bugs/*': [FAIL],

View File

View File

@ -4,27 +4,9 @@
// Flags: --experimental-d8-web-snapshot-api --allow-natives-syntax
'use strict';
function use(exports) {
const result = Object.create(null);
exports.forEach(x => result[x] = globalThis[x]);
return result;
}
function takeAndUseWebSnapshot(createObjects, exports) {
// Take a snapshot in Realm r1.
const r1 = Realm.create();
Realm.eval(r1, createObjects, { type: 'function' });
const snapshot = Realm.takeWebSnapshot(r1, exports);
// Use the snapshot in Realm r2.
const r2 = Realm.create();
const success = Realm.useWebSnapshot(r2, snapshot);
assertTrue(success);
const result =
Realm.eval(r2, use, { type: 'function', arguments: [exports] });
%HeapObjectVerify(result);
return result;
}
d8.file.execute('test/mjsunit/web-snapshot/web-snapshot-helpers.js');
(function TestMinimal() {
function createObjects() {

View File

@ -4,27 +4,9 @@
// Flags: --experimental-d8-web-snapshot-api --allow-natives-syntax
'use strict';
function use(exports) {
const result = Object.create(null);
exports.forEach(x => result[x] = globalThis[x]);
return result;
}
function takeAndUseWebSnapshot(createObjects, exports) {
// Take a snapshot in Realm r1.
const r1 = Realm.create();
Realm.eval(r1, createObjects, { type: 'function' });
const snapshot = Realm.takeWebSnapshot(r1, exports);
// Use the snapshot in Realm r2.
const r2 = Realm.create();
const success = Realm.useWebSnapshot(r2, snapshot);
assertTrue(success);
const result =
Realm.eval(r2, use, { type: 'function', arguments: [exports] });
%HeapObjectVerify(result);
return result;
}
d8.file.execute('test/mjsunit/web-snapshot/web-snapshot-helpers.js');
(function TestObjectReferencingObject() {
function createObjects() {

View File

@ -0,0 +1,95 @@
// 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.
// Flags: --experimental-d8-web-snapshot-api --allow-natives-syntax
'use strict';
d8.file.execute('test/mjsunit/web-snapshot/web-snapshot-helpers.js');
(function TestObjectWithPackedElements() {
function createObjects() {
globalThis.foo = {
'0': 'zero', '1': 'one', '2': 'two', '3': 'three'
};
}
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
// Objects always get HOLEY_ELEMENTS; no PACKED or SMI_ELEMENTS.
const elementsKindTest = {0: 0, 1: 1, 2: 2};
assertFalse(%HasPackedElements(elementsKindTest));
assertFalse(%HasSmiElements(elementsKindTest));
assertFalse(%HasPackedElements(foo));
assertFalse(%HasSmiElements(foo));
assertEquals('zeroonetwothree', foo[0] + foo[1] + foo[2] + foo[3]);
})();
(function TestObjectWithPackedSmiElements() {
function createObjects() {
globalThis.foo = {
'0': 0, '1': 1, '2': 2, '3': 3
};
}
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
assertFalse(%HasPackedElements(foo));
assertFalse(%HasSmiElements(foo));
assertEquals('0123', '' + foo[0] + foo[1] + foo[2] + foo[3]);
})();
(function TestObjectWithHoleyElements() {
function createObjects() {
globalThis.foo = {
'1': 'a', '11': 'b', '111': 'c'
};
}
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
assertFalse(%HasPackedElements(foo));
assertFalse(%HasSmiElements(foo));
assertEquals('abc', foo[1] + foo[11] + foo[111]);
})();
(function TestObjectWithHoleySmiElements() {
function createObjects() {
globalThis.foo = {
'1': 0, '11': 1, '111': 2
};
}
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
assertFalse(%HasPackedElements(foo));
assertFalse(%HasSmiElements(foo));
assertEquals('012', '' + foo[1] + foo[11] + foo[111]);
})();
(function TestObjectWithPropertiesAndElements() {
function createObjects() {
globalThis.foo = {
'prop1': 'value1', '1': 'a', 'prop2': 'value2', '11': 'b', '111': 'c'
};
}
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
assertFalse(%HasPackedElements(foo));
assertFalse(%HasSmiElements(foo));
assertEquals('abc', foo[1] + foo[11] + foo[111]);
assertEquals('value1value2', foo.prop1 + foo.prop2);
})();
(function TestObjectsWithSamePropertiesButDifferentElementsKind() {
function createObjects() {
globalThis.foo = {
'prop1': 'value1', 'prop2': 'value2', '1': 'a', '11': 'b', '111': 'c'
};
globalThis.bar = {
'prop1': 'value1', 'prop2': 'value2', '0': 0, '1': 0
}
}
const { foo, bar } = takeAndUseWebSnapshot(createObjects, ['foo', 'bar']);
assertFalse(%HasPackedElements(foo));
assertFalse(%HasSmiElements(foo));
assertEquals('abc', foo[1] + foo[11] + foo[111]);
assertEquals('value1value2', foo.prop1 + foo.prop2);
assertFalse(%HasPackedElements(bar));
assertFalse(%HasSmiElements(bar));
assertEquals('00', '' + bar[0] + bar[1]);
assertEquals('value1value2', bar.prop1 + bar.prop2);
})();

View File

@ -0,0 +1,24 @@
// 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.
function use(exports) {
const result = Object.create(null);
exports.forEach(x => result[x] = globalThis[x]);
return result;
}
function takeAndUseWebSnapshot(createObjects, exports) {
// Take a snapshot in Realm r1.
const r1 = Realm.create();
Realm.eval(r1, createObjects, { type: 'function' });
const snapshot = Realm.takeWebSnapshot(r1, exports);
// Use the snapshot in Realm r2.
const r2 = Realm.create();
const success = Realm.useWebSnapshot(r2, snapshot);
assertTrue(success);
const result =
Realm.eval(r2, use, { type: 'function', arguments: [exports] });
%HeapObjectVerify(result);
return result;
}