Skip to content

LowerToSparseCoreLlvm

本页中的所有地址、符号和偏移都从 libtpu-0.0.40-cp314 wheel 中的 libtpu.so 逐字节读取(构建 libtpu_lts_20260413_b_RC00,BuildID md5 89edbbe81c5b328a958fe628a9f2207d,781,691,048 字节,未 stripped,.text VA == 文件偏移)。.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) 内部的三子阶段驱动器。首先,一个独立的 scfcf lowering 在整个模块上运行(无类型转换);然后逐个 func::FuncOp 执行 ScDialectllvm/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 loweringlowerScfToCfg,带两个自定义 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。顺序是强制的 — scf op 必须先变为控制流, 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 0x135707c0IndexSwitchLowering 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 已经消失。

入口点

text
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角色
runOnOperation0x13566d00三子阶段驱动器
CreateLowerToSparseCoreLlvmPass0x135667c0工厂;接收 Target&InlinedVector<long,4>SparseCoreConfig&DebugInfoTracker*
lowerFunc0x13568280func.func 的 typed ScDialect→LLVM 转换(子阶段 2)
lowerScfToCfg collector lambda0x13572640walk 回调;把 scf.{for,if,parallel,while,index_switch} 推入 worklist
ForLowering::matchAndRewrite0x135707c0scf.forcf branch(自定义,benefit 2)
IndexSwitchLowering::matchAndRewrite0x13571ec0scf.index_switchcf(自定义,benefit 2)
AssertOpLowering::matchAndRewrite0x135b7080带 granule mask 的 cf.assert lowering(子阶段 3)
getArgument / getDescription0x13566cc0 / 0x13566ce0ODS pass 注册元数据
getDependentDialects0x13566c00声明该 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 唯一可调项;它存在是为了让测试能检查 scfcf 输出,而不下降到(依赖 target 的)intrinsic 选择。

注意 — 该 pass 是一个 builtin.module pass,但子阶段 2 和 3 在内部迭代函数,而不是依赖 func 作用域的 pass manager。原因是子阶段顺序:func-pass manager 会按函数交错三轮转换,但驱动器需要在任何函数进入子阶段 2 的 typed conversion 之前,先在模块范围完成全部子阶段 1(scfcf)。把整个流程保持为带显式内部循环的 module pass,正是强制“先全部控制流扁平化,再全部 SC op lowering,最后全部 assert finalize”的机制。

怪癖 — 子阶段 1 会对每个收集到的根 scf op 运行一次 applyFullConversion,而不是对模块运行一次。collector lambda (0x13572640) 会把每个顶层 scf.{for,if,parallel,while,index_switch} 收集到 worklist,然后驱动器循环,为每个 root 构建新的 ConversionTarget 并调用 applyFullConversion。为 scfcf 运行单次模块范围 applyFullConversion 的重新实现者,在良构输入上会得到相同结果,但失败粒度不同 — per-root 循环允许一个畸形 loop 失败,而不影响其他 loop 的诊断。


scf → cf Lowering(子阶段 1)

目的

在任何 ScDialect op 被 typed-convert 之前,所有结构化控制流都必须变成非结构化 cf branch,因为 tpu_* intrinsic 和 LLVM 方言没有 scf.for/scf.if region 的概念。子阶段 1 是一个普通(无类型)的 scfcf legalization:它复用 MLIR 上游的 populateSCFToControlFlowConversionPatterns 来处理 scf.if/scf.while/scf.parallel,但为 scf.forscf.index_switch 提供两个自定义高 benefit pattern,SparseCore 路径需要以不同于上游的方式 lower 它们(例如 SparseCore 的 loop induction 和 sequencer-aware index switch)。

两个自定义 pattern

Pattern源 opVABenefit锚点字符串
ForLoweringscf.for0x135707c02"…ForLowering]"(57 chars)
IndexSwitchLoweringscf.index_switch0x13571ec02"…IndexSwitchLowering]"(65 chars)

二者都以 PatternBenefit(2) 注册 — 高于上游 populateSCFToControlFlowConversionPatterns pattern(默认 benefit 1)— 因此它们会赢得 scf.forscf.index_switch 的匹配,而上游处理其余部分。collector lambda (0x13572640) 是一个 function_ref<void(Operation*)> 回调,它在每个被访问 op 上把 op 的 TypeIDscf::{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),因此任何非 scf op 都按原样合法。

随后 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 形态

c
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 success

HW 语义(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 三元组 — 在 0x135ac0c00x135acb20 范围内共有 36 个 std::function thunk 符号 — 这确认该表是在函数内构建,而不是静态分发表。分发前,CastTileSmemPointerToSmem 会把任何 tile-resident 端点规范化为 generic SMEM,其结果送入 CheckAddressSpaces 合法性门(“SMEM 上的 simple DMA ⇒ 仅 SCS”契约)— 二者都由 SCTypeConverterLowerToMlo DMA bridge 负责。

注意 — create arity 编码 descriptor 层级:tpu_dma_*_sc_simple 接收 8 个 Value_single_strided 接收 11 个,_general 接收 16 个(tpu_dma_hbm_to_hbm_sc_simple 0x146d8c60tpu_dma_hbm_to_hbm_sc_single_strided 0x146d9080tpu_dma_hbm_to_hbm_sc_general 0x146d8820)。这些操作数字段角色(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 粒度)、CheckLinearStreamIsValidCheckStartAndEndWithinMemref(bounds)。每个叶子 HW stream-engine opcode 对应的约 834 个 stream intrinsic 的 SparseCoreStream slot 编码见 Stream Gather/Scatter;该函数体只选择哪个 intrinsic。

addrspacecast — 省略或失败

MemorySpaceCastOpLowering (0x135a5c20) 是最小也最关键的函数体。它转换源操作数类型和结果类型并比较:

c
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.assertConvertToLLVMPattern,携带 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
getStridedElementPtrmemref+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 ISelMemorySpaceCastOpLowering 在类型不匹配时 fall through 到的 llvm.addrspacecast 指令选择。
  • LowerToMlo DMA Bridge-Cast — 前序两阶段 DMA lowering,其 tile-expanded 输出由此 pass finalize;任何传递中 bridge-cast 的来源。
  • DialectConversion Legalizer — 三个子阶段驱动的 ConversionTarget/applyFullConversion 机制。
  • ConversionPatternRewritermatchAndRewrite 函数体调用 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 tpu MLIR Dialect — 这些 pattern 重写的 tpu/sparse_core op 的 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-id 89edbbe81c5b328a958fe628a9f2207d
  • 索引项: Part V — Compiler: Lowering & Optimization Passes / MLIR lowering chain — 返回索引