Index: pkg/compiler/lib/src/cps_ir/type_propagation.dart |
diff --git a/pkg/compiler/lib/src/cps_ir/type_propagation.dart b/pkg/compiler/lib/src/cps_ir/type_propagation.dart |
index 493201f397869d45228db117788fbe6399bf04c4..a4b9acf6dc3c72089f53d5fbe123021852f82d73 100644 |
--- a/pkg/compiler/lib/src/cps_ir/type_propagation.dart |
+++ b/pkg/compiler/lib/src/cps_ir/type_propagation.dart |
@@ -666,6 +666,7 @@ class TransformingVisitor extends DeepRecursiveVisitor { |
JavaScriptBackend get backend => compiler.backend; |
TypeMaskSystem get typeSystem => lattice.typeSystem; |
types.DartTypes get dartTypes => lattice.dartTypes; |
+ World get classWorld => typeSystem.classWorld; |
Map<Variable, ConstantValue> get values => analyzer.values; |
final InternalErrorFunction internalError; |
@@ -2202,39 +2203,131 @@ class TransformingVisitor extends DeepRecursiveVisitor { |
Primitive visitInterceptor(Interceptor node) { |
AbstractValue value = getValue(node.input.definition); |
- // If the exact class of the input is known, replace with a constant |
- // or the input itself. |
- ClassElement singleClass; |
- if (lattice.isDefinitelyInt(value)) { |
- // Classes like JSUInt31 and JSUInt32 do not exist at runtime, so ensure |
- // all the int classes get mapped tor their runtime class. |
- singleClass = backend.jsIntClass; |
- } else if (lattice.isDefinitelyNum(value)) { |
- if (jsNumberClassSuffices(node)) { |
- singleClass = backend.jsNumberClass; |
+ TypeMask interceptedInputs = value.type.intersection( |
+ typeSystem.interceptorType.nullable(), classWorld); |
+ bool interceptNull = |
+ node.interceptedClasses.contains(backend.jsNullClass) || |
+ node.interceptedClasses.contains(backend.jsInterceptorClass); |
+ |
+ void filterInterceptedClasses() { |
+ if (lattice.isDefinitelyInt(value, allowNull: !interceptNull)) { |
+ node.interceptedClasses..clear()..add(backend.jsIntClass); |
+ return; |
} |
- } else if (lattice.isDefinitelyNativeList(value)) { |
- // Ensure all the array subclasses get mapped to the array class. |
- singleClass = backend.jsArrayClass; |
- } else { |
- singleClass = typeSystem.singleClass(value.type); |
- } |
- if (singleClass != null && |
- singleClass.isSubclassOf(backend.jsInterceptorClass)) { |
- node.constantValue = new InterceptorConstantValue(singleClass.rawType); |
- } |
- // Filter out intercepted classes that do not match the input type. |
- node.interceptedClasses.retainWhere((ClassElement clazz) { |
- if (clazz == typeSystem.jsNullClass) { |
- return value.isNullable; |
- } else { |
- TypeMask classMask = typeSystem.nonNullSubclass(clazz); |
- return !typeSystem.areDisjoint(value.type, classMask); |
+ if (lattice.isDefinitelyNum(value, allowNull: !interceptNull) && |
+ jsNumberClassSuffices(node)) { |
+ node.interceptedClasses..clear()..add(backend.jsNumberClass); |
+ return; |
} |
- }); |
+ TypeMask interceptedClassesType = new TypeMask.unionOf( |
+ node.interceptedClasses.map(typeSystem.getInterceptorSubtypes), |
+ classWorld); |
+ TypeMask interceptedAndCaught = |
+ interceptedInputs.intersection(interceptedClassesType, classWorld); |
+ ClassElement single = interceptedAndCaught.singleClass(classWorld); |
+ if (single != null) { |
+ node.interceptedClasses..clear()..add(backend.getRuntimeClass(single)); |
+ return; |
+ } |
+ |
+ // Filter out intercepted classes that do not match the input type. |
+ node.interceptedClasses.retainWhere((ClassElement clazz) { |
+ return !typeSystem.areDisjoint( |
+ value.type, |
+ typeSystem.getInterceptorSubtypes(clazz)); |
+ }); |
+ |
+ // The interceptor root class will usually not be filtered out because all |
+ // intercepted values are subtypes of it. But it can be "shadowed" by a |
+ // more specific interceptor classes if all possible intercepted values |
+ // will hit one of the more specific classes. |
+ // |
+ // For example, if all classes can respond to a given call, but we know |
+ // the input is a string or an unknown JavaScript object, we want to |
+ // use a specialized interceptor: |
+ // |
+ // getInterceptor(x).get$hashCode(x); |
+ // ==> |
+ // getInterceptor$su(x).get$hashCode(x) |
+ // |
+ // In this case, the intercepted classes that were not filtered out above |
+ // are {UnknownJavaScriptObject, JSString, Interceptor}, but there is no |
+ // need to include the Interceptor class. |
+ // |
+ // We only do this optimization if the resulting interceptor call is |
+ // sufficiently specialized to be worth it (#classes <= 2). |
+ // TODO(asgerf): Reconsider when TypeTest interceptors don't intercept |
+ // ALL interceptor classes. |
+ if (node.interceptedClasses.length > 1 && |
+ node.interceptedClasses.length < 4 && |
+ node.interceptedClasses.contains(backend.jsInterceptorClass)) { |
+ TypeMask specificInterceptors = new TypeMask.unionOf( |
+ node.interceptedClasses |
+ .where((cl) => cl != backend.jsInterceptorClass) |
+ .map(typeSystem.getInterceptorSubtypes), |
+ classWorld); |
+ if (specificInterceptors.containsMask(interceptedInputs, classWorld)) { |
+ // All possible inputs are caught by an Interceptor subclass (or are |
+ // self-interceptors), so there is no need to have the check for |
+ // the Interceptor root class (which is expensive). |
+ node.interceptedClasses.remove(backend.jsInterceptorClass); |
+ } |
+ } |
+ } |
+ |
+ filterInterceptedClasses(); |
+ |
// Remove the interceptor call if it can only return its input. |
if (node.interceptedClasses.isEmpty) { |
node.input.definition.substituteFor(node); |
+ return null; |
+ } |
+ |
+ node.flags = Interceptor.ALL_FLAGS; |
+ |
+ // If there is only one caught interceptor class, determine more precisely |
+ // how this might resolve at runtime. Later optimizations depend on this, |
+ // but do not have refined type information available. |
+ if (node.interceptedClasses.length == 1) { |
+ ClassElement class_ = node.interceptedClasses.single; |
+ if (value.isDefinitelyNotNull) { |
+ node.clearFlag(Interceptor.NULL); |
+ } else if (interceptNull) { |
+ node.clearFlag(Interceptor.NULL_BYPASS); |
+ if (class_ == backend.jsNullClass) { |
+ node.clearFlag(Interceptor.NULL_INTERCEPT_SUBCLASS); |
+ } else { |
+ node.clearFlag(Interceptor.NULL_INTERCEPT_EXACT); |
+ } |
+ } else { |
+ node.clearFlag(Interceptor.NULL_INTERCEPT); |
+ } |
+ if (typeSystem.isDefinitelyIntercepted(value.type, allowNull: true)) { |
+ node.clearFlag(Interceptor.SELF_INTERCEPT); |
+ } |
+ TypeMask interceptedInputsNonNullable = interceptedInputs.nonNullable(); |
+ TypeMask interceptedType = typeSystem.getInterceptorSubtypes(class_); |
+ if (interceptedType.containsMask(interceptedInputsNonNullable, |
+ classWorld)) { |
+ node.clearFlag(Interceptor.NON_NULL_BYPASS); |
+ } |
+ if (class_ == backend.jsNumberClass) { |
+ // See [jsNumberClassSuffices]. We know that JSInt and JSDouble are |
+ // not intercepted classes so JSNumber is sufficient. |
+ node.clearFlag(Interceptor.NON_NULL_INTERCEPT_SUBCLASS); |
+ } else if (class_ == backend.jsArrayClass) { |
+ // JSArray has compile-time subclasses like JSFixedArray, but should |
+ // still be considered "exact" if the input is any subclass of JSArray. |
+ if (typeSystem.isDefinitelyNativeList(interceptedInputsNonNullable)) { |
+ node.clearFlag(Interceptor.NON_NULL_INTERCEPT_SUBCLASS); |
+ } |
+ } else { |
+ TypeMask exactInterceptedType = typeSystem.nonNullExact(class_); |
+ if (exactInterceptedType.containsMask(interceptedInputsNonNullable, |
+ classWorld)) { |
+ node.clearFlag(Interceptor.NON_NULL_INTERCEPT_SUBCLASS); |
+ } |
+ } |
} |
return null; |
} |