TPUInstrNameData / Descs / RegEncoding
本页中的每个偏移、数值和地址都逐字节精确读取自
libtpu-0.0.40-cp314wheel 中的libtpu.so(BuildID md589edbbe81c5b328a958fe628a9f2207d)。其他版本会有所不同。
摘要
三个按 opcode 索引的表位于 TPU 后端 .lrodata 中的 InstBits 旁边,并提供位布局数据库没有包含的全部内容:每个 opcode 的助记符、说明每个 opcode 有多少操作数以及每个操作数如何编码的描述符元数据(MCInstrDesc),以及把虚拟寄存器号转换为指令字段所携带硬件位的寄存器编码映射。它们是标准 LLVM TPUGenInstrInfo / TPUGenRegisterInfo TableGen 输出,按原样嵌入:
TPUInstrNameData(0x33f2be0, 274764 B) —— 平坦的、以 null 结尾的助记符字符串池,通过TPUInstrNameIndices(0x3435d30,6166 × u32字节偏移) 间接索引。mnemonic(op) = TPUInstrNameData + TPUInstrNameIndices[op]。TPUDescs(0x33bf650, 210320 B =0x33590) —— 每个 opcode 的MCInstrDesc数组:6166条记录,步长为32字节(6166 × 32 = 197312B),保存{NumOperands, NumDefs, Size, SchedClass, Flags, TSFlags}以及操作数编码器要查询的 operand-info 索引。该符号剩余的13008B(210320 − 197312)保存描述符索引到的尾随 operand-info / implicit-operand 数组;符号大小不是6166 × 32。TPURegEncodingTable(0x34469b0,889 × u16) —— 寄存器号 → 硬件编码位,是每个寄存器操作数背后的movzwl (table, reg, 2)查表。
这三个表都由 createTPUMCInstrInfo (0x13c7a500) 接入 MC 层;该函数分配一个 64 字节的 MCInstrInfo,存入 NumOpcodes = 6166,并把它指向 TPUInstrNameIndices 和 TPUInstrNameData。本页记录每个表的记录布局、索引空间以及读取它的访问器 —— 也就是编码器的元数据侧,其中 InstBits 是位侧,而 239 位记录 是二者共同生成的内容。
对于重新实现,约定如下:
- 两级助记符查找:一个含
6166项的u32偏移数组,指向平坦字符串池,而不是固定步长的名称表。 - opcode 索引空间:总计
6166个 opcode;opcode0..498是伪指令 / target-independent,499..6165是 TPU MC opcode(与 InstBits 在opcode − 499处索引的同一段)。 TPUDescs的32字节MCInstrDesc记录,以及从中读取每个操作数编码类别的getSpecialOpEncoding查询。TPURegEncodingTable的u16查找,以及它划分出的寄存器号块(predicate1..15,scalar / vector 递减)。
| 名称池 | TPUInstrNameData @ 0x33f2be0, 0x4314c (274764 B), 以 null 结尾的字符串 |
| 名称索引 | TPUInstrNameIndices @ 0x3435d30, 0x6058 (6166 × u32 字节偏移) |
| 描述符 | TPUDescs @ 0x33bf650, 0x33590 (6166 × 32 B MCInstrDesc) |
| 寄存器编码 | TPURegEncodingTable @ 0x34469b0, 0x6f2 (889 × u16) |
| 接线 | createTPUMCInstrInfo @ 0x13c7a500 (new(0x40), NumOpcodes = 6166) |
| 助记符访问器 | TPUInstrNameData + TPUInstrNameIndices[op] |
| 描述符查询 | getSpecialOpEncoding(MCInstrDesc&, opno) @ 0x13c63a80 |
| Opcode 数量 | 6166 (0x1816); pseudo 0..498, TPU MC 499..6165 |
| 置信度 | CONFIRMED(字节锚定),除非行内另有说明 |
索引空间和接线
三个表共享同一个 opcode 索引空间,由 createTPUMCInstrInfo 建立。反编译后的函数体固定了数量和指针:
// createTPUMCInstrInfo @ 0x13c7a500 (decompiled)
MCInstrInfo *info = operator new(0x40); // 64-byte MCInstrInfo
info->vtable = &TPUMCInstrInfo_vtable; // result[0]
info->NameIndices = TPUInstrNameIndices; // result[+8] (GOT-relative ptr)
info->NameData = TPUInstrNameData; // result[+0x10] (GOT-relative ptr)
info->Descs = TPUDescs; // (zeroed-then-set region +0x18)
info->NumOpcodes = 6166; // result[+0x28] = 0x1816
return info;
```text
`NumOpcodes = 6166` 是反编译函数中的字面量 `*(_DWORD *)(result + 40) = 6166` —— 也是本页每个按 opcode 索引表大小的规范锚点。名称表指针落在 `+8`(`TPUInstrNameIndices`)和 `+0x10`(`TPUInstrNameData`);IDA 反编译器把这些 GOT-relative 加载渲染为它们恰好指向附近的无关字符串(`"y?"`、`"G_FLOG10"`),这是反汇编伪影,不是真实值。
索引空间在 opcode `499` 处分裂:
| Opcode 区间 | 数量 | 角色 | 覆盖它的表 |
|---|---:|---|---|
| `0..498` (`≤ 0x1F2`) | 499 | pseudo / target-independent (`PHI`, `INLINEASM`, MC pseudo branches) | 仅 name + desc;**不在** InstBits 中 |
| `499..6165` (`0x1F3..0x1815`) | 5667 | TPU MC opcodes | name + desc + InstBits (`index = opcode − 499`) |
第二个区间的 `5667` 数量正好等于 InstBits 行数 —— 同一批 opcode,在那里按 `opcode − 499` 索引,在名称和描述符表中则直接按 `opcode` 索引。因此重新实现者会在四个表之间维护同一个 opcode enum,只在 InstBits 位布局查找时减去 `499`。参见 [InstBits DB](instbits-master-db.md#in-binary-representation)。
---
## TPUInstrNameData / TPUInstrNameIndices
### 布局
助记符数据库由两个数组组成。`TPUInstrNameIndices` 是一个平坦的 `6166 × u32`;条目 `op` 是进入 `TPUInstrNameData`(以 null 结尾的字符串池)的**字节偏移**。访问器只有一次间接引用:
```c
// mnemonic of opcode `op`
const char *mnemonic(uint32_t op) {
return TPUInstrNameData + TPUInstrNameIndices[op]; // op in [0, 6166)
}这种两级形式(偏移数组 + 池化字符串)是标准 LLVM getInstrName 表示;它会去重共享子串,并避免固定名称宽度。索引数组为 6166 × 4 = 24664 = 0x6058 字节(与符号大小匹配);字符串池为 274764 = 0x4314c 字节。观察到的最大索引是 274754,刚好位于池的 274764 字节范围内 —— 这确认索引数组寻址整个池,且池大小准确。
已验证的 opcode → 助记符样例
这些行是固定索引空间的主要锚点;它们直接从两个表读取,而不是推断得到:
| Opcode | Hex | 助记符 | 备注 |
|---|---|---|---|
| 0 | 0x000 | PHI | target-independent |
| 1 | 0x001 | INLINEASM | |
| 239 | 0x0ef | G_PTRMASK | 0xEF generic opcode |
| 499 | 0x1f3 | ADDri | 第一个 TPU MC opcode(InstBits index 0) |
| 505 | 0x1f9 | BRabs | sequencer:绝对分支 |
| 507 | 0x1fb | BRind | sequencer:间接分支 |
| 508 | 0x1fc | BRrel | sequencer:相对分支 |
| 509 | 0x1fd | BRrelrot | sequencer:rel 分支 + rotate |
| 514 | 0x202 | CALLabs | sequencer:绝对调用 |
| 515 | 0x203 | CALLrel | sequencer:相对调用 |
| 540 | 0x21c | EVENT | predicate-index finder 中的特判 |
| 571 | 0x23b | HALT | sequencer:halt |
| 3977 | 0xf89 | bcHALT | BarnaCore halt |
| 3978 | 0xf8a | bcLOOP_START | BarnaCore loop slot |
| 3982 | 0xf8e | bcVLDi | BarnaCore vector load (imm) |
| 3983 | 0xf8f | bcVLDr | BarnaCore vector load (reg) |
| 3991 | 0xf97 | bcVSTr | BarnaCore vector store (reg) |
注意 —— sequencer
BR(325)、BRcond(328)、BRcondrot(330)、BRret(331) opcode 位于 499 以下 —— 它们是在 MC 发射前展开的 MC pseudo branches,永远不会到达 InstBits switch。具体形式(BRabs/BRind/BRrel/BRrelrot/CALLabs/CALLrel/HALT)是≥ 499,会到达编码器,但路由到零基默认值,并由 proto-bundle 路径编码。基于助记符表驱动指令选择的重新实现者必须区分 pseudo 区间(< 499)和 MC 区间(≥ 499);只有后者具有 InstBits 和描述符编码语义。参见 InstBits DB §字段映射。
TPUDescs —— 每个 Opcode 的 MCInstrDesc
记录布局
TPUDescs 是 LLVM MCInstrDesc 数组:每个 opcode 一个描述符,共 6166 项,以 32 字节步长干净解码(6166 × 32 = 197312 B 描述符记录;前导 uint16 递减为 6165, 6164, …,并在条目 6165,也就是第 6166 条记录处正好到达 0 —— 这是对条目数量的字节锚定确认)。0x33590(210320 B)符号大于 197312 B;尾随的 13008 B 是描述符索引到的 operand-info / implicit-operand 数组。每条描述符记录携带标准 MCInstrDesc 载荷 —— {NumOperands, NumDefs, Size, SchedClass, Flags, TSFlags} 加一个 operand-info 索引。前三个条目按 uint16 元组查看为:
entry0: (6165, 0, 7, 3, 117, 0, 1504, 0)
entry1: (6164, 0, 5, 1, 116, 0, 1499, 0)
entry2: (6163, 0, 7, 3, 115, 0, 1504, 0)
```text
前导 `uint16` 递减(`6165, 6164, 6163, …`)—— 它是 operand-info / implicit-ops 索引,而不是 opcode 本身。描述符是位发射器做出每个操作数编码决策的来源:一次 deposit 的 `(pos, width)` 由指令类固定,但给定操作数采用*哪种编码*(寄存器 vs special-immediate vs expression)在这里读取。
> **易错点 ——** `TPUDescs` 的步长是 **32 字节**,不是历史 LLVM `MCInstrDesc` 的 `24` 字节大小:前导 `uint16` 每 `32` 字节递减一次(`6165, 6164, …`),并在条目 `6165`,也就是第 `6166` 条记录处命中 `0` —— 与 `createTPUMCInstrInfo` 的 `NumOpcodes` 匹配。描述符*数组*是 `6166 × 32 = 197312` B,而不是完整的 `0x33590`(`210320` B)符号 —— `0x33590 / 32`(`= 6572.5`)和 `0x33590 / 24`(`≈ 8763`)都不会得到条目数量,因为该符号在描述符记录之后捆绑了尾随 operand-info 数据。`32` 字节步长和 `6166` 数量为 CONFIRMED;此结构版本中确切的 `uint16` 字段偏移绑定(哪个 `uint16` 是 `NumOperands`,哪个是 operand-info 索引)为 MEDIUM 置信度。
### 描述符查询
操作数 lowering 期间,描述符由 `getSpecialOpEncoding(MCInstrDesc&, opno)` (`0x13c63a80`) 读取,该函数由 `getMachineOpValue` 调用。反编译后的函数体索引一个 32 字节步长的描述符,并二分搜索一个编码兼容性表,以返回每个操作数的编码类别:
```c
// getSpecialOpEncoding @ 0x13c63a80 (decompiled, condensed)
uint32_t opcode_field = *(uint32_t *)desc;
uint8_t enc_class = *((uint8_t *)desc + 32*opcode_field
+ 6*desc->numImplicit + 6*opno + 35); // 32B-stride desc record
if (enc_class >= 0x0D) { // special-encoding class
// binary search ImmediateCompatibilityTable (17 entries)
// return matched (class | 0x100000000) or fall through
}
// binary search the 702-entry per-opcode encoding table (GOT - 65201892)
// keyed on opcode_field, with a per-operand bittest gate
return matched ? (class | 0x100000000) : 0;32 * opcode_field 项从第二个独立位置确认了 32 字节描述符步长(描述符查询位置,不同于 createTPUMCInstrInfo)。该函数返回一个 (found, class) 对:高位(| 0x100000000)标记“此操作数有 special encoding”,低 32 位是位发射器用来选择如何 lowering 操作数的 class id。这就是为什么 case 函数体中的同一个 insertBits 位置可以根据操作数 deposit 一个原始立即数、一个可重定位表达式或一个 label fixup —— 位置由 class 固定,值由描述符驱动。参见 记录格式 §操作数值来源。
寄存器编码(TPURegEncodingTable)
布局和查找
TPURegEncodingTable(0x34469b0, 889 × u16, 0x6f2 字节)把 LLVM 寄存器号映射为寄存器操作数携带的硬件编码位。查找是一次 uint16 加载,在反汇编中渲染为 movzwl (table, reg, 2) —— 也就是以 2 字节步长访问 table[reg]:
// register operand encoding, inside getMachineOpValue / encodePredicateOperand
uint16_t reg_enc = *(uint16_t *)(TPURegEncodingTable + 2 * reg_index);
```text
在 `encodePredicateOperand` 中,这正是 `insertBits(dst, *(u16*)(table + 2*reg_index), 0, 4)` 这次 deposit —— predicate 字段的 4 位寄存器索引。表中的最大值为 `128`,因此每个编码都适合 8 位;字段宽度由指令类设置,而不是由表设置。
### 寄存器号块
该表把寄存器号空间划分成与寄存器类对齐的块。可见结构如下:
| 块 | 寄存器号 | 编码值 | 字段宽度 |
|---|---|---|---:|
| predicate | `P0..P14` | `1..15` | 4 位(在 predicate 字段中) |
| scalar / vector | 递减块 | `0..128` | 按 class |
保存 `1..15`(`P0..P14`)的 predicate 块,是 predicate 字段寄存器索引正好为 4 位的字节锚定原因 —— 这个 `15` 也同时作为 `kPredicateRegisterCount` / `kAlwaysExecute` 出现在每代硬件 bundle 常量中,并作为 `kNeverExecute = 31` skip 编码的一部分(5 位字段,其中 `0..14` 引用寄存器,`15` 是 always-execute,`31` 是 never-execute)。scalar 和 vector 块的完整 reg# → (class, encoding) 划分需要交叉解码 `TPURegClassInfos`(`0x334ea60`)和 `TPURegDesc`(`0x343e7b0`),这里保留为 MEDIUM 置信度。运行时寄存器编号侧见 [ArchRegno 编号](archregno-numbering.md)。
> **易错点 —— 寄存器号不是寄存器编码。** `TPURegEncodingTable[reg]` 是一次转换,而不是恒等映射。重新实现如果把 LLVM 寄存器号直接 deposit 到指令字段(而不是 `table[reg]`),会静默地错误编码每个寄存器操作数 —— 位字段宽度正确,但数值错误。在 `getMachineOpValue` 和 `encodePredicateOperand` 中,每个寄存器操作数的值路径都会查询该表。
---
## 交叉引用
- [InstBits DB](instbits-master-db.md) —— 这些表供给的每个 opcode 的 base-bits 数据库;`TPUDescs` 和 `TPURegEncodingTable` 为其中的位位置(`(pos, width)` 窗口)提供*值*。
- [MC-Emitter](mc-emitter.md) —— 读取本页三个表的 `getBinaryCodeForInstr` 和操作数编码器(`getMachineOpValue`、`encodePredicateOperand`)。
- [ArchRegno 编号](archregno-numbering.md) —— 生成 `TPURegEncodingTable` 所翻译寄存器号的运行时寄存器编号(`ToArchRegno` / register-numbering init)。