[web snapshot] Add support for sparse array
This CL adds the array type flag to distinguish between sparse array and dense array and adds serialization and deserialization for sparse array. Bug: v8:11525 Change-Id: Ia302af017d6f52924256a2b3fa9e3d2b34caa442 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3629739 Commit-Queue: 王澳 <wangao.james@bytedance.com> Reviewed-by: Marja Hölttä <marja@chromium.org> Cr-Commit-Position: refs/heads/main@{#80449}
This commit is contained in:
parent
1c94e7a94c
commit
abe1b97dd2
@ -946,23 +946,46 @@ void WebSnapshotSerializer::DiscoverArray(Handle<JSArray> array) {
|
||||
arrays_ = ArrayList::Add(isolate_, arrays_, array);
|
||||
|
||||
auto elements_kind = array->GetElementsKind();
|
||||
if (elements_kind != PACKED_SMI_ELEMENTS &&
|
||||
elements_kind != PACKED_ELEMENTS &&
|
||||
elements_kind != PACKED_SEALED_ELEMENTS &&
|
||||
elements_kind != PACKED_FROZEN_ELEMENTS &&
|
||||
elements_kind != HOLEY_SMI_ELEMENTS && elements_kind != HOLEY_ELEMENTS) {
|
||||
Throw("Unsupported array");
|
||||
return;
|
||||
}
|
||||
|
||||
DisallowGarbageCollection no_gc;
|
||||
|
||||
// TODO(v8:11525): Support sparse arrays & arrays with holes.
|
||||
// TODO(v8:11525): Handle sealed & frozen elements correctly. (Also: handle
|
||||
// sealed & frozen objects.)
|
||||
DisallowGarbageCollection no_gc;
|
||||
FixedArray elements = FixedArray::cast(array->elements());
|
||||
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_));
|
||||
switch (elements_kind) {
|
||||
case PACKED_SMI_ELEMENTS:
|
||||
case PACKED_ELEMENTS:
|
||||
case PACKED_SEALED_ELEMENTS:
|
||||
case PACKED_FROZEN_ELEMENTS:
|
||||
case HOLEY_SMI_ELEMENTS:
|
||||
case HOLEY_ELEMENTS: {
|
||||
FixedArray elements = FixedArray::cast(array->elements());
|
||||
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_));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DICTIONARY_ELEMENTS: {
|
||||
Handle<NumberDictionary> dict(array->element_dictionary(), isolate_);
|
||||
ReadOnlyRoots roots(isolate_);
|
||||
for (InternalIndex index : dict->IterateEntries()) {
|
||||
Handle<Object> key = handle(dict->KeyAt(index), isolate_);
|
||||
if (!dict->IsKey(roots, *key)) {
|
||||
continue;
|
||||
}
|
||||
DCHECK(key->IsNumber());
|
||||
Handle<Object> object = handle(dict->ValueAt(index), isolate_);
|
||||
if (!object->IsHeapObject()) continue;
|
||||
discovery_queue_.push(Handle<HeapObject>::cast(object));
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Throw("Unsupported array");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1297,17 +1320,24 @@ void WebSnapshotSerializer::SerializeObject(Handle<JSObject> object) {
|
||||
}
|
||||
|
||||
// Format (serialized array):
|
||||
// - Array Type (dense or sparse)
|
||||
// - Length
|
||||
// - For each element:
|
||||
// - Serialized value
|
||||
// If dense array
|
||||
// - For each element:
|
||||
// - Serialized value
|
||||
// If sparse array
|
||||
// - For each element:
|
||||
// - Element index
|
||||
// - Serialized value
|
||||
void WebSnapshotSerializer::SerializeArray(Handle<JSArray> array) {
|
||||
auto elements_kind = array->GetElementsKind();
|
||||
// TODO(v8:11525): Support sparse arrays and double arrays.
|
||||
// TODO(v8:11525): Support double arrays.
|
||||
switch (elements_kind) {
|
||||
case PACKED_SMI_ELEMENTS:
|
||||
case PACKED_ELEMENTS:
|
||||
case HOLEY_SMI_ELEMENTS:
|
||||
case HOLEY_ELEMENTS: {
|
||||
array_serializer_.WriteUint32(ArrayType::kDense);
|
||||
uint32_t length = static_cast<uint32_t>(array->length().ToSmi().value());
|
||||
array_serializer_.WriteUint32(length);
|
||||
Handle<FixedArray> elements =
|
||||
@ -1317,6 +1347,23 @@ void WebSnapshotSerializer::SerializeArray(Handle<JSArray> array) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DICTIONARY_ELEMENTS: {
|
||||
DisallowGarbageCollection no_gc;
|
||||
ReadOnlyRoots roots(isolate_);
|
||||
array_serializer_.WriteUint32(ArrayType::kSparse);
|
||||
Handle<NumberDictionary> dict =
|
||||
handle(array->element_dictionary(), isolate_);
|
||||
array_serializer_.WriteUint32(dict->NumberOfElements());
|
||||
for (InternalIndex index : dict->IterateEntries()) {
|
||||
Handle<Object> key = handle(dict->KeyAt(index), isolate_);
|
||||
if (!dict->IsKey(roots, *key)) continue;
|
||||
DCHECK(key->IsNumber());
|
||||
uint32_t element_index = static_cast<uint32_t>(key->Number());
|
||||
array_serializer_.WriteUint32(element_index);
|
||||
WriteValue(handle(dict->ValueAt(index), isolate_), array_serializer_);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Throw("Unsupported array");
|
||||
return;
|
||||
@ -2571,6 +2618,76 @@ void WebSnapshotDeserializer::DeserializeObjects() {
|
||||
}
|
||||
}
|
||||
|
||||
WebSnapshotDeserializer::ArrayType WebSnapshotDeserializer::ReadArrayType() {
|
||||
uint32_t array_type;
|
||||
if (!deserializer_.ReadUint32(&array_type)) {
|
||||
Throw("Malformed array type");
|
||||
return ArrayType::kDense;
|
||||
}
|
||||
if (array_type != ArrayType::kDense && array_type != ArrayType::kSparse) {
|
||||
Throw("Unknown array type");
|
||||
return ArrayType::kDense;
|
||||
}
|
||||
return static_cast<ArrayType>(array_type);
|
||||
}
|
||||
|
||||
Handle<JSArray> WebSnapshotDeserializer::ReadDenseArrayElements(
|
||||
uint32_t length) {
|
||||
Handle<FixedArray> elements = factory()->NewFixedArray(length);
|
||||
ElementsKind elements_kind = PACKED_SMI_ELEMENTS;
|
||||
bool has_hole = false;
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
Object value = ReadValue(elements, i);
|
||||
DisallowGarbageCollection no_gc;
|
||||
if (!value.IsSmi()) {
|
||||
elements_kind = PACKED_ELEMENTS;
|
||||
}
|
||||
if (value.IsTheHole()) {
|
||||
has_hole = true;
|
||||
}
|
||||
elements->set(static_cast<int>(i), value);
|
||||
}
|
||||
if (has_hole) {
|
||||
elements_kind =
|
||||
elements_kind == PACKED_ELEMENTS ? HOLEY_ELEMENTS : HOLEY_SMI_ELEMENTS;
|
||||
}
|
||||
return factory()->NewJSArrayWithElements(elements, elements_kind, length);
|
||||
}
|
||||
|
||||
Handle<JSArray> WebSnapshotDeserializer::ReadSparseArrayElements(
|
||||
uint32_t length) {
|
||||
Handle<NumberDictionary> dict = NumberDictionary::New(isolate_, length);
|
||||
uint32_t max_element_index = 0;
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
uint32_t element_index;
|
||||
if (!deserializer_.ReadUint32(&element_index)) {
|
||||
Throw("Malformed element index in sparse array");
|
||||
return isolate_->factory()->NewJSArray(0);
|
||||
}
|
||||
Object value = ReadValue(dict, element_index);
|
||||
Handle<NumberDictionary> new_dict =
|
||||
dict->Set(isolate_, dict, element_index, handle(value, isolate_));
|
||||
// The number dictionary didn't grow, since it was preallocated to be
|
||||
// large enough before.
|
||||
DCHECK_EQ(*new_dict, *dict);
|
||||
USE(new_dict);
|
||||
if (element_index > max_element_index) {
|
||||
max_element_index = element_index;
|
||||
}
|
||||
}
|
||||
Handle<JSArray> array = isolate_->factory()->NewJSArray(0);
|
||||
|
||||
Handle<Object> array_length =
|
||||
isolate_->factory()->NewNumberFromUint(max_element_index + 1);
|
||||
Handle<Map> map =
|
||||
JSObject::GetElementsTransitionMap(array, DICTIONARY_ELEMENTS);
|
||||
DisallowGarbageCollection no_gc;
|
||||
array->set_length(*array_length);
|
||||
array->set_elements(*dict);
|
||||
array->set_map(*map, kReleaseStore);
|
||||
return array;
|
||||
}
|
||||
|
||||
void WebSnapshotDeserializer::DeserializeArrays() {
|
||||
RCS_SCOPE(isolate_, RuntimeCallCounterId::kWebSnapshotDeserialize_Arrays);
|
||||
if (!deserializer_.ReadUint32(&array_count_) ||
|
||||
@ -2583,30 +2700,21 @@ void WebSnapshotDeserializer::DeserializeArrays() {
|
||||
arrays_ = *arrays_handle_;
|
||||
for (; current_array_count_ < array_count_; ++current_array_count_) {
|
||||
uint32_t length;
|
||||
ArrayType array_type = ReadArrayType();
|
||||
if (!deserializer_.ReadUint32(&length) || length > kMaxItemCount) {
|
||||
Throw("Malformed array");
|
||||
return;
|
||||
}
|
||||
Handle<FixedArray> elements = factory()->NewFixedArray(length);
|
||||
ElementsKind elements_kind = PACKED_SMI_ELEMENTS;
|
||||
bool has_hole = false;
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
Object value = ReadValue(elements, i);
|
||||
DisallowGarbageCollection no_gc;
|
||||
if (!value.IsSmi()) {
|
||||
elements_kind = PACKED_ELEMENTS;
|
||||
}
|
||||
if (value.IsTheHole()) {
|
||||
has_hole = true;
|
||||
}
|
||||
elements->set(static_cast<int>(i), value);
|
||||
|
||||
Handle<JSArray> array;
|
||||
// TODO(v8::11525): we need to convert array to dictionary mode if there are
|
||||
// too many elements for a dense array.
|
||||
if (array_type == ArrayType::kDense) {
|
||||
array = ReadDenseArrayElements(length);
|
||||
} else {
|
||||
array = ReadSparseArrayElements(length);
|
||||
}
|
||||
if (has_hole) {
|
||||
elements_kind = elements_kind == PACKED_ELEMENTS ? HOLEY_ELEMENTS
|
||||
: HOLEY_SMI_ELEMENTS;
|
||||
}
|
||||
Handle<JSArray> array =
|
||||
factory()->NewJSArrayWithElements(elements, elements_kind, length);
|
||||
DCHECK(!array->is_null());
|
||||
arrays_.set(static_cast<int>(current_array_count_), *array);
|
||||
}
|
||||
}
|
||||
@ -2990,6 +3098,19 @@ void WebSnapshotDeserializer::ProcessDeferredReferences() {
|
||||
PropertyArray::cast(container).set(index, target);
|
||||
} else if (InstanceTypeChecker::IsContext(instance_type)) {
|
||||
Context::cast(container).set(index, target);
|
||||
} else if (InstanceTypeChecker::IsNumberDictionary(instance_type)) {
|
||||
// NumberDictionary::Set may need create HeapNumber for index.
|
||||
AllowGarbageCollection allow_gc;
|
||||
Handle<NumberDictionary> new_container = NumberDictionary::Set(
|
||||
isolate_, handle(NumberDictionary::cast(container), isolate_), index,
|
||||
handle(target, isolate_));
|
||||
// The number dictionary didn't grow, since it was preallocated to be
|
||||
// large enough in DeserializeArrays.
|
||||
DCHECK_EQ(*new_container, container);
|
||||
USE(new_container);
|
||||
// We also need to reload raw_deferred_references because
|
||||
// NumberDictionary::Set may allocate.
|
||||
raw_deferred_references = *deferred_references_;
|
||||
} else if (InstanceTypeChecker::IsFixedArray(instance_type)) {
|
||||
FixedArray::cast(container).set(index, target);
|
||||
} else if (InstanceTypeChecker::IsJSFunction(instance_type)) {
|
||||
|
@ -67,6 +67,8 @@ class WebSnapshotSerializerDeserializer {
|
||||
kGlobal = 2
|
||||
};
|
||||
|
||||
enum ArrayType : uint8_t { kDense = 0, kSparse = 1 };
|
||||
|
||||
static constexpr uint8_t kMagicNumber[4] = {'+', '+', '+', ';'};
|
||||
|
||||
enum ContextType : uint8_t { FUNCTION, BLOCK };
|
||||
@ -421,6 +423,9 @@ class V8_EXPORT WebSnapshotDeserializer
|
||||
Object ReadBuiltinObjectReference();
|
||||
Object ReadExternalReference();
|
||||
bool ReadMapType();
|
||||
ArrayType ReadArrayType();
|
||||
Handle<JSArray> ReadDenseArrayElements(uint32_t length);
|
||||
Handle<JSArray> ReadSparseArrayElements(uint32_t length);
|
||||
|
||||
void ReadFunctionPrototype(Handle<JSFunction> function);
|
||||
bool SetFunctionPrototype(JSFunction function, JSReceiver prototype);
|
||||
|
@ -70,6 +70,95 @@ d8.file.execute('test/mjsunit/web-snapshot/web-snapshot-helpers.js');
|
||||
assertEquals([1,,"123"], foo);
|
||||
})();
|
||||
|
||||
(function TestDictionaryElementsArray() {
|
||||
function createObjects() {
|
||||
const array = [];
|
||||
// Add a large index to force dictionary elements.
|
||||
array[2 ** 30] = 10;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
array[i * 101] = i;
|
||||
}
|
||||
globalThis.foo = array;
|
||||
}
|
||||
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
|
||||
assertTrue(%HasDictionaryElements(foo));
|
||||
assertEquals(2 ** 30 + 1, foo.length);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
assertEquals(i,foo[i * 101]);
|
||||
}
|
||||
})();
|
||||
|
||||
(function TestDictionaryElementsArrayContainingArray() {
|
||||
function createObjects() {
|
||||
const array = [];
|
||||
// Add a large index to force dictionary elements.
|
||||
array[2 ** 30] = 10;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
array[i * 101] = [i];
|
||||
}
|
||||
globalThis.foo = array;
|
||||
}
|
||||
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
|
||||
assertTrue(%HasDictionaryElements(foo));
|
||||
assertEquals(2 ** 30 + 1, foo.length);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
assertEquals([i],foo[i * 101]);
|
||||
}
|
||||
})();
|
||||
|
||||
(function TestDictionaryElementsArrayContainingObject() {
|
||||
function createObjects() {
|
||||
const array = [];
|
||||
// Add a large index to force dictionary elements.
|
||||
array[2 ** 30] = 10;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
array[i * 101] = {i:i};
|
||||
}
|
||||
globalThis.foo = array;
|
||||
}
|
||||
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
|
||||
assertTrue(%HasDictionaryElements(foo));
|
||||
assertEquals(2 ** 30 + 1, foo.length);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
assertEquals({i:i}, foo[i * 101]);
|
||||
}
|
||||
})();
|
||||
|
||||
(function TestDictionaryElementsArrayContainingFunction() {
|
||||
function createObjects() {
|
||||
const array = [];
|
||||
// Add a large index to force dictionary elements.
|
||||
array[2 ** 30] = 10;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
array[i * 101] = function() { return i; };
|
||||
}
|
||||
globalThis.foo = array;
|
||||
}
|
||||
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
|
||||
assertTrue(%HasDictionaryElements(foo));
|
||||
assertEquals(2 ** 30 + 1, foo.length);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
assertEquals(i, foo[i * 101]());
|
||||
}
|
||||
})();
|
||||
|
||||
(function TestDictionaryElementsArrayContainingString() {
|
||||
function createObjects() {
|
||||
const array = [];
|
||||
// Add a large index to force dictionary elements.
|
||||
array[2 ** 30] = 10;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
array[i * 101] = `${i}`;
|
||||
}
|
||||
globalThis.foo = array;
|
||||
}
|
||||
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
|
||||
assertTrue(%HasDictionaryElements(foo));
|
||||
assertEquals(2 ** 30 + 1, foo.length);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
assertEquals(`${i}`, foo[i * 101]);
|
||||
}
|
||||
})();
|
||||
|
||||
(function TestEmptyArray() {
|
||||
function createObjects() {
|
||||
|
Loading…
Reference in New Issue
Block a user