oracle/graal · error · PermanentBailoutException

Frame size (%d) exceeded maximum allowed frame size (%d).

Error message

Frame size (%d) exceeded maximum allowed frame size (%d).

What it means

FrameMap.finish() finalizes the frame size (spill slots + outgoing argument area + saved registers, rounded up to stack alignment) and enforces the target's maximum frame size from the RegisterConfig. Exceeding it throws a PermanentBailoutException because the platform ABI cannot address a larger frame. This is a deliberate, non-retryable bailout: the method simply cannot be compiled within the ABI limit.

Source

Thrown at compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/lir/framemap/FrameMap.java:188

     * Aligns the given frame size to the stack alignment size and return the aligned size.
     *
     * @param size the initial frame size to be aligned
     * @return the aligned frame size
     */
    protected int alignFrameSize(int size) {
        return NumUtil.roundUp(size, getTarget().stackAlignment);
    }

    /**
     * Computes the final size of this frame. After this method has been called, methods that change
     * the frame size cannot be called anymore, e.g., no more spill slots or outgoing arguments can
     * be requested.
     */
    public void finish() {
        GraalError.guarantee(frameSize == -1, "frame size may only be computed once");
        frameSize = currentFrameSize();
        if (frameSize > getRegisterConfig().getMaximumFrameSize()) {
            throw new PermanentBailoutException("Frame size (%d) exceeded maximum allowed frame size (%d).", frameSize, getRegisterConfig().getMaximumFrameSize());
        }
    }

    /**
     * Computes the offset of a stack slot relative to the frame register.
     *
     * @param slot a stack slot
     * @return the offset of the stack slot
     */
    public int offsetForStackSlot(StackSlot slot) {
        if (slot.isInCallerFrame()) {
            accessesCallerFrame = true;
        }
        return slot.getOffset(totalFrameSize());
    }

    /**
     * Informs the frame map that the compiled code calls a particular method, which may need stack

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Split the offending method into smaller methods so each compiled unit needs fewer spill slots
  2. Reduce inlining of the offending call chain (make helpers non-inlinable, e.g., @TruffleBoundary in Truffle contexts or lower inline thresholds)
  3. Cap loop unrolling/peeling growth for that method so the LIR does not balloon
  4. Locate the method with -Dgraal.Dump=:1 (the bailout dump shows the frame size and method) and target only that method

Example fix

// before: thousands of locals live simultaneously after inlining
void dispatch(int op) { /* 4000 lines, all locals live */ }

// after: partition into per-op methods
void dispatch(int op) { switch (op) { case 0 -> op0(); case 1 -> op1(); ... } }
Defensive patterns

Strategy: fallback

Try / catch

try {
    compile(method);
} catch (PermanentBailoutException e) { // frame-size bailout is permanent
    // ABI hard limit: this exact method can never be compiled; use interpreter/baseline
    runInterpreted(method);
}

Prevention

When it happens

Trigger: A compiled method with an enormous number of spill slots (very high register pressure across a huge method) or very large outgoing argument areas (huge call signatures), so currentFrameSize() > getRegisterConfig().getMaximumFrameSize(). Frequently seen after aggressive inlining or full unrolling creates one gigantic method.

Common situations: AOT-compiling generated code with giant methods; deep inlining chains fusing into one compilation unit; machine-generated dispatch methods with thousands of locals; sparc/aaarch64 ABIs with tighter frame limits than amd64.

Related errors


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