Skip to content

转换模式重写器

此页面上的所有地址、符号和偏移量均适用于 libtpu-0.0.40-cp314 轮中的 libtpu.so(构建 ID 89edbbe81c5b328a958fe628a9f2207d,781,691,048 字节,未剥离,.text VA == 文件偏移量)。其他版本会有所不同;将每个 VA 视为版本固定。

摘要

方言转换合法化器 推测性地尝试模式:它选择以操作为根的深度最便宜的模式,应用它,然后递归该模式生成的任何内容 - 如果任何生成的操作本身无法合法化,它必须撤消该模式所做的一切并尝试下一个候选者。在 MLIR 中,撤消并不是免费的:运行 replaceOp、插入三个操作、移动第四个操作并突变第五个属性的模式已经在五个位置触及了 IR。使这种情况可逆是 mlir::detail::ConversionPatternRewriterImpl 的工作,ConversionPatternRewriter& 是 TPU 降低模式接收的每个 ConversionPatternRewriter& 背后的专用引擎。此页面拥有该引擎:重写日志(撤消记录堆栈)、展开它的回滚1:N 结果替换路径(一个 tpu 操作被多个 sparse_core 值替换),以及桥接尚未转换的类型的未解析的转换物化

重新实现者需要的重构是这样的:ConversionPatternRewriter 不是 RewriterBase 就地改变 IR。它是一个录音重写器。模式执行的每个突变(插入、替换、移动、就地修改、块拼接)都会被 Impl 的侦听器接口拦截,并作为一条 IRRewrite 记录附加到 Impl+0x48 处的 SmallVector<unique_ptr<IRRewrite>>。 IR 发生了变化(因此递归会看到新的操作),但每个更改也会被记录并保存足够的状态以反转它。合法化失败时,日志将向后重播(undoRewrites,最新的在前);成功后,它将向前重播两次(applyRewrites:全部提交,然后全部擦除)。值替换记录不会在记录时重新连接 SSA 使用 - 它们记录意图*并推迟真正的 replaceAllUsesWith 提交,这使得 1:N 替换和铸造物化变得干净。

保存上游 MLIR DialectConversion.cpp 帧:ConversionPatternRewriterImplIRRewrite 类层次结构、RewriterStateundoRewrites/resetState/applyRewritesbuildUnresolvedMaterializationfindOrBuildReplacementValue。此页面是该帧的字节级恢复,因为它在 libtpu.so 中提供,具有 11 条记录层次结构、5 条虚拟记录 ABI 和 0x1e0 字节 Impl 布局,所有这些都固定到重定位和读取它们的三个驱动程序函数。当恢复的行为很微妙时——延迟的两遍提交、LIFO/FIFO 不对称、创建记录和移动记录之间的 operator new 大小分割——它就会被调用。

重新实现,合约为:

  • 重写日志。 SmallVector<unique_ptr<IRRewrite>, 6> 位于 Impl+0x48;抽象IRRewrite基础下的11种具体记录类型;每个记录都带有一个 {vptr, tag, Impl*, target} 标头以及特定于叶的保存状态,并由重写器的 listener 方法附加,而不是由模式作者附加。
  • 记录ABI。 5槽vtable {D1 dtor, D0 dtor, rollback, commit, cleanup};回滚引擎纯粹通过 [vtable+0x10/0x18/0x20] 进行调度,并且从不向下转型。
  • 回滚。 undoRewrites(numToKeep) 以最新的方式弹出日志,在每个日志上调用 rollback() — 因此每个撤消都会看到与记录推送后完全相同的 IR。 resetState(checkpoint) 在顶部分层忽略操作和替换操作簿记倒带。
  • 提交 + 1:N + 物化。 applyRewrites 分两遍重播(提交,然后清理-擦除)。 ReplaceOperationRewrite::commit 对每个结果调用 findOrBuildReplacementValue — 当结果的映射替换仍然具有错误类型时,会插入 builtin.unrealized_conversion_cast(未解析的实现)并记录它。 1:N 替换路径(replaceOpSmallVector<SmallVector<Value>>)通过 ConversionValueMapping 将每个原始结果映射到替换的向量
发动机mlir::detail::ConversionPatternRewriterImpl(MLIR 的 DialectConversion.cpp 专用)
实施尺寸0x1e0 字节(堆 operator new(0x1e0));由 ConversionPatternRewriter ctor 0x1c9512a0 构建;存储在外部this+0x10this+0x28
重写日志SmallVector<unique_ptr<(anon)::IRRewrite>,6> at Impl+0x48(数据)/ +0x50(大小,u32)/ +0x54(上限,u32;ctor inits cap=6)
记录层次结构11 混凝土 (anon)::IRRewrite 叶子;抽象基IRRewrite/BlockRewrite/OperationRewrite没有自己的vtable
记录ABI5槽vtable:+0x00 D1 dtor·+0x08 D0 dtor·+0x10回滚·+0x18提交·+0x20清理
回滚undoRewrites(unsigned, StringRef)0x1c94d060(后进先出,调用 [vtable+0x10])
检查点回滚resetState((anon)::RewriterState, StringRef)0x1c95bf60
提交applyRewrites()0x1c94c1c0(正向,两次传递:[vtable+0x18],然后 [vtable+0x20])
日志附加来源notifyOperationInserted0x1c950260replaceOp (1:N) — 0x1c950540replaceValueUses (1:1) — 0x1c94f740
物化buildUnresolvedMaterialization0x1c94dcc0findOrBuildReplacementValue0x1c94fde0
信心已确认(字节锚定)日志布局、记录 ABI、回滚/提交驱动程序、1:N 替换路径和物化;高/低,一行如此说明

