はじめに
今更ですが、V8のソースコード読解とデバッグをはじめてしばらく経過したので、ちょっとつまずいた部分などをメモしていきます。なお、V8(d8)のバージョンは、13.3.0をソースコードからビルドしました。
仮想マシンの実装方法
Ignitionで生成されたバイトコードがどのように実行されているかを確認しようとしたのですが、 https://github.com/v8/v8/tree/main/src/interpreter
のフォルダにあるファイルにブレークポイントを設定しても、実行されている形跡がありません。
調べているうちに、どうやら、このコードは、V8がコンパイルされたときに、すぐに実行され、ビルドした環境の機械語のみが直接、V8に組み込まれるようでした。
組み込まれたソースコードの一覧は、d8 --log-code などと打つと、v8.logファイルに組み込まれた機械語の関数一覧が表示されます。
該当行は、ログファイルの行先頭にcode-creationと書かれていて、その次に、Builtinまたは、BytecodeHandlerと書かれています。
...
new,MemoryChunk,0xbdd00000000,262144
code-creation,Builtin,2,142921,0x7fff7f2c0000,1028,DeoptimizationEntry_Eager
...
code-creation,BytecodeHandler,0,191851,0x7fff7fd90d40,304,Wide
...
BytecodeHandlerですが、これは、Ignitionが生成したバイトコードの命令と対応しているようです。バイトコードが読み取られた後に、BytecodeHandlerのアドレスが、ある関数テーブルにあり、バイトコードの命令を指す最初の1バイトが直接BytecodeHandlerのアドレスを指し示すようになっているようです。
code-creation,BytecodeHandler,0,191851,0x7fff7fd90d40,304,Wide
code-creation,BytecodeHandler,0,191874,0x7fff7fd90e80,304,ExtraWide
code-creation,BytecodeHandler,0,191897,0x7fff7fd90fc0,560,DebugBreakWide
code-creation,BytecodeHandler,0,191919,0x7fff7fd91200,560,DebugBreakExtraWide
code-creation,BytecodeHandler,0,191942,0x7fff7fd91440,664,DebugBreak0
以下は、デバッガによる関数テーブル先頭からのメモリの内容です。
-exec x/500x 0x55555572d820
0x55555572d820: 0x7fd90d40 0x00007fff 0x7fd90e80 0x00007fff
0x55555572d830: 0x7fd90fc0 0x00007fff 0x7fd91200 0x00007fff
0x55555572d840: 0x7fd91440 0x00007fff 0x7fd91700 0x00007fff
0x55555572d850: 0x7fd91940 0x00007fff 0x7fd91b80 0x00007fff
0x55555572d860: 0x7fd91dc0 0x00007fff 0x7fd92000 0x00007fff
0x55555572d870: 0x7fd92240 0x00007fff 0x7fd92480 0x00007fff
0x55555572d880: 0x7fd925c0 0x00007fff 0x7fd92780 0x00007fff
0x55555572d890: 0x7fd92980 0x00007fff 0x7fd92b40 0x00007fff
...
BytecodeHandlerの置かれたアドレスと関数テーブルのアドレスが一致していることが確認できました。
InterpreterEntryTrampolineの実装
次に、Builtinですが、これは、個々の関数毎色々役割があります。BytecodeHandlerから呼ばれるものが多いですが、個々のBytecodeHandlerを呼び出すCallerとしての役割である、InterpreterEntryTrampolineの機械語について見ておくとよいでしょう。
InterpreterEntryTrampoline
# 0xf may be JSFunction::kSharedFunctionInfoOffset
# r11 is shared_function_info
0x7fff7f30cec0: mov r11d,DWORD PTR [rdi+0xf]
0x7fff7f30cec4: add r11,r14
# ResetSharedFunctionInfoAge
# 0x2b is SharedFunctionInfo::kAgeOffset
0x7fff7f30cec7: mov WORD PTR [r11+0x2b],0x0
# GetSharedFunctionInfoBytecodeOrBaseline
0x7fff7f30cece: mov r10d,DWORD PTR [r11+0x3]
## CheckSharedFunctionInfoBytecodeOrBaseline
0x7fff7f30ced2: test r10b,0x1
0x7fff7f30ced6: je 0x7fff7f30cef2
0x7fff7f30ced8: mov r12,QWORD PTR [r13+0x26e8]
0x7fff7f30cedf: shr r10d,0x9
0x7fff7f30cee3: shl r10d,0x4
0x7fff7f30cee7: mov r12,QWORD PTR [r12+r10*1+0x8]
0x7fff7f30ceec: or r12,0x1
0x7fff7f30cef0: jmp 0x7fff7f30cf0e
0x7fff7f30cef2: shr r10d,0x9
0x7fff7f30cef6: mov r12,QWORD PTR [r13+0x218]
0x7fff7f30cefd: mov r12,QWORD PTR [r12+r10*8]
0x7fff7f30cf01: movabs r10,0x80ffffffffffff
0x7fff7f30cf0b: and r12,r10
0x7fff7f30cf0e: mov r10d,DWORD PTR [r12-0x1]
0x7fff7f30cf13: cmp r10d,0xd59
0x7fff7f30cf1a: jne 0x7fff7f30cf42
0x7fff7f30cf20: mov r10d,DWORD PTR [r12+0x1b]
0x7fff7f30cf25: and r10,0xf
0x7fff7f30cf29: cmp r10d,0xa
0x7fff7f30cf2d: je 0x7fff7f30cf3c
0x7fff7f30cf2f: mov edx,0x16
0x7fff7f30cf34: call QWORD PTR [r13+0x57b0] # Abort
0x7fff7f30cf3b: int3
0x7fff7f30cf3c: je 0x7fff7f30d185
0x7fff7f30cf42: cmp r10d,0x20a5
0x7fff7f30cf49: jne 0x7fff7f30cf57
0x7fff7f30cf4b: mov r12d,DWORD PTR [r12+0x7]
0x7fff7f30cf50: or r12,QWORD PTR [r13+0x210]
0x7fff7f30cf57: mov r10d,DWORD PTR [r12-0x1]
0x7fff7f30cf5c: cmp r10d,0x941
0x7fff7f30cf63: jne 0x7fff7f30d10b
############################################################
# LoadParameterCountFromJSDispatchTable
# r13+0x26f8 is ExternalReference::js_dispatch_table_address
0x7fff7f30cf69: mov r10,QWORD PTR [r13+0x26f8]
0x7fff7f30cf70: mov r8,r15
0x7fff7f30cf73: shr r8d,0x9
0x7fff7f30cf77: shl r8d,0x4
0x7fff7f30cf7b: movzx r8d,WORD PTR [r10+r8*1+0x8]
# 0x1f is BytecodeArray::kParameterSizeOffset
0x7fff7f30cf81: cmp r8w,WORD PTR [r12+0x1f]
0x7fff7f30cf87: je 0x7fff7f30cf96
0x7fff7f30cf89: mov edx,0x9a
0x7fff7f30cf8e: call QWORD PTR [r13+0x57b0] # Abort
0x7fff7f30cf95: int3
# LoadFeedBackVector ???
0x7fff7f30cf96: mov ebx,DWORD PTR [rdi+0x17]
0x7fff7f30cf99: mov ebx,DWORD PTR [r14+rbx*1+0x3]
0x7fff7f30cf9e: add rbx,r14
0x7fff7f30cfa1: mov ecx,DWORD PTR [rbx-0x1]
0x7fff7f30cfa4: cmp ecx,0x801
0x7fff7f30cfaa: je 0x7fff7f30cfb2
0x7fff7f30cfac: lea rbx,[r14+0x11]
0x7fff7f30cfb0: jmp 0x7fff7f30cfc1
# ResetFeedbackVectorOsrUrgency
# 0xc is FeedbackVector::kOsrStateOffset
0x7fff7f30cfb2: mov r10b,BYTE PTR [rbx+0xc]
# 0x8 is Immediate(~FeedbackVector::OsrUrgencyBits::kMask)
0x7fff7f30cfb6: and r10b,0xf8
0x7fff7f30cfba: mov BYTE PTR [rbx+0xc],r10b
# 0x7 may be FeedbackVector::kInvocationCountOffset
0x7fff7f30cfbe: inc DWORD PTR [rbx+0x7]
0x7fff7f30cfc1: push rbp
0x7fff7f30cfc2: mov rbp,rsp
0x7fff7f30cfc5: push rsi # kContextRegister
0x7fff7f30cfc6: push rdi # kJavaScriptCallTargetRegister
0x7fff7f30cfc7: push rax # kJavaScriptCallArgCountRegister
# 0x27 is BytecodeArray::kHeaderSize(0x28) - kHeapObjectTag(1)
# r9 is kInterpreterBytecodeOffsetRegister
0x7fff7f30cfc8: mov r9d,0x27
# r12 is kInterpreterBytecodeArrayRegister
0x7fff7f30cfce: push r12
0x7fff7f30cfd0: mov ecx,r9d
0x7fff7f30cfd3: add ecx,ecx
0x7fff7f30cfd5: rol rcx,0x20
0x7fff7f30cfd9: xor rcx,0x515151
0x7fff7f30cfe0: ror rcx,0x20
#
0x7fff7f30cfe4: push rcx
# rbx is FeedbackVector
0x7fff7f30cfe5: push rbx
0x7fff7f30cfe6: mov ecx,DWORD PTR [r12+0x1b]
0x7fff7f30cfeb: mov rax,rsp
0x7fff7f30cfee: sub rax,rcx
0x7fff7f30cff1: cmp rax,QWORD PTR [r13-0x70]
0x7fff7f30cff5: jb 0x7fff7f30d1fe
0x7fff7f30cffb: lea rax,[r14+0x11]
0x7fff7f30cfff: jmp 0x7fff7f30d002
0x7fff7f30d001: push rax
0x7fff7f30d002: sub rcx,0x8
0x7fff7f30d006: jge 0x7fff7f30d001
0x7fff7f30d008: movsxd rcx,DWORD PTR [r12+0x23]
0x7fff7f30d00d: test ecx,ecx
0x7fff7f30d00f: je 0x7fff7f30d016
0x7fff7f30d011: mov QWORD PTR [rbp+rcx*8+0x0],rdx
0x7fff7f30d016: cmp rsp,QWORD PTR [r13-0x60]
0x7fff7f30d01a: jb 0x7fff7f30d0c8
# load the head address of byte code handler table
0x7fff7f30d020: mov r15,QWORD PTR [r13+0x4f78]
# r12 + r9 will be the offset to the bytecode.
# read the bytecode to jump to bytecode handler.
0x7fff7f30d027: movzx r10d,BYTE PTR [r12+r9*1]
# r15 is a head of function pointer
# r10 is an offset to the bytecode handler to be called.
0x7fff7f30d02c: mov rcx,QWORD PTR [r15+r10*8]
############################################################
=> 0x7fff7f30d030: call rcx
############################################################
0x7fff7f30d032: mov r12,QWORD PTR [rbp-0x20]
## if it is 0, will abort.
0x7fff7f30d036: mov r9d,DWORD PTR [rbp-0x28]
0x7fff7f30d03a: test r9d,0x10000000
0x7fff7f30d041: je 0x7fff7f30d050
0x7fff7f30d043: mov edx,0x4
# Abort
0x7fff7f30d048: call QWORD PTR [r13+0x57b0]
0x7fff7f30d04f: int3
##########
0x7fff7f30d050: shr r9d,1
# r12 + r9d should be the next bytecode to be executed.
0x7fff7f30d053: movzx ebx,BYTE PTR [r12+r9*1]
0x7fff7f30d058: mov r8,r9
0x7fff7f30d05b: mov rcx,QWORD PTR [r13+0x1e90]
0x7fff7f30d062: cmp bl,0x3
0x7fff7f30d065: ja 0x7fff7f30d084
0x7fff7f30d067: inc r9d
0x7fff7f30d06a: test bl,0x1
0x7fff7f30d06d: movzx ebx,BYTE PTR [r12+r9*1]
0x7fff7f30d072: jne 0x7fff7f30d07d
0x7fff7f30d074: add rcx,0xd0
0x7fff7f30d07b: jmp 0x7fff7f30d084
0x7fff7f30d07d: add rcx,0x1a0
0x7fff7f30d084: cmp bl,0xb3
0x7fff7f30d087: je 0x7fff7f30d0ad
0x7fff7f30d08d: cmp bl,0xb9
0x7fff7f30d090: je 0x7fff7f30d0ad
0x7fff7f30d096: cmp bl,0x92
0x7fff7f30d099: jne 0x7fff7f30d0a0
0x7fff7f30d09b: mov r9,r8
0x7fff7f30d09e: jmp 0x7fff7f30d0a8
0x7fff7f30d0a0: movzx r10d,BYTE PTR [rcx+rbx*1]
0x7fff7f30d0a5: add r9d,r10d
0x7fff7f30d0a8: jmp 0x7fff7f30d020
0x7fff7f30d0ad: mov rbx,QWORD PTR [rbp-0x20]
0x7fff7f30d0b1: movzx ebx,WORD PTR [rbx+0x1f]
0x7fff7f30d0b5: mov rcx,QWORD PTR [rbp-0x18]
0x7fff7f30d0b9: cmp rbx,rcx
0x7fff7f30d0bc: cmovl rbx,rcx
0x7fff7f30d0c0: leave
0x7fff7f30d0c1: pop rcx
0x7fff7f30d0c2: lea rsp,[rsp+rbx*8]
0x7fff7f30d0c6: push rcx
0x7fff7f30d0c7: ret
############################################################
0x7fff7f30d0c8: mov r10d,0x4c
0x7fff7f30d0ce: mov QWORD PTR [rbp-0x28],r10
0x7fff7f30d0d2: xor eax,eax
0x7fff7f30d0d4: mov rbx,QWORD PTR [r13+0x40f8] # rbx=0x7ffff4c6
0x7fff7f30d0db: call 0x7fff7f744540 # CEntry_Return1_ArgvOnStack_NoBuiltinExit
# _ZN2v88internal18Runtime_StackGuardEiPmPNS0_7IsolateE
0x7fff7f30d0e0: mov r12,QWORD PTR [rbp-0x20]
0x7fff7f30d0e4: mov r9d,0x27
0x7fff7f30d0ea: lea rax,[r14+0x11]
0x7fff7f30d0ee: mov ecx,r12d
0x7fff7f30d0f1: add ecx,ecx
0x7fff7f30d0f3: rol rcx,0x20
0x7fff7f30d0f7: xor rcx,0x515151
0x7fff7f30d0fe: ror rcx,0x20
0x7fff7f30d102: mov QWORD PTR [rbp-0x28],rcx
0x7fff7f30d106: jmp 0x7fff7f30d020
0x7fff7f30d10b: push rbp # // Caller's frame pointer.
0x7fff7f30d10c: mov rbp,rsp
0x7fff7f30d10f: push 0x2e
0x7fff7f30d111: push rdi
0x7fff7f30d112: push rdx
0x7fff7f30d113: add eax,eax
0x7fff7f30d115: rol rax,0x20
0x7fff7f30d119: xor rax,0x515151
0x7fff7f30d11f: ror rax,0x20
0x7fff7f30d123: push rax
0x7fff7f30d124: push r15
0x7fff7f30d126: push rdi
0x7fff7f30d127: mov eax,0x1
0x7fff7f30d12c: mov rbx,QWORD PTR [r13+0x3c78] # 0x7ffff4c3d720:CompileLazy
0x7fff7f30d133: call 0x7fff7f744540
0x7fff7f30d138: mov rcx,rax
0x7fff7f30d13b: pop r15
0x7fff7f30d13d: pop rax
0x7fff7f30d13e: test eax,0x10000000
0x7fff7f30d143: je 0x7fff7f30d152
0x7fff7f30d145: mov edx,0x4
0x7fff7f30d14a: call QWORD PTR [r13+0x57b0]
0x7fff7f30d151: int3
0x7fff7f30d152: shr eax,1
0x7fff7f30d154: pop rdx
0x7fff7f30d155: pop rdi
0x7fff7f30d156: cmp QWORD PTR [rbp-0x8],0x2e
0x7fff7f30d15b: je 0x7fff7f30d16a
0x7fff7f30d15d: mov edx,0x60
0x7fff7f30d162: call QWORD PTR [r13+0x57b0]
0x7fff7f30d169: int3
0x7fff7f30d16a: mov rsp,rbp
0x7fff7f30d16d: pop rbp
0x7fff7f30d16e: mov r10,QWORD PTR [r13+0x26e8]
0x7fff7f30d175: mov ecx,DWORD PTR [rcx+0x3]
0x7fff7f30d178: shr ecx,0x9
0x7fff7f30d17b: shl ecx,0x4
0x7fff7f30d17e: mov rcx,QWORD PTR [r10+rcx*1]
0x7fff7f30d182: jmp rcx
0x7fff7f30d184: int3
###################################################
0x7fff7f30d185: push rbp
0x7fff7f30d186: mov rbp,rsp
0x7fff7f30d189: push 0x2e
0x7fff7f30d18b: push rdi
0x7fff7f30d18c: push rdx
0x7fff7f30d18d: add eax,eax
0x7fff7f30d18f: rol rax,0x20
0x7fff7f30d193: xor rax,0x515151
0x7fff7f30d199: ror rax,0x20
0x7fff7f30d19d: push rax
0x7fff7f30d19e: push r15
0x7fff7f30d1a0: push rdi
0x7fff7f30d1a1: mov eax,0x1
マーク「=>」のある0x7fff7f30d030のcall rcx
から各BytecodeHandlerが呼ばれます。このrcxは前の命令を見ると、[r15+r10*8]
のメモリを読み取っています。このr15
が関数テーブルの先頭を指しています。r10
は、その前の命令を見ると、[r12+r9*1]
を読み取っていて、このアドレスは実は、現在読み込もうとしているバイトコードの命令の1バイト目を指しています。
この機械語を生成するソースコードはこちら(https://github.com/v8/v8/blob/main/src/builtins/x64/builtins-x64.cc#L1106) にありますが、ソースコードだけを読んでもなかなか分かりにくいところです。
なお、このTrampolineから全てのHandlerが呼ばれるわけではなく、BasicBlockの先頭の命令のみが呼ばれます。それ以外の命令に関しては、各命令列のHandlerから直接jmp命令を経由して鎖のように呼ばれます。
LdaZeroの実装
幾つかのHandlerの機械語を確認して、BytecodeHandlerがどのように実装されているかを見てみましょう。
LdaZero (0x0c)の命令は、アキュミュレータレジスタに0をロードする命令です。この命令の解釈には以下のような機械語が実装されていました。
LdaZero
0x7fff7fd925c0: lea rbx,[rip+0xfffffffffffffff9] # 0x7fff7fd925c0
0x7fff7fd925c7: cmp rbx,rcx
0x7fff7fd925ca: je 0x7fff7fd925d9
0x7fff7fd925cc: mov edx,0x88
0x7fff7fd925d1: call QWORD PTR [r13+0x57b0]
0x7fff7fd925d8: int3
0x7fff7fd925d9: push rbp
0x7fff7fd925da: mov rbp,rsp
0x7fff7fd925dd: push 0x24
0x7fff7fd925df: sub rsp,0x20
0x7fff7fd925e3: mov r10,rsp
0x7fff7fd925e6: sub rsp,0x8
0x7fff7fd925ea: and rsp,0xfffffffffffffff0
0x7fff7fd925ee: mov QWORD PTR [rsp],r10
#################################################
0x7fff7fd925f2: mov QWORD PTR [rbp-0x10],r12
0x7fff7fd925f6: mov QWORD PTR [rbp-0x18],r15
0x7fff7fd925fa: mov QWORD PTR [rbp-0x28],rax
0x7fff7fd925fe: mov QWORD PTR [rbp-0x20],r9
#################################################
0x7fff7fd92602: mov esi,0x20e
0x7fff7fd92607: mov rdx,QWORD PTR [r13+0x1b98]
0x7fff7fd9260e: mov edx,DWORD PTR [rdx+0x6627]
0x7fff7fd92614: add rdx,r14
0x7fff7fd92617: mov rdi,r12
0x7fff7fd9261a: mov rax,QWORD PTR [r13+0x1e98]
0x7fff7fd92621: test spl,0xf
0x7fff7fd92625: je 0x7fff7fd92628
0x7fff7fd92627: int3
0x7fff7fd92628: lea r10,[rip+0x10] # 0x7fff7fd9263f
0x7fff7fd9262f: mov QWORD PTR [r13+0xa8],r10
0x7fff7fd92636: mov QWORD PTR [r13+0xa0],rbp
0x7fff7fd9263d: call rax
0x7fff7fd9263f: mov QWORD PTR [r13+0xa0],0x0
0x7fff7fd9264a: mov rsp,QWORD PTR [rsp]
0x7fff7fd9264e: mov r10,rsp
0x7fff7fd92651: sub rsp,0x8
0x7fff7fd92655: and rsp,0xfffffffffffffff0
0x7fff7fd92659: mov QWORD PTR [rsp],r10
0x7fff7fd9265d: xor esi,esi
0x7fff7fd9265f: mov rdx,QWORD PTR [r13+0x1b98]
0x7fff7fd92666: mov edx,DWORD PTR [rdx+0x662b]
0x7fff7fd9266c: add rdx,r14
0x7fff7fd9266f: mov rdi,QWORD PTR [rbp-0x28]
0x7fff7fd92673: mov rax,QWORD PTR [r13+0x1e98]
0x7fff7fd9267a: test spl,0xf
0x7fff7fd9267e: je 0x7fff7fd92681
0x7fff7fd92680: int3
0x7fff7fd92681: lea r10,[rip+0x10] # 0x7fff7fd92698
0x7fff7fd92688: mov QWORD PTR [r13+0xa8],r10
0x7fff7fd9268f: mov QWORD PTR [r13+0xa0],rbp
0x7fff7fd92696: call rax
0x7fff7fd92698: mov QWORD PTR [r13+0xa0],0x0
0x7fff7fd926a3: mov rsp,QWORD PTR [rsp]
#################################################
0x7fff7fd926a7: mov rbx,QWORD PTR [rbp-0x20]
0x7fff7fd926ab: lea r9,[rbx+0x1]
0x7fff7fd926af: mov r12,QWORD PTR [rbp-0x10]
0x7fff7fd926b3: movzx r8d,BYTE PTR [r12+rbx*1+0x1]
#################################################
0x7fff7fd926b9: mov r10d,0xffffffff
0x7fff7fd926bf: cmp r8,r10
0x7fff7fd926c2: jbe 0x7fff7fd926d1
0x7fff7fd926c4: mov edx,0x2
0x7fff7fd926c9: call QWORD PTR [r13+0x57b0]
0x7fff7fd926d0: int3
0x7fff7fd926d1: cmp r8b,0xbf
0x7fff7fd926d5: jae 0x7fff7fd926eb
0x7fff7fd926d7: mov r15,QWORD PTR [rbp-0x18]
0x7fff7fd926db: mov rcx,QWORD PTR [r15+r8*8]
0x7fff7fd926df: mov rbp,QWORD PTR [rbp+0x0]
0x7fff7fd926e3: xor eax,eax
0x7fff7fd926e5: add rsp,0x30
0x7fff7fd926e9: jmp rcx
0x7fff7fd926eb: cmp r8b,0xce
0x7fff7fd926ef: ja 0x7fff7fd92737
0x7fff7fd926f1: mov r11,QWORD PTR [rbp+0x0]
0x7fff7fd926f5: mov QWORD PTR [r11+r8*8-0x6a8],0x0
0x7fff7fd92701: add r9,0x1
#################################################
0x7fff7fd92705: movzx ebx,BYTE PTR [r12+rbx*1+0x2]
0x7fff7fd9270b: mov r10d,0xffffffff
0x7fff7fd92711: cmp rbx,r10
0x7fff7fd92714: jbe 0x7fff7fd92723
0x7fff7fd92716: mov edx,0x2
0x7fff7fd9271b: call QWORD PTR [r13+0x57b0]
0x7fff7fd92722: int3
0x7fff7fd92723: mov r15,QWORD PTR [rbp-0x18]
0x7fff7fd92727: mov rcx,QWORD PTR [r15+rbx*8]
0x7fff7fd9272b: mov rbp,QWORD PTR [rbp+0x0]
0x7fff7fd9272f: xor eax,eax
0x7fff7fd92731: add rsp,0x30
0x7fff7fd92735: jmp rcx
0x7fff7fd92737: mov rdx,QWORD PTR [r13+0x1b98]
0x7fff7fd9273e: mov edx,DWORD PTR [rdx+0x663b]
0x7fff7fd92744: add rdx,r14
0x7fff7fd92747: call 0x7fff7f46c780
0x7fff7fd9274c: int3
0x7fff7fd9274d: int3
0x7fff7fd9274e: int3
先ず、0x7fff7fd925f2
から始まる機械語に着目してみましょう。r9
、r12
がスタックに格納されています。この関数がInterpreterEntryTrampoline
から呼ばれることがあることに着目すると、r9
とr12
を足し合わせたメモリに現在のバイトコード(0x0c)が格納されています。
このr9
とr12
が、0x7fff7fd926a7
から始まる命令列で再びレジスタに読み込まれています。そして、0x7fff7fd926b3
で、movzx r8d,BYTE PTR [r12+rbx*1+0x1]
しているということは、これは、LdaZero
の次の命令列を読みだそうとしていることを意味します。なお、LdaZeroは、1バイト命令のため、これはオペランドの読み込みでは無く、次の命令のロードとなります。この後、この値が、0x7fff7fd926d1
と0x7fff7fd926eb
で、0xbfと0xceの間にある値かどうか確認しています。先ず、0xbfより小さい場合、0x7fff7fd926e9
のjmp rcx
で次のBytecodeHandlerに遷移します。そして、肝心のLdaZeroの処理ですが、0x7fff7fd926e3
で、eax
を0にしています。実は、rax
はアキュミュレータレジスタを格納するために使用されており、これが実質的に、LdaZero
の処理を指します。
次の命令列が、0xbfと0xceの間にある場合はどうでしょうか。実は、この範囲の命令は、全てstor0,stor1などのアキュミュレータレジスタからX番目レジスタに値を移す命令です。以下の部分の処理ですが、これはX番目のレジスタに0を格納するということを意味します。
0x7fff7fd926f1: mov r11,QWORD PTR [rbp+0x0]
0x7fff7fd926f5: mov QWORD PTR [r11+r8*8-0x6a8],0x0
アキュミュレータ以外のレジスタ値は、[rbp+0x0]の指すメモリ上に列を成して格納されているということです。
つまり、LdaZeroのBytecodeHandlerの処理の中で、次の命令がStor系の命令の場合は、アキュミュレータに0を格納するという点を省略して、直接それ以外のレジスタに値を格納しています。
こんな感じで、BytecodeHandlerの中で、レジスタが何を指すか理解することにより、仮想マシンについて理解を進めることができます。