Skip to content

Tile-Index 展开

本页中的所有地址、符号和偏移均适用于来自 libtpu-0.0.40-cp314 wheel 的 libtpu.so(构建 libtpu_lts_20260413_b_RC00,build-id md5 89edbbe81c5b328a958fe628a9f2207d,781,691,048 字节)。该映像剥离符号;反混淆后的 C++ 符号名按原文引用。.text VMA 等于文件偏移(.text 基址 0xe63c000);.data.rel.ro 带有 0x200000 的 VMA→文件增量。其他版本会不同,请将每个 VA 都视为版本绑定。

摘要

ExpandTiledMemRefsPass 是 SparseCore 阶段 2 pass,用来完成 LowerToMlo DMA bridge-cast 有意推迟的 DMA lowering。在阶段 1 中,一个 tpu.enqueue_dma 被停放在带有 sc.unlowering 标记的 builtin.unrealized_conversion_cast 后面,因为该 op 的最终 SparseCore 形状依赖 LowerToMlo 看不到的 tiled memref 布局;一旦 tile 布局固定,本 pass 就会运行,于是 tile→address 算术终于可计算。被停放 op 的消费者是 LowerMemrefToMlo::lowerEnqueueDma (0x135105a0)。

该 pass 拥有一块对其上下所有层都不可见的编译器算术:de-tiling algebra。它把一个 tpu::Tile 布局 memref 加上一组逻辑索引 ValueRange 转换为 row-major HBM / TILE_SPMEM 地址算术,并把该算术绑定到 SparseCore DMA / Stream 描述符操作数的逐 tile 传输发射器上。一个 tile 大小为 t 的 tiled 维度 d 必须以两种一致的方式拆分:shape 拆为 (ceil(d/t), t) (getExpandedShape),index i 拆为 (i/t, i%t) (expandTiledIndices)。外半部分索引 tile grid;内半部分索引 tile 内部;row-major 后缀乘积 stride (getExpandedStrides) 将展开后的索引转换为物理 word 偏移。

本页只负责三件事:

  • De-tiling primitivesexpandTiledMemRef(tiled MemRefType → flat strided MemRefType)、getExpandedShaped → (ceil(d/t), t))、getExpandedStrides(inner dims 上的 row-major 后缀乘积),以及索引孪生 expandTiledIndices(通过 arith.divui / arith.remuii → (i/t, i%t),含 vector<i32> 广播变体)。
  • Transfer issuersissueContiguousTransfer(零索引 whole-memref bulk move,发出一个 DmaSimpleStartOp 或一个 LinearStreamStartOp)、getDMATiling(retiling reshape),以及 issueRetiledTransfer(逐 tile 描述符循环,其中 signed Div/Rem/Mul 是索引代数的 retiled 孪生)。
  • Deferred-DMA consumptionlowerEnqueueDma,阶段 2 dispatch,将 getTransferKind 连接到 retiled-vs-contiguous 分支并擦除原始 op。

本页负责:阶段 1 的 bridge-cast 标记机制和 ConversionTargetLowerToMlo DMA Bridge-Cast);描述符字段布局、mem_id/core_id 和 slot opcode 枚举(Intra-Chip DMA Descriptor);issueRolled* / issueGeneral* 传输体发射器(Rolled / Strided / General Emitters);Simple-vs-Strided 选择器(DmaParameters Selector)。

Passxla::tpu::sparse_core::ExpandTiledMemRefsPass(pattern install …::populateTpuPatterns @ 0x134e7700
来源溯源.rodata 字符串 platforms/xla/sparse_core/mlo/passes/expand_tiled_memrefs_pass.ccexpandTiledMemRef 中第 230 行)
类型 de-tilingxla::tpu::sparse_core::(anonymous namespace)::expandTiledMemRef(mlir::MemRefType) @ 0x134e16e0
Shape de-tilingfree mlir::tpu::(anonymous namespace)::getExpandedShape(ArrayRef<long>, ArrayRef<xla::Tile>, bool) @ 0x14aa7dc0;wrapper TiledLayoutAttr::getExpandedShape @ 0x14aa7cc0
Stride de-tilingmlir::tpu::TiledLayoutAttr::getExpandedStrides() @ 0x14aa7400
Index de-tilingxla::tpu::sparse_core::(anonymous namespace)::expandTiledIndices(ValueRange, ArrayRef<xla::Tile>, Location, OpBuilder&) @ 0x134e1f20
Contiguous issuermlir::tpu::LowerMemrefToMlo::issueContiguousTransfer @ 0x1350a3e0
Retiling reshapemlir::tpu::LowerPassBase::getDMATiling(...) const @ 0x13518660
逐 tile 循环mlir::tpu::LowerPassBase::issueRetiledTransfer(...) @ 0x13519480
阶段 2 dispatchmlir::tpu::LowerMemrefToMlo::lowerEnqueueDma(EnqueueDMAOp, EnqueueDMAOpAdaptor, ConversionPatternRewriter&) @ 0x135105a0
证据等级可重实现级 / 已按 IDA 反编译字节确认(primitives、index algebra、dispatch 均已验证)

