Skip to content

分派表分类法

本页上的所有地址、节名、步幅和计数都适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so:一个 781,691,048 字节的 ELF64 共享对象,build-id 为 89edbbe81c5b328a958fe628a9f2207d(唯一无歧义的版本锚点,请固定到它)。包元数据报告版本 0.0.40;周边工具携带了 0.103 插件版本,但它在二进制中不能作为字面字符串观察到,因此这里不把它用作静态锚点。其他 wheel 的每个地址都会不同。

摘要

libtpu.so 携带两类结构上不同的间接分派数据,而它们经常被混为一谈。第一类是函数指针表群体:40,313 张表,保存 516,323 个指针,几乎全部是经过重定位的 C++ vtable。第二类是已编译 switch 跳转表群体:33,016 个由 LLVM 降低的 switch 语句,通过一个独立的只读偏移区域间接跳转到 4,673,757 个 case 目标。著名的“40,313 张表 → 17 个类”标题没有正确概括任何一个群体:40,313 这个数字只对应函数指针数量,并且在规范化 thunk、std:: 和局部作用域改编符号之后,该分类法解析为覆盖 99.6% 表的 19 个类,而不是 17 个。

本页是这份普查。它确立了权威的逐类计数、每个类的结构签名,即成员表在内存中的样子(步幅、条目种类、谁索引它、它位于哪个节),以及每个类的代表地址。最重要的单一结构事实是:这是一个 PIE。在文件映像中,vtable 槽位是,真实目标是加载器应用的 R_X86_64_RELATIVE 重定位的加数;该二进制的重定位中有 924,033 个(总计 1,069,006 个 RELATIVE)落在 .data.rel.ro 中。IDA 的表 sidecar 已经通过其重定位解析了每个槽位,因此恢复出的 target_func 名称是加载后的事实。

参照框架是 Itanium C++ ABI。每张“真”表都是按 [offset-to-top][typeinfo*][slot 0][slot 1]… 布局的 vtable,以其地址点_ZTV<X>+0x10)寻址,每个槽位 8 字节。那些不是 vtable 的类,即 LLVM UniqueFunctionBase 类型擦除池、libpfm4 PMU C 表、abseil 容器策略 thunk、AnyInvocable invoker,会被明确标出,因为把它们当成 vtable 的重新实现者会错误建模它们的可变性和索引方式。主要层级的逐槽方法标注,以及 thunk 和顶层 vtable 类的深层机制,会留给相邻页面;本页负责群体级分类。

对重新实现而言,契约是:

  • 双群体划分:40,313 张函数指针表(本页主题)对比 33,016 张 switch 跳转表,以及为什么绝不能把它们相加。
  • vtable 表的 Itanium-ABI 内存签名:步幅 8,地址点在 _ZTV+0x10,槽位在文件中为零并由加载器填充;以及打破这一模式的四类非 vtable 签名。
  • 19 类分解及其键控规则(分类器基于符号,例如 RegisteredOperationName::Model Op-Model 标记,而不是表大小),以及节不变量(.data.rel.ro = const vtables,.data = 运行时可变池,.rodata = 纯/成员指针表)。
