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

  1. Simplify the loop variable of the merge-exploded method: use a plain int counter so the switch value stays the raw phi
  2. File a GraalVM issue with the method: the message includes the offending switchValue node, which the maintainers need
  3. 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

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


AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14). Data as JSON: /api/errors/f36a795cd9ae8e4b. Report an issue: GitHub.