录音重写器

用途

在日志之前,修复记录重写器存在的“原因”。合法化者的整个策略是“尝试一种模式,递归,失败时撤消”。只有在 IR 发生突变后可以撤消时,这才有效——这意味着每个突变都必须是可逆的。 ConversionPatternRewriterImpl 通过在 RewriterBase::Listener 接口上进行插入来使突变可逆:该模式认为它正在调用普通重写器,但每个调用都应用于实时 IR记录作为撤消条目。

的插入

ConversionPatternmatchAndRewrite 接收 ConversionPatternRewriter&。像 rewriter.create<...>()rewriter.replaceOp(op, vals)rewriter.eraseOp(op)rewriter.modifyOpInPlace(...) 这样的调用会通过 RewriterBase 路由,其侦听器是 Impl。每个侦听器回调执行两件事:

  1. 改变实时 IR,以便递归合法化器看到新状态(生成的操作必须存在,legalize 才能递归到它们)。
  2. 将一条 IRRewrite 记录附加到 Impl+0x48 的日志中,捕获其 rollback() 所需的任何已保存状态。
text
pattern code                Impl listener (0x1c95xxxx)          rewrite-log @Impl+0x48
------------                --------------------------          ----------------------
rewriter.create<MloOp>() →  notifyOperationInserted   0x1c950260 →  push CreateOperationRewrite
rewriter.replaceOp(...)  →  replaceOp / replaceValueUses 0x1c950540 →  push ReplaceOperationRewrite
                                                       /0x1c94f740 →  (or ReplaceValueRewrite)
rewriter.modifyOpInPlace →  (start/finalize modify)             →  push ModifyOperationRewrite
move into block          →  notifyOperationInserted w/ parent   →  push MoveOperationRewrite
type-cast bridge         →  buildUnresolvedMaterialization 0x1c94dcc0 →  push UnresolvedMaterializationRewrite
block splice / signature →  inlineBlockBefore / convertRegionTypes →  push *BlockRewrite
```text

> **注意 - 模式作者从未接触过日志。** 追加路径 *是* 侦听器接口;重新实现者将记录连接到 `RewriterBase::Listener` 回调中,而不是连接到模式 API 中。针对普通 `OpBuilder` 编写的模式会改变 IR,但不会产生撤消记录,并且合法化者的回滚不会默默执行任何操作 - 这是此页面上最危险的重新实现陷阱。
>
> **GOTCHA — 录音由转换模式控制。** 每个侦听器都会检查 `ConversionConfig+0x29` 处的一个字节(读作 `[Impl+0x178][+0x29]`,`notifyOperationInserted`/`replaceOp` 中 `*(_BYTE*)(v7+41)` 处的字段;`Impl+0x178` 是`ConversionConfig*` 由 ctor 存储作为其第二个参数)。当它是`1`(启用回滚的转换)时,侦听器采用*记录*路径(分配记录,追加到日志)。否则,它将直接转发到真正的侦听器并跳过日志。因此,同一个 Impl 根据驱动程序的模式充当记录重写器或传递 — 重新实现者必须线程化该标志,否则回滚将不会记录任何内容。

---

## 重写日志结构

### 用途

日志是中心数据结构。该单元固定其容器、其元素类型以及其内部的 0x1e0 字节 Impl,因为每个驱动程序函数都直接索引这些偏移量。

### Impl 布局(0x1e0 字节)

Impl 是一个单堆对象(`operator new(0x1e0)` 位于 ctor `0x1c9512da` 处)。外部`ConversionPatternRewriter`将指针存储在`this+0x10`和`this+0x28`处。字段映射,从 ctor 初始化 (`0x1c9512da..0x1c95143d`) 恢复,并根据四个驱动程序读取的偏移量进行交叉检查:

```text
ConversionPatternRewriterImpl  (0x1e0 bytes)
  +0x00 .. 0x40   OpBuilder / RewriterBase base (vptr, insertion point, listener)
  +0x48           rewrite-log: data ptr        } SmallVector<unique_ptr<IRRewrite>,6>
  +0x50           rewrite-log: size  (u32)      }   THE ACTION LOG  (drivers read [+0x50])
  +0x54           rewrite-log: capacity (u32)
  +0x58           rewrite-log: inline buffer
  +0x88 .. 0xa8   ignoredOps  : SetVector<Operation*>   (live count at +0xa8)
  +0xc0/+0xc8/+0xd0  replacedOps : DenseMap<Operation*> (count at +0xd0)
  +0xd8 .. 0xf8   newlyCreatedOps SetVector  (the "produced new ops" set; count +0xe8)
  +0x100 .. 0x120 rootReplacedOps SetVector  (the "produced replaced-root ops" set)
  +0x128 .. 0x150 trackedUnrealizedCasts / appliedRecursivePatterns guard
  +0x140/+0x148/+0x150 unresolvedMaterializations : DenseMap<Operation*>  (cast tracking)
  +0x178          ConversionConfig*  (ctor arg 2; mode byte at [+0x178]+0x29)
  +0x180          OperationConverter*  backref (ctor arg 3; owns this)
  +0x1c0          MLIRContext*
  +0x1c8          context actionHandler

回滚机制最常接触的三个偏移量是 +0x50(日志大小)、+0xa8(ignoredOps 计数)和 +0xd0(replacedOps 计数)——这三个 u32 正是 RewriterState 检查点(如下)。 +0xd8 (newlyCreatedOps) 和 +0x100 (rootReplacedOps) 是合法化器用来查找必须递归到的操作的两个集合; +0x140(unresolvedMaterializations)是物化回滚修剪的铸造注册表。

