| OLD | NEW |
| (Empty) |
| 1 library dart2js.cps_ir.backward_null_check_remover; | |
| 2 | |
| 3 import 'cps_fragment.dart'; | |
| 4 import 'cps_ir_nodes.dart'; | |
| 5 import 'optimizers.dart'; | |
| 6 import 'type_mask_system.dart'; | |
| 7 | |
| 8 /// Removes null checks that are follwed by another instruction that will | |
| 9 /// perform the same check. | |
| 10 /// | |
| 11 /// For example: | |
| 12 /// | |
| 13 /// x.toString; // NullCheck instruction | |
| 14 /// print(x.length); | |
| 15 /// | |
| 16 /// ==> | |
| 17 /// | |
| 18 /// print(x.length); | |
| 19 /// | |
| 20 /// `x.length` will throw when x is null, so the original [ReceiverCheck] is not | |
| 21 /// needed. This changes the error message, but at least for now we are | |
| 22 /// willing to accept this. | |
| 23 /// | |
| 24 /// Note that code motion may not occur after this pass, since the [ReceiverChec
k] | |
| 25 /// nodes are not there to restrict it. | |
| 26 // | |
| 27 // TODO(asgerf): It would be nice with a clear specification of when we allow | |
| 28 // the wording of error message to change. E.g. "toString" is already pretty | |
| 29 // bad so changing that should be ok, but changing a field access is not as | |
| 30 // clear. | |
| 31 // | |
| 32 class BackwardNullCheckRemover extends BlockVisitor implements Pass { | |
| 33 String get passName => 'Backward null-check remover'; | |
| 34 | |
| 35 final TypeMaskSystem typeSystem; | |
| 36 | |
| 37 /// When the analysis of an expression completes, [nullCheckValue] refers to | |
| 38 /// a value that is checked in the beginning of that expression. | |
| 39 Primitive nullCheckedValue; | |
| 40 | |
| 41 /// The [nullCheckedValue] at the entry point of a continuation. | |
| 42 final Map<Continuation, Primitive> nullCheckedValueAt = | |
| 43 <Continuation, Primitive>{}; | |
| 44 | |
| 45 BackwardNullCheckRemover(this.typeSystem); | |
| 46 | |
| 47 void rewrite(FunctionDefinition node) { | |
| 48 BlockVisitor.traverseInPostOrder(node, this); | |
| 49 } | |
| 50 | |
| 51 /// Returns an operand of [prim] that throws if null is passed into it. | |
| 52 Primitive getNullCheckedOperand(Primitive prim) { | |
| 53 if (prim is ReceiverCheck) return prim.value; | |
| 54 if (prim is GetLength) return prim.object; | |
| 55 if (prim is GetField) return prim.object; | |
| 56 if (prim is GetIndex) return prim.object; | |
| 57 if (prim is SetField) return prim.object; | |
| 58 if (prim is SetIndex) return prim.object; | |
| 59 if (prim is InvokeMethod && !selectorsOnNull.contains(prim.selector)) { | |
| 60 return prim.receiver; | |
| 61 } | |
| 62 if (prim is ForeignCode) { | |
| 63 return prim.isNullGuardOnNullFirstArgument() ? prim.argument(0) : null; | |
| 64 } | |
| 65 return null; | |
| 66 } | |
| 67 | |
| 68 /// It has been determined that the null check in [prim] made redundant by | |
| 69 /// [newNullCheck]. Eliminate [prim] if it is not needed any more. | |
| 70 void tryEliminateRedundantNullCheck(Primitive prim, Primitive newNullCheck) { | |
| 71 if (prim is ReceiverCheck && prim.isNullCheck) { | |
| 72 Primitive value = prim.value; | |
| 73 LetPrim let = prim.parent; | |
| 74 prim | |
| 75 ..replaceUsesWith(value) | |
| 76 ..destroy(); | |
| 77 let.remove(); | |
| 78 } else if (prim is GetLength || prim is GetField || prim is GetIndex) { | |
| 79 if (prim.hasNoRefinedUses) { | |
| 80 destroyRefinementsOfDeadPrimitive(prim); | |
| 81 LetPrim let = prim.parent; | |
| 82 prim..destroy(); | |
| 83 let.remove(); | |
| 84 } | |
| 85 } | |
| 86 } | |
| 87 | |
| 88 /// True if [prim] can be moved above a null check. This is safe if [prim] | |
| 89 /// cannot throw or have side effects and does not carry any path-sensitive | |
| 90 /// type information, such as [Refinement] nodes do. | |
| 91 // | |
| 92 // TODO(asgerf): This prevents elimination of the .length created for a bounds | |
| 93 // check, because there is a refinement node below it. To handle this, we | |
| 94 // would have to relocate the [Refinement] node below the new null check. | |
| 95 bool canMoveAboveNullCheck(Primitive prim) { | |
| 96 return prim.isSafeForReordering; | |
| 97 } | |
| 98 | |
| 99 void visitLetPrim(LetPrim node) { | |
| 100 Primitive prim = node.primitive; | |
| 101 Primitive receiver = getNullCheckedOperand(prim); | |
| 102 if (receiver != null) { | |
| 103 if (nullCheckedValue != null && receiver.sameValue(nullCheckedValue)) { | |
| 104 tryEliminateRedundantNullCheck(prim, nullCheckedValue); | |
| 105 } | |
| 106 nullCheckedValue = receiver; | |
| 107 } else if (!canMoveAboveNullCheck(prim)) { | |
| 108 nullCheckedValue = null; | |
| 109 } | |
| 110 } | |
| 111 | |
| 112 void visitContinuation(Continuation cont) { | |
| 113 if (nullCheckedValue != null) { | |
| 114 nullCheckedValueAt[cont] = nullCheckedValue; | |
| 115 nullCheckedValue = null; | |
| 116 } | |
| 117 } | |
| 118 | |
| 119 void visitLetHandler(LetHandler node) { | |
| 120 nullCheckedValue = null; | |
| 121 } | |
| 122 | |
| 123 visitInvokeContinuation(InvokeContinuation node) { | |
| 124 if (!node.isRecursive) { | |
| 125 nullCheckedValue = nullCheckedValueAt[node.continuation]; | |
| 126 } | |
| 127 } | |
| 128 } | |
| OLD | NEW |