SC EmitX 调度器
本页中的每个 opcode 值、oneof 标签、跳转表边界、RE2 正则字面量和引擎标签常量,均从
libtpu-0.0.40-cp314wheel(build-id89edbbe81c5b328a958fe628a9f2207d;完整 C++ 符号,未 strip)里的libtpu.so逐字节读取而来,来源包括反编译的ConsumeScalarAluInstruction<glc::SparseCoreTacBundle>函数体、ScSection::ComputeKind、GhostliteEmitter::ConsumeProgram,以及它们位于0xae8db28的.rodata跳转表。其他版本会不同。
摘要
本页记录两个组件,它们针对每条解码后的 MCInst 决定 哪个 EmitX 模板填充哪个引擎的 proto bundle:(1) crc32 section 分类器,通过把 LLVM section 名与静态 RE2 正则表匹配来选择 engine — SCS / TAC / TEC;(2) 位于 0xae8db28、按 bundle 类型划分的 ConsumeScalarAluInstruction 跳转表,从 MCInst opcode 选择 op + EmitX template。二者合起来就是 SparseCore 版的 LLVM 后端“选择指令 → 绑定功能单元 → 调用匹配 emitter”:section 名把 op 路由到三个引擎程序之一,然后 scalar-ALU opcode 块(0x222..0x290)上的 111 项跳转表把每个 op 路由到一个 SparseCoreScalarAlu proto 子消息,以及填充它的 EmitScalar{Unop,Binop,CompareOp,YUnop,Weird} / EmitSetRegister 模板。
这个调度器位于本 wiki 已有的两页之间。上游,SC Backend Pipeline 将 SC dialect 降低到 MLO,并把由 MCInst 组成的 SCStreamer 交给 RunCodeGen。下游,OneSlot Scalar Router 决定一个 scalar op 占用 哪个 bundle slot,并尾调用每个 slot 对应的 Consume<Slot>Instruction 叶子函数。本页是 ScalarAlu slot 中位于 OneSlot router 下一层的叶子:对于 scalar-ALU 块中的任意 opcode,ConsumeScalarAluInstruction 就是 router 抵达的函数,其内部跳转表就是 op→EmitX 选择。OneSlot router 回答“哪个 slot”;这个跳转表回答“哪个 op + 哪个 EmitX 模板填充 ScalarAlu proto”。
最重要的结构事实是:op 选择被拆成两个表,并且 keyed on 两种不同对象。 engine keyed on section name(运行时 RE2 匹配,产生一个 SectionKind 序号,同时也作为引擎标签 6/7/8);op + EmitX template keyed on MCInst opcode(按 (generation, bundle-type) 分开的 .rodata 跳转表)。二者都不是 MLIR pattern table 或 TableGen itinerary,而是从二进制直接读出的手写表。重新实现者需要同时具备正则表和按 (gen,bundle) 划分的 opcode 跳转表;缺少任意一个都会把每个 op 路由错。
重新实现时,契约是:
- 引擎分类器是一个静态 RE2 section-name 表。
ScSection::ComputeKind(0x13afefe0) 懒构建九个RE2正则(__cxa_guard0x224de388),并按固定顺序对 section 名执行FullMatchN;第一次匹配返回一个SectionKindint。随后GhostliteEmitter::ConsumeProgram通过crc32探测flat_hash_map<SectionKind, ScSection>,并按匹配 section 在slot+0x28处的引擎标签路由:8 → Scs,7 → Tec,6 → Tac。 - op 分类器是
0xae8db28跳转表。ConsumeScalarAluInstruction<glc::SparseCoreTacBundle>(0x139f09c0) 读取DWORD[MCInst],减去基址0x222,做0x6e边界检查(111 项),然后间接跳转。37 个不同的表内分支(20 个 direct-accessor、16 个双 opcodeEmitX对、1 个Halt)选择一个SparseCoreScalarAluproto op 和一个EmitScalar*/EmitSetRegister模板;两个块外 opcode(0xb25 → Halt,共享表内Halt分支;以及0xf86 → IsInfOrNan)在默认 fall-through 中处理;所有其他 opcode 命中 “Unsupported opcode for Scalar Alu slot.” - 每个
(generation, bundle-type)都拥有自己的跳转表,覆盖同一个 opcode 块。glc::TacBundle→0xae8db28,glc::ScsBundle→0xaea9df8,vfc::TacBundle→0xae667d8;它们共享基址0x222、边界0x6e和同一 op family。只有EmitX的 bundle-template 参数不同(SparseCoreTacBundlevsSparseCoreScsBundle)。scalar-ALU ISA 在 SCS 和 TAC 引擎之间共享。 - EmitX 填充 proto;它不打包 bit。 每个分支的
EmitScalar*模板填充一个SparseCoreScalarAluoneof 子消息(判别器[proto+0x50],活动消息[proto+0x48])。绝对 bundle bit 位置由后续<Slot>Encoder::Encode→BitCopy阶段写入,位于ConvertToTpuCoreProgram下游。
| Op 跳转表(glc TAC) | ConsumeScalarAluInstruction<glc::SparseCoreTacBundle> @ 0x139f09c0; jt 0xae8db28, base 0x222, bound 0x6e(111 项) |
| 兄弟跳转表 | glc::ScsBundle @ 0x13a4f7c0(jt 0xaea9df8)· vfc::TacBundle @ 0x13985100(jt 0xae667d8)— 同一块、同一 op family |
| Op 分类器输入 | DWORD[MCInst] opcode;索引 opcode − 0x222;37 个表内分支(20 A + 16 B-pairs + 1 Halt)+ default + 2 OOB |
| 引擎分类器 | ScSection::ComputeKind @ 0x13afefe0 — 9 个静态 RE2 正则(__cxa_guard 0x224de388),第一个 FullMatchN 胜出 → SectionKind |
| 引擎路由 | GhostliteEmitter::ConsumeProgram @ 0x139ed5e0 — crc32 flat_hash_map<SectionKind,ScSection> probe → engine tag slot+0x28: 8=Scs, 7=Tec, 6=Tac |
| Proto oneof | SparseCoreScalarAlu; 判别器 [proto+0x50](_oneof_case_[0]),活动 msg [proto+0x48] |
| EmitX family | EmitScalarUnop · EmitScalarBinop · EmitScalarCompareOp<PredicateDest,…> · EmitScalarYUnop · EmitScalarWeird<PredicateDest,…> · EmitSetRegister + 20 个直接 mutable_<op>() accessor |
| 默认错误 | "Unsupported opcode for Scalar Alu slot: $0 : $1"(isa_emitter.cc) |
NOTE — “EmitX dispatcher” 是两个表,不是一个。 不要寻找单一 op→EmitX map。engine 由 section name(运行时 RE2 匹配)选择,op 由 opcode(
.rodata跳转表)选择。OneSlot router(oneslot-router.md)在二者之间插入第三个决策 — bundle 内的 slot。重新实现者需要全部三者;本页负责 engine 分类器和 ScalarAlu op 表。
引擎分类器 — Section Name → Engine Tag
为什么 engine keyed on section name
SparseCore 程序作为带命名 section 的 LLVM object 发出 — .text.tile_access、.text.tile_execute、.text(可带可选的 .scs* 后缀),以及数据 section(smem、tilespmem、spmem、hbm、sflag)。每个 .text* section 对应三个 SC sequencer engine 之一。libtpu 并不把 engine 作为 MCInst 属性携带,而是从 指令被发射到的 section 推导它:SCStreamer::changeSection 对每个 section 分类一次,把得到的 SectionKind 存入 ScSection,之后 ConsumeProgram 再把该 kind 读回作为 engine tag。
ScSection::ComputeKind (0x13afefe0)
分类器是一个静态 RE2 正则表,在 __cxa_guard(0x224de388)保护下懒构建。反编译函数体按以下精确顺序构造九个正则,然后依次对 section 名执行 FullMatchN,并返回第一次匹配项的 SectionKind int:
// ScSection::ComputeKind(string_view section_name) // 0x13afefe0
// returns { ok_flag@+0, kind@+8 }
if (!kSectionRegexes_guard) { // lazy __cxa_guard 0x224de388
RE2::RE2(&e[0].re, "\\.(data|bss)\\.smem"); e[0].kind = 0; // SMEM
RE2::RE2(&e[1].re, "\\.(data|bss)\\.tilespmem"); e[1].kind = 1; // TILE-SPMEM
RE2::RE2(&e[2].re, "\\.(data|bss)\\.spmem"); e[2].kind = 2; // SPMEM
RE2::RE2(&e[3].re, "\\.(data|bss)\\.hbm"); e[3].kind = 3; // HBM
RE2::RE2(&e[4].re, "\\.(data|bss)\\.sflag"); e[4].kind = 4; // SFLAG
RE2::RE2(&e[5].re, "\\.text\\.tile_execute"); e[5].kind = 7; // TEC code
RE2::RE2(&e[6].re, "\\.text\\.tile_access"); e[6].kind = 6; // TAC code
RE2::RE2(&e[7].re, "\\.text(\\.scs.*)?$"); e[7].kind = 8; // SCS code (fallback .text)
RE2::RE2(&e[8].re, ".note.GNU-stack"); e[8].kind = 9; // ELF note marker
kSectionRegexes = e;
}
for (i = 0; i < 9; ++i)
if (RE2::FullMatchN(name, &e[i].re, 0, 0)) {
result.ok = true;
result.kind = e[i].kind; // *(int*)matched_entry
return result;
}
return Error("Unknown section kind for \"...\""); // no regex matched
```text
> **GOTCHA — 正则的*构造*顺序不是 kind 的*序号*顺序。** 该表按 `smem(0), tilespmem(1), spmem(2), hbm(3), sflag(4), tile_execute(7), tile_access(6), .text/.scs(8), GNU-stack(9)` 构建 — 两个 `.text.tile_*` 项存储 kind `7` 和 `6`(注意 `tile_execute=7` 在 `tile_access=6` 之前构建),catch-all `.text` 项存储 `8`。`SectionKind` 是 entry 的 `int` 字段(反编译中写在每个 regex slot 前一个 DWORD:`*v8=0`、`v8[38]=1`、…、`v8[266]=8`、`v8[304]=9`),**不是**它在表中的位置。按表索引分配 kind 的重新实现会交换 TEC/TAC,并把 SCS 标错。
>
> **GOTCHA — `.text(\.scs.*)?$` 是 SCS *fallback*。** 普通 `.text`(没有 `.tile_access` / `.tile_execute` 后缀),无论是否带 `.scs*` 后缀,都会分类为 SCS(kind 8 — 与 `ConsumeProgram` 路由使用的值相同,因此不存在 `9→8` remap)。因此两个 tile section 必须在裸 `.text` 正则之前测试,构造顺序保证了这一点(entry 5/6 在 entry 7 之前)。任何既不是 tile-access 也不是 tile-execute 的 SC text section 默认都是 SCS。
### 分类器 map 和引擎路由
`SCStreamer::changeSection` (`0x13b03e20`) 调用 `ComputeKind`,然后在 `SCStreamer+0x140` 处的 `flat_hash_map` 中 `EmplaceDecomposable` 一个 `{SectionKind → ScSection}` entry,把 kind 盖到 `ScSection+0x20`(= map slot 的 `+0x28`)。在 `SCStreamer+0x240` 的按 generation 标志下,map key 会应用一个 `TAC(6) → TEC(7)` key remap(`cmove`)。
`GhostliteEmitter::ConsumeProgram` (`0x139ed5e0`) 遍历 `SCStreamer+0x128` 的有序 `SectionKind` 列表,用 `crc32` 探测 map,并按匹配 section 在 `slot+0x28` 的 engine tag 路由。反编译函数体对 key 及其三倍值使用 SSE4.2 `crc32`(`_mm_crc32_u64(0, key)` 和 `_mm_crc32_u64(…, 3*key)`),然后默认构造匹配的 engine program:
```c
// GhostliteEmitter::ConsumeProgram // 0x139ed5e0 (engine route)
R8 = _mm_crc32_u64(0, key);
h = _mm_crc32_u64(*(u16*)(a2+0x148), 3*key) | (R8 << 32); // flat_hash_map probe
... SSE4.2 group probe (vpcmpeqb / vpmovmskb / tzcnt) → matched slot ...
tag = *(u32*)(slot + 0x28); // engine tag
if (tag == 8) DefaultConstruct<SparseCoreScsProgram>(); // SCS (seq3)
if (tag == 7) DefaultConstruct<SparseCoreTecProgram>(); // TEC (seq5)
if (tag == 6) DefaultConstruct<SparseCoreTacProgram>(); // TAC (seq4)SectionKind(engine tag) | Engine program(DefaultConstruct) | Seqtype | Bundle | 源 section |
|---|---|---|---|---|
6 | SparseCoreTacProgram(gxc::glc::isa) | 4 TAC | SparseCoreTacBundle | .text.tile_access |
7 | SparseCoreTecProgram | 5 TEC | SparseCoreTecBundle | .text.tile_execute |
8 | SparseCoreScsProgram | 3 SCS | SparseCoreScsBundle | .text / .text.scs*(regex kind 8) |
crc32 probe、tag == 7/6/8 dispatch,以及 gxc::glc::isa::SparseCore{Tec,Tac,Scs}Program default-construct 都已在 ConsumeProgram 反编译中确认。ViperfishEmitter::ConsumeProgram (0x13981d20) 形状相同,但使用 vfc program 类型。
NOTE — SCS regex kind 与 SCS engine tag 是同一个值(8);没有 normalization edge。
ComputeKind为.text(\.scs.*)?$正则分配SectionKind = 8(反编译中存于v8[266]的int),而ConsumeProgram对 engine tag8路由到 SCS — regex kind 直接作为 map key,并且无9→8步骤地匹配 route tag。Kind9是.note.GNU-stack标记(v8[304]),不是 SC code section,也不会抵达DefaultConstruct。changeSection应用的唯一 key transform 是+0x240标志下的6→7(TAC→TEC)remap。完整 regex kind SET 是{smem=0, tilespmem=1, spmem=2, hbm=3, sflag=4, tile_access=6, tile_execute=7, .text/.scs=8, GNU-stack=9};engine-tag SET 是{6=TAC, 7=TEC, 8=SCS}。
Op 跳转表 — Opcode → Proto Op → EmitX
ConsumeScalarAluInstruction<glc::SparseCoreTacBundle> (0x139f09c0)
一旦 engine program 建好,且 OneSlot router 已经把 scalar-ALU op 放入 ScalarAlu slot,这个函数就把该 op 降低到该 slot 的 SparseCoreScalarAlu proto。它是一个基于 opcode 的单次间接跳转:
// ConsumeScalarAluInstruction<glc::SparseCoreTacBundle> // 0x139f09c0
// args: (printer a2/*MCInst*/, a4, proto a3 /*SparseCoreScalarAlu*/)
opcode = *(u32*)MCInst; // DWORD[MCInst]
idx = opcode - 0x222; // jt base 0x222
if ((unsigned)idx > 0x6e) // bound 0x6e (111 entries)
goto OUT_OF_BLOCK;
switch jt[idx]: // jt @0xae8db28, 111×int32 rel offsets, 37 in-table arms
... per-arm: select SparseCoreScalarAlu oneof + EmitX template ...
OUT_OF_BLOCK:
if (opcode == 0xb25) { ...clear_inst; oneof=6; DefaultConstruct Halt; return OK; } // 2853
if (opcode == 0xf86) { p = mutable_is_inf_or_nan(proto);
return EmitScalarWeird<PredicateDest, IsInfOrNan>(MCInst, p); } // 3974
... else → DEFAULT error ...
```text
反编译将跳转表渲染为 C `switch (*(_DWORD *)a2)`;底层 prologue 是原始叙述记录的间接跳转(`lea eax,[r15-0x222]; cmp eax,0x6e; ja OOB; lea rdx,[rip→0xae8db28]; movsxd rax,[rdx+rax*4]; add rax,rdx; jmp rax`)。两个块外 opcode(`0xb25`、`0xf86`)在边界检查之后的 `default:` block 中处理,不在表内。
### 分支形状
37 个表内分支均为以下三种形状之一,已在反编译中逐字节确认(OOB `IsInfOrNan` 是第四种、形似 `EmitScalarWeird` 的分支,但只通过 `default:` block 抵达):
| 形状 | 行为 | 示例 |
|---|---|---|
| **A — direct accessor** | `p = SparseCoreScalarAlu::mutable_<op>(proto)`,随后以 `p` 尾调用 `EmitScalarUnop`/`EmitSetRegister`,或内联填充子消息并 `return OK`。20 个 op。 | `0x222`: `mutable_ceiling` → `EmitScalarUnop<…,Ceiling>` |
| **B — 双 opcode EmitX** | 一对 opcode 都测试 oneof 判别器 `[proto+0x50] == <tag>`;若尚未设置,则 `clear_inst`,设置 `[proto+0x50] = <tag>`,`Arena::DefaultConstruct<SparseCoreScalarAlu_<Op>>`,存到 `[proto+0x48]`;然后尾调用 `EmitScalar*`/`EmitSetRegister<Op,Bundle>`。16 个分支对。 | `0x223,0x224`: oneof `37`, `EmitScalarCompareOp<PredicateDest,…,CompareFloatingPointEq>` |
| **H — Halt** | `clear_inst`;设置 oneof `[proto+0x50] = 6`;`DefaultConstruct<SparseCoreScalarAlu_Halt>`;`return OK`。从 `0x23b`(表内)和 `0xb25`(OOB)抵达。 | `0x23b`: `goto Halt` |
B-arm pair 的两个 opcode 是同一 op 的 **predicated 和 non-predicated MCInst 形式** — 二者都路由到相同 oneof tag 和 EmitX 模板。(这里不解码两种形式之间的精确 MCInst operand 差异。)
### TABLE A — 完整的 `0xae8db28` opcode → op → EmitX map(glc TacBundle)
下表每一行均从 `ConsumeScalarAluInstruction<glc::SparseCoreTacBundle>` (`0x139f09c0`) 的反编译 `switch` 读取。`oneof` 列是写入 `[proto+0x50]` 的判别器值(二进制中为十进制;十六进制用于交叉引用)。
| opcode(s) | oneof(dec/hex) | op(`SparseCoreScalarAlu_<X>`) | arm | EmitX template / accessor(TacBundle) |
|---|---|---|---|---|
| `0x222` | — | `Ceiling` | A | `mutable_ceiling` → `EmitScalarUnop<…,Ceiling>` |
| `0x223,0x224` | 37 / `0x25` | `CompareFloatingPointEq` | B | `EmitScalarCompareOp<PredicateDest,…,…Eq>` |
| `0x225,0x226` | 40 / `0x28` | `CompareFloatingPointGte` | B | `EmitScalarCompareOp<PredicateDest,…,…Gte>` |
| `0x227,0x228` | 39 / `0x27` | `CompareFloatingPointGt` | B | `EmitScalarCompareOp<PredicateDest,…,…Gt>` |
| `0x229,0x22a` | 42 / `0x2a` | `CompareFloatingPointLte` | B | `EmitScalarCompareOp<PredicateDest,…,…Lte>` |
| `0x22b,0x22c` | 41 / `0x29` | `CompareFloatingPointLt` | B | `EmitScalarCompareOp<PredicateDest,…,…Lt>` |
| `0x22d,0x22e` | 38 / `0x26` | `CompareFloatingPointNeq` | B | `EmitScalarCompareOp<PredicateDest,…,…Neq>` |
| `0x22f` | — | `Floor` | A | `mutable_floor` → `EmitScalarUnop<…,Floor>` |
| `0x231,0x232` | 29 / `0x1d` | `MaxOfTwoFloatingPointValues` | B | `EmitScalarBinop<…,Max…>` |
| `0x233,0x234` | 30 / `0x1e` | `MinOfTwoFloatingPointValues` | B | `EmitScalarBinop<…,Min…>` |
| `0x237,0x238` | 17 / `0x11` | `ConvertFloat32ToInt32` | B | `EmitScalarUnop<…,ConvertF32ToI32>` |
| `0x23b` | 6 | `Halt` | H | `clear_inst` → `DefaultConstruct Halt` |
| `0x24d` | — | `delay` | A | `mutable_delay`; inline `[+24]=operand`, `[+16] \|= 1`; `return OK` |
| `0x254` | — | `scalar_fence` | A | `mutable_scalar_fence` |
| `0x255` | — | `scalar_fence_scmf` | A | `mutable_scalar_fence_scmf` |
| `0x256,0x257` | 64 / `0x40` | `ScalarFenceSelect` | B | `EmitScalarYUnop<…,ScalarFenceSelect>` |
| `0x259` | — | `scalar_fence_stream_hbm` | A | `mutable_scalar_fence_stream_hbm` |
| `0x25a` | — | `scalar_fence_stream_spmem` | A | `mutable_scalar_fence_stream_spmem` |
| `0x25d,0x25e` | 16 / `0x10` | `ConvertInt32ToFloat32` | B | `EmitScalarUnop<…,ConvertI32ToF32>` |
| `0x265` | — | `pop_drf` | A | `mutable_pop_drf` |
| `0x269` | — | `read_register_dif_depth_register` | A | `mutable_read_register_dif_depth_register` |
| `0x26b` | — | `read_register_fence_status` | A | `mutable_read_register_fence_status` |
| `0x26c` | — | `read_register_gtc_high` | A | `mutable_read_register_gtc_high` |
| `0x26d` | — | `read_register_gtc_low` | A | `mutable_read_register_gtc_low` |
| `0x26e` | — | `read_register_lcc_high` | A | `mutable_read_register_lcc_high` |
| `0x26f` | — | `read_register_lcc_low` | A | `mutable_read_register_lcc_low` |
| `0x270` | — | `read_register_sparse_core_id` | A | `mutable_read_register_sparse_core_id` |
| `0x271` | — | `read_register_tag` | A | `mutable_read_register_tag` |
| `0x272` | — | `read_register_task_bitmap` | A | `mutable_read_register_task_bitmap` |
| `0x273` | — | `read_register_tracemark` | A | `mutable_read_register_tracemark` |
| `0x274` | — | `read_register_yield_request` | A | `mutable_read_register_yield_request` |
| `0x27b,0x27c` | 73 / `0x49` | `SetDmaCredit` | B | `EmitSetRegister<SetDmaCredit,…>` |
| `0x27d,0x27e` | 72 / `0x48` | `SetIndirectFilterValue` | B | `EmitSetRegister<SetIndirectFilterValue,…>` |
| `0x27f,0x280` | 71 / `0x47` | `SetPrefetchDepth` | B | `EmitSetRegister<SetPrefetchDepth,…>` |
| `0x283,0x284` | 74 / `0x4a` | `SetDmaThrottleSflagRange` | B | `EmitSetRegister<SetDmaThrottleSflagRange,…>` |
| `0x285,0x286` | 8 | `SetTag` | B | `EmitSetRegister<SetTag,…>` |
| `0x290` | — | `read_register_tileid` | A | `mutable_read_register_tileid` |
| `0xb25`(OOB) | 6 | `Halt` | H | OOB: `opcode == 2853` → Halt arm |
| `0xf86`(OOB) | — | `IsInfOrNan` | — | OOB: `mutable_is_inf_or_nan` → `EmitScalarWeird<PredicateDest,IsInfOrNan>` |
| **DEFAULT** | — | — | — | 58 个 opcode(`0x230, 0x235, 0x236, 0x239, 0x23a, 0x23c–0x24c, 0x24e–0x253, 0x258, 0x25b, 0x25c, 0x25f–0x264, 0x266–0x268, 0x26a, 0x275–0x27a, 0x281, 0x282, 0x287–0x28f`)→ `"Unsupported opcode for Scalar Alu slot: $0 : $1"` |
### EmitX 模板 family(proto 填充层)
这些分支恰好抵达六个 `EmitX` 模板 family 加 20 个 direct accessor(其中两个 — `Ceiling`、`Floor` — 将 `mutable_<op>` accessor 与 `EmitScalarUnop` 配对;`IsInfOrNan` 的 `mutable_is_inf_or_nan` 则供给 OOB `EmitScalarWeird` 分支)。这些模板**填充** proto 子消息;它们不写 bundle bit(那是下游 `<Slot>Encoder::Encode` 的工作)。
| Family | 抵达它的 op | 说明 |
|---|---|---|
| `EmitScalarUnop<Bundle,Op>` | `Ceiling`, `Floor`, `ConvertFloat32ToInt32`, `ConvertInt32ToFloat32` | 单源 scalar op |
| `EmitScalarBinop<Bundle,Op>` | `MaxOfTwoFloatingPointValues`, `MinOfTwoFloatingPointValues` | 双源 scalar op |
| `EmitScalarCompareOp<PredicateDest,Bundle,Op>` | `CompareFloatingPoint{Eq,Gt,Gte,Lt,Lte,Neq}` | 结果是 predicate(注意 `PredicateDest`) |
| `EmitScalarYUnop<Bundle,Op>` | `ScalarFenceSelect` | Y-form unop |
| `EmitScalarWeird<PredicateDest,Op>` | `IsInfOrNan` | 只通过 OOB opcode `0xf86` 抵达 |
| `EmitSetRegister<Op,Bundle>` | `SetDmaCredit`, `SetIndirectFilterValue`, `SetPrefetchDepth`, `SetDmaThrottleSflagRange`, `SetTag` | register-write ops |
| direct `mutable_<op>()` | `delay`, `pop_drf`, `scalar_fence[_scmf/_stream_hbm/_stream_spmem]`, `read_register_*`(12) | 简单 op;没有独立 `EmitX` |
> **NOTE — `EmitScalarCompareOp` 和 `EmitScalarWeird` 携带 `PredicateDest`。** 六个浮点比较和 `IsInfOrNan` 产生 predicate 结果,因此其模板的第一个参数是 `PredicateDest`(predicate-register 目的端),不是 bundle type。对于 compare,bundle type 是第二个参数;对 `Weird` 则只隐含一个参数。重新实现者必须在这些分支中传递 predicate destination,这不同于写 scalar register 的 `Unop`/`Binop`/`SetRegister` 分支。
---
## Per-(Generation, Bundle) 跳转表 family
`ConsumeScalarAluInstruction` 是按 bundle type 模板化的,每个 `(generation × bundle-type)` 对都会用自己的 `.rodata` 跳转表实例化它,覆盖**同一个** `0x222..0x290` opcode 块。它们的 op→proto 映射相同;只有 `EmitX` bundle-template 参数和按 generation 划分的 namespace 不同。
| Consumer | Address | Jump table | Base | Bound | Block |
|---|---|---|---|---|---|
| `glc ConsumeScalarAluInstruction<…TacBundle>` | `0x139f09c0` | `0xae8db28` | `0x222` | `0x6e` | `0x222..0x290` |
| `glc ConsumeScalarAluInstruction<…ScsBundle>` | `0x13a4f7c0` | `0xaea9df8` | `0x222` | `0x6e` | `0x222..0x290` |
| `vfc ConsumeScalarAluInstruction<…TacBundle>` | `0x13985100` | `0xae667d8` | `0x222` | `0x6e` | `0x222..0x290` |
| `vfc ConsumeScalarAluInstruction<…ScsBundle>` | `0x139d71a0` | (own jt) | `0x222` | `0x6e` | `0x222..0x290` |
| `glc/vfc ConsumeScalarAluInstruction<…TecBundle>` | `0x13a14b60` / `0x139a8dc0` | (own jt) | `0x222` | `0x6e` | `0x222..0x290` |
`glc::ScsBundle` consumer 以 `SparseCoreScsBundle` 模板参数抵达相同的 `SparseCoreScalarAlu` op family(`EmitScalarUnop/Binop/CompareOp<PredicateDest>/YUnop<…,SparseCoreScsBundle,…>`)。**因此 scalar-ALU ISA 在 SCS(seq3)和 TAC(seq4)引擎之间共享** — 唯一的按引擎差异是 EmitX 写入的 bundle slot(由上面的 engine 分类器选择,引擎内 slot 由 [OneSlot router](oneslot-router.md) 选择)。
> **GOTCHA — 相同 opcode,不同 bundle,相同 op 但不同 slot。** 像 `0x222`(Ceiling)这样的 scalar-ALU opcode 在 TAC 和 SCS consumer 中都会路由到 `EmitScalarUnop<…,Ceiling>` — 同一个 proto op,不同 bundle-template 参数,不同物理 slot。重新实现者应写一次 op-selection switch 并按 bundle 参数化;不得按 engine 复制。38-arm 结构、base 和 bound 在所有实例化之间不变。
>
> **NOTE — TEC bundle 的非 scalar op 在别处调度。** 上述 TEC `ConsumeScalarAluInstruction` 实例化只处理 TEC bundle 的 *scalar-ALU* slot。TEC vector slot(`VectorAlu`/`VectorLoad`/`VectorStore`/`VectorExtended`)由单独的 `ConsumeOneTecBundleInstruction` (`0x13a08e00`) 路由,并通过 `ConsumeVectorAluInstruction` 抵达 142-op `VectorAlu` 表 — 参见 [OneSlot Scalar Router](oneslot-router.md) 和 [TEC Vector Opcode Enumeration](vector-opcode-enum.md)。
---
## 端到端路由
从解码后的 `MCInst` 到已填充 proto op 的完整路径,以及每个表及其 key:
```text
SC dialect → LowerToSparseCoreLlvmPass → LLVM backend → MCInst(位于命名 section 中)
│
▼ SCStreamer::changeSection (0x13b03e20)
│ ScSection::ComputeKind (0x13afefe0) ── RE2 section-name table → SectionKind
│ EmplaceDecomposable {SectionKind → ScSection} into flat_hash_map @SCStreamer+0x140
│ stamp kind into ScSection+0x20 (engine tag); TAC(6)→TEC(7) key remap under +0x240
│
▼ RunCodeGen → MakeTpuCoreProgram (gen switch: variant_name → glc / vfc)
│
▼ {Ghostlite,Viperfish}Emitter::ConsumeProgram (0x139ed5e0 / 0x13981d20)
│ crc32 probe flat_hash_map → engine tag @slot+0x28
│ 8 → SparseCoreScsProgram (SCS, seq3)
│ 7 → SparseCoreTecProgram (TEC, seq5)
│ 6 → SparseCoreTacProgram (TAC, seq4)
│ per-engine: GetScalarAluSlot + EmitPredicationToSlot + ↓
│
▼ ConsumeScalarAluInstruction<gen::Bundle> ── JUMP TABLE on DWORD[MCInst] (THIS PAGE)
│ idx = opcode - 0x222; bound 0x6e; jt[idx] → 1 of 37 in-table arms (+ 2 OOB + default)
│ arm: select SparseCoreScalarAlu oneof [proto+0x50] + active msg [proto+0x48]
│
▼ EmitScalar{Unop,Binop,CompareOp,YUnop,Weird} / EmitSetRegister<Op,Bundle>
│ populate the proto submessage (NO bit packing)
│
▼ ConvertToTpuCoreProgram → <Engine>CodecBase::Encode → <Slot>Encoder::Encode → BitCopy
the absolute bundle bit positions (downstream; not this page)两个分类器 key 彼此正交:section name(RE2 → SectionKind → engine tag)选择 engine program,从而选择运行哪个 ConsumeScalarAluInstruction<…Bundle> 实例化;MCInst opcode(0xae8db28 jt)在其中选择 op + EmitX template。
GOTCHA — op/kind 映射中两个相邻的
0xC0陷阱。 有两个赋值很容易反过来,这里按字节固定:SCS 正则.text(\.scs.*)?$存储SectionKind8 — 与ConsumeProgram读回作为 SCS engine tag 的值相同(v8[266]=8),因此不存在9↔8normalization edge;kind 9 只属于.note.GNU-stack(v8[304])。而0x25d,0x25e是ConvertInt32ToFloat32(oneof 16),不是Max/Min—Max/Min位于0x231,0x232/0x233,0x234(oneof 29/30)。
交叉引用
- SC Backend Pipeline — 将 SC dialect 降低到本调度器消费的
MCInststream 的十二遍 MLIR pipeline;位于RunCodeGen上一层。 - OneSlot Scalar Router — scalar-slot 放置 router,选择一个 scalar op 占用 哪个 bundle slot,并为 ScalarAlu slot 尾调用
ConsumeScalarAluInstruction(本页)。 - TEC Vector Opcode Enumeration — 单独 vector dispatcher 抵达的 142-op
VectorAlu表;本 scalar op 表的 vector 对应物。 - SCS Scalar Opcode Enumeration — scalar opcode 名册,以及本调度器上游的
setSlotFlagInMCInstslot-flag 纪律。 - SCS (Scalar) Engine — seq3 SCS 引擎(engine tag 8);与 TAC 共享 scalar-ALU op family。
- TAC Engine — seq4 tile-access 引擎(engine tag 6);本页记录的
glc::TacBundle跳转表所有者。 - TEC (Vector) Engine — seq5 vector 引擎(engine tag 7);其 scalar-ALU slot 复用此 op 表,其 vector slot 则不复用。
- getSequencerType — SCS/TAC/TEC engine-selection 函数;本页 section 分类器的 engine-tag 对应物。
- SparseCore Overview — 三类 engine、按 generation 的存在情况(vfc / glc / gfc)以及 codec-template sequencer enum。
- Binary:
extracted/libtpu-0.0.40-cp314-cp314-manylinux_2_31_x86_64/libtpu/libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d) - 索引项: Part IX — SparseCore & BarnaCore / SparseCore back-end — 返回索引