日志容器

text
rewrite-log : llvm::SmallVector<std::unique_ptr<(anon)::IRRewrite>, 6>
  +0x48  data    (T* — array of unique_ptr; inline buffer at +0x58 until it spills)
  +0x50  size    (u32 — number of live records; THE log depth)
  +0x54  capacity(u32)
```text

在合法化期间它是一个“仅附加堆栈”:记录仅被推送(由侦听器)并从尾部弹出(由 `undoRewrites`)。除了通过提交/回滚遍历之外,它永远不会被随机索引。 `unique_ptr` 元素意味着每个记录的删除 dtor (`[vtable+0x08]`) 是在 pop 上释放它的。 6 元素内联缓冲区(`SmallVector<…,6>`;构造函数在 `[Impl+0x54]` 处将容量初始化为 6,并将数据指向内联缓冲区 `Impl+0x58`)意味着最多 6 个记录的转换不会对数组进行堆分配。

> **QUIRK — 日志大小*是*检查点坐标。** 没有单独的“版本计数器”。检查点只是通过值捕获的三重 `(log.size, ignoredOps.count, replacedOps.count)`;回滚到检查点意味着将日志弹出到捕获的大小,并将两个辅助簿记容器缩小到捕获的计数。这就是为什么合法化者可以在三个 `mov` 中进行检查点并回滚而不复制任何 IR。

---

## IRRewrite 记录层次结构

### 用途

这11种记录类型是可逆突变的字母表。该单元对它们进行清点,固定每个叶子的 vtable 及其回滚/提交/清理,并修复驱动程序调度所通过的 5 个虚拟 ABI。

### 11条具体记录

每条记录都是 `(anonymous namespace)::IRRewrite` 单基层次结构的叶子。中间抽象基 `BlockRewrite` 和 `OperationRewrite` 没有自己的 vtable(仅 typeinfo)——只有 11 个具体叶子有,并且所有 11 个 vtable 都根据它们的 `_ZTV` 符号进行验证。每个叶子的 `{vtable, rollback, commit, cleanup}` 地址:

| IRRewrite 叶 | 虚拟表 | 回滚 | 提交 | 清理 |
|---|---|---|---|---|
| `UnresolvedMaterializationRewrite` | `0x21c234a0` | `0x1c95b660` | (底座) | (底座) |
| `InlineBlockRewrite` | `0x21c235e0` | `0x1c962920` | (底座) | (底座) |
| `BlockTypeConversionRewrite` | `0x21c23648` | `0x1c9629e0` | `0x1c962a60` | (底座) |
| `CreateOperationRewrite` | `0x21c23698` | `0x1c962f60` | `0x1c963080` | (底座) |
| `MoveOperationRewrite` | `0x21c236e8` | `0x1c9630c0` | `0x1c963160` | (底座) |
| `ReplaceOperationRewrite` | `0x21c23738` | `0x1c963500` | `0x1c9635a0` | `0x1c963920` |
| `ReplaceValueRewrite` | `0x21c23788` | `0x1c963b00` | `0x1c963b60` | (底座) |
| `EraseBlockRewrite` | `0x21c237f0` | `0x1c963fc0` | `0x1c964020` | `0x1c9640a0` |
| `CreateBlockRewrite` | `0x21c23840` | `0x1c9641e0` | `0x1c9642e0` | (底座) |
| `MoveBlockRewrite` | `0x21c23890` | `0x1c964340` | `0x1c964400` | (底座) |
| `ModifyOperationRewrite` | `0x21c238e0` | `0x1c964860` | `0x1c964960` | (底座) |

`(base)` = 叶继承无操作 `IRRewrite::commit`(`0x1c95b760`,裸 `ret`)或 `IRRewrite::cleanup`(`0x1c95b780`)。 `IRRewrite::~IRRewrite`(D1,slot0)是 `0x1c964300`,在所有叶子之间共享。只有 `ReplaceOperationRewrite` 和 `EraseBlockRewrite` 带有重要的 `cleanup` — 延迟擦除步骤(如下)。

这六个操作级别记录分为三个系列:

- **结构创建/移动**:`CreateOperationRewrite`、`MoveOperationRewrite` — 由 `notifyOperationInserted` 附加。
- **值替换**:`ReplaceOperationRewrite`(替换整个操作的结果),`ReplaceValueRewrite`(替换一个值的使用)- 由 `replaceOp` / `replaceValueUses` 附加。这些是 1:N 和 1:1 替换记录。
- **就地 + 转换**:`ModifyOperationRewrite`(为 `modifyOpInPlace` 保存的属性/操作数/后继/区域)、`UnresolvedMaterializationRewrite`(`builtin.unrealized_conversion_cast` 桥)。

五个 `*Block*` 记录涵盖区域签名转换和 SCF 结构转换 - 它们是 func/scf 动态合法性 lambda 的回滚一半(请参阅 [方言转换合法化器](dialect-conversion-legalizer.md))。它们的 vtable 和回滚地址固定在上面;他们的主体是高的(没有单独反编译)并且超出了这里的范围。

### 5-虚拟ABI

从 `ReplaceOperationRewrite` vtable 重定位集进行时隙遍历(`0x21c23738`;`unique_ptr` 使用的地址点是 `vtable+0x10 = 0x21c23748`,它保存 slot0):

```text
record vptr layout (all 11 leaves share this shape)
  vptr+0x00  slot0  ~IRRewrite()  (complete/D1)        = 0x1c964300  (shared)
  vptr+0x08  slot1  ~Leaf()       (deleting/D0)        = leaf-specific (frees the record)
  vptr+0x10  slot2  rollback()                          ← undoRewrites calls [+0x10]
  vptr+0x18  slot3  commit(RewriterBase&)               ← applyRewrites calls [+0x18]
  vptr+0x20  slot4  cleanup(RewriterBase&)              ← applyRewrites calls [+0x20]

