Skip to content

DmaParameters 选择器

本页所有地址均适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(构建 libtpu_lts_20260413_b_RC00,build-id md5 89edbbe81c5b328a958fe628a9f2207d)。该镜像剥离符号;反混淆后的 C++ 符号名按原样引用。.text VMA 等于文件偏移(.text 基址 0xe63c000)。所有 +0xNN 偏移都进入 DmaParameters 包(内存中的 C++ 结构体,不是线描述符)。其他版本会不同。

摘要

一旦 LowerPassBase 将一个平铺的 enqueue_dma 拆成逐 tile 传输,每个 tile 都会交给一个单独的逐 tile 发射器LowerMemrefToMlo::issueStridedTransfer0x1350cb60lower_memref_to_mlo.cc)。它的工作是选择能够表达该 tile 访问模式的成本最低的具体 LLO DMA-start op。该决策是一个二维选择器:一条轴是 TransferKindkDma vs kStream),另一条轴是 tile 有多少级 stride,直接从 DmaParameters::src_byte_strides.size()+0x18)读取。零级 stride 表示平坦连续区间(DmaSimpleStart / LinearStream 快路径);一级 stride 表示规则的二维矩形(DmaSingleStridedStart / StridedStream);超过一级表示需要完整描述符的不规则 gather(GeneralDma,或者对于 stream 是硬错误,因为 stream 最多支持一级 striding)。

只根据 src_byte_strides.size() 选择形式会很幼稚:一个 4-D tile 的内侧三个维度如果恰好是 row-major 连续的,应该发射为平坦拷贝,而不是 4 级 general DMA。因此选择之前会先运行一个维度合并优化器,该优化器位于调用方 LowerPassBase::issueRolledTransfer0x13516ca0lower_pass_base.cc:123)内部。它从最内侧维度向外遍历逐维 stride 数组,并扁平化任意相邻对 (d, d+1),只要该对退化(内侧 extent 是常量 1),或者确实是 row-major 连续(外侧 stride 正好等于内侧 stride 乘以外侧 extent,并分别对 source 和 destination 检查)。每次合并都会通过 arith.muli 将两个维度折叠成一个更大的 stride,用 memmove 将三个并行数组各缩短一项,并记录 VLOG(1) "Flattening striding dimension <d>"。合并先运行;只有未能扁平化的残余维度会成为逐 tile 的 DmaParameters,随后选择器据此分派。净效果是:传输越连续,编译器落到的描述符形式越便宜。

本页负责两件事:issueStridedTransfer 内部的形式选择规则TransferKind × src_byte_strides.size() 的 3×2 网格、入口检查强制的 DmaParameters 大小不变量、stream gather/scatter 门控,以及 length→granularity 除数),以及维度合并优化器(反向相邻对扫描和精确的连续性合并谓词)。六个发射器主体,也就是每个 ::create 如何将操作数绑定到描述符,见 Rolled / Strided / General 发射器;描述符字段布局和 memory-space/opcode 枚举见 片上 DMA 描述符;构造 DmaParameters 数组的 tile-index→stride 代数见 Tile-Index 展开

重新实现时,契约是:

  • 3×2 选择网格TransferKindkDma=0 / kStream=1 / 其他→OpError)与 src_byte_strides.size()0 / 1 / >1)交叉,映射到六个具体 SparseCore op 加两个错误分支。
  • DmaParameters 大小不变量|src_byte_strides| == |tgt_byte_strides| == |steps_per_stride| - 1,由 strided 分支入口的两个 CHECK_EQ 强制,并且形式根据 |src_byte_strides| 选择。
  • 合并谓词:当且仅当 inner_extent == 1 或者outer_base 是编译期常量 并且 每侧 stride[outer] == stride[inner] × outer_extent)时折叠 (d, d+1),并执行 arith.muli + 3 次 memmove 折叠。
  • stream 门控kStream 传输最多只能携带一级 stride(两个不同的 <= 1 断言,每侧一个);gather 不允许 destination 带 stride;scatter 不允许 source 带 stride。
