oracle/graal · error · PermanentBailoutException

Graal implementation restriction: Method with %s loop explos

Error message

Graal implementation restriction: Method with %s loop explosion must not have more than one top-level loop

What it means

GraphDecoder handles merge-explode loop explosion by converting loop begins into plain merges and reconstructing loops later; it supports at most one top-level loop per method (tracked via loopExplosionHead). When a second top-level loop is encountered during MERGE_EXPLODE decoding, it throws a documented Graal implementation restriction as a PermanentBailoutException.

Source

Thrown at compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/GraphDecoder.java:1743

                for (EndNode predecessor : predecessors) {
                    existingState.merge.addForwardEnd(predecessor);
                }
                return;
            }
        }

        /*
         * Merge explosion: When doing merge-explode PE loops are detected after partial evaluation
         * in a dedicated steps. Therefore, we create merge nodes instead of loop begins and loop
         * exits and later replace them with the detected loop begin and loop exit nodes.
         */
        MergeNode merge = graph.add(new MergeNode());
        methodScope.loopExplosionMerges.add(merge);

        if (methodScope.loopExplosion.mergeLoops()) {
            if (loopScope.iterationStates.size() == 0 && loopScope.loopDepth == 1) {
                if (methodScope.loopExplosionHead != null) {
                    throw new PermanentBailoutException("Graal implementation restriction: Method with %s loop explosion must not have more than one top-level loop",
                                    LoopExplosionPlugin.LoopExplosionKind.MERGE_EXPLODE);
                }
                methodScope.loopExplosionHead = merge;
            }
        }

        loopBegin.replaceAtUsagesAndDelete(merge);
        merge.setStateAfter(frameState);
        merge.setNext(successor);
        for (EndNode predecessor : predecessors) {
            merge.addForwardEnd(predecessor);
        }

        if (methodScope.loopExplosion.mergeLoops()) {
            LoopExplosionState explosionState = new LoopExplosionState(frameState, merge);
            loopScope.iterationStates.put(explosionState, explosionState);
        }
    }

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Move each top-level loop into its own method so no MERGE_EXPLODE method has more than one top-level loop
  2. Switch that method to a different LoopExplosionKind (FULL_UNROLL, DUP_WITHOUT_EXIT, ...) that does not carry the single-loop restriction
  3. Drop the explode annotation from the offending method if the explosion is not essential

Example fix

// before
@LoopExplosionKind(MERGE_EXPLODE)
static int run(VMState s) {
    while (s.step1()) { }
    while (s.step2()) { }   // second top-level loop -> bailout
}

// after
@LoopExplosionKind(MERGE_EXPLODE) static int run1(VMState s) { while (s.step1()) { } return 0; }
@LoopExplosionKind(MERGE_EXPLODE) static int run2(VMState s) { while (s.step2()) { } return 0; }
static int run(VMState s) { run1(s); return run2(s); }
Defensive patterns

Strategy: validation

Validate before calling

// Static check before annotating/running PE: count top-level loops in the method
long topLevelLoops = countTopLevelLoops(methodBytecode);
if (kind == MERGE_EXPLODE && topLevelLoops > 1) {
    throw new IllegalArgumentException("MERGE_EXPLODE supports only one top-level loop: " + method);
}

Try / catch

try { pe(method); } catch (PermanentBailoutException e) { /* restructure method or change explosion kind */ throw e; }

Prevention

When it happens

Trigger: Partial evaluation of a method annotated (or registered via LoopExplosionPlugin) with LoopExplosionKind.MERGE_EXPLODE whose encoded graph contains more than one top-level loop (loopScope.loopDepth == 1 with an already-set loopExplosionHead).

Common situations: Truffle interpreters or substitution graphs where a helper method got MERGE_EXPLODE semantics (annotation, plugin, or @LoopExplosionKind inheritance) but the method body contains two sibling loops; refactoring exploded methods by adding a second loop.

Related errors


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