1 /*
   2  * Copyright (c) 2014, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package org.graalvm.compiler.phases.common;
  24 
  25 import java.util.List;
  26 
  27 import org.graalvm.compiler.debug.Debug;
  28 import org.graalvm.compiler.debug.DebugCounter;
  29 import org.graalvm.compiler.graph.Node;
  30 import org.graalvm.compiler.nodeinfo.InputType;
  31 import org.graalvm.compiler.nodes.AbstractBeginNode;
  32 import org.graalvm.compiler.nodes.AbstractDeoptimizeNode;
  33 import org.graalvm.compiler.nodes.AbstractEndNode;
  34 import org.graalvm.compiler.nodes.AbstractMergeNode;
  35 import org.graalvm.compiler.nodes.BeginNode;
  36 import org.graalvm.compiler.nodes.DeoptimizeNode;
  37 import org.graalvm.compiler.nodes.DynamicDeoptimizeNode;
  38 import org.graalvm.compiler.nodes.FixedNode;
  39 import org.graalvm.compiler.nodes.IfNode;
  40 import org.graalvm.compiler.nodes.LogicNode;
  41 import org.graalvm.compiler.nodes.StructuredGraph;
  42 import org.graalvm.compiler.nodes.ValueNode;
  43 import org.graalvm.compiler.nodes.ValuePhiNode;
  44 import org.graalvm.compiler.nodes.calc.IsNullNode;
  45 import org.graalvm.compiler.nodes.extended.NullCheckNode;
  46 import org.graalvm.compiler.nodes.util.GraphUtil;
  47 import org.graalvm.compiler.phases.BasePhase;
  48 import org.graalvm.compiler.phases.tiers.LowTierContext;
  49 
  50 import jdk.vm.ci.meta.DeoptimizationReason;
  51 import jdk.vm.ci.meta.JavaConstant;
  52 import jdk.vm.ci.meta.MetaAccessProvider;
  53 
  54 public class UseTrappingNullChecksPhase extends BasePhase<LowTierContext> {
  55 
  56     private static final DebugCounter counterTrappingNullCheck = Debug.counter("TrappingNullCheck");
  57     private static final DebugCounter counterTrappingNullCheckUnreached = Debug.counter("TrappingNullCheckUnreached");
  58     private static final DebugCounter counterTrappingNullCheckDynamicDeoptimize = Debug.counter("TrappingNullCheckDynamicDeoptimize");
  59 
  60     @Override
  61     protected void run(StructuredGraph graph, LowTierContext context) {
  62         if (context.getTarget().implicitNullCheckLimit <= 0) {
  63             return;
  64         }
  65         assert graph.getGuardsStage().areFrameStatesAtDeopts();
  66 
  67         for (DeoptimizeNode deopt : graph.getNodes(DeoptimizeNode.TYPE)) {
  68             tryUseTrappingNullCheck(deopt, deopt.predecessor(), deopt.reason(), deopt.getSpeculation());
  69         }
  70         for (DynamicDeoptimizeNode deopt : graph.getNodes(DynamicDeoptimizeNode.TYPE)) {
  71             tryUseTrappingNullCheck(context.getMetaAccess(), deopt);
  72         }
  73     }
  74 
  75     private static void tryUseTrappingNullCheck(MetaAccessProvider metaAccessProvider, DynamicDeoptimizeNode deopt) {
  76         Node predecessor = deopt.predecessor();
  77         if (predecessor instanceof AbstractMergeNode) {
  78             AbstractMergeNode merge = (AbstractMergeNode) predecessor;
  79 
  80             // Process each predecessor at the merge, unpacking the reasons and speculations as
  81             // needed.
  82             ValueNode reason = deopt.getActionAndReason();
  83             ValuePhiNode reasonPhi = null;
  84             List<ValueNode> reasons = null;
  85             int expectedPhis = 0;
  86 
  87             if (reason instanceof ValuePhiNode) {
  88                 reasonPhi = (ValuePhiNode) reason;
  89                 if (reasonPhi.merge() != merge) {
  90                     return;
  91                 }
  92                 reasons = reasonPhi.values().snapshot();
  93                 expectedPhis++;
  94             } else if (!reason.isConstant()) {
  95                 return;
  96             }
  97 
  98             ValueNode speculation = deopt.getSpeculation();
  99             ValuePhiNode speculationPhi = null;
 100             List<ValueNode> speculations = null;
 101             if (speculation instanceof ValuePhiNode) {
 102                 speculationPhi = (ValuePhiNode) speculation;
 103                 if (speculationPhi.merge() != merge) {
 104                     return;
 105                 }
 106                 speculations = speculationPhi.values().snapshot();
 107                 expectedPhis++;
 108             }
 109 
 110             if (merge.phis().count() != expectedPhis) {
 111                 return;
 112             }
 113 
 114             int index = 0;
 115             for (AbstractEndNode end : merge.cfgPredecessors().snapshot()) {
 116                 ValueNode thisReason = reasons != null ? reasons.get(index) : reason;
 117                 ValueNode thisSpeculation = speculations != null ? speculations.get(index++) : speculation;
 118                 if (!thisReason.isConstant() || !thisSpeculation.isConstant() || !thisSpeculation.asConstant().equals(JavaConstant.NULL_POINTER)) {
 119                     continue;
 120                 }
 121                 DeoptimizationReason deoptimizationReason = metaAccessProvider.decodeDeoptReason(thisReason.asJavaConstant());
 122                 tryUseTrappingNullCheck(deopt, end.predecessor(), deoptimizationReason, null);
 123             }
 124         }
 125     }
 126 
 127     private static void tryUseTrappingNullCheck(AbstractDeoptimizeNode deopt, Node predecessor, DeoptimizationReason deoptimizationReason, JavaConstant speculation) {
 128         if (deoptimizationReason != DeoptimizationReason.NullCheckException && deoptimizationReason != DeoptimizationReason.UnreachedCode) {
 129             return;
 130         }
 131         if (speculation != null && !speculation.equals(JavaConstant.NULL_POINTER)) {
 132             return;
 133         }
 134         if (predecessor instanceof AbstractMergeNode) {
 135             AbstractMergeNode merge = (AbstractMergeNode) predecessor;
 136             if (merge.phis().isEmpty()) {
 137                 for (AbstractEndNode end : merge.cfgPredecessors().snapshot()) {
 138                     checkPredecessor(deopt, end.predecessor(), deoptimizationReason);
 139                 }
 140             }
 141         } else if (predecessor instanceof AbstractBeginNode) {
 142             checkPredecessor(deopt, predecessor, deoptimizationReason);
 143         }
 144     }
 145 
 146     private static void checkPredecessor(AbstractDeoptimizeNode deopt, Node predecessor, DeoptimizationReason deoptimizationReason) {
 147         Node current = predecessor;
 148         AbstractBeginNode branch = null;
 149         while (current instanceof AbstractBeginNode) {
 150             branch = (AbstractBeginNode) current;
 151             if (branch.anchored().isNotEmpty()) {
 152                 // some input of the deopt framestate is anchored to this branch
 153                 return;
 154             }
 155             current = current.predecessor();
 156         }
 157         if (current instanceof IfNode) {
 158             IfNode ifNode = (IfNode) current;
 159             if (branch != ifNode.trueSuccessor()) {
 160                 return;
 161             }
 162             LogicNode condition = ifNode.condition();
 163             if (condition instanceof IsNullNode) {
 164                 replaceWithTrappingNullCheck(deopt, ifNode, condition, deoptimizationReason);
 165             }
 166         }
 167     }
 168 
 169     private static void replaceWithTrappingNullCheck(AbstractDeoptimizeNode deopt, IfNode ifNode, LogicNode condition, DeoptimizationReason deoptimizationReason) {
 170         counterTrappingNullCheck.increment();
 171         if (deopt instanceof DynamicDeoptimizeNode) {
 172             counterTrappingNullCheckDynamicDeoptimize.increment();
 173         }
 174         if (deoptimizationReason == DeoptimizationReason.UnreachedCode) {
 175             counterTrappingNullCheckUnreached.increment();
 176         }
 177         IsNullNode isNullNode = (IsNullNode) condition;
 178         AbstractBeginNode nonTrappingContinuation = ifNode.falseSuccessor();
 179         AbstractBeginNode trappingContinuation = ifNode.trueSuccessor();
 180         NullCheckNode trappingNullCheck = deopt.graph().add(new NullCheckNode(isNullNode.getValue()));
 181         trappingNullCheck.setStateBefore(deopt.stateBefore());
 182         deopt.graph().replaceSplit(ifNode, trappingNullCheck, nonTrappingContinuation);
 183 
 184         /*
 185          * We now have the pattern NullCheck/BeginNode/... It's possible some node is using the
 186          * BeginNode as a guard input, so replace guard users of the Begin with the NullCheck and
 187          * then remove the Begin from the graph.
 188          */
 189         nonTrappingContinuation.replaceAtUsages(InputType.Guard, trappingNullCheck);
 190         if (nonTrappingContinuation instanceof BeginNode) {
 191             FixedNode next = nonTrappingContinuation.next();
 192             nonTrappingContinuation.clearSuccessors();
 193             trappingNullCheck.setNext(next);
 194             nonTrappingContinuation.safeDelete();
 195         } else {
 196             trappingNullCheck.setNext(nonTrappingContinuation);
 197         }
 198 
 199         GraphUtil.killCFG(trappingContinuation);
 200         GraphUtil.tryKillUnused(isNullNode);
 201     }
 202 }