oracle/graal · error · PermanentBailoutException

OSR with stack entries not supported: %s

Error message

OSR with stack entries not supported: %s

What it means

PermanentBailoutException thrown by OnStackReplacementPhase.getEntryMarker when the EntryMarkerNode's frame state has a non-empty operand stack (stackSize() != 0). An OSR entry with live operand-stack entries cannot be reconstructed as a normal compilation start, so Graal bails out permanently (with the full frame state appended to the message).

Source

Thrown at compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/phases/OnStackReplacementPhase.java:356

     *
     * @return a bit set with a bit set for each local variable that contains a live oop at
     *         {@code bci}
     */
    private static BitSet getOopMapAt(ResolvedJavaMethod method, int bci) {
        return ((HotSpotResolvedJavaMethod) method).getOopMapAt(bci);
    }

    private static EntryMarkerNode getEntryMarker(StructuredGraph graph) {
        NodeIterable<EntryMarkerNode> osrNodes = graph.getNodes(EntryMarkerNode.TYPE);
        EntryMarkerNode osr = osrNodes.first();
        if (osr == null) {
            throw new GraalError("No OnStackReplacementNode generated");
        }
        if (osrNodes.count() > 1) {
            throw new GraalError("Multiple OnStackReplacementNodes generated");
        }
        if (osr.stateAfter().stackSize() != 0) {
            throw new PermanentBailoutException("OSR with stack entries not supported: %s", osr.stateAfter().toString(Verbosity.All));
        }
        return osr;
    }

    private static LoopBeginNode osrLoop(EntryMarkerNode osr, CoreProviders providers) {
        // Check that there is an OSR loop for the OSR begin
        LoopsData loops = providers.getLoopsDataProvider().getLoopsData(osr.graph());
        CFGLoop<HIRBlock> l = loops.getCFG().getNodeToBlock().get(osr).getLoop();
        if (l == null) {
            return null;
        }
        return (LoopBeginNode) l.getHeader().getBeginNode();
    }

    private static boolean osrWithLocks(EntryMarkerNode osr) {
        return osr.stateAfter().locksSize() != 0;
    }

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Accept the bailout: execution continues in the interpreter; often a later OSR at a different bci succeeds.
  2. If it is performance-critical, restructure the hot loop so the loop header is at a stack-empty position (avoid splitting expressions across the backedge).
  3. Compiler developers: dump the graph at OnStackReplacement initial and inspect osr.stateAfter() to see which stack entries survive.
Defensive patterns

Strategy: fallback

Try / catch

try {
    compileResult = compile(task);
} catch (PermanentBailoutException e) {
    if (e.getMessage().startsWith("OSR with stack entries not supported")) {
        // interpreter continues; a later OSR at another bci may succeed
    }
}

Prevention

When it happens

Trigger: HotSpot requests OSR at a bci whose interpreter frame has values on the expression stack — e.g. the loop backedge bci sits in the middle of an expression, or the OSR trigger point is between operand pushes; parser produces an EntryMarkerNode whose stateAfter carries stack slots.

Common situations: Specific bytecode shapes where the OSR invocation point is not at a stack-empty position (long chained expressions inside hot loops); typically transient — the method usually gets OSR-compiled at another bci or runs interpreted. Mostly seen by compiler developers when frontend changes alter frame states.

Related errors


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