All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jdk.graal.compiler.nodes.loop.LoopSafepointVerification Maven / Gradle / Ivy

There is a newer version: 24.1.1
Show newest version
/*
 * Copyright (c) 2024, 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * 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 jdk.graal.compiler.nodes.loop;

import org.graalvm.collections.EconomicMap;
import org.graalvm.collections.EconomicSet;
import org.graalvm.collections.Equivalence;

import jdk.graal.compiler.debug.Assertions;
import jdk.graal.compiler.debug.TTY;
import jdk.graal.compiler.graph.Node;
import jdk.graal.compiler.graph.NodeSourcePosition;
import jdk.graal.compiler.nodes.AbstractMergeNode;
import jdk.graal.compiler.nodes.FixedNode;
import jdk.graal.compiler.nodes.FrameState;
import jdk.graal.compiler.nodes.LoopBeginNode;
import jdk.graal.compiler.nodes.LoopExitNode;
import jdk.graal.compiler.nodes.StructuredGraph;

/**
 * Verification utility to ensure that all optimizations during compilation respect safepoint
 * invariants in the compiler. That is: a loop with {@link LoopBeginNode#canEndsSafepoint()}
 * {@code ==false} must never be replaced by a loop with {@code canEndsSafepoint==true}.
 */
public class LoopSafepointVerification {

    public static final boolean PRINT_SAFEPOINT_NOT_FOUND = false;

    private EconomicMap safepointVerificationData;

    static class SafepointData {
        /**
         * Determine if there can be a safepoint on any of the loop ends of this loop. If true, it
         * means that there was no explicit phase requesting a complete disabling of all safepoints
         * on this loop. Once safepoints have been disabled for a loop, they must not be enabled
         * again. This means any loop that is replaced by loop optimizations with other loops must
         * still retain the same safepoint rules.
         */
        boolean canHaveSafepoints;
        LoopBeginNode lb;
        NodeSourcePosition nsp;
        FrameState fs;
        int outerLoops;
        int innerLoops;

        static SafepointData fromLoopBegin(LoopBeginNode lb, int innerLoops, int outerLoops) {
            SafepointData sd = new SafepointData();
            sd.lb = lb;
            sd.canHaveSafepoints = lb.canEndsSafepoint();
            sd.fs = lb.stateAfter();
            sd.nsp = lb.getNodeSourcePosition();
            sd.innerLoops = innerLoops;
            sd.outerLoops = outerLoops;
            return sd;
        }

        /**
         * Assert that the {@code newData} is not weaker with respect to safepoint invariants than
         * {@code this} loop. For this to make "sense" {@code newData} is supposed to be a
         * new/different/optimized version of {@code this} loop. The use case is that over the
         * course of compilation {@code this} loop was replaced by {@code newData} via a loop
         * optimization for example.
         */
        boolean assertNotWeaker(SafepointData newData) {
            if (this.canHaveSafepoints) {
                // all good, other can do what it wants
            } else {
                // this cannot safepoint -> ensure other also cannot safepoint
                assert !newData.canHaveSafepoints : Assertions.errorMessage("Safepoint verification cannot become weaker", lb,
                                "previously the loop had canHaveSafepoints=false but now it has canHaveSafepoints=true", newData.lb);
            }
            return true;
        }

        public boolean sameStateOrNsp(SafepointData otherData) {
            if (otherData.fs == fs) {
                return true;
            }
            // node source position must match
            if (this.nsp != null && otherData.nsp != null && !otherData.nsp.equals(nsp)) {
                return false;
            }
            // not the same state or also not the same NSP - check if framestates represent the same
            // position
            final FrameState thisState = fs;
            final FrameState otherState = otherData.fs;
            if (thisState != null && otherState != null && !thisState.valueEquals(otherState)) {
                return false;
            }
            if (this.innerLoops != otherData.innerLoops) {
                return false;
            }
            if (this.outerLoops != otherData.outerLoops) {
                return false;
            }
            if (!optimizerRelatedLoops(this.lb, otherData.lb)) {
                return false;
            }
            return true;
        }

