Skip to content

TPUMCImm / SyImm32 操作数

本页中的每个偏移、值和地址均从 libtpu-0.0.40-cp314 wheel 中的 libtpu.so 逐字节读取(BuildID md5 89edbbe81c5b328a958fe628a9f2207d,未 strip,完整 C++ 符号)。所有地址都是虚拟地址;.text / .rodata / .lrodata 按 1:1 映射(VA == 文件偏移)。其他 wheel 版本会不同。

摘要

LLO 指令所需的常量(分支/调用目标、sync-flag id、tile-overlay sflag、标量字面量)会在 LLVM-MC 层中作为 TPUMCImmExpr 传递;它是 TPU 后端的目标特定 MCExprMCExpr::Kind == 5,即 Target kind)。它类似于 LLVM 的 MCSymbolRefExpr 加一个变体:它包装一个子表达式(通常是 MCConstantExpr),并用两个正交判别符标记它,一个是命名该值所属的重定位/变体族TPUMCImmKind,另一个是命名该值应按哪种操作数编码类OpEnc::OpEncodings)打包的 8 位 encoding-idSyImm32 是这个家族中最宽的标量成员:encoding-id 0x2c,完整 32 位标量立即数,也是在紧凑的零扩展/一扩展/移位扩展形式无法容纳时使用的兜底形式。

本页是 TPUMCImmExpr 对象和 TPUMCImmKind 枚举的规范参考。它涵盖 TPUMCImmExpr::create 写入的 56 字节对象布局、从打印器逐字节恢复出的枚举六个值(VK_TPU_none = 0embed = 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 位于 +0x08TPUMCImmKind 位于 +0x18getImmKind()),子表达式指针位于 +0x20,8 位 imm-base h1 位于 +0x28getImmBase()),8 位 encoding-id h2 位于 +0x29TPUMCImmType 位于 +0x2cMCContext* 位于 +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 0x1aSyImm32 = 0x2c
  • SyImm32 值由 ResourceSolver 打包canAddImmInternal 读取操作数的 OperandTypeImmediateCompatibilityTable 将其映射到 OpEnc 类,getPackedImm / getFullImmediate 分配 bundle 立即数槽并写入 encoding-id 0x2c;宽于一个槽的值会拆分为 lo/hi 并跨两个槽写入。
  • PatchOverlay 在分段后重写地址操作数:对每个 overlay,它计算 ConvertOffsetByteToWord(...),断言操作数 word 是 DMA 对齐的,并将相对于 overlay 的 word 写回 bundle 操作数;入口条件由 !trampolines_patched_encoded_word_offset < 0 约束。
MC-expr 类llvm::TPUMCImmExprMCExpr::Kind == 5getSubKind() == 1);vtable off_21934360
对象大小0x38 = 56 B,MCContext BumpPtrAllocatorcreate @ 0x13c784a0
Kind 字段+0x18 intgetImmKind());VK_TPU_none == 0
Encoding-id 字段+0x29 ucharOpEnc::OpEncodings);SyImm32 == 0x2c
SyImm32 编码器getFirstSyImm32Encoding @ 0x13c63a00return 0x2c
打包器入口ResourceSolver::canAddImmInternal @ 0x13bebce0getPackedImm @ 0x13bec4e0 / getFullImmediate @ 0x13be79a0
OperandTypeImmediateCompatibilityTable @ 0xaed36d0(17 × 12 B,{key=OperandType−13, mask, OpEnc class});key 4OperandType 17)→ OpEnc class 5,mask 0x0f
Overlay fixupOverlay::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 写入每个字段。反汇编精确确定了布局:

