[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:
parent
14331ec537
commit
4f3dd3db80
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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],
|
||||
|
0
test/mjsunit/web-snapshot-helpers.js
Normal file
0
test/mjsunit/web-snapshot-helpers.js
Normal 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() {
|
||||
|
@ -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() {
|
||||
|
95
test/mjsunit/web-snapshot/web-snapshot-3.js
Normal file
95
test/mjsunit/web-snapshot/web-snapshot-3.js
Normal 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);
|
||||
})();
|
24
test/mjsunit/web-snapshot/web-snapshot-helpers.js
Normal file
24
test/mjsunit/web-snapshot/web-snapshot-helpers.js
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user