/*
 * Copyright (c) 2016, 2017, 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.hotspot.phases.aot;

import static org.graalvm.compiler.core.common.cfg.AbstractControlFlowGraph.strictlyDominates;
import static org.graalvm.compiler.hotspot.nodes.aot.LoadMethodCountersNode.getLoadMethodCountersNodes;
import static org.graalvm.compiler.nodes.ConstantNode.getConstantNodes;

import java.util.HashSet;
import java.util.List;

import jdk.internal.vm.compiler.collections.EconomicMap;
import org.graalvm.compiler.core.common.cfg.BlockMap;
import org.graalvm.compiler.core.common.type.ObjectStamp;
import org.graalvm.compiler.core.common.type.Stamp;
import org.graalvm.compiler.core.common.type.StampFactory;
import org.graalvm.compiler.debug.GraalError;
import org.graalvm.compiler.graph.Node;
import org.graalvm.compiler.graph.NodeMap;
import org.graalvm.compiler.hotspot.meta.HotSpotConstantLoadAction;
import org.graalvm.compiler.hotspot.nodes.aot.InitializeKlassNode;
import org.graalvm.compiler.hotspot.nodes.aot.LoadConstantIndirectlyFixedNode;
import org.graalvm.compiler.hotspot.nodes.aot.LoadConstantIndirectlyNode;
import org.graalvm.compiler.hotspot.nodes.aot.LoadMethodCountersNode;
import org.graalvm.compiler.hotspot.nodes.aot.ResolveConstantNode;
import org.graalvm.compiler.hotspot.nodes.aot.ResolveDynamicConstantNode;
import org.graalvm.compiler.hotspot.nodes.aot.ResolveMethodAndLoadCountersNode;
import org.graalvm.compiler.nodes.AbstractBeginNode;
import org.graalvm.compiler.nodes.AbstractMergeNode;
import org.graalvm.compiler.nodes.ConstantNode;
import org.graalvm.compiler.nodes.FixedNode;
import org.graalvm.compiler.nodes.FixedWithNextNode;
import org.graalvm.compiler.nodes.FrameState;
import org.graalvm.compiler.nodes.LoopBeginNode;
import org.graalvm.compiler.nodes.LoopExitNode;
import org.graalvm.compiler.nodes.StateSplit;
import org.graalvm.compiler.nodes.StructuredGraph;
import org.graalvm.compiler.nodes.StructuredGraph.ScheduleResult;
import org.graalvm.compiler.nodes.ValueNode;
import org.graalvm.compiler.nodes.calc.FloatingNode;
import org.graalvm.compiler.nodes.cfg.Block;
import org.graalvm.compiler.phases.BasePhase;
import org.graalvm.compiler.phases.graph.ReentrantNodeIterator;
import org.graalvm.compiler.phases.graph.ReentrantNodeIterator.NodeIteratorClosure;
import org.graalvm.compiler.phases.schedule.SchedulePhase;
import org.graalvm.compiler.phases.schedule.SchedulePhase.SchedulingStrategy;
import org.graalvm.compiler.phases.tiers.PhaseContext;

import jdk.vm.ci.code.BytecodeFrame;
import jdk.vm.ci.hotspot.HotSpotMetaspaceConstant;
import jdk.vm.ci.hotspot.HotSpotObjectConstant;
import jdk.vm.ci.hotspot.HotSpotResolvedJavaType;
import jdk.vm.ci.hotspot.HotSpotResolvedObjectType;
import jdk.vm.ci.meta.Constant;
import jdk.vm.ci.meta.ConstantReflectionProvider;
import jdk.vm.ci.meta.MetaAccessProvider;
import jdk.vm.ci.meta.ResolvedJavaType;

public class ReplaceConstantNodesPhase extends BasePhase<PhaseContext> {

    private final boolean verifyFingerprints;

    static Class<?> characterCacheClass = Character.class.getDeclaredClasses()[0];
    static Class<?> byteCacheClass = Byte.class.getDeclaredClasses()[0];
    static Class<?> shortCacheClass = Short.class.getDeclaredClasses()[0];
    static Class<?> integerCacheClass = Integer.class.getDeclaredClasses()[0];
    static Class<?> longCacheClass = Long.class.getDeclaredClasses()[0];

    static class ClassInfo {

        private ResolvedJavaType stringType;
        private final HashSet<ResolvedJavaType> builtIns = new HashSet<>();

        ClassInfo(MetaAccessProvider metaAccessProvider) {
            builtIns.add(metaAccessProvider.lookupJavaType(Boolean.class));

            assert "java.lang.Character$CharacterCache".equals(characterCacheClass.getName());
            builtIns.add(metaAccessProvider.lookupJavaType(characterCacheClass));

            assert "java.lang.Byte$ByteCache".equals(byteCacheClass.getName());
            builtIns.add(metaAccessProvider.lookupJavaType(byteCacheClass));

            assert "java.lang.Short$ShortCache".equals(shortCacheClass.getName());
            builtIns.add(metaAccessProvider.lookupJavaType(shortCacheClass));

            assert "java.lang.Integer$IntegerCache".equals(integerCacheClass.getName());
            builtIns.add(metaAccessProvider.lookupJavaType(integerCacheClass));

            assert "java.lang.Long$LongCache".equals(longCacheClass.getName());
            builtIns.add(metaAccessProvider.lookupJavaType(longCacheClass));

            stringType = metaAccessProvider.lookupJavaType(String.class);
        }
    }

    private static boolean isReplacementNode(Node n) {
        // @formatter:off
        return n instanceof LoadConstantIndirectlyNode      ||
                n instanceof LoadConstantIndirectlyFixedNode ||
                n instanceof ResolveDynamicConstantNode      ||
                n instanceof ResolveConstantNode             ||
                n instanceof InitializeKlassNode;
        // @formatter:on
    }

    private static boolean anyUsagesNeedReplacement(ConstantNode node) {
        return node.usages().filter(n -> !isReplacementNode(n)).isNotEmpty();
    }

    private static boolean anyUsagesNeedReplacement(LoadMethodCountersNode node) {
        return node.usages().filter(n -> !(n instanceof ResolveMethodAndLoadCountersNode)).isNotEmpty();
    }

    private static boolean checkForBadFingerprint(HotSpotResolvedJavaType type) {
        if (type.isArray()) {
            if (type.getElementalType().isPrimitive()) {
                return false;
            }
            return ((HotSpotResolvedObjectType) (type.getElementalType())).getFingerprint() == 0;
        }
        return ((HotSpotResolvedObjectType) type).getFingerprint() == 0;
    }

    
Insert the replacement node into the graph. We may need to insert it into a place different than the original FloatingNode since we need to make sure that replacement will have a valid state assigned.
Params:
  • graph –
  • stateMapper –
  • node –
  • replacement –
/** * Insert the replacement node into the graph. We may need to insert it into a place different * than the original {@link FloatingNode} since we need to make sure that replacement will have * a valid state assigned. * * @param graph * @param stateMapper * @param node * @param replacement */
private static void insertReplacement(StructuredGraph graph, FrameStateMapperClosure stateMapper, FloatingNode node, FixedWithNextNode replacement) { FixedWithNextNode insertionPoint = findInsertionPoint(graph, stateMapper, node); graph.addAfterFixed(insertionPoint, replacement); stateMapper.addState(replacement, stateMapper.getState(insertionPoint)); }
Find a good place to insert a stateful fixed node that is above the given node. A good insertion point should have a valid FrameState reaching it.
Params:
  • graph –
  • stateMapper –
  • node – start search from this node up
