add-ir-instruction について
Hermes IR に命令を追加するための開発スキルです。コンパイラのコントリビューター向けに、必要な宣言、パイプライン変更、テストを案内します。
主な機能
- 01ファイル別の実装チェックリスト
- 02IR ドキュメントの規約
- 03検証と型推論の更新
- 04コード生成と低レベル化の案内
- 05テスト配置の指針
活用シーン
- 01Hermes IR の新命令の計画
- 02変更が必要なコンパイラファイルの確認
- 03低レベル化とパイプライン登録の追加
- 04IR とコード生成テストの選定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ facebook/ hermes --skill add-ir-instructionインストール後、Claude Code で/add-ir-instructionを入力して読み込まれたか確認します。
詳細
Hermes に新しい IR 命令を追加する
新しい IR 命令を追加する際は、特定のファイル一式を変更する必要があります。このガイドでは、各ファイルと従うべきパターン、重要な規約について説明します。
変更するファイルのチェックリスト
doc/IR.md— ドキュメント(ドキュメントコメントを記述する唯一の場所)include/hermes/IR/Instrs.def— 命令の登録include/hermes/IR/Instrs.h— クラス定義(ここにドキュメントコメントを記述しないでください)include/hermes/IR/IRBuilder.h— ビルダーの宣言lib/IR/IRBuilder.cpp— ビルダーの実装lib/IR/IRVerifier.cpp— 検証ロジックlib/Optimizer/Scalar/TypeInference.cpp— 型推論のスタブlib/BCGen/HBC/ISel.cpp— HBC 命令選択(スタブまたは実装)lib/BCGen/SH/SH.cpp— 静的 Hermes コード生成(スタブまたは実装)lib/BCGen/facebook/Mins/Mins.cpp— Mins コード生成(スタブまたは実装)- テスト — 少なくとも
test/内のテストを更新または追加します
命令にローワリングが必要な場合(つまり、バイトコードのオペコードに直接対応しない場合)は、次のファイルも必要です。
include/hermes/BCGen/Lowering.h— ローワリングパスの宣言lib/BCGen/Lowering.cpp— ローワリングパスの実装lib/BCGen/HBC/LoweringPipelines.cpp— HBC パイプラインへのパスの登録lib/BCGen/SH/SH.cpp(lowerModuleIR内)— SH パイプラインへのパスの登録
手順ガイド
1. doc/IR.md にドキュメントを記述する
このファイルが、命令のドキュメントを記述する唯一の場所です。 Instrs.h にドキュメントコメントを追加しないでください。
該当するセクションに Markdown の表の項目を追加します。
### MyNewInst
MyNewInst | _
--- | --- |
Description | Brief description of what the instruction does.
Example | `MyNewInst %arg1, %arg2 : type`
Arguments | *%arg1* is ... *%arg2* is ...
Semantics | Describe the semantics, referencing the spec where appropriate.
Effects | Describe side effects (e.g., "May read and write memory.", "May read memory and throw.", "Does not read or write memory.").
2. Instrs.def に登録する
DEF_VALUE の項目を追加します。関連する命令の近くに配置してください。
DEF_VALUE(MyNewInst, Instruction)
別の命令のサブクラスである場合は、2 番目の引数に親クラスを指定します。ターミネーターの場合は、DEF_VALUE の代わりに TERMINATOR を使用します。
3. Instrs.h にクラスを定義する
このファイルにドキュメントコメントを追加しないでください。 ドキュメントは doc/IR.md に記述します。
次のパターンに正確に従ってください。
class MyNewInst : public Instruction {
MyNewInst(const MyNewInst &) = delete;
void operator=(const MyNewInst &) = delete;
public:
enum { Arg1Idx, Arg2Idx };
explicit MyNewInst(Value *arg1, Value *arg2)
: Instruction(ValueKind::MyNewInstKind) {
// Optional assertions on operand types:
// assert(arg2->getType().isSomeType() && "message");
// Set the result type:
setType(Type::createNoType()); // for instructions with no output
// or: setType(Type::createFoo()); for typed instructions
pushOperand(arg1);
pushOperand(arg2);
}
explicit MyNewInst(
const MyNewInst *src,
llvh::ArrayRef<Value *> operands)
: Instruction(src, operands) {}
Value *getArg1() const {
return getOperand(Arg1Idx);
}
Value *getArg2() const {
return getOperand(Arg2Idx);
}
static bool hasOutput() {
return false; // true if the instruction produces a value
}
static bool isTyped() {
return false; // true if the output type is meaningful
}
SideEffect getSideEffectImpl() const {
// Compose side effects. Common patterns:
// return {}; // pure
// return SideEffect{}.setReadHeap(); // reads memory
// return SideEffect{}.setReadHeap().setWriteHeap(); // reads+writes
// return SideEffect{}.setThrow().setReadHeap(); // may throw + read
return SideEffect{}.setThrow().setReadHeap();
}
static bool classof(const Value *V) {
ValueKind kind = V->getKind();
return kind == ValueKind::MyNewInstKind;
}
};
4. IRBuilder.h に IRBuilder の宣言を追加する
MyNewInst *createMyNewInst(Value *arg1, Value *arg2);
5. IRBuilder.cpp に IRBuilder の実装を追加する
MyNewInst *IRBuilder::createMyNewInst(Value *arg1, Value *arg2) {
auto *inst = new MyNewInst(arg1, arg2);
insert(inst);
return inst;
}
6. IRVerifier.cpp に検証を追加する
不変条件を確認する visit メソッドを追加します。
bool Verifier::visitMyNewInst(const MyNewInst &Inst) {
AssertIWithMsg(
Inst,
Inst.getArg2()->getType().isSomeType(),
"MyNewInst::Arg2 must be of SomeType");
return true;
}
7. TypeInference.cpp に型推論を追加する
infer メソッドを追加します。出力のない命令では、createNoType() を返します。
Type inferMyNewInst(MyNewInst *inst) {
return Type::createNoType();
}
8. コード生成スタブを追加する
コード生成の前に命令がローワリングされる場合は、致命的エラーを発生させるスタブを追加します。それ以外の場合は、実際のコード生成を実装します。
HBC ISel(lib/BCGen/HBC/ISel.cpp):
void HBCISel::generateMyNewInst(MyNewInst *Inst, BasicBlock *next) {
hermes_fatal("MyNewInst should have been lowered.");
}
SH(lib/BCGen/SH/SH.cpp):
void generateMyNewInst(MyNewInst &inst) {
hermes_fatal("MyNewInst should have been lowered");
}
Mins(lib/BCGen/facebook/Mins/Mins.cpp):
依頼がない限り、新しい命令の Mins コード生成は実装しないでください。スタブのままにしてください。
void generateMyNewInst(MyNewInst &inst) {
unimplemented(inst);
}
9. (必要な場合)ローワリングパスを追加する
Lowering.h で宣言します。
/// Brief description of what the lowering does.
Pass *createLowerMyNewInst();
Lowering.cpp で実装します。
Pass *hermes::createLowerMyNewInst() {
class ThisPass : public FunctionPass {
public:
explicit ThisPass() : FunctionPass("LowerMyNewInst") {}
bool runOnFunction(Function *F) override {
IRBuilder builder{F};
bool changed = false;
// Collect instructions first to avoid iterator invalidation.
llvh::SmallVector<MyNewInst *, 4> insts;
for (auto &BB : *F) {
for (auto &I : BB) {
if (auto *MNI = llvh::dyn_cast<MyNewInst>(&I))
insts.push_back(MNI);
}
}
for (auto *MNI : insts) {
// Replace MNI with lowered IR...
MNI->eraseFromParent();
changed = true;
}
return changed;
}
};
return new ThisPass();
}
パイプラインに登録します。
lib/BCGen/HBC/LoweringPipelines.cpp と lib/BCGen/SH/SH.cpp(lowerModuleIR)に、適切な位置で PM.addPass(createLowerMyNewInst()); を追加します(ローワリングによって導入される命令を処理する必要があるパスより前に追加してください)。
10. テストを追加または更新する
適切な test/ サブディレクトリに lit テストを追加します。IR の出力を検証するには、%FileCheck または %FileCheckOrRegen を使用してください。既存のテストで機能がカバーされている場合は、新しい命令を反映するよう期待される出力を更新します。
使用するサブディレクトリは、命令がコンパイラパイプラインとどのように連携するかによって異なります。すべての命令にすべてのディレクトリでのテストが必要なわけではありません。
test/IRGen/— 命令が JavaScript ソースから直接生成される場合に使用します。ここでのテストでは、IRGen が期待される IR を生成することを検証します。test/Optimizer/— 命令が最適化パスに影響する場合、または最適化パスと連携する場合に使用します。test/BCGen/— 使用頻度は高くありませんが、この IR 命令が新しいバイトコード命令と組み合わせて使われる場合は、バイトコード生成のテストをここに配置します。
主な規約
Instrs.hにドキュメントコメントを書かないでください。 すべてのドキュメントはdoc/IR.mdに記載します。Instrs.h内のクラスには、その命令の動作を説明する///や/** */コメントを記述しないでください。副作用など、実装上の分かりにくい点を説明する簡潔なインラインコメントは問題ありません。- 配置が重要です。 すべてのファイルで、新しい命令は関連する命令の近くに配置してください (例:プライベートフィールドの命令はまとめて配置します)。
- 命名を統一してください。 命令名(例:
FooBarInst)は、すべてのファイルで統一する必要があります。対象はInstrs.def、Instrs.h、IRBuilder.h/cpp、IRVerifier.cpp、TypeInference.cpp、およびすべてのコード生成ファイルです。 ValueKindは自動的に生成されます。Instrs.defにDEF_VALUE(MyNewInst, Instruction)を追加すると、列挙値ValueKind::MyNewInstKindが自動的に生成されます。