Skip to content

Commit affd971

Browse files
committed
[GR-60440] UnstructuredLocking: Cut off control flow at merges with incompatible lock stacks.
PullRequest: graal/19643
2 parents b23f159 + 3f8a6d5 commit affd971

File tree

4 files changed

+89
-63
lines changed

4 files changed

+89
-63
lines changed

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/BytecodeParser.java

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -864,17 +864,24 @@ protected static final class Target {
864864
final FixedNode entry;
865865
final FixedNode originalEntry;
866866
final FrameStateBuilder state;
867+
final boolean reachable;
867868

868869
public Target(FixedNode entry, FrameStateBuilder state) {
870+
this(entry, state, null);
871+
}
872+
873+
public Target(FixedNode entry, FrameStateBuilder state, FixedNode originalEntry) {
869874
this.entry = entry;
870875
this.state = state;
871-
this.originalEntry = null;
876+
this.originalEntry = originalEntry;
877+
this.reachable = true;
872878
}
873879

874-
public Target(FixedNode entry, FrameStateBuilder state, FixedNode originalEntry) {
880+
public Target(FixedNode entry, FrameStateBuilder state, FixedNode originalEntry, boolean reachable) {
875881
this.entry = entry;
876882
this.state = state;
877883
this.originalEntry = originalEntry;
884+
this.reachable = reachable;
878885
}
879886

880887
public FixedNode getEntry() {
@@ -888,6 +895,15 @@ public FixedNode getOriginalEntry() {
888895
public FrameStateBuilder getState() {
889896
return state;
890897
}
898+
899+
/**
900+
* Indicates whether the target block is actually reachable. If not, {@link #getEntry()}
901+
* will lead to a dead end (e.g., exception). Thus, no ends must be added to the target
902+
* block's merge.
903+
*/
904+
public boolean isReachable() {
905+
return reachable;
906+
}
891907
}
892908

893909
@SuppressWarnings("serial")
@@ -3438,6 +3454,13 @@ private FixedNode createTarget(BciBlock block, FrameStateBuilder state, boolean
34383454
} else {
34393455
setFirstInstruction(block, graph.add(new BeginNode()));
34403456
}
3457+
/*
3458+
* The target is the block's first instruction which may be preceded by exits of
3459+
* loops or exception handling that must be done before the jump. The target.entry
3460+
* holds the start of this sequence of operations. As the block is seen the first
3461+
* time as jump target, we cannot check for unstructured locking, as this requires
3462+
* to compare the lock stacks of two framestates to be merged.
3463+
*/
34413464
Target target = checkLoopExit(checkUnwind(getFirstInstruction(block), block, state), block);
34423465
FixedNode result = target.entry;
34433466
FrameStateBuilder currentEntryState = target.state == state ? (canReuseState ? state : state.copy()) : target.state;
@@ -3461,6 +3484,11 @@ private FixedNode createTarget(BciBlock block, FrameStateBuilder state, boolean
34613484
// location instead of the destination.
34623485
loopEnd = graph.add(new LoopEndNode(loopBegin));
34633486
}
3487+
/*
3488+
* The target is created from the loopEnd which may be preceded by exits of loops or
3489+
* exception handling that must be done before the jump. The target.entry holds the
3490+
* start of this sequence of operations.
3491+
*/
34643492
Target target = checkUnstructuredLocking(checkLoopExit(new Target(loopEnd, state.copy()), block), block, getEntryState(block));
34653493
FixedNode result = target.entry;
34663494
/*
@@ -3471,15 +3499,30 @@ private FixedNode createTarget(BciBlock block, FrameStateBuilder state, boolean
34713499
assert !(block instanceof ExceptionDispatchBlock) : block;
34723500
assert !getEntryState(block).rethrowException();
34733501
target.state.setRethrowException(false);
3474-
getEntryState(block).merge(loopBegin, target.state);
3475-
debug.log("createTarget %s: merging backward branch to loop header %s, result: %s", block, loopBegin, result);
3502+
3503+
if (target.isReachable()) {
3504+
getEntryState(block).merge(loopBegin, target.state);
3505+
debug.log("createTarget %s: merging backward branch to loop header %s, result: %s", block, loopBegin, result);
3506+
} else {
3507+
debug.log("createTarget %s: Unreachable target. This could be due to an exception being thrown (e.g., unstructured locking).", block);
3508+
}
34763509

34773510
return result;
34783511
}
34793512
assert currentBlock == null || currentBlock.getId() < block.getId() : "must not be backward branch";
34803513
assert getFirstInstruction(block).next() == null : "bytecodes already parsed for block";
34813514

3482-
if (getFirstInstruction(block) instanceof AbstractBeginNode && !(getFirstInstruction(block) instanceof AbstractMergeNode)) {
3515+
// The EndNode for the new edge to merge.
3516+
EndNode newEnd = graph.add(new EndNode());
3517+
/*
3518+
* The target is created from the newEnd which may be preceded by exits of loops or
3519+
* exception handling that must be done before the jump. The target.entry holds the
3520+
* start of this sequence of operations.
3521+
*/
3522+
Target target = checkUnstructuredLocking(checkLoopExit(checkUnwind(newEnd, block, state), block), block, getEntryState(block));
3523+
FixedNode result = target.entry;
3524+
3525+
if (target.isReachable() && getFirstInstruction(block) instanceof AbstractBeginNode && !(getFirstInstruction(block) instanceof AbstractMergeNode)) {
34833526
/*
34843527
* This is the second time we see this block. Create the actual MergeNode and the
34853528
* End Node for the already existing edge.
@@ -3505,15 +3548,14 @@ private FixedNode createTarget(BciBlock block, FrameStateBuilder state, boolean
35053548
setFirstInstruction(block, mergeNode);
35063549
}
35073550

3508-
AbstractMergeNode mergeNode = (AbstractMergeNode) getFirstInstruction(block);
3509-
3510-
// The EndNode for the newly merged edge.
3511-
EndNode newEnd = graph.add(new EndNode());
3512-
Target target = checkUnstructuredLocking(checkLoopExit(checkUnwind(newEnd, block, state), block), block, getEntryState(block));
3513-
FixedNode result = target.entry;
3514-
getEntryState(block).merge(mergeNode, target.state);
3515-
mergeNode.addForwardEnd(newEnd);
3516-
debug.log("createTarget %s: merging state, result: %s", block, result);
3551+
if (target.isReachable()) {
3552+
AbstractMergeNode mergeNode = (AbstractMergeNode) getFirstInstruction(block);
3553+
getEntryState(block).merge(mergeNode, target.state);
3554+
mergeNode.addForwardEnd(newEnd);
3555+
debug.log("createTarget %s: merging state, result: %s", block, result);
3556+
} else {
3557+
debug.log("createTarget %s: Unreachable target. This could be due to an exception being thrown (e.g., unstructured locking).", block);
3558+
}
35173559

35183560
return result;
35193561
}

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/FrameStateBuilder.java

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,17 +1210,4 @@ public FrameState createInitialIntrinsicFrameState(ResolvedJavaMethod original)
12101210
new FrameState(null, new ResolvedJavaMethodBytecode(original), 0, newLocals, newStack, stackSize, null, null, locks, Collections.emptyList(), FrameState.StackState.BeforePop));
12111211
return stateAfterStart;
12121212
}
1213-
1214-
/**
1215-
* Sets monitorIds and lockedObjects of this FrameStateBuilder to the values in {@code from}.
1216-
*/
1217-
public void setLocks(FrameStateBuilder from) {
1218-
lockedObjects = new ValueNode[from.lockedObjects.length];
1219-
monitorIds = new MonitorIdNode[from.lockedObjects.length];
1220-
for (int i = 0; i < from.lockedObjects.length; i++) {
1221-
lockedObjects[i] = from.lockedObjects[i];
1222-
monitorIds[i] = from.monitorIds[i];
1223-
}
1224-
}
1225-
12261213
}

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/PhiNode.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -77,9 +77,24 @@ public void setMerge(AbstractMergeNode x) {
7777
public boolean verifyNode() {
7878
assertTrue(merge() != null, "missing merge");
7979
assertTrue(merge().phiPredecessorCount() == valueCount(), "mismatch between merge predecessor count and phi value count: %d != %d", merge().phiPredecessorCount(), valueCount());
80+
verifyNoIllegalSelfLoops();
8081
return super.verifyNode();
8182
}
8283

84+
private void verifyNoIllegalSelfLoops() {
85+
if (!(merge instanceof LoopBeginNode)) {
86+
for (int i = 0; i < valueCount(); i++) {
87+
GraalError.guarantee(valueAt(i) != this, "non-loop phi at merge %s must not have a cycle, but value at index %s is itself: %s", merge(), i, valueAt(i));
88+
}
89+
}
90+
}
91+
92+
private void verifyNoIllegalSelfLoop(ValueNode value) {
93+
if (!(merge instanceof LoopBeginNode)) {
94+
GraalError.guarantee(value != this, "non-loop phi at merge %s must not have a cycle, but value to be added is itself: %s", merge(), value);
95+
}
96+
}
97+
8398
/**
8499
* Get the instruction that produces the value associated with the i'th predecessor of the
85100
* merge.
@@ -98,13 +113,15 @@ public ValueNode valueAt(int i) {
98113
* @param x the new phi input value for the given location
99114
*/
100115
public void initializeValueAt(int i, ValueNode x) {
116+
verifyNoIllegalSelfLoop(x);
101117
while (values().size() <= i) {
102118
values().add(null);
103119
}
104120
values().set(i, x);
105121
}
106122

107123
public void setValueAt(int i, ValueNode x) {
124+
verifyNoIllegalSelfLoop(x);
108125
values().set(i, x);
109126
}
110127

@@ -158,6 +175,7 @@ public void addInput(ValueNode x) {
158175
"x",
159176
x);
160177
assert !(this instanceof ValuePhiNode) || x.stamp(NodeView.DEFAULT).isCompatible(stamp(NodeView.DEFAULT)) : Assertions.errorMessageContext("this", this, "x", x);
178+
verifyNoIllegalSelfLoop(x);
161179
values().add(x);
162180
}
163181

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java

Lines changed: 14 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -748,52 +748,31 @@ protected Target checkUnstructuredLocking(Target target, BciBlock targetBlock, F
748748
if (mergeState.areLocksMergeableWith(target.getState())) {
749749
return target;
750750
}
751-
752-
/*
753-
* If the current locks are not compatible with the target merge,
754-
* the following code is created
755-
*
756-
* @formatter:off
757-
*
758-
* if(false) // UnreachableBeginNode
759-
* goto originalTarget // using adapted locks
760-
* else
761-
* releaseMonitors() // also methodSynchronizedObject
762-
* throw new UnsupportedFeatureError()
763-
*
764-
* @formatter:on
765-
*
766-
* Returns the newly created IfNode as updated target.
767-
*/
751+
// Create an UnsupportedFeatureException and unwind.
768752
FixedWithNextNode originalLast = lastInstr;
769753
FrameStateBuilder originalState = frameState;
770754

771-
IfNode ifNode = graph.add(new IfNode(LogicConstantNode.contradiction(graph), graph.add(new UnreachableBeginNode()), graph.add(new BeginNode()), BranchProbabilityNode.NEVER_TAKEN_PROFILE));
772-
773-
/*
774-
* Create an UnsupportedFeatureException in the always entered (false) branch and
775-
* unwind.
776-
*/
777-
lastInstr = ifNode.falseSuccessor();
755+
BeginNode holder = graph.add(new BeginNode());
756+
lastInstr = holder;
778757
frameState = target.getState().copy();
779758
genReleaseMonitors(true);
780759
genThrowUnsupportedFeatureError("Incompatible lock states at merge. Native Image enforces structured locking (JVMS 2.11.10)");
781760

782-
/*
783-
* Update the never entered (true) branch to have a matching lock stack with the
784-
* subsequent merge. This branch will fold away during canonicalization. Add an
785-
* UnreachableNode as assurance.
786-
*/
761+
FixedNode exceptionPath = holder.next();
762+
787763
Target newTarget;
788-
FrameStateBuilder newState = target.getState().copy();
789-
newState.setLocks(mergeState);
790764
if (target.getOriginalEntry() == null) {
791-
newTarget = new Target(ifNode, newState, target.getEntry());
765+
newTarget = new Target(exceptionPath, frameState, null, false);
766+
target.getEntry().replaceAtPredecessor(exceptionPath);
767+
target.getEntry().safeDelete();
792768
} else {
793-
target.getOriginalEntry().replaceAtPredecessor(ifNode);
794-
newTarget = new Target(target.getEntry(), newState, target.getOriginalEntry());
769+
newTarget = new Target(target.getEntry(), frameState, exceptionPath, false);
770+
target.getOriginalEntry().replaceAtPredecessor(exceptionPath);
771+
target.getOriginalEntry().safeDelete();
795772
}
796-
ifNode.trueSuccessor().setNext(newTarget.getOriginalEntry());
773+
774+
holder.setNext(null);
775+
holder.safeDelete();
797776

798777
lastInstr = originalLast;
799778
frameState = originalState;

0 commit comments

Comments
 (0)