Node.jsをインストールしてコマンドプロンプト(もしくはWindows Power Shell)から以下のコマンドを打ってください。
# node --print-bytecode <ファイル名>.js
基本的なことですが、JavaScriptはスクリプト型言語だが、内部ではバイトコードが生成されて、そのバイトコードがさらにネイティブなマシンコードに変換され高速に動作するようになっています。
バイトコードは仮想的なマシンコードでレジスタをもつスタックマシンとして動作します。
まあ堅苦しいことは抜きにして、実際に簡単なサンプルコードで早速バイトコードを読んでみましょう。
なおここで言っているバイトコードとは、V8のバイトコードになります。
V8はGoogleのオープンソースJavaScriptエンジンです。Chrome、Node.js、およびその他の多くのアプリケーションでV8が使用されています。
■簡単なJavaScriptのサンプルコードとそのバイトコードの対応
【JavaScriptコード】
1 var a=1999;
2 var b=3121;
3 var c=659;
4 function muladd1(x,y,z) {
5 let r=x+y*z;
6 return r;
7 }
8 function muladd2(x,y,z) {
9 let r=x*y+z;
10 return r;
11 }
12 function func_example(x,y,z) {
13 var d=muladd1(x,y,z)+muladd2(x,y,z);
14 if (d==341) {
15 return 333;
16 }
17 return 777;
18
19 }
20
21 var e=func_example(a,b,c);
22 console.log(e);
【バイトコード】
1行目~3行目、21行目~22行目
1行目~3行目、21行目~22行目
StackCheck # スタックチェック
LdaSmi.Wide [1999] # 即値1999をアキュムレータレジスタにロードする
StaCurrentContextSlot [4] # アキュムレータレジスタからCurrentContextSlot[4]にストア
LdaSmi.Wide [3121] # 即値3121をアキュムレータレジスタにロードする
StaCurrentContextSlot [5] # アキュムレータレジスタからCurrentContextSlot[5]にストア
LdaSmi.Wide [659] # 即値659をアキュムレータレジスタにロードする
StaCurrentContextSlot [6] # アキュムレータレジスタからCurrentContextSlot[6]にストア
LdaImmutableCurrentContextSlot [4] # CurrentContextSlot[4]からアキュムレータレジスタにロードする
Star r4 # アキュムレータレジスタからレジスタr4にストア
LdaImmutableCurrentContextSlot [5] # CurrentContextSlot[5]からアキュムレータレジスタにロードする
Star r5 # アキュムレータレジスタからレジスタr5にストア
LdaImmutableCurrentContextSlot [6] # CurrentContextSlot[6]からアキュムレータレジスタにロードする
Star r6 # アキュムレータレジスタからレジスタr6にストア
CallUndefinedReceiver r0, r4-r6, [0] # func_example関数(レジスタr0にアドレスが入っている)を呼び出す
# 実引数リストは、r4-r6番に入っている
Star r1 # 返却値が入っているアキュムレータレジスタをレジスタr1にストア
LdaGlobal [4], [2] # グローバル変数eのアドレスをアキュムレータレジスタにロード
Star r4 # アキュムレータレジスタをレジスタr4にストア
LdaNamedProperty r4, [5], [4] # 名前付けられた属性(r4)をアキュムレートレジスタにロード
Star r3 # console.log関数のアドレスをレジスタr3にストア
CallProperty1 r3, r4, r1, [6] # レジスタr4の属性でレジスタr1のある値を引数にしてレジスタr3の関数を呼び出す
LdaUndefined # 定数"undefined"をアキュムレータレジスタにロードする
Return # アキュムレータレジスタの値を返却する
Constant pool (size = 6)
[generated bytecode for function: muladd1]
Parameter count 4
Register count 1
Frame size 8
StackCheck # スタックチェック
Ldar a2 # 第3引数が入ったレジスタa2をアキュムレートレジスタにロードする。
Mul a1, [1] # アキュムレータレジスタと第2引数が入ったレジスタa1を掛け算して
# アキュムレータレジスタに結果を格納する
Add a0, [0] # アキュムレータレジスタと第1引数が入ったレジスタa0を足し算して
# アキュムレータレジスタに結果を格納する
Star r0 # アキュムレータレジスタの値をレジスタr0にストアする
Return # アキュムレータレジスタの値を返却する
Constant pool (size = 0)
Handler Table (size = 0)
[generated bytecode for function: muladd2]
Parameter count 4
Register count 2
Frame size 16
StackCheck # スタックチェック
Ldar a1 # 第2引数が入ったレジスタa1をアキュムレートレジスタにロードする。
Mul a0, [1] # アキュムレータレジスタと第1引数が入ったレジスタa0を掛け算して
# アキュムレータレジスタに結果を格納する
Star r1 # アキュムレータレジスタをレジスタr1にストアする
Ldar a2 # 第3引数が入ったレジスタa2をアキュムレートレジスタにロードする。
Add r1, [0] # アキュムレータレジスタとレジスタr1を足し算して
# アキュムレータレジスタに結果を格納する
Star r0 # アキュムレータレジスタの値をレジスタr0にストアする
Return # アキュムレータレジスタの値を返却する
Constant pool (size = 0)
Handler Table (size = 0)
[generated bytecode for function: func_example]
Parameter count 4
Register count 6
Frame size 48
StackCheck # スタックチェック
LdaImmutableCurrentContextSlot [4] # CurrentContextSlot[4](muladd1の関数アドレス)からアキュムレータレジスタにロードする
Star r1 # アキュムレータレジスタをレジスタr1にストアする
Mov a0, r2 # 第1引数が入ったレジスタa0をレジスタr2に移動する
Mov a1, r3 # 第2引数が入ったレジスタa1をレジスタr3に移動する
Mov a2, r4 # 第3引数が入ったレジスタa2をレジスタr4に移動する
CallUndefinedReceiver r1, r2-r4, [1] # muladd1関数(レジスタr1にアドレスが入っている)を呼び出す
# 実引数リストは、r2-r4番に入っている
Star r1 # 返却値が入っているアキュムレータレジスタをレジスタr1にストア
LdaImmutableCurrentContextSlot [5] # CurrentContextSlot[5](muladd2の関数アドレス)からアキュムレータレジスタにロードする
Star r2 # アキュムレータレジスタからレジスタr2にストア
Mov a0, r3 # 第1引数が入ったレジスタa0をレジスタr3に移動する
Mov a1, r4 # 第2引数が入ったレジスタa1をレジスタr4に移動する
Mov a2, r5 # 第3引数が入ったレジスタa2をレジスタr5に移動する
CallUndefinedReceiver r2, r3-r5, [3] # muladd2関数(レジスタr2にアドレスが入っている)を呼び出す
# 実引数リストは、r3-r5番に入っている
Add r1, [0] # アキュムレータレジスタとレジスタr1を足し算して
# アキュムレータレジスタに結果を格納する
Star r0 # 返却値が入っているアキュムレータレジスタをレジスタr0にストア
LdaSmi.Wide [341] # 即値341をアキュムレータレジスタにロードする
TestEqual r0, [5] # アキュムレータレジスタとレジスタr0が等しいかテストする
JumpIfFalse [7] (000000AA7251FD80 @ 58) # もし比較結果がFALSEなら000000AA7251FD80番地にジャンプする
LdaSmi.Wide [333] # 即値333をアキュムレータレジスタにロードする
Return # アキュムレータレジスタの値を返却する
000000AA7251FD80 @ 58
LdaSmi.Wide [777] # 即値777をアキュムレータレジスタにロードする
Return # アキュムレータレジスタの値を返却する
Constant pool (size = 0)
Handler Table (size = 0)
大分、JavaScriptのバイトコードに慣れてきたのではないでしょうか。
JavaScriptのバイトコードに関する仕様書やドキュメントらしきものはないため、何かのお役に立てばよいかと思っています。
■各バイトコード命令の簡単な意味
●StackCheck
スタックポインタがスタックのlimit値を超えているかどうかチェックする命令
・・・工事中・・・
■その他
●意味不明な[数値]
関数のいわゆるフィードバックベクトルのインデックス。
フィードバックベクトルには、パフォーマンスの最適化に使用される実行時情報が含まれているが、バイトコードを読む分にはとりあえずは無視してよい。
■参考文献
・Understanding V8’s Bytecode by @fhinkel
・Ignition: V8 Interpreter
・V8 Iginition Interpreter - abcdefGets
・The Journey of the Code