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
- Accept the bailout: execution continues in the interpreter; often a later OSR at a different bci succeeds.
- 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).
- 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
- Avoid splitting long expressions across loop backedges in extremely hot code.
- Treat this as a compiler limitation signal, not an application bug.
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
- OSR compilation without OSR entry loop.
- OSR with locks disabled.
- Unbalanced monitor enter-exit in OSR compilation with locks:
- At %s, bci %s: opcode %s (%s) has a stack effect of %s, the
- Compilation exceeded %.3f seconds%s. %n Phase timings:%n %s
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/d888be921c6d7b33.
Report an issue: GitHub.