VEX 掩码 / 目标端口 / 子操作码映射
本页中的每个位位置、字段宽度、操作码立即数、寄存器区间和 proto 偏移,都逐字节读取自
libtpu-0.0.40-cp314wheel 中的libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d,buildlibtpu_lts_20260413_b_RC00)。地址为 VMA;在.text/.rodata中,VMA == 文件偏移。其他版本会不同。
摘要
SparseCore VectorExtended (VEX) 槽是 TEC 向量数据通路中的 scan / sort / dedup / uniquify 系列。VEX 操作数端口绑定 页面负责七个 V0..V6 源读端口选择器以及填充它们的贪心分配器;本页解码 VEX bundle 中剩余三个控制字段,这些字段的位位置此前已经确定,但其含义和源操作数仍未确定:
- 向量掩码字段
@bit0x104(5 位)—— 用什么来屏蔽 lane, - 目标读端口字段
@bit0x10c(3 位)—— VEX 结果被路由到何处以便写回,以及 - 子操作码字段
@bit0x10f(6 位)—— bundle 携带 48 个编码器 VEX 操作族中的哪一个成员。
核心结构性发现是:向量掩码字段不是 lane 位掩码,也不是 sublane 计数;它是一个 5 位索引,命名 32 个架构掩码寄存器 M0..M31 之一。被选中的 M 寄存器本身保存 2D(lane × sublane)active/inactive 谓词;bundle 字段只选择由哪个寄存器提供它。每个 scan/sort 操作都有掩码 —— emit body 无条件附加掩码 —— 因此 VEX scan 数据通路是完整的带掩码 scan。这个逐 lane 的向量掩码与整条指令谓词化(最后一个 MCInst 操作数,由 EmitPredicationToSlot 处理)正交:一个 VEX 操作同时携带二者。
目标读端口字段 @bit0x10c 由 VectorExtended 编码器写入,并有两个真实情形:对 Sort 而言,它是第一个源操作数所分配的读端口索引(并且 Sort 还为第二个源写入第二个 3 位端口字段 @bit0x109);对纯 scan 操作而言,它不存在 —— scan 结果由另一个 bundle 槽中的独立 PopXrf 结果提交操作以带外方式提交。它读取的 proto +0x18 槽也被 VresMove 操作使用,但 VresMove 是单独的 SparseCoreTecVectorResult 槽操作,编码到不同的 bundle 区域(其目标 vreg 落在 bundle bit 245,而不是 bit0x10c);共享的是 proto 偏移,不同 emit body 对它的含义不同,而不是 bundle 位相同。子操作码字段在 48 个逐字节精确的编码器中选择,范围连续为 0x04..0x33,没有空洞也没有重复。
对重新实现而言,契约如下:
- 向量掩码是 5 位 M 寄存器选择器,不是位掩码。 将
bit0x104..0x108解码为寄存器索引m ∈ [0,31];架构寄存器 id 为m + 0x5f(M0 = 0x5f .. M31 = 0x7e)。被命名的 M 寄存器提供逐 (lane,sublane) 执行谓词。掩码写入目标(index-scan / uniquify 掩码结果)被限制在低半区M0..M15。 bit0x10c处的目标读端口(位于VectorExtended槽)是路由,不是数据。Sort→ 第一个源所分配的端口(+ 第二个源在bit0x109);纯 scan → 字段不存在,结果经由PopXrf。3 位宽度(0..6)匹配 7 项读端口分配器V0..V6。(VresMove操作复用同一个 proto+0x18槽来表示其目标 vreg,但它是不同的槽操作,编码到 bundle bit245—— 见 §2.2。)bit0x10f处的子操作码(6 位)选择 48 个连续编码器0x04..0x33之一。 其中 32 个可通过 dispatch 到达;16 个存在但不可到达的变体;4 个可到达操作共享一个 F32/U32 兄弟编码器(dtype 区分由 pack-format 属性层携带,而不是由子操作码携带)。
| 槽 | VectorExtended (VEX) — TEC scan/sort/dedup 系列 |
| 掩码字段 | bit0x104..0x108 (5b) — M 寄存器选择器 M0..M31 |
| 掩码 getter | GetVectorMask<glc::isa::SparsecoreVectorMask> @0x13a33320(Ghostlite;区间 [0x5f,0x7e],值 id − 0x5f) |
| 掩码源操作数 | MCInst operand[1] |
| 目标读端口字段 | bit0x10c..0x10e (3b),proto +0x18,present +0x10 & 0x1,范围 0..6 |
| 第 2 端口(仅 Sort) | bit0x109..0x10b (3b),proto +0x1c,present +0x10 & 0x2 |
| 子操作码字段 | bit0x10f..0x114 (6b),proto +0x50 选择编码器;编码器写入 bit0x10f |
| 子操作码范围 | 0x04..0x33 连续,48 个编码器(32 个可到达,16 个不可到达,4 个 dtype 共享) |
| 置信度 | CONFIRMED(由反编译锚定),除非某行或说明另有标注 |
注意 —— 本页解码三个 VEX 控制字段;七个
V0..V6操作数读端口以及 7 项贪心分配器(FindAndEmitToUnusedPort)在 VEX 操作数端口绑定 页面中,不在此处覆盖。 带掩码 scan 的 inactive-lane output 微语义以及 M 寄存器谓词字的内部布局位于此编码之下一层,在 M-register predicate 页面中覆盖。VEX opcode → op dispatch 表和完整操作清单在 VectorExtended / VEX 页面中。
1. 向量掩码字段 @bit0x104 —— 5 位 M 寄存器选择器
1.1 getter 证明它是寄存器,而不是位掩码
掩码字段写入的是 GetVectorMask<SparsecoreVectorMask> @0x13a33320 返回的值。其函数体短小且决定性:
// xla::tpu::sparse_core::isa_emitter::GetVectorMask<...SparsecoreVectorMask> @0x13a33320
__int64 GetVectorMask(__int64 a1) { // a1 = &MCOperand
if ( *(_BYTE *)a1 != 1 ) { /* "operand.isReg()" */ LogFatal(...); }
unsigned int v1 = *(_DWORD *)(a1 + 8); // the register id
if ( v1 <= 0x5E ) { /* "regno >= llvm::TPU::M0" (id < 0x5f) */ LogFatal(...); }
if ( v1 >= 0x7F ) { /* "regno <= llvm::TPU::M31" (id > 0x7e) */ LogFatal(...); }
return v1 - 95; // 95 == 0x5f, so value in [0,0x1f]
}
```text
由此得到三个事实:
1. **该操作数必须是寄存器。** `*a1 != 1`(`MCOperand` kind tag)会以 `operand.isReg()` 陷入(来源 `isa_emitter_base.h:555`)。位掩码立即数绝不会是寄存器;这是寄存器*名称*。
2. **它的 id 必须落在 M 寄存器区间 `[0x5f, 0x7e]`。** 两个范围陷阱分别引用 `regno >= llvm::TPU::M0`(`isa_emitter_base.h:557`)和 `regno <= llvm::TPU::M31`(`:558`)。这正好是 32 个值:`M0 = 0x5f .. M31 = 0x7e`。
3. **返回值为 `id − 0x5f ∈ [0,0x1f]`** —— 稠密的 5 位索引,正好等于 `bit0x104` 字段宽度。5 位字段可以命名 32 个寄存器;sublane 计数或 lane 位掩码根本不会是寄存器操作数(`isReg()` 陷阱会拒绝任何立即数)。**该字段是 M 寄存器选择器。**
这里展示的两个实例都属于 Ghostlite (`gxc::glc`) 目标;第二个实例 `GetVectorMask<glc::isa::SparsecoreVmask>` `@0x13a2d900` 与之逐字节相同(同一区间,同一个 `id − 0x5f`),并且二者被 VEX emitters 互换使用。(6acc60406/`gfc` 目标在 `0x13aa9b60` / `0x13ab1c80` 处有自己的结构相同副本。)
> **注意 —— M 寄存器*是什么*。** 被命名的寄存器保存一个 2D(lane-range × sublane-range)active/inactive 谓词,由 region builder 根据四个边界参数构造,并与 `Target::SublaneCount` 和 `Target::LaneCount` 校验(确切 lane/sublane 计数是目标表值,本页不覆盖)。bundle 字段选择*哪个* M 寄存器;谓词字的*内容*(其 (lane,sublane) 位打包以及 inactive-lane output 处置)在此编码之下一层 —— 见 [M-register predicate](m-register-predicate.md)。
### 1.2 proto 布局与无条件 present-bit
选中的掩码值通过逐操作 proto 消息和编码器中的 `BitCopy` 到达 bundle。按操作族有两种布局。
**Scan 族。** 掩码值落在 proto `+0x38`,present-bit 为 proto `+0x11 & 0x1`。编码器尾部将其复制到 bundle(来自 `EncodeSparseCoreTecVectorExtendedAddScanF32` `@0x1eb32380`):
```c
// tail of the AddScanF32 encoder — the mask copy
if ( (*((_BYTE *)proto + 17) & 1) != 0 ) { // present +0x11 & 1 (byte 17 == 0x11)
v13[0] = *((int *)proto + 14); // value +0x38 (int[14] == 0x38)
BitCopy(a1, 260, v13, 0, 5); // dest bit 260 == 0x104, width 5
}emit body 对每个 scan 操作都无条件附加掩码(它总是设置 proto +0x11 & 0x1 present bit)。编码中不存在“无掩码 scan”形式 —— 每个 VEX scan/sort/dedup 操作都是带掩码操作。Inactive lanes 被挡在 scan/reduce 数据通路之外(它们贡献归约单位元并且不产生输出写入);这个带掩码 scan 数据通路行为在数据通路层面为 INFERRED,而在编码/寄存器类层面才是 CONFIRMED。
Sort 族。 Sort 在 proto +0x3c 携带掩码,present-bit 为 proto +0x11 & 0x2,复制到同一个 bundle 位置 bit0x104(5b)。来自 EncodeSparseCoreTecVectorExtendedSortIntegerAscending @0x1eb3ac00:
// tail of the SortIntegerAscending encoder — the mask copy
if ( (*((_BYTE *)proto + 17) & 2) != 0 ) { // present +0x11 & 2
v14[0] = *((int *)proto + 15); // value +0x3c (int[15] == 0x3c)
BitCopy(a1, 260, v14, 0, 5); // dest bit 260 == 0x104, width 5
}
```text
对应的 emit body `EmitVectorSort<SparsecoreVectorMask, SparsecoreVregReadPort, SparseCoreTecVectorExtended_SortIntegerAscending>` `@0x13a4a3a0`(操作是*第三个*模板参数)从 `MCInst` operand[1] 读取掩码,并在一次 store 中设置两个 present bit:
```c
int v4 = GetVectorMask<...SparsecoreVectorMask>(*(operands) + 0x10); // operand[1] → mask reg
// ... two source vregs via GetVregno + FindAndEmitToUnusedPort<Sort> ...
*(proto + 0x10 dword) |= 0x203u; // +0x10 &= 0x03 (two dest ports present) ; +0x11 &= 0x02 (mask present)单个 |= 0x203 同时设置 proto +0x10 & 0x03(两个目标读端口 present bit,§2)和 proto +0x11 & 0x02(Sort 掩码 present bit)。
1.3 掩码写入区间 —— M0..M15
Index-scan 和 uniquify 操作会产生掩码结果;此类写入的目标由 GetVMDestregno @0x13a65b20 读取,其区间是读取区间的一半:
// xla::tpu::sparse_core::isa_emitter::GetVMDestregno @0x13a65b20
if ( *(_BYTE *)this != 1 ) { /* "operand.isReg()" */ LogFatal(...); } // :110
unsigned int v2 = *((_DWORD *)this + 2);
if ( v2 <= 0x5E ) { /* "regno >= llvm::TPU::M0" */ LogFatal(...); } // :112
if ( v2 >= 0x6F ) { /* "regno <= llvm::TPU::M15" */ LogFatal(...); } // :113 (id > 0x6e)
return v2 - 95;
```text
区间 `[0x5f, 0x6e]` = `M0..M15`(16 个寄存器)。因此掩码*读取*路径可以命名 `M0..M31` 的任意一个,但掩码*写入*路径被限制在低 16 个。`M16..M31` 是否为没有写路径的只读谓词输入,是从这个区间拆分中 **INFERRED** 出来的(不是通过写路径缺失搜索证明的)。
### 1.4 与整操作谓词化正交
逐 lane 向量掩码独立于指令级谓词化。后者由 `EmitPredicationToSlot<PredicateDest,Predication,...>` `@0x13a4a160` 发出,它读取**最后一个** `MCInst` 操作数(由 `NumOperands` 得出)并对*整条*指令进行门控。因此一个 VEX 操作同时携带两种谓词机制:逐 (lane,sublane) 的 M-mask(本字段)和整操作谓词(单独的槽字段)。它们由不同操作数产生,并服务于不同粒度;重新实现者必须同时编码二者。
---
### 表 A —— 向量掩码字段 `@bit0x104` (5b)
| 属性 | 值 |
|---|---|
| Bundle bits | `bit0x104..0x108` (5 bits) |
| 语义 | 命名 32 个 M 寄存器 `M0..M31` 之一的索引(不是位掩码,不是计数) |
| Getter | `GetVectorMask<glc::isa::SparsecoreVectorMask>` `@0x13a33320`(Ghostlite;== `<glc::isa::SparsecoreVmask>` `@0x13a2d900`) |
| 操作数种类检查 | `MCOperand.isReg()` (`isa_emitter_base.h:555`) |
| 寄存器区间 | `[0x5f, 0x7e]` = `M0..M31`(asserts `:557 / :558`) |
| 编码值 | `regid − 0x5f` ∈ `[0,0x1f]` |
| 掩码源操作数 | `MCInst` operand[1] |
| Scan-family proto | value `+0x38`, present `+0x11 & 0x1` |
| Sort-family proto | value `+0x3c`, present `+0x11 & 0x2` |
| Presence | emit body 总是附加(每个 VEX 操作都有掩码) |
| 掩码写入区间 | `GetVMDestregno` `@0x13a65b20` → `[0x5f, 0x6e]` = `M0..M15` (16) |
| Inactive-lane output (zero/preserve/skip) | 被门控掉,贡献归约单位元,不写入 |
| 正交于 | 整操作谓词化(`EmitPredicationToSlot` `@0x13a4a160`,最后一个操作数) |
---
## 2. 目标读端口字段 `@bit0x10c` —— 写回路由
目标读端口字段是 `bit0x10c..0x10e`(3 位),来源为 proto `+0x18`,present-bit 为 proto `+0x10 & 0x1`。3 位宽度给出范围 0..6 —— 正好是 VEX 操作数分配器的 7 项读端口集合 `V0..V6`(见 [VEX 操作数端口绑定](vex-operand-port.md))。在 `VectorExtended` 编码器中,此字段有两个真实情形(Sort 写入它,纯 scan 省略它)。proto `+0x18` 槽*也*由单独的 `VresMove` 操作读取,但 `VresMove` 是 `SparseCoreTecVectorResult` 槽操作(oneof tag `13`),其编码器把 `+0x18` 映射到*不同的* bundle 位;不同 emit body 之间变化的是共享 proto 偏移的含义,而不是 `bit0x10c` 的含义。
### 2.1 纯 scan 操作 —— 字段不存在
对纯 scan 操作(`AddScan`、`MinScan`、`MaxScan`、index-scans、segmented variants、`DuplicateCount`、`Uniquify`)而言,emit body 从不写 proto `+0x18`;present bit 保持清零,编码器跳过 `bit0x10c` copy。`AddScanF32` 编码器在 present bit 上门控该 copy:
```c
// AddScanF32 encoder @0x1eb32380 — the dest read-port copy is conditional
if ( (*((_BYTE *)proto + 16) & 1) != 0 ) { // present +0x10 & 1
v13[0] = *((int *)proto + 6); // value +0x18 (int[6] == 0x18)
BitCopy(a1, 268, v13, 0, 3); // dest bit 268 == 0x10c, width 3
}对纯 scan 来说,结果不是通过内联目标读端口写回。它由单独的 PopXrf 结果提交操作(EmitXrfResultOp @0x13a14180)以带外方式提交,占据自己的 bundle 槽。PopXrf 操作携带一个 XRF 写组选择器 —— 也位于 proto +0x18,但在不同的子消息(SparseCoreTecVectorResult-PopXrf)中,因此它不是目标读端口的同一个字段。内联目标读端口(本字段)和带外 PopXrf 写组是 VEX 结果路径的两半;PopXrf 写组编码本身记录在 VEX 操作数端口绑定 页面中。
2.2 VresMove —— 单独的 VectorResult 槽操作,不是 bit0x10c
VresMove(“将读端口持有的值移动到 vreg”的操作,proto oneof tag 13)是把读端口持有的值排入命名 vreg 的操作。它不写 bit0x10c:它是 SparseCoreTecVectorResult 槽操作,由 EmitVectorResultMove<...VregReadPort, SparseCoreTecVectorResult> @0x13a4a220 发出,并由 EncodeSparseCoreTecVectorResultVresMove @0x1eb41fa0 编码,其字段落在不同的 bundle 区域。emit body:
// EmitVectorResultMove<...> @0x13a4a220
int dst = GetVregno(this, a2); // operand[0] → destination vreg
unsigned src = GetVregno(this+16, a2); // operand[1] → source vreg
// ... DefaultConstruct<SparseCoreTecVectorResult_VresMove>(...) , oneof tag = 13 ...
*(_DWORD *)(vresmove + 24) = dst; // proto +0x18 gets dest vreg
*(_BYTE *)(vresmove + 16) |= 1u; // +0x10 & 1 present
FindAndEmitToUnusedPort<...>(&st, a2, src, vresmove); // src to an unused read-port
*(_DWORD *)(vresmove + 28) = port_idx; // proto +0x1c gets source's allocated read-port
*(_BYTE *)(vresmove + 16) |= 2u; // +0x10 & 2 present
```text
`VresMove` 编码器把这些 proto 槽映射到它自己的槽的 bundle 位,**而不是** `bit0x10c`/`bit0x109`:
```c
// EncodeSparseCoreTecVectorResultVresMove @0x1eb41fa0
v13[0] = 7; BitCopy(a1, 252, v13, 0, 3); // VectorResult slot opcode 7 @ bit 252, 3b
v13[0] = proto[+0x18]; BitCopy(a1, 245, v13, 0, 6); // dest vreg @ bit 245, 6b (present +0x10&1)
v13[0] = proto[+0x1c]; BitCopy(a1, 235, v13, 0, 3); // source port @ bit 235, 3b (present +0x10&2)
// ... then the V0..V6 vreg-array fields at 346/443/455/406/418/369/381 ...因此 VresMove 与 VectorExtended 目标读端口共享 proto +0x18/+0x1c 偏移,但其 bundle 位置是 bit 245(目标 vreg,6b)和 bit 235(源端口,3b)—— 它在结构上是 VectorResult 槽操作,不属于 bit0x10c 字段。二者唯一共享的是 proto 子消息字节布局。
2.3 Sort —— 两个 3 位读端口,key 和 value
Sort(ascending/descending × integer/float 的操作码)需要两个源读端口 —— key 和 value —— 因此它把 bit0x10c 用于第一个源,并把第二个 3 位字段 bit0x109 用于第二个源。SortIntegerAscending 编码器在子操作码之后紧接着连续写入二者:
// SortIntegerAscending encoder @0x1eb3ac00 (head)
v14[0] = 20; BitCopy(a1, 271, v14, 0, 6); // sub-opcode 0x14 @ bit 0x10f (271), 6b
v14[0] = proto[+0x18]; BitCopy(a1, 268, v14, 0, 3); // dest port 1 @ bit 0x10c (268), 3b
v14[0] = proto[+0x1c]; BitCopy(a1, 265, v14, 0, 3); // dest port 2 @ bit 0x109 (265), 3b
// ... then the V0..V6 vreg-array fields and the mask copy (§1.2) ...
```text
emit body `EmitVectorSort<...Sort>` `@0x13a4a3a0` 从两次 `FindAndEmitToUnusedPort<Sort>` 分配中填充 proto `+0x18` 和 `+0x1c`(第一个源的端口和第二个源的端口)。两个 present bit 均由 §1.2 中的 `|= 0x203` store 设置(`+0x10 & 0x03`)。对 Sort 而言,`bit0x10c`/`bit0x109` 处的两个 3 位字段是*已分配读端口索引*;纯 scan 将二者都留空(§2.1)。`VresMove` 也到达同样的 proto `+0x18`/`+0x1c` 偏移,但作为 `VectorResult` 槽操作,它发射到 bundle bits `245`/`235`(§2.2)。
---
### 表 B —— 按操作族划分的目标读端口字段 `@bit0x10c` (3b)
表 B-1 —— `bit0x10c` 字段的情形(`VectorExtended` 槽目标读端口):
| 操作族 | proto `+0x18` 源 → `bit0x10c` | 额外字段 |
|---|---|---|
| 纯 scan(`AddScan`/`MinScan`/`MaxScan`/index-scans/`Segmented*`/`DuplicateCount`/`Uniquify`) | 不写入 —— 字段不存在;结果经由 `PopXrf`(`EmitXrfResultOp` `@0x13a14180`) | — |
| `Sort`(asc/desc × int/float) | 第一个源的**已分配读端口**(`FindAndEmitToUnusedPort<Sort>`) | 第二个源的端口 → `+0x1c` → `bit0x109`(3b,present `+0x10 & 0x2`) |
表 B-2 —— `VresMove` 共享 proto 偏移,但它是单独的 `VectorResult` 槽操作(不是 `bit0x10c`):
| 操作 | proto 槽 | bundle bit | 宽度 | 来源 |
|---|---|---|---|---|
| `VresMove`(oneof tag `13`)目标 vreg | `+0x18`(present `+0x10 & 0x1`) | `245` | 6b | operand[0](`GetVregno` `@0x13a659c0`) |
| `VresMove` 源读端口 | `+0x1c`(present `+0x10 & 0x2`) | `235` | 3b | `FindAndEmitToUnusedPort<...VresMove>` |
| `VresMove` 槽 opcode | — | `252` | 3b | 常量 `7` |
| 字段 | Bits | proto | Present | 范围 |
|---|---|---|---|---|
| 目标读端口 1(`VectorExtended`) | `bit0x10c..0x10e` (3b) | `+0x18` | `+0x10 & 0x1` | 0..6 |
| 目标读端口 2(Sort) | `bit0x109..0x10b` (3b) | `+0x1c` | `+0x10 & 0x2` | 0..6 |
> **注意 —— `bit0x10c` ≠ PopXrf 写组,即使二者都位于 proto `+0x18`。** 目标读端口位于 `VectorExtended` / `VectorResult` 子消息中,命名哪个内联读端口携带结果以供写回。PopXrf XRF 写组位于 PopXrf 子消息中,命名带外 scan 结果提交到哪个 XRF 分区。它们是由不同操作码到达的不同字段;不要混淆。
---
## 3. 子操作码字段 `@bit0x10f` —— 48 编码器 VEX 操作映射
子操作码是 `bit0x10f..0x114`(6 位),在每个 VEX 编码器中作为*第一个* `BitCopy` 写入。恢复过程枚举了全部 48 个 `EncodeSparseCoreTecVectorExtended<Op>` 编码器,读取编码器加载的常量,并在 `bit0x10f` 上确认。模式不变:
```c
// every VEX encoder begins with the sub-opcode copy, e.g. AddScanF32 @0x1eb32380:
v13[0] = 5; // the sub-opcode constant
BitCopy(a1, 271, v13, 0, 6); // dest bit 271 == 0x10f, width 6抽查常量(本轮反编译验证):MaxIndexScanU32 = 4 (0x04)、AddScanF32 = 5 (0x05)、MinScanF32 = 6 (0x06)、MaxScanF32 = 7 (0x07)、MinIndexScanF32 = 8 (0x08)、SortIntegerAscending = 20 (0x14)、SortFloatDescending = 23 (0x17)、UniquifyFloat = 27 (0x1b)、SegmentedMaxIndexScanBf16 = 51 (0x33)。九者都与下表一致,并且常量准确落在连续范围端点(0x04 和 0x33)上。
该映射连续为 0x04..0x33 —— 48 个编码器,零空洞,零重复。可到达性分成三类:
- 32 个 dispatch 可到达编码器 —— 通过 VEX opcode dispatch 表接线。
- 16 个存在但不可到达编码器(标为
*)——U16-index/Bf16-index/*PartialSumS32/*PartialSumF32变体。它们存在且拥有有效子操作码,但在这一代中不会由 dispatch 表到达;它们在其他 SparseCore 代中是否可到达为 INFERRED(此处未追踪 arm)。 - 4 个 dtype 共享的可到达操作(标为
‡)——AddScanS32、MaxScanU32、MinScanU32、MinIndexScanU32没有专用编码器;它们在同一个子操作码上复用其 F32/U32 兄弟编码器。S32/U32-vs-F32 的区别由 pack-format 属性层(VpackFormat)携带,而不是由bit0x10f携带。因此 36 个可到达操作 ≡ 32 个可到达编码器 + 4 个编码器共享操作,且 48 − 32 = 16 个不可到达编码器。
表 C —— 完整 48 编码器 VEX 子操作码映射(bit0x10f,6b;CONFIRMED 逐字节精确;连续)
* = 编码器存在但在这一代 dispatch 不可到达。‡ = 可到达操作通过 dtype 兄弟共享此编码器(dtype 由 pack-format 层携带)。
| sub-op | op (SparseCoreTecVectorExtended_<X>) | encoder @ | sub-op | op | encoder @ |
|---|---|---|---|---|---|
0x04 | MaxIndexScanU32 | 0x1eb32000 | 0x1c | AddScanS16PartialSumS16 | 0x1eb33500 |
0x05 | AddScanF32 (‡also AddScanS32) | 0x1eb32380 | 0x1d * | AddScanS16PartialSumS32 | 0x1eb33880 |
0x06 | MinScanF32 (‡also MinScanU32) | 0x1eb32700 | 0x1e | MinScanU16 | 0x1eb33c00 |
0x07 | MaxScanF32 (‡also MaxScanU32) | 0x1eb32a80 | 0x1f | MaxScanU16 | 0x1eb33f80 |
0x08 | MinIndexScanF32 (‡also MinIndexScanU32) | 0x1eb32e00 | 0x20 * | MinIndexScanU16 | 0x1eb34300 |
0x09 | MaxIndexScanF32 | 0x1eb33180 | 0x21 * | MaxIndexScanU16 | 0x1eb34680 |
0x0a | SegmentedAddScanS32 | 0x1eb35f00 | 0x22 | AddScanBf16PartialSumBf16 | 0x1eb34a00 |
0x0b | SegmentedMinScanU32 | 0x1eb36280 | 0x23 * | AddScanBf16PartialSumF32 | 0x1eb34d80 |
0x0c | SegmentedMaxScanU32 | 0x1eb36600 | 0x24 | MinScanBf16 | 0x1eb35100 |
0x0d | SegmentedMinIndexScanU32 | 0x1eb36980 | 0x25 | MaxScanBf16 | 0x1eb35480 |
0x0e | SegmentedMaxIndexScanU32 | 0x1eb36d00 | 0x26 * | MinIndexScanBf16 | 0x1eb35800 |
0x0f | SegmentedAddScanF32 | 0x1eb37080 | 0x27 * | MaxIndexScanBf16 | 0x1eb35b80 |
0x10 | SegmentedMinScanF32 | 0x1eb37400 | 0x28 | SegmentedAddScanS16PartialSumS16 | 0x1eb38200 |
0x11 | SegmentedMaxScanF32 | 0x1eb37780 | 0x29 * | SegmentedAddScanS16PartialSumS32 | 0x1eb38580 |
0x12 | SegmentedMinIndexScanF32 | 0x1eb37b00 | 0x2a * | SegmentedMinScanU16 | 0x1eb38900 |
0x13 | SegmentedMaxIndexScanF32 | 0x1eb37e80 | 0x2b * | SegmentedMaxScanU16 | 0x1eb38c80 |
0x14 | SortIntegerAscending | 0x1eb3ac00 | 0x2c * | SegmentedMinIndexScanU16 | 0x1eb39000 |
0x15 | SortIntegerDescending | 0x1eb3afc0 | 0x2d * | SegmentedMaxIndexScanU16 | 0x1eb39380 |
0x16 | SortFloatAscending | 0x1eb3b380 | 0x2e | SegmentedAddScanBf16PartialSumBf16 | 0x1eb39700 |
0x17 | SortFloatDescending | 0x1eb3b740 | 0x2f * | SegmentedAddScanBf16PartialSumF32 | 0x1eb39a80 |
0x18 | DuplicateCountInteger | 0x1eb3bb00 | 0x30 * | SegmentedMinScanBf16 | 0x1eb39e00 |
0x19 | DuplicateCountFloat | 0x1eb3be80 | 0x31 * | SegmentedMaxScanBf16 | 0x1eb3a180 |
0x1a | UniquifyInteger | 0x1eb3c200 | 0x32 * | SegmentedMinIndexScanBf16 | 0x1eb3a500 |
0x1b | UniquifyFloat | 0x1eb3c580 | 0x33 * | SegmentedMaxIndexScanBf16 | 0x1eb3a880 |
总计: 48 个编码器,sub-ops 0x04..0x33 连续(0 个空洞,0 个重复)。32 个 dispatch 可到达,16 个 * 不可到达,4 个 ‡ 可到达操作共享 F32/U32 兄弟编码器。
4. 端到端:带掩码 VEX scan bundle 如何构建
把三个字段合在一起,一个 VEX scan 操作(例如 AddScanF32)按如下方式组装:
MCInst (VEX scan)
operand[0] result placeholder ─────────────────────────► (pure scan: no inline dest port)
operand[1] ──► GetVectorMask ─► reg id ∈ [0x5f,0x7e] ─ 0x5f ─► proto +0x38 (present +0x11&1)
operand[2..]──► GetVregno / FindAndEmitToUnusedPort ────────► V0..V6 ports (see VEX operand-port page)
last operand ─► EmitPredicationToSlot ─────────────────────► whole-op predicate slot
│
encoder EncodeSparseCoreTecVectorExtendedAddScanF32 @0x1eb32380
v13[0]=5; BitCopy(.,0x10f,.,6) ── sub-opcode 0x05 ──► bit0x10f
if(+0x10&1) BitCopy(.,0x10c,.,3) ── dest read-port (absent for pure scan)
... V0..V6 source-port BitCopys (0x15a/0x1bb/0x1c7/0x196/0x1a2/0x171/0x17d) ...
if(+0x11&1) BitCopy(.,0x104,.,5) ── mask register (M0..M31) ──► bit0x104
scan RESULT committed out-of-line by PopXrf (EmitXrfResultOp @0x13a14180), separate bundle slot.
```text
对 `Sort` 而言,目标读端口(`bit0x10c`)和第二个读端口(`bit0x109`)存在(key + value),掩码位于 proto `+0x3c`(present `+0x11 & 0x2`)。`VresMove` 是单独的 `VectorResult` 槽操作:其目标 vreg 进入 bundle bit `245`,其源端口进入 bit `235`(不是 `bit0x10c`/`bit0x109`)。子操作码字段总是携带编码器的常量;掩码字段总是携带 M 寄存器选择器。
---
## 5. 尚未钉住的内容
- **带掩码 scan 的 inactive-lane output 微语义**(zero-fill vs register-preserve vs no-drive)。掩码寄存器*选择*(`M0..M31`)和带掩码 scan 分类是 CONFIRMED;masked-off lanes 的逐 lane 写行为位于编码之下的数据通路层 —— 见 [M-register predicate](m-register-predicate.md)。
- **M 寄存器谓词字的内部布局**(确切的 (lane,sublane) 位打包)。5 位选择器是 CONFIRMED;存储谓词的位顺序未在此处解码。
- **16 个 `*` 标注编码器的逐代可到达性。** 它们存在且具有有效子操作码;其在其他 SparseCore 代中的 dispatch 可到达性未追踪 arm。
- **`M16..M31` 是否有任何写路径。** 读取区间深度为 32,写入区间(`GetVMDestregno`)深度为 16;上半区为只读谓词输入是根据区间拆分推断的,不是根据写路径缺失证明得到的。
---
## 交叉引用
- [VectorExtended / VEX](vectorextended-vex.md) —— VEX 操作族、opcode dispatch 和完整操作清单。
- [VEX 操作数端口绑定](vex-operand-port.md) —— `V0..V6` 源读端口、7 项贪心分配器(`FindAndEmitToUnusedPort`)以及 PopXrf 写组。
- [M-register predicate](m-register-predicate.md) —— M 寄存器谓词字以及带掩码 scan 的 inactive-lane 语义。
- [TEC(向量)引擎](tec-engine.md) —— 承载 VEX 槽的 64 字节 SparseCore 向量 bundle。
- [TEC 向量操作码枚举](vector-opcode-enum.md) —— `VectorAlu` 操作码清单(兄弟 compute-slot 恢复)。
- [SparseCore 概览](overview.md) —— TEC/VEX 数据通路在 SparseCore 架构中的位置。