模块初始化与插件发现
本页所有地址均适用于
libtpu-0.0.40-cp314wheel 中的libtpu.so(构建libtpu_lts_20260413_b_RC00,build-id md589edbbe81c5b328a958fe628a9f2207d,781,691,048 字节,ELF x86-64 DYN,未 strip;反修饰后的 C++ 符号按原文引用)。其他版本会不同。
摘要
PJRT 插件是一个由框架 dlopen 并完全通过一个导出的 C 入口符号驱动的 .so。对 libtpu 来说,发现契约被有意收窄:框架(JAX、TensorFlow 或 PyTorch-XLA)通过自己的 PJRT 插件注册表找到 libtpu.so(Python 侧路径、PJRT_PLUGIN/PJRT_NAMES_AND_LIBRARY_PATHS 环境映射,或打包的入口点),对其执行 dlopen,用 dlsym 查找唯一符号 GetPjrtApi,然后无参数调用它。TPU 内部细节不会越过这条边界;框架只知道符号名和最前面几个 PJRT_Api 字段偏移。本页负责 overview.md 与 get-pjrt-api-thunk.md 只指向的三件事:发现握手(解析什么名字,以及框架可以假设什么)、动态链接器在 dlopen 时驱动的加载期初始化链(CPU 特性硬门禁、2900 项 .init_array 静态构造风暴,以及它不会运行什么),以及把刚加载的 .so 变成一个活动 TPU 驱动会话的一次性启动门禁。
决定性的结构事实是:dlopen 时几乎什么都不会发生。动态链接器会运行一个 CPU 特性快速失败探测,然后运行约 2900 个 C++ 静态构造函数,但这些构造函数只做注册:它们填充 absl flag 表、protobuf 描述符、LLVM/MLIR 后端,以及 Google 风格的模块初始化依赖 DAG。对顺序敏感的 TPU bring-up(HAL 工厂、XLA target functor、StreamExecutor platform)会在这里被注册,但不会运行。PJRT_Api 表本身也不会被构建,它是 .lbss 中一个零填充的 Meyers singleton。真正的工作由后续两个惰性、一次性事件完成:第一次 GetPjrtApi 调用在 17 个 __cxa_guard 组成的链下物化 140 槽表,而第一次 PJRT_Plugin_Initialize 调用(槽 8)获取跨进程 TPU 锁,并按拓扑顺序运行模块 DAG,执行链接器先前只是记录下来的注册。硅片检测会被进一步推迟到 PJRT_Client_Create。
这种分离是经典的 Google base/init_google 模式(REGISTER_MODULE_INITIALIZER):加载时静态注册,首次初始化时有序执行。它存在的目的正是避免跨翻译单元静态初始化顺序(其唯一保证是链接顺序)决定对顺序敏感的 TPU 栈是否正确。
对重新实现而言,契约是:
- 发现握手:恰好一个导出的入口符号(
GetPjrtApi,小写jrt),@@VERS_1.0;拼写、大小写和零参数签名都至关重要。 - 加载期初始化链:一个 PREINIT CPU 门禁会在任何构造函数运行前,对缺失 ISA 特性的情况执行
raise(SIGILL),随后是一个只注册的.init_array风暴,它构建GoogleInitializer模块 DAG,但不执行任何模块。 - 一次性启动门禁:
PJRT_Plugin_Initialize(槽 8):struct_size兼容性检查、kPjRtCApiTpuInitType选择器、TryAcquireTpuLock、GetLibTpuInitArguments、InitializeDriver→RealInitGoogle→RunInitializers(DAG 运行),全部通过层叠的 once-guard 保持幂等。
| 导出入口符号 | GetPjrtApi @ 0xe6a83a0 — 5 字节 jmp thunk,GetPjrtApi@@VERS_1.0 |
| 真实引擎 | pjrt::tpu_plugin::GetTpuPjrtApi @ 0xe6aa440 (1336 B) |
| 表存储 | GetTpuPjrtApi()::pjrt_api @ 0x227BA840,.lbss (NOBITS),1120 B = 140 × 8 |
| PREINIT_ARRAY | @ 0x22048b30(16 B,2 项):CPU gate + dl-debug hook |
| INIT_ARRAY | @ 0x215f26f0(23200 B = 2900 项,全部为 R_X86_64_RELATIVE) |
| FINI_ARRAY | @ 0x215f8190(16 B,2 项) |
| 启动门禁 | pjrt::tpu_plugin::PJRT_Plugin_Initialize @ 0xe6a9d00 (303 B),PJRT 槽 8 |
| init-type 选择器 | kPjRtCApiTpuInitType(静态 = 2)@ 0x22255b40(.data) |
| DAG 运行驱动器 | GoogleInitializer::RunInitializers @ 0x210b2d20(PHASE B,首次 init 时) |
| 可信度 | CONFIRMED(字节锚定 vs 反编译),除非某行或标注另有说明 |
1. 插件发现握手
目的
PJRT 存在的全部理由,是提供一个 ABI 稳定的会合点。框架不了解 libtpu 的内部;它只知道入口符号名和最前面几个 PJRT_Api 字段的布局,其余内容都在运行时通过 struct_size 和 extension chain 发现。本节固定这个会合点,让重新实现者可以交付一个 stock JAX/PyTorch-XLA 构建会加载的 .so。深层的 GetTpuPjrtApi 函数体和 tpu_plugin 对象归 get-pjrt-api-thunk.md 负责;PJRT_Api 结构形状归 ../pjrt/overview.md 和 ../pjrt/api-vtable-reconstruction.md 负责。本页只负责握手本身。
框架如何找到 libtpu
插件路径是在 C 边界之前、由框架侧建立的。没有任何 libtpu 代码会向 OS “注册”该插件;发现是框架的职责:
framework PJRT plugin registry (Python side)
├─ a packaged entry point / pip-installed plugin descriptor names libtpu, OR
├─ env PJRT_NAMES_AND_LIBRARY_PATHS / a "tpu" name → /path/to/libtpu.so, OR
└─ a default search of the installed wheel's libtpu/libtpu.so
│
dlopen("libtpu.so") ── dynamic linker runs the load-time init chain (§2)
dlsym(handle, "GetPjrtApi") ── the ONLY name that resolves
│ "GetTpuPjrtApi" is an INTERNAL helper, not exported
└─ GetPjrtApi 0xe6a83a0 ── 5-byte: jmp 0xe6aa440
└─ pjrt::tpu_plugin::GetTpuPjrtApi 0xe6aa440 (lazy 140-slot build — §1.2)
```text
> **GOTCHA —** 拼写和大小写至关重要。导出的符号是 `GetPjrtApi`(小写 `jrt`),匹配公开 PJRT 插件约定,版本化为 `GetPjrtApi@@VERS_1.0`。它是唯一匹配 `/Pjrt/` 的 `GLOBAL FUNC` 导出。`GetTpuPjrtApi` 是内部 helper,**不会**导出。若 loader 对 `GetTpuPjrtApi` 执行 `dlsym`,或某个构建只导出带 `Tpu` 前缀的名字,发现会静默失败。共享这个二进制的 194 个 `Tpu*_*` 导出属于*旧式* StreamExecutor C-ABI(`TpuExecutor_*` ×25、`TpuTransferManager_*` ×19、`TpuProgram_*` ×18、`TpuTopology_*` ×17、`TpuPlatform_*` ×11,…),全部为 `@@VERS_1.0`,由 `tensorflow/core/tpu/` 直接链接,而不会通过 PJRT 到达。
### 入口契约
```c
// The one symbol the framework dlsym's. No arguments, returns the table.
const PJRT_Api* GetPjrtApi(void); // exported, GetPjrtApi@@VERS_1.0GetPjrtApi @ 0xe6a83a0 是一个纯 tail-call thunk;反编译中确认为单行 return pjrt::tpu_plugin::GetTpuPjrtApi(a1)(a1 寄存器是死的;规范签名不接收参数)。该 thunk 的存在是为了给公开名称提供外部链接,而实际引擎留在匿名的 pjrt::tpu_plugin 命名空间中。
调用方随后读取 api->struct_size 以了解该插件提供多少槽,读取 api->pjrt_api_version(此构建中 minor 为 103),遍历 api->extension_start 以发现可选能力,然后才调用 api->PJRT_Plugin_Initialize(槽 8)和 api->PJRT_Client_Create(槽 15)。这两个槽分别进入启动门禁(§3)和硅片检测。
为什么表在首次调用时构建,而不是加载时
GetTpuPjrtApi 的 pjrt_api 是 .lbss 中的函数局部 static(NOBITS,@ 0x227BA840),加载时零填充。第一次 GetPjrtApi 时,引擎运行 17 个受 __cxa_guard 保护的一次性块:其中 16 个构建 .bss extension chain(每个以之前的节点作为 .next,从 .data 静态 profiler 播种),第 17 个调用 pjrt::CreatePjrtApi 写入全部 140 个槽。反编译逐字确认了这条 16-builder 梯子:
function GetTpuPjrtApi(): // 0xe6aa440
// 16 one-shot extension builders, construction order (each takes the prior as .next):
once: CreateRawBufferExtension(&raw_buffer_ext, &profiler_extension) // seed = .data profiler
once: CreateLayoutsExtension(&layouts_ext, &raw_buffer_ext)
once: CreateMemoryDescriptionsExtension(&mem_desc_ext, &layouts_ext)
once: CreateExecutableMetadataExtension(&exec_meta_ext, &mem_desc_ext, GetTpuExecutableMetadata)
once: CreateHostAllocatorExtension(&host_alloc_ext, &exec_meta_ext, GetPreferredAlignment, Allocate, Free)
once: CreateCrossHostTransfersExtension(...)
once: CreatePhaseCompileExtension(..., GetTpuPhaseCompiler, DestroyTpuPhaseCompiler)
once: CreateCallbackExtension(...)
once: CreateTpuTopologyExtension(...)
once: CreateTpuExecutableExtension(...)
once: CreateMegascaleExtension(...)
once: CreateShardingsExtension(...)
once: CreateTpuAbiVersionExtension(...)
once: CreateCollectivesExtension(...)
once: CreateMultiSliceExtension(...)
once: CreateHostMemoryAllocatorExtension(&hma_ext, &multi_slice_ext) // last-built = chain head
// 17th guard: write all 140 slots, chain head = host_memory_allocator_extension
once: CreatePjrtApi(&pjrt_api,
PJRT_Client_Create, PJRT_ExecuteContext_Create,
PJRT_TopologyDescription_Create, PJRT_Plugin_Initialize,
&host_memory_allocator_extension /*chain head*/,
PJRT_Plugin_Attributes_Xla)
return &pjrt_api // 0x227BA840
```text
> **NOTE —** 因为表在首次调用时物化,静态反汇编看不到已填充的槽值;`.lbss` 镜像在运行前全为零。140 槽到实现的映射是从 `CreatePjrtApi` 的函数体重建的,而不是从二进制的数据段重建的。一次性构建完成后,该结构在进程生命周期内不可变;读取方不加锁,并发的首次调用方通过 Itanium-ABI `__cxa_guard_acquire/release` 串行化。chain 构建细节(16+1 个 builder、newest-first chain、五个 TPU 注入槽)完全归 [get-pjrt-api-thunk.md](get-pjrt-api-thunk.md) 和 [../pjrt/extension-chain.md](../pjrt/extension-chain.md) 负责;这里展示它只是为了把“首次调用,而非加载时”的边界具体化。
---
## 2. 加载期初始化链(`dlopen` 驱动什么)
### 目的
本节中的所有内容都由动态链接器驱动,同步运行在 `dlopen` 调用内部,发生在 `GetPjrtApi` 被调用之前。重新实现者的心智模型必须是:`dlopen` 运行一个硬 CPU 门禁和一个只注册的构造函数风暴,然后就*停止*。不会触碰 TPU 硬件,不存在 `PJRT_Api` 表,也没有活动的驱动会话。ELF entry/`init_proc` 和 `__do_init/__do_fini` 机制归 `elf-entry-and-init-proc` 与 `do-init/do-fini` 页面负责;本节覆盖该链中与*插件发现相关*的内容:CPU 门禁,以及构造函数风暴注册了什么、没有运行什么。
### 链接器驱动的四个阶段
```text
dlopen("libtpu.so")
1. Relocation ── ALL of INIT_ARRAY (2900 slots), PREINIT_ARRAY (2),
FINI_ARRAY (2) are R_X86_64_RELATIVE — in-file slots are
zero, the linker fills every target VA at load.
2. DT_INIT (.init @ 0xe635524)
── vestigial glibc __gmon_start__ check-and-call stub.
ALL real init runs through .init_array, not here.
3. PREINIT_ARRAY @ 0x22048b30 (runs BEFORE any C++ constructor)
[0] (anon)::cpu_feature_fail_fast 0x2110abc0 ── CPU ISA hard gate (§2.1)
[1] setup_dl_debug_hook 0x2114eec0 ── dl debug rendezvous
4. INIT_ARRAY @ 0x215f26f0 (2900 entries, in array order)
__cpu_indicator_init → Rust std::sys args ARGV init → 2898 C++ static ctors
(register-only — §2.2)2.1 CPU 特性硬门禁(PREINIT_ARRAY[0])
(anonymous namespace)::cpu_feature_fail_fast @ 0x2110abc0 最先运行,早于任何构造函数,并隔离整个静态初始化风暴。它调用 __cpu_indicator_init(GCC ifunc 支持例程)来填充全局特性掩码(dword_22598A0C),然后逐项检查该二进制编译时要求的 ISA 特性。若缺少某项特性,它会向 stderr 执行 write(2, …) 输出 "FATAL ERROR: This binary was compiled with <feat> enabled, but this feature is not available on this processor (go/sigill-fail-fast)." 消息,并执行 raise(SIGILL)(raise(4)):这是无法恢复的硬中止。
该门禁通过 __cpu_indicator_init 掩码检查十一项特性,按固定 fall-through 顺序进行,另有一个独立的 cpuid leaf-1 ECX 探测用于 CMPXCHG16B。完整集合(mask bit → feature)如下。
| 顺序 | 掩码测试(dword_22598A0C &) | 特性 |
|---|---|---|
| 1 | 0x40000 | AES |
| 2 | 0x200 | AVX |
| 3 | 0x2 | MMX |
| 4 | 0x80000 | PCLMUL |
| 5 | 0x4 | POPCNT |
| 6 | 0x8 | SSE |
| 7 | 0x10 | SSE2 |
| 8 | 0x20 | SSE3 |
| 9 | 0x80 | SSE4.1 |
| 10 | 0x100 | SSE4.2 |
| 11 | 0x40 | SSSE3 |
| 12 | cpuid(1).ecx & 0x2000 | CMPXCHG16B |
function cpu_feature_fail_fast(): // 0x2110abc0
__cpu_indicator_init() // fills dword_22598A0C
mask = dword_22598A0C
// fall-through chain: each missing feature writes a FATAL string + raise(SIGILL),
// then continues testing the next (so all missing features are reported).
if !(mask & 0x40000): fatal("aes"); ... // AES
if !(mask & 0x200): fatal("avx"); ... // AVX
... MMX, PCLMUL, POPCNT, SSE, SSE2, SSE3, SSE4.1, SSE4.2, SSSE3 ...
// CMPXCHG16B is a direct cpuid probe, not the indicator mask:
eax = 1; cpuid
if !(ecx & 0x2000): fatal("cmpxchg16b"); raise(SIGILL)
return
```text
> **GOTCHA —** 这个门禁在 `dlopen` 时运行,而不是首次使用时运行。若宿主 CPU 缺少这十一项基线特性中的任意一项,框架一 `dlopen` `libtpu.so` 就会 SIGILL,远早于 `GetPjrtApi`,并且只有 stderr 消息,没有 PJRT 级错误返回。把它移植到非常规宿主的重新实现者必须满足完整的 SSE/SSSE3/AES/AVX/PCLMUL/POPCNT/CMPXCHG16B 基线;不存在优雅降级路径。
### 2.2 构造函数风暴只注册,不运行 TPU bring-up
`INIT_ARRAY @ 0x215f26f0` 为 23200 字节 = 2900 项,每一项都是 `R_X86_64_RELATIVE` reloc(文件内槽为零,由链接器填充)。前几项是 `__cpu_indicator_init`,然后是 Rust runtime 的 `std::sys::args::unix::imp::ARGV_INIT_ARRAY`(libtpu 静态链接了一个 Rust 组件),再后面是剩余 2898 个 C++ 静态构造函数。按符号类别统计(全部 2900 个槽上字节精确的计数):
| 构造函数类型 | 数量 | 作用 |
|---|---|---|
| `_GLOBAL__sub_I_<file>.cc/.cpp` | 1885 | 每个翻译单元的静态初始化 |
| `_GLOBAL__I_NNNNNN` | 759 | 分组的 C++ ctors |
| `__cxx_global_var_init[.N]` | 221 | 单个全局变量初始化 |
| 匿名 / 无符号 ctors + `__do_init` + `upb_GeneratedRegistry_Constructor` | 33 | 其余 C++ ctors |
| `__cpu_indicator_init` | 1 | GCC ifunc 支持(第一个槽) |
| Rust `ARGV_INIT_ARRAY` | 1 | Rust std args bootstrap |
| **总计** | **2900** | 匹配 `INIT_ARRAYSZ`/8 |
这些构造函数会注册但不会执行对顺序敏感的 TPU 栈。它们填充:absl 命令行 flag 表(`_GLOBAL__sub_I_*_flags.cc`、`commandlineflags.cc`)和 absl logging;protobuf 描述符(7 个 `*proto/descriptor` TU + upb `linkarr_upb_AllExts` mini-table 数组 `@ 0x224c2480..0x224c2920`);LLVM target 后端(X86/AArch64/AMDGPU/ARM `*TargetMachine.cpp`、`AsmPrinter.cpp`);MLIR dialect/pass 注册(mhlo/stablehlo/`mlir_bridge_pass`);以及发现流程的关键部分:`GoogleInitializer` **模块描述符**及其依赖边。
```c
// Each _GLOBAL__sub_I_<module>_registration.cc ctor, at dlopen, does ONLY:
function _GLOBAL__sub_I_tpu_platform_registration(): // 0x2121f040
// bind module NAME → google_init_module_tpu_platform fn-ptr, insert into registry
GoogleInitializer(LiteralTag, "tpu_platform", file, &google_init_module_tpu_platform) // 0x210b2780
// ... and register FLAGS_tf_jf_* absl flags ...
// the module FUNCTION is NOT called here.
// e.g. the HAL modules also register a dependency edge:
function _GLOBAL__sub_I_tpu_hal_jxc_hardware_impl_registration(): // 0x2121bcb0
GoogleInitializer(LiteralTag, "tpu_hal_jxc_hardware_impl", file, &google_init_module_...)
GoogleInitializer::DependencyRegisterer(..., Dependency) // 0x210b29e0
// edge: tpu_hal_jxc_hardware_impl → depends on tpu_halNOTE — C++ 静态初始化在
INIT_ARRAY内的顺序是链接顺序,这是唯一保证。libtpu 有意不依赖它保证正确性:每个对顺序敏感的注册(HAL 工厂、XLA target functor、StreamExecutor platform)都会在dlopen时被记录进GoogleInitializer依赖 DAG,并在后续首次 init(§3)时按拓扑顺序运行。因此重新实现者可以按任意链接顺序注册模块 ctors;决定执行顺序的是 DAG,而不是链接器。
3. 一次性启动门禁
目的
PJRT_Plugin_Initialize(槽 8)是把已加载但惰性的 .so 变成活动 TPU 驱动会话的唯一入口。它连接了 dlopen 留下的“所有东西已注册、什么都没运行”状态与运行中的模块 DAG。它是幂等的,受运行时选择器控制,并且是唯一获取跨进程 TPU 锁的位置。更深的 option ingest 和 TfTpu_Initialize 系列细节归 tftpu-initialize-bootstrap.md 负责;本节负责该门禁的控制流及其 once-guard 纪律。
算法
pjrt::tpu_plugin::PJRT_Plugin_Initialize @ 0xe6a9d00(303 B),由框架在 GetPjrtApi 后调用:
function PJRT_Plugin_Initialize(args): // 0xe6a9d00, PJRT slot 8
// (a) backward-compat size gate — accepts args struct from min=27 down to cur=16
ok = ActualStructSizeIsGreaterOrEqual("PJRT_Plugin_Initialize_Args", 27, 16, args->struct_size)
if ok != 1:
return new uint8_t[8]{ ok } // error wrapper (heap-boxed status)
// (b) runtime init-type selector (statically kPjRtCApiTpuInitType == 2)
if kPjRtCApiTpuInitType != 0: // @ 0x22255b40 (.data)
if TryAcquireTpuLock("PJRT_Plugin_Initialize_Args") == 1: // 0x20ccbc40
GetLibTpuInitArguments(&argv) // 0x20ccca20 — reads LIBTPU_INIT_ARGS env
InitializeDriver(flag, argc, argv,
init_type_is_2 = (kPjRtCApiTpuInitType == 2)) // 0x204cecc0
free(argv …) // release the temporary arg vectors
return NULL // success
else:
return new uint8_t[8]{ status } // lock-fail / size-mismatch error wrapper
// (c) init-type 0 → no-op
return NULL
```text
反编译确认了每条分支:头部 `ActualStructSizeIsGreaterOrEqual(…, 27, 16, *a1)`;`if (kPjRtCApiTpuInitType)` 选择器;`TryAcquireTpuLock` → 当 `== 1` 时调用 `GetLibTpuInitArguments`,再调用 `InitializeDriver(…, kPjRtCApiTpuInitType == 2, …)`,随后 argv-free 循环并 `return 0`;失败路径上的 `operator new(8u)` error-wrapper;以及 init-type-0 短路中的 `return 0`。
### 锁、参数与 DAG 运行
```text
PJRT_Plugin_Initialize (slot 8) 0xe6a9d00
├─ TryAcquireTpuLock 0x20ccbc40 ── cross-process TPU acquisition:
│ function-static absl::Mutex (guard 0x225925d0 / obj 0x225925c8),
│ env TPU_LOAD_LIBRARY (str @ file 0x887356a), scans /dev for TPU device
│ nodes (opendir/readdir/stat). Returns 1 iff the lock is taken.
├─ GetLibTpuInitArguments 0x20ccca20 ── reads env LIBTPU_INIT_ARGS
│ (str @ file 0x918c880), splits into an argv-style vector.
└─ InitializeDriver 0x204cecc0 ── driver bring-up:
├─ AppendNewCloudTPUArgs ── fold Cloud-TPU default flags into argv
├─ InitGoogleExceptChangeRootAndUser 0x210b0180 ── tail-jmp →
│ RealInitGoogle 0x210ae860
│ ├─ absl::ParseCommandLine
│ └─ GoogleInitializer::RunInitializers 0x210b2d20 *** PHASE B ***
│ └─ run all registered modules in topological dep order:
│ google_init_module_tpu_hal_{jxc,pxc,vxc,glc,gfc}_*
│ → TpuHalFactory::Register(PlatformType, TpuVersion, factory) 0x1fbb16a0
│ google_init_module_xla_target_{jellyfish,…,ghostlite}
│ → RegisterTargetCreationFunctor(N, …)
│ google_init_module_tpu_platform 0x213eabc0 (jmp)
│ → RegisterTpuPlatform → PlatformManager::RegisterPlatform
│ … all other registered modules …
└─ telemetry: RegisterLibtpuGaugeTelemetry, RegisterMegascaleErrorHandler,
EnableRuntimeUptimeTelemetry, InitializeUptimeMetricViaEnvironmentVariables这里才是 §2.2 的注册真正运行的位置(PHASE B)。RunInitializers 通过 TypeData::RunIfNecessary @ 0x210b3320 / RunOne @ 0x210b3000 驱动 DAG,由 absl::Mutex 和每个模块的运行状态门控,State::CanRun @ 0x210b3c80 决定就绪性,kInitGoogleDone @ 0x22040038 / CheckInitGoogleIsDone @ 0x210adec0 标记完成。
QUIRK — HAL 工厂按 TpuVersion 注册,但不会扫描硅片。
google_init_module_tpu_hal_jxc_hardware_impl @ 0x213e9d80两次调用TpuHalFactory::Register:TpuVersion 0(kJellyfish)和 1(kDragonfish),二者都使用TpuHalJxcHardwareFactory;pxc 注册 v2(kPufferfish),vxc 注册 v3(kViperfish),glc 注册 v4(kGhostlite),gfc 注册 v5。初始化时,registry 只是按(PlatformType, TpuVersion)被填充。实际硅片扫描,即通过*HardwareScanner::CreateHAL 组件把 PCI device ID 匹配到TpuVersion,会在之后的PJRT_Client_Create(槽 15)内部运行。因此 TpuVersion 检测比dlopen晚两个阶段:首次 init 时注册,首次 client 时检测。
StreamExecutor platform 注册
DAG 中的一个模块是 StreamExecutor TpuPlatform。google_init_module_tpu_platform @ 0x213eabc0 是一个 thunk,跳到 tensorflow::tpu::RegisterTpuPlatform @ 0xe99a3a0:
function RegisterTpuPlatform(): // 0xe99a3a0
fn = stream_executor::tpu::ExecutorApiFn() // 0x20819360
if IsStreamExecutorEnabled(fn) // 0x20819380
and !tpu_platform_registered: // byte guard @ 0x224c5388 (one-shot)
p = new tensorflow::tpu::TpuPlatform() // 0xe999960, sizeof = 0x98 (152 B)
tpu_registered_platform = p
st = PlatformManager::RegisterPlatform(p) // 0x1d0fe120
if st != OK:
LOG(FATAL) at tpu_platform.cc:178 // CHECK-fail
tpu_platform_registered = 1
return 1
```text
这会在 PJRT `PjRtClient` 之下安装旧式 StreamExecutor `TpuPlatform`。§1 中的 194 个 `Tpu*_*` C-ABI 导出(包括 `TpuPlatform_*`)包装的是同一对象家族;它是现代 PJRT 栈所处的设备层。
### Once-guard 纪律
三种不同的 once-guard 机制让整条链保持幂等;再次调用 `GetPjrtApi` 或 `PJRT_Plugin_Initialize` 会快速 no-op:
| 机制 | 位置 | 地址 |
|---|---|---|
| C++ `__cxa_guard`(libtpu 自己的 libc++abi,不是 glibc 的) | `GetTpuPjrtApi` 16 个 ext + `pjrt_api`(×17) | acquire/release/abort `0x213e9ac0`/`0x213e9be0`/`0x213e9c20`;例如 raw_buffer guard `0x224c39e0` |
| `absl::Mutex` once-lock | `TryAcquireTpuLock::mu` | guard `0x225925d0` / obj `0x225925c8` |
| `absl::Mutex` registry lock | `GoogleInitializer::RunInitializers` | 位于 `0x210b2d20` 内 |
| 函数静态 byte guard | `RegisterTpuPlatform::tpu_platform_registered` | `0x224c5388` |
| 函数静态 byte guard | `__do_init` / `__do_fini` | `0xe63c000` / `0xe63c020` |
| 环境变量门禁 | `TPU_LOAD_LIBRARY`(在 `TryAcquireTpuLock` 中) | str `@ 0x887356a` |
| 环境变量参数 | `LIBTPU_INIT_ARGS`(在 `GetLibTpuInitArguments` 中) | str `@ 0x918c880` |
| init-type 选择器 | `kPjRtCApiTpuInitType` (= 2) | `0x22255b40`(`.data`) |
> **NOTE —** `kPjRtCApiTpuInitType` 在 `.data` 中静态为 `2`。Type 2 走完整 TPU bring-up 路径(`InitializeDriver(…, init_type_is_2 = true, …)`);type 0 会让 `PJRT_Plugin_Initialize` no-op。是否存在某条路径会把选择器改写为 `0`/`1`(例如选择旧式 TF init-type)尚未追踪(改写存在性的可信度为 LOW;静态值 `2` 为 CONFIRMED)。
---
## 4. 拆卸
### 目的
为了对称性,这里说明卸载路径。不存在大型的 PJRT surface `atexit` 拆卸;`PJRT_Api` 表和 17 个 extension 都是退出时泄漏的函数局部 static(`.lbss`/`.data`/`.bss`),这是插件 `.so` 常见的 Meyers-singleton 生命周期。Client、executable 和 profiler handle 通过显式的 `PJRT_*_Destroy` C-API 调用拆除,而不是在进程退出时拆除。
### 卸载时运行什么
```text
dlclose / process exit
├─ DT_FINI (.fini @ 0xe63553c) ── empty (sub/add/ret)
└─ FINI_ARRAY @ 0x215f8190 (2 entries, R_X86_64_RELATIVE)
[0] __do_fini 0xe63c020 ── trivial guarded dtor stub
[1] rand_thread_state_clear_all 0x2063df60 ── clears per-thread BoringSSL/RNG statelibtpu 还提供 atexit / __cxa_thread_atexit shim(@ 0x21217360 / 0x2120f1e0)以及用于刷新日志的 threadlogger::FlushLogsAtExit(@ 0x20f3dfe0),但没有任何 PJRT 对象析构被接入其中。
相关组件
| 组件 | 关系 |
|---|---|
GetPjrtApi @ 0xe6a83a0 | 单一导出入口符号;发现会合点 |
pjrt::tpu_plugin::GetTpuPjrtApi @ 0xe6aa440 | 惰性 140 槽构建引擎(归 get-pjrt-api-thunk.md 负责) |
pjrt::CreatePjrtApi @ 0xf874160 | 在第 17 个 guard 上把全部 140 个槽写入 .lbss |
cpu_feature_fail_fast @ 0x2110abc0 | PREINIT CPU ISA 硬门禁(缺少基线时 SIGILL) |
GoogleInitializer registry / RunInitializers @ 0x210b2d20 | 加载时注册 / 首次 init 时运行的模块 DAG |
PJRT_Plugin_Initialize @ 0xe6a9d00 | 一次性启动门禁(PJRT 槽 8) |
TryAcquireTpuLock @ 0x20ccbc40 | 跨进程 TPU 获取锁 |
RegisterTpuPlatform @ 0xe99a3a0 | 在 PJRT 之下安装 StreamExecutor TpuPlatform |
Tpu*_* C-ABI(194 个导出) | 共享同一二进制的旧式 StreamExecutor surface,不会通过 PJRT 到达 |
交叉引用
- overview.md — 生命周期章节地图:从
dlopen到可用 client - get-pjrt-api-thunk.md —
GetPjrtApithunk、GetTpuPjrtApi引擎函数体、完整的 16+1__cxa_guard链,以及tpu_plugin对象 - tftpu-initialize-bootstrap.md — initialize 入口、
LIBTPU_INIT_ARGSoption ingest,以及InitializeDriverflag 集 - ../pjrt/overview.md — PJRT C-ABI 地图:
PJRT_Api结构形状、握手,以及按区域划分的 extension chain - ../pjrt/api-vtable-reconstruction.md — 完整的 140 槽逐字段表(每个槽 → impl symbol + address)
- ../pjrt/extension-chain.md — 17 节点 extension 链表、逐节点布局,以及
PJRT_Extension_Base机制