VM例程调用

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);

}

此时栈的情况如下:

stackframe-call_VM.drawio

来到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非常简单,所以也略过了,至此按照正常的返回方式就可以返回到调用者中去了。

从jit代码调用

posted @ 2022-09-21 22:58  aana  阅读(95)  评论(0编辑  收藏  举报