oracle/graal · error · PermanentBailoutException
Graal implementation restriction: Method with %s loop explos
Error message
Graal implementation restriction: Method with %s loop explosion %s
What it means
During merge-explode loop reconstruction, GraphDecoder rebuilds loop back-edges as an IntegerSwitchNode over the loop-variable phi (createSwitch). isIntSwitchValue validates the switched value is exactly the int phi, or a NarrowNode narrowing a long phi to 32 bits. Any other shape is an internal inconsistency, reported through bailout() as a PermanentBailoutException prefixed 'Graal implementation restriction: Method with MERGE_EXPLODE loop explosion ...'.
Source
Thrown at compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/GraphDecoder.java:3308
case Int -> loopVariablePhi;
case Long -> graph.addOrUnique(NarrowNode.create(loopVariablePhi, Integer.SIZE, NodeView.DEFAULT));
default -> throw bailout("must have a loop variable of type int or long. " + loopVariablePhi);
};
}
/**
* Validates that {@code switchValue} conforms to one of the shapes produced by {@link #asIntSwitchValue}.
*/
private static boolean isIntSwitchValue(ValueNode switchValue, ValuePhiNode loopVariablePhi) {
return switch (loopVariablePhi.getStackKind()) {
case Int -> switchValue == loopVariablePhi;
case Long -> switchValue instanceof NarrowNode narrow && narrow.getValue() == loopVariablePhi && narrow.getResultBits() == Integer.SIZE;
default -> throw bailout("switch value did not conform to expected shape. " + switchValue);
};
}
private static RuntimeException bailout(String msg) {
throw new PermanentBailoutException("Graal implementation restriction: Method with %s loop explosion %s", LoopExplosionPlugin.LoopExplosionKind.MERGE_EXPLODE, msg);
}
private static IntegerSwitchNode createSwitch(ValueNode switchedValue, SortedMap<Integer, AbstractBeginNode> dispatchTable, AbstractBeginNode defaultSuccessor) {
int numKeys = dispatchTable.size();
int numSuccessors = numKeys + 1;
AbstractBeginNode[] switchSuccessors = new AbstractBeginNode[numSuccessors];
int[] switchKeys = new int[numKeys];
double[] switchKeyProbabilities = new double[numSuccessors];
int[] switchKeySuccessors = new int[numSuccessors];
int idx = 0;
for (Map.Entry<Integer, AbstractBeginNode> entry : dispatchTable.entrySet()) {
switchSuccessors[idx] = entry.getValue();
switchKeys[idx] = entry.getKey();
switchKeyProbabilities[idx] = 1d / numKeys;
switchKeySuccessors[idx] = idx;
idx++;View on GitHub (pinned to a66e9ccd1d)
Solutions
- Simplify the loop variable of the merge-exploded method: use a plain int counter so the switch value stays the raw phi
- File a GraalVM issue with the method: the message includes the offending switchValue node, which the maintainers need
- Switch the method to FULL_UNROLL or DUP_WITHOUT_EXIT explosion to bypass the switch reconstruction entirely
Example fix
// before: long counter with char-based update -> switch value not the phi / Narrow(phi)
@LoopExplosionKind(MERGE_EXPLODE)
void loop() { long i = 0; while (c(i)) { i = nextChar(i); } }
// after: plain int induction variable
@LoopExplosionKind(MERGE_EXPLODE)
void loop() { for (int i = 0; c(i); i++) { } } Defensive patterns
Strategy: validation
Validate before calling
// In interpreter code destined for MERGE_EXPLODE: keep induction variables as plain int
if (kind == MERGE_EXPLODE && usesNonIntInductionVariable(method)) {
warnOrReject("merge-explode switch reconstruction requires int (or 32-bit-narrowed long) counters");
} Try / catch
try { pe(method); } catch (PermanentBailoutException e) { /* internal shape violation: simplify counter, then retry; else file issue */ throw e; } Prevention
- Use plain int loop counters in merge-exploded loops
- Avoid widening chains (char/byte/long round-trips) on the induction variable
- Report unexpected shapes upstream with the printed switchValue node
When it happens
Trigger: A MERGE_EXPLODE loop whose induction variable phi is widened or transformed so the switch dispatch value is no longer the raw phi nor a 32-bit Narrow of it (e.g., an unexpected SignExtendNode, a char/byte phi widened elsewhere, or a compiler pass altering the shape between encoding and decoding).
Common situations: Truffle interpreter loops under merge explosion where the loop counter is a long used in ways that produce a different narrowing chain; Graal version changes to integer conversion canonicalization; hand-written substitutions with unusual induction variables.
Related errors
- Graal implementation restriction: Method with %s loop explos
- too many iterations in computeGlobalLiveSets
- Frame size (%d) exceeded maximum allowed frame size (%d).
- NormalizeCompareNode connected to %s (%s %s %s)
- LogFile substitution %s cannot be combined with any other ch
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/f36a795cd9ae8e4b.
Report an issue: GitHub.