沙箱 IR:LLVM IR 之上的事务层

沙箱 IR 是 LLVM IR 之上的一个 IR 层,允许您保存/恢复其状态。

API

沙箱 IR API 的设计旨在让人感觉像 LLVM,复制了许多常见的 API 类和函数以镜像 LLVM API。类层次结构类似(但在 llvm::sandboxir 命名空间中)。例如,以下是其中一小部分

namespace sandboxir {
              Value
              /  \
            User BasicBlock ...
           /   \
  Instruction Constant
        /
     ...
}

设计

沙箱 IR 值 <-> LLVM IR 值映射

每个 LLVM IR 值都映射到一个沙箱 IR 值。反之亦然,在大多数情况下,除了映射到多个 LLVM IR 指令的沙箱 IR 指令外。此类指令可以在基本沙箱 IR 的扩展中定义。

  • 前向映射:沙箱 IR 值 -> LLVM IR 值每个沙箱 IR 值都包含一个 llvm::Value *Val 成员变量,该变量指向相应的 LLVM IR 值。

  • 反向映射:LLVM IR 值 -> 沙箱 IR 值此映射存储在 sandboxir::Context::LLVMValueToValue 中。

例如,对于 sandboxir::User *Usandboxir::User::getOperand(OpIdx) 的工作原理如下

  • 首先,我们找到 LLVM User:llvm::User *LLVMU = U->Val

  • 接下来,我们获取 LLVM 值操作数:llvm::Value *LLVMOp = LLVMU->getOperand(OpIdx)

  • 最后,我们通过查询沙箱 IR 上下文中的映射来获取对应于 LLVMOp 的沙箱 IR 操作数:retrun Ctx.getValue(LLVMOp)

沙箱 IR 是直写式

沙箱 IR 的设计依赖于 LLVM IR 来获取其状态。因此,对沙箱 IR 对象所做的任何更改都会直接更新相应的 LLVM IR。

这具有以下好处

  • 它最大程度地减少了状态的复制,并且

  • 它确保沙箱 IR 和 LLVM IR 始终保持同步,这有助于避免错误并消除了降低步骤的需要。

  • 无需序列化/反序列化基础结构,因为我们可以依靠 LLVM IR 来实现。

  • 可以将实际的 llvm::Instruction 传递给成本建模 API。

修改 IR 状态的沙箱 IR API 函数将调用修改 LLVM IR 状态的相应 LLVM IR 函数。例如,对于 sandboxir::User::setOperand(OpIdx, sandboxir::Value *Op)

  • 我们获取相应的 LLVM User:llvm::User *LLVMU = cast<llvm::User>(Val)

  • 接下来,我们获取相应的 LLVM 操作数:llvm::Value *LLVMOp = Op->Val

  • 最后,我们修改 LLVMU 的操作数:`LLVMU->setOperand(OpIdx, LLVMOp)

IR 更改跟踪

沙箱 IR 的状态可以保存和恢复。这是借助与公共沙箱 IR API 函数紧密耦合的跟踪器组件完成的。请注意,目前不支持嵌套保存/恢复。

要保存状态并启用跟踪,用户需要调用 sandboxir::Context::save()。从这一点开始,对沙箱 IR 状态所做的任何更改都会自动创建一个更改对象并将其注册到跟踪器中,而无需用户的任何干预。更改会累积在跟踪器中的向量中。

要回滚到保存的状态,用户需要调用 sandboxir::Context::revert()。回滚到保存的状态只是反向遍历所有累积的更改并撤消每个单独的更改的问题。

要接受对 IR 所做的更改,用户需要调用 sandboxir::Context::accept()。在内部,这将遍历更改并运行所需的任何最终操作。

请注意,在调用 revert()accept() 之后,跟踪将停止。要再次开始跟踪,用户需要调用 save()