Returns:an insertion point
/** * Find a good place to insert a stateful fixed node that is above the given node. A good * insertion point should have a valid FrameState reaching it. * * @param graph * @param stateMapper * @param node start search from this node up * @return an insertion point */
private static FixedWithNextNode findInsertionPoint(StructuredGraph graph, FrameStateMapperClosure stateMapper, FloatingNode node) { FixedWithNextNode fixed = findFixedBeforeFloating(graph, node); FixedWithNextNode result = findFixedWithValidState(graph, stateMapper, fixed); return result; }
Find the first FixedWithNextNode that is currently scheduled before the given floating node.
Params:
  • graph –
  • node – start search from this node up
Returns:the first FixedWithNextNode
/** * Find the first {@link FixedWithNextNode} that is currently scheduled before the given * floating node. * * @param graph * @param node start search from this node up * @return the first {@link FixedWithNextNode} */
private static FixedWithNextNode findFixedBeforeFloating(StructuredGraph graph, FloatingNode node) { ScheduleResult schedule = graph.getLastSchedule(); NodeMap<Block> nodeToBlock = schedule.getNodeToBlockMap(); Block block = nodeToBlock.get(node); BlockMap<List<Node>> blockToNodes = schedule.getBlockToNodesMap(); FixedWithNextNode result = null; for (Node n : blockToNodes.get(block)) { if (n.equals(node)) { break; } if (n instanceof FixedWithNextNode) { result = (FixedWithNextNode) n; } } assert result != null; return result; }
Find first dominating FixedWithNextNode that has a valid state reaching it starting from the given node.
Params:
  • graph –
  • stateMapper –
  • node –
