oracle/graal · error · PermanentBailoutException

Early inlining exceeded the maximum depth of %s for method %

Error message

Early inlining exceeded the maximum depth of %s for method %s.

What it means

PermanentBailoutException thrown by the Truffle early-inlining phase when the recursive walk over @Inlineinvoke-annotated calls exceeds the configured maximum depth. The phase deliberately uses a simple depth counter instead of building an inlining tree or cycle detection, so unbounded chains of early-inlineable methods (typically unintended recursion where an early-inlined helper again calls early-inline-annotated methods) hit this guard. Being 'permanent', it aborts this compilation without expecting a retry to help.

Source

Thrown at compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/truffle/phases/TruffleEarlyInliningPhase.java:128

    protected void run(StructuredGraph graph) {
        EconomicSet<Node> canonicalizableNodes = EconomicSet.create();
        boolean progress = true;
        int depth = 0;
        while (progress) {
            progress = false;
            if (depth > maxDepth) {
                /*
                 * We intentionally use a simple iteration/depth limit instead of managing an
                 * explicit inlining tree or recursion detection.
                 *
                 * Hitting this limit typically indicates an unintended recursive early-inlining
                 * pattern, or an early-inlined helper that expands into calls that are again
                 * annotated for early inlining without a clear bound.
                 *
                 * If recursion in early-inline methods is a valid use case, this phase needs to be
                 * extended with proper cycle detection or a more precise inlining policy.
                 */
                throw new PermanentBailoutException("Early inlining exceeded the maximum depth of %s for method %s.", maxDepth, graph.method());
            }
            List<Invoke> workList = new ArrayList<>();
            for (Node node : graph.getNodes()) {
                if (!(node instanceof Invoke invoke)) {
                    continue;
                }
                if (shouldInline(invoke)) {
                    workList.add(invoke);
                }
            }
            for (Invoke invoke : workList) {
                if (shouldInline(invoke)) {
                    inlineCall(canonicalizableNodes, graph, invoke);
                    progress = true;
                }
            }

            if (!canonicalizableNodes.isEmpty()) {

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Find the recursion: the exception message names graph.method() and maxDepth; dump the graph before the phase (-Dgraal.Dump=TruffleEarlyInlining) and trace which @Inline-annotated calls form the cycle.
  2. Break the cycle by removing the early-inlining annotation (@Inline in Truffle) from one call in the loop, or make the recursive helper a normal (non-early-inlined) call.
  3. Add an explicit base case so the annotated call chain is bounded below maxDepth.
  4. Only if recursion in early-inline methods is a legitimate use case: raise the depth limit option, and consider extending the phase with real cycle detection (as its comment notes) rather than relying on the depth counter.

Example fix

// before: mutual early-inlined recursion
@Inline
static Value evalA(Node n) { return evalB(n); }
@Inline
static Value evalB(Node n) { return evalA(n); }

// after: break the cycle — one side stays a普通 call
@Inline
static Value evalA(Node n) { return evalB(n); }
static Value evalB(Node n) { /* plain call, not early-inlined */ return evalA(n); }
Defensive patterns

Strategy: validation

Validate before calling

// Before enabling early inlining, sanity-check the call graph for cycles among @Inline methods
boolean cyclic = hasCycleAmongEarlyInlineMethods(entryMethod, /*maxProbes*/ maxDepth);
if (cyclic) {
    throw new IllegalStateException("early-inline recursion detected; fix annotations before compiling");
}

Try / catch

// Permanent bailout: do NOT transparently retry the same compilation
try {
    compileWithEarlyInlining(graph);
} catch (PermanentBailoutException e) {
    if (e.getMessage().contains("Early inlining exceeded")) {
        logAndDisableEarlyInliningFor(graph.method()); // fall back to normal compilation path
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling TruffleEarlyInliningPhase.runOnGraph on a graph where shouldInline(invoke) keeps returning true along a call chain deeper than maxDepth (derived from the early-inlining depth option), e.g. method A -> B -> C -> ... where every call target is annotated for early inlining and the chain is recursive or unbounded.

Common situations: A Truffle node helper annotated @Inline invokes another helper that (transitively) invokes back, creating a cycle with no explicit bound; adding a new early-inlined utility that fans out into many further early-inline calls; enabling aggressive early-inlining options after a language refactor.

Related errors


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