zoukankan      html  css  js  c++  java
  • 第37篇Interpreter::_invoke_return_entry等例程

    我们在之前介绍过return字节码指令的执行逻辑,这个字节码指令只会执行释放锁和退出当前栈帧的操作,但是当控制权转移给调用者时,还需要恢复调用者的栈帧状态,如让%r13指向bcp、%r14指向局部变量表等,另外还需要弹出压入的实参、跳转到调用者的下一个字节码指令继续执行,而这一切操作都是由Interpreter::_return_entry例程负责的。这个例程在之前介绍invokevirtual和invokeinterface等字节码指令时介绍过,当使用这些字节码指令调用方法时,会根据方法的返回类型压入Interpreter::_return_entry一维数组中保存的对应例程地址,这样return字节码指令执行完成后就会执行这段例程。

    在invokevirtual和invokeinterface等字节码指令中通过调用如下函数获取对应的例程入口:

    address* TemplateInterpreter::invoke_return_entry_table_for(Bytecodes::Code code) {
      switch (code) {
      case Bytecodes::_invokestatic:
      case Bytecodes::_invokespecial:
      case Bytecodes::_invokevirtual:
      case Bytecodes::_invokehandle:
        return Interpreter::invoke_return_entry_table();
      case Bytecodes::_invokeinterface:
        return Interpreter::invokeinterface_return_entry_table();
      default:
        fatal(err_msg("invalid bytecode: %s", Bytecodes::name(code)));
        return NULL;
      }
    }
    

    可以看到invokeinterface字节码从Interpreter::_invokeinterface_return_entry数组中获取对应的例程,而其它的从Interpreter::_invoke_return_entry一维数组中获取。如下:

    address TemplateInterpreter::_invoke_return_entry[TemplateInterpreter::number_of_return_addrs];
    address TemplateInterpreter::_invokeinterface_return_entry[TemplateInterpreter::number_of_return_addrs];
    address TemplateInterpreter::_invokedynamic_return_entry[TemplateInterpreter::number_of_return_addrs];

    当返回一维数组后,会根据方法返回类型进一步确定例程入口地址。下面我们就看一下这些例程的生成过程。 

    TemplateInterpreterGenerator::generate_all()函数中会生成Interpreter::_return_entry入口,如下:

    {
        CodeletMark cm(_masm, "invoke return entry points");
        const TosState states[]           = {itos, itos, itos, itos, ltos, ftos, dtos, atos, vtos};
        const int invoke_length           = Bytecodes::length_for(Bytecodes::_invokestatic);     // invoke_length=3
        const int invokeinterface_length  = Bytecodes::length_for(Bytecodes::_invokeinterface);  // invokeinterface=5
    
        for (int i = 0; i < Interpreter::number_of_return_addrs; i++) { // number_of_return_addrs = 9
           TosState state = states[i]; // TosState是枚举类型
           Interpreter::_invoke_return_entry[i] = generate_return_entry_for(state, invoke_length, sizeof(u2)); 
           Interpreter::_invokeinterface_return_entry[i] = generate_return_entry_for(state, invokeinterface_length, sizeof(u2));
        }
      }
    

    除invokedynamic字节码指令外,其它的方法调用指令在解释执行完成后都需要调用由generate_return_entry_for()函数生成的例程,生成例程的generate_return_entry_for()函数实现如下:

    address TemplateInterpreterGenerator::generate_return_entry_for(TosState state, int step, size_t index_size) {
    
      // Restore stack bottom in case万一 i2c adjusted stack
      __ movptr(rsp, Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize)); // interpreter_frame_last_sp_offset=-2
      // and NULL it as marker that esp is now tos until next java call
      __ movptr(Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize), (int32_t)NULL_WORD);
    
      __ restore_bcp();
      __ restore_locals();
    
      // ...
    
      const Register cache = rbx;
      const Register index = rcx;
      __ get_cache_and_index_at_bcp(cache, index, 1, index_size);
    
      const Register flags = cache;
      __ movl(flags, Address(cache, index, Address::times_ptr, ConstantPoolCache::base_offset() + ConstantPoolCacheEntry::flags_offset()));
      __ andl(flags, ConstantPoolCacheEntry::parameter_size_mask);
      __ lea(rsp, Address(rsp, flags, Interpreter::stackElementScale())   ); // 栈元素标量为8
      __ dispatch_next(state, step);
    
      return entry;
    }

    根据state的不同(方法的返回类型的不同),会在选择执行调用者方法的下一个字节码指令时,决定要从字节码指令的哪个入口处开始执行。我们看一下,当传递的state为itos(也就是当方法的返回类型为int时)时生成的汇编代码如下:

    // 将-0x10(%rbp)存储到%rsp后,置空-0x10(%rbp)
    0x00007fffe1006ce0: mov    -0x10(%rbp),%rsp   // 更改rsp
    0x00007fffe1006ce4: movq   $0x0,-0x10(%rbp)   // 更改栈中特定位置的值
    // 恢复bcp和locals,使%r14指向本地变量表,%r13指向bcp
    0x00007fffe1006cec: mov    -0x38(%rbp),%r13
    0x00007fffe1006cf0: mov    -0x30(%rbp),%r14
     // 获取ConstantPoolCacheEntry的索引并加载到%ecx
    0x00007fffe1006cf4: movzwl 0x1(%r13),%ecx     
    
     // 获取栈中-0x28(%rbp)的ConstantPoolCache并加载到%ecx
    0x00007fffe1006cf9: mov    -0x28(%rbp),%rbx   
    // shl是逻辑左移,获取字偏移
    0x00007fffe1006cfd: shl    $0x2,%ecx           
    // 获取ConstantPoolCacheEntry中的_flags属性值
    0x00007fffe1006d00: mov    0x28(%rbx,%rcx,8),%ebx
    // 获取_flags中的低8位中保存的参数大小
    0x00007fffe1006d04: and    $0xff,%ebx          
    
    // lea指令将地址加载到内存寄存器中,也就是恢复调用方法之前栈的样子
    0x00007fffe1006d0a: lea    (%rsp,%rbx,8),%rsp  
    
    // 跳转到下一指令执行
    0x00007fffe1006d0e: movzbl 0x3(%r13),%ebx  
    0x00007fffe1006d13: add    $0x3,%r13
    0x00007fffe1006d17: movabs $0x7ffff73b7ca0,%r10
    0x00007fffe1006d21: jmpq   *(%r10,%rbx,8)  

    如上汇编代码的逻辑非常简单,这里不再过多介绍。  

    公众号 深入剖析Java虚拟机HotSpot 已经更新虚拟机源代码剖析相关文章到60+,欢迎关注,如果有任何问题,可加作者微信mazhimazh,拉你入虚拟机群交流

      

  • 相关阅读:
    【转】Visual Studio 选择相同变量高亮
    匿名委托(方法) 以 ThreadStart 为例
    执行多个Sql脚本,Sqlplus
    【转】Win8 下 管理无线网络
    ORA-22992:没法使用从远程表选择的LOB定位器
    【转】NET中管理数字证书(Digital Certificate)的一些类
    修改数据库内存
    C# 计算一串字符串算法
    奇数平方的九宫格
    lua 的一些常用概念
  • 原文地址:https://www.cnblogs.com/mazhimazhi/p/15506946.html
Copyright © 2011-2022 走看看