逐 tile 发射器(选择器)mlir::tpu::LowerMemrefToMlo::issueStridedTransfer(OpBuilder&, EnqueueDMAOp, Value src, Value dst, DmaParameters const&, optional<DeviceAndCoreIds>, int priority, …::TransferKind, bool) @ 0x1350cb60
合并优化器mlir::tpu::LowerPassBase::issueRolledTransfer(…) @ 0x13516ca0 — 合并循环主体,lower_pass_base.cc:123
逐侧 stride 验证器issueStridedTransfer::$_0::operator()(ArrayRef<int>) @ 0x13510340
general 形式 helpermlir::tpu::(anonymous namespace)::issueGeneralDma(…) @ 0x1350b3a0
常量整数探测LowerPassBase vtable +0x20(*(this)+0x20)(this, value))— getConstantIntValue 风格的 fold
形式键DmaParameters::src_byte_strides.size() @ +0x18
源文件lower_memref_to_mlo.cc(选择器),lower_pass_base.cc(合并)
证据等级重新实现级 / 经 IDA 反编译字节确认(issueStridedTransfer 主体,issueRolledTransfer 合并循环)

1. 选择网格(TransferKind × stride-count

目的

issueStridedTransfer 是抽象逐 tile 传输变成具体硬件 op 的唯一位置。它不决定哪些 tile存在(那是上游 rolled-loop 机制的工作),而是针对一个已经平铺好的传输决定描述符形式。具体形式正好有六种,每个 TransferKind 三种,而选择完全由两个整数决定:kind,以及 stride 级数。

两条轴

轴 1 — TransferKind 是第八个参数(一个字节,位于 int priority 之后)。分派是三路 switch:

TransferKind路径
kDma0DMA 形式(Simple / SingleStrided / General)
kStream1Stream 形式(Linear / Strided / 错误
(任何其他值)≥2emitOpError("Unsupported transfer kind: %d")

默认分支通过 absl::str_format_internal::FormatPack("Unsupported transfer kind: %d", 29, …) 将原始整数格式化进 InFlightDiagnostic,并返回失败的 LogicalResult。这是面向用户的错误,不是 CHECK,因为 kind 来源于正在 lowering 的 op。

轴 2 — stride 级数DmaParameters::src_byte_strides.size(),从包偏移 +0x18 读取为 u32。在每种 kind 内,选择器按 0 / 1 / >1 分支:

c
// issueStridedTransfer @ 0x1350cb60 — form selection (reconstructed)
n = p.src_byte_strides.size();        // *(u32*)(p + 0x18)
switch (kind) {
case kDma:                            // TransferKind == 0
    if (n == 0 && !force_descriptor)  // also gated by the bool arg / cross-device presence
        emit DmaSimpleStartOp;        // contiguous
    else if (n == 1)
        emit DmaSingleStridedStartOp; // one stride level
    else /* n > 1 */
        issueGeneralDma(...);         // multi-stride -> GeneralDma
    break;
case kStream:                         // TransferKind == 1
    if (n == 0)
        emit LinearStreamStartOp;
    else if (n == 1)
        emit StridedStreamStartOp;    // CHECK: steps_per_stride.size() == 2
    else /* n > 1 */
        // hard error: streams support <= 1 stride level (see §4)
    break;
default:
    return emitOpError("Unsupported transfer kind: %d", kind);
}
```text

### 3×2 网格

从 `(kind, src_byte_strides.size())` 到发射 op 的完整映射如下(每个 op 的操作数绑定见 [Rolled / Strided / General 发射器](rolled-strided-general.md),op 填充的描述符见 [片上 DMA 描述符](intra-chip-descriptor.md)):

| `src_byte_strides.size()` | `kDma` (0) | `kStream` (1) |
|---|---|---|
| **0**(连续) | `DmaSimpleStartOp::create` @ `0x145b9740` | `LinearStreamStartOp::create` @ `0x145e3440` |
| **1**(一级 stride) | `DmaSingleStridedStartOp::create` @ `0x145bcd20` | `StridedStreamStartOp::create` @ `0x1460b8e0` |
| **>1**(多级 stride) | `issueGeneralDma` @ `0x1350b3a0` → `GeneralDma` | **错误** — `"Streams support up to 1 level of striding"` |
| (否则 `TransferKind`) | `emitOpError("Unsupported transfer kind: %d")` | 同左 |

> **注意 —** `kDma`/`n==0` 的 Simple 快路径还会被尾随 `bool` 参数(`[rbp+0x38]`)以及 `optional<DeviceAndCoreIds>` 跨设备 id 的存在性额外门控:只要任一者被设置,连续情形就会被强制进入 Strided/General 分支,而不是采用 Simple 形态。门控算术已按字节确认;该标志的精确语义为 **HIGH**,与“is-remote / force-descriptor”角色一致(跨设备传输不能使用本地 Simple 描述符)。重新实现者可以将规则视为:*只有纯本地、零 stride 的 tile 才采用 Simple。*

### priority 前置条件

该函数做的第一件事就是断言其 `int priority` 参数为零:

```c
// @ entry — lower_memref_to_mlo.cc:1142
if (priority != 0)
    LogMessageFatal("priority == 0");   // MakeCheckOpString<long,long>

这是硬 CHECKabsl::log_internal::LogMessageFatal),不是可恢复错误;逐 tile 发射器只会以 priority 0 调用。priority 参数是为了匹配与更高层 enqueue 路径共享的 API 形状;在这里它必须为 0。


2. DmaParameters

目的

DmaParameters 是选择器消费的逐 tile 描述符,也就是传入的内存中 C++ 结构体(以 const& 传递),不同于它最终产生的线格式 OciDescriptorCommonIssuedFromTcs。它由 rolled-loop 机制逐 tile 构建(构造点在 issueRetiledTransfer;见 Tile-Index 展开),并在这里逐字段读取。选择器只读取它,从不修改它。

布局

字段名来自 CHECK_EQ 字符串字面量(p.src_byte_strides / p.tgt_byte_strides / p.steps_per_stride);偏移和访问宽度来自反编译读取:

偏移大小字段类型
+0x008srcmlir::Value
+0x088dstmlir::Value
+0x108src_byte_strides.data()int*(4-B 条目)
+0x184src_byte_strides.size()u32
+0x508tgt_byte_strides.data()int*(4-B 条目)
+0x584tgt_byte_strides.size()u32
+0x908transfer length / sflagmlir::Value
+0x988steps_per_stride.data()Value*(8-B 条目)
+0xA04steps_per_stride.size()u32

注意 — +0x90 是作为 len/sflag create 参数使用的 Value;精确字段名(transfer length 还是 sync-flag)不在任何 CHECK 字符串中,因此是推断。偏移、8 字节 Value 类型以及作为 create 参数的用途均由字节钉牢。

大小不变量

在进入 strided/general 分支时,两个 CHECK_EQ 会强制三个 vector 耦合(lower_memref_to_mlo.cc:843/:844):

c
CHECK_EQ(p.src_byte_strides.size(), p.tgt_byte_strides.size());
//   *(u32*)(p+0x18) == *(u32*)(p+0x58)
CHECK_EQ(p.src_byte_strides.size(), p.steps_per_stride.size() - 1);
//   *(u32*)(p+0x18) == *(u32*)(p+0xA0) - 1
```text

因此,格式良好的包拥有长度匹配的 source/target stride vector,并且 `steps_per_stride` 条目数正好比 stride 级数**多一**;`steps_per_stride` 是 `N` 级 stride 对应的 `N+1` 累积 extent vector。连续(`N=0`)描述符因此有 `steps_per_stride.size() == 1`。随后形式由 `N = src_byte_strides.size()` 选择:`0` 为 Simple/Linear,`1` 为 SingleStrided/Strided,`>1` 为 General/错误。这是整个 3×2 网格据以分派的唯一整数,也正因如此,会缩小 `N` 的合并 pass(§5)必须先运行。

---

## 3. Length → Granularity 除数

在构建 op 之前,传输长度会从字节转换为硬件*粒度*单位(描述符携带的是粒度计数,而不是原始字节计数;见 [片上 DMA 描述符](intra-chip-descriptor.md) 中的 `(length, length_granule)` 对)。两种 kind 使用不同的 granularity 来源:

- **`kDma`** 除以 `xla::jellyfish::Target::GranuleBytes()`(`0x1d617f80`),使用普通截断整数除法(`len / gran`;`(gran | len) >> 32` 测试只是在 64 位有符号和 32 位无符号 `idiv` 宽度之间选择,**不是**向上取整)。精确可整除性由 `isGuaranteedDivisible(…, 128, …)` 检查保证,失败时抛出 `emitOpError("Inner DMA transfer size divisible by DMA's inner vector length (%d). Got %s")`(通过 `:878` 处的 `MakeErrorImpl<3>` 构建),所以无需舍入。
- **`kStream`** 除以 `xla::tpu::sparse_core::xla_mlo_util::TransferGranularityInBytes(SparseCoreTarget const&, MemorySpace, bool)`(`0x14a89ea0`),通过 `target()->[+0x948]` 到达;这是 SPMEM-stripe / DMA-word 单位,取决于 memory space,同样使用普通截断除法。

granule 值物化为 `arith.ConstantIndexOp`;stream strided 路径将除法发射为 `DivUIOp`(`createOrFold<arith::DivUIOp>`),而 kDma 路径会将商内联为已 fold 的常量。HBM endpoint 测试(`srcSpace == HBM (4)` / `dstSpace == HBM (4)`,通过与 `4` 比较计算)会馈入 Linear/Strided stream `::create` 接收的 `dstIsHbm` bool

---

## 4. Stream Striding 门控

stream 比 DMA 更受限制:硬件 stream 引擎最多处理**一级** striding,并且 gather/scatter stream 会禁止其中一侧带 stride。`issueStridedTransfer` 会在发射 `LinearStream`/`StridedStream` 前强制全部三个约束。

### 一级限制(两个断言)

对于 `kStream` strided 路径,二进制按**每侧**断言级数边界,带有两个不同的 `CHECK` 和两个不同的诊断字符串:

```c
// lower_memref_to_mlo.cc:1189
CHECK(src_byte_strides.size() <= 1)
    else "Streams support up to 1 level of striding. Got %d levels of source striding.";
// lower_memref_to_mlo.cc:1193
CHECK(tgt_byte_strides.size() <= 1)
    else "Streams support up to 1 level of striding. Got %d levels of steps per stride.";

单 stride stream 发射本身还由 CHECK_EQ(off_tile_byte_strides.size(), 1):1263)和 CHECK_EQ(p.steps_per_stride.size(), 2):1264)守护,也就是正好一级 stride,以及匹配的两个累积 step。

