[vm] Add --print-object-layout-to option to gen_snapshot

This option dumps offsets of Dart fields and sizes of Dart instances
into the specified file in JSON format.
Could be useful for debugging or memory usage investigations.

Typical output:

{"class":"_JsonUtf8Parser","size":80,"fields":[{"field":"emptyChunk","static":true},{"field":"decoder","offset":56},{"field":"chunk","offset":64},{"field":"chunkEnd","offset":72}]}

TEST=runtime/tests/vm/dart/print_object_layout_test.dart
Fixes https://github.com/dart-lang/sdk/issues/47892

Change-Id: I0b2f7ed9e4991e5fcd18517d5a06aa826c6d7125
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/223662
Reviewed-by: Dan Field <dnfield@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Markov 2021-12-20 21:40:33 +00:00 committed by Commit Bot
parent 36c051687c
commit 591b2bd724
8 changed files with 326 additions and 0 deletions

View file

@ -0,0 +1,25 @@
// Copyright (c) 2021, 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.
// Test script for print_object_layout_test.dart
class ClassA {
String fieldA1 = 'a';
int fieldA2 = 1;
}
class ClassB extends ClassA {
String fieldB1 = 'b';
int fieldB2 = 2;
int unusedB3 = 3;
static int staticB4 = 4;
}
@pragma('vm:never-inline')
useFields(ClassB obj) =>
"${obj.fieldA1}${obj.fieldA2}${obj.fieldB1}${obj.fieldB2}${ClassB.staticB4}";
main() {
useFields(ClassB());
}

View file

@ -0,0 +1,92 @@
// Copyright (c) 2021, 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.
// OtherResources=print_object_layout_script.dart
// Test for --print-object-layout-to option of gen_snapshot.
import 'dart:convert' show jsonDecode;
import 'dart:math' show max;
import 'dart:io' show File, Platform;
import 'package:expect/expect.dart';
import 'package:path/path.dart' as path;
import 'snapshot_test_helper.dart';
verifyObjectLayout(String path) {
final classes = jsonDecode(File(path).readAsStringSync());
var sizeA, fieldsA;
var sizeB, fieldsB;
for (var cls in classes) {
if (cls['class'] == 'ClassA') {
sizeA = cls['size'].toInt();
fieldsA = cls['fields'];
print(cls);
} else if (cls['class'] == 'ClassB') {
sizeB = cls['size'].toInt();
fieldsB = cls['fields'];
print(cls);
}
}
Expect.isNotNull(sizeA);
Expect.isTrue(sizeA > 0);
Expect.isTrue(fieldsA.length == 2);
int maxOffsetA = 0;
for (var field in fieldsA) {
String fieldName = field['field'];
Expect.isTrue(fieldName == 'fieldA1' || fieldName == 'fieldA2');
int offset = field['offset'].toInt();
Expect.isTrue((offset > 0) && (offset < sizeA));
maxOffsetA = max(offset, maxOffsetA);
}
Expect.isNotNull(sizeB);
Expect.isTrue(sizeB > 0);
Expect.isTrue(sizeA < sizeB);
Expect.isTrue(fieldsB.length == 3);
for (var field in fieldsB) {
String fieldName = field['field'];
if (fieldName == 'staticB4') {
Expect.isTrue(field['static']);
} else {
Expect.isTrue(fieldName == 'fieldB1' || fieldName == 'fieldB2');
int offset = field['offset'].toInt();
Expect.isTrue((offset > 0) && (offset < sizeB));
Expect.isTrue(offset > maxOffsetA);
}
}
}
main() async {
// We don't have access to the SDK on Android.
if (Platform.isAndroid) {
print('Skipping test on Android');
return;
}
final testScriptUri =
Platform.script.resolve('print_object_layout_script.dart');
await withTempDir((String temp) async {
final appDillPath = path.join(temp, 'app.dill');
final snapshotPath = path.join(temp, 'aot.snapshot');
final objectLayoutPath = path.join(temp, 'layout.json');
await runGenKernel('BUILD DILL FILE', [
'--aot',
'--link-platform',
'--output=$appDillPath',
testScriptUri.toFilePath(),
]);
await runGenSnapshot('GENERATE SNAPSHOT', [
'--snapshot-kind=app-aot-elf',
'--elf=$snapshotPath',
'--print-object-layout-to=$objectLayoutPath',
appDillPath,
]);
verifyObjectLayout(objectLayoutPath);
});
}

