| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library dart2js.cps_ir.optimize_interceptors; | 5 library dart2js.cps_ir.optimize_interceptors; |
| 6 | 6 |
| 7 import 'optimizers.dart'; | 7 import 'optimizers.dart'; |
| 8 import 'cps_ir_nodes.dart'; | 8 import 'cps_ir_nodes.dart'; |
| 9 import 'loop_hierarchy.dart'; | 9 import 'loop_hierarchy.dart'; |
| 10 import 'cps_fragment.dart'; | 10 import 'cps_fragment.dart'; |
| 11 import '../constants/values.dart'; | 11 import '../constants/values.dart'; |
| 12 import '../elements/elements.dart'; | 12 import '../elements/elements.dart'; |
| 13 import '../js_backend/backend_helpers.dart' show BackendHelpers; | 13 import '../js_backend/backend_helpers.dart' show BackendHelpers; |
| 14 import '../js_backend/js_backend.dart' show JavaScriptBackend; | 14 import '../js_backend/js_backend.dart' show JavaScriptBackend; |
| 15 import '../types/types.dart' show TypeMask; | 15 import '../types/types.dart' show TypeMask; |
| 16 import '../io/source_information.dart' show SourceInformation; | 16 import '../io/source_information.dart' show SourceInformation; |
| 17 import '../world.dart'; |
| 18 import 'type_mask_system.dart'; |
| 17 | 19 |
| 18 /// Replaces `getInterceptor` calls with interceptor constants when possible, | 20 /// Replaces `getInterceptor` calls with interceptor constants when possible, |
| 19 /// or with "almost constant" expressions like "x && CONST" when the input | 21 /// or with "almost constant" expressions like "x && CONST" when the input |
| 20 /// is either null or has a known interceptor. | 22 /// is either null or has a known interceptor. |
| 21 // | 23 /// |
| 22 // TODO(asgerf): Compute intercepted classes in this pass. | 24 /// Narrows the set of intercepted classes for interceptor calls. |
| 25 /// |
| 26 /// Replaces calls on interceptors with one-shot interceptors. |
| 23 class OptimizeInterceptors extends TrampolineRecursiveVisitor implements Pass { | 27 class OptimizeInterceptors extends TrampolineRecursiveVisitor implements Pass { |
| 24 String get passName => 'Optimize interceptors'; | 28 String get passName => 'Optimize interceptors'; |
| 25 | 29 |
| 26 JavaScriptBackend backend; | 30 final TypeMaskSystem typeSystem; |
| 31 final JavaScriptBackend backend; |
| 27 LoopHierarchy loopHierarchy; | 32 LoopHierarchy loopHierarchy; |
| 28 Continuation currentLoopHeader; | 33 Continuation currentLoopHeader; |
| 29 | 34 |
| 30 OptimizeInterceptors(this.backend); | 35 OptimizeInterceptors(this.backend, this.typeSystem); |
| 31 | 36 |
| 32 BackendHelpers get helpers => backend.helpers; | 37 BackendHelpers get helpers => backend.helpers; |
| 38 World get classWorld => backend.compiler.world; |
| 33 | 39 |
| 34 Map<Interceptor, Continuation> loopHeaderFor = <Interceptor, Continuation>{}; | 40 Map<Interceptor, Continuation> loopHeaderFor = <Interceptor, Continuation>{}; |
| 35 | 41 |
| 36 void rewrite(FunctionDefinition node) { | 42 void rewrite(FunctionDefinition node) { |
| 37 // TODO(asgerf): Computing the LoopHierarchy here may be overkill when all | 43 // TODO(asgerf): Computing the LoopHierarchy here may be overkill when all |
| 38 // we want is to hoist constants out of loops. | 44 // we want is to hoist constants out of loops. |
| 39 loopHierarchy = new LoopHierarchy(node); | 45 loopHierarchy = new LoopHierarchy(node); |
| 40 visit(node.body); | 46 visit(node.body); |
| 41 new ShareConstants().visit(node); | 47 new ShareConstants().visit(node); |
| 42 } | 48 } |
| 43 | 49 |
| 44 @override | 50 @override |
| 45 Expression traverseContinuation(Continuation cont) { | 51 Expression traverseContinuation(Continuation cont) { |
| 46 Continuation oldLoopHeader = currentLoopHeader; | 52 Continuation oldLoopHeader = currentLoopHeader; |
| 47 currentLoopHeader = loopHierarchy.getLoopHeader(cont); | 53 currentLoopHeader = loopHierarchy.getLoopHeader(cont); |
| 48 pushAction(() { | 54 pushAction(() { |
| 49 currentLoopHeader = oldLoopHeader; | 55 currentLoopHeader = oldLoopHeader; |
| 50 }); | 56 }); |
| 51 return cont.body; | 57 return cont.body; |
| 52 } | 58 } |
| 53 | 59 |
| 54 /// If only one method table can be returned by the given interceptor, | |
| 55 /// returns a constant for that method table. | |
| 56 InterceptorConstantValue getInterceptorConstant(Interceptor node) { | |
| 57 if (node.interceptedClasses.length == 1 && | |
| 58 node.isInterceptedClassAlwaysExact) { | |
| 59 ClassElement interceptorClass = node.interceptedClasses.single; | |
| 60 return new InterceptorConstantValue(interceptorClass.rawType); | |
| 61 } | |
| 62 return null; | |
| 63 } | |
| 64 | |
| 65 bool hasNoFalsyValues(ClassElement class_) { | 60 bool hasNoFalsyValues(ClassElement class_) { |
| 66 return class_ != helpers.jsInterceptorClass && | 61 return class_ != helpers.jsInterceptorClass && |
| 67 class_ != helpers.jsNullClass && | 62 class_ != helpers.jsNullClass && |
| 68 class_ != helpers.jsBoolClass && | 63 class_ != helpers.jsBoolClass && |
| 69 class_ != helpers.jsStringClass && | 64 class_ != helpers.jsStringClass && |
| 70 !class_.isSubclassOf(helpers.jsNumberClass); | 65 !class_.isSubclassOf(helpers.jsNumberClass); |
| 71 } | 66 } |
| 72 | 67 |
| 73 Continuation getCurrentOuterLoop({Continuation scope}) { | 68 Continuation getCurrentOuterLoop({Continuation scope}) { |
| 74 Continuation inner = null, outer = currentLoopHeader; | 69 Continuation inner = null, outer = currentLoopHeader; |
| (...skipping 21 matching lines...) Expand all Loading... |
| 96 Continuation loop = getCurrentOuterLoop(); | 91 Continuation loop = getCurrentOuterLoop(); |
| 97 if (loop != null) { | 92 if (loop != null) { |
| 98 LetCont loopBinding = loop.parent; | 93 LetCont loopBinding = loop.parent; |
| 99 letPrim.insertAbove(loopBinding); | 94 letPrim.insertAbove(loopBinding); |
| 100 } else { | 95 } else { |
| 101 letPrim.insertAbove(useSite); | 96 letPrim.insertAbove(useSite); |
| 102 } | 97 } |
| 103 return prim; | 98 return prim; |
| 104 } | 99 } |
| 105 | 100 |
| 101 void computeInterceptedClasses(Interceptor interceptor) { |
| 102 Set<ClassElement> intercepted = interceptor.interceptedClasses; |
| 103 intercepted.clear(); |
| 104 for (Reference ref = interceptor.firstRef; ref != null; ref = ref.next) { |
| 105 Node use = ref.parent; |
| 106 if (use is InvokeMethod) { |
| 107 TypeMask type = use.dartReceiver.type; |
| 108 bool canOccurAsReceiver(ClassElement elem) { |
| 109 return classWorld.isInstantiated(elem) && |
| 110 !typeSystem.areDisjoint(type, |
| 111 typeSystem.getInterceptorSubtypes(elem)); |
| 112 } |
| 113 Iterable<ClassElement> classes = |
| 114 backend.getInterceptedClassesOn(use.selector.name); |
| 115 intercepted.addAll(classes.where(canOccurAsReceiver)); |
| 116 } else { |
| 117 intercepted.clear(); |
| 118 intercepted.add(backend.helpers.jsInterceptorClass); |
| 119 break; |
| 120 } |
| 121 } |
| 122 if (intercepted.contains(backend.helpers.jsInterceptorClass) || |
| 123 intercepted.contains(backend.helpers.jsNullClass)) { |
| 124 // If the null value is intercepted, update the type of the interceptor. |
| 125 // The Tree IR uses this information to determine if the method lookup |
| 126 // on an InvokeMethod might throw. |
| 127 interceptor.type = interceptor.type.nonNullable(); |
| 128 } |
| 129 } |
| 130 |
| 131 /// True if [node] may return [JSNumber] instead of [JSInt] or [JSDouble]. |
| 132 bool jsNumberClassSuffices(Interceptor node) { |
| 133 // No methods on JSNumber call 'down' to methods on JSInt or JSDouble. If |
| 134 // all uses of the interceptor are for methods is defined only on JSNumber |
| 135 // then JSNumber will suffice in place of choosing between JSInt or |
| 136 // JSDouble. |
| 137 for (Reference ref = node.firstRef; ref != null; ref = ref.next) { |
| 138 if (ref.parent is InvokeMethod) { |
| 139 InvokeMethod invoke = ref.parent; |
| 140 if (invoke.receiver != ref) return false; |
| 141 var interceptedClasses = |
| 142 backend.getInterceptedClassesOn(invoke.selector.name); |
| 143 if (interceptedClasses.contains(helpers.jsDoubleClass)) return false; |
| 144 if (interceptedClasses.contains(helpers.jsIntClass)) return false; |
| 145 continue; |
| 146 } |
| 147 // Other uses need full distinction. |
| 148 return false; |
| 149 } |
| 150 return true; |
| 151 } |
| 152 |
| 153 /// True if [node] can intercept a `null` value and return the [JSNull] |
| 154 /// interceptor. |
| 155 bool canInterceptNull(Interceptor node) { |
| 156 for (Reference ref = node.firstRef; ref != null; ref = ref.next) { |
| 157 Node use = ref.parent; |
| 158 if (use is InvokeMethod) { |
| 159 if (selectorsOnNull.contains(use.selector) && |
| 160 use.dartReceiver.type.isNullable) { |
| 161 return true; |
| 162 } |
| 163 } else { |
| 164 return true; |
| 165 } |
| 166 } |
| 167 return false; |
| 168 } |
| 169 |
| 170 /// Returns the only interceptor class that may be returned by [node], or |
| 171 /// `null` if no such class could be found. |
| 172 ClassElement getSingleInterceptorClass(Interceptor node) { |
| 173 // TODO(asgerf): This could be more precise if we used the use-site type, |
| 174 // since the interceptor may have been hoisted out of a loop, where a less |
| 175 // precise type is known. |
| 176 Primitive input = node.input.definition; |
| 177 TypeMask type = input.type; |
| 178 if (canInterceptNull(node)) return null; |
| 179 type = type.nonNullable(); |
| 180 if (typeSystem.isDefinitelyArray(type)) { |
| 181 return backend.helpers.jsArrayClass; |
| 182 } |
| 183 if (typeSystem.isDefinitelyInt(type)) { |
| 184 return backend.helpers.jsIntClass; |
| 185 } |
| 186 if (typeSystem.isDefinitelyNum(type) && jsNumberClassSuffices(node)) { |
| 187 return backend.helpers.jsNumberClass; |
| 188 } |
| 189 ClassElement singleClass = type.singleClass(classWorld); |
| 190 if (singleClass != null && |
| 191 singleClass.isSubclassOf(backend.helpers.jsInterceptorClass)) { |
| 192 return singleClass; |
| 193 } |
| 194 return null; |
| 195 } |
| 196 |
| 197 /// Try to replace [interceptor] with a constant, and return `true` if |
| 198 /// successful. |
| 106 bool constifyInterceptor(Interceptor interceptor) { | 199 bool constifyInterceptor(Interceptor interceptor) { |
| 107 LetPrim let = interceptor.parent; | 200 LetPrim let = interceptor.parent; |
| 108 InterceptorConstantValue constant = getInterceptorConstant(interceptor); | 201 Primitive input = interceptor.input.definition; |
| 202 ClassElement classElement = getSingleInterceptorClass(interceptor); |
| 109 | 203 |
| 110 if (constant == null) return false; | 204 if (classElement == null) return false; |
| 205 ConstantValue constant = new InterceptorConstantValue(classElement.rawType); |
| 111 | 206 |
| 112 if (interceptor.isAlwaysIntercepted) { | 207 if (!input.type.isNullable) { |
| 113 Primitive constantPrim = makeConstantFor(constant, | 208 Primitive constantPrim = makeConstantFor(constant, |
| 114 useSite: let, | 209 useSite: let, |
| 115 type: interceptor.type, | 210 type: interceptor.type, |
| 116 sourceInformation: interceptor.sourceInformation); | 211 sourceInformation: interceptor.sourceInformation); |
| 117 constantPrim.useElementAsHint(interceptor.hint); | 212 constantPrim.useElementAsHint(interceptor.hint); |
| 118 interceptor..replaceUsesWith(constantPrim)..destroy(); | 213 interceptor..replaceUsesWith(constantPrim)..destroy(); |
| 119 let.remove(); | 214 let.remove(); |
| 120 } else if (interceptor.isAlwaysNullOrIntercepted) { | 215 } else { |
| 121 Primitive input = interceptor.input.definition; | |
| 122 Primitive constantPrim = makeConstantFor(constant, | 216 Primitive constantPrim = makeConstantFor(constant, |
| 123 useSite: let, | 217 useSite: let, |
| 124 type: interceptor.type.nonNullable(), | 218 type: interceptor.type.nonNullable(), |
| 125 sourceInformation: interceptor.sourceInformation); | 219 sourceInformation: interceptor.sourceInformation); |
| 126 CpsFragment cps = new CpsFragment(interceptor.sourceInformation); | 220 CpsFragment cps = new CpsFragment(interceptor.sourceInformation); |
| 127 Parameter param = new Parameter(interceptor.hint); | 221 Parameter param = new Parameter(interceptor.hint); |
| 128 param.type = interceptor.type; | 222 param.type = interceptor.type; |
| 129 Continuation cont = cps.letCont(<Parameter>[param]); | 223 Continuation cont = cps.letCont(<Parameter>[param]); |
| 130 if (interceptor.interceptedClasses.every(hasNoFalsyValues)) { | 224 if (hasNoFalsyValues(classElement)) { |
| 131 // If null is the only falsy value, compile as "x && CONST". | 225 // If null is the only falsy value, compile as "x && CONST". |
| 132 cps.ifFalsy(input).invokeContinuation(cont, [input]); | 226 cps.ifFalsy(input).invokeContinuation(cont, [input]); |
| 133 } else { | 227 } else { |
| 134 // If there are other falsy values compile as "x == null ? x : CONST". | 228 // If there are other falsy values compile as "x == null ? x : CONST". |
| 135 Primitive condition = cps.applyBuiltin( | 229 Primitive condition = cps.applyBuiltin( |
| 136 BuiltinOperator.LooseEq, | 230 BuiltinOperator.LooseEq, |
| 137 [input, cps.makeNull()]); | 231 [input, cps.makeNull()]); |
| 138 cps.ifTruthy(condition).invokeContinuation(cont, [input]); | 232 cps.ifTruthy(condition).invokeContinuation(cont, [input]); |
| 139 } | 233 } |
| 140 cps.invokeContinuation(cont, [constantPrim]); | 234 cps.invokeContinuation(cont, [constantPrim]); |
| 141 cps.context = cont; | 235 cps.context = cont; |
| 142 cps.insertAbove(let); | 236 cps.insertAbove(let); |
| 143 interceptor..replaceUsesWith(param)..destroy(); | 237 interceptor..replaceUsesWith(param)..destroy(); |
| 144 let.remove(); | 238 let.remove(); |
| 145 } | 239 } |
| 146 return true; | 240 return true; |
| 147 } | 241 } |
| 148 | 242 |
| 149 @override | 243 @override |
| 150 Expression traverseLetPrim(LetPrim node) { | 244 Expression traverseLetPrim(LetPrim node) { |
| 151 Expression next = node.body; | 245 Expression next = node.body; |
| 152 visit(node.primitive); | 246 visit(node.primitive); |
| 153 return next; | 247 return next; |
| 154 } | 248 } |
| 155 | 249 |
| 156 @override | 250 @override |
| 157 void visitInterceptor(Interceptor node) { | 251 void visitInterceptor(Interceptor node) { |
| 158 if (constifyInterceptor(node)) return; | 252 if (constifyInterceptor(node)) return; |
| 253 computeInterceptedClasses(node); |
| 159 if (node.hasExactlyOneUse) { | 254 if (node.hasExactlyOneUse) { |
| 160 // Set the loop header on single-use interceptors so [visitInvokeMethod] | 255 // Set the loop header on single-use interceptors so [visitInvokeMethod] |
| 161 // can determine if it should become a one-shot interceptor. | 256 // can determine if it should become a one-shot interceptor. |
| 162 loopHeaderFor[node] = currentLoopHeader; | 257 loopHeaderFor[node] = currentLoopHeader; |
| 163 } | 258 } |
| 164 } | 259 } |
| 165 | 260 |
| 166 @override | 261 @override |
| 167 void visitInvokeMethod(InvokeMethod node) { | 262 void visitInvokeMethod(InvokeMethod node) { |
| 168 if (node.callingConvention != CallingConvention.Intercepted) return; | 263 if (node.callingConvention != CallingConvention.Intercepted) return; |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 219 sharedConstantFor.remove(prim.value); | 314 sharedConstantFor.remove(prim.value); |
| 220 }); | 315 }); |
| 221 } | 316 } |
| 222 return next; | 317 return next; |
| 223 } | 318 } |
| 224 | 319 |
| 225 bool shouldShareConstant(Constant constant) { | 320 bool shouldShareConstant(Constant constant) { |
| 226 return constant.value.isInterceptor; | 321 return constant.value.isInterceptor; |
| 227 } | 322 } |
| 228 } | 323 } |
| OLD | NEW |