陷阱 — “>1 stride”的 stream 情形是致命 CHECK不是 kDma >1 情形路由到 issueGeneralDma 时的可恢复 emitOpError。不存在“general stream”形式:必须为 stream 的多 stride 传输到达这里时已经是编译器 bug,因此二进制会 trap。重新实现者必须保证上游 tiler 永远不会把 >1-stride 包交给 kStream 分支(§5 中的合并正是让连续内侧维度满足该保证的机制)。

Gather / scatter 侧约束

mlir::tpu::isGather0x14afb1e0)分类 op 后,stream 分支会运行逐侧连续性移除 lambda $_00x13510340operator()(ArrayRef<int>)),source 侧和 destination 侧各一次。$_0 首先断言 CHECK_EQ(p.steps_per_stride.size(), 2):1208),读取 steps_per_stride+0x98),并通过 LowerPassBase vtable +0x20getConstantIntValue 探测折叠 steps_per_stride[1],也就是合并循环使用的同一个探测(§5)。当且仅当该级别折叠为编译期常量,且等于传入该侧的 byte-stride extent 时,它返回 true;也就是说,单 stride 级别描述的是连续区间,可以被丢弃。当它返回 true 时,调用方将该侧 stride count 置零,并记录 VLOG(1) "Removing the source striding representing a contiguous access pattern.":1220)或 target 侧变体(:1225)。随后分类器会基于残余逐侧 stride count 拒绝不对称情形:

条件诊断(emitOpError
isGather destination 带 stride"Gather streams do not support destination striding. Got %d level(s) of target striding."
isScatter source 带 stride"Scatter streams do not support source striding. Got %d level(s) of source striding."

gather 将分散的 source 元素拉入 packed destination,因此 destination 本质上是连续的;scatter 则相反。对“packed”侧加 stride 没有意义,并会以面向用户的错误拒绝。


5. 维度合并优化器

目的

选择器(§1)以 src_byte_strides.size() 为键,因此 tile 的 stride 级数越少,落到的形式越便宜。合并 pass 的存在是为了最小化该计数:它在包到达选择器之前,将每个连续(或退化)的相邻维度对折叠成一个更大的 stride。访问模式在数学上是平坦区间的传输会被扁平化到 N=0,并发射为 DmaSimpleStart,即使它最初用多个名义维度描述。合并位于调用方 issueRolledTransferlower_pass_base.cc)中;无法扁平化的残余 leading dim 会成为单个 scf.for 循环,内侧已扁平化维度则成为选择器消费的逐迭代 DmaParameters

反向相邻对扫描

issueRolledTransfer 维护三个并行逐维数组:bases(逐维 extent,8 字节 Value 条目)、src_byte_strides(4 字节 int)和 dst_byte_strides(4 字节 int),并带有三个独立计数。合并循环从最内侧维度向外遍历相邻对,索引 r15 → bases[outer]r12 = r15−8 → bases[inner]。对每一对,它都会通过常量整数探测((*(this)+0x20)(this, base))fold 两个 base,该探测返回打包为 { bit32 = present | low32 = value }optional<int64>

c
v47 = getConstantIntValue(bases[outer]);   // (*(a1)+0x20)(a1, &bases[outer])
v48 = getConstantIntValue(bases[inner]);    // same probe on the inner dim
```text

### 精确连续性合并谓词

当且仅当如下条件成立时,相邻对 `(outer d, inner d+1)` 会被折叠(字节精确,`issueRolledTransfer` 反编译第 317320 行;`0x100000001` = present-bit | value 1):

```c
if (   (v48 & 0x1FFFFFFFF) == 0x100000001                          // (i)
    || (v47 & 0x100000000) != 0                                    // (ii) gate
       && src_stride[outer] == (int)v47 * src_stride[inner]        // (ii) src contiguity
       && dst_stride[outer] == dst_stride[inner] * (int)v47 )      // (ii) dst contiguity
  • 条件 (i) — 退化内侧维度。 getConstantIntValue(bases[inner]) 存在等于 1。内侧 extent 为 1 不贡献迭代,因此该对总是 fold,无需检查 stride。((x & 0x1ffffffff) == 0x100000001 掩码在一次比较中测试“present value == 1”。)
  • 条件 (ii) — row-major 连续性。 getConstantIntValue(bases[outer]) 存在(编译期常量 extent,即 test r13, 0x100000000 present-bit 门控)并且按每侧,外侧 stride 等于内侧 stride 乘以外侧 extent:src_stride[outer] == src_stride[inner] × outer_extent dst_stride[outer] == dst_stride[inner] × outer_extent。这正是两个 row-major 维度无间隙相邻时的代数;它们描述一个更大的连续 stride。

若两者都不成立,该对会保持原样,并成为残余 stride/loop 维度。

步骤测试含义
av47 = getConstantIntValue(bases[outer])对外侧 extent 进行 vtable +0x20 fold
bv48 = getConstantIntValue(bases[inner])对内侧 extent 做同样 fold
c(v48 & 0x1ffffffff) == 0x100000001(i) 内侧 extent 是常量 1
dv47 & 0x100000000(ii) 门控:外侧 extent 是常量
esrc_stride[outer] == (int)v47 × src_stride[inner](ii) source 连续性
fdst_stride[outer] == dst_stride[inner] × (int)v47(ii) destination 连续性
MERGE(c) (d && e && f)折叠 (d, d+1) → 一个维度

