Index: tests/compiler/dart2js/inference_stats_test.dart |
diff --git a/tests/compiler/dart2js/inference_stats_test.dart b/tests/compiler/dart2js/inference_stats_test.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e742b0a6616d5cb93afa5696d2736ba1b802b5e5 |
--- /dev/null |
+++ b/tests/compiler/dart2js/inference_stats_test.dart |
@@ -0,0 +1,339 @@ |
+// Copyright (c) 2015, 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. |
+// SharedOptions=-Dsend_stats=true |
+ |
+/// Tests that we compute send metrics correctly in many simple scenarios. |
+library dart2js.test.send_measurements_test; |
+ |
+import 'dart:async'; |
+import 'package:test/test.dart'; |
+import 'package:dart2js_info/info.dart'; |
+import 'memory_compiler.dart'; |
+import 'dart:io'; |
+ |
+main() { |
+ test('nothing is reachable, nothing to count', () { |
+ return _check(''' |
+ main() {} |
+ test() { int x = 3; } |
+ '''); |
+ }); |
+ |
+ test('local variable read', () { |
+ return _check(''' |
+ main() => test(); |
+ test() { int x = 3; int y = x; } |
+ ''', |
+ localSend: 1); // from `int y = x`; |
+ }); |
+ |
+ test('generative constructor call', () { |
+ return _check(''' |
+ class A { |
+ get f => 1; |
+ } |
+ main() => test(); |
+ test() { new A(); } |
+ ''', |
+ constructorSend: 1); // from new A() |
+ }); |
+ |
+ group('instance call', () { |
+ test('monomorphic only one implementor', () { |
+ return _check(''' |
+ class A { |
+ get f => 1; |
+ } |
+ main() => test(); |
+ test() { new A().f; } |
+ ''', |
+ constructorSend: 1, // new A() |
+ instanceSend: 1); // f resolved to A.f |
+ }); |
+ |
+ test('monomorphic only one type possible from types', () { |
+ return _check(''' |
+ class A { |
+ get f => 1; |
+ } |
+ class B extends A { |
+ get f => 1; |
+ } |
+ main() => test(); |
+ test() { new B().f; } |
+ ''', |
+ constructorSend: 1, |
+ instanceSend: 1); // f resolved to B.f |
+ }); |
+ |
+ test('monomorphic only one type possible from liveness', () { |
+ return _check(''' |
+ class A { |
+ get f => 1; |
+ } |
+ class B extends A { |
+ get f => 1; |
+ } |
+ main() => test(); |
+ test() { A x = new B(); x.f; } |
+ ''', |
+ constructorSend: 1, // new B() |
+ localSend: 1, // x in x.f |
+ instanceSend: 1); // x.f known to resolve to B.f |
+ }); |
+ |
+ test('monomorphic one possible, more than one live', () { |
+ return _check(''' |
+ class A { |
+ get f => 1; |
+ } |
+ class B extends A { |
+ get f => 1; |
+ } |
+ main() { new A(); test(); } |
+ test() { B x = new B(); x.f; } |
+ ''', |
+ constructorSend: 1, // new B() |
+ localSend: 1, // x in x.f |
+ instanceSend: 1); // x.f resolves to B.f |
+ }); |
+ |
+ test('polymorphic-virtual couple possible types from liveness', () { |
+ // Note: this would be an instanceSend if we used the inferrer. |
+ return _check(''' |
+ class A { |
+ get f => 1; |
+ } |
+ class B extends A { |
+ get f => 1; |
+ } |
+ main() { new A(); test(); } |
+ test() { A x = new B(); x.f; } |
+ ''', |
+ constructorSend: 1, // new B() |
+ localSend: 1, // x in x.f |
+ virtualSend: 1); // x.f may be A.f or B.f (types alone is not enough) |
+ }); |
+ |
+ test("polymorphic-dynamic: type annotations don't help", () { |
+ return _check(''' |
+ class A { |
+ get f => 1; |
+ } |
+ class B extends A { |
+ get f => 1; |
+ } |
+ main() { new A(); test(); } |
+ test() { var x = new B(); x.f; } |
+ ''', |
+ constructorSend: 1, // new B() |
+ localSend: 1, // x in x.f |
+ dynamicSend: 1); // x.f could be any `f` or no `f` |
+ }); |
+ }); |
+ |
+ group('instance this call', () { |
+ test('monomorphic only one implementor', () { |
+ return _check(''' |
+ class A { |
+ get f => 1; |
+ test() => this.f; |
+ } |
+ main() => new A().test(); |
+ ''', |
+ instanceSend: 1); // this.f resolved to A.f |
+ }); |
+ |
+ test('monomorphic only one type possible from types & liveness', () { |
+ return _check(''' |
+ class A { |
+ get f => 1; |
+ test() => this.f; |
+ } |
+ class B extends A { |
+ get f => 1; |
+ } |
+ main() => new B().test(); |
+ ''', |
+ instanceSend: 1); // this.f resolved to B.f |
+ }); |
+ |
+ test('polymorphic-virtual couple possible types from liveness', () { |
+ // Note: this would be an instanceSend if we used the inferrer. |
+ return _check(''' |
+ class A { |
+ get f => 1; |
+ test() => this.f; |
+ } |
+ class B extends A { |
+ get f => 1; |
+ } |
+ main() { new A(); new B().test(); } |
+ ''', |
+ virtualSend: 1); // this.f may be A.f or B.f |
+ }); |
+ }); |
+ |
+ group('noSuchMethod', () { |
+ test('error will be thrown', () { |
+ return _check(''' |
+ class A { |
+ } |
+ main() { test(); } |
+ test() { new A().f; } |
+ ''', |
+ constructorSend: 1, // new B() |
+ nsmErrorSend: 1); // f not there, A has no nSM |
+ }); |
+ |
+ test('nSM will be called - one option', () { |
+ return _check(''' |
+ class A { |
+ noSuchMethod(i) => null; |
+ } |
+ main() { test(); } |
+ test() { new A().f; } |
+ ''', |
+ constructorSend: 1, // new B() |
+ singleNsmCallSend: 1); // f not there, A has nSM |
+ }); |
+ |
+ // TODO(sigmund): is it worth splitting multiNSMvirtual? |
+ test('nSM will be called - multiple options', () { |
+ return _check(''' |
+ class A { |
+ noSuchMethod(i) => null; |
+ } |
+ class B extends A { |
+ noSuchMethod(i) => null; |
+ } |
+ main() { new A(); test(); } |
+ test() { A x = new B(); x.f; } |
+ ''', |
+ constructorSend: 1, // new B() |
+ localSend: 1, // x in x.f |
+ multiNsmCallSend: 1); // f not there, A has nSM |
+ }); |
+ |
+ // TODO(sigmund): is it worth splitting multiNSMvirtual? |
+ test('nSM will be called - multiple options', () { |
+ return _check(''' |
+ class A { |
+ noSuchMethod(i) => null; |
+ } |
+ class B extends A { |
+ // don't count A's nsm as distinct |
+ } |
+ main() { new A(); test(); } |
+ test() { A x = new B(); x.f; } |
+ ''', |
+ constructorSend: 1, // new B() |
+ localSend: 1, // x in x.f |
+ singleNsmCallSend: 1); // f not there, A has nSM |
+ }); |
+ |
+ test('nSM will be called - multiple options', () { |
+ return _check(''' |
+ class A { |
+ noSuchMethod(i) => null; |
+ } |
+ class B extends A { |
+ get f => null; |
+ } |
+ main() { new A(); test(); } |
+ test() { A x = new B(); x.f; } |
+ ''', |
+ constructorSend: 1, // new B() |
+ localSend: 1, // x in x.f |
+ dynamicSend: 1); // f not known to be there there, A has nSM |
+ }); |
+ |
+ test('nSM in super', () { |
+ return _check(''' |
+ class A { |
+ noSuchMethod(i) => null; |
+ } |
+ class B extends A { |
+ get f => super.f; |
+ } |
+ main() { new A(); test(); } |
+ test() { A x = new B(); x.f; } |
+ ''', |
+ singleNsmCallSend: 1, // super.f |
+ testMethod: 'f'); |
+ }); |
+ }); |
+} |
+ |
+ |
+/// Checks that the `test` function in [code] produces the given distribution of |
+/// sends. |
+_check(String code, {int staticSend: 0, int superSend: 0, int localSend: 0, |
+ int constructorSend: 0, int typeVariableSend: 0, int nsmErrorSend: 0, |
+ int singleNsmCallSend: 0, int instanceSend: 0, int interceptorSend: 0, |
+ int multiNsmCallSend: 0, int virtualSend: 0, int multiInterceptorSend: 0, |
+ int dynamicSend: 0, String testMethod: 'test'}) async { |
+ |
+ // Set up the expectation. |
+ var expected = new Measurements(); |
+ int monomorphic = staticSend + superSend + localSend + constructorSend + |
+ typeVariableSend + nsmErrorSend + singleNsmCallSend + instanceSend + |
+ interceptorSend; |
+ int polymorphic = multiNsmCallSend + virtualSend + multiInterceptorSend + |
+ dynamicSend; |
+ |
+ expected.counters[Metric.monomorphicSend] = monomorphic; |
+ expected.counters[Metric.staticSend] = staticSend; |
+ expected.counters[Metric.superSend] = superSend; |
+ expected.counters[Metric.localSend] = localSend; |
+ expected.counters[Metric.constructorSend] = constructorSend; |
+ expected.counters[Metric.typeVariableSend] = typeVariableSend; |
+ expected.counters[Metric.nsmErrorSend] = nsmErrorSend; |
+ expected.counters[Metric.singleNsmCallSend] = singleNsmCallSend; |
+ expected.counters[Metric.instanceSend] = instanceSend; |
+ expected.counters[Metric.interceptorSend] = interceptorSend; |
+ |
+ expected.counters[Metric.polymorphicSend] = polymorphic; |
+ expected.counters[Metric.multiNsmCallSend] = multiNsmCallSend; |
+ expected.counters[Metric.virtualSend] = virtualSend; |
+ expected.counters[Metric.multiInterceptorSend] = multiInterceptorSend; |
+ expected.counters[Metric.dynamicSend] = dynamicSend; |
+ |
+ expected.counters[Metric.send] = monomorphic + polymorphic; |
+ |
+ // Run the compiler to get the results. |
+ var all = await _compileAndGetStats(code); |
+ var function = all.functions.firstWhere((f) => f.name == testMethod, |
+ orElse: () => null); |
+ var result = function?.measurements; |
+ if (function == null) { |
+ expect(expected.counters[Metric.send], 0); |
+ return; |
+ } |
+ |
+ expect(result, isNotNull); |
+ |
+ _compareMetric(Metric key) { |
+ var expectedValue = expected.counters[key]; |
+ var value = result.counters[key]; |
+ if (value == null) value = 0; |
+ if (value == expectedValue) return; |
+ expect(expectedValue, value, |
+ reason: "count for `$key` didn't match:\n" |
+ "expected measurements:\n${recursiveDiagnosticString(expected, key)}\n" |
+ "actual measurements:\n${recursiveDiagnosticString(result, key)}"); |
+ } |
+ |
+ _compareMetric(Metric.send); |
+ expected.counters.keys.forEach(_compareMetric); |
+} |
+ |
+/// Helper that runs the compiler and returns the [GlobalResult] computed for |
+/// it. |
+Future<AllInfo> _compileAndGetStats(String program) async { |
+ var result = await runCompiler( |
+ memorySourceFiles: {'main.dart': program}, options: ['--dump-info']); |
+ expect(result.compiler.compilationFailed, isFalse); |
+ return result.compiler.dumpInfoTask.infoCollector.result; |
+} |