vcreate_mask 与 M-Register(Vector-Mask)文件
本页中的每一个偏移、地址、位位置、常量和立即数,都按字节精确读取自
libtpu-0.0.40-cp314wheel 中的libtpu.so(BuildID md589edbbe81c5b328a958fe628a9f2207d,781,691,048 字节,未 strip)。.text/.rodataVMA 等于其文件偏移。其他 libtpu 构建会不同。
摘要
SparseCore VPU 通过一个 32 项的向量掩码寄存器文件(M0..M31,LLVM MPRRegClass)执行逐 lane 谓词控制。带掩码的向量 op 用一个 5 位选择器命名一个 M-register;被命名的 M-register 就是 二维 {sublane-range × lane-range} 的 active/inactive 谓词。本页记录一个 M-register 如何被构造:vcreate_mask(LLVM-MIR llo.vcmask)指令用一个 32 位标量立即数,在 Target::GetVcmaskFieldOffsets() 返回的字段偏移处打包四个范围边界;以及构造出的掩码随后如何被消费,并以 masked-scan 家族作为完整示例。
重新实现时必须精确复现两个架构事实,还有一个很容易出错的点。第一,谓词是基于范围的,而不是存储的 bitmask:一个 M-register 编码一个矩形 {sublane ∈ [s_start, s_end]} ∧ {lane ∈ [l_start, l_end]},并且结束边界是闭区间(最后一个 active 索引)。第二,六个 xla::jellyfish::Target 子类中只有两个有原生 vcreate_mask 指令:ViperfishTarget(v5p,v5e 共用)和 GhostliteTarget(v6e,也被 v7x 6acc60406/gfc 复用,因为 v7x 没有自己的 Target 子类);另外三个(Jellyfish、Dragonfish、Pufferfish)会从 iota 比较合成相同矩形,从不读取字段偏移。第三,也就是 masked-scan 陷阱:M-mask 是 scan op 本身的内在操作数(sc_tpu.scan operand[0]);它不是编译器插入的 scan 后 select。inactive 输入 lane 贡献 reduction identity。
重新实现的契约是:
- 原生 packed-word 布局:
WORD = (s_start<<0) | (l_start<<3) | (s_end<<10) | (l_end<<13),其中 sublane 字段为 3 位(⇒ 8 个 sublane),lane 字段为 7 位(⇒ ≤128 个 lane),在两个原生代际之间稳定。 - 每代
GetVcmaskFieldOffsets()/HasVcmaskInstruction()的扫描结果:哪些代际是原生、哪些是合成,以及字段偏移{0,3,10,13}不会随每代 lane 数变化而漂移。 - 结束边界闭区间约定,以及三个 builder API 边界上的
+1/−1调整(CreateVmask为闭区间,CreateLaneVmask/CreateSublaneVmask为半开区间)。 - masked-scan 的 inactive-lane 模型:M-mask = scan-op operand[0],inactive 输入 lane = reduction identity,没有按家族由编译器选择的 else 操作数。
| 掩码寄存器文件 | Vmreg / LLVM TPU::MPRRegClass @ 0x2192f0c0 — 32 项 M0..M31 |
| 掩码选择器(读带) | GetVectorMask @ 0x13a33320 — reg-id ∈ [0x5f, 0x7e],值 = regid − 0x5f ∈ [0,31] |
| 掩码写目的带 | GetVMDestregno @ 0x13a65b20 — reg-id ∈ [0x5f, 0x6e] = M0..M15(16 深度写子集) |
| 原生构造 op | LloRegionBuilder::Vcmask(s_start, s_end, l_start, l_end) @ 0x1d53f9c0 → llo.vcmask |
| Packed word | (s_start<<0) | (l_start<<3) | (s_end<<10) | (l_end<<13)(偏移 {0,3,10,13}) |
| 字段偏移 | Target::GetVcmaskFieldOffsets()(vtable +0x420)→ Viperfish + Ghostlite 上为 {0,3,10,13} |
| 原生门控 | Target::HasVcmaskInstruction()(vtable +0x410)— VF/GL 上为 1,JF/DF/PF 上为 0 |
| MLIR 消费 op | mlir::llo::VectorCreateMaskOp::create @ 0x13fb3ba0 → mnemonic llo.vcmask |
| Masked scan op | mlir::sparse_core::ScanOp::create(builder, loc, Type, MASK, DATA, reduction) @ 0x145f93e0 → sc_tpu.scan |
| 几何形状 | SublaneCount = 8(3 位字段);LaneCount ≤ 128(7 位字段) |
| 置信度 | 除非行内另有说明,否则为 CONFIRMED(字节锚定) |
M-Register 是范围矩形,不是 Bitmask
第一个重新实现陷阱,是把 M-register 当成逐 lane 的 bit 数组。它不是这样存储的。每个 M-register 保存一个二维矩形描述符:一对闭区间边界 {sublane-range × lane-range}。完整的逐 (lane × sublane) 谓词是在消费者处从该矩形物化出来的,而不是在寄存器中逐 bit 保存。
消费 op 中的 5 位字段(SC VEX bundle 的 bit0x104..0x108,由 VEX Mask / Dest-Port / Sub-Opcode 拥有;消费者侧的 Vmsk 选择文件见 VPU Slot §predicate and vector-mask files)只是一个选择器:它命名 32 个 M-register 中哪一个提供谓词。GetVectorMask(0x13a33320)按字节精确解码它:断言 operand.isReg(),然后 reg-id ∈ [0x5f, 0x7e](断言 "regno >= llvm::TPU::M0" / "regno <= llvm::TPU::M31",二者都存在于 .rodata),返回 regid − 0x5f:
// GetVectorMask<SparsecoreVectorMask> @ 0x13a33320 (decompiled, exact)
if (op.kind != Reg) LogFatal("operand.isReg()"); // isa_emitter_base.h:555
v = op.reg; // [a1 + 8]
if (v <= 0x5E) LogFatal("regno >= llvm::TPU::M0"); // :557
if (v >= 0x7F) LogFatal("regno <= llvm::TPU::M31"); // :558
return v - 95; // regid - 0x5f ∈ [0, 0x1f] = M0..M31
```text
> **陷阱 — 选择器 ≠ 谓词。** 这个 5 位字段是*索引*,不是掩码。把它当成 lane bitmask、sublane 计数或谓词值,会把每个带掩码向量 op 都编码错。它命名 32 个掩码寄存器之一;被选中的寄存器保存矩形。该选择器也与整条 op 的标量谓词控制([Predicate File](slot-predicate.md) 的 `Preg`)正交,后者门控整条指令;一个带掩码向量 op 同时携带逐 lane M-mask 和整 op 的 `Preg` guard。
32 深度读带有一个 16 深度写子集。*产生*掩码结果的 op(index-scan、uniquify)通过 `GetVMDestregno`(`0x13a65b20`)写入,其带为 `[0x5f, 0x6e]` = M0..M15(断言 `"regno <= llvm::TPU::M15"`,存在于 `.rodata`)。因此 M16..M31 是只读谓词输入(编译器物化的 `vcreate_mask` 结果);只有 M0..M15 是合法的 op 写目的。读 32 / 写 16 的拆分就是 M-register 文件的分区模型。
---
## 原生 vcreate_mask — Packed-Word 布局
在两个原生 SC 代际上,M-register 由一条 `vcreate_mask` 指令构造:一个 32 位标量立即数打包四个矩形边界,随后 `LloInstruction::CreateVectorCreateMask` 将其转换为 `llo.vcmask` op。打包函数是 `LloRegionBuilder::Vcmask(s_start, s_end, l_start, l_end)`(`0x1d53f9c0`),其函数体在反编译中按字节精确确认,会从 target vtable 读取字段偏移并把每个边界移到对应位置:
```c
// LloRegionBuilder::Vcmask(s_start, s_end, l_start, l_end) @ 0x1d53f9c0 (decompiled)
// args: a2 = s_start, a3 = s_end, a4 = l_start, a5 = l_end
offs = (*vtable[1056])(target); // vtable +0x420 = GetVcmaskFieldOffsets()
// rax (offs.lo) = 0x300000000 → off0 = 0 (low dword), off1 = 3 (high dword)
// rdx (offs.hi) = 0xd0000000a → off2 = 10 (low dword), off3 = 13 (high dword)
WORD = (s_start << off0) // a2 << 0
| (l_start << off1) // a4 << 3
| (s_end << off2) // a3 << 10
| (l_end << off3); // a5 << 13
imm = LloModule::ScalarU32ConstantImpl(WORD); // @ 0x1d506020
m = LloInstruction::CreateVectorCreateMask(imm); // @ 0x1d4db820 → llo.vcmask
LloRegion::AppendInstruction(region, m); // @ 0x1d50f9a0反编译器把四个移位呈现为 (a4 << SBYTE4(v11)) | (a2 << v11) | (a3 << v12) | (a5 << SBYTE4(v12)),其中 v11 = 0x300000000(所以把 v11 当 int32 得到 0,SBYTE4(v11) = 3),v12 = 0xd0000000a(所以把 v12 当 int32 得到 10,SBYTE4(v12) = 13)。该指令还会盖上人类可读注解 [s_start:s_end,l_start:l_end](由 CatPieces 从字面量 "["、":"、","、":"、"]" 构造),这独立确认了 (s_start, s_end) / (l_start, l_end) 的字段分配。
| 字段 | Vcmask 参数 | 移位 | 位 | 宽度 | 含义 |
|---|---|---|---|---|---|
sublane_start | a2 (s_start) | << 0 | [2:0] | 3 | 第一个 active sublane (0..7) |
lane_start | a4 (l_start) | << 3 | [9:3] | 7 | 第一个 active lane (0..127) |
sublane_end | a3 (s_end) | << 10 | [12:10] | 3 | 最后一个 active sublane(闭区间) |
lane_end | a5 (l_end) | << 13 | [19:13] | 7 | 最后一个 active lane(闭区间) |
3 位 sublane 字段(start 在 bit 0,end 在 bit 10,每个 3 位宽,因为 off1 − off0 = 3 且 off3 − off2 = 3)固定出 SublaneCount = 8;LloModule::VectorMaskConstantPacked(uint8) 字面量掩码构造器(0x1d506a80)是一个 8 位 packed sublane 掩码,也独立确认了 8 个 sublane。7 位 lane 字段(off2 − off1 = 7,end 在 bit 13)界定 LaneCount ≤ 128。几何叶子为 Target::SublaneCount() = QWORD[[Target+0x3b8]+0x1a0](0x1d60f300)和 Target::LaneCount() = QWORD[[Target+0x3b8]+0x198](0x1d60f400);精确的每代 LaneCount 位于运行时配置 blob 中,而不是这条代码路径中(LOW — 有界 ≤ 128,精确值不在代码中)。
示例:覆盖 sublane 0..3 和 lane 16..63 的掩码(所有边界为闭区间,传给 Vcmask 的值):
// s_start=0, s_end=3, l_start=16, l_end=63
WORD = (0 << 0) // 0x00000000 sublane_start
| (16 << 3) // 0x00000080 lane_start
| (3 << 10) // 0x00000c00 sublane_end (inclusive)
| (63 << 13); // 0x0007e000 lane_end (inclusive)
// = 0x0007ec80
```text
从半开区间 `CreateLaneVmask(16, 64)` 编码的重新实现会得到同一个 `lane_end = 63`,因为原生路径会减 1(`l_hi − 1`)。补集(例如 `[16,63]` 之外的 lane)在原生代际的 `vcreate_mask` 边界上*不是*一个 `start > end` packed word,而是通过 `VectorMaskNegate`(op `0x198`)否定该掩码来构造。
---
## 每代字段偏移扫描
原生 packed-word 路径由每代的 `Target::HasVcmaskInstruction()`(vtable `+0x410`)门控。只有两个子类返回 true;其余子类会合成矩形,并且永远不会到达 `GetVcmaskFieldOffsets()`。所有五个具体 `HasVcmaskInstruction` 函数体和两个真实 `GetVcmaskFieldOffsets` 函数体都已从反编译中读取。
```c
JellyfishTarget ::HasVcmaskInstruction() @ 0x1d4904c0 -> return 0; // synthesized
PufferfishTarget::HasVcmaskInstruction() @ 0x1d494b60 -> return 0; // synthesized
GhostliteTarget ::HasVcmaskInstruction() @ 0x1d497d20 -> return 1; // NATIVE
ViperfishTarget ::HasVcmaskInstruction() @ 0x1d49ae60 -> return 1; // NATIVE
Target ::HasVcmaskInstruction() @ 0x1d61dcc0 -> LogFatal // abstract base两个原生 GetVcmaskFieldOffsets 函数体都会先重新断言 HasVcmaskInstruction()(vtable +0x410,反编译器显示为 vtable[1040]),然后返回相同的常量对:
// GhostliteTarget::GetVcmaskFieldOffsets() @ 0x1d497d60 (Viperfish @ 0x1d49aea0 byte-identical)
if (!HasVcmaskInstruction()) LogFatal("HasVcmaskInstruction()"); // target_ghostlite.h:646
return 0x300000000LL; // rax = {off0=0, off1=3} ; rdx = 0xd0000000a = {off2=10, off3=13}
// Target::GetVcmaskFieldOffsets() @ 0x1d490500 (abstract base — never reached)
LogFatal("Unsupported instruction: vcmask"); // target.h:2667 (__noreturn)
```text
抽象基类函数体为 `__noreturn`,并发出逐字字符串 `"Unsupported instruction: vcmask"`(存在于 `.rodata`),所以如果一个合成代际分派到它就会 abort;但这不会发生,因为合成路径由 `HasVcmaskInstruction() == 0` 门控。
| Target 子类(gen) | `HasVcmaskInstruction` | `GetVcmaskFieldOffsets` | 偏移 |
|---|---|---|---|
| `Target`(抽象基类) | `0x1d61dcc0` LogFatal | `0x1d490500` LogFatal (`__noreturn`) | — |
| `JellyfishTarget` (v2 / JXC) | `0x1d4904c0` → `0` | base(不可达) | 合成 |
| `DragonfishTarget` (v3) | 继承 Jellyfish → `0` | base(不可达) | 合成 |
| `PufferfishTarget` (v4 / PXC) | `0x1d494b60` → `0` | base(不可达) | 合成 |
| `ViperfishTarget` (v5p, v5e / VXC) | `0x1d49ae60` → `1` | `0x1d49aea0` → `{0,3,10,13}` | `{0,3,10,13}` |
| `GhostliteTarget` (v6e / GXC `gxc::glc`; 被 v7x `gxc::gfc` 复用) | `0x1d497d20` → `1` | `0x1d497d60` → `{0,3,10,13}` | `{0,3,10,13}` |
> **注意 — packed-word 布局跨代稳定;它不会随 lane count 漂移。** 两个原生子类返回*字节完全相同*的偏移对 `{0,3,10,13}`,即使 v6e/v7x 拥有比 v5p 更宽的计算结构。更宽 lane 的代际不会加宽或移动 packed 字段;这个 3 位 sublane / 7 位 lane 布局是跨 v5p 和 v6e/v7x 的单一固定 SC 谓词寄存器线格式。Dragonfish(v3)target 通过其 vtable `+0x410` 槽继承 `JellyfishTarget::HasVcmaskInstruction`。不存在单独的 `6acc60406`/`gfc` `Target` 子类:只有五个 `xla::jellyfish::*Target` 类携带 Vcmask 方法(Jellyfish、Pufferfish、Viperfish、Ghostlite 和抽象基类),所以 v7x `gfc` 后端虽然有自己的 LLVM `TPUGfcSubtarget`(`0x13c628c0`)和 `gxc::gfc::isa` encoder 命名空间,但它共享 `GhostliteTarget` 的 `llo_region_builder` Vcmask 路径。两个 SparseCore *cost-model* 类(`GhostLiteSparseCoreTarget` / `ViperfishSparseCoreTarget`)使用不同的 vtable 布局;其 `+0x410`/`+0x420` 槽映射到 `MxuNoncontractingSize`/`ShouldReverseTileForLatching`,不是 Vcmask ABI,也不参与 `vcreate_mask` 路径。
>
> **DECODER CAVEAT (LOW)。** HW `vcreate_mask` *decoder* 分支(`SparseCoreTecVectorAlu*VectorCreateMask` 中的反向字段抽取)没有单独反汇编。结束边界闭区间约定已从 encode 侧 CONFIRMED(每个 `Vcmask` 调用点都接收闭区间结束;三个 builder API 相互一致,见下文),但反向 decode 把 end 字段读作闭区间(而不是 one-past)只是强烈暗示,并未字节证明。
---
## 结束边界闭区间与 Builder-API 边界
packed 的 `s_end`/`l_end` 字段保存**最后一个 active 索引**(闭区间),而不是 one-past-the-end 边界。这可以从三个 public builder API 以两种独立方式证明;它们最终都会以闭区间 end 进入 `Vcmask`。
`LloRegionBuilder::CreateLaneVmask(l_lo, l_hi)`(`0x1d53f740`)接收**半开** `[l_lo, l_hi)` 范围;它的边界检查是 `start >= 0`、`end <= target().LaneCount()`(因此 `l_hi` 可以等于 count)和 `start <= end`;原生路径上会**减 1**,转换为闭区间 end:
```c
// CreateLaneVmask(l_lo, l_hi) @ 0x1d53f740 (decompiled, exact)
LloCheck(l_lo >= 0, "start >= 0"); // Op2 = `>=`
LloCheck(l_hi <= target().LaneCount(), "end <= target().LaneCount()");// Op3 = `<=` (half-open)
LloCheck(l_lo <= l_hi, "start <= end"); // Op3 = `<=`
if (l_lo == 0 && l_hi == LaneCount()) return VectorMaskConstant(true); // full
if (l_lo == l_hi) return VectorMaskConstant(false); // empty
if (HasVcmaskInstruction()) // vtable +0x410
return Vcmask(0, SublaneCount() - 1, l_lo, l_hi - 1); // NATIVE: l_hi-1 = inclusive lane END
else
return CreateVmaskHelper(Vxlaneid(), l_lo, l_hi, 0, LaneCount()); // synthesized iota-cmpVcmask(0, SublaneCount()−1, l_lo, l_hi−1) 调用传入 SublaneCount()−1(所有 sublane,闭区间)和 l_hi−1(最后一个 active lane,闭区间)。CreateSublaneVmask(s_lo, s_hi)(0x1d53d7c0)是对称情况,调用 Vcmask(s_lo, s_hi−1, 0, LaneCount()−1)。
LloRegionBuilder::CreateVmask(s_start, s_end, l_start, l_end)(0x1d53fc40)则接收闭区间边界。它的八个边界检查都对 count 使用严格 <(start_sublane < SublaneCount()、end_sublane < SublaneCount()、start_lane < LaneCount()、end_lane < LaneCount(),每个都配对 >= 0),所以每个边界,包括 end,都必须是合法索引 ∈ [0, Count−1]。原生路径会把四个参数原样传给 Vcmask;委托给半开 builder 时则会加 1:
// CreateVmask(s_start, s_end, l_start, l_end) @ 0x1d53fc40 (decompiled, exact)
LloCheck(s_start >= 0, "start_sublane >= 0"); // Op2
LloCheck(s_start < SublaneCount(), "start_sublane < target().SublaneCount()"); // Op1 = `<` ⇒ inclusive arg
LloCheck(s_end >= 0, "end_sublane >= 0"); // Op2
LloCheck(s_end < SublaneCount(), "end_sublane < target().SublaneCount()"); // Op1
LloCheck(l_start >= 0, "start_lane >= 0"); // Op2
LloCheck(l_start < LaneCount(), "start_lane < target().LaneCount()"); // Op1
LloCheck(l_end >= 0, "end_lane >= 0"); // Op2
LloCheck(l_end < LaneCount(), "end_lane < target().LaneCount()"); // Op1
if (both ranges full) return VectorMaskConstant(true);
if (HasVcmaskInstruction()) return Vcmask(s_start, s_end, l_start, l_end); // NATIVE: raw, inclusive
// delegate path adds 1 to convert inclusive -> half-open:
// CreateSublaneVmask(s_start, s_end + 1) AND CreateLaneVmask(l_start, l_end + 1)
```text
`LloCheckForFailure` op 语义在反编译中按字节精确:模板参数 `(LloCheckOp)2` 是 `>=`(`cmp [rdi],rax; jl fail`),`(LloCheckOp)1` 是严格 `<`(`cmp rax,[rsi]; jge fail`),`(LloCheckOp)3` 是 `<=`(`cmp rax,[rsi]; jg fail`)。检查字符串(`"start_sublane < target().SublaneCount()"`、`"end <= target().LaneCount()"`)存在于 `.rodata`,源自 `platforms/xla/service/jellyfish/llo_region_builder.cc`。
| Builder API | 范围约定 | `Vcmask` 边界处的 end 处理 |
|---|---|---|
| `Vcmask` @ `0x1d53f9c0` | 闭区间 `[lo, hi]` | primitive — 原样打包 ends |
| `CreateVmask` @ `0x1d53fc40` | 闭区间 `[lo, hi]` | 原生:原样传递;委托:`+1`(闭区间 → 半开) |
| `CreateLaneVmask` @ `0x1d53f740` | 半开 `[lo, hi)` | 原生:`Vcmask(0, SublaneCount−1, l_lo, l_hi−1)` |
| `CreateSublaneVmask` @ `0x1d53d7c0` | 半开 `[lo, hi)` | 原生:`Vcmask(s_lo, s_hi−1, 0, LaneCount−1)` |
> **陷阱 — public API 按设计使用不同约定;线格式没有不同。** `CreateVmask` 是闭区间,`CreateLaneVmask`/`CreateSublaneVmask` 是半开区间;但三者都汇合为让 `Vcmask` 接收闭区间 end,因此 *packed M-register word 始终存储一个闭区间矩形*。`start > end`(闭区间)描述补集 / wrap-around,在合成路径上通过 `SimplifyPredicateNegate`(`CreateVectorMaskUnop` op `0x198` = `VectorMaskNegate`,反编译显示字面量 `408`)物化。
---
## 合成路径(Jellyfish / Dragonfish / Pufferfish)
三个非原生代际会从 iota 比较构造相同的逻辑矩形,而不是用一个 packed 立即数。`CreateLaneVmask` 落到 `CreateVmaskHelper(Vxlaneid(), l_lo, l_hi, 0, LaneCount())`;`CreateSublaneVmask` 使用 `Vslaneid()`(单 sublane → `Vcmp`-EQ 常量;多 sublane → 按 LaneCount 缩放的 `VectorLaneSequence`)。`CreateVmaskHelper`(`0x1d53f380`)将 `iota ∈ [lo, hi)` 构造为一个或两个 `Vcmp`(`VcmpHelper` dir 5 再 dir 2),并通过 `CreateVectorMaskBinop` op `0x195`(`VectorMaskAnd`)AND 起来。`CreateVmask` 随后也通过 op `0x195` AND sublane 子掩码和 lane 子掩码(每个都可选地用 `VectorMaskNegate` 取补)。结果是同一个二维矩形,只是物化为一串 LLO 谓词指令,而不是一条 `vcreate_mask` 立即数。合成 iota-compare 路径详见 [SparseCore M-Register Predicate Word](../sparsecore/m-register-predicate.md)。
---
## 带掩码 op 如何消费 M-Register:Scan 家族
带掩码 SC 向量 op 不是事后 select:M-mask 是 op 本身的**操作数**。MLIR `sc_tpu.scan` op 把 mask 作为 operand[0],data 作为 operand[1]。`mlir::sparse_core::ScanOp::create`(`0x145f93e0`)接收 `(builder, loc, Type, MASK Value, DATA Value, reduction StringAttr)` 并构造名为 `"sc_tpu.scan"` 的 op;`ScanOp::build`(`0x145f92e0`)按这个顺序添加操作数:
```c
// ScanOp::build(builder, state, Type, mask, data, reduction) @ 0x145f92e0 (decompiled)
if (mask) OperationState::addOperands(state, &mask, 1); // operand[0] = mask (OPTIONAL)
OperationState::addOperands(state, &data, 1); // operand[1] = data
getOrAddProperties<ScanOpGenericAdaptorBase::Properties>().reduction = reduction; // sum/min/max只有 mask 非空时才会添加它(if (mask)),这正是无掩码路径的 hook(见下面的 i1 情况)。当 mask 存在时,lowering 会直接把它传入 typed intrinsic:ScanOpLowering::matchAndRewrite 重新发出 tpu_{add,min,max}_scan1xN{f,i}(每个都是 2 操作数 op,NOperands<2> = {mask, data}),而 SegmentedScanOpLowering(按 reduction 字符串 sum/max/min 进行 XOR 分派)会把 segment-boundary reset intrinsic 与 op 一起重新发出为 tpu_{add,min,max}_seg_scan1xN{f,i}。intrinsic mnemonic tpu_add_scan1xNf、tpu_max_scan1xNf、tpu_min_scan1xNi、tpu_add_seg_scan1xNf、tpu_dupcntf、tpu_unique、tpu_mprefix,以及字符串 sc_tpu.scan / masked_scan 都存在于 .rodata。
陷阱 — 不存在 scan 后的
VectorSelect。 M-mask 是 scan op 自己的逐 lane 参与谓词,作为sc_tpu.scanoperand[0] 携带,并原样传入 intrinsic。把 masked scan lower 为在 unmasked scan 上执行select(mask, scan_result, else)的重新实现,会产生不同的 op graph 和不同的 inactive-lane 行为。mask 在硬件 scan op 内部门控 lane。
ScanOp verifier(其断言字符串都逐字存在于 .rodata)固定了 op 契约:
Verify 断言(.rodata) | 含义 |
|---|---|
"Scan is supported only on the SC vector subcore" | 仅 SC VPU |
"Input must be a rank 1 or 2 vector." | rank-1/2(匹配 1D/2D lane×sublane 几何) |
"Mask must be a rank 1 vector." | mask 是 rank-1 i1 vector |
"Expected mask shape to match result shape:" | mask shape == result shape(每个结果 lane 一个 mask bit) |
"Output element type must be i32 vector for i1 vector inputs." | i1→i32 计数形式 |
"Only sum reduction is supported for i1 vector inputs." | i1 路径仅支持 sum(count-active) |
"Mask is not supported for i1 vector inputs." | i1 sum-count 不支持掩码(所有 lane 都计数) |
Inactive-Lane 语义
inactive-lane 模型直接来自 mask 是 op 的内在谓词这一事实。对于普通和 segmented scan(AddScan/MinScan/MaxScan/IndexScan,1xN 及 seg 变体):
- Inactive 输入 lane 贡献 reduction identity(
add → +0、min → +INF、max → −INF),因此它们不会扰动运行中的 prefix。这是标准 masked-scan 解读,由 i1-no-mask 分类和 masked-scan op 契约确认。 - 没有按家族由编译器选择的 else 操作数。 被 mask 关掉的输出 lane 携带 HW scan datapath 为 inactive lane 驱动的任何值(carry、identity 或 undriven)——这是比二进制低一层的硬件微 datapath 细节(LOW / INFERRED)。
| Scan 家族 | Op / intrinsic(操作数) | Mask 处理 |
|---|---|---|
AddScan/MinScan/MaxScan(1xN typed) | ScanOp(mask, data, reduction) → tpu_{add,min,max}_scan1xN{f,i} NOperands<2> | intrinsic operand[0];无 select |
IndexScan(Min/Max,1xN) | 同一 ScanOp 路径(dtype 经 VpackFormat) | intrinsic operand[0] |
SegmentedAddScan/Min/Max | SegmentedScanOp → tpu_{add,min,max}_seg_scan1xN{f,i} NOperands<2> | intrinsic mask + intrinsic segment reset |
DuplicateCount / Uniquify | tpu_dupcnt{f,s} / tpu_unique + ReplaceOpWithExtracts | 无 select,无 sentinel;直接产生结果 |
| i1→i32 sum(count-active) | ScanOp i1 input,sum-only | 不支持 mask — 所有 lane 都计数 |
row-size consumer (lower_scan) | ScanOp("sum") + tpu_mprefix(1 操作数) | scan_mask = BroadcastBoolToVector(..., true) = all-active |
| masked-off 输出 lane 值 | — | HW datapath(carry/identity/undriven) |
DuplicateCount / Uniquify 会发出专用 tpu_dupcnt{f,s} / tpu_unique op 加 ReplaceOpWithExtracts(仅结构体 unpack),没有 select,没有 sentinel;uniquify mask 结果直接写入一个 VMDest(M0..M15)。唯一显式的 “else” 类布线存在于 row-size consumer(lower_scan / max_ell_row_size_scan over chunk_size_),它构造一个全 active scan_mask = lowering_util::BroadcastBoolToVector(b, loc, chunk_size_, true),并为 masked-prefix 位置使用 tpu_mprefix(1 操作数)。i1→i32 sum-scan(count-active-lanes,即 DuplicateCount prefix 形式)明确不允许 mask。
注意 — 对普通/segmented/index scan,不存在编译器插入的 scan 后
VectorSelect:ScanOp::build反编译显示 mask 是 scan op 自己的 operand[0]。Inactive 输入 lane 贡献 reduction identity;masked-off 输出值是 HW datapath 细节,而不是每次调用由编译器选择的 else。
函数映射
| 函数 | 地址 | 角色 |
|---|---|---|
LloRegionBuilder::Vcmask(s_start,s_end,l_start,l_end) | 0x1d53f9c0 | 原生 packed-word builder(llo.vcmask) |
LloRegionBuilder::CreateVmask(s_start,s_end,l_start,l_end) | 0x1d53fc40 | 闭区间边界的 2D mask builder(8 个检查) |
LloRegionBuilder::CreateLaneVmask(l_lo,l_hi) | 0x1d53f740 | 半开 lane-range mask |
LloRegionBuilder::CreateSublaneVmask(s_lo,s_hi) | 0x1d53d7c0 | 半开 sublane-range mask |
LloRegionBuilder::CreateVmaskHelper | 0x1d53f380 | 合成 iota-compare 构造 |
Target::GetVcmaskFieldOffsets()(base) | 0x1d490500 | __noreturn LogFatal "Unsupported instruction: vcmask" |
GhostliteTarget::GetVcmaskFieldOffsets() | 0x1d497d60 | 返回 {0,3,10,13} |
ViperfishTarget::GetVcmaskFieldOffsets() | 0x1d49aea0 | 返回 {0,3,10,13}(字节相同) |
Target::HasVcmaskInstruction()(base) | 0x1d61dcc0 | LogFatal(抽象) |
JellyfishTarget::HasVcmaskInstruction() | 0x1d4904c0 | return 0(合成) |
PufferfishTarget::HasVcmaskInstruction() | 0x1d494b60 | return 0(合成) |
GhostliteTarget::HasVcmaskInstruction() | 0x1d497d20 | return 1(原生) |
ViperfishTarget::HasVcmaskInstruction() | 0x1d49ae60 | return 1(原生) |
mlir::llo::VectorCreateMaskOp::create | 0x13fb3ba0 | MLIR llo.vcmask op create |
mlir::sparse_core::ScanOp::create | 0x145f93e0 | sc_tpu.scan create(mask、data、reduction) |
mlir::sparse_core::ScanOp::build | 0x145f92e0 | 添加 operand[0]=mask(可选)、operand[1]=data |
ScanOpLowering<ScanOp,ScanOp>::matchAndRewrite | 0x135f2580 | dtype Pack/Unpack splitter;mask 透传 |
GetVectorMask<SparsecoreVectorMask> | 0x13a33320 | 5 位 M-selector 解码(regid − 0x5f) |
GetVMDestregno | 0x13a65b20 | M0..M15 mask-write 带 [0x5f,0x6e] |
未解码内容
- HW
vcreate_maskdecoder 字段抽取分支(SparseCoreTecVectorAlu*VectorCreateMask)。结束边界闭区间约定已从 encode 侧 CONFIRMED;反向 decode 把 end 字段读作闭区间(而非 one-past)以及 7 位 lane 字段是截断到 LaneCount 还是完整 128,尚未字节证明(LOW)。 - masked-scan 逐 lane write-enable 微 datapath:VPU 是否在 masked-off lane 上物理抑制输出写,或者总是物化一个值(carry/identity/undriven)。mask 是 op 的内在谓词(CONFIRMED),inactive 输入 lane 贡献 identity(CONFIRMED),但 masked-off 输出 lane 值是比二进制低一层的硬件细节(INFERRED)。
- 精确的每代运行时
LaneCount值(来自[[Target+0x3b8]+0x198])。SublaneCount = 8 已 CONFIRMED(3 位 packed 字段 + 8 位VectorMaskConstantPacked);LaneCount ≤ 128 由 7 位字段限定;精确值位于运行时配置 blob 中(LOW)。 - Viperfish/Ghostlite 之后的任何未来 SC gen 是否会 fork 出独立的 native-vmask
Target(v0.0.40 中 v7xgfc共享GhostliteTarget;目前不存在)。
交叉引用
- VPU Slot — 消费者侧的
Vmsk向量掩码寄存器文件(M-register 文件中可由 select 消费的 16 深度视图)以及VectorSelectVmskop。 - VEX Mask / Dest-Port / Sub-Opcode — SC VEX
bit0x104..0x1085 位 M-selector 字段的所有者,该字段命名本页构造的 M-register;其GetVectorMask解码(0x13a33320)与本文引用的是同一个。 - Predicate Register File — 标量谓词文件(
Preg),用于门控整 slot/branch,与本文记录的逐 lane M-mask 正交;这两个文件的拆分是谓词控制的核心事实。 - SparseCore M-Register Predicate Word — 合成 iota-compare 路径以及 SparseCore-ISA 语境中的 masked-scan 输出模型。
- Scan Datapath — M-register 的消费者侧:scan-emit 分支如何附加 M-selector(
proto+0x38),以及 scan 内 inactive-lane-identity 模型。 - Bundle Model — 带掩码向量 op 及其 M-selector 字段打包进入的 VLIW bundle,以及
kNeverExecute空 slot 约定。