36 invoke static/special/virtual/interface

Posted 蓝风9

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了36 invoke static/special/virtual/interface相关的知识,希望对你有一定的参考价值。

前言

// 呵呵 加更, 今天早上看到了一个很治愈很可爱的小朋友, 和小朋友聊天是一件很有意思的事情, 从他/她们身上也能了解到很多, 文末mark一下 

这四条指令的使用场景请参见 : Chapter 4. The class File Format

以下代码, 截图 基于 jdk9 

invokestatic

请直接参照这篇文章 : 方法调用的流程(invokestatic为例) 

直接获取的声明的类型上面的这个方法, 然后进行调用 

判断了一下 方法是否已经解析完成, 如果没有解析 则调用 InterpreterRuntime::resolve_from_cache 触发解析

然后 之后更新 rbx 为方法的 解析的目标地址, 然后 跳到给定的方法的 entry_point

entry_point 部分的调用代码 可以参考 15 main方法的栈帧信息 

当然 其中还有一些 处理标志, 处理返回地址, profiling 方法的调用信息 等等 

void TemplateTable::invokestatic(int byte_no) 
  transition(vtos, vtos);
  assert(byte_no == f1_byte, "use this argument");
  prepare_invoke(byte_no, rbx);  // get f1 Method*
  // do the call
  __ profile_call(rax);
  __ profile_arguments_type(rax, rbx, rbcp, false);
  __ jump_from_interpreted(rbx, rax);

invokespecial

类似于上面的 invokestatic 不过这里多了一些东西, 获取 receiver(this, 调用方法的对象), 对 receiver 做空校验 

直接获取的声明的类型上面的这个方法, 然后进行调用 

void TemplateTable::invokespecial(int byte_no) 
  transition(vtos, vtos);
  assert(byte_no == f1_byte, "use this argument");
  prepare_invoke(byte_no, rbx, noreg,  // get f1 Method*
                 rcx);  // get receiver also for null check
  __ verify_oop(rcx);
  __ null_check(rcx);
  // do the call
  __ profile_call(rax);
  __ profile_arguments_type(rax, rbx, rbcp, false);
  __ jump_from_interpreted(rbx, rax);

invokevirtual 

多态调用 receiver 上面的这个方法 

void TemplateTable::invokevirtual(int byte_no) 
  transition(vtos, vtos);
  assert(byte_no == f2_byte, "use this argument");
  prepare_invoke(byte_no,
                 rbx,    // method or vtable index
                 noreg,  // unused itable index
                 rcx, rdx); // recv, flags

  // rbx: index
  // rcx: receiver
  // rdx: flags

  invokevirtual_helper(rbx, rcx, rdx);


void TemplateTable::invokevirtual_helper(Register index,
                                         Register recv,
                                         Register flags) 
  // Uses temporary registers rax, rdx
  assert_different_registers(index, recv, rax, rdx);
  assert(index == rbx, "");
  assert(recv  == rcx, "");

  // Test for an invoke of a final method
  Label notFinal;
  __ movl(rax, flags);
  __ andl(rax, (1 << ConstantPoolCacheEntry::is_vfinal_shift));
  __ jcc(Assembler::zero, notFinal);

  const Register method = index;  // method must be rbx
  assert(method == rbx,
         "Method* must be rbx for interpreter calling convention");

  // do the call - the index is actually the method to call
  // that is, f2 is a vtable index if !is_vfinal, else f2 is a Method*

  // It's final, need a null check here!
  __ null_check(recv);

  // profile this call
  __ profile_final_call(rax);
  __ profile_arguments_type(rax, method, rbcp, true);

  __ jump_from_interpreted(method, rax);

  __ bind(notFinal);

  // get receiver klass
  __ null_check(recv, oopDesc::klass_offset_in_bytes());
  __ load_klass(rax, recv);

  // profile this call
  __ profile_virtual_call(rax, rlocals, rdx);
  // get target Method* & entry point
  __ lookup_virtual_method(rax, index, method);
  __ profile_called_method(method, rdx, rbcp);

  __ profile_arguments_type(rdx, method, rbcp, true);
  __ jump_from_interpreted(method, rdx);


// virtual method calling
void MacroAssembler::lookup_virtual_method(Register recv_klass,
                                           RegisterOrConstant vtable_index,
                                           Register method_result) 
  const int base = in_bytes(Klass::vtable_start_offset());
  assert(vtableEntry::size() * wordSize == wordSize, "else adjust the scaling in the code below");
  Address vtable_entry_addr(recv_klass,
                            vtable_index, Address::times_ptr,
                            base + vtableEntry::method_offset_in_bytes());
  movptr(method_result, vtable_entry_addr);

