--- /dev/null 2016-05-31 09:42:47.975716356 -0700 +++ new/src/jdk.vm.compiler/share/classes/org.graalvm.compiler.phases.common/src/org/graalvm/compiler/phases/common/GuardLoweringPhase.java 2016-12-09 00:56:14.181159446 -0800 @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.graalvm.compiler.phases.common; + +import static org.graalvm.compiler.core.common.GraalOptions.OptImplicitNullChecks; + +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import org.graalvm.compiler.core.common.cfg.Loop; +import org.graalvm.compiler.debug.Debug; +import org.graalvm.compiler.debug.DebugCloseable; +import org.graalvm.compiler.debug.DebugCounter; +import org.graalvm.compiler.graph.Node; +import org.graalvm.compiler.nodes.AbstractBeginNode; +import org.graalvm.compiler.nodes.BeginNode; +import org.graalvm.compiler.nodes.DeoptimizeNode; +import org.graalvm.compiler.nodes.FixedWithNextNode; +import org.graalvm.compiler.nodes.GuardNode; +import org.graalvm.compiler.nodes.IfNode; +import org.graalvm.compiler.nodes.LogicNode; +import org.graalvm.compiler.nodes.LoopBeginNode; +import org.graalvm.compiler.nodes.LoopExitNode; +import org.graalvm.compiler.nodes.PiNode; +import org.graalvm.compiler.nodes.StateSplit; +import org.graalvm.compiler.nodes.StructuredGraph; +import org.graalvm.compiler.nodes.StructuredGraph.GuardsStage; +import org.graalvm.compiler.nodes.StructuredGraph.ScheduleResult; +import org.graalvm.compiler.nodes.ValueNode; +import org.graalvm.compiler.nodes.calc.IsNullNode; +import org.graalvm.compiler.nodes.cfg.Block; +import org.graalvm.compiler.nodes.memory.Access; +import org.graalvm.compiler.nodes.memory.FixedAccessNode; +import org.graalvm.compiler.nodes.memory.FloatingAccessNode; +import org.graalvm.compiler.nodes.memory.MemoryNode; +import org.graalvm.compiler.nodes.memory.address.OffsetAddressNode; +import org.graalvm.compiler.nodes.util.GraphUtil; +import org.graalvm.compiler.phases.BasePhase; +import org.graalvm.compiler.phases.graph.ScheduledNodeIterator; +import org.graalvm.compiler.phases.schedule.SchedulePhase; +import org.graalvm.compiler.phases.schedule.SchedulePhase.SchedulingStrategy; +import org.graalvm.compiler.phases.tiers.MidTierContext; + +import jdk.vm.ci.meta.JavaConstant; + +/** + * This phase lowers {@link GuardNode GuardNodes} into corresponding control-flow structure and + * {@link DeoptimizeNode DeoptimizeNodes}. + * + * This allow to enter the {@link GuardsStage#FIXED_DEOPTS FIXED_DEOPTS} stage of the graph where + * all node that may cause deoptimization are fixed. + *

+ * It first makes a schedule in order to know where the control flow should be placed. Then, for + * each block, it applies two passes. The first one tries to replace null-check guards with implicit + * null checks performed by access to the objects that need to be null checked. The second phase + * does the actual control-flow expansion of the remaining {@link GuardNode GuardNodes}. + */ +public class GuardLoweringPhase extends BasePhase { + + private static final DebugCounter counterImplicitNullCheck = Debug.counter("ImplicitNullCheck"); + + private static class UseImplicitNullChecks extends ScheduledNodeIterator { + + private final Map nullGuarded = Node.newIdentityMap(); + private final int implicitNullCheckLimit; + + UseImplicitNullChecks(int implicitNullCheckLimit) { + this.implicitNullCheckLimit = implicitNullCheckLimit; + } + + @Override + protected void processNode(Node node) { + if (node instanceof GuardNode) { + processGuard(node); + } else if (node instanceof Access) { + processAccess((Access) node); + } else if (node instanceof PiNode) { + processPi((PiNode) node); + } + if (node instanceof StateSplit && ((StateSplit) node).stateAfter() != null) { + nullGuarded.clear(); + } else { + /* + * The OffsetAddressNode itself never forces materialization of a null check, even + * if its input is a PiNode. The null check will be folded into the first usage of + * the OffsetAddressNode, so we need to keep it in the nullGuarded map. + */ + if (!(node instanceof OffsetAddressNode)) { + Iterator> it = nullGuarded.entrySet().iterator(); + while (it.hasNext()) { + Entry entry = it.next(); + ValueNode guard = entry.getValue(); + if (guard.usages().contains(node)) { + it.remove(); + } else if (guard instanceof PiNode && guard != node) { + PiNode piNode = (PiNode) guard; + if (piNode.getGuard().asNode().usages().contains(node)) { + it.remove(); + } + } + } + } + } + } + + private boolean processPi(PiNode node) { + ValueNode guardNode = nullGuarded.get(node.object()); + if (guardNode != null && node.getGuard() == guardNode) { + nullGuarded.put(node, node); + return true; + } + return false; + } + + private void processAccess(Access access) { + if (access.canNullCheck() && access.getAddress() instanceof OffsetAddressNode) { + OffsetAddressNode address = (OffsetAddressNode) access.getAddress(); + check(access, address); + } + } + + private void check(Access access, OffsetAddressNode address) { + ValueNode base = address.getBase(); + ValueNode guard = nullGuarded.get(base); + if (guard != null && isImplicitNullCheck(address.getOffset())) { + if (guard instanceof PiNode) { + PiNode piNode = (PiNode) guard; + assert guard == address.getBase(); + assert piNode.getGuard() instanceof GuardNode : piNode; + address.setBase(piNode.getOriginalNode()); + } else { + assert guard instanceof GuardNode; + } + counterImplicitNullCheck.increment(); + access.setGuard(null); + FixedAccessNode fixedAccess; + if (access instanceof FloatingAccessNode) { + FloatingAccessNode floatingAccessNode = (FloatingAccessNode) access; + MemoryNode lastLocationAccess = floatingAccessNode.getLastLocationAccess(); + fixedAccess = floatingAccessNode.asFixedNode(); + replaceCurrent(fixedAccess); + if (lastLocationAccess != null) { + // fixed accesses are not currently part of the memory graph + GraphUtil.tryKillUnused(lastLocationAccess.asNode()); + } + } else { + fixedAccess = (FixedAccessNode) access; + } + fixedAccess.setNullCheck(true); + GuardNode guardNode = null; + if (guard instanceof GuardNode) { + guardNode = (GuardNode) guard; + } else { + PiNode piNode = (PiNode) guard; + guardNode = (GuardNode) piNode.getGuard(); + } + LogicNode condition = guardNode.getCondition(); + guardNode.replaceAndDelete(fixedAccess); + if (condition.hasNoUsages()) { + GraphUtil.killWithUnusedFloatingInputs(condition); + } + nullGuarded.remove(base); + } + } + + private void processGuard(Node node) { + GuardNode guard = (GuardNode) node; + if (guard.isNegated() && guard.getCondition() instanceof IsNullNode && (guard.getSpeculation() == null || guard.getSpeculation().equals(JavaConstant.NULL_POINTER))) { + ValueNode obj = ((IsNullNode) guard.getCondition()).getValue(); + nullGuarded.put(obj, guard); + } + } + + private boolean isImplicitNullCheck(ValueNode offset) { + JavaConstant c = offset.asJavaConstant(); + if (c != null) { + return c.asLong() < implicitNullCheckLimit; + } else { + return false; + } + } + } + + private static class LowerGuards extends ScheduledNodeIterator { + + private final Block block; + private boolean useGuardIdAsDebugId; + + LowerGuards(Block block, boolean useGuardIdAsDebugId) { + this.block = block; + this.useGuardIdAsDebugId = useGuardIdAsDebugId; + } + + @Override + protected void processNode(Node node) { + if (node instanceof GuardNode) { + GuardNode guard = (GuardNode) node; + FixedWithNextNode lowered = guard.lowerGuard(); + if (lowered != null) { + replaceCurrent(lowered); + } else { + lowerToIf(guard); + } + } + } + + @SuppressWarnings("try") + private void lowerToIf(GuardNode guard) { + try (DebugCloseable position = guard.withNodeSourcePosition()) { + StructuredGraph graph = guard.graph(); + AbstractBeginNode fastPath = graph.add(new BeginNode()); + @SuppressWarnings("deprecation") + int debugId = useGuardIdAsDebugId ? guard.getId() : DeoptimizeNode.DEFAULT_DEBUG_ID; + DeoptimizeNode deopt = graph.add(new DeoptimizeNode(guard.getAction(), guard.getReason(), debugId, guard.getSpeculation(), null)); + AbstractBeginNode deoptBranch = BeginNode.begin(deopt); + AbstractBeginNode trueSuccessor; + AbstractBeginNode falseSuccessor; + insertLoopExits(deopt); + if (guard.isNegated()) { + trueSuccessor = deoptBranch; + falseSuccessor = fastPath; + } else { + trueSuccessor = fastPath; + falseSuccessor = deoptBranch; + } + IfNode ifNode = graph.add(new IfNode(guard.getCondition(), trueSuccessor, falseSuccessor, trueSuccessor == fastPath ? 1 : 0)); + guard.replaceAndDelete(fastPath); + insert(ifNode, fastPath); + } + } + + private void insertLoopExits(DeoptimizeNode deopt) { + Loop loop = block.getLoop(); + StructuredGraph graph = deopt.graph(); + while (loop != null) { + LoopExitNode exit = graph.add(new LoopExitNode((LoopBeginNode) loop.getHeader().getBeginNode())); + graph.addBeforeFixed(deopt, exit); + loop = loop.getParent(); + } + } + } + + @Override + protected void run(StructuredGraph graph, MidTierContext context) { + if (graph.getGuardsStage().allowsFloatingGuards()) { + SchedulePhase schedulePhase = new SchedulePhase(SchedulingStrategy.EARLIEST); + schedulePhase.apply(graph); + ScheduleResult schedule = graph.getLastSchedule(); + + for (Block block : schedule.getCFG().getBlocks()) { + processBlock(block, schedule, context != null ? context.getTarget().implicitNullCheckLimit : 0); + } + graph.setGuardsStage(GuardsStage.FIXED_DEOPTS); + } + + assert assertNoGuardsLeft(graph); + } + + private static boolean assertNoGuardsLeft(StructuredGraph graph) { + assert graph.getNodes().filter(GuardNode.class).isEmpty(); + return true; + } + + private static void processBlock(Block block, ScheduleResult schedule, int implicitNullCheckLimit) { + if (OptImplicitNullChecks.getValue() && implicitNullCheckLimit > 0) { + new UseImplicitNullChecks(implicitNullCheckLimit).processNodes(block, schedule); + } + new LowerGuards(block, Debug.isDumpEnabledForMethod() || Debug.isLogEnabledForMethod()).processNodes(block, schedule); + } +}