驱动程序“纯粹”通过这些插槽进行调度 - 回滚引擎从不检查记录的标签来决定做什么,它只是调用 [vtable+0x10]。这是经过字节精确验证的:undoRewrites 调用 [rax+0x10](回滚),applyRewrites 调用 [rax+0x18](提交),然后调用 [rax+0x20](清理)。 record+0x8 处的标签(例如 MoveOperationRewrite5ReplaceOperationRewrite7CreateOperationRewrite8)在其他地方存在于 dyn_cast,但撤消/提交行走忽略它。

公共记录头

text
IRRewrite record header (offsets common to all 11 leaves)
  +0x00  vptr            (one of the 11 vtables above)
  +0x08  kind tag (u32)  (RTTI-style discriminator; e.g. Create=8, Move=5, Replace=7)
  +0x10  Impl*           (back-reference to the owning ConversionPatternRewriterImpl)
  +0x18  target          (Operation*  for op-rewrites; Block* for block-rewrites)
  +0x20+ leaf-specific saved state (see per-record rollback bodies below)
```text

> **注意 — `CreateOperationRewrite` 是 32 字节; `MoveOperationRewrite` 为 48。** 两者均由相同的侦听器方法 `notifyOperationInserted` (`0x1c950260`) 分配,不同的保存状态具有不同的大小。 *create*路径(标签`8`,vtable地址点`0x21c236a8`)分配`operator new(0x20)` = 32字节——仅头,没有额外保存的状态,因为回滚只是取消链接操作。 *移动*路径(标记`5`,vtable地址点`0x21c236f8`)分配`operator new(0x30)` = 48字节,保存前一个块(`+0x20`)和插入前操作(`+0x28`),因此回滚可以将操作移回。

---

## 回滚

### 用途

这是失败路径:丢弃在检查点之后进行的每个 IR 突变,按照它们进行的完全相反的顺序,每个记录都反转自己的更改。两个函数实现它 - `undoRewrites`(日志原语)和 `resetState`(包装它的公共检查点回滚)。

### `undoRewrites` — LIFO 展开

`ConversionPatternRewriterImpl::undoRewrites(unsigned numRewritesToKeep, StringRef)` 位于 `0x1c94d060`。控制流,字节解码:

```c
// undoRewrites(numToKeep)   @ 0x1c94d060
void undoRewrites(Impl* this, unsigned numToKeep):
    unsigned n = this->log.size;                  // [Impl+0x50]
    if (n == numToKeep) return;                   // nothing to undo

    // PASS 1 — ROLLBACK, newest-first (LIFO)
    for (i = n - 1; i >= numToKeep; --i):          // walk DOWN from size-1
        IRRewrite* rec = log[i];
        rec->rollback();                           // call [rec_vtable+0x10]  (slot2)

    // PASS 2 — POP + free the popped records
    for (i = n - 1; i >= numToKeep; --i):
        IRRewrite* rec = log[i];
        log[i] = nullptr;
        if (rec) rec->~Leaf();                     // call [rec_vtable+0x08]  (D0, frees record)
    // shrink the SmallVector: realloc-down (mallocForGrow 0x208d1820) if it
    // drops below the inline threshold, else memset the freed tail to 0

    this->log.size = numToKeep;                    // [Impl+0x50] = numToKeep

反编译确认每一步:第28行读取[a1+80](大小); 29号线是早回岗;第 35-40 行的展开循环将 v8 (= 8*size) 向下v7 (= 8*numToKeep),调用 (**(rec) + 16) = [vtable+0x10] = 回滚;第 48-56 / 127-138 行的 pop 循环调用 (*rec + 8) = [vtable+0x08] = 删除 dtor 并将每个槽置空;第 85 条线 [a1+80] = numToKeep

QUIRK — 回滚从最新开始运行;这是强制性的,而不是风格上的。 记录以相反的插入顺序展开,以便每个 rollback() 看到的 IR 完全处于刚刚推送该记录之后的状态。在使用替换值的 CreateOperationRewrite 之前推送的 ReplaceOperationRewrite 必须在创建之后回滚 - 首先撤消相关更改。在回滚时运行日志前移将使记录反转其先决条件不再存在的更改。 LIFO 顺序与提交的 FIFO 顺序(如下)相反,并且不对称是故意的。

每记录回滚语义

四个操作级回滚加上物化回滚,解码:

c
// CreateOperationRewrite::rollback   @ 0x1c962f60
//   The op was inserted; unlink it. Walk the created op's nested block lists
//   and detach each node (ilist_traits<Block>::removeNodeFromList 0x1d8e05c0),
//   removing the inserted op from the IR. The op is NOT erased here — erase is
//   deferred (it has no cleanup; rollback just detaches).

// ReplaceOperationRewrite::rollback   @ 0x1c963500
//   For each OpResult of the replaced op (getNextResultAtOffset), erase that
//   value's entry from the ConversionValueMapping (0x1c95b7a0). This undoes the
//   result -> replacement remap so the original op's uses snap back to it.
//   (Confirmed: loops over *(op+0x24) results, calls ConversionValueMapping::erase
//    per result with a {value, range} key.)

