oracle/graal · error · GraalGraphError
accessing node id in %s across %d graph compression%s
Error message
accessing node id in %s across %d graph compression%s
What it means
Node ids are dense array indices that are renumbered whenever a graph is compressed (StructuredGraph.maybeCompress reuses ids of deleted nodes to keep arrays small). A NodeIdAccessor records the graph's compression counter (epoch) at creation; verifyIdsAreStable() throws GraalGraphError if any compression happened since, because an id captured earlier may now point at a different node. The check runs via assert, so it fires with assertions enabled (the default in Graal development runs).
Source
Thrown at compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graph/NodeIdAccessor.java:52
NodeIdAccessor(Graph graph) {
this.graph = graph;
this.epoch = graph.compressions;
}
Graph getGraph() {
return graph;
}
/**
* Verifies that node identifiers have not changed since this object was created.
*
* @return true if the check succeeds
* @throws GraalGraphError if the check fails
*/
boolean verifyIdsAreStable() {
int compressions = graph.compressions - epoch;
if (compressions != 0) {
throw new GraalGraphError("accessing node id in %s across %d graph compression%s", graph, compressions, compressions == 1 ? "" : "s");
}
return true;
}
/**
* Gets the identifier for a node. If assertions are enabled, this method asserts that the
* identifier is stable.
*/
int getNodeId(Node node) {
assert verifyIdsAreStable();
if (!node.isAlive()) {
throw new InternalError(node.toString());
}
return node.id();
}
}
View on GitHub (pinned to a66e9ccd1d)
Solutions
- Recreate NodeIdAccessor-dependent structures (NodeMap, NodeBitMap) after any point where compression may occur instead of caching them across phases.
- Never store raw node ids; store the Node objects or use Node.getId() only for transient logging within one phase.
- Register with the graph's compression events / check graph.compressions against your epoch before using ids (mirror verifyIdsAreStable).
- As a stopgap for experiments you can disable compression (e.g. StructuredGraph option governing maybeCompress), but fix the lifecycle for real code.
Example fix
// before NodeMap<T> map = new NodeMap<>(graph); runOtherPhases(graph); // may compress the graph map.set(someNode, value); // id epoch mismatch -> GraalGraphError // after runOtherPhases(graph); NodeMap<T> map = new NodeMap<>(graph); // build after the last possible compression map.set(someNode, value);
Defensive patterns
Strategy: validation
Validate before calling
// NodeIdAccessor is package-private; the caller-level equivalent is to not cache id-dependent state:
int epoch = graph.getCompressions(); // use whatever accessor your Graal version exposes (graph.compressions)
// ... later, before using ids:
if (epoch != graph.getCompressions()) {
rebuildIdKeyedStructures(); // recreate NodeMap/NodeBitMap now
} Try / catch
try {
int id = accessor.getNodeId(node); // throws GraalGraphError via assert when enabled
} catch (GraalGraphError e) {
// ids stale: rebuild accessor and retry the lookup
accessor = new NodeIdAccessor(graph);
id = accessor.getNodeId(node);
} Prevention
- Scope NodeMap/NodeBitMap/NodeIdAccessor lifetimes to a single phase; rebuild across phase-suite boundaries where compression happens.
- Never persist raw node ids beyond the phase that produced them.
- Run development builds with assertions enabled so instability is caught at first access.
When it happens
Trigger: Creating a NodeIdAccessor (or NodeMap/NodeBitMap keyed by ids) in one phase, then calling getNodeId() after a phase that triggers graph compression between them. Caching raw node ids (int values) across maybeCompress() calls. Long-lived maps built in an early phase and consulted in a late phase.
Common situations: Custom phases that keep NodeMaps alive across phase suites; Graal runs compression at phase-suite boundaries to reclaim id space. Debug code that stashes node ids for later correlation. Timing-dependent hits: only graphs with enough deletions actually compress, so the bug appears intermittently.
Related errors
- Graph was permanetly frozen.
- NodeBitMap was modified between the calls to hasNext() and n
- this node is not alive:
- unknown verbosity:
- Could not load Graal NodeClass TYPE field for
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/d21d33684f5a18f0.
Report an issue: GitHub.