1. De-Tiling 代数 — 为什么 Tiled DMA 不能过早 Lower

目的

操作数为 tpu::Tile 布局 memref 的 tpu.enqueue_dma,在 tile 布局被展开为显式维度和 row-major stride 之前,不能转换成 SparseCore 描述符。描述符的地址操作数是普通 flat offset;DMA 引擎遍历的是 stride list,而不是 tile grid;所以编译器必须先同时对类型(memref 布局)和索引(逻辑访问)进行 de-tile。De-tiling 是同一个恒等式的两半:shape 拆分 (ceil(d/t), t) 和 index 拆分 (i/t, i%t) 互为镜像,而后缀乘积 stride 让展开后的索引解析为单个 word offset。这就是 ExpandTiledMemRefsPass 存在的算术,也正是 bridge-cast 必须等待它的原因。

De-tiling 恒等式概览

逻辑对象de-tiled / 展开形式计算者 (@VA)
tile 为 t 的 tiled dim d(ceil(d/t) outer, t inner)getExpandedShape @ 0x14aa7dc0 (ceil-div)
tiled stridestored outer strides ++ suffix-product(inner)getExpandedStrides @ 0x14aa7400 (imul chain)
tiled MemRefTypeflat strided MemRefTypeexpandTiledMemRef @ 0x134e16e0
逻辑索引 i(i/t outer, i%t inner)expandTiledIndices @ 0x134e1f20 (divui/remui)
  vector<i32> 索引broadcast(const i32 t),然后 divui/remuiexpandTiledIndices vector branch
物理 word 偏移Σ_d expIndex[d] * expStride[d]expandTiledMemRef 生成的 StridedLayoutAttr

NOTE — 本页中的 “tile rank” 都是 tile.dims >> 1。一个 xla::Tile 条目存储 2·tile_rank 个整数,后面的 tile_rank 个是 tile sizes,反编译中以 24 字节 (0x18) 的 ArrayRef stride 读取它们。shape、stride 和 index primitives 都消费同一个 ArrayRef<xla::Tile>,这正是三种拆分保持一致的原因。


2. expandTiledMemRef — Tiled MemRefType → Flat Strided MemRefType

目的

这是类型层面的 de-tiling:它把 rank-N 的 tiled memref 转换为 rank-(N + tile_rank) 的 flat strided memref,展开索引 e 的地址为 Σ_d e[d]·expStride[d]。下游所有内容都寻址这个 flat memref;tile 结构消失,被展开为显式 outer/inner dims 和一个 StridedLayoutAttr

算法

0x134e16e0 处的函数体已按反编译字节确认,包括断言携带的源码文件名和行号。

c
// expandTiledMemRef — @ 0x134e16e0
// (platforms/xla/sparse_core/mlo/passes/expand_tiled_memrefs_pass.cc:230)
MemRefType expandTiledMemRef(MemRefType orig):
    layout = orig.getLayout()
    // GATE: layout MUST be a TiledLayoutAttr; else absl LogFatal.
    // decompile: [layout_impl + 0x88] == TypeIDResolver<TiledLayoutAttr>::id ?
    if not isa<TiledLayoutAttr>(layout):
        LogFatal("isa<TiledLayoutAttr>(orig_type.getLayout())")   // @0x134e193c

    expShape = TiledLayoutAttr::getExpandedShape(layout, orig.getShape())  // §3
    expStr   = TiledLayoutAttr::getExpandedStrides(layout)                 // §4

    suffix = computeSuffixProduct(expShape)        // @0x1ca6fac0 — row-major strides of expShape
    if bcmp(expStr, suffix) == 0:                  // CONTIGUOUS: identity strided layout
        stridedLayout = <identity for expShape>
    else:
        stridedLayout = StridedLayoutAttr::get(ctx, /*offset=*/0, /*strides=*/expStr)  // @0x1d85be00

    return MemRefType::get(expShape, orig.getElementType(),
                           stridedLayout, orig.getMemorySpace())           // @0x1d897680
```text

与 `computeSuffixProduct` 的 `bcmp` 是一个 contiguous 快捷路径:当已存储的 expanded strides 等于 expanded shape 的 row-major 后缀乘积时,flat memref 不需要显式 strided layout(identity layout 即可)。否则会盖上 offset 为 `0` 的显式 `StridedLayoutAttr`。

> **GOTCHA —** `TiledLayoutAttr` gate 是硬性的 `absl::log_internal::LogMessageFatal`,不是软性的 `emitError`。`expandTiledMemRef` 是内部 primitive,只应在 tiled layout 上到达;这里出现 non-tiled memref 是编译器不变量破坏,不是用户错误。重实现必须在上游保证该 gate,而不是期待优雅失败。

---

## 3. `getExpandedShape` — 每个 Tiled Dim 拆为 `(ceil(d/t), t)`

### 目的

De-tiling 的 shape 半边。tile size 为 `t` 的 tiled dim `d` 变成 OUTER tile-count `ceil(d/t)`,后接 INNER tile-size `t`。这正是 §5 中 index 拆分的镜像,它让 outer index 遍历 tile grid,让 inner index 在 tile 内部取值。

### 算法

free function `getExpandedShape(ArrayRef<long> shape, ArrayRef<xla::Tile> tiles, bool strict)` @ `0x14aa7dc0` 返回 `optional<SmallVector<long>>`(result `+0x40` 处 present-byte)。`TiledLayoutAttr::getExpandedShape` wrapper @ `0x14aa7cc0` 读取 layout 的 `ArrayRef<Tile>`(`ptr @ +0x8`,`count @ +0x10`)并转发。

```c
// getExpandedShape (free) — @ 0x14aa7dc0
optional<SmallVector<long>> getExpandedShape(ArrayRef<long> shape,
                                             ArrayRef<Tile> tiles, bool strict):
    result = copy(shape)
    for tile in tiles:                       // 24-byte (0x18) stride per Tile
        tile_rank = tile.dims >> 1
        // operate on the LAST tile_rank dims of the accumulating result
        for k in 0 .. tile_rank-1:
            d = result[end - tile_rank + k]
            t = tile.sizes[k]                // [tile.ptr + k*8]
            if d == kDynamic:                // 0x8000000000000000 passes through
                continue
            outer = ceil_div(d, t)           // (d>0) ? (d-1)/t + 1 : 0   — setne/sub/idiv/add
            if strict and (d % t != 0):
                return nullopt               // exact-tiling required; fail @0x14aa8190
            result[end - tile_rank + k] = outer
            result.append(t)                 // APPEND the inner tile size
    return result

ceiling-division 是标准的 (setne bl; sub d,bl; div t; add bl) 惯用法,也就是当 d > 0 时为 (d-1)/t + 1kDynamic (0x8000000000000000) 维度原样通过。

NOTE — strict 控制精确可整除性。getExpandedStrides (§4) 以 strict = 1 调用它(r9d = 1 @ 0x14aa74d8),因为从 ragged tile 计算 inner strides 没有意义;expandTiledMemRef 的类型路径使用 wrapper。在 strict 下出现非零 idiv 余数会进入 divisibility-fail 路径,将 present-byte 设为 0,返回 nullopt


4. getExpandedStrides — Inner Dims 上的 Row-Major 后缀乘积

目的

Stride 半边。展开索引 e 的物理地址为 Σ_d e[d]·expStride[d];此函数构造 expStride 向量,供 expandTiledMemRef 包装进 StridedLayoutAttr

算法

TiledLayoutAttr::getExpandedStrides() @ 0x14aa7400:

c
// getExpandedStrides — @ 0x14aa7400
SmallVector<long> getExpandedStrides():
    result = copy(layout.storedExpandedStrides)   // [impl+0x18] ptr, [impl+0x20] count
                                                  // the precomputed OUTER strides
    inner = getExpandedShape(/*strict=*/1)        // @0x14aa74de — the inner-tile shape
    // row-major SUFFIX PRODUCT over the inner shape (unrolled imul chain)
    for d in inner:                               // @0x14aa7540..0x14aa7589
        stride[d] = product(inner[d+1 ..])
    result.append(innerStrides)
    return result
```text

因此 expanded stride 向量是 layout 中*已存储* outer strides 与新计算的 row-major *inner-tile* strides 的拼接,即 `stride[inner_d] = prod(inner_shape[d+1 ..])`。与 §5 的 index 拆分组合后,展开索引会解析为单个物理 word offset。

---

## 5. `expandTiledIndices` — Index `i` → `(i/t, i%t)`

### 目的

`getExpandedShape` 的索引孪生。沿 tiled dim 的每个逻辑索引 `i` 都变成一对 `(i/t [which tile], i%t [offset within tile])`。对整个索引列表组合后,它会寻址 §2 的 de-tiled flat memref:outer (div) indices 选择 tile grid,inner (rem) indices 选择 tile 内位置,而 §4 的后缀乘积 strides 将这对值转换为 word offset。

### 算法

`expandTiledIndices(ValueRange logicalIndices, ArrayRef<xla::Tile> tiles, Location, OpBuilder&)` @ `0x134e1f20` 返回 `SmallVector<Value>`。已按反编译字节确认(`divui`/`remui`/`ConstantIndexOp`/`Broadcast`/`isIndex`/`isSignlessInteger(32)`/`emitError` 调用清单全部存在)。

```c
// expandTiledIndices — @ 0x134e1f20
SmallVector<Value> expandTiledIndices(ValueRange logical, ArrayRef<Tile> tiles,
                                      Location loc, OpBuilder& b):
    result = copy(logical)                        // dereference_iterator @0x1d8d9c60
    for tile in tiles:                            // 24-byte stride; r12 += 0x18 @0x134e23ea
        tile_rank = tile.dims >> 1
        tail = result[end - tile_rank .. end]     // the last tile_rank indices
        div_block = []; rem_block = []
        for k, idx in enumerate(tail):
            t = tile.sizes[k]                     // [tile.ptr + k*8]
            if idx.getType().isIndex():           // @0x1d8e86e0
                c = arith::ConstantIndexOp::create(b, loc, t)      // @0x1cacab00
            elif idx is vector<i32>:              // VectorType elem isSignlessInteger(32) @0x1d8e87e0
                k0 = arith::ConstantOp::create(b, loc, i32, i32Attr(t))  // @0x1cb002e0
                c  = vector::BroadcastOp::create(b, loc, vecTy, k0)      // @0x178d98c0
            else:
                return emitError("Unsupported index type: " + ty)       // @0x134e2400
            div_block.append( arith::DivUIOp::create(b, loc, idx, c, /*isExact=*/false) )  // @0x1cb06d00
            rem_block.append( arith::RemUIOp::create(b, loc, idx, c) )                     // @0x1cb20800
        // replace the last tile_rank indices with 2*tile_rank values:
        //   FIRST the div (outer) block, THEN the rem (inner) block
        result[end - tile_rank .. end] = div_block ++ rem_block         // memcpy @0x134e2360
    return result

GOTCHA — 替换后的布局是 [outer_0..outer_{r-1}][inner_0..inner_{r-1}],也就是先整个 div block,再整个 rem block,不是交错的 (outer, inner) 对。反编译中的临时缓冲区布局为 [div...][rem...],最终 memcpy 用 2·tile_rank 个值覆盖原来的 tile_rank 个值。若重实现交错这些 pair,就会相对于 §4 中期待 outer dims 后接 inner dims 的 expanded stride table 错排操作数,并静默破坏每个 tiled 地址。

NOTE — vector<i32> 分支是 lane-broadcast 形式:除数 t 先物化为 i32 arith.constant,再通过 vector.broadcast splat 到索引 vector 类型,使 divui/remui 逐 lane 运算。标量与 vector 形式产生相同的 (i/t, i%t) 代数;区别只在除数物化方式。

NOTE — expandTiledIndices 的消费者位于相邻页面。expandSCStreamStart<…StreamStartOp> (0x134ea400) 会把每个操作数组({Src,Dst,Offset,Sflag}Indices)送入 expandTiledIndices,然后用 MutableOperandRange::assign 替换。动态 shape 孪生 expandTiledShape (0x134e2aa0) 将可能动态的 shape 展开成同样的 (outer, inner) 形式,并为 runtime dims 发出 arith ops;其完整函数体未在此解码(LOW)。


6. issueContiguousTransfer — 零索引 Bulk Move

目的

终端传输发射器:单个 whole-memref bulk move,恰好发出一个描述符,即一个 DmaSimpleStartOp(对 kDma)或一个 LinearStreamStartOp(对 kStream)。它从 offset 0 寻址 WHOLE memref(所有索引操作数都是 ConstantIndexOp(0));实际基址是 memref 自身的 base。issueRetiledTransfer (§8) 通过逐 tile callback 每 tile 调用一次它,所以它既是 tiled 路径的逐 tile leaf,也是 contiguous 路径的直接 emitter。

算法

issueContiguousTransfer(OpBuilder&, EnqueueDMAOp, Value srcBase, Value dstBase, Value length, Value sflag, …, optional<DeviceAndCoreIds>, TransferKind kind, bool) @ 0x1350a3e0GetMemorySpace×2、零索引 ConstantIndexOp(0)sc.inside_trace_region 探测、DivUIOp stream-length divide、TraceLocalDma 以及两个 …StartOp::create 调用均已在反编译中确认。

c
// issueContiguousTransfer — @ 0x1350a3e0
LogicalResult issueContiguousTransfer(b, op, srcBase, dstBase, length, sflag,
                                      ..., kind, ...):
    srcSpace = sparse_core::GetMemorySpace(getMemRefType(srcBase))   // @0x1459c7e0
    dstSpace = sparse_core::GetMemorySpace(getMemRefType(dstBase))
    // MemorySpace enum: HBM=4, TILE_SPMEM=2, SPMEM=3  (Intra-Chip Descriptor)

    srcZeroIdx = [ ConstantIndexOp(0) ] * rank(src)   // zero offsets — whole memref
    dstZeroIdx = [ ConstantIndexOp(0) ] * rank(dst)   // @0x1350a4f2 / @0x1350a633

    // trace-region flag: getInherentAttr("sc.inside_trace_region", 22) / DictionaryAttr fallback
    insideTrace = probe sc.inside_trace_region        // matches the stage-1 bridge tag

    dstIsHbm = (dstSpace == 4)                        // memspace XOR-4 test @0x1350a870

    switch kind:
      case kDma:                                      // @0x1350abc4
        sparse_core::DmaSimpleStartOp::create(b, loc,
            srcBase, srcZeroIdx, dstBase, dstZeroIdx,
            length, sflagIdx, sflag)                  // @0x145b9740  → SimpleDma slot 0x6
      case kStream:                                   // @0x1350a9c0
        stripe    = target()->[+0x948]->[+0xa4]       // SPMEM stripe / DMA-word granularity (bytes)
        streamLen = DivUIOp(length, ConstantIndexOp(stripe), /*isExact=*/false)  // @0x1cb06d00
        trace     = Target::TraceLocalDma()           // @0x1d6186c0
        sparse_core::LinearStreamStartOp::create(b, loc,
            /*b1=*/false, /*b2=*/dstIsHbm, /*b3=*/trace, /*opcode=*/0,
            srcBase, srcZeroIdx, dstBase, dstZeroIdx,
            /*off=*/streamLen, /*IntegerAttr=*/null, sflag, sflagIdx)  // @0x145e3440 → Stream Linear slot 0x3b
      default:
        return emitOpError("Unsupported transfer kind: %d", kind)  // @0x1350aecb
