[vm] Update ObjectSlots to handle _LinkedHashBase
Generalize code which computes slots map to handle class hierarchies which include base classes with VM-described layout like _LinkedHashBase. Fixes https://github.com/dart-lang/sdk/issues/63180 TEST=vm/dart/heap_snapshot_regress_63180 Change-Id: I32ab0e258e735f745d45f25813177d0f6a6a6964 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/496861 Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Slava Egorov <vegorov@google.com>
This commit is contained in:
committed by
dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
5ae14a808c
commit
488f97eb76
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
|
||||
// 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.
|
||||
|
||||
import 'dart:collection';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'heap_snapshot_test.dart';
|
||||
import 'use_flag_test_helper.dart';
|
||||
|
||||
main() async {
|
||||
if (const bool.fromEnvironment('dart.vm.product')) return;
|
||||
|
||||
await withTempDir('heap_snapshot_test', (String dir) async {
|
||||
final file = path.join(dir, 'state1.heapsnapshot');
|
||||
final map = LinkedHashMap(
|
||||
equals: (a, b) => a == b,
|
||||
hashCode: (a) => a.hashCode,
|
||||
);
|
||||
map[1] = 2;
|
||||
NativeRuntime.writeHeapSnapshotToFile(file);
|
||||
});
|
||||
}
|
||||
@@ -212,7 +212,8 @@ size unique-size count class data
|
||||
}
|
||||
|
||||
await run(
|
||||
'stats foobar = (follow (follow global) ^:type_arguments ^Root ^Smi)');
|
||||
'stats foobar = (follow (follow global) ^:type_arguments ^Root ^Smi)',
|
||||
);
|
||||
expectLogPattern('''
|
||||
size count class
|
||||
-------- -------- --------
|
||||
@@ -225,13 +226,13 @@ size count class
|
||||
await run('examine users foobar');
|
||||
expectLogPattern(r'''
|
||||
_List@\d+ .* {
|
||||
type_arguments_
|
||||
type_arguments
|
||||
length_
|
||||
\[0\] *Foo@\d+ .*/cli_test.dart
|
||||
\[1\] *Foo@\d+ .*/cli_test.dart
|
||||
}
|
||||
_List@\d+ .* {
|
||||
type_arguments_
|
||||
type_arguments
|
||||
length_
|
||||
\[0\] *Bar@\d+ .*/cli_test.dart
|
||||
\[1\] *Bar@\d+ .*/cli_test.dart
|
||||
@@ -367,8 +368,9 @@ class Global {
|
||||
// report their length (such as /dev/zero).
|
||||
final bool supportsExternalTypedDataTest = File('/dev/zero').existsSync();
|
||||
|
||||
final Uint8List externalTypedData1234567 =
|
||||
File('/dev/zero').openSync().readSync(1234567);
|
||||
final Uint8List externalTypedData1234567 = File(
|
||||
'/dev/zero',
|
||||
).openSync().readSync(1234567);
|
||||
|
||||
final weakTest = WeakTest(Object());
|
||||
|
||||
@@ -378,8 +380,8 @@ class WeakTest {
|
||||
final Finalizer finalizer;
|
||||
|
||||
WeakTest(this.object)
|
||||
: weakList = List.filled(1, WeakReference(object)),
|
||||
finalizer = Finalizer((_) {})..attach(object, Object(), detach: object);
|
||||
: weakList = List.filled(1, WeakReference(object)),
|
||||
finalizer = Finalizer((_) {})..attach(object, Object(), detach: object);
|
||||
|
||||
String get use => '$object|$weakList|$finalizer';
|
||||
}
|
||||
|
||||
@@ -12417,6 +12417,8 @@ class LinkedHashBase : public Instance {
|
||||
static constexpr intptr_t kInitialIndexSize = 1 << (kInitialIndexBits + 1);
|
||||
static constexpr intptr_t kUninitializedIndexSize = 1;
|
||||
|
||||
static const ClassId kClassId = kLinkedHashBaseCid;
|
||||
|
||||
private:
|
||||
LinkedHashBasePtr ptr() const { return static_cast<LinkedHashBasePtr>(ptr_); }
|
||||
UntaggedLinkedHashBase* untag() const {
|
||||
|
||||
+41
-37
@@ -67,48 +67,52 @@ class ObjectSlots {
|
||||
if (!cls.is_finalized()) continue;
|
||||
|
||||
auto slots = cid2object_slots_[cid] = new ObjectSlotsType();
|
||||
for (const auto& entry : OffsetsTable::offsets_table()) {
|
||||
if (entry.class_id == cid) {
|
||||
slots->Add(ObjectSlot(entry.offset, entry.is_compressed_pointer,
|
||||
entry.field_name));
|
||||
}
|
||||
// If the class has native fields, the native fields array is the first
|
||||
// field and therefore starts after the `kWordSize` tagging word.
|
||||
if (cls.num_native_fields() > 0) {
|
||||
slots->Add(ObjectSlot(kWordSize, true, "native_fields"));
|
||||
}
|
||||
// If the class or any super class is generic, it will have a type
|
||||
// arguments vector.
|
||||
const auto tav_offset = cls.host_type_arguments_field_offset();
|
||||
if (tav_offset != Class::kNoTypeArguments) {
|
||||
slots->Add(ObjectSlot(tav_offset, true, "type_arguments"));
|
||||
}
|
||||
|
||||
// The VM doesn't define a layout for the object, so it's a regular Dart
|
||||
// class.
|
||||
if (slots->is_empty()) {
|
||||
// If the class has native fields, the native fields array is the first
|
||||
// field and therefore starts after the `kWordSize` tagging word.
|
||||
if (cls.num_native_fields() > 0) {
|
||||
slots->Add(ObjectSlot(kWordSize, true, "native_fields"));
|
||||
}
|
||||
// If the class or any super class is generic, it will have a type
|
||||
// arguments vector.
|
||||
const auto tav_offset = cls.host_type_arguments_field_offset();
|
||||
if (tav_offset != Class::kNoTypeArguments) {
|
||||
slots->Add(ObjectSlot(tav_offset, true, "type_arguments"));
|
||||
}
|
||||
|
||||
// Add slots for all user-defined instance fields in the hierarchy.
|
||||
while (!cls.IsNull()) {
|
||||
fields = cls.fields();
|
||||
if (!fields.IsNull()) {
|
||||
for (intptr_t i = 0; i < fields.Length(); ++i) {
|
||||
field ^= fields.At(i);
|
||||
if (!field.is_instance()) continue;
|
||||
name = field.name();
|
||||
// If the field is unboxed, we don't know the size of it (may be
|
||||
// multiple words) - but that doesn't matter because
|
||||
// a) we will process instances using the slots we collect
|
||||
// (instead of regular GC visitor);
|
||||
// b) we will not write the value of the field and instead treat
|
||||
// it like a dummy reference to 0 (like we do with Smis).
|
||||
slots->Add(ObjectSlot(field.HostOffset(), !field.is_unboxed(),
|
||||
name.ToCString()));
|
||||
// Add slots for all user-defined instance fields in the hierarchy.
|
||||
while (!cls.IsNull()) {
|
||||
const intptr_t current_cid = cls.id();
|
||||
if (current_cid < kNumPredefinedCids) {
|
||||
bool slots_added = false;
|
||||
for (const auto& entry : OffsetsTable::offsets_table()) {
|
||||
if (entry.class_id == current_cid) {
|
||||
slots->Add(ObjectSlot(entry.offset, entry.is_compressed_pointer,
|
||||
entry.field_name));
|
||||
slots_added = true;
|
||||
}
|
||||
}
|
||||
cls = cls.SuperClass();
|
||||
if (slots_added) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fields = cls.fields();
|
||||
if (!fields.IsNull()) {
|
||||
for (intptr_t i = 0; i < fields.Length(); ++i) {
|
||||
field ^= fields.At(i);
|
||||
if (!field.is_instance()) continue;
|
||||
name = field.name();
|
||||
// If the field is unboxed, we don't know the size of it (may be
|
||||
// multiple words) - but that doesn't matter because
|
||||
// a) we will process instances using the slots we collect
|
||||
// (instead of regular GC visitor);
|
||||
// b) we will not write the value of the field and instead treat
|
||||
// it like a dummy reference to 0 (like we do with Smis).
|
||||
slots->Add(ObjectSlot(field.HostOffset(), !field.is_unboxed(),
|
||||
name.ToCString()));
|
||||
}
|
||||
}
|
||||
cls = cls.SuperClass();
|
||||
}
|
||||
|
||||
// We sort the slots, so we'll visit the slots in memory order.
|
||||
|
||||
@@ -155,37 +155,15 @@ namespace dart {
|
||||
F(Closure, length_and_flags_) \
|
||||
F(Closure, hash_) \
|
||||
F(String, length_) \
|
||||
F(Array, type_arguments_) \
|
||||
F(Array, length_) \
|
||||
F(ImmutableArray, type_arguments_) \
|
||||
F(ImmutableArray, length_) \
|
||||
F(GrowableObjectArray, type_arguments_) \
|
||||
F(GrowableObjectArray, length_) \
|
||||
F(GrowableObjectArray, data_) \
|
||||
F(Map, type_arguments_) \
|
||||
F(Map, index_) \
|
||||
F(Map, hash_mask_) \
|
||||
F(Map, data_) \
|
||||
F(Map, used_data_) \
|
||||
F(Map, deleted_keys_) \
|
||||
F(ConstMap, type_arguments_) \
|
||||
F(ConstMap, index_) \
|
||||
F(ConstMap, hash_mask_) \
|
||||
F(ConstMap, data_) \
|
||||
F(ConstMap, used_data_) \
|
||||
F(ConstMap, deleted_keys_) \
|
||||
F(Set, type_arguments_) \
|
||||
F(Set, index_) \
|
||||
F(Set, hash_mask_) \
|
||||
F(Set, data_) \
|
||||
F(Set, used_data_) \
|
||||
F(Set, deleted_keys_) \
|
||||
F(ConstSet, type_arguments_) \
|
||||
F(ConstSet, index_) \
|
||||
F(ConstSet, hash_mask_) \
|
||||
F(ConstSet, data_) \
|
||||
F(ConstSet, used_data_) \
|
||||
F(ConstSet, deleted_keys_) \
|
||||
F(LinkedHashBase, hash_mask_) \
|
||||
F(LinkedHashBase, data_) \
|
||||
F(LinkedHashBase, used_data_) \
|
||||
F(LinkedHashBase, deleted_keys_) \
|
||||
F(LinkedHashBase, index_) \
|
||||
F(TypedData, length_) \
|
||||
F(ExternalTypedData, length_) \
|
||||
F(ReceivePort, send_port_) \
|
||||
@@ -207,12 +185,10 @@ namespace dart {
|
||||
F(WeakProperty, key_) \
|
||||
F(WeakProperty, value_) \
|
||||
F(WeakReference, target_) \
|
||||
F(WeakReference, type_arguments_) \
|
||||
F(Finalizer, detachments_) \
|
||||
F(Finalizer, all_entries_) \
|
||||
F(Finalizer, entries_collected_) \
|
||||
F(Finalizer, callback_) \
|
||||
F(Finalizer, type_arguments_) \
|
||||
F(NativeFinalizer, detachments_) \
|
||||
F(NativeFinalizer, all_entries_) \
|
||||
F(NativeFinalizer, entries_collected_) \
|
||||
@@ -225,7 +201,6 @@ namespace dart {
|
||||
F(MirrorReference, referent_) \
|
||||
F(UserTag, label_) \
|
||||
F(Pointer, data_) \
|
||||
F(Pointer, type_arguments_) \
|
||||
F(DynamicLibrary, handle_) \
|
||||
F(DynamicLibrary, isClosed_) \
|
||||
F(DynamicLibrary, canBeClosed_) \
|
||||
@@ -234,8 +209,7 @@ namespace dart {
|
||||
F(FfiTrampolineData, callback_exceptional_return_) \
|
||||
F(TypedDataView, length_) \
|
||||
F(TypedDataView, typed_data_) \
|
||||
F(TypedDataView, offset_in_bytes_) \
|
||||
F(FutureOr, type_arguments_)
|
||||
F(TypedDataView, offset_in_bytes_)
|
||||
|
||||
#define AOT_CLASSES_AND_FIELDS(F)
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
namespace dart {
|
||||
|
||||
class Zone;
|
||||
|
||||
#if defined(DART_PRECOMPILER) || defined(DART_ENABLE_HEAP_SNAPSHOT_WRITER)
|
||||
|
||||
class OffsetsTable : public ZoneObject {
|
||||
|
||||
Reference in New Issue
Block a user