Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Allocator Drivers — ColoringAllocator Wiring and Loop-Aware Linearize

All symbols and addresses on this page apply to neuronx_cc 2.24.5133.0+58f8de22 (cp310; cp310/11/12 are byte-identical Cython rebuilds). Everything here lives in neuronxcc/starfish/lib/libwalrus.so. For .text (0x62d660+) and .rodata (0x1c72000+) the virtual address equals the file offset; the 0x5e90200x62d650 range is .plt thunks, so every body address cited here is the real high body (0x9x…/0xax…/0xbx…), never its thunk. .data carries a +0x400000 delta. Other wheels differ — treat every address as version-pinned.

Abstract

The walrus backend places every tensor with graph coloring, not linear scan, at default optimization. The colorer itself is Chaitin–Briggs — simplify, select, spill, repeat — but the driver that runs it is what this page documents: the wiring layer that decides which memory space is colored first, how spilled tensors cascade down the memory hierarchy, and how the loop-aware variant flattens loop nests so a value live across an iteration gets one allocation. This is the seam between the pass pipeline (pass-pipeline-optlevels) and the per-space colorers (the *_Allocator classes, planned as separate pages).

There are two driver families. The first is neuronxcc::backend::ColoringAllocator (ctor @0x9870c0), one class registered under eight pass names. The registry collapse was established in backendpass-registry: the eight names are the surplus of a single class whose constructor takes a (Type, is_post_link, MemoryAddressSpace, MemoryAddressSpace) discriminator tuple. This page enumerates what each of the eight does — which space it colors, whether it runs pre- or post-link, and the four constructor invariants the binary asserts. The default pre-codegen pipeline sub_80A6E0 wires four of them into a strict cascade PSUM → SB → DRAM → DRAM-shared, each followed by its own DMA-optimization and address-rotation re-tuning; the low-level builder sub_805870 wires the two post_lnk variants plus the REG allocator after bir_linker.

The second family is ColoringAllocatorWithLoop (ctor @0xa84fc0), a distinct class selected only at optlevel 6. Its Rep::allocate driver runs PSUM → SB itself over a LinearizedFunction produced by a loop-flattening linearize (flattern_loop @0xa89a60) that inlines loop bodies into one instruction-point stream. The page closes with the single most important correction for a reimplementer: the per-space "best-of-n" phrasing in the binary's own log strings is not a fixed-count best-of-N search — it is a spill-iteration fixpoint that re-colors until no tensor needs spilling. Proven below from the SB colorer's restart loop.

For reimplementation, the contract is:

  • The eight ColoringAllocator registrations — the (Type, post_lnk, space) matrix and what each colors.
  • The PSUM → SB → DRAM → DRAM-shared cascade order and why the hierarchy forces it.
  • The loop-aware linearize: how flattern_loop expands InstLoop bodies inline so cross-iteration live ranges are visible to one interference graph.
  • The spill-fixpoint loop — the correction to the "best-of-n" reading — and its termination condition.
Plain driver classneuronxcc::backend::ColoringAllocator ctor 0x9870c0, run 0x985100
Registrations8 factory _M_invoke lambdas, one class (see matrix)
Loop-aware driver classColoringAllocatorWithLoop ctor 0xa84fc0, run 0xa84bb0, driver Rep::allocate 0xa87030
Pre-codegen buildersub_80A6E0 (0x80a6e0) — optlevel 1/2/3, cascade PSUM→SB→DRAM→DRAM-shared
Loop-aware buildersub_807EF0 (0x807ef0) — optlevel 6
Post-link buildersub_805870 (0x805870) — dram_post_lnk, dram_shared_post_lnk, reg
Cascade orderPSUM (frozen first) → SB → DRAM (spill target) → DRAM-shared (cross-core)
LinearizeRep::linearize 0xa89e70flattern_loop 0xa89a60 (opcode 105 = InstLoop)
Coloring coreper-space *_Allocator::allocate, Chaitin–Briggs + spill fixpoint (not best-of-N)
Source pathneuronxcc/walrus/coloring_allocator/src/coloring_allocator.cpp (asserts at :24/:25/:30/:31)

