| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2016, 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 library dart2js.cps_ir.use_field_initializers; | |
| 5 | |
| 6 import '../elements/elements.dart'; | |
| 7 import '../js_backend/js_backend.dart'; | |
| 8 import 'cps_ir_nodes.dart'; | |
| 9 import 'optimizers.dart'; | |
| 10 | |
| 11 /// Eliminates [SetField] instructions when the value can instead be passed into | |
| 12 /// the field initializer of a [CreateInstance] instruction. | |
| 13 /// | |
| 14 /// This compensates for a somewhat common pattern where fields are initialized | |
| 15 /// in the constructor body instead of using initializers. For example: | |
| 16 /// | |
| 17 /// class Foo { | |
| 18 /// var x, y; | |
| 19 /// Foo(x, y) { | |
| 20 /// this.x = x; | |
| 21 /// this.y = y; | |
| 22 /// } | |
| 23 /// } | |
| 24 /// | |
| 25 /// ==> (IR for Foo constructor) | |
| 26 /// | |
| 27 /// foo = new D.Foo(null, null); | |
| 28 /// foo.x = 'a'; | |
| 29 /// foo.y = 'b'; | |
| 30 /// | |
| 31 /// ==> (after this pass) | |
| 32 /// | |
| 33 /// foo = new D.Foo('a', 'b'); | |
| 34 // | |
| 35 // TODO(asgerf): Store forwarding and load elimination could most likely | |
| 36 // handle this more generally. | |
| 37 // | |
| 38 class UseFieldInitializers extends BlockVisitor implements Pass { | |
| 39 String get passName => 'Use field initializers'; | |
| 40 | |
| 41 final JavaScriptBackend backend; | |
| 42 | |
| 43 final Set<CreateInstance> unescaped = new Set<CreateInstance>(); | |
| 44 | |
| 45 /// Continuation bindings separating the current traversal position from an | |
| 46 /// unescaped [CreateInstance]. When [CreateInstance] is sunk, these | |
| 47 /// continuations must sink as well to ensure the object remains in scope | |
| 48 /// inside the bound continuations. | |
| 49 final List<LetCont> letConts = <LetCont>[]; | |
| 50 | |
| 51 /// If non-null, the bindings in [letConts] should sink to immediately below | |
| 52 /// this node. | |
| 53 InteriorNode letContSinkTarget = null; | |
| 54 EscapeVisitor escapeVisitor; | |
| 55 | |
| 56 UseFieldInitializers(this.backend); | |
| 57 | |
| 58 void rewrite(FunctionDefinition node) { | |
| 59 escapeVisitor = new EscapeVisitor(this); | |
| 60 BlockVisitor.traverseInPreOrder(node, this); | |
| 61 } | |
| 62 | |
| 63 void escape(Reference ref) { | |
| 64 Definition def = ref.definition; | |
| 65 if (def is CreateInstance) { | |
| 66 unescaped.remove(def); | |
| 67 if (unescaped.isEmpty) { | |
| 68 sinkLetConts(); | |
| 69 letConts.clear(); | |
| 70 } | |
| 71 } | |
| 72 } | |
| 73 | |
| 74 void visitContinuation(Continuation node) { | |
| 75 endBasicBlock(); | |
| 76 } | |
| 77 | |
| 78 void visitLetHandler(LetHandler node) { | |
| 79 endBasicBlock(); | |
| 80 } | |
| 81 | |
| 82 void visitInvokeContinuation(InvokeContinuation node) { | |
| 83 endBasicBlock(); | |
| 84 } | |
| 85 | |
| 86 void visitBranch(Branch node) { | |
| 87 endBasicBlock(); | |
| 88 } | |
| 89 | |
| 90 void visitRethrow(Rethrow node) { | |
| 91 endBasicBlock(); | |
| 92 } | |
| 93 | |
| 94 void visitThrow(Throw node) { | |
| 95 endBasicBlock(); | |
| 96 } | |
| 97 | |
| 98 void visitUnreachable(Unreachable node) { | |
| 99 endBasicBlock(); | |
| 100 } | |
| 101 | |
| 102 void visitLetMutable(LetMutable node) { | |
| 103 escape(node.valueRef); | |
| 104 } | |
| 105 | |
| 106 void visitLetCont(LetCont node) { | |
| 107 if (unescaped.isNotEmpty) { | |
| 108 // Ensure we do not lift a LetCont if there is a sink target set above | |
| 109 // the current node. | |
| 110 sinkLetConts(); | |
| 111 letConts.add(node); | |
| 112 } | |
| 113 } | |
| 114 | |
| 115 void sinkLetConts() { | |
| 116 if (letContSinkTarget != null) { | |
| 117 for (LetCont letCont in letConts.reversed) { | |
| 118 letCont | |
| 119 ..remove() | |
| 120 ..insertBelow(letContSinkTarget); | |
| 121 } | |
| 122 letContSinkTarget = null; | |
| 123 } | |
| 124 } | |
| 125 | |
| 126 void endBasicBlock() { | |
| 127 sinkLetConts(); | |
| 128 letConts.clear(); | |
| 129 unescaped.clear(); | |
| 130 } | |
| 131 | |
| 132 void visitLetPrim(LetPrim node) { | |
| 133 Primitive prim = node.primitive; | |
| 134 if (prim is CreateInstance) { | |
| 135 unescaped.add(prim); | |
| 136 prim.argumentRefs.forEach(escape); | |
| 137 return; | |
| 138 } | |
| 139 if (unescaped.isEmpty) return; | |
| 140 if (prim is SetField) { | |
| 141 escape(prim.valueRef); | |
| 142 Primitive object = prim.object; | |
| 143 if (object is CreateInstance && unescaped.contains(object)) { | |
| 144 int index = getFieldIndex(object.classElement, prim.field); | |
| 145 if (index == -1) { | |
| 146 // This field is not initialized at creation time, so we cannot pull | |
| 147 // set SetField into the CreateInstance instruction. We have to | |
| 148 // leave the instruction here, and this counts as a use of the object. | |
| 149 escape(prim.objectRef); | |
| 150 } else { | |
| 151 // Replace the field initializer with the new value. There are no uses | |
| 152 // of the object before this, so the old value cannot have been seen. | |
| 153 object.argumentRefs[index].changeTo(prim.value); | |
| 154 prim.destroy(); | |
| 155 // The right-hand side might not be in scope at the CreateInstance. | |
| 156 // Sink the creation down to this point. | |
| 157 rebindCreateInstanceAt(object, node); | |
| 158 letContSinkTarget = node; | |
| 159 } | |
| 160 } | |
| 161 return; | |
| 162 } | |
| 163 if (prim is GetField) { | |
| 164 // When reading the field of a newly created object, just use the initial | |
| 165 // value and destroy the GetField. This can unblock the other optimization | |
| 166 // since we remove a use of the object. | |
| 167 Primitive object = prim.object; | |
| 168 if (object is CreateInstance && unescaped.contains(object)) { | |
| 169 int index = getFieldIndex(object.classElement, prim.field); | |
| 170 if (index == -1) { | |
| 171 escape(prim.objectRef); | |
| 172 } else { | |
| 173 prim.replaceUsesWith(object.argument(index)); | |
| 174 prim.destroy(); | |
| 175 node.remove(); | |
| 176 } | |
| 177 } | |
| 178 return; | |
| 179 } | |
| 180 escapeVisitor.visit(node.primitive); | |
| 181 } | |
| 182 | |
| 183 void rebindCreateInstanceAt(CreateInstance prim, LetPrim newBinding) { | |
| 184 removeBinding(prim); | |
| 185 newBinding.primitive = prim; | |
| 186 prim.parent = newBinding; | |
| 187 } | |
| 188 | |
| 189 /// Returns the index of [field] in the canonical initialization order in | |
| 190 /// [classElement], or -1 if the field is not initialized at creation time | |
| 191 /// for that class. | |
| 192 int getFieldIndex(ClassElement classElement, FieldElement field) { | |
| 193 // There is no stored map from a field to its index in a given class, so we | |
| 194 // have to iterate over all instance fields until we find it. | |
| 195 int current = -1, index = -1; | |
| 196 classElement.forEachInstanceField((host, currentField) { | |
| 197 if (!backend.isNativeOrExtendsNative(host)) { | |
| 198 ++current; | |
| 199 if (currentField == field) { | |
| 200 index = current; | |
| 201 } | |
| 202 } | |
| 203 }, includeSuperAndInjectedMembers: true); | |
| 204 return index; | |
| 205 } | |
| 206 | |
| 207 void removeBinding(Primitive prim) { | |
| 208 LetPrim node = prim.parent; | |
| 209 node.remove(); | |
| 210 } | |
| 211 } | |
| 212 | |
| 213 class EscapeVisitor extends DeepRecursiveVisitor { | |
| 214 final UseFieldInitializers main; | |
| 215 EscapeVisitor(this.main); | |
| 216 | |
| 217 processReference(Reference ref) { | |
| 218 main.escape(ref); | |
| 219 } | |
| 220 } | |
| OLD | NEW |