美文网首页
模板解释器

模板解释器

作者: 程序员札记 | 来源:发表于2022-06-26 09:57 被阅读0次

    解释器生成

    解释器的机器代码片段都是在
    TemplateInterpreterGenerator::generate_all()中生成的,下面将分小节详细展示该函数的具体细节,以及解释器某个组件的机器代码生成过程与逻辑。与第4章不同的是,本节中的各部分出现的顺序与它们在代码中的顺序不一致。

    在研究解释器前了解调试手段是有必要的。由于运行时生成的机器代码是人类不可读的二进制形式,要想阅读它们,可以下载hsdis-amd64插件,并将该插件放到编译后的JDK中的/lib/server目录下面,然后开启虚拟机参数-XX:+PrintAssembly和-XX:+PrintInterpreter,然后便可输出解释器各个例程的机器代码的汇编表示形式了。也可以开启-XX:+TraceBytecodes跟踪解释器正在执行的字节码和对应方法。
    Java的模板解析执行需要模板表与转发表的支持,而这2个表中的数据在HotSpot虚拟机启动时就会初始化。

    源码

    在启动虚拟机阶段会调用init_globals()方法初始化全局模块,在这个方法中通过调用interpreter_init()方法初始化模板解释器,调用栈如下:

    TemplateInterpreter::initialize() templateInterpreter.cpp
    interpreter_init() interpreter.cpp
    init_globals() init.cpp
    Threads::create_vm() thread.cpp
    JNI_CreateJavaVM() jni.cpp
    InitializeJVM() java.c
    JavaMain() java.c
    start_thread() pthread_create.c
    

    init_globals

    在介绍字节码的执行之前,需要先知道字节码指令的定义。在Bytecodes::initialize()函数中会定义字节码指令的一些属性。这个函数的调用链如下:

    init_globals()
        bytecodes_init() 
             Bytecodes::initialize()
    

    在Bytecodes::initialize()函数中有类似这样的定义:

    //  bytecode               bytecode name           format   wide f.   result tp  stk traps
    def(_nop                 , "nop"                 , "b"    , NULL    , T_VOID   ,  0, false);
    def(_aconst_null         , "aconst_null"         , "b"    , NULL    , T_OBJECT ,  1, false);
    def(_iconst_m1           , "iconst_m1"           , "b"    , NULL    , T_INT    ,  1, false);
    def(_iconst_0            , "iconst_0"            , "b"    , NULL    , T_INT    ,  1, false);
    def(_iconst_1            , "iconst_1"            , "b"    , NULL    , T_INT    ,  1, false);
    // ...
    

    现在Java虚拟机规范定义的202个字节码指令都会向上图那样,调用def()函数进行定义,我们需要重点关注调用def()函数时传递的参数bytecode name、format等。 下面一个一个解释,如下:

    bytecode name就是字节码名称;
    wide表示字节码前面是否可以加wide,如果可以,则值为"wbii";
    result tp表示指令执行后的结果类型,如为T_ILLEGAL时,表示只参考当前字节码无法决定执行结果的类型,如_invokevirtual方法调用指令,结果类型应该为方法返回类型,但是此时只参考这个调用方法的字节码指令是无法决定的;
    stk表示对表达式栈深度的影响,如_nop指令不执行任何操作,所以对表达式栈的深度无影响,stk的值为0;当用_iconst_0向栈中压入0时,栈的深度增加1,所以stk的值为1。当为_lconst_0时,栈的深度会增加2;当为_lstore_0时,栈的深度会减少2;
    traps表示can_trap,这个比较重要,在后面会详细介绍。
    format,这个属性能表达2个意思,首先能表达字节码的格式,另外还能表示字节码的长度。
    下面我们需要重点介绍一下format这个参数。format表示字节码的格式,当字符串中有一个字符时就是一个字节长度的字节码,当为2个字符时就是2个字节长度的字节码…,如_iconst_0就是一个字节宽度的字节码,_istore的format为"bi",所以是2个字节宽度。format还可能为空字符串,当为空字符串时,表示当前的字节码不是Java虚拟机规范中定义的字节码,如为了提高解释执行效率的_fast_agetfield、_fast_bgetfield等字节码,这些字节码是虚拟机内部定义的。还能表达字节码的格式,其中的字符串中各个字符的含义如下:

    b: 表示字节码指令是非可变长度的,所以对于tableswitch、lookupswitch这种可变长度的指令来说,format字符串中不会含有b字符;
    c:操作数为有符号的常量,如bipush指令将byte带符号扩展为一个int类型的值,然后将这个值入栈到操作数栈中;
    i:操作数为无符号的本地变量表索引值,如iload指令从局部变量表加载一个int类型的值到操作数栈中;
    j:操作数为常量池缓存的索引,注意常量池缓存索引不同与常量池索引,关于常量池索引,在《深入剖析Java虚拟机:源码剖析与实例详解》基础卷中详细介绍过,这里不再介绍;
    k:操作数为无符号的常量池索引,如ldc指令将从运行时常量池中提取数据并压入操作数栈,所以格式为"bk";
    o:操作数为分支偏移,如ifeq表示整数与零比较,如果整数为0,则比较结果为真,将操作数看为分支偏移量进行跳转,所以格式为”boo“;
    _:可直接忽略
    w:可用来扩展局部变量表索引的字节码,这些字节码有iload、fload等,所以wild的值为"wbii";

    调用的def()函数的实现如下:

    void Bytecodes::def(
    Code          code,
    const char*   name,
    const char*   format,
    const char*   wide_format,
    BasicType     result_type,
    int           depth,
    bool          can_trap,
    Code          java_code
    ) {
      int len  = (format      != NULL ? (int) strlen(format)      : 0);
      int wlen = (wide_format != NULL ? (int) strlen(wide_format) : 0);
    
      _name          [code] = name;
      _result_type   [code] = result_type;
      _depth         [code] = depth;
      _lengths       [code] = (wlen << 4) | (len & 0xF); // 0xF的二进制值为1111
      _java_code     [code] = java_code;
    
    
      int bc_flags = 0;
      if (can_trap){
        // ldc、ldc_w、ldc2_w、_aload_0、iaload、iastore、idiv、ldiv、ireturn等
        // 字节码指令都会含有_bc_can_trap
        bc_flags |= _bc_can_trap; 
      }
      if (java_code != code){
        bc_flags |= _bc_can_rewrite; // 虚拟机内部定义的指令都会有_bc_can_rewrite
      }
    
      // 在这里对_flags赋值操作
      _flags[(u1)code+0*(1<<BitsPerByte)] = compute_flags(format,      bc_flags);
      _flags[(u1)code+1*(1<<BitsPerByte)] = compute_flags(wide_format, bc_flags);
    }
    

    其中的_name、_result_type等都是在Bytecodes类中定义的静态数组,其下标为Opcode值,而存储的值就是name、result_type等。这些变量的定义如下:

    const char*     Bytecodes::_name          [Bytecodes::number_of_codes];
    BasicType       Bytecodes::_result_type   [Bytecodes::number_of_codes];
    s_char          Bytecodes::_depth         [Bytecodes::number_of_codes];
    u_char          Bytecodes::_lengths       [Bytecodes::number_of_codes];
    Bytecodes::Code Bytecodes::_java_code     [Bytecodes::number_of_codes];
    u_short         Bytecodes::_flags         [(1<<BitsPerByte)*2];
    

    Bytecodes::number_of_codes的值为234,足够存储所有的字节码指令了(包含虚拟机内部扩展的指令)。

    回看Bytecodes::def()函数,通过调用compute_flags()函数根据传入的wide_format和format来计算字节码的一些属性,然后存储到高8位和低8位中。调用的compute_flags()函数的实现如下:

    int Bytecodes::compute_flags(const char* format, int more_flags) {
      if (format == NULL) {
          return 0;  // not even more_flags
      }
    
      int flags = more_flags;
      const char* fp = format;
      switch (*fp) {
      case '\0':
        flags |= _fmt_not_simple; // but variable
        break;
      case 'b':
        flags |= _fmt_not_variable;  // but simple
        ++fp;  // skip 'b'
        break;
      case 'w':
        flags |= _fmt_not_variable | _fmt_not_simple;
        ++fp;  // skip 'w'
        guarantee(*fp == 'b', "wide format must start with 'wb'");
        ++fp;  // skip 'b'
        break;
      }
    
      int has_nbo = 0, has_jbo = 0, has_size = 0;
      for (;;) {
        int this_flag = 0;
        char fc = *fp++;
        switch (fc) {
        case '\0':  // end of string
          assert(flags == (jchar)flags, "change _format_flags");
          return flags;
    
        case '_': continue;         // ignore these
    
        case 'j': this_flag = _fmt_has_j; has_jbo = 1; break;
        case 'k': this_flag = _fmt_has_k; has_jbo = 1; break;
        case 'i': this_flag = _fmt_has_i; has_jbo = 1; break;
        case 'c': this_flag = _fmt_has_c; has_jbo = 1; break;
        case 'o': this_flag = _fmt_has_o; has_jbo = 1; break;
    
        case 'J': this_flag = _fmt_has_j; has_nbo = 1; break;
        ...
        default:  guarantee(false, "bad char in format");
        }// 结束switch
    
        flags |= this_flag;
    
        guarantee(!(has_jbo && has_nbo), "mixed byte orders in format");
        if (has_nbo){
          flags |= _fmt_has_nbo;
        }
    
        int this_size = 1;
        if (*fp == fc) {
          // advance beyond run of the same characters
          this_size = 2;
          while (*++fp == fc){
              this_size++;
          }
          switch (this_size) {
          case 2: flags |= _fmt_has_u2; break; // 如sipush、ldc_w、ldc2_w、wide iload等
          case 4: flags |= _fmt_has_u4; break; // 如goto_w和invokedynamic指令
          default:
              guarantee(false, "bad rep count in format");
          }
        }
    
        has_size = this_size;
      }
    }
    

    函数要根据wide_format和format来计算flags的值,通过flags中的值能够表示字节码的b、c、i、j、k、o、w(在之前介绍format时介绍过)和字节码操作数的大小(操作数是2字节还是4字节)。以_fmt开头的一些变量在枚举类中已经定义,如下:

    // Flag bits derived from format strings, can_trap, can_rewrite, etc.:
    enum Flags {
    // semantic flags:
    _bc_can_trap      = 1<<0,     // bytecode execution can trap(卡住) or block
    // 虚拟机内部定义的字节码指令都会含有这个标识
    _bc_can_rewrite   = 1<<1,     // bytecode execution has an alternate(代替者) form
    
    // format bits (determined only by the format string):
    _fmt_has_c        = 1<<2,     // constant, such as sipush "bcc"
    _fmt_has_j        = 1<<3,     // constant pool cache index, such as getfield "bjj"
    _fmt_has_k        = 1<<4,     // constant pool index, such as ldc "bk"
    _fmt_has_i        = 1<<5,     // local index, such as iload
    _fmt_has_o        = 1<<6,     // offset, such as ifeq
    
    _fmt_has_nbo      = 1<<7,     // contains native-order field(s)
    _fmt_has_u2       = 1<<8,     // contains double-byte field(s)
    _fmt_has_u4       = 1<<9,     // contains quad-byte field
    _fmt_not_variable = 1<<10,    // not of variable length (simple or wide) 不可变长度的指令
    _fmt_not_simple   = 1<<11,    // either wide or variable length 或者是可加wild的字节码指令,或者是可变长度的指令
    _all_fmt_bits     = (_fmt_not_simple*2 - _fmt_has_c),
    
    // ...
    };
    

    与format的对应关系如下:

    image.png

    这样通过组合就可表示出不同的值,枚举类中定义了常用的组合如下:

    _fmt_b      = _fmt_not_variable,
    _fmt_bc     = _fmt_b | _fmt_has_c,
    _fmt_bi     = _fmt_b | _fmt_has_i,
    _fmt_bkk    = _fmt_b | _fmt_has_k | _fmt_has_u2,
    _fmt_bJJ    = _fmt_b | _fmt_has_j | _fmt_has_u2 | _fmt_has_nbo,
    _fmt_bo2    = _fmt_b | _fmt_has_o | _fmt_has_u2,
    _fmt_bo4    = _fmt_b | _fmt_has_o | _fmt_has_u4
    

    例如字节码为bipush时,format就是"bc",那么flags的值为_fmt_b | _fmt_has_c,ldc字节码的format为"bk",则flags的值为_fmt_b | _fmt_has_k。

    interpreter_init()

    方法主要是通过调用TemplateInterpreter::initialize()方法来完成逻辑,initialize()方法的实现如下:

    源代码位置:/src/share/vm/interpreter/templateInterpreter.cpp

    void TemplateInterpreter::initialize()
    {
        if ( _code != NULL )
            return;
    /*
     * 抽象解释器AbstractInterpreter的初始化,AbstractInterpreter是基于汇编模型的解释器的共同基类,
     * 定义了解释器和解释器生成器的抽象接口
     */
        AbstractInterpreter::initialize();
    /* 模板表TemplateTable的初始化,模板表TemplateTable保存了各个字节码的模板 */
        TemplateTable::initialize();
    /* generate interpreter */
        {
            ResourceMark    rm;
            int     code_size = InterpreterCodeSize;
    /* CodeCache的Stub队列StubQueue的初始化 */
            _code = new StubQueue( new InterpreterCodeletInterface, code_size, NULL, "Interpreter" );
    /* 实例化模板解释器生成器对象TemplateInterpreterGenerator */
            InterpreterGenerator g( _code );
        }
    /* initialize dispatch table */
        _active_table = _normal_table;
    }
    

    模板解释器的初始化包括如下几个方面:

    • 抽象解释器AbstractInterpreter的初始化,AbstractInterpreter是基于汇编模型的解释器的共同基类,定义了解释器和解释器生成器的抽象接口。
    • 模板表TemplateTable的初始化,模板表TemplateTable保存了各个字节码的模板(目标代码生成函数和参数);
    • CodeCache的Stub队列StubQueue的初始化;
    • 解释器生成器InterpreterGenerator的初始化。

    在之前介绍过,在TemplateInterpreter::initialize()中通过调用语句来间接调用generate_method_entry()generate_normal_entry()创建方法执行的栈帧:

    InterpreterGenerator g(_code);
    

    不过在如上语句调用之前,首先需要调用TemplateInterpreter类中的initialize()方法初始化模板表,如下:

    TemplateTable::initialize();
    

    模板表TemplateTable保存了各个字节码的模板(目标代码生成函数和参数),initialize()方法的实现如下:

    源代码位置:/src/share/vm/interpreter/templateInterpreter.cpp

    void TemplateTable::initialize()
    {
        if ( _is_initialized )
            return;
        _bs = Universe::heap()->barrier_set();
    /* For better readability */
        const char  _   = ' ';
        const int   ____    = 0;
        const int   ubcp    = 1 << Template::uses_bcp_bit;
        const int   disp    = 1 << Template::does_dispatch_bit;
        const int   clvm    = 1 << Template::calls_vm_bit;
        const int   iswd    = 1 << Template::wide_bit;
    /*
     * interpr. templates
     * Java spec bytecodes ubcp|disp|clvm|iswd in out generator argument
     */
        def( Bytecodes::_nop, ____ | ____ | ____ | ____, vtos, vtos, nop, _ );
        def( Bytecodes::_aconst_null, ____ | ____ | ____ | ____, vtos, atos, aconst_null, _ );
        def( Bytecodes::_iconst_m1, ____ | ____ | ____ | ____, vtos, itos, iconst, -1 );
        def( Bytecodes::_iconst_0, ____ | ____ | ____ | ____, vtos, itos, iconst, 0 );
    /* ... */
        def( Bytecodes::_tableswitch, ubcp | disp | ____ | ____, itos, vtos, tableswitch, _ );
        def( Bytecodes::_lookupswitch, ubcp | disp | ____ | ____, itos, itos, lookupswitch, _ );
        def( Bytecodes::_ireturn, ____ | disp | clvm | ____, itos, itos, _return, itos );
        def( Bytecodes::_lreturn, ____ | disp | clvm | ____, ltos, ltos, _return, ltos );
        def( Bytecodes::_freturn, ____ | disp | clvm | ____, ftos, ftos, _return, ftos );
        def( Bytecodes::_dreturn, ____ | disp | clvm | ____, dtos, dtos, _return, dtos );
        def( Bytecodes::_areturn, ____ | disp | clvm | ____, atos, atos, _return, atos );
        def( Bytecodes::_return, ____ | disp | clvm | ____, vtos, vtos, _return, vtos );
        def( Bytecodes::_getstatic, ubcp | ____ | clvm | ____, vtos, vtos, getstatic, f1_byte );
        def( Bytecodes::_putstatic, ubcp | ____ | clvm | ____, vtos, vtos, putstatic, f2_byte );
        def( Bytecodes::_getfield, ubcp | ____ | clvm | ____, vtos, vtos, getfield, f1_byte );
        def( Bytecodes::_putfield, ubcp | ____ | clvm | ____, vtos, vtos, putfield, f2_byte );
        def( Bytecodes::_invokevirtual, ubcp | disp | clvm | ____, vtos, vtos, invokevirtual, f2_byte );
        def( Bytecodes::_invokespecial, ubcp | disp | clvm | ____, vtos, vtos, invokespecial, f1_byte );
        def( Bytecodes::_invokestatic, ubcp | disp | clvm | ____, vtos, vtos, invokestatic, f1_byte );
        def( Bytecodes::_invokeinterface, ubcp | disp | clvm | ____, vtos, vtos, invokeinterface, f1_byte );
        def( Bytecodes::_invokedynamic, ubcp | disp | clvm | ____, vtos, vtos, invokedynamic, f1_byte );
        def( Bytecodes::_new, ubcp | ____ | clvm | ____, vtos, atos, _new, _ );
        def( Bytecodes::_newarray, ubcp | ____ | clvm | ____, itos, atos, newarray, _ );
        def( Bytecodes::_anewarray, ubcp | ____ | clvm | ____, itos, atos, anewarray, _ );
        def( Bytecodes::_arraylength, ____ | ____ | ____ | ____, atos, itos, arraylength, _ );
        def( Bytecodes::_athrow, ____ | disp | ____ | ____, atos, vtos, athrow, _ );
        def( Bytecodes::_checkcast, ubcp | ____ | clvm | ____, atos, atos, checkcast, _ );
        def( Bytecodes::_instanceof, ubcp | ____ | clvm | ____, atos, itos, instanceof, _ );
        def( Bytecodes::_monitorenter, ____ | disp | clvm | ____, atos, vtos, monitorenter, _ );
        def( Bytecodes::_monitorexit, ____ | ____ | clvm | ____, atos, vtos, monitorexit, _ );
        def( Bytecodes::_wide, ubcp | disp | ____ | ____, vtos, vtos, wide, _ );
        def( Bytecodes::_multianewarray, ubcp | ____ | clvm | ____, vtos, atos, multianewarray, _ );
        def( Bytecodes::_ifnull, ubcp | ____ | clvm | ____, atos, vtos, if_nullcmp, equal );
        def( Bytecodes::_ifnonnull, ubcp | ____ | clvm | ____, atos, vtos, if_nullcmp, not_equal );
        def( Bytecodes::_goto_w, ubcp | ____ | clvm | ____, vtos, vtos, goto_w, _ );
        def( Bytecodes::_jsr_w, ubcp | ____ | ____ | ____, vtos, vtos, jsr_w, _ );
    /* wide Java spec bytecodes */
        def( Bytecodes::_iload, ubcp | ____ | ____ | iswd, vtos, itos, wide_iload, _ );
        def( Bytecodes::_lload, ubcp | ____ | ____ | iswd, vtos, ltos, wide_lload, _ );
    /*
     * ...
     * JVM bytecodes
     */
        def( Bytecodes::_fast_agetfield, ubcp | ____ | ____ | ____, atos, atos, fast_accessfield, atos );
        def( Bytecodes::_fast_bgetfield, ubcp | ____ | ____ | ____, atos, itos, fast_accessfield, itos );
        def( Bytecodes::_fast_cgetfield, ubcp | ____ | ____ | ____, atos, itos, fast_accessfield, itos );
        def( Bytecodes::_fast_dgetfield, ubcp | ____ | ____ | ____, atos, dtos, fast_accessfield, dtos );
        def( Bytecodes::_fast_fgetfield, ubcp | ____ | ____ | ____, atos, ftos, fast_accessfield, ftos );
        def( Bytecodes::_fast_igetfield, ubcp | ____ | ____ | ____, atos, itos, fast_accessfield, itos );
        def( Bytecodes::_fast_lgetfield, ubcp | ____ | ____ | ____, atos, ltos, fast_accessfield, ltos );
        def( Bytecodes::_fast_sgetfield, ubcp | ____ | ____ | ____, atos, itos, fast_accessfield, itos );
        def( Bytecodes::_fast_aputfield, ubcp | ____ | ____ | ____, atos, vtos, fast_storefield, atos );
        def( Bytecodes::_fast_bputfield, ubcp | ____ | ____ | ____, itos, vtos, fast_storefield, itos );
        def( Bytecodes::_fast_cputfield, ubcp | ____ | ____ | ____, itos, vtos, fast_storefield, itos );
        def( Bytecodes::_fast_dputfield, ubcp | ____ | ____ | ____, dtos, vtos, fast_storefield, dtos );
        def( Bytecodes::_fast_fputfield, ubcp | ____ | ____ | ____, ftos, vtos, fast_storefield, ftos );
        def( Bytecodes::_fast_iputfield, ubcp | ____ | ____ | ____, itos, vtos, fast_storefield, itos );
        def( Bytecodes::_fast_lputfield, ubcp | ____ | ____ | ____, ltos, vtos, fast_storefield, ltos );
        def( Bytecodes::_fast_sputfield, ubcp | ____ | ____ | ____, itos, vtos, fast_storefield, itos );
        def( Bytecodes::_fast_aload_0, ____ | ____ | ____ | ____, vtos, atos, aload, 0 );
        def( Bytecodes::_fast_iaccess_0, ubcp | ____ | ____ | ____, vtos, itos, fast_xaccess, itos );
        def( Bytecodes::_fast_aaccess_0, ubcp | ____ | ____ | ____, vtos, atos, fast_xaccess, atos );
        def( Bytecodes::_fast_faccess_0, ubcp | ____ | ____ | ____, vtos, ftos, fast_xaccess, ftos );
        def( Bytecodes::_fast_iload, ubcp | ____ | ____ | ____, vtos, itos, fast_iload, _ );
        def( Bytecodes::_fast_iload2, ubcp | ____ | ____ | ____, vtos, itos, fast_iload2, _ );
        def( Bytecodes::_fast_icaload, ubcp | ____ | ____ | ____, vtos, itos, fast_icaload, _ );
        def( Bytecodes::_fast_invokevfinal, ubcp | disp | clvm | ____, vtos, vtos, fast_invokevfinal, f2_byte );
        def( Bytecodes::_fast_linearswitch, ubcp | disp | ____ | ____, itos, vtos, fast_linearswitch, _ );
        def( Bytecodes::_fast_binaryswitch, ubcp | disp | ____ | ____, itos, vtos, fast_binaryswitch, _ );
        def( Bytecodes::_fast_aldc, ubcp | ____ | clvm | ____, vtos, atos, fast_aldc, false );
        def( Bytecodes::_fast_aldc_w, ubcp | ____ | clvm | ____, vtos, atos, fast_aldc, true );
        def( Bytecodes::_return_register_finalizer, ____ | disp | clvm | ____, vtos, vtos, _return, vtos );
        def( Bytecodes::_invokehandle, ubcp | disp | clvm | ____, vtos, vtos, invokehandle, f1_byte );
        def( Bytecodes::_shouldnotreachhere, ____ | ____ | ____ | ____, vtos, vtos, shouldnotreachhere, _ );
    /* platform specific bytecodes */
        pd_initialize();
        _is_initialized = true;
    }
    

    TemplateTable的初始化调用def()将所有字节码的目标代码生成函数和参数保存在_template_table或_template_table_wide(wide指令)模板数组中。除了虚拟机规范本身定义的字节码指令外,HotSpot虚拟机也定义了一些字节码指令,这些指令为了辅助虚拟机进行更好、理简单的功能实现,例如Bytecodes::_return_register_finalizer等在之前已经介绍过,可以更好的实现finalizer类型对象的注册功能。

    对于调用def()函数时传递的一些参数在后面会解释。def()函数有2个,接收的参数不同,实现如下:

    void TemplateTable::def(
        Bytecodes::Code code,                                           /* 字节码指令 */
        int flags,                                                      /* 标志位 */
        TosState in,                                                    /* 模板执行前TosState */
        TosState out,                                                   /* 模板执行后TosState */
        void (*gen)(),                                                  /* 模板生成器,是模板的核心组件 */
        char filler
        )
    {
        assert( filler == ' ', "just checkin'" );
        def( code, flags, in, out, (Template::generator) gen, 0 );      /* 调用下面的def()函数 */
    }
    
    

    模板表由模板表数组与一组生成器组成:

    • 模板表数组有_template_table与_template_table_wild,数组的下标为bytecode,值为Template,按照字节码指令的操作码递增顺序排列。
    • 一组生成器,所有与bytecode配套的生成器,在初始化模板表时作为gen参数传给相应的Template。

    Template类的定义如下:

    源代码位置:hotspot/src/share/vm/interpreter/templateTable.hpp

    /*
     * A Template describes the properties of a code template for a given bytecode
     * and provides a generator to generate the code template.
     */
    class Template VALUE_OBJ_CLASS_SPEC {
    private:
        enum Flags {
    /* 字节码指令指的是该字节码的操作数是否存在于字节码里面 */
            uses_bcp_bit,           /* set if template needs the bcp pointing to bytecode */
            does_dispatch_bit,      /* set if template dispatches on its own */
            calls_vm_bit,           /* set if template calls the vm */
            wide_bit                /* set if template belongs to a wide instruction */
        };
        typedef void (*generator)( int arg );
        int     _flags;         /* describes interpreter template properties (bcp unknown) */
        TosState    _tos_in;        /* tos cache state before template execution */
        TosState    _tos_out;       /* tos cache state after template execution */
        generator   _gen;           /* template code generator */
        int     _arg;           /* argument for template code generator */
        ...
    /* Templates */
        static Template* template_for( Bytecodes::Code code )
        {
            Bytecodes::check( code );
            return(&_template_table[code]);
        }
    
    
        static Template* template_for_wide( Bytecodes::Code code )
        {
            Bytecodes::wide_check( code );
            return(&_template_table_wide[code]);
        }
    };
    

    调用的template_for()与template_for_wild()方法从_template_table或_template_for_wild数组中取值。这2个变量定义在TemplateTable类中,如下:

    static Template _template_table [Bytecodes::number_of_codes];
    static Template _template_table_wide[Bytecodes::number_of_codes];
    

    TemplateTable的初始化调用def()将所有字节码的目标代码生成函数和参数保存在_template_table或_template_table_wide(wide指令)模板数组中。除了虚拟机规范本身定义的字节码指令外,HotSpot虚拟机也定义了一些字节码指令,这些指令为了辅助虚拟机进行更好的功能实现,例如Bytecodes::_return_register_finalizer等在之前已经介绍过,可以更好的实现finalizer类型对象的注册功能。

    我们只给出部分字节码指令的模板定义,调用def()函数对每个字节码指令的模板进行定义,传递的参数是我们关注的重点:

    (1)指出为哪个字节码指令定义模板
    (2)ubcp|disp|clvm|iswd,这是一个组合数字,具体的数字与Template中定义的枚举类紧密相关,枚举类中定义的常量如下
    我们只给出部分字节码指令的模板定义,调用def()函数对每个字节码指令的模板进行定义,传递的参数是我们关注的重点:

    (1)指出为哪个字节码指令定义模板
    (2)ubcp|disp|clvm|iswd,这是一个组合数字,具体的数字与Template中定义的枚举类紧密相关,枚举类中定义的常量如下:

    enum Flags {
     uses_bcp_bit,        // set if template needs the bcp pointing to bytecode
     does_dispatch_bit,   // set if template dispatches on its own 就其本身而言; 靠自己
     calls_vm_bit,        // set if template calls the vm
     wide_bit             // set if template belongs to a wide instruction
    };
    

    下面详细解释这几个参数,如下:

    uses_bcp_bit,标志需要使用字节码指针(byte code pointer,数值为字节码基址+字节码偏移量)。表示生成的模板代码中是否需要使用指向字节码指令的指针,其实也就是说是否需要读取字节码指令的操作数,所以含有操作数的指令大部分都需要bcp,但是有一些是不需要的,如monitorenter与monitorexit等,这些的操作数都在表达式栈中,表达式栈顶就是其操作数,并不需要从Class文件中读取,所以不需要bcp;
    does_dispatch_bit,标志表示自己是否含有控制流转发逻辑,如tableswitch、lookupswitch、invokevirtual、ireturn等字节码指令,本身就需要进行控制流转发;
    calls_vm_bit,标志是否需要调用JVM函数,在调用TemplateTable::call_VM()函数时都会判断是否有这个标志,通常方法调用JVM函数时都会通过调用TemplateTable::call_VM()函数来间接完成调用。JVM函数就是用C++写的函数。
    wide_bit,标志是否是wide指令(使用附加字节扩展全局变量索引)
    (3)_tos_in与_tos_out:表示模板执行前与模板执行后的TosState(操作数栈栈顶元素的数据类型,TopOfStack,用来检查模板所声明的输出输入类型是否和该函数一致,以确保栈顶元素被正确使用)。

    _tos_in与_tos_out的值必须是枚举类中定义的常量,如下:

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

    如iload指令,执行之前栈顶状态为vtos,表示并不会使用栈顶的数据,所以如果程序为了提高执行效率将上一次执行的结果缓存到了寄存器中,那么此时就应该在执行iload指令之前将这个寄存器的值压入栈顶。iload指令执行之后的栈顶状态为itos,因为iload是向操作数栈中压入一个整数,所以此时的栈顶状态为int类型,那么这个值可以缓存到寄存器中,假设下一个指令为ireturn,那么栈顶之前与之后的状态分别为itos和itos,那么可直接将缓存在寄存器中的int类型返回即可,不需要做任何和操作数栈相关的操作。

    (4)_gen与_arg:_gen表示模板生成器(函数指针),这个函数会为对应的字节码生成对应的执行逻辑;_arg表示为模板生成器传递的参数。调用函数指针会为每个字节码指令按其语义针对不同的平台上生成不同的机器指令,这里我们只讨论x86架构下64位的机器指令实现,由于机器指令很难读懂,所以我们后续只阅读由机器指令反编译的汇编指令。

    下面看一下TemplateTable::initialize()函数中调用的Template::def()函数,如下:

    void TemplateTable::def(
      Bytecodes::Code code,    // 字节码指令
      int flags,               // 标志位
      TosState in,             // 模板执行前TosState
      TosState out,            // 模板执行后TosState
      void (*gen)(int arg),    // 模板生成器,是模板的核心组件
      int arg 
    ) {
      // 表示是否需要bcp指针
      const int ubcp = 1 << Template::uses_bcp_bit;     
      // 表示是否在模板范围内进行转发
      const int disp = 1 << Template::does_dispatch_bit; 
      // 表示是否需要调用JVM函数
      const int clvm = 1 << Template::calls_vm_bit;   
      // 表示是否为wide指令   
      const int iswd = 1 << Template::wide_bit;          
      
      // 如果是允许在字节码指令前加wide字节码指令的一些指令,那么
      // 会使用_template_table_wild模板数组进行字节码转发,否则
      // 使用_template_table模板数组进行转发
      bool is_wide = (flags & iswd) != 0;
      Template* t = is_wide ? template_for_wide(code) : template_for(code);
      
      // 调用模板表t的initialize()方法初始化模板表
      t->initialize(flags, in, out, gen, arg); 
    }
    

    模板表由模板表数组与一组生成器组成:

    模板数组有_template_table与_template_table_wild,数组的下标为字节码的Opcode,值为Template。定义如下:

    Template  TemplateTable::_template_table[Bytecodes::number_of_codes];
    Template TemplateTable::_template_table_wide[Bytecodes::number_of_codes];
    

    模板数组的值为Template,这个Template类中定义了保存标志位flags的_flags属性,保存栈顶缓存状态in和out的_tos_in和_tos_out,还有保存生成器gen及参数arg的_gen与_arg,所以调用t->initialize()后其实是初始化Template中的变量。initialize()函数的实现如下:

    void Template::initialize(
     int flags, 
     TosState tos_in, 
     TosState tos_out, 
     generator gen, 
     int arg
    ) {
      _flags   = flags;
      _tos_in  = tos_in;
      _tos_out = tos_out;
      _gen     = gen;
      _arg     = arg;
    }
    

    不过这里并不会调用gen函数生成对应的汇编代码,只是将传递给def()函数的各种信息保存到Template实例中,在TemplateTable::def()函数中,通过template_for()或template_for_wild()函数获取到数组中对应的Template实例后,就会调用Template::initialize()函数将信息保存到对应的Template实例中,这样就可以根据字节码索引从数组中获取对应的Template实例,进而获取字节码指令模板的相关信息。

    虽然这里并不会调用gen来生成字节码指令对应的机器指令,但是我们可以提前看一下gen这个指针函数是怎么为某个字节码指令生成对应的机器指令的。

    看一下TemplateTable::initialize()函数中对def()函数的调用,以_iinc(将局部变量表中对应的slot位的值增加1)为例,调用如下:

    def(
     Bytecodes::_iinc,     // 字节码指令
     ubcp|____|clvm|____,  // 标志
     vtos,                 // 模板执行前的TosState
     vtos,                 // 模板执行后的TosState
     iinc ,                // 模板生成器,是一个iinc()函数的指针
     _                     // 不需要模板生成器参数
    ); 
    

    设置标志位uses_bcp_bit和calls_vm_bit,表示iinc指令的生成器需要使用bcp指针函数at_bcp(),且需要调用JVM函数,下面给出了生成器的定义:

    源代码位置:/hotspot/src/cpu/x86/vm/templateTable_x86_64.cpp

    void TemplateTable::iinc() {
      transition(vtos, vtos);
      __ load_signed_byte(rdx, at_bcp(2)); // get constant
      locals_index(rbx);
      __ addl(iaddress(rbx), rdx);
    }
    

    由于iinc指令只涉及到对局部变量表的操作,并不会影响操作数栈,也不需要使用操作数栈顶的值,所以栈顶之前与之后的状态为vtos与vtos,调用transition()函数只是验证栈顶缓存的状态是否正确。

    iinc指令的字节码格式如下:

    iinc
    index // 局部变量表索引值
    const // 将局部变量表索引值对应的slot值加const
    

    操作码iinc占用一个字节,而index与const分别占用一个字节。使用at_bcp()函数获取iinc指令的操作数,2表示偏移2字节,所以会将const取出来存储到rdx中。调用locals_index()函数取出index,locals_index()就是JVM函数。最终生成的汇编如下:

    // %r13存储的是指向字节码的指针,偏移
    // 2字节后取出const存储到%edx
    movsbl 0x2(%r13),%edx
    // 取出index存储到%ebx
    movzbl 0x1(%r13),%ebx
    neg    %rbx
    // %r14指向本地变量表的首地址,将%edx加到
    // %r14+%rbx*8指向的内存所存储的值上
    // 之所以要对%rbx执行neg进行符号反转,
    // 是因为在Linux内核的操作系统上,
    // 栈是向低地址方向生长的
    add    %edx,(%r14,%rbx,8)
    

    相关文章

      网友评论

          本文标题:模板解释器

          本文链接:https://www.haomeiwen.com/subject/ztgdvrtx.html