匯編器的繼承體系如下:
為解析器提供的相關匯編接口,所以每個字節碼指令都會關聯一個生成器函數,而生成器函數會調用匯編器生成機器指令片段,例如為iload字節碼指令生成例程時,調用的生成函數為TemplateTable::iload(int n),此函數的實現如下:
源代碼位置:hotspot/src/cpu/x86/vm/templateTable_x86_64.cpp void TemplateTable::iload() { transition(vtos, itos); ... // Get the local value into tos locals_index(rbx); __ movl(rax, iaddress(rbx)); // iaddress(rb)為源操作數,rax為目地操作數 }
函數調用了__ movl(rax,iaddress(rbx))函數生成對應的機器指令,所生成的機器指令為mov reg,operand,其中__為宏,定義如下:
源代碼位置:hotspot/src/cpu/x86/vm/templateTable_x86_64.cpp #define __ _masm->
_masm變量的定義如下:
源代碼位置:hotspot/src/share/vm/interpreter/abstractInterpreter.hpp class AbstractInterpreterGenerator: public StackObj { protected: InterpreterMacroAssembler* _masm; ... }
最終_masm會被實例化為InterprterMacroAssembler類型。
在x86的64位平台上調用movl()函數的實現如下:
源代碼位置:hotspot/src/cpu/x86/vm/assembler_86.cpp void Assembler::movl(Register dst, Address src) { InstructionMark im(this); prefix(src, dst); emit_int8((unsigned char)0x8B); emit_operand(dst, src); }
調用prefix()、emit_int8()等定義在匯編器中的函數時,這些函數會通過AbstractAssembler::_code_section屬性向InterpreterCodelet實例中寫入機器指令,這個內容在之前已經介紹過,這里不再介紹。
1、AbstractAssembler類
AbstractAssembler類中定義了生成匯編代碼的抽象公共基礎函數,如獲取關聯CodeBuffer的當前內存位置的pc()函數,將機器指令全部刷新到InterpreterCodelet實例中的flush()函數,綁定跳轉標簽的bind()函數等。AbstractAssembler類的定義如下:
源代碼位置:hotspot/src/share/vm/asm/assembler.hpp // The Abstract Assembler: Pure assembler doing NO optimizations on the // instruction level; i.e., what you write is what you get. // The Assembler is generating code into a CodeBuffer. class AbstractAssembler : public ResourceObj { friend class Label; protected: CodeSection* _code_section; // section within the code buffer OopRecorder* _oop_recorder; // support for relocInfo::oop_type ... void emit_int8(int8_t x) { code_section()->emit_int8(x); } void emit_int16(int16_t x) { code_section()->emit_int16(x); } void emit_int32(int32_t x) { code_section()->emit_int32(x); } void emit_int64(int64_t x) { code_section()->emit_int64(x); } void emit_float( jfloat x) { code_section()->emit_float(x); } void emit_double( jdouble x) { code_section()->emit_double(x); } void emit_address(address x) { code_section()->emit_address(x); } ... }
匯編器會生成機器指令序列,並且將生成的指令序列存儲到緩存中,而_code_begin指向緩存區首地址,_code_pos指向緩存區的當前可寫入的位置。
這個匯編器提供了寫機器指令的基礎函數,通過這些函數可方便地寫入8位、16位、32位和64位等的數據或指令。這個匯編器中處理的業務不會依賴於特定平台。
2、Assembler類
assembler.hpp文件中除定義AbstractAssembler類外,還定義了jmp跳轉指令用到的標簽Lable類,調用bind()函數后就會將當前Lable實例綁定到指令流中一個特定的位置,比如jmp指令接收Lable參數,就會跳轉到對應的位置處開始執行,可用於實現循環或者條件判斷等控制流操作。
Assembler的定義跟CPU架構有關,通過assembler.hpp文件中的宏包含特定CPU下的Assembler實現,如下:
源代碼位置:hotspot/src/share/vm/asm/assembler.hpp #ifdef TARGET_ARCH_x86 # include "assembler_x86.hpp" #endif
Assembler類添加了特定於CPU架構的指令實現和指令操作相關的枚舉。 定義如下:
源代碼位置:hotspot/src/cpu/x86/vm/assembler_x86.hpp // The Intel x86/Amd64 Assembler: Pure assembler doing NO optimizations on the instruction // level (e.g. mov rax, 0 is not translated into xor rax, rax!); i.e., what you write // is what you get. The Assembler is generating code into a CodeBuffer. class Assembler : public AbstractAssembler { ... }
提供的許多函數基本是對單個機器指令的實現,例如某個movl()函數的實現如下:
void Assembler::movl(Register dst, Address src) { InstructionMark im(this); prefix(src, dst); emit_int8((unsigned char)0x8B); emit_operand(dst, src); }
subq()函數的實現如下:
void Assembler::subq(Register dst, int32_t imm32) { (void) prefixq_and_encode(dst->encoding()); emit_arith(0x81, 0xE8, dst, imm32); }
如上函數將會調用emit_arith()函數,如下:
void Assembler::emit_arith(int op1, int op2, Register dst, int32_t imm32) { assert(isByte(op1) && isByte(op2), "wrong opcode"); assert((op1 & 0x01) == 1, "should be 32bit operation"); assert((op1 & 0x02) == 0, "sign-extension bit should not be set"); if (is8bit(imm32)) { emit_int8(op1 | 0x02); // set sign bit emit_int8(op2 | encode(dst)); emit_int8(imm32 & 0xFF); } else { emit_int8(op1); emit_int8(op2 | encode(dst)); emit_int32(imm32); } }
調用emit_int8()或emit_int32()等函數寫入機器指令。最后寫入的指令如下:
83 EC 08
由於8可由8位有符號數表示,第一個字節為0x81 | 0x02,即0x83,rsp的寄存器號為4,第二個字節為0xE8 | 0x04,即0xEC,第三個字節為0x08 & 0xFF,即0x08,該指令即AT&T風格的sub $0x8,%rsp。
我在這里並不會詳細解讀匯編器中emit_arith()等函數的實現邏輯,這些函數如果在不理解機器指令編碼的情況下很難理解其實現過程。后面我們根據Intel手冊介紹了機器指令編碼格式后會選幾個典型的實現進行解讀。
3、MacroAssembler類
MacroAssembler類繼承自Assembler類,主要是添加了一些常用的匯編指令支持。類的定義如下:
源代碼位置:hotspot/src/cpu/x86/vm/macroAssembler_x86.hpp // MacroAssembler extends Assembler by frequently used macros. // // Instructions for which a 'better' code sequence exists depending // on arguments should also go in here. class MacroAssembler: public Assembler { ... }
這個類中的函數通過調用MacroAssembler類或Assembler類中定義的一些函數來完成,可以看作是通過對機器指令的組合來完成一些便於業務代碼操作的函數。
根據一些方法傳遞的參數可知,能夠支持JVM內部數據類型級別的操作。
例如機器指令在做加法操作時,不允許兩個操作數同時都是存儲器操作數,或者一個來自內存,另外一個來自立即數,但是MacroAssembler匯編器中卻提供了這樣的函數。
4、InterpreterMacroAssembler類
在templateTable.hpp文件中已經根據平台判斷要引入的文件了,如下:
#ifdef TARGET_ARCH_x86 # include "interp_masm_x86.hpp" #endif
在interp_masm_x86.hpp文件中定義了InterpreterMacroAssembler類,如下:
源代碼位置:hotspot/src/cpu/x86/vm/interp_masm_x86.hpp // This file specializes the assember with interpreter-specific macros class InterpreterMacroAssembler: public MacroAssembler { ... #ifdef TARGET_ARCH_MODEL_x86_64 # include "interp_masm_x86_64.hpp" #endif ... }
對於64位平台來說,引入了interp_masm_x86_64.hpp文件。
在interp_masm_x86_64.cpp文件中定義了如下幾個函數:
(1)InterpreterMacroAssembler::lock_object()
(2)InterpreterMacroAssembler::unlock_object()
(3)InterpreterMacroAssembler::remove_activation()
(4)InterpreterMacroAssembler::dispatch_next()
其中的dispatch_next()函數大家應該不陌生,這個函數在之前介紹過,是為分發字節碼指令生成例程;lock_object()和unlock_object()是在解釋執行的情況下,為加載和釋放鎖操作生成對應的例程,在后面介紹鎖相關的知識時會詳細介紹;remove_activation()函數表示移除對應的棧幀,例如在遇到異常時,如果當前的方法不能處理此異常,那就需要對棧進行破壞性展開,在展開過程中需要移除對應的棧幀。
推薦閱讀:
第2篇-JVM虛擬機這樣來調用Java主類的main()方法
第13篇-通過InterpreterCodelet存儲機器指令片段
如果有問題可直接評論留言或加作者微信mazhimazh
關注公眾號,有HotSpot VM源碼剖析系列文章!