如果方法是 final(不可重写) 方法, 则对 reciver 做空校验, 直接调用 该方法 

如果是非 final 方法, 则查询 vtable(receiver 的 InstanceKlass) 中 vtable_index 对应的需要执行的方法, 然后调用该方法 

vtable 结构相关 请参见 : 根据 InstanceKlass 查找 vtable 的数据 

invokeinterface

多态调用 receiver 上面的这个方法 

void TemplateTable::invokeinterface(int byte_no) 
  transition(vtos, vtos);
  assert(byte_no == f1_byte, "use this argument");
  prepare_invoke(byte_no, rax, rbx,  // get f1 Klass*, f2 itable index
                 rcx, rdx); // recv, flags

  // rax: interface klass (from f1)
  // rbx: itable index (from f2)
  // rcx: receiver
  // rdx: flags

  // Special case of invokeinterface called for virtual method of
  // java.lang.Object.  See cpCacheOop.cpp for details.
  // This code isn't produced by javac, but could be produced by
  // another compliant java compiler.
  Label notMethod;
  __ movl(rlocals, rdx);
  __ andl(rlocals, (1 << ConstantPoolCacheEntry::is_forced_virtual_shift));

  __ jcc(Assembler::zero, notMethod);

  invokevirtual_helper(rbx, rcx, rdx);
  __ bind(notMethod);

  // Get receiver klass into rdx - also a null check
  __ restore_locals();  // restore r14
  __ null_check(rcx, oopDesc::klass_offset_in_bytes());
  __ load_klass(rdx, rcx);

  // profile this call
  __ profile_virtual_call(rdx, rbcp, rlocals);

  Label no_such_interface, no_such_method;

  __ lookup_interface_method(// inputs: rec. class, interface, itable index
                             rdx, rax, rbx,
                             // outputs: method, scan temp. reg
                             rbx, rbcp,
                             no_such_interface);

  // rbx: Method* to call
  // rcx: receiver
  // Check for abstract method error
  // Note: This should be done more efficiently via a throw_abstract_method_error
  //       interpreter entry point and a conditional jump to it in case of a null
  //       method.
  __ testptr(rbx, rbx);
  __ jcc(Assembler::zero, no_such_method);

  __ profile_called_method(rbx, rbcp, rdx);
  __ profile_arguments_type(rdx, rbx, rbcp, true);

  // do the call
  // rcx: receiver
  // rbx,: Method*
  __ jump_from_interpreted(rbx, rdx);
  __ should_not_reach_here();

  // exception handling code follows...
  // note: must restore interpreter registers to canonical
  //       state for exception handling to work correctly!

  __ bind(no_such_method);
  // throw exception
  __ pop(rbx);           // pop return address (pushed by prepare_invoke)
  __ restore_bcp();      // rbcp must be correct for exception handler   (was destroyed)
  __ restore_locals();   // make sure locals pointer is correct as well (was destroyed)
  __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodError));
  // the call_VM checks for exception, so we should never return here.
  __ should_not_reach_here();

  __ bind(no_such_interface);
  // throw exception
  __ pop(rbx);           // pop return address (pushed by prepare_invoke)
  __ restore_bcp();      // rbcp must be correct for exception handler   (was destroyed)
  __ restore_locals();   // make sure locals pointer is correct as well (was destroyed)
  __ call_VM(noreg, CAST_FROM_FN_PTR(address,
                   InterpreterRuntime::throw_IncompatibleClassChangeError));
  // the call_VM checks for exception, so we should never return here.
  __ should_not_reach_here();


