V8 Torque組み込み関数

このドキュメントは、Torque組み込み関数の記述に関する入門として、V8開発者を対象としています。Torqueは、新しい組み込み関数を実装するための推奨方法として、CodeStubAssemblerに取って代わります。このガイドのCSAバージョンについては、CodeStubAssembler組み込み関数を参照してください。

組み込み関数 #

V8では、組み込み関数は、ランタイム時にVMによって実行可能なコードの塊と見なすことができます。一般的なユースケースとしては、組み込みオブジェクト(RegExpPromiseなど)の関数を実装することですが、組み込み関数は、他の内部機能(例:ICシステムの一部として)を提供するためにも使用できます。

V8の組み込み関数は、いくつかの異なる方法(それぞれトレードオフが異なります)を使用して実装できます。

以降のドキュメントでは、後者を中心に説明し、JavaScriptに公開される単純なTorque組み込み関数の開発に関する簡単なチュートリアルを示します。Torqueに関するより完全な情報については、V8 Torqueユーザーマニュアルを参照してください。

Torque組み込み関数の記述 #

このセクションでは、単一の引数を取り、それが数値42を表しているかどうかを返す単純なCSA組み込み関数を記述します。組み込み関数は、(可能なので)MathオブジェクトにインストールすることでJSに公開されます。

この例では、以下を示します。

ローカルで追跡したい場合は、次のコードはリビジョン589af9f257166f66774b4fb3008cd09f192c2614に基づいています。

MathIs42の定義 #

Torqueコードは、src/builtins/*.tqファイルに配置され、おおよそトピック別に整理されています。Math組み込み関数を記述するので、その定義をsrc/builtins/math.tqに配置します。このファイルはまだ存在しないため、BUILD.gntorque_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.gntorque_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());
}