TPUMCImm / SyImm32 操作数
本页中的每个偏移、值和地址均从
libtpu-0.0.40-cp314wheel 中的libtpu.so逐字节读取(BuildID md589edbbe81c5b328a958fe628a9f2207d,未 strip,完整 C++ 符号)。所有地址都是虚拟地址;.text/.rodata/.lrodata按 1:1 映射(VA == 文件偏移)。其他 wheel 版本会不同。
摘要
LLO 指令所需的常量(分支/调用目标、sync-flag id、tile-overlay sflag、标量字面量)会在 LLVM-MC 层中作为 TPUMCImmExpr 传递;它是 TPU 后端的目标特定 MCExpr(MCExpr::Kind == 5,即 Target kind)。它类似于 LLVM 的 MCSymbolRefExpr 加一个变体:它包装一个子表达式(通常是 MCConstantExpr),并用两个正交判别符标记它,一个是命名该值所属的重定位/变体族的 TPUMCImmKind,另一个是命名该值应按哪种操作数编码类(OpEnc::OpEncodings)打包的 8 位 encoding-id。SyImm32 是这个家族中最宽的标量成员:encoding-id 0x2c,完整 32 位标量立即数,也是在紧凑的零扩展/一扩展/移位扩展形式无法容纳时使用的兜底形式。
本页是 TPUMCImmExpr 对象和 TPUMCImmKind 枚举的规范参考。它涵盖 TPUMCImmExpr::create 写入的 56 字节对象布局、从打印器逐字节恢复出的枚举六个值(VK_TPU_none = 0 到 embed = 6)、OpEnc::OpEncodings encoding-id 家族(SyImm32 = 0x2c 及其 +4 步进邻居),以及 SyImm32 如何通过 ResourceSolver 打包器进入实际 bundle 位。SyImm32 最终落到的槽位位置,即各代的 16/20 位立即数阶梯,位于 Immediate Slot;本页负责 MC-expr 载体,并链接到那里说明落点。
本页最后说明 overlay PatchOverlay 机制:这是按 overlay 运行的 pass,在程序分段后重写 bundle 内地址操作数,重新计算每个操作数相对于 overlay 的 word 偏移并写回。PatchOverlay 是编码后的 fixup,消费的是 SyImm32 所走的同一套地址操作数机制,因此它放在这里与立即数操作数并列说明。
对重新实现而言,契约如下:
TPUMCImmExpr是一个 56 字节(0x38)的MCExpr子类:MCExpr::Kind = 5位于+0x08,TPUMCImmKind位于+0x18(getImmKind()),子表达式指针位于+0x20,8 位 imm-baseh1位于+0x28(getImmBase()),8 位 encoding-idh2位于+0x29,TPUMCImmType位于+0x2c,MCContext*位于+0x30。TPUMCImmKind枚举是{0 VK_TPU_none, 1 zext, 2 oneext, 3 shl12, 4 shl16, 5 i32, 6 embed},这是从打印器按 kind 输出的字符串后缀恢复出的,而不是推断出的。VK_TPU_none = 0是分支/调用目标必须使用的无重定位普通立即数。- encoding-id(
+0x29)是来自getFirst<Class>Encoding常量函数的OpEnc::OpEncodings值:标量ZeroExt 0x20 / OneExt 0x24 / Shl 0x28 / Imm32 0x2c(+4步进),向量VyImm32 0x1a。SyImm32 = 0x2c。 SyImm32值由ResourceSolver打包:canAddImmInternal读取操作数的OperandType,ImmediateCompatibilityTable将其映射到OpEnc类,getPackedImm/getFullImmediate分配 bundle 立即数槽并写入 encoding-id0x2c;宽于一个槽的值会拆分为 lo/hi 并跨两个槽写入。- PatchOverlay 在分段后重写地址操作数:对每个 overlay,它计算
ConvertOffsetByteToWord(...),断言操作数 word 是 DMA 对齐的,并将相对于 overlay 的 word 写回 bundle 操作数;入口条件由!trampolines_patched_和encoded_word_offset < 0约束。
| MC-expr 类 | llvm::TPUMCImmExpr(MCExpr::Kind == 5,getSubKind() == 1);vtable off_21934360 |
| 对象大小 | 0x38 = 56 B,MCContext BumpPtrAllocator(create @ 0x13c784a0) |
| Kind 字段 | +0x18 int(getImmKind());VK_TPU_none == 0 |
| Encoding-id 字段 | +0x29 uchar(OpEnc::OpEncodings);SyImm32 == 0x2c |
SyImm32 编码器 | getFirstSyImm32Encoding @ 0x13c63a00(return 0x2c) |
| 打包器入口 | ResourceSolver::canAddImmInternal @ 0x13bebce0 → getPackedImm @ 0x13bec4e0 / getFullImmediate @ 0x13be79a0 |
OperandType 表 | ImmediateCompatibilityTable @ 0xaed36d0(17 × 12 B,{key=OperandType−13, mask, OpEnc class});key 4(OperandType 17)→ OpEnc class 5,mask 0x0f |
| Overlay fixup | Overlay::PatchOverlay @ 0x1406a940(+ 每个 patch lambda $_2 @ 0x1406c3e0) |
| 槽位位置 | 见 Immediate Slot(V5 前为 16 位,V5+ 为 20 位) |
| 置信度 | CONFIRMED(以字节为锚点),除非某行另有说明 |
TPUMCImmExpr 对象
TPUMCImmExpr::create(TPUMCImmKind, MCExpr*, uchar h1, uchar h2, MCContext&)(0x13c784a0)会在 MCContext arena 中 bump 分配一个 56 字节对象,并以字面量 store 写入每个字段。反汇编精确确定了布局:
// llvm::TPUMCImmExpr::create(kind a1, MCExpr* a2, uchar h1 a3, uchar h2 a4, MCContext& a5) @ 0x13c784a0
// result = MCContext::Allocate(56, align 8)
*(_QWORD *)(result + 0x00) = &off_21934360; // vtable (TPUMCImmExpr)
*(_QWORD *)(result + 0x08) = 5; // MCExpr::Kind = 5 (Target / custom)
*(_QWORD *)(result + 0x10) = 0; // (MCExpr base spare)
*(_DWORD *)(result + 0x18) = a1; // TPUMCImmKind <- getImmKind()
*(_QWORD *)(result + 0x20) = a2; // MCExpr* sub-expr (the value)
*(_BYTE *)(result + 0x28) = a3; // h1 imm-base <- getImmBase()
*(_BYTE *)(result + 0x29) = a4; // h2 encoding-id (OpEnc::OpEncodings)
*(_DWORD *)(result + 0x2c) = 0; // TPUMCImmType (kind-only overload writes 0)
*(_QWORD *)(result + 0x30) = a5; // MCContext*
```text
这些字段彼此独立。`+0x18` 处的 `TPUMCImmKind` 是*变体*;`+0x29` 处的 encoding-id 是*操作数编码类*;`+0x28` 处的 `h1`/imm-base 是按操作数执行的零检查(调用立即数必须满足 `getImmBase() == 0`)。三个访问器读取该对象:
| 访问器 | 地址 | 读取 | 结果 |
|---|---|---|---|
| `getSubKind` | `0x13c78da0` | (常量) | `1`,即标记这是 `TPUMCImmExpr` 的 `MCExpr::Kind==5` 子判别符 |
| `getImmKind` | (内联) | `+0x18` | `TPUMCImmKind` |
| `getImmBase` | (内联) | `+0x28` | `h1` 字节(分支/调用时 `== 0`) |
| `getBitWidth` | `0x13c78660` | `+0x2c` | `(TPUMCImmType < 2) ? 32 : 16` |
`getBitWidth` 反编译为 `16 * (this->type < 2) + 16`,即低于 `2` 的 `TPUMCImmType` 是 32 位立即数,否则是 16 位。五参数重载(`0x13c78580`)的字段完全相同,只是它把调用者给出的 `TPUMCImmType` 写到 `+0x2c`,而不是写 `0`。发射器通过 `GetTPUMCImmExpr`(`0x13a65900`)从 `MCInst` 操作数恢复 `TPUMCImmExpr`,它检查 `*(MCExpr) == 5 && getSubKind() == 1`,否则返回 *"Could not cast MCExpr to TPUMCImmExpr."*(`isa_emitter_base.cc:49`);包装的值由 `GetValueFromSubExpr`(`0x13a658e0`)按 `*(sub-expr + 0x10)`(`MCConstantExpr` 的 value word)读取。
> **NOTE —** 同族 target-MCExpr 共享 `+0x18` kind / `+0x28` h1 / `+0x29` h2 布局:`TPUMCFuncExpr::create`(`0x13c77ce0`,还携带 `TPUMCCoreKind`)、`TPUMCLinkageExpr::create`(`0x13c78dc0`,48 B)、`TPUMCFuncSizeExpr::create`(`0x13c780a0`,在 `+0x30` 增加一个 `long` size)、`TPUMCSectionSizeExpr::create`(`0x13c79140`)。重新实现可以把它们建模为同一个基类加各子类尾部。
---
## TPUMCImmKind 枚举
`TPUMCImmKind` 是 LLVM `VariantKind` 风格的枚举("`VK_TPU_*`" 家族),存储为 `+0x18` 处的 `int`。它的六个值是**从 `TPUMCImmExpr::printImpl`(`0x13c787a0`)逐字节恢复出的**;该函数的 `switch (this->kind)` 会在值之后打印人类可读名称后缀 `" (<name> imm encoding <id>)"`,因此 case 标签*就是* kind 值,字符串*就是*名称。只有在 `kind != 0` 时才打印 kind 后缀,这直接证明 `0` 是普通、无后缀的 `VK_TPU_none`:
```c
// TPUMCImmExpr::printImpl @ 0x13c787a0 (the kind-name switch, decompiled)
switch (*((_DWORD *)this + 6)) { // this+6*4 = +0x18 = the TPUMCImmKind
case 1: print("zext imm"); break; // SyZeroExt (encoding 0x20)
case 2: print("oneext imm"); break; // SyOneExt (encoding 0x24)
case 3: print("shl12 imm"); break; // Sy shift-by-12
case 4: print("shl16 imm"); break; // Sy shift-by-16
case 5: print("i32 imm"); break; // SyImm32 (encoding 0x2c) <- this page
case 6: print("embed"); break; // embedded / resource-allocated
}
// ... then print(" encoding "), then print(this->h2 /*+0x29 encoding-id*/)TPUMCImmKind | 名称(打印器) | encoding-id(+0x29) | 生成它的 getFirst…Encoding | 作用 |
|---|---|---|---|---|
| 0 | VK_TPU_none | (无;普通 MCConstantExpr) | — | 普通立即数(分支/调用偏移) |
| 1 | zext | 0x20 | getFirstSyZeroExtEncoding | 零扩展标量立即数 |
| 2 | oneext | 0x24 | getFirstSyOneExtEncoding | 一扩展标量立即数 |
| 3 | shl12 | 0x28 | getFirstSyShlEncoding | 左移标量立即数(×2¹²) |
| 4 | shl16 | — | (移位变体) | 左移标量立即数(×2¹⁶) |
| 5 | i32 (SyImm32) | 0x2c | getFirstSyImm32Encoding | 完整 32 位标量立即数 |
| 6 | embed | (getSyEncodings) | resource-allocated | 嵌入式/通用标量编码 |
kind 是重定位/变体判别符;+0x29 处的 encoding-id 是操作数字段选择器。它们在整数立即数家族中协同变化(kind 5 ⇒ encoding 0x2c),但它们是不同字段,有不同消费者:打印器读取二者,发射器读取 kind 进行门控(如下),打包器读取 encoding-id 以选择槽位。
NOTE — kind 3 和 4 是两个不同的标量移位变体(
shl12、shl16),不是单个shlkind。i32 imm(本页记录的SyImm32)是 kind 5;kind 6 是embed。Vy*向量编码有自己的 encoding-id(例如VyImm32 = 0x1a),与这个标量TPUMCImmKind枚举是不同轴。
VK_TPU_none 对分支/调用是强制的
分支或调用目标必须是普通立即数,即 VK_TPU_none。SparseCore EmitCallOp<…CallAbsolute> 模板(例如 GLC SCS @ 0x13a5d4c0)从操作数 0 恢复 TPUMCImmExpr 并对其门控:
// EmitCallOp<…, …_CallAbsolute> @ 0x13a5d4c0 (decompiled, exact)
if (*(_BYTE *)operand0_expr != 5) // not an MCExpr Target kind
RetCheckFail("slot_inst.getOperand(0).isExpr()"); // isa_emitter_base.h:1359
GetTPUMCImmExpr(&imm, operand0_expr+8); // cast (kind==5 && getSubKind()==1)
if (*((_DWORD *)imm + 6)) // imm->kind (+0x18) != 0
RetCheckFail("call_imm_expr->getImmKind() == "
"llvm::TPUMCImmKind::VK_TPU_none"); // isa_emitter_base.h:1362
if (*((_BYTE *)imm + 40)) // imm->h1 (+0x28) != 0
RetCheckFail("call_imm_expr->getImmBase() == 0"); // isa_emitter_base.h:1363
```text
`*((_DWORD *)imm + 6)` 读取的是 `imm + 0x18`,即 `getImmKind()` 字段;RetCheck 要求它为零。因此,调用/分支目标不携带重定位变体;带 encoding 标签的 kind(`zext`/`oneext`/`shl*`/`i32`/`embed`)由立即数打包和 overlay 路径构造,而不是由分支发射器构造。同样的断言出现在 VFC/GLC/GFC SparseCore bundle 类型的八个 `EmitCallOp` / `EmitBranchOp` 实例中。
---
## OpEnc::OpEncodings Encoding-Id 家族
存储在 `+0x29` 的 encoding-id 是 `getFirst<Class>Encoding(bool secondOperand)` 函数返回的 `OpEnc::OpEncodings` 值。标量(`Sy`)类是单指令常量返回,按从 `0x20` 开始的 **`+4` 步进**排列,已逐字节验证:
```c
char getFirstSyZeroExtEncoding(bool) { return 0x20; } // 0x13c63940
char getFirstSyOneExtEncoding (bool) { return 0x24; } // 0x13c63980
char getFirstSyShlEncoding (bool) { return 0x28; } // 0x13c639c0
char getFirstSyImm32Encoding (bool) { return 0x2c; } // 0x13c63a00 <- SyImm32| 类 | 函数 @ 地址 | 值 | 说明 |
|---|---|---|---|
| SyZeroExt | 0x13c63940 | 0x20 | 标量零扩展 |
| SyOneExt | 0x13c63980 | 0x24 | 标量一扩展 |
| SyShl | 0x13c639c0 | 0x28 | 标量左移 |
| SyImm32 | 0x13c63a00 | 0x2c | 完整 32 位标量立即数 |
| VyImm32 | 0x13c639e0 | 0x1a − arg | 完整 32 位向量立即数 |
| VyZeroExt / VyOneExt / VyShl | 0x13c63920 / 0x13c63960 / 0x13c639a0 | 0x08 / 0x0e / 0x14 − arg | 向量变体 |
SyImm32 = 0x2c 是四个标量整数立即数类中最宽的一个,即完整 32 位标量立即数;当 ZeroExt(0x20)、OneExt(0x24)和 Shl(0x28)无法紧凑表示该值时,打包器会落到它。字节 0x2c 会原样存入 TPUMCImmExpr+0x29,并由打包器重新发射为槽位的 encoding-id。
SyImm32 如何进入 Bundle 位:ResourceSolver 流程
TPUMCImmExpr 是载体;实际放入 bundle 立即数槽的位置分配,是在 MachineInstr 层由 ResourceSolver 运行时完成的。链路分三步,全部以字节为锚点。
// ResourceSolver::canAddImmInternal @ 0x13bebce0
opnd_type = MCInstrDesc.operand[opno].optype_byte; // TPUOp::OperandType (record byte +3)
rec = getOperandTypeRecord(opnd_type); // 0x13c63b80: key = opnd_type-13, binary search
// getSpecialOpEncoding(MCInstrDesc&, opno) // 0x13c63a80
opclass = rec.openc_class; // ImmediateCompatibilityTable col 3 (OpEnc class)
// then dispatch to getPackedImm (auto-select) or getFullImmediate (class forced)
```text
1. **`canAddImmInternal`(`0x13bebce0`)** 从每 opcode 的 `MCInstrDesc` 操作数记录中读取操作数的 `TPUOp::OperandType`(operand-type 字节),并通过 **`ImmediateCompatibilityTable`**(`0xaed36d0`,17 条 × 12 B,`{key u32, compat_mask u32, OpEnc class u32}`)经 `getOperandTypeRecord`(`0x13c63b80`)映射。`getOperandTypeRecord` 先从 `OperandType` 减去 13,然后按该 key 二分查找,所以表的 `key` 列是 `OperandType − 13`。标量立即数行的 key 是 `4`(即原始 `OperandType 17`):`→ OpEnc class 5`(标量整数立即数选择器),兼容 mask 为 `0x0f`。
> **NOTE —** 这里的 `4` 是表 *key*,不是原始 `OperandType`:`getOperandTypeRecord` 和 `getSpecialOpEncoding` 都会在搜索前计算 `key = OperandType − 13`(`0x13c63b80` 处以字节为锚点的 `v1 = (uchar)(a1 - 13)`),因此标量立即数行的原始 `OperandType` 是 `17`。`0x0f` 是该行的兼容性位掩码;把它的四个置位位映射到 `ZeroExt`/`OneExt`/`Shl`/`Imm32` 类是合理的,但 UNVERIFIED(这些位不是 encoding-id `0x20/0x24/0x28/0x2c`)。
2. **`getPackedImm`(`0x13bec4e0`)** 自动选择最紧凑的编码:基于 `OpEnc` class 的跳转表将 class `5`(标量)/`4`(向量)路由到整数选择器,后者测试该值有多少高位非零(每种编码宽度是一个 subtarget vtable 槽),并选择 `ZeroExt`/`OneExt`/`Shl`;对于需要完整 32 位的值,落到 **`Imm32 = 0x2c`**。选出的 id 写入 `ImmediateEncoding+0x5`。
3. **`getFullImmediate`(`0x13be79a0`)** 是强制 class 路径:它断言 class 属于 `{4,5,6}`,对 class 4 强制 `VyImm32 0x1a`,否则强制 **`SyImm32 0x2c`**,然后从每程序槽位池分配 bundle 立即数槽;宽于一个槽的值会拆分成低半和高半,跨两个槽放置。
`SyImm32` 会落到的槽位*位置*,即 20 位 V5+ 阶梯(`TC 430/410/…`,`SCS 67/47/27/7`)以及 V5 前的 16 位池,在 [Immediate Slot](slot-immediate.md#how-a-value-is-chosen-and-placed--the-resourcesolver-walk) 中完整记录;该页负责 `ResourceSolver` 池模型。交接关系是:**`OperandType 17`(表 key `4`)→ `OpEnc` class 5 → encoding-id `0x2c` → 一个空闲立即数槽 → 每代 `<gen>ImmediatesEncoder::Encode` 的位位置。**
> **GOTCHA —** SparseCore tile-overlay 例程(`overlayer::OverlayProgram` @ `0x1395bba0`)会*绕过* `ResourceSolver` 并手工构造一个 `SyImm32`:它对 tile-overlay sflag 执行 `movslq`,用 `MCConstantExpr::create` 创建它,调用 `getFirstSyImm32Encoding()`(= `0x2c`),再调用 `TPUMCImmExpr::create(kind=5, expr, h1=0, h2=0x2c, ctx)`,然后将其作为 `SLOT_S1` 中 operand-kind-5 的 `MCInst` 操作数追加。重新实现必须支持到同一个 `0x2c` encoding-id 的两条路径:自动分配的 MachineInstr 路径和直接构造的 MCInst 路径。见下文 [Overlay PatchOverlay](#overlay-patchoverlay--post-encode-address-fixup)。
---
## evaluateAsRelocatableImpl:重定位路径
`TPUMCImmExpr::evaluateAsRelocatableImpl`(`0x13c78d20`)是汇编器调用来把表达式折叠为 `MCValue` 的 `MCExpr` override。它委托子表达式的 `evaluateAsRelocatable`,成功后给 `MCValue` 的 `RefKind` 打标:
```c
// TPUMCImmExpr::evaluateAsRelocatableImpl(this a1, MCValue& a2, MCAssembler* a3) @ 0x13c78d20
bool ok = MCExpr::evaluateAsRelocatable(*(MCExpr**)(this + 0x20), &out, asm); // fold the sub-expr
if (ok)
*(_DWORD *)(out + 0x18) = *(unsigned __int8 *)(this + 8); // MCValue.RefKind <- this->Kind byte
return ok;折叠值来自 +0x20 处的子表达式;MCValue 的 RefKind(out+0x18)从 this+8 处的字节设置。这是 TPUMCImmExpr,所以 this+8 是 MCExpr::Kind 字段(常量 5,即 Target kind),汇编器的重定位逻辑正是用它在 fixup 解析期间识别 TPU target 表达式。
NOTE —
MCValue的RefKind携带的是表达式类标签(this+8处的MCExpr::Kind,常量5),不是TPUMCImmKind变体(this+0x18)。变体 kind 驱动编码选择和打印;重定位只依据表达式类标签。重新实现如果把 fixup 表接到MCValueRefKind上,必须使用5,而不是立即数变体 kind。
Overlay PatchOverlay:编码后地址 Fixup {#overlay-patchoverlay--post-encode-address-fixup}
当 tensor 程序被分段成驻留在 HBM 的 overlay 时,指向现在位于不同 overlay 段中 bundle 的地址操作数必须被重写为段相对形式。Overlay::PatchOverlay(0x1406a940)是按 overlay 执行的 fixup pass;每个 patch site 的工作在它的 $_2 lambda(0x1406c3e0)中完成。它消费的是 SyImm32 所走的同一套 bundle 内地址操作数机制,只是应用在编码之后,因此放在这里说明。
守卫和设置
// Overlay::PatchOverlay(BuildContext& ctx, LloAddress addr, uchar mem_space) @ 0x1406a940
if (ctx[+136] == 1) // !trampolines_patched_
RetCheckFail("!trampolines_patched_"); // overlay.cc:4635
if (ctx[+264] >= 0) // encoding_info().encoded_word_offset
RetCheckFail("encoding_info().encoded_word_offset < 0"); // overlay.cc:4649 (must be unset)
if (addr < 0) LogFatal("has_offset()"); // llo_address.h:56
// compute the overlay's own encoded word offset and stash it:
ctx[+264] = address_util::ConvertOffsetByteToWord(mem_space,
program[overlay].entry_byte(+248) + addr, target); // -> encoded_word_offset
```text
该 pass 拒绝运行两次(`!trampolines_patched_`),也拒绝重新编码已编码的 overlay(入口必须满足 `encoded_word_offset < 0`)。随后它通过 `address_util::ConvertOffsetByteToWord` 计算该 overlay 自身的 word 偏移,并存入 `ctx+264`(= `0x108`,即 `encoded_word_offset`)。程序范围通过 `GetIsaProgramUtil` / `IsaProgram::IsaProgramsCase` 和 `BundleCountInProgram` 读取;该 pass **只** patch TENSOR `IsaProgram`(function-overlay 路径断言的替代条件是 `callee_overlay->kind() == Kind::kHloFunction`)。
### Patch Sites
PatchOverlay 扫描程序 bundle,并按地址操作数通过 patch kind(操作数的 `[+48]` 字节)上的 `switch` 分类,将其插入每个 overlay 的 `absl::flat_hash_set<long>` patch-site 集合(函数主体中占主导的是 SIMD `crc32` / `vpcmpeqb` hash-set insert)。这些 kind 为:
| patch kind | 覆盖范围 | miss 时的 RetCheck |
|---|---|---|
| 0 / 3 | 直接的 overlay 内 bundle 地址 | (操作数存在)overlay.cc:4709 |
| 1 / 2 | 跨 overlay 目标(`target_overlay_number`) | `t.target_overlay_number.has_value()` (4694) |
| 4 | 非目标 size patch | `!t.target_overlay_number.has_value()` (4674) |
| 5 | HLO-function overlay(`kind() == kHloFunction`,4681) | `kind() == Kind::kHloFunction` |
### 单个 Site 的重写
`$_2` lambda 对一个 `PatchData` 执行实际操作数写入:
```c
// Overlay::PatchOverlay(...)::$_2::operator()(PatchData& patch) @ 0x1406c3e0
if (patch.overlay_number >= ctx.overlays.size()) // overlay.cc:4740
RetCheckFail("patch.overlay_number < context.overlays.size()");
// kind 0 (kAddress): recompute the overlay-relative word offset
word = address_util::ConvertOffsetByteToWord(mem_space,
program[overlay].entry(+248) + patch.offset, target);
unit = target.SharedMemoryToImemDmaUnitWords(); // vtable[+360] = vtable[0x168]
if (word % unit != 0) // overlay.cc:4765
LogFatal("value % target_.SharedMemoryToImemDmaUnitWords() == 0");
bundle_setter(patch.operand_index, word / unit); // [setter +264] writes the operand
// kind 1 (kSize): assert pack.GetEntryOverlay() == patch.overlay_number, then patch size对于地址 patch(kind 0),lambda 重新计算 ConvertOffsetByteToWord,断言结果是 SharedMemoryToImemDmaUnitWords() 的整数倍(DMA 粒度,对应 Target vtable 槽 +360 = 0x168),再除以它,把偏移表达为 DMA 单位,并通过 vtable +264(0x108)的 setter 写入 bundle 操作数。对于 size patch(kind 1 / kSize),它在写入前断言 pack.GetEntryOverlay() == patch.overlay_number(否则为 "Attempt to patch the size of packed non-entry HLO function",overlay.cc:4757)。所有 bundle patch 完成后,PatchOverlay 设置 ctx[+136] = 1(trampolines_patched_),如果需要 continuation delay slot,则运行 IsaEmitterFactory::Create continuation-tail 路径(overlay.cc:4794 的 "No place for the delay slot" 守卫)。
NOTE — PatchOverlay 只重写地址操作数;它不会改变它们的 encoding-id。被 patch 的地址操作数如果原本是
SyImm32(0x2c),仍保持为SyImm32;只有它的值会被重新计算为相对于 overlay 的 DMA 单位 word 偏移。消费这些段的 overlay-fetch DMA descriptor,以及它的 overlay 预留 sflag(本身也是SyImm32立即数),记录在 Immediate Slot §EncodeOverlaysForDma。