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 == null`. |
| 23 /// |
| 24 /// Refinement nodes are inserted after a method invocation to refine the |
| 25 /// receiver to the types that can respond to the given selector. |
| 26 class InsertRefinements extends RecursiveVisitor implements Pass { |
| 27 String get passName => 'Insert refinement nodes'; |
| 28 |
| 29 final TypesTask types; |
| 30 final World world; |
| 31 final TypeMask nonNullType; |
| 32 final TypeMask nullType; |
| 33 |
| 34 /// Maps unrefined primitives to its refinement currently in scope (if any). |
| 35 final Map<Primitive, Refinement> refinementFor = <Primitive, Refinement>{}; |
| 36 |
| 37 InsertRefinements(this.types, World world) |
| 38 : this.world = world, |
| 39 nonNullType = new TypeMask.nonNullSubtype(world.objectClass, world), |
| 40 nullType = new TypeMask.empty(); |
| 41 |
| 42 void rewrite(FunctionDefinition node) { |
| 43 new ParentVisitor().visit(node); |
| 44 visit(node.body); |
| 45 } |
| 46 |
| 47 /// Updates references to refer to the refinement currently in scope. |
| 48 void processReference(Reference node) { |
| 49 Refinement refined = refinementFor[node.definition]; |
| 50 if (refined != null) { |
| 51 node.changeTo(refined); |
| 52 } |
| 53 } |
| 54 |
| 55 /// Sinks the binding of [cont] to immediately above [use]. |
| 56 /// |
| 57 /// This is used to ensure that everything in scope at [use] is also in scope |
| 58 /// inside [cont], so refinements can be inserted inside [cont] without |
| 59 /// accidentally referencing a primitive out of scope. |
| 60 /// |
| 61 /// It is always safe to do this for single-use continuations, because |
| 62 /// strictly more things are in scope at the use site, and there can't be any |
| 63 /// other use of [cont] that might fall out of scope since there is only |
| 64 /// that single use. |
| 65 void sinkContinuationToUse(Continuation cont, Expression use) { |
| 66 assert(cont.hasExactlyOneUse && cont.firstRef.parent == use); |
| 67 assert(!cont.isRecursive); |
| 68 LetCont let = cont.parent; |
| 69 InteriorNode useParent = use.parent; |
| 70 if (useParent == let) return; |
| 71 if (let.continuations.length > 1) { |
| 72 // Create a new LetCont binding only this continuation. |
| 73 let.continuations.remove(cont); |
| 74 let = new LetCont(cont, null); |
| 75 cont.parent = let; |
| 76 } else { |
| 77 // Remove LetCont from current position. |
| 78 InteriorNode letParent = let.parent; |
| 79 letParent.body = let.body; |
| 80 let.body.parent = letParent; |
| 81 } |
| 82 |
| 83 // Insert LetCont before use. |
| 84 useParent.body = let; |
| 85 let.body = use; |
| 86 use.parent = let; |
| 87 let.parent = useParent; |
| 88 } |
| 89 |
| 90 Primitive unfoldInterceptor(Primitive prim) { |
| 91 return prim is Interceptor ? prim.input.definition : prim; |
| 92 } |
| 93 |
| 94 /// Enqueues [cont] for processing in a context where [refined] is the |
| 95 /// current refinement for its value. |
| 96 void pushRefinement(Continuation cont, Refinement refined) { |
| 97 Primitive value = refined.effectiveDefinition; |
| 98 Primitive currentRefinement = refinementFor[value]; |
| 99 pushAction(() { |
| 100 refinementFor[value] = currentRefinement; |
| 101 if (refined.hasNoUses) { |
| 102 // Clean up refinements that are not used. |
| 103 refined.value.unlink(); |
| 104 } else { |
| 105 cont.body = new LetPrim(refined, cont.body); |
| 106 refined.parent = cont.body; |
| 107 refined.value.parent = refined; |
| 108 } |
| 109 }); |
| 110 push(cont); |
| 111 pushAction(() { |
| 112 refinementFor[value] = refined; |
| 113 }); |
| 114 } |
| 115 |
| 116 void visitInvokeMethod(InvokeMethod node) { |
| 117 Continuation cont = node.continuation.definition; |
| 118 |
| 119 // Update references to their current refined values. |
| 120 processReference(node.receiver); |
| 121 node.arguments.forEach(processReference); |
| 122 |
| 123 // If the call is intercepted, we want to refine the actual receiver, |
| 124 // not the interceptor. |
| 125 Primitive receiver = unfoldInterceptor(node.receiver.definition); |
| 126 |
| 127 // Sink the continuation to the call to ensure everything in scope |
| 128 // here is also in scope inside the continuations. |
| 129 sinkContinuationToUse(cont, node); |
| 130 |
| 131 if (node.selector.isClosureCall) { |
| 132 // Do not try to refine the receiver of closure calls; the class world |
| 133 // does not know about closure classes. |
| 134 push(cont); |
| 135 } else { |
| 136 // Filter away receivers that throw on this selector. |
| 137 TypeMask type = world.allFunctions.receiverType(node.selector, node.mask); |
| 138 pushRefinement(cont, new Refinement(receiver, type)); |
| 139 } |
| 140 } |
| 141 |
| 142 CallExpression getCallWithResult(Primitive prim) { |
| 143 if (prim is Parameter && prim.parent is Continuation) { |
| 144 Continuation cont = prim.parent; |
| 145 if (cont.hasExactlyOneUse && cont.firstRef.parent is CallExpression) { |
| 146 return cont.firstRef.parent; |
| 147 } |
| 148 } |
| 149 return null; |
| 150 } |
| 151 |
| 152 bool isTrue(Primitive prim) { |
| 153 return prim is Constant && prim.value.isTrue; |
| 154 } |
| 155 |
| 156 TypeMask getTypeOf(ConstantValue constant) { |
| 157 return computeTypeMask(types.compiler, constant); |
| 158 } |
| 159 |
| 160 void visitBranch(Branch node) { |
| 161 processReference(node.condition); |
| 162 Primitive condition = node.condition.definition; |
| 163 CallExpression call = getCallWithResult(condition); |
| 164 |
| 165 Continuation trueCont = node.trueContinuation.definition; |
| 166 Continuation falseCont = node.falseContinuation.definition; |
| 167 |
| 168 // Sink both continuations to the Branch to ensure everything in scope |
| 169 // here is also in scope inside the continuations. |
| 170 sinkContinuationToUse(trueCont, node); |
| 171 sinkContinuationToUse(falseCont, node); |
| 172 |
| 173 // If the condition is an 'is' check, promote the checked value. |
| 174 if (condition is TypeTest && condition.type.element is ClassElement) { |
| 175 Primitive value = condition.value.definition; |
| 176 ClassElement classElement = condition.type.element; |
| 177 TypeMask type = new TypeMask.nonNullSubtype(classElement, world); |
| 178 Primitive refinedValue = new Refinement(value, type); |
| 179 pushRefinement(trueCont, refinedValue); |
| 180 push(falseCont); |
| 181 return; |
| 182 } |
| 183 |
| 184 // If the condition is comparison with a constant, promote the other value. |
| 185 if (call is InvokeMethod && call.selector == Selectors.equals) { |
| 186 Primitive first = call.arguments[0].definition; |
| 187 Primitive second = call.arguments[1].definition; |
| 188 if (second is Constant && second.value.isNull) { |
| 189 Refinement refinedTrue = new Refinement(first, nullType); |
| 190 Refinement refinedFalse = new Refinement(first, nonNullType); |
| 191 pushRefinement(trueCont, refinedTrue); |
| 192 pushRefinement(falseCont, refinedFalse); |
| 193 return; |
| 194 } |
| 195 if (first is Constant && first.value.isNull) { |
| 196 Refinement refinedTrue = new Refinement(second, nullType); |
| 197 Refinement refinedFalse = new Refinement(second, nonNullType); |
| 198 pushRefinement(trueCont, refinedTrue); |
| 199 pushRefinement(falseCont, refinedFalse); |
| 200 return; |
| 201 } |
| 202 } |
| 203 |
| 204 push(trueCont); |
| 205 push(falseCont); |
| 206 } |
| 207 |
| 208 @override |
| 209 Expression traverseLetCont(LetCont node) { |
| 210 for (Continuation cont in node.continuations) { |
| 211 if (cont.hasExactlyOneUse && |
| 212 (cont.firstRef.parent is InvokeMethod || |
| 213 cont.firstRef.parent is Branch)) { |
| 214 // Do not push the continuation here. |
| 215 // visitInvokeMethod and visitBranch will do that. |
| 216 } else { |
| 217 push(cont); |
| 218 } |
| 219 } |
| 220 return node.body; |
| 221 } |
| 222 } |
OLD | NEW |