Skip to content

TraceEntriesCoder

本页中的所有地址和偏移都适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(build-id 89edbbe81c5b328a958fe628a9f2207d)。该二进制文件 stripped,完整 C++ 符号存在,并且 .text VMA 等于文件偏移。其他版本会有所不同。

摘要

TraceEntriesCoder 是设备端 profiler trace-entry 编解码器:每个 TPU 硬件 trace 事件都会打包进这种固定宽度、LSB-first 的位格式中,同时它也是在该线缆包和 proto2 TraceEntry 消息之间转换的、按芯片家族区分的解码器/编码器。它是 xprof device-trace pipeline 的最底层,也就是位于压缩 riegeli containerTraceEntry → 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/DMASparseCore bandvfc/vlc/gfcjxc 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_idDecodeEntry 跳转表),encode 按密集 oneof 字段号(EncodeEntry 跳转表);valid/started framing 语义;每个事件的总位数 CHECK
  • 按家族的工厂GetTraceCodec(DeviceIdentifiers, int) 从以芯片代号为键的静态类型工厂中,选择 pxc/vfc/vlc/glc/gfc 固定宽度 codec 之一(或 jxc legacy PerformanceTraceEntry 路径)。
Codec 接口asic_sw::driver::deepsea::profiler::TraceCodecInterface<TraceEntry>(抽象;vtable:DecodeEntry/EncodeEntry/GetMaxEntrySize/GetEntryPacketSize
包大小固定 16 字节(128 位)GetEntryPacketSize()==0x10GetMaxEntrySize()==0x20(decoded-proto 上界),全部 5 个家族
位顺序LSB-first;通过 BitDecoder::GetBits64NoInline @ 0x21073760SkipBitsNoInline @ 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 entrypxc TraceEntriesCoder::DecodeEntry @ 0xf5af3a0 → 两个连续 rel32 跳转表:111-entry @ 0xab85bc0(ids 0..0x6e)+ 145-entry @ 0xab85d7c(ids 0x6f..0xff
Encode entrypxc TraceEntriesCoder::EncodeEntry @ 0xf5c5e60 → 并行跳转表 @ 0xab85fc0
Decode driverxprof::tpu::DecodeTraceBuffers<TraceEntry> @ 0xf59ffa0(pxc):inflate 并遍历 16 字节包
Codec selectorxprof::tpu::GetTraceCodec(asic_sw::DeviceIdentifiers, int) @ 0xf5a2900
Factorypxc::driver::profiler::CreateTraceCodecplc symbol @ 0xf5af2c0);vfc @ 0xf5f5da0vlc @ 0xf5d5180glc @ 0xf6282e0gfc @ 0xf65ed00
Source paths (rodata)platforms/asic_sw/driver/deepsea/<fam>/profiler/trace_entries.proto…/trace_codec_factory.ccthird_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,地址分别为 pxc 0xf5d4ec0、vfc 0xf628020、vlc 0xf5f5ae0、glc 0xf65ea40、gfc 0xf697aa0
  • GetMaxEntrySize() 在对应的 -0x20 地址返回 0x20(32):这是反序列化后 proto 在 RAM 中的上界,不是 wire size。重新实现者不能混淆二者:wire 上是 16 字节,作为反序列化 TraceEntry 时是 ≤32 字节。
  • 每个事件的解码器在读取前都会按输入长度设门:cmp $0xf,%rdx; ja … 要求 string_view 长于 15 字节(即至少一个完整 16 字节包),成功时记录 bytes-consumed = 0x10movq $0x10,0x8(%rbx))。

该包是通过共享 BitDecoder window({cursor@+0x8, end@+0x10, buffer@+0x18, bits_avail@+0x20},0x28 字节对象)读取的 LSB-first bit-stream。在反编译的 DecodeEntry 中,window 从输入 string_view 在线栈构造:

c
// 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` 开头,二者合起来是一个**5961 位 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<<10ts(45)<<13payload<<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_ARGUMENT status:解码器构造 MakeErrorImpl<3>("Found a valid but not started packet.")(字符串 @ 0x9ff8a9c<3> 模板参数是 INVALID_ARGUMENT status code,0x2e02 是传给 AddSourceLocationImpl 的源码行号)。

反编译的 DecodeEntry 开头展示了精确读取顺序和分支逻辑:

c
// 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)。各家族宽度如下:

FamilyDecodeTraceHeaderidblock_idtimestampheader bitspayload start
pxc0xf5d4f2083485961
vfc0xf62808086455961
vlc0xf5f5b4083455658
glc0xf65eaa086455961
gfc0xf697b0086455961