        /**
         * This method is a heuristic approximation for loop safepoint verification. When we remove
         * a loop from a graph and replace it by something else we try to find the replacement
         * loop(s). Which is not always trivial, thus we need to have a "best" guess as in - the new
         * loop has the same node source position, framestate based on properties and loop general
         * properties. Therefore we consider {@code lb1} the "original" loop and try to determine if
         * {@code lb2} was derived from {@code lb1} via an optimization (unrolling, strip mining,
         * etc).
         */
        @SuppressWarnings("deprecation")
        private static boolean optimizerRelatedLoops(LoopBeginNode lb1, LoopBeginNode lb2) {
            if (lb1.isCompilerInverted() != lb2.isCompilerInverted()) {
                return false;
            }
            /*
             * Only if this loop was touched by strip mining already verify the properties, else we
             * might be verifying during a replace. Note that strip mining is special in that it
             * creates a second loop with the same state and the same node source position with an
             * inner/outer mapping. Any other loop optimization is sequential in that it does only
             * copy a loop but never creates new loop nests.
             */
            final boolean isTouchedByStripMining = lb1.isStripMinedInner() || lb1.isStripMinedOuter();
            if (isTouchedByStripMining) {
                if (lb1.isStripMinedInner() != lb2.isStripMinedInner()) {
                    return false;
                }
                if (lb2.isStripMinedOuter() != lb2.isStripMinedOuter()) {
                    return false;
                }
            }

            final long cloneFromIdLb1 = lb1.getClonedFromNodeId();
            final long cloneFromIdLb2 = lb2.getClonedFromNodeId();

            if ((cloneFromIdLb1 != -1 || cloneFromIdLb2 != -1)) {
                assert lb2.isAlive() : Assertions.errorMessage("When verifying loops the second one must be alive always", lb1, lb2);
                if (lb1.isDeleted() && cloneFromIdLb2 != -1) {
                    /*
                     * The original loop is deleted - the new one not: if the new one is cloned from
                     * another loop determine if it was cloned from lb1.
                     */
                    long lb1IdBeforeDeletion = lb1.getIdBeforeDeletion();
                    return lb1IdBeforeDeletion == cloneFromIdLb2;
                } else {
                    if (lb1.getId() == cloneFromIdLb2) {
                        /*
                         * lb2 was cloned from lb1 - ensure they match
                         */
                        return true;
                    }
                    /*
                     * Both loops are alive and either one of them (or both) have been cloned from
                     * another loop. Assure they are cloned from the same one.
                     */
                    return cloneFromIdLb1 == cloneFromIdLb2;
                }
            }
            return true;
        }
    }

    static class LoopContext {
        EconomicSet innerLoopBegins = EconomicSet.create();
        LoopBeginNode lb;
        int depth;

        LoopContext(LoopBeginNode lb) {
            this.lb = lb;
        }

        static void printContexts(EconomicMap contexts) {
            var cursor = contexts.getEntries();
            while (cursor.advance()) {
                var context = cursor.getValue();
                TTY.printf("Loop %s at depth %s has inner loops %s %n", context.lb, context.depth, context.innerLoopBegins);
            }
        }

