VM例程调用
也就是模板解释器代码(或者被jit编译的代码)执行过程中调用VM例程的过程。
从模板解释器调用
一些模板解释器的代码会调用虚拟机中的例程,比如newarray
:
void TemplateTable::newarray() {
transition(itos, atos);
Register rarg1 = LP64_ONLY(c_rarg1) NOT_LP64(rdx);
__ load_unsigned_byte(rarg1, at_bcp(1));
// 此时rax存储了数组的长度,而rarg1存放了数组元素的类型,同时指定了rax存放返回值。
call_VM(rax, CAST_FROM_FN_PTR(address, InterpreterRuntime::newarray),
rarg1, rax);
}
被调用的InterpreterRuntime::newarray
实际也并不复杂,我们不对oopFactory::new_typeArray
进一步深入:
IRT_ENTRY(void, InterpreterRuntime::newarray(JavaThread* thread, BasicType type, jint size))
oop obj = oopFactory::new_typeArray(type, size, CHECK);
thread->set_vm_result(obj);
IRT_END
这里调用了TemplateTable::call_VM
,其实现非常简单,直接略过来到MacroAssembler::call_VM
:
// 似乎不只是从解释器中会调用,在编译代码似乎也会使用call_VM()
void MacroAssembler::call_VM(Register oop_result, // rax
address entry_point, // InterpreterRuntime::newarray
Register arg_1, // rarg1
Register arg_2, // rax
bool check_exceptions) {
Label C, E;
call(C, relocInfo::none);
jmp(E);
bind(C);
LP64_ONLY(assert(arg_1 != c_rarg2, "smashed arg"));
// 将寄存器值移动到指定的寄存器,满足调用规约。
pass_arg2(this, arg_2);
pass_arg1(this, arg_1);
call_VM_helper(oop_result, entry_point, 2, check_exceptions);
ret(0);
bind(E);
}
这里使用了一个call来进行跳转,而跳转的位置只是跳转到一个很近的地方,这么做的目的大概是为了获取到返回地址,我目前还不太清楚这么做的目的,因为我看到的执行路径上并没有使用pc的地方,这可能是处于某种调用规约吧。这个推入的地址会在19
中被弹出,随后执行9
生成的jmp。
跳转到C后继续执行,会进入到MacroAssembler::call_VM_helper
:
void MacroAssembler::call_VM_helper(Register oop_result, address entry_point, int number_of_arguments, bool check_exceptions) {
// Calculate the value for last_Java_sp
// somewhat subtle. call_VM does an intermediate call
// which places a return address on the stack just under the
// stack pointer as the user finsihed with it. This allows
// use to retrieve last_Java_pc from last_Java_sp[-1].
// On 32bit we then have to push additional args on the stack to accomplish
// the actual requested call. On 64bit call_VM only can use register args
// so the only extra space is the return address that call_VM created.
// This hopefully explains the calculations here.
// We've pushed one address, correct last_Java_sp
lea(rax, Address(rsp, wordSize)); // rax为推入返回地址前的指针
call_VM_base(oop_result, noreg, rax, entry_point, number_of_arguments, check_exceptions);
}
此时栈的情况如下:
来到MacroAssembler::call_VM_base
,删掉不必要的代码之后其实非常简洁,因为要做的事情前面都做的差不多了:
void MacroAssembler::call_VM_base(Register oop_result, // rax
Register java_thread, // noreg
Register last_java_sp, // rax
address entry_point, // InterpreterRuntime::newarray
int number_of_arguments, // 2
bool check_exceptions) {
// determine java_thread register
if (!java_thread->is_valid()) {
java_thread = r15_thread;
}
// determine last_java_sp register
if (!last_java_sp->is_valid()) {
last_java_sp = rsp;
}
// push java thread (becomes first argument of C function)
LP64_ONLY(mov(c_rarg0, r15_thread)); // thread作为第0个参数,其中保存了rbp和rsp,rbp可以获取许多结构。
// 设置最后一个java栈的有关信息,放入thread对象中的位置,在VM例程中可以用来生成用于遍历Java栈的对象。
// Only interpreter should have to set fp
set_last_Java_frame(java_thread, last_java_sp, rbp, NULL);
// do the call, remove parameters
MacroAssembler::call_VM_leaf_base(entry_point, number_of_arguments);
// reset last Java frame
// Only interpreter should have to clear fp
reset_last_Java_frame(java_thread, true);
// get oop result if there is one and reset the value in the thread
if (oop_result->is_valid()) {
get_vm_result(oop_result, java_thread);
}
}
主要就做了两件事情,第一个是获取了thread指针,并将其作为第0个参数,另外一个是在thread中设置了last_frame,这个结构在例程中可以用来获取栈中一些数据,比如可以获取到调用者有关的信息,我们当前的栈还是解释器栈,所以能够获取那些信息可以去看图1。第二个就是将thread对象中存储的返回值放入到了oop_result,也就是rax中。
由于MacroAssembler::call_VM_leaf_base
非常简单,所以也略过了,至此按照正常的返回方式就可以返回到调用者中去了。