MemorySpace 枚举 (17)
本页中的所有地址、枚举整数和字符串偏移都从
libtpu-0.0.40-cp314wheel 中的libtpu.so按字节精确解码得到(BuildID md589edbbe81c5b328a958fe628a9f2207d)。其他版本会有所不同。
摘要
每个命名内存区域的 LLO 操作数都携带一个 xla::jellyfish::MemorySpace —— 一个小整数枚举,用来告诉分配器、DMA 发射器和 bundle 打包器某个缓冲区位于哪个物理(或虚拟)池中。它是 TensorCore 侧对应于 SparseCore 地址空间 ID(地址空间 ID)的概念:每个池一个紧凑整数,在缓冲区跨越层级边界的所有位置用作分派键。该枚举有 17 个具名值(0..16),规范解码器是一个单指令字符串表查找,即位于 0x1d6ffae0 的 xla::jellyfish::MemorySpaceToString(MemorySpace),它读取 off_21CE6B08[ms]。
这 17 个值覆盖五个物理 TensorCore 层级(hbm、vmem、smem、sflag、imem)、仅 Pufferfish 使用的第二暂存区(cmem)、主机接口层级(hib)、四个 BarnaCore 子空间(barna_core_{bmem,smem,sflag,imem})、两个 SparseCore sequencer 池(sparse_core_sequencer_{sflag,smem})、SparseCore 私有栈区域(sparse_core_private_stack_hbm)、主机和固定页主机池(host、pinned_hbm),以及值为 0 的“无内存空间”哨兵。它与 LLVM 层面的 SparseCore 地址空间 ID(地址空间 ID 中的 addrspace(N) 整数)以及尤其是 wire-format MemorySpaceProto 字段号都是不同的枚举。运行时 C++ 枚举和 proto 字段号在多个空间的整数分配上并不一致;把二者混为一谈的序列化器会错误标记每个缓冲区。这种差异正是本页记录的核心陷阱。
对于重新实现,契约包括 17 值运行时枚举(MemorySpaceToString 索引的整数,也就是 LLO 操作数和槽位编码携带的值)、它的 1:1 字符串表、proto↔enum 重映射、DMA-id 辅助函数用来拒绝不可寻址空间的掩码有效性门,以及它与 SparseCore AS-ID 空间和片上层级模型的关系。
| 运行时枚举 | xla::jellyfish::MemorySpace — 17 个具名值,0..16 |
| 解码器 | xla::jellyfish::MemorySpaceToString(MemorySpace) @ 0x1d6ffae0 (14 B) |
| 字符串表 | off_21CE6B08 — 8 字节指针数组,经 RELATIVE 重定位,按枚举整数索引 |
| 线格式 | xla.jellyfish.MemorySpaceProto descriptor @ VA 0xbf8cc80 (641 B),17 个值 |
| 操作数载体 | LloMemUnit / LloAddress — 每个内存 LLO 操作上的 (MemorySpace, offset) 对 |
| 标签宽度 | 硬件 bundle 字中的 3 位(多数槽位只寻址一个子集) |
| 兄弟 AS-ID 枚举 | mlir::sparse_core::MemorySpace — 22 个值,编号无关 |
| 置信度 | CONFIRMED,除非单元格另有标注 |
17 值枚举
MemorySpaceToString 是整数→区域映射的事实来源:它是一条 mov rax, [off_21CE6B08 + rax*8],因此枚举值是指针数组的直接索引,每个槽位上的 C 字符串就是规范的小写区域名。下面列出 17 个槽位(0..16)以及每个槽位解析到的字符串(跟随其 R_X86_64_RELATIVE 重定位之后);“区域 / 层级”列把名称映射到其背后的物理内存,该映射来自 内存层次结构 的层级模型以及 BarnaCore/SparseCore 子核心分类。
| Enum# | 字符串 (off_21CE6B08[n]) | 区域 / 层级 | 含义 |
|---|---|---|---|
| 0 | <no memory space> | —(哨兵) | 未设置 / 无效;默认构造值 |
| 1 | hbm | HBM(片外) | 设备全局后备存储:程序 I/O、spill、embeddings |
| 2 | hib | Host-Interface Buffer | HBM↔主机暂存层级,由 HIB DMA 引擎驱动 |
| 3 | vmem | VMEM(每个 TensorCore) | 向量工作集 — MXU/VPU 操作数经由它暂存 |
| 4 | cmem | CMEM(仅 Pufferfish/v4) | VMEM 之上的第二个大型芯片级暂存区 |
| 5 | smem | SMEM(每个 TensorCore) | 标量 / sequencer 暂存区:地址、循环边界 |
| 6 | sflag | SFLAG(每个 TensorCore) | 由 DMA 完成和屏障轮询的同步标志字 |
| 7 | imem | IMEM(每个 TensorCore) | sequencer 从中抓取 bundle 的指令内存 |
| 8 | barna_core_bmem | BarnaCore BMEM | BarnaCore(embedding 引擎)大容量暂存区 |
| 9 | barna_core_smem | BarnaCore SMEM | BarnaCore 标量暂存区 |
| 10 | barna_core_sflag | BarnaCore SFLAG | BarnaCore 同步标志层级(不同于 TC SFLAG) |
| 11 | barna_core_imem | BarnaCore IMEM | BarnaCore 指令内存 |
| 12 | sparse_core_sequencer_sflag | SparseCore sequencer SFLAG | SC-sequencer 同步标志 bank |
| 13 | host | Host DRAM | 驻留在主机的缓冲区(传输源/汇) |
| 14 | sparse_core_sequencer_smem | SparseCore sequencer SMEM | SC-sequencer 标量暂存区 |
| 15 | sparse_core_private_stack_hbm | SC private-stack HBM window | 从 HBM 划出的每 SC 私有栈 |
| 16 | pinned_hbm | Pinned HBM | 用于主机可见 DMA 的页固定 HBM 区域 |
陷阱 —
MemorySpaceToString表并不会停在索引 16。索引 17/18/19 分别解析为absolute(VA0x868144c)、heap_relative(0x8678cad)和stack_relative(0x8678cbb)。这些是附加到同一字符串数组的指针相对性标签,不是MemorySpaceProto的成员(后者正好有 17 个值,0..16— 见下文)。按字符串表长度确定MemorySpace枚举大小,或把absolute/heap_relative/stack_relative当成内存池的重新实现都是错误的:它们属于LloAddress重定位模型,而不是区域枚举。规范区域枚举有 17 个值;字符串表更长,是因为它与相对性标签共享存储。
为什么是 MemorySpace,而不是地址空间 ID
TensorCore LLO 不会像 SparseCore lowering 那样为内存操作数使用 LLVM addrspace(N) 整数。LLO 在由 LloMemUnit / LloAddress 携带的 (MemorySpace, offset) 对上运行;分配器为每个缓冲区恰好分配一个 MemorySpace,bundle 打包器把该空间编码为槽位中的小标签。两个枚举只在 SparseCore 边界相遇,在那里 sparse_core_sequencer_{sflag,smem}(枚举 12/14)和 sparse_core_private_stack_hbm(枚举 15)命名 SC 侧池,而 地址空间 ID 表也在自己的 mlir::sparse_core::MemorySpace 编号下覆盖这些池。编号彼此无关:SparseCore smem 是 AS-ID 派生的 MS 1,而这里的 LLO smem 是枚举 5。请把两个枚举保留在不同命名空间中。
MemorySpaceToString 解码器
该解码器是子系统中最小的非平凡函数,并精确固定了表布局:
const char *MemorySpaceToString(MemorySpace ms): // sub_1D6FFAE0, 14 bytes
// mov eax, edi ; zero-extend the enum int
// lea rcx, off_21CE6B08 ; base of the 8-byte pointer array
// mov rax, [rcx+rax*8] ; load the C-string pointer at index ms
return off_21CE6B08[ms]
```text
这里没有边界检查:枚举整数被当作原始数组索引使用。传入越界 `ms` 的调用者会读取相邻的 rodata 指针(`absolute`、`heap_relative`、…以及更后面的内容),因此可能持有不可信值的调用者会先做掩码处理 — `MemorySpaceToString((uint8_t)ms)` 是常见调用形态(在下面的 DMA-id 辅助函数中可逐字看到)。该函数恢复出的 64 个调用者证明 `MemorySpace` 是一等 LLO 操作数属性,而不是后端实现细节:它们包括 `DmaEmitter::Emit`、`net_router::AddressToPointer`、`LloAddress::ToString`、`LloInstruction::ToString`、`LloAllocation::ToString`、每个 `<gen>Target::DmaAddressGranule(MemorySpace)`,以及 `ProgramMemorySpaceSummary` 报告器。
### 字符串表来源
`off_21CE6B08` 位于可写映像中(第二个 `LOAD`,`0x215f25e0`+),每个槽位在加载时由指向 `.rodata` 的 `R_X86_64_RELATIVE` 重定位填充;在磁盘映像上这些槽位读作零。17 个区域字符串在 `.rodata` 中并不连续 — 每个都是更大拼接字符串 blob 的空终止前缀(例如 `0x861833c` 处的 `hbm` 位于 `for opcode: kDmaHostToHbm…` 之前),因此必须读到第一个 `NUL`,而不能用固定步长读取。
---
## 线格式 vs 运行时枚举 — 重映射陷阱
LLO 指令通过 `MemorySpaceProto`(`platforms/xla/service/jellyfish/proto/memory_space.proto`,descriptor 位于 VA `0xbf8cc80`)序列化。proto 枚举和运行时 C++ 枚举命名**相同的 17 个空间**,但给它们分配了**不同整数**。proto 字段号来自 `.proto` 中的声明顺序;C++ 枚举被重新排序,让连续的 TensorCore 层级(`hbm`、`hib`、`vmem`、`cmem`、`smem`、`sflag`、`imem`)位于 BarnaCore 块之前的 `1`..`7`,而 proto 用不同方式交错它们。
| 区域 | C++ enum# (`MemorySpaceToString`) | Proto# (`MemorySpaceProto`) |
|--------|----------------------------------:|----------------------------:|
| NONE / `<no memory space>` | 0 | 0 (`MEMORY_SPACE_NONE`) |
| `hbm` | 1 | 1 (`…_HBM`) |
| `hib` | **2** | **10** (`…_HIB`) |
| `vmem` | **3** | **2** (`…_VMEM`) |
| `cmem` | **4** | **11** (`…_CMEM`) |
| `smem` | **5** | **3** (`…_SMEM`) |
| `sflag` | **6** | **4** (`…_SFLAG`) |
| `imem` | **7** | **5** (`…_IMEM`) |
| `barna_core_bmem` | **8** | **6** (`…_BARNA_CORE_BMEM`) |
| `barna_core_smem` | **9** | **7** (`…_BARNA_CORE_SMEM`) |
| `barna_core_sflag` | **10** | **8** (`…_BARNA_CORE_SFLAG`) |
| `barna_core_imem` | **11** | **9** (`…_BARNA_CORE_IMEM`) |
| `sparse_core_sequencer_sflag` | 12 | 12 (`…_SPARSE_CORE_SEQUENCER_SFLAG`) |
| `host` | 13 | 13 (`…_HOST`) |
| `sparse_core_sequencer_smem` | 14 | 14 (`…_SPARSE_CORE_SEQUENCER_SMEM`) |
| `sparse_core_private_stack_hbm` | 15 | 15 (`…_SPARSE_CORE_PRIVATE_STACK_HBM`) |
| `pinned_hbm` | 16 | 16 (`…_PINNED_HBM`) |
> **陷阱 —** 两种编号在 `0`、`1` 和 `12`..`16` 上一致,但在 `2`..`11` 范围内分歧。`hib` 在 C++ 中是 `2`,但在 proto 中是 `10`;`vmem` 在 C++ 中是 `3`,但在 proto 中是 `2`;`cmem` 是 `4` 对 `11`。通过 `MemorySpaceProto` 往返 LLO 缓冲区的重新实现**必须**在(反)序列化边界重映射两个整数 — 把 protobuf 字段号当成运行时枚举值会静默地把 `vmem` 缓冲区错标成 `hib`、把 `cmem` 错标成 `vmem`,依此类推。必须应用该重映射的边界是 `LloOpcodeProto`(反)序列化器;编译器其余部分只在 C++ 枚举上运行。
>
> **注意 —** 正确的 `MemorySpaceProto` 区域枚举有 **17 个值**(`0`..`16`),与 proto descriptor 精确匹配。`0x21ce6b08` 处的 `MemorySpaceToString` 表超过 17,只是因为它与索引 17+ 的 `absolute`/`heap_relative`/`stack_relative` 相对性标签共享存储 — 这些尾随项不是区域值。
---
## 枚举在操作数和槽位中的使用方式
一条内存 LLO 指令(`kDmaHbmToVmem`、`kLoad`、`kStore`、36 个 DMA opcode)把其操作数携带为 `LloMemUnit` / `LloAddress` = `(MemorySpace, byte-offset)`。`MemorySpace` 选择:
1. **DMA opcode 族。** `OpcodeFromDmaMemorySpace(Target, MemorySpace src, MemorySpace dst, …)` 根据 `(src, dst)` 对选择 DMA opcode(例如 `kDmaHbmToVmem` vs `kDmaVmemToHbm`)— 这与 SparseCore lowering 在 AS-ID 上使用的 `(srcMS, dstMS)` 分派形态相同。
2. **地址粒度。** 每个 `<gen>Target::DmaAddressGranule(MemorySpace)` 返回按空间划分的 DMA 对齐(HBM 字、VMEM 512-B 字等),用于把字节偏移转换为硬件字地址。
3. **槽位内存空间标签。** bundle 打包器把 `MemorySpace` 作为 VLIW bundle 的内存槽位中的 3 位字段发出(见 [内存加载槽位](slot-memory-load.md) / [内存存储槽位](slot-memory-store.md))。三位无法编码 17 个值,因此每种槽位类型只能寻址其硬件端口可达的空间子集 — 下面的有效性门会强制执行该子集。
### DMA 可寻址子集(掩码有效性门)
两个独立的 DMA-id 辅助函数证明,在给定 target 上,17 个空间中只有一个*子集*可作为 DMA 端点到达,并且二者都基于**运行时枚举**编号进行门控:
```c
int DmaMemoryId(MemorySpace ms): // sub_1C4AC1C0, pufferfish::proto_utils
if (uint8_t)(ms - 1) > 7 || ((0xDD >> (ms - 1)) & 1) == 0:
LOG(FATAL) << "Unimplemented DMA." << ms // MemorySpaceToString(ms)
return dword_A2E7040[ms - 1] // local-mem-id lookup
int dma_utils::MemorySpaceToLocalMemId(MemorySpace ms): // sub_1D5AE120, pufferfish::dma_utils
if (uint8_t)(ms - 1) >= 0x0B || !bittest(0x5DD, ms - 1):
LOG(FATAL) << "Unhandled memory space " << ms
return byte_B53A8C8[ms - 1]DmaMemoryId接受ms-1 ∈ {0,2,3,4,6,7}(掩码0xDD = 0b1101_1101)→ 空间hbm(1)、vmem(3)、cmem(4)、smem(5)、imem(7)、barna_core_bmem(8)。MemorySpaceToLocalMemId接受ms-1 ∈ {0,2,3,4,6,7,8,10}(掩码0x5DD = 0b101_1101_1101)→ 同样六个,再加上剩余 BarnaCore 空间中的两个:barna_core_smem(9)和barna_core_imem(11)。注意位9在0x5DD中是清零的,因此barna_core_sflag(10)不被该辅助函数接受。
第三个辅助函数 ghostlite::GhostliteProtoUtils::MemorySpaceToLocalMemId(sub_1C5EF520)把空间折叠到 3 值 local-mem id:{hbm(1), vmem(3)} → 0、{hib(2), smem(5), sparse_core_sequencer_smem(14)} → 1、{imem(7)} → 2,其余全部 InvalidArgument。这里的 hib/vmem/smem 情况只有在运行时枚举(hib=2、vmem=3、smem=5)下才会与合理的 local-mem id 对齐,这是确认流经 emitter 的是枚举整数而非 proto 字段号的最干净证据。
注意 — 掩码按代际划分(上面的 Pufferfish
0xDD/0x5DD),因此可达子集会随硅片演进而增长。常量数组索引(dword_A2E7040[ms-1]、byte_B53A8C8[ms-1])基于ms-1,因为none哨兵(0)从不是有效 DMA 端点,所以辅助函数在索引密集 local-mem-id 表之前先减 1。
与 SparseCore AS-ID 空间的关系
LLO MemorySpace 枚举和 SparseCore mlir::sparse_core::MemorySpace 枚举(地址空间 ID)是两个独立、分别编号的枚举,它们命名重叠的物理内存。TensorCore 枚举(本页)是 xla::jellyfish LLO 携带的内容;SparseCore 枚举是 LowerToSparseCoreLlvm pass 从 LLVM addrspace(N) 整数派生的内容。它们只在 SparseCore-sequencer 和 SC-private-stack 池处相交:
| 物理池 | LLO MemorySpace(本页) | SparseCore AS-ID / MS(兄弟页面) |
|---|---|---|
| SC-sequencer SFLAG | sparse_core_sequencer_sflag = 12 | AS-band sflag pools (sflag/sflag_tile/sflag_scs), MS 5/14/20 |
| SC-sequencer SMEM | sparse_core_sequencer_smem = 14 | smem family, MS 1/16/21 (AS 0/219/224) |
| SC private-stack HBM | sparse_core_private_stack_hbm = 15 | hbm family, MS 4/10 (AS 203/213) |
怪癖 — 同一个词
smem在两个枚举中表示不同整数:在这里的 LLOMemorySpace中是5,在 SparseCore AS-ID 表中是 MS1(AS0)。跨子系统分析必须限定MemorySpace值来自哪个枚举。SparseCore 枚举是 1 基的,有一个值 8 空洞和 22 个值;这个 LLO 枚举是 0 基、无空洞、17 个值。它们不能通过算术转换 — 只有上面列出的具名池相对应,而且只按物理身份对应,不按整数对应。
相关组件
| 名称 | 关系 |
|---|---|
MemorySpaceToString @ 0x1d6ffae0 | 规范的 enum-int → 区域名解码器(off_21CE6B08[ms]) |
OpcodeFromDmaMemorySpace (jellyfish anon-ns) | 从 (srcMS, dstMS) 对选择 DMA opcode |
<gen>Target::DmaAddressGranule(MemorySpace) | 按空间划分的 DMA 对齐 / 字几何 |
DmaMemoryId @ 0x1c4ac1c0 / MemorySpaceToLocalMemId @ 0x1d5ae120 | 掩码有效性门 → 密集 local-mem-id,按代际划分 |
LloMemUnit / LloAddress | 每个内存 LLO 操作上的 (MemorySpace, offset) 操作数对 |
MemorySpaceProto @ VA 0xbf8cc80 | 序列化线枚举(17 个值,整数已重映射) |