OLD | NEW |
(Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library cps_ir.optimization.insert_refinements; |
| 6 |
| 7 import 'optimizers.dart' show Pass; |
| 8 import 'shrinking_reductions.dart' show ParentVisitor; |
| 9 import 'cps_ir_nodes.dart'; |
| 10 import '../types/types.dart'; |
| 11 import '../types/constants.dart'; |
| 12 import '../constants/values.dart'; |
| 13 import '../world.dart'; |
| 14 import '../common/names.dart'; |
| 15 import '../universe/universe.dart'; |
| 16 import '../elements/elements.dart'; |
| 17 |
| 18 /// Inserts [Refinement] nodes in the IR to allow for sparse path-sensitive |
| 19 /// type analysis in the [TypePropagator] pass. |
| 20 /// |
| 21 /// Refinement nodes are inserted at the arms of a [Branch] node with a |
| 22 /// condition of form `x is T` or `x == CONST`. |
| 23 /// |
| 24 /// Refinement nodes are inserted after a method invocation to account for the |
| 25 /// fact that the receiver cannot be null afterwards (in most cases). |
| 26 /// |
| 27 // TODO(asgerf): Generalize the non-null pattern to all selectors/classes? |
| 28 class InsertRefinements extends RecursiveVisitor implements Pass { |
| 29 String get passName => 'Insert refinement nodes'; |
| 30 |
| 31 Parameter thisParameter; |
| 32 final TypesTask types; |
| 33 final World world; |
| 34 final TypeMask nonNullType; |
| 35 final TypeMask nullType; |
| 36 |
| 37 /// Maps unrefined primitives to its refinement currently in scope (if any). |
| 38 final Map<Primitive, Refinement> refinementFor = <Primitive, Refinement>{}; |
| 39 |
| 40 InsertRefinements(this.types, World world) |
| 41 : this.world = world, |
| 42 nonNullType = new TypeMask.nonNullSubtype(world.objectClass, world), |
| 43 nullType = new TypeMask.empty(); |
| 44 |
| 45 void rewrite(FunctionDefinition node) { |
| 46 new ParentVisitor().visit(node); |
| 47 thisParameter = node.thisParameter; |
| 48 visit(node.body); |
| 49 } |
| 50 |
| 51 /// Updates references to the refer to the refinement currently in scope. |
| 52 void processReference(Reference node) { |
| 53 Refinement refined = refinementFor[node.definition]; |
| 54 if (refined != null) { |
| 55 node.changeTo(refined); |
| 56 } |
| 57 } |
| 58 |
| 59 /// True if [prim] might evaluate to null. |
| 60 /// |
| 61 /// Used to avoid inserting refinement nodes that are obviously redundant. |
| 62 bool maybeNull(Primitive prim) { |
| 63 if (prim is Constant) return !prim.value.isNull; |
| 64 if (prim is Interceptor) return maybeNull(prim.input.definition); |
| 65 if (prim is Refinement) { |
| 66 if (!prim.type.isNullable) return false; |
| 67 return maybeNull(prim.value.definition); |
| 68 } |
| 69 if (prim == thisParameter) return false; |
| 70 return true; |
| 71 } |
| 72 |
| 73 /// True if an exception is thrown when the given selector is invoked on null. |
| 74 bool failsOnNull(Selector selector) { |
| 75 if (selector.isGetter) { |
| 76 return selector.name != 'toString' && |
| 77 selector.name != 'hashCode' && |
| 78 selector.name != 'noSuchMethod' && |
| 79 selector.name != 'runtimeType'; |
| 80 } else { |
| 81 return selector != Selectors.toString_ && |
| 82 selector != Selectors.hashCode_ && |
| 83 selector != Selectors.equals; |
| 84 } |
| 85 } |
| 86 |
| 87 /// Moves the binding of [cont] to before [use]. |
| 88 /// |
| 89 /// This is used to ensure that everything in scope at [use] is also in scope |
| 90 /// inside [cont], so refinements can be inserted inside [cont] without |
| 91 /// accidentally referencing a primitive out of scope. |
| 92 /// |
| 93 /// It is always safe to do this for single-use continuations, because |
| 94 /// strictly more things are in scope at the use site, and there can't be any |
| 95 /// other use of [cont] that might fall out of scope since there is only |
| 96 /// that single use. |
| 97 void sinkContinuationToUse(Continuation cont, Expression use) { |
| 98 assert(cont.hasExactlyOneUse && cont.firstRef.parent == use); |
| 99 LetCont let = cont.parent; |
| 100 InteriorNode useParent = use.parent; |
| 101 if (useParent == let) return; |
| 102 if (let.continuations.length > 1) { |
| 103 // Create a new LetCont binding only this continuation. |
| 104 let.continuations.remove(cont); |
| 105 let = new LetCont(cont, null); |
| 106 cont.parent = let; |
| 107 } else { |
| 108 // Remove LetCont from current position. |
| 109 InteriorNode letParent = let.parent; |
| 110 letParent.body = let.body; |
| 111 let.body.parent = letParent; |
| 112 } |
| 113 |
| 114 // Insert LetCont before use. |
| 115 useParent.body = let; |
| 116 let.body = use; |
| 117 use.parent = let; |
| 118 let.parent = useParent; |
| 119 } |
| 120 |
| 121 Primitive unfoldInterceptor(Primitive prim) { |
| 122 return prim is Interceptor ? prim.input.definition : prim; |
| 123 } |
| 124 |
| 125 /// Enqueues [cont] for processing in a context where [refined] is the |
| 126 /// current refinement for its value. |
| 127 void pushRefinement(Continuation cont, Refinement refined) { |
| 128 Primitive value = refined.effectiveDefinition; |
| 129 Primitive currentRefinement = refinementFor[value]; |
| 130 pushAction(() { |
| 131 refinementFor[value] = currentRefinement; |
| 132 if (refined.hasNoUses) { |
| 133 // Clean up refinements that are not used. |
| 134 refined.value.unlink(); |
| 135 } else { |
| 136 cont.body = new LetPrim(refined, cont.body); |
| 137 refined.parent = cont.body; |
| 138 refined.value.parent = refined; |
| 139 } |
| 140 }); |
| 141 push(cont); |
| 142 pushAction(() { |
| 143 refinementFor[value] = refined; |
| 144 }); |
| 145 } |
| 146 |
| 147 void visitInvokeMethod(InvokeMethod node) { |
| 148 Continuation cont = node.continuation.definition; |
| 149 |
| 150 // Update references to their current refined values. |
| 151 processReference(node.receiver); |
| 152 node.arguments.forEach(processReference); |
| 153 |
| 154 // If the call is intercepted, we want to refine the actual receiver, |
| 155 // not the interceptor. |
| 156 Primitive receiver = unfoldInterceptor(node.receiver.definition); |
| 157 |
| 158 // Sink the continuation to the call to ensure everything in scope |
| 159 // here is also in scope inside the continuations. |
| 160 sinkContinuationToUse(cont, node); |
| 161 |
| 162 if (node.selector.isClosureCall) { |
| 163 // Do not try to refine the receiver of closure calls; the class world |
| 164 // does not know about closure classes. |
| 165 push(cont); |
| 166 } else { |
| 167 // Filter away receivers that throw on this selector. |
| 168 TypeMask type = world.allFunctions.receiverType(node.selector, node.mask); |
| 169 pushRefinement(cont, new Refinement(receiver, type)); |
| 170 } |
| 171 } |
| 172 |
| 173 CallExpression getCallWithResult(Primitive prim) { |
| 174 if (prim is Parameter && prim.parent is Continuation) { |
| 175 Continuation cont = prim.parent; |
| 176 if (cont.hasExactlyOneUse && cont.firstRef.parent is CallExpression) { |
| 177 return cont.firstRef.parent; |
| 178 } |
| 179 } |
| 180 return null; |
| 181 } |
| 182 |
| 183 bool isTrue(Primitive prim) { |
| 184 return prim is Constant && prim.value.isTrue; |
| 185 } |
| 186 |
| 187 TypeMask getTypeOf(ConstantValue constant) { |
| 188 return computeTypeMask(types.compiler, constant); |
| 189 } |
| 190 |
| 191 void visitBranch(Branch node) { |
| 192 processReference(node.condition); |
| 193 Primitive condition = node.condition.definition; |
| 194 CallExpression call = getCallWithResult(condition); |
| 195 |
| 196 Continuation trueCont = node.trueContinuation.definition; |
| 197 Continuation falseCont = node.falseContinuation.definition; |
| 198 |
| 199 // Sink both continuations to the Branch to ensure everything in scope |
| 200 // here is also in scope inside the continuations. |
| 201 sinkContinuationToUse(trueCont, node); |
| 202 sinkContinuationToUse(falseCont, node); |
| 203 |
| 204 // If the condition is an 'is' check, promote the checked value. |
| 205 if (condition is TypeTest && condition.type.element is ClassElement) { |
| 206 Primitive value = condition.value.definition; |
| 207 ClassElement classElement = condition.type.element; |
| 208 TypeMask type = new TypeMask.nonNullSubtype(classElement, world); |
| 209 Primitive refinedValue = new Refinement(value, type); |
| 210 pushRefinement(trueCont, refinedValue); |
| 211 push(falseCont); |
| 212 return; |
| 213 } |
| 214 |
| 215 // If the condition is comparison with a constant, promote the other value. |
| 216 if (call is InvokeMethod && call.selector == Selectors.equals) { |
| 217 Primitive first = call.arguments[0].definition; |
| 218 Primitive second = call.arguments[1].definition; |
| 219 if (second is Constant && second.value.isNull) { |
| 220 Refinement refinedTrue = new Refinement(first, nullType); |
| 221 Refinement refinedFalse = new Refinement(first, nonNullType); |
| 222 pushRefinement(trueCont, refinedTrue); |
| 223 pushRefinement(falseCont, refinedFalse); |
| 224 return; |
| 225 } |
| 226 if (first is Constant && first.value.isNull) { |
| 227 Refinement refinedTrue = new Refinement(second, nullType); |
| 228 Refinement refinedFalse = new Refinement(second, nonNullType); |
| 229 pushRefinement(trueCont, refinedTrue); |
| 230 pushRefinement(falseCont, refinedFalse); |
| 231 return; |
| 232 } |
| 233 // For non-null constants, we can only refine in the true branch, |
| 234 // since TypeMask has no complement type for other constants. |
| 235 if (second is Constant) { |
| 236 Refinement refinedTrue = new Refinement(first, getTypeOf(second.value)); |
| 237 pushRefinement(trueCont, refinedTrue); |
| 238 push(falseCont); |
| 239 return; |
| 240 } |
| 241 if (first is Constant) { |
| 242 Refinement refinedTrue = new Refinement(second, getTypeOf(first.value)); |
| 243 pushRefinement(trueCont, refinedTrue); |
| 244 push(falseCont); |
| 245 return; |
| 246 } |
| 247 } |
| 248 |
| 249 push(trueCont); |
| 250 push(falseCont); |
| 251 } |
| 252 |
| 253 @override |
| 254 Expression traverseLetCont(LetCont node) { |
| 255 for (Continuation cont in node.continuations) { |
| 256 if (cont.hasExactlyOneUse && |
| 257 (cont.firstRef.parent is InvokeMethod || |
| 258 cont.firstRef.parent is Branch)) { |
| 259 // Do not push the continuation here. |
| 260 // visitInvokeMethod and visitBranch will do that. |
| 261 } else { |
| 262 push(cont); |
| 263 } |
| 264 } |
| 265 return node.body; |
| 266 } |
| 267 } |
OLD | NEW |