Skip to content

Route-Cache 位编解码器

地址适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so。其他版本会不同。二进制: extracted/libtpu-0.0.40-cp314-cp314-manylinux_2_31_x86_64/libtpu/libtpu.so(build-id 89edbbe81c5b328a958fe628a9f2207d,build libtpu_lts_20260413_b_RC00.text VMA == 文件偏移)。已反混淆名称和地址已与 IDA 反编译结果交叉核对(ToroidalRouteCache::DecodePathFromBitsBitDecoder::{GetVarInt,GetGamma,GetBits64NoInline}TopologyRotationHelper::{Create,RotateId,RotateCoordinates}Direction::OrientationToDimension)。

摘要

本文记录单个缓存路由路径的 位编解码器:跳序列如何被打包进 bit_encoded_path(type-2)缓存值并解包回来,该编解码器依赖的 基础位读写工具集gloop bitcoding.cc / coder.cc),以及编码前和解码后用于规范化路径坐标系的 轴置换数学TopologyRotationHelper)。这是环面路由缓存四篇文档之一;解压驱动 展开磁盘上的 blob,容器 持有消息层级并遍历每种 scheme,去重器 提供缓存键和每个形状的 orientation 列表。本文负责内部编解码器DecodePathFromBits、推断出的 BitEncoder 路径写入器、四个 BitDecoder 基元(GetVarInt/GetGamma/GetBits64/SkipBits)及其 BitEncoder 对偶,以及 TopologyRotationHelper 旋转。

编解码器很小但很精确。缓存必须从 一个 按固定规范轴顺序写入的烘焙 blob,服务一个 torus 形状的每一种 X/Y/Z 轴 orientation。因此解码路径分两阶段:(1) DecodePathFromBits规范 坐标系中从位流重建 vector<proto::Direction>;(2) TopologyRotationHelper 重新标记芯片 id 和坐标系,使规范路径落到实际物理轴上。编码路径(离线生成器,未随 libtpu.so 发布)是严格逆过程,其字节格式由发布的解码器恢复,并与共享的 gloop 写入器工具集做往返验证。

重新实现时,契约为:

  • 解码DecodePathFromBits @0x20b5c5a0):一个自定界的 GetVarInt(chunk=4) 跳数头,随后每跳一个 2-bit orientation 字段({0,1,2} → X/Y/Z)加 1 个 polarity bit({0,1} → POSITIVE/NEGATIVE),字段 3重复前一跳 转义且不消耗 polarity bit。基于 BitEncoder::mask_[k]=(1<<k)-1 的 LSB-first 64-bit 窗口;尾部验证字节对齐的零填充(任何非零或 ≥1 字节空余都会硬错误)。
  • 编码(推断的 BitEncoder 打包器):字节级精确逆过程,即 PutVarInt(4, n),然后每跳 PutBits(2, orient-1)+PutBits(1, polarity-1),或对同方向重复写 PutBits(2, 3),最后零填充 Flush()。最小位数转义策略是从解码器 推断 的,不是字节追踪得到的(生成器不在二进制中)。
  • 基元gloop bitcoding.cc):四个 BitDecoder 读取器及其 BitEncoder 写入器,运行在一个 LSB-first 64-bit 窗口和一张共享 mask_ 表上。路由缓存 使用 GetVarInt/PutVarInt(chunk=4) 加固定的 2-bit/1-bit PutBitsGetGamma/GetBits64/SkipBits 属于其他消费者(设备 trace profiler),此处为完整性而记录。
  • 旋转TopologyRotationHelper):坐标向量的 纯轴置换 out[k] = in[orient[k]-1],以及 mixed-radix 芯片 id 重标记,不做符号翻转,不加偏移。
