Skip to content

Commit 59988f2

Browse files
mralephCommit Queue
authored andcommitted
[vm] Improve code mapping in analyze_snapshot
* Make sure to iterate object_store and StubCode to collect more code objects * Include name field all code objects and try to provide a meaningful name where possible. * Identify stubs (is_stub: true) and assign them names * Include pseudo-code objects for all Code-less entries in instructions tables. TEST=vm/dart/analyze_snapshot_binary_test Change-Id: I842cd5482bc407a203131da8e50041228bd9c372 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/438181 Reviewed-by: Martin Kustermann <[email protected]> Commit-Queue: Slava Egorov <[email protected]>
1 parent 5e77a23 commit 59988f2

File tree

7 files changed

+241
-51
lines changed

7 files changed

+241
-51
lines changed

runtime/tests/vm/dart/analyze_snapshot_binary_test.dart

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'dart:io';
77
import 'dart:ffi';
88
import 'dart:async';
99

10+
import 'package:collection/collection.dart';
1011
import 'package:expect/expect.dart';
1112
import 'package:native_stack_traces/elf.dart';
1213
import 'package:path/path.dart' as path;
@@ -27,12 +28,8 @@ Future<void> testAOT(
2728
bool forceDrops = false,
2829
bool stripUtil = false, // Note: forced true if useAsm.
2930
bool stripFlag = false,
30-
bool disassemble = false,
3131
}) async {
3232
const isProduct = const bool.fromEnvironment('dart.vm.product');
33-
if (isProduct && disassemble) {
34-
Expect.isFalse(disassemble, 'no use of disassembler in PRODUCT mode');
35-
}
3633

3734
final analyzeSnapshot = path.join(buildDir, 'analyze_snapshot');
3835

@@ -55,9 +52,6 @@ Future<void> testAOT(
5552
if (stripUtil) {
5653
descriptionBuilder.write('-extstrip');
5754
}
58-
if (disassemble) {
59-
descriptionBuilder.write('-disassembled');
60-
}
6155

6256
final description = descriptionBuilder.toString();
6357
Expect.isTrue(
@@ -77,26 +71,32 @@ Future<void> testAOT(
7771
'--no-retain-function-objects',
7872
'--no-retain-code-objects',
7973
],
80-
if (disassemble) '--disassemble', // Not defined in PRODUCT mode.
8174
dillPath,
8275
];
8376

77+
final int textSectionSize;
8478
if (useAsm) {
8579
final assemblyPath = path.join(tempDir, 'test.S');
8680

87-
await (disassemble ? runSilent : run)(genSnapshot, <String>[
88-
'--snapshot-kind=app-aot-assembly',
89-
'--assembly=$assemblyPath',
90-
...commonSnapshotArgs,
91-
]);
81+
textSectionSize = _findTextSectionSize(
82+
await runOutput(genSnapshot, <String>[
83+
'--snapshot-kind=app-aot-assembly',
84+
'--assembly=$assemblyPath',
85+
'--print-snapshot-sizes',
86+
...commonSnapshotArgs,
87+
], ignoreStdErr: true),
88+
);
9289

9390
await assembleSnapshot(assemblyPath, snapshotPath);
9491
} else {
95-
await (disassemble ? runSilent : run)(genSnapshot, <String>[
96-
'--snapshot-kind=app-aot-elf',
97-
'--elf=$snapshotPath',
98-
...commonSnapshotArgs,
99-
]);
92+
textSectionSize = _findTextSectionSize(
93+
await runOutput(genSnapshot, <String>[
94+
'--snapshot-kind=app-aot-elf',
95+
'--elf=$snapshotPath',
96+
'--print-snapshot-sizes',
97+
...commonSnapshotArgs,
98+
], ignoreStdErr: true),
99+
);
100100
}
101101

102102
print("Snapshot generated at $snapshotPath.");
@@ -371,6 +371,34 @@ Future<void> testAOT(
371371
analyzerJson['metadata']['analyzer_version'] == 2,
372372
'invalid snapshot analyzer version',
373373
);
374+
375+
// Find all code objects.
376+
final codeObjects = objects
377+
.where((o) => o['type'] == 'Code' && o['size'] != 0)
378+
.map(
379+
(o) => (
380+
name: o['name'] as String,
381+
stub: (o['is_stub'] as bool? ?? false),
382+
offset: o['offset'] as int,
383+
size: o['size'] as int,
384+
),
385+
)
386+
.toList();
387+
codeObjects.sort((a, b) => a.offset.compareTo(b.offset));
388+
Expect.isNotEmpty(codeObjects);
389+
Expect.isNotNull(
390+
codeObjects.firstWhereOrNull((o) => o.stub && o.name == 'AllocateArray'),
391+
'expected stubs to be identified',
392+
);
393+
final int totalSize = codeObjects.fold(0, (size, code) => size + code.size);
394+
int totalGap = 0;
395+
for (int i = 0; i < codeObjects.length - 1; i++) {
396+
final code = codeObjects[i];
397+
final nextCode = codeObjects[i + 1];
398+
totalGap += code.offset + code.size - nextCode.offset;
399+
}
400+
Expect.isTrue(totalGap < 500);
401+
Expect.isTrue((totalSize + totalGap - textSectionSize) < 500);
374402
});
375403
}
376404

