zoukankan      html  css  js  c++  java
  • 第50篇调用约定(2)

    前面已经介绍了解释执行的Java方法、编译执行的Java方法和native方法的调用约定。这一篇我们看一下HotSpot VM中辅助实现调用约定的相关函数。

    1、SharedRuntime::java_calling_convention()函数

    当需要编译执行Java方法时,会调用SharedRuntime::java_calling_convention()函数,此函数的实现如下:

    int SharedRuntime::java_calling_convention(
       const BasicType   *sig_bt, // sig_bt相当于是数组
       VMRegPair         *regs,
       int               total_args_passed,
       int               is_outgoing // 值一般为false
    ) {
    
      // Register的类型为RegisterImpl*,而VMReg的类型为VMRegImpl*
      // 通过数组来将相关的参数存储到对应的寄存器上
      static const Register  INT_ArgReg[Argument::n_int_register_parameters_j] =
      {
              j_rarg0,  // 6
    	  j_rarg1,  // 2
    	  j_rarg2,  // 1
    	  j_rarg3,  // 8
    	  j_rarg4,  // 9
    	  j_rarg5   // 7
      };
      
      static const XMMRegister  FP_ArgReg[Argument::n_float_register_parameters_j] =
      {
              j_farg0, // 0
    	  j_farg1, // 1
    	  j_farg2, // 2
    	  j_farg3, // 3
              j_farg4, // 4
    	  j_farg5, // 5
    	  j_farg6, // 6
    	  j_farg7  // 7
      };
    
      // ...
    }
    

    在调用如上函数时,会传入表示Java方法参数的类型数组sig_bt,总的参数数量total_args_passed。我们将要传递参数使用的寄存器和栈slot通过regs数组来保存。其中的BasicType枚举类的定义如下:

    enum BasicType {
      T_BOOLEAN     =  4,
      T_CHAR        =  5,
      T_FLOAT       =  6,
      T_DOUBLE      =  7,
      T_BYTE        =  8,
      T_SHORT       =  9,
      T_INT         = 10,
      T_LONG        = 11,
      T_OBJECT      = 12,
      T_ARRAY       = 13,
    
      T_VOID        = 14,
      T_ADDRESS     = 15, // t_address ret指令用到的表示返回地址的returnAddress类型
      T_NARROWOOP   = 16, // t_narrowoop
      T_METADATA    = 17, // t_metadata
      T_NARROWKLASS = 18, // t_narrowklass
      T_CONFLICT    = 19, // t_conflict for stack value type with conflicting contents
      T_ILLEGAL     = 99  // t_illegal
    };

    如上枚举类中定义的类型已经足够表示Java字节码中的任何类型了,所以Java方法中的类型会统一使用如上枚举类来表示。

    VMRegPair类的定义如下:

    class VMRegPair {
    private:
      VMReg   _second;
      VMReg   _first;
    public:
      void set_bad () {
         _second=VMRegImpl::Bad();
         _first=VMRegImpl::Bad();
      }
      void set1 (VMReg v) {
    	  _second=VMRegImpl::Bad(); // 值为-1
    	  _first=v;
      }
      void set2 (VMReg v) {
          _second=v->next(); // 就是v的值加1
          _first=v;
      }
    
      // ...
    }
    

    我们看到了这个类中定义了_first和_second这一对寄存器,这主要是为32位实现考虑的,因为32位在传递long或double类型的参数时,需要2个寄存器来完成,一个存储高32位,一个存储低32位。对于64位来说,通过只使用_first寄存器就可完成任务。所以我们在讨论64位实现时,可不用太在意_second属性。

    VMRegPair中的_first和_second属性的类型为VMReg。VMReg是VMRegImpl*的别名,定义如下:

    typedef VMRegImpl*   VMReg;
    
    // 这个类中只有静态属性,并且也没虚函数,所以占用的内存大小为1个字
    class VMRegImpl {
    private:
      enum { BAD = -1  };
    
      static VMReg stack0;  
    public:
      static VMReg  as_VMReg(int val, bool bad_ok = false) {
    	  assert(val > BAD || bad_ok, "invalid");
              // 一个整数转换为VMRegImpl*,注意VMReg是VMRegImpl*的别名
    	  return (VMReg) (intptr_t) val; 
      }
      
      static VMReg stack2reg( int idx ) {
        intptr_t x = stack0->value(); // x的值为184
        return (VMReg) (intptr_t) (x + idx); // stack0->value()的值为184
      }
    
      uintptr_t reg2stack() {
        return value() - stack0->value();
      }
    
      // ...
    }
    

    需要注意的是,stack0是VMReg类型,也就是指针类型,指针类型是可以直接和整数相互转换的,所以我们通常会在stack0中存储一个整数。通过判断这个整数,我们能够知道,当前的VMRegImpl实例到底代表的是通用寄存器、浮点寄存器还是栈上的位置。

    在C/C++函数中,可将整数转换为指针类型,因为指针类型表示地址,其实地址也是一个数值。举个例子,如下:

    // 定义一个空类a,占用的内存空间大小为1
    class a{}; 
    
    int num = 2;
    
    // 将整数转换为指针,这是被允许的
    a* res = (a*)num; 

    直接将一个整数转换为指针类型。但是我们在使用时要记住,这通常不是一个合法的地址。  

    继续看函数的实现逻辑:

    int SharedRuntime::java_calling_convention(
       const BasicType   *sig_bt, // sig_bt相当于是数组
       VMRegPair         *regs,
       int               total_args_passed,
       int               is_outgoing
    ) {
    
      // ...
    
      uint int_args = 0;
      uint fp_args = 0;
      // 如果寄存器使用完,则多出来的参数需要通过栈来传递,这个变量记录需要的
      // slot(这里为了考虑32位情况,每个slot是4个字节,所以在64位情况下,
      // 每次需要2个slot,所以stk_args每次需要增加2
      uint stk_args = 0; 
      for (int i = 0; i < total_args_passed; i++) {
    	switch (sig_bt[i]) { // sig_bt[i]的类型为字,当前是64位,8个字节
    	case T_BOOLEAN:
    	case T_CHAR:
    	case T_BYTE:
    	case T_SHORT:
    	case T_INT:
    		// 当小于6个参数时,参数放在寄存器上,n_int_register_parameters_j=6
    		if (int_args < Argument::n_int_register_parameters_j) {
    			VMReg tmp = INT_ArgReg[int_args++]->as_VMReg();  // VMReg是VMRegImpl*类型的别名
    			regs[i].set1(tmp);
    		} else {  // 放在栈上
    			VMReg tmp = VMRegImpl::stack2reg(stk_args);
    			regs[i].set1(tmp);
    			stk_args += 2;
    		}
    		break;
    	case T_VOID:
    		// halves of T_LONG or T_DOUBLE
                    // long和double需要2个slot(这里的slot为8字节)
    		assert(i != 0 && (sig_bt[i - 1] == T_LONG || sig_bt[i - 1] == T_DOUBLE), "expecting half");
    		regs[i].set_bad();
    		break;
    	case T_LONG:
    		assert(sig_bt[i + 1] == T_VOID, "expecting half");
    	case T_OBJECT:
    	case T_ARRAY:
    	case T_ADDRESS:
    		if (int_args < Argument::n_int_register_parameters_j) {
    			VMReg tmp = INT_ArgReg[int_args++]->as_VMReg();
    			regs[i].set2(tmp);
    		} else {
    			VMReg tmp = VMRegImpl::stack2reg(stk_args);
    			regs[i].set2(tmp);
    			stk_args += 2;
    		}
    		break;
    	case T_FLOAT:
    		if (fp_args < Argument::n_float_register_parameters_j) {
    			VMReg tmp = FP_ArgReg[fp_args++]->as_VMReg();
    			regs[i].set1(tmp);
    		} else {
    			VMReg tmp = VMRegImpl::stack2reg(stk_args);
    			regs[i].set1(tmp);
    			stk_args += 2;
    		}
    		break;
    	case T_DOUBLE:
    		assert(sig_bt[i + 1] == T_VOID, "expecting half");
    		if (fp_args < Argument::n_float_register_parameters_j) {
    			VMReg tmp = FP_ArgReg[fp_args++]->as_VMReg();
    			regs[i].set2(tmp);
    		} else {
    			VMReg tmp = VMRegImpl::stack2reg(stk_args);
    			regs[i].set2(tmp);
    			stk_args += 2;
    		}
    		break;
    	default:
    		ShouldNotReachHere();
    		break;
    	}
      }
    
      return round_to(stk_args, 2);
    }
    

    当类型为非浮点数类型时,通过通用寄存器来传递。通过通用寄存器传递参数时,如果为boolean、byte、short、char和int时,调用VMRegPair::set1()函数,否则调用VMRegPair::set2()函数。对于64位来说,我们不需要关注VMRegPair::_second属性的值,所以我们只关心_first参数的值即可。

    最终会在regs数组中存储与参数个数相同的VMRegPair个实例,我们总结一下:

    (1)当存储T_BOOLEAN、T_BYTE、T_SHORT、T_CHAR和T_INT时,_first的值小于32,表示使用通用寄存器来传递参数;

    (2)当存储T_OBJECT、T_ARRAY、T_ADDRESS和T_LONG类型时,_first的值是仍然小于32,表示使用通用寄存器来传递参数;

    (3)当存储T_FLOAT和T_DOUBLE时,_first的值大于等于48,小于148,表示使用浮点寄存器来传递参数;

    (4)当存储的_first的值大于等于148时,表示对应的参数需要通过栈来传递;

    (5)当_first的值为其它时,非法;

    只有在寄存器用完后才会通过栈来传递参数,所以要通过stk_args来统计需要开辟多大的栈空间。举个例子如下:

    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);  

    本地方法共有5个参数,所以可以通过前5个寄存器来传递参数。函数入参为:

    const int total_args_passed=5 
     
    BasicType* sig_bbt=[T_OBJECT,T_INT,T_INT,T_OBJECT,T_INT,T_INT] 

    最终stk_args为0,而regs的值如下:

    VMRegPair* in_regs=[
       VMRegPair(_first=6*2,_second=13) // 传递的是T_OBJECT
       VMRegPair(_first=2*2,_second=-1)
       VMRegPair(_first=1*2,_second=-1)
       VMRegPair(_first=8*2,_second=17) // 传递的是T_OBJECT
       VMRegPair(_first=9*2,_second=-1)
    ]

    我们可以通过判断_first的值来区分出浮点类型与其它剩余类型。由于如上的5个参数都是通过通用寄存器传递的,所以_first的值都小于32。 

    2、SharedRuntime::c_calling_convention()函数

    调用的函数的实现如下:

    int SharedRuntime::c_calling_convention(
     const BasicType  *sig_bt,
     VMRegPair        *regs,
     int              total_args_passed
    ){ // 共需要向C传递的参数数量
        // Register的定义为RegisterImpl*
        static const Register INT_ArgReg[Argument::n_int_register_parameters_c] = {
    		  c_rarg0, // 0x7
    		  c_rarg1, // 0x6
    		  c_rarg2, // 0x2
    		  c_rarg3, // 0x1
    		  c_rarg4, // 0x8
    		  c_rarg5  // 0x9
        };
        static const XMMRegister FP_ArgReg[Argument::n_float_register_parameters_c] = {
    		  c_farg0,
    		  c_farg1,
    		  c_farg2,
    		  c_farg3,
    		  c_farg4,
    		  c_farg5,
    		  c_farg6,
    		  c_farg7
        };
    
        uint int_args = 0;
        uint fp_args = 0;
        uint stk_args = 0; // inc by 2 each time
        // 参数优先向寄存器中分配,如果没有寄存器时再向栈中分配
        for (int i = 0; i < total_args_passed; i++) {
          switch (sig_bt[i]) {
          case T_BOOLEAN:
          case T_CHAR:
          case T_BYTE:
          case T_SHORT:
          case T_INT:
            if (int_args < Argument::n_int_register_parameters_c) {
              VMReg tmp = INT_ArgReg[int_args++]->as_VMReg();
              regs[i].set1(tmp);
            } else {
              VMReg tmp = VMRegImpl::stack2reg(stk_args);
              regs[i].set1(tmp);
              stk_args += 2;
            }
            break;
          case T_LONG:
            assert(sig_bt[i + 1] == T_VOID, "expecting half");
            // fall through
          case T_OBJECT:
          case T_ARRAY:
          case T_ADDRESS:
          case T_METADATA:
            // n_int_register_parameters_c的值为6
            if (int_args < Argument::n_int_register_parameters_c) { 
              VMReg tmp = INT_ArgReg[int_args++]->as_VMReg() ;
              regs[i].set2(  tmp );
            } else {
              VMReg tmp = VMRegImpl::stack2reg(stk_args);
              regs[i].set2(tmp);
              stk_args += 2;
            }
            break;
          case T_FLOAT:
            if (fp_args < Argument::n_float_register_parameters_c) {
              VMReg tmp = 	FP_ArgReg[fp_args++]->as_VMReg();
              regs[i].set1(tmp);
            } else {
              VMReg tmp = VMRegImpl::stack2reg(stk_args);
              regs[i].set1(tmp);
              stk_args += 2;
            }
            break;
          case T_DOUBLE:
            assert(sig_bt[i + 1] == T_VOID, "expecting half");
            if (fp_args < Argument::n_float_register_parameters_c) {
            	VMReg tmp =FP_ArgReg[fp_args++]->as_VMReg();
              regs[i].set2(tmp);
            } else {
              VMReg tmp = VMRegImpl::stack2reg(stk_args);
              regs[i].set2(tmp);
              stk_args += 2;
            }
            break;
          case T_VOID: // Halves of longs and doubles
            assert(i != 0 && (sig_bt[i - 1] == T_LONG || sig_bt[i - 1] == T_DOUBLE), 
               "expecting half");
            regs[i].set_bad();
            break;
          default:
            ShouldNotReachHere();
            break;
          }
        }
    
      return stk_args;
    }
    

    其实现非常类似于SharedRuntime::java_calling_convention()函数,这里不再过多介绍。

    arraycopy()对应的本地函数的实现如下:

    JVM_ENTRY(void, JVM_ArrayCopy(
        JNIEnv *env, jclass ignored,
        jobject src, jint src_pos,
        jobject dst, jint dst_pos,
        jint length))
      // ...
      arrayOop s = arrayOop(JNIHandles::resolve_non_null(src));
      arrayOop d = arrayOop(JNIHandles::resolve_non_null(dst));
      // 进行数组的拷贝操作
      s->klass()->copy_array(s, src_pos, d, dst_pos, length, thread);
    JVM_END

    共有7个参数,所以在调用本地函数时,需要将1个参数存储在栈上。入参及计算的最终的regs的值如下:

    const int total_args_passed=5 
     
    BasicType* sig_bbt=[T_OBJECT,T_INT,T_INT,T_OBJECT,T_INT,T_INT] 
     
    VMRegPair*  regs=[
       VMRegPair(_first=6*2,_second=13) // 传递的是T_OBJECT
       VMRegPair(_first=2*2,_second=-1)
       VMRegPair(_first=1*2,_second=-1)
       VMRegPair(_first=8*2,_second=17) // 传递的是T_OBJECT
       VMRegPair(_first=9*2,_second=-1)
    ]

    6个值都可以通过通用寄存器传递,所以_first的值都小于32。另外还有个整数需要传递,所以stk_args的值为2(表示用2个、每个大小为4字节的slot传递整数类型参数)。需要注意的是,对于64位来说,如果要传递long和double类型的值,其实也需要2个4字节大小的slot即可,也就是1个8字节的slot即可,并不是需要2个8字节的slot,这是由调用约定规定的。

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

     

      

      

  • 相关阅读:
    homebrew
    Flutter状态管理之provide和provider的使用区别
    Flutter Bloc状态管理 简单上手
    Flutter 路由传入中文参数报错无法push问题
    Flutter 一些常用第三方库、插件
    js替换字符串中的空格,换行符 或 替换成<br>
    Flutter状态管理Provider,简单上手
    Flutter Resolving dependencies...很慢解决办法
    Flutter布局--appbar导航栏和状态栏
    Flutter运行报错 `kernel_snapshot for errors` 解决方案
  • 原文地址:https://www.cnblogs.com/mazhimazhi/p/15773763.html
Copyright © 2011-2022 走看看