Sandbox IR: LLVM IR のトランザクションレイヤー

Sandbox IR は、LLVM IR の状態を保存/復元できる、LLVM IR の上に構築された IR レイヤーです。

API

Sandbox IR API は、LLVM API のように感じられるように設計されており、多くの一般的な API クラスと関数を複製して LLVM API を反映しています。クラス階層も似ています (ただし、llvm::sandboxir 名前空間にあります)。たとえば、その一部を以下に示します。

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

設計

Sandbox IR Value <-> LLVM IR Value マッピング

各 LLVM IR Value は、単一の Sandbox IR Value にマップされます。逆もほとんどの場合に当てはまりますが、Sandbox IR 命令の場合は、複数の LLVM IR 命令にマップされる場合を除きます。このような命令は、ベースの Sandbox IR の拡張機能で定義できます。

  • 順方向マッピング: Sandbox IR Value -> LLVM IR Value 各 Sandbox IR Value には、対応する LLVM IR Value を指す llvm::Value *Val メンバー変数が含まれています。

  • 逆方向マッピング: LLVM IR Value -> Sandbox IR Value このマッピングは、sandboxir::Context::LLVMValueToValue に格納されます。

たとえば、sandboxir::User::getOperand(OpIdx) は、sandboxir::User *U に対して次のように動作します。

  • まず、LLVM User を見つけます: llvm::User *LLVMU = U->Val

  • 次に、LLVM Value オペランドを取得します: llvm::Value *LLVMOp = LLVMU->getOperand(OpIdx)

  • 最後に、Sandbox IR コンテキスト内のマップを照会して、LLVMOp に対応する Sandbox IR オペランドを取得します: retrun Ctx.getValue(LLVMOp)

Sandbox IR はライトスルーです

Sandbox IR は、その状態を LLVM IR に依存するように設計されています。そのため、Sandbox IR オブジェクトに加えられた変更は、対応する LLVM IR を直接更新します。

これには次の利点があります。

  • 状態の複製を最小限に抑え、

  • Sandbox IR と LLVM IR が常に同期していることを保証し、バグを回避し、低レベル化の手順の必要性をなくします。

  • LLVM IR に依存できるため、シリアル化/デシリアル化インフラストラクチャは不要です。

  • 実際の llvm::Instruction をコストモデリング API に渡すことができます。

IR の状態を変更する Sandbox 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 変更トラッキング

Sandbox IR の状態は保存および復元できます。これは、パブリック Sandbox IR API 関数と緊密に結合されたトラッカーコンポーネントの助けを借りて行われます。ネストされた保存/復元は現在サポートされていないことに注意してください。

状態を保存してトラッキングを有効にするには、ユーザーは sandboxir::Context::save() を呼び出す必要があります。この時点から、Sandbox IR の状態に加えられた変更は、ユーザーの介入なしに、自動的に変更オブジェクトを作成し、トラッカーに登録します。変更はトラッカー内のベクターに累積されます。

保存された状態にロールバックするには、ユーザーは sandboxir::Context::revert() を呼び出す必要があります。保存された状態に戻すことは、累積されたすべての変更を逆方向にたどり、個々の変更を元に戻すことです。

IR に加えられた変更を受け入れるには、ユーザーは sandboxir::Context::accept() を呼び出す必要があります。内部的には、変更を調べて、必要な最終処理を実行します。

revert() または accept() を呼び出した後、トラッキングは停止することに注意してください。トラッキングを再開するには、ユーザーは save() を呼び出す必要があります。