LowerToSparseCoreLlvm
本页中的所有地址、符号和偏移都从
libtpu-0.0.40-cp314wheel 中的libtpu.so逐字节读取(构建libtpu_lts_20260413_b_RC00,BuildID md589edbbe81c5b328a958fe628a9f2207d,781,691,048 字节,未 stripped,.textVA == 文件偏移)。.symtab未 stripped;每个结论都锚定到一个 demangled 符号或反编译函数体。其他版本会不同 — 请把每个 VA 都视为版本绑定。
摘要
LowerToSparseCoreLlvmPass 是 SparseCore 路径上的终端 MLIR lowering:它接收一个仍携带 mlir::sparse_core (ScDialect) op 表面的模块 — gather/scatter stream、circular-buffer register、sync-flag wait、simple DMA、EUP transcendental、address-space cast — 并把它降到 llvm 方言加 SparseCore 专属的 llvm_tpu intrinsic 方言(记录在 LlvmTpu Intrinsic Catalog 中的 1356 个 intrinsic tpu_* 目录)。它是 TensorCore tpu → LLO ODS 下降的 SparseCore 对应物,也是 LowerToMlo DMA bridge 已经展开 tiled memref 之后紧接着的阶段。上游 MLIR convert-to-llvm 会把 arith/memref/func 降到 LLVM,而此 pass 添加约 118 个 SparseCore *OpLowering conversion pattern,为每个 ScDialect op 选择正确的 tpu_* intrinsic,全部依托 SCTypeConverter 构建的类型映射。
熟悉 MLIR DialectConversion 的读者应记住一个结构事实:这不是一次 applyFullConversion,而是 runOnOperation (0x13566d00) 内部的三子阶段驱动器。首先,一个独立的 scf → cf lowering 在整个模块上运行(无类型转换);然后逐个 func::FuncOp 执行 ScDialect → llvm/llvm_tpu 转换(118 个 pattern 和 SCTypeConverter 就在这里,位于 lowerFunc 0x13568280 内);最后逐个 LLVM::LLVMFuncOp 执行 assert + memref-finalize pass,清理 cf.assert 和任何残留 memref op(lowerAsserts)。lower-scf-to-cfg-only 测试标志可在第一个子阶段后停止。本页负责说明按类划分的重写函数体(matchAndRewrite 代数:操作数映射、属性过滤、intrinsic 选择、replaceOp)、scf→cfg lowering(lowerScfToCfg,带两个自定义 ForLowering/IndexSwitchLowering pattern)以及 pass 驱动器自身(三个子阶段、工厂、conversion target)。
本页有意不重新推导的部分,因为各有同级页面负责:address-space-ID → !llvm.ptr<N> 类型映射和 sequencer-context flatten、CheckAddressSpaces 合法性门,以及 42 个实例的 EUP 名册都位于 SCTypeConverter;完整 AS-id ↔ MemorySpace ↔ pool 表位于 Fat Pointers (AS7/8/9);按 cast 的 addrspacecast 指令选择位于 addrspacecast ISel。本页记录重写函数体如何消费转换后的类型并到达 intrinsic。
对于重新实现,契约是:
- 三子阶段驱动器。
scf→cf(模块范围、无类型)→ 逐函数ScDialect→llvm/llvm_tpu(有类型)→ 逐函数 assert/memref-finalize。顺序是强制的 —scfop 必须先变为控制流, typed conversion 才能运行;cf.assert必须存活到 LLVM-func 子阶段。 - 统一重写形态。 每个
*OpLowering::matchAndRewrite都是“把操作数解析为 LLVM value →FilterLLVMAttributes(丢弃access_groups)→ 按 dtype/memspace/predicate 选择叶子tpu_*intrinsic →tpu_X::create(b, loc, …)→replaceOp”。六个代表性函数体已逐字节解码。 - 各类分发键。 DMA = (srcMemSpaceID, dstMemSpaceID) 元组表;stream = (dtype, off-tile memspace, verb) lambda 表;wait = comparison-predicate attr;sync = local/tile/remote attr;addrspacecast =
convertType(src) == convertType(dst)省略或失败。
| Pass 类 | xla::tpu::sparse_core::(anon)::LowerToSparseCoreLlvmPass(ODS 基类 mlir::sparse_core::impl::LowerToSparseCoreLlvmBase) |
| Pass 种类 / 作用域 | builtin.module pass(对象大小 0x298) |
| 驱动器 | LowerToSparseCoreLlvmPass::runOnOperation @ 0x13566d00 |
| 工厂 | CreateLowerToSparseCoreLlvmPass(Target&, InlinedVector<long,4>, SparseCoreConfig&, DebugInfoTracker*) @ 0x135667c0 |
| scf→cfg 子阶段 | lowerScfToCfg(collector lambda 0x13572640);自定义 ForLowering 0x135707c0、IndexSwitchLowering 0x13571ec0 |
| 逐函数 typed 子阶段 | LowerToSparseCoreLlvmPass::lowerFunc(func::FuncOp) @ 0x13568280 |
| assert/memref 子阶段 | lowerAsserts(遍历 LLVM::LLVMFuncOp);AssertOpLowering::matchAndRewrite @ 0x135b7080 |
| 仅测试标志 | lower-scf-to-cfg-only ("Only lower scf ops to cf ops (for testing)",42 chars) |
| 属性过滤器 | FilterLLVMAttributes @ 0x135b7a20 — 丢弃 access_groups(13 chars) |
| Pattern 数量 | 约 118 个 *OpLowering 类(模板族会实例化出更多) |
| 置信度 | CONFIRMED(反编译锚定),除非行或标注另有说明 |
Pass 驱动器 — 三个子阶段
目的
runOnOperation (0x13566d00) 是一个 builtin.module pass 驱动器,而不是单一 conversion。它按顺序运行三轮不同的 applyFullConversion,每轮都有自己的 ConversionTarget 和 pattern 集合,并由 IR walk 分隔。之所以拆分,是因为这三轮作用于三个不同的 op 宇宙 — 结构化控制流 (scf)、ScDialect op 表面(逐个 func.func)以及最终的 llvm.func 函数体 — 且每轮都需要干净的 legalizer 状态。把它们折叠成一次 applyFullConversion 的重新实现者会让 legalizer 死锁:typed ScDialect pattern 期望控制流已经 lowering,assert/memref finalize 期望 SC op 已经消失。
入口点
RegisterAllPhases (0xf849ec0) → Phase 2a (SparseCore custom-call path)
└─ CreateLowerToSparseCoreLlvmPass (0x135667c0) ── builds the pass object (0x298 B)
└─ runOnOperation (0x13566d00) ── module pass driver
├─ [substage 1] lowerScfToCfg ── scf → cf, module-wide, untyped
│ ForLowering (0x135707c0) ── scf.for → cf branches (benefit 2)
│ IndexSwitchLowering (0x13571ec0) ── scf.index_switch → cf (benefit 2)
│ populateSCFToControlFlowConversionPatterns ── upstream scf.{if,while,parallel}
├─ [substage 2] per func::FuncOp: lowerFunc (0x13568280)
│ SCTypeConverter + ~118 *OpLowering patterns → llvm / llvm_tpu
└─ [substage 3] per LLVM::LLVMFuncOp: lowerAsserts
AssertOpLowering (0x135b7080) + populateFinalizeMemRefToLLVMConversionPatterns
```text
### 算法
```c
function runOnOperation(pass): // 0x13566d00
module = pass.getOperation() // builtin.module
ctx = module.getContext()
// ---- substage 1: scf -> cf, module-wide, NO type conversion ----
scfPatterns = RewritePatternSet(ctx)
scfPatterns.add<ForLowering>(benefit=2) // 0x135707c0 "scf.for"
scfPatterns.add<IndexSwitchLowering>(benefit=2) // 0x13571ec0 "scf.index_switch"
populateSCFToControlFlowConversionPatterns(scfPatterns) // upstream scf.{if,parallel,while}
frozen1 = FrozenRewritePatternSet(scfPatterns)
// collect every scf.{for,if,parallel,while,index_switch} op into a worklist
worklist = []
module.walk(lowerScfToCfg_collect) // callback 0x13572640 (lambda #1)
for op in worklist: // one applyFullConversion PER root op
target = ConversionTarget(ctx)
target.addIllegalOp("scf.for") // setOpAction(..., 1) via lambda #2
target.addDynamicallyLegalOp<scf.{If,Parallel,While,IndexSwitch}>(...)
target.markOpRecursivelyLegal("scf.for","scf.if","scf.parallel","scf.while","scf.index_switch")
target.markUnknownOpDynamicallyLegal(lambda #3) // setLegalityCallback (catch-all legal)
if applyFullConversion(op, target, frozen1) != success:
pass.signalPassFailure(); return // sets bit 2 of pass flags
if pass.lowerScfToCfgOnly: // test flag, *(pass+456)
return // stop after scf->cf
// ---- substage 2: per-func ScDialect -> llvm / llvm_tpu (TYPED) ----
pass.target = SparseCoreTargetForModule(pass.cfg, module) // xla_mlo_util, 0x...
for fn in module.getOps<func::FuncOp>():
if !lowerFunc(pass, fn): // 0x13568280
pass.signalPassFailure(); return
// ---- substage 3: per-llvm.func assert + memref finalize ----
if module.walk<LLVM::LLVMFuncOp>(lowerAsserts) == interrupt:
opts = LowerToLLVMOptions(ctx); opts.useBarePtrCallConv = true
opts.dataLayout = DataLayout("…-S64") // 64-bit stack alignment
tc = LLVMTypeConverter(ctx, opts)
tc.registerTypeAttributeConversion( // SC memory-space attr -> i64 addrspace
lowerAsserts_memSpaceLambda) // (no sequencer flatten; see SCTypeConverter)
assertPatterns = RewritePatternSet(ctx)
assertPatterns.add<AssertOpLowering>(benefit=1, // 0x135b7080 "cf.assert"
granuleMask = -1 << (log2(stackBytes) - log2(target.GranuleBytes())))
populateFinalizeMemRefToLLVMConversionPatterns(tc)
llvmTarget = LLVMConversionTarget(ctx)
llvmTarget.addLegalDialect("llvm_tpu") // setDialectAction, 1
llvmTarget.addIllegalOp("builtin.module") // setOpAction, 0
llvmTarget.addDynamicallyLegalOp("llvm.alloca", "builtin.unrealized_conversion_cast")
if applyFullConversion(module, llvmTarget, assertPatterns) != success:
pass.signalPassFailure()
return函数映射
| 函数 | VA | 角色 |
|---|---|---|
runOnOperation | 0x13566d00 | 三子阶段驱动器 |
CreateLowerToSparseCoreLlvmPass | 0x135667c0 | 工厂;接收 Target&、InlinedVector<long,4>、SparseCoreConfig&、DebugInfoTracker* |
lowerFunc | 0x13568280 | 逐 func.func 的 typed ScDialect→LLVM 转换(子阶段 2) |
lowerScfToCfg collector lambda | 0x13572640 | walk 回调;把 scf.{for,if,parallel,while,index_switch} 推入 worklist |
ForLowering::matchAndRewrite | 0x135707c0 | scf.for → cf branch(自定义,benefit 2) |
IndexSwitchLowering::matchAndRewrite | 0x13571ec0 | scf.index_switch → cf(自定义,benefit 2) |
AssertOpLowering::matchAndRewrite | 0x135b7080 | 带 granule mask 的 cf.assert lowering(子阶段 3) |
getArgument / getDescription | 0x13566cc0 / 0x13566ce0 | ODS pass 注册元数据 |
getDependentDialects | 0x13566c00 | 声明该 pass 实例化的方言 |
工厂与测试标志
CreateLowerToSparseCoreLlvmPass (0x135667c0) 会 new 一个 0x298 字节的 pass 对象,其 op-name 锚点是 "builtin.module"(14 chars,存储在 +16)。三份状态被捕获进该对象:xla::jellyfish::Target&(位于 +536)、core ID 的 absl::InlinedVector<long,4>(位于 +552,超过 inline 4 时通过 Storage::InitFrom 复制)和一个 SparseCoreConfig(在 +592 placement-construct),另有 DebugInfoTracker* 位于 +656。它注册的唯一 cl::opt<bool> 是 lower-scf-to-cfg-only(21-char 名称,描述 "Only lower scf ops to cf ops (for testing)",42 chars)— 设置后,驱动器在子阶段 1 后返回。这是该 pass 唯一可调项;它存在是为了让测试能检查 scf→cf 输出,而不下降到(依赖 target 的)intrinsic 选择。
注意 — 该 pass 是一个
builtin.modulepass,但子阶段 2 和 3 在内部迭代函数,而不是依赖func作用域的 pass manager。原因是子阶段顺序:func-pass manager 会按函数交错三轮转换,但驱动器需要在任何函数进入子阶段 2 的 typed conversion 之前,先在模块范围完成全部子阶段 1(scf→cf)。把整个流程保持为带显式内部循环的 module pass,正是强制“先全部控制流扁平化,再全部 SC op lowering,最后全部 assert finalize”的机制。怪癖 — 子阶段 1 会对每个收集到的根
scfop 运行一次applyFullConversion,而不是对模块运行一次。collector lambda (0x13572640) 会把每个顶层scf.{for,if,parallel,while,index_switch}收集到 worklist,然后驱动器循环,为每个 root 构建新的ConversionTarget并调用applyFullConversion。为scf→cf运行单次模块范围applyFullConversion的重新实现者,在良构输入上会得到相同结果,但失败粒度不同 — per-root 循环允许一个畸形 loop 失败,而不影响其他 loop 的诊断。
scf → cf Lowering(子阶段 1)
目的
在任何 ScDialect op 被 typed-convert 之前,所有结构化控制流都必须变成非结构化 cf branch,因为 tpu_* intrinsic 和 LLVM 方言没有 scf.for/scf.if region 的概念。子阶段 1 是一个普通(无类型)的 scf → cf legalization:它复用 MLIR 上游的 populateSCFToControlFlowConversionPatterns 来处理 scf.if/scf.while/scf.parallel,但为 scf.for 和 scf.index_switch 提供两个自定义高 benefit pattern,SparseCore 路径需要以不同于上游的方式 lower 它们(例如 SparseCore 的 loop induction 和 sequencer-aware index switch)。
两个自定义 pattern
| Pattern | 源 op | VA | Benefit | 锚点字符串 |
|---|---|---|---|---|
ForLowering | scf.for | 0x135707c0 | 2 | "…ForLowering]"(57 chars) |
IndexSwitchLowering | scf.index_switch | 0x13571ec0 | 2 | "…IndexSwitchLowering]"(65 chars) |
二者都以 PatternBenefit(2) 注册 — 高于上游 populateSCFToControlFlowConversionPatterns pattern(默认 benefit 1)— 因此它们会赢得 scf.for 和 scf.index_switch 的匹配,而上游处理其余部分。collector lambda (0x13572640) 是一个 function_ref<void(Operation*)> 回调,它在每个被访问 op 上把 op 的 TypeID 与 scf::{IndexSwitchOp, WhileOp, ParallelOp, ForOp, IfOp} 比较(五路 TypeIDResolver 身份检查),命中时把 op 指针追加到 SmallVector worklist。
Per-root conversion target
对于每个收集到的根 op,驱动器构建一个 ConversionTarget,它会:
- 把
scf.for标记为非法(setOpAction(..., 1)),因此必须重写; - 把
scf.{If, Parallel, While, IndexSwitch}标记为动态合法(当且仅当已 lowering 时合法); - 把所有五种
scf.{for, if, parallel, while, index_switch}作为容器标记为递归合法(markOpRecursivelyLegal),从而遍历嵌套 region; - 安装 catch-all
markUnknownOpDynamicallyLegal(合法性回调 lambda #3),因此任何非scfop 都按原样合法。
随后 applyFullConversion(rootOp, target, frozenScfPatterns) 驱动重写。返回 false 会发出 pass failure 信号(设置 pass+40 处 pass flags 的 bit 2)。
按类划分的重写函数体(子阶段 2 — lowerFunc)
统一重写形态
lowerFunc (0x13568280) 构建 SCTypeConverter(见该页面)并注册约 118 个 *OpLowering conversion pattern,然后在单个 func::FuncOp 上运行 applyFullConversion。无论类别如何,每个 pattern 都遵循同样的五步 matchAndRewrite 形态:
function matchAndRewrite(op, adaptor, rewriter): // generic ScDialect *OpLowering
// (a) resolve operands: memref+index -> raw LLVM pointer/offset Value
ptr = getStridedElementPtr(adaptor.memref, adaptor.indices) // SC-specialised GEP
vals = [ptr, adaptor.scalarOperands…]
// (b) filter the op's attribute dictionary
attrs = FilterLLVMAttributes(op.getAttrs()) // 0x135b7a20 — drops "access_groups"
// (c) pick the leaf tpu_* intrinsic by dtype / memspace / predicate
intr = selectIntrinsic(op, target) // class-specific dispatch key
// (d) create the intrinsic
res = intr::create(rewriter, op.getLoc(), {resultType}, vals, attrs)
// (e) replace
rewriter.replaceOp(op, res)
return success
```text
属性过滤器是统一的:`FilterLLVMAttributes` (`0x135b7a20`) 会把 `ScDialect` op 的属性字典转发给新的 `tpu_*` intrinsic,但会**丢弃 `access_groups`** — 这是从内联字符串比较立即数 `0x675f737365636361` (`"access_g"`) / `0x7370756f72675f73` (`"s_groups"`) 解码出的 13-char 属性名。intrinsic 会在 LLVM lowering 时重新生成自己的 LLVM `AccessGroup` metadata,因此传递源属性会导致双重标记。
操作数映射也是统一的:`ScDialect` 的 `memref`+`index` 操作数会通过 SC-specialised `getStridedElementPtr`(或 `ConvertToLLVMPattern::getStridedElementPtr`)变成单个原始 `!llvm.ptr`/offset `Value`;标量操作数直接传递。某些类的“1:N”特征是**op 身份选择**,而不是操作数展开:SC 类型系统把每个 HW 变体编码为*不同 intrinsic*,分发键选择其中一个。
> **易错点 —** “重写是 1:1”和“该类 lowering 到 N 个 intrinsic”同时为真,且并不矛盾。单次 `matchAndRewrite` 调用恰好发射一个 intrinsic(加上任何 helper op);“N”是分发可从中选择的候选 intrinsic 的*静态*数量。`DmaSimpleStartOp` 有一个重写函数体,但拥有一个含 12 项候选 `tpu_dma_<src>_to_<dst>_sc_simple` intrinsic 的表(在二进制注册的 16 个此类 `_sc_simple` intrinsic 中);该函数体每次调用选择一个。把 1:1 op-to-intrinsic map 写死的重新实现者会错过分发并发射错误 DMA。
### 表 1 — 六个代表性重写函数体
每个功能类别一个代表,逐字节从 `matchAndRewrite` 解码。`→ intrinsic` 是函数体尾调用的 `tpu_*::create`;`dispatch key` 是选择叶子的依据。
| 类 | Rewriter(`matchAndRewrite` @VA) | → 叶子 intrinsic | Dispatch key | 代数 |
|---|---|---|---|---|
| CBREG advance | `AdvanceCbOffsetOpLowering` `0x1353bf80` | `tpu_cbreg_add_offset::create` | 无(1:1) | descriptor 读-改-写(见下) |
| CBREG read | `ReadCbOffsetOpLowering` `0x1353c400` | `tpu_rdcbreg_offset::create` `0x14734820` | 无(1:1) | 从 cbreg 读取 OFFSET 子寄存器 → `replaceOp` |
| sync add | `SyncAddOpLowering` `0x13591660` | `tpu_syncadd` / `_tile` / `_remote` | `SflagCoreType`/`SflagLocal` + sequencer attr | local (V,V) / tile (V,V) / remote (V×5) |
| sync wait | `SyncWaitOpLowering` `0x13593040` | `tpu_wait{ge,eq,ne,lt,le,gt}` / `waitdone` / `waitnotdone` | comparison-predicate attr | predicate → 选择 wait intrinsic → `replaceOp` |
| DMA simple | `DmaSimpleStartOpLowering` `0x135a9100` | `tpu_dma_<src>_to_<dst>_sc_simple`(12-lambda 表) | `(srcMemSpaceID, dstMemSpaceID)` 元组 | `vector<tuple<u32,u32,fn>>` 分发(见下) |
| stream gather/scatter | `LinearStreamStartOpLowering::rewriteSparseCoreStreamOpToLLVM` `0x13542000` | `tpu_stream_linear_<verb>_<src>_to_<dst>[_add]` | `(dtype, off-tile memspace, verb)` lambda 表 | 16 项分发(见下) |
| addrspacecast | `MemorySpaceCastOpLowering` `0x135a5c20` | 无(省略)**或** 通用 `llvm.addrspacecast` | `convertType(src) == convertType(dst)` | 省略或失败(见下) |
### CBREG — descriptor 读-改-写
`AdvanceCbOffsetOpLowering` (`0x1353bf80`) 比平坦的 1:1 replace 更丰富。cbreg 位于 `CircularBufferDescriptor`(转换后 memref 上的 `StructBuilder`)内部,因此函数体是对该 struct 的读-改-写:
```c
function AdvanceCbOffset::matchAndRewrite(op, adaptor, rewriter): // 0x1353bf80
desc = StructBuilder(adaptor.cbDescriptor) // dereference operand 0
cbreg = desc.CbReg() // CircularBufferDescriptor::CbReg
delta = adaptor.deltaOperand // operand 1
newReg = tpu_cbreg_add_offset::create(rewriter, loc, // 0x146d7e60 (in_place @0x146d7f60)
cbreg.getType(), cbreg, delta) // offset += delta mod size in HW
out = StructBuilder(adaptor.cbDescriptor)
out.SetCbReg(newReg) // write new register field back
out.SetMemRef(desc.MemRef()) // carry the memref field through
rewriter.replaceOp(op, out.value())
return successHW 语义(offset 按 buffer size 取模回绕、{base, offset, size} register bit layout)属于 CBREG 页面;该函数体的贡献是 descriptor plumbing — 新 offset 写回到 memref 已经所在的同一个 descriptor struct,因此函数其余部分继续用一个 SSA value 表示 circular buffer。
Sync / wait — 属性驱动的分发
SyncWaitOpLowering (0x13593040) 读取 op 的 comparison-predicate 属性并选择八种 wait intrinsic 之一:tpu_waitge (0x14a30aa0)、waiteq (0x14a30a00)、waitne (0x14a30dc0)、waitlt (0x14a30d20)、waitle (0x14a30c80)、waitgt (0x14a30be0) — 都是 2 操作数 {sflag, threshold} 比较形式 — 以及 waitdone (0x14a30960) 和 waitnotdone (0x14a30e60),即 1 操作数 {sflag} done 形式。SyncAddOpLowering (0x13591660) 读取 getSflagCoreType/getSflagLocal 和父 sequencer type,以选择 tpu_syncadd(local,V×2)、tpu_syncadd_tile(tile bank,V×2)或 tpu_syncadd_remote(ICI peer,V×5 — 构建一个 ChipIdOp + arith.IndexCast + LLVM::ConstantOp 以进行 {device, core, id} 路由)。二者都会先通过 getStridedElementPtr 把 sflag memref 解析为指针,并运行 ValidateSyncFlagsIndices。
DMA — memspace-pair 元组表
DmaSimpleStartOpLowering (0x135a9100) 构建一个 std::vector<std::tuple<u32, u32, std::function<void()>>>,行内容为 (srcMemSpaceID, dstMemSpaceID, create-lambda);然后从 op 读取 getSrcBufferMemorySpace/getDstBufferMemorySpace,并运行 (src, dst) 对匹配的 lambda。每个 lambda 创建具体的 tpu_dma_<src>_to_<dst>_sc_simple intrinsic。该函数体的反编译携带 12 个不同 lambda closure(lambda #1…#12),每个都物化为一个 __call_func/__large_clone/__large_destroy 三元组 — 在 0x135ac0c0…0x135acb20 范围内共有 36 个 std::function thunk 符号 — 这确认该表是在函数内构建,而不是静态分发表。分发前,CastTileSmemPointerToSmem 会把任何 tile-resident 端点规范化为 generic SMEM,其结果送入 CheckAddressSpaces 合法性门(“SMEM 上的 simple DMA ⇒ 仅 SCS”契约)— 二者都由 SCTypeConverter 和 LowerToMlo DMA bridge 负责。
注意 — create arity 编码 descriptor 层级:
tpu_dma_*_sc_simple接收 8 个Value,_single_strided接收 11 个,_general接收 16 个(tpu_dma_hbm_to_hbm_sc_simple0x146d8c60;tpu_dma_hbm_to_hbm_sc_single_strided0x146d9080;tpu_dma_hbm_to_hbm_sc_general0x146d8820)。这些操作数字段角色(base vs offset vs size vs stride vs sflag)不在这里解码 — 这里只确认数量和 memspace-pair 分发。字段语义归 descriptor-encoder 分析负责,而不是此 lowering。
Stream — (dtype, memspace, verb) lambda 表
LinearStreamStartOpLowering::rewriteSparseCoreStreamOpToLLVM (0x13542000) 构建一个 16 项 vector<tuple<dtype, off-tile-memspace, verb, lambda>>,键为 (dtype, off-tile memspace, verb=gather/scatter),并创建 tpu_stream_linear_<verb>_<src>_to_<dst>(gather 形式 0x148eb0a0;_hbm4b_ 变体 0x148eae40;scatter 0x149368c0;indirect-gather-add 0x147922c0,V×8)。它在分发前运行 adjustOffsetForHbm4b(HBM_4B 粒度)、CheckLinearStreamIsValid 和 CheckStartAndEndWithinMemref(bounds)。每个叶子 HW stream-engine opcode 对应的约 834 个 stream intrinsic 的 SparseCoreStream slot 编码见 Stream Gather/Scatter;该函数体只选择哪个 intrinsic。
addrspacecast — 省略或失败
MemorySpaceCastOpLowering (0x135a5c20) 是最小也最关键的函数体。它转换源操作数类型和结果类型并比较:
function MemorySpaceCast::matchAndRewrite(op, adaptor, rewriter): // 0x135a5c20
srcT = TypeConverter::convertType(op.getSource().getType()) // -> !llvm.ptr<N_src>
resT = TypeConverter::convertType(op.getType()) // -> !llvm.ptr<N_dst>
if srcT != resT:
return failure // -> generic emits llvm.addrspacecast
rewriter.replaceOp(op, adaptor.source) // ELIDE: same ptr type, no cast
return success
```text
如果两个转换后的类型**相等**,cast 会被省略(`replaceOp(op, sourceValue)` — 不发射指令);如果它们**不同**,pattern 返回 failure,MLIR 的通用 `ConvertOpToLLVMPattern` 会为 `memref.memory_space_cast` 发射真正的 `llvm.addrspacecast`。因此该决策*完全*由 `SCTypeConverter` 决定 — sequencer-context flatten(在 sequencer 函数外把 `{sflag_tile, sflag_scs, sflag_tc, sflag} → 204` 以及 `{smem_tile, smem_scs, smem} → 0` 折叠)让两个不同 `MemorySpace` 转换到*相同* `!llvm.ptr<N>`,从而省略。该 flatten 表位于 [SCTypeConverter](sc-type-converter.md);下游 `llvm.addrspacecast` 指令选择位于 [addrspacecast ISel](../sparsecore/addrspacecast-isel.md)。
> **怪癖 —** 该 pattern 发射**零个或一个** op,从不触达其他类别所使用的 intrinsic 族。它是唯一一个其“intrinsic”有时为*无*的 `*OpLowering`。重新实现者必须以高于上游通用 `memref.memory_space_cast` → `llvm.addrspacecast` pattern 的 benefit 注册 SC 专属省略 pattern,让省略先有机会匹配;只有它失败时,通用 cast 才会触发。benefit 顺序错误会发射一个冗余 same-address-space cast,之后还要 LLVM 折叠掉。
### 约 118 个 pattern 名册(按类)
`lowerFunc` 注册约 118 个不同的 `(anonymous namespace)::*OpLowering` 类(模板族 — `UnaryFloatVectorOpLowering`、`AluEpOpLowering` — 实例化数量多于类名数量)。不逐一列出全部 118 个,而按分发维度查看:
| 组 | 数量 | 代表成员 | 分发轴 |
|---|---|---|---|
| STREAM | ~10 | `LinearStream[Add]Start`、`StridedStream[Add]Start`、`IndirectStream[Add]Start`、`IndirectVectorStream[Add]Start` | (dtype, off-tile memspace, verb) |
| DMA | 4 | `DmaSimpleStart`、`DmaSingleStridedStart`、`DmaGeneralStart`、`DmaWait` | (srcMemSpace, dstMemSpace) 元组 |
| SYNC/WAIT | ~10 | `SyncAdd`、`SyncSet`、`SyncSetRemote`、`SyncWait`、`MemoryWait`、`Sfence`、`FetchAndAdd`、`TileWaitScsSmem` | predicate / local-tile-remote attr |
| CBREG | ~5 | `CreateCb`、`ReshapeCb`、`AdvanceCbOffset[InPlace]`、`ReadCbOffset` | 无(descriptor RMW) |
| TRANSCENDENTAL/EUP | ~42 | `UnaryFloatVector` (12)、`AluEp` (30) — 见 SCTypeConverter | packed-operand → 1:1 macro vs 1:N unpack/pack |
| PACK/UNPACK | ~8 | `PackF/SI/UI`、`UnpackF/SI/UI`、`UnpackI32Pair`、`CarryOut` | element format |
| SCAN/SORT | ~4 | `Scan`、`SegmentedScan`、`Sort`、`DuplicateCountUnique` | — |
| VECTOR MEM | ~10 | `VectorLoad[Idx]`、`VectorStore[Idx]`、`VolatileLoad/Store`、`ZeroMem`、`VectorBroadcast/Extract` | memspace |
| LANE/PERMUTE | ~6 | `Permute`、`Vlaneseq`、`VShiftInsert`、`VectorMaskCount{TrailingZeros,Population}` | — |
| ADDRESS/PTR | ~12 | `AddressOf`、`AllocateAtOffset`、`FoldOffsetIntoPtr`、`GetRemoteMemRef`、`MemRefView`、`MemorySpaceCast`、`SCSubView`、`SCView`、`HbmStackStartOffset` | type-converter equality(cast) |
| CONTROL/TASK | ~14 | `Barrier`、`Lock`、`Unlock`、`LaunchTileTask`、`Assert`、`GetCoreLocation`、`GetDynamic{DeviceAssignment,DimensionSize}`、`SetPTState`、`SetTag`、`SetTraceMark` | — |
| TRACE/TELEMETRY | ~8 | `Trace`、`LogEvent`、`LogMemRef`、`Read{Global,Local}CycleCount`、`SetDmaCredit`、`SetIndirectFilterValue` | — |
EUP 组(`UnaryFloatVectorOpLowering` 1:1 macro vs `AluEpOpLowering` 1:N unpack/compute/pack,由 `IsDynamicallyLegal` 选择)已在 [SCTypeConverter](sc-type-converter.md)(表 B/C)完整列出 — 这里不再重复成表。
---
## Assert / MemRef-Finalize Lowering(子阶段 3)
### 目的
子阶段 2 把每个 `func.func` 函数体变成带 `llvm`/`llvm_tpu` op 的 `llvm.func` 后,还剩两类残留:`cf.assert` op(可能由子阶段 1 的 `scf`→`cf` 产生,也可能原本就存在)和任何未 finalize 的 `memref` op。子阶段 3 遍历每个 `LLVM::LLVMFuncOp`,如果 walk 发出需要工作的信号,就运行最后一次 `applyFullConversion`,lower `cf.assert` 并完成 memref-to-LLVM finalize。
### Conversion 轮次
```c
function lowerAsserts_finalize(module): // tail of runOnOperation
opts = LowerToLLVMOptions(ctx)
opts.useBarePtrCallConv = true
opts.dataLayout = DataLayout("…-S64") // "-S64": 64-bit natural stack alignment
tc = LLVMTypeConverter(ctx, opts)
tc.registerTypeAttributeConversion(lowerAsserts_memSpaceLambda) // SC mem-space attr (no flatten)
patterns.add<AssertOpLowering>(benefit=1) // 0x135b7080 "cf.assert"
populateFinalizeMemRefToLLVMConversionPatterns(tc)
target = LLVMConversionTarget(ctx)
target.addLegalDialect("llvm_tpu") // SC intrinsic dialect is the legal target
target.addIllegalOp("builtin.module")
target.addDynamicallyLegalOp("llvm.alloca") // alloca legal only post-finalize
target.addDynamicallyLegalOp("builtin.unrealized_conversion_cast") // bridge-cast passthrough
applyFullConversion(module, target, patterns)AssertOpLowering (0x135b7080) 是针对 cf.assert 的 ConvertToLLVMPattern,携带 target 的 GranuleBytes — 它计算 granule mask -1 << (log2(stackBytes) − log2(GranuleBytes))(target +88 字段与 GranuleBytes 上的一对 _BitScanReverse64),使 assert 的 spill/scratch 使用按 SparseCore granule 对齐。lowerAsserts memory-space lambda 是与子阶段 2 的 SCTypeConverter 相同的 MemorySpaceAttr → i64 addrspace 转换,但没有 sequencer-context flatten override — assertion text 希望命名精确的 per-tile/per-SCS bank,因此使用未 flatten 的 ID。两个 lambda 都记录在 SCTypeConverter。
注意 —
llvm_tpu方言在这个最终 target 中被标记为合法方言,而不是非法。到子阶段 3 时,子阶段 2 发射的 SC intrinsic 是期望输出,因此 assert/memref finalize 必须把它们视为按原样合法,只重写残留的cf.assert/memref/builtin.module结构。builtin.unrealized_conversion_cast被保持为动态合法,因此任何仍在 LowerToMlo DMA bridge 中传递的 bridge-cast 都会原样穿过。
相关组件
| 名称 | 关系 |
|---|---|
SCTypeConverter (lowerFunc 0x13568280) | 构建本页重写函数体所依托的类型映射和 EUP 名册 |
FilterLLVMAttributes (0x135b7a20) | 每个重写函数体调用的统一属性过滤器(丢弃 access_groups) |
getStridedElementPtr | memref+index → DMA/sync/stream 操作数解析所用的原始 !llvm.ptr |
CheckAddressSpaces (0x135b8e00) | DmaSimpleStartOpLowering 查询的 DMA 合法性门(由 SCTypeConverter 拥有) |
tpu_*::create 族 | 重写函数体尾调用的 1356 个 intrinsic 叶子集合(LlvmTpu catalog) |
populateSCFToControlFlowConversionPatterns | 自定义 ForLowering/IndexSwitchLowering 增强的上游 scf→cf pattern |
交叉引用
- SCTypeConverter — address-space →
!llvm.ptr类型映射、sequencer-context flatten、CheckAddressSpaces,以及重写函数体消费的 42 个实例的 EUP 名册(不要重复)。 - Fat Pointers (AS7/8/9) — 分发键索引的完整 AS-id ↔
MemorySpace↔ pool 表。 - addrspacecast ISel —
MemorySpaceCastOpLowering在类型不匹配时 fall through 到的llvm.addrspacecast指令选择。 - LowerToMlo DMA Bridge-Cast — 前序两阶段 DMA lowering,其 tile-expanded 输出由此 pass finalize;任何传递中 bridge-cast 的来源。
- DialectConversion Legalizer — 三个子阶段驱动的
ConversionTarget/applyFullConversion机制。 - ConversionPatternRewriter —
matchAndRewrite函数体调用replaceOp的 speculative-apply/rollback rewriter。 - LlvmTpu Intrinsic Catalog — 作为每个重写函数体叶子目标的 1356 个
tpu_*intrinsic。 - CBREG — CBREG 重写函数体读写的
{base, offset, size}circular-buffer register。 - Stream Gather/Scatter — stream 分发选择的约 834 个 stream intrinsic 背后的 SparseCoreStream slot 编码。
- SCS Engine — sync/DMA 合法性契约引用的 Scalar Core Sequencer。
- The
tpuMLIR Dialect — 这些 pattern 重写的tpu/sparse_coreop 的 op-registration ABI。 - Compiler Overview — Part V 导览;SC lowering 在五阶段下降中的位置。
- Binary:
extracted/libtpu-0.0.40-cp314-cp314-manylinux_2_31_x86_64/libtpu/libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d) - 索引项: Part V — Compiler: Lowering & Optimization Passes / MLIR lowering chain — 返回索引