Skip to content

VEX 掩码 / 目标端口 / 子操作码映射

本页中的每个位位置、字段宽度、操作码立即数、寄存器区间和 proto 偏移,都逐字节读取自 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(build-id 89edbbe81c5b328a958fe628a9f2207d,build libtpu_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 操作同时携带二者。

目标读端口字段 @bit0x10cVectorExtended 编码器写入,并有两个真实情形:对 Sort 而言,它是第一个源操作数所分配的读端口索引(并且 Sort 还为第二个源写入第二个 3 位端口字段 @bit0x109);对纯 scan 操作而言,它不存在 —— scan 结果由另一个 bundle 槽中的独立 PopXrf 结果提交操作以带外方式提交。它读取的 proto +0x18VresMove 操作使用,但 VresMove 是单独的 SparseCoreTecVectorResult 槽操作,编码到不同的 bundle 区域(其目标 vreg 落在 bundle bit 245,而不是 bit0x10c);共享的是 proto 偏移,不同 emit body 对它的含义不同,而不是 bundle 位相同。子操作码字段在 48 个逐字节精确的编码器中选择,范围连续为 0x04..0x33,没有空洞也没有重复。

对重新实现而言,契约如下:

  • 向量掩码是 5 位 M 寄存器选择器,不是位掩码。bit0x104..0x108 解码为寄存器索引 m ∈ [0,31];架构寄存器 id 为 m + 0x5fM0 = 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 bit 245 —— 见 §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
掩码 getterGetVectorMask<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 返回的值。其函数体短小且决定性:

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

c
// 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 读取,其区间是读取区间的一半

c
// 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:

c
// 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 ...

因此 VresMoveVectorExtended 目标读端口共享 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 编码器在子操作码之后紧接着连续写入二者:

c
// 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)。九者都与下表一致,并且常量准确落在连续范围端点(0x040x33)上。

该映射连续为 0x04..0x33 —— 48 个编码器,零空洞,零重复。可到达性分成三类:

  • 32 个 dispatch 可到达编码器 —— 通过 VEX opcode dispatch 表接线。
  • 16 个存在但不可到达编码器(标为 *)—— U16-index / Bf16-index / *PartialSumS32 / *PartialSumF32 变体。它们存在且拥有有效子操作码,但在这一代中不会由 dispatch 表到达;它们在其他 SparseCore 代中是否可到达为 INFERRED(此处未追踪 arm)。
  • 4 个 dtype 共享的可到达操作(标为 )—— AddScanS32MaxScanU32MinScanU32MinIndexScanU32 没有专用编码器;它们在同一个子操作码上复用其 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-opop (SparseCoreTecVectorExtended_<X>)encoder @sub-opopencoder @
0x04MaxIndexScanU320x1eb320000x1cAddScanS16PartialSumS160x1eb33500
0x05AddScanF32 (‡also AddScanS32)0x1eb323800x1d *AddScanS16PartialSumS320x1eb33880
0x06MinScanF32 (‡also MinScanU32)0x1eb327000x1eMinScanU160x1eb33c00
0x07MaxScanF32 (‡also MaxScanU32)0x1eb32a800x1fMaxScanU160x1eb33f80
0x08MinIndexScanF32 (‡also MinIndexScanU32)0x1eb32e000x20 *MinIndexScanU160x1eb34300
0x09MaxIndexScanF320x1eb331800x21 *MaxIndexScanU160x1eb34680
0x0aSegmentedAddScanS320x1eb35f000x22AddScanBf16PartialSumBf160x1eb34a00
0x0bSegmentedMinScanU320x1eb362800x23 *AddScanBf16PartialSumF320x1eb34d80
0x0cSegmentedMaxScanU320x1eb366000x24MinScanBf160x1eb35100
0x0dSegmentedMinIndexScanU320x1eb369800x25MaxScanBf160x1eb35480
0x0eSegmentedMaxIndexScanU320x1eb36d000x26 *MinIndexScanBf160x1eb35800
0x0fSegmentedAddScanF320x1eb370800x27 *MaxIndexScanBf160x1eb35b80
0x10SegmentedMinScanF320x1eb374000x28SegmentedAddScanS16PartialSumS160x1eb38200
0x11SegmentedMaxScanF320x1eb377800x29 *SegmentedAddScanS16PartialSumS320x1eb38580
0x12SegmentedMinIndexScanF320x1eb37b000x2a *SegmentedMinScanU160x1eb38900
0x13SegmentedMaxIndexScanF320x1eb37e800x2b *SegmentedMaxScanU160x1eb38c80
0x14SortIntegerAscending0x1eb3ac000x2c *SegmentedMinIndexScanU160x1eb39000
0x15SortIntegerDescending0x1eb3afc00x2d *SegmentedMaxIndexScanU160x1eb39380
0x16SortFloatAscending0x1eb3b3800x2eSegmentedAddScanBf16PartialSumBf160x1eb39700
0x17SortFloatDescending0x1eb3b7400x2f *SegmentedAddScanBf16PartialSumF320x1eb39a80
0x18DuplicateCountInteger0x1eb3bb000x30 *SegmentedMinScanBf160x1eb39e00
0x19DuplicateCountFloat0x1eb3be800x31 *SegmentedMaxScanBf160x1eb3a180
0x1aUniquifyInteger0x1eb3c2000x32 *SegmentedMinIndexScanBf160x1eb3a500
0x1bUniquifyFloat0x1eb3c5800x33 *SegmentedMaxIndexScanBf160x1eb3a880

总计: 48 个编码器,sub-ops 0x04..0x33 连续(0 个空洞,0 个重复)。32 个 dispatch 可到达,16 个 * 不可到达,4 个 可到达操作共享 F32/U32 兄弟编码器。


4. 端到端:带掩码 VEX scan bundle 如何构建

把三个字段合在一起,一个 VEX scan 操作(例如 AddScanF32)按如下方式组装:

text
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 架构中的位置。