Skip to content

按代际划分的函数分发器

本页中的所有地址均适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(build-id 89edbbe81c5b328a958fe628a9f2207d,745 MB,未 strip)。VMA == .text/.rodata/.lrodata 中的文件偏移。其他构建会有所不同。

摘要

一个为六代芯片发布同一个二进制文件的 TPU 编译器,需要一种方式在运行时根据正在为其编译的设备,选择正确的按代际实现:正确的目标描述符、成本模型、编解码器、ISA 发射器、HAL 工厂。按代际划分的函数分发器就是执行这种选择的结构性类。它并不是像天真阅读 stripped 二进制时可能以为的那样,是一个巨大的 switch (version)。主导机制是 util_registration::FunctionRegistry<Key, Signature>:一个由互斥锁保护的 abseil swiss-table(flat_hash_map),保存类型擦除的 std::function 工厂函子,并以 tpu::TpuVersion 枚举为键(对于 ISA 发射器,则以 (TpuVersion, TpuSequencerType) 这一对为键)。查找是真正的 swiss-table find();命中时,会调用恢复出的函子来构造按代际划分的对象。

这与 LLVM 工程师熟悉的 TargetRegistry 形状相同(RegisterTarget/lookupTarget 模式,使一个 llc 二进制可以为每个后端发射代码),但它构建在 Google 的 util_registration 库和 abseil 容器之上,而不是 LLVM 的侵入式链表。注册发生在静态初始化时:每一代贡献一个很小的三指令 GoogleInitializer thunk(lea functor, %rsi; mov $version, %edi; jmp Register…),把一个工厂以一个版本键插入。选择随后是 lock_sharedfind → 调用函子的序列,热路径中没有任何分支梯形结构。

还有另外两种其他按代际选择机制与 registry 并存,本页也记录它们以作对比:一小组由 LLVM 降低出的 switch (version) 跳转表(编解码器工厂 tpu::TpuCodec::Create、低层助记符编码器工厂、枚举字符串工具),以及一个按 [PlatformType][TpuVersion] 索引的静态二维数组(HAL 工厂存储 g_hal_factories_by_type)。三者中的任意一种一旦选择并构造了按代际划分的对象,所有后续按代际行为都会通过该对象的 vtable 分发:并行的按代号划分的 vtable 家族(Target 266 个槽位,CycleTable 5 个槽位,TpuCodec 6 个槽位,IsaEmitter 152 个槽位)。分发器选择家族;vtable 运行它。本页是更广泛的分发表分类中的一个类别;它深入覆盖按代际 registry 及其两个同级机制。

对于重新实现,契约是:

  • registry 对象:一个 40 字节的 {absl::Mutex, raw_hash_set CommonFields} Meyers 单例,通过 __cxa_guard_acquire + operator new(0x28) + 零初始化惰性构造。
  • 查找:在共享锁下,对 4 字节(或 8 字节 pair)枚举键执行 swiss-table find,返回类型擦除的 std::function;未命中时返回空函子哨兵。
  • TpuVersion 序号 → 代号映射:用于索引每个按代际表,并说明每个 registry 从哪里读取它的键。
  • 注册路径:填充 registries 的 GoogleInitializer thunks,每个代号一个(ISA 发射器还按 sequencer type 细分)。
  • 每个 registry 的未命中策略Target 为优雅的 InvalidArgumentCycleTable 为硬性的 LogMessageFatal,LLO/emitter registries 为空函子。
