Skip to content

JIT codewriter: retire remaining pcdep regalloc and walker exception-CFG adaptations #371

Description

@youknowone

STATUS AUDIT (2026-07-23, main at 4d652c2dfa6). The previous body was stale and internally contradictory. The original walker-slot map/filter work is complete. This issue remains open for two independent structural tails:

  1. pcdep-derived register-allocation compensations that make CPython frame-slot liveness visible to coloring; and
  2. walker-specific exception-CFG reconstructions in flatten.rs.

Draft PR #742 optimizes the cost of the first tail, but intentionally does not claim to delete it.

Background

This is a follow-up to #267. The runtime flat slot-pinning resume model has been retired, and later work moved runtime lookup to JitCode-keyed metadata:

The original #371 body continued to describe already-deleted walker machinery and an obsolete PYRE_NO_XSLOT_FILTER reproduction. Those descriptions are removed below.

Completed work

PR #424 and subsequent cleanup retired the mechanisms named in the original title:

  • filter_cross_slot_coalesce_pairs is deleted.
  • walker_slot_for_variable, slot_pre_color, same-slot pinning, and the post-walk inputarg seeding are deleted from production code.
  • Cross-slot candidate rejection now goes through the RPython RegAllocator._try_coalesce-equivalent has_edge guard on the pre-renaming graph.
  • pcdep_canonical_slot + build_slot_disjoint_interference replaced the old walker-slot post-filter as the source of extra slot-identity edges.

The old four kept-stack/short-circuit failures remain useful regression coverage, but they are no longer a reproduction for a live filter:

  • kept_stack_branch_depths
  • short_circuit_boxed_int_cross_fn
  • short_circuit_value_kept_stack
  • short_circuit_value_local_kept

Current pipeline on main

transform_graph_to_jitcode currently performs the following Ref-bank allocation sequence before canonical flattening:

  1. Collect CFG link.args ↔ target.inputargs coalesce candidates on the pre-renaming graph.
  2. Replay the RPython interference check with filter_coalesce_pairs_by_interference(..., &[]).
  3. Build the initial graph coloring.
  4. Derive canonical CPython slots from pcdep_slot_var and pcdep_slot_var_resume.
  5. Build build_slot_disjoint_interference edges and filter the candidates again.
  6. Build the accepted-pair value partition with build_value_parent.
  7. Build CPython-co-live edges with build_colive_interference from both pcdep snapshot families.
  8. Rerun Ref allocation through perform_register_allocation_all_kinds_with_pairs_and_interference and flatten that coloring.

This is substantially different from the pipeline described in the original issue.

Remaining work A: delete the pcdep-derived regalloc compensation

The remaining adaptation surface is:

  • pcdep_canonical_slot
  • build_slot_disjoint_interference
  • build_value_parent
  • build_colive_interference
  • filter_coalesce_pairs_by_interference's external-interference replay
  • perform_register_allocation_*_with_pairs_and_interference
  • the second Ref allocation driven by those extra pairs/edges

Why it is still needed

The old body claimed that moving coalescing after dependency construction would make the separate co-live pass unnecessary. That is no longer accurate.

Current main already applies the upstream has_edge rule on the pre-renaming graph. The remaining problem is that ordinary flowgraph SSA liveness does not express all live Python frame slots:

  • fresh Variables are created across local/stack rereads;
  • a value can remain live in a Python frame slot across the end of its SSA live range; and
  • two distinct slots can occupy disjoint PCs yet still be unsafe to merge into one resume color.

RegAllocator.make_dependencies() therefore cannot see every edge required by color-indexed guard resume. build_colive_interference supplies the missing per-resume-PC edges, while build_slot_disjoint_interference prevents disjoint-PC, distinct-slot coalesces that would extend a color through a box-less region.

RPython-parity direction

Make frame/resume state structurally visible in the graph that the normal RPython allocation pipeline consumes. Once ordinary make_dependencies() observes the correct Variables as live and ordinary coalesce_variables() runs exactly once after it, delete the pcdep-derived slot oracle, value side partition, extra interference plumbing, and second allocation pass.

Do not replace these helpers with another side table. The target shape is the upstream sequence:

make_dependencies()
coalesce_variables()
find_node_coloring()

Draft PR #742 replaces the current naive union-find walk with the existing RPython-style UnionFind and caches dense representatives. This removes a large transform_graph_to_jitcode cost for regex/compiler-sized JitCodes, but the functions remain and this deletion criterion remains open.

Remaining work B: delete walker-specific exception-CFG reconstruction

This tail is independent of the coalescing work and did not fall out when #424 landed.

Live adaptations include:

  • Link::explicit_raise_value plus carry_explicit_raise_value_on_catch_stack and the single-exit explicit-raise arm in FlattenGraph::insert_exits;
  • emitted-stream block_can_raise detection;
  • the hoisted_tail rewrite that moves vable stores around catch_exception; and
  • the release assert! that enforces -live- / catch_exception adjacency after reconstructing it.

They exist because pyre's walker-built graph does not yet have the same exception shape as RPython flowspace:

  • RPython flowcontext.py::guessexception closes a block at each caught can-raise operation.
  • Explicit raise is represented in the graph/exception links consumed by the standard flattener.
  • Pyre can keep later vable-mirror stores in the same block and records an explicit raised value out-of-band on the link, so flatten.rs reconstructs the bytecode shape after the fact.

RPython-parity direction

Build the exception CFG in the flowspace shape: split at caught can-raise operations and represent explicit raise semantics in the graph itself. Then the ordinary flatten.py::insert_exits port should emit the correct adjacent -live- / catch_exception sequence without scanning emitted instructions, moving a tail, or consulting explicit_raise_value.

#174 is already closed by #551, which promoted the adjacency check from debug_assert! to release assert!. That hardening prevents silent release mis-resume, but it did not remove these structural adaptations; there is no longer a coordinated-closure dependency between #174 and this issue.

Done criteria

Regalloc tail

  • The production Ref allocation follows one upstream-shaped dependency/coalesce/color pass.
  • pcdep_canonical_slot, build_slot_disjoint_interference, build_value_parent, and build_colive_interference are deleted.
  • External pair/interference allocation wrappers used only by the splice adaptation are deleted.
  • Runtime guard/bridge resume remains unambiguous for every live local/stack slot.

Exception-CFG tail

  • Caught can-raise operations have structural flowgraph block boundaries matching flowcontext.py.
  • Explicit raise reaches the standard graph flattener without Link::explicit_raise_value.
  • The emitted-stream block_can_raise scan and hoisted_tail reconstruction are deleted.
  • catch_exception adjacency follows from graph structure and standard flattening rather than a repair.

Validation

  • cargo check --features dynasm
  • cargo test --features dynasm
  • Full check.py on dynasm and Cranelift, including the kept-stack/short-circuit regressions above and exception/inline-frame synths.
  • No Cranelift regression on fib_recursive, raise_catch, fannkuch, or nbody.
  • Byte-exact guard/blackhole resume versus the no-JIT oracle.

audited and rewritten against current main on 2026-07-23

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions