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

Indirect-Gather Descriptors — INDIRECT16B / INDIRECT20B / MXINDIRECT16B

All symbols and addresses on this page apply to neuronx_cc 2.24.5133.0+58f8de22 (cp310 wheel; cp310/11/12 share the C++ core, but libwalrus.so is rebuilt per wheel — re-confirm any raw offset against cp311/cp312). The three encoders, their static siblings, and the wire-validators live in libwalrus.so (build-id 92b4d331…, .text/.rodata base 0x62d660/0x1c72000, VA == file offset); the bir::AccessPattern indirect accessors live in libBIR.so (build-id a9b1ea38…). See Build & Version Provenance.

Abstract

An indirect-gather descriptor is a MEM_PATTERN slot whose addressing is supplied at runtime by an index vector rather than by static strides. It is how the Tonga backend encodes a gather/scatter: instead of out[p][i] = data[p][stride·i], the descriptor names an index operand and a data operand, and the hardware reads out[p][i] = data[p][index[p][i]]. Three on-wire forms exist — INDIRECT16B, INDIRECT20B, and MXINDIRECT16B — and they are not a separate struct family: each one reuses the byte width of a plain static pattern (TENSOR3D = 16 B, TENSOR4D = 20 B, MXMEM_PATTERN1D = 16 B) and overwrites its leading fields with gather operands. The descriptor is built atop ADDR4 (2.2): the gather is signalled by setting the bit-29 indirect marker (0x20) on byte 3 of the index ADDR4 word — the exact mode-nibble value (0b01) whose resolution ADDR4 §"the mode nibble" already pins.

The three forms differ on two orthogonal axes. WidthINDIRECT16B (3-D AP) vs. INDIRECT20B (4-D AP): the indirect payload (index@+0, data@+4, num@+8) is byte-identical; the only difference is the slot width of the static sibling the same encoder tail-calls on its non-indirect branch. MX-nessMXINDIRECT16B is reached only when an MX operand (an fp8/fp4-x4 tensor carrying an E8M0 block-scale stream) is itself gathered; it inserts a third ADDR4 (the scale word) in front of the data, pushing every later field by 4. There is no MXINDIRECT20B in 2.24 — the MX gather is 1-D only.

The bar for this page is that a reader can encode any of the three indirect descriptors by hand — placing the ADDR4 words, stamping the bit-29 marker, writing the gather count — and knows which BIR op and which kernel consumes each variant. Everything is recovered from the libwalrus.so encoder/decoder bodies (the dynamic symbol table survives, so nm -DC demangles them); the byte offsets are pinned against the literal mov/or/shl constants in the disassembly, not inferred. The reimplementation contract is:

  • The byte layout of all three slots — every ADDR4 offset, the count field, the MX scale-partition control byte, and which bytes are inert.
  • The encoder fork — every assignAccess* function handles both the static and the indirect case off one isTensorIndirectDynamicAP() test; the indirect branch is a 3-field record, not a static-pattern fill.
  • The bit-29 marker as the encoder↔decoder contract, and the per-descriptor quadrant invariant the decoder enforces.
  • The op→kernel bindings — which BIR op emits each descriptor, and which gather/scatter kernel consumes it.

At a glance

Non-MX 3-D encoderCoreV4GenImpl::assignAccess3D (= assignIndirectPattern<INDIRECT16B>) 0x150ccf0
Non-MX 4-D encoderCoreV4GenImpl::assignAccess4D (= assignIndirectPattern<INDIRECT20B>) 0x150d8e0
MX encoderCoreV4GenImpl::assignIndirectPatternForMX<MXINDIRECT16B> 0x150de90
MX routing leafCoreV4GenImpl::assignAccessForMX<MXMEM_PATTERN1D> 0x150e2f0
CoreV2 twinsCoreV2GenImpl::assignAccess3D 0x1176de0 / assignAccess4D 0x1177620
MX validatorcore_v4::mxmem1d_valid 0x1447190
3-D quadrant validatorcore_v4::indirect_quadrant_check_src3d 0x1448450
4-D quadrant validatorcore_v4::indirect_quadrant_check_src4d 0x14484a0
DMA-indirect validatorcore_v3::has_dma_indirect_valid_idx_start_addr 0x13707c0
Gather count accessorbir::*AccessPattern::getNumIndirectIndices (vtbl +0x90; base 0x20fed0)
Slot widthsINDIRECT16B 16 B · INDIRECT20B 20 B · MXINDIRECT16B 16 B
Indirect markeror byte [INDEX_ADDR4+3], 0x20 — bit 29, mode nibble 0b01 (per ADDR4 2.2)

NOTE — the descriptor name encodes the slot width, not a distinct struct. INDIRECT16B/20B/MXINDIRECT16B are template instantiations of assignIndirectPattern<…> keyed on the static pattern they share a slot with (TENSOR3D/TENSOR4D/MXMEM_PATTERN1D). On the wire an indirect record is narrower than its named width — it writes only 3 (non-MX) or 4 (MX) fields and leaves the rest of the slot inert. The width comes from the AP's free-dim count, which fixes which slot the record occupies.

The byte map — three slots, one indirect payload

 INDIRECT16B  (16 B; non-MX 3-D)      MXINDIRECT16B (16 B; MX 1-D)
 +0x00  ADDR4  INDEX   (bit29 set)    +0x00  ADDR4  INDEX   (bit29 set)
 +0x04  ADDR4  DATA                   +0x04  ADDR4  DATA
 +0x08  u16    NUM                    +0x08  ADDR4  SCALE   (a4=1, MX scale)
 +0x0A..0x0F   inert                  +0x0C  u16    x4-NUM
 (= TENSOR3D static slot, unfilled)   +0x0E  -       (count high half, spare)
                                      +0x0F  u8      SCALE base-partition % PE_count

 INDIRECT20B  (20 B; non-MX 4-D)
 +0x00  ADDR4  INDEX   (bit29 set)
 +0x04  ADDR4  DATA
 +0x08  u16    NUM
 +0x0A..0x13   inert  (= TENSOR4D static slot, unfilled)

   in EVERY form:  byte (INDEX_ADDR4 + 3).bit5 = 0x20  ← the TENSOR-INDIRECT marker
FieldOff (16B/20B)Off (MX)TypeSourceConfidence
INDEX ADDR4+0x00+0x00u32getTensorIndirectIndicesAP() (vtbl +0x68), a4=0CONFIRMED
DATA ADDR4+0x04+0x04u32the gathered data AP, a4=0CONFIRMED
SCALE ADDR4+0x08u32the E8M0 scale AP, a4=1CONFIRMED
NUM / x4-NUM+0x08 (u16)+0x0C (u16)u16getNumIndirectIndices() (vtbl +0x90); ×4 on MX iff Dtype∈{2,8,9}CONFIRMED
SCALE base-part+0x0F (u8)u8scalePart % PE_countCONFIRMED
indirect marker+0x03.bit5+0x03.bit5bitor [+3],0x20 on the INDEX ADDR4CONFIRMED

QUIRK — three full 32-bit ADDR4s fit in a 16-byte MX slot with room for the count. 3 × ADDR4 = 12 B (+0/+4/+8) + u16 count (+0xC/+0xD) + 1 control byte (+0xF) = exactly 16. The words are contiguous and non-overlapping — nothing is bit-packed. Byte +0xE is the spare high half of the +0xC count word. This is the plain MXMEM_PATTERN1D (data@+0 / scale@+4 / num@+8 / step@+0xA / part@+0xB) with the INDEX ADDR4 inserted at the front, shifting every later field +4. The MXMEM_PATTERN1D step-direction byte at +0xA is not re-emitted on the gather path — the index vector supplies the ordering. (+0xE being a spare count byte is INFERRED from the u16 store; everything else is disasm-pinned.)

INDIRECT16B — the non-MX 3-D gather (16 bytes)

Purpose

The generic on-chip gather descriptor with no microscaling: an index vector and a data tensor, both SBUF- or PSUM-resident. It is produced when an op with a 3-free-dim access pattern carries a tensor-indirect data operand — a per-partition out[p][i] = data[p][index[p][i]].

Algorithm

assignAccess3D handles both the static-3D and the indirect-3D case off one fork. The mangled symbol is assignAccess3D, but the body is the template assignIndirectPattern<NEURON_ISA_TPB_INDIRECT16B> (CONFIRMED — rodata context-name 0x1d71fa8 "assignIndirectPattern<…INDIRECT16B>" loaded at entry):

// CoreV4GenImpl::assignAccess3D(void *slot, bir::AccessPattern const& ap, bool)  @ 0x150ccf0
// = assignIndirectPattern<core_v4::NEURON_ISA_TPB_INDIRECT16B>
void assignAccess3D(u8 *B, AccessPattern const& ap, bool out) {
    if (!ap.isTensorIndirectDynamicAP())          // call [r12+0x80]  @0x150cd35 -> fork
        return assignStaticPattern<TENSOR3D>(B, ap, out);  // jmp 0x150c390  @0x150cf0b
                                                  //   16-byte 4+4N static fill (D-N02)

    // --- the INDIRECT branch ---
    AccessPattern* idxAP = ap.getTensorIndirectIndicesAP();      // call [r12+0x68]  @0x150cdbf
    assignStartAddr<ADDR4>(&B[0], idxAP, /*a4=*/0);              // INDEX @+0       @0x150cdc6
    assignStartAddr<ADDR4>(&B[4], ap,    /*a4=*/0);              // DATA  @+4       @0x150cdda
    u16 num = ap.getNumIndirectIndices();                       // call [r12+0x90] @0x150cdef
    *(u16*)&B[8] = num;                                          // NUM @+8 (NO x4) @0x150ce0b

    u8 gran = archModel->granularity;     // movzx r12d,[rax+0x38]                  @0x150ce17
    assert((gran & B[3]) == 0,  "TODO: support Tensor Indirect Indices AP to have dynamic offset");
    assert((gran & B[7]) == 0,  "TODO: support Tensor Indirect Data AP to have dynamic offset");
    B[3] |= 0x20;                         // or byte [rax+3],0x20  ← bit29 marker    @0x150ced8
    return;                               // ret  — does NOT fill the 3D stride/num  @0x150ceea
}

CORRECTION (D-N05 C-1) — the indirect branch does NOT fall through to the TENSOR3D static fill. A prior preliminary reading held that INDIRECT16B "uses the TENSOR3D static pattern." The full disassembly shows the same assignAccess3D forks on isTensorIndirectDynamicAP(): the indirect branch writes only {INDEX@+0, DATA@+4, NUM u16@+8, +bit29@+3} and returns at 0x150ceea; only the non-indirect branch tail-calls assignStaticPattern<TENSOR3D> (0x150c390). So on the wire an INDIRECT16B's bytes +0xA..+0xF are inert — not stride/num-filled. The "16B" suffix names the static sibling's slot width (which is also the slot the indirect record sits in), not its byte content. Fixed in place. CONFIRMED from disasm of both branches.

The count comes from getNumIndirectIndices() (vtbl +0x90), not the partition-element count, and is not ×4-scaled — there is no shl eax,2 between the call [+0x90] (0x150cdef) and the mov [rcx+8],ax (0x150ce0b). See the §"Decoder validation" CORRECTION for why this slot is +0x90 and not getNumElementsPerPartition.

There are exactly two dynamic-offset asserts (index +3, data +7) and no scale ADDR4 — that is precisely what distinguishes INDIRECT16B from MXINDIRECT16B.

INDIRECT20B — the non-MX 4-D gather (20 bytes)

Purpose

The 4-D sibling of INDIRECT16B. Identical indirect payload, identical fork, but the static fallback tail-calls assignStaticPattern<TENSOR4D> (the 20-byte 4-D pattern), so the slot is 20 bytes. A 4-free-dim AP routes here because one dim cannot be folded into the partition axis and spills to a fourth descriptor dim — exactly the TENSOR4D spill rule (see tensor descriptors, 2.3).

Algorithm

// CoreV4GenImpl::assignAccess4D(void *slot, bir::AccessPattern const& ap, bool)  @ 0x150d8e0
// = assignIndirectPattern<core_v4::NEURON_ISA_TPB_INDIRECT20B>   (rodata 0x1d72038)
void assignAccess4D(u8 *B, AccessPattern const& ap, bool out) {
    if (!ap.isTensorIndirectDynamicAP())          // call [+0x80]    @0x150d925
        return assignStaticPattern<TENSOR4D>(B, ap, out);  // jmp 0x150cf60  @0x150dafb
                                                  //   20-byte 4-D fill, unused dims 1-filled

    AccessPattern* idxAP = ap.getTensorIndirectIndicesAP();      // call [+0x68]  @0x150d9af
    assignStartAddr<ADDR4>(&B[0], idxAP, 0);                     // INDEX @+0     @0x150d9be
    assignStartAddr<ADDR4>(&B[4], ap,    0);                     // DATA  @+4     @0x150d9d3
    *(u16*)&B[8] = ap.getNumIndirectIndices();                  // NUM @+8 (NO x4) @0x150d9fb
    // index assert [B+3], data assert [B+7]  vs archModel->granularity
    B[3] |= 0x20;                          // or byte [rax+3],0x20               @0x150dac8
}

INDIRECT20B = INDIRECT16B + 4 bytes of static-fallback slot width. The indirect payload is byte-identical (index@+0, data@+4, num u16@+8, marker +3); the 16-vs-20 choice is the AP free-dim count (3-D → assignAccess3D → 16 B; 4-D → assignAccess4D → 20 B). Same two asserts, no scale. The same C-1 correction applies: the indirect branch returns without filling the 4-D stride/num region.

MXINDIRECT16B — the MX DGE gather (16 bytes)

Purpose

The microscaling gather. Reached only when an MX operand — a QuantizeMx output, or a MatmultMx ifmap/weights — is itself tensor-indirect. This is the MoE expert/token data-gather-engine (DGE) path: the routed-token / expert index vector selects which fp8/fp4-x4 rows (and their E8M0 block scales) feed the quantized matmul. The MX leaf self-routes: assignAccessForMX<MXMEM_PATTERN1D> (0x150e2f0) tests dataAP.isTensorIndirectDynamicAP() (vtbl +0x80) and tail-jumps here when the data operand is indirect.

Algorithm

// CoreV4GenImpl::assignIndirectPatternForMX<core_v4::NEURON_ISA_TPB_MXINDIRECT16B>  @ 0x150de90
void assignIndirectPatternForMX(u8 *B, AccessPattern const& dataAP, AccessPattern const& scaleAP) {
    // --- four asserts (strings recovered verbatim, §Evidence) ---
    assert(scaleAP.parentInstr == dataAP.parentInstr,
           "MX DataAP and ScaleAP are not from the same parent instruction");
    assert(dataAP.isTensorIndirectDynamicAP(),  "DataAP must be a Tensor Indirect AP");
    assert(scaleAP.isTensorIndirectDynamicAP(),
           "If the argument of matmult MX is indirect, the corresponding scale should also have indirection");
    assert(scaleAP.indexArgId == dataAP.indexArgId,   // DynamicAPINFO+0xf0
           "Furthermore, the corresponding scale must have exactly the same indirection as the argument");

    // --- the triple-ADDR4 packing ---
    AccessPattern* idxAP = dataAP.getTensorIndirectIndicesAP();   // call [rdx+0x68] @0x150e092
    assignStartAddr<ADDR4>(&B[0], idxAP,   /*a4=*/0);             // INDEX @+0       @0x150e099
    assignStartAddr<ADDR4>(&B[4], dataAP,  /*a4=*/0);             // DATA  @+4       @0x150e0aa
    assignStartAddr<ADDR4>(&B[8], scaleAP, /*a4=*/1);             // SCALE @+8 (MX)  @0x150e0c5

    // --- scale base-partition modulo PE_count ---
    u64 q = scalePart;  u32 pe = archModel->PE_count;            // mov ecx,[rdx+0x64] @0x150e0e2
    B[0xF] = (u8)(q % pe);              // div rcx; mov [rax+0xF],dl                  @0x150e0e7/ee

    // --- the x4 K-extent ---
    u32 n = dataAP.getNumIndirectIndices();          // call [+0x90]                  @0x150e108
    if (dataAP.dtype == 2 || (u32)(dataAP.dtype - 8) <= 1)   // Dtype in {2,8,9}      @0x150e10e
        n <<= 2;                                     // shl eax,2  ← x4-packed dtypes  @0x150e11e
    *(u16*)&B[0xC] = (u16)n;                          // x4-NUM @+0xC                  @0x150e139

    // --- three dynamic-offset asserts + the indirect bit ---
    assert((gran & B[3])  == 0, "TODO: support Tensor Indirect Indices AP to have dynamic offset");
    assert((gran & B[7])  == 0, "TODO: support Tensor Indirect Data AP to have dynamic offset");
    assert((gran & B[0xB])== 0, "TODO: support MX scale AP to have dynamic offset");
    B[3] |= 0x20;                       // or byte [rax+3],0x20  ← bit29 marker        @0x150e26b
}

GOTCHA — the x4-NUM is the only place a gather count is multiplied. The x4 gate is Dtype == 2 || (unsigned)(Dtype − 8) ≤ 1Dtype ∈ {2, 8, 9} = {float4_e2m1fn_x4, float8_e4m3fn_x4, float8_e5m2_x4} — the four-element-packed MX dtypes. Their gather count is in packed groups, so the hardware K-extent is 4 × getNumIndirectIndices(). The non-MX INDIRECT16B/20B NUM@+8 is never ×4 (no shl in assignAccess3D/4D). A reimplementation that ×4s the non-MX count, or that fails to ×4 the MX count for these dtypes, mis-sizes the gather. CONFIRMED — 0x150e11e: c1 e0 02 shl eax,2 exists only in the MX encoder; the gate cmp edx,2 / sub edx,8 / cmp edx,1 / ja is at 0x150e10e–11c.

The a4 scale-stream selector

The third bool a4 to assignStartAddr<ADDR4> routes the address resolver (see ADDR4 2.2, §"the data-vs-scale resolver fork"): a4=0 → Hwm vtbl+0x20 (getStartAddress, DATA path); a4=1vtbl+0x28 (getStartAddressForMXScale, SB-only). INDEX and DATA pass a4=0; SCALE passes a4=1 (mov ecx,1 at 0x150e0c5) — the only thing distinguishing the scale ADDR4 from the data ADDR4 in the shared word emitter. There is no MXINDIRECT20B in 2.24 (CONFIRMED — zero occurrences of the symbol in the binary): the MX gather is 1-D only.

NOTE — all three MX operands must carry static base addresses. The three high-byte asserts (+3 index, +7 data, +0xB scale) AND each ADDR4 byte 3 against the arch granularity mask archModel[+0x38] and reject a register/dynamic byte-offset on any operand. In 2.24 the index vector is the sole dynamic-addressing source; the three ADDR4 bases are static. The "TODO: …" strings are the assert messages, not dead code — they fire if a caller hands in a dynamic-offset AP.

Which BIR ops + kernels consume each variant

The selection is three orthogonal axes, all decided at descriptor-encode time:

AxisValuesDecided bySource
MX vs non-MXMX → MXINDIRECT16B; non-MX → INDIRECT16B/20BassignAccessForMX self-routes on vtbl +0x80CONFIRMED
16B vs 20B (non-MX)3-free-dim → 16B; 4-free-dim → 20BAP free-dim count picks assignAccess3D/4DCONFIRMED
indirect vs staticevery assignAccess*isTensorIndirectDynamicAP() (vtbl +0x80)CONFIRMED
DescriptorBIR op(s)Kernel / codegen leafConfidence
MXINDIRECT16BInstMatmultMx / InstQuantizeMx, when an MX operand AP is tensor-indirect → assignAccessForMXMXINDIRECT16BMoE expert/token DGE gather: backend::ExpertMLPsTKGKernel (lowerKernel 0xd88640) — computeGateUpProj/DownProj For{Tokens,Experts}STRONG
INDIRECT16B (2-ADDR4, 3-D)InstGather (92) ← klr::NcNGather; InstIndirectCopy (26) ← klr::NcLocalGather; InstIndirectLoad/Save (43/45); InstGenericIndirectLoad/Save (42/44)per-partition int32 row gather (NcNGather); 16-partition-block u16-offset SBUF gather (NcLocalGatherIndirectCopy)STRONG
INDIRECT20B (2-ADDR4, 4-D)same op family with a 4-free-dim index/data APsame kernels, 4-D index/data geometrySTRONG
(scatter)InstIndirectSaveAccumulate (46) — opcode 0xCA, plain-ADDR4 RMW (no 0x20 mode bit; CoreV2)MoE scatter-add / embedding-bag back-write — the scatter mirror of the gathersSTRONG

The MoE expert-routing chain: a router top-k yields an expert-id / routed-token index tensor; the gather of selected tokens/experts is NcNGather → InstGather(92) (out[p][i] = params[p][indices[p][i]], the INDIRECT16B/20B record) or NcLocalGather → InstIndirectCopy(26) (a 16-partition-block u16-offset SBUF gather, where an index > num_valid_indices is a −1 skip). The MX-quantized expert matmul (a MatmultMx with a gathered ifmap/weights operand) is the MXINDIRECT16B path. The scatter back is InstIndirectSaveAccumulate(46) — a plain ADDR4 RMW that carries no 0x20 indirect mode bit and is emitted by the CoreV2 family.

NOTE — GenericIndirectLoad/Save are concretized before reaching these encoders. GenericIndirectLoad(42)/Save(44) are expanded by a LowerGenericIndirect pass (codegenIndirectLoadSave 0xb580e0) into concrete offset arithmetic + an indirect DMA. By contrast Gather(92)/IndirectCopy(26) are already concrete (a single resolved index axis) and flow straight to their CoreV*GenImpl::visitInst* emitters, which call assignAccess3D/4D to materialize the INDIRECT16B/20B record. The DMA-engine indirect descriptor (opcode 0xBB) takes a different validator (see below). The op-number bindings are STRONG — assembled from the codegen-leaf symbols and the MX-operand routing — not byte-fixture CONFIRMED; no emitted .neff gather descriptor was diffed.

Decoder validation + encoder↔decoder byte agreement

There is no dedicated indirect16b_valid/indirect20b_valid symbol. An indirect record is validated as a MEM_PATTERN slot: the bit-29 marker selects the indirect arm inside a shared validator, then a per-descriptor quadrant check enforces the gather invariant.

The mode-nibble selector

; core_v4::mxmem1d_valid @ 0x1447190  (validates MXMEM_PATTERN1D and MXINDIRECT16B)
0x144719c:  49 c1 ee 18    shr  r14, 18h        ; byte3 of the first (INDEX/DATA) ADDR4
0x14471a9:  83 e0 60       and  eax, 60h        ; isolate bits[30:29] (the mode nibble)
0x14471b4:  3c 20          cmp  al, 20h         ; ⭐ == 0b01 INDIRECT? → take the gather arm
;   then up to 3× tensor_start_addr_valid on the INDEX / DATA / SCALE words

The same 16-byte struct decodes as MXMEM_PATTERN1D (nibble 0) or MXINDIRECT16B (nibble 0x20); each ADDR4 (data, scale, and the index when indirect) is re-validated through tensor_start_addr_valid (see ADDR4 2.2) for addr/align/PSUM-window/register-mode.

The same-quadrant invariant

; core_v4::indirect_quadrant_check_src3d @ 0x1448450  (INDIRECT16B)
0x1448457:  c1 e8 18       shr  eax, 18h        ; byte3 of the INDEX ADDR4
0x144845a:  83 e0 e0       and  eax, 0E0h       ; isolate bits[31:29]  (mode AND register-mode)
0x144845d:  3c 20          cmp  al, 20h         ; ⭐ == 0x20 (indirect, register-mode clear)?
;   if (!indirect) return 1            ; not a gather descriptor — skip
;   data = slot[+4] & 0x1FFFFFFF
0x1448480:  3d ff ff 3f 00 cmp  eax, 3FFFFFh    ; DATA in PSUM window? → ok
;   else index = slot[+0] & 0x1FFFFFFF
0x1448491:  e9 …           jmp  addresses_in_same_sbuf_quadrant(index, data)   ; ⭐ same quadrant

For an SBUF gather the INDEX ADDR4 and the DATA ADDR4 must live in the same SBUF quadrant — the hardware gather engine indexes within one quadrant. indirect_quadrant_check_src4d (0x14484a0) is the 20-byte twin: it reads the marker at struct byte +0xB and runs the same PSUM-window + same-quadrant test on the index@+8 / data@+0xC region of the MEM_PATTERN4D it is handed.

The DMA-indirect path

has_dma_indirect_valid_idx_start_addr (0x13707c0, the DMA-engine indirect descriptor, opcode 0xBB) validates the index ADDR4 (start_addr_active_channels + tensor_start_addr_valid), gates the data/secondary slots with a register-mode test ((byte3 & 0xa0) == 0x80 reject), and reads the DMA_INDIRECT_FLAGS (& 3 ∈ {1,2}) to select the load/save direction.

Byte agreement (both ways)

  • The encoder OR's bit-29 onto the INDEX ADDR4 byte 3 (or [B+3],0x20); every decoder reads (byte3 >> 0x18) & {0x60, 0xE0} == 0x20 on that same byte to select the indirect arm. (assignAccess3D/4D/MX +3mxmem1d_valid shr 0x18; and 0x60quadrant_check_src3d/4d and 0xE0.)
  • The encoder writes INDEX@+0 / DATA@+4 (/ SCALE@+8); the decoders re-validate those exact ADDR4 offsets via tensor_start_addr_valid and read index@+0 vs data@+4 (src3d) / index@+8 vs data@+0xC (src4d) for the quadrant equality.
  • The MX x4-NUM@+0xC and scale-part@+0xF the encoder writes are exactly where mxmem1d_valid's partition/region tests read.
  • The three (MX) / two (non-MX) dynamic-offset asserts the encoder runs guarantee the emitted ADDR4s carry no register-mode byte — so the decoders' register-mode gates (& 0xa0 == 0x80 reject) never fire on a legally-encoded gather.

CORRECTION (D-N05 C-2) — the +0x90 vtable slot is getNumIndirectIndices, not getNumElementsPerPartition. A prior pass labeled AP.vtable+144 (= +0x90) as the per-partition element count. The libBIR.so disassembly settles it: the base getNumIndirectIndices() @0x20fed0 is 31 c0 c3xor eax,eax; ret (a virtual, overridden in PhysicalAccessPattern/SymbolicAccessPattern), whereas getNumElementsPerPartition() const @0x202060 is a non-virtual add rdi,0x50; jmp … accessor of Pattern+0x50 — a different function, not the +0x90 slot. The indirect branch is reached only when isTensorIndirectDynamicAP() == true, so the count is the gather index count. Fixed in place. CONFIRMED from libBIR disasm of both accessors.

CoreV2 / CoreV4 and arch-universality

The indirect encoders are CoreV4-family (assignAccess3D/4D 0x150ccf0/0x150d8e0, assignIndirectPatternForMX 0x150de90). CoreV2 has its own assignAccess3D/4D (0x1176de0/0x1177620) for the CoreV2 indirect opcode family (IndirectCopy 0xE7, IndirectSaveAccumulate 0xCA, DMA-indirect 0xBB). MX descriptors are CoreV4-only — the MXMEM/MXINDIRECT16B and QuantizeMx encoders are gated core_v4. Each ADDR4 word's internal bit packing (29-bit address / region class / register-mode bit / per-dtype alignment) is assignStartAddr<ADDR4>'s job (see ADDR4 2.2), shared with every CoreV4 op; the indirect encoders add only the descriptor-level bit-29 marker on the INDEX ADDR4 and the {num, scale-part} control fields. The static siblings assignStaticPattern<TENSOR3D/4D> supply the stride/num arrays on the non-indirect branch only; the indirect branch leaves that region inert because the index vector is the addressing source.