The Eight ColoringAllocator Registrations

Purpose

The four pass names the pipeline emits most visibly — coloring_allocator_{psum,sb,dram,dram_shared} — are not four classes. They are four of eight registrations of one class. backendpass-registry proved the registry maps a pass-NAME string to a factory std::function<unique_ptr<BackendPass>(PassOptions const&)>; the eight coloring_allocator_* names (the ninth, coloring_allocator_with_loop, is the distinct loop-aware class) all resolve to the same ColoringAllocator constructor with different discriminator arguments. This section enumerates the matrix and what each row colors.

Entry Point

Each pass name is built inline by the pipeline builder and resolved through the registry to a factory whose _M_invoke constructs the parameterized allocator:

sub_80A6E0 / sub_807EF0 (pipeline builder)
  └─ build name string "coloring_allocator_<X>"        (helper sub_7FAE40)
       └─ BackendPassManager::addModParallelPass(name)  0x1748bd0
            └─ lookupGenerator(name)                     0x1744cb0
                 └─ GeneratorRegistration::getGenerator  0x1735740   → factory std::function
                      └─ factory _M_invoke               (per-name; table below)
                           ├─ tc_new(48)  BackendPass object
                           └─ ColoringAllocator::ctor    0x9870c0   (Type, post_lnk, space, space2)

An unregistered name aborts with "Backend pass: <n> is not registered!". [CONFIRMED — resolution chain matches backendpass-registry; ctor call site read from each factory disasm.]

The constructor matrix

The single point of parameterization is the constructor @0x9870c0:

ColoringAllocator::ColoringAllocator(
    PassOptions const&,            // config bundle, shared by all factories
    Type        type,              // edx: 0=SB 1=PSUM 2=DRAM 3=REG
    bool        is_post_link,      // cl/ecx
    MemoryAddressSpace from_space, // r8d: 0=Local 1=Shared 2=Debug
    MemoryAddressSpace to_space);  // r9d: 0=Local in ALL eight registrations

Each factory _M_invoke tc_news the BackendPass, then calls the ctor with constant immediates. The immediates below were read directly from each factory's disasm at the ctor call site (call ColoringAllocatorC1… preceded by the register loads). This independently reproduces the matrix:

Pass name (registry)Factory _M_invokeedx Typeecx post_lnkr8d from_spaceColors
coloring_allocator_sb0x98a6f0xor→0 SBxor→0 falsexor→0 LocalSBUF working tensors (RepT<SB_Allocator>)
coloring_allocator_psum0x98a490mov 1 PSUMxor→0 falsexor→0 LocalPSUM matmul accumulators (RepT<PSUM_Allocator>)
coloring_allocator_dram0x98a5c0mov 2 DRAMxor→0 falsexor→0 Localper-core DRAM spill / large tensors (RepT<DRAM_Allocator>)
coloring_allocator_reg0x98a0f0mov 3 REGxor→0 falsexor→0 Localengine registers (RepT<REG_Allocator>, the L33 register allocator)
coloring_allocator_dram_shared0x989e80mov 2 DRAMxor→0 falsemov 1 Sharedcross-core shared-DRAM lifetimes
coloring_allocator_dram_debug0x98a360mov 2 DRAMxor→0 falsemov 2 Debugdevice-print / debug buffers
coloring_allocator_dram_post_lnk0x989fc0mov 2 DRAMmov 1 TRUExor→0 LocalDRAM re-laid across the LINKED image
coloring_allocator_dram_shared_post_lnk0x98a220mov 2 DRAMmov 1 TRUEmov 1 Sharedshared-DRAM re-laid across the LINKED image