// Look up the method for a megamorphic invokeinterface call.
// The target method is determined by <intf_klass, itable_index>.
// The receiver klass is in recv_klass.
// On success, the result will be in method_result, and execution falls through.
// On failure, execution transfers to the given label.
void MacroAssembler::lookup_interface_method(Register recv_klass,
                                             Register intf_klass,
                                             RegisterOrConstant itable_index,
                                             Register method_result,
                                             Register scan_temp,
                                             Label& L_no_such_interface) 
  assert_different_registers(recv_klass, intf_klass, method_result, scan_temp);
  assert(itable_index.is_constant() || itable_index.as_register() == method_result,
         "caller must use same register for non-constant itable index as for method");

  // Compute start of first itableOffsetEntry (which is at the end of the vtable)
  int vtable_base = in_bytes(Klass::vtable_start_offset());
  int itentry_off = itableMethodEntry::method_offset_in_bytes();
  int scan_step   = itableOffsetEntry::size() * wordSize;
  int vte_size    = vtableEntry::size_in_bytes();
  Address::ScaleFactor times_vte_scale = Address::times_ptr;
  assert(vte_size == wordSize, "else adjust times_vte_scale");

  movl(scan_temp, Address(recv_klass, Klass::vtable_length_offset()));

  // %%% Could store the aligned, prescaled offset in the klassoop.
  lea(scan_temp, Address(recv_klass, scan_temp, times_vte_scale, vtable_base));

  // Adjust recv_klass by scaled itable_index, so we can free itable_index.
  assert(itableMethodEntry::size() * wordSize == wordSize, "adjust the scaling in the code below");
  lea(recv_klass, Address(recv_klass, itable_index, Address::times_ptr, itentry_off));

  // for (scan = klass->itable(); scan->interface() != NULL; scan += scan_step) 
  //   if (scan->interface() == intf) 
  //     result = (klass + scan->offset() + itable_index);
  //   
  // 
  Label search, found_method;

  for (int peel = 1; peel >= 0; peel--) 
    movptr(method_result, Address(scan_temp, itableOffsetEntry::interface_offset_in_bytes()));
    cmpptr(intf_klass, method_result);

    if (peel) 
      jccb(Assembler::equal, found_method);
     else 
      jccb(Assembler::notEqual, search);
      // (invert the test to fall through to found_method...)
    

    if (!peel)  break;

    bind(search);

    // Check that the previous entry is non-null.  A null entry means that
    // the receiver class doesn't implement the interface, and wasn't the
    // same as when the caller was compiled.
    testptr(method_result, method_result);
    jcc(Assembler::zero, L_no_such_interface);
    addptr(scan_temp, scan_step);
  

  bind(found_method);

  // Got a hit.
  movl(scan_temp, Address(scan_temp, itableOffsetEntry::offset_offset_in_bytes()));
  movptr(method_result, Address(recv_klass, scan_temp, Address::times_1));

如果 "is the interface reference forced to virtual mode", 直接采用 invokevirtual 的方式调用当前方法 

如果 itable 里面没有找到目标接口, 抛出 IncompatibleClassChangeError

如果 itable 里面没有找到目标方法, 抛出 AbstractMethodError

然后 重点是 在 itable 里面查询 目标方法 

获取到 目标方法之后, 调用目标方法 

我们吧 lookup_interface_method 里面的循环展开来看  

void MacroAssembler::lookup_interface_method(Register recv_klass,
                                             Register intf_klass,
                                             RegisterOrConstant itable_index,
                                             Register method_result,
                                             Register scan_temp,
                                             Label& L_no_such_interface) 
  assert_different_registers(recv_klass, intf_klass, method_result, scan_temp);
  assert(itable_index.is_constant() || itable_index.as_register() == method_result,
         "caller must use same register for non-constant itable index as for method");

  // Compute start of first itableOffsetEntry (which is at the end of the vtable)
  int vtable_base = in_bytes(Klass::vtable_start_offset());
  int itentry_off = itableMethodEntry::method_offset_in_bytes();
  int scan_step   = itableOffsetEntry::size() * wordSize;
  int vte_size    = vtableEntry::size_in_bytes();
  Address::ScaleFactor times_vte_scale = Address::times_ptr;
  assert(vte_size == wordSize, "else adjust times_vte_scale");

  movl(scan_temp, Address(recv_klass, Klass::vtable_length_offset()));

  // %%% Could store the aligned, prescaled offset in the klassoop.
  lea(scan_temp, Address(recv_klass, scan_temp, times_vte_scale, vtable_base));

  // Adjust recv_klass by scaled itable_index, so we can free itable_index.
  assert(itableMethodEntry::size() * wordSize == wordSize, "adjust the scaling in the code below");
  lea(recv_klass, Address(recv_klass, itable_index, Address::times_ptr, itentry_off));

  // for (scan = klass->itable(); scan->interface() != NULL; scan += scan_step) 
  //   if (scan->interface() == intf) 
  //     result = (klass + scan->offset() + itable_index);
  //   
  // 
  Label search, found_method;

  movptr(method_result, Address(scan_temp, itableOffsetEntry::interface_offset_in_bytes()));
  cmpptr(intf_klass, method_result);
  jccb(Assembler::equal, found_method);

  bind(search);

  // Check that the previous entry is non-null.  A null entry means that
  // the receiver class doesn't implement the interface, and wasn't the
  // same as when the caller was compiled.
  testptr(method_result, method_result);
  jcc(Assembler::zero, L_no_such_interface);
  addptr(scan_temp, scan_step);

  movptr(method_result, Address(scan_temp, itableOffsetEntry::interface_offset_in_bytes()));
  cmpptr(intf_klass, method_result);
  jccb(Assembler::notEqual, search);

  bind(found_method);

  // Got a hit.
  movl(scan_temp, Address(scan_temp, itableOffsetEntry::offset_offset_in_bytes()));
  movptr(method_result, Address(recv_klass, scan_temp, Address::times_1));

