认识Stub与StubQueue
初始化模板表我们先容过TemplateInterpreter::initialize()函数,在这个函数中会调用TemplateTable::initialize()函数初始化模板表,随后会利用new关键字初始化界说在AbstractInterpreter类中的_code静态属性,如下:
static StubQueue* _code;由于TemplateInterpreter继续自AbstractInterpreter,以是在TemplateInterpreter中初始化的_code属性实在就是AbstractInterpreter类中界说的_code属性。
在initialize()函数中初始化_code变量的代码如下:
// InterpreterCodeSize是在平台相干// 的templateInterpreter_x86.hpp中// 界说的,64位下是256 * 1024int code_size = InterpreterCodeSize;_code = new StubQueue( new InterpreterCodeletInterface, code_size, NULL, "Interpreter");StubQueue是用来生存天生的本地代码的Stub队列,队列每一个元素对应一个InterpreterCodelet对象,InterpreterCodelet对象继续自抽象基类Stub,包罗了字节码对应的本地代码以及一些调试和输出信息。下面我们先容一下StubQueue类及相干类Stub、InterpreterCodelet类和CodeletMark类。
1、InterpreterCodelet与Stub类
Stub类的界说如下:
class Stub VALUE_OBJ_CLASS_SPEC { ... };InterpreterCodelet类继续自Stub类,详细的界说如下:
class InterpreterCodelet: public Stub { private: int _size; // the size in bytes const char* _description; // a description of the codelet, for debugging & printing Bytecodes::Code _bytecode; // associated bytecode if any public: // Code info address code_begin() const { return (address)this + round_to(sizeof(InterpreterCodelet), CodeEntryAlignment); } address code_end() const { return (address)this + size(); } int size() const { return _size; } // ... int code_size() const { return code_end() - code_begin(); } // ...};InterpreterCodelet实例存储在StubQueue中,每个InterpreterCodelet实例都代表一段呆板指令(包罗了字节码对应的呆板指令片断以及一些调试和输出信息),如每个字节码都有一个InterpreterCodelet实例,以是在表明实行时,假如要实行某个字节码,则实行的就是由InterpreterCodelet实例代表的呆板指令片断。
类中界说了3个属性及一些函数,其内存结构如下图所示。
在对齐至CodeEntryAlignment后,紧接着InterpreterCodelet的就是天生的目标代码。
2、StubQueue类
StubQueue是用来生存天生的本地呆板指令片断的Stub队列,队列每一个元素都是一个InterpreterCodelet实例。
StubQueue类的界说如下:
class StubQueue: public CHeapObj<mtCode> { private: StubInterface* _stub_interface; // the interface prototype address _stub_buffer; // where all stubs are stored int _buffer_size; // the buffer size in bytes int _buffer_limit; // the (byte) index of the actual buffer limit (_buffer_limit <= _buffer_size) int _queue_begin; // the (byte) index of the first queue entry (word-aligned) int _queue_end; // the (byte) index of the first entry after the queue (word-aligned) int _number_of_stubs; // the number of buffered stubs bool is_contiguous() const { return _queue_begin <= _queue_end; } int index_of(Stub* s) const { int i = (address)s - _stub_buffer; return i; } Stub* stub_at(int i) const { return (Stub*)(_stub_buffer + i); } Stub* current_stub() const { return stub_at(_queue_end); } // ...}这个类的构造函数如下:
StubQueue::StubQueue( StubInterface* stub_interface, // InterpreterCodeletInterface对象 int buffer_size, // 256*1024 Mutex* lock, const char* name) : _mutex(lock){ intptr_t size = round_to(buffer_size, 2*BytesPerWord); // BytesPerWord的值为8 BufferBlob* blob = BufferBlob::create(name, size); // 在StubQueue中创建BufferBlob对象 _stub_interface = stub_interface; _buffer_size = blob->content_size(); _buffer_limit = blob->content_size(); _stub_buffer = blob->content_begin(); _queue_begin = 0; _queue_end = 0; _number_of_stubs = 0;}stub_interface用来生存一个InterpreterCodeletInterface类型的实例,InterpreterCodeletInterface类中界说了操纵Stub的函数,克制了在Stub中界说虚函数。每个StubQueue都有一个InterpreterCodeletInterface,可以通过这个来操纵StubQueue中存储的每个Stub实例。
调用BufferBlob::create()函数为StubQueue分配内存,这里我们须要记住StubQueue用的内存是通过BufferBlob分配出来的,也就是BufferBlob其本质大概是一个StubQueue。下面就来详细先容下create()函数。
BufferBlob* BufferBlob::create(const char* name, int buffer_size) { // ... BufferBlob* blob = NULL; unsigned int size = sizeof(BufferBlob); // align the size to CodeEntryAlignment size = align_code_offset(size); size += round_to(buffer_size, oopSize); // oopSize是一个指针的宽度,在64位上就是8 { MutexLockerEx mu(CodeCache_lock, Mutex::_no_safepoint_check_flag); blob = new (size) BufferBlob(name, size); } return blob;}通过new关键字为BufferBlob分配内存,new重载运算符如下:
void* BufferBlob:perator new(size_t s, unsigned size, bool is_critical) throw() { void* p = CodeCache::allocate(size, is_critical); return p;}从codeCache中分配内存,CodeCache利用的是本地内存,有本身的内存管理办法,在反面将会详细先容。
StubQueue的结构结构如下图所示。
队列中的InterpreterCodelet表现一个小例程,好比iconst_1对应的呆板码,invokedynamic对应的呆板码,非常处理惩罚对应的代码,方法入口点对应的代码,这些代码都是一个个InterpreterCodelet。整个表明器都是由这些小块代码例程构成的,每个小块例程完成表明器的部分功能,以此实现整个表明器。
认识CodeletMark
InterpreterCodelet依靠CodeletMark完成自动创建和初始化。CodeletMark继续自ResourceMark,允许自动析构,实行的重要操纵就是,会按照InterpreterCodelet中存储的现实呆板指令片断分配内存并提交。这个类的界说如下:
class CodeletMark: ResourceMark { private: InterpreterCodelet* _clet; // InterpreterCodelet继续自Stub InterpreterMacroAssembler** _masm; CodeBuffer _cb; public: // 构造函数 CodeletMark( InterpreterMacroAssembler*& masm, const char* description, Bytecodes::Code bytecode = Bytecodes::_illegal): // AbstractInterpreter::code()获取的是StubQueue*类型的值,调用request()方法获取的 // 是Stub*类型的值,调用的request()方法实如今vm/code/stubs.cpp文件中 _clet( (InterpreterCodelet*)AbstractInterpreter::code()->request(codelet_size()) ), _cb(_clet->code_begin(), _clet->code_size()) { // 初始化InterpreterCodelet中的_description和_bytecode属性 _clet->initialize(description, bytecode); // InterpreterMacroAssembler->MacroAssembler->Assembler->AbstractAssembler // 通过传入的cb.insts属性的值来初始化AbstractAssembler的_code_section与_oop_recorder属性的值 // create assembler for code generation masm = new InterpreterMacroAssembler(&_cb); // 在构造函数中,初始化r13指向bcp、r14指向本地局部变量表 _masm = &masm; } // ... 省略析构函数};在构造函数中重要完成2个使命:
(1)初始化InterpreterCodelet类型的变量_clet。对InterpreterCodelet实例中的3个属性赋值;
(2)创建一个InterpreterMacroAssembler实例并赋值给masm与_masm,此实例会通过CodeBuffer向InterpreterCodelet实例写入呆板指令。
在析构函数中,通常在代码块竣事时会自动调用析构函数,在析构函数中完成InterpreterCodelet利用的内存的提交并清算相干变量的值。
1、CodeletMark构造函数
在CodeletMark构造函数会从StubQueue中为InterpreterCodelet分配内存并初始化相干变量
在初始化_clet变量时,调用AbstractInterpreter::code()方法返回AbstractInterpreter类的_code属性的值,这个值在之前TemplateInterpreter::initialize()方法中已经初始化了。继续调用StubQueue类中的request()方法,转达的就是要求分配的用来存储code的巨细,通过调用codelet_size()函数来获取,如下:
int codelet_size() { // Request the whole code buffer (minus a little for alignment). // The commit call below trims it back for each codelet. int codelet_size = AbstractInterpreter::code()->available_space() - 2*K; return codelet_size;}须要留意,在创建InterpreterCodelet时,会将StubQueue中剩下的险些全部可用的内存都分配给此次的InterpreterCodelet实例,这一定会有很大的浪费,不过我们在析构函数中会按照InterpreterCodelet实例的实例巨细提交内存的,以是不消担心浪费这个题目。这么做的重要缘故原由就是让各个InterpreterCodelet实例在内存中连续存放,如许有一个非常告急的应用,那就是只要简朴通过pc判定就可知道栈帧是否为表明栈帧了,反面将会详细先容。
通过调用StubQueue::request()函数从StubQueue中分配内存。函数的实现如下:
Stub* StubQueue::request(int requested_code_size) { Stub* s = current_stub(); int x = stub_code_size_to_size(requested_code_size); int requested_size = round_to( x , CodeEntryAlignment); // CodeEntryAlignment=32 // 比力须要为新的InterpreterCodelet分配的内存和可用内存的巨细情况 if (requested_size <= available_space()) { if (is_contiguous()) { // 判定_queue_begin小于便是_queue_end时,函数返回true // Queue: |...|XXXXXXX|.............| // ^0 ^begin ^end ^size = limit assert(_buffer_limit == _buffer_size, "buffer must be fully usable"); if (_queue_end + requested_size <= _buffer_size) { // code fits in(顺应) at the end => nothing to do CodeStrings strings; stub_initialize(s, requested_size, strings); return s; // 假如够的话就直接返回 } else { // stub doesn't fit in at the queue end // => reduce buffer limit & wrap around assert(!is_empty(), "just checkin'"); _buffer_limit = _queue_end; _queue_end = 0; } } } // ... return NULL;}通过如上的函数,我们可以大概清楚看到怎样从StubQueue中分配InterpreterCodelet内存的逻辑。
起首盘算此次须要从StubQueue中分配的内存巨细,调用的相干函数如下:
调用的stub_code_size_to_size()函数的实现如下:
// StubQueue类中界说的函数int stub_code_size_to_size(int code_size) const { return _stub_interface->code_size_to_size(code_size);} // InterpreterCodeletInterface类中界说的函数virtual int code_size_to_size(int code_size) const { return InterpreterCodelet::code_size_to_size(code_size);} // InterpreterCodelet类中界说的函数static int code_size_to_size(int code_size) { // CodeEntryAlignment = 32 // sizeof(InterpreterCodelet) = 32 return round_to(sizeof(InterpreterCodelet), CodeEntryAlignment) + code_size;}通过如上的分配内存巨细的方式可知内存结构如下:
在StubQueue::request()函数中盘算出须要从StubQueue中分配的内存巨细后,下面进行内存分配。StubQueue::request()函数只给出了最一样寻常的情况,也就是假设全部的InterpreterCodelet实例都是从StubQueue的_stub_buffer地址开始连续分配的。is_contiguous()函数用来判定地区是否连续,实现如下:
bool is_contiguous() const { return _queue_begin <= _queue_end;} 调用的available_space()函数得到StubQueue可用地区的巨细,实现如下:
// StubQueue类中界说的方法int available_space() const { int d = _queue_begin - _queue_end - 1; return d < 0 ? d + _buffer_size : d;} 调用如上函数后得到的巨细为下图的黄色地区部分。
继续看StubQueue::request()函数,当能满意此次InterpreterCodelet实例要求的内存巨细时,会调用stub_initialize()函数,此函数的实现如下:
// 下面都是通过stubInterface来操纵Stub的void stub_initialize(Stub* s, int size,CodeStrings& strings) { // 通过_stub_interface来操纵Stub,会调用s的initialize()函数 _stub_interface->initialize(s, size, strings);} // 界说在InterpreterCodeletInterface类中函数virtual void initialize(Stub* self, int size,CodeStrings& strings){ cast(self)->initialize(size, strings);} // 界说在InterpreterCodelet类中的函数void initialize(int size,CodeStrings& strings) { _size = size;}我们通过StubInterface类中界说的函数来操纵Stub,至于为什么要通过StubInterface来操纵Stub,就是由于Stub实例很多,以是为了克制在Stub中写虚函数(C++中对含有虚函数的类须要分配一个指针的空间指向虚函数表)浪费内存空间而采取的办法。
如上3个函数终极只完成了一件事儿,就是将此次分配到的内存巨细记录在InterpreterCodelet的_size属性中。前面在先容函数codelet_size()时提到过,这个值在存储了呆板指令片断后通常还会空余很多空间,不过不要发急,下面要先容的析构函数会根据InterpreterCodelet实例中现实天生的呆板指令的巨细更新这个属性值。
2、CodeletMark析构函数
析构函数的实现如下:
// 析构函数~CodeletMark() { // 对齐InterpreterCodelet (*_masm)->align(wordSize); // 确保天生的全部呆板指令片断都存储到了InterpreterCodelet实例中 (*_masm)->flush(); // 更新InterpreterCodelet实例的相干属性值 AbstractInterpreter::code()->commit((*_masm)->code()->pure_insts_size(), (*_masm)->code()->strings()); // 设置_masm,如许就无法通过这个值继续向此InterpreterCodelet实例中天生呆板指令了 *_masm = NULL;}调用AbstractInterpreter::code()函数获取StubQueue。调用(*_masm)->code()->pure_insts_size()获取的就是InterpreterCodelet实例的呆板指令片断现实须要的内存巨细。
StubQueue::commit()函数的实现如下:
void StubQueue::commit(int committed_code_size, CodeStrings& strings) { int x = stub_code_size_to_size(committed_code_size); int committed_size = round_to(x, CodeEntryAlignment); Stub* s = current_stub(); assert(committed_size <= stub_size(s), "committed size must not exceed requested size"); stub_initialize(s, committed_size, strings); _queue_end += committed_size; _number_of_stubs++;}调用stub_initialize()函数通过InterpreterCodelet实例的_size属性记录此实例中呆板指令片断现实内存巨细。同时更新StubQueue的_queue_end和_number_of_stubs属性的值,如许就可以为下次InterpreterCodelet实例继续分配内存了。
通过InterpreterCodelet存储呆板指令片断
在TemplateInterpreterGenerator::generate_all()函数中天生了很多字节码指令以及一些捏造机辅助实行的呆板指令片断,比方天生空指针非常抛收支口的实现如下:
{ CodeletMark cm(_masm, "throw exception entrypoints"); // ... Interpreter::_throw_NullPointerException_entry = generate_exception_handler("java/lang/NullPointerException",NULL); // ...}调用generate_exception_handler()函数天生抛出空指针的代码片断。
address generate_exception_handler(const char* name, const char* message) { return generate_exception_handler_common(name, message, false);}调用的generate_exception_handler_common()函数的实现如下:
address TemplateInterpreterGenerator::generate_exception_handler_common(const char* name, const char* message, bool pass_oop) { assert(!pass_oop || message == NULL, "either oop or message but not both"); address entry = __ pc(); if (pass_oop) { // object is at TOS __ pop(c_rarg2); } // expression stack must be empty before entering the VM if an // exception happened __ empty_expression_stack(); // setup parameters __ lea(c_rarg1, ExternalAddress((address)name)); if (pass_oop) { __ call_VM(rax, CAST_FROM_FN_PTR(address,InterpreterRuntime::create_klass_exception), c_rarg1,c_rarg2); } else { // kind of lame ExternalAddress can't take NULL because // external_word_Relocation will assert. if (message != NULL) { __ lea(c_rarg2, ExternalAddress((address)message)); } else { __ movptr(c_rarg2, NULL_WORD); } __ call_VM(rax, CAST_FROM_FN_PTR(address, InterpreterRuntime::create_exception), c_rarg1, c_rarg2); } // throw exception __ jump(ExternalAddress(Interpreter::throw_exception_entry())); return entry;}天生的汇编代码如下:
0x00007fffe10101cb: mov -0x40(%rbp),%rsp0x00007fffe10101cf: movq $0x0,-0x10(%rbp)0x00007fffe10101d7: movabs $0x7ffff6e09878,%rsi0x00007fffe10101e1: movabs $0x0,%rdx0x00007fffe10101eb: callq 0x00007fffe10101f50x00007fffe10101f0: jmpq 0x00007fffe10102880x00007fffe10101f5: lea 0x8(%rsp),%rax0x00007fffe10101fa: mov %r13,-0x38(%rbp)0x00007fffe10101fe: mov %r15,%rdi0x00007fffe1010201: mov %rbp,0x200(%r15)0x00007fffe1010208: mov %rax,0x1f0(%r15)0x00007fffe101020f: test $0xf,%esp0x00007fffe1010215: je 0x00007fffe101022d0x00007fffe101021b: sub $0x8,%rsp0x00007fffe101021f: callq 0x00007ffff66b3fbc0x00007fffe1010224: add $0x8,%rsp0x00007fffe1010228: jmpq 0x00007fffe10102320x00007fffe101022d: callq 0x00007ffff66b3fbc0x00007fffe1010232: movabs $0x0,%r100x00007fffe101023c: mov %r10,0x1f0(%r15)0x00007fffe1010243: movabs $0x0,%r100x00007fffe101024d: mov %r10,0x200(%r15)0x00007fffe1010254: cmpq $0x0,0x8(%r15)0x00007fffe101025c: je 0x00007fffe10102670x00007fffe1010262: jmpq 0x00007fffe10004200x00007fffe1010267: mov 0x250(%r15),%rax0x00007fffe101026e: movabs $0x0,%r100x00007fffe1010278: mov %r10,0x250(%r15)0x00007fffe101027f: mov -0x38(%rbp),%r130x00007fffe1010283: mov -0x30(%rbp),%r140x00007fffe1010287: retq 0x00007fffe1010288: jmpq 0x00007fffe100f3d3在这里的重点不是读懂TemplateInterpreterGenerator::generate_exception_handler_common()函数的逻辑及天生的汇编代码,而是要清楚知道CodeletMark的应用,以及generate_exception_handler_common()函数天生的呆板指令是怎样写入InterpreterCodelet实例中的。之前先容过InterpreterCodelet与CodeBuffer类,如下:
通过CodeBuffer操纵InterpreterCodelet实例的存储呆板指令片断的内存地区,而CodeBuffer中的代码部分(CodeSection)被赋值给AbstractAssembler::_code_section。如许我们就可以通过_code_section属性向InterpreterCodelet实例中写入呆板指令了。
向CodeletMark中传入的_masm参数界说在AbstractInterpreterGenerator类中,如下:
class AbstractInterpreterGenerator: public StackObj { protected: InterpreterMacroAssembler* _masm; // ...}generate_exception_handler_common()函数中的__是一个宏,界说如下:
#define __ _masm->如许实在就是调用InterpreterMacroAssembler类中的相干函数写呆板指令,比方
__ pop(c_rarg2);调用的pop()函数如下:
// 界说在InterpreterMacroAssembler中void pop(Register r ) { ((MacroAssembler*)this)->pop(r);} // 界说在Assembler类中void Assembler::pop(Register dst) { int encode = prefix_and_encode(dst->encoding()); emit_int8(0x58 | encode);} // 界说在AbstractAssembler类中void emit_int8( int8_t x) { code_section()->emit_int8( x); }code_section()函数获取的就是AbstractAssembler的_code_section属性的值。
天生告急的例程
之前先容过TemplateInterpreter::initialize()函数,在这个函数中初始化了模板表和StubQueue实例,通过如下方式创建InterpreterGenerator实例:
InterpreterGenerator g(_code);在创建InterpreterGenerator实例时会调用generate_all()函数,如下:
InterpreterGenerator::InterpreterGenerator(StubQueue* code) : TemplateInterpreterGenerator(code) { generate_all(); }在generate_all()函数中天生各种例程(呆板指令片断)并存储到Interpretercodelet实例中。在HotSpot VM中,不但有字节码对应的例程,另有很多辅助捏造机运行时的例程,如之前先容的平凡方法入口entry_point例程,处理惩罚非常的例程等等。这些例程都会存储到StubQueue中,如下图所示。
天生的一些告急例程如下表所示。
此中非本地方法的入口、本地方法的入口和字节码的入口比力告急,也是我们反面先容的重点内容。 这一篇先容非本地方法的入口和字节码的入口,对于本地方法的入口将在先容本地方法时详细先容,这里不过多先容。
1、非本地方法入口
我们在之前先容为非本地的平凡方法创建Java栈帧的时间提到过,重要的非本地方法入口有如下几类:
enum MethodKind { zerolocals, /* method needs locals initialization */ zerolocals_synchronized, /* method needs locals initialization & is synchronized */ native, /* native method */ native_synchronized, /* native method & is synchronized */ empty, /* empty method (code: _return) */ accessor, /* accessor method (code: _aload_0, _getfield, _(a|i)return) */ abstract, /* abstract method (throws an AbstractMethodException) */ method_handle_invoke_FIRST, /* java.lang.invoke.MethodHandles::invokeExact, etc. */ method_handle_invoke_LAST = (method_handle_invoke_FIRST + (vmIntrinsics:AST_MH_SIG_POLY - vmIntrinsics::FIRST_MH_SIG_POLY) ), java_lang_math_sin, /* implementation of java.lang.Math.sin (x) */ java_lang_math_cos, /* implementation of java.lang.Math.cos (x) */ java_lang_math_tan, /* implementation of java.lang.Math.tan (x) */ java_lang_math_abs, /* implementation of java.lang.Math.abs (x) */ java_lang_math_sqrt, /* implementation of java.lang.Math.sqrt (x) */ java_lang_math_log, /* implementation of java.lang.Math.log (x) */ java_lang_math_log10, /* implementation of java.lang.Math.log10 (x) */ java_lang_math_pow, /* implementation of java.lang.Math.pow (x,y) */ java_lang_math_exp, /* implementation of java.lang.Math.exp (x) */ java_lang_ref_reference_get, /* implementation of java.lang.ref.Reference.get() */ java_util_zip_CRC32_update, /* implementation of java.util.zip.CRC32.update() */ java_util_zip_CRC32_updateBytes, /* implementation of java.util.zip.CRC32.updateBytes() */ java_util_zip_CRC32_updateByteBuffer, /* implementation of java.util.zip.CRC32.updateByteBuffer() */ number_of_method_entries, invalid = -1};在generate_all()函数中天生平凡方法平静凡的同步方法的入口逻辑如下:
{ CodeletMark cm(_masm, "method entry point (kind = " "zerolocals" ")"); Interpreter::_entry_table[Interpreter::zerolocals] = generate_method_entry(Interpreter::zerolocals);}{ CodeletMark cm(_masm, "method entry point (kind = " "zerolocals_synchronized" ")"); Interpreter::_entry_table[Interpreter::zerolocals_synchronized] = generate_method_entry(Interpreter::zerolocals_synchronized);}调用的generate_method_entry()函数在已经详细先容过,终极会天生创建Java栈帧的例程,将例程的首地址存储到Interpreter::_entry_table数组中。
address AbstractInterpreterGenerator::generate_method_entry( AbstractInterpreter::MethodKind kind ){/* determine code generation flags */ bool synchronized = false; address entry_point = NULL; InterpreterGenerator * ig_this = (InterpreterGenerator *) this; switch ( kind ) { case Interpreter::zerolocals: /* zerolocals类型表现正常的Java函数 */ break; case Interpreter::zerolocals_synchronized: synchronized = true; break; case Interpreter::native: entry_point = ig_this->generate_native_entry( false ); break; case Interpreter::native_synchronized: entry_point = ig_this->generate_native_entry( true ); break; case Interpreter::empty: entry_point = ig_this->generate_empty_entry(); break; case Interpreter::accessor: entry_point = ig_this->generate_accessor_entry(); break; case Interpreter::abstract: entry_point = ig_this->generate_abstract_entry(); break; case Interpreter::java_lang_math_sin: /* fall thru */ case Interpreter::java_lang_math_cos: /* fall thru */ case Interpreter::java_lang_math_tan: /* fall thru */ case Interpreter::java_lang_math_abs: /* fall thru */ case Interpreter::java_lang_math_log: /* fall thru */ case Interpreter::java_lang_math_log10: /* fall thru */ case Interpreter::java_lang_math_sqrt: /* fall thru */ case Interpreter::java_lang_math_pow: /* fall thru */ case Interpreter::java_lang_math_exp: entry_point = ig_this->generate_math_entry( kind ); break; case Interpreter::java_lang_ref_reference_get: entry_point = ig_this->generate_Reference_get_entry(); break; case Interpreter::java_util_zip_CRC32_update: entry_point = ig_this->generate_CRC32_update_entry(); break; case Interpreter::java_util_zip_CRC32_updateBytes:/* fall thru */ case Interpreter::java_util_zip_CRC32_updateByteBuffer: entry_point = ig_this->generate_CRC32_updateBytes_entry( kind ); break; default: fatal( err_msg( "unexpected method kind: %d", kind ) ); break; } if ( entry_point ) { return(entry_point); } return(ig_this->generate_normal_entry( synchronized ) );}关于同步方法的栈帧建立及特别逻辑的处理惩罚将在先容锁相干知识时详细先容,这里不在过多先容。
除了平凡的方法外,还为一些方法天生了一些特别的入口地址,如为java.lang.Math.sin()、java.lang.Math.cos()等方法天生的例程。假如各人有爱好可以本身研究一下,这里不在详细先容。
2、字节码入口
在generate_all()函数中会调用set_entry_points_for_all_bytes()函数,此函数对全部被界说的字节码天生例程并通过对应的属性生存入口,这些入口指向了例程的首地址。set_entry_points_for_all_bytes()函数的实现如下:
void TemplateInterpreterGenerator::set_entry_points_for_all_bytes() { for (int i = 0; i < DispatchTable::length; i++) { Bytecodes::Code code = (Bytecodes::Code)i; if (Bytecodes::is_defined(code)) { set_entry_points(code); } else { set_unimplemented(i); } }}当code是Java捏造机规范中界说的字节码指令时,调用set_entry_points()函数,此函数取出该字节码指令对应的Template模板并调用set_short_enrty_points()函数进行处理惩罚,将入口地址生存在转发表(DispatchTable)_normal_table或_wentry_table(利用wide指令)中。Template模板在之前已经先容过,字节码指令都会对应一个Template模板,而模板中生存着字节码指令天生对应代码例程中须要的信息。
set_entry_points()函数的实现如下:
void TemplateInterpreterGenerator::set_entry_points(Bytecodes::Code code) { CodeletMark cm(_masm, Bytecodes::name(code), code); address bep = _illegal_bytecode_sequence; address cep = _illegal_bytecode_sequence; address sep = _illegal_bytecode_sequence; address aep = _illegal_bytecode_sequence; address iep = _illegal_bytecode_sequence; address lep = _illegal_bytecode_sequence; address fep = _illegal_bytecode_sequence; address dep = _illegal_bytecode_sequence; address vep = _unimplemented_bytecode; address wep = _unimplemented_bytecode; // 处理惩罚非wide指令,留意指的是那些不能在前面加wide指令的字节码指令 if (Bytecodes::is_defined(code)) { Template* t = TemplateTable::template_for(code); set_short_entry_points(t, bep, cep, sep, aep, iep, lep, fep, dep, vep); } // 处理惩罚wide指令,留意指的是那些能在前面加wide指令的字节码指令 if (Bytecodes::wide_is_defined(code)) { Template* t = TemplateTable::template_for_wide(code); set_wide_entry_point(t, wep); } // 当为非wide指令时,共有9个入口,当为wide指令时,只有一个入口 EntryPoint entry(bep, cep, sep, aep, iep, lep, fep, dep, vep); Interpreter::_normal_table.set_entry(code, entry); Interpreter::_wentry_point[code] = wep;}留意函数开始时声明时创建了一个变量cm,此时会调用CodeletMark构造函数在StubQueue中创建出存储呆板片断的InterpreterCodelet实例,以是调用TemplateInterpreterGenerator::set_short_entry_points()等函数天生的呆板指令都会写入到这个实例中。当函数实行完成后,CodeletMark析构函数会提交利用的内存并重置相干属性值。
接下来就是为表现栈顶缓存(Top-of-Stack Caching,缩写为TOSCA,简称Tos)状态的变量赋初始值,此中的_illegal_bytecode_sequence与_unimplemented_bytecode变量指向的也是特定例程的入口地址,这些例程就是在generate_all()函数中天生的,假如各人有爱好,可以研究一下这些例程是怎么处理惩罚非法字节码等情况的。
调用set_short_entry_points()函数时,须要传入栈顶缓存状态,也就是上一个字节码实行时大概会将产生的效果存储到寄存器中。利用栈顶缓存重要照旧为了进步表明实行的服从。HotSpot VM共界说了9种TosState,通过摆列常量来表现,如下:
enum TosState { // describes the tos cache contents btos = 0, // byte, bool tos cached ctos = 1, // char tos cached stos = 2, // short tos cached itos = 3, // int tos cached ltos = 4, // long tos cached ftos = 5, // float tos cached dtos = 6, // double tos cached atos = 7, // object cached vtos = 8, // tos not cached number_of_states, ilgl // illegal state: should not occur};以非wide指令为例进行分析,bep(byte entry point)、cep、 sep、aep、iep、lep、fep、dep、vep分别表现指令实行前栈顶元素状态为byte/boolean、char、short、array/reference(对象引用)、int、long、float、double、void类型时的入口地址。举个例子,如iconst_0表现向栈中压入常量0,那么字节码指令模板中有如下界说:
def(Bytecodes::_iconst_0 , ____|____|____|____, vtos, itos, iconst,0);第3个参数指明确tos_in,第4个参数为tos_out,tos_in与tos_out是指令实行前后的TosState。也就是说,实行此字节码指令之前不须要获取栈顶缓存的值,以是为void;实行完成后栈顶会缓存一个int类型的整数,也就是0。缓存通常会缓存到寄存器中,以是比起压入栈中,获取的服从要更高一些,假如下一个实行的字节码指令不须要,那么还须要将缓存的0值压入栈内。假设下一个实行的字节码也为iconst,那么要从iconst指令的iep(上一个缓存了int类型整数0)入口来实行,由于iconst的入口要求为vtos,以是须要将寄存器中的int类型数值0入栈。以是每个字节码指令都会有多个入口,如许任何一个字节码指令在实行完成后,都可以根据当前实行后的栈顶缓存状态找到下一个须要实行字节码的对应入口。
再转头看一下我们先容的分发字节码相干内容,为各个字节码设置入口的函数DispatchTable::set_entry(),此中的_table的一维为栈顶缓存状态,二维为Opcode,通过这2个维度可以大概找到一段呆板指令,这就是根据当前的栈顶缓存状态定位到的字节码须要实行的例程。我们看一下TemplateInterpreterGenerator::set_entry_points()函数,末了会调用DispatchTable::set_entry()函数为_table属性赋值。如许类型为DispatchTable的TemplateInterpreter::_normal_table与TemplateInterpreter::_wentry_point变量就可以完成字节码分发了。
调用TemplateTable::template_for()函数可以从TemplateTable::_template_table数组中获取对应的Template实例,然后调用set_short_entry_points()函数天生例程。非wild指令调用set_short_entry_points()函数,set_short_entry_points()函数的实现如下:
void TemplateInterpreterGenerator::set_short_entry_points(Template* t,address& bep, address& cep, address& sep, address& aep, address& iep,address& lep, address& fep, address& dep, address& vep) { switch (t->tos_in()) { case btos: case ctos: case stos: ShouldNotReachHere(); break; case atos: vep = __ pc(); __ pop(atos); aep = __ pc(); generate_and_dispatch(t); break; case itos: vep = __ pc(); __ pop(itos); iep = __ pc(); generate_and_dispatch(t); break; case ltos: vep = __ pc(); __ pop(ltos); lep = __ pc(); generate_and_dispatch(t); break; case ftos: vep = __ pc(); __ pop(ftos); fep = __ pc(); generate_and_dispatch(t); break; case dtos: vep = __ pc(); __ pop(dtos); dep = __ pc(); generate_and_dispatch(t); break; case vtos: set_vtos_entry_points(t, bep, cep, sep, aep, iep, lep, fep, dep, vep); break; default : ShouldNotReachHere(); break; }set_short_entry_points()函数会根据Template实例中生存的字节码模板信息天生最多9个栈顶入口并赋值给传入参数bep、cep等,也就是给Template代表的特定字节码指令天生相应的入口地址。
set_short_entry_points()函数根据操纵数栈栈顶元素类型进行判定,起首byte、char和short类型都应被当做int类型进行处理惩罚,以是不会为字节码指令天生这几个类型的入口地址;假如当前字节码实行之前要求有栈顶元素而且类型是atos对象类型,那么当没有栈顶缓存时,从vep入口进入,然后弹出表达式栈中的对象到栈顶缓存寄存器后,就可以直接从aep进入,itos、ltos、ftos和dtos也都类似,会分别成为2个入口地址;假如不要求有栈顶元素,那么就是vtos,非void类型将调用generate_and_dispatch()函数天生各种入口。
set_vtos_entry_points()函数的实现如下:void TemplateInterpreterGenerator::set_vtos_entry_points( Template* t, address& bep, address& cep, address& sep, address& aep, address& iep, address& lep, address& fep, address& dep, address& vep) { Label L; aep = __ pc(); __ push_ptr(); __ jmp(L); fep = __ pc(); __ push_f(); __ jmp(L); dep = __ pc(); __ push_d(); __ jmp(L); lep = __ pc(); __ push_l(); __ jmp(L); bep = cep = sep = iep = __ pc(); __ push_i(); vep = __ pc(); __ bind(L); generate_and_dispatch(t);}假如字节码不要求有栈顶缓存时(即vtos状态),会为当前字节码天生9个入口地址,由bep、cep等生存下来。如天生aep入口时,由于当前实行的字节码栈不须要顶缓存状态,以是要把值压入表达式栈中,然后跳转到L处实行,也就是相当于从vep入口进入实行了。
如今简朴梳理一下,上一个字节码指令到底从哪个入口进入到下一个字节码指令要通过上一个字节码指令的实行效果而定。假如上一个字节码指令实行的效果为fep,而当前字节码指令实行之前的栈顶缓存状态要求是vtos,则从TemplateInterpreterGenerator::set_vtos_entry_points()函数中给fep赋值的地方开始实行。以是说,上一个字节码指令的实行效果和下一个将要实行的字节码指令实行之前要求的栈顶缓存状态共同决定了从哪个入口进入。
push_f()函数的实现如下:
源代码位置:/hotspot/src/cpu/x86/vm/interp_masm_x86_64.cpp
void InterpreterMacroAssembler::push_f(XMMRegister r) { // r的默认值为xmm0 subptr(rsp, wordSize); // wordSize为呆板字长,64位下为8字节,以是值为8 movflt(Address(rsp, 0), r);} void MacroAssembler::subptr(Register dst, int32_t imm32) { LP64_ONLY(subq(dst, imm32)) NOT_LP64(subl(dst, imm32));} void Assembler::subq(Register dst, int32_t imm32) { (void) prefixq_and_encode(dst->encoding()); emit_arith(0x81, 0xE8, dst, imm32);} 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_arith()、emit_int8()等函数天生呆板指令片断,天生的内容末了会存储到StubQueue的InterpreterCodelet实例中,关于呆板指令和天生存储过程在之前已经先容过,这里不做过多先容。
set_vtos_entry_points()函数天生的呆板指令片断颠末反编译后,对应的汇编代码后如下:
// aep的入口push %rax jmpq L // fep入口sub $0x8,%rsp movss %xmm0,(%rsp)jmpq L // dep入口 sub $0x10,%rsp movsd %xmm0,(%rsp)jmpq L // lep入口sub $0x10,%rsp mov %rax,(%rsp)jmpq L // iep入口push %rax // ---- L ---- set_vtos_entry_points()函数末了调用generate_and_dispatch()函数写入当前字节码指令对应的呆板指令片断和跳转到下一个字节码指令继续实行的逻辑处理惩罚部分。
generate_and_dispatch()函数的重要实现如下:
void TemplateInterpreterGenerator::generate_and_dispatch(Template* t, TosState tos_out) { // 天生当前字节码指令对应的呆板指令片断 t->generate(_masm); if (t->does_dispatch()) { // asserts } else { // 生因素发到下一个字节码指令的逻辑 __ dispatch_epilog(tos_out, step); }}这里以iconst字节码为例分析generate()函数的实现:
void Template::generate(InterpreterMacroAssembler* masm) { // parameter passing TemplateTable::_desc = this; TemplateTable::_masm = masm; // code generation _gen(_arg); masm->flush();}generate()函数会调用天生器函数_gen(_arg),对于iconst指令来说,天生器函数为iconst()。generate()函数根据平台而差别,如x86_64平台下,界说如下:
源代码位置:/hotspot/src/cpu/x86/vm/templateTable_x86_64.cpp
void TemplateTable::iconst(int value) { if (value == 0) { __ xorl(rax, rax); } else { __ movl(rax, value); }}我们知道,iconst_i指令是将i压入栈,这里天生器函数iconst()在i为0时,没有直接将0写入rax,而是利用异或运算清零,即向代码缓冲区写入指令”xor %rax, %rax”;当i不为0时,写入指令”mov $0xi, %rax”
当不须要转发时,会在TemplateInterpreterGenerator::generate_and_dispatch()函数中调用dispatch_epilog()函数天生取下一条指令和分派的目标代码:
void InterpreterMacroAssembler::dispatch_epilog(TosState state, int step) { dispatch_next(state, step);}dispatch_next()函数的实现如下:
void InterpreterMacroAssembler::dispatch_next(TosState state, int step) { // load next bytecode (load before advancing r13 to prevent AGI) load_unsigned_byte(rbx, Address(r13, step)); // advance r13 increment(r13, step); dispatch_base(state, Interpreter::dispatch_table(state));}这个函数在之前已经先容过,这里不再先容。
表明器及表明器天生器
方法表明实行时须要表明器与表明器天生器的支持。表明器与表明器天生器的继续体系如下:
下面详细先容表明器与表明器天生器。
1、表明器
表明器是一堆本地代码例程构造的,这些例程会在捏造机启动的时间写入到StubQueue中,以后表明实行时就只须要进入指定例程即可。
表明器的继续体系如下:
AbstractInterpreter /interpreter/abstractInterpreter.hpp CppInterpreter TemplateInterpreter /interpreter/templateInterpreter.hpp Interpreter /interpreter/templateInterpreter.hppInterpreter通过宏可以继续自CppInterpreter大概TemplateInterpreter,前者称为C++表明器,每个字节码指令都对应一段C++代码,通过switch的方式处理惩罚字节码,后者称为模板表明器,每个指令对应一段呆板指令片断,通过指令模板的方式处理惩罚字节码,HotSpot VM默认利用模板表明器。
(1)抽象表明器AbstractInterpreter
全部的表明器都继续自抽象表明器,类及告急属性的界说如下:
class AbstractInterpreter{ StubQueue* _code address _entry_table[n]; // ...}; _code属性在之前已经先容过,这是一个队列,队列中的InterpreterCodelet表现一个例程,好比iconst_1对应的代码,invokedynamic对应的代码,非常处理惩罚对应的代码,方法入口点对应的代码,这些代码都是一个个InterpreterCodelet。整个表明器都是由这些例程构成的,每个例程完成表明器的部分功能,以此实现整个表明器。
_entry_table数组会会生存方法入口点,比方平凡方法的入口点为_entry_table[0]、同步的平凡方法的入口点为_entry_table[1],这些_entry_table[0],_entry_table[1]指向的就是之前_code队列内里的例程。这些逻辑都在是generate_all()函数中完成的,如下:
void TemplateInterpreterGenerator::generate_all() { // ... method_entry(zerolocals) method_entry(zerolocals_synchronized) method_entry(empty) method_entry(accessor) method_entry(abstract) method_entry(java_lang_math_sin ) method_entry(java_lang_math_cos ) method_entry(java_lang_math_tan ) method_entry(java_lang_math_abs ) method_entry(java_lang_math_sqrt ) method_entry(java_lang_math_log ) method_entry(java_lang_math_log10) method_entry(java_lang_math_exp ) method_entry(java_lang_math_pow ) method_entry(java_lang_ref_reference_get) // ...}method_entry宏的界说如下:
#define method_entry(kind) \{ \ CodeletMark cm(_masm, "method entry point (kind = " #kind ")"); \ Interpreter::_entry_table[Interpreter::kind] = generate_method_entry(Interpreter::kind); \}可以看到,调用generate_method_entry()函数会返回例程对应的入口地址,然后生存到AbstractInterpreter类中界说的_entry_table数组中。调用generate_method_entry()函数传入的参数是摆列常量,表现一些特别的方法和一些常见的方法类型。
(2)模板表明器TemplateInterpreter
模板表明器类的界说如下:
class TemplateInterpreter: public AbstractInterpreter { protected: // 数组越界非常例程 static address _throw_ArrayIndexOutOfBoundsException_entry; // 数组存储非常例程 static address _throw_ArrayStoreException_entry; // 算术非常例程 static address _throw_ArithmeticException_entry; // 类型转换非常例程 static address _throw_ClassCastException_entry; // 空指针非常例程 static address _throw_NullPointerException_entry; // 抛非常公共例程 static address _throw_exception_entry; // ...}抽象表明器界说了须要的例程,详细的表明器在这之上另有本身的特设的例程。模板表明器就是一个例子,它继续自抽象表明器,在那些例程之上另有本身的特设例程,比方上面界说的一些属性,生存了步伐非常时的入口例程,实在另有很多为生存例程入口而界说的字段或数组,这里就不逐一先容了。
(3)表明器Interpreter
类的界说如下:
class Interpreter: public CC_INTERP_ONLY(CppInterpreter) NOT_CC_INTERP(TemplateInterpreter) { // ...}没有界说新的属性,只有几个函数。Interpreter默认通过宏扩展的方式继续TemplateInterpreter。
2、表明器天生器
要想得到可运行的表明器还须要表明器天生器。表明器天生器原来可以独自完成添补工作,大概为相识耦,也大概是为告终构清楚,HotSpot VM将字节码的例程抽了出来放到了TemplateTable模板表中,它辅助模板表明器天生器templateInterpreterGenerator天生各种例程。
表明器天生器的继续体系如下:
AbstractInterpreterGenerator /interpreter/abstractInterpreter.hpp TemplateInterpreterGenerator /interpreter/templateInterpreter.hpp InterpreterGenerator /interpreter/interpreter.hpp模板表明器天生器扩展了抽象表明器天生器。表明器天生器与表明器实在有某种意义上的对应关系,如抽象表明器天生器中界说了一些函数,调用这些函数会初始化抽象表明器中的属性,如生存例程的_entry_table数组等,在模板表明器天生器中界说的函数会初始化模板表明器中界说的一些属性,如_throw_ArrayIndexOutOfBoundsException_entry等。之前先容过空指针的例程就是在这个TemplateInterpreterGenerator类的generate_all()函数中天生的。如下:
{ CodeletMark cm(_masm, "throw exception entrypoints"); // ... Interpreter::_throw_NullPointerException_entry = generate_exception_handler("java/lang/NullPointerException",NULL); // ...}关于表明器天生器不再过多先容。
这里我们须要提示的是,表明器和表明器天生器中界说的函数在实现过程中,有些平静台是无关的,以是会在/interpreter文件夹下的文件中实现。比方Interpreter和InterpreterGenerator类界说在/interpreter文件夹下,此中界说的函数会在/interpreter文件夹下的interpreter.cpp文件中实现,但是有些函数是针对特定平台,我们只讨论linux在x86架构下的64位实现,以是cpu/x86/vm文件夹下也有interpreter_x86.hpp和interpreter_x86_64.cpp等文件,只须要在界说Interpreter类时包罗interpreter_x86.hpp文件即可。
捏造机中的汇编器
汇编器的继续体系如下:
为分析器提供的相干汇编接口,以是每个字节码指令都会关联一个天生器函数,而天生器函数会调用汇编器天生呆板指令片断,比方为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->1_masm变量的界说如下:源代码位置:hotspot/src/share/vm/interpreter/abstractInterpreter.hppclass 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:op_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"#endifAssembler类添加了特定于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()函数表现移除对应的栈帧,比方在碰到非常时,假如当前的方法不能处理惩罚此非常,那就须要对栈进行粉碎性睁开,在睁开过程中须要移除对应的栈帧。 |