c
// 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作用
0VK_TPU_none(无;普通 MCConstantExpr普通立即数(分支/调用偏移)
1zext0x20getFirstSyZeroExtEncoding零扩展标量立即数
2oneext0x24getFirstSyOneExtEncoding一扩展标量立即数
3shl120x28getFirstSyShlEncoding左移标量立即数(×2¹²)
4shl16(移位变体)左移标量立即数(×2¹⁶)
5i32 (SyImm32)0x2cgetFirstSyImm32Encoding完整 32 位标量立即数
6embedgetSyEncodingsresource-allocated嵌入式/通用标量编码

kind 是重定位/变体判别符+0x29 处的 encoding-id 是操作数字段选择器。它们在整数立即数家族中协同变化(kind 5 ⇒ encoding 0x2c),但它们是不同字段,有不同消费者:打印器读取二者,发射器读取 kind 进行门控(如下),打包器读取 encoding-id 以选择槽位。

NOTE — kind 3 和 4 是两个不同的标量移位变体(shl12shl16),不是单个 shl kind。i32 imm(本页记录的 SyImm32)是 kind 5;kind 6 是 embedVy* 向量编码有自己的 encoding-id(例如 VyImm32 = 0x1a),与这个标量 TPUMCImmKind 枚举是不同轴。

VK_TPU_none 对分支/调用是强制的

分支或调用目标必须是普通立即数,即 VK_TPU_none。SparseCore EmitCallOp<…CallAbsolute> 模板(例如 GLC SCS @ 0x13a5d4c0)从操作数 0 恢复 TPUMCImmExpr 并对其门控:

c
// 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
函数 @ 地址说明
SyZeroExt0x13c639400x20标量零扩展
SyOneExt0x13c639800x24标量一扩展
SyShl0x13c639c00x28标量左移
SyImm320x13c63a000x2c完整 32 位标量立即数
VyImm320x13c639e00x1a − arg完整 32 位向量立即数
VyZeroExt / VyOneExt / VyShl0x13c63920 / 0x13c63960 / 0x13c639a00x08 / 0x0e / 0x14 − arg向量变体

SyImm32 = 0x2c 是四个标量整数立即数类中最宽的一个,即完整 32 位标量立即数;当 ZeroExt0x20)、OneExt0x24)和 Shl0x28)无法紧凑表示该值时,打包器会落到它。字节 0x2c 会原样存入 TPUMCImmExpr+0x29,并由打包器重新发射为槽位的 encoding-id。


SyImm32 如何进入 Bundle 位:ResourceSolver 流程

TPUMCImmExpr载体;实际放入 bundle 立即数槽的位置分配,是在 MachineInstr 层由 ResourceSolver 运行时完成的。链路分三步,全部以字节为锚点。

c
// 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 处的子表达式;MCValueRefKindout+0x18)从 this+8 处的字节设置。这是 TPUMCImmExpr,所以 this+8MCExpr::Kind 字段(常量 5,即 Target kind),汇编器的重定位逻辑正是用它在 fixup 解析期间识别 TPU target 表达式。

NOTE — MCValueRefKind 携带的是表达式类标签(this+8 处的 MCExpr::Kind,常量 5),不是 TPUMCImmKind 变体(this+0x18)。变体 kind 驱动编码选择和打印;重定位只依据表达式类标签。重新实现如果把 fixup 表接到 MCValue RefKind 上,必须使用 5,而不是立即数变体 kind。


Overlay PatchOverlay:编码后地址 Fixup {#overlay-patchoverlay--post-encode-address-fixup}

当 tensor 程序被分段成驻留在 HBM 的 overlay 时,指向现在位于不同 overlay 段中 bundle 的地址操作数必须被重写为段相对形式。Overlay::PatchOverlay0x1406a940)是按 overlay 执行的 fixup pass;每个 patch site 的工作在它的 $_2 lambda(0x1406c3e0)中完成。它消费的是 SyImm32 所走的同一套 bundle 内地址操作数机制,只是应用在编码之后,因此放在这里说明。

守卫和设置

c
// 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 +2640x108)的 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] = 1trampolines_patched_),如果需要 continuation delay slot,则运行 IsaEmitterFactory::Create continuation-tail 路径(overlay.cc:4794 的 "No place for the delay slot" 守卫)。

NOTE — PatchOverlay 只重写地址操作数;它不会改变它们的 encoding-id。被 patch 的地址操作数如果原本是 SyImm320x2c),仍保持为 SyImm32;只有它的值会被重新计算为相对于 overlay 的 DMA 单位 word 偏移。消费这些段的 overlay-fetch DMA descriptor,以及它的 overlay 预留 sflag(本身也是 SyImm32 立即数),记录在 Immediate Slot §EncodeOverlaysForDma