Add set tracing.
Change-Id: I889c5ac8d6207c9a25721d939b0f01b4e945a940
Reviewed-on: https://dart-review.googlesource.com/c/92149
Commit-Queue: Mayank Patke <fishythefish@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/inferrer/inferrer_engine.dart b/pkg/compiler/lib/src/inferrer/inferrer_engine.dart
index 021979d..4a0ad05 100644
--- a/pkg/compiler/lib/src/inferrer/inferrer_engine.dart
+++ b/pkg/compiler/lib/src/inferrer/inferrer_engine.dart
@@ -33,6 +33,7 @@
import 'locals_handler.dart';
import 'list_tracer.dart';
import 'map_tracer.dart';
+import 'set_tracer.dart';
import 'type_graph_dump.dart';
import 'type_graph_inferrer.dart';
import 'type_graph_nodes.dart';
@@ -78,6 +79,7 @@
void analyze(MemberEntity member);
void analyzeListAndEnqueue(ListTypeInformation info);
+ void analyzeSetAndEnqueue(SetTypeInformation info);
void analyzeMapAndEnqueue(MapTypeInformation info);
/// Notifies to the inferrer that [analyzedElement] can have return type
@@ -441,6 +443,23 @@
workQueue.add(info.elementType);
}
+ void analyzeSetAndEnqueue(SetTypeInformation info) {
+ if (info.analyzed) return;
+ info.analyzed = true;
+
+ SetTracerVisitor tracer = new SetTracerVisitor(info, this);
+ bool succeeded = tracer.run();
+ if (!succeeded) return;
+
+ info.bailedOut = false;
+ info.elementType.inferred = true;
+
+ tracer.assignments.forEach(info.elementType.addAssignment);
+ // Enqueue the set for later refinement.
+ workQueue.add(info);
+ workQueue.add(info.elementType);
+ }
+
void analyzeMapAndEnqueue(MapTypeInformation info) {
if (info.analyzed) return;
info.analyzed = true;
@@ -480,6 +499,11 @@
analyzeListAndEnqueue(info);
});
+ // Try to infer element types of sets and compute their escape information.
+ types.allocatedSets.values.forEach((TypeInformation info) {
+ analyzeSetAndEnqueue(info);
+ });
+
// Try to infer the key and value types for maps and compute the values'
// escape information.
types.allocatedMaps.values.forEach((TypeInformation info) {
@@ -587,6 +611,13 @@
'at ${abstractValueDomain.getAllocationElement(info.originalType)}'
'after ${info.refineCount}');
});
+ types.allocatedSets.values.forEach((_info) {
+ SetTypeInformation info = _info;
+ print('${info.type} '
+ 'for ${abstractValueDomain.getAllocationNode(info.originalType)} '
+ 'at ${abstractValueDomain.getAllocationElement(info.originalType)} '
+ 'after ${info.refineCount}');
+ });
types.allocatedMaps.values.forEach((_info) {
MapTypeInformation info = _info;
print('${info.type} '
@@ -1176,6 +1207,7 @@
generativeConstructorsExposingThis.clear();
types.allocatedMaps.values.forEach(cleanup);
+ types.allocatedSets.values.forEach(cleanup);
types.allocatedLists.values.forEach(cleanup);
_memberData.clear();
diff --git a/pkg/compiler/lib/src/inferrer/node_tracer.dart b/pkg/compiler/lib/src/inferrer/node_tracer.dart
index 81b343f2..0a3d2ee 100644
--- a/pkg/compiler/lib/src/inferrer/node_tracer.dart
+++ b/pkg/compiler/lib/src/inferrer/node_tracer.dart
@@ -51,6 +51,31 @@
'checkGrowable',
]);
+Set<String> doesNotEscapeSetSet = new Set<String>.from(const <String>[
+ // From Object.
+ '==',
+ 'hashCode',
+ 'toString',
+ 'noSuchMethod',
+ 'runtimeType',
+
+ // From Iterable.
+ 'isEmpty',
+ 'isNotEmpty',
+ 'length',
+ 'contains',
+ 'join',
+
+ // From Set.
+ 'add',
+ 'addAll',
+ 'clear',
+ 'containsAll',
+ 'remove',
+ 'removeAll',
+ 'retainAll',
+]);
+
Set<String> doesNotEscapeMapSet = new Set<String>.from(const <String>[
// From Object.
'==',
@@ -142,6 +167,9 @@
while (!listsToAnalyze.isEmpty) {
analyzeStoredIntoList(listsToAnalyze.removeLast());
}
+ while (setsToAnalyze.isNotEmpty) {
+ analyzeStoredIntoSet(setsToAnalyze.removeLast());
+ }
while (!mapsToAnalyze.isEmpty) {
analyzeStoredIntoMap(mapsToAnalyze.removeLast());
}
@@ -244,6 +272,24 @@
}
}
+ void analyzeStoredIntoSet(SetTypeInformation set) {
+ inferrer.analyzeSetAndEnqueue(set);
+ if (set.bailedOut) {
+ bailout('Stored in a set that bailed out');
+ } else {
+ set.flowsInto.forEach((flow) {
+ flow.users.forEach((dynamic user) {
+ if (user.receiver != flow) return;
+ if (user.selector.isIndex) {
+ addNewEscapeInformation(user);
+ } else if (!doesNotEscapeSetSet.contains(user.selector.name)) {
+ bailout('Escape from a set via [${user.selector.name}]');
+ }
+ });
+ });
+ }
+ }
+
void analyzeStoredIntoMap(MapTypeInformation map) {
inferrer.analyzeMapAndEnqueue(map);
if (map.bailedOut) {
diff --git a/pkg/compiler/lib/src/inferrer/set_tracer.dart b/pkg/compiler/lib/src/inferrer/set_tracer.dart
new file mode 100644
index 0000000..6c809ad
--- /dev/null
+++ b/pkg/compiler/lib/src/inferrer/set_tracer.dart
@@ -0,0 +1,131 @@
+// Copyright (c) 2019, 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.
+
+library compiler.src.inferrer.set_tracer;
+
+import '../elements/entities.dart';
+import '../js_backend/backend.dart' show JavaScriptBackend;
+import '../universe/selector.dart' show Selector;
+import 'node_tracer.dart';
+import 'type_graph_nodes.dart';
+
+/// A set of selector names that [Set] implements and which we know do not
+/// change the element type of the set or let the set escape to code that might
+/// change the element type.
+Set<String> okSetSelectorSet = new Set<String>.from(const <String>[
+ // From Object.
+ '==',
+ 'hashCode',
+ 'toString',
+ 'noSuchMethod',
+ 'runtimeType',
+
+ // From Iterable.
+ 'iterator',
+ 'map',
+ 'where',
+ 'expand',
+ 'contains',
+ 'forEach',
+ 'reduce',
+ 'fold',
+ 'every',
+ 'join',
+ 'any',
+ 'toList',
+ 'toSet',
+ 'length',
+ 'isEmpty',
+ 'isNotEmpty',
+ 'take',
+ 'takeWhile',
+ 'skip',
+ 'skipWhile',
+ 'first',
+ 'last',
+ 'single',
+ 'firstWhere',
+ 'lastWhere',
+ 'singleWhere',
+ 'elementAt',
+
+ // From Set.
+ 'clear',
+ 'containsAll',
+ 'difference',
+ 'intersection',
+ 'lookup',
+ 'remove',
+ 'removeAll',
+ 'removeWhere',
+ 'retainAll',
+ 'retainWhere',
+ 'union',
+]);
+
+class SetTracerVisitor extends TracerVisitor {
+ List<TypeInformation> assignments = <TypeInformation>[];
+
+ SetTracerVisitor(tracedType, inferrer) : super(tracedType, inferrer);
+
+ /// Returns [true] if the analysis completed successfully, [false] if it
+ /// bailed out. In the former case, [assignments] holds a list of
+ /// [TypeInformation] nodes that flow into the element type of this set.
+ bool run() {
+ analyze();
+ SetTypeInformation set = tracedType;
+ if (continueAnalyzing) {
+ set.addFlowsIntoTargets(flowsInto);
+ return true;
+ }
+ assignments = null;
+ return false;
+ }
+
+ @override
+ visitClosureCallSiteTypeInformation(ClosureCallSiteTypeInformation info) {
+ bailout('Passed to a closure');
+ }
+
+ @override
+ visitStaticCallSiteTypeInformation(StaticCallSiteTypeInformation info) {
+ super.visitStaticCallSiteTypeInformation(info);
+ MemberEntity called = info.calledElement;
+ if (inferrer.closedWorld.commonElements.isForeign(called) &&
+ called.name == JavaScriptBackend.JS) {
+ bailout('Used in JS ${info.debugName}');
+ }
+ }
+
+ @override
+ visitDynamicCallSiteTypeInformation(DynamicCallSiteTypeInformation info) {
+ super.visitDynamicCallSiteTypeInformation(info);
+ Selector selector = info.selector;
+ String selectorName = selector.name;
+ if (currentUser == info.receiver) {
+ if (!okSetSelectorSet.contains(selectorName)) {
+ if (selector.isCall) {
+ switch (selectorName) {
+ case 'add':
+ assignments.add(info.arguments.positional[0]);
+ break;
+ case 'addAll':
+ // TODO(fishythefish): Extract type argument from type
+ // information.
+ bailout('Adding iterable with unknown typeinfo to current set');
+ break;
+ default:
+ bailout('Set used in a not-ok selector [$selectorName]');
+ }
+ return;
+ }
+ }
+ } else if (selector.isCall &&
+ (info.hasClosureCallTargets ||
+ info.concreteTargets.any((element) => !element.isFunction))) {
+ bailout('Passed to a closure');
+ return;
+ }
+ }
+}
diff --git a/tests/compiler/dart2js/analyses/dart2js_allowed.json b/tests/compiler/dart2js/analyses/dart2js_allowed.json
index 8ebdcf1..e42f240 100644
--- a/tests/compiler/dart2js/analyses/dart2js_allowed.json
+++ b/tests/compiler/dart2js/analyses/dart2js_allowed.json
@@ -271,6 +271,12 @@
"pkg/compiler/lib/src/ssa/value_set.dart": {
"Dynamic invocation of 'add'.": 2
},
+ "pkg/compiler/lib/src/inferrer/node_tracer.dart": {
+ "Dynamic access of 'receiver'.": 1,
+ "Dynamic access of 'selector'.": 3,
+ "Dynamic access of 'isIndex'.": 1,
+ "Dynamic access of 'name'.": 2
+ },
"pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart": {
"Dynamic access of 'keys'.": 1,
"Dynamic invocation of 'toSet'.": 1,