就回发现 他的思路是 遍历 itable 里面的 itableOffsetEntry, 查询 目标接口 对应的 itableOffsetEntry

然后获取 目标接口 对应的 methodEntry 的偏移 

然后在加上 itable_index 获取我们需要的目标方法的地址 

itable 结构相关 请参见 : 根据 InstanceKlass 查找 itable 的数据

添加于2020.07.04 - bytecodeInterpreter.cpp 中的逻辑代码

bytecodeInterpreter.cpp 中可读性更好的代码 

      CASE(_invokeinterface): 
        u2 index = Bytes::get_native_u2(pc+1);

        // QQQ Need to make this as inlined as possible. Probably need to split all the bytecode cases
        // out so c++ compiler has a chance for constant prop to fold everything possible away.

        ConstantPoolCacheEntry* cache = cp->entry_at(index);
        if (!cache->is_resolved((Bytecodes::Code)opcode)) 
          CALL_VM(InterpreterRuntime::resolve_from_cache(THREAD, (Bytecodes::Code)opcode),
                  handle_exception);
          cache = cp->entry_at(index);
        

        istate->set_msg(call_method);

        // Special case of invokeinterface called for virtual method of
        // java.lang.Object.  See cpCacheOop.cpp for details.
        // This code isn't produced by javac, but could be produced by
        // another compliant java compiler.
        if (cache->is_forced_virtual()) 
          Method* callee;
          CHECK_NULL(STACK_OBJECT(-(cache->parameter_size())));
          if (cache->is_vfinal()) 
            callee = cache->f2_as_vfinal_method();
            // Profile 'special case of invokeinterface' final call.
            BI_PROFILE_UPDATE_FINALCALL();
           else 
            // Get receiver.
            int parms = cache->parameter_size();
            // Same comments as invokevirtual apply here.
            oop rcvr = STACK_OBJECT(-parms);
            VERIFY_OOP(rcvr);
            Klass* rcvrKlass = rcvr->klass();
            callee = (Method*) rcvrKlass->method_at_vtable(cache->f2_as_index());
            // Profile 'special case of invokeinterface' virtual call.
            BI_PROFILE_UPDATE_VIRTUALCALL(rcvrKlass);
          
          istate->set_callee(callee);
          istate->set_callee_entry_point(callee->from_interpreted_entry());
#ifdef VM_JVMTI
          if (JvmtiExport::can_post_interpreter_events() && THREAD->is_interp_only_mode()) 
            istate->set_callee_entry_point(callee->interpreter_entry());
          
#endif /* VM_JVMTI */
          istate->set_bcp_advance(5);
          UPDATE_PC_AND_RETURN(0); // I'll be back...
        

        // this could definitely be cleaned up QQQ
        Method* callee;
        Klass* iclass = cache->f1_as_klass();
        // InstanceKlass* interface = (InstanceKlass*) iclass;
        // get receiver
        int parms = cache->parameter_size();
        oop rcvr = STACK_OBJECT(-parms);
        CHECK_NULL(rcvr);
        InstanceKlass* int2 = (InstanceKlass*) rcvr->klass();
        itableOffsetEntry* ki = (itableOffsetEntry*) int2->start_of_itable();
        int i;
        for ( i = 0 ; i < int2->itable_length() ; i++, ki++ ) 
          if (ki->interface_klass() == iclass) break;
        
        // If the interface isn't found, this class doesn't implement this
        // interface.  The link resolver checks this but only for the first
        // time this interface is called.
        if (i == int2->itable_length()) 
          VM_JAVA_ERROR(vmSymbols::java_lang_IncompatibleClassChangeError(), "", note_no_trap);
        
        int mindex = cache->f2_as_index();
        itableMethodEntry* im = ki->first_method_entry(rcvr->klass());
        callee = im[mindex].method();
        if (callee == NULL) 
          VM_JAVA_ERROR(vmSymbols::java_lang_AbstractMethodError(), "", note_no_trap);
        

        // Profile virtual call.
        BI_PROFILE_UPDATE_VIRTUALCALL(rcvr->klass());

        istate->set_callee(callee);
        istate->set_callee_entry_point(callee->from_interpreted_entry());