View file

@ -0,0 +1,27 @@
// Copyright (c) 2021, 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.
// @dart = 2.9
// Test script for print_object_layout_test.dart
class ClassA {
String fieldA1 = 'a';
int fieldA2 = 1;
}
class ClassB extends ClassA {
String fieldB1 = 'b';
int fieldB2 = 2;
int unusedB3 = 3;
static int staticB4 = 4;
}
@pragma('vm:never-inline')
useFields(ClassB obj) =>
"${obj.fieldA1}${obj.fieldA2}${obj.fieldB1}${obj.fieldB2}${ClassB.staticB4}";
main() {
useFields(ClassB());
}

View file

@ -0,0 +1,94 @@
// Copyright (c) 2021, 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.
// @dart = 2.9
// OtherResources=print_object_layout_script.dart
// Test for --print-object-layout-to option of gen_snapshot.
import 'dart:convert' show jsonDecode;
import 'dart:math' show max;
import 'dart:io' show File, Platform;
import 'package:expect/expect.dart';
import 'package:path/path.dart' as path;
import 'snapshot_test_helper.dart';
verifyObjectLayout(String path) {
final classes = jsonDecode(File(path).readAsStringSync());
var sizeA, fieldsA;
var sizeB, fieldsB;
for (var cls in classes) {
if (cls['class'] == 'ClassA') {
sizeA = cls['size'].toInt();
fieldsA = cls['fields'];
print(cls);
} else if (cls['class'] == 'ClassB') {
sizeB = cls['size'].toInt();
fieldsB = cls['fields'];
print(cls);
}
}
Expect.isNotNull(sizeA);
Expect.isTrue(sizeA > 0);
Expect.isTrue(fieldsA.length == 2);
int maxOffsetA = 0;
for (var field in fieldsA) {
String fieldName = field['field'];
Expect.isTrue(fieldName == 'fieldA1' || fieldName == 'fieldA2');
int offset = field['offset'].toInt();
Expect.isTrue((offset > 0) && (offset < sizeA));
maxOffsetA = max(offset, maxOffsetA);
}
Expect.isNotNull(sizeB);
Expect.isTrue(sizeB > 0);
Expect.isTrue(sizeA < sizeB);
Expect.isTrue(fieldsB.length == 3);
for (var field in fieldsB) {
String fieldName = field['field'];
if (fieldName == 'staticB4') {
Expect.isTrue(field['static']);
} else {
Expect.isTrue(fieldName == 'fieldB1' || fieldName == 'fieldB2');
int offset = field['offset'].toInt();
Expect.isTrue((offset > 0) && (offset < sizeB));
Expect.isTrue(offset > maxOffsetA);
}
}
}
main() async {
// We don't have access to the SDK on Android.
if (Platform.isAndroid) {
print('Skipping test on Android');
return;
}
final testScriptUri =
Platform.script.resolve('print_object_layout_script.dart');
await withTempDir((String temp) async {
final appDillPath = path.join(temp, 'app.dill');
final snapshotPath = path.join(temp, 'aot.snapshot');
final objectLayoutPath = path.join(temp, 'layout.json');
await runGenKernel('BUILD DILL FILE', [
'--aot',
'--link-platform',
'--output=$appDillPath',
testScriptUri.toFilePath(),
]);
await runGenSnapshot('GENERATE SNAPSHOT', [
'--snapshot-kind=app-aot-elf',
'--elf=$snapshotPath',
'--print-object-layout-to=$objectLayoutPath',
appDillPath,
]);
verifyObjectLayout(objectLayoutPath);
});
}

View file