Returns:FixedWithNextNode that we can use as an insertion point
/** * Find first dominating {@link FixedWithNextNode} that has a valid state reaching it starting * from the given node. * * @param graph * @param stateMapper * @param node * @return {@link FixedWithNextNode} that we can use as an insertion point */
private static FixedWithNextNode findFixedWithValidState(StructuredGraph graph, FrameStateMapperClosure stateMapper, FixedWithNextNode node) { ScheduleResult schedule = graph.getLastSchedule(); NodeMap<Block> nodeToBlock = schedule.getNodeToBlockMap(); Block block = nodeToBlock.get(node); Node n = node; do { if (isFixedWithValidState(stateMapper, n)) { return (FixedWithNextNode) n; } while (n != block.getBeginNode()) { n = n.predecessor(); if (isFixedWithValidState(stateMapper, n)) { return (FixedWithNextNode) n; } } block = block.getDominator(); if (block != null) { n = block.getEndNode(); } } while (block != null); return graph.start(); } private static boolean isFixedWithValidState(FrameStateMapperClosure stateMapper, Node n) { if (n instanceof FixedWithNextNode) { FixedWithNextNode fixed = (FixedWithNextNode) n; assert stateMapper.getState(fixed) != null; if (!BytecodeFrame.isPlaceholderBci(stateMapper.getState(fixed).bci)) { return true; } } return false; }
Compute frame states for all fixed nodes in the graph.
/** * Compute frame states for all fixed nodes in the graph. */
private static class FrameStateMapperClosure extends NodeIteratorClosure<FrameState> { private NodeMap<FrameState> reachingStates; @Override protected FrameState processNode(FixedNode node, FrameState previousState) { FrameState currentState = previousState; if (node instanceof StateSplit) { StateSplit stateSplit = (StateSplit) node; FrameState stateAfter = stateSplit.stateAfter(); if (stateAfter != null) { currentState = stateAfter; } } reachingStates.put(node, currentState); return currentState; } @Override protected FrameState merge(AbstractMergeNode merge, List<FrameState> states) { FrameState singleFrameState = singleFrameState(states); FrameState currentState = singleFrameState == null ? merge.stateAfter() : singleFrameState; reachingStates.put(merge, currentState); return currentState; } @Override protected FrameState afterSplit(AbstractBeginNode node, FrameState oldState) { return oldState; } @Override protected EconomicMap<LoopExitNode, FrameState> processLoop(LoopBeginNode loop, FrameState initialState) { return ReentrantNodeIterator.processLoop(this, loop, initialState).exitStates; } private static FrameState singleFrameState(List<FrameState> states) { FrameState singleState = states.get(0); for (int i = 1; i < states.size(); ++i) { if (states.get(i) != singleState) { return null; } } return singleState; } FrameStateMapperClosure(StructuredGraph graph) { reachingStates = new NodeMap<>(graph); } public FrameState getState(Node n) { return reachingStates.get(n); } public void addState(Node n, FrameState s) { reachingStates.setAndGrow(n, s); } }
Try to find dominating node doing the resolution that can be reused.
Params:
/** * Try to find dominating node doing the resolution that can be reused. * * @param graph * @param node {@link ConstantNode} containing a {@link HotSpotResolvedJavaType} that needs * resolution. */
private static void tryToReplaceWithExisting(StructuredGraph graph, ConstantNode node) { ScheduleResult schedule = graph.getLastSchedule(); NodeMap<Block> nodeToBlock = schedule.getNodeToBlockMap(); BlockMap<List<Node>> blockToNodes = schedule.getBlockToNodesMap(); EconomicMap<Block, Node> blockToExisting = EconomicMap.create(); for (Node n : node.usages().filter(n -> isReplacementNode(n))) { blockToExisting.put(nodeToBlock.get(n), n); } for (Node use : node.usages().filter(n -> !isReplacementNode(n)).snapshot()) { boolean replaced = false; Block b = nodeToBlock.get(use); Node e = blockToExisting.get(b); if (e != null) { // There is an initialization or resolution in the same block as the use, look if // the use is scheduled after it. for (Node n : blockToNodes.get(b)) { if (n.equals(use)) { // Usage is before initialization, can't use it break; } if (n.equals(e)) { use.replaceFirstInput(node, e); replaced = true; break; } } } if (!replaced) { // Look for dominating blocks that have existing nodes for (Block d : blockToExisting.getKeys()) { if (strictlyDominates(d, b)) { use.replaceFirstInput(node, blockToExisting.get(d)); break; } } } } }
Replace the uses of a constant with either LoadConstantIndirectlyNode or ResolveConstantNode.
Params:
/** * Replace the uses of a constant with either {@link LoadConstantIndirectlyNode} or * {@link ResolveConstantNode}. * * @param graph * @param stateMapper * @param node {@link ConstantNode} containing a {@link HotSpotResolvedJavaType} that needs * resolution. */
private static void replaceWithResolution(StructuredGraph graph, FrameStateMapperClosure stateMapper, ConstantNode node, ClassInfo classInfo) { HotSpotMetaspaceConstant metaspaceConstant = (HotSpotMetaspaceConstant) node.asConstant(); HotSpotResolvedJavaType type = (HotSpotResolvedJavaType) metaspaceConstant.asResolvedJavaType(); ResolvedJavaType topMethodHolder = graph.method().getDeclaringClass(); ValueNode replacement; if (type.isArray() && type.getComponentType().isPrimitive()) { // Special case for primitive arrays. The AOT runtime pre-resolves them, so we may // omit the resolution call. replacement = graph.addOrUnique(new LoadConstantIndirectlyNode(node)); } else if (type.equals(topMethodHolder) || (type.isAssignableFrom(topMethodHolder) && !type.isInterface())) { // If it's a supertype of or the same class that declares the top method, we are // guaranteed to have it resolved already. If it's an interface, we just test for // equality. replacement = graph.addOrUnique(new LoadConstantIndirectlyNode(node)); } else { FixedWithNextNode fixedReplacement; if (classInfo.builtIns.contains(type)) { // Special case of klass constants that come from {@link BoxingSnippets}. fixedReplacement = graph.add(new ResolveConstantNode(node, HotSpotConstantLoadAction.INITIALIZE)); } else { fixedReplacement = graph.add(new ResolveConstantNode(node)); } insertReplacement(graph, stateMapper, node, fixedReplacement); replacement = fixedReplacement; } node.replaceAtUsages(replacement, n -> !isReplacementNode(n)); }
Replace ConstantNode containing a HotSpotResolvedJavaType with indirection.
Params:
/** * Replace {@link ConstantNode} containing a {@link HotSpotResolvedJavaType} with indirection. * * @param graph * @param stateMapper * @param node {@link ConstantNode} containing a {@link HotSpotResolvedJavaType} that needs * resolution. */
private void handleHotSpotMetaspaceConstant(StructuredGraph graph, FrameStateMapperClosure stateMapper, ConstantNode node, ClassInfo classInfo) { HotSpotMetaspaceConstant metaspaceConstant = (HotSpotMetaspaceConstant) node.asConstant(); HotSpotResolvedJavaType type = (HotSpotResolvedJavaType) metaspaceConstant.asResolvedJavaType(); if (type != null) { if (verifyFingerprints && checkForBadFingerprint(type)) { throw new GraalError("Type with bad fingerprint: " + type); } assert !metaspaceConstant.isCompressed() : "No support for replacing compressed metaspace constants"; tryToReplaceWithExisting(graph, node); if (anyUsagesNeedReplacement(node)) { replaceWithResolution(graph, stateMapper, node, classInfo); } } else { throw new GraalError("Unsupported metaspace constant type: " + type); } }
Replace an object constant with an indirect load ResolveConstantNode. Currently we support only strings.
Params:
/** * Replace an object constant with an indirect load {@link ResolveConstantNode}. Currently we * support only strings. * * @param graph * @param stateMapper * @param node {@link ConstantNode} containing a {@link HotSpotObjectConstant} that needs * resolution. */
private static void handleHotSpotObjectConstant(StructuredGraph graph, FrameStateMapperClosure stateMapper, ConstantNode node, ClassInfo classInfo) { HotSpotObjectConstant constant = (HotSpotObjectConstant) node.asJavaConstant(); HotSpotResolvedJavaType type = (HotSpotResolvedJavaType) constant.getType(); if (type.equals(classInfo.stringType)) { assert !constant.isCompressed() : "No support for replacing compressed oop constants"; FixedWithNextNode replacement = graph.add(new ResolveConstantNode(node)); insertReplacement(graph, stateMapper, node, replacement); node.replaceAtUsages(replacement, n -> !(n instanceof ResolveConstantNode)); } else { throw new GraalError("Unsupported object constant type: " + type); } }
Replace LoadMethodCountersNode with indirect load ResolveMethodAndLoadCountersNode, expose a klass constant of the holder.
Params:
  • graph –
  • stateMapper –
  • node –
  • context –
/** * Replace {@link LoadMethodCountersNode} with indirect load * {@link ResolveMethodAndLoadCountersNode}, expose a klass constant of the holder. * * @param graph * @param stateMapper * @param node * @param context */
private static void handleLoadMethodCounters(StructuredGraph graph, FrameStateMapperClosure stateMapper, LoadMethodCountersNode node, PhaseContext context) { ResolvedJavaType type = node.getMethod().getDeclaringClass(); Stamp hubStamp = context.getStampProvider().createHubStamp((ObjectStamp) StampFactory.objectNonNull()); ConstantReflectionProvider constantReflection = context.getConstantReflection(); ConstantNode klassHint = ConstantNode.forConstant(hubStamp, constantReflection.asObjectHub(type), context.getMetaAccess(), graph); FixedWithNextNode replacement = graph.add(new ResolveMethodAndLoadCountersNode(node.getMethod(), klassHint)); insertReplacement(graph, stateMapper, node, replacement); node.replaceAtUsages(replacement, n -> !(n instanceof ResolveMethodAndLoadCountersNode)); }
Replace LoadMethodCountersNode with ResolveMethodAndLoadCountersNode, expose klass constants.
Params:
  • graph –
  • stateMapper –
  • context –
/** * Replace {@link LoadMethodCountersNode} with {@link ResolveMethodAndLoadCountersNode}, expose * klass constants. * * @param graph * @param stateMapper * @param context */
private static void replaceLoadMethodCounters(StructuredGraph graph, FrameStateMapperClosure stateMapper, PhaseContext context) { new SchedulePhase(SchedulingStrategy.LATEST_OUT_OF_LOOPS, true).apply(graph, false); for (LoadMethodCountersNode node : getLoadMethodCountersNodes(graph)) { if (anyUsagesNeedReplacement(node)) { handleLoadMethodCounters(graph, stateMapper, node, context); } } }
Replace object and klass constants with resolution nodes or reuse preceding initializations.
Params:
  • graph –
  • stateMapper –
/** * Replace object and klass constants with resolution nodes or reuse preceding initializations. * * @param graph * @param stateMapper */
private void replaceKlassesAndObjects(StructuredGraph graph, FrameStateMapperClosure stateMapper, ClassInfo classInfo) { new SchedulePhase(SchedulingStrategy.LATEST_OUT_OF_LOOPS, true).apply(graph, false); for (ConstantNode node : getConstantNodes(graph)) { Constant constant = node.asConstant(); if (constant instanceof HotSpotMetaspaceConstant && anyUsagesNeedReplacement(node)) { handleHotSpotMetaspaceConstant(graph, stateMapper, node, classInfo); } else if (constant instanceof HotSpotObjectConstant && anyUsagesNeedReplacement(node)) { handleHotSpotObjectConstant(graph, stateMapper, node, classInfo); } } } @Override protected void run(StructuredGraph graph, PhaseContext context) { FrameStateMapperClosure stateMapper = new FrameStateMapperClosure(graph); ReentrantNodeIterator.apply(stateMapper, graph.start(), null); // Replace LoadMethodCountersNode with ResolveMethodAndLoadCountersNode, expose klass // constants. replaceLoadMethodCounters(graph, stateMapper, context); // Replace object and klass constants (including the ones added in the previous pass) with // resolution nodes. replaceKlassesAndObjects(graph, stateMapper, new ClassInfo(context.getMetaAccess())); } @Override public boolean checkContract() { return false; } public ReplaceConstantNodesPhase() { this(true); } public ReplaceConstantNodesPhase(boolean verifyFingerprints) { this.verifyFingerprints = verifyFingerprints; } }