// MoveOperationRewrite::rollback   @ 0x1c9630c0
//   transferNodesFromList (0x1d8cccc0) moves the op back to its saved location
//   {previousBlock @rec+0x20, insertBeforeOp @rec+0x28}.

// ModifyOperationRewrite::rollback   @ 0x1c964860
//   Full in-place-modification undo: restore saved loc (rec+0x28 -> op+0x18),
//   setAttrs(saved @rec+0x30, 0x1d8cbd60), setOperands(saved @rec+0x38/+0x40,
//   0x1d8cc3a0), per-successor setSuccessor (rec+0x88/+0x90, 0x1d8cd400),
//   and restore saved regions (rec+0xa8).

// UnresolvedMaterializationRewrite::rollback   @ 0x1c95b660
//   1. erase the cast's results from the ConversionValueMapping (if any mapped);
//   2. remove the cast op from unresolvedMaterializations DenseMap [Impl+0x140]
//      (open-address probe, tombstone -8192 / sentinel -4096);
//   3. Operation::erase() the builtin.unrealized_conversion_cast (0x1d8ccd20).
```text

`ReplaceOperationRewrite::rollback` 反编译(`0x1c963500`)是最清晰的:它迭代替换操作的结果(`getNextResultAtOffset`)并使用键 `{result, {1, 2}}` 在每个结果上调用 `ConversionValueMapping::erase` — 取消映射值→替换关系。因为替换是*记录*而不是*应用*(在记录时间没有发生SSA重新布线),回滚只是删除映射条目;原始操作及其用途从未受到干扰。

### `resetState` — 回滚到标记的检查点

`ConversionPatternRewriterImpl::resetState((anon)::RewriterState state, StringRef)` 位于 `0x1c95bf60`。 `RewriterState` 是按值传递的 12 字节 POD:

```text
RewriterState (12 bytes; the legalizer's checkpoint coordinate)
  +0x00  numRewrites    (u32 — target log.size)        captured from [Impl+0x50]
  +0x04  numIgnoredOps  (u32 — target ignoredOps.count) captured from [Impl+0xa8]
  +0x08  numReplacedOps (u32 — target replacedOps.count)captured from [Impl+0xd0]
c
// resetState(state)   @ 0x1c95bf60
LogicalResult resetState(Impl* this, RewriterState state):
    undoRewrites(state.numRewrites);                 // 1. unwind the action log (KF above)

    while (this->ignoredOps.count > state.numIgnoredOps):  // [Impl+0xa8]
        ignoredOps.pop_back();                       // 2. drop ops marked ignored after checkpoint
                                                     //    (SetVector::pop_back 0x1c95c080)

    while (this->replacedOps.count > state.numReplacedOps): // [Impl+0xd0]
        remove most-recent replacedOps entry;        // 3. shrink the replaced-op DenseMap
                                                     //    (open-address probe, tombstone 0xffffffffffffe000)
```text

反编译确认:第17行调用`undoRewrites`;第18-23行弹出ignoredOps SetVector(`a1[42]` = `Impl+0xa8`作为`DWORD`索引),直到到达`numIgnoredOps`;第 24-76 行使用 `-8192`/`-4096` 墓碑/哨兵探针缩小 ReplacedOps DenseMap (`a1[52]` = `Impl+0xd0`)。 `resetState` 是每个合法化模式失败臂在尝试下一个深度排序候选者之前调用的原子倒带函数。

> **注意 — 三个容器,一个倒带。** 检查点不仅仅是一个日志位置。在检查点之间,模式可能具有(a)推送日志记录,(b)标记操作*忽略*(已经合法/就地擦除),以及(c)记录*替换*操作。 `resetState` 将所有三个倒回到捕获的计数。仅回滚日志的重新实现者将留下陈旧的忽略/替换簿记,从而使下一次合法化尝试不同步。

---

## 提交和 1:N 替换

### 用途

成功之路。当子树完全合法化时,其日志必须永久化:推测性替换变成真正的 SSA 重新连接,并且分离的操作被删除。这也是 1:N 结果替换和铸造物化实际发生的地方 - 它们仅在模式应用期间“记录”。

### `applyRewrites` — 两遍转发提交

`ConversionPatternRewriterImpl::applyRewrites()` 在 `0x1c94c1c0`:

```c
// applyRewrites()   @ 0x1c94c1c0
void applyRewrites(Impl* this):
    IRRewriter rw(context);                          // stack rewriter, vtable 0x217e9608

    // PASS 1 — COMMIT, oldest-first (FIFO)
    for (i = 0; i < this->log.size; ++i):            // FORWARD over [Impl+0x48]
        log[i]->commit(rw);                          // call [rec_vtable+0x18]  (slot3)

    // PASS 2 — CLEANUP-ERASE, oldest-first
    SingleEraseRewriter erw;                          // vtable 0x21c23388
    for (i = 0; i < this->log.size; ++i):
        log[i]->cleanup(erw);                        // call [rec_vtable+0x20]  (slot4)

反编译确认了两遍:第 41-48 行是提交循环(v6 从 0 向上,(*rec + 24) = [vtable+0x18]),第 75-80 行是清理循环(v13 从 0 向上,(*rec + 32) = [vtable+0x20])。第1次绑定mlir::IRRewriter; pass 2 绑定了一个 SingleEraseRewriter,其 eraseOp 是实际的删除。

明白了 — 提交和擦除是两个单独的 FORWARD 传递,从不交错。 首先运行所有提交,然后运行所有擦除。如果每个记录融合提交和擦除,则后面的记录的 commit 可以取消引用已删除的较早记录的操作。拆分为 commit-all-then-erase-all 可以保证没有提交引用已释放的操作。 ReplaceOperationRewriteEraseBlockRewrite 记录是唯一具有重要 cleanup(延迟擦除,槽 4)的记录;所有其他记录的清理都是基本的无操作。 ReplaceOperationRewrite::cleanup (0x1c963920) 是一个单行:erw.eraseOp(rec->op) 通过 [erw_vtable+0x10]rec+0x18 保存的操作上。

QUIRK — 提交是 FIFO,回滚是 LIFO。 提交会按最早的优先顺序重放,因为每个提交 构建在 先前的操作数之上(较晚的操作的操作数可能是较早操作的结果,已经重新连接)。回滚会按最新的顺序重放,因为每个撤消“取决于”较晚的撤消先消失。相同的日志,相反的方向——顺序是由数据依赖性决定的,逆转任何一个都是正确性错误,而不是性能错误。

ReplaceOperationRewrite::commit — 替换和物化发生的地方

ReplaceOperationRewrite::commit(RewriterBase&) 位于 0x1c9635a0。这是将记录的替换转换为真实的replaceAllUsesWith 的函数,在替换的类型与使用不匹配的情况下实现强制转换:

c
// ReplaceOperationRewrite::commit(rw)   @ 0x1c9635a0
void commit(ReplaceOperationRewrite* this, RewriterBase& rw):
    Operation* op = this->op;                        // rec+0x18
    SmallVector<Value> replacements;                 // one final value per result

    // 1. resolve the FINAL replacement value for each result of `op`
    for (OpResult r : op->getResults()):             // getNextResultAtOffset
        Value v = findOrBuildReplacementValue(impl, r, typeConverter);  // 0x1c94fde0
        replacements.push_back(v);                    //   -- may INSERT a cast (below)

    // 2. rewire all uses: real replaceAllUsesWith via the listener
    listener.replaceAllUsesWith(op, ValueRange(replacements));  // [listener_vtable+0x38]

    // 3. unlink op from its parent block (the erase itself is deferred to cleanup)
    //    (reportFatalInternalError if op has no parent block)
    op->parentBlock.removeNodeFromList(op);
```text

反编译(`0x1c9635a0`)显示每个结果循环调用`findOrBuildReplacementValue(v3[2]=Impl, result, v3[4]=typeConverter)`(第106行),在解析值上组装`ValueRange`(第116行),在`[vtable+0x38]`(第118行)调用侦听器的replace-all-uses,最后取消链接操作(第 207-217 行,如果第 209 行 `op->parentBlock` 为空,则会出现致命错误)。

### `findOrBuildReplacementValue` — 铸造物化点

`findOrBuildReplacementValue(Value, TypeConverter*)` at `0x1c94fde0` 是桥接尚未转换的类型的函数。对于给定的结果值:

```c
// findOrBuildReplacementValue(value, typeConverter)   @ 0x1c94fde0
Value findOrBuildReplacementValue(Impl* this, Value value, TypeConverter* tc):
    // 1. look the value up in the ConversionValueMapping (typed lookup first)
    if (mapped = lookupOrNull(value, /*desiredType=*/value.getType())):
        return mapped;                               // already have a matching replacement
    // 2. untyped lookup: the latest mapping regardless of type
    vals = lookupOrNull(value, /*any type*/);
    // 3. if no usable replacement exists, BUILD an unresolved materialization cast
    insertPt = computeInsertPoint(vals)  or  value.getParentBlock();
    cast = buildUnresolvedMaterialization(this, /*kind=*/1, insertPt, ...,   // 0x1c94dcc0
                                          loc, vals, /*outputType=*/value.getType(), tc);
    return cast.getResult(0);                        // a builtin.unrealized_conversion_cast result

反编译显示类型化的 lookupOrNull(第 62 行)、非类型化的后备(第 119 行),以及(当两者都没有产生可用值时)buildUnresolvedMaterialization(a1, 1u, ...) 调用(第 206 行)返回强制转换的第一个结果(第 222 行)。 1u 是具体化种类(源/目标)。生成的演员表本身记录为 UnresolvedMaterializationRewrite,因此它像任何其他记录一样参与回滚。这些转换是稍后的 reconcileUnrealizedCasts 传递(或 DMA 桥消费者)解析的“未解析”桥。

1:N 替换路径

这两个替换入口点的数量不同:

入口点VA签名数量
replaceOp0x1c950540replaceOp(Operation*, SmallVector<SmallVector<Value,6>,1>&&)1:N (每个结果→值的向量)
replaceValueUses0x1c94f740replaceValueUses(Value, ValueRange, TypeConverter*, function_ref)1:1(一个值→一个值范围)

1:N 路径是 replaceOp(已损坏的 replaceOp(Operation*, llvm::SmallVector<llvm::SmallVector<mlir::Value,6u>,1u>&&) — 已确认)。它是 MLIR replaceOpWithMultiple 背后的底层,当一个源操作的单个结果扩展到多个值时使用(TupleType/I32PairType 1:N 类型转换)。解码:

c
// replaceOp(op, SmallVector<SmallVector<Value>>&& newValues)   @ 0x1c950540
void replaceOp(Impl* this, Operation* op, SmallVector<SmallVector<Value>>&& vals):
    if (recordingMode == 1):                          // [OperationConverter+0x29]
        // 1. map EACH original result to ITS vector of replacement values
        for (i = 0; i < op->numResults; ++i):
            OpResult r = op->getResult(i);            // getNextResultAtOffset
            ConversionValueMapping::map(this->valueMapping, r, vals[i]);  // 1:N map
        // 2. push a ReplaceOperationRewrite record (32-byte hdr + saved op)
        rec = operator new(0x28); rec.tag = 7; rec.vtable = 0x21c23748;
        rec.Impl = this; rec.op = op;
        log.push_back(rec);                           // append to [Impl+0x48]/[+0x50]
        // 3. walk the now-detached op's region, recording produced ops ($_2)
        walk(op, replaceOp::$_2);
    else:
        // pass-through (non-recording): build replacement ValueRange, real replaceOp
        getReplacementValues(...); rw.replaceOp(op, range);
```text

反编译确认记录分支(第 40 行,`if (*(_BYTE*)(a1[47]+41))`):每个结果循环(第 52-71 行)调用 `ConversionValueMapping::map<SmallVector<Value,2>,SmallVector<Value,2>>` — **值向量到值向量** 映射,它是 1:N 的核心 — 然后 `operator new(0x28)` (40 字节)分配`ReplaceOperationRewrite`(标记`7`,vtable地址点`0x21c23748`)并将其附加到日志(第73-118行)。非记录分支(第127-150行)构建`ValueRange`并转发到`RewriterBase::replaceOp`。

> **QUIRK — 1:N 替换被记录为值-*向量*映射,但 `commit` 将其折叠为每个结果一个值。** 在模式应用期间,结果 `r` 映射到 `SmallVector<Value>`(可能是多个)。在提交时,`findOrBuildReplacementValue` 将其解析为*单个*替换值 - 如果 `r` 映射到与用户预期不同类型的多个值,它会构建一个物化转换,该转换采用值向量并生成一个原始类型的值。因此,1:N 扩展存在于 *mapping* 中,并通过在提交时强制物化来协调到 SSA 图中,而不是通过保留多个定义供一次使用。这是 `tpu` 1:N 降低(Iota → vlaneseq + index_cast;参见 [LowerToMlo DMA 桥](lower-to-mlo-dma-bridge.md))通过转换保持类型正确的精确机制。

---

## 日志如何填充

### 用途

关闭循环:哪个监听器方法推送哪个记录。这是每次模式改变 IR 时 [方言转换合法化器](dialect-conversion-legalizer.md) 隐式驱动的追加源。

### `notifyOperationInserted` — 创建与移动

`ConversionPatternRewriterImpl::notifyOperationInserted(Operation*, InsertPoint)` 位于 `0x1c950260` 是操作插入的单个侦听器。它根据操作是否已经有父级(*move*)或新插入(*create*)来推送两种记录类型之一:

```c
// notifyOperationInserted(op, insertPoint)   @ 0x1c950260
void notifyOperationInserted(Impl* this, Operation* op, InsertPoint ip):
    if (recordingMode != 1):
        realListener.notifyOperationInserted(op, ip); // pass-through
        return;

    if (op had a previous parent block):              // ip.priorBlock != null
        rec = operator new(0x30);                     // MOVE record (48 B)
        rec.tag = 5; rec.vtable = 0x21c236f8;         // MoveOperationRewrite
        rec.Impl = this; rec.op = op;
        rec.previousBlock = ip.block; rec.insertBeforeOp = ip.beforeOp;  // +0x20/+0x28
    else:
        ignoredOps/replacedOps bookkeeping ...
        newlyCreatedOps.insert(op);                   // [Impl+0xd8] produced-op set
        rec = operator new(0x20);                     // CREATE record (32 B)
        rec.tag = 8; rec.vtable = 0x21c236a8;         // CreateOperationRewrite
        rec.Impl = this; rec.op = op;
    log.push_back(rec);                               // append to [Impl+0x48]; ++[Impl+0x50]

反编译引脚包括:移动路径(第 54-95 行、operator new(0x30u)、标签 5、vtable off_21C236F8、在 +0x20 处保存 a2 以及在 +0x28 处的 insert-before 操作)和创建路径(第112-153行,operator new(0x20u),标记8,vtable off_21C236A8newlyCreatedOps lookupOrInsertIntoBucket位于_RBX+216 = Impl+0xd8)。两者都附加到 _RBX+72/+80 (= Impl+0x48/+0x50) 处的日志 SmallVector。

完整附加图

重写器调用监听器/方法VA记录已推送
create<Op>()(新鲜插入)notifyOperationInserted0x1c950260CreateOperationRewrite (32 B)
将操作移入块notifyOperationInserted(先前父级)0x1c950260MoveOperationRewrite (48 B)
replaceOp(op, vecs) (1:N)replaceOp0x1c950540ReplaceOperationRewrite (40 B)
replaceUsesOfWith (1:1)replaceValueUses0x1c94f740ReplaceValueRewrite
modifyOpInPlace启动/完成修改ModifyOperationRewrite
型失配电桥buildUnresolvedMaterialization0x1c94dcc0UnresolvedMaterializationRewrite
区域签名转换convertRegionTypes0x1c94e7a0BlockTypeConversionRewrite
inlineBlockBefore直插块InlineBlockRewrite

功能图

功能VA角色
ConversionPatternRewriter ctor0x1c9512a0构建 0x1e0 字节 Impl (operator new(0x1e0));字段初始化
ConversionPatternRewriterImpl::undoRewrites0x1c94d060LIFO回滚:pop日志到numToKeep,调用每个rollback()
ConversionPatternRewriterImpl::resetState0x1c95bf60检查点回滚:undoRewrites + 忽略/替换倒带
ConversionPatternRewriterImpl::applyRewrites0x1c94c1c0FORWARD 两遍提交(commit 然后 cleanup 擦除)
ConversionPatternRewriterImpl::notifyOperationInserted0x1c950260推送在插入时创建 (32 B)/移动 (48 B) 记录
ConversionPatternRewriterImpl::replaceOp (1:N)0x1c950540映射每个结果 → 值向量;推ReplaceOperationRewrite
ConversionPatternRewriterImpl::replaceValueUses (1:1)0x1c94f740ReplaceValueRewrite
ConversionPatternRewriterImpl::buildUnresolvedMaterialization0x1c94dcc0插入 builtin.unrealized_conversion_cast;推送物化记录
ConversionPatternRewriterImpl::findOrBuildReplacementValue0x1c94fde0解决最终替换;构建类型不匹配的强制转换
ReplaceOperationRewrite::rollback0x1c963500ConversionValueMapping 中擦除每个结果条目 每个结果
ReplaceOperationRewrite::commit0x1c9635a0findOrBuildReplacementValue + replaceAllUsesWith
ReplaceOperationRewrite::cleanup0x1c963920延迟替换操作的 eraseOp(插槽 4)
CreateOperationRewrite::rollback0x1c962f60分离插入的操作 (removeNodeFromList)
MoveOperationRewrite::rollback0x1c9630c0transferNodesFromList 返回保存的块/操作前
ModifyOperationRewrite::rollback0x1c964860restore saved loc/attrs/operands/successors/regions
UnresolvedMaterializationRewrite::rollback0x1c95b660取消映射投射结果 + 修剪 unresolvedMaterializations + 擦除投射
IRRewrite::~IRRewrite(D1,共享插槽0)0x1c964300记录控制器
IRRewrite::commit(基本无操作)0x1c95b760ret(由2叶继承:UnresolvedMaterialization、InlineBlock)
IRRewrite::cleanup(基本无操作)0x1c95b780ret(由 9 叶继承;仅覆盖 EraseBlock + ReplaceOperation)
ConversionValueMapping::erase0x1c95b7a0删除值→替换映射(回滚时使用)
mallocForGrow(小向量)0x208d1820日志在弹出时重新分配

本页未包含的内容

  • 深度递归和合法性模型。 哪种模式以什么顺序触发,并且哪些操作是合法的由方言转换合法化器拥有(computeOpLegalizationDepth递归、ConversionTarget LegalizationAction枚举、三个TPU通行证的合法性声明)。该页面仅拥有合法化者驱动的重写日志+回滚+1:N+物化底层。
  • legalizeWithPattern lambdas($_0 可以在成功时应用 0x1c95c100$_1 0x1c95c1a0$_2 递归 0x1c95c6e0)— 在递归/失败臂上调用 resetState/undoRewrites。这些属于合法化驱动程序;此页面记录了它们调用的原语,而不是线束。
  • 每个 tpu 操作降低到什么。 每个操作 matchAndRewrite 主体(Iota、DeviceId、DMA 桥接、1:N 扩展)归 LowerToMlo DMA 桥tpu → LLO 降低LowerToSparseCoreLLVM 所有。
  • 类型转换器。 registerConversion/registerTypeAttributeConversion 和 1:N TupleType/I32PairType 类型规则位于 SparseCore 型转换器 上;本页仅记录如何记录和提交 1:N 结果替换,而不记录如何计算 类型
  • 块级回滚体 (BlockTypeConversionRewrite 0x1c9629e0InlineBlockRewrite 0x1c962920CreateBlockRewrite 0x1c9641e0MoveBlockRewrite 0x1c964340EraseBlockRewrite 0x1c963fc0)。 Vtables和地址被固定(上表);主体为高电平(未反编译)——区域签名/SCF 结构转换撤消。超出这里的范围。
  • reconcileUnrealizedCasts — 解析幸存的 builtin.unrealized_conversion_cast 桥的转换结束通道。此页面记录了如何创建和记录;它们后来如何被“消除”是一个单独的过程。
  • 每个替换站点的 ReplaceValueRewriteReplaceOperationRewrite 选择(replaceOp 0x1c950540replaceValueUses 0x1c94f740)— 均附加记录;每个替换模式选择的选项是从签名中推断出来的(1:N 与 1:1),而不是在每个调用站点都固定分支(详尽选择规则上的)。

交叉引用

  • 方言转换合法化器 — 深度感知驱动程序,捕获 RewriterState 检查点、应用候选模式、递归并通过此引擎调用 resetState/undoRewrites(回滚)或 applyRewrites(提交)。
  • LowerToMlo DMA 桥tpu → sparse_core 模式主体,其 1:N 替换和 DMA 桥 unrealized_conversion_cast 在此记录并提交。
  • LowerToSparseCoreLLVMsparse_core → LLVM 通道,最重的 1:N 类型路径(I32PairType → 2×i32TupleType → element types)执行值向量替换映射。
  • SparseCore 型转换器 — 此重写器记录的值向量映射背后的 1:N 类型转换规则。
  • tpu → LLO 降低 — TensorCore 降低,主要是 1:1,其模式驱动相同的记录重写器。
  • TPU编译器 — 所有这些转换都在其中运行的五阶段方言血统。
  • 二进制: extracted/libtpu-0.0.40-cp314-cp314-manylinux_2_31_x86_64/libtpu/libtpu.so(构建 ID 89edbbe81c5b328a958fe628a9f2207d)
  • 索引条目: 第五部分 — 编译器:降低和优化过程/MLIR 降低链 — 返回索引