[web snapshot] Implement deferred references
This allows forward references among objects as well as contexts referencing objects. Bug: v8:11525 Change-Id: I45fd132344c5e0125d8287c668eac444fe1f8802 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2947408 Reviewed-by: Camillo Bruni <cbruni@chromium.org> Commit-Queue: Marja Hölttä <marja@chromium.org> Cr-Commit-Position: refs/heads/master@{#75069}
This commit is contained in:
parent
e3d280ce5d
commit
c1e9da818a
@ -534,6 +534,10 @@ void ArrayList::Set(int index, Object obj, WriteBarrierMode mode) {
|
||||
FixedArray::cast(*this).set(kFirstIndex + index, obj, mode);
|
||||
}
|
||||
|
||||
void ArrayList::Set(int index, Smi value) {
|
||||
DCHECK(Object(value).IsSmi());
|
||||
Set(index, value, SKIP_WRITE_BARRIER);
|
||||
}
|
||||
void ArrayList::Clear(int index, Object undefined) {
|
||||
DCHECK(undefined.IsUndefined());
|
||||
FixedArray::cast(*this).set(kFirstIndex + index, undefined,
|
||||
|
@ -443,6 +443,10 @@ class ArrayList : public TorqueGeneratedArrayList<ArrayList, FixedArray> {
|
||||
Handle<ArrayList> array,
|
||||
Handle<Object> obj1,
|
||||
Handle<Object> obj2);
|
||||
V8_EXPORT_PRIVATE static Handle<ArrayList> Add(Isolate* isolate,
|
||||
Handle<ArrayList> array,
|
||||
Handle<Object> obj1, Smi obj2,
|
||||
Smi obj3);
|
||||
static Handle<ArrayList> New(Isolate* isolate, int size);
|
||||
|
||||
// Returns the number of elements in the list, not the allocated size, which
|
||||
@ -461,6 +465,8 @@ class ArrayList : public TorqueGeneratedArrayList<ArrayList, FixedArray> {
|
||||
inline void Set(int index, Object obj,
|
||||
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
|
||||
|
||||
inline void Set(int index, Smi obj);
|
||||
|
||||
// Set the element at index to undefined. This does not change the Length().
|
||||
inline void Clear(int index, Object undefined);
|
||||
|
||||
|
@ -4018,8 +4018,12 @@ Handle<ArrayList> ArrayList::Add(Isolate* isolate, Handle<ArrayList> array,
|
||||
array = EnsureSpace(isolate, array, length + 1);
|
||||
// Check that GC didn't remove elements from the array.
|
||||
DCHECK_EQ(array->Length(), length);
|
||||
array->Set(length, *obj);
|
||||
array->SetLength(length + 1);
|
||||
{
|
||||
DisallowGarbageCollection no_gc;
|
||||
ArrayList raw_array = *array;
|
||||
raw_array.Set(length, *obj);
|
||||
raw_array.SetLength(length + 1);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
@ -4030,9 +4034,30 @@ Handle<ArrayList> ArrayList::Add(Isolate* isolate, Handle<ArrayList> array,
|
||||
array = EnsureSpace(isolate, array, length + 2);
|
||||
// Check that GC didn't remove elements from the array.
|
||||
DCHECK_EQ(array->Length(), length);
|
||||
array->Set(length, *obj1);
|
||||
array->Set(length + 1, *obj2);
|
||||
array->SetLength(length + 2);
|
||||
{
|
||||
DisallowGarbageCollection no_gc;
|
||||
ArrayList raw_array = *array;
|
||||
raw_array.Set(length, *obj1);
|
||||
raw_array.Set(length + 1, *obj2);
|
||||
raw_array.SetLength(length + 2);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
Handle<ArrayList> ArrayList::Add(Isolate* isolate, Handle<ArrayList> array,
|
||||
Handle<Object> obj1, Smi obj2, Smi obj3) {
|
||||
int length = array->Length();
|
||||
array = EnsureSpace(isolate, array, length + 3);
|
||||
// Check that GC didn't remove elements from the array.
|
||||
DCHECK_EQ(array->Length(), length);
|
||||
{
|
||||
DisallowGarbageCollection no_gc;
|
||||
ArrayList raw_array = *array;
|
||||
raw_array.Set(length, *obj1);
|
||||
raw_array.Set(length + 1, obj2);
|
||||
raw_array.Set(length + 2, obj3);
|
||||
raw_array.SetLength(length + 3);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
|
@ -378,6 +378,7 @@ void WebSnapshotSerializer::SerializeContext(Handle<Context> context,
|
||||
|
||||
void WebSnapshotSerializer::SerializeObject(Handle<JSObject> object,
|
||||
uint32_t& id) {
|
||||
// TODO(v8:11525): Serialize the leaf objects first.
|
||||
DCHECK(!object->IsJSFunction());
|
||||
if (InsertIntoIndexMap(object_ids_, object, id)) {
|
||||
return;
|
||||
@ -539,6 +540,7 @@ bool WebSnapshotDeserializer::UseWebSnapshot(const uint8_t* data,
|
||||
}
|
||||
|
||||
deserializer_.reset(new ValueDeserializer(isolate_, data, buffer_size));
|
||||
deferred_references_ = ArrayList::New(isolate_, 30);
|
||||
DeserializeStrings();
|
||||
DeserializeMaps();
|
||||
DeserializeContexts();
|
||||
@ -642,6 +644,7 @@ void WebSnapshotDeserializer::DeserializeMaps() {
|
||||
Handle<Map> map = isolate_->factory()->NewMap(
|
||||
JS_OBJECT_TYPE, JSObject::kHeaderSize * kTaggedSize, HOLEY_ELEMENTS, 0);
|
||||
map->InitializeDescriptors(isolate_, *descriptors);
|
||||
// TODO(v8:11525): Set 'constructor'.
|
||||
|
||||
maps_->set(i, *map);
|
||||
}
|
||||
@ -710,7 +713,8 @@ void WebSnapshotDeserializer::DeserializeContexts() {
|
||||
|
||||
Handle<Object> value;
|
||||
Representation representation;
|
||||
ReadValue(value, representation);
|
||||
ReadValue(value, representation, context,
|
||||
scope_info->ContextHeaderLength() + variable_index);
|
||||
context->set(scope_info->ContextHeaderLength() + variable_index, *value);
|
||||
}
|
||||
}
|
||||
@ -861,7 +865,7 @@ void WebSnapshotDeserializer::DeserializeObjects() {
|
||||
}
|
||||
STATIC_ASSERT(kMaxItemCount <= FixedArray::kMaxLength);
|
||||
objects_ = isolate_->factory()->NewFixedArray(object_count_);
|
||||
for (size_t object_ix = 0; object_ix < object_count_; ++object_ix) {
|
||||
for (; current_object_count_ < object_count_; ++current_object_count_) {
|
||||
uint32_t map_id;
|
||||
if (!deserializer_->ReadUint32(&map_id) || map_id >= map_count_) {
|
||||
Throw("Web snapshot: Malformed object");
|
||||
@ -871,12 +875,13 @@ void WebSnapshotDeserializer::DeserializeObjects() {
|
||||
Handle<DescriptorArray> descriptors =
|
||||
handle(map->instance_descriptors(kRelaxedLoad), isolate_);
|
||||
int no_properties = map->NumberOfOwnDescriptors();
|
||||
// TODO(v8:11525): In-object properties.
|
||||
Handle<PropertyArray> property_array =
|
||||
isolate_->factory()->NewPropertyArray(no_properties);
|
||||
for (int i = 0; i < no_properties; ++i) {
|
||||
Handle<Object> value;
|
||||
Representation wanted_representation = Representation::None();
|
||||
ReadValue(value, wanted_representation);
|
||||
ReadValue(value, wanted_representation, property_array, i);
|
||||
// Read the representation from the map.
|
||||
PropertyDetails details = descriptors->GetDetails(InternalIndex(i));
|
||||
CHECK_EQ(details.location(), kField);
|
||||
@ -894,8 +899,9 @@ void WebSnapshotDeserializer::DeserializeObjects() {
|
||||
}
|
||||
Handle<JSObject> object = isolate_->factory()->NewJSObjectFromMap(map);
|
||||
object->set_raw_properties_or_hash(*property_array);
|
||||
objects_->set(static_cast<int>(object_ix), *object);
|
||||
objects_->set(static_cast<int>(current_object_count_), *object);
|
||||
}
|
||||
ProcessDeferredReferences();
|
||||
}
|
||||
|
||||
void WebSnapshotDeserializer::DeserializeExports() {
|
||||
@ -909,6 +915,8 @@ void WebSnapshotDeserializer::DeserializeExports() {
|
||||
Handle<String> export_name = ReadString(true);
|
||||
Handle<Object> export_value;
|
||||
Representation representation;
|
||||
// No deferred references should occur at this point, since all objects have
|
||||
// been deserialized.
|
||||
ReadValue(export_value, representation);
|
||||
|
||||
// Check for the correctness of the snapshot (thus far) before producing
|
||||
@ -928,8 +936,10 @@ void WebSnapshotDeserializer::DeserializeExports() {
|
||||
}
|
||||
}
|
||||
|
||||
void WebSnapshotDeserializer::ReadValue(Handle<Object>& value,
|
||||
Representation& representation) {
|
||||
void WebSnapshotDeserializer::ReadValue(
|
||||
Handle<Object>& value, Representation& representation,
|
||||
Handle<Object> object_for_deferred_reference,
|
||||
uint32_t index_for_deferred_reference) {
|
||||
uint32_t value_type;
|
||||
// TODO(v8:11525): Consider adding a ReadByte.
|
||||
if (!deserializer_->ReadUint32(&value_type)) {
|
||||
@ -984,14 +994,22 @@ void WebSnapshotDeserializer::ReadValue(Handle<Object>& value,
|
||||
}
|
||||
case ValueType::OBJECT_ID:
|
||||
uint32_t object_id;
|
||||
if (!deserializer_->ReadUint32(&object_id) ||
|
||||
object_id >= object_count_) {
|
||||
// TODO(v8:11525): Handle circular references + contexts referencing
|
||||
// objects.
|
||||
if (!deserializer_->ReadUint32(&object_id) || object_id > kMaxItemCount) {
|
||||
Throw("Web snapshot: Malformed variable");
|
||||
return;
|
||||
}
|
||||
value = handle(objects_->get(object_id), isolate_);
|
||||
if (object_id < current_object_count_) {
|
||||
value = handle(objects_->get(object_id), isolate_);
|
||||
} else {
|
||||
// The object hasn't been deserialized yet.
|
||||
value = isolate_->factory()->undefined_value();
|
||||
if (object_for_deferred_reference.is_null()) {
|
||||
Throw("Web snapshot: Invalid object reference");
|
||||
return;
|
||||
}
|
||||
AddDeferredReference(object_for_deferred_reference,
|
||||
index_for_deferred_reference, object_id);
|
||||
}
|
||||
representation = Representation::Tagged();
|
||||
break;
|
||||
case ValueType::FUNCTION_ID:
|
||||
@ -1031,5 +1049,41 @@ void WebSnapshotDeserializer::ReadValue(Handle<Object>& value,
|
||||
}
|
||||
}
|
||||
|
||||
void WebSnapshotDeserializer::AddDeferredReference(
|
||||
Handle<Object> container, uint32_t index, uint32_t target_object_index) {
|
||||
deferred_references_ =
|
||||
ArrayList::Add(isolate_, deferred_references_, container,
|
||||
Smi::FromInt(index), Smi::FromInt(target_object_index));
|
||||
}
|
||||
|
||||
void WebSnapshotDeserializer::ProcessDeferredReferences() {
|
||||
DisallowGarbageCollection no_gc;
|
||||
ArrayList raw_deferred_references = *deferred_references_;
|
||||
FixedArray raw_objects = *objects_;
|
||||
// Deferred references is a list of (object, index, target object index)
|
||||
// tuples.
|
||||
for (int i = 0; i < raw_deferred_references.Length() - 2; i += 3) {
|
||||
Object container = raw_deferred_references.Get(i);
|
||||
int index = raw_deferred_references.Get(i + 1).ToSmi().value();
|
||||
int object_index = raw_deferred_references.Get(i + 2).ToSmi().value();
|
||||
if (static_cast<uint32_t>(object_index) >= object_count_) {
|
||||
// Throw can allocate, but it's ok, since we're not using the raw pointers
|
||||
// after that.
|
||||
AllowGarbageCollection allow_gc;
|
||||
|
||||
Throw("Web Snapshots: Invalid object reference");
|
||||
return;
|
||||
}
|
||||
Object object = raw_objects.get(object_index);
|
||||
if (container.IsPropertyArray()) {
|
||||
PropertyArray::cast(container).set(index, object);
|
||||
} else if (container.IsContext()) {
|
||||
Context::cast(container).set(index, object);
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -176,8 +176,14 @@ class V8_EXPORT WebSnapshotDeserializer
|
||||
void DeserializeFunctions();
|
||||
void DeserializeObjects();
|
||||
void DeserializeExports();
|
||||
void ReadValue(Handle<Object>& value, Representation& representation);
|
||||
void ReadValue(
|
||||
Handle<Object>& value, Representation& representation,
|
||||
Handle<Object> object_for_deferred_reference = Handle<Object>(),
|
||||
uint32_t index_for_deferred_reference = 0);
|
||||
|
||||
void AddDeferredReference(Handle<Object> container, uint32_t index,
|
||||
uint32_t target_object_index);
|
||||
void ProcessDeferredReferences();
|
||||
// Not virtual, on purpose (because it doesn't need to be).
|
||||
void Throw(const char* message);
|
||||
|
||||
@ -186,12 +192,14 @@ class V8_EXPORT WebSnapshotDeserializer
|
||||
Handle<FixedArray> contexts_;
|
||||
Handle<FixedArray> functions_;
|
||||
Handle<FixedArray> objects_;
|
||||
Handle<ArrayList> deferred_references_;
|
||||
|
||||
uint32_t string_count_ = 0;
|
||||
uint32_t map_count_ = 0;
|
||||
uint32_t context_count_ = 0;
|
||||
uint32_t function_count_ = 0;
|
||||
uint32_t object_count_ = 0;
|
||||
uint32_t current_object_count_ = 0;
|
||||
|
||||
std::unique_ptr<ValueDeserializer> deserializer_;
|
||||
|
||||
|
@ -187,3 +187,39 @@ function takeAndUseWebSnapshot(createObjects, exports) {
|
||||
assertTrue(re.test('aBc'));
|
||||
assertFalse(re.test('ac'));
|
||||
})();
|
||||
|
||||
(function TestObjectReferencingObject() {
|
||||
function createObjects() {
|
||||
globalThis.foo = {
|
||||
bar: {baz: 11525}
|
||||
};
|
||||
}
|
||||
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
|
||||
assertEquals(11525, foo.bar.baz);
|
||||
})();
|
||||
|
||||
(function TestContextReferencingObject() {
|
||||
function createObjects() {
|
||||
function outer() {
|
||||
let o = {value: 11525};
|
||||
function inner() { return o; }
|
||||
return inner;
|
||||
}
|
||||
globalThis.foo = {
|
||||
func: outer()
|
||||
};
|
||||
}
|
||||
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
|
||||
assertEquals(11525, foo.func().value);
|
||||
})();
|
||||
|
||||
(function TestCircularObjectReference() {
|
||||
function createObjects() {
|
||||
globalThis.foo = {
|
||||
bar: {}
|
||||
};
|
||||
globalThis.foo.bar.circular = globalThis.foo;
|
||||
}
|
||||
const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']);
|
||||
assertSame(foo, foo.bar.circular);
|
||||
})();
|
||||
|
Loading…
Reference in New Issue
Block a user