Backend Polyhedral Dependence-Distance (libwalrus)
All symbols and addresses on this page apply to
neuronx_cc2.24.5133.0+58f8de22. Driver bodies (neuronxcc::backend::LoopOptimization) live inneuronxcc/starfish/lib/libwalrus.so; the distance primitive (bir::getDefUseDistance) lives inneuronxcc/starfish/lib/libBIR.so. Both retain a full C++ symbol table (nm -DC), so every body named here is a demangled symbol, not asub_*. cp310 addresses are primary; cp311/cp312 differ by a small fixed delta (table in §1).Provenance:
libwalrus.soandlibBIR.soare present in the corpus — as per-symbol decompiled/disasm sidecars and as full IDA databases underida/— so the cited bodies (e.g.getDefUseDistance @ libBIR 0x20d4e0) are disassembled, not merely declared. The addresses are CONFIRMED. The same provenance holds on Symbolic-AP Register-ALU, DGE Level Selection, the Dynamic For-Loop, and Dynamic-Shape Synthesis.
Abstract
The backend loop optimizer in libwalrus.so ships a function named constructPolyhedralDependenceGraphWithDistance. The name is a decoy. There is no isl anywhere in this gate — not in the driver, not in the per-loop builder, not in the distance kernel. The word "polyhedral" is nominal. The actual machine is custom Neuron C++ built over boost::icl::interval_set (exact byte-address intervals), the Boost Graph Library (boost::adjacency_list), and LLVM ADT (MapVector, DenseMap, SmallVector). The real isl-based polyhedral path is a separate analysis living in the Penguin/affine bridge — see 5.16 ISL Dependence-Graph for the genuine article. This page documents the impostor, and documents it as exactly that.
The gate answers one question for the loop fusion / interchange legality checker: if I merge these two loops, does any read end up consuming a value that its producer writes at a later relative iteration? It answers it not by subtracting symbolic base offsets — the cheap thing — but by tiling each access footprint into iteration groups, materializing the exact byte-address intervals each group touches as a boost::icl::interval_set, and testing pairwise interval intersection over the iteration cross-product. The returned "distance" is the largest def-iteration index whose footprint is still live at some use iteration. A strictly positive value flags an illegal backward loop-carried dependence. That mechanism — verified instruction-by-instruction against the real getDefUseDistance body at libBIR 0x20d4e0 — is the heart of the page.
Three functions form the gate. constructPolyhedralDependenceGraphWithDistance(Module&) is a two-pass module driver that populates a per-loop SAP×SAP distance cache. constructLoopDependenceGraph(InstLoop*) builds, per loop, that distance cache plus a debug dependence-direction-vector graph. bir::getDefUseDistance(SAP, SAP, int) is the interval-overlap kernel both rely on, also consumed directly by the fusion-legality predicate has_negative_distance(InstLoop*, InstLoop*). This page carries four in-place corrections to D-K14, which named these bodies but never disassembled them.
Reimplementation contract
To rebuild this gate you must reproduce:
- The two-pass driver — pass 1 buckets instructions by loop nesting level; pass 2 visits every
InstLoop(opcode105) and builds its dependence graph, keeping only the side-effect distance cache atInstLoop+0x210. - The dependence-existence gate — two SAPs are a candidate dependence iff they touch the same
MemoryLocationSet(pointer-equal after a-280normalization), or, in the per-loop builder, the same tensor name (memcmpof the name bytes). - The distance kernel — evaluate both access patterns, tile into
nitersiteration groups, build per-groupboost::icl::interval_set<u64, right_open_interval>, and returnmax{ i : ∃ j, intersects(def[i], use[j]) }. - The
nitersformula —niters = gcd(tripcount(L1), tripcount(L2)) × Π tripcount(shared parent loops). - The sign rule —
distance > 0 ⇒ illegal(thehas_negative_distancereject), distance≤ 0 ⇒ legal. - The def/use polarity — DEF = output-arg SAP (
ArgumentKind == 2), USE = input-arg SAP (ArgumentKind == 1).
| Module driver | LoopOptimization::constructPolyhedralDependenceGraphWithDistance(Module&) @ libwalrus 0xbae000 |
| Per-loop builder | LoopOptimization::constructLoopDependenceGraph(InstLoop*)→DDGTy @ libwalrus 0xbabe50 |
| Distance kernel | bir::getDefUseDistance(SAP const&, SAP const&, int) @ libBIR 0x20d4e0 |
| Fusion-legality gate | LoopOptimization::has_negative_distance(InstLoop*, InstLoop*) @ libwalrus 0xb8f100 |
| Distance cache | InstLoop + 0x210 (528) = MapVector<SAP*, MapVector<SAP*, int>> |
| DDG type | std::multimap<std::pair<Instruction const*, Instruction const*>, std::vector<int>> |
| Footprint substrate | boost::icl::interval_set<unsigned long, right_open_interval> — zero isl |
__FILE__ anchors | neuronxcc/walrus/ir/lib/IR/AccessPattern.cpp:0x554; …/loop_optimization/src/loop_profitable_fusion.cpp |
GOTCHA — the name lies.
constructPolyhedralDependenceGraphWithDistancecontains zeroisl_*calls. So doesgetDefUseDistance(verified:rg -c isl_over the0x20d4e0body returns 0). If you are looking for the schedule-tree legality machinery, it is not here — it is in 5.16. This gate is "polyhedral" in name only; it is exact byte-interval overlap over an unrolled iteration space.
1. Symbol Map
All in class neuronxcc::backend::LoopOptimization (libwalrus) unless noted. Addresses are nm -DC-confirmed across three ABIs.
| Symbol | cp310 | cp311 | cp312 | Conf |
|---|---|---|---|---|
constructPolyhedralDependenceGraphWithDistance(Module&) | 0xbae000 | 0xbadfd0 | 0xbadf80 | CONFIRMED |
constructLoopDependenceGraph(InstLoop*)→DDGTy | 0xbabe50 | 0xbabe20 | 0xbabdd0 | CONFIRMED |
has_negative_distance(InstLoop*, InstLoop*) | 0xb8f100 | 0xb8f0d0 | 0xb8f080 | CONFIRMED |
viewDependenceGraph(multimap&, string&) | 0xbab900 | — | — | CONFIRMED |
collectInstructionsByNestingLevel(BasicBlock&) | 0x5f3c20† | — | — | CONFIRMED |
collect_def_in_loop(InstLoop*, vector<SAP*>&) [free fn] | 0xb8e740 | — | — | CONFIRMED |
collect_use_in_loop(InstLoop*, vector<SAP*>&) [free fn] | 0xb8e8a0 | — | — | CONFIRMED |
collect_illegal_common_ap(InstLoop*, InstLoop*) | 0xb8ea70 | — | — | CONFIRMED |
bir::getDefUseDistance(SAP const&, SAP const&, int) | libBIR 0x20d4e0 | — | — | CONFIRMED |
bir::doAccessesOverlap(SAP const&, SAP const&, int) | libBIR 0x20f0b0 | — | — | CONFIRMED (sibling; not called by the three targets) |
bir::intersecting(APINFO const&, APINFO const&) | libBIR 0x20c840 | — | — | CONFIRMED (per-AP footprint test) |
bir::getAddressIntervals(…) | libBIR 0x17b570 / 0x20a380 | — | — | CONFIRMED (returns into boost::icl) |
†
collectInstructionsByNestingLevelis exported in the per-symbol decompiled set at the libwalrus-internal VA0x5f3c20(asLoopOptimization::collectInstructionsByNestingLevel(bir::BasicBlock&); a siblingLoopOptDoctor::overload sits at0x5ef870). Thenm -Ddriver-VA for the same body is reported as0xba2ab0; both refer to the same function.
NOTE — two image bases. The per-symbol
decompiled/…libwalrus.so__*.ccorpus is a thunk-and-internal-VA view (e.g.collect_use_in_loopappears as a tail-call thunk at0x5f1210). The0xbae000-range addresses in this table are thenm -DCbody addresses oflibwalrus.soproper. Where a body is reachable in both views the symbol name is identical; only the offset frame differs.
Recovered types
// The graph the per-loop builder emits (CONFIRMED from viewDependenceGraph's
// demangled signature + the _M_emplace_equal call):
using DDGTy = std::multimap< std::pair<bir::Instruction const*, bir::Instruction const*>, // key = (defInst, useInst)
std::vector<int> >; // val = DIRECTION VECTOR {-1,0,+1}
// The persistent per-loop distance cache (CONFIRMED: lea r12,[rax+210h] @0xbac093):
using PerLoopDistMap = // dist[def][use] = int (stored symmetrically)
llvm::MapVector< bir::SymbolicAccessPattern*,
llvm::MapVector< bir::SymbolicAccessPattern*, int > >; // cached @ InstLoop + 0x210 (528)
// The SCRATCH build graph (destroyed before constructLoopDependenceGraph returns):
using Graph = boost::adjacency_list< boost::vecS, boost::listS, boost::directedS,
boost::property< neuronxcc::backend::instruction_name_t, bir::Instruction* >, // vertex bundle
neuronxcc::backend::DependenceEdgeT, // edge bundle (40 bytes)
boost::no_property, boost::listS >;
Struct offsets (CONFIRMED via disasm/decompile)
| Offset | Field | Use |
|---|---|---|
SAP + 0x38 (56) | owning StorageBase* | dependence-existence gate root |
SAP + 0x20 (32) | owning bir::Instruction* | getLoopnest / graph vertex |
StorageBase + 0x110 (272) | type tag (== 1 ⇒ MemoryLocationSet) | the K14 +0x110 gate |
StorageBase + 0x188 (392) | MemoryLocationSet* (normalise −280) | identity key in the kernel |
StorageBase + 0x128/0x130 (296/304) | tensor-name char* / length | memcmp identity in the per-loop builder |
InstLoop + 0x210 (528) | PerLoopDistMap | the persistent distance cache |
Instruction + 0x50 (80) | opcode (105 == InstLoop) | the loop-detection tag |
Instruction + 0x98/0xA0 (152/160) | input-arg ilist head | collect_use (ArgumentKind == 1) |
Instruction + 0xB8/0xC0 (184/192) | output-arg ilist head | collect_def (ArgumentKind == 2) |
2. The driver — constructPolyhedralDependenceGraphWithDistance
@ 0xbae000. Signature (LoopOptimization* this, bir::Module& M). The module is an ilist of BasicBlock; M[1] is the first block, &M itself is the sentinel. Two passes.
// PASS 1 — build the nesting-level index.
void constructPolyhedralDependenceGraphWithDistance(LoopOptimization* this, Module& M) {
for (BasicBlock& BB : M) // ilist walk
collectInstructionsByNestingLevel(this, BB); // @0xbae05f call
// buckets instructions by loop depth into
// the per-level worklists the rest of LoopOptimization consumes
// PASS 2 — per-loop dependence distance.
for (BasicBlock& BB : M)
for (Instruction& k : BB)
if (*(uint*)(&k + 0x50) == 0x69) { // opcode == 105 == InstLoop
// disasm @0xbae10e: cmp dword [r15+50h], 69h
InstLoop* L = (InstLoop*)((char*)&k - 0x60); // InstLoop sub-object = inst − 0x60
DDGTy ddg = constructLoopDependenceGraph(this, L); // @0xbae120
// DISCARD ddg: walk its boost-adjacency edge nodes (48-byte nodes,
// node freed with operator delete[](node, 0x48)). The multimap RESULT is
// thrown away here. The PERSISTENT output is the side effect inside
// constructLoopDependenceGraph: PerLoopDistMap @ L + 0x210.
}
}
The driver's job is to populate every InstLoop's +0x210 SAP×SAP distance map (and emit the per-loop viewDependenceGraph debug dump). The returned per-loop multimap is consumed only for that debug view; it is not retained module-wide.
GROUNDING (disasm 0xbae000):
0xbae05f call collectInstructionsByNestingLevel ; pass 1
0xbae10e cmp dword ptr [r15+50h], 69h ; 'i' ; opcode == 105 == InstLoop
0xbae113 jnz <skip non-loops>
0xbae119 lea rdx, [r15-60h] ; InstLoop* arg
0xbae120 call constructLoopDependenceGraph
0xbae12f.. the ddg-node free loop (delete[] 0x48 nodes)
Opcode 105 (0x69) at inst+0x50 is the same InstLoop tag the collect_def/use_in_loop recursors test (cmp [v+0x50], 69h) — consistent across all four functions in the gate.
3. The per-loop builder — constructLoopDependenceGraph
@ 0xbabe50. Returns DDGTy. Asserts bake in the source path …/loop_optimization/src/loop_profitable_fusion.cpp. This is the loop-level heart.
Step A — collect access patterns
DDGTy constructLoopDependenceGraph(LoopOptimization* this, InstLoop* L) {
std::vector<SAP*> defs, uses;
collect_loop_outputs(L, &defs); // the loop's WRITE/output SAPs (local analogue of collect_def_in_loop)
collect_loop_inputs (L, &uses); // the loop's READ/input SAPs (local analogue of collect_use_in_loop)
Graph graph; // boost::adjacency_list, SCRATCH
DDGTy& ddg = *(DDGTy*)this; // the returned multimap is constructed in-place in `this`
Step B — def × use pairing, gated on same tensor
for (SAP* d : defs)
for (SAP* u : uses) {
StorageBase* sbD = *(StorageBase**)((char*)d + 0x38);
StorageBase* sbU = *(StorageBase**)((char*)u + 0x38);
assert(*(int*)((char*)sbD + 0x110) == 1); // isa<MemoryLocationSet> (disasm @0xbabfd1/@0xbabff4: cmp [rax+110h],1)
assert(*(int*)((char*)sbU + 0x110) == 1);
char* ptrD = *(char**)((char*)sbD + 0x128); size_t lenD = *(size_t*)((char*)sbD + 0x130); // tensor NAME @0xbac001/@0xbac00f
char* ptrU = *(char**)((char*)sbU + 0x128); size_t lenU = *(size_t*)((char*)sbU + 0x130); // @0xbac008/@0xbac016
if (lenD == lenU && (lenD == 0 || memcmp(ptrU, ptrD, lenD) == 0)) { // SAME tensor
int dist = bir::getDefUseDistance(d, u, niters); // ⭐ the distance (see §4)
// store symmetrically into the per-loop SAP×SAP cache @ L + 0x210:
PerLoopDistMap& m = *(PerLoopDistMap*)((char*)L + 0x210); // disasm @0xbac093: lea r12,[rax+210h]
m[d][u] = dist;
m[u][d] = dist;
// add a boost-graph edge d.inst → u.inst (stored_edge_property<void*,DependenceEdgeT>,
// 40-byte edge node, list-hooked). The owning Instruction* of a SAP = *(Instruction**)(SAP + 0x20).
...
}
}
NOTE — the per-loop
nitersis the same divisor. Inside this body the third arg togetDefUseDistanceis the loop-local iteration count derived fromd's evaluated predicate count. The Hex-Rays prototype mistypes it as aSAP*(v9 = (int)uses[1]is the predicate count, not a pointer). The authoritativenitersderivation appears unambiguously inhas_negative_distance(§5):gcd(tripcounts) × Π parent tripcounts. [STRONG]
Step C — the dependence direction vector
For each matched pair the builder computes a classic dependence direction vector, one element per shared loop level:
auto evalD = SAP::getEvaluatedAps(d); auto loopD = Instruction::getLoopnest(d->inst);
auto evalU = SAP::getEvaluatedAps(u); auto loopU = Instruction::getLoopnest(u->inst);
std::vector<int> dirVec;
// walk loopD / loopU axis lists in lock-step; for each common axis map it to its
// per-iteration index via two DenseMaps (DenseMap<pelican::AffineIdx*,long> and
// DenseMap<bir::LoopAxis*,unsigned>), yielding iD (def axis idx) and iU (use axis idx):
for (axis a : common_axes) {
int delta = iD - iU; // disasm: *(v129+8) − *(v130+2)
if (delta > 0) dirVec.push_back(+1); // def AFTER use ('>')
else if (delta < 0) dirVec.push_back(-1); // def BEFORE use ('<')
else dirVec.push_back( 0); // same iteration ('=')
}
The canonical {<, =, >} is encoded as {-1, 0, +1}. The debug banner literally prints ", distance vector = [" … "]" (string CONFIRMED in .rodata).
Step D — emplace into the DDG multimap
auto key = std::make_pair((const Instruction*)d->inst, (const Instruction*)u->inst);
// a std::set<std::vector<int>> per key DEDUPES identical direction vectors (find → _M_insert_unique),
// then:
ddg._M_emplace_equal(key, dirVec); // multimap insert, loop_profitable_fusion.cpp:0x423/0x424
Step E — debug dump + teardown
viewDependenceGraph(this, ddg, "<loop-tag>"); // @0xbab900 — pretty-prints the multimap
// destroy defs/uses and the scratch boost::adjacency_list `graph`
return ddg; // constructed in-place in `this`; the boost graph is local scratch, destroyed first
}
QUIRK — the graph the function is named for is thrown away. Two structures are built: a transient
boost::adjacency_list(scratch, destroyed before return) and theDDGTymultimap (returned, then discarded by the driver in §2). Neither survives. The only persistent product is the integerPerLoopDistMapcache atInstLoop+0x210plus the human-readable debug dump. The "dependence graph" is, in production flow, debug scaffolding.
Debug strings present exactly once each in libwalrus.so .rodata: "(DEBUG) def = ", "(DEBUG) use = ", "(DEBUG) def -> use: …, dependent = …", "(DEBUG) def [offset, tensor_id] = …", "(DEBUG) use [offset, tensor_id] = …", ", distance vector = [". The dependent= boolean is bir::intersecting(APINFO, APINFO) (libBIR 0x20c840) — the per-pair footprint-intersect test; the direction vector is only meaningful when the footprints actually intersect.
4. The distance kernel — bir::getDefUseDistance
@ libBIR 0x20d4e0. Returns int = "max iteration distance" (always ≥ 0). This is not a base-offset subtraction. It is exact byte-interval overlap over the unrolled iteration space. Every claim in this section is verified directly against the decompiled 0x20d4e0 body.
Preflight — same memory location or bail
int getDefUseDistance(const SymbolicAccessPattern& def, const SymbolicAccessPattern& use, int niters) {
StorageBase* sbDef = *((StorageBase**)&def + 7); // def + 0x38
if (*(int*)((char*)sbDef + 272) != 1) assert_fail(); // +0x110 == 1 ⇒ isa<MemoryLocationSet> (decompile :260)
void* mlsDef = *(void**)((char*)sbDef + 392); // +0x188
if (mlsDef) mlsDef = (char*)mlsDef - 280; // normalise base (:262-264)
StorageBase* sbUse = *((StorageBase**)&use + 7);
if (*(int*)((char*)sbUse + 272) != 1) assert_fail();
void* mlsUse = *(void**)((char*)sbUse + 392);
int maxDist = 0; // v183 = 0 (:270)
if (mlsUse) mlsUse = (char*)mlsUse - 280;
if (mlsUse != mlsDef) return maxDist; // different MemoryLocationSet ⇒ NO dependence ⇒ 0 (:273)
CORRECTION (C-existence) — the existence gate is pointer equality, not name
memcmp, here. The per-loop builder (§3) gates by tensor-namememcmp; the kernel gates byMemoryLocationSetpointer equality after the−280normalization (if (mlsUse != mlsDef) return 0, decompile line 273). The two are equivalent for the common case but are different code paths — the builder picks pairs by name, the kernel re-verifies by location and short-circuits to distance 0 on mismatch.
Evaluate and tile
resetEvaluatedAps(&def); resetEvaluatedAps(&use); // :275-276
auto evDef = getEvaluatedAps(&def); auto evUse = getEvaluatedAps(&use);
auto predDef = getEvaluatedPredicates(&def); // :279
auto predUse = getEvaluatedPredicates(&use); // :280
int niters1 = #predicates(predDef) * #aps(def); // total def iterations in evaluated space (v11, :283)
int niters2 = #predicates(predUse) * #aps(use); // total use iterations (v14, :287)
assert(niters1 / niters != 0 && niters2 / niters != 0); // AccessPattern.cpp:0x554 (:292-296)
// groupsDef = niters1 / niters ; groupsUse = niters2 / niters
The __assert_fail message and path are recovered verbatim: "niters1 / niters != 0 && niters2 / niters != 0", "…/neuronxcc/walrus/ir/lib/IR/AccessPattern.cpp", line 0x554.
Build per-iteration byte intervals
// For each evaluated AP, for each active predicate bit (the predicate bitset gates live iterations):
// dtype = *(ap + 4) (≤ 0x13, else "Unknown dtype")
// bytes = elementSize[dtype] * extent
// getAddressIntervals(bytes, addrSet, dtype, scratch) // → byte ranges this iter touches
// iterKey = elemIdx % niters // bin into one of `niters` groups
// defGroups[iterKey] |= interval; // boost::icl::interval_set<u64, right_open_interval>, segmental join
// (same loop again for use → useGroups)
std::unordered_map<int, icl::interval_set<u64,right_open_interval>> defGroups, useGroups; // CONFIRMED type @:1249
getAddressIntervals demangles with boost::icl in its very return type (…RN5boost3icl12…), and returns the interval_set. The interval merge is boost::icl segmental join_left/join_right.
Overlap → distance
int v183 = 0;
for (int gj = 0; gj < niters; ++gj) // outer: use iter-group (v193)
for (int gi = 0; gi < niters; ++gi) { // inner: def iter-group (v190)
bool hit = boost::icl::intersects(defGroups[gi], useGroups[gj]); // CONFIRMED @:1255
if (hit) { int cand = gi; if (cand > v183) v183 = cand; } // running max (:1270-1272)
}
return v183; // :1364
}
The reduction is exactly the decompiled if ((int)v183 >= (int)v194) v76 = v183; v183 = v76; running maximum, with both loop bounds v201 == niters. getDefUseDistance returns the largest def-iteration index whose byte footprint is still touched by some use iteration = the longest live def→use span measured in iterations.
CORRECTION (C1 of D-K14) — not a base-offset subtraction. K14 read the distance as
sub (%r15),%rax ; sub (%rsi),%raxof evaluated base offsets. The disassembly disagrees: the base-offset subtraction is only the cheap pre-step insidegetAddressIntervalsthat positions each footprint; the returned distance is theboost::icl::intersectsmax-iteration result over the binned interval sets. Verified: the0x20d4e0body containsboost::icl::intersects, anunordered_map<int, interval_set<…right_open_interval…>>, and thev183max reduction — and zeroisl_*references.
GOTCHA —
nitersis a divisor, not a count.nitersis the iteration-group size; the kernel divides the total evaluated-iteration counts (niters1,niters2) by it to get the number of groups, and the twoniters1/niters != 0asserts fire if a caller passes aniterslarger than either footprint. This is why thehas_negative_distancenitersformula (§5) must be the shared iteration granularity of the fused loops, never a raw trip count.
5. The fusion-legality gate — has_negative_distance
@ 0xb8f100. Returns 1 iff fusing L1 and L2 would introduce a backward (negative) loop-carried dependence; 0 otherwise. This is the predicate behind the "[STOP FUSION] negative distance dep in def-use" diagnostic.
Step A — collect
int has_negative_distance(InstLoop* L1, InstLoop* L2) {
std::vector<SAP*> uses, defs;
collect_use_in_loop(L2, &uses); // L2 input/read SAPs (ArgumentKind == 1)
collect_def_in_loop(L1, &defs); // L1 output/write SAPs (ArgumentKind == 2)
Step B — co-located pairing
std::vector<std::pair<SAP*,SAP*>> pairs;
for (SAP* u : uses)
for (SAP* d : defs) {
void* mlsU = *(void**)((char*)*(void**)((char*)u+0x38) + 0x188); if (mlsU) mlsU = (char*)mlsU - 280;
void* mlsD = *(void**)((char*)*(void**)((char*)d+0x38) + 0x188); if (mlsD) mlsD = (char*)mlsD - 280;
if (mlsU == mlsD) pairs.push_back({d, u}); // same MemoryLocationSet ⇒ candidate dep
}
// def is pair.first, use is pair.second (disasm @0xb8f34d-0xb8f357: rdi=[rbx]=def, rsi=[rbx+8]=use, edx=niters)
Step C — the authoritative niters formula
int tc1 = L2->getTripCount(); // call [vtbl+0x140] @0xb8f298
int tc2 = L1->getTripCount(); // call [vtbl+0x140] @0xb8f281
int niters = gcd(tc1, tc2); // Euclidean: idiv r12 @0xb8f2b5
InstLoop* p1 = getParentLoop(L1); // @0xb8f2c8
InstLoop* p2 = getParentLoop(L2); // @0xb8f2d4
while (p1 && p2 && p1 == p2) { // multiply by every COMMON enclosing parent loop
niters *= p1->getTripCount(); // [vtbl+0x140] @0xb8f2f7
p1 = getParentLoop(p1); // @0xb8f304
p2 = getParentLoop(p2); // @0xb8f30f
}
// niters = gcd(tripcount L1, tripcount L2) × Π tripcount(shared parent loops)
This is the divisor whose niters1/niters, niters2/niters non-zero asserts fire inside the kernel — the granularity of the common iteration space the two loops would share after fusion.
CORRECTION (C2 of D-K14) — the
intthird arg is the iteration-group divisor, not a partition-axis index. K14 inferred the third parameter ofgetDefUseDistancewas a "partition axis index." The dataflow refutes it: threegetTripCountcalls ([vtbl+0x140]), a GCDidiv, and fourgetParentLoopcalls produce a single integer that is then passed as that arg. It isgcd × Π parent-trips, full stop.
Step D — the sign test
for (auto& [d, u] : pairs) {
int dist = bir::getDefUseDistance(d, u, niters); // @0xb8f35a
assert(*(int*)((char*)*(void**)((char*)u+0x38) + 0x110) == 1); // use MLSet gate
if (dist > 0) { // CONFIRMED: test r14d,r14d; jle skip @0xb8f388
LOG("ZZZZZZZZZZZZmax iteration distance <tensor>@<...> : use -> def : %d "
"(iteration groups:%d)\n", dist, niters);
return 1; // NEGATIVE distance ⇒ illegal
}
}
return 0;
}
CORRECTION (C3 of D-K14) — the sign is
dist > 0 ⇒ negative, inverted from K14's prose. K14 wrote "if distance ≤ 0 it flags negative." The disassembly is the opposite:test r14d,r14d ; jle <continue>— a non-positive distance is skipped (legal); a strictly positive distance triggers the reject. Read against the kernel (max def-iteration index still live at a use iteration), a positive value on a(def∈L1, use∈L2)pair meansL2's read consumes a valueL1writes at a later relative iteration. After fusingL1+L2into one body, that read would precede its producing write within the same iteration carry → a backward (negative) loop-carried dependence. Hence the banner says"max iteration distance … use -> def"and the function ishas_NEGATIVE_distance: forward in pre-fusion order ⇒ negative after fusion.
CORRECTION (C4 of D-K14) — a per-level direction vector is materialized after all. K14 §5.2 said interchange uses "a per-pair direction test, not a materialized direction matrix." Refined:
constructLoopDependenceGraph(§3C) does materialize a per-pair direction vector (std::vector<int>of{-1,0,+1}) into theDDGTymultimap. The legality decision, however, still reduces to the scalarhas_negative_distance > 0test; the vector is built and dumped but its only observed consumer is the debug projection.
6. collect_def_in_loop / collect_use_in_loop
@ 0xb8e740 / 0xb8e8a0. Free functions. Both recurse into nested loops and harvest access-pattern args from every non-loop instruction.
void collect_def_in_loop(InstLoop* L, std::vector<SAP*>& out) {
for (Instruction& inst : body(L)) {
if (*(uint*)((char*)&inst + 0x50) == 0x69) { // opcode == 105 == InstLoop
collect_def_in_loop((InstLoop*)((char*)&inst - 8), out); // recurse
continue;
}
for (Arg& a : output_arg_ilist(inst /* +0xB8..+0xC0 */)) // OUTPUT args
if (*(int*)((char*)&a + 0x10) == 2) // ArgumentKind == 2 ⇒ write
out.push_back((SAP*)((char*)&a - 8)); // cast<SymbolicAccessPattern>
}
}
// collect_use_in_loop is identical but walks the INPUT-arg ilist (inst +0x98..+0xA0)
// and selects ArgumentKind == 1 (reads).
DEF = output/written SAP, USE = input/read SAP — the standard def-use polarity. [STRONG: ilist heads CONFIRMED; the
ArgumentKindenum values 1/2 are read from the decompiler + ilist offsets and are consistent, but the enum name is inferred.] Thecollect_use_in_loopsymbol is independently CONFIRMED in the corpus with signature(bir::InstLoop*, std::vector<bir::SymbolicAccessPattern*>&).
7. Consumers and place in the loop optimizer
has_negative_distance (0xb8f100) is called from these named bodies (disasm xref):
| Caller | Address | Role |
|---|---|---|
check_loop_fusion(BasicBlock&, int) | 0xb95a70 | non-greedy fusion legality |
check_loop_fusion_fast(BasicBlock&) | 0xb92d60 | fast-path fusion legality |
checkLoopFusionGreedy(BasicBlock&, int) | 0xba9210 | greedy fusion legality (K14 §4 driver) |
check_loop_interchange(…) | 0xb93430 | interchange legality |
The dependence-distance machinery is the legality gate for loop fusion and loop interchange inside LoopOptimization. Flow: run() → per-BB checkLoopFusionGreedy / check_loop_fusion → for each candidate loop pair (within the positional distanceThreshold = 8 window) → has_negative_distance(L1, L2) → if true, emit "[STOP FUSION] negative distance dep in def-use" and reject; else fusion/interchange may proceed.
The per-loop PerLoopDistMap (InstLoop+0x210) built by constructLoopDependenceGraph / the polyhedral driver is the cached SAP×SAP integer distance table the same fusion-profit/legality code consults, alongside collect_illegal_common_ap (0xb8ea70). The DDG multimap + direction vectors are the human-readable projection (viewDependenceGraph) and the structured form a reorder check would read (direction vector all-= or leading-< ⇒ legal).
Relation to the real isl path
There is no structural relation to the genuine isl machinery. The Penguin loop transforms validate against this access-pattern dependence graph (byte-interval overlap over iteration groups), not the isl schedule checker. The stock islpy / TongaIsl analysis (5.16, isl-schedule-tree-legality) is a separate machine in the Penguin/affine bridge and is invoked by none of the three functions on this page. Confirmed by the absence of any isl_* reference in getDefUseDistance @ 0x20d4e0.
Downstream live ranges. The dependence distance also bounds def→use live ranges: a use that reads
kiterations after its def needsklive buffer copies. The symbolic-AP buffer materialization / software-pipeline-depth sizing consumes the same integerkthis gate produces. [cross-ref, STRONG]
8. Confidence, gaps, and adversarial self-verify
The five strongest claims, re-challenged against the binary:
- "Zero isl — boost::icl, name-only polyhedral."
rg -c isl_over the decompiledgetDefUseDistance @ 0x20d4e0body → 0. The same body contains 72icltokens, 24interval_set, and oneboost::icl::intersects. CONFIRMED. getDefUseDistanceis interval-overlap, not offset subtraction. Decompile line 1255 isboost::icl::intersects(defGroups[gi], useGroups[gj]); lines 1270–1272 are thev183running max; both loops bounded byv201 == niters. CONFIRMED — supersedes K14's offset-subtraction reading.- Existence gate
+0x110 == 1and+0x188 − 280location identity. Decompile lines 260/266 (+272 == 1⇒isa<MemoryLocationSet>), 262–272 (+392,−280), 273 (if (mlsUse != mlsDef) return 0). CONFIRMED. niters= GCD × parent trips.has_negative_distanceshows threegetTripCount([vtbl+0x140]), GCDidiv, fourgetParentLoop. CONFIRMED — supersedes K14's "partition-axis index."- Sign:
dist > 0 ⇒ reject.test r14d,r14d ; jle <continue>@0xb8f388. CONFIRMED — supersedes K14's inverted prose.
STRONG (not byte-pinned): the ArgumentKind ∈ {1,2} enum name; the per-loop niters equals the kernel divisor (Hex-Rays mistypes it, but the predicate-count dataflow + shared contract make it strong); DependenceEdgeT node size = 40 bytes (tc_new(40)).
INFERRED / GAPS: exact DependenceEdgeT member layout (only the 40-byte size and that it stores the target Instruction* are recovered); whether a multi-level mixed-sign direction vector (e.g. [<,>]) is ever consumed for a reorder decision beyond the scalar has_negative_distance > 0 reject — only the scalar consumption is observed; the getAddressIntervals dtype byte-size table (Dtype.h, ≤ 0x13 dtypes) is used but not enumerated here.
Corroborates D-K14 unchanged: the +0x110 == 1 MemoryLocationSet gate on both SAP ends; the "[STOP FUSION]" reject + fusion-driver call graph; the per-loop SAP×SAP integer distance map.