函数指针表40,313(sidecar 记录数,精确)
所有表中的总指针数516,323
节划分.data.rel.ro 38,664 · .data 1,442 · .rodata 207
Switch 跳转表(独立)33,016 / 4,673,757 个 case 目标
"vtable for" RTTI 记录39,155(总共 160,351 条 RTTI 记录)
R_X86_64_RELATIVE 重定位1,069,006(DT_RELACOUNT.data.rel.ro 中 924,033 个)
最大表0x223393a0 — 2,595 个条目(UniqueFunctionBase.data
Op-Model 指纹size-23:6,129 张表,6,050 张带有 Model 条目
分类法类别19 行 = 18 个归属类别(99.6%)+ 1 个残余类(157 / 0.4%)

两个群体

在进入分类法之前,先固定“40,313 → 17 个类”标题模糊掉的区别。二进制中有两套独立的间接分派机制,由两个独立的 sidecar 记录,并且它们没有共享条目。

text
function-pointer tables (this page)        switch jump tables (separate)
  40,313 tables                              33,016 tables
  516,323 pointer slots                      4,673,757 case targets
  live in .data.rel.ro / .data / .rodata     indirect jmp through .lrodata
  8-byte stride, each slot a code pointer     N-byte case→offset, computed-goto
  almost all are C++ vtables                  LLVM-lowered C/C++ switch statements
  indexed by object vptr + slot index         indexed by (scrutinee - lo) → jmp
```text

函数指针表由*对象*读取:对象在其隐藏的 vptr 字段中存储指向表地址点的指针,虚调用加载槽位 `i` 并跳转。switch 跳转表由*函数体*读取:降低后的 `switch` 减去 case 最小值,做边界检查,从 `.lrodata` 数组加载相对偏移,并计算 goto。二者从不重叠;把它们相加(40,313 + 33,016)没有意义。

> **陷阱 —** 标题中的“40,313 张表”*只*是函数指针计数。它不是 switch 计数(33,016),不是 `R_X86_64_RELATIVE` 重定位计数(1,069,006),也不是 "vtable for" RTTI 符号计数(39,155)。把“40,313 张分派表”读成“40,313 张 vtable”的重新实现者很接近,但仍错了约 1,158 张:这个数字包括类型擦除池和 C 运行时表,它们并不是 vtable。

### PIE 加载器填充不变量

本页所有“目标”计数都是重定位后的。该二进制是位置无关可执行文件,其 vtable 槽位在**文件映像中为 0x0**;加载器在加载时通过 `R_X86_64_RELATIVE` 重定位修补它们,而重定位加数就是真实目标 VA。重定位分布证明了这些表的位置:

| 节 | 重定位(所有类型) | 其中 `RELATIVE` | 角色 |
|---|---:|---:|---|
| `.data.rel.ro` | 924,033 | 924,015 | 重定位后常量 vtable + RTTI thunk |
| `.data` | 131,596 | 131,590 | 运行时可变池(`UniqueFunctionBase`、libpfm4) |
| `.got` / `.got.plt` | 6,698 | 6,069 | 跨模块间接访问的 GOT 条目 |
| all others | 7,332 | 7,332 | `.ldata`(3,507)、`.init_array`(2,471)、`.tbss`/杂项 |

`R_X86_64_RELATIVE` 是主导重定位类型,但不是唯一类型。二进制的 `.rela.dyn` + `.rela.plt` 总共保存 **1,069,659** 个重定位条目,其中 **1,069,006** 个是 `R_X86_64_RELATIVE`(`DT_RELACOUNT` 报告的计数;`readelf -rW` 中的重定位代码 `12`),即填充文件中为零的 vtable 槽位的加载器填充加数。剩余 653 个是非填充类型(473 个 `JUMP_SLOT`、99 个 `GLOB_DAT`、57 个 `DTPMOD64`、24 个 `R_X86_64_64`),并不指向分派表槽位。924,033 / 131,596 这两个节锚点是*所有类型*的重定位计数(并与 `index.md`/`overview.md` 匹配);其中分别只有 18 个和 6 个不是 `RELATIVE`。本页每个“目标”计数都是经过 `RELATIVE` 重定位后的。
因此,对每个类 `X`,Itanium-ABI vtable 布局为:

```text
_ZTV<X> + 0x00   offset-to-top   (0 for a primary vtable)
_ZTV<X> + 0x08   &_ZTI<X>        (typeinfo — binds the table to its class)
_ZTV<X> + 0x10   slot 0          <-- ADDRESS POINT: what the object's vptr holds,
_ZTV<X> + 0x18   slot 1               and the key under which the table sidecar
_ZTV<X> + 0x10+8i slot i              records this table; entry count == slot count

sidecar 在地址点(+0x10)为每张表建键,并把其条目计数报告为槽位计数。把某个槽位追溯到方法名的细节,即在 +0x10+8i 处执行 readelf -r,加数等于 &method_i,覆盖符号名称即方法名,由 RTTI/vtable 普查负责(见交叉引用)。


分类概览 — 函数指针表

十九个类覆盖了 40,313 张表的 99.6%。分类器基于表内容中恢复出的符号(其 vtable 拥有者的命名空间,或 RegisteredOperationName::Model 这类标记符号的存在)建键,不是基于表大小;size-23 的巧合由标记解析,而不是由元数解析。步幅/条目列给出内存签名。

ID类别计数%中位数最大值步幅 / 条目种类
ETPU ISA encoder vtables(asic_sw9,93224.6%6674.data.rel.ro8 B vtable;逐代逐 lane-cluster encoder/clone
AMLIR Op-Model arrays6,08515.1%23113.data.rel.ro8 B;23 槽 Model<Op> 接口 ABI
Fmlir:: vtables(非 Op-Model)4,27010.6%8108.data.rel.ro8 B vtable;pass/dialect/interface 对象
Illvm:: vtables2,6116.5%10336.data.rel.ro8 B vtable;TargetLowering/ISel/pass
Ddnnl / Xbyak JIT vtables2,2895.7%1029.data.rel.ro8 B vtable;JIT primitive + code-gen
Gxla:: / stablehlo:: vtables2,1545.3%6266.data.rel.ro8 B vtable;包含 6× 266 槽逐代 Target
Htensorflow:: / tsl:: vtables2,1535.3%789.data.rel.ro8 B vtable;grappler/runtime 对象
Pabseil hash-container policy thunks2,0665.1%7447.data.rel.ro8 B;类型擦除的 flat/node_hash policy
O长尾具名命名空间 vtables1,8664.6%6111.data.rel.ro8 B vtable;约 150 个小命名空间
Klibc++ std:: thunks1,8024.5%5345.data.rel.ro8 B;shared_ptr_emplace/__policy_func
NTPU runtime / profiler vtables1,1302.8%663.data.rel.ro8 B vtable;TpuHal/TpuCore/TpuCodec
MgRPC / grpc_core vtables9312.3%530.data.rel.ro8 B vtable;channel/filter/promise 状态
Clibpfm4 PMU event tables8332.1%510.data(可变)8 B;C struct,非 vtable
Lprotobuf message/descriptor vtables7121.8%6117.data.rel.ro8 B vtable;reflection/MapEntry
Z1anonymous-namespace static helpers6981.7%10165.data.rel.ro8 B vtable;_GLOBAL__N_ TU-local
BLLVM UniqueFunctionBase pools5891.5%92,595.data(可变)8 B;类型擦除池,非 vtable
RC-runtime / Rust handler tables330.1%930.data.rel.ro8 B;cURL/BoringSSL/zstd/Rust C structs
Qabseil AnyInvocable invokers20.0%44.rodata8 B;InvokeObject 类型擦除
Z未分类(IDA 自动命名)1570.4%660.data.rel.ro8 B;仅 pure-virtual / 无 owner 符号

注 — 逐类库计数(E、F、I、G、……)很高但并非确定无疑:一个“thunk”表与它转发到的 vtable 之间的边界,以及相邻命名空间之间的边界,取决于符号规范化。总数(40,313 张表、516,323 个条目、节划分、size-23 Op-Model 指纹)可从 sidecar 精确重导出,因而是确定的。请把类别行视为空间形状的权威描述;只有当下游论断依赖某个单一类别的精确计数时,才重新导出该类别。

注: 19 类分解依赖符号规范化。分类器会在建键之前,将 thunk 前缀改编(_ZThn/_ZTv)、libc++ std:: thunk,以及局部作用域(_ZZ)改编折叠到其拥有类上;没有这一步,abseil raw_hash_set policy thunk(Class P,约 2,000 张表)会掉入长尾,残余未分类桶会从 157(0.4%)膨胀到数千。dnnl 和 Xbyak JIT vtable(Class D)是一个类,而 C-runtime/Rust handler tables(Class R)是真正的 handler tables,不是 trampoline 误报。

结构分解(独立于库)

横跨这 19 个库类别,40,313 张表按种类分解如下。这是重新实现者关心的分解,因为种类决定可变性和索引方式:

text
~39,155   true C++ vtables          .data.rel.ro, const-after-reloc, vptr-indexed
   6,070   contain an Op-Model entry  (subset of the vtables; Model<Op> arrays)
     589   UniqueFunctionBase pools  .data, runtime-mutable, NOT vtables (Class B)
     833   libpfm4 PMU C-tables      .data, C structs, NOT vtables       (Class C)
  ~1,158   other non-vtable dispatch abseil policy thunks, member-ptr,
                                     C-runtime handler tables (P/Q/R + tail)
```text

“39,155 张 vtable / 40,313 张表”的关系是中心锚点:几乎每张分派表都是重定位后的 C++ vtable,而 39,155 正好是 "vtable for" RTTI 记录的数量。约 1,158 张表的差距正是非 vtable 类;把全部 40,313 张表都建模为 const vtable 的重新实现者,会错误处理 1,442 张 `.data` 表的可变性。

---

## Class A — MLIR Op-Model Arrays

### 目的

MLIR 通过 `RegisteredOperationName` 注册每个 operation,而 op-interface 分派(verify、parse、print、fold、getCanonicalizationPatterns,……)由每个已注册 op 安装的 `Model<ConcreteOp>` 数组承载。这是单个最大的*语义均一*类别:6,085 张表,横跨约 50 个 dialect 的每个已注册 MLIR op 各有一个族。

### 结构签名

Class A 表是 `.data.rel.ro` 中一张 23 槽 const vtable,步幅 8,其内容是 `RegisteredOperationName::Model<Op>` 成员函数。大小 23 是此构建中 Op-Model 接口 ABI 的指纹。

```text
size-23 tables in the binary ............ 6,129
  of which carry a Model<Op> entry ...... 6,050   <-- Class A core
  23-slot vtables that merely coincide .. 79      <-- e.g. PjRtDevice has 23 vmethods
tables with >=1 Model entry (any size) .. 6,070

怪癖 — size-23 是近乎完美但并不完美的检测器。有 79 张 size-23 表是无关类的普通 vtable,只是碰巧正好有 23 个虚方法(xla::MegaScalePjRtDevice,以及 PJRT 侧文档中的 23 槽 PjRtDevice vtable)。分类器基于表内容中RegisteredOperationName::Model 符号建键,而不是基于元数,因此不会把这 79 张错分桶。只按大小建键的重新实现者会误标它们。

代表地址

地址条目第一个符号
0x219bfbe8.data.rel.ro23mlir::RegisteredOperationName::Model<mlir::ROCDL::BlockIdXOp>
0x219d4e48.data.rel.ro23mlir::RegisteredOperationName::Model<xla::PureCallOp>

注: Class A 锚点必须是已确认 Model<Op> vtable 的地址点,即 _ZTV…+0x10,不是 _ZTV 符号本身,也不是巧合的 size-23 vtable。0x215fca58xla::MegaScalePjRtDevice_ZTV(本页标出的 79 个 size-23 非 Model 巧合之一),而 0xa2c33e0 落在 asic_sw::…profiler PMU kCmq_lookup C 表(.rodata,基址 0xa2c33c0内部,二者都不是 Model<Op> vtable。正确锚点是 0x219bfbe8_ZTVN4mlir23RegisteredOperationName5ModelINS_5ROCDL10BlockIdXOpEEE,23 槽,.data.rel.ro)和 0x219d4e48Model<xla::PureCallOp>,23 槽)。该二进制恰好携带 6,050vtable for …RegisteredOperationName::Model<…> 符号,确认了下面的 size-23/with-Model 计数。

dialect 分布强烈偏向 TPU/sparse-core dialect;按 Op-Model 计数,头部贡献者是 sparse_coreTFspirvROCDLlloLLVMtransformNVVMvhlomhlostablehlo。为 op-registration 表定尺寸的重新实现者应预期约 6,000 个已注册 op,而不是上游 MLIR 标准构建中的几百个。


Class E — TPU ISA Encoder Vtables(asic_sw

目的

按表数计最大的类别(9,932,24.6%)是 TPU ISA 的逐指令 instruction-encoder 分派,位于 asic_sw::deepsea 命名空间下。每张表都是一个 ISA operation 的 encoder(或其 clone)的小 vtable,且该群体可按硅代际和 lane cluster清晰分区;这是此二进制中逐代分派采用的结构形式(不是 TpuVersion switch)。

结构签名

Class E 表是 .data.rel.ro 中的小型 const vtable(中位数 6 槽,最大 674)。其定义性特征是命名空间分区:表按 <gen>xc::<cluster>fc::isa 分组,并且在一个代际内计数近似对称,暗示每个 opcode 有成对的 encode/clone vtable。

text
lane-cluster partition of the 9,932 Class E tables:
  gxc/gfc  2,290     gxc/glc  2,270     <-- the dominant generation
  vxc/isa  1,328     vxc/vfc    427
  pxc/isa    592     pxc/pfc    241
  jxc/dfc      4     jxc/jfc      2     pxc/plc  2
```text

> **怪癖 —** gxc/gfc(2,290)与 gxc/glc(2,270)的近似对称是 encode/clone 成对签名。该配对是否与 ISA opcode 精确 1:1(而非 helper 副本)尚未对照 opcode 普查确认,因此请把 encoder↔opcode 比例视为中等可信。

### 代表地址

| 地址 | 节 | 条目 | 第一个符号 |
|---|---|---:|---|
| `0x21e0d0a0` | `.data.rel.ro` | 674 | `asic_sw::deepsea::gxc::gfc::isa::TensorCoreVectorAlu::Compact`(vtable `_ZTV…+0x10`) |
| `0x21e0d0a0…` | `.data.rel.ro` | 674/623/620 | 逐 lane-cluster `TensorCoreVectorAlu::Compact` encoders |

---

## Class B — LLVM `UniqueFunctionBase` 类型擦除池

### 目的

`llvm::detail::UniqueFunctionBase` 是 LLVM 的 move-only 类型擦除 callable。589 张 Class B 表**不是 vtable**,而是 `CallImpl` thunk 的运行时可变池,每种不同 callable 签名一个池。整个二进制中最大的单张表就是其中之一。

### 结构签名

Class B 表位于 **`.data`(可变)**,而不是 `.data.rel.ro`。步幅是 8,但条目是 `CallImpl<Lambda>` thunk,而不是某个类的虚方法,并且该池在模块初始化时填充,运行时可能被修改;vtable 类的重定位后常量不变量在这里*不*成立。

```text
.data table population (1,442 total, all runtime-mutable):
  833   libpfm4 PMU C-tables           (Class C)
  585   UniqueFunctionBase pools       (Class B core; 589 incl. variant mangling)
  ~24   misc gRPC / cURL handler tables

代表地址

地址条目第一个符号
0x223393a0.data2,595UniqueFunctionBase<LogicalResult(Operation*,ArrayRef<Attribute>,…)>::CallImpl<…>

0x223393a0 处的 2,595 条目表是统一的 MLIR op verify/parse/print/fold 分派池:每个已注册 op 的 fold-hook lambda 都被类型擦除到一个 UniqueFunctionBase<LogicalResult(Operation*, …)> 池中。它是二进制中最大的函数指针表,并且是可变的。

陷阱 — 把 Class B 和 C 表当作 const vtable 是正确性 bug:它们位于 .data,在初始化时被写入,并且不是由对象 vptr 索引。1,442 张可变表是“几乎每张表都是 const vtable”的例外;建模它们的可变性,否则就会丢失它。


Class C — libpfm4 PMU Event Tables

目的

833 张表是 libpfm4 的逐微架构 performance-monitoring-unit event lookup tables,用于在 TPU kernel 运行时启用主机 CPU PMU 采样(xprof)。这些是 C structs,不是 C++ vtable。

结构签名

小型(中位数 5,最大 10),位于 .data(可变),在初始化时填充。条目是指向 libpfm4 detect/encode 例程的 C 函数指针,以主机微架构为键。该库覆盖完整的现代 x86 谱系,包括 Intel Core/Atom 到 Sapphire Rapids、AMD fam10h–fam19h、NetBurst 和 perf_raw

代表地址

地址第一个符号
0x222662f8.datapfm_perf_event_os_detect

Class P / Q — abseil 类型擦除分派

目的

abseil 的哈希容器(flat_hash_set/mapnode_hash_set/map)通过逐实例化 policy 做类型擦除。Class P(2,066 张表)就是该 policy-thunk 分派;Class Q(2 张表)是 AnyInvocable/InvokeObject invoker thunk。这些是分派结构,不是类 vtable。

结构签名

定义性成员是一张全局 447 条目的 policy 表,它扇出到二进制中每个 hashmap 实例化;这是一个全局类型擦除点,而不是逐容器 vtable。剩余约 2,000 张是更小的逐 policy thunk 表。Class Q 的两张表是 .rodata 中很小的(4 槽)invoker。

代表地址

地址条目第一个符号
0x21c1d590.data.rel.ro447absl::container_internal::GetRefForEmptyClass
0xa30c788.rodata4absl::functional_internal::InvokeObject<…>

注: 表面上大量的“AnyInvocable”表实际上是 raw_hash_set policy thunk;Class P 有 2,066 张表,而 Class Q 只有 2 张。447 条目的全局 policy 表位于 0x21c1d590


Class N / G — 逐代分派族

目的

逐硅代际分派由并行 vtable 族承载,而不是由 TpuVersion switch 承载。Class N(TPU runtime,1,130)和 Class G(xla/jellyfish,2,154)保存安装逐代行为的族:cost model、ISA codec 和 target descriptor;每个代际一张 vtable。

结构签名

逐代族表现为连续的小型 vtable 运行段,元数相同,每个代际/lane-cluster 一个:

text
5x *CycleTable vtables {Jf,Pf,Vf,Glc,Gfc}   0x21c1ffc8 .. 0x21c201d8, 5 slots each
   TpuCodec{Jellyfish..Ghostlite} 6-slot      0x21d35810 ..
6x jellyfish 266-slot Target vtables           0x21cc6358 .. 0x21cce6b0  (Class G)
   {Dragonfish,Jellyfish,Pufferfish,Viperfish,Ghostlite}Target + base Target
```text

> **怪癖 —** 整个二进制中只有 12 个字面的 `TpuVersion` switch 跳转表(下面的 Class S-GEN)。逐代分派压倒性地基于 vtable:在 target 构造时选择一次 generation,安装正确的 vtable 族;之后分派就是普通虚调用。把逐代行为建模为巨大 `switch(version)` 的重新实现者,建模的是错误机制。
>
> **注:** 直接从符号表测量槽位计数(到下一个 `_ZTV`/`_ZTI` 符号的间隙,减去 2 槽 offset-to-top/typeinfo 头)会得到 `0x21cc6358…0x21cce6b0` 中**6 张 `Target` vtable 正好为 266 槽**:五个具体代际(`xla::jellyfish::{Dragonfish,Jellyfish,Pufferfish,Viperfish,Ghostlite}Target`)加上抽象基类 `xla::jellyfish::Target`。同一地址带中的两张 `*SparseCoreTarget` vtable 是 **28** 槽,不是 266,也不是此族的一部分。这些族的逐槽方法标注由 top-vtable / per-gen 相邻页面负责(见交叉引用)。

这些族的细节,即槽级方法名、跨代际 override 矩阵,属于 RTTI/vtable 普查和 per-gen dispatcher 页面,这里不重复。

---

## 长尾和残余类别

其余类别在结构上都是普通的 8 字节 const vtable,只按拥有命名空间区分:

- **Class F / I / H / D / L / M**(`mlir`、`llvm`、`tensorflow`/`tsl`、dnnl/Xbyak、protobuf、gRPC)—— 标准库对象 vtable。最大的元数在这里:336 槽 `llvm` vtable、345 槽 `std::` thunk 表、117 槽 protobuf descriptor vtable。这些是嵌入库的 codegen/runtime object models。
- **Class O** —— 约 150 个小命名空间(Eigen、OR-tools、RE2、riegeli、antlr、ICU,……),每个贡献少量 vtable;总计 1,866。中等可信,因为命名空间边界模糊。
- **Class K** —— libc++ `std::` thunk:`shared_ptr_emplace`、`__function::__policy_func`、sort-policy thunk。1,802 张表;中等可信,因为这些是 ICF 折叠和 thunk 前缀最重的符号。
- **Class Z1** —— anonymous-namespace(`_GLOBAL__N_`)TU-local pass/lambda 分派,698。
- **Class R** —— 33 张真正的 C-runtime/Rust handler tables(cURL、BoringSSL connection filters、zstd、hwloc、Rust `_RNv` 改编)。这些是真 handler tables,不是 trampoline 误报。
- **Class Z** —— 157 张表(0.4%)IDA 无法归属:仅 pure-virtual 的抽象类 vtable(`__cxa_pure_virtual`)或没有可恢复 owner 符号的 `sub_`/`nullsub_` 自动命名表。它们的 owner 可以通过把槽位地址匹配到 `.text` 函数范围来恢复,而不能从符号恢复。低可信。

---

## Switch 跳转表 — 独立群体

这里报告 33,016 张已编译 `switch` 跳转表只是为了固定边界;它们是由 LLVM 降低的 `switch` 语句,通过 `.lrodata` 偏移数组间接跳转,在结构上不同于上面的函数指针表。总 case-target 计数为 4,673,757(约为函数指针表条目总数的 140×),由 TPU ISA encode/decode opcode switches 主导。

| ID | 类别 | 计数 | % | 最大 case 数 | 代表 |
|----|-------|------:|----:|----:|----------------|
| S-ISA | TPU ISA encode/decode opcode switch | 11,746 | 35.6% | 7,529 | `…gxc::glc::profiler::PerformanceCounterNameToString` |
| S-OTH | other named-namespace switch | 6,454 | 19.5% | 685 | `tcmalloc::FindExperimentByName` |
| S-LLVM | LLVM IR / codegen switch | 3,566 | 10.8% | 4,111 | `function_ref<…>::callback_fn`(MLIR walk) |
| S-XLA | XLA HLO opcode / shape switch | 2,985 | 9.0% | 5,549 | `xla::primitive_util::PrimitiveTypeSwitch` |
| S-Z | unclassified switch | 2,155 | 6.5% | 5,501 | `TF_TString_ResizeUninitialized` |
| S-DNNL | oneDNN primitive/isa switch | 2,002 | 6.1% | 765 | `memory_desc_wrapper::compute_blocking` |
| S-ANON | anonymous-namespace static switch | 1,651 | 5.0% | 2,594 | `(anonymous)::TpuToDmaCoreId` |
| S-MLIR | MLIR op/dialect/attr switch | 1,423 | 4.3% | 548 | `tf_device::ReplicateOp::getInherentAttr` |
| S-TF | TensorFlow op switch | 413 | 1.3% | 132 | `TPUPartitionedCallOp::SetDeviceOrdinal` |
| S-GRPC | gRPC state-machine switch | 336 | 1.0% | 94 | `channelz::BaseNode::KindToEntityType` |
| S-PROTO | protobuf field/wiretype switch | 200 | 0.6% | 90 | `TreeNode::MergeRepeatedField` |
| S-TG | LLVM TableGen instr-select/encode | 73 | 0.2% | 40,813 | `AMDGPUMCCodeEmitter::getBinaryCodeForInstr` |
| S-GEN | per-generation `TpuVersion` direct switch | 12 | 0.0% | 25 | `TpuCodec::Create(TpuVersion)` |

> **注 —** 二进制中单个最大的 switch 是 `AMDGPUMCCodeEmitter::getBinaryCodeForInstr`,有 **40,813 个 case**,这是 TableGen 生成的 instruction-encoder 分派(Class S-TG)。它是一个 *switch*,不是函数指针表;尽管数字巧合,它并不贡献到 40,313 这个数字中。asic_sw ISA switches(S-ISA)占 11,746 张表和 4.67M 总 case 中的 3.94M,包括 11 个逐代 `PerformanceCounterNameToString` switch,每个有 7,529 个 case。

---

## 验证说明

本页重定位数字直接来自对二进制运行 `readelf -dW`/`-rW`,其权威性高于任何分析 sidecar 重定位总数(见下面的计数来源说明)。已确认数字:

| 数量 | 值 |
|---|---:|
| 函数指针表 | 40,313 |
| 总指针条目 | 516,323 |
| 节划分 | 38,664 / 1,442 / 207 |
| `.data` 构成 | 833 pfm + 585 UFB(+ 约 24 misc) |
| Switch 跳转表 | 33,016 |
| Switch case 目标 | 4,673,757 |
| 最大 switch | 40,813 cases |
| size-23 表 / 带 Model / 不带 | 6,129 / 6,050 / 79 |
| 带 ≥1 个 Model 条目的表 | 6,070 |
| "vtable for" RTTI 记录 | 39,155 |
| `R_X86_64_RELATIVE` 重定位(`DT_RELACOUNT`) | 1,069,006 |
| 总重定位条目(所有类型) | 1,069,659 |
| `.data.rel.ro` 重定位(所有类型 / `RELATIVE`) | 924,033 / 924,015 |
| `0x223393a0` / `0x21c1d590` / `0x21e0d0a0` 条目 | 2,595 / 447 / 674 |

> **注(计数来源):** 重定位总数必须来自二进制上的 `readelf -dW` / `readelf -rW`,而不是分析 sidecar。权威计数是 `DT_RELACOUNT` = **1,069,006** 个 `R_X86_64_RELATIVE` 重定位,以及所有类型合计 **1,069,659** 个重定位条目。sidecar 的 1,069,603 数字对 `RELATIVE` 集合多计 597,并对所有类型总数少计 56,二者都不匹配。两个节锚点确实可从二进制复现:`.data.rel.ro` 中 924,033 个重定位、`.data` 中 131,596 个重定位(所有类型计数;其中 924,015 / 131,590 个是 `RELATIVE`)。vtable 计数(39,155)以及本页每个表数字都来自去重后的符号表,而不是 decompile-tree grep。

尚未解决:157 个 Class Z 残余的逐表 demangling(可通过 `.text` 地址带匹配恢复,而不是通过符号恢复);每张 vtable 的槽级语义标注(只在相邻页面上对主要层级完成);以及确切的 Class E encoder↔opcode 比例。

---

## 相关组件

| 群体 | 关系 |
|---|---|
| 39,155 条 "vtable for" RTTI 记录 | C++ vtable 骨架,约占全部 40,313 张表的 97% |
| 1,069,006 个 `R_X86_64_RELATIVE` 重定位 | 填充文件中零槽位的加载器填充机制 |
| 33,016 张 switch 跳转表 | 独立的 computed-goto 分派群体 |
| 6,070 张 Op-Model 表 | MLIR op-registration 表面(Class A) |

## 交叉引用

- [二进制取证概览](overview.md) — 节映射,以及 table/switch 群体在整个二进制中的位置。
- [ELF 解剖](elf-anatomy.md) — `.data.rel.ro` / `.data` / `.rodata` / `.got` 节边界,以及填充 vtable 槽位的 PIE 重定位模型。
- [RTTI / Vtable 普查](rtti-vtable-census.md) — 负责逐槽方法标注、RTTI→vtable 绑定链,以及 Classes A/E/N/G 引用的 39,155 条 "vtable for" 细节。
- [多态入口点](polymorphic-entry-points.md) — thunk-table forwarding-stub 类,以及重定位槽位如何指向 trampolines。
- [逐代函数分派器](per-gen-function-dispatcher.md) — Classes N 和 G 背后的 vtable-family 机制(cost model、codec、Target installation)。
- [PJRT_Api 函数指针表重建](../pjrt/api-vtable-reconstruction.md) — 属于 79 张非 Model size-23 表之一的 23 槽 `PjRtDevice` vtable。
- [分派表分类法 — 完整目录](../appendix/dispatch-table-taxonomy-full.md) — 支撑本页类别摘要的穷尽逐表清单。