分发器类util_registration::FunctionRegistry<Key, Signature>
registry 对象大小40 字节(operator new(0x28)):{Mutex @+0x00, CommonFields @+0x08..+0x27}
查找引擎absl::container_internal::raw_hash_set<…>::find(swiss-table,SOO 阈值 0x20000
键类型(按代际)tpu::TpuVersion(4 字节枚举)或 pair<TpuVersion, TpuSequencerType>(8 字节)
按代际 registries5 × 以 TpuVersion 为键 + 1 × 以 pair 为键(Target、CycleTable、3 个 LLO op、IsaEmitter)
同级机制M2 switch (version) 跳转表;M3 静态 [PlatformType][TpuVersion] 数组
规范 Getxla::jellyfish::target::GetForVersion @ 0x1d49f500FunctionRegistry::Get @ 0x1d49f580
代号→序号映射DeviceTypeFromDeviceIdentifiers @ 0xf6993a0TpuVersionToString @ 0x20b3a480

按代际划分的分发家族概览

六个不同家族会选择一个按代际实现。五个是以 TpuVersion 为键的 registries;一个以 (version, sequencer-type) pair 为键。每一行列出 selector 入口点、它从哪里读取版本键,以及查找未命中时会发生什么。未命中策略刻意按家族不同,也是表中最重要的重新实现细节。

#家族(构造出的对象)Selector Get @Register @键来源未命中策略
1Target(按代际描述符,266 槽位 vtable)0x1d49f5800x1d49f760TpuTopology+0x8InvalidArgumentStatusOr 错误)
2CycleTable(成本模型,5 槽位 vtable)0x1c89cd200x1c89d400Target+0x398LogMessageFatal(硬崩溃)
3LLO ATR op(LloValue* 工厂)0x1d5452800x1d5ae3c0TpuVersion空函子
4LLO DMA op(LloValue* 工厂)0x1d5466000x1d5aebe0TpuVersion空函子
5LLO route op(LloValue* 工厂)0x1d54e0200x1d5aa7a0TpuVersion空函子
6IsaEmitter(152 槽位 vtable)0x140af4e00x140c2360(version, seqtype) pair空函子

同一套 FunctionRegistry 机制还支撑八个按代际的 registries,它们以 op-name 字符串为键(custom-call ShouldFuseOperandData lowering、HloCostAnalysis、SPMD partitioning、system profiler、DMA-descriptor-state factory)。它们共享完全相同的 Register/Get/find swiss-table 代码,但键是字符串而不是版本;它们是 custom-call / op-emitter registries,不是按代际分发器,因此不在本文范围内。

注意 — 分发器是选择器,不是行为。家族 1、2、6 各自构造一个对象,随后该对象的 vtable 承载所有按代际运行时逻辑。266 槽位 Target vtable、5 槽位 CycleTable vtable,以及 152 槽位 IsaEmitter vtable 记录在 RTTI ↔ vtable 普查中。本页止步于构造;vtable 页面从这里继续。


分发机制

目的

把运行时 TpuVersion(有时还有 TpuSequencerType)映射到一个函数,该函数构造正确的按代际对象,同时在编译时不需要知道链接进来了哪些代际。registry 反转控制:每一代在静态初始化时注册自己,selector 按键查询。添加一代意味着添加一个注册 thunk,而不是编辑一个 switch

registry 对象

每个按代际 registry 都是一个 Meyers 单例,即一个函数局部 static,其构造由 __cxa_guard_acquire/__cxa_guard_release 保护。对象正好是 40 字节(operator new(0x28)),零初始化,布局如下:

字段偏移类型含义
mutex+0x00absl::Mutex(8 B)读取时共享(Get),写入时独占(Register
set+0x08raw_hash_set CommonFields(32 B)flat_hash_map<Key, shared_ptr<MapValue>>{control-ptr/H2 bytes, slots-ptr, size, capacity}

MapValue 是类型擦除的载荷:一个 std::function<Signature>,再加上注册时记录的 absl::SourceLocation {file, line},用于重复键诊断。它在堆上分配(operator new(0x48) = Target registry 中为 72 B),并由 shared_ptr 持有,使并发的 Get 可以在锁外增加引用计数。

算法 — 选择

c
function GetForVersion(version):                       // sub_1D49F500 (Target)
    if !guard(target_registry):                        // __cxa_guard_acquire
        r = operator new(0x28)                          // 40 bytes
        memzero(r, 0x28)                                // vxorps + vmovups ymm0
        target_registry = r                             // Meyers singleton
        release_guard()
    return FunctionRegistry::Get(target_registry, &version)   // sub_1D49F580

function FunctionRegistry::Get(this, key):             // sub_1D49F580
    Mutex::lock_shared(this + 0x00)
    it = raw_hash_set::find(this + 0x08, key)          // sub_1D49FAC0 — swiss-table
    if it:                                              // HIT
        mv = *it.shared_ptr                             // MapValue
        atomic_inc(mv.refcount)                         // _InterlockedIncrement64
        if mv.function not empty:
            out = copy of mv's __policy_func            // FunctionWrapper trampoline
        else:
            out = __empty_func                          // null-construct sentinel
    else:                                               // MISS
        out = __empty_func                              // empty std::function
    Mutex::unlock_shared(this + 0x00)
    return out
```text

`find` 是真正的 abseil swiss-table 探测,不是线性扫描。对于 4 字节 `TpuVersion` 键:对键取 hash,用 SSE 对 H2 control bytes 做 group-match(`vpcmpeqb %xmm1,%xmm0; vpmovmskb`),用 `tzcnt` 选择匹配的槽位,然后对存储的枚举 int 做一次 4 字节键比较。容量会与 small-object-optimization 阈值 `0x20000` 比较。调用方随后*调用*返回的 `std::function` 来构造对象;空函子表示“这一代没有实现”。

> **怪癖 —** selector 从不按版本值本身分支。热路径上任何地方都没有 `if (version == kJellyfish) …`;唯一的比较是在 `find` 深处由 swiss-table 进行的 key-int 比较。把它建模为 `switch` 的重新实现者会得到正确的*行为*,但结构是错的,并且会漏掉代际可在链接时插拔这一点。

### 算法 — 注册

```c
// One GoogleInitializer module per codename, emitted as a 3-instruction thunk:
//     lea  <$_0::__invoke>, %rsi        ; raw factory fn-ptr (e.g. → GhostliteTarget::Create)
//     mov  $<version>,      %edi        ; the TpuVersion key (xor %edi,%edi for v0)
//     jmp  RegisterTargetCreationFunctor ; tail-call

function RegisterTargetCreationFunctor(version, fn_ptr):   // sub_1D49F680
    ensure target_registry exists                          // same Meyers guard
    fn = wrap(fn_ptr)                                       // std::function via __policy_func + FunctionWrapper
    FunctionRegistry::Register(target_registry, &version, fn,
                               SourceLocation{"target_registry.cc", 25})  // sub_1D49F760

function FunctionRegistry::Register(this, key, fn, loc):   // sub_1D49F760
    Mutex::lock(this + 0x00)                                // EXCLUSIVE
    mv = operator new(0x48)                                 // 72-byte MapValue
    move fn into mv ; store loc at mv+0x38 / mv+0x40
    (it, inserted) = find_or_prepare_insert(this+0x08, key) // sub_1D49FC80; stores key int32
    if not inserted:
        StrCat duplicate-key error with version + SourceLocation
    Mutex::unlock(this + 0x00)

工厂函子是一个原始函数指针,通过 libc++ 的 __policy_func 类型擦除包装成 std::function。包装类是 FunctionRegistry<…>::FunctionWrapper;它的 __call_func/__create/__empty_func policy 槽位在反编译的 Get 函数体中按名称出现,这就是无需源码也能恢复类型擦除的方式。

函数映射

函数地址角色
target::GetForVersion0x1d49f500Target registry 的惰性初始化 + selector 入口
FunctionRegistry<TpuVersion,…Target>::Get0x1d49f580共享锁保护的 swiss-table 查找 → std::function
raw_hash_set<…>::find(Target)0x1d49fac0SSE group-match + tzcnt + int32 键比较
target::RegisterTargetCreationFunctor0x1d49f680静态初始化注册入口(每个代号一个)
FunctionRegistry<…>::Register0x1d49f760独占锁保护的 find_or_prepare_insert
find_or_prepare_insert_large0x1d49fc80Register 的槽位分配 + key-int 存储

TpuVersion 索引

序号 → 代号映射

每个按代际表都由 tpu::TpuVersion 枚举索引。序号是稠密的 0..5,并按如下方式映射到代号。该映射从 codec switch case(它们是字面量 case 0..5)、Target 注册 thunk(每个 mov $N,%edi)以及每个叶子工厂符号中的代号恢复得出。

序号代号短标签
0Jellyfishjxc(legacy)
1Dragonfish—(共享 Jf 成本模型)
2Pufferfishpxc / plc
3Viperfishvfc / vlc
4Ghostliteglc
5(匿名,6acc60406 / TPU7x)gfc(mktg "Ironwood")

陷阱 — 用于索引按代际表TpuVersion 枚举,不是 DeviceTypeFromDeviceIdentifiers0xf6993a0)返回的 DeviceType/DeviceIdentifiers 序号轴。该函数把外部设备标识符映射到另一套编号(其中一个轴把 Jellyfish 放在 3,把 Ghostlite 放在 13)。registries 以稠密的内部 TpuVersion(0..5)为键,这是在设备标识符层规范化之后到达的。混淆两者会索引错误的表。本页中一切内容的规范索引都是内部 TpuVersion;应把 DeviceTypeFromDeviceIdentifiers 视为上游规范化器,而不是表索引。