@ -20,6 +20,7 @@ dart/isolates/concurrency_stress_sanity_test: Pass, Slow # Spawns subprocesses
dart/isolates/fast_object_copy_test: Pass, Slow # Slow due to doing a lot of transitive object copies.
dart/minimal_kernel_test: Pass, Slow # Spawns several subprocesses
dart/null_safety_autodetection_in_kernel_compiler_test: Pass, Slow # Spawns several subprocesses
dart/print_object_layout_test: Pass, Slow # Spawns several subprocesses
dart/slow_path_shared_stub_test: Pass, Slow # Uses --shared-slow-path-triggers-gc flag.
dart/snapshot_version_test: Skip # This test is a Dart1 test (script snapshot)
dart/stack_overflow_shared_test: Pass, Slow # Uses --shared-slow-path-triggers-gc flag.
@ -32,6 +33,7 @@ dart_2/isolates/concurrency_stress_sanity_test: Pass, Slow # Spawns subprocesses
dart_2/isolates/fast_object_copy_test: Pass, Slow # Slow due to doing a lot of transitive object copies.
dart_2/minimal_kernel_test: Pass, Slow # Spawns several subprocesses
dart_2/null_safety_autodetection_in_kernel_compiler_test: Pass, Slow # Spawns several subprocesses
dart_2/print_object_layout_test: Pass, Slow # Spawns several subprocesses
dart_2/slow_path_shared_stub_test: Pass, Slow # Uses --shared-slow-path-triggers-gc flag.
dart_2/snapshot_version_test: Skip # This test is a Dart1 test (script snapshot)
dart_2/stack_overflow_shared_test: Pass, Slow # Uses --shared-slow-path-triggers-gc flag.
@ -55,10 +57,12 @@ dart/appjit*: SkipByDesign # Test needs to a particular opt-counter value
dart/kernel_determinism_test: SkipSlow
dart/minimal_kernel_test: SkipSlow # gen_kernel is too slow with optimization_counter_threshold
dart/null_safety_autodetection_in_kernel_compiler_test: SkipSlow # gen_kernel is too slow with optimization_counter_threshold
dart/print_object_layout_test: SkipSlow # gen_kernel is too slow with optimization_counter_threshold
dart_2/appjit*: SkipByDesign # Test needs to a particular opt-counter value
dart_2/kernel_determinism_test: SkipSlow
dart_2/minimal_kernel_test: SkipSlow # gen_kernel is too slow with optimization_counter_threshold
dart_2/null_safety_autodetection_in_kernel_compiler_test: SkipSlow # gen_kernel is too slow with optimization_counter_threshold
dart_2/print_object_layout_test: SkipSlow # gen_kernel is too slow with optimization_counter_threshold
[ $builder_tag == tsan ]
dart/appjit_cha_deopt_test: SkipSlow
@ -127,12 +131,14 @@ dart/appjit_cha_deopt_test: Pass, Slow # Quite slow in debug mode, uses --optimi
dart/b162922506_test: SkipSlow # Generates very large input file
dart/minimal_kernel_test: SkipSlow # gen_kernel is too slow in debug mode
dart/null_safety_autodetection_in_kernel_compiler_test: SkipSlow # gen_kernel is too slow in debug mode
dart/print_object_layout_test: SkipSlow # gen_kernel is too slow in debug mode
dart/spawn_shutdown_test: Pass, Slow # VM Shutdown test, It can take some time for all the isolates to shutdown in a Debug build.
dart/type_casts_with_null_safety_autodetection_test: Pass, Slow # Very slow in debug mode, uses --optimization-counter-threshold=10
dart_2/appjit_cha_deopt_test: Pass, Slow # Quite slow in debug mode, uses --optimization-counter-threshold=100
dart_2/b162922506_test: SkipSlow # Generates very large input file
dart_2/minimal_kernel_test: SkipSlow # gen_kernel is too slow in debug mode
dart_2/null_safety_autodetection_in_kernel_compiler_test: SkipSlow # gen_kernel is too slow in debug mode
dart_2/print_object_layout_test: SkipSlow # gen_kernel is too slow in debug mode
dart_2/spawn_shutdown_test: Pass, Slow # VM Shutdown test, It can take some time for all the isolates to shutdown in a Debug build.
[ $mode == product ]
@ -245,12 +251,14 @@ dart/b162922506_test: SkipSlow # Generates large input file
dart/data_uri_spawn_test: Skip # Please triage.
dart/minimal_kernel_test: SkipSlow # gen_kernel is too slow on simulated architectures
dart/null_safety_autodetection_in_kernel_compiler_test: SkipSlow # gen_kernel is too slow on simulated architectures
dart/print_object_layout_test: SkipSlow # gen_kernel is too slow on simulated architectures
dart/snapshot_version_test: RuntimeError # Please triage.
dart_2/appjit*: SkipSlow # DFE too slow
dart_2/b162922506_test: SkipSlow # Generates large input file
dart_2/data_uri_spawn_test: Skip # Please triage.
dart_2/minimal_kernel_test: SkipSlow # gen_kernel is too slow on simulated architectures
dart_2/null_safety_autodetection_in_kernel_compiler_test: SkipSlow # gen_kernel is too slow on simulated architectures
dart_2/print_object_layout_test: SkipSlow # gen_kernel is too slow on simulated architectures
dart_2/snapshot_version_test: RuntimeError # Please triage.
[ $compiler == dartk && ($hot_reload || $hot_reload_rollback) ]
@ -264,6 +272,7 @@ dart/appjit*: SkipByDesign # Test needs to run from source
dart/kernel_determinism_test: SkipByDesign # Test needs to run from source
dart/minimal_kernel_test: SkipByDesign # Test needs to run from source
dart/null_safety_autodetection_in_kernel_compiler_test: SkipByDesign # Test needs to run from source
dart/print_object_layout_test: SkipByDesign # Test needs to run from source
dart/regress_44026_test: SkipByDesign # Test needs to run from source
dart/snapshot_depfile_test: SkipByDesign # Test needs to run from source
dart/type_casts_with_null_safety_autodetection_test: SkipByDesign # Test needs to run from source
@ -271,6 +280,7 @@ dart_2/appjit*: SkipByDesign # Test needs to run from source
dart_2/kernel_determinism_test: SkipByDesign # Test needs to run from source
dart_2/minimal_kernel_test: SkipByDesign # Test needs to run from source
dart_2/null_safety_autodetection_in_kernel_compiler_test: SkipByDesign # Test needs to run from source
dart_2/print_object_layout_test: SkipByDesign # Test needs to run from source
dart_2/snapshot_depfile_test: SkipByDesign # Test needs to run from source
[ $compiler == dartkp && ($arch == simarm || $arch == simarm64 || $arch == simarm64c) ]
@ -430,6 +440,7 @@ dart/issue_31959_31960_test: SkipSlow
dart/minimal_kernel_test: SkipSlow # gen_kernel is too slow in hot reload testing mode
dart/null_safety_autodetection_in_kernel_compiler_test: SkipSlow # gen_kernel is too slow in hot reload testing mode
dart/print_flow_graph_determinism_test: SkipSlow
dart/print_object_layout_test: SkipSlow # gen_kernel is too slow in hot reload testing mode
dart/slow_path_shared_stub_test: SkipSlow # Too slow with --shared-slow-path-triggers-gc flag and not relevant outside precompiled.
dart/spawn_infinite_loop_test: Skip # We can shutdown an isolate before it reloads.
dart/spawn_shutdown_test: Skip # We can shutdown an isolate before it reloads.
@ -441,6 +452,7 @@ dart_2/issue_31959_31960_test: SkipSlow
dart_2/minimal_kernel_test: SkipSlow # gen_kernel is too slow in hot reload testing mode
dart_2/null_safety_autodetection_in_kernel_compiler_test: SkipSlow # gen_kernel is too slow in hot reload testing mode
dart_2/print_flow_graph_determinism_test: SkipSlow
dart_2/print_object_layout_test: SkipSlow # gen_kernel is too slow in hot reload testing mode
dart_2/slow_path_shared_stub_test: SkipSlow # Too slow with --shared-slow-path-triggers-gc flag and not relevant outside precompiled.
dart_2/spawn_infinite_loop_test: Skip # We can shutdown an isolate before it reloads.
dart_2/spawn_shutdown_test: Skip # We can shutdown an isolate before it reloads.