[ALL CONFIRMED — edx/ecx/r8d immediates read from each factory's disasm; e.g. psum @0x98a4ae: xor r8d,r8d; xor ecx,ecx; mov edx,1 then the ctor call @0x98a4c1.]

QUIRK — the fourth constructor argument to_space (r9d) is xor r9d,r9d = 0/Local in all eight factories. It is a second MemoryAddressSpace the ctor accepts but the registered factories never exercise; its role (a secondary space, plausibly a spill source) is INFERRED, not observable from these call sites.

The four ctor-enforced invariants

The constructor dispatches on Type to install one of four RepT<*_Allocator> vtables into a tc_new(696) Rep object, and guards the (post_lnk, space) combination with four NeuronAssertions. The assert strings and coloring_allocator.cpp line numbers were read verbatim from the ctor decompile @0x9870c0:

Source lineAsserted conditionMeaning
coloring_allocator.cpp:24is_post_link == false (PSUM path)PSUM is never a post-link allocator
coloring_allocator.cpp:25from_addr_space == Local (PSUM path)PSUM is always core-local
coloring_allocator.cpp:30is_post_link == false (SB path)SB is never a post-link allocator
coloring_allocator.cpp:31from_addr_space == Local (SB path)SB is always core-local (SBUF)

These match the matrix exactly: only DRAM-family rows carry post_lnk=true or a non-Local from_space. [CONFIRMED — four logging::NeuronAssertion<ErrorCode> sites at error codes 508–511 in the ctor decompile, each preceded by the .cpp:NN path string.]

NOTE — the Rep object is tc_new(696) = 0x2B8 bytes; the RepT<SB_Allocator> / RepT<PSUM_Allocator> / RepT<DRAM_Allocator> / RepT<REG_Allocator> vtable is installed at Rep+0. The vptr stored is the _ZTV symbol + 0x10 (past the offset-to-top / typeinfo header), not the symbol itself.


The PSUM → SB → DRAM → DRAM-Shared Cascade

Purpose

At optlevel 1/2/3 (the default "coloring" plane) the builder sub_80A6E0 emits the four base allocators in a fixed order interleaved with per-space re-tuning passes. The order is not arbitrary: it is dictated by the memory hierarchy and by the fact that one space's allocator produces constraints (or spill targets) the next space's allocator consumes.

Entry Point

sub_80A6E0 @0x80a6e0 is the real builder body — it directly drives BackendPassManager::addModParallelPass / addSubgraphParallelPass / addCoreParallelPass (≈139 add-call sites, ≈127 named pass steps). The cascade and its inter-allocator passes:

sub_80A6E0  (optlevel 1/2/3, --allocator != "lsa")
  … runtime_memory_reservation …
  ┌── PSUM block ────────────────────────────────────────────
  │  coloring_allocator_psum          Type=1, Local        *** colored first, frozen ***
  │  dma_optimization_psum            DMA tuned into the PSUM placement
  │  address_rotation_psum            tile-layout rotation on PSUM
  │  [memory_analysis_after_address_rotation_psum]
  ├── SB block ──────────────────────────────────────────────
  │  coloring_allocator_sb            Type=0, Local
  │  [memory_analysis_after_coloring_allocator_sb]
  │  dma_optimization_sb  /  address_rotation_sb  + memory_analysis recomputes
  │  non_ssa_legalization             *** POST PSUM+SB: re-legalize SSA-clones to fixed slots ***
  ├── DRAM block ────────────────────────────────────────────
  │  coloring_allocator_dram          Type=2, Local        gate: !--disable-dram-allocation
  │  address_rotation_dram            gate: !--disable-dram-rotation
  │  dynamic_dma_cleanup / build_fdeps / anti_dependency_analyzer
  └── DRAM-shared block ─────────────────────────────────────
     coloring_allocator_dram_shared   Type=2, Shared       (cross-core; gated paths)
     sync_shared_allocations / extend_shared_lifetimes / lower_local_collectives
     coloring_allocator_dram_debug    Type=2, Debug        (device-print buffers)

[CONFIRMED — cascade order and inter-allocator passes from the sub_80A6E0 builder; gate predicates from the surrounding if() bodies.] The relative ordering PSUM → SB → DRAM → DRAM-shared is exact; the realized step numbers drift because a birverifier/lnc_verifier is interposed after most passes when enableVerifier is set.

Algorithm — the per-space re-tuning cycle

Every space allocator is immediately followed by its own DMA-optimization and address-rotation, because each allocator has just fixed physical addresses for that space and the DMA and tile layout must be re-tuned to that placement before the next space is colored:

function ColorOneSpace(space):               // the PSUM/SB/DRAM repeat in sub_80A6E0
    emit "coloring_allocator_<space>"        // place: assign physical addresses for space
    emit "dma_optimization_<space>"          // DMAOptimization: re-route DMA into the placement
    emit "address_rotation_<space>"          // AddressRotation: rotate tile layout (i mod N)
    if "<…>" in --memory-analysis-after set:  // runtime membership test sub_7FC770
        emit "memory_analysis_after_<space>" // re-measure HWM / footprint for next space

GOTCHA — the DRAM block has no dma_optimization_dram. DRAM placement is followed by dynamic_dma_cleanup / dynamic_dma_scan + build_fdeps + remove_redundancies instead — the DMA into DRAM is handled by the dynamic-DMA machinery, not the space-scoped DMAOptimization used for PSUM/SB. A reimplementer that mechanically appends dma_optimization_<space> after every allocator will emit a pass that does not exist for DRAM.

Considerations — why this order

The cascade follows the memory hierarchy, and each step freezes constraints for the next:

  • PSUM first. PSUM (MemoryType=32, the matmul accumulator space, a small bank-addressed window) is the most constrained and is RMW-pinned by accumulation groups. It is colored first and frozen so its banks become immovable constraints for everything downstream. psum_legalization precedes it.
  • SB second. SBUF working tensors (MemoryType=16, partition-addressed) are colored with the PSUM accumulators already immovable. SB spill decisions target DRAM, so SB must precede DRAM. After SB+PSUM are placed, non_ssa_legalization re-legalizes the SSA-clones inserted before allocation to the now-fixed physical SB/PSUM slots (the sb_allocated attribute flips true) — see reorder-nonssa-presched.
  • DRAM third. DRAM (MemoryType=8) is the spill / large-tensor backing store. It must follow SB so the SB allocator's spill decisions have a DRAM target.
  • DRAM-shared last. Cross-core shared-DRAM lifetimes need every per-core DRAM placement done first, then coordination across NeuronCores via extend_shared_lifetimes / sync_shared_allocations + anti_dependency_analyzer_post_shared_dram.

The geometry of PSUM banks and SB partition rectangles is detailed in sbuf-psum-geometry; the tensor / memory-location model the colorers operate over is memory-location and tensor-buffer-node.


The Post-Link Allocator Wiring

Purpose

The two post_lnk registrations and the reg allocator do not run in the pre-codegen pipeline. They run in the low-level builder sub_805870 (0x805870), which always executes after codegen, right after bir_linker links the multi-LNC modules — so DRAM is re-allocated across the linked address space.

Entry Point

sub_805870  (low-level, always run, after bir_linker)
  report_stats
  coloring_allocator_dram_post_lnk          Type=2, Local,  post_lnk=1
  coloring_allocator_dram_shared_post_lnk   Type=2, Shared, post_lnk=1
  sync_shared_allocations
  coloring_allocator_dram_post_lnk
  anti_dependency_analyzer_post_lnk / separate_load_and_compute_post_ada
  … engine lowering …  lower_ap
  coloring_allocator_reg                    Type=3 REG  (the L33 register allocator)
  vnc_remote_addr_map …

[CONFIRMED — sub_805870 emission order.] This is exactly why the constructor permits post_lnk=true only for the DRAM family (PSUM/SB asserted pre-link, §invariants): PSUM and SB are core-local and fixed pre-link, while DRAM — including cross-core Shared — must be re-laid across the linked image.


The Loop-Aware Driver — ColoringAllocatorWithLoop

Purpose

At optlevel 6 the builder sub_807EF0 wires a distinct class, ColoringAllocatorWithLoop, registered as coloring_allocator_with_loop. It expresses the same Chaitin–Briggs algorithm as the plain family but adds a loop-flattening linearization so live ranges that cross loop bodies — including values live across the loop back-edge — are visible to a single interference graph. Its nested colorers operate on a LinearizedFunction*, not a bir::Function* + CFG.

Entry Point

coloring_allocator_with_loop  (optlevel 6 only, --allocator != "lsa")
  ColoringAllocatorWithLoop::run(bir::Module&)            0xa84bb0
    └─ Rep::allocate(Module&)                             0xa87f70   // per-Function loop
         └─ Rep::allocate(Module*, Function*)             0xa87030   *** THE DRIVER ***
              ├─ lf  = Rep::linearize(Function)           0xa89e70   // flattern_loop: bodies inlined
              ├─ PSUM_Allocator::allocate(lf)             0xad7140   *** PSUM FIRST, frozen ***
              ├─ SB_Allocator::allocate(lf')              0xa95310   // consumes PSUM-mutated lf
              ├─ Rep::indice_legalization(lf'', Function) 0xa8eb00   // AP index fixup post-place
              └─ Rep::free_linearized_function(lf)        0xa89b90

Algorithm

function Rep_allocate(module, function):              // sub_0xa87030
    lf  = Rep::linearize(function)                    // 0xa89e70 — flattern_loop, def/use check
    psum = PSUM_Allocator(opts, module, function)     // ctor 0xad9970
    lf  = psum.allocate(lf)                           // 0xad7140  *** PSUM colored first ***
    sb   = SB_Allocator(opts, module, function)       // ctor 0xa97750
    lf  = sb.allocate(lf)                             // 0xa95310  *** SB consumes PSUM-mutated lf ***
    Rep::indice_legalization(lf, function)            // 0xa8eb00  AP indices now physical
    Rep::free_linearized_function(lf)                 // 0xa89b90
    // DRAM, REG, shared-DRAM are downstream SEPARATE passes, NOT this driver

[CONFIRMED — Rep::allocate(Module*,Function*) decompile: linearize (line 703) → PSUM_Allocator::allocate (705) → SB_Allocator::allocate (747) → indice_legalization (882) → free_linearized_function (883).]

CORRECTION (D-K01) — a "SB→PSUM→DRAM→REG per-space sequence inside the with-loop Rep" reading is wrong on two counts. (1) The with-loop driver runs PSUM then SB, in that order, sharing one LinearizedFunction. (2) The driver runs only PSUM and SB — DRAM is colored by the downstream coloring_allocator_{dram,…,dram_debug} passes (plain-family style) and REG by coloring_allocator_reg in the low-level builder. The with-loop translation unit contains sibling DRAM_Allocator colorers (@0xafa350), but Rep::allocate never invokes them; they are the loop-aware DRAM colorer used when the TU is driven by the standalone bin/coloring_allocator_with_loop harness.

Considerations

PSUM is colored first and frozen for the same reason as the plain cascade (its RMW-pinned accumulator banks become immovable constraints); SB is colored second over the same LinearizedFunction that PSUM_Allocator::allocate mutated, so loop-crossing live ranges are consistent between the two passes. The PassOptions are snapshot-copied into the Rep by Rep::Rep (@0xa886b0); the Rep holds no persistent colorer members — PSUM_Allocator / SB_Allocator are stack-constructed per Function.


The Loop-Flattening Linearize

Purpose

Rep::linearize builds a LinearizedFunction — a chain of instruction-point nodes the colorer numbers for liveness — and flattern_loop is its loop-aware core. The plain-family SB_Allocator::linearize / PSUM_Allocator::linearize (@0x9cefe0 / @0xa30b10) treat a loop as one opaque instruction: a tensor produced before a loop and consumed inside it has its live range conservatively span the whole loop as a black box. flattern_loop instead inlines the loop body's instruction-points into the single linear stream, giving the colorer the true intra-loop live ranges.

Entry Point

Rep::linearize(bir::Function*)                  0xa89e70
  ├─ per BasicBlock: tc_new(40) LinearizedBlock
  │    └─ flattern_loop(BasicBlock&, LinearizedBlock*, &tail)   0xa89a60
  ├─ LinearizedFunction = tc_new(40); first BB stored
  └─ "  linearize and check"  — static def/use validation over operand APs

Source: walrus/walrus_loop_flow/coloring_allocator_with_loop/src/linearize_with_loop.cpp (asserts cite this path).

Algorithm

function flattern_loop(bb, lin_block, &tail):        // sub_0xa89a60
    for inst in bb.instructions:                      // intrusive ilist
        node = tc_new(40)                             // LinearizedInstruction: +0 = bir::Instruction*
        append node to *tail                          // chain position IS the instruction-point;
                                                      //   dense id assigned later by renumber_locations
        if *(int*)(inst + 0x50) == 105:               // bir::InstLoop opcode (D-D05 Loop=105)
            body = bir::BasicBlockHolder::blocks(inst - 96)
            for child_bb in body:                     // recurse INTO the loop body
                flattern_loop(child_bb, lin_block, &tail)   // splice body points INLINE, nested loops recurse

[CONFIRMED — flattern_loop body @0xa89a60: tc_new(40) (line 22/32), the opcode test *(_DWORD *)(v3 + 80) == 105 (line 38), bir::BasicBlockHolder::blocks((…)(v3 - 96)) (line 40), and the recursive flattern_loop(...) calls (lines 48/55). The llvm::isa<bir::InstLoop> cast confirms the opcode-105 node is the loop instruction.]

QUIRK — there is no explicit integer instruction-point index stored by flattern_loop. The LinearizedInstruction is 40 bytes: +0 is the bir::Instruction*, +8 is the doubly-linked chain pointer (with the LinearizedBlock back-pointer packed in the high lane). The dense integer instruction-point id is assigned later, by each *_Allocator::renumber_locations, by walking the chain in order. A reimplementer who expects linearize to hand back numbered points will not find them; the number is the chain position. (struct layout STRONG — derived from flattern_loop, not recovered by IDA.)

The static def/use check

In the same pass, linearize walks every instruction's input and output AccessPattern lists, resolves each to a MemoryLocationSet, and maintains a defined-set and a used-set (llvm::DenseSet<MemoryLocationSet*>). On a mismatch it emits "Bad input file - failed static checks.", "<N> names were undefined at use point", "<N> names were unused at definition point". Output tensors are skipped via bir::isTensorKindOutput(). This is the SSA-style use-before-def / dead-def validation the liveness analysis assumes. [CONFIRMED — strings + DenseSet logic in Rep::linearize.]


The Spill Fixpoint — "Best-of-N" Is a Correction

Purpose

This is the headline correction of the page. Each per-space colorer's log strings say " best-of-n loop, heuristic = ", and that phrase reads as a bounded best-of-N search: run N coloring attempts, keep the best. It is not. The loop is a spill-iteration fixpoint: color, and if select reports any node that could not be placed, insert spill code and re-color the spilled-and-reloaded function, repeating until the spill set is empty. There is no fixed iteration count N — termination is "no more spills."

Algorithm

Transcribed from SB_Allocator::allocate(LinearizedFunction*) @0xa95310 (the with-loop colorer; the plain-family @0x9d1b10 has the same structure):

function SB_Allocator_allocate(lf):                  // sub_0xa95310
    log "  allocating SB"
    renumber_locations(lf)                            // dense id per MemoryLocationSet
    memloc_split(lf)                                  // live-range split of multi-range memlocs

  restart:                                            // LABEL_79 (line 798) — THE FIXPOINT HEAD
    renumber_locations(lf)
    find_partners(lf)                                 // coalescing / co-allocation candidates
    find_first_defs(lf); find_last_uses(lf); find_loads(lf)
    live_range(lf)                                    // liveness over instruction points
    build(lf)                                         // interference graph
    find_costs(lf)                                    // spill-cost annotation
    log "      best-of-n loop, heuristic = "          // <-- the misleading string
    stack    = simplify(lf)                           // Chaitin simplify  → NodeStack*
    placement= select(lf, stack, &score)             // 2-D place; sets need-spill flag at placement+8
    log "        SB score = " score

    if placement.need_spill == 0:                     // *(_DWORD*)(placement+2) == 0  → no spill
        log "      best SB heuristic = "
        commit(lf); return                            // LABEL_123 (line 1139): coloring succeeded

    // --- spill iteration: a tensor could not be placed ---
    create_eintervals(lf, spillset)                   // placement intervals for the spill set
    lf = insert_spill_code(lf, spillset)              // mint reload/store DMA memlocs → NEW lf
    goto restart                                       // re-color the spilled function (FIXPOINT)

[CONFIRMED — SB_Allocator::allocate decompile: simplify (line 1032) → select (1033) → the branch if (!*((_DWORD *)v213 + 2)) on the select result v213 (line 1066, commit path v101=1, LABEL_123) versus the spill path create_eintervals (line 1083) → insert_spill_code returning a new inserted linearized function (line 1115). The fixpoint head LABEL_79 is at line 798; the only edge that re-enters it threads through the spill path. No bounded counter gates the loop.]

Why "best-of-n" is the wrong reading

The string is a label for the per-attempt heuristic log, not a count. Two facts from the binary prove the loop is a fixpoint, not a fixed-N search:

  1. The termination condition is data, not a counter. The branch out of the loop is if (select.need_spill == 0) — i.e. the colorer leaves the loop the moment an attempt places every tensor. There is no for (i = 0; i < N; i++) and no "best score so far" register compared across a fixed number of trials.
  2. Each iteration operates on a strictly larger function. insert_spill_code returns a new LinearizedFunction with reload/store memlocs minted in; the next iteration colors that, not a re-perturbed copy of the original. A best-of-N search re-runs the same problem with a different seed; this re-runs a different, spill-augmented problem each time. That is the definition of a spill fixpoint.

Two further strings settle it. The colorer logs "Number of iterations in SB spills do loop: " at the loop exit — it counts the iterations it took rather than bounding them, which is exactly what a fixpoint does and a best-of-N does not. And the only ceiling on the loop is a wall-clock safety valve, not an iteration count: if the colorer runs longer than ~2700 seconds it aborts with "GCA is taking too long" and exit(2). A timeout is a watchdog, not a search budget.

The colorer reports the work it did: "spilling from SB cost about <N> cycles", "number of tensors spilled from SB = ", "total size of spilled tensors = <N> bytes/partition". Failure (a tensor that cannot be placed even after spilling) is "couldn't allocate every tensor in SB and spilling can't help". [CONFIRMED — strings in the colorer body; the iteration-count log and the exit(2) timeout were read at the loop tail.]

NOTE — the PSUM colorer PSUM_Allocator::allocate @0xad7140 has the identical phase order, recovered from its disasm call targets: " allocating PSUM"renumber_locationslive_rangebuildfind_costs" best-of-n loop, heuristic = "simplify (@0xad7ce6) → select (@0xad7d09) → insert_spill_code (@0xad8a25) → "spilling from PSUM cost about ". The two colorers differ only in geometry (PSUM = banks; SB = partition × byte rectangles) and a few special cases (PSUM accumulation banks; SB pinning).


Allocator-Family Selection

Two independent selectors decide which allocator family is wired:

SelectorMechanismResult
optlevel → builderrun_backend_driver reads optlevel, picks the buildersub_806F80 (opt0) → LSA only; sub_80A6E0 (opt1/2/3) → plain coloring cascade; sub_807EF0 (opt6) → coloring_allocator_with_loop; sub_80D9D0 (opt7) → SMT allocation; sub_809580 (opt8) → pure LSA
--allocator stringstd::string::compare("lsa") @0x80b089 inside the builder== 0 → wire linear_scan_allocator, skip the coloring sub-pipeline; != 0 (default / "coloring") → wire the four-space cascade

The DRAM step has a further gate: even on the coloring path, coloring_allocator_dram is skipped under --disable-dram-allocation, and address_rotation_dram under --disable-dram-rotation. [CONFIRMED — builder/optlevel mapping and the compare site; gate predicates from the surrounding if() bodies.] --smt-allocation forces optlevel 7. The detailed optlevel-plane mapping is in pass-pipeline-optlevels.


Infrastructure Functions

FunctionAddressRoleConfidence
ColoringAllocator::ColoringAllocator(PassOptions&,Type,bool,AddrSpace,AddrSpace)0x9870c0the 8-way parameterized ctor; 4 invariant assertsCONFIRMED
ColoringAllocator::run(bir::Module&)0x985100plain-family pass entryCONFIRMED
ColoringAllocatorWithLoop ctor / run0xa84fc0 / 0xa84bb0loop-aware class (distinct)CONFIRMED
Rep::allocate(Module*,Function*)0xa87030the loop-aware driver (PSUM→SB)CONFIRMED
Rep::linearize(Function*)0xa89e70builds LinearizedFunction + def/use checkCONFIRMED
flattern_loop(BasicBlock&,LinearizedBlock*,&tail)0xa89a60opcode-105 loop-body inliningCONFIRMED
Rep::indice_legalization(LinearizedFunction*,Function*)0xa8eb00AP index fixup post-placementCONFIRMED
SB_Allocator::allocate(LinearizedFunction*)0xa95310with-loop SB colorer + spill fixpointCONFIRMED
PSUM_Allocator::allocate(LinearizedFunction*)0xad7140with-loop PSUM colorer + spill fixpointCONFIRMED
SB_Allocator::insert_spill_code0xac87d0mints reload/store memlocs → new lfCONFIRMED
pre-codegen builder sub_80A6E00x80a6e0optlevel 1/2/3 cascadeCONFIRMED
loop-aware builder sub_807EF00x807ef0optlevel 6CONFIRMED
post-link builder sub_8058700x805870dram_post_lnk / dram_shared_post_lnk / regCONFIRMED
BackendPassManager::lookupGenerator(string&)0x1744cb0name → factory resolutionCONFIRMED
factory _M_invoke (sb/psum/dram/reg/…)see matrixconstruct parameterized ColoringAllocatorCONFIRMED

Gaps. The per-space colorer bodies (live_range / build / find_costs / simplify / select / insert_spill_code internals) are out of scope here — named and addressed, not transcribed. The geometric select search and the heuristic score formula are the colorer's internals. The LinearizedFunction / LinearizedBlock struct layouts are derived from flattern_loop, not recovered by IDA (STRONG, not CONFIRMED). The opt7 SMT-allocation interleaving (sub_80D9D0) confirms the allocator set but not the full step order. The fourth ctor argument to_space's role is INFERRED.


NameRelationship
SB_Allocator / PSUM_Allocator / DRAM_Allocator / REG_Allocatorthe per-space Chaitin–Briggs colorers this driver runs (separate pages, planned)
LinearScanAllocatorthe alternative family wired by --allocator lsa / optlevel 0/8
dma_optimization_<space> / address_rotation_<space>the per-space re-tuning passes interleaved into the cascade
non_ssa_legalizationre-legalizes SSA-clones to fixed SB/PSUM slots after PSUM+SB coloring

Cross-References