怪癖: timestamp原始设备 cycle counter(48 或 45 位),不是皮秒。cycle→ps 转换(按代际设备时钟频率)以及每条 line 的 timestamp_ns 原点都在下游 TpuXLineBuilder 中应用,绝不在 codec 中应用。若重新实现把 wire 上的 timestamp 当作皮秒,就会差一个时钟周期倍数,并错误计算 counter wrap interval(48 位时约为 2^48 cycles)。参见 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)在所有家族中保持不变。

text
 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..0x6ecmp $0x6e; ja),而 default-path 第二表 @ 0xab85d7c(145 entries,在 0xf5b032f 到达,此处 id -= 0x6f; cmp $0x90; ja)覆盖 ids 0x6f..0xff。这些表按字节精确读取;其 band 结构镜像 trace-point registry

Bandtrace_point_id (pxc)Subsystem
UHI0–6host-DMA / address translation
OCI7–10, 20–27, 49–55, 91–96 (OCI-issued-from-TCS)on-chip interconnect engine
ICI40–48inter-chip interconnect / collective fabric
TCS80–90TensorCore sequencer sync/control (sync/fence/instruction + interrupt)
Throttle97thermal/electrical throttle state
BC (SparseCore/broadcast)100–134BcFsm / Bcs / BcOci controllers
CMQ140–149command queue / VPU DMA
Dummy255 (0xff)DummyTraceEntryDummyTracePoint
(reserved)11–19, 28–39, 56–79, 98–99, 135–139, 150–254unhandled → 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 @ 0xf5c5e60TraceEntry+0x28 中保存的密集 proto oneof 字段号(proto2 oneof case),通过 0xab85fc0 处的并行 rel32 表分发:

c
// 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)idoneofdecoderCHECK constant(s)width sequence (framing · header · [idhdr] · payload)
UhiHostDmaTransactionStartedAddressTranslation020xf5b0b80128, 2162 · 8/3/48 · 21/3/12 · 5,16,10,1,1,54,32
UhiHostPhysicalRequestRead130xf5b0f20128, 2332 · 8/3/48 · 21/3/12 · 1,30,1,1,29,26,8,20,20
TcsInternalSetSyncFlag81380xf5b97a01212 · 8/3/48 · (no idhdr) · 32,1,9,16,1,1
IciPacketPacketReceivedOnLinkInput40210xf5b56c01252 · 8/3/48 · 21/3/12 · 3,3,6,1,1,12,1,1
ThrottleStateThermalAndElectrical97540xf5bc6201202 · 8/3/48 · (no idhdr) · 4,5,5,10,4,21,5,5

注意: 每事件 CHECK 常量不总是单个值DecodeUhiHostDmaTransactionStartedAddressTranslationDecodeUhiHostPhysicalRequestRead 都包含两个 MakeCheckOpString 位置:第一个在 128 位处 CHECK prefix path,第二个在可选/条件 payload group 之后 CHECK full path(分别为 216233)。ThrottleStateThermalAndElectrical decoder 虽然名字如此,但只携带单个 oneof case(字段 54 = 0x36)和单个 CHECK == 120(decode 中没有 0x36/0x37 A/B 区分)。机制,即每条消费路径一个硬编码总位数 CHECK,已按字节确认 CERTAIN;对有 optional/repeated payload 字段的事件,精确常量取决于分支。应从该事件的 Decode<Name>/Encode<Name> 配对中恢复每个分支的精确集合。

每个 band 的语义字段映射,即每个宽度表示什么,由 payload 页面负责:UHI/OCI/ICI/DMASparseCore bandvfc/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:

c
// 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 和表偏移

SymbolAddress / offsetRole
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 timestampuniversal header
TraceIdHeader (proto2)+0x10 presence;+0x18 transaction_id;+0x1c core_id;+0x20 chip_ididentity 子记录: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_0xbe7944065-qword mask table,mask_[k]=(1<<k)-1
Decode jump table (low)0xab85bc0111 个 rel32 entries,ids 0..0x6e,按 8 位 trace_point_id 索引(pxc)
Decode jump table (high)0xab85d7c145 个 rel32 entries,ids 0x6f..0xffid-0x6f),紧接 low table
Encode jump table0xab85fc0rel32 entries,按 oneof_field - 2 索引(pxc)
MakeErrorImpl<3> string0x9ff8a9c"Found a valid but not started packet."(INVALID_ARGUMENT;0x2e02 是 source line)

相关组件

ComponentRelationship
riegeli Trace ContainerDecodeTraceBuffers 遍历 16 字节包之前 inflate 的压缩 transport
TracePoints Master Registry双分发所实现的 wire-id ↔ oneof-field 双 id 空间表
Payload: UHI/OCI/ICI/DMAhost-DMA/interconnect/fabric band 的 per-band payload 字段映射
Payload: SparseCore BandTCS/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 的下游阶段

交叉引用