View file

@ -534,6 +534,70 @@ void ClassTable::UpdateClassSize(intptr_t cid, ClassPtr raw_cls) {
shared_class_table_->SetSizeAt(cid, size);
}
#if defined(DART_PRECOMPILER)
void ClassTable::PrintObjectLayout(const char* filename) {
Class& cls = Class::Handle();
Array& fields = Array::Handle();
Field& field = Field::Handle();
JSONWriter js;
js.OpenArray();
for (intptr_t i = ClassId::kObjectCid; i < top_; i++) {
if (!HasValidClassAt(i)) {
continue;
}
cls = At(i);
ASSERT(!cls.IsNull());
ASSERT(cls.id() != kIllegalCid);
ASSERT(cls.is_finalized()); // Precompiler already finalized all classes.
ASSERT(!cls.IsTopLevel());
js.OpenObject();
js.PrintProperty("class", cls.UserVisibleNameCString());
js.PrintProperty("size", cls.target_instance_size());
js.OpenArray("fields");
fields = cls.fields();
if (!fields.IsNull()) {
for (intptr_t i = 0, n = fields.Length(); i < n; ++i) {
field ^= fields.At(i);
js.OpenObject();
js.PrintProperty("field", field.UserVisibleNameCString());
if (field.is_static()) {
js.PrintPropertyBool("static", true);
} else {
js.PrintProperty("offset", field.TargetOffset());
}
js.CloseObject();
}
}
js.CloseArray();
js.CloseObject();
}
js.CloseArray();
auto file_open = Dart::file_open_callback();
auto file_write = Dart::file_write_callback();
auto file_close = Dart::file_close_callback();
if ((file_open == nullptr) || (file_write == nullptr) ||
(file_close == nullptr)) {
OS::PrintErr("warning: Could not access file callbacks.");
return;
}
void* file = file_open(filename, /*write=*/true);
if (file == nullptr) {
OS::PrintErr("warning: Failed to write object layout: %s\n", filename);
return;
}
char* output = nullptr;
intptr_t output_length = 0;
js.Steal(&output, &output_length);
file_write(output, output_length, file);
free(output);
file_close(file);
}
#endif // defined(DART_PRECOMPILER)
#ifndef PRODUCT
void ClassTable::PrintToJSONObject(JSONObject* object) {
Class& cls = Class::Handle();

View file

@ -394,6 +394,10 @@ class ClassTable {
void Print();
#if defined(DART_PRECOMPILER)
void PrintObjectLayout(const char* filename);
#endif
#ifndef PRODUCT
// Describes layout of heap stats for code generation. See offset_extractor.cc
struct ArrayTraits {

View file

@ -67,6 +67,10 @@ DEFINE_FLAG(bool,
"Print per-phase breakdown of time spent precompiling");
DEFINE_FLAG(bool, print_unique_targets, false, "Print unique dynamic targets");
DEFINE_FLAG(bool, print_gop, false, "Print global object pool");
DEFINE_FLAG(charp,
print_object_layout_to,
nullptr,
"Print layout of Dart objects to the given file");
DEFINE_FLAG(bool, trace_precompiler, false, "Trace precompiler.");
DEFINE_FLAG(
int,
@ -469,6 +473,10 @@ void Precompiler::DoCompileAll() {
FinalizeAllClasses();
ASSERT(Error::Handle(Z, T->sticky_error()).IsNull());
if (FLAG_print_object_layout_to != nullptr) {
IG->class_table()->PrintObjectLayout(FLAG_print_object_layout_to);
}
ClassFinalizer::SortClasses();
// Collects type usage information which allows us to decide when/how to