@@ -559,14 +587,6 @@ main() async {
559587
testAOT(aotDillPath, stripFlag: true),
560588
]);
561589

562-
// Since we can't force disassembler support after the fact when running
563-
// in PRODUCT mode, skip any --disassemble tests. Do these tests last as
564-
// they have lots of output and so the log will be truncated.
565-
if (!const bool.fromEnvironment('dart.vm.product')) {
566-
// Regression test for dartbug.com/41149.
567-
await Future.wait([testAOT(aotDillPath, disassemble: true)]);
568-
}
569-
570590
// Test unstripped ELF generation that is then externally stripped.
571591
await Future.wait([testAOT(aotDillPath, stripUtil: true)]);
572592

@@ -587,3 +607,10 @@ main() async {
587607
Future<String> readFile(String file) {
588608
return new File(file).readAsString();
589609
}
610+
611+
int _findTextSectionSize(List<String> output) {
612+
const prefix = 'Instructions(CodeSize): ';
613+
return int.parse(
614+
output.firstWhere((l) => l.startsWith(prefix)).substring(prefix.length),
615+
);
616+
}

runtime/tests/vm/dart/use_flag_test_helper.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,14 +253,20 @@ Future<void> runSilent(String executable, List<String> args) async {
253253
}
254254
}
255255

