TraceEntriesCoder
本页中的所有地址和偏移都适用于
libtpu-0.0.40-cp314wheel 中的libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d)。该二进制文件未 stripped,完整 C++ 符号存在,并且.textVMA 等于文件偏移。其他版本会有所不同。
摘要
TraceEntriesCoder 是设备端 profiler trace-entry 编解码器:每个 TPU 硬件 trace 事件都会打包进这种固定宽度、LSB-first 的位格式中,同时它也是在该线缆包和 proto2 TraceEntry 消息之间转换的、按芯片家族区分的解码器/编码器。它是 xprof device-trace pipeline 的最底层,也就是位于压缩 riegeli container 和 TraceEntry → XEvent/XStat 整形之间的阶段。route-cache 记录是自定界的 varint 流,而 profiler trace 事件正好相反:一个固定 16 字节(128 位)的包,包含 2 位 framing 前缀、56 或 59 位 header(pxc/vfc/glc/gfc 为 59,vlc 为 56)、可选的 36/38 位 transaction-identity 子记录(pxc 为 36,vfc/vlc/glc/gfc 为 38),以及每个事件的固定宽度 payload,全部使用共享的 GetBits64/SkipBits bit-codec 原语读取。
该 codec 有一个刻意设计为不对称的双 id 空间分发;正确处理它就是重新实现的核心。Decode 会窥视 2 个 framing 位和 8 位 trace_point_id,即分带的硬件 enum 值,有空洞,pxc 最大处理到 0x95(149),另有一个 0xff dummy,然后索引一对连续的 rel32 跳转表(第一个位于 0xab85bc0,上界 0x6e;第二个位于 0xab85d7c,覆盖 ids 0x6f..0xff)以到达匿名 namespace 中的 Decode<EventName>()。Encode 则通过并行跳转表,按存储在 TraceEntry+0x28 的密集 proto oneof 字段号进行分发。这两个 id 空间不可互换;把它们配对的注册表由 TracePoints Master Registry 负责,若重新实现时用 wire id 驱动 encode(或用 oneof 字段驱动 decode),每个事件都会键错。
本页负责codec 格式和分发:16 字节包、framing/header/TraceIdHeader 位布局、每个事件的总位数 CHECK 校验、每个家族的 CreateTraceCodec/GetTraceCodec 工厂接线,以及 header 分割如何随硅片代际变化。它不负责每个 band 的 payload 字段映射(UHI/OCI/ICI/DMA、SparseCore band、vfc/vlc/gfc、jxc legacy)、trace-point id 注册表(master registry)、压缩容器(riegeli)或 XEvent 转换(trace-entry-to-xevent)。
对重新实现而言,契约如下:
- 固定 16 字节包和 bit-stream reader:LSB-first,
GetBits64NoInline(n)读取 n 位字段,SkipBitsNoInline(n)前进,并由mask_[k] = (1<<k)-1掩码。 - 2 位 framing 前缀 + 56/59 位
TraceHeader+ 可选 36/38 位TraceIdHeader:这是 envelope,包含每家族的block_id/timestamp分割(pxc/vfc/glc/gfc = 59 位 header;vlc = 56 位),以及 Viperfish 上chip_id从 12→14 的加宽。 - 双分发:decode 按 8 位 wire
trace_point_id(DecodeEntry跳转表),encode 按密集 oneof 字段号(EncodeEntry跳转表);valid/startedframing 语义;每个事件的总位数CHECK。 - 按家族的工厂:
GetTraceCodec(DeviceIdentifiers, int)从以芯片代号为键的静态类型工厂中,选择 pxc/vfc/vlc/glc/gfc 固定宽度 codec 之一(或 jxc legacyPerformanceTraceEntry路径)。
| Codec 接口 | asic_sw::driver::deepsea::profiler::TraceCodecInterface<TraceEntry>(抽象;vtable:DecodeEntry/EncodeEntry/GetMaxEntrySize/GetEntryPacketSize) |
| 包大小 | 固定 16 字节(128 位):GetEntryPacketSize()==0x10,GetMaxEntrySize()==0x20(decoded-proto 上界),全部 5 个家族 |
| 位顺序 | LSB-first;通过 BitDecoder::GetBits64NoInline @ 0x21073760、SkipBitsNoInline @ 0x21073580、mask table mask_ @ 0xbe79440 读取 |
| Header 布局 | valid:1 · started:1 · trace_point_id:8 · block_id:3│6 · timestamp:48│45:framing+header = pxc/vfc/glc/gfc 的 61 位(payload @ bit 61);vlc 为 8/3/45 → 56 位 header,payload @ bit 58 |
| Decode entry | pxc TraceEntriesCoder::DecodeEntry @ 0xf5af3a0 → 两个连续 rel32 跳转表:111-entry @ 0xab85bc0(ids 0..0x6e)+ 145-entry @ 0xab85d7c(ids 0x6f..0xff) |
| Encode entry | pxc TraceEntriesCoder::EncodeEntry @ 0xf5c5e60 → 并行跳转表 @ 0xab85fc0 |
| Decode driver | xprof::tpu::DecodeTraceBuffers<TraceEntry> @ 0xf59ffa0(pxc):inflate 并遍历 16 字节包 |
| Codec selector | xprof::tpu::GetTraceCodec(asic_sw::DeviceIdentifiers, int) @ 0xf5a2900 |
| Factory | pxc::driver::profiler::CreateTraceCodec(plc symbol @ 0xf5af2c0);vfc @ 0xf5f5da0,vlc @ 0xf5d5180,glc @ 0xf6282e0,gfc @ 0xf65ed00 |
| Source paths (rodata) | platforms/asic_sw/driver/deepsea/<fam>/profiler/trace_entries.proto,…/trace_codec_factory.cc;third_party/gloop/util/coding/bitcoding.cc |
固定 16 字节包
每个设备端 profiler trace 事件都是一个定长的 16 字节(128 位)包,不是 varint、gamma,也不是任何自定界记录。这是它与 gloop bit-codec 工具集其余部分最重要的差异;其他地方使用 varint-framed 记录,而 profiler 路径是纯固定宽度。
二进制中有三种方式证明该常量大小:
GetEntryPacketSize()在所有五个家族中都返回0x10(16):每个都是一条mov $0x10,%eax; ret,地址分别为 pxc0xf5d4ec0、vfc0xf628020、vlc0xf5f5ae0、glc0xf65ea40、gfc0xf697aa0。GetMaxEntrySize()在对应的-0x20地址返回0x20(32):这是反序列化后 proto 在 RAM 中的上界,不是 wire size。重新实现者不能混淆二者:wire 上是 16 字节,作为反序列化TraceEntry时是 ≤32 字节。- 每个事件的解码器在读取前都会按输入长度设门:
cmp $0xf,%rdx; ja …要求string_view长于 15 字节(即至少一个完整 16 字节包),成功时记录 bytes-consumed =0x10(movq $0x10,0x8(%rbx))。
该包是通过共享 BitDecoder window({cursor@+0x8, end@+0x10, buffer@+0x18, bits_avail@+0x20},0x28 字节对象)读取的 LSB-first bit-stream。在反编译的 DecodeEntry 中,window 从输入 string_view 在线栈构造:
// DecodeEntry @0xf5af3a0 — window init (decompiled v14[] = the BitDecoder)
v14[1] = data_ptr; // +0x8 cursor = start of the 16-byte packet
v14[0] = data_ptr; // +0x0/+0x18 buffer base (LSB-first source)
v14[2] = data_ptr + length; // +0x10 end
v15 = 0; // +0x20 bits_avail
```text
字段通过 `bitcoding.cc` 中的两个原语提取:
| Primitive | Address | Effect |
|---|---|---|
| `BitDecoder::GetBits64NoInline(dec, n, out)` | `0x21073760` | 将接下来的 `n` 位(LSB-first)读入 `*out`,由 `mask_[n]` 掩码;cursor 前进 `n` 位 |
| `BitDecoder::SkipBitsNoInline(dec, n)` | `0x21073580` | 不物化位值,直接让 cursor 前进 `n` 位 |
| `mask_[k] = (1<<k)-1` | `0xbe79440` | 65-qword `.rodata` mask table;`+0x8`→`mask_[1]`,`+0x18`→`mask_[3]`,`+0x40`→`mask_[8]`,`+0xa8`→`mask_[21]`,`+0x100`→`mask_[32]`,`+0x180`→`mask_[48]`(偏移已按字段宽度验证) |
> **注意:** `GetBits64NoInline` 在 `n > 64` 时 fault(`ud1`),并在内部处理跨 buffer 边界的情况;该 codec 单次调用从不请求超过 48 位。`NoInline` 变体是 out-of-line 副本;同一逻辑在每个事件解码器的数千个调用点处被内联。
---
## Framing 前缀和 TraceHeader
每个包都以 2 位 framing 前缀和一个 `TraceHeader` 开头,二者合起来是一个**59 或 61 位 envelope**,具体取决于家族,之后 typed payload 开始。
```text
bit field width proto
─── ──────────────────── ─────────────────────── ──────────────────────────────
0 valid 1 (framing — 0 ⇒ end of buffer)
1 started 1 (framing — valid&&!started ⇒ error)
2-9 trace_point_id 8 TraceHeader.trace_point_id (f1)
10-K trace_point_block_id 3 (pxc/vlc) │ 6 (vfc/glc/gfc) TraceHeader.f2
K+1 timestamp 48 (pxc) │ 45 (vfc/vlc/glc/gfc) TraceHeader.f3
(pxc/vfc/glc/gfc header ends at bit 60 ⇒ framing+header = 61, payload @ bit 61)
(vlc header ends at bit 57 ⇒ framing+header = 58, payload @ bit 58)header 子预算(id + block_id + timestamp)在 pxc/vfc/glc/gfc 中为 59 位,但在 vlc 中为 56 位(字节级确认:vlc DecodeTraceHeader @0xf5f5b40 读取 8 / 3 / 45,不是 8 / 3 / 48)。对 59 位家族而言,较新的硅片把 block_id 从 3 位加宽到 6 位,同时把 timestamp 从 48 位收窄到 45 位,以保持 envelope 不变,因此这四个家族的 payload 起点保持在 bit 61。vlc 是例外:它保留 3 位 block_id,同时使用 45 位 timestamp,因此它的 header 只有 56 位,payload 从 bit 58 开始(vlc Encode<…> @0xf5f3700 反向确认:block<<10、ts(45)<<13、payload<<58)。跨代重新实现必须从各家族的 DecodeTraceHeader 推导 timestamp 宽度和 payload 起点,绝不能假定固定 59 位 envelope。
Framing 语义:valid 和 started
这两个 framing 位不是 proto 的一部分;它们是 wire-level flow control:
valid(bit 0)是 empty-slot sentinel。valid == 0的包表示正常流结束:buffer 可以在没有显式 entry count 的情况下被读到容量上限;解码器停止并以*started_out = 0报告成功。这就是 ring buffer 可以被过量分配并一直读取到第一个清零 slot 的原因。started(bit 1)用于捕获 torn/partial 硬件写。valid && !started会返回INVALID_ARGUMENTstatus:解码器构造MakeErrorImpl<3>("Found a valid but not started packet.")(字符串 @0x9ff8a9c;<3>模板参数是INVALID_ARGUMENTstatus code,0x2e02是传给AddSourceLocationImpl的源码行号)。
反编译的 DecodeEntry 开头展示了精确读取顺序和分支逻辑:
// pxc TraceEntriesCoder::DecodeEntry @0xf5af3a0
GetBits64NoInline(dec, 1, &valid); // slot -0x58 (bit 0)
GetBits64NoInline(dec, 1, &started); // slot -0x50 (bit 1)
GetBits64NoInline(dec, 8, &id); // slot -0x48 (bits 2..9) — peek, then re-decode in handler
if (valid) {
if (started) {
*valid_out = 1;
*started_out = 0; // (sic — set then overwritten by handler success path)
if (id <= 0x6e) { // first jump table @0xab85bc0 (111 entries, ids 0..0x6e)
switch (id) {
case 0: DecodeUhiHostDmaTransactionStartedAddressTranslation(...); break;
case 1: DecodeUhiHostPhysicalRequestRead(...); break;
// … 0-6 UHI, 7-10/20-27 OCI, 40-48 ICI, 49-55 OCI, 80-90 TCS, 91-96 OCI-from-TCS, 97 throttle …
default: goto second_table; // reserved slots fall to @0xf5b032f
}
} else { // second jump table @0xab85d7c (ids 0x6f..0xff)
second_table: // @0xf5b032f: id -= 0x6f; if (id > 0x90) goto unhandled;
switch (id) {
// … 100-134 BcFsm/Bcs/BcOci (SparseCore), 140-149 CMQ, 0xff dummy …
default: goto unhandled; // @0xf5b0b33: bytes_consumed=0, return OK
}
}
} else {
return MakeErrorImpl<3>("Found a valid but not started packet."); // @0x9ff8a9c
}
} else {
*valid_out = 0; // EOS — graceful, status OK
return OK;
}
```text
> **陷阱:** `DecodeEntry` 读取 8 位 id 只是为了*索引跳转表*。每个 `Decode<Name>()` handler 随后都会从 bit 0 重新解码该包:先用 `SkipBits(2)` 跳过 framing,再完整执行 `DecodeTraceHeader`,然后读取 typed payload,因为 handler 需要把 header 字段物化进 proto,而不仅是刚才窥视到的 id。若重新实现试图在 dispatcher 和 handler 之间共享一次 header decode,就会重复消费或让 cursor 定位错误。
### 每代 header 宽度
`DecodeTraceHeader` 是每个家族各一个的匿名 namespace helper,它按顺序执行恰好三次 `GetBits64` 调用,并填充 `TraceHeader` proto。pxc 版本已在反编译中按字节确认:
```c
// pxc DecodeTraceHeader @0xf5d4f20
GetBits64NoInline(dec, 8, &id); th = Arena::DefaultConstruct<TraceHeader>(); th[+0x18]=id; th[+0x10] |= 1;
GetBits64NoInline(dec, 3, &block); th[+0x1c]=block; th[+0x10] |= 2;
GetBits64NoInline(dec, 48, &ts); th[+0x20]=ts; th[+0x10] |= 4;|= 1 / 2 / 4 是 proto2 has-bits(presence byte 位于 TraceHeader+0x10,bit0=id,bit1=block_id,bit2=timestamp)。各家族宽度如下:
| Family | DecodeTraceHeader | id | block_id | timestamp | header bits | payload start |
|---|---|---|---|---|---|---|
| pxc | 0xf5d4f20 | 8 | 3 | 48 | 59 | 61 |
| vfc | 0xf628080 | 8 | 6 | 45 | 59 | 61 |
| vlc | 0xf5f5b40 | 8 | 3 | 45 | 56 | 58 |
| glc | 0xf65eaa0 | 8 | 6 | 45 | 59 | 61 |
| gfc | 0xf697b00 | 8 | 6 | 45 | 59 | 61 |
怪癖:
timestamp是原始设备 cycle counter(48 或 45 位),不是皮秒。cycle→ps 转换(按代际设备时钟频率)以及每条 line 的timestamp_ns原点都在下游TpuXLineBuilder中应用,绝不在 codec 中应用。若重新实现把 wire 上的 timestamp 当作皮秒,就会差一个时钟周期倍数,并错误计算 counter wrap interval(48 位时约为2^48cycles)。参见 TraceEntry → XEvent/XStat。
TraceIdHeader 子记录
多数 variant 消息会在 header 后立即携带一个 TraceIdHeader,即让多包 DMA 能被重新拼接到一起的 per-transaction identity。它被内联解码(没有独立的 DecodeTraceIdHeader 符号)到嵌套 proto2 TraceIdHeader 子消息中。其宽度在 pxc 上为 36 位(chip_id = 12),但在 vfc/vlc/glc/gfc 上为 38 位(Viperfish 起 chip_id 加宽到 14);transaction_id(21)和 core_id(3)在所有家族中保持不变。
rel bit field width proto
─────── ─────────────── ─────────── ────────────────────────────────────────
0-20 transaction_id 21 TraceIdHeader.transaction_id (f1)
21-23 core_id 3 f2 — enum {TC0,TC1,BC0..BC3,NONCORE,…} (8 values ⇒ 3 bits)
24-.. chip_id 12 (pxc) │ 14 (vfc/vlc/glc/gfc) f3
= 36 bits (pxc) │ 38 bits (vfc/vlc/glc/gfc), immediately after the header when present
```text
在 pxc `DecodeUhiHostPhysicalRequestRead` @ `0xf5b0f20` 中已按字节确认,开头三次 `GetBits64` 调用为 `21 / 3 / 12`;在较新代的 `DecodeScMessageOutboundInternalMessage`(vfc)@ `0xf60e6c0` 中则是 `21 / 3 / 14`:
```c
// DecodeUhiHostPhysicalRequestRead @0xf5b0f20 — TraceIdHeader, then payload
GetBits64NoInline(dec, 21, &transaction_id); // TraceIdHeader f1
GetBits64NoInline(dec, 3, &core_id); // f2 (3-bit enum)
GetBits64NoInline(dec, 12, &chip_id); // f3
// then the typed payload: 1, 30, 1, 1, 29, 26, 8, 20, 20 …3 位 core_id 正好容纳 8 值 CORE_ID enum(RESERVED/NONCORE/TC0/TC1/BC0..BC3)。有些事件会携带多个 TraceIdHeader:OCI read/write command 嵌入三个(cmd0/cmd1/cmd2),也就是在任何其他 payload 前先有 3 × 36 = 108 位 identity。哪些事件携带一个、三个或零个的逐 band 细节由 payload pages 负责。
双分发
该 codec 被刻意设计为不对称:decode 和 encode 索引不同的跳转表,且 keyed by 不同的 id 空间。
Decode:按 8 位 wire trace_point_id
DecodeEntry 按 wire 上的 8 位 trace_point_id 分发,即分带的硬件 enum,有空洞,pxc 最大处理 0x95 = 149(另有一个 0xff dummy),并通过两个连续的 rel32 跳转表实现:第一个 @ 0xab85bc0(111 entries)覆盖 ids 0..0x6e(cmp $0x6e; ja),而 default-path 第二表 @ 0xab85d7c(145 entries,在 0xf5b032f 到达,此处 id -= 0x6f; cmp $0x90; ja)覆盖 ids 0x6f..0xff。这些表按字节精确读取;其 band 结构镜像 trace-point registry:
| Band | trace_point_id (pxc) | Subsystem |
|---|---|---|
| UHI | 0–6 | host-DMA / address translation |
| OCI | 7–10, 20–27, 49–55, 91–96 (OCI-issued-from-TCS) | on-chip interconnect engine |
| ICI | 40–48 | inter-chip interconnect / collective fabric |
| TCS | 80–90 | TensorCore sequencer sync/control (sync/fence/instruction + interrupt) |
| Throttle | 97 | thermal/electrical throttle state |
| BC (SparseCore/broadcast) | 100–134 | BcFsm / Bcs / BcOci controllers |
| CMQ | 140–149 | command queue / VPU DMA |
| Dummy | 255 (0xff) | DummyTraceEntryDummyTracePoint |
| (reserved) | 11–19, 28–39, 56–79, 98–99, 135–139, 150–254 | unhandled → graceful return |
陷阱: reserved id range 不会 fall through 到相邻 handler。第一个表中的 reserved slot(ids ≤
0x6e)会跳到0xf5b032f,这不是错误标签,而是第二个分发表的入口;两个表中 out-of-range / unfilled slot 最终都会落到0xf5b0b33,设置bytes_consumed = 0并返回OK(unhandled id 会被静默跳过,而不是 status error)。这确认 band gap 是刻意预留空间,不是 decode bug。band 检测应由每个家族跳转表的内容驱动,绝不要靠硬编码的 pxc range;随着 trace-point 数量增长,band 边界会按代际移动(pxc 99 个 handled cases → gfc 144 个)。
Encode:按密集 oneof 字段号
EncodeEntry @ 0xf5c5e60 按 TraceEntry+0x28 中保存的密集 proto oneof 字段号(proto2 oneof case),通过 0xab85fc0 处的并行 rel32 表分发:
// EncodeEntry @0xf5c5e60 — dispatch + inlined header pack
field = *(int*)(entry + 0x28); // proto oneof case (dense)
idx = field - 2; // table indexed by field-2
if (idx > 0x62 /*pxc bound*/) default;
goto *jumptable_0xab85fc0[idx]; // → Encode<Name>()
// inlined header pack (Encode<Name>, word0):
word0 = (mask_[1] & flag) * 3; // flag*3 ⇒ bits 0 and 1 both set (valid=1, started=1)
word0 |= id << 2; // trace_point_id
word0 |= block << 10; // block_id
word0 |= ts << 13; // timestamp (pxc/vlc) — vfc/glc/gfc shift <<16 (6-bit block)
word0 |= payload_lo << 61; // payload @ bit 61 (pxc/vfc/glc/gfc); vlc shifts <<58
// word1 = remaining payload; two qwords (16 B) written to the encoder SSO buffer
```text
encode shift 是 decode `GetBits` 宽度的字节级精确反向:`<<2`(在 2 个 framing 位之后)、`<<10`(在 id 之后),随后 block/timestamp 分割按家族变化:pxc/vlc 使用 `ts<<13`(3 位 block 之后),vfc/glc/gfc 使用 `ts<<16`(6 位 block 之后);payload 起点在 pxc/vfc/glc/gfc 为 `<<61`,但在 **vlc 为 `<<58`**(vlc 的 45 位 timestamp + 3 位 block ⇒ 56 位 header;已在 `Encode<…> @0xf5f3700` 字节级确认)。framing 写作 `flag*3`,即 `mask_[1] & 1` 后 `lea(r8,r8,2)`,这会同时设置 bit 0 和 bit 1,也就是每个真实 entry 都为 `valid=1, started=1`。
### 两个已配对的 id 空间
wire id 和 oneof 字段是不同 namespace;handler 通过 `movl $field,0x28(%entry)` 填写 oneof 字段。经字节确认的配对示例:
| `trace_point_id` (wire) | event | oneof field (proto) | encode bound `cmp` |
|---|---|---|---|
| 0 | `UhiHostDmaTransactionStartedAddressTranslation` | 2 | — |
| 1 | `UhiHostPhysicalRequestRead` | 3 | — |
| 40 | `IciPacketPacketReceivedOnLinkInput` | 21 (`0x15`) | — |
| 81 | `TcsInternalSetSyncFlag` | 38 (`0x26`) | — |
| 97 | `ThrottleStateThermalAndElectrical` | 54 (`0x36`) | — |
各家族 oneof-field encode bounds(跳转前的 `cmp $imm`):pxc `0x62`,vlc `0x4d`,vfc `0x79`,glc `0x7f`,gfc `0x7f`。完整 id↔field 注册表由 [TracePoints Master Registry](tracepoints-master-registry.md) 负责。
> **怪癖:** decode 最大 handled wire id(pxc 为 `0x95`,分散在以 `0x6e`/`0x6f` 分割的两个跳转表中)和 encode 最大 oneof field(pxc 为 `0x62`,在 `field-2` 偏置之后)不同,因为 wire id 是*带空洞的分带空间*,而 oneof field 是*密集空间*。二者计数的是同一组事件(pxc 99 个);只有索引方式不同。若重新实现把一个表按另一个表的上界定大小,就会越界或截断。
---
## 每事件 Decode 契约和总位数 CHECK
每个 `Decode<Name>(string_view, bool* started_out, TraceEntry* out)` 都遵循同一形态:
```c
// generic Decode<EventName> contract (e.g. DecodeUhiHostPhysicalRequestRead @0xf5b0f20)
function Decode<Name>(view, started_out, entry):
if view.length <= 0xf: return error // need a full 16-byte packet
BitDecoder dec(view);
SkipBits(dec, 2); // skip the 2 framing bits
DecodeTraceHeader(entry, dec); // id/block/timestamp into TraceHeader
[ DecodeTraceIdHeader inline: 21/3/12 ] // when the event carries identity
variant = entry.mutable_<Name>(); // construct the oneof submessage
entry[+0x28] = <oneof field number>; // stamp the dense proto case
GetBits64(dec, w0, &variant.f0); // typed fixed-width payload …
GetBits64(dec, w1, &variant.f1);
…
consumed = (view.length * 8) - dec.bits_remaining(); // BitsDecoded()
CHECK(consumed == <hardcoded constant>); // MakeCheckOpString on mismatch ⇒ FATAL
*bytes_consumed = 0x10; // 16 bytes, on success最终的 CHECK 校验 handler 恰好消费了格式要求的位数:absl::log_internal::MakeCheckOpString<…>(consumed, K, "decoder.BitsDecoded() == K") 在不匹配时触发 fatal log。这是 codec 的自一致性保护:它固定了每个事件的总 wire 宽度。
经字节确认的总位数 CHECK 常量(直接从每个 decoder 的 MakeCheckOpString 位置读取)及其完整宽度序列如下。每个序列以 2 个 framing 位开始,接着是 [8/3/48] DecodeTraceHeader,然后(对携带 identity 的事件)是 [21/3/12] TraceIdHeader,最后是 typed payload:
| Event (pxc) | id | oneof | decoder | CHECK constant(s) | width sequence (framing · header · [idhdr] · payload) |
|---|---|---|---|---|---|
UhiHostDmaTransactionStartedAddressTranslation | 0 | 2 | 0xf5b0b80 | 128, 216 | 2 · 8/3/48 · 21/3/12 · 5,16,10,1,1,54,32 |
UhiHostPhysicalRequestRead | 1 | 3 | 0xf5b0f20 | 128, 233 | 2 · 8/3/48 · 21/3/12 · 1,30,1,1,29,26,8,20,20 |
TcsInternalSetSyncFlag | 81 | 38 | 0xf5b97a0 | 121 | 2 · 8/3/48 · (no idhdr) · 32,1,9,16,1,1 |
IciPacketPacketReceivedOnLinkInput | 40 | 21 | 0xf5b56c0 | 125 | 2 · 8/3/48 · 21/3/12 · 3,3,6,1,1,12,1,1 |
ThrottleStateThermalAndElectrical | 97 | 54 | 0xf5bc620 | 120 | 2 · 8/3/48 · (no idhdr) · 4,5,5,10,4,21,5,5 |
注意: 每事件
CHECK常量不总是单个值。DecodeUhiHostDmaTransactionStartedAddressTranslation和DecodeUhiHostPhysicalRequestRead都包含两个MakeCheckOpString位置:第一个在128位处CHECKprefix path,第二个在可选/条件 payload group 之后CHECKfull path(分别为216和233)。ThrottleStateThermalAndElectricaldecoder 虽然名字如此,但只携带单个 oneof case(字段54 = 0x36)和单个CHECK == 120(decode 中没有0x36/0x37A/B 区分)。机制,即每条消费路径一个硬编码总位数CHECK,已按字节确认 CERTAIN;对有 optional/repeated payload 字段的事件,精确常量取决于分支。应从该事件的Decode<Name>/Encode<Name>配对中恢复每个分支的精确集合。
每个 band 的语义字段映射,即每个宽度表示什么,由 payload 页面负责:UHI/OCI/ICI/DMA、SparseCore band、vfc/vlc/gfc。每个家族全部 99–144 个事件的完整(offset, width, semantic)元组可以从 Decode<Name>/Encode<Name> 配对机械 dump 出来,但本页不列成表(完整性 LOW confidence,与 payload pages 注记中的缺口相同)。
Decode Driver 和按家族的 Factory
DecodeTraceBuffers:遍历循环
xprof::tpu::DecodeTraceBuffers<TraceEntry> @ 0xf59ffa0(pxc 实例化)是把压缩 device-trace blob 转成 RepeatedPtrField<TraceEntry> 的 driver:
// DecodeTraceBuffers<TraceEntry> @0xf59ffa0
function DecodeTraceBuffers(codec, out_entries, ..., scratch):
StringReader src(blob); // @0xf59eac0
ZlibReader zin(&src); zin.Initialize(); // ZlibReaderBase::Initialize @0xf69f9e0
ReadAllImpl(&zin, &decompressed); // read_all_internal::ReadAllImpl @0xf5acf40
view = decompressed;
while (view.length >= 0x10) { // one 16-byte packet at a time
bool valid, started;
TraceEntry* e = out_entries.Add();
codec->vtable.DecodeEntry(view, e, &valid, &started); // call *0x18(vtable)
if (!valid) break; // graceful EOS
view.remove_prefix(0x10); // advance 16 bytes
}
```text
transport,即 `ZlibReader` 外层的 riegeli record framing、zlib window/dictionary,以及多个 per-core ring drain 是否是单独 riegeli record,由 [riegeli Trace Container](riegeli-trace-container.md) 负责。`DecodeTraceBuffers` 本身只看到一个 inflated `StringReader` stream。
### TraceCodecInterface 和 factory
每个芯片家族的 codec 都是一个具体的 `TraceCodecInterface<TraceEntry>`(抽象基类;四个 vtable slot 是 `DecodeEntry`/`EncodeEntry`/`GetMaxEntrySize`/`GetEntryPacketSize`)。它由每个家族的 `CreateTraceCodec` 构造,并通过 `DeviceIdentifiersAsString` 注册进以 `asic_sw::DeviceIdentifiers`(芯片代号)为键的静态类型工厂:
| Family | `CreateTraceCodec` | `DecodeEntry` | `DecodeTraceHeader` | block/ts | `DecodeTraceBuffers` template |
|---|---|---|---|---|---|
| pxc | `0xf5af2c0`(`plc` symbol) | `0xf5af3a0` | `0xf5d4f20` | 3/48 | `<pxc::…::TraceEntry>` |
| vfc | `0xf5f5da0` | (per family) | `0xf628080` | 6/45 | `<vxc::vfc::…::TraceEntry>` |
| vlc | `0xf5d5180` | (per family) | `0xf5f5b40` | **3/45** | `<vxc::vlc::…::TraceEntry>` |
| glc | `0xf6282e0` | (per family) | `0xf65eaa0` | 6/45 | `<gxc::glc::…::TraceEntry>` |
| gfc | `0xf65ed00` | (per family) | `0xf697b00` | 6/45 | `<gxc::gfc::…::TraceEntry>` |
| jxc | (legacy path) | — | — | — | `<jxc::PerformanceTraceEntry>` |
### GetTraceCodec:selector
`xprof::tpu::GetTraceCodec(asic_sw::DeviceIdentifiers, int)` @ `0xf5a2900` 是运行时 selector。反编译显示它遍历一串 `asic_sw::internal::TypeFactoryBase<DeviceIdentifiers, &DeviceIdentifiersAsString, TraceCodecInterface<…::TraceEntry>, false>::Create<>` 尝试;函数体引用 `vlc`、`vfc`、`glc`、`gfc` 和 `jxc` 家族 codec,并最终 fall through 到 **pxc** factory 作为默认值:
```c
// GetTraceCodec @0xf5a2900 (decompiled skeleton)
function GetTraceCodec(out, device_ids, gen):
if (try TypeFactoryBase<…vlc::TraceEntry>::Create(out, device_ids)) return out;
if (try TypeFactoryBase<…vfc::TraceEntry>::Create(out, device_ids)) return out;
if (try TypeFactoryBase<…glc::TraceEntry>::Create(out, device_ids)) return out;
// … gfc via its own GetTraceCodec<gfc::TraceEntry> @0xf5a2b60; jxc PerformanceTraceEntry path …
TypeFactoryBase<…pxc::TraceEntry>::Create(out, device_ids); // default
return out;每个家族还有一个模板化的 GetTraceCodec<…::TraceEntry> 实例化(例如 gfc @ 0xf5a2b60),用于包装家族特定的 unique_ptr<TraceCodecInterface<TraceEntry>> 构造。selector 返回一个 unique_ptr,随后由 DecodeTraceBuffers driver 以多态方式驱动。
怪癖: jxc 不共享此 codec。它使用另一种 proto 类型:
asic_sw::driver::deepsea::jxc::PerformanceTraceEntry,并由自己的DecodeTraceBuffers<PerformanceTraceEntry>实例化解码,而不是固定 16 字节的TraceEntry路径。若重新实现假定所有代际共用一种包 schema,就会误解析 jxc traces。jxc 细节见 Payload: jxc Legacy。
相关 Struct 和表偏移
| Symbol | Address / offset | Role |
|---|---|---|
TraceEntry (proto2) | +0x18 trace_header ptr;+0x20 active oneof variant ptr;+0x28 oneof case(proto field number,即 encode dispatch key) | decoded message |
TraceHeader (proto2) | +0x10 presence(bit0=id,bit1=block,bit2=ts);+0x18 trace_point_id;+0x1c block_id;+0x20 timestamp | universal header |
TraceIdHeader (proto2) | +0x10 presence;+0x18 transaction_id;+0x1c core_id;+0x20 chip_id | identity 子记录:pxc 上 36 位(chip_id 12),vfc/vlc/glc/gfc 上 38 位(chip_id 14) |
BitDecoder | +0x8 cursor,+0x10 end,+0x18 buffer(LSB-first),+0x20 bits_avail(总计 0x28) | bit-stream window |
mask_ | 0xbe79440 | 65-qword mask table,mask_[k]=(1<<k)-1 |
| Decode jump table (low) | 0xab85bc0 | 111 个 rel32 entries,ids 0..0x6e,按 8 位 trace_point_id 索引(pxc) |
| Decode jump table (high) | 0xab85d7c | 145 个 rel32 entries,ids 0x6f..0xff(id-0x6f),紧接 low table |
| Encode jump table | 0xab85fc0 | rel32 entries,按 oneof_field - 2 索引(pxc) |
MakeErrorImpl<3> string | 0x9ff8a9c | "Found a valid but not started packet."(INVALID_ARGUMENT;0x2e02 是 source line) |
相关组件
| Component | Relationship |
|---|---|
| riegeli Trace Container | DecodeTraceBuffers 遍历 16 字节包之前 inflate 的压缩 transport |
| TracePoints Master Registry | 双分发所实现的 wire-id ↔ oneof-field 双 id 空间表 |
| Payload: UHI/OCI/ICI/DMA | host-DMA/interconnect/fabric band 的 per-band payload 字段映射 |
| Payload: SparseCore Band | TCS/SparseCore band payload 字段映射 |
| Payload: vfc/vlc/gfc | 较新家族的 payload delta(6-bit block_id、45-bit timestamp) |
| Payload: jxc Legacy | 独立的 PerformanceTraceEntry schema 及其自身 codec |
| TraceEntry → XEvent/XStat | 将解码后的 TraceEntry 整形为 device-plane XEvent + XStats 的下游阶段 |
交叉引用
- Profiling and Telemetry Overview:五阶段 capture→encode→decode→xplane pipeline,本 codec 是其中第 3 阶段
- riegeli Trace Container:第 2 阶段,即喂给
DecodeTraceBuffers的 zlib/riegeli 压缩 transport - TracePoints Master Registry:本页双分发所 keyed on 的分带 wire id 和密集 oneof field id 空间
- TraceEntry → XEvent/XStat:第 5 阶段,解码后的 proto 在此变成 XEvent,且原始 cycle timestamp 被转换为皮秒
- Payload: UHI/OCI/ICI/DMA · SparseCore Band · vfc/vlc/gfc · jxc Legacy:本 codec 页面刻意不重复的 per-band payload 字段映射