注意 — 条件 (i) 是刻意的快路径,不是 (ii) 的子集:count-1 维度会在不查询任一侧 stride 的情况下 fold。当内侧 stride 为 0 时,(i) 是否在逻辑上被 (ii) 包含是推断;二进制中的两个分支目标不同,而 (i) 对退化维度是保持正确性的 (ii) 超集。

折叠

合并时,内侧 extent 会乘入外侧以形成合并后维度的 extent,诊断会被记录,三个数组会越过被消费的槽向下移动一位:

c
// merged extent
merged = OpBuilder::createOrFold<arith::MulIOp>(bases[outer], bases[inner]);
VLOG(1) << "Flattening striding dimension " << d;     // lower_pass_base.cc:123
bases[d] = merged;
memmove(bases + d*8 ...,         8 * (count - 1));     --bases_count;   // 8-B Value entries
memmove(src_strides + d*4 ...,   4 * (count - 1));     --src_count;     // 4-B int entries
memmove(dst_strides + d*4 ...,   4 * (count - 1));     --dst_count;     // 4-B int entries
```text

`arith.muli` 是 `createOrFold`(两个 `Value` 上的 `mlir::arith::MulIOp`),因此两个编译期常量 extent 会立即 fold 为常量。三个 `memmove` 以 lock-step 缩短并行数组(8 字节 bases、4 字节 source strides、4 字节 destination strides),并分别递减各自计数。随后扫描在缩短后的数组上继续。当它耗尽时,残余 leading dim 成为单个 `scf::ForOp`,扁平化后的包被交给逐 tile 回调,也就是交给 `issueStridedTransfer` 和 §1 的选择器。

---

## 6. 成本排序

选择器编码了一套成本层级:只要访问模式允许,就优先选择更便宜的描述符。从最便宜到最昂贵:

| 排名 | 形式(`kDma` / `kStream`) | 选择时机 | 成本依据 |
|---:|---|---|---|
| 1 | `DmaSimpleStart` / `LinearStream` | `src_byte_strides.size() == 0`(且本地) | 一个连续区间;无 stride 操作数,描述符最小 |
| 2 | `DmaSingleStridedStart` / `StridedStream` | `src_byte_strides.size() == 1` | 一级 stride;单个二维矩形,四个额外 stride `Value` |
| 3 | `GeneralDma`(仅 `kDma`) | `src_byte_strides.size() > 1` | 多级 / 跨 core;完整 stride list + 双侧 sync |
|| 错误 | `kStream` 带 `> 1` 级,或未知 kind | 无法表示为单个 op |

合并(§5)是将传输*上移*到该排序中更便宜位置的机制:通过扁平化连续维度,它将 `src_byte_strides.size()` 推向 0,因此同一个逻辑传输会用其实际内存布局允许的最便宜形式发射。选择规则本身随后只是对合并后计数做平坦查表;没有成本模型或搜索,计数*就是*成本类别。

---

## 7. 诊断字符串

两个函数可能打印的所有字符串,以及 trap 种类和源码位置。`CHECK`/`LogMessageFatal` 是*编译器内部*不变量(违反表示上游 tiler 中的 bug);`emitOpError` 是*面向用户*的(违反表示输入 op 无法表示)。`VLOG(1)` 是信息性日志。

| 字符串 | 种类 | 位置 | 触发条件 |
|---|---|---|---|
| `priority == 0` | `CHECK`(致命) | `lower_memref_to_mlo.cc:1142` | 入口处 `priority != 0` |
| `p.src_byte_strides.size() == p.tgt_byte_strides.size()` | `CHECK_EQ`(致命) | `:843` | source/target stride vector 长度不同 |
| `p.src_byte_strides.size() == p.steps_per_stride.size() - 1` | `CHECK_EQ`(致命) | `:844` | 对 `N` 个 stride,`steps_per_stride` 不是 `N+1` |
| `off_tile_byte_strides.size() == 1` | `CHECK_EQ`(致命) | `:1263` | 单 stride stream 的 off-tile stride 数不等于 1 |
| `p.steps_per_stride.size() == 2` | `CHECK_EQ`(致命) | `:1264` | 单 stride stream 的 cumulative step 数不等于 2 |
| `src_byte_strides.size() <= 1` | `CHECK`(致命) | `:1189` | stream 上 source stride 级数 >1 |
| `tgt_byte_strides.size() <= 1` | `CHECK`(致命) | `:1193` | stream 上 target stride 级数 >1 |
| `Unsupported transfer kind: %d` | `emitOpError` | (kind 分派默认分支) | `TransferKind` ∉ {`kDma`, `kStream`} |
| `Streams support up to 1 level of striding. Got %d levels of source striding.` | `emitOpError` | (stream 门控) | stream source stride 过多 |
| `Streams support up to 1 level of striding. Got %d levels of steps per stride.` | `emitOpError` | (stream 门控) | stream target stride 过多 |
| `Gather streams do not support destination striding. Got %d level(s) of target striding.` | `emitOpError` | (gather 门控) | `isGather` 且 destination 带 stride |
| `Scatter streams do not support source striding. Got %d level(s) of source striding.` | `emitOpError` | (scatter 门控) | `isScatter` 且 source 带 stride |
| `Flattening striding dimension <d>` | `VLOG(1)` | `lower_pass_base.cc:123` | 合并 pass 合并了维度对 `(d, d+1)` |

---

## 8. 函数映射

| 地址 | 符号 | 角色 |
|---|---|---|
| `0x1350cb60` | `LowerMemrefToMlo::issueStridedTransfer` | 逐 tile 选择器(本页 §1–§4|
| `0x13510340` | `issueStridedTransfer::$_0::operator()(ArrayRef<int>)` | 逐侧 stream stride-level 验证器(§4|
| `0x13516ca0` | `LowerPassBase::issueRolledTransfer` | 承载合并 pass 的 rolled-loop 调用方(§5|
| `0x1350b3a0` | `(anonymous)::issueGeneralDma` | `>1`-stride / 跨 core 的 `GeneralDma` 发射器 |
| `0x145b9740` | `DmaSimpleStartOp::create` | 连续 DMA 发射器 |
| `0x145bcd20` | `DmaSingleStridedStartOp::create` | 单 stride DMA 发射器 |
| `0x145e3440` | `LinearStreamStartOp::create` | 连续 stream 发射器 |
| `0x1460b8e0` | `StridedStreamStartOp::create` | 单 stride stream 发射器 |
| `0x14afb1e0` | `mlir::tpu::isGather` | gather/scatter 分类器(§4|
| `0x1d617f80` | `Target::GranuleBytes` | `kDma` length 除数(§3|
| `0x14a89ea0` | `xla_mlo_util::TransferGranularityInBytes` | `kStream` length 除数(§3|
| `0xfaaae00` | `OpBuilder::createOrFold<arith::MulIOp, Value&, Value&>` | 合并 base-merge(§5|
| `LowerPassBase` vtable `+0x20` | (抽象)`getConstantIntValue` 探测 | `$_0` 和合并循环共同使用的 constant-fold |

---

## 交叉引用

- [Rolled / Strided / General 发射器](rolled-strided-general.md) — 六个 `::create` 发射器主体(`DmaSimpleStart` / `DmaSingleStridedStart` / `issueGeneralDma` / `LinearStream` / `StridedStream`),以及各自如何将 `DmaParameters` 操作数绑定进描述符
- [片上 DMA 描述符](intra-chip-descriptor.md) — `OciDescriptorCommonIssuedFromTcs` 线布局、`(mem_id, core_id, opcode)` endpoint 枚举,以及 granularity 除数(§3)馈入的 `(length, length_granule)` size 对
- [Tile-Index 展开](tile-index-expansion.md) — `ExpandTiledMemRefs` / `expandTiledIndices`,它们构造此选择器据以分派的 `DmaParameters` stride 数组
- [Continuation Queue](continuation-queue.md) — 已发射 DMA/stream op bump 的运行时 SFLAG completion 协议
- [返回索引](../index.md) — Part X — On-Chip Memory & DMA / DMA