| Index: pkg/compiler/lib/src/cps_ir/optimize_interceptors.dart
|
| diff --git a/pkg/compiler/lib/src/cps_ir/optimize_interceptors.dart b/pkg/compiler/lib/src/cps_ir/optimize_interceptors.dart
|
| deleted file mode 100644
|
| index 76b359151a84b16bcc7e30dcee91bb17958637aa..0000000000000000000000000000000000000000
|
| --- a/pkg/compiler/lib/src/cps_ir/optimize_interceptors.dart
|
| +++ /dev/null
|
| @@ -1,328 +0,0 @@
|
| -// 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.
|
| -
|
| -library dart2js.cps_ir.optimize_interceptors;
|
| -
|
| -import '../constants/values.dart';
|
| -import '../elements/elements.dart';
|
| -import '../io/source_information.dart' show SourceInformation;
|
| -import '../js_backend/backend_helpers.dart' show BackendHelpers;
|
| -import '../js_backend/js_backend.dart' show JavaScriptBackend;
|
| -import '../types/types.dart' show TypeMask;
|
| -import '../world.dart';
|
| -import 'cps_fragment.dart';
|
| -import 'cps_ir_nodes.dart';
|
| -import 'loop_hierarchy.dart';
|
| -import 'optimizers.dart';
|
| -import 'type_mask_system.dart';
|
| -
|
| -/// Replaces `getInterceptor` calls with interceptor constants when possible,
|
| -/// or with "almost constant" expressions like "x && CONST" when the input
|
| -/// is either null or has a known interceptor.
|
| -///
|
| -/// Narrows the set of intercepted classes for interceptor calls.
|
| -///
|
| -/// Replaces calls on interceptors with one-shot interceptors.
|
| -class OptimizeInterceptors extends TrampolineRecursiveVisitor implements Pass {
|
| - String get passName => 'Optimize interceptors';
|
| -
|
| - final TypeMaskSystem typeSystem;
|
| - final JavaScriptBackend backend;
|
| - LoopHierarchy loopHierarchy;
|
| - Continuation currentLoopHeader;
|
| -
|
| - OptimizeInterceptors(this.backend, this.typeSystem);
|
| -
|
| - BackendHelpers get helpers => backend.helpers;
|
| - World get classWorld => backend.compiler.world;
|
| -
|
| - Map<Interceptor, Continuation> loopHeaderFor = <Interceptor, Continuation>{};
|
| -
|
| - void rewrite(FunctionDefinition node) {
|
| - // TODO(asgerf): Computing the LoopHierarchy here may be overkill when all
|
| - // we want is to hoist constants out of loops.
|
| - loopHierarchy = new LoopHierarchy(node);
|
| - visit(node.body);
|
| - new ShareConstants().visit(node);
|
| - }
|
| -
|
| - @override
|
| - Expression traverseContinuation(Continuation cont) {
|
| - Continuation oldLoopHeader = currentLoopHeader;
|
| - currentLoopHeader = loopHierarchy.getLoopHeader(cont);
|
| - pushAction(() {
|
| - currentLoopHeader = oldLoopHeader;
|
| - });
|
| - return cont.body;
|
| - }
|
| -
|
| - bool hasNoFalsyValues(ClassElement class_) {
|
| - return class_ != helpers.jsInterceptorClass &&
|
| - class_ != helpers.jsNullClass &&
|
| - class_ != helpers.jsBoolClass &&
|
| - class_ != helpers.jsStringClass &&
|
| - !class_.isSubclassOf(helpers.jsNumberClass);
|
| - }
|
| -
|
| - Continuation getCurrentOuterLoop({Continuation scope}) {
|
| - Continuation inner = null, outer = currentLoopHeader;
|
| - while (outer != scope) {
|
| - inner = outer;
|
| - outer = loopHierarchy.getEnclosingLoop(outer);
|
| - }
|
| - return inner;
|
| - }
|
| -
|
| - /// Binds the given constant in a primitive, in scope of the [useSite].
|
| - ///
|
| - /// The constant will be hoisted out of loops, and shared with other requests
|
| - /// for the same constant as long as it is in scope.
|
| - Primitive makeConstantFor(ConstantValue constant,
|
| - {Expression useSite,
|
| - TypeMask type,
|
| - SourceInformation sourceInformation,
|
| - Entity hint}) {
|
| - Constant prim =
|
| - new Constant(constant, sourceInformation: sourceInformation);
|
| - prim.hint = hint;
|
| - prim.type = type;
|
| - LetPrim letPrim = new LetPrim(prim);
|
| - Continuation loop = getCurrentOuterLoop();
|
| - if (loop != null) {
|
| - LetCont loopBinding = loop.parent;
|
| - letPrim.insertAbove(loopBinding);
|
| - } else {
|
| - letPrim.insertAbove(useSite);
|
| - }
|
| - return prim;
|
| - }
|
| -
|
| - void computeInterceptedClasses(Interceptor interceptor) {
|
| - Set<ClassElement> intercepted = interceptor.interceptedClasses;
|
| - intercepted.clear();
|
| - for (Reference ref = interceptor.firstRef; ref != null; ref = ref.next) {
|
| - Node use = ref.parent;
|
| - if (use is InvokeMethod) {
|
| - TypeMask type = use.receiver.type;
|
| - bool canOccurAsReceiver(ClassElement elem) {
|
| - return classWorld.isInstantiated(elem) &&
|
| - !typeSystem.areDisjoint(
|
| - type, typeSystem.getInterceptorSubtypes(elem));
|
| - }
|
| -
|
| - Iterable<ClassElement> classes =
|
| - backend.getInterceptedClassesOn(use.selector.name);
|
| - intercepted.addAll(classes.where(canOccurAsReceiver));
|
| - } else {
|
| - intercepted.clear();
|
| - intercepted.add(backend.helpers.jsInterceptorClass);
|
| - break;
|
| - }
|
| - }
|
| - if (intercepted.contains(backend.helpers.jsInterceptorClass) ||
|
| - intercepted.contains(backend.helpers.jsNullClass)) {
|
| - // If the null value is intercepted, update the type of the interceptor.
|
| - // The Tree IR uses this information to determine if the method lookup
|
| - // on an InvokeMethod might throw.
|
| - interceptor.type = interceptor.type.nonNullable();
|
| - }
|
| - }
|
| -
|
| - /// True if [node] may return [JSNumber] instead of [JSInt] or [JSDouble].
|
| - bool jsNumberClassSuffices(Interceptor node) {
|
| - // No methods on JSNumber call 'down' to methods on JSInt or JSDouble. If
|
| - // all uses of the interceptor are for methods is defined only on JSNumber
|
| - // then JSNumber will suffice in place of choosing between JSInt or
|
| - // JSDouble.
|
| - for (Reference ref = node.firstRef; ref != null; ref = ref.next) {
|
| - if (ref.parent is InvokeMethod) {
|
| - InvokeMethod invoke = ref.parent;
|
| - if (invoke.interceptorRef != ref) return false;
|
| - var interceptedClasses =
|
| - backend.getInterceptedClassesOn(invoke.selector.name);
|
| - if (interceptedClasses.contains(helpers.jsDoubleClass)) return false;
|
| - if (interceptedClasses.contains(helpers.jsIntClass)) return false;
|
| - continue;
|
| - }
|
| - // Other uses need full distinction.
|
| - return false;
|
| - }
|
| - return true;
|
| - }
|
| -
|
| - /// True if [node] can intercept a `null` value and return the [JSNull]
|
| - /// interceptor.
|
| - bool canInterceptNull(Interceptor node) {
|
| - for (Reference ref = node.firstRef; ref != null; ref = ref.next) {
|
| - Node use = ref.parent;
|
| - if (use is InvokeMethod) {
|
| - if (selectorsOnNull.contains(use.selector) &&
|
| - use.receiver.type.isNullable) {
|
| - return true;
|
| - }
|
| - } else {
|
| - return true;
|
| - }
|
| - }
|
| - return false;
|
| - }
|
| -
|
| - /// Returns the only interceptor class that may be returned by [node], or
|
| - /// `null` if no such class could be found.
|
| - ClassElement getSingleInterceptorClass(Interceptor node) {
|
| - // TODO(asgerf): This could be more precise if we used the use-site type,
|
| - // since the interceptor may have been hoisted out of a loop, where a less
|
| - // precise type is known.
|
| - Primitive input = node.input;
|
| - TypeMask type = input.type;
|
| - if (canInterceptNull(node)) return null;
|
| - type = type.nonNullable();
|
| - if (typeSystem.isDefinitelyArray(type)) {
|
| - return backend.helpers.jsArrayClass;
|
| - }
|
| - if (typeSystem.isDefinitelyInt(type)) {
|
| - return backend.helpers.jsIntClass;
|
| - }
|
| - if (typeSystem.isDefinitelyNum(type) && jsNumberClassSuffices(node)) {
|
| - return backend.helpers.jsNumberClass;
|
| - }
|
| - ClassElement singleClass = type.singleClass(classWorld);
|
| - if (singleClass != null &&
|
| - singleClass.isSubclassOf(backend.helpers.jsInterceptorClass)) {
|
| - return singleClass;
|
| - }
|
| - return null;
|
| - }
|
| -
|
| - /// Try to replace [interceptor] with a constant, and return `true` if
|
| - /// successful.
|
| - bool constifyInterceptor(Interceptor interceptor) {
|
| - LetPrim let = interceptor.parent;
|
| - Primitive input = interceptor.input;
|
| - ClassElement classElement = getSingleInterceptorClass(interceptor);
|
| -
|
| - if (classElement == null) return false;
|
| - ConstantValue constant = new InterceptorConstantValue(classElement.rawType);
|
| -
|
| - if (!input.type.isNullable) {
|
| - Primitive constantPrim = makeConstantFor(constant,
|
| - useSite: let,
|
| - type: interceptor.type,
|
| - sourceInformation: interceptor.sourceInformation);
|
| - constantPrim.useElementAsHint(interceptor.hint);
|
| - interceptor
|
| - ..replaceUsesWith(constantPrim)
|
| - ..destroy();
|
| - let.remove();
|
| - } else {
|
| - Primitive constantPrim = makeConstantFor(constant,
|
| - useSite: let,
|
| - type: interceptor.type.nonNullable(),
|
| - sourceInformation: interceptor.sourceInformation);
|
| - CpsFragment cps = new CpsFragment(interceptor.sourceInformation);
|
| - Parameter param = new Parameter(interceptor.hint);
|
| - param.type = interceptor.type;
|
| - Continuation cont = cps.letCont(<Parameter>[param]);
|
| - if (hasNoFalsyValues(classElement)) {
|
| - // If null is the only falsy value, compile as "x && CONST".
|
| - cps.ifFalsy(input).invokeContinuation(cont, [input]);
|
| - } else {
|
| - // If there are other falsy values compile as "x == null ? x : CONST".
|
| - Primitive condition =
|
| - cps.applyBuiltin(BuiltinOperator.LooseEq, [input, cps.makeNull()]);
|
| - cps.ifTruthy(condition).invokeContinuation(cont, [input]);
|
| - }
|
| - cps.invokeContinuation(cont, [constantPrim]);
|
| - cps.context = cont;
|
| - cps.insertAbove(let);
|
| - interceptor
|
| - ..replaceUsesWith(param)
|
| - ..destroy();
|
| - let.remove();
|
| - }
|
| - return true;
|
| - }
|
| -
|
| - @override
|
| - Expression traverseLetPrim(LetPrim node) {
|
| - Expression next = node.body;
|
| - visit(node.primitive);
|
| - return next;
|
| - }
|
| -
|
| - @override
|
| - void visitInterceptor(Interceptor node) {
|
| - if (constifyInterceptor(node)) return;
|
| - computeInterceptedClasses(node);
|
| - if (node.hasExactlyOneUse) {
|
| - // Set the loop header on single-use interceptors so [visitInvokeMethod]
|
| - // can determine if it should become a one-shot interceptor.
|
| - loopHeaderFor[node] = currentLoopHeader;
|
| - }
|
| - }
|
| -
|
| - @override
|
| - void visitInvokeMethod(InvokeMethod node) {
|
| - if (node.callingConvention != CallingConvention.Intercepted) return;
|
| - Primitive interceptor = node.interceptor;
|
| - if (interceptor is! Interceptor ||
|
| - interceptor.hasMultipleUses ||
|
| - loopHeaderFor[interceptor] != currentLoopHeader) {
|
| - return;
|
| - }
|
| - // TODO(asgerf): Consider heuristics for when to use one-shot interceptors.
|
| - // E.g. using only one-shot interceptors with a fast path.
|
| - node.makeOneShotIntercepted();
|
| - }
|
| -
|
| - @override
|
| - void visitTypeTestViaFlag(TypeTestViaFlag node) {
|
| - Primitive interceptor = node.interceptor;
|
| - if (interceptor is! Interceptor ||
|
| - interceptor.hasMultipleUses ||
|
| - loopHeaderFor[interceptor] != currentLoopHeader ||
|
| - !backend.mayGenerateInstanceofCheck(node.dartType)) {
|
| - return;
|
| - }
|
| - Interceptor inter = interceptor;
|
| - Primitive value = inter.input;
|
| - node.replaceWith(new TypeTest(value, node.dartType, [])..type = node.type);
|
| - }
|
| -}
|
| -
|
| -/// Shares interceptor constants when one is in scope of another.
|
| -///
|
| -/// Interceptor optimization runs after GVN, hence this clean-up step is needed.
|
| -///
|
| -/// TODO(asgerf): Handle in separate constant optimization pass? With some other
|
| -/// constant-related optimizations, like cloning small constants at use-site.
|
| -class ShareConstants extends TrampolineRecursiveVisitor {
|
| - Map<ConstantValue, Constant> sharedConstantFor = <ConstantValue, Constant>{};
|
| -
|
| - Expression traverseLetPrim(LetPrim node) {
|
| - Expression next = node.body;
|
| - if (node.primitive is Constant && shouldShareConstant(node.primitive)) {
|
| - Constant prim = node.primitive;
|
| - Constant existing = sharedConstantFor[prim.value];
|
| - if (existing != null) {
|
| - existing.useElementAsHint(prim.hint);
|
| - prim
|
| - ..replaceUsesWith(existing)
|
| - ..destroy();
|
| - node.remove();
|
| - return next;
|
| - }
|
| - sharedConstantFor[prim.value] = prim;
|
| - pushAction(() {
|
| - assert(sharedConstantFor[prim.value] == prim);
|
| - sharedConstantFor.remove(prim.value);
|
| - });
|
| - }
|
| - return next;
|
| - }
|
| -
|
| - bool shouldShareConstant(Constant constant) {
|
| - return constant.value.isInterceptor;
|
| - }
|
| -}
|
|
|