V8 Torque組み込み関数
このドキュメントは、Torque組み込み関数の記述に関する入門として、V8開発者を対象としています。Torqueは、新しい組み込み関数を実装するための推奨方法として、CodeStubAssemblerに取って代わります。このガイドのCSAバージョンについては、CodeStubAssembler組み込み関数を参照してください。
組み込み関数 #
V8では、組み込み関数は、ランタイム時にVMによって実行可能なコードの塊と見なすことができます。一般的なユースケースとしては、組み込みオブジェクト(RegExp
やPromise
など)の関数を実装することですが、組み込み関数は、他の内部機能(例:ICシステムの一部として)を提供するためにも使用できます。
V8の組み込み関数は、いくつかの異なる方法(それぞれトレードオフが異なります)を使用して実装できます。
- プラットフォーム依存のアセンブリ言語:非常に効率的ですが、すべてのプラットフォームへの手動ポートが必要であり、保守が困難です。
- C++:ランタイム関数と非常に似たスタイルで、V8の強力なランタイム機能にアクセスできますが、通常はパフォーマンス重視の領域には適していません。
- JavaScript:簡潔で読みやすいコード、高速な組み込み関数へのアクセスが可能ですが、遅いランタイム呼び出しを頻繁に使用し、型汚染による予測不可能なパフォーマンス、そして(複雑で分かりにくい)JSセマンティクスに関する微妙な問題の影響を受けます。JavaScript組み込み関数は非推奨であり、今後追加すべきではありません。
- CodeStubAssembler:プラットフォームに依存せず、可読性を維持しながら、アセンブリ言語に非常に近い効率的な低レベルの機能を提供します。
- V8 Torque:CodeStubAssemblerに変換されるV8固有のドメイン固有言語です。そのため、CodeStubAssemblerを拡張し、静的型付けと可読性が高く表現力豊かな構文を提供します。
以降のドキュメントでは、後者を中心に説明し、JavaScriptに公開される単純なTorque組み込み関数の開発に関する簡単なチュートリアルを示します。Torqueに関するより完全な情報については、V8 Torqueユーザーマニュアルを参照してください。
Torque組み込み関数の記述 #
このセクションでは、単一の引数を取り、それが数値42
を表しているかどうかを返す単純なCSA組み込み関数を記述します。組み込み関数は、(可能なので)Math
オブジェクトにインストールすることでJSに公開されます。
この例では、以下を示します。
- JS関数のように呼び出すことができる、JavaScriptリンケージを持つTorque組み込み関数の作成。
- 単純なロジックの実装のためのTorqueの使用:型の違い、Smiとヒープ数の処理、条件分岐。
Math
オブジェクトへのCSA組み込み関数のインストール。
ローカルで追跡したい場合は、次のコードはリビジョン589af9f257166f66774b4fb3008cd09f192c2614に基づいています。
MathIs42
の定義 #
Torqueコードは、src/builtins/*.tq
ファイルに配置され、おおよそトピック別に整理されています。Math
組み込み関数を記述するので、その定義をsrc/builtins/math.tq
に配置します。このファイルはまだ存在しないため、BUILD.gn
のtorque_files
に追加する必要があります。
namespace math {
javascript builtin MathIs42(
context: Context, receiver: Object, x: Object): Boolean {
// At this point, x can be basically anything - a Smi, a HeapNumber,
// undefined, or any other arbitrary JS object. ToNumber_Inline is defined
// in CodeStubAssembler. It inlines a fast-path (if the argument is a number
// already) and calls the ToNumber builtin otherwise.
const number: Number = ToNumber_Inline(x);
// A typeswitch allows us to switch on the dynamic type of a value. The type
// system knows that a Number can only be a Smi or a HeapNumber, so this
// switch is exhaustive.
typeswitch (number) {
case (smi: Smi): {
// The result of smi == 42 is not a Javascript boolean, so we use a
// conditional to create a Javascript boolean value.
return smi == 42 ? True : False;
}
case (heapNumber: HeapNumber): {
return Convert<float64>(heapNumber) == 42 ? True : False;
}
}
}
}
Torque名前空間math
に定義を配置します。この名前空間は以前は存在しなかったため、BUILD.gn
のtorque_namespaces
に追加する必要があります。
Math.is42
の添付 #
Math
などの組み込みオブジェクトは、主にsrc/bootstrapper.cc
で設定されます(一部の設定は.js
ファイルで行われます)。新しい組み込み関数の添付は簡単です。
// Existing code to set up Math, included here for clarity.
Handle<JSObject> math = factory->NewJSObject(cons, TENURED);
JSObject::AddProperty(global, name, math, DONT_ENUM);
// […snip…]
SimpleInstallFunction(isolate_, math, "is42", Builtins::kMathIs42, 1, true);
is42
が添付されたので、JSから呼び出すことができます。
$ out/debug/d8
d8> Math.is42(42);
true
d8> Math.is42('42.0');
true
d8> Math.is42(true);
false
d8> Math.is42({ valueOf: () => 42 });
true
スタブリンケージによる組み込み関数の定義と呼び出し #
組み込み関数は、(上記のMathIs42
で使用したJSリンケージではなく)スタブリンケージを使用して作成することもできます。このような組み込み関数は、一般的に使用されるコードを別のコードオブジェクトに抽出するために役立ち、複数の呼び出し元で使用できますが、コードは一度だけ生成されます。ヒープ数を処理するコードをHeapNumberIs42
という別の組み込み関数に抽出し、MathIs42
から呼び出してみましょう。
定義も簡単です。JavaScriptリンケージを持つ組み込み関数との唯一の違いは、キーワードjavascript
を省略し、レシーバー引数がないことです。
namespace math {
builtin HeapNumberIs42(implicit context: Context)(heapNumber: HeapNumber):
Boolean {
return Convert<float64>(heapNumber) == 42 ? True : False;
}
javascript builtin MathIs42(implicit context: Context)(
receiver: Object, x: Object): Boolean {
const number: Number = ToNumber_Inline(x);
typeswitch (number) {
case (smi: Smi): {
return smi == 42 ? True : False;
}
case (heapNumber: HeapNumber): {
// Instead of handling heap numbers inline, we now call our new builtin.
return HeapNumberIs42(heapNumber);
}
}
}
}
そもそも組み込み関数を気にするべき理由は何でしょうか?コードをインラインのままにする(または可読性を向上させるためにマクロに抽出する)のはなぜでしょうか?
重要な理由の1つはコードサイズです。組み込み関数はコンパイル時に生成され、V8スナップショットに含まれるか、バイナリに埋め込まれます。一般的に使用される大規模なコードを個別の組み込み関数に抽出すると、10KBから100KBの範囲で迅速に容量を削減できます。
スタブリンケージ組み込み関数のテスト #
新しい組み込み関数は標準的ではない(少なくともC++ではない)呼び出し規約を使用していますが、テストケースを作成することは可能です。次のコードをtest/cctest/compiler/test-run-stubs.cc
に追加して、すべてのプラットフォームで組み込み関数をテストできます。
TEST(MathIsHeapNumber42) {
HandleAndZoneScope scope;
Isolate* isolate = scope.main_isolate();
Heap* heap = isolate->heap();
Zone* zone = scope.main_zone();
StubTester tester(isolate, zone, Builtins::kMathIs42);
Handle<Object> result1 = tester.Call(Handle<Smi>(Smi::FromInt(0), isolate));
CHECK(result1->BooleanValue());
}