        /**
         * Process the dominated parts of the {@code graph} reachable from the given loop begin node
         * {@code lb}. This method will be called for all loops in a graph. It will incrementally
         * build a data structure presenting all loops of a graph and their nesting.
         *
         * The algorithm behind this logic works the following way:
         *
         * 
    *
  • Call {@link #getLoopRelations(LoopBeginNode, StructuredGraph, EconomicMap)} for all * {@link LoopBeginNode} nodes of a graph. While doing so build a side data structure * capturing all {@code LoopContext} for each loop in the graph. This data structure is * given as an in/out parameter to * {@link #getLoopRelations(LoopBeginNode, StructuredGraph, EconomicMap)}: * {@code contexts}.
  • *
  • For each loop begin start iterating the graph backwards until the * {@link StructuredGraph#start()} is found.
  • *
  • During iteration record all loops that are found: for loop exits skip the entire loop * of the exit and record it as an inner loop. For loop begins visited: attribute all * current inner loops to the loop begin's loop inner loops. This way * {@link #getLoopRelations(LoopBeginNode, StructuredGraph, EconomicMap)} incrementally * builds a full set of inner/outer loop relations in form of the loop context map.
  • *
  • When a regular control flow merge is visited then pick an arbitrary (we take the * first one for simplicity) predecessor and continue from there. If there are other inner * loops in other predecessors of a merge they will be visited by other calls to * {@link #getLoopRelations(LoopBeginNode, StructuredGraph, EconomicMap)}
  • *
*/ static void getLoopRelations(LoopBeginNode lb, StructuredGraph graph, EconomicMap contexts) { if (!contexts.containsKey(lb)) { contexts.put(lb, new LoopContext(lb)); } // the set of inner loops reachable from the starting point EconomicSet innerLoops = EconomicSet.create(); innerLoops.add(lb); int enterSeen = 0; FixedNode cur = lb.forwardEnd(); while (cur != null) { if (cur instanceof LoopExitNode lex) { innerLoops.add(lex.loopBegin()); cur = lex.loopBegin().forwardEnd(); continue; } if (cur instanceof LoopBeginNode cl) { /* * We visit a loop begin, all inner loops found can be attributed to the outer * loop */ if (!contexts.containsKey(cl)) { contexts.put(cl, new LoopContext(cl)); } contexts.get(cl).innerLoopBegins.addAll(innerLoops); enterSeen++; } if (cur.predecessor() != null) { cur = (FixedNode) cur.predecessor(); continue; } else { /* * Pick an arbitrary branch. If another branch contains an inner loop, we will * collect that loop when this method is called with on that loop's begin node. */ if (cur instanceof AbstractMergeNode am) { cur = am.forwardEndAt(0); continue; } else if (cur == graph.start()) { break; } } } contexts.get(lb).depth = enterSeen; } } public boolean verifyLoopSafepoints(StructuredGraph g) { if (!g.hasLoops()) { return true; } EconomicMap contexts = EconomicMap.create(); for (LoopBeginNode lb : g.getNodes(LoopBeginNode.TYPE)) { if (lb.isAlive()) { LoopContext.getLoopRelations(lb, g, contexts); } } if (safepointVerificationData == null) { safepointVerificationData = EconomicMap.create(Equivalence.IDENTITY_WITH_SYSTEM_HASHCODE); } EconomicSet loopsToVisit = EconomicSet.create(); EconomicSet originalLoopsInTheGraph = EconomicSet.create(); for (Node lb : safepointVerificationData.getKeys()) { loopsToVisit.add((LoopBeginNode) lb); originalLoopsInTheGraph.add((LoopBeginNode) lb); } for (LoopBeginNode lb : g.getNodes(LoopBeginNode.TYPE)) { final SafepointData newData = SafepointData.fromLoopBegin(lb, contexts.get(lb).innerLoopBegins.size(), contexts.get(lb).depth); if (safepointVerificationData.containsKey(lb)) { assert loopsToVisit.contains(lb); // all loops that are still in the graph just need verification, no replacement // verification loopsToVisit.remove(lb); assert safepointVerificationData.get(lb).assertNotWeaker(newData); } // now overwrite (or propagate new data) to the map, if it was a faulty loop it // would have hit the assertions above safepointVerificationData.put(lb, newData); } /* * Now we cleaned up all the loops that are in the graph. What remains is to cleanup the old * loops that are no longer part of the graph. They have been removed in between. This is * where it becomes fuzzy: if a loop optimization removed the original loop with (a) new * one(s) we must verify that/them. We use framestate and node source position to verify * them, which is a poor mans heuristic but we cannot do much better. */ for (LoopBeginNode lb : loopsToVisit) { assert lb.isDeleted() : Assertions.errorMessage("This loop must be deleted since it was not found during iteration", lb); // lets remove it from the map, either we cant verify and fail or its good and we // verified correctly, both ways the loop should be removed from the map SafepointData sd = safepointVerificationData.removeKey(lb); if (sd.canHaveSafepoints) { // the loop was allowed to safepoint, if the new ones (if there are any) are allowed // to safepoint or not is not of real interest, thus we are good } else { // the loop was not allowed to safepoint, any replacement should also not // safepoint inner: for (Node n : safepointVerificationData.getKeys()) { LoopBeginNode other = (LoopBeginNode) n; if (originalLoopsInTheGraph.contains(other)) { // only compare old deleted loops with newly added ones continue inner; } SafepointData otherData = safepointVerificationData.get(other); assert otherData != null : Assertions.errorMessage("Must be in map as map was propagated previously", other); assert other != lb : Assertions.errorMessage("Must be different nodes since one was deleted and the other is in the graph", lb, other); if (sd.sameStateOrNsp(otherData)) { assert sd.assertNotWeaker(otherData); } else { if (PRINT_SAFEPOINT_NOT_FOUND) { TTY.printf("Could not find replacement loop for %s in %s%n", sd.lb, this); } } } } } return true; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy