Tile-Index 展开
本页中的所有地址、符号和偏移均适用于来自
libtpu-0.0.40-cp314wheel 的libtpu.so(构建libtpu_lts_20260413_b_RC00,build-id md589edbbe81c5b328a958fe628a9f2207d,781,691,048 字节)。该映像未剥离符号;反混淆后的 C++ 符号名按原文引用。.textVMA 等于文件偏移(.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 primitives —
expandTiledMemRef(tiledMemRefType→ flat stridedMemRefType)、getExpandedShape(d → (ceil(d/t), t))、getExpandedStrides(inner dims 上的 row-major 后缀乘积),以及索引孪生expandTiledIndices(通过arith.divui/arith.remui做i → (i/t, i%t),含vector<i32>广播变体)。 - Transfer issuers —
issueContiguousTransfer(零索引 whole-memref bulk move,发出一个DmaSimpleStartOp或一个LinearStreamStartOp)、getDMATiling(retiling reshape),以及issueRetiledTransfer(逐 tile 描述符循环,其中 signedDiv/Rem/Mul是索引代数的 retiled 孪生)。 - Deferred-DMA consumption —
lowerEnqueueDma,阶段 2 dispatch,将getTransferKind连接到 retiled-vs-contiguous 分支并擦除原始 op。
本页不负责:阶段 1 的 bridge-cast 标记机制和 ConversionTarget(LowerToMlo DMA Bridge-Cast);描述符字段布局、mem_id/core_id 和 slot opcode 枚举(Intra-Chip DMA Descriptor);issueRolled* / issueGeneral* 传输体发射器(Rolled / Strided / General Emitters);Simple-vs-Strided 选择器(DmaParameters Selector)。
| Pass | xla::tpu::sparse_core::ExpandTiledMemRefsPass(pattern install …::populateTpuPatterns @ 0x134e7700) |
| 来源溯源 | .rodata 字符串 platforms/xla/sparse_core/mlo/passes/expand_tiled_memrefs_pass.cc(expandTiledMemRef 中第 230 行) |
| 类型 de-tiling | xla::tpu::sparse_core::(anonymous namespace)::expandTiledMemRef(mlir::MemRefType) @ 0x134e16e0 |
| Shape de-tiling | free mlir::tpu::(anonymous namespace)::getExpandedShape(ArrayRef<long>, ArrayRef<xla::Tile>, bool) @ 0x14aa7dc0;wrapper TiledLayoutAttr::getExpandedShape @ 0x14aa7cc0 |
| Stride de-tiling | mlir::tpu::TiledLayoutAttr::getExpandedStrides() @ 0x14aa7400 |
| Index de-tiling | xla::tpu::sparse_core::(anonymous namespace)::expandTiledIndices(ValueRange, ArrayRef<xla::Tile>, Location, OpBuilder&) @ 0x134e1f20 |
| Contiguous issuer | mlir::tpu::LowerMemrefToMlo::issueContiguousTransfer @ 0x1350a3e0 |
| Retiling reshape | mlir::tpu::LowerPassBase::getDMATiling(...) const @ 0x13518660 |
| 逐 tile 循环 | mlir::tpu::LowerPassBase::issueRetiledTransfer(...) @ 0x13519480 |
| 阶段 2 dispatch | mlir::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 stride | stored outer strides ++ suffix-product(inner) | getExpandedStrides @ 0x14aa7400 (imul chain) |
tiled MemRefType | flat strided MemRefType | expandTiledMemRef @ 0x134e16e0 |
逻辑索引 i | (i/t outer, i%t inner) | expandTiledIndices @ 0x134e1f20 (divui/remui) |
vector<i32> 索引 | broadcast(const i32 t),然后 divui/remui | expandTiledIndices 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) 的ArrayRefstride 读取它们。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 处的函数体已按反编译字节确认,包括断言携带的源码文件名和行号。
// 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 resultceiling-division 是标准的 (setne bl; sub d,bl; div t; add bl) 惯用法,也就是当 d > 0 时为 (d-1)/t + 1。kDynamic (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:
// 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 resultGOTCHA — 替换后的布局是
[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先物化为i32arith.constant,再通过vector.broadcastsplat 到索引 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 发出arithops;其完整函数体未在此解码(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) @ 0x1350a3e0。GetMemorySpace×2、零索引 ConstantIndexOp(0)、sc.inside_trace_region 探测、DivUIOp stream-length divide、TraceLocalDma 以及两个 …StartOp::create 调用均已在反编译中确认。
// 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 successoutStrides 供给 SingleStrided / GeneralDma stride 操作数;outElemsPerStride 供给 elements_per_stride。canReinterpretToUntiledMemref gate 将 retiling 限制在能 flatten 为物理 MXU sublane×lane tile geometry 的 contiguous 侧(SublaneCount @ 0x1d60f300,LaneCount @ 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/Mul 是 expandTiledIndices 中 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。
// 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端到端轨迹
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