From dc88076767c19a46669255d44ab4fa440f2293a2 Mon Sep 17 00:00:00 2001 From: Tess Strickland Date: Wed, 14 Apr 2021 08:44:58 +0000 Subject: [PATCH] [vm] Handle WSRs more generally in v8 snapshot profile writing. Instead of special-casing the current fields that may have WeakSerializationReferences, handle WSRs appearing as elements or properties of objects more generally. This removes existing special casing and avoids the need for it in case of new future uses of WSRs. For artificial nodes being added for dropped WeakSerializationReference targets, add them as kArtificial nodes (not kSnapshot) that has the original offset (element) or name (property). The replacement is added as a kSnapshot node that has a negative offset with the same magnitude as the artificial node (element) or ":real_" (property). This simplifies the work done in pkg/vm_snapshot_analysis to use the artificial nodes instead of replacement ones for reassembling hierarchies and the like. This CL also cleans up the old SerializerWritingObjectScope class, both moving it to Serializer::WritingObjectScope and allowing nesting of WritingObjectScopes with the correct semantics. Thanks to this, not only can we recur when under a WritingObjectScope instead of lifting recursion outside of those scopes, but we can also create artificial nodes for non-empty per-code object pools and static call target tables instead of attributing their contents as supposed elements of the Code object being written. TEST=pkg/vm_snapshot_analysis/test/instruction_sizes_test Cq-Include-Trybots: luci.dart.try:pkg-linux-release-try,pkg-mac-release-try,pkg-win-release-try Change-Id: Ib945c5afcd89b1458b8be3559b6eae24048aba2f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/194243 Commit-Queue: Tess Strickland Reviewed-by: Ryan Macnak Reviewed-by: Vyacheslav Egorov --- pkg/vm_snapshot_analysis/lib/v8_profile.dart | 29 +- pkg/vm_snapshot_analysis/test/utils.dart | 19 +- runtime/vm/clustered_snapshot.cc | 616 +++++++++++-------- runtime/vm/clustered_snapshot.h | 196 +++--- runtime/vm/image_snapshot.cc | 14 +- runtime/vm/image_snapshot.h | 6 +- runtime/vm/v8_snapshot_writer.cc | 199 +++--- runtime/vm/v8_snapshot_writer.h | 134 ++-- 8 files changed, 696 insertions(+), 517 deletions(-) diff --git a/pkg/vm_snapshot_analysis/lib/v8_profile.dart b/pkg/vm_snapshot_analysis/lib/v8_profile.dart index 5ef79afa243..cbbe717a474 100644 --- a/pkg/vm_snapshot_analysis/lib/v8_profile.dart +++ b/pkg/vm_snapshot_analysis/lib/v8_profile.dart @@ -88,6 +88,25 @@ class Snapshot { m['strings'], edgesStartIndexForNode); } + + @override + String toString() { + final buffer = StringBuffer(); + buffer + ..write("Node count: ") + ..writeln(nodeCount) + ..write("Edge count: ") + ..writeln(edgeCount); + buffer.write("Nodes:"); + for (final node in nodes) { + buffer + ..writeln() + ..write(node.index) + ..write(': ') + ..writeln(node); + } + return buffer.toString(); + } } /// Meta-information about the serialized snapshot. @@ -228,12 +247,6 @@ class Node { }.toString(); } - /// Returns the target of an outgoing edge with the given name (if any), - /// but first checks for a corresponding artificial edge indicating a dropped - /// object. - Node possiblyDroppedTarget(String edgeName) => - this[':$edgeName'] ?? this[edgeName]; - /// Returns the target of an outgoing edge with the given name (if any). Node operator [](String edgeName) => this .edges @@ -404,7 +417,7 @@ class _ProgramInfoBuilder { ProgramInfoNode createInfoNodeFor(Node node) { switch (node.type) { case 'Code': - var owner = node.possiblyDroppedTarget('owner_'); + var owner = node['owner_']; if (owner.type != 'Type') { final ownerNode = owner.type == 'Null' ? program.stubs : getInfoNodeFor(owner); @@ -428,7 +441,7 @@ class _ProgramInfoBuilder { // Artificial nodes may not have a data_ field. var data = node['data_']; if (data?.type == 'ClosureData') { - owner = data.possiblyDroppedTarget('parent_function_'); + owner = data['parent_function_']; } return makeInfoNode(node.index, name: node.name, diff --git a/pkg/vm_snapshot_analysis/test/utils.dart b/pkg/vm_snapshot_analysis/test/utils.dart index bdc14e773b4..830ca06492a 100644 --- a/pkg/vm_snapshot_analysis/test/utils.dart +++ b/pkg/vm_snapshot_analysis/test/utils.dart @@ -60,17 +60,21 @@ void main(List args) => input.main(args); if (flag != null) '$flag=${snapshot.sizesJson}', ]; - // Compile input.dart to native and output instruction sizes. - final result = await Process.run(dart2native, [ + final args = [ '-o', snapshot.outputBinary, '--packages=$packages', '--extra-gen-snapshot-options=${extraGenSnapshotOptions.join(',')}', mainDart, - ]); + ]; + + // Compile input.dart to native and output instruction sizes. + final result = await Process.run(dart2native, args); expect(result.exitCode, equals(0), reason: ''' -Compilation completed successfully. +Compilation completed with exit code ${result.exitCode}. + +Command line: $dart2native ${args.join(' ')} stdout: ${result.stdout} stderr: ${result.stderr} @@ -86,13 +90,18 @@ stderr: ${result.stderr} }); } +const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES'; + Future withTempDir(Future Function(String dir) f) async { final tempDir = Directory.systemTemp.createTempSync('instruction-sizes-test-'); try { await f(tempDir.path); } finally { - tempDir.deleteSync(recursive: true); + if (!Platform.environment.containsKey(keepTempKey) || + Platform.environment[keepTempKey].isEmpty) { + tempDir.deleteSync(recursive: true); + } } } diff --git a/runtime/vm/clustered_snapshot.cc b/runtime/vm/clustered_snapshot.cc index 12f1e10aaf2..8841d37b3f7 100644 --- a/runtime/vm/clustered_snapshot.cc +++ b/runtime/vm/clustered_snapshot.cc @@ -1045,12 +1045,6 @@ class FunctionDeserializationCluster : public DeserializationCluster { } }; -// If DROPPED_NAME(name) is used for a v8 snapshot profile edge, then -// possiblyDroppedTarget() should be used to retrieve the edge target in -// pkg/vm_snapshot_analysis/v8_profile.dart so the artificial node is found -// instead of its in-snapshot replacement. -#define DROPPED_NAME(name) (":" #name) - #if !defined(DART_PRECOMPILED_RUNTIME) class ClosureDataSerializationCluster : public SerializationCluster { public: @@ -1097,25 +1091,6 @@ class ClosureDataSerializationCluster : public SerializationCluster { } } - // Some closure data objects have their parent functions dropped from the - // snapshot, which makes it is impossible to recover program structure when - // analysing snapshot profile. To facilitate analysis of snapshot profiles - // we include artificial nodes into profile representing such dropped - // parent functions. - void WriteDroppedParentFunctionsIntoProfile(Serializer* s) { - ASSERT(s->profile_writer() != nullptr); - - for (auto data : objects_) { - ObjectPtr parent_function = - WeakSerializationReference::Unwrap(data->untag()->parent_function()); - if (s->CreateArtificialNodeIfNeeded(parent_function)) { - AutoTraceObject(data); - s->AttributePropertyRef(parent_function, DROPPED_NAME(parent_function_), - /*permit_artificial_ref=*/true); - } - } - } - private: GrowableArray objects_; }; @@ -1958,38 +1933,19 @@ class CodeSerializationCluster : public SerializationCluster { if (kind == Snapshot::kFullAOT && FLAG_use_bare_instructions && code->untag()->object_pool_ != ObjectPool::null()) { ObjectPoolPtr pool = code->untag()->object_pool_; - - for (intptr_t i = 0; i < pool->untag()->length_; i++) { - uint8_t bits = pool->untag()->entry_bits()[i]; - if (ObjectPool::TypeBits::decode(bits) == - ObjectPool::EntryType::kTaggedObject) { - s->AttributeElementRef(pool->untag()->data()[i].raw_obj_, i); - } - } + // Non-empty per-code object pools should not be reachable in this mode. + ASSERT(!s->HasRef(pool) || pool == Object::empty_object_pool().ptr()); + s->CreateArtificialNodeIfNeeded(pool); + s->AttributePropertyRef(pool, "object_pool_"); } - if (code->untag()->static_calls_target_table_ != Array::null()) { - array_ = code->untag()->static_calls_target_table_; - intptr_t index = code->untag()->object_pool_ != ObjectPool::null() - ? code->untag()->object_pool_->untag()->length_ - : 0; - for (auto entry : StaticCallsTable(array_)) { - auto kind = Code::KindField::decode( - Smi::Value(entry.Get())); - switch (kind) { - case Code::kCallViaCode: - // Code object in the pool. - continue; - case Code::kPcRelativeTTSCall: - // TTS will be reachable through type object which itself is - // in the pool. - continue; - case Code::kPcRelativeCall: - case Code::kPcRelativeTailCall: - auto destination = entry.Get(); - ASSERT(destination->IsHeapObject() && destination->IsCode()); - s->AttributeElementRef(destination, index++); - } - } + if (kind != Snapshot::kFullJIT && + code->untag()->static_calls_target_table_ != Array::null()) { + auto const table = code->untag()->static_calls_target_table_; + // Non-empty static call target tables shouldn't be reachable in this + // mode. + ASSERT(!s->HasRef(table) || table == Object::empty_array().ptr()); + s->CreateArtificialNodeIfNeeded(table); + s->AttributePropertyRef(table, "static_calls_target_table_"); } } #endif // defined(DART_PRECOMPILER) @@ -1999,6 +1955,15 @@ class CodeSerializationCluster : public SerializationCluster { // for the discarded Code objects. ASSERT(kind == Snapshot::kFullAOT && FLAG_use_bare_instructions && FLAG_dwarf_stack_traces_mode && !FLAG_retain_code_objects); +#if defined(DART_PRECOMPILER) + if (FLAG_write_v8_snapshot_profile_to != nullptr) { + // Keep the owner as a (possibly artificial) node for snapshot analysis. + const auto& owner = code->untag()->owner_; + s->CreateArtificialNodeIfNeeded(owner); + s->AttributePropertyRef(owner, "owner_"); + } +#endif + return; } @@ -2047,25 +2012,6 @@ class CodeSerializationCluster : public SerializationCluster { GrowableArray* objects() { return &objects_; } GrowableArray* deferred_objects() { return &deferred_objects_; } - // Some code objects would have their owners dropped from the snapshot, - // which makes it is impossible to recover program structure when - // analysing snapshot profile. To facilitate analysis of snapshot profiles - // we include artificial nodes into profile representing such dropped - // owners. - void WriteDroppedOwnersIntoProfile(Serializer* s) { - ASSERT(s->profile_writer() != nullptr); - - for (auto code : objects_) { - ObjectPtr owner = - WeakSerializationReference::Unwrap(code->untag()->owner_); - if (s->CreateArtificialNodeIfNeeded(owner) || Code::IsDiscarded(code)) { - AutoTraceObject(code); - s->AttributePropertyRef(owner, DROPPED_NAME(owner_), - /*permit_artificial_ref=*/true); - } - } - } - private: static const char* MakeDisambiguatedCodeName(Serializer* s, CodePtr c) { if (s->profile_writer() == nullptr) { @@ -2417,43 +2363,28 @@ class WeakSerializationReferenceSerializationCluster void Trace(Serializer* s, ObjectPtr object) { ASSERT(s->kind() == Snapshot::kFullAOT); - WeakSerializationReferencePtr weak = - WeakSerializationReference::RawCast(object); - objects_.Add(weak); + objects_.Add(WeakSerializationReference::RawCast(object)); } void RetraceEphemerons(Serializer* s) { for (intptr_t i = 0; i < objects_.length(); i++) { WeakSerializationReferencePtr weak = objects_[i]; - if (!s->HasRef(weak->untag()->target())) { + if (!s->IsReachable(weak->untag()->target())) { s->Push(weak->untag()->replacement()); } } } - intptr_t FinalizeWeak(Serializer* s) { return objects_.length(); } + intptr_t Count(Serializer* s) { return objects_.length(); } void WriteAlloc(Serializer* s) { - s->WriteCid(kWeakSerializationReferenceCid); + UNREACHABLE(); // No WSRs are serialized, and so this cluster is not added. } - void ForwardWeakRefs(Serializer* s) { - Heap* heap = s->heap(); - for (intptr_t i = 0; i < objects_.length(); i++) { - WeakSerializationReferencePtr weak = objects_[i]; - - intptr_t id = heap->GetObjectId(weak->untag()->target()); - if (id == kUnreachableReference) { - id = heap->GetObjectId(weak->untag()->replacement()); - ASSERT(id != kUnreachableReference); - } - ASSERT(IsAllocatedReference(id)); - heap->SetObjectId(weak, id); - } + void WriteFill(Serializer* s) { + UNREACHABLE(); // No WSRs are serialized, and so this cluster is not added. } - void WriteFill(Serializer* s) {} - private: GrowableArray objects_; }; @@ -2748,11 +2679,9 @@ class RODataSerializationCluster for (intptr_t i = 0; i < count; i++) { ObjectPtr object = objects_[i]; s->AssignRef(object); - if (is_string_cluster) { - s->TraceStartWritingObject(type_, object, String::RawCast(object)); - } else { - s->TraceStartWritingObject(type_, object, nullptr); - } + const StringPtr name = + is_string_cluster ? String::RawCast(object) : nullptr; + Serializer::WritingObjectScope scope(s, type_, object, name); uint32_t offset = s->GetDataOffset(object); s->TraceDataOffset(offset); ASSERT(Utils::IsAligned( @@ -2761,7 +2690,6 @@ class RODataSerializationCluster s->WriteUnsigned((offset - running_offset) >> compiler::target::ObjectAlignment::kObjectAlignmentLog2); running_offset = offset; - s->TraceEndWritingObject(); } WriteCanonicalSetLayout(s); } @@ -5050,7 +4978,7 @@ class WeakPropertySerializationCluster : public SerializationCluster { void RetraceEphemerons(Serializer* s) { for (intptr_t i = 0; i < objects_.length(); i++) { WeakPropertyPtr property = objects_[i]; - if (s->HasRef(property->untag()->key())) { + if (s->IsReachable(property->untag()->key())) { s->Push(property->untag()->value()); } } @@ -5650,18 +5578,18 @@ class VMSerializationRoots : public SerializationRoots { if (!should_write_symbols_ && s->profile_writer() != nullptr) { // If writing V8 snapshot profile create an artifical node representing // VM isolate symbol table. - auto symbols_ref = s->AssignArtificialRef(symbols_.ptr()); - const V8SnapshotProfileWriter::ObjectId symbols_snapshot_id( - V8SnapshotProfileWriter::kSnapshot, symbols_ref); + s->AssignArtificialRef(symbols_.ptr()); + const auto& symbols_snapshot_id = s->GetProfileId(symbols_.ptr()); s->profile_writer()->AddRoot(symbols_snapshot_id, "vm_symbols"); - s->profile_writer()->SetObjectTypeAndName(symbols_snapshot_id, "Symbols", - nullptr); + s->profile_writer()->SetObjectType(symbols_snapshot_id, "Symbols"); for (intptr_t i = 0; i < symbols_.Length(); i++) { - const V8SnapshotProfileWriter::ObjectId code_id( - V8SnapshotProfileWriter::kSnapshot, s->RefId(symbols_.At(i))); s->profile_writer()->AttributeReferenceTo( symbols_snapshot_id, - {code_id, V8SnapshotProfileWriter::Reference::kElement, i}); + { + V8SnapshotProfileWriter::Reference::kElement, + {.offset = i}, + }, + s->GetProfileId(symbols_.At(i))); } } } @@ -6156,16 +6084,16 @@ Serializer::~Serializer() { void Serializer::AddBaseObject(ObjectPtr base_object, const char* type, const char* name) { - intptr_t ref = AssignRef(base_object); + AssignRef(base_object); num_base_objects_++; if ((profile_writer_ != nullptr) && (type != nullptr)) { if (name == nullptr) { name = ""; } - profile_writer_->SetObjectTypeAndName( - {V8SnapshotProfileWriter::kSnapshot, ref}, type, name); - profile_writer_->AddRoot({V8SnapshotProfileWriter::kSnapshot, ref}); + const auto& profile_id = GetProfileId(base_object); + profile_writer_->SetObjectTypeAndName(profile_id, type, name); + profile_writer_->AddRoot(profile_id); } } @@ -6184,7 +6112,8 @@ intptr_t Serializer::AssignRef(ObjectPtr object) { } intptr_t Serializer::AssignArtificialRef(ObjectPtr object) { - ASSERT(object.IsHeapObject()); + ASSERT(!object.IsHeapObject() || !object.IsInstructions()); + ASSERT(heap_->GetObjectId(object) == kUnreachableReference); const intptr_t ref = -(next_ref_index_++); ASSERT(IsArtificialReference(ref)); heap_->SetObjectId(object, ref); @@ -6192,120 +6121,248 @@ intptr_t Serializer::AssignArtificialRef(ObjectPtr object) { return ref; } -void Serializer::FlushBytesWrittenToRoot() { -#if defined(DART_PRECOMPILER) - if (profile_writer_ != nullptr) { - ASSERT(object_currently_writing_.id_ == 0); - // All bytes between objects are attributed into root node. - profile_writer_->AttributeBytesTo( - V8SnapshotProfileWriter::ArtificialRootId(), - stream_->Position() - object_currently_writing_.stream_start_); - object_currently_writing_.stream_start_ = stream_->Position(); - } -#endif +void Serializer::FlushProfile() { + if (profile_writer_ == nullptr) return; + const intptr_t bytes = + stream_->Position() - object_currently_writing_.last_stream_position_; + profile_writer_->AttributeBytesTo(object_currently_writing_.id_, bytes); + object_currently_writing_.last_stream_position_ = stream_->Position(); } -void Serializer::TraceStartWritingObject(const char* type, - ObjectPtr obj, - StringPtr name) { +V8SnapshotProfileWriter::ObjectId Serializer::GetProfileId( + ObjectPtr object) const { + // Instructions are handled separately. + ASSERT(!object->IsHeapObject() || !object->IsInstructions()); + intptr_t heap_id = UnsafeRefId(object); + if (IsArtificialReference(heap_id)) { + return {V8SnapshotProfileWriter::kArtificial, -heap_id}; + } + ASSERT(IsAllocatedReference(heap_id)); + return {V8SnapshotProfileWriter::kSnapshot, heap_id}; +} + +void Serializer::AttributeReference( + ObjectPtr object, + const V8SnapshotProfileWriter::Reference& reference) { if (profile_writer_ == nullptr) return; +#if defined(DART_PRECOMPILER) + // Make artificial nodes for dropped targets in WSRs. + if (object->IsHeapObject() && object->IsWeakSerializationReference()) { + const auto& wsr = WeakSerializationReference::RawCast(object); + const auto& target = wsr->untag()->target(); + if (!CreateArtificialNodeIfNeeded(wsr) && HasArtificialRef(target)) { + // The target has artificial information used for snapshot analysis and + // the replacement is part of the snapshot, so write information for both. + const auto& replacement = wsr->untag()->replacement(); + profile_writer_->AttributeDroppedReferenceTo( + object_currently_writing_.id_, reference, GetProfileId(target), + GetProfileId(replacement)); + return; + } + // Either the target of the WSR is strongly referenced or the WSR itself is + // unreachable, in which case it shares an artificial object ID with the + // target due to CreateArtificialNodeIfNeeded, so fall through. + ASSERT(HasRef(target) || HasArtificialRef(wsr)); + } else if (object_currently_writing_.id_.first == + V8SnapshotProfileWriter::kArtificial) { + // We may need to recur when writing members of artificial nodes in + // CreateArtificialNodeIfNeeded. + CreateArtificialNodeIfNeeded(object); + } +#endif + profile_writer_->AttributeReferenceTo(object_currently_writing_.id_, + reference, GetProfileId(object)); +} + +Serializer::WritingObjectScope::WritingObjectScope( + Serializer* serializer, + const V8SnapshotProfileWriter::ObjectId& id, + ObjectPtr object) + : serializer_(serializer), + old_object_(serializer->object_currently_writing_.object_), + old_id_(serializer->object_currently_writing_.id_), + old_cid_(serializer->object_currently_writing_.cid_) { + if (serializer_->profile_writer_ == nullptr) return; + // The ID should correspond to one already added appropriately to the + // profile writer. + ASSERT(serializer_->profile_writer_->HasId(id)); + serializer_->FlushProfile(); + serializer_->object_currently_writing_.object_ = object; + serializer_->object_currently_writing_.id_ = id; + serializer_->object_currently_writing_.cid_ = + object == nullptr ? -1 : object->GetClassIdMayBeSmi(); +} + +Serializer::WritingObjectScope::~WritingObjectScope() { + if (serializer_->profile_writer_ == nullptr) return; + serializer_->FlushProfile(); + serializer_->object_currently_writing_.object_ = old_object_; + serializer_->object_currently_writing_.id_ = old_id_; + serializer_->object_currently_writing_.cid_ = old_cid_; +} + +V8SnapshotProfileWriter::ObjectId Serializer::WritingObjectScope::ReserveId( + Serializer* s, + const char* type, + ObjectPtr obj, + StringPtr name) { const char* name_str = nullptr; if (name != nullptr) { - REUSABLE_STRING_HANDLESCOPE(thread()); + REUSABLE_STRING_HANDLESCOPE(s->thread()); String& str = reused_string_handle.Handle(); str = name; name_str = str.ToCString(); } - - TraceStartWritingObject(type, obj, name_str); + return ReserveId(s, type, obj, name_str); } -void Serializer::TraceStartWritingObject(const char* type, - ObjectPtr obj, - const char* name) { - if (profile_writer_ == nullptr) return; - - intptr_t id = heap_->GetObjectId(obj); - intptr_t cid = obj->GetClassIdMayBeSmi(); - if (IsArtificialReference(id)) { - id = -id; +V8SnapshotProfileWriter::ObjectId Serializer::WritingObjectScope::ReserveId( + Serializer* s, + const char* type, + ObjectPtr obj, + const char* name) { + if (s->profile_writer_ == nullptr) { + return V8SnapshotProfileWriter::kArtificialRootId; } - ASSERT(IsAllocatedReference(id)); - - FlushBytesWrittenToRoot(); - object_currently_writing_.object_ = obj; - object_currently_writing_.id_ = id; - object_currently_writing_.stream_start_ = stream_->Position(); - object_currently_writing_.cid_ = cid; - profile_writer_->SetObjectTypeAndName( - {V8SnapshotProfileWriter::kSnapshot, id}, type, name); -} - -void Serializer::TraceEndWritingObject() { - if (profile_writer_ != nullptr) { - ASSERT(IsAllocatedReference(object_currently_writing_.id_)); - profile_writer_->AttributeBytesTo( - {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_}, - stream_->Position() - object_currently_writing_.stream_start_); - object_currently_writing_ = ProfilingObject(); - object_currently_writing_.stream_start_ = stream_->Position(); + if (name == nullptr) { + // Handle some cases where there are obvious names to assign. + switch (obj->GetClassIdMayBeSmi()) { + case kSmiCid: { + name = OS::SCreate(s->zone(), "%" Pd "", Smi::Value(Smi::RawCast(obj))); + break; + } + case kMintCid: { + name = OS::SCreate(s->zone(), "%" Pd64 "", + Mint::RawCast(obj)->untag()->value_); + break; + } + case kOneByteStringCid: + case kTwoByteStringCid: { + REUSABLE_STRING_HANDLESCOPE(s->thread()); + String& str = reused_string_handle.Handle(); + str = String::RawCast(obj); + name = str.ToCString(); + break; + } + } } + const auto& obj_id = s->GetProfileId(obj); + s->profile_writer_->SetObjectTypeAndName(obj_id, type, name); + return obj_id; } #if !defined(DART_PRECOMPILED_RUNTIME) bool Serializer::CreateArtificialNodeIfNeeded(ObjectPtr obj) { ASSERT(profile_writer() != nullptr); - if (obj->GetClassId() == kWeakSerializationReferenceCid) { - auto wsr = static_cast(obj); - return CreateArtificialNodeIfNeeded(wsr->untag()->target()); - } - - intptr_t id = heap_->GetObjectId(obj); - if (IsAllocatedReference(id)) { - return false; - } + // UnsafeRefId will do lazy reference allocation for WSRs. + intptr_t id = UnsafeRefId(obj); + ASSERT(id != kUnallocatedReference); if (IsArtificialReference(id)) { return true; } + if (obj->IsHeapObject() && obj->IsWeakSerializationReference()) { + // The object ID for the WSR may need lazy resolution. + if (id == kUnallocatedReference) { + id = UnsafeRefId(obj); + } + ASSERT(id != kUnallocatedReference); + // Create an artificial node for an unreachable target at this point, + // whether or not the WSR itself is reachable. + const auto& target = + WeakSerializationReference::RawCast(obj)->untag()->target(); + CreateArtificialNodeIfNeeded(target); + if (id == kUnreachableReference) { + ASSERT(HasArtificialRef(target)); + // We can safely set the WSR's object ID to the target's artificial one, + // as that won't make it look reachable. + heap_->SetObjectId(obj, heap_->GetObjectId(target)); + return true; + } + // The WSR is reachable, so continue to the IsAllocatedReference behavior. + } + if (IsAllocatedReference(id)) { + return false; + } ASSERT_EQUAL(id, kUnreachableReference); id = AssignArtificialRef(obj); + auto property = [](const char* name) -> V8SnapshotProfileWriter::Reference { + return {V8SnapshotProfileWriter::Reference::kProperty, {.name = name}}; + }; + auto element = [](intptr_t index) -> V8SnapshotProfileWriter::Reference { + return {V8SnapshotProfileWriter::Reference::kElement, {.offset = index}}; + }; + const char* type = nullptr; StringPtr name_string = nullptr; const char* name = nullptr; - GrowableArray> links; - switch (obj->GetClassId()) { + GrowableArray> links; + switch (obj->GetClassIdMayBeSmi()) { + // For profiling static call target tables in AOT mode. + case kSmiCid: { + type = "Smi"; + break; + } + // For profiling per-code object pools in bare instructions mode. + case kObjectPoolCid: { + type = "ObjectPool"; + auto const pool = ObjectPool::RawCast(obj); + for (intptr_t i = 0; i < pool->untag()->length_; i++) { + uint8_t bits = pool->untag()->entry_bits()[i]; + if (ObjectPool::TypeBits::decode(bits) == + ObjectPool::EntryType::kTaggedObject) { + auto const elem = pool->untag()->data()[i].raw_obj_; + // Elements should be reachable from the global object pool. + ASSERT(HasRef(elem)); + links.Add({elem, element(i)}); + } + } + break; + } + // For profiling static call target tables in AOT mode. + case kArrayCid: { + type = "Array"; + auto const array = Array::RawCast(obj); + for (intptr_t i = 0, n = Smi::Value(array->untag()->length()); i < n; + i++) { + ObjectPtr elem = array->untag()->data()[i]; + links.Add({elem, element(i)}); + } + break; + } case kFunctionCid: { FunctionPtr func = static_cast(obj); type = "Function"; name = FunctionSerializationCluster::MakeDisambiguatedFunctionName(this, func); - links.Add({func->untag()->owner(), "owner_"}); + links.Add({func->untag()->owner(), property("owner_")}); ObjectPtr data = func->untag()->data(); if (data->GetClassId() == kClosureDataCid) { - links.Add({func->untag()->data(), "data_"}); + links.Add({func->untag()->data(), property("data_")}); } break; } case kClosureDataCid: { auto data = static_cast(obj); type = "ClosureData"; - links.Add({data->untag()->parent_function(), "parent_function_"}); + links.Add( + {data->untag()->parent_function(), property("parent_function_")}); break; } case kClassCid: { ClassPtr cls = static_cast(obj); type = "Class"; name_string = cls->untag()->name(); - links.Add({cls->untag()->library(), "library_"}); + links.Add({cls->untag()->library(), property("library_")}); break; } case kPatchClassCid: { PatchClassPtr patch_cls = static_cast(obj); type = "PatchClass"; - links.Add({patch_cls->untag()->patched_class(), "patched_class_"}); + links.Add( + {patch_cls->untag()->patched_class(), property("patched_class_")}); break; } case kLibraryCid: { @@ -6325,23 +6382,57 @@ bool Serializer::CreateArtificialNodeIfNeeded(ObjectPtr obj) { name = str.ToCString(); } - // CreateArtificialNodeIfNeeded might call TraceStartWritingObject - // and these calls don't nest, so we need to call this outside - // of the tracing scope created below. + Serializer::WritingObjectScope scope(this, type, obj, name); for (const auto& link : links) { - CreateArtificialNodeIfNeeded(link.first); + AttributeReference(link.first, link.second); } - - TraceStartWritingObject(type, obj, name); - for (const auto& link : links) { - AttributePropertyRef(link.first, link.second, - /*permit_artificial_ref=*/true); - } - TraceEndWritingObject(); return true; } #endif // !defined(DART_PRECOMPILED_RUNTIME) +intptr_t Serializer::RefId(ObjectPtr object) const { + auto const id = UnsafeRefId(object); + if (IsAllocatedReference(id)) { + return id; + } + ASSERT(id == kUnreachableReference || IsArtificialReference(id)); + REUSABLE_OBJECT_HANDLESCOPE(thread()); + auto& handle = thread()->ObjectHandle(); + handle = object; + FATAL("Reference to unreachable object %s", handle.ToCString()); +} + +intptr_t Serializer::UnsafeRefId(ObjectPtr object) const { + // The object id weak table holds image offsets for Instructions instead + // of ref indices. + ASSERT(!object->IsHeapObject() || !object->IsInstructions()); + if (!Snapshot::IncludesCode(kind_) && + object->GetClassIdMayBeSmi() == kCodeCid) { + return RefId(Object::null()); + } + auto id = heap_->GetObjectId(object); + if (id != kUnallocatedReference) { + return id; + } + // This is the only case where we may still see unallocated references after + // WriteAlloc is finished. + if (object->IsWeakSerializationReference()) { + // Lazily set the object ID of the WSR to the object which will replace + // it in the snapshot. + auto const wsr = static_cast(object); + // Either the target or the replacement must be allocated, since the + // WSR is reachable. + id = HasRef(wsr->untag()->target()) ? RefId(wsr->untag()->target()) + : RefId(wsr->untag()->replacement()); + heap_->SetObjectId(wsr, id); + return id; + } + REUSABLE_OBJECT_HANDLESCOPE(thread()); + auto& handle = thread()->ObjectHandle(); + handle = object; + FATAL("Reference for object %s is unallocated", handle.ToCString()); +} + const char* Serializer::ReadOnlyObjectType(intptr_t cid) { switch (cid) { case kPcDescriptorsCid: @@ -6606,15 +6697,17 @@ void Serializer::WriteInstructions(InstructionsPtr instr, const intptr_t offset = image_writer_->GetTextOffsetFor(instr, code); #if defined(DART_PRECOMPILER) if (profile_writer_ != nullptr) { - ASSERT(IsAllocatedReference(object_currently_writing_.id_)); + ASSERT(IsAllocatedReference(object_currently_writing_.id_.second)); const auto offset_space = vm_ ? V8SnapshotProfileWriter::kVmText : V8SnapshotProfileWriter::kIsolateText; const V8SnapshotProfileWriter::ObjectId to_object(offset_space, offset); - const V8SnapshotProfileWriter::ObjectId from_object( - V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_); profile_writer_->AttributeReferenceTo( - from_object, {to_object, V8SnapshotProfileWriter::Reference::kProperty, - profile_writer_->EnsureString("")}); + object_currently_writing_.id_, + { + V8SnapshotProfileWriter::Reference::kProperty, + {.name = ""}, + }, + to_object); } if (FLAG_precompiled_mode && FLAG_use_bare_instructions) { @@ -6646,17 +6739,19 @@ void Serializer::WriteInstructions(InstructionsPtr instr, void Serializer::TraceDataOffset(uint32_t offset) { if (profile_writer_ != nullptr) { // ROData cannot be roots. - ASSERT(IsAllocatedReference(object_currently_writing_.id_)); + ASSERT(IsAllocatedReference(object_currently_writing_.id_.second)); auto offset_space = vm_ ? V8SnapshotProfileWriter::kVmData : V8SnapshotProfileWriter::kIsolateData; - V8SnapshotProfileWriter::ObjectId from_object = { - V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_}; V8SnapshotProfileWriter::ObjectId to_object = {offset_space, offset}; // TODO(sjindel): Give this edge a more appropriate type than element // (internal, maybe?). profile_writer_->AttributeReferenceTo( - from_object, - {to_object, V8SnapshotProfileWriter::Reference::kElement, 0}); + object_currently_writing_.id_, + { + V8SnapshotProfileWriter::Reference::kElement, + {.offset = 0}, + }, + to_object); } } @@ -6797,13 +6892,37 @@ static int CompareClusters(SerializationCluster* const* a, } } +#define CID_CLUSTER(Type) \ + reinterpret_cast(clusters_by_cid_[k##Type##Cid]) + ZoneGrowableArray* Serializer::Serialize(SerializationRoots* roots) { + // While object_currently_writing_ is initialized to the artificial root, we + // set up a scope to ensure proper flushing to the profile. + Serializer::WritingObjectScope scope( + this, V8SnapshotProfileWriter::kArtificialRootId); roots->AddBaseObjects(this); NoSafepointScope no_safepoint; roots->PushRoots(this); + // Resolving WeakSerializationReferences and WeakProperties may cause new + // objects to be pushed on the stack, and handling the changes to the stack + // may cause the targets of WeakSerializationReferences and keys of + // WeakProperties to become reachable, so we do this as a fixed point + // computation. Note that reachability is computed monotonically (an object + // can change from not reachable to reachable, but never the reverse), which + // is technically a conservative approximation for WSRs, but doing a strict + // analysis that allows non-motonoic reachability may not halt. + // + // To see this, take a WSR whose replacement causes the target of another WSR + // to become reachable, which then causes the target of the first WSR to + // become reachable, but the only way to reach the target is through the + // target of the second WSR, which was only reachable via the replacement + // the first. + // + // In practice, this case doesn't come up as replacements tend to be either + // null, smis, or singleton objects that do not contain WSRs currently. while (stack_.length() > 0) { // Strong references. while (stack_.length() > 0) { @@ -6812,19 +6931,23 @@ ZoneGrowableArray* Serializer::Serialize(SerializationRoots* roots) { // Ephemeron references. #if defined(DART_PRECOMPILER) - if (auto const cluster = - reinterpret_cast( - clusters_by_cid_[kWeakSerializationReferenceCid])) { + if (auto const cluster = CID_CLUSTER(WeakSerializationReference)) { cluster->RetraceEphemerons(this); } #endif - if (auto const cluster = - reinterpret_cast( - clusters_by_cid_[kWeakPropertyCid])) { + if (auto const cluster = CID_CLUSTER(WeakProperty)) { cluster->RetraceEphemerons(this); } } +#if defined(DART_PRECOMPILER) + if (auto const cluster = CID_CLUSTER(WeakSerializationReference)) { + // Now that we have computed the reachability fixpoint, we remove the + // count of now-reachable WSRs as they are not actually serialized. + num_written_objects_ -= cluster->Count(this); + } +#endif + GrowableArray canonical_clusters; // The order that PostLoad runs matters for some classes because of // assumptions during canonicalization of some classes about what is already @@ -6857,19 +6980,16 @@ ZoneGrowableArray* Serializer::Serialize(SerializationRoots* roots) { clusters.Add(clusters_by_cid_[kCodeCid]); } for (intptr_t cid = 0; cid < num_cids_; cid++) { - if (clusters_by_cid_[cid] != nullptr && cid != kCodeCid) { + // We don't actually have any WSR objects, references to them are replaced + // either with the target or replacement. + if (cid == kWeakSerializationReferenceCid) continue; + // The code serialization cluster is already handled above. + if (cid == kCodeCid) continue; + if (clusters_by_cid_[cid] != nullptr) { clusters.Add(clusters_by_cid_[cid]); } } -#if defined(DART_PRECOMPILER) - if (auto const cluster = - reinterpret_cast( - clusters_by_cid_[kWeakSerializationReferenceCid])) { - num_written_objects_ -= cluster->FinalizeWeak(this); - } -#endif - instructions_table_len_ = PrepareInstructions(); intptr_t num_objects = num_base_objects_ + num_written_objects_; @@ -6913,26 +7033,6 @@ ZoneGrowableArray* Serializer::Serialize(SerializationRoots* roots) { // And recorded them all in [objects_]. ASSERT(objects_->length() == num_objects); -#if defined(DART_PRECOMPILER) - if (auto cluster = - reinterpret_cast( - clusters_by_cid_[kWeakSerializationReferenceCid])) { - cluster->ForwardWeakRefs(this); - } - - // When writing snapshot profile, we want to retain some of the program - // structure information (e.g. information about libraries, classes and - // functions - even if it was dropped when writing snapshot itself). - if (FLAG_write_v8_snapshot_profile_to != nullptr) { - static_cast(clusters_by_cid_[kCodeCid]) - ->WriteDroppedOwnersIntoProfile(this); - if (auto const cluster = static_cast( - clusters_by_cid_[kClosureDataCid])) { - cluster->WriteDroppedParentFunctionsIntoProfile(this); - } - } -#endif - for (SerializationCluster* cluster : canonical_clusters) { cluster->WriteAndMeasureFill(this); #if defined(DEBUG) @@ -6952,9 +7052,6 @@ ZoneGrowableArray* Serializer::Serialize(SerializationRoots* roots) { Write(kSectionMarker); #endif - FlushBytesWrittenToRoot(); - object_currently_writing_.stream_start_ = stream_->Position(); - PrintSnapshotSizes(); heap()->ResetObjectIdTable(); @@ -6989,6 +7086,14 @@ void Serializer::WriteDispatchTable(const Array& entries) { #if defined(DART_PRECOMPILER) if (kind() != Snapshot::kFullAOT) return; + AssignArtificialRef(entries.ptr()); + const auto& dispatch_table_snapshot_id = GetProfileId(entries.ptr()); + if (profile_writer_ != nullptr) { + profile_writer_->AddRoot(dispatch_table_snapshot_id, "dispatch_table"); + profile_writer_->SetObjectType(dispatch_table_snapshot_id, "DispatchTable"); + } + WritingObjectScope scope(this, dispatch_table_snapshot_id); + const intptr_t bytes_before = bytes_written(); const intptr_t table_length = entries.IsNull() ? 0 : entries.Length(); @@ -6999,8 +7104,7 @@ void Serializer::WriteDispatchTable(const Array& entries) { return; } - auto const code_cluster = - reinterpret_cast(clusters_by_cid_[kCodeCid]); + auto const code_cluster = CID_CLUSTER(Code); ASSERT(code_cluster != nullptr); // Reference IDs in a cluster are allocated sequentially, so we can use the // first code object's reference ID to calculate the cluster index. @@ -7079,29 +7183,19 @@ void Serializer::WriteDispatchTable(const Array& entries) { } dispatch_table_size_ = bytes_written() - bytes_before; - object_currently_writing_.stream_start_ = stream_->Position(); - // If any bytes were written for the dispatch table, add it to the profile. - if (dispatch_table_size_ > 0 && profile_writer_ != nullptr) { - // Grab an unused ref index for a unique object id for the dispatch table. - const auto dispatch_table_id = next_ref_index_++; - const V8SnapshotProfileWriter::ObjectId dispatch_table_snapshot_id( - V8SnapshotProfileWriter::kSnapshot, dispatch_table_id); - profile_writer_->AddRoot(dispatch_table_snapshot_id, "dispatch_table"); - profile_writer_->SetObjectTypeAndName(dispatch_table_snapshot_id, - "DispatchTable", nullptr); - profile_writer_->AttributeBytesTo(dispatch_table_snapshot_id, - dispatch_table_size_); - - if (!entries.IsNull()) { - for (intptr_t i = 0; i < entries.Length(); i++) { - auto const code = Code::RawCast(entries.At(i)); - if (code == Code::null()) continue; - const V8SnapshotProfileWriter::ObjectId code_id( - V8SnapshotProfileWriter::kSnapshot, RefId(code)); - profile_writer_->AttributeReferenceTo( - dispatch_table_snapshot_id, - {code_id, V8SnapshotProfileWriter::Reference::kElement, i}); - } + // If any bytes were written for the dispatch table, add the elements of + // the dispatch table in the profile. + if (profile_writer_ != nullptr && !entries.IsNull()) { + for (intptr_t i = 0; i < entries.Length(); i++) { + auto const code = Code::RawCast(entries.At(i)); + if (code == Code::null()) continue; + profile_writer_->AttributeReferenceTo( + dispatch_table_snapshot_id, + { + V8SnapshotProfileWriter::Reference::kElement, + {.offset = i}, + }, + GetProfileId(code)); } } diff --git a/runtime/vm/clustered_snapshot.h b/runtime/vm/clustered_snapshot.h index cbb34de4c75..ebaa3a438f5 100644 --- a/runtime/vm/clustered_snapshot.h +++ b/runtime/vm/clustered_snapshot.h @@ -251,12 +251,52 @@ class Serializer : public ThreadStackResource { intptr_t bytes_written() { return stream_->bytes_written(); } intptr_t bytes_heap_allocated() { return bytes_heap_allocated_; } - void FlushBytesWrittenToRoot(); - void TraceStartWritingObject(const char* type, ObjectPtr obj, StringPtr name); - void TraceStartWritingObject(const char* type, - ObjectPtr obj, - const char* name); - void TraceEndWritingObject(); + class WritingObjectScope : ValueObject { + public: + WritingObjectScope(Serializer* serializer, + const char* type, + ObjectPtr object, + StringPtr name) + : WritingObjectScope(serializer, + ReserveId(serializer, type, object, name), + object) {} + + WritingObjectScope(Serializer* serializer, + const char* type, + ObjectPtr object, + const char* name) + : WritingObjectScope(serializer, + ReserveId(serializer, type, object, name), + object) {} + + WritingObjectScope(Serializer* serializer, + const V8SnapshotProfileWriter::ObjectId& id, + ObjectPtr object = nullptr); + + WritingObjectScope(Serializer* serializer, ObjectPtr object) + : WritingObjectScope(serializer, + serializer->GetProfileId(object), + object) {} + + ~WritingObjectScope(); + + private: + static V8SnapshotProfileWriter::ObjectId ReserveId(Serializer* serializer, + const char* type, + ObjectPtr object, + StringPtr name); + + static V8SnapshotProfileWriter::ObjectId ReserveId(Serializer* serializer, + const char* type, + ObjectPtr object, + const char* name); + + private: + Serializer* const serializer_; + const ObjectPtr old_object_; + const V8SnapshotProfileWriter::ObjectId old_id_; + const classid_t old_cid_; + }; // Writes raw data to the stream (basic type). // sizeof(T) must be in {1,2,4,8}. @@ -276,72 +316,50 @@ class Serializer : public ThreadStackResource { } void Align(intptr_t alignment) { stream_->Align(alignment); } + V8SnapshotProfileWriter::ObjectId GetProfileId(ObjectPtr object) const; + void WriteRootRef(ObjectPtr object, const char* name = nullptr) { intptr_t id = RefId(object); WriteUnsigned(id); if (profile_writer_ != nullptr) { - profile_writer_->AddRoot({V8SnapshotProfileWriter::kSnapshot, id}, name); + profile_writer_->AddRoot(GetProfileId(object), name); } } + // Record a reference from the currently written object to the given object + // and return reference id for the given object. + void AttributeReference(ObjectPtr object, + const V8SnapshotProfileWriter::Reference& reference); + + void AttributeElementRef(ObjectPtr object, intptr_t index) { + AttributeReference(object, {V8SnapshotProfileWriter::Reference::kElement, + {.offset = index}}); + } + void WriteElementRef(ObjectPtr object, intptr_t index) { - WriteUnsigned(AttributeElementRef(object, index)); + AttributeElementRef(object, index); + WriteUnsigned(RefId(object)); } - // Record a reference from the currently written object to the given object - // and return reference id for the given object. - intptr_t AttributeElementRef(ObjectPtr object, - intptr_t index, - bool permit_artificial_ref = false) { - intptr_t id = RefId(object, permit_artificial_ref); - if (profile_writer_ != nullptr) { - profile_writer_->AttributeReferenceTo( - {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_}, - {{V8SnapshotProfileWriter::kSnapshot, id}, - V8SnapshotProfileWriter::Reference::kElement, - index}); - } - return id; + void AttributePropertyRef(ObjectPtr object, const char* property) { + AttributeReference(object, {V8SnapshotProfileWriter::Reference::kProperty, + {.name = property}}); } void WritePropertyRef(ObjectPtr object, const char* property) { - WriteUnsigned(AttributePropertyRef(object, property)); - } - - // Record a reference from the currently written object to the given object - // and return reference id for the given object. - intptr_t AttributePropertyRef(ObjectPtr object, - const char* property, - bool permit_artificial_ref = false) { - intptr_t id = RefId(object, permit_artificial_ref); - if (profile_writer_ != nullptr) { - profile_writer_->AttributeReferenceTo( - {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_}, - {{V8SnapshotProfileWriter::kSnapshot, id}, - V8SnapshotProfileWriter::Reference::kProperty, - profile_writer_->EnsureString(property)}); - } - return id; + AttributePropertyRef(object, property); + WriteUnsigned(RefId(object)); } void WriteOffsetRef(ObjectPtr object, intptr_t offset) { intptr_t id = RefId(object); WriteUnsigned(id); if (profile_writer_ != nullptr) { - const char* property = offsets_table_->FieldNameForOffset( - object_currently_writing_.cid_, offset); - if (property != nullptr) { - profile_writer_->AttributeReferenceTo( - {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_}, - {{V8SnapshotProfileWriter::kSnapshot, id}, - V8SnapshotProfileWriter::Reference::kProperty, - profile_writer_->EnsureString(property)}); + if (auto const property = offsets_table_->FieldNameForOffset( + object_currently_writing_.cid_, offset)) { + AttributePropertyRef(object, property); } else { - profile_writer_->AttributeReferenceTo( - {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_}, - {{V8SnapshotProfileWriter::kSnapshot, id}, - V8SnapshotProfileWriter::Reference::kElement, - offset}); + AttributeElementRef(object, offset); } } } @@ -417,26 +435,29 @@ class Serializer : public ThreadStackResource { // Returns the reference ID for the object. Fails for objects that have not // been allocated a reference ID yet, so should be used only after all // WriteAlloc calls. - intptr_t RefId(ObjectPtr object, bool permit_artificial_ref = false) { - // The object id weak table holds image offsets for Instructions instead - // of ref indices. - ASSERT(!object->IsHeapObject() || !object->IsInstructions()); - auto const id = heap_->GetObjectId(object); - if (permit_artificial_ref && IsArtificialReference(id)) { - return -id; - } - ASSERT(!IsArtificialReference(id)); - if (IsAllocatedReference(id)) { - return id; - } - if (object->IsCode() && !Snapshot::IncludesCode(kind_)) { - return RefId(Object::null()); - } - FATAL("Missing ref"); - } + intptr_t RefId(ObjectPtr object) const; + // Same as RefId, but allows artificial and unreachable references. Still + // fails for unallocated references. + intptr_t UnsafeRefId(ObjectPtr object) const; + + // Whether the object is reachable. + bool IsReachable(ObjectPtr object) const { + return IsReachableReference(heap_->GetObjectId(object)); + } + // Whether the object has an allocated reference. bool HasRef(ObjectPtr object) const { - return heap_->GetObjectId(object) != kUnreachableReference; + return IsAllocatedReference(heap_->GetObjectId(object)); + } + // Whether the object only appears in the V8 snapshot profile. + bool HasArtificialRef(ObjectPtr object) const { + return IsArtificialReference(heap_->GetObjectId(object)); + } + // Whether a node for the object already has been added to the V8 snapshot + // profile. + bool HasProfileNode(ObjectPtr object) const { + ASSERT(profile_writer_ != nullptr); + return profile_writer_->HasId(GetProfileId(object)); } bool IsWritten(ObjectPtr object) const { return heap_->GetObjectId(object) > num_base_objects_; @@ -444,6 +465,7 @@ class Serializer : public ThreadStackResource { private: const char* ReadOnlyObjectType(intptr_t cid); + void FlushProfile(); Heap* heap_; Zone* zone_; @@ -471,8 +493,11 @@ class Serializer : public ThreadStackResource { V8SnapshotProfileWriter* profile_writer_ = nullptr; struct ProfilingObject { ObjectPtr object_ = nullptr; - intptr_t id_ = 0; - intptr_t stream_start_ = 0; + // Unless within a WritingObjectScope, any bytes written are attributed to + // the artificial root. + V8SnapshotProfileWriter::ObjectId id_ = + V8SnapshotProfileWriter::kArtificialRootId; + intptr_t last_stream_position_ = 0; intptr_t cid_ = -1; } object_currently_writing_; OffsetsTable* offsets_table_ = nullptr; @@ -494,10 +519,10 @@ class Serializer : public ThreadStackResource { }; #define AutoTraceObject(obj) \ - SerializerWritingObjectScope scope_##__COUNTER__(s, name(), obj, nullptr) + Serializer::WritingObjectScope scope_##__COUNTER__(s, name(), obj, nullptr) #define AutoTraceObjectName(obj, str) \ - SerializerWritingObjectScope scope_##__COUNTER__(s, name(), obj, str) + Serializer::WritingObjectScope scope_##__COUNTER__(s, name(), obj, str) #define WriteFieldValue(field, value) s->WritePropertyRef(value, #field); @@ -509,29 +534,6 @@ class Serializer : public ThreadStackResource { #define WriteCompressedField(obj, name) \ s->WritePropertyRef(obj->untag()->name(), #name "_") -class SerializerWritingObjectScope { - public: - SerializerWritingObjectScope(Serializer* serializer, - const char* type, - ObjectPtr object, - StringPtr name) - : serializer_(serializer) { - serializer_->TraceStartWritingObject(type, object, name); - } - - SerializerWritingObjectScope(Serializer* serializer, - const char* type, - ObjectPtr object, - const char* name) - : serializer_(serializer) { - serializer_->TraceStartWritingObject(type, object, name); - } - - ~SerializerWritingObjectScope() { serializer_->TraceEndWritingObject(); } - - private: - Serializer* serializer_; -}; // This class can be used to read version and features from a snapshot before // the VM has been initialized. diff --git a/runtime/vm/image_snapshot.cc b/runtime/vm/image_snapshot.cc index f20022eb7da..982cb7ad4e0 100644 --- a/runtime/vm/image_snapshot.cc +++ b/runtime/vm/image_snapshot.cc @@ -493,7 +493,7 @@ void ImageWriter::WriteROData(NonStreamingWriteStream* stream, bool vm) { if (profile_writer_ != nullptr) { const intptr_t end_position = stream->Position(); profile_writer_->AttributeBytesTo( - V8SnapshotProfileWriter::ArtificialRootId(), + V8SnapshotProfileWriter::kArtificialRootId, end_position - start_position); } #endif @@ -683,7 +683,11 @@ void ImageWriter::WriteText(bool vm) { const intptr_t element_offset = id.second - parent_id.second; profile_writer_->AttributeReferenceTo( parent_id, - {id, V8SnapshotProfileWriter::Reference::kElement, element_offset}); + { + V8SnapshotProfileWriter::Reference::kElement, + {.offset = element_offset}, + }, + id); // Later objects will have the InstructionsSection as a parent if in // bare instructions mode, otherwise the image. if (bare_instruction_payloads) { @@ -747,7 +751,11 @@ void ImageWriter::WriteText(bool vm) { const intptr_t element_offset = id.second - parent_id.second; profile_writer_->AttributeReferenceTo( parent_id, - {id, V8SnapshotProfileWriter::Reference::kElement, element_offset}); + { + V8SnapshotProfileWriter::Reference::kElement, + {.offset = element_offset}, + }, + id); } #endif diff --git a/runtime/vm/image_snapshot.h b/runtime/vm/image_snapshot.h index a11356fb752..84040d44b93 100644 --- a/runtime/vm/image_snapshot.h +++ b/runtime/vm/image_snapshot.h @@ -472,13 +472,14 @@ class TraceImageObjectScope : ValueObject { stream_(ASSERT_NOTNULL(stream)), section_offset_(section_offset), start_offset_(stream_->Position() - section_offset), - object_type_(writer->ObjectTypeForProfile(object)) {} + object_type_(writer->ObjectTypeForProfile(object)), + object_name_(object.IsString() ? object.ToCString() : nullptr) {} ~TraceImageObjectScope() { if (writer_->profile_writer_ == nullptr) return; ASSERT(writer_->IsROSpace()); writer_->profile_writer_->SetObjectTypeAndName( - {writer_->offset_space_, start_offset_}, object_type_, nullptr); + {writer_->offset_space_, start_offset_}, object_type_, object_name_); writer_->profile_writer_->AttributeBytesTo( {writer_->offset_space_, start_offset_}, stream_->Position() - section_offset_ - start_offset_); @@ -490,6 +491,7 @@ class TraceImageObjectScope : ValueObject { const intptr_t section_offset_; const intptr_t start_offset_; const char* const object_type_; + const char* const object_name_; DISALLOW_COPY_AND_ASSIGN(TraceImageObjectScope); }; diff --git a/runtime/vm/v8_snapshot_writer.cc b/runtime/vm/v8_snapshot_writer.cc index 4f4c1c12de2..c828b4f9c2d 100644 --- a/runtime/vm/v8_snapshot_writer.cc +++ b/runtime/vm/v8_snapshot_writer.cc @@ -2,8 +2,6 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -#if defined(DART_PRECOMPILER) - #include "vm/v8_snapshot_writer.h" #include "vm/dart.h" @@ -11,18 +9,20 @@ namespace dart { -const char* ZoneString(Zone* Z, const char* str) { - const intptr_t len = strlen(str) + 1; - char* dest = Z->Alloc(len); - snprintf(dest, len, "%s", str); - return dest; +const V8SnapshotProfileWriter::ObjectId + V8SnapshotProfileWriter::kArtificialRootId{kArtificial, 0}; + +#if defined(DART_PRECOMPILER) + +static const char* ZoneString(Zone* Z, const char* str) { + return OS::SCreate(Z, "%s", str); } V8SnapshotProfileWriter::V8SnapshotProfileWriter(Zone* zone) : zone_(zone), node_types_(zone_), edge_types_(zone_), - strings_(zone), + strings_(zone_), roots_(zone_) { node_types_.Insert({"Unknown", kUnknown}); node_types_.Insert({"ArtificialRoot", kArtificialRoot}); @@ -35,28 +35,21 @@ V8SnapshotProfileWriter::V8SnapshotProfileWriter(Zone* zone) strings_.Insert({"", kUnknownString}); strings_.Insert({"", kArtificialRootString}); - nodes_.Insert({ArtificialRootId(), - { - kArtificialRoot, - kArtificialRootString, - ArtificialRootId(), - 0, - nullptr, - 0, - }}); + nodes_.Insert(NodeInfo(zone_, kArtificialRoot, kArtificialRootString, + kArtificialRootId, 0, 0)); } void V8SnapshotProfileWriter::SetObjectTypeAndName(ObjectId object_id, const char* type, const char* name) { ASSERT(type != nullptr); - NodeInfo* info = EnsureId(object_id); if (!node_types_.HasKey(type)) { node_types_.Insert({ZoneString(zone_, type), node_types_.Size()}); } intptr_t type_id = node_types_.LookupValue(type); + NodeInfo* info = EnsureId(object_id); ASSERT(info->type == kUnknown || info->type == type_id); info->type = type_id; if (name != nullptr) { @@ -72,46 +65,66 @@ void V8SnapshotProfileWriter::AttributeBytesTo(ObjectId object_id, EnsureId(object_id)->self_size += num_bytes; } -void V8SnapshotProfileWriter::AttributeReferenceTo(ObjectId object_id, - Reference reference) { - EnsureId(reference.to_object_id); - NodeInfo* info = EnsureId(object_id); +V8SnapshotProfileWriter::ConstantEdgeType +V8SnapshotProfileWriter::ReferenceTypeToEdgeType(Reference::Type type) { + switch (type) { + case Reference::kElement: + return ConstantEdgeType::kElement; + case Reference::kProperty: + return ConstantEdgeType::kProperty; + } +} - ASSERT(reference.offset_or_name >= 0); - info->edges->Add({ - static_cast(reference.reference_type == Reference::kElement - ? kElement - : kProperty), - reference.offset_or_name, - reference.to_object_id, - }); +void V8SnapshotProfileWriter::AttributeReferenceTo(ObjectId from_object_id, + Reference reference, + ObjectId to_object_id) { + const bool is_element = reference.reference_type == Reference::kElement; + ASSERT(is_element ? reference.offset >= 0 : reference.name != nullptr); + + EnsureId(to_object_id); + const Edge edge(ReferenceTypeToEdgeType(reference.reference_type), + is_element ? reference.offset : EnsureString(reference.name)); + EnsureId(from_object_id)->AddEdge(edge, to_object_id); ++edge_count_; } -V8SnapshotProfileWriter::NodeInfo V8SnapshotProfileWriter::DefaultNode( - ObjectId object_id) { - return { - kUnknown, - kUnknownString, - object_id, - 0, - new (zone_) ZoneGrowableArray(zone_, 0), - -1, - }; +void V8SnapshotProfileWriter::AttributeDroppedReferenceTo( + ObjectId from_object_id, + Reference reference, + ObjectId to_object_id, + ObjectId replacement_object_id) { + ASSERT(to_object_id.first == kArtificial); + ASSERT(replacement_object_id.first != kArtificial); + + const bool is_element = reference.reference_type == Reference::kElement; + ASSERT(is_element ? reference.offset >= 0 : reference.name != nullptr); + + // The target node is added normally. + AttributeReferenceTo(from_object_id, reference, to_object_id); + + // Put the replacement node at an invalid offset or name that can still be + // associated with the real one. For offsets, this is the negative offset. + // For names, it's the name prefixed with ":replacement_". + EnsureId(replacement_object_id); + const Edge replacement_edge( + ReferenceTypeToEdgeType(reference.reference_type), + is_element ? -reference.offset + : EnsureString( + OS::SCreate(zone_, ":replacement_%s", reference.name))); + EnsureId(from_object_id)->AddEdge(replacement_edge, replacement_object_id); + ++edge_count_; } -const V8SnapshotProfileWriter::NodeInfo& -V8SnapshotProfileWriter::ArtificialRoot() { - return nodes_.Lookup(ArtificialRootId())->value; +bool V8SnapshotProfileWriter::HasId(const ObjectId& object_id) { + return nodes_.HasKey(object_id); } V8SnapshotProfileWriter::NodeInfo* V8SnapshotProfileWriter::EnsureId( ObjectId object_id) { - if (!nodes_.HasKey(object_id)) { - NodeInfo info = DefaultNode(object_id); - nodes_.Insert({object_id, info}); + if (!HasId(object_id)) { + nodes_.Insert(NodeInfo(zone_, kUnknown, kUnknownString, object_id, 0, -1)); } - return &nodes_.Lookup(object_id)->value; + return nodes_.Lookup(object_id); } intptr_t V8SnapshotProfileWriter::EnsureString(const char* str) { @@ -122,24 +135,23 @@ intptr_t V8SnapshotProfileWriter::EnsureString(const char* str) { return strings_.LookupValue(str); } -void V8SnapshotProfileWriter::WriteNodeInfo(JSONWriter* writer, - const NodeInfo& info) { +intptr_t V8SnapshotProfileWriter::WriteNodeInfo(JSONWriter* writer, + const NodeInfo& info) { writer->PrintValue(info.type); writer->PrintValue(info.name); writer->PrintValue(NodeIdFor(info.id)); writer->PrintValue(info.self_size); - // The artificial root has 'nullptr' edges, it actually points to all the - // roots. - writer->PrintValue64(info.edges != nullptr ? info.edges->length() - : roots_.Size()); + writer->PrintValue64(info.edges->Length()); writer->PrintNewline(); + return kNumNodeFields; } void V8SnapshotProfileWriter::WriteEdgeInfo(JSONWriter* writer, - const EdgeInfo& info) { - writer->PrintValue64(info.type); - writer->PrintValue64(info.name_or_index); - writer->PrintValue64(nodes_.LookupValue(info.to_node).offset); + const Edge& info, + const ObjectId& target) { + writer->PrintValue64(info.first); + writer->PrintValue64(info.second); + writer->PrintValue64(nodes_.LookupValue(target).offset); writer->PrintNewline(); } @@ -151,11 +163,13 @@ void V8SnapshotProfileWriter::AddRoot(ObjectId object_id, // (most likely an oversight). if (roots_.HasKey(object_id)) return; - ObjectIdToNodeInfoTraits::Pair pair; - pair.key = object_id; - pair.value = NodeInfo{ - 0, name != nullptr ? EnsureString(name) : -1, object_id, 0, nullptr, 0}; - roots_.Insert(pair); + auto const info = NodeInfo( + zone_, 0, name != nullptr ? EnsureString(name) : -1, object_id, 0, 0); + roots_.Insert(info); + auto const root = EnsureId(kArtificialRootId); + root->AddEdge(info.name != -1 ? Edge(kProperty, info.name) + : Edge(kInternal, root->edges->Length()), + object_id); } void V8SnapshotProfileWriter::WriteStringsTable( @@ -226,50 +240,37 @@ void V8SnapshotProfileWriter::Write(JSONWriter* writer) { } writer->CloseObject(); + const auto& root = *nodes_.Lookup(kArtificialRootId); + auto nodes_it = nodes_.GetIterator(); + { writer->OpenArray("nodes"); - // Write the artificial root node. - WriteNodeInfo(writer, ArtificialRoot()); - intptr_t offset = kNumNodeFields; - ObjectIdToNodeInfoTraits::Pair* entry = nullptr; - auto it = nodes_.GetIterator(); - while ((entry = it.Next()) != nullptr) { - ASSERT(entry->key == entry->value.id); - if (entry->value.id == ArtificialRootId()) { - continue; // Written separately above. - } - entry->value.offset = offset; - WriteNodeInfo(writer, entry->value); - offset += kNumNodeFields; + // Always write the information for the artificial root first. + intptr_t offset = WriteNodeInfo(writer, root); + for (auto entry = nodes_it.Next(); entry != nullptr; + entry = nodes_it.Next()) { + if (entry->id == kArtificialRootId) continue; + entry->offset = offset; + offset += WriteNodeInfo(writer, *entry); } writer->CloseArray(); + nodes_it.Reset(); } { + auto write_edges = [&](const NodeInfo& info) { + auto edges_it = info.edges->GetIterator(); + while (auto const pair = edges_it.Next()) { + WriteEdgeInfo(writer, pair->edge, pair->target); + } + }; writer->OpenArray("edges"); - - // Write references from the artificial root to the actual roots. - ObjectIdToNodeInfoTraits::Pair* entry = nullptr; - auto roots_it = roots_.GetIterator(); - for (int i = 0; (entry = roots_it.Next()) != nullptr; ++i) { - if (entry->value.name != -1) { - WriteEdgeInfo(writer, {kProperty, entry->value.name, entry->key}); - } else { - WriteEdgeInfo(writer, {kInternal, i, entry->key}); - } + // Always write the information for the artificial root first. + write_edges(root); + while (auto const entry = nodes_it.Next()) { + if (entry->id == kArtificialRootId) continue; + write_edges(*entry); } - - auto nodes_it = nodes_.GetIterator(); - while ((entry = nodes_it.Next()) != nullptr) { - if (entry->value.edges == nullptr) { - continue; // Artificial root, its edges are written separately above. - } - - for (intptr_t i = 0; i < entry->value.edges->length(); ++i) { - WriteEdgeInfo(writer, entry->value.edges->At(i)); - } - } - writer->CloseArray(); } @@ -308,6 +309,6 @@ void V8SnapshotProfileWriter::Write(const char* filename) { } } -} // namespace dart - #endif + +} // namespace dart diff --git a/runtime/vm/v8_snapshot_writer.h b/runtime/vm/v8_snapshot_writer.h index 34486ffc22d..3fc3e6183a6 100644 --- a/runtime/vm/v8_snapshot_writer.h +++ b/runtime/vm/v8_snapshot_writer.h @@ -51,12 +51,14 @@ class V8SnapshotProfileWriter : public ZoneAllocated { typedef std::pair ObjectId; struct Reference { - ObjectId to_object_id; - enum { + enum Type { kElement, kProperty, } reference_type; - intptr_t offset_or_name; + union { + intptr_t offset; // kElement + const char* name; // kProperty + }; }; enum ConstantStrings { @@ -64,21 +66,35 @@ class V8SnapshotProfileWriter : public ZoneAllocated { kArtificialRootString = 1, }; + static const ObjectId kArtificialRootId; + #if !defined(DART_PRECOMPILER) explicit V8SnapshotProfileWriter(Zone* zone) {} virtual ~V8SnapshotProfileWriter() {} + void SetObjectType(ObjectId object_id, const char* type) {} void SetObjectTypeAndName(ObjectId object_id, const char* type, const char* name) {} void AttributeBytesTo(ObjectId object_id, size_t num_bytes) {} - void AttributeReferenceTo(ObjectId object_id, Reference reference) {} + void AttributeReferenceTo(ObjectId from_object_id, + Reference reference, + ObjectId to_object_id) {} + void AttributeWeakReferenceTo( + ObjectId from_object_id, + Reference reference, + ObjectId to_object_id, + ObjectId replacement_object_id = kArtificialRootId) {} void AddRoot(ObjectId object_id, const char* name = nullptr) {} - intptr_t EnsureString(const char* str) { return 0; } + bool HasId(const ObjectId& object_id) { return false; } #else explicit V8SnapshotProfileWriter(Zone* zone); virtual ~V8SnapshotProfileWriter() {} + void SetObjectType(ObjectId object_id, const char* type) { + SetObjectTypeAndName(object_id, type, nullptr); + } + // Records that the object referenced by 'object_id' has type 'type'. The // 'type' for all 'Instance's should be 'Instance', not the user-visible type // and use 'name' for the real type instead. @@ -92,9 +108,22 @@ class V8SnapshotProfileWriter : public ZoneAllocated { void AttributeBytesTo(ObjectId object_id, size_t num_bytes); // Records that a reference to the object with id 'to_object_id' was written - // in order to serialize the object with id 'object_id'. This does not affect - // the number of bytes charged to 'object_id'. - void AttributeReferenceTo(ObjectId object_id, Reference reference); + // in order to serialize the object with id 'from_object_id'. This does not + // affect the number of bytes charged to 'from_object_id'. + void AttributeReferenceTo(ObjectId from_object_id, + Reference reference, + ObjectId to_object_id); + + // Records that a weak serialization reference to a dropped object + // with id 'to_object_id' was written in order to serialize the object with id + // 'from_object_id'. 'to_object_id' must be an artificial node and + // 'replacement_object_id' is recorded as the replacement for the + // dropped object in the snapshot. This does not affect the number of + // bytes charged to 'from_object_id'. + void AttributeDroppedReferenceTo(ObjectId from_object_id, + Reference reference, + ObjectId to_object_id, + ObjectId replacement_object_id); // Marks an object as being a root in the graph. Used for analysis of the // graph. @@ -103,26 +132,43 @@ class V8SnapshotProfileWriter : public ZoneAllocated { // Write to a file in the V8 Snapshot Profile (JSON/.heapsnapshot) format. void Write(const char* file); - intptr_t EnsureString(const char* str); - - static ObjectId ArtificialRootId() { return {kArtificial, 0}; } + // Whether the given object ID has been added to the profile (via AddRoot, + // SetObjectTypeAndName, etc.). + bool HasId(const ObjectId& object_id); private: static constexpr intptr_t kNumNodeFields = 5; static constexpr intptr_t kNumEdgeFields = 3; - struct EdgeInfo { - intptr_t type; - intptr_t name_or_index; - ObjectId to_node; + using Edge = std::pair; + + struct EdgeToObjectIdMapTrait { + using Key = Edge; + using Value = ObjectId; + + struct Pair { + Pair() : edge{kContext, -1}, target(kArtificialRootId) {} + Pair(Key key, Value value) : edge(key), target(value) {} + Edge edge; + ObjectId target; + }; + + static Key KeyOf(Pair kv) { return kv.edge; } + static Value ValueOf(Pair kv) { return kv.target; } + static intptr_t Hashcode(Key key) { + return FinalizeHash(CombineHashes(key.first, key.second), 30); + } + static bool IsKeyEqual(Pair kv, Key key) { return kv.edge == key; } }; + using EdgeMap = ZoneDirectChainedHashMap; + struct NodeInfo { - intptr_t type; - intptr_t name; + intptr_t type = 0; + intptr_t name = 0; ObjectId id; - intptr_t self_size; - ZoneGrowableArray* edges = nullptr; + intptr_t self_size = 0; + EdgeMap* edges = nullptr; // Populated during serialization. intptr_t offset = -1; // 'trace_node_id' isn't supported. @@ -132,29 +178,36 @@ class V8SnapshotProfileWriter : public ZoneAllocated { bool operator!=(const NodeInfo& other) { return id != other.id; } bool operator==(const NodeInfo& other) { return !(*this != other); } - NodeInfo(intptr_t type, + void AddEdge(const Edge& edge, const ObjectId& target) { + edges->Insert({edge, target}); + } + bool HasEdge(const Edge& edge) { return edges->HasKey(edge); } + + // To allow NodeInfo to be used as the pair in ObjectIdToNodeInfoTraits. + NodeInfo() : id{kSnapshot, -1} {} + + NodeInfo(Zone* zone, + intptr_t type, intptr_t name, - ObjectId id, + const ObjectId& id, intptr_t self_size, - ZoneGrowableArray* edges, intptr_t offset) : type(type), name(name), id(id), self_size(self_size), - edges(edges), + edges(new (zone) EdgeMap(zone)), offset(offset) {} }; - NodeInfo DefaultNode(ObjectId object_id); - const NodeInfo& ArtificialRoot(); - NodeInfo* EnsureId(ObjectId object_id); static intptr_t NodeIdFor(ObjectId id) { return (id.second << kIdSpaceBits) | id.first; } - enum ConstantEdgeTypes { + intptr_t EnsureString(const char* str); + + enum ConstantEdgeType { kContext = 0, kElement = 1, kProperty = 2, @@ -165,36 +218,33 @@ class V8SnapshotProfileWriter : public ZoneAllocated { kExtra = 7, }; - enum ConstantNodeTypes { + static ConstantEdgeType ReferenceTypeToEdgeType(Reference::Type type); + + enum ConstantNodeType { kUnknown = 0, kArtificialRoot = 1, }; struct ObjectIdToNodeInfoTraits { + typedef NodeInfo Pair; typedef ObjectId Key; - typedef NodeInfo Value; + typedef Pair Value; - struct Pair { - Key key; - Value value; - Pair() - : key{kSnapshot, -1}, value{0, 0, {kSnapshot, -1}, 0, nullptr, -1} {}; - Pair(Key k, Value v) : key(k), value(v) {} - }; + static Key KeyOf(const Pair& pair) { return pair.id; } - static Key KeyOf(const Pair& pair) { return pair.key; } - - static Value ValueOf(const Pair& pair) { return pair.value; } + static Value ValueOf(const Pair& pair) { return pair; } static size_t Hashcode(Key key) { return NodeIdFor(key); } - static bool IsKeyEqual(const Pair& x, Key y) { return x.key == y; } + static bool IsKeyEqual(const Pair& x, Key y) { return x.id == y; } }; Zone* zone_; void Write(JSONWriter* writer); - void WriteNodeInfo(JSONWriter* writer, const NodeInfo& info); - void WriteEdgeInfo(JSONWriter* writer, const EdgeInfo& info); + intptr_t WriteNodeInfo(JSONWriter* writer, const NodeInfo& info); + void WriteEdgeInfo(JSONWriter* writer, + const Edge& info, + const ObjectId& target); void WriteStringsTable(JSONWriter* writer, const DirectChainedHashMap& map);