#ifdef VM_JVMTI
        if (JvmtiExport::can_post_interpreter_events() && THREAD->is_interp_only_mode()) 
          istate->set_callee_entry_point(callee->interpreter_entry());
        
#endif /* VM_JVMTI */
        istate->set_bcp_advance(5);
        UPDATE_PC_AND_RETURN(0); // I'll be back...
      

      CASE(_invokevirtual):
      CASE(_invokespecial):
      CASE(_invokestatic): 
        u2 index = Bytes::get_native_u2(pc+1);

        ConstantPoolCacheEntry* cache = cp->entry_at(index);
        // QQQ Need to make this as inlined as possible. Probably need to split all the bytecode cases
        // out so c++ compiler has a chance for constant prop to fold everything possible away.

        if (!cache->is_resolved((Bytecodes::Code)opcode)) 
          CALL_VM(InterpreterRuntime::resolve_from_cache(THREAD, (Bytecodes::Code)opcode),
                  handle_exception);
          cache = cp->entry_at(index);
        

        istate->set_msg(call_method);
        
          Method* callee;
          if ((Bytecodes::Code)opcode == Bytecodes::_invokevirtual) 
            CHECK_NULL(STACK_OBJECT(-(cache->parameter_size())));
            if (cache->is_vfinal()) 
              callee = cache->f2_as_vfinal_method();
              // Profile final call.
              BI_PROFILE_UPDATE_FINALCALL();
             else 
              // get receiver
              int parms = cache->parameter_size();
              // this works but needs a resourcemark and seems to create a vtable on every call:
              // Method* callee = rcvr->klass()->vtable()->method_at(cache->f2_as_index());
              //
              // this fails with an assert
              // InstanceKlass* rcvrKlass = InstanceKlass::cast(STACK_OBJECT(-parms)->klass());
              // but this works
              oop rcvr = STACK_OBJECT(-parms);
              VERIFY_OOP(rcvr);
              Klass* rcvrKlass = rcvr->klass();
              /*
                Executing this code in java.lang.String:
                    public String(char value[]) 
                          this.count = value.length;
                          this.value = (char[])value.clone();
                     

                 a find on rcvr->klass() reports:
                 type array chartype array class
                  - klass: other class

                  but using InstanceKlass::cast(STACK_OBJECT(-parms)->klass()) causes in assertion failure
                  because rcvr->klass()->is_instance_klass() == 0
                  However it seems to have a vtable in the right location. Huh?
                  Because vtables have the same offset for ArrayKlass and InstanceKlass.
              */
              callee = (Method*) rcvrKlass->method_at_vtable(cache->f2_as_index());
              // Profile virtual call.
              BI_PROFILE_UPDATE_VIRTUALCALL(rcvrKlass);
            
           else 
            if ((Bytecodes::Code)opcode == Bytecodes::_invokespecial) 
              CHECK_NULL(STACK_OBJECT(-(cache->parameter_size())));
            
            callee = cache->f1_as_method();

            // Profile call.
            BI_PROFILE_UPDATE_CALL();
          

          istate->set_callee(callee);
          istate->set_callee_entry_point(callee->from_interpreted_entry());
#ifdef VM_JVMTI
          if (JvmtiExport::can_post_interpreter_events() && THREAD->is_interp_only_mode()) 
            istate->set_callee_entry_point(callee->interpreter_entry());
          
#endif /* VM_JVMTI */
          istate->set_bcp_advance(3);
          UPDATE_PC_AND_RETURN(0); // I'll be back...
        
      

完 

-- 2022.05.04 08:44 ^_^ 

参考

方法调用的流程(invokestatic为例)

根据 InstanceKlass 查找 vtable 的数据

根据 InstanceKlass 查找 itable 的数据

以上是关于36 invoke static/special/virtual/interface的主要内容,如果未能解决你的问题,请参考以下文章

在 Dispatcher.Invoke() 中使用 lambda 表达式作为参数

P/Invoke 使用 vc140 编译器中断

Cloud Functions 返回 403 错误也具有 Cloud Functions Invoker 权限

使用 gem 安装 rails,错误加载命令:安装未定义的方法“invoke_with_build_args”

Dubbo:深入理解Dubbo核心模型Invoker

Invoke BeginInvoke