256-
Future<List<String>> runOutput(String executable, List<String> args) async {
256+
Future<List<String>> runOutput(
257+
String executable,
258+
List<String> args, {
259+
bool ignoreStdErr = false,
260+
}) async {
257261
final result = await runHelper(executable, args);
258262

259263
if (result.exitCode != 0) {
260264
throw 'Command failed with unexpected exit code (was ${result.exitCode})';
261265
}
262266
Expect.isTrue(result.stdout.isNotEmpty);
263-
Expect.isTrue(result.stderr.isEmpty);
267+
if (!ignoreStdErr) {
268+
Expect.isTrue(result.stderr.isEmpty);
269+
}
264270

265271
return LineSplitter.split(result.stdout).toList(growable: false);
266272
}

runtime/vm/analyze_snapshot_api_impl.cc

Lines changed: 142 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
#include <cstddef>
66
#include <cstdint>
7-
#include <map>
87
#include <set>
98
#include <sstream>
9+
#include <unordered_map>
1010
#include <vector>
1111

1212
#include "include/analyze_snapshot_api.h"
@@ -71,6 +71,7 @@ class SnapshotAnalyzer {
7171
const std::vector<const Field*>& fields);
7272
void DumpFunction(const Function& function);
7373
void DumpCode(const Code& code);
74+
void DumpCode(uword start_pc, uword end_pc, const char* name);
7475
void DumpField(const Field& field);
7576
void DumpString(const String& string);
7677
void DumpInstance(const Object& object);
@@ -85,6 +86,7 @@ class SnapshotAnalyzer {
8586
const Dart_SnapshotAnalyzerInformation& info_;
8687
std::vector<std::vector<const Field*>> class_fields_;
8788
std::vector<std::vector<const Field*>> top_level_class_fields_;
89+
std::unordered_map<uword, const char*> stub_names_;
8890

8991
JSONWriter js_;
9092
Thread* thread_;
@@ -250,7 +252,7 @@ void SnapshotAnalyzer::DumpClassInstanceSlots(
250252

251253
void SnapshotAnalyzer::DumpFunction(const Function& function) {
252254
js_.PrintProperty("type", "Function");
253-
js_.PrintProperty("name", function.ToCString());
255+
js_.PrintProperty("name", function.QualifiedScrubbedNameCString());
254256

255257
js_.PrintProperty("signature",
256258
String::Handle(function.InternalSignature()).ToCString());
@@ -262,28 +264,108 @@ void SnapshotAnalyzer::DumpFunction(const Function& function) {
262264
}
263265
}
264266

267+
namespace {
268+
// Try to identify stubs which were effectively copied into the isolate
269+
// instructions section by comparing payloads.
270+
const char* TryIdentifyIsolateSpecificStubCopy(ObjectStore* object_store,
271+
const Code& code) {
272+
#define MATCH(member, name) \
273+
if (object_store->member() != Code::null() && \
274+
StubCode::name().ptr() == object_store->member() && \
275+
StubCode::name().Size() == code.Size() && \
276+
memcmp(reinterpret_cast<void*>(code.PayloadStart()), \
277+
reinterpret_cast<void*>(StubCode::name().PayloadStart()), \
278+
code.Size()) == 0) { \
279+
return "_iso_stub_" #name "Stub"; \
280+
}
281+
OBJECT_STORE_STUB_CODE_LIST(MATCH)
282+
#undef MATCH
283+
284+
return nullptr;
285+
}
286+
} // namespace
287+
265288
void SnapshotAnalyzer::DumpCode(const Code& code) {
266289
js_.PrintProperty("type", "Code");
267290
const auto instruction_base =
268291
reinterpret_cast<uint64_t>(info_.vm_isolate_instructions);
269292

293+
if (code.IsUnknownDartCode()) {
294+
js_.PrintProperty64("offset", 0);
295+
js_.PrintProperty64("size", 0);
296+
js_.PrintProperty("name", "UnknownDartCode");
297+
js_.PrintProperty("section", "_kDartVmSnapshotInstructions");
298+
return;
299+
}
300+
270301
// On different architectures the type of the underlying
271302
// dart::uword can result in an unsigned long long vs unsigned long
272303
// mismatch.
273304
const auto code_addr = static_cast<uint64_t>(code.PayloadStart());
274-
// Invoking code.PayloadStart() for _kDartVmSnapshotInstructions
275-
// when the tree has been shaken always returns 0
276-
if (code_addr == 0) {
277-
js_.PrintProperty64("offset", 0);
278-
js_.PrintProperty64("size", 0);
279-
js_.PrintProperty("section", "_kDartVmSnapshotInstructions");
305+
js_.PrintProperty64("offset", code_addr - instruction_base);
306+
js_.PrintProperty64("size", static_cast<uint64_t>(code.Size()));
307+
js_.PrintProperty("section", "_kDartIsolateSnapshotInstructions");
308+
309+
if (code.owner() != Object::null()) {
310+
const auto& owner = Object::Handle(code.owner());
311+
js_.PrintProperty("owner", GetObjectId(owner.ptr()));
312+
if (owner.IsClass()) {
313+
js_.PrintfProperty("name", "new %s",
314+
Class::Cast(owner).ScrubbedNameCString());
315+
js_.PrintPropertyBool("is_stub", true);
316+
} else if (owner.IsAbstractType()) {
317+
js_.PrintfProperty("name", "as %s",
318+
AbstractType::Cast(owner).ScrubbedNameCString());
319+
js_.PrintPropertyBool("is_stub", true);
320+
} else if (owner.IsFunction()) {
321+
js_.PrintProperty("name", Function::Cast(owner).UserVisibleNameCString());
322+
} else if (owner.IsSmi()) {
323+
// This is a class id of the class which owned the function.
324+
// See Precompiler::DropFunctions.
325+
const auto cid = Smi::Cast(owner).Value();
326+
auto class_table = thread_->isolate_group()->class_table();
327+
if (class_table->IsValidIndex(cid) &&
328+
class_table->At(cid) != Class::null()) {
329+
const auto& cls = Class::Handle(class_table->At(cid));
330+
js_.PrintProperty("owner", GetObjectId(cls.ptr()));
331+
js_.PrintfProperty("name", "unknown function of %s",
332+
Class::Cast(cls).ScrubbedNameCString());
333+
} else {
334+
js_.PrintfProperty("name", "unknown function of class #%" Pd "", cid);
335+
}
336+
} else {
337+
// Expected to handle all possibilities.
338+
UNREACHABLE();
339+
}
280340
} else {
281-
js_.PrintProperty64("offset", code_addr - instruction_base);
282-
js_.PrintProperty64("size", static_cast<uint64_t>(code.Size()));
283-
js_.PrintProperty("section", "_kDartIsolateSnapshotInstructions");
341+
js_.PrintPropertyBool("is_stub", true);
342+
343+
const auto it = stub_names_.find(code.EntryPoint());
344+
if (it != stub_names_.end()) {
345+
js_.PrintProperty("name", it->second);
346+
} else if (auto stub_name = TryIdentifyIsolateSpecificStubCopy(
347+
thread_->isolate_group()->object_store(), code)) {
348+
js_.PrintProperty("name", stub_name);
349+
} else {
350+
UNREACHABLE();
351+
}
284352
}
285353
}
286354

355+
void SnapshotAnalyzer::DumpCode(uword start_pc,
356+
uword end_pc,
357+
const char* name) {
358+
js_.PrintProperty("type", "Code");
359+
const auto instruction_base =
360+
reinterpret_cast<uint64_t>(info_.vm_isolate_instructions);
361+
362+
js_.PrintProperty64("offset",
363+
static_cast<uint64_t>(start_pc) - instruction_base);
364+
js_.PrintProperty64("size", static_cast<uint64_t>(end_pc - start_pc));
365+
js_.PrintProperty("name", name);
366+
js_.PrintProperty("section", "_kDartIsolateSnapshotInstructions");
367+
}
368+
287369
void SnapshotAnalyzer::DumpField(const Field& field) {
288370
const auto& name = String::Handle(field.name());
289371
const auto& type = AbstractType::Handle(field.type());
@@ -383,6 +465,12 @@ void SnapshotAnalyzer::DumpObjectPool(const ObjectPool& pool) {
383465
}
384466

385467
void SnapshotAnalyzer::DumpInterestingObjects() {
468+
// Collect stubs into stub_names to enable quick name lookup
469+
StubCode::ForEachStub([&](const char* name, uword entry_point) {
470+
stub_names_[entry_point] = name;
471+
return true;
472+
});
473+
386474
Zone* zone = thread_->zone();
387475
auto class_table = thread_->isolate_group()->class_table();
388476
class_table->NumCids();
@@ -426,6 +514,29 @@ void SnapshotAnalyzer::DumpInterestingObjects() {
426514
object = class_table->At(cid);
427515
handle_object(object.ptr());
428516
}
517+
518+
// - All instructions tables
519+
const auto& instruction_tables = GrowableObjectArray::Handle(
520+
thread_->isolate_group()->object_store()->instructions_tables());
521+
for (intptr_t i = 0; i < instruction_tables.Length(); i++) {
522+
object = instruction_tables.At(i);
523+
object = InstructionsTable::Cast(object).code_objects();
524+
handle_object(object.ptr());
525+
}
526+
527+
// - All VM stubs
528+
for (intptr_t i = 0; i < StubCode::NumEntries(); i++) {
529+
if (!StubCode::EntryAt(i).IsNull()) {
530+
handle_object(StubCode::EntryAt(i).ptr());
531+
}
532+
}
533+
534+
// - Object store.
535+
//
536+
// This will include a bunch of stuff we don't care about
537+
// but it will also capture things like isolate specific stubs and
538+
// canonicalized types which themselves include references to stubs.
539+
thread_->isolate_group()->object_store()->VisitObjectPointers(&visitor);
429540
}
430541

431542
// Sometimes we have [Field] objects for fields but they are not available
@@ -490,9 +601,28 @@ void SnapshotAnalyzer::DumpInterestingObjects() {
490601
} else if (object->IsInstance()) {
491602
DumpInstance(*object);
492603
}
493-
494604
js_.CloseObject();
495605
}
606+
607+
// Finally dump pseudo-Code objects for all entries in the instructions
608+
// tables without code objects.
609+
uint64_t pseudo_code_id = kStartIndex + discovered_objects.size();
610+
const auto& instruction_tables = GrowableObjectArray::Handle(
611+
thread_->isolate_group()->object_store()->instructions_tables());
612+
auto& instructions_table = InstructionsTable::Handle();
613+
for (intptr_t i = 0; i < instruction_tables.Length(); i++) {
614+
instructions_table ^= instruction_tables.At(i);
615+
for (intptr_t index = 0; index < instructions_table.FirstEntryWithCode();
616+
index++) {
617+
js_.OpenObject();
618+
js_.PrintProperty64("id", pseudo_code_id);
619+
DumpCode(instructions_table.EntryPointAt(index),
620+
instructions_table.EntryPointAt(index + 1), "Unknown Code");
621+
js_.CloseObject();
622+
pseudo_code_id++;
623+
}
624+
}
625+
496626
js_.CloseArray();
497627
}
498628

runtime/vm/compiler/aot/precompiler.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2235,6 +2235,8 @@ void Precompiler::DropFunctions() {
22352235
function.ClearCode();
22362236
// Wrap the owner of the code object in case the code object will be
22372237
// serialized but the function object will not.
2238+
// If you are changing this code you might want to adjust
2239+
// SnapshotAnalyzer::DumpCode which looks at owners.
22382240
owner = code.owner();
22392241
owner = WeakSerializationReference::New(
22402242
owner, Smi::Handle(Smi::New(owner.GetClassId())));

0 commit comments

Comments
 (0)