每个 registry 从哪里读取它的键

每个家族从不同来源读取键,因为它们运行在流水线的不同阶段,而保存权威版本的是不同对象:

  • Target registry:在 target::CreateFromTopology0x1d48e520)中读取 *(u32*)(topology + 8),即存储在 TpuTopology 上的版本。topology 是最早携带版本的对象。
  • CycleTable registry:在 CycleTable::Create0x1c89cc00)内部读取 *((u32*)target + 230) = Target+0x398Target::tpu_version())。到成本模型阶段时,Target 已经存在并且是权威来源。
  • IsaEmitter registry:读取一个 8 字节 {version, seqtype} pair;version 半边是同一个内部 TpuVersion,第二半边是 TpuSequencerType

在每种情况下,4 字节(或 8 字节)键都会复制到栈上的临时变量,并以 const& 传给 Get

注意 — Target+0x398CycleTable 键)和 Target+0x3fc(写入于 CreateFromTopology 的 chip-parts 版本)是两个不同的类似版本的 u32 字段。它们的精确区别,即内部 TpuVersionTpuVersionProto,还是某种变体判别符,并未从二进制中完全协调清楚;+0x398 是成本模型查找所用的键(对 +0x3fc 精确语义的置信度为 LOW)。


按地址列出的代表性分发器

Target registry:规范示例

Target 选择是该机制最干净的实例,也是字节追踪最完整的一个。从 topology 到构造出的 Target 的流程:

text
CreateFromTopology(topology, l, l)          0x1d48e520
  version = *(u32*)(topology + 8)           ── key from topology
  fn = target::GetForVersion(version)       0x1d49f500  ── registry lookup
  if fn.empty():
      return InvalidArgument("No Target registered for ", version)
  Target* t = fn(topology, l, l)            ── INVOKE the per-gen factory functor
  t[+0x3fc] = chip_parts_version            ── post-construct field writes
  t[+0x928] = config
  return StatusOr{t}
```text

一共有**六个**注册的 `Target` 工厂,版本 0..5。五个是具名的 `…Target::Create` 模块(jellyfish..ghostlite);第六个是 v5(`gfc`/TPU7x)注册 thunk,位于 `google_init_cold` section,没有自己的模块符号。它的 `__invoke`(`0x1d49f100`)不会 tail-call 五个具名 `…Target::Create` 函数中的任何一个;它运行一个 outlined 工厂体(`0x1d49c9c0`),该工厂体 `new` 并 `Target::Init` 一个 **`ViperfishSparseCoreTarget`**(typeinfo `_ZTIN…25ViperfishSparseCoreTargetE`,`0x21cc9080`),通过 `Target::Init(…, unique_ptr<SparseCoreTarget>, …)`(`0x1d60fc20`)构造。因此 v5 有自己的(匿名、SparseCore 派生的)`Target`,不是复用 v4 Ghostlite 描述符;这与 `CycleTable`(`GfcCycleTable`)和 codec 的独立 v5 case 一样,都是完整填充。

| `TpuVersion` | 代号 | `…Target::Create` @ | `GoogleInitializer` 模块 @ |
|---|---|---|---|
| 0 | jellyfish | `0x1d492040` | `xla_target_jellyfish` `0x213eeea0`(edi=0,通过 `xor`) |
| 1 | dragonfish | `0x1d48f0a0` | `xla_target_dragonfish` `0x213eee80`(edi=1) |
| 2 | pufferfish | `0x1d493620` | `xla_target_pufferfish` `0x213eeec0`(edi=2) |
| 3 | viperfish | `0x1d4995a0` | `xla_target_viperfish` `0x213eef00`(edi=3) |
| 4 | ghostlite | `0x1d496640` | `xla_target_ghostlite` `0x213eeee0`(edi=4) |
| 5 | (匿名,`gfc`/TPU7x) | 通过 `0x1d49f100`→`0x1d49c9c0` 的 `ViperfishSparseCoreTarget` | `google_init_cold` 中的 thunk `0x213eef20`(edi=5) |

每个模块的 `$_0::__invoke` thunk(例如 ghostlite `__invoke` `0x1d499040`)把自身 `lea` 到 `%rsi`,在 `%edi` 中设置版本,然后 tail-call `RegisterTargetCreationFunctor`;`__invoke` 随后又 tail-call 具名的 `…Target::Create`。`Target` registry 的单例位于 `target_registry`(`0x2257e170`,guard `0x2257e178`),未命中会产生优雅字符串 `"No Target registered for "`(`0xa1e75db`)。

### CycleTable registry:未命中时 fatal

成本模型 registry 结构上完全相同,但它的未命中策略走向相反的极端。`CycleTable::Create`(`0x1c89cc00`)以 `Target+0x398` 为键,查找 registry 单例(`GetCycleTableRegistry::r`,`0x225799e8`),如果返回的函子是空哨兵,它会调用 `LogMessageFatal` 并**使进程崩溃**:

```c
function CycleTable::Create(target):                   // sub_1C89CC00
    ensure GetCycleTableRegistry::r                     // Meyers guard, operator new(0x28)
    key = *((u32*)target + 230)                         // Target+0x398
    fn = FunctionRegistry::Get(r, &key)                 // sub_1C89CD20
    if fn.is_empty():                                    // *(byte*)(it+16) == 1
        LogMessageFatal("cycle_table.cc", 960,           // HARD CRASH
                        "cycle_table_creator != nullptr")
            << "No cycle table registered for platform: " << key
    return fn(target)                                    // construct the per-gen CycleTable

