5. JIT Optimization

Escape Analysis for Stack Allocation

seniorjvm-internalsjit-optimization

Not every object needs to live on the heap. If the JIT can prove an allocated object never "escapes" the method that created it -- never returned, never stored into another (escaping) object's field, never handed to an opaque call -- it can allocate it on the stack instead, where it's freed for free when the method returns, skipping GC entirely for that object. This is escape analysis, and it's one of the biggest wins C2 (HotSpot's JIT) gets from proving things about your code beyond what the bytecode alone says.

Implement analyze_escapes(instructions) over a small straight-line IR (allocations + assignments + field stores + calls + a return):

  • ("ALLOC", var) -- var now holds a freshly allocated object. Each ALLOC is a distinct allocation SITE (numbered 0, 1, 2, ... in program order) -- even a variable reused across two ALLOCs refers to two independent sites, one at a time.
  • ("ASSIGN", dst, src) -- dst now aliases whatever object src currently refers to.
  • ("STORE_FIELD", obj_var, field, val_var) -- obj_var's object gets a reference to val_var's object stored into one of its fields. If obj_var's allocation site ever turns out to escape, val_var's site escapes too (transitively) -- because anything that can reach the container can now reach what's stored inside it. Storing into a site that never escapes doesn't force an escape.
  • ("CALL", [arg_var, ...]) -- an opaque call; every object passed as an argument escapes (the JIT can't see what the callee does with it).
  • ("RETURN", var) -- the returned object escapes to the caller.

Return the set of allocation-site ids that never escape by any of these paths -- the stack-allocation candidates.

Example

analyze_escapes([("ALLOC", "x")])
# -> {0}                      -- purely local, never touched again

analyze_escapes([
    ("ALLOC", "container"),   # site 0
    ("ALLOC", "payload"),     # site 1
    ("STORE_FIELD", "container", "data", "payload"),
    ("RETURN", "container"),
])
# -> set()                    -- container escapes directly (RETURN),
#                                 payload escapes transitively through it

Constraints

  • This IR is straight-line (no branches/loops) -- each variable is bound to exactly one allocation site at any point in the walk.
  • Escaping propagates through STORE_FIELD in only one direction: storing an escaping object into a container does NOT make the container escape; storing ANY object into an ALREADY (or eventually) escaping container DOES make the stored object escape.
  • A variable name is just a name -- reusing it for a second ALLOC creates a brand-new, independent site.

Real-world grounding

  • HotSpot C2's escape analysis pass -- its PointsToNode states (NoEscape, ArgEscape, GlobalEscape) are exactly the "does this allocation site escape" classification this exercise's return value collapses into a binary stack-allocate/don't: src/hotspot/share/opto/escape.cpp

Real C2 builds a full points-to CONNECTION GRAPH over SSA-form IR (handling branches, loops, and merges a straight-line walk can't express), and a NoEscape classification enables both scalar replacement (fields become individual SSA values, no object at all) and true stack allocation -- this exercise implements the core "does escaping propagate through a field store" propagation rule underneath both.

hints
escape_analysis.pysign in to run
Loading...
test result

This is a preview. Sign in to run this exercise against the real tests.