oracle/graal · error · PermanentBailoutException

too many iterations in computeGlobalLiveSets

Error message

too many iterations in computeGlobalLiveSets

What it means

Thrown by LinearScan register allocation when computing global liveness sets via fixpoint iteration over the CFG. The do/while loop keeps iterating while changes occur; if changes are still occurring after more than 50 iterations, the algorithm is considered non-convergent and a PermanentBailoutException aborts the compilation. The comment in the source explicitly says this 'should never happen', so it indicates a CFG pathology (e.g., self-modifying pred sets or extremely deep loop nesting) rather than normal compiler behavior.

Source

Thrown at compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/alloc/lsra/LinearScanLifetimeAnalysisPhase.java:416

                                debug.log("block %d: livein = %s,  liveout = %s", block.getId(), liveIn, blockSets.liveOut);
                            }
                            int predecessorCount = block.getPredecessorCount();
                            for (int p = 0; p < predecessorCount; p++) {
                                BasicBlock<? extends BasicBlock<?>> predecessor = block.getPredecessorAt(p);
                                BlockData predBlockSet = allocator.getBlockData(predecessor);
                                // Process this block on the next iteration
                                predBlockSet.dirty = true;
                            }
                        }
                    }
                    iterationCount++;

                    if (changeOccurred && iterationCount > 50) {
                        /*
                         * Very unlikely, should never happen: If it happens we cannot guarantee it
                         * won't happen again.
                         */
                        throw new PermanentBailoutException("too many iterations in computeGlobalLiveSets");
                    }
                }
            } while (changeOccurred);

            assert verifyLiveness();

            computeGlobalLiveSetsBlocksProcessed.add(allocator.debug, blocksProcessed);
            computeGlobalLiveSetsBlocks.add(allocator.debug, numBlocks);

            // check that the liveIn set of the first block is empty
            BasicBlock<?> startBlock = allocator.getLIR().getControlFlowGraph().getStartBlock();
            if (allocator.getBlockData(startBlock).liveIn.cardinality() != 0) {
                if (allocator.isDetailedAsserts()) {
                    reportFailure(numBlocks);
                }
                SparseBitSet bs = allocator.getBlockData(startBlock).liveIn;
                StringBuilder sb = new StringBuilder();
                for (int i = bs.iterateValues(0); i >= 0; i = bs.iterateValues(i + 1)) {

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Capture the failure with -Dgraal.Dump=:1 and file a GraalVM issue including the method and dump, since this is an internal invariant that should never fire
  2. Exclude the offending method from compilation: add it to the compilation blacklist (e.g., -Dgraal.CompileTheStoreBlack.../TruffleBoundary or mx/--Dgraal.MethodFilter to isolate, then use compilation-exclusion options) so it stays on the baseline/interpreter path
  3. If the method is generated code, restructure it to reduce loop-nesting depth or split it into smaller methods
  4. Retry the build on a newer GraalVM release, as fixpoint-handling bugs in LSRA liveness have historically been fixed

Example fix

// before: one giant method with 12 nested loops -> triggers non-convergent liveness
void process(Matrix m) { for(...) { for(...) { ... } } }

// after: split into smaller methods so each compiled unit has a shallow CFG
void process(Matrix m) { for (Row r : m.rows()) processRow(r); }
void processRow(Row r) { for (Cell c : r.cells()) processCell(c); }
Defensive patterns

Strategy: try-catch

Validate before calling

// No meaningful pre-validation: convergence is an internal property.
// Optionally cap compiled-method complexity before submitting it.
if (methodBytecodeSize > 8000 || loopNestingDepth(method) > 8) {
    skipCompilationAndUseBaseline(method);
}

Try / catch

try {
    compile(method);
} catch (PermanentBailoutException e) {
    // non-retryable: fall back to baseline/interpreter for this method, log and report
    fallbackToBaseline(method, e);
}

Prevention

When it happens

Trigger: Compiling a method whose control-flow graph causes the liveIn/liveOut backward dataflow in LinearScanLifetimeAnalysisPhase.computeGlobalLiveSets to still be changing after iteration 51 (iterationCount > 50 && changeOccurred). Typically requires very deeply nested loops or an unusual CFG shape produced by bytecode instrumentation or complex loop transformations.

Common situations: JIT compiling or AOT-image-building an unusually large method with many nested loops; generated/instrumented bytecode with irregular CFGs; rarely, a Graal version regression in liveness handling. Mostly seen as a one-off on a specific method.

Related errors


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