理由是:任何可到达的代际都必须有成本模型,因此缺失注册是值得崩溃的构建/链接错误,而不是像缺失目标描述符那样可恢复的条件。

CycleTable registry 与 Target 在结构上有一点不同:它有六个叶子,0..5。成本模型从头到尾都是按代际的,即使 v5 Target 是共享的。具名的按代号 CycleTable vtables:

TpuVersion叶子_ZTV vtable 符号 @首槽地址(+0x10)
0 / 1JfCycleTable0x21c1ffb80x21c1ffc8
2PfCycleTable0x21c200480x21c20058
3VfCycleTable0x21c200c80x21c200d8
4GlcCycleTable0x21c201480x21c20158
5GfcCycleTable0x21c201c80x21c201d8

注意(vptr 约定): 上面两列是引用 vtable 的两种正确方式。_ZTV 符号地址(0x21c1ffb8 …)是 group base,它以 16 字节 {offset-to-top, typeinfo-ptr} 头部开始;首槽地址(0x21c1ffc8 …)是 _ZTV+0x10,即对象的 vtable pointer 实际持有的值。把 call *0xN(%rax) 调用点与符号匹配时,槽位索引从 _ZTV+0x10 开始度量,而不是从 _ZTV 符号开始。具名叶子的步长是 0x80 字节(base CycleTable vtable 位于 Jf 和 Pf 之间,使这一处间隙扩展为 0x90)。

IsaEmitter registry:最宽、以 pair 为键

ISA-emitter registry 是最宽的按代际分发器,也是唯一以 pair 为键的分发器:pair<TpuVersion, TpuSequencerType>。它的 find0x140af5e0)用 _mm_crc32_u64 对两个 int32 键半部取 hash,对 H2 control bytes 做 group-match(vpcmpeqb/vpmovmskb),对槽位执行 tzcnt,然后比较两个半部

c
// raw_hash_set<…pair<TpuVersion,TpuSequencerType>…>::find — sub_140AF5E0
if *(u32*)(slot)     == version &&        // first int32: TpuVersion
   *(u32*)(slot + 4) == seqtype:          // second int32: TpuSequencerType
    return slot                            // pair hit
