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);