Region → Sequencer 提取器
本页上的每个函数地址、op 名字符串、属性名称/长度以及错误消息字符串,都读取自
libtpu-0.0.40-cp314wheel 中的libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d;buildlibtpu_lts_20260413_b_RC00)——来源包括TileTaskOutliningPass::runOnOperation(@0x13606220)、它的逐TileTaskOpwalk 回调(@0x136066e0),以及LaunchTileTaskOpbuilders 的反编译 C++。其他版本会有所不同。
摘要
SparseCore 后端把每个卸载计算运行在三个子引擎上:SCS(标量控制)、TAC(tile-access / DMA)、TEC(向量计算),并把这种拆分表示为普通 MLIR 函数:每个引擎一个函数,每个函数都带有一个 sc.sequencer StringAttr。创建这些函数的 pass 是 TileTaskOutliningPass。它是一个经典的 region outliner,形态是 getUsedValuesDefinedAbove → FuncOp::create → Region::cloneInto → replace-with-launch,和 LLVM/MLIR 把并行 region 提取成 kernel 的形状相同;它为 SparseCore 做了专门化:捕获内容(只捕获静态 memref)、盖章内容(sc.sequencer = "execute"),以及留下的内容(一个 sc_tpu.launch_tile_task op;在 overlayer 路径上还会有一个 PrefetchTileTaskOp 和一个 overlay memref.alloc)。
本页负责说明pass 机制和 launch 发射:sc_tpu.tile_task region 如何变成独立的 func.func,它的 live-in 如何变成函数参数,body 如何被克隆,原始 op 如何被 launch 替换,以及提取出的函数携带哪些属性。本页刻意不重新推导逐 op→engine 的选择谓词,也就是决定某个 lowered op 是 Stream 还是 DMA、以及它落入哪个 TileTask region 的规则。该谓词由 getSequencerType 负责(GetTransferKind + sc.sequencer 回读);这里把 engine 字符串视为 pass 的输出,并按字节确认在 6acc60406(gfc)上为 "execute"。
重新实现者必须内化的单个结构事实是:TileTaskOutliningPass 对每个 sc_tpu.tile_task op 精确产生一个提取出的函数,标记为 "execute"(TEC),并把该 op 替换为一个 sc_tpu.launch_tile_task,其 execute_func 符号指向该函数。 外层函数,也就是发出 tile_task 的函数,是 SCS 控制程序(sc.sequencer = "scs")。在 Viperfish/Ghostlite 上,同一个按 Target 参数化的 pass 还会拆出一个 "access"(TAC)函数;在 6acc60406 上没有 TAC,所以 tile-fetch 工作折叠进 "execute"。本页记录 gfc(6acc60406)单 "execute" 路径,也就是反编译回调发射的路径,并把 TAC 拆分标为 LOW。
对重新实现来说,契约是:
- 一个 pass,一次 walk,每个 task 一个 launch。
runOnOperation先运行 Timem 冲突 gate,解析SparseCoreTarget,然后walk每个sc_tpu.tile_taskop。每次匹配都独立提取;提取出的函数之间没有跨 task 共享。 - 捕获项是 region live-in,且必须是静态 memref。 参数来自
getUsedValuesDefinedAbove;每个参数都必须是形状完全静态的MemRefType,否则 pass 会以"Tile tasks only support capture of static memrefs"中止(tile_task_outlining_pass.cc:62)。 - 函数名是
"execute"加逐 pass 计数器。 前缀"execute"会和十进制计数器(APInt::toString)一起渲染成唯一符号;属性值sc.sequencer则是裸"execute"(7 个字符)。 - 替换 op 是
sc_tpu.launch_tile_task(23 字符 op 名),携带一个FlatSymbolRefAttr execute_func、一个clear_ibufUnitAttr、overlay-alloc 操作数,以及捕获的ValueRange。原始tile_task会被erase。
| Pass | xla::tpu::sparse_core::(anon)::TileTaskOutliningPass::runOnOperation @0x13606220 |
| Factory | CreateTileTaskOutliningPass(const jellyfish::Target&) @0x13605fe0 |
| 基类 | mlir::sparse_core::impl::TileTaskOutliningBase<…>(TableGen pass base) |
| 逐 op 回调 | walk<TileTaskOp> callback @0x136066e0;嵌套 terminator walk @0x136071c0 |
| 被提取的 op | sc_tpu.tile_task(mlir::sparse_core::TileTaskOp)→ 提取出的 func::FuncOp |
| Launch op | sc_tpu.launch_tile_task(mlir::sparse_core::LaunchTileTaskOp);create(FuncOp overload,本处调用)@0x145dd0e0,build @0x1459c060 |
| Engine 标记 | setAttr("sc.sequencer", StringAttr "execute") — 名称 12 字符,值 7 字符 |
| 下游读取方 | LowerSequencerFunctionsPass::runOnOperation @0x13532120 |
| 置信度 | CONFIRMED(反编译已验证),除非某行或 callout 另有说明 |
关于 engine-selection 决策(Stream/DMA、sc.sequencer 谓词),见 getSequencerType;关于此 pass 在十二个 pass 的 codegen pipeline 中的位置,见 SC Backend Pipeline;关于提取出的函数最终编码进入的逐 engine bundle,见 Per-Engine Bundle Slot-Base Map。
Pass 所在位置
Outlining 运行在十二个 pass 的 SparseCore codegen pipeline(CustomKernelEmitter::RunPasses)之前,而不是其中。RunPasses 消费的 module 已经存在逐 engine 函数且已经打上标记;它的第一个 pass(ConvertIntegerMemrefs)作用在此 pass 生成的 func.func 上。因此顺序是:
tpu-dialect module with sc_tpu.tile_task ops
│
▼ TileTaskOutliningPass::runOnOperation @0x13606220 ── THIS PAGE
│ for each sc_tpu.tile_task:
│ outline region → func.func (sc.sequencer="execute")
│ replace op with sc_tpu.launch_tile_task
│
▼ module: SCS control program (sc.sequencer="scs")
│ + one "execute" func per task, launched from it
│
▼ CustomKernelEmitter::RunPasses @0x13202780 ── see sc-backend-pipeline.md
│ 12 single-pass managers, ending in LowerToMlo
│
▼ LowerSequencerFunctionsPass @0x13532120 ── per-engine body lowering
│ reads sc.sequencer via the ScDialect predicates
│
▼ per-engine bundle codec (SCS / TAC / TEC) ── see bundle-slot-base-map.md
```text
与 [getSequencerType](getsequencertype.md) 的分工是精确的:该页的 *Layer 2* 把这个 outliner 命名为“写入 `sc.sequencer` 的地方”,并明确把 pass 机制转交给本页;本页把 `"scs"`/`"access"`/`"execute"` 字符串集合和逐 op region 分配规则视为已经决定的输入,只记录把它们物化出来的 IR 手术。
---
## `runOnOperation` — Pass 驱动器(`@0x13606220`)
### 目的
在整个 `ModuleOp` 上驱动 outlining:先检查 Timem/tile-task 冲突,解析 SparseCore target,然后访问每个 `sc_tpu.tile_task` op 并提取它。该 pass 作用于 module(它是 module-level pass,挂在顶层,不嵌在 func 内)。
### 算法
```c
// TileTaskOutliningPass::runOnOperation(this) @0x13606220
void runOnOperation(TileTaskOutliningPass *this):
ModuleOp module = this->getOperation(); // this+5 (masked ptr)
// 1. Timem conflict gate.
if (overlayer::ContainsExplicitTimemAccess(module)) { // @0x1395bb60
// only a problem if the module ALSO launches tile tasks:
if (walk<TileTaskOp>(module, ContainsTileTask) == found) { // @ ContainsTileTask lambda
emitOpError(module)
<< "programs that launch tile tasks while also explicitly accessing "
<< "Timem are not supported"; // two appended literals
report();
this->signalPassFailure(); // this+40 |= 4
return;
}
}
// 2. Resolve the SparseCore target (gen / geometry) and cache it.
this->target_ = xla_mlo_util::SparseCoreTargetForModule( // @0x14a8b5c0
this->target_arg_, module); // stored at this+43
// 3. A naming/context seed: the module body's first op anchor + an
// MLIRContext handle (this+352 holds the monotonic func counter).
ctx = module.getContext();
// 4. Walk every sc_tpu.tile_task op; outline each via the per-op callback.
if (walk<TileTaskOp>(module, &outline_one /* @0x136066e0 */) == interrupted)
this->signalPassFailure(); // this+40 |= 4重新实现者必须复现两个驱动层事实:
- Timem gate 是硬错误,不是跳过。 如果一个 module 既显式读/写 Timem,又发起 tile task,就会用两个片段的诊断拒绝它(
"programs that launch tile tasks while also explicitly accessing "+"Timem are not supported")。检查分两次walk:Timem 侧为ContainsExplicitTimemAccess(@0x1395bb60),launch 侧为ContainsTileTaskwalk lambda;只有二者同时为真才报错。这保护了 tile-overlay 机制(见下文),因为后者会复用 Timem 区域。 - target 在 walk 前解析一次。
SparseCoreTargetForModule(@0x14a8b5c0)把 module 映射到SparseCoreTarget,结果缓存到 pass 对象上(this+43)。逐 op 回调会读回它来驱动 overlayer 决策。target 参数化了 6acc60406 与 VF/GL 的行为差异(单"execute"vs"access"+"execute"拆分)。
函数映射
| Function | VA | 作用 |
|---|---|---|
TileTaskOutliningPass::runOnOperation | 0x13606220 | pass 驱动器:gate → target → walk |
CreateTileTaskOutliningPass(Target&) | 0x13605fe0 | pass factory(绑定 Target) |
overlayer::ContainsExplicitTimemAccess | 0x1395bb60 | Timem 冲突 gate |
xla_mlo_util::SparseCoreTargetForModule | 0x14a8b5c0 | module → SparseCoreTarget |
walk<TileTaskOp> callback(outline one) | 0x136066e0 | 逐 op outlining body |
nested terminator walk(OutlineSequencerFunction lambda) | 0x136071c0 | 重接克隆 region 的 terminator |
逐 op 回调(@0x136066e0) {#the-per-op-callback-0x136066e0}
这是 pass 的核心:给定一个 sc_tpu.tile_task op,它构建提取出的函数、克隆 body、把 op 替换为 launch,并打上 engine 属性。反编译回调按以下顺序执行。
算法
// walk<TileTaskOp> callback "outline one" @0x136066e0
WalkResult outline_one(TileTaskOp op):
if (op == null || op.typeID != TileTaskOp::id) // line 126 — TypeID gate
return advance; // (walk visits all ops; skip non-tasks)
Region ®ion = op.getRegion(); // op + body offset
// 1. Live-ins → future function arguments.
SetVector<Value> liveIns;
getUsedValuesDefinedAbove(region, liveIns); // @0x1c974440 (line 155)
// 2. Read the per-task overlay budget attribute (inherent → dictionary).
Attribute hwm = op.getInherentAttr("sc.execute_alloc_high_water_mark", 32);
if (!hwm) hwm = op.getDictionaryAttr().get("sc.execute_alloc_high_water_mark", 32);
// 3. Build the function name: "execute" + decimal(pass.funcCounter++).
name = "execute";
APInt(64, this->funcCounter++).toString(name, /*radix=*/10); // pass+352 counter
// 4. Argument types: each live-in must be a STATIC memref.
SmallVector<Type> argTypes;
for (Value v : liveIns) {
MemRefType m = dyn_cast<MemRefType>(v.getType());
// CHECK(memref.hasStaticShape()) — abort otherwise:
if (!m || !m.hasRank() || hasDynamicDim(m.getShape())) // @0x1d896e20 / @0x1d8921e0
LOG(FATAL) << "Tile tasks only support capture of static memrefs"; // .cc:62
argTypes.push_back(m);
}
FunctionType fty = FunctionType::get(ctx, argTypes, /*results=*/{}); // @0x1d891c80
// 5. Create the func and its entry block.
FuncOp fn = func::FuncOp::create(builder, loc, name, fty); // @0x1d8006a0
Block *entry = fn.addEntryBlock(); // @0xea4b680
// 6. Map each live-in Value → the matching entry block argument.
IRMapping map;
for (i in 0..liveIns.size())
map.map(liveIns[i], entry->getArgument(i)); // DenseMap<Value,Value>
// 7. Clone the tile_task body into the function under the mapping.
region.cloneInto(fn.getBody(), map); // @0x1d8dfa60
// 8. Wire the synthetic entry to the cloned first block.
cf::BranchOp::create(builder, clonedEntry, /*operands=*/{}); // @0x17bd69a0
// 9. Fix up the cloned region's terminators (sequencer-function form).
walk<TerminatorOp>(fn, OutlineSequencerFunction_terminatorCb); // @0x136071c0
// 10. Stamp the engine + budget attributes on the outlined func.
fn.setAttr("sc.sequencer", StringAttr::get(ctx, "execute")); // @0xea37860, value 7 chars
if (hwm)
fn.setAttr("sc.alloc_high_water_mark", hwm); // 24-char name
// 11. Tile-overlayer path (gated on the target/module).
Value overlayAlloc = null;
if (overlayer::IsTileOverlayerEnabled(target)) { // @0x1395d880
int sz = overlayer::GetTileOverlaysSize(target); // @0x1395ba20
fn.setAttr("sc.func_size_limit", getI32IntegerAttr(sz)); // 18-char name
Type ovTy = overlayer::GetTileOverlayMemRefType(target,…); // @0x1395b960
overlayAlloc = memref::AllocOp::create(builder, loc, ovTy);// @ memref alloc
sparse_core::PrefetchTileTaskOp::create(builder, loc, fn, overlayAlloc);
}
// 12. Replace the original op with a launch of the outlined func, then erase it.
Value task = op.getOperand(0); // the tile-task descriptor operand
LaunchTileTaskOp::create(builder, loc, /*execute=*/fn /*FuncOp*/, overlayAlloc, task,
/*captures=*/liveIns, /*clear_ibuf=*/true); // @0x145dd0e0 (FuncOp overload)
op.erase(); // @0x1d8ccd20
return advance;
```text
### 步骤注记
编号步骤各自带有一个值得说明的重新实现细节。
- **步骤 1 — live-in 就是捕获集合。** `getUsedValuesDefinedAbove(region, SetVector)`(`@0x1c974440`)返回 region 使用但在其外定义的每个 SSA value。这些 value 会按*迭代顺序*成为函数参数,因此 launch 必须按相同顺序传递它们(回调会对同一个 `SetVector` 迭代两次:一次构建参数类型,一次构建 IRMapping 和 launch 操作数)。顺序就是 launch site 与函数签名之间的契约。
- **步骤 4 — 静态 memref 捕获会被强制执行,并且是 fatal。** 参数类型循环要求每个捕获 value 都是 `MemRefType`(`TypeIDResolver<MemRefType>` 检查),带 rank,且形状完全静态(shape walk 把 `0x8000000000000000` = `ShapedType::kDynamic` 作为失败哨兵)。动态维度会在 `tile_task_outlining_pass.cc:62` 触发带 `"Tile tasks only support capture of static memrefs"` 的 `LOG(FATAL)`。重新实现者不能提取捕获动态形状 buffer 的 tile task,因为 SC tile 模型没有对应的运行时大小 descriptor。
- **步骤 7 — 使用 `cloneInto`,不是移动。** `Region::cloneInto(funcBody, IRMapping)`(`@0x1d8dfa60`)会在 value mapping 下*克隆* body;原始 op 不会被移动,而是在步骤 12 擦除 op 时删除。这就是为什么步骤 6 需要新 IRMapping(live-in → block-arg):clone 内部的每个外部 use 都会被重写为函数 block 参数。
- **步骤 9 — terminator fix-up 是“sequencer function”形态。** 克隆后,会对新函数进行第二次 `walk`(`@0x136071c0`),把它的 region terminator 重写成 sequencer-function 形态(嵌入的 lambda 名为 `OutlineSequencerFunction`)。这一步把通用克隆 region 变成良构的 `func.func` body。它没有逐行 bit-trace(HIGH)。
- **步骤 10–11 — 三类属性。** 提取出的函数携带:(a) engine 标记 `sc.sequencer = "execute"`;(b) 从 op 的 `sc.execute_alloc_high_water_mark` inherent attr 复制来的逐 task 分配预算 `sc.alloc_high_water_mark`;并且仅当 tile-overlayer 启用时,携带 (c) `sc.func_size_limit`(由 `GetTileOverlaysSize` 给出的 i32)。overlayer 路径还会在 launch 之前注入 overlay buffer 的 `memref.alloc` 和一个 `PrefetchTileTaskOp`。
> **NOTE — high-water-mark 属性从 op 读取,并以不同名称重新盖到 func 上。** 回调从 `tile_task` op 读取 `sc.execute_alloc_high_water_mark`(32 字符名称),然后在提取出的函数上把该值写回为 `sc.alloc_high_water_mark`(24 字符名称)。早期原始分析把它记录为 pass *设置* `sc.execute_alloc_high_water_mark`;反编译显示该名称是*来源*(read),而 `sc.alloc_high_water_mark` 是*目的地*(write)。把 32 字符名称视为 task 上的输入属性,把 24 字符名称视为函数上的输出。
>
> **GOTCHA — 函数名和属性值不是同一个字符串。** 符号是 `"execute" + N`(例如 `execute0`、`execute1`、…),由逐 pass 计数器通过 `APInt::toString` 唯一化。`sc.sequencer` 属性值是裸 `"execute"`(精确 7 字符,也就是 `@0x1459a020` 的 `HasExecuteSequencerTypeAttribute` 谓词检查的长度)。如果重新实现者把 `sc.sequencer` 设成*符号名*,会无法通过 [getSequencerType](getsequencertype.md) 中下游的 length-7 字节比较。符号名可自由命名;engine 标记必须使用裸词。
### 函数映射
| Function | VA | 作用 |
|---|---|---|
| `getUsedValuesDefinedAbove` | `0x1c974440` | region live-in 收集 → arg/capture set |
| `BaseMemRefType::hasRank` | `0x1d896e20` | 捕获 rank 检查 |
| `MemRefType::getShape` | `0x1d8921e0` | 捕获静态形状检查 |
| `FunctionType::get` | `0x1d891c80` | 构建提取出的 func type |
| `func::FuncOp::create` | `0x1d8006a0` | 创建提取出的函数 |
| `FunctionOpInterface::addEntryBlock` | `0xea4b680` | entry block + block args |
| `Region::cloneInto` | `0x1d8dfa60` | 在 IRMapping 下克隆 task body |
| `cf::BranchOp::create` | `0x17bd69a0` | 连接 synthetic entry |
| `StringAttr::get` | `0x1d85dda0` | 构建 attr name/value StringAttrs |
| `Operation::setAttr` | `0xea37860` | 盖上 `sc.sequencer` / budget attrs |
| `Operation::erase` | `0x1d8ccd20` | 删除原始 `tile_task` |
| `overlayer::IsTileOverlayerEnabled` | `0x1395d880` | gate overlay 路径 |
| `overlayer::GetTileOverlaysSize` | `0x1395ba20` | `sc.func_size_limit` 的 i32 size |
| `overlayer::GetTileOverlayMemRefType` | `0x1395b960` | overlay-buffer memref type |
| `memref::AllocOp::create` | `0x183015a0` | overlay buffer 分配 |
| `sparse_core::PrefetchTileTaskOp::create` | `0x145f4cc0` | launch 前的 overlay prefetch |
| `LaunchTileTaskOp::create`(FuncOp overload) | `0x145dd0e0` | 回调发射的 launch |
---
## Launch Op — `sc_tpu.launch_tile_task`
### 目的
该 op 留在提取出的 `tile_task` 原位置。它用符号命名提取出的 `"execute"` 函数,并携带函数运行时将接收的操作数。后续 lowering(`LaunchTileTaskOpLowering` `@0x135901c0`,LLVM conversion pattern)会把它变成实际 launch 序列;arguments-spill pass 会把它的 `execute_func` 符号解析回 `func::FuncOp`。
### Op 形态(来自 `build` / `create`)
存在两个 `create` overload:接收 `func::FuncOp` 的版本(`@0x145dd0e0`,签名 `create(OpBuilder&, Location, func::FuncOp, Value, Value, ValueRange, bool)`)——**也就是 outliner 回调调用的那个**(在 call site `136070ec → 0x145dd0e0` 已验证)——以及预先形成的 `Value` 变体(`@0x145dcfa0`,签名 `create(OpBuilder&, Location, Value, Value, ValueRange, bool)`)。FuncOp overload 转发到规范 builder `LaunchTileTaskOp::build`(`@0x1459c060`,签名 `build(OpBuilder&, OperationState&, Value, Value, UnitAttr, FlatSymbolRefAttr, ValueRange)`),后者把 func 解析为其 `FlatSymbolRefAttr`,并把 `clear_ibuf` 设为 `UnitAttr`。两个 overload 固定同一个 op 表面:
```c
// LaunchTileTaskOp::create(builder, loc, fn /*FuncOp*/, alloc, task, captures, clear_ibuf) @0x145dd0e0
op = OperationState("sc_tpu.launch_tile_task", 23); // op-name literal, 23 chars
// forwards to build(@0x1459c060):
op.addOperands(alloc); // the overlay-alloc Value (overlayer path)
op.addOperands(task); // the tile-task descriptor Value
op.addOperands(captures); // N — the ValueRange of region live-ins
if (clear_ibuf)
properties.clear_ibuf = builder.getUnitAttr(); // UnitAttr present ⇔ true
properties.execute_func = FlatSymbolRefAttr(fn.getSymName()); // symbol of the outlined func
return builder.create(op); // verified TypeID == LaunchTileTaskOp::id| 元素 | 种类 | 来源 |
|---|---|---|
| op name | string "sc_tpu.launch_tile_task" (23) | create @0x145dd0e0 / @0x145dcfa0 |
execute_func | FlatSymbolRefAttr | build @0x1459c060;accessor getExecuteFunc @0x145dcf40 |
clear_ibuf | UnitAttr(存在 ⇔ true) | create bool arg;accessor getClearIbuf @0x145dcf20 |
operand 0(task) | Value | addOperands site 1 |
operand 1(alloc) | Value(overlay buffer) | addOperands site 2 |
| trailing operands | ValueRange(captures) | addOperands site 3 |
execute_func 符号往返
launch 不嵌入函数,而是通过符号引用函数。LaunchTileTaskOp::getExecuteFunc(@0x145dcf40)返回 FlatSymbolRefAttr 的 root-reference StringAttr 值(函数名 "execute<N>")。自由函数 GetExecuteFunc(@0x136054e0)会解析它:它在 launch 的 parent op 上构建 SymbolTable,lookup launch.getExecuteFunc(),并在 tile_task_arguments_spill.cc:70 以 "execute_func != nullptr" 断言结果非空。这是二进制给出的保证:每个 launch 都指向同一 module 中真实存在的提取函数。
QUIRK — outliner 无条件以
clear_ibuf = true调用create。 在回调(@0x136066e0)中,launch 构建时bool参数被置位,因此产生的 op 总是携带clear_ibufUnitAttr。该属性存在是为了让下游消费者可以抑制 instruction-buffer clearing,但 outliner 自身从不产生 cleared-false形式;镜像此 pass 的重新实现者应发射存在的 unit attribute。
Engine 分配 — 字符串的含义
该 pass 在 6acc60406(gfc)上只写 "execute"。完整的三值映射("scs" / "access" / "execute")以及决定 op 落入哪个 region 的逐 op 规则由 getSequencerType 负责;本节只记录 outliner 产生什么以及逐 generation 的形态。
sc.sequencer | Engine | 产生方 | 出现于 | 此 pass 发射 |
|---|---|---|---|---|
"scs" | SCS(标量控制) | 外层函数(发出 launch 的程序) | VF · GL · GF | 间接(parent func) |
"access" | TAC(tile-access / DMA) | 同一 pass 的 VF/GL TAC 拆分 | 仅 VF · GL | 不在 gfc 回调中 |
"execute" | TEC(向量计算) | 此回调(fn.setAttr,值 7 字符) | VF · GL · GF | 是 — 每个 task |
NOTE — 6acc60406 回调只盖
"execute"。 反编译的逐 op 回调(@0x136066e0)设置sc.sequencer = "execute",没有设置其他值;这个gfcbuild 中没有发射"access"的分支,并且 gfc 二进制无法到达 TAC 拆分(6acc60406 上没有SparseCoreTacCodecBase,见 getSequencerType)。逐 op Access-vs-Execute 规则由上游负责(此处 LOW confidence)。NOTE — parent function 的
"scs"标记不是此 pass 设置的。 提取出的"execute"函数在此处创建;外层控制程序上的"scs"标记单独附加(它是此 pass 运行后sc_tpu.launch_tile_taskops 所在的函数)。重新实现者必须确保控制程序携带sc.sequencer = "scs",这样 launch 及其周围的 sync/addressing 代码才会 lower 到 SCS codec。
下游回读
提取出的函数由 LowerSequencerFunctionsPass::runOnOperation(@0x13532120)消费。该 pass walk 每个 LLVM::LLVMFuncOp,通过 ScDialect 谓词读取其 sc.sequencer,并 lower 逐 engine body。字符串到 engine 的谓词已按字节确认:
ScDialect::HasCoreSequencerTypeAttribute(@0x14599ec0)— 匹配"scs"(len 3)。ScDialect::HasExecuteSequencerTypeAttribute(@0x1459a020)— 匹配"execute"(len 7)。ParentHasSequencerTypeAttribute(@0x1353e980)— 沿Block::getParentOp走到外层LLVMFuncOp,并要求它标为"scs"或"execute"。TileTaskOp/LaunchTileTaskOp/PrefetchTileTaskOp/TileTaskWaitOpfamily 携带此 trait。
完整谓词解码(little-endian 字节字面量、"access" 没有谓词的不对称性、trait enforcement)在 getSequencerType 中,本页不重复。交接契约是:此 pass 写字符串;那个 pass 读字符串。 选中的 TpuSequencerType codec 模板参数({SCS=3, TAC=4, TEC=5})随后驱动逐 engine bundle encoder;见 Per-Engine Bundle Slot-Base Map。
重新实现清单
要复现 SparseCore region→sequencer outliner:
- 作为 codegen 前的 module-level pass 运行。 检查 Timem/tile-task 冲突(硬错误),解析一次
SparseCoreTarget,然后walk每个sc_tpu.tile_taskop。 - 按标准 region-outline 配方提取。 live-in 通过
getUsedValuesDefinedAbove获取 → arg types →FuncOp::create+addEntryBlock→IRMapping(live-in → block-arg)→Region::cloneInto→cf::BranchOpentry wiring → terminator fix-up。 - 强制静态 memref 捕获。 拒绝(fatal)任何不是静态形状
MemRefType的捕获 value。 - 符号命名为
"execute"+N;属性标记为裸"execute"。 保持二者不同;下游 reader 会对裸 7 字符值做长度检查。 - 盖上 budget 属性。 把 op 的
sc.execute_alloc_high_water_mark复制到 func 的sc.alloc_high_water_mark;在 overlayer 路径上添加sc.func_size_limit(i32)、一个memref.allocoverlay buffer,以及一个PrefetchTileTaskOp。 - 替换为
sc_tpu.launch_tile_task并擦除原始 op。 launch 通过FlatSymbolRefAttr execute_func引用 func,按 live-in 顺序以ValueRange携带 captures,并设置clear_ibufUnitAttr。 - 推迟 engine decision。 此 pass 物化 engine membership;它不做决策。在 TAC generation 上,应从上游 Stream/DMA classifier 驱动 Access-vs-Execute 拆分(见 getSequencerType)。
置信度摘要
| Claim | Evidence |
|---|---|
runOnOperation gates on Timem+tile-task conflict, resolves target, walks TileTaskOp | decompile @0x13606220: ContainsExplicitTimemAccess + two error fragments + SparseCoreTargetForModule + walk |
| Per-op callback outlines via live-ins → FuncOp → cloneInto → launch → erase | decompile @0x136066e0 (lines 126–476) |
Captures = getUsedValuesDefinedAbove, must be static memrefs | @0x1c974440; hasRank/getShape + LOG(FATAL) tile_task_outlining_pass.cc:62 |
Func name = "execute" + decimal counter (APInt::toString) | callback: qmemcpy("execute",7) + counter pass+352 + APInt::toString |
sc.sequencer stamped "execute" (12-char name, 7-char value) via setAttr | setAttr @0xea37860 with StringAttr "sc.sequencer"/"execute" |
Budget attr read from sc.execute_alloc_high_water_mark (32), written as sc.alloc_high_water_mark (24) | callback getInherentAttr(…,32) read + setAttr(…,24) write |
Overlayer path adds sc.func_size_limit (18) + memref.alloc + PrefetchTileTaskOp | IsTileOverlayerEnabled/GetTileOverlaysSize/GetTileOverlayMemRefType + create calls |
Launch op name "sc_tpu.launch_tile_task" (23 chars); execute_func FlatSymbolRef; clear_ibuf UnitAttr | both create overloads (@0x145dd0e0 / @0x145dcfa0) carry the op-name literal; build @0x1459c060 signature; getExecuteFunc/getClearIbuf accessors |
Outliner always sets clear_ibuf = true | callback passes bool=1 to create |
GetExecuteFunc resolves the symbol via SymbolTable::lookup, asserts non-null | @0x136054e0 tile_task_arguments_spill.cc:70 |
6acc60406 callback emits only "execute"; VF/GL also emit "access" | gfc callback has no "access" branch; cross-gen pass is Target-parameterized (not traced) |
Callback calls the func::FuncOp LaunchTileTaskOp::create overload 0x145dd0e0 (the Value-only overload 0x145dcfa0 is a sibling, not the call here) | disasm call site 136070ec → 0x145dd0e0; 0x145dd0e0 forwards to build @0x1459c060 |
Terminator fix-up walk @0x136071c0 (OutlineSequencerFunction lambda) reshapes the cloned region | callback nested walk call site; lambda name in symbol |
| Per-op Access-vs-Execute region-selection rule | not bit-traced; owned by getSequencerType |
交叉引用
- getSequencerType — engine-selection decision(Stream/DMA classifier +
sc.sequencer回读谓词),其输出会流入此 pass;逐 op region-assignment 规则的负责人。 - SC Backend Pipeline — 在此 outliner 之后、作用于它生成的
func.func的十二个 pass codegen pipeline。 - SparseCore Overview — 三个 engine class(SCS/TAC/TEC)、逐 generation 存在性,以及 SCv0 弃用背景。
- SCS (Scalar) Engine — 发出
sc_tpu.launch_tile_taskops 的"scs"控制程序。 - TAC Engine —
"access"tile-fetch engine 及其在 6acc60406 上的移除(解释为什么 gfc 回调只发射"execute")。 - TEC (Vector) Engine — 提取出的函数面向的
"execute"向量 engine。 - Per-Engine Bundle Slot-Base Map — 提取并 lower 后的函数最终编码进入的逐 engine bundle。
- Binary:
extracted/libtpu-0.0.40-cp314-cp314-manylinux_2_31_x86_64/libtpu/libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d) - Index entry: Part IX — SparseCore & BarnaCore / SparseCore engines — back to index