解码入口ToroidalRouteCache::DecodePathFromBits(string_view) @0x20b5c5a0(584 行反编译代码)
编码入口推断的 BitEncoder 路径打包器(离线;精确逆过程,INFERRED-POLICY)
基础读取器BitDecoder::{GetVarInt @0x20b5cfa0, GetGamma @0x21073160, GetBits64NoInline @0x21073760, SkipBitsNoInline @0x21073580}
基础写入器内联的 BitEncoder::{PutBits, PutVarInt, PutGamma, Flush};由 BitEncoder::Initialize @0x21072d40 见证
字节缓冲区gloop Encoder(coder.cc);析构函数 @0x21073980CHECK buf_ <= limit_
掩码表BitEncoder::mask_ @0xbe79440(.rodata,65 个 qword,mask_[k]=(1<<k)-1
旋转TopologyRotationHelper::{Create @0x20bf2380, RotateId @0x20bf3020, RotateCoordinates @0x20bf2f00}
源文件gloop/util/coding/{coder.cc, bitcoding.cc}slice_builder/internal/{toroidal_route_cache.cc, topology_rotation_helper.cc}slice_builder/friends/direction.cc

1. 编解码器所在位置

该编解码器是缓存解码的叶子。解压驱动CompressedToroidalRouteCache blob 转成 ToroidalRouteCache proto;容器 遍历每个 RouteScheme 并按其 variant tag 分派。bit_encoded_path(type-2)variant 携带路由的最紧凑形式,即带长度前缀的位流,也是本文解包的对象。其他 variant(distancestatic_pathrandom_hop)由容器处理;本文 讨论位流和包裹它的旋转。

text
CompressedToroidalRouteCache  (on-disk, embedded blob)
        │  Decompress(...)               → route-cache-decompress.md

ToroidalRouteCache proto  +  per-shape vector<proto::Orientation> (the dedup VALUE → route-cache-dedup.md)
        │  CacheRead(proto, orients)     → toroidal-route-cache.md  (@0x20b5da20)
        │     helper = TopologyRotationHelper::Create(topo, orients)        ── §4
        │     for scheme in proto.routing_schemes:
        │        skey = helper.RotateId(scheme.src_chip_id)                  ── §4.2
        │        dkey = helper.RotateId(scheme.dest_chip_id)
        │        switch scheme.variant:
        │           2 bit_encoded:  hops = DecodePathFromBits(bytes)         ── §2  (THIS PAGE)

vector<proto::Direction>  (canonical frame; rotated keys index the actual frame)
```text

> **注意 —** 该编解码器在 **规范** 轴坐标系中产生跳;*坐标/id* 由 `RotateId`/`RotateCoordinates` 移到实际坐标系(§4)。重新实现者必须区分这两半:`DecodePathFromBits` 从不接触旋转,旋转也从不接触位流。

---

## 2. 解码 — `DecodePathFromBits`(@`0x20b5c5a0`)

`DecodePathFromBits(string_view bytes)` 是整个二进制中 `GetVarInt` 的唯一消费者(rel32 call-site 扫描:正好一个位置,位于 `0x20b5c5e2`)。它从 type-2 流重建 `vector<proto::Direction>`。反编译函数体有 584 行;结构如下:

```c
// ToroidalRouteCache::DecodePathFromBits @0x20b5c5a0  (decompiled, condensed)
StatusOr<vector<Direction>> DecodePathFromBits(string_view bytes) {
    BitDecoder bd(bytes);                       // LSB-first 64-bit window (§3.1)
    uint32_t count;
    if (!bd.GetVarInt(/*chunk=*/4, &count))     // line 102; esi=4 @0x20b5c5dd
        return Error("ICI routing cache does not encode path length correctly.");  // @0x9fd728f
    out.reserve(count);                         // line 114
    Direction prev{};
    for (uint32_t h = 0; h < count; ++h) {
        uint32_t field = bd.ReadBits(2);        // mask_[2]=0x3; buffer>>=2  (line 251/261)
        if (field == 3) {                       // REPEAT-PREVIOUS escape
            out.push_back(prev);                //   copy prev.orientation(+0x18)+polarity(+0x1c)
            continue;                            //   NO polarity bit consumed
        }
        uint32_t pol = bd.ReadBits(1);          // mask_[1]=0x1; buffer>>=1  (line 250/492)
        Direction d;
        d.orientation = field + 1;              // 0/1/2 → X(1)/Y(2)/Z(3)   (+0x18)
        d.polarity    = pol   + 1;              // 0/1   → POSITIVE(1)/NEGATIVE(2)  (+0x1c)
        d.presence   |= 0x3;                    // set has-bits for both fields (+0x10)
        out.push_back(d);                       // emplace_back, stride 0x20  (line 541)
        prev = d;
    }
    // TAIL VALIDATION (the encoder's flush contract)
    long remaining = bd.bits_avail + (bd.end - bd.cursor) * 8;   // lea [rcx+rdi*8] @0x20b5cb83
    if (remaining >= 8) return Error("...Remaining bits >= 1 byte.");           // @0xa0764b0
    if ((bd.buffer & mask_[remaining]) != 0)
        return Error("...Remaining bits are not cleared to zero.");             // @0xa044c1c
    return out;
}

2.1 流布局

字段宽度编码含义
hop_count可变GetVarInt(chunk=4)跳数(自定界;§3.2)
每跳:orient_field2 bitsmask_[2]{0,1,2} → 轴 X/Y/Z;3 → 重复前一跳转义
每跳:polarity1 bitmask_[1]仅当 orient_field<30→POSITIVE,1→NEGATIVE
尾部<8 bits零填充对齐到字节边界;必须全为零

关键 — 这个 2-bit 字段 只覆盖三条轴。值 3 从不是第 4 条轴(A);它是 重复前一跳 转义,会复用上一跳的 (orientation, polarity) 并且 消耗 polarity bit。这是该编解码器的 run-length 装置:沿一个方向的直线多跳会打包为一个显式 3-bit 跳,后面跟 N−1 个 2-bit 重复。反编译中已确认:0x20b5c697cmp field, 3,转义分支在 0x20b5c6aa..dd 复制 prev+0x18/prev+0x1c,显式分支在 0x20b5c708 读取 polarity bit。

2.2 proto::Direction 元素

每个解码出的跳都是一个 proto::Direction(大小 0x20,vtable @0x22019600):

偏移字段类型取值
+0x00vptr
+0x08arena / InternalMetadata
+0x10has-bits / presenceuint32|= 0x3(两个字段都存在)
+0x18orientationintX=1,Y=2,Z=3
+0x1cpolarityintPOSITIVE=1,NEGATIVE=2

在 bit-encoded 形式中,proto::Direction 内部 没有 hop_count 字段:一个元素正好是一跳。这不同于 CreateRoutePathFromDistance 使用的 运行时 packed code(|d|<<6 | polarity<<3 | orient&7),后者在一个条目里携带每条轴的跳数;两者都会解码成相同的 {orientation, polarity} 对,但缓存形式是一元素一跳,转义用于表示 run-length。

2.3 失败模式

失败原因诊断(.rodata
头读取失败varint 终止前流已耗尽"ICI routing cache does not encode path length correctly." @0x9fd728f
orientation 下溢读取 2-bit 字段时流在跳中间结束"ICI routing failed to retrieve %dth hop dimension from bit encoded cache data." @0xa0ba533
polarity 下溢读取 1-bit polarity 前流已结束"ICI routing failed to retrieve %dth hop polarity from bit encoded cache data." @0xa0ba4e5
尾部空余 ≥ 1 字节hop_count 太小 / 流损坏"ICI route's encoded schema is erroneous. Remaining bits >= 1 byte." @0xa0764b0
尾部位非零flush 未做零填充"ICI route's encoding schema is erroneous. Remaining bits are not cleared to zero." @0xa044c1c

易错点 — 尾部验证在 两个 方面都很严格。remaining = bits_avail + (end-cursor)*8 必须 < 8(不能剩整字节),并且残余的 <8 位必须正好为零。重新实现的编码器如果发出一个空余 bit,或用非零值填充,读取时都会被拒绝。这钉住了编码器的 Flush() 语义(§3.3)。


3. 基础工具集(gloop bitcoding.cc / coder.cc

该编解码器依赖一个小型共享 bit-stream 工具集。读取侧是 BitDecoder(四个方法);写入侧是内联的 BitEncoder,位于一个 gloop Encoder 字节缓冲区之上。不存在独立的 Put*/WriteBits 符号,所有写入基元都被内联;它们是从 BitEncoder::Initialize @0x21072d40(一个 gamma 表自测:用 PutGamma 编码 1..255,再用 GetGamma 解码并 CHECK 相等)以及使用同一工具集固定宽度编码的 profiler trace encoder 中按字节精确重建的。

3.1 LSB-first 64-bit 窗口与 mask_

BitDecoder 是一个覆盖字节流的 0x28 字节滑动窗口:

c
struct BitDecoder {            // 0x28 bytes
  /*+0x00*/ const char* base;  // string_view start (not touched by the refill ladder)
  /*+0x08*/ const char* cursor;// next byte to pull
  /*+0x10*/ const char* end;   // one-past-last byte
  /*+0x18*/ uint64_t    buffer;// LSB-first bit buffer; sentinel 0xFFFFFFFFFFFFFFFF == "empty/needs refill"
  /*+0x20*/ int32_t     bits_avail;
};
```text

一次 `ReadBits(n)` 的过程是:如果 `bits_avail < n`,就按 LSB-first 逐字节 refill buffer(一个 8 路展开的字节阶梯会把每个新字节左移到当前 bit 位置并 OR 进去,最多拉取 8 字节 / 64 位);然后 `out = buffer & mask_[n]; buffer >>= n; bits_avail -= n`。(反编译确认:`GetVarInt` 在 `*((_QWORD*)this+3)` = `+0x18` 读取 `buffer`,将其与 `-1` 比较,并在 `*((_DWORD*)this+8)` = `+0x20` 读取 `bits_avail`。)

`BitEncoder::mask_` @`0xbe79440`(.rodata,65 个 qword,`mask_[k]=(1<<k)-1`,索引 0..64)由读取器和写入器共享:读取器用它提取 `n` 位,写入器用它在 OR 进 accumulator 前将值截断到 `n` 位:

| k | `mask_[k]` | k | `mask_[k]` |
|---|---|---|---|
| 0 | `0x0` | 8 | `0xff` |
| 1 | `0x1` | 16 | `0xffff` |
| 2 | `0x3` | 24 | `0xffffff` |
| 3 | `0x7` | 32 | `0xffffffff` |
| 4 | `0xf` | 63 | `0x7fffffffffffffff` |
| 7 | `0x7f` | 64 | `0xffffffffffffffff` |

> **注意 —** 路由缓存的 per-hop 循环只读取 `mask_[1]` 和 `mask_[2]`(反编译第 250251 行:`v79 = mask_[1]; v6 = mask_[2]`),另在 `GetVarInt` 和尾部检查中读取一个可变 `mask_[n]`。完整表存在是因为同一个 `BitDecoder` 类还在其他地方服务更宽的 fixed-width 读取(§3.4)。

### 3.2 四个读取基元

| 方法 | 地址 | 编码 | 路由缓存用途 |
|---|---|---|---|
| `GetVarInt(chunk, *out)` | `0x20b5cfa0` | 自定界整数 | 跳数头,chunk=4(唯一用途) |
| `GetGamma(*out)` | `0x21073160` | Elias-gamma(值 ≥ 1| 路由缓存中无 |
| `GetBits64NoInline(n, *out)` | `0x21073760` | 固定 n-bit,n∈[0,64] | 路由缓存中无 |
| `SkipBitsNoInline(n)` | `0x21073580` | 前进 n bit,不读出 | 路由缓存中无 |

**`GetVarInt(chunk, *out)`** — 路由缓存使用的唯一 varint(始终 `chunk=4`)。`k` = buffer 的前导 1-bit 个数(LSB-first),通过 `not buffer; tzcnt` 找到(反编译:`_RAX = ~v11; tzcnt r8, rax`)。消耗这些 `k` 个 1 **以及** 终止 0(`k+1` 位);`num_groups = k+1`。随后读取 `num_groups` 个 `chunk` 位组;`value = sum_{g} group_g << (chunk*g)`。返回 bool(`al=1` ok / `al=0` 流耗尽)。成本 = `(k+1)·(1+chunk)` 位。示例(chunk=4):`v=12` → `00011`(前缀 `0`,组 `1100`);`v=255` → `1011111111`(前缀 `10`,组 `1111`,`1111`);`v=4096` → 20 位。

### `GetGamma(*out)`** — Elias gamma,值 ≥ 1。`b` = 前导 0-bit 个数(`not buffer; tzcnt`);若 `b > 0x1F` 则失败(反编译:`if ((unsigned)_RDX > 0x1F) ... return 0`);消耗 `b` 个 01 个终止符(`b+1` 位);`suffix = buffer & mask_[b]`;`value = (1<<b) + suffix`。码字长度 `2b+1`。示例:`v=1`→`1`;`v=2`→`010`;`v=8`→`0001000`;`v=255`→`000000011111111`(15 位)。**仅由 `BitEncoder::Initialize` 自测使用(1 个 call site,@`0x210730bd`),在此 build 中不位于任何路由缓存或实时运行路径上。

**`GetBits64NoInline(n, *out)`** — 固定 n-bit,`n∈[0,64]`(`n>64` → `ud1` trap)。`out = buffer & mask_[n]; buffer>>=n; bits_avail-=n`。如果 `n` 跨越 refill,则先取低 `bits_avail` 位,refill 后再 OR 进左移后的高 `(n-bits_avail)` 位。它是 profiler trace decoder 的主力(6423 个 call site,路由中无)。

**`SkipBitsNoInline(n)`** — 不读出地前进 `n` 位:如果 `n < bits_avail`,只需 `buffer>>=n; bits_avail-=n`;否则将 `cursor` 跳过 `(n-bits_avail)/8` 字节,并 refill `n%8` 的余数。578 个 call site,全部为 profiler-trace(跳过保留字段)。

### 3.3 写入侧(`Encoder` + `BitEncoder`)

字节缓冲区是 `gloop` 的 `Encoder`(coder.cc,0x20 字节):

```c
struct Encoder {               // 0x20 bytes
  /*+0x00*/ char* cursor;      // buf_  (next write position)
  /*+0x08*/ char* limit;       // limit_
  /*+0x10*/ char* begin;       // owned alloc base (freed in dtor)
  /*+0x18*/ char* alloc_end | flags;  // top bit set ⇒ owns heap; inline-SSO marked 0x8000000000000028
};
// ~Encoder @0x21073980:  CHECK buf_ <= limit_  ("buf_ <= limit_", overrun guard) → free(begin)

BitEncoder 包裹一个 Encoder、一个 uint64 accumulator 和一个 bit count。内联基元如下:

写入器行为对偶
PutBits(value, n)acc |= (value & mask_[n]) << bitpos; bitpos += n;bitpos>=8 时按 LSB-first 溢出整字节(mov [cursor],acc_lo8; inc cursor; acc>>=8; bitpos-=8GetBits64
PutVarInt(chunk, v)num_groups = max(1, ceil(bitlen(v)/chunk));发出 num_groups-1 个 one-bit 加一个 0,然后发出 num_groups 个 little-endian chunk-bit 组GetVarInt
PutGamma(v≥1)b = floor(log2(v));发出 b 个 0 加一个 1,再发出 (v & mask_[b])GetGamma
Flush()用 0 填充最后一个部分字节并溢出

PutBits 字节溢出循环已在真实 encoder 中按字节确认(EncodeUhiOciRequestRead @0xf5c6b20mov [r8],sil; inc r8; shr rsi,8; ecx-=8)。BitEncoder::Initialize @0x21072d40 在运行时构建 gamma_(@0x22593d00,.bss,256×uint32,gamma_[i]=i|((2·floor(log2 i)+1)<<24)),并证明 PutGamma/GetGamma 对 1..255 是精确逆过程。

注意 — gamma_.bss(运行时构建),mask_.rodata(烘焙)。重新实现路由缓存编解码器只需要 mask_gamma_ 属于路由从不执行的 gamma 路径。

3.4 两个消费者,一个工具集

同一个 BitDecoder/Encoder/BitEncoder 工具集服务两个无关子系统,且 布局不同

消费者使用的基元布局Call site
路由缓存(本文)GetVarInt/PutVarInt(chunk=4) + 固定 PutBits(2)/PutBits(1)varint 头 + 每跳 2b/1bGetVarInt:1(DecodePathFromBits @0x20b5c5e2
设备 trace profilerGetBits64/PutBits(固定宽度)+ SkipBitsfixed-width TraceHeader 字段GetBits64:6423;SkipBits:578;Encoder 析构函数:566

Profiler 的 fixed-width trace header 不在本文范围内(见 ProfilingVFC/VLC/GFC payloads)。与路由相关的事实是:没有任何 route-cache 字段使用 gamma 或 fixed-width GetBits64,主体只是 PutBits(2)+PutBits(1),并带一个 GetVarInt(chunk=4) 头。


4. 编码侧 — 推断的路径打包器

离线路由缓存生成器 存在于 libtpu.so 中(.binarypb.compressed blob 是预计算并嵌入的)。下面的打包器被重建为 DecodePathFromBits 的字节级精确逆过程,并在软件中与共享 BitEncoder 做往返;尾部契约(§2.3)钉住了它的 Flush() 语义:

c
// inverse of DecodePathFromBits — INFERRED-POLICY (escape preference reasoned from decoder)
string EncodePathToBits(const vector<proto::Direction>& hops) {
    BitEncoder enc;
    enc.PutVarInt(/*chunk=*/4, hops.size());          // self-delimiting hop-count header
    for (size_t h = 0; h < hops.size(); ++h) {
        if (h > 0 && hops[h].orientation == hops[h-1].orientation
                  && hops[h].polarity    == hops[h-1].polarity) {
            enc.PutBits(2, 3);                         // REPEAT-PREVIOUS escape (field==3)
        } else {
            enc.PutBits(2, hops[h].orientation - 1);   // X(1)/Y(2)/Z(3) → 0/1/2
            enc.PutBits(1, hops[h].polarity   - 1);    // POSITIVE(1)/NEGATIVE(2) → 0/1
        }
    }
    enc.Flush();                                       // zero-pad to byte boundary (tail contract)
    return enc.bytes();
}
```text

每跳预算:显式跳 3 位(2 orient + 1 polarity),同方向重复 2 位。头通过 `GetVarInt(chunk=4)` 占 `(k+15` 位。整个流通过零填充最后一个部分字节实现字节对齐。

> **置信度(推断)—** `{2-bit-repeat | 3-bit-explicit}` 字母表由解码器 *强制* 决定(field==3 专门是 repeat escape),所以任何合规 encoder 都被限制在其中。但生成器精确的 **tie-breaking**,即它是否总是在同方向重复时优先使用转义,或是否曾为了保持某种对齐而发出显式跳,并未做字节追踪,因为生成器二进制未发布。上面的“始终优先转义”策略是最紧凑的合法编码,也是最小重新实现应发出的编码;对精确生成器行为标为 **LOW**,对字节格式和 flush 契约标为 **HIGH**

---

## 5. 旋转 — `TopologyRotationHelper`(规范化)

`TopologyRotationHelper` 是消费去重 VALUE(`vector<proto::Orientation>`)的对象。它 **不是** 位流的一部分,而是应用到解码路径的 key 和坐标向量上的坐标系变换,使一个规范 blob 能服务一个形状的每种 X/Y/Z 轴 orientation。`CacheRead`(@`0x20b5da20`)为每个 proto 构建一次,并应用到每个 scheme。

### 5.1 `Create` — 构建两个坐标系(@`0x20bf2380`)

`Create(topo, orients)` 分配一个 0x28 字节 helper,其中持有 **两个** 基础 `Topology` 对象和 orientation 列表:

```c
struct TopologyRotationHelper {     // 0x28 bytes
  Topology* actual;     // +0x00  Topology(ORIGINAL dims, ORIGINAL wraps)  — running/physical frame
  Topology* canonical;  // +0x08  Topology(PERMUTED dims, PERMUTED wraps)  — baked-blob frame
  vector<proto::Orientation> orientations;  // +0x10 base / +0x18 size / +0x20 cap (the permutation)
};

// Create @0x20bf2380 (decompiled, condensed)
helper = (TopologyRotationHelper*) operator new(0x28);           // line 106
dims   = topo.GetDimensionSizes();        // vtable+0x50
pdims  = PermuteVector<int>(dims, orients);     // @0x20bf2900  (line 68)
wraps  = topo.GetDimensionWraps();        // vtable+0x60
pwraps = PermuteVector<bool>(wraps, orients);   // @0x20bf2c60  (line 91)
helper->actual    = new Topology(dims,  wraps);   // 0x58-byte Topology  (line 184)
helper->canonical = new Topology(pdims, pwraps);  // 0x58-byte Topology  (line 188)
helper->orientations = orients;                   // copy into +0x10..

(反编译确认:helper 使用 operator new(0x28),两个 operator new(0x58u) + Topology::Topology 构造函数,以及 PermuteVector<int>/PermuteVector<bool> 调用。)置换后的内部 topology 会把 orientation 列表同时应用到 dimswraps,因此规范坐标系中 GetId 使用的 radix 与 blob 的轴顺序匹配。

5.2 RotateId — 每个 key 的重映射(@0x20bf3020

每个缓存的 (src_chip_id, dest_chip_id) 在插入前都会重新生成 key:

c
int RotateId(int id) {
    Coordinates coord  = actual.GetCoordinate(id);    // vtable+0x88; id→coord, actual-frame radix
    Coordinates rcoord = RotateCoordinates(coord);    // @0x20bf2f00; gather coords by orientation
    return canonical.GetId(rcoord);                   // vtable+0x90 (call [...+144]); coord→id, canonical radix
}
```text

`GetCoordinate` 和 `GetId` 是基于每个坐标系维度大小的 row-major mixed-radix(`coord[k] = id mod dimsize[k]`,商继续传递;`GetCoordinate` @`0x20bf50e0` 是一个 `idiv [dims+k*4]` 链)。因此 `RotateId` 会把规范坐标系的线性芯片 id 转成轴置换后 topology 的实际坐标系芯片 id。(反编译确认了 `RotateCoordinates` 调用以及 offset `144` = `0x90` 处的 `canonical->GetId` vtable 调用。)

### 5.3 `RotateCoordinates` / `PermuteVector` — 纯轴 gather(@`0x20bf2f00`)

```c
Coordinates RotateCoordinates(const Coordinates& c) {
    vector<int> vals = c.GetCoordinates();
    vector<int> out  = PermuteVector<int>(vals, orientations);  // out[k] = vals[orientations[k]-1]
    return Coordinates(Span<int>(out));
}
// PermuteVector<T>(in, orients):  for k:  dim = OrientationToDimension(orients[k]);  out[k] = in[dim]

关键 — 旋转是坐标向量的 纯轴置换out[k] = in[orient[k]-1]没有逐轴符号翻转,也没有加性偏移;去重从不镜像或平移,只是重新标记哪条物理轴扮演规范轴 k 的角色。任何 seam/twist 几何都存在于缓存 blob 和实时距离度量中,而不是这里。

5.4 Orientation↔Dimension 双射

PermuteVector 通过 Direction::OrientationToDimension(@0x20c027c0)及其逆函数 DimensionToOrientation(@0x20c02700)把 orientation 映射到 dimension index:

proto::OrientationintOrientationToDimension
UNKNOWN0ERROR "Unknown orientation" @0x85edbbb
X10
Y21
Z32
A43
B54
C65

OrientationToDimension(o) = o - 1(反编译:*(_DWORD*)(a1+8) = *a2 - 1,带 UNKNOWN guard);DimensionToOrientation(d) = d + 1,其中 d∈[0,5],二者是精确逆函数。RotateOrientation(o)(@0x20bf3180)是单个轴标签的 置换:在 orientation 列表中用 wmemchro,返回 index+1,否则报错 "Rotation doesn't include dimension %s" @0x857efd8

注意 — OrientationToDimension 支持 6 条轴(X/Y/Z/A/B/C),但 bit-encoded path 的 2-bit 字段只能到 X/Y/Z(field∈{0,1,2})。slice-builder twisted 形状最多 3 条物理轴,因此 A/B/C 可由旋转 API 触达,但绝不会出现在烘焙的 twisted blob 中,这与 2-bit 字段宽度一致。

5.5 为什么这是 4× 去重收益

去重器 会用多个 orientation key {X,Y,Z,NONE} 注册每个形状;去重 VALUE 是 canonical→actual 的 vector<proto::Orientation> 映射。一个 烘焙 blob(按规范轴顺序写入)能够服务该形状的每种 X/Y/Z 旋转,因为 CacheRead 会通过 RotateId 重新生成每个 (src,dst) 的 key,并通过 RotateCoordinates 重新定向每个 distance coordinate,也就是只做轴置换。编解码器发出相同的规范跳序列;旋转把它落到正确的物理芯片上。


6. 往返摘要

text
ENCODE (offline generator; INFERRED inverse)      DECODE (DecodePathFromBits @0x20b5c5a0; CONFIRMED)
  enc = BitEncoder()                                dec = BitDecoder(bytes)
  enc.PutVarInt(4, n)        ── header ──           n = dec.GetVarInt(4)            // mask_[n], unary prefix
  for hop in hops:                                  out.reserve(n); for h in 0..n-1:
    if repeat: enc.PutBits(2, 3)  ── per hop ──       f = dec.ReadBits(2)            // mask_[2]
    else: enc.PutBits(2, o-1)                         if f==3: out += copy(prev)     // repeat escape
          enc.PutBits(1, p-1)                         else:    p = dec.ReadBits(1)   // mask_[1]
  enc.Flush()  (zero-pad)    ── tail ──                        out += Dir{o=f+1, p=p+1}; prev=out.back()
                                                    assert remaining < 8 && all-zero // tail contract

Shared primitives (LSB-first window, mask_[k]=(1<<k)-1 @0xbe79440):
  GetVarInt/PutVarInt : unary prefix + N·chunk-bit groups   (route cache, chunk=4 — 1 call site)
  GetGamma/PutGamma   : Elias gamma                          (BitEncoder::Initialize self-test only)
  GetBits64/PutBits   : fixed N-bit                          (profiler trace headers; route cache uses 2b/1b)
  SkipBits            : advance N bits                       (profiler trace only)

Then per scheme (toroidal-route-cache.md CacheRead):  skey = RotateId(src); dkey = RotateId(dst)
  RotateId(id)         = canonical.GetId( RotateCoordinates( actual.GetCoordinate(id) ) )
  RotateCoordinates(c) = Coordinates( PermuteVector<int>(c.vals, orients) )   // out[k]=in[orient[k]-1]
```text

---

## 7. 函数与数据映射

| 符号 | 地址 | 作用 |
|---|---|---|
| `ToroidalRouteCache::DecodePathFromBits` | `0x20b5c5a0` | type-2 位流解码器(584 行) |
| `BitDecoder::GetVarInt` | `0x20b5cfa0` | 自定界 varint 读取器(路由缓存中 chunk=4) |
| `BitDecoder::GetGamma` | `0x21073160` | Elias-gamma 读取器(仅 Initialize 自测) |
| `BitDecoder::GetBits64NoInline` | `0x21073760` | 固定 n-bit 读取器(profiler) |
| `BitDecoder::SkipBitsNoInline` | `0x21073580` | 前进 n bit(profiler) |
| `BitEncoder::Initialize` | `0x21072d40` | gamma 表构建器 + encode/decode 自测(写入器见证) |
| `Encoder::~Encoder` | `0x21073980` | 字节缓冲区析构函数(`CHECK buf_ <= limit_`) |
| `BitEncoder::mask_` | `0xbe79440` | 65-qword `(1<<k)-1` 表(.rodata) |
| `BitEncoder::gamma_` | `0x22593d00` | 256-uint32 运行时 gamma 表(.bss) |
| 推断的路径打包器 | — | `PutVarInt(4,n)`+`PutBits(2)/(1)`+`Flush` 逆过程 |
| `TopologyRotationHelper::Create` | `0x20bf2380` | 构建 actual+canonical Topology + orient 列表 |
| `TopologyRotationHelper::RotateId` | `0x20bf3020` | id→coord→permute→id 芯片 key 重映射 |
| `TopologyRotationHelper::RotateCoordinates` | `0x20bf2f00` | 坐标向量的轴 gather |
| `TopologyRotationHelper::RotateOrientation` | `0x20bf3180` | 逆轴标签置换(`wmemchr`) |
| `PermuteVector<int>` / `<bool>` | `0x20bf2900` / `0x20bf2c60` | `out[k]=in[orient[k]-1]` gather |
| `Direction::OrientationToDimension` | `0x20c027c0` | `o-1`(UNKNOWN → error) |
| `Direction::DimensionToOrientation` | `0x20c02700` | `d+1`,d∈[0,5] |
| `proto::Direction` ctor / vtable | `0x20c0ad60` / `0x22019600` | 0x20 字节跳元素(orient+0x18,polarity+0x1c) |

---

## 交叉引用

### 路由缓存 — 相邻文档(该编解码器的邻居)

- [Route-Cache 解压](route-cache-decompress.md) — `Decompress(CompressedToroidalRouteCache)`,为该编解码器供给输入的 inflate 步骤
- [Toroidal Route Cache](toroidal-route-cache.md) — 消息层级和 `CacheRead`,后者会按 scheme 调用 `DecodePathFromBits` 与 `RotateId`/`RotateCoordinates`
- [Route-Cache 去重](route-cache-dedup.md) — `RouteCacheDeduplicator::Find`、缓存键,以及该旋转消费的 `vector<proto::Orientation>` value

### 路由上下文

- [Routing — 章节地图](overview.md) — 缓存在路由流水线中的位置(family B,§2.2)
- [Route-Table 生成](route-table-generation.md) — 实时生成器(family A),它的离线对应物会产生该编解码器读取的烘焙 blob
- [Randomized Toroidal WildFirst](randomized-toroidal-wildfirst.md) — 路径算法,其输出被编码进缓存
- [Get Distances](get-distances.md) — `distance`(type-0)scheme variant 存储的 torus-reduced distance,是 bit-encoded path 的同级项

### 其他工具集消费者

- [Profiling — 概览](../profiling/overview.md) · [VFC/VLC/GFC payloads](../profiling/payload-vfc-vlc-gfc.md) — 设备 trace profiler,同一 `gloop` bit 工具集的 fixed-width `GetBits64`/`SkipBits` 消费者

- [返回索引](../index.md)