NOTE — archModel[+0x38] (granularity mask) and archModel[+0x64] (PE_count) are constructor constants. They live in the per-arch Target constructors compiled into libwalrus.so, not in any JSON config. The page treats them as opaque per-arch constants; a reimplementation must source the same granularity mask and PE count from its own arch model. (STRONG — read from the encoder's reads of those offsets, not from a decoded table.)

Evidence anchors

libwalrus.so / libBIR.so cp310; VA == file offset; objdump -M intel / IDA.

Encoders. assignAccess3D (INDIRECT16B) 0x150ccf0: fork call [r12+0x80]; test al,al; je (0x150cd35); INDEX@+0 (0x150cdc6); DATA@+4 (0x150cdda); call [r12+0x90] (0x150cdef); mov [rcx+8],ax NUM@+8 — no shl (0x150ce0b); or [rax+3],0x20 (0x150ced8); ret (0x150ceea); static fallback jmp assignStaticPattern<TENSOR3D> (0x150cf0b0x150c390); context-name rodata 0x1d71fa8. — assignAccess4D (INDIRECT20B) 0x150d8e0: same shape; or [rax+3],0x20 (0x150dac8); static fallback jmp assignStaticPattern<TENSOR4D> (0x150dafb0x150cf60); context-name 0x1d72038. — assignIndirectPatternForMX<MXINDIRECT16B> 0x150de90: INDEX@+0 / DATA@+4 / SCALE@+8 (a4=1, mov ecx,1 0x150e0c5); div rcx; mov [rax+0xF],dl scale-part (0x150e0e7/ee); x4 gate cmp edx,2 / sub edx,8 / cmp edx,1 / ja (0x150e10e–11c); shl eax,2 (0x150e11e); mov [r15+0xC],ax x4-NUM (0x150e139); or [rax+3],0x20 (0x150e26b).

Assert strings (rodata, verbatim, all present). "must be a Tensor Indirect AP" (non-MX); "DataAP must be a Tensor Indirect AP" (MX); "MX DataAP and ScaleAP are not from the same parent instruction"; "If the argument of matmult MX is indirect, the corresponding scale should also have indirection"; "Furthermore, the corresponding scale must have exactly the same indirection as the argument"; "TODO: support Tensor Indirect Indices AP to have dynamic offset"; "TODO: support Tensor Indirect Data AP to have dynamic offset"; "TODO: support MX scale AP to have dynamic offset".

Decoders. mxmem1d_valid 0x1447190: shr r14,0x18; and 0x60; cmp 0x20 (0x144719c–b4). — indirect_quadrant_check_src3d 0x1448450: shr eax,0x18; and 0xE0; cmp 0x20 (0x1448457–5d), PSUM-window cmp 0x3FFFFF (0x1448480), jmp addresses_in_same_sbuf_quadrant (0x1448491). — indirect_quadrant_check_src4d 0x14484a0 (marker byte +0xB). — has_dma_indirect_valid_idx_start_addr 0x13707c0.

libBIR AP accessors. getNumIndirectIndices base 0x20fed0 = 31 c0 c3 (xor eax,eax; ret); isTensorIndirectDynamicAP 0x20feb0; getTensorIndirectIndicesAP 0x20fe80; getNumElementsPerPartition() const 0x202060 (add rdi,0x50; jmp — DISTINCT, not the +0x90 slot). PhysicalAccessPattern overrides at 0x175100/0x17bee0/0x179c60.

Ops/kernels. bir::InstGather / CoreV2GenImpl::visitInstGather 0x12532e0; bir::InstIndirectCopy / visitInstIndirectCopy 0x1275c40; klr::NcNGather / klr::NcLocalGather (RTTI present); backend::ExpertMLPsTKGKernel (RTTI 0x1de7b60, vtable 0x3d8fb40); codegenIndirectLoadSave 0xb580e0. MXINDIRECT20B: zero occurrences in the binary (confirms 1-D-only MX gather).

Cross-References

  • ADDR4 — the 32-Bit Address Word (2.2) — the bit-29 indirect mode (0x20) the INDEX ADDR4 carries, and the a4 data-vs-scale resolver fork the SCALE word uses; this page's or [B+3],0x20 is the marker ADDR4 §"the mode nibble" pins as 0b01.
  • TENSOR1D / 2D / 3D Descriptors — the 4+4N Rule (2.3) — the TENSOR3D/TENSOR4D static patterns whose slot widths name the 16B/20B variants and whose static fill the non-indirect branch emits.
  • MEM_PATTERN2D / 3D — the DST/PSUM Role (2.5) — the MEM_PATTERN family these indirect records are validated as.
  • MXMEM_PATTERN1D — the MX twin (2.6, planned) — the plain MX 1-D pattern (data@+0 / scale@+4 / num@+8) that MXINDIRECT16B shifts +4 by inserting the INDEX ADDR4.
  • Penguin middle-end — TensorCopyDynamic (5.24, Part 5) — the dynamic-copy op that lowers to the indirect gather/scatter family.
  • libwalrus backend — lower_dynamic_dma / DGE (Part 8, walrus) — the DMA-indirect lowering and the data-gather-engine path the MXINDIRECT16B MoE gather feeds.