```text

`Get` 是 `0x140af4e0`,`Register` 是 `0x140c2360`。registry 由约 28 个 `google_init_module_*_emitter` 静态初始化模块填充。其中一些是按代号、按 sequencer 划分的(用于 `jellyfish`、`pufferfish` tensor-core 与 barna-core、`viperfish` tensor-core、`ghostlite` tensor-core、barna-core address-handler 的 `MakeIsaEmitter<>` lambda);其余是 op-specific(cholesky、qr、lu、eigh、topk、resize、padding、alloc、window-prefetch、async-collective start/done),它们把自己的 emitter 注册到特定的 sequencer-type 单元格下。完整的 `(version × sequencer-type) → IsaEmitter-leaf` 单元格普查尚未穷尽枚举(它需要解码每个模块的 `__invoke` thunk 及其嵌入的 pair);registry *机制*和签名是 CERTAIN,按单元格的矩阵尚未追踪。

> **陷阱 —** `IsaEmitter` 键是一个 *pair*,并且 SOO(small-object)路径会把 8 字节键作为单个 `vpcmpeqd` 与 inline slot 比较。把该 registry 只以 `TpuVersion` 为键的重新实现者,会让同一代中的每个 sequencer type 都碰撞到一个槽位,并静默返回错误的 emitter。sequencer-type 半边是选择键的一部分,不是平局决胜项。

### 同级机制 M2:codec switch-jump

并不是每个按代际选择都是 registry。`tpu::TpuCodec::Create`(`0x1e835fa0`)是一个字面量 `switch (version)`,即 LLVM-lowered jump table,覆盖 case 0..5

```c
function TpuCodec::Create(out, version):               // sub_1E835FA0
    switch version:
        case 0: codec = CreateTpuCodecJellyfish()       // 0x1e840ac0
        case 1: codec = CreateTpuCodecDragonfish()      // 0x1e8360e0
        case 2: codec = CreateTpuCodecPufferfish()      // 0x1e841fa0
        case 3: codec = CreateTpuCodecViperfish()       // 0x1e843f00
        case 4: codec = CreateTpuCodecGhostlite()       // 0x1e83bce0
        case 5: codec = sub_1E838380()                  // anon v5 codec
    out[1] = codec ; out[0] = 1                          // StatusOr{codec}

codec 路径保留了一个具名 v5 case(sub_1E838380,匿名 v5 codec),即使 v5 Target 是共享的。这与 CycleTable 一样,是同一种 v5 不对称性。低层助记符编码器工厂 jfc::mnemonics::factory::CreateEncoder(version, seqtype)0x1d1e3820)是类似的 cmp $N,%esi 梯形结构,枚举字符串工具(TpuVersionToString 0x20b3a480TpuVersionToExternalName 0x20b3a500)则是更多 switches。这些是按代际分发家族中的字面量 switch 成员;数量少于十个,并且局限在 codec/encoder/enum-string 层。

同级机制 M3:HAL 工厂二维数组

HAL 工厂框架既不用 registry,也不用 switch,而是使用静态二维数组 tpu::(anon)::g_hal_factories_by_type0x22583be0,mutex 0x22583bd8)。它按 [PlatformType]0x30 字节步长,通过 lea (rcx,rcx,2); shl $4)× [TpuVersion](8 字节 unique_ptr<TpuHalFactory> 槽位,按 0x6 做边界检查)索引,并在 +0x90 处有已注册平台 bitmask。TpuHalFactory::Get0x1fbb19c0)以 (TpuVersion, optional<PlatformType>) 为键;Register0x1fbb16a0)设置平台位并写入 unique_ptr。未命中会产生 "No TPU platform registered for "0xa1e75f5)或 "No TPU platforms registered; check deps."0x860012b)。这是第三种按代际选择形状;所有三种如何分类,见分发表分类


相关组件

组件关系
Target 266 槽位 vtable 家族由 registry #1 选择的对象家族;运行时按代际行为
CycleTable 5 槽位 vtable 家族由 registry #2 选择;按代际成本模型
IsaEmitter 152 槽位 vtable 家族由以 pair 为键的 registry #6 选择;按代际 ISA 编码器
TpuCodec 6 槽位 vtable 家族由 M2 codec switch-jump 选择
优化重写分发器一个独立类别:MLIR PatternApplicator 按 benefit 排序的分发,不以版本为键

优化重写分发器(MLIR PatternApplicator / OperationLegalizer 引擎)经常与这个分发器混淆,因为二者都是“dispatchers”,但它按 op-kind 和 benefit 选择重写规则,而不是按版本选择按代际实现。它单独记录。

交叉引用

  • 分发表分类 — 父级分类;本页是深入考察的按代际 registry 类别,并与 M2/M3 同级机制并列
  • RTTI ↔ Vtable 普查 — 此分发器选择并构造的 266/5/152 槽位 vtable 家族
  • 多态分发入口点 — 构造出的按代际对象暴露的间接调用点和 thunk 表
  • 取证概览 — 按代际分发器在二进制剖析中的位置
  • ISA Emitter Registry — 从 ISA 侧描述的以 pair 为键的 IsaEmitter registry 及其按 sequencer 划分的单元格
  • 每代 Sequencer Ops — 构成 IsaEmitter 键第二半边的 TpuSequencerType