JavaScriptのバイトコードを読んでみよう
以前に私は以下のブログ記事で、JavaScriptのバイトコードの出力方法について書いた。
・JavaScriptのバイトコード(逆アセンブル疑似コード)の見方
今度は、実際に読んでみようということで少しずつだけど、ブログ記事を書いておこうと思う。
まず基本的なことだが、JavaScriptはスクリプト型言語だが、内部ではバイトコードが生成されて
そのバイトコードがさらにネイティブなマシンコードに変換され高速に動作するようになっている。
バイトコードは仮想的なマシンコードでレジスタをもつスタックマシンとして動作する。
まあ堅苦しいことは抜かして、実際に簡単なサンプルコードで早速バイトコードを読んでみよう。
なおここで言っているバイトコードとは、V8のバイトコードになります。
V8はGoogleのオープンソースJavaScriptエンジンです。Chrome、Node.js、およびその他の多くの
アプリケーションでV8が使用されています。
■簡単なJavaScriptのサンプルコードとそのバイトコードの対応
【JavaScriptコード】
【バイトコード】
1行目~3行目、21行目~22行目
大分、JavaScriptのバイトコードに慣れてきたのではないだろうか。
JavaScriptのバイトコードに関する仕様書やドキュメントらしきものはないため、何かのお役に立てばよいかと思う。
■各バイトコード命令の簡単な意味
●StackCheck
スタックポインタがスタックのlimit値を超えているかどうかチェックする命令
・・・工事中・・・
■その他
●意味不明な[数値]
関数のいわゆるフィードバックベクトルのインデックス。
フィードバックベクトルには、パフォーマンスの最適化に使用される実行時情報が含まれているが、
バイトコードを読む分にはとりあえずは無視してよい。
■参考文献
・Understanding V8’s Bytecode by @fhinkel
・Ignition: V8 Interpreter
・Speculative Optimization in V8--An Introduction to Speculative Optimization in V8
■変更歴
ver0.0 初版(2021/03/11):今後も拡張予定
以前に私は以下のブログ記事で、JavaScriptのバイトコードの出力方法について書いた。
・JavaScriptのバイトコード(逆アセンブル疑似コード)の見方
今度は、実際に読んでみようということで少しずつだけど、ブログ記事を書いておこうと思う。
まず基本的なことだが、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行目
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
・Speculative Optimization in V8--An Introduction to Speculative Optimization in V8
■変更歴
ver0.0 初版(2021/03/11):今後も拡張予定