Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2025, 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.core.test;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

import org.junit.Test;

import jdk.graal.compiler.api.directives.GraalDirectives;
import jdk.graal.compiler.nodes.StructuredGraph;
import jdk.graal.compiler.nodes.cfg.ControlFlowGraph;
import jdk.graal.compiler.nodes.cfg.HIRBlock;

public class LoggingCFGDecoratorTest extends GraalCompilerTest {

public static int foo(int a) {
int result = 0;
if (a == 0) {
result = 1;
} else {
result = 2;
}
GraalDirectives.controlFlowAnchor();
return result;
}

@Test
public void test01() {
StructuredGraph g = parseEager("foo", StructuredGraph.AllowAssumptions.NO);
ControlFlowGraph cfg = ControlFlowGraph.computeForSchedule(g);
ControlFlowGraph.RecursiveVisitor<Integer> visitor = new ControlFlowGraph.RecursiveVisitor<Integer>() {
int number;

@Override
public Integer enter(HIRBlock b) {
number++;
// pushed elements
return 1;
}

@Override
public void exit(HIRBlock b, Integer value) {
number -= value;
}

@Override
public String toString() {
return "TestIterator";
}
};
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream o = new PrintStream(baos);
ControlFlowGraph.LoggingCFGDecorator<?> dec = new ControlFlowGraph.LoggingCFGDecorator<>(o, visitor, cfg);
cfg.visitDominatorTreeDefault(dec);
String result = baos.toString();
assert result.contains(ExpectedOutput);
}

private static final String ExpectedOutput = "B0 [dom null, post dom null]\n" +
"\tB1 [dom B0, post dom null]\n" +
"\tB2 [dom B0, post dom null]\n" +
"\tB3 [dom B0, post dom null]\n" +
"Enter block B0 for TestIterator\n" +
"\tEnter block B1 for TestIterator\n" +
"\tExit block B1 with value 1 for TestIterator\n" +
"\tEnter block B2 for TestIterator\n" +
"\tExit block B2 with value 1 for TestIterator\n" +
"\tEnter block B3 for TestIterator\n" +
"\tExit block B3 with value 1 for TestIterator\n" +
"Exit block B0 with value 1 for TestIterator";

}
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ public static boolean inIntrinsic() {
public static void controlFlowAnchor() {
}

/**
* Like {@link #controlFlowAnchor()} except this node can be optimized away if its
* {@code condition} argument becomes constant {@code 0}.
*/
public static void controlFlowAnchor(@SuppressWarnings("unused") long condition) {
}

/**
* A call to this method will disable strip mining of the enclosing loop in the compiler.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
*/
package jdk.graal.compiler.nodes.cfg;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
Expand Down Expand Up @@ -446,38 +447,41 @@ public void updateCachedLocalLoopFrequency(LoopBeginNode lb, Function<LoopFreque

/**
* Debug only decorator for {@link RecursiveVisitor} to log all basic blocks how they are
* visited one by one.
* visited one by one. LoggingCFGDecoratorTest illustrates the usage of the decorator to debug
* iteration dependent dominator tree algorithms.
*/
public static class LoggingCFGDecorator implements ControlFlowGraph.RecursiveVisitor<HIRBlock> {
private final ControlFlowGraph.RecursiveVisitor<HIRBlock> visitor;
public static class LoggingCFGDecorator<L> implements ControlFlowGraph.RecursiveVisitor<L> {
private final ControlFlowGraph.RecursiveVisitor<L> visitor;
private String indent = "";
private final PrintStream out;

public LoggingCFGDecorator(ControlFlowGraph.RecursiveVisitor<HIRBlock> visitor, ControlFlowGraph cfg) {
public LoggingCFGDecorator(PrintStream out, ControlFlowGraph.RecursiveVisitor<L> visitor, ControlFlowGraph cfg) {
this.visitor = visitor;
TTY.printf("DomTree for %s%n", cfg.graph);
printDomTree(cfg.getStartBlock(), "");
this.out = out;
out.printf("DomTree for %s%n", cfg.graph);
printDomTree(out, cfg.getStartBlock(), "");
}

private static void printDomTree(HIRBlock cur, String indent) {
TTY.printf("%s%s [dom %s, post dom %s]%n", indent, cur, cur.getDominator(), cur.getPostdominator());
private static void printDomTree(PrintStream out, HIRBlock cur, String indent) {
out.printf("%s%s [dom %s, post dom %s]%n", indent, cur, cur.getDominator(), cur.getPostdominator());
HIRBlock dominated = cur.getFirstDominated();
while (dominated != null) {
printDomTree(dominated, indent + "\t");
printDomTree(out, dominated, indent + "\t");
dominated = dominated.getDominatedSibling();
}
}

@Override
public HIRBlock enter(HIRBlock b) {
TTY.printf("%sEnter block %s for %s%n", indent, b, visitor);
public L enter(HIRBlock b) {
out.printf("%sEnter block %s for %s%n", indent, b, visitor);
indent += "\t";
return visitor.enter(b);
}

@Override
public void exit(HIRBlock b, HIRBlock value) {
public void exit(HIRBlock b, L value) {
indent = indent.substring(0, indent.length() - 1);
TTY.printf("%sExit block %s with value %s for %s%n", indent, b, value, visitor);
out.printf("%sExit block %s with value %s for %s%n", indent, b, value, visitor);
visitor.exit(b, value);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,28 @@
import jdk.graal.compiler.nodeinfo.NodeInfo;
import jdk.graal.compiler.nodes.FixedWithNextNode;
import jdk.graal.compiler.nodes.Invoke;
import jdk.graal.compiler.nodes.ValueNode;
import jdk.graal.compiler.nodes.spi.Canonicalizable;
import jdk.graal.compiler.nodes.spi.CanonicalizerTool;
import jdk.graal.compiler.nodes.spi.LIRLowerable;
import jdk.graal.compiler.nodes.spi.NodeLIRBuilderTool;

@NodeInfo(cycles = CYCLES_0, size = SIZE_0)
public final class ControlFlowAnchorNode extends FixedWithNextNode implements LIRLowerable, ControlFlowAnchored {
public final class ControlFlowAnchorNode extends FixedWithNextNode implements LIRLowerable, ControlFlowAnchored, Canonicalizable {

public static final NodeClass<ControlFlowAnchorNode> TYPE = NodeClass.create(ControlFlowAnchorNode.class);

@OptionalInput protected ValueNode numericCondition;

public ControlFlowAnchorNode() {
super(TYPE, StampFactory.forVoid());
}

public ControlFlowAnchorNode(ValueNode numericCondition) {
this();
this.numericCondition = numericCondition;
}

/**
* Used by MacroSubstitution.
*/
Expand All @@ -61,4 +71,16 @@ public void generate(NodeLIRBuilderTool generator) {
protected void afterClone(Node other) {
assert other.graph() != null && other.graph() != graph() : this + " should never be cloned in the same graph";
}

@Override
public Node canonical(CanonicalizerTool tool) {
if (numericCondition != null) {
// if we have a condition that evals to 0
if (numericCondition.isConstant() && numericCondition.asJavaConstant().asLong() == 0L) {
// delete this node
return null;
}
}
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
import java.util.Objects;
import java.util.function.BiFunction;

import jdk.graal.compiler.replacements.nodes.ThreadedSwitchNode;
import org.graalvm.word.LocationIdentity;

import jdk.graal.compiler.api.directives.GraalDirectives;
Expand Down Expand Up @@ -213,6 +212,7 @@
import jdk.graal.compiler.replacements.nodes.ProfileBooleanNode;
import jdk.graal.compiler.replacements.nodes.ReverseBitsNode;
import jdk.graal.compiler.replacements.nodes.ReverseBytesNode;
import jdk.graal.compiler.replacements.nodes.ThreadedSwitchNode;
import jdk.graal.compiler.replacements.nodes.VectorizedHashCodeNode;
import jdk.graal.compiler.replacements.nodes.VectorizedMismatchNode;
import jdk.graal.compiler.replacements.nodes.VirtualizableInvokeMacroNode;
Expand Down Expand Up @@ -1788,6 +1788,13 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec
return true;
}
});
r.register(new RequiredInlineOnlyInvocationPlugin("controlFlowAnchor", long.class) {
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode condition) {
b.add(new ControlFlowAnchorNode(condition));
return true;
}
});
r.register(new RequiredInlineOnlyInvocationPlugin("neverStripMine") {
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,6 @@ public static void sessionExceptionHandler(MemorySessionImpl session, Object bas
}
// avoid any optimization based code duplication in the cluster, it disrupts later matching
// of control flow when searching for this pattern
GraalDirectives.controlFlowAnchor();
GraalDirectives.controlFlowAnchor(scope);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,9 @@ private void cleanupClusterNodes(StructuredGraph graph, MidTierContext context,
clusterNode.delete();
}
}

graph.getDebug().dump(DebugContext.VERY_DETAILED_LEVEL, graph, "Before running final canonicalization");

canonicalizer.apply(graph, context);
scheduleVerify(graph);

Expand Down Expand Up @@ -878,21 +881,48 @@ private static EconomicMap<Node, List<ScopedAccess>> enumerateScopedAccesses(Opt

@Override
public Integer enter(HIRBlock b) {
int newDominatingValues = 0;
int newScopesToPop = 0;
// all new nodes for checking arenas, pop if we go out of scope of this block again
int newArenaValidInScopeNodes = 0;
// all scopes that are closed in this block that have been open before already
Deque<ScopedMethodNode> scopesToRepush = new ArrayDeque<>();

EconomicSet<ScopedMethodNode> newOpenedScopes = EconomicSet.create();

for (FixedNode f : b.getNodes()) {
if (f instanceof MemoryArenaValidInScopeNode mas) {
defs.push(new ReachingDefScope(mas));
newDominatingValues++;
newArenaValidInScopeNodes++;
} else if (f instanceof ScopedMethodNode scope) {
/*
* When we process scope nodes we have 3 major situations to deal with.
*
* no open scope, no scopes opened in "this" block -> do nothing
*
* we open a new scope s, we do not close it -> pop it when exit() is called
*
* we close a scope that we have not opened -> pop it and repush on exit()
*
* we open and close the same new scope in this block -> do nothing on
* exit()
*/

if (scope.getType() == ScopedMethodNode.Type.START) {
scopes.push(scope);
newScopesToPop++;
// we open a new scope, we have to pop it again if this block goes out
// of scope
newOpenedScopes.add(scope);
} else if (scope.getType() == ScopedMethodNode.Type.END) {
ScopedMethodNode start = scopes.pop();
scopesToRepush.push(start);
// we close a scope, we only have to repush it again in the dom tree
// traversal if it is a scope that was open upon the enter call to the
// current block
if (!newOpenedScopes.contains(start)) {
scopesToRepush.push(start);
} else {
// remove it again from the opened scopes, it is already closed
// again
newOpenedScopes.remove(start);
}
assert scope.getStart() == start : Assertions.errorMessage("Must match", start, scope, scope.getStart());
} else {
throw GraalError.shouldNotReachHere("Unknown type " + scope.getType());
Expand All @@ -902,8 +932,10 @@ public Integer enter(HIRBlock b) {
}
}

final int finalNewDominatingValues = newDominatingValues;
final int finalNewScopesToPop = newScopesToPop;
final int finalNewDominatingValues = newArenaValidInScopeNodes;
// all new scopes that have not been closed already need to be removed again when we
// go out of this block
final int finalNewScopesToPop = newOpenedScopes.size();

actions.push(new Runnable() {

Expand All @@ -925,8 +957,20 @@ public void run() {
return 1;
}

@Override
public void exit(HIRBlock b, Integer pushedForBlock) {
for (int i = 0; i < pushedForBlock; i++) {
actions.pop().run();
}
}

private void processNode(FixedNode f) {
if (!scopes.isEmpty() && f instanceof Invoke i) {
if (scopes.isEmpty()) {
// no open scopes, nothing to check, we are not inside an SharedArena access
// method.
return;
}
if (f instanceof Invoke i) {
if (i.getTargetMethod() != null && !config.isSafeCallee(i.getTargetMethod())) {
if (!defs.isEmpty()) {
dominatedCalls.add(new DominatedCall(defs.peek().defNode, i));
Expand Down Expand Up @@ -1055,15 +1099,7 @@ private EconomicSet<CFGLoop<HIRBlock>> visitEveryLoopHeaderInBetween(ReachingDef
return loopsToCheck;
}

@Override
public void exit(HIRBlock b, Integer pushedForBlock) {
for (int i = 0; i < pushedForBlock; i++) {
actions.pop().run();
}
}

};

cfg.visitDominatorTreeDefault(visitor);
return nodeAccesses.size() > 0 ? nodeAccesses : null;
}
Expand Down
Loading