V8バイトコードを理解する方法

V8は、GoogleのオープンソースJavaScriptエンジンです。 Chrome、Node.js、および他の多くのアプリケーションで使用されます。 Googleの従業員Francisco Hinkelmannが作成したこの資料では、V8バイトコード形式について説明しています。 いくつかの基本的なことを理解すれば、バイトコードは非常に読みやすくなります。

画像

V8コンパイルパイプライン



点火! スタート! Ignitionインタープリターは、その名前を「点火」と翻訳することができ、2016年からV8コンパイルパイプラインの一部となっています。

V8がJavaScriptコードをコンパイルすると、パーサーは抽象構文ツリーを生成します。 構文ツリーは、JSコードの構文構造のツリービューです。 Ignitionインタープリターは、このデータ構造からバイトコードを生成します。 最適化TurboFanコンパイラは、最終的にバイトコードから最適化されたマシンコードを生成します。


V8コンパイルパイプライン

V8に2つの実行モードがある理由を知りたい場合は、JSConfEUでの私のパフォーマンスを見てください。

V8バイトコードの基本


バイトコードはマシンコードの抽象化です 。 バイトコードが物理プロセッサで使用されているのと同じ計算モデルを使用して設計されている場合、バイトコードをマシンコードにコンパイルするのは簡単です。 そのため、通訳者はしばしばマシンを登録またはスタックします。

Ignitionインタープリターは、累積レジスターを持つレジスターマシンです。


左のコードは人々にとって便利です。 右側のコード-車用

V8バイトコードは、JavaScript機能を実装できる小さなビルディングブロックと考えることができます。 V8には数百のバイトコードがあります。 AddTypeOfなどのステートメント、またはLdaNamedPropertyなどのプロパティをロードするためのコードがあります。 V8には、 CreateObjectLiteralSuspendGeneratorなど、かなり具体的なバイトコードもあります。 bytecodes.hヘッダーファイルには、V8バイトコードの完全なリストがあります。

各バイトコードは、入力および出力データをレジスタオペランドとして定義します。 Ignitionは、レジスターr0, r1, r2, ... 、および累積レジスターを使用します。 ほとんどすべてのバイトコードはストレージレジスタを使用します。 通常の場合と似ていますが、バイトコードで明示的に示されていない点が異なります。 たとえば、 Add r1コマンドは、レジスタr1値を累積レジスタに格納されている値に追加します。 これにより、バイトコードが短くなり、メモリが節約されます。

多くのバイトコード名はLdaまたはSta始まります。 LdaおよびStaの文字aは、累積器(累積レジスタ)の略語です。

たとえば、 LdaSmi [42]コマンドLdaSmi [42]は、小さな整数(Small Integer、Smi) 42を累積レジスタにロードします。 Star r0コマンドは、累算レジスタにある値をr0レジスタに書き込みます。

機能バイトコード分析


ここで、基本的な概念を検討した後、実際の関数のバイトコードを見てみましょう。

 function incrementX(obj) { return 1 + obj.x; } incrementX({x: 42});  //  V8 , ,     ,      

JavaScriptコードのバイトコードを表示したい場合は、 --print-bytecodeフラグを指定してD8またはNode.js デバッガー (バージョン8.3以降)を呼び出し表示できます。 Chromeの場合、コマンドラインからキー--js-flags="--print-bytecode"ます。 これがChromiumのキーコールです

 $ node --print-bytecode incrementX.js ... [generating bytecode for function: incrementX] Parameter count 2 Frame size 8 12 E> 0x2ddf8802cf6e @    StackCheck 19 S> 0x2ddf8802cf6f @    LdaSmi [1]       0x2ddf8802cf71 @    Star r0 34 E> 0x2ddf8802cf73 @    LdaNamedProperty a0, [0], [4] 28 E> 0x2ddf8802cf77 @    Add r0, [6] 36 S> 0x2ddf8802cf7a @    Return Constant pool (size = 1) 0x2ddf8802cf21: [FixedArray] in OldSpace - map = 0x2ddfb2d02309 <Map(HOLEY_ELEMENTS)> - length: 1          0: 0x2ddf8db91611 <String[1]: x> Handler Table (size = 16) 

バイトコードに焦点を当てて、このデータのかなりの部分を無視できます。 ここに表示される内容の説明を示します。

LdaSmi [1]

LdaSmi [1]コマンドは、定数1を累積レジスタにロードします。



スターr0

Star r0コマンドは、累積レジスタの値、つまり1r0レジスタに書き込みます。



LdaNamedProperty a0、[0]、[4]

LdaNamedPropertyコマンドは、名前付きプロパティa0を累積レジスターにロードします。 aiコンストラクトは、 incrementX()関数のi番目の引数を参照しincrementX() 。 この例では、 a0の名前付きプロパティ、つまりincrementX()最初の引数にアクセスします。 名前は定数0によって決定され0LdaNamedProperty0を使用して、別のテーブルで名前を検索します。

 - length: 1          0: 0x2ddf8db91611 <String[1]: x> 

ここで0 xマッピングされます。 その結果、このバイトコードがobj.xロードすることがobj.x

4オペランドは何に使用されますか? これは、 increment(x)いわゆるフィードバックベクトルのインデックスです。 フィードバックベクトルには、パフォーマンスを最適化するために使用されるランタイム情報が含まれています。

現在、レジスタの内容は次のとおりです。


r0を追加、[6]

最後の命令はr0の内容を累積レジスタに追加し、最終的な値は43ます。 番号6 — 、フィードバックベクトルの別のインデックスです。



戻る

Returnコマンドは、累積レジスタの内容を返します。 これは、 incrementX()関数の完了です。 incrementX()原因は、累積レジスタの番号43から始まり、この値でいくつかのアクションを実行し続けることができます。

この素材が専用のバイトコードは、V8バージョン6.2、Chrome 62、およびまだリリースされていないノード9で使用されていることに注意してください。 V8の他のバージョンでは、ここで説明したものとバイトコードにいくつかの違いがある場合があります。

まとめ


一見すると、V8バイトコードは、特に大量の追加情報が表示されている場合は、かなりわかりにくいように見えます。 ただし、Ignitionが累積レジスタを持つレジスタマシンであることがわかると、ほとんどのバイトコードの目的を理解できます。

親愛なる読者! JSプログラムのバイトコードを分析する予定ですか?

Source: https://habr.com/ru/post/J336294/


All Articles