diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json index ef7591c4167..059a1e62afe 100644 --- a/.dart_tool/package_config.json +++ b/.dart_tool/package_config.json @@ -11,7 +11,7 @@ "constraint, update this by running tools/generate_package_config.dart." ], "configVersion": 2, - "generated": "2021-04-13T13:32:11.977579", + "generated": "2021-04-14T10:32:11.514470", "generator": "tools/generate_package_config.dart", "packages": [ { @@ -749,7 +749,7 @@ "name": "vm_snapshot_analysis", "rootUri": "../pkg/vm_snapshot_analysis", "packageUri": "lib/", - "languageVersion": "2.8" + "languageVersion": "2.12" }, { "name": "wasm", diff --git a/pkg/vm_snapshot_analysis/lib/ascii_table.dart b/pkg/vm_snapshot_analysis/lib/ascii_table.dart index a0e54041832..37218146502 100644 --- a/pkg/vm_snapshot_analysis/lib/ascii_table.dart +++ b/pkg/vm_snapshot_analysis/lib/ascii_table.dart @@ -16,7 +16,7 @@ abstract class Row { /// Note: there is a border on the left and right of each column /// plus whitespace around it. static int totalWidth(List widths) => - widths.fold(0, (sum, width) => sum + width + 3) + 1; + widths.fold(0, (sum, width) => sum + width + 3) + 1; } enum Separator { @@ -91,7 +91,7 @@ class Text { final String value; final AlignmentDirection direction; - Text({this.value, this.direction}); + Text({required this.value, required this.direction}); Text.left(String value) : this(value: value, direction: AlignmentDirection.Left); Text.right(String value) @@ -113,7 +113,6 @@ class Text { final diff = width - value.length; return ' ' * (diff ~/ 2) + value + (' ' * (diff - diff ~/ 2)); } - return null; // Make analyzer happy. } int get length => value.length; @@ -126,7 +125,7 @@ class AsciiTable { final List rows = []; - AsciiTable({List header, this.maxWidth = unlimitedWidth}) { + AsciiTable({List? header, this.maxWidth = unlimitedWidth}) { if (header != null) { addSeparator(); addRow(header); diff --git a/pkg/vm_snapshot_analysis/lib/instruction_sizes.dart b/pkg/vm_snapshot_analysis/lib/instruction_sizes.dart index 635f8dd504f..a1db07f09d4 100644 --- a/pkg/vm_snapshot_analysis/lib/instruction_sizes.dart +++ b/pkg/vm_snapshot_analysis/lib/instruction_sizes.dart @@ -40,16 +40,20 @@ class SymbolInfo { /// If this instructions object originated from a function then [libraryUri] /// will contain uri of the library of that function. - final String libraryUri; + final String? libraryUri; /// If this instructions object originated from a function then [className] /// would contain name of the class owning that function. - final String className; + final String? className; /// Size of the instructions object in bytes. final int size; - SymbolInfo({String name, this.libraryUri, this.className, this.size}) + SymbolInfo( + {required String name, + this.libraryUri, + this.className, + required this.size}) : name = Name(name); static SymbolInfo _fromJson(Map map) { @@ -74,37 +78,41 @@ ProgramInfo toProgramInfo(List symbols, final program = ProgramInfo(); for (var sym in symbols) { final scrubbed = sym.name.scrubbed; - if (sym.libraryUri == null) { + final libraryUri = sym.libraryUri; + + // Handle stubs specially. + if (libraryUri == null) { assert(sym.name.isStub); final node = program.makeNode( name: scrubbed, parent: program.stubs, type: NodeType.functionNode); assert(node.size == null || sym.name.isTypeTestingStub); node.size = (node.size ?? 0) + sym.size; - } else { - // Split the name into components (names of individual functions). - final path = sym.name.components; - - var node = program.root; - final package = packageOf(sym.libraryUri); - if (package != sym.libraryUri) { - node = program.makeNode( - name: package, parent: node, type: NodeType.packageNode); - } - node = program.makeNode( - name: sym.libraryUri, parent: node, type: NodeType.libraryNode); - node = program.makeNode( - name: sym.className, parent: node, type: NodeType.classNode); - node = program.makeNode( - name: path.first, parent: node, type: NodeType.functionNode); - for (var name in path.skip(1)) { - if (collapseAnonymousClosures) { - name = Name.collapse(name); - } - node = program.makeNode( - name: name, parent: node, type: NodeType.functionNode); - } - node.size = (node.size ?? 0) + sym.size; + continue; } + + // Split the name into components (names of individual functions). + final path = sym.name.components; + + var node = program.root; + final package = packageOf(libraryUri); + if (package != libraryUri) { + node = program.makeNode( + name: package, parent: node, type: NodeType.packageNode); + } + node = program.makeNode( + name: libraryUri, parent: node, type: NodeType.libraryNode); + node = program.makeNode( + name: sym.className!, parent: node, type: NodeType.classNode); + node = program.makeNode( + name: path.first, parent: node, type: NodeType.functionNode); + for (var name in path.skip(1)) { + if (collapseAnonymousClosures) { + name = Name.collapse(name); + } + node = program.makeNode( + name: name, parent: node, type: NodeType.functionNode); + } + node.size = (node.size ?? 0) + sym.size; } return program; diff --git a/pkg/vm_snapshot_analysis/lib/name.dart b/pkg/vm_snapshot_analysis/lib/name.dart index 3b17bd11190..5d4e569d84b 100644 --- a/pkg/vm_snapshot_analysis/lib/name.dart +++ b/pkg/vm_snapshot_analysis/lib/name.dart @@ -15,9 +15,6 @@ class Name { /// Raw textual representation of the name as it occurred in the output /// of the AOT compiler. final String raw; - String _scrubbed; - - Name(this.raw); /// Pretty version of the name, with some of the irrelevant information /// removed from it. @@ -26,16 +23,18 @@ class Name { /// so we are not removing any details that are used for disambiguation. /// The only exception are type testing stubs, these refer to type names /// and types names are not bound to be unique between compilations. - String get scrubbed => _scrubbed ??= + late final String scrubbed = raw.replaceAll(isStub ? _stubScrubbingRe : _scrubbingRe, ''); + Name(this.raw); + /// Returns true if this name refers to a stub. bool get isStub => raw.startsWith('[Stub] '); /// Returns true if this name refers to an allocation stub. bool get isAllocationStub => raw.startsWith('[Stub] Allocate '); - /// Returns ture if this name refers to a type testing stub. + /// Returns true if this name refers to a type testing stub. bool get isTypeTestingStub => raw.startsWith('[Stub] Type Test '); /// Split this name into individual '.' separated components (e.g. names of diff --git a/pkg/vm_snapshot_analysis/lib/precompiler_trace.dart b/pkg/vm_snapshot_analysis/lib/precompiler_trace.dart index 54d86fe765b..3c9843c249d 100644 --- a/pkg/vm_snapshot_analysis/lib/precompiler_trace.dart +++ b/pkg/vm_snapshot_analysis/lib/precompiler_trace.dart @@ -11,7 +11,8 @@ import 'package:vm_snapshot_analysis/program_info.dart'; /// Build [CallGraph] based on the trace written by `--trace-precompiler-to` /// flag. -CallGraph loadTrace(Object inputJson) => _TraceReader(inputJson).readTrace(); +CallGraph loadTrace(Object inputJson) => + _TraceReader(inputJson as Map).readTrace(); /// [CallGraphNode] represents a node of the call-graph. It can either be: /// @@ -39,7 +40,7 @@ class CallGraphNode { /// Dominator of this node. /// /// Computed by [CallGraph.computeDominators]. - CallGraphNode dominator; + late CallGraphNode dominator; /// Nodes dominated by this node. /// @@ -99,13 +100,13 @@ class CallGraph { // Mapping from [ProgramInfoNode] to a corresponding [CallGraphNode] (if any) // via [ProgramInfoNode.id]. - final List _graphNodeByEntityId; + final List _graphNodeByEntityId; CallGraph._(this.program, this.nodes, this._graphNodeByEntityId); CallGraphNode get root => nodes.first; - CallGraphNode lookup(ProgramInfoNode node) => _graphNodeByEntityId[node.id]; + CallGraphNode lookup(ProgramInfoNode node) => _graphNodeByEntityId[node.id]!; Iterable get dynamicCalls => nodes.where((n) => n.isDynamicCallNode); @@ -113,7 +114,7 @@ class CallGraph { /// Compute a collapsed version of the call-graph, where CallGraph collapse(NodeType type, {bool dropCallNodes = false}) { final graphNodesByData = {}; - final graphNodeByEntityId = []; + final graphNodeByEntityId = []; ProgramInfoNode collapsed(ProgramInfoNode nn) { // Root always collapses onto itself. @@ -127,7 +128,7 @@ class CallGraph { // hitting the root node. var n = nn; while (n.parent != program.root && n.type != type) { - n = n.parent; + n = n.parent!; } return n; } @@ -186,14 +187,14 @@ class CallGraph { /// /// See README.md for description of the format. class _TraceReader { - final List trace; - final List strings; - final List entities; + final List trace; + final List strings; + final List entities; final program = ProgramInfo(); /// Mapping between entity ids and corresponding [ProgramInfoNode] nodes. - final entityById = List.filled(1024, null, growable: true); + final entityById = List.filled(1024, null, growable: true); /// Mapping between functions (represented as [ProgramInfoNode]s) and /// their selector ids. @@ -203,21 +204,22 @@ class _TraceReader { final dynamicFunctions = Set(); _TraceReader(Map data) - : strings = data['strings'], + : strings = (data['strings'] as List).cast(), entities = data['entities'], trace = data['trace']; /// Read all trace events and construct the call graph based on them. CallGraph readTrace() { var pos = 0; // Position in the [trace] array. - CallGraphNode currentNode; + late CallGraphNode currentNode; + int maxId = 0; final nodes = []; - final nodeByEntityId = []; + final nodeByEntityId = []; final callNodesBySelector = {}; final allocated = Set(); - Object next() => trace[pos++]; + T next() => trace[pos++] as T; CallGraphNode makeNode({dynamic data}) { final n = CallGraphNode(nodes.length, data: data); @@ -232,6 +234,9 @@ class _TraceReader { if (nodeByEntityId.length <= n.id) { nodeByEntityId.length = n.id * 2 + 1; } + if (n.id > maxId) { + maxId = n.id; + } return nodeByEntityId[n.id] ??= makeNode(data: n); } @@ -363,10 +368,13 @@ class _TraceReader { /// Read the entity at the given [index] in [entities]. ProgramInfoNode readEntityAt(int index) { final type = entities[index]; + final idx0 = entities[index + 1] as int; + final idx1 = entities[index + 2] as int; + final idx2 = entities[index + 3] as int; switch (type) { case 'C': // Class: 'C', , , 0 - final libraryUri = strings[entities[index + 1]]; - final className = strings[entities[index + 2]]; + final libraryUri = strings[idx0]; + final className = strings[idx1]; return program.makeNode( name: className, @@ -375,9 +383,9 @@ class _TraceReader { case 'S': case 'F': // Function: 'F'|'S', , , - final classNode = getEntityAt(entities[index + 1]); - final functionName = strings[entities[index + 2]]; - final int selectorId = entities[index + 3]; + final classNode = getEntityAt(idx0); + final functionName = strings[idx1]; + final int selectorId = idx2; final path = Name(functionName).rawComponents; if (path.last == 'FfiTrampoline') { @@ -398,8 +406,8 @@ class _TraceReader { return node; case 'V': // Field: 'V', , , 0 - final classNode = getEntityAt(entities[index + 1]); - final fieldName = strings[entities[index + 2]]; + final classNode = getEntityAt(idx0); + final fieldName = strings[idx1]; return program.makeNode( name: fieldName, parent: classNode, type: NodeType.other); diff --git a/pkg/vm_snapshot_analysis/lib/program_info.dart b/pkg/vm_snapshot_analysis/lib/program_info.dart index e24304875e3..cca2bca0cf9 100644 --- a/pkg/vm_snapshot_analysis/lib/program_info.dart +++ b/pkg/vm_snapshot_analysis/lib/program_info.dart @@ -5,8 +5,6 @@ /// Classes for representing information about the program structure. library vm_snapshot_analysis.program_info; -import 'package:meta/meta.dart'; - import 'package:vm_snapshot_analysis/v8_profile.dart'; /// Represents information about compiled program. @@ -22,7 +20,7 @@ class ProgramInfo { /// V8 snapshot profile if this [ProgramInfo] object was created from an /// output of `--write-v8-snapshot-profile-to=...` flag. - SnapshotInfo snapshotInfo; + SnapshotInfo? snapshotInfo; ProgramInfo._(this.root, this.stubs, this.unknown); @@ -45,24 +43,22 @@ class ProgramInfo { } ProgramInfoNode makeNode( - {@required String name, - @required ProgramInfoNode parent, - @required NodeType type}) { - return parent.children.putIfAbsent(name, () { - final node = ProgramInfoNode._( - id: _nextId++, name: name, parent: parent ?? root, type: type); - node.parent.children[name] = node; - return node; - }); + {required String name, + required ProgramInfoNode parent, + required NodeType type}) { + return parent.children.putIfAbsent( + name, + () => ProgramInfoNode._( + id: _nextId++, name: name, parent: parent, type: type)); } /// Recursively visit all function nodes, which have [FunctionInfo.info] /// populated. void visit( - void Function( - String pkg, String lib, String cls, String fun, ProgramInfoNode n) + void Function(String? pkg, String lib, String? cls, String? fun, + ProgramInfoNode n) callback) { - final context = List.filled(NodeType.values.length, null); + final context = List.filled(NodeType.values.length, null); void recurse(ProgramInfoNode node) { final prevContext = context[node._type]; @@ -72,11 +68,11 @@ class ProgramInfo { context[node._type] = node.name; } - final String pkg = context[NodeType.packageNode.index]; - final String lib = context[NodeType.libraryNode.index]; - final String cls = context[NodeType.classNode.index]; - final String mem = context[NodeType.functionNode.index]; - callback(pkg, lib, cls, mem, node); + final pkg = context[NodeType.packageNode.index]; + final lib = context[NodeType.libraryNode.index]; + final cls = context[NodeType.classNode.index]; + final mem = context[NodeType.functionNode.index]; + callback(pkg, lib!, cls, mem, node); for (var child in node.children.values) { recurse(child); @@ -96,12 +92,14 @@ class ProgramInfo { Map toJson() => root.toJson(); /// Lookup a node in the program given a path to it. - ProgramInfoNode lookup(List path) { + ProgramInfoNode? lookup(List path) { var n = root; for (var p in path) { - if ((n = n.children[p]) == null) { - break; + final next = n.children[p]; + if (next == null) { + return null; } + n = next; } return n; } @@ -121,12 +119,12 @@ String _typeToJson(NodeType type) => const { NodeType.classNode: 'class', NodeType.functionNode: 'function', NodeType.other: 'other', - }[type]; + }[type]!; class ProgramInfoNode { final int id; final String name; - final ProgramInfoNode parent; + final ProgramInfoNode? parent; final Map children = {}; final int _type; @@ -151,13 +149,13 @@ class ProgramInfoNode { /// Assuming that both `C.f` and `C.g` are included into AOT snapshot /// and string `"something"` does not occur anywhere else in the program /// then the size of `"something"` is going to be attributed to `C`. - int size; + int? size; ProgramInfoNode._( - {@required this.id, - @required this.name, - @required this.parent, - @required NodeType type}) + {required this.id, + required this.name, + required this.parent, + required NodeType type}) : _type = type.index; NodeType get type => NodeType.values[_type]; @@ -175,9 +173,10 @@ class ProgramInfoNode { var prefix = ''; // Do not include root name or package name (library uri already contains // package name). - if (parent?.parent != null && parent?.type != NodeType.packageNode) { - prefix = parent.qualifiedName; - if (parent.type != NodeType.libraryNode) { + final p = parent; + if (p != null && p.parent != null && p.type != NodeType.packageNode) { + prefix = p.qualifiedName; + if (p.type != NodeType.libraryNode) { prefix += '.'; } else { prefix += '::'; @@ -198,7 +197,7 @@ class ProgramInfoNode { var n = this; while (n.parent != null) { result.add(n.name); - n = n.parent; + n = n.parent!; } return result.reversed.toList(); } @@ -215,24 +214,24 @@ ProgramInfo computeDiff(ProgramInfo oldInfo, ProgramInfo newInfo) { final programDiff = ProgramInfo(); var path = []; - void recurse(ProgramInfoNode oldNode, ProgramInfoNode newNode) { + void recurse(ProgramInfoNode? oldNode, ProgramInfoNode? newNode) { if (oldNode?.size != newNode?.size) { var diffNode = programDiff.root; for (var i = 0; i < path.length; i += 2) { - final name = path[i]; - final type = path[i + 1]; + final name = path[i] as String; + final type = path[i + 1] as NodeType; diffNode = programDiff.makeNode(name: name, parent: diffNode, type: type); } - diffNode.size ??= 0; - diffNode.size += (newNode?.size ?? 0) - (oldNode?.size ?? 0); + diffNode.size = + (diffNode.size ?? 0) + (newNode?.size ?? 0) - (oldNode?.size ?? 0); } for (var key in _allKeys(newNode?.children, oldNode?.children)) { final newChildNode = newNode != null ? newNode.children[key] : null; final oldChildNode = oldNode != null ? oldNode.children[key] : null; path.add(key); - path.add(oldChildNode?.type ?? newChildNode?.type); + path.add((oldChildNode?.type ?? newChildNode?.type)!); recurse(oldChildNode, newChildNode); path.removeLast(); path.removeLast(); @@ -244,7 +243,7 @@ ProgramInfo computeDiff(ProgramInfo oldInfo, ProgramInfo newInfo) { return programDiff; } -Iterable _allKeys(Map a, Map b) { +Iterable _allKeys(Map? a, Map? b) { return {...?a?.keys, ...?b?.keys}; } @@ -267,14 +266,14 @@ class Histogram { Histogram._(this.bucketInfo, this.buckets) : bySize = buckets.keys.toList(growable: false) - ..sort((a, b) => buckets[b] - buckets[a]), + ..sort((a, b) => buckets[b]! - buckets[a]!), totalSize = buckets.values.fold(0, (sum, size) => sum + size); static Histogram fromIterable( Iterable entries, { - @required int Function(T) sizeOf, - @required String Function(T) bucketFor, - @required BucketInfo bucketInfo, + required int Function(T) sizeOf, + required String Function(T) bucketFor, + required BucketInfo bucketInfo, }) { final buckets = {}; @@ -290,20 +289,26 @@ class Histogram { /// Rebuckets the histogram given the new bucketing rule. Histogram map(String Function(String) bucketFor) { return Histogram.fromIterable(buckets.keys, - sizeOf: (key) => buckets[key], + sizeOf: (key) => buckets[key]!, bucketFor: bucketFor, bucketInfo: bucketInfo); } } /// Construct the histogram of specific [type] given a [ProgramInfo]. +/// +/// [filter] glob can be provided to skip some of the nodes in the [info]: +/// a string is created which contains library name, class name and function +/// name for the given node and if this string does not match the [filter] +/// glob then this node is ignored. Histogram computeHistogram(ProgramInfo info, HistogramType type, - {String filter}) { - bool Function(String, String, String) matchesFilter; + {String? filter}) { + bool Function(String, String?, String?) matchesFilter; if (filter != null) { final re = RegExp(filter.replaceAll('*', '.*'), caseSensitive: false); - matchesFilter = (lib, cls, fun) => re.hasMatch("${lib}::${cls}.${fun}"); + matchesFilter = + (lib, cls, fun) => re.hasMatch("${lib}::${cls ?? ''}.${fun ?? ''}"); } else { matchesFilter = (_, __, ___) => true; } @@ -318,10 +323,12 @@ Histogram computeHistogram(ProgramInfo info, HistogramType type, }); } + final snapshotInfo = info.snapshotInfo!; + return Histogram.fromIterable( - info.snapshotInfo.snapshot.nodes.where((n) => + snapshotInfo.snapshot.nodes.where((n) => filter == null || - filteredNodes.contains(info.snapshotInfo.ownerOf(n).id)), + filteredNodes.contains(snapshotInfo.ownerOf(n).id)), sizeOf: (n) { return n.selfSize; }, @@ -329,17 +336,18 @@ Histogram computeHistogram(ProgramInfo info, HistogramType type, bucketInfo: const BucketInfo(nameComponents: ['Type'])); } else { final buckets = {}; - final bucketing = Bucketing._forType[type]; + final bucketing = Bucketing._forType[type]!; info.visit((pkg, lib, cls, fun, node) { - if (node.size == null || node.size == 0) { + final sz = node.size; + if (sz == null || sz == 0) { return; } if (!matchesFilter(lib, cls, fun)) { return; } final bucket = bucketing.bucketFor(pkg, lib, cls, fun); - buckets[bucket] = (buckets[bucket] ?? 0) + node.size; + buckets[bucket] = (buckets[bucket] ?? 0) + sz; }); return Histogram._(bucketing, buckets); @@ -363,15 +371,15 @@ class BucketInfo { /// one returned by [nameComponents]). List namesFromBucket(String bucket) => [bucket]; - const BucketInfo({@required this.nameComponents}); + const BucketInfo({required this.nameComponents}); } abstract class Bucketing extends BucketInfo { /// Constructs the bucket name from the given library name [lib], class name /// [cls] and function name [fun]. - String bucketFor(String pkg, String lib, String cls, String fun); + String bucketFor(String? pkg, String lib, String? cls, String? fun); - const Bucketing({@required List nameComponents}) + const Bucketing({required List nameComponents}) : super(nameComponents: nameComponents); static const _forType = { @@ -387,11 +395,11 @@ const String _nameSeparator = ';;;'; class _BucketBySymbol extends Bucketing { @override - String bucketFor(String pkg, String lib, String cls, String fun) { + String bucketFor(String? pkg, String lib, String? cls, String? fun) { if (fun == null) { return '@other${_nameSeparator}'; } - return '$lib${_nameSeparator}${cls}${cls != '' ? '.' : ''}${fun}'; + return '$lib${_nameSeparator}${cls ?? ''}${cls != '' && cls != null ? '.' : ''}${fun}'; } @override @@ -402,7 +410,7 @@ class _BucketBySymbol extends Bucketing { class _BucketByClass extends Bucketing { @override - String bucketFor(String pkg, String lib, String cls, String fun) { + String bucketFor(String? pkg, String lib, String? cls, String? fun) { if (cls == null) { return '@other${_nameSeparator}'; } @@ -417,14 +425,14 @@ class _BucketByClass extends Bucketing { class _BucketByLibrary extends Bucketing { @override - String bucketFor(String pkg, String lib, String cls, String fun) => '$lib'; + String bucketFor(String? pkg, String lib, String? cls, String? fun) => '$lib'; const _BucketByLibrary() : super(nameComponents: const ['Library']); } class _BucketByPackage extends Bucketing { @override - String bucketFor(String pkg, String lib, String cls, String fun) => + String bucketFor(String? pkg, String lib, String? cls, String? fun) => pkg ?? lib; const _BucketByPackage() : super(nameComponents: const ['Package']); diff --git a/pkg/vm_snapshot_analysis/lib/src/commands/compare.dart b/pkg/vm_snapshot_analysis/lib/src/commands/compare.dart index 4a9442e01c7..bb2bdaff248 100644 --- a/pkg/vm_snapshot_analysis/lib/src/commands/compare.dart +++ b/pkg/vm_snapshot_analysis/lib/src/commands/compare.dart @@ -64,23 +64,25 @@ precisely based on their source position (which is included in their name). @override Future run() async { - if (argResults.rest.length != 2) { + final args = argResults!; + + if (args.rest.length != 2) { usageException('Need to provide path to old.json and new.json reports.'); } - final columnWidth = argResults['column-width']; + final columnWidth = args['column-width']; final maxWidth = int.tryParse(columnWidth); if (maxWidth == null) { usageException( 'Specified column width (${columnWidth}) is not an integer'); } - final oldJsonPath = _checkExists(argResults.rest[0]); - final newJsonPath = _checkExists(argResults.rest[1]); + final oldJsonPath = _checkExists(args.rest[0]); + final newJsonPath = _checkExists(args.rest[1]); printComparison(oldJsonPath, newJsonPath, maxWidth: maxWidth, - granularity: _parseHistogramType(argResults['by']), - collapseAnonymousClosures: argResults['collapse-anonymous-closures']); + granularity: _parseHistogramType(args['by']), + collapseAnonymousClosures: args['collapse-anonymous-closures']); } HistogramType _parseHistogramType(String value) { @@ -93,8 +95,9 @@ precisely based on their source position (which is included in their name). return HistogramType.byLibrary; case 'package': return HistogramType.byPackage; + default: + usageException('Unrecognized histogram type $value'); } - return null; } File _checkExists(String path) { @@ -136,10 +139,10 @@ precisely based on their source position (which is included in their name). printHistogram(diff, histogram, sizeHeader: 'Diff (Bytes)', prefix: histogram.bySize - .where((k) => histogram.buckets[k] > 0) + .where((k) => histogram.buckets[k]! > 0) .take(numLargerSymbolsToReport), suffix: histogram.bySize.reversed - .where((k) => histogram.buckets[k] < 0) + .where((k) => histogram.buckets[k]! < 0) .take(numSmallerSymbolsToReport) .toList() .reversed, @@ -159,7 +162,7 @@ precisely based on their source position (which is included in their name). final newTypeHistogram = computeHistogram(newSizes, HistogramType.byNodeType); - final diffTypeHistogram = Histogram.fromIterable( + final diffTypeHistogram = Histogram.fromIterable( Set() ..addAll(oldTypeHistogram.buckets.keys) ..addAll(newTypeHistogram.buckets.keys), diff --git a/pkg/vm_snapshot_analysis/lib/src/commands/explain.dart b/pkg/vm_snapshot_analysis/lib/src/commands/explain.dart index 33c141803b7..e81833f4802 100644 --- a/pkg/vm_snapshot_analysis/lib/src/commands/explain.dart +++ b/pkg/vm_snapshot_analysis/lib/src/commands/explain.dart @@ -53,13 +53,15 @@ precompiler trace (an output of --trace-precompiler-to flag). @override Future run() async { - final sizesJson = File(argResults.rest[0]); + final args = argResults!; + + final sizesJson = File(args.rest[0]); if (!sizesJson.existsSync()) { usageException('Size profile ${sizesJson.path} does not exist!'); } final sizesJsonRaw = await loadJsonFromFile(sizesJson); - final traceJson = File(argResults.rest[1]); + final traceJson = File(args.rest[1]); if (!traceJson.existsSync()) { usageException('Size profile ${traceJson.path} does not exist!'); } @@ -103,7 +105,7 @@ precompiler trace (an output of --trace-precompiler-to flag). }, bucketInfo: BucketInfo(nameComponents: ['Selector'])); printHistogram(programInfo, histogram, - prefix: histogram.bySize.where((key) => histogram.buckets[key] > 0)); + prefix: histogram.bySize.where((key) => histogram.buckets[key]! > 0)); // For top 10 dynamic selectors print the functions which contain these // dynamic calls. diff --git a/pkg/vm_snapshot_analysis/lib/src/commands/summary.dart b/pkg/vm_snapshot_analysis/lib/src/commands/summary.dart index 13854d29a3b..90b87b9c0b7 100644 --- a/pkg/vm_snapshot_analysis/lib/src/commands/summary.dart +++ b/pkg/vm_snapshot_analysis/lib/src/commands/summary.dart @@ -12,7 +12,6 @@ import 'dart:io'; import 'dart:math' as math; import 'package:args/command_runner.dart'; -import 'package:meta/meta.dart'; import 'package:vm_snapshot_analysis/ascii_table.dart'; import 'package:vm_snapshot_analysis/precompiler_trace.dart'; @@ -95,18 +94,20 @@ precisely based on their source position (which is included in their name). @override Future run() async { - if (argResults.rest.length != 1) { + final args = argResults!; + + if (args.rest.length != 1) { usageException('Need to specify input JSON.'); } - final input = File(argResults.rest[0]); + final input = File(args.rest[0]); if (!input.existsSync()) { usageException('Input file ${input.path} does not exist!'); } - final granularity = _parseHistogramType(argResults['by']); + final granularity = _parseHistogramType(args['by']); - final traceJson = argResults['precompiler-trace']; + final traceJson = args['precompiler-trace']; if (traceJson != null) { if (!File(traceJson).existsSync()) { usageException('Trace ${traceJson} does not exist!'); @@ -119,21 +120,21 @@ precisely based on their source position (which is included in their name). } } - final columnWidth = argResults['column-width']; + final columnWidth = args['column-width']; final maxWidth = int.tryParse(columnWidth); if (maxWidth == null) { usageException( 'Specified column width (${columnWidth}) is not an integer'); } - final depsStartDepthStr = argResults['deps-start-depth']; + final depsStartDepthStr = args['deps-start-depth']; final depsStartDepth = int.tryParse(depsStartDepthStr); if (depsStartDepth == null) { usageException('Specified depsStartDepth (${depsStartDepthStr})' ' is not an integer'); } - final depsDisplayDepthStr = argResults['deps-display-depth']; + final depsDisplayDepthStr = args['deps-display-depth']; final depsDisplayDepth = int.tryParse(depsDisplayDepthStr); if (depsDisplayDepth == null) { usageException('Specified depsDisplayDepth (${depsStartDepthStr})' @@ -143,14 +144,14 @@ precisely based on their source position (which is included in their name). await outputSummary(input, maxWidth: maxWidth, granularity: granularity, - collapseAnonymousClosures: argResults['collapse-anonymous-closures'], - filter: argResults['where'], + collapseAnonymousClosures: args['collapse-anonymous-closures'], + filter: args['where'], traceJson: traceJson != null ? File(traceJson) : null, depsStartDepth: depsStartDepth, depsDisplayDepth: depsDisplayDepth); } - static HistogramType _parseHistogramType(String value) { + HistogramType _parseHistogramType(String value) { switch (value) { case 'method': return HistogramType.bySymbol; @@ -160,17 +161,18 @@ precisely based on their source position (which is included in their name). return HistogramType.byLibrary; case 'package': return HistogramType.byPackage; + default: + usageException('Unrecognized histogram type $value'); } - return null; } } -void outputSummary(File input, +Future outputSummary(File input, {int maxWidth = 0, bool collapseAnonymousClosures = false, HistogramType granularity = HistogramType.bySymbol, - String filter, - File traceJson, + String? filter, + File? traceJson, int depsStartDepth = 2, int depsDisplayDepth = 4, int topToReport = 30}) async { @@ -182,7 +184,7 @@ void outputSummary(File input, // If precompiler trace is provided, collapse entries based on the dependency // graph (dominator tree) extracted from the trace. - void Function() printDependencyTrees; + void Function()? printDependencyTrees; if (traceJson != null && (granularity == HistogramType.byLibrary || granularity == HistogramType.byPackage)) { @@ -235,8 +237,8 @@ void outputSummary(File input, var totalCount = 1; for (var n in node.dominated) { computeTotalsRecursively(n); - totalSize += totalSizes[n.data.name]; - totalCount += totalCounts[n.data.name]; + totalSize += totalSizes[n.data.name]!; + totalCount += totalCounts[n.data.name]!; } totalSizes[node.data.name] = totalSize; totalCounts[node.data.name] = totalCount; @@ -255,7 +257,7 @@ void outputSummary(File input, final collapsedEntries = histogram.bySize .take(topToReport) .map((k) => collapsed[k]) - .where((n) => n != null); + .whereType(); if (collapsedEntries.isNotEmpty) { print('\bDependency trees:'); for (var n in collapsedEntries) { @@ -302,9 +304,9 @@ void outputSummary(File input, void _printDominatedNodes(CallGraphNode node, {int displayDepth = 4, int maxChildrenToPrint = 10, - List isLastPerLevel, - @required Map totalSizes, - @required Map totalCounts}) { + List? isLastPerLevel, + required Map totalSizes, + required Map totalCounts}) { isLastPerLevel ??= []; // Subtract one to account for the parent node that is printed before the @@ -315,8 +317,8 @@ void _printDominatedNodes(CallGraphNode node, final sizes = node.dominated.map((n) => totalSizes[n.data.name]).toList(); final order = List.generate(node.dominated.length, (i) => i) - ..sort((a, b) => sizes[b] - sizes[a]); - final lastIndex = order.lastIndexWhere((i) => sizes[i] > 0); + ..sort((a, b) => sizes[b]! - sizes[a]!); + final lastIndex = order.lastIndexWhere((i) => sizes[i]! > 0); for (var j = 0, n = math.min(maxChildrenToPrint - 1, lastIndex); j <= n; @@ -339,7 +341,7 @@ void _printDominatedNodes(CallGraphNode node, if (maxChildrenToPrint < lastIndex) { isLastPerLevel.add(true); print( - '${_treeLines(isLastPerLevel)} ... (+${totalCounts[node.data.name] - 1} deps)'); + '${_treeLines(isLastPerLevel)} ... (+${totalCounts[node.data.name]! - 1} deps)'); isLastPerLevel.removeLast(); } } diff --git a/pkg/vm_snapshot_analysis/lib/src/commands/treemap.dart b/pkg/vm_snapshot_analysis/lib/src/commands/treemap.dart index 059d3532e47..2de0694dd55 100644 --- a/pkg/vm_snapshot_analysis/lib/src/commands/treemap.dart +++ b/pkg/vm_snapshot_analysis/lib/src/commands/treemap.dart @@ -56,24 +56,27 @@ viewed in a browser: @override Future run() async { - if (argResults.rest.length != 2) { + final args = argResults!; + + if (args.rest.length != 2) { usageException('Need to specify input JSON and output directory.'); } - final input = File(argResults.rest[0]); - final outputDir = Directory(argResults.rest[1]); + final input = File(args.rest[0]); + final outputDir = Directory(args.rest[1]); if (!input.existsSync()) { usageException('Input file ${input.path} does not exist!'); } - if (outputDir.existsSync() && !argResults['force']) { + if (outputDir.existsSync() && !args['force']) { usageException( 'Output directory ${outputDir.path} already exists, specify --force to ignore.'); } await generateTreeMap(input, outputDir, - format: _formatFromString[argResults['format']]); + format: _formatFromString[args['format']] ?? + (throw 'Unrecognized format ${args['format']}')); } // Note: the first key in this map is the default format. @@ -95,8 +98,8 @@ Future generateTreeMap(File input, Directory outputDir, // Create output directory and copy all auxiliary files from binary_size tool. await outputDir.create(recursive: true); - final assetsUri = await Isolate.resolvePackageUri( - Uri.parse('package:vm_snapshot_analysis/src/assets')); + final assetsUri = (await Isolate.resolvePackageUri( + Uri.parse('package:vm_snapshot_analysis/src/assets')))!; final assetsDir = assetsUri.toFilePath(); final d3SrcDir = p.join(assetsDir, 'd3', 'src'); @@ -114,7 +117,7 @@ Future generateTreeMap(File input, Directory outputDir, final dataJsPath = p.join(outputDir.path, 'data.js'); final sink = File(dataJsPath).openWrite(); sink.write('var tree_data='); - await sink.addStream(Stream.fromIterable([tree]) + await sink.addStream(Stream.fromIterable([tree]) .transform(json.encoder.fuse(utf8.encoder))); await sink.close(); diff --git a/pkg/vm_snapshot_analysis/lib/src/commands/utils.dart b/pkg/vm_snapshot_analysis/lib/src/commands/utils.dart index f6a5bbe2f71..c73dab8b424 100644 --- a/pkg/vm_snapshot_analysis/lib/src/commands/utils.dart +++ b/pkg/vm_snapshot_analysis/lib/src/commands/utils.dart @@ -6,9 +6,9 @@ import 'dart:convert'; import 'dart:io'; Future loadJsonFromFile(File input) async { - return await input + return (await input .openRead() .transform(utf8.decoder) .transform(json.decoder) - .first; + .first)!; } diff --git a/pkg/vm_snapshot_analysis/lib/src/dominators.dart b/pkg/vm_snapshot_analysis/lib/src/dominators.dart index 5e9078f5921..92504f3dbb8 100644 --- a/pkg/vm_snapshot_analysis/lib/src/dominators.dart +++ b/pkg/vm_snapshot_analysis/lib/src/dominators.dart @@ -13,16 +13,16 @@ import 'dart:math' as math; /// native compiler (see runtime/vm/compiler/backend/flow_graph.cc). @pragma('vm:prefer-inline') List computeDominators({ - int size, - int root, - Iterable succ(int n), - Iterable predOf(int n), - void handleEdge(int from, int to), + required int size, + required int root, + required Iterable Function(int) succ, + required Iterable Function(int) predOf, + required void handleEdge(int from, int to), }) { // Compute preorder numbering for the graph using DFS. final parent = List.filled(size, -1); - final preorder = List.filled(size, null); - final preorderNumber = List.filled(size, null); + final preorder = List.filled(size, -1); + final preorderNumber = List.filled(size, -1); var N = 0; void dfs() { @@ -32,7 +32,7 @@ List computeDominators({ final p = s.p; final n = s.n; handleEdge(s.n, s.p); - if (preorderNumber[n] == null) { + if (preorderNumber[n] == -1) { preorderNumber[n] = N; preorder[preorderNumber[n]] = n; parent[preorderNumber[n]] = p; @@ -122,5 +122,5 @@ List computeDominators({ class _DfsState { final int p; final int n; - _DfsState({this.p, this.n}); + _DfsState({required this.p, required this.n}); } diff --git a/pkg/vm_snapshot_analysis/lib/treemap.dart b/pkg/vm_snapshot_analysis/lib/treemap.dart index c0535e7a6c8..2d6da720ad8 100644 --- a/pkg/vm_snapshot_analysis/lib/treemap.dart +++ b/pkg/vm_snapshot_analysis/lib/treemap.dart @@ -82,10 +82,11 @@ Map treemapFromJson(Object inputJson, final root = {'n': '', 'children': {}, 'k': kindPath, 'maxDepth': 0}; if (v8_profile.Snapshot.isV8HeapSnapshot(inputJson)) { - _treemapFromSnapshot(root, v8_profile.Snapshot.fromJson(inputJson), + _treemapFromSnapshot( + root, v8_profile.Snapshot.fromJson(inputJson as Map), format: format); } else { - final symbols = instruction_sizes.fromJson(inputJson); + final symbols = instruction_sizes.fromJson(inputJson as List); for (var symbol in symbols) { _addSymbol(root, _treePath(symbol), symbol.name.scrubbed, symbol.size); } @@ -163,20 +164,22 @@ void _treemapFromSnapshot(Map root, v8_profile.Snapshot snap, return; } + final snapshotInfo = info.snapshotInfo!; + final ownerPathCache = - List.filled(info.snapshotInfo.infoNodes.length, null); + List.filled(snapshotInfo.infoNodes.length, null); ownerPathCache[info.root.id] = info.root.name; String ownerPath(ProgramInfoNode n) { - return ownerPathCache[n.id] ??= - ((n.parent != info.root) ? '${ownerPath(n.parent)}/${n.name}' : n.name); + return ownerPathCache[n.id] ??= ((n.parent != info.root) + ? '${ownerPath(n.parent!)}/${n.name}' + : n.name); } - final nameFormatter = _nameFormatters[format]; - + final nameFormatter = _nameFormatters[format]!; for (var node in snap.nodes) { if (node.selfSize > 0) { - final owner = info.snapshotInfo.ownerOf(node); + final owner = snapshotInfo.ownerOf(node); final name = nameFormatter(node); final path = ownerPath(owner); @@ -227,7 +230,7 @@ Map _addChild( } /// Add the given symbol to the tree. -void _addSymbol(Map root, String path, String name, int size, +void _addSymbol(Map root, String path, String name, int? size, {String symbolType = symbolTypeGlobalText}) { if (size == null || size == 0) { return; diff --git a/pkg/vm_snapshot_analysis/lib/utils.dart b/pkg/vm_snapshot_analysis/lib/utils.dart index abf3eb32ca1..40ef457d4d9 100644 --- a/pkg/vm_snapshot_analysis/lib/utils.dart +++ b/pkg/vm_snapshot_analysis/lib/utils.dart @@ -13,10 +13,11 @@ import 'package:vm_snapshot_analysis/v8_profile.dart' as v8_profile; ProgramInfo loadProgramInfoFromJson(Object json, {bool collapseAnonymousClosures = false}) { if (v8_profile.Snapshot.isV8HeapSnapshot(json)) { - return v8_profile.toProgramInfo(v8_profile.Snapshot.fromJson(json), + return v8_profile.toProgramInfo( + v8_profile.Snapshot.fromJson(json as Map), collapseAnonymousClosures: collapseAnonymousClosures); } else { - return instruction_sizes.loadProgramInfo(json, + return instruction_sizes.loadProgramInfo(json as List, collapseAnonymousClosures: collapseAnonymousClosures); } } @@ -62,7 +63,7 @@ void printHistogram(ProgramInfo info, Histogram histogram, final visibleRows = [prefix, suffix].expand((l) => l).toList(); final visibleSize = - visibleRows.fold(0, (sum, key) => sum + histogram.buckets[key]); + visibleRows.fold(0, (sum, key) => sum + histogram.buckets[key]!); final numRestRows = histogram.length - (suffix.length + prefix.length); final hiddenRows = Set.from(histogram.bySize) .difference(Set.from(visibleRows)); @@ -71,7 +72,7 @@ void printHistogram(ProgramInfo info, Histogram histogram, if (prefix.isNotEmpty) { for (var key in prefix) { - final size = histogram.buckets[key]; + final size = histogram.buckets[key]!; table.addRow([ ...histogram.bucketInfo.namesFromBucket(key), size.toString(), @@ -99,7 +100,7 @@ void printHistogram(ProgramInfo info, Histogram histogram, table.addRow([ ...histogram.bucketInfo.namesFromBucket(key), histogram.buckets[key].toString(), - formatPercent(histogram.buckets[key], histogram.totalSize), + formatPercent(histogram.buckets[key]!, histogram.totalSize), ]); } table.addSeparator(Separator.Line); diff --git a/pkg/vm_snapshot_analysis/lib/v8_profile.dart b/pkg/vm_snapshot_analysis/lib/v8_profile.dart index cbbe717a474..7dde86778d3 100644 --- a/pkg/vm_snapshot_analysis/lib/v8_profile.dart +++ b/pkg/vm_snapshot_analysis/lib/v8_profile.dart @@ -6,7 +6,7 @@ /// produced by `--write-v8-snapshot-profile-to` VM flag. library vm_snapshot_analysis.v8_profile; -import 'package:meta/meta.dart'; +import 'package:collection/collection.dart'; import 'package:vm_snapshot_analysis/src/dominators.dart' as dominators; import 'package:vm_snapshot_analysis/name.dart'; @@ -35,7 +35,7 @@ class Snapshot { /// for the given node index. final List _edgesStartIndexForNode; - List _dominators; + late final List _dominators = _computeDominators(this); final List strings; @@ -53,7 +53,6 @@ class Snapshot { /// Return dominator node for the given node [n]. Node dominatorOf(Node n) { - _dominators ??= _computeDominators(this); return nodeAt(_dominators[n.index]); } @@ -67,7 +66,7 @@ class Snapshot { // Extract meta information first. final meta = Meta._fromJson(m['snapshot']['meta']); - final nodes = m['nodes']; + final nodes = (m['nodes'] as List).cast(); // Build an array of starting indexes of edges for each node. final edgesStartIndexForNode = [0]; @@ -130,18 +129,18 @@ class Meta { final List edgeTypes; Meta._( - {this.nodeTypeIndex, - this.nodeNameIndex, - this.nodeIdIndex, - this.nodeSelfSizeIndex, - this.nodeEdgeCountIndex, - this.nodeFieldCount, - this.edgeTypeIndex, - this.edgeNameOrIndexIndex, - this.edgeToNodeIndex, - this.edgeFieldCount, - this.nodeTypes, - this.edgeTypes}); + {required this.nodeTypeIndex, + required this.nodeNameIndex, + required this.nodeIdIndex, + required this.nodeSelfSizeIndex, + required this.nodeEdgeCountIndex, + required this.nodeFieldCount, + required this.edgeTypeIndex, + required this.edgeNameOrIndexIndex, + required this.edgeToNodeIndex, + required this.edgeFieldCount, + required this.nodeTypes, + required this.edgeTypes}); factory Meta._fromJson(Map m) { final nodeFields = m['node_fields']; @@ -171,7 +170,7 @@ class Edge { /// Index of this [Edge] within the [snapshot]. final int index; - Edge._({this.snapshot, this.index}); + Edge._({required this.snapshot, required this.index}); String get type => snapshot .meta.edgeTypes[snapshot._edges[_offset + snapshot.meta.edgeTypeIndex]]; @@ -212,7 +211,7 @@ class Node { /// Index of this [Node] within the [snapshot]. final int index; - Node._({this.snapshot, this.index}); + Node._({required this.snapshot, required this.index}); int get edgeCount => snapshot._nodes[_offset + snapshot.meta.nodeEdgeCountIndex]; @@ -248,10 +247,8 @@ class Node { } /// Returns the target of an outgoing edge with the given name (if any). - Node operator [](String edgeName) => this - .edges - .firstWhere((e) => e.name == edgeName, orElse: () => null) - ?.target; + Node? operator [](String edgeName) => + this.edges.firstWhereOrNull((e) => e.name == edgeName)?.target; @override bool operator ==(Object other) { @@ -317,7 +314,7 @@ class _ProgramInfoBuilder { /// See [findCommonAncestor] method. final Map commonAncestorCache = {}; - _ProgramInfoBuilder({this.collapseAnonymousClosures}); + _ProgramInfoBuilder({required this.collapseAnonymousClosures}); /// Recover [ProgramInfo] structure from the snapshot profile. /// @@ -372,7 +369,7 @@ class _ProgramInfoBuilder { return program; } - ProgramInfoNode getInfoNodeFor(Node node) { + ProgramInfoNode? getInfoNodeFor(Node node) { var info = infoNodeByIndex[node.index]; if (info == null) { info = createInfoNodeFor(node); @@ -390,7 +387,7 @@ class _ProgramInfoBuilder { switch (node.type) { case 'Code': // Freeze ownership of the Instructions object. - final instructions = node['']; + final instructions = node['']!; nodesWithFrozenOwner.add(instructions.index); ownerOf[instructions.index] = findCommonAncestor(ownerOf[instructions.index], info.id); @@ -403,7 +400,7 @@ class _ProgramInfoBuilder { if (e.target.type == 'Script') { nodesWithFrozenOwner.add(e.target.index); ownerOf[e.target.index] = - findCommonAncestor(ownerOf[e.target.index], info.id); + findCommonAncestor(ownerOf[e.target.index]!, info.id); } } } @@ -414,13 +411,13 @@ class _ProgramInfoBuilder { return info; } - ProgramInfoNode createInfoNodeFor(Node node) { + ProgramInfoNode? createInfoNodeFor(Node node) { switch (node.type) { case 'Code': - var owner = node['owner_']; + final owner = node['owner_']!; if (owner.type != 'Type') { final ownerNode = - owner.type == 'Null' ? program.stubs : getInfoNodeFor(owner); + owner.type == 'Null' ? program.stubs : getInfoNodeFor(owner)!; if (owner.type == 'Function') { // For normal functions we just attribute Code object and all // objects dominated by it to the function itself. @@ -436,29 +433,29 @@ class _ProgramInfoBuilder { case 'Function': if (node.name != '') { - var owner = node['owner_']; + var owner = node['owner_']!; // Artificial nodes may not have a data_ field. var data = node['data_']; - if (data?.type == 'ClosureData') { - owner = data['parent_function_']; + if (data != null && data.type == 'ClosureData') { + owner = data['parent_function_']!; } return makeInfoNode(node.index, name: node.name, - parent: getInfoNodeFor(owner), + parent: getInfoNodeFor(owner)!, type: NodeType.functionNode); } break; case 'PatchClass': - return getInfoNodeFor(node['patched_class_']); + return getInfoNodeFor(node['patched_class_']!); case 'Class': // Default to root node. Some builtin classes (void, dynamic) don't have // any information about their library written out. var ownerNode = program.root; if (node['library_'] != null) { - ownerNode = getInfoNodeFor(node['library_']) ?? ownerNode; + ownerNode = getInfoNodeFor(node['library_']!) ?? ownerNode; } return makeInfoNode(node.index, @@ -477,20 +474,16 @@ class _ProgramInfoBuilder { case 'Field': return makeInfoNode(node.index, name: node.name, - parent: getInfoNodeFor(node['owner_']), + parent: getInfoNodeFor(node['owner_']!)!, type: NodeType.other); } return null; } - ProgramInfoNode makeInfoNode(int index, - {@required ProgramInfoNode parent, - @required String name, - @required NodeType type}) { - assert(parent != null, - 'Trying to create node of type ${type} with ${name} and no parent.'); - assert(name != null); - + ProgramInfoNode makeInfoNode(int? index, + {required ProgramInfoNode parent, + required String name, + required NodeType type}) { name = Name(name).scrubbed; if (collapseAnonymousClosures) { name = Name.collapse(name); @@ -527,10 +520,10 @@ class _ProgramInfoBuilder { } /// Returns id of a common ancestor between [ProgramInfoNode] with [idA] and - /// [idB]. - int findCommonAncestor(int idA, int idB) { + /// [idB]. At least either [idA] or [idB] are expected to be not null. + int findCommonAncestor(int? idA, int? idB) { if (idA == null) { - return idB; + return idB!; } if (idB == null) { return idA; @@ -558,9 +551,8 @@ class _ProgramInfoBuilder { static List pathToRoot(ProgramInfoNode node) { final path = []; - while (node != null) { - path.add(node); - node = node.parent; + for (ProgramInfoNode? n = node; n != null; n = n.parent) { + path.add(n); } return path; } @@ -609,14 +601,15 @@ structure nodes (usually VM internal objects). /// The code for dominator tree computation is taken verbatim from the /// native compiler (see runtime/vm/compiler/backend/flow_graph.cc). List _computeDominators(Snapshot snap) { - final predecessors = List.filled(snap.nodeCount, null); + final predecessors = List.filled(snap.nodeCount, null); void addPred(int n, int p) { - if (predecessors[n] == null) { + final pred = predecessors[n]; + if (pred == null) { predecessors[n] = p; - } else if (predecessors[n] is int) { - predecessors[n] = [predecessors[n], p]; + } else if (pred is int) { + predecessors[n] = [pred, p]; } else { - (predecessors[n] as List).add(p); + (pred as List).add(p); } } diff --git a/pkg/vm_snapshot_analysis/pubspec.yaml b/pkg/vm_snapshot_analysis/pubspec.yaml index 5b75e9164e5..3a37d388fef 100644 --- a/pkg/vm_snapshot_analysis/pubspec.yaml +++ b/pkg/vm_snapshot_analysis/pubspec.yaml @@ -5,7 +5,7 @@ version: 0.6.0 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/vm_snapshot_analysis environment: - sdk: '>=2.8.0 <3.0.0' + sdk: '>=2.12.0 <3.0.0' executables: snapshot_analysis: analyse @@ -13,7 +13,7 @@ executables: dependencies: args: ^2.0.0 path: ^1.8.0 - meta: ^1.3.0 + collection: ^1.15.0 dev_dependencies: pedantic: ^1.11.0 diff --git a/pkg/vm_snapshot_analysis/test/instruction_sizes_test.dart b/pkg/vm_snapshot_analysis/test/instruction_sizes_test.dart index 5347acc856c..16b25041642 100644 --- a/pkg/vm_snapshot_analysis/test/instruction_sizes_test.dart +++ b/pkg/vm_snapshot_analysis/test/instruction_sizes_test.dart @@ -5,6 +5,8 @@ import 'dart:io'; import 'package:test/test.dart'; +import 'package:collection/collection.dart'; + import 'package:vm_snapshot_analysis/instruction_sizes.dart' as instruction_sizes; import 'package:vm_snapshot_analysis/program_info.dart'; @@ -242,7 +244,7 @@ void main() async { test('basic-parsing', () async { await withSymbolSizes(testSource, (sizesJson) async { final json = await loadJson(File(sizesJson)); - final symbols = instruction_sizes.fromJson(json); + final symbols = instruction_sizes.fromJson(json as List); expect(symbols, isNotNull, reason: 'Sizes file was successfully parsed'); expect(symbols.length, greaterThan(0), @@ -254,7 +256,7 @@ void main() async { final symbolScrubbedNames = >>{}; Set getSetOfNames(Map>> map, - String libraryUri, String className) { + String? libraryUri, String? className) { return map .putIfAbsent(libraryUri ?? '', () => {}) .putIfAbsent(className ?? '', () => {}); @@ -280,10 +282,12 @@ void main() async { expect(inputDartSymbolNames, isNotNull, reason: 'Symbols from input.dart are included into sizes output'); + inputDartSymbolNames!; // Checked above. Promote the variable type. + expect(inputDartSymbolNames[''], isNotNull, reason: 'Should include top-level members from input.dart'); expect(inputDartSymbolNames[''], contains('makeSomeClosures')); - final closures = inputDartSymbolNames[''].where( + final closures = inputDartSymbolNames['']!.where( (name) => name.startsWith('makeSomeClosures. child.type == NodeType.functionNode) .length, equals(3)); @@ -555,9 +615,9 @@ void main() async { 'Allocate A', '[tear-off-extractor] get:tornOff' ]) { - expect(inputLib.children['A'].children, contains(name)); + expect(inputLib.children['A']!.children, contains(name)); } - expect(inputLib.children['A'].children['tornOff'].children, + expect(inputLib.children['A']!.children['tornOff']!.children, contains('[tear-off] tornOff')); for (var name in [ @@ -565,35 +625,35 @@ void main() async { 'Allocate B', '[tear-off-extractor] get:tornOff' ]) { - expect(inputLib.children['B'].children, contains(name)); + expect(inputLib.children['B']!.children, contains(name)); } - expect(inputLib.children['B'].children['tornOff'].children, + expect(inputLib.children['B']!.children['tornOff']!.children, contains('[tear-off] tornOff')); - final classC = inputLib.children['C']; + final classC = inputLib.children['C']!; expect(classC.children, contains('tornOff')); for (var name in ['tornOff{body}', '[tear-off] tornOff']) { - expect(classC.children['tornOff'].children, contains(name)); + expect(classC.children['tornOff']!.children, contains(name)); } // Verify that [ProgramInfoNode] owns its corresponding snapshot [Node]. - final classesOwnedByC = info.snapshotInfo.snapshot.nodes - .where((n) => info.snapshotInfo.ownerOf(n) == classC) + final classesOwnedByC = info.snapshotInfo!.snapshot.nodes + .where((n) => info.snapshotInfo!.ownerOf(n) == classC) .where((n) => n.type == 'Class') .map((n) => n.name); expect(classesOwnedByC, equals(['C'])); - final classD = inputLib.children['D']; + final classD = inputLib.children['D']!; expect(classD.children, contains('tornOff')); for (var name in ['tornOff{body}', '[tear-off] tornOff']) { - expect(classD.children['tornOff'].children, contains(name)); + expect(classD.children['tornOff']!.children, contains(name)); } - expect(classD.children['tornOff'].children['tornOff{body}'].children, + expect(classD.children['tornOff']!.children['tornOff{body}']!.children, contains('tornOff{body depth 2}')); // Verify that [ProgramInfoNode] owns its corresponding snapshot [Node]. - final classesOwnedByD = info.snapshotInfo.snapshot.nodes - .where((n) => info.snapshotInfo.ownerOf(n) == classD) + final classesOwnedByD = info.snapshotInfo!.snapshot.nodes + .where((n) => info.snapshotInfo!.ownerOf(n) == classD) .where((n) => n.type == 'Class') .map((n) => n.name); expect(classesOwnedByD, equals(['D'])); @@ -616,20 +676,20 @@ void main() async { '::', 'makeSomeClosures' ]); - final codeNode = fromProfile.snapshotInfo.snapshot.nodes.firstWhere( + final codeNode = fromProfile.snapshotInfo!.snapshot.nodes.firstWhere( (n) => n.type == 'Code' && - fromProfile.snapshotInfo.ownerOf(n) == functionNode && + fromProfile.snapshotInfo!.ownerOf(n) == functionNode && n.name.contains('makeSomeClosures')); expect(codeNode[''], isNotNull); - final instructionsSize = codeNode[''].selfSize; + final instructionsSize = codeNode['']!.selfSize; final symbolSize = fromSymbolSizes.lookup([ 'package:input', 'package:input/input.dart', '', 'makeSomeClosures' - ]).size; - expect(instructionsSize - symbolSize, equals(0)); + ])!.size; + expect(instructionsSize - symbolSize!, equals(0)); }); }); }); @@ -786,9 +846,10 @@ void main() async { String nameOf(Map node) => node['n']; - Map findChild(Map node, String name) { + Map? findChild( + Map node, String name) { return childrenOf(node) - .firstWhere((child) => nameOf(child) == name, orElse: () => null); + .firstWhereOrNull((child) => nameOf(child) == name); } Set childrenNames(Map node) { @@ -802,7 +863,7 @@ void main() async { // for some reason. expect(findChild(treemap, 'package:input/input.dart'), isNotNull); } else { - expect(childrenNames(findChild(treemap, 'package:input')), + expect(childrenNames(findChild(treemap, 'package:input')!), equals({'main.dart', 'input.dart'})); } }); @@ -813,7 +874,7 @@ void main() async { // Note: computing dominators also verifies that we don't have // unreachable nodes in the snapshot. final infoJson = await loadJson(File(profileJson)); - final snapshot = Snapshot.fromJson(infoJson); + final snapshot = Snapshot.fromJson(infoJson as Map); for (var n in snapshot.nodes.skip(1)) { expect(snapshot.dominatorOf(n), isNotNull); } @@ -839,7 +900,7 @@ Future withV8Profile( // simply ignore entry point library (main.dart). // Additionally this function removes all nodes with the size below // the given threshold. -Map diffToJson(ProgramInfo diff, +Map? diffToJson(ProgramInfo diff, {bool keepOnlyInputPackage = false}) { final diffJson = diff.toJson(); diffJson.removeWhere((key, _) => @@ -847,7 +908,7 @@ Map diffToJson(ProgramInfo diff, // Rebuild the diff JSON discarding all nodes with size below threshold. const smallChangeThreshold = 16; - Map discardSmallChanges(Map map) { + Map? discardSmallChanges(Map map) { final result = {}; // First recursively process all children (skipping #type and #size keys). diff --git a/pkg/vm_snapshot_analysis/test/precompiler_trace_test.dart b/pkg/vm_snapshot_analysis/test/precompiler_trace_test.dart index 239217127e6..f00e9778e52 100644 --- a/pkg/vm_snapshot_analysis/test/precompiler_trace_test.dart +++ b/pkg/vm_snapshot_analysis/test/precompiler_trace_test.dart @@ -75,7 +75,7 @@ void main() async { callGraph.computeDominators(); final main = callGraph.program - .lookup(['package:input', 'package:input/input.dart', '', 'main']); + .lookup(['package:input', 'package:input/input.dart', '', 'main'])!; final mainNode = callGraph.lookup(main); final retainedClasses = mainNode.dominated diff --git a/pkg/vm_snapshot_analysis/test/utils.dart b/pkg/vm_snapshot_analysis/test/utils.dart index 830ca06492a..1163ae7f2bc 100644 --- a/pkg/vm_snapshot_analysis/test/utils.dart +++ b/pkg/vm_snapshot_analysis/test/utils.dart @@ -24,7 +24,7 @@ class AotSnapshot { final String outputBinary; final String sizesJson; - AotSnapshot({this.outputBinary, this.sizesJson}); + AotSnapshot({required this.outputBinary, required this.sizesJson}); } Future withFlag( @@ -33,7 +33,7 @@ Future withFlag( } Future withFlagImpl( - Map source, String flag, Future Function(AotSnapshot) f) { + Map source, String? flag, Future Function(AotSnapshot) f) { return withTempDir((dir) async { final snapshot = AotSnapshot( outputBinary: path.join(dir, 'output.exe'), @@ -90,7 +90,8 @@ stderr: ${result.stderr} }); } -const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES'; +late final shouldKeepTemporaryDirectories = + Platform.environment['KEEP_TEMPORARY_DIRECTORIES']?.isNotEmpty == true; Future withTempDir(Future Function(String dir) f) async { final tempDir = @@ -98,17 +99,16 @@ Future withTempDir(Future Function(String dir) f) async { try { await f(tempDir.path); } finally { - if (!Platform.environment.containsKey(keepTempKey) || - Platform.environment[keepTempKey].isEmpty) { + if (shouldKeepTemporaryDirectories) { tempDir.deleteSync(recursive: true); } } } Future loadJson(File input) async { - return await input + return (await input .openRead() .transform(utf8.decoder) .transform(json.decoder) - .first; + .first)!; }