```text

| issuer (kind) | `sparse_core::…::create` (`@VA`) | slot | 操作数绑定 |
|---|---|---|---|
| `kDma` | `DmaSimpleStartOp::create` `0x145b9740` | SimpleDma `0x6` | `(srcBase, srcZeroIdx, dstBase, dstZeroIdx, length, sflagIdx, sflag)` → `{src,dst}mem_{core,mem}_id`, `dma_length`, `dest_sync_flags` |
| `kStream` | `LinearStreamStartOp::create` `0x145e3440` | Stream Linear `0x3b` | `(false, dstIsHbm, traceLocalDma, opcode=0, srcBase, srcZeroIdx, dstBase, dstZeroIdx, off=length/stripe, IntegerAttr=null, sflag, sflagIdx)` → `off_tile_start_offset`, stream verb, trace bit |

> **NOTE —** slot opcodes(`SimpleDma 0x6`、`Stream Linear 0x3b`、`GeneralDma 0x8`、`SingleStrided 0x7`)以及这些 create-args 填充的描述符字段语义记录在 [Intra-Chip DMA Descriptor](intra-chip-descriptor.md)。本页绑定的是*编译器侧操作数来源*;字段布局通过交叉引用给出,而不重复。
>
> **HIGH —** stream divisor 是 `target()->[+0x948]->[+0xa4]`,一个 runtime chip-parts geometry DWORD;分析将其匹配到 SPMEM-stripe / DMA-word granularity(`SparseCoreSpmemStripeGranularityBytes` const accessor `0x1d499440` 返回 `32`)。把 `length` 除成 stripe/word 单位已按字节确认;除 offset chain 之外的精确 runtime-field 身份是推断。`{b1=false, b2=dstIsHbm, b3=TraceLocalDma, opcode=0, IntegerAttr=null}` 参数 VALUES 已按字节确认;它们到 `LinearStreamStartOp` ODS slot fields(`hbm4b` / `enable_trace` / stream verb)的精确映射是结构性判断,参见 [Intra-Chip DMA Descriptor](intra-chip-descriptor.md)。

---

## 7. `getDMATiling` — Retiling Reshape

### 目的

当一个 DMA endpoint 是 tiled,而另一个是 contiguous-but-reinterpretable 时,必须先调和两种布局,才能发出逐 tile 传输。`getDMATiling` 计算逐 tile 的 STRIDE 结构(以 element/word 为单位的 tile strides)和一个 retiled `TiledLayoutAttr`,使 tiled 侧的 tile grid 映射到 contiguous 侧的 flat layout。

### 算法

`getDMATiling(OpBuilder&, Operation*, TypedValue<MemRefType> src, TypedValue<MemRefType> dst, TiledLayoutAttr& srcLayout, TiledLayoutAttr& dstLayout, SmallVectorImpl<long>& outStrides, long& outElemsPerStride) const` @ `0x13518660`(一个 `const` method,mangled `ZNK…`)。

```c
// getDMATiling — @ 0x13518660
LogicalResult getDMATiling(b, op, src, dst, &srcLayout, &dstLayout,
                           &outStrides, &outElemsPerStride):
    // GATE 1: both layouts must be tiled
    if src.getTiles().empty() or dst.getTiles().empty():        // @0x14a9dac0
        return emitOpError("DMA source and target must have tiled layout.")   // @0x135186db

    // GATE 2: tile ranks match AND rank in {1,2}
    tile_rank = src.tile_dims >> 1
    if tile_rank != dst.tile_rank or tile_rank not in {1,2}:    // lea ecx,[rbx-3]; cmp ecx,0xfffffffd
        return emitOpError("Not implemented: DMA with tiling that is not 1 or 2D. ...")

    if leading tile dims MATCH:                                  // contiguous reshape
        getElementTypeBitwidth() gate                            // @0x11233400
        outStrides = existing tile strides / element stride
    else:                                                        // 2-D reshape @0x13518ad9
        require Tile::operator==(inner(src), inner(dst))         // @0x13519340 — inner tiles match
        require canReinterpretToUntiledMemref(contiguousSide,
                  {SublaneCount, LaneCount}, false)              // @0x14b74480 — flatten to sublane×lane
        require elementBitwidth == 32 and leadingDim == 1
        outStrides = contiguousSide.getTileStrides() / untiledElemStride   // idiv, divisibility-checked
            // nonzero remainder -> emitOpError "Failed to update target tile strides."  @0x13518f95
        retiled = TiledLayoutAttr::get(ctx, tiles, dividedStrides)         // @0x14a9d980
        *outLayoutRef = retiled                                  // write *[rbp+0x10]
        outElemsPerStride = 1                                    // write *[rbp+0x20]
    return success

outStrides 供给 SingleStrided / GeneralDma stride 操作数;outElemsPerStride 供给 elements_per_stridecanReinterpretToUntiledMemref gate 将 retiling 限制在能 flatten 为物理 MXU sublane×lane tile geometry 的 contiguous 侧(SublaneCount @ 0x1d60f300LaneCount @ 0x1d60f400)。


8. issueRetiledTransfer — 逐 Tile 描述符循环

目的

把 tiled DMA 转换为一串逐 tile contiguous transfers。每个 tile 的 outer index 会变成 base offset(tile_index × tile_stride × elemBytes);inner offset 由逐 tile 的单次传输处理。这里的 signed Div/Rem/MulexpandTiledIndices 中 unsigned Div/Rem 的 retiled 孪生,即同一个 (i/t, i%t) 代数,只是表达为描述符的 base + stride 操作数,而不是索引 ValueRange

算法

issueRetiledTransfer(OpBuilder&, EnqueueDMAOp, Value src, Value dst, Value length, Value sflag, ValueRange dynShape, long, unsigned numTiles, optional<DeviceAndCoreIds>, int, function<LogicalResult(OpBuilder&, EnqueueDMAOp, Value, Value, DmaParameters const&, optional<DeviceAndCoreIds>, int)> const& perTileCallback) @ 0x13519480

c
// issueRetiledTransfer — @ 0x13519480
LogicalResult issueRetiledTransfer(b, op, src, dst, length, sflag, dynShape,
                                   ..., numTiles, deviceIds, ..., perTileCallback):
    getDMATiling(b, op, src, dst, srcLayout, dstLayout, outStrides, elemsPerStride)  // §7 @0x135195fa
    getTiledDynamicShape(staticShape, dynShape, ...)            // @0x13516840 — resolve dynamic extents
    totalTiles = product(outStrides) * elementBitwidth          // vpmulld @0x13519780 ; imul @0x135197fd

    for each tile dim with tile-stride s:                       // @0x135199bb..0x13519ae0
        c0    = $_0(s)                                          // i32 ConstantOp @0x1351a7c0
        inner = createOrFold<arith::RemSIOp>(idx, c0)           // idx % s  @0x1351ad20
        c1    = $_0(...)
        outer = createOrFold<arith::DivSIOp>(idx, c1)           // idx / s  @0x1351af20
    scale strides by element size:  imul stride, elemBits       // @0x13519b53 + vpmuludq @0x13519bb0
    perTileBase = createOrFold<arith::MulIOp>(tileIndex, tileStride)  // @0x1351ab00

    for each tile:
        // invoked via std::function::operator()  call [rax+0x18]  @0x1351a067 / @0x1351a106
        perTileCallback(b, op, perTileSrcBase, perTileDstBase,
                        DmaParameters, deviceIds, tileIndex)
        // DmaParameters predicate queried via call [rax+0x10]  @0x13519990

    // MULTI-TILE FALLBACK: issueRolledTransfer for the rolled (loop) form
    // @0x13516ca0 / @0x1351a1ea
    return success
```text

在 `lowerEnqueueDma` 路径(§9)中,逐 tile callback 是包裹 `lowerEnqueueDma::$_0`(policy func `0x13516720`)的 `std::function`,这个 closure 每个 tile 调用一次 `issueContiguousTransfer`(§6)。当 tile grid 大到 unrolling 会导致代码尺寸爆炸时,该路径会 fallback 到 `issueRolledTransfer` (`0x13516ca0`),后者发出 loop 或 rolled multi-stride 描述符;该 emitter 归 [Rolled / Strided / General Emitters](rolled-strided-general.md) 负责。

> **NOTE —** `getTiledDynamicShape` (`0x13516840`) 和 `DmaParameters` struct(第 5 个 callback 参数,其 predicate 通过 `0x13519990` 处的 `[rax+0x10]` 查询)只在签名中命名,其函数体/字段布局****在此解码(LOW)。它们是逐 tile 参数包中 runtime-shaped 的一半。

---

## 9. `lowerEnqueueDma` — 阶段 2 Dispatch

### 目的

这是 deferred DMA 的消费者:`ExpandTiledMemRefsPass` 为 bridged `tpu.enqueue_dma` 注册的 conversion pattern,它把整条 datapath 连接起来并擦除原始 op。它闭合了 [LowerToMlo bridge-cast](../compiler/lower-to-mlo-dma-bridge.md) 打开的循环;阶段 1 将 op 停放在 `sc.unlowering` cast 后面,正是因为*这个*函数需要只有 `ExpandTiledMemRefsPass` 拥有的 tile layout(`expandTiledIndices` / `getDMATiling`)。

### 算法

`lowerEnqueueDma(EnqueueDMAOp, EnqueueDMAOpAdaptor, ConversionPatternRewriter&)` @ `0x135105a0`。dispatch 顺序已按反编译字节确认:`getVerifiedDmaShapes`、`getTransferKind<EnqueueDMAOp>`、`getRemoteDeviceAndSparseCoreIds<EnqueueDMAOp>`、`issueRetiledTransfer` vs `get_transfer_size_bytes`+`issueContiguousTransfer` 分支,然后 `eraseOp`。

```c
// lowerEnqueueDma — @ 0x135105a0  (the ExpandTiledMemRefs stage-2 consumer)
LogicalResult lowerEnqueueDma(EnqueueDMAOp op, adaptor, ConversionPatternRewriter& rw):
    getVerifiedDmaShapes(b, locGen, op, srcMemref, dstMemref, ...)   // @0x13509dc0 — transfer-compatible?
    kind     = getTransferKind<EnqueueDMAOp>(target, srcSpace, dstSpace)  // @0x135114a0 → kDma/kStream
    deviceIds= getRemoteDeviceAndSparseCoreIds<EnqueueDMAOp>(b, locGen, op, src, dst)  // @0x13511660

    if memref is TILED:                                  // needs per-tile expansion
        issueRetiledTransfer(b, op, src, dst, len, sflag, dynShape,
                             ..., deviceIds, ..., /*callback=*/$_0)  // @0x13510e97
                             // $_0 = lowerEnqueueDma::$_0 (0x13516720) wrapping issueContiguousTransfer
    else:                                                // CONTIGUOUS
        sizeBytes = get_transfer_size_bytes(b, locGen, mixedShape, memref)  // @0x13511be0
        issueContiguousTransfer(b, op, src, dst, sizeBytes, sflag,
                                ..., deviceIds, kind, ...)              // @0x13510fbb

    rw.eraseOp(op)                                       // @0x1c951760 — original tpu.enqueue_dma erased
    return success

端到端轨迹

text
  STAGE 1 — LowerToMlo (ModuleOp pass)   [../compiler/lower-to-mlo-dma-bridge.md]
    tpu.enqueue_dma %src, %dst, %sflag    (tiled MemRef operands)
      └─ parked behind builtin.unrealized_conversion_cast {sc.unlowering}
         + op tagged sc.unlowered
  ── applyFullConversion succeeds, bridge intact ──
  STAGE 2 — ExpandTiledMemRefsPass (tile layout now fixed)   [THIS PAGE]
    lowerEnqueueDma  0x135105a0
      ├─ getVerifiedDmaShapes / getTransferKind / getRemoteDeviceAndSparseCoreIds
      ├─ TILED  → issueRetiledTransfer 0x13519480
      │            ├─ getDMATiling 0x13518660           (retile reshape)
      │            └─ per tile: $_0 → issueContiguousTransfer 0x1350a3e0
      └─ CONTIG → issueContiguousTransfer 0x1350a3e0
                    ├─ kDma    → DmaSimpleStartOp::create   0x145b9740  (slot 0x6)
                    └─ kStream → LinearStreamStartOp::create 0x145e3440 (slot 0x3b)
      └─ eraseOp(op)  0x1c951760
    (the underlying memref type is de-tiled by expandTiledMemRef 0x134e16e0;
     index operands by expandTiledIndices 0x134e1f20)
```text

---

## 本页不包含的内容

- **阶段 1 bridge 机制** — `sc.unlowering` / `sc.unlowered` 标记、逐操作数选择性 cast、`ConversionTarget` legality predicates,以及 `substituteUnloweringConversionCastOp`,归 [LowerToMlo DMA Bridge-Cast](../compiler/lower-to-mlo-dma-bridge.md) 负责。本页从该页面结束处开始:`lowerEnqueueDma`。
- **描述符字段布局** — `(mem_id, core_id)` 多态 memory-space 枚举、`Src`/`Dst` opcode 枚举、`(length, length_granule)` size pair,以及 `…StartOp::create` 调用填充的 `+0x18`..`+0x5c` 字段范围,归 [Intra-Chip DMA Descriptor](intra-chip-descriptor.md) 负责。
- **Rolled / strided / general 传输体** — `issueRolledTransfer` (`0x13516ca0`)、`DmaGeneralStartOp` / `DmaSingleStridedStartOp` emitters,以及 multi-stride loop-nest,归 [Rolled / Strided / General Emitters](rolled-strided-general.md) 负责。本页标识 `issueRolledTransfer` 是 multi-tile fallback,但不解码它。
- **Simple-vs-Strided 选择** — 决定一个传输使用哪种描述符变体的 `DmaParameters` predicate,归 [DmaParameters Selector](dma-parameters-selector.md) 负责。
- **`getTiledDynamicShape` 和 `DmaParameters` struct layout** — runtime-shaped tiling 半边;已定位(`0x13516840`;predicate at `0x13519990`)但未解码,上文标为 LOW。

---

## 交叉引用

- [LowerToMlo DMA Bridge-Cast](../compiler/lower-to-mlo-dma-bridge.md) — 阶段 1 producer:`tpu.enqueue_dma` 如何停放在 `sc.unlowering` bridge-cast 后面,并由*本* pass 通过 `lowerEnqueueDma` 消费
- [Intra-Chip DMA Descriptor](intra-chip-descriptor.md) — 本页发出的 `DmaSimpleStartOp` / `LinearStreamStartOp` 最终填充的描述符记录:`mem_id`/`core_id` enums、opcode enums、field layout、slot opcodes
- [Rolled / Strided / General Emitters](rolled-strided-general.md) — `issueRolledTransfer` 以及 retiled 路径 fallback 到的 `DmaGeneralStartOp` / `DmaSingleStridedStartOp` transfer bodies
- [DmaParameters Selector](dma-parameters-selector.md) — Simple-vs-Strided `DmaParameters` selection,用来分类逐 tile callback 发出的每个 transfer