第46篇-signature_handler與result_handler

  • 2021 年 12 月 17 日
  • 筆記

在之前介紹為native方法設置解釋執行的入口時介紹過,當Method::native_function為空時會調用InterpreterRuntime::prepare_native_call()函數,這個函數不但會查找本地函數,而且還會確保Method::signature_handler也完成了設置。這一篇將詳細介紹signature_handler的查找設置過程。

1、signature_handler

Method實例的第2個附加slot的signature_handler指向的常式用來消除Java解釋器棧和C/C++棧調用約定的不同,將位於解析器棧中的參數適配到本地函數使用的C棧。

在調用本地函數時,要確保signature handler被安裝,之前介紹過,如果signature_handler沒有安裝,那麼InterpreterRuntime::prepare_native_call()函數通過調用SignatureHandlerLibrary::add()函數來安裝。add()函數的實現如下:

源程式碼位置:openjdk/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp 
// 根據方法簽名解析方法參數的解析器,當方法參數大小小於Fingerprinter::max_size_of_parameters
// 時可以生成並使用根據方法簽名訂製的快速的解析器,否則使用通用的相對較慢的解析器。
void SignatureHandlerLibrary::add(methodHandle method) {
  // 只有在signature_handler的值為NULL時才會執行如下邏輯,否則不做任何操作
  if (method->signature_handler() == NULL) {
    int handler_index = -1;
    // UseFastSignatureHandlers的值默認為true
    // Fingerprinter::max_size_of_parameters的值為13,也就是13個slot
    if (UseFastSignatureHandlers && method->size_of_parameters() <= Fingerprinter::max_size_of_parameters) {
      MutexLocker mu(SignatureHandlerLibrary_lock);
      // 確保使用到的相關變數都已經初始化完成
      initialize();
      // lookup method signature's fingerprint
      // 讀出Method::_constMethod::_fingerprinter的值,也就是
      // 根據方法簽名得到對應的指紋值,然後在_fingerprints數組中查找到
      // 句柄下標索引,由於句柄存儲在_handlers數組中,所以可以根據這個下標
      // 索引從_handlers數組中獲取
      uint64_t fingerprint = Fingerprinter(method).fingerprint();
      handler_index = _fingerprints->find(fingerprint);
      // 如果handler_index小於0,則說明沒有這個方法簽名對應的signature_handler,需要創建一個新的
      // signature_handler
      if (handler_index < 0) {
        ResourceMark rm;
        ptrdiff_t    align_offset = (address)round_to((intptr_t)_buffer, CodeEntryAlignment) - (address)_buffer;

        CodeBuffer buffer(
				 (address)(_buffer + align_offset),
				 SignatureHandlerLibrary::buffer_size - align_offset
	           );
        // 生成signature_handler,其實就是生成一段常式,這段常式可消除Java解釋器棧
        // 和C/C++棧調用約定的不同,將位於解析器棧中的參數適配到本地函數使用的C棧
        InterpreterRuntime::SignatureHandlerGenerator tmp = InterpreterRuntime::SignatureHandlerGenerator(method, &buffer);
        tmp.generate(fingerprint);
        // signature_handler對應的常式臨時保存在了CodeBuffer中,調用set_handler保存到BufferBlob中,
        // 這個BufferBlob中的記憶體是從CodeCache中分配出來的,解釋執行所需要的所有常式基本都保存在CodeCache中
        address handler = set_handler(&buffer);
        if (handler == NULL) {
          // 使用普通的、相對較慢的解釋器
        } else {
          // 向_fingerprints和_handlers數組中添加方法簽名和signature_handler,這樣下次就可以
          // 根據方法簽名快速定位對應的signature_handler
          _fingerprints->append(fingerprint);
          _handlers->append(handler);
          handler_index = _fingerprints->length() - 1;
        }
      } // 結束 if (handler_index < 0)

      if (handler_index < 0) {
	  // 使用通用的相對較慢的解析器
	  address tmp = Interpreter::slow_signature_handler();
	  method->set_signature_handler(tmp);
       } else {
	  // 使用快速的解析器
	  address tmp = _handlers->at(handler_index);
	  method->set_signature_handler(tmp);
       }
    } else {
      // 沒有快速的解釋器,只能使用相對較慢的普通解釋器
      method->set_signature_handler(Interpreter::slow_signature_handler());
    }
  }

}

對於參數不超過13個slot大小(int、byte、對象地址等佔用一個slot,而double和long佔用2個slot)的native方法來說,signature_handler會走快速路徑。就是根據方法簽名字元串得到一個64位的整數方法指紋(Method Fingerprint)值,後續signature_handler將不需要每次都解析native方法簽名字元串得到參數個數和參數類型,而是直接用方法指紋值。這個方法指紋值的格式如下圖所示。

每個方法指紋值都會存儲在元素類型為uint64_t的數組中,所以方法指紋值不能超過64位大小,另外加上還需要存儲結果類型、是否為靜態方法等資訊,所以能表示方法參數類型的參數存儲區只有52位大小,所以才會要求方法參數大小不超過13個slot的大小(每個參數的類型存儲需要佔用4位)。

在SignatureHandlerLibrary::add()函數中使用了_fingerprints和_handlers來保存方法指紋值,這兩個變數是靜態的,所以說,如果兩個方法的指紋值相同,則可以重用快速解釋器。

在之前介紹過為native方法生成解釋執行的入口時,會在Method::native_funciton執行之前調用Method::signature_handler,而在調用Method::signature_handler之前會根據方法要求的參數大小從native棧幀中開闢對應的存儲空間,棧的狀態如下圖所示。

其中最下面的param n … param 1中會壓入調用C/C++實現的本地函數需要的參數,不過Java方法的解釋執行需要將參數從左向右入棧,所以我們能夠看到上圖中方法的局部變數表中實參的順序為param 1… param n,但是本地函數根據調用約定,其參數需要從右到左入棧,這就要求最後一個參數最先入棧(注意,C/C++函數只有在參數過多的情況下才會藉助棧來傳遞參數)。

(1)快速解釋器

調用InterpreterRuntime::SignatureHandlerGenerator::generate()函數生成快速解釋器,函數的實現如下:

void InterpreterRuntime::SignatureHandlerGenerator::generate(uint64_t fingerprint) {
  // 處理參數
  iterate(fingerprint);

  // 查找並返回result_handler
  BasicType bt = method()->result_type();
  address adr = Interpreter::result_handler(bt);

  __ lea(rax, ExternalAddress(adr));
  __ ret(0);

  __ flush();
}

這個函數在生成signature_handler時還會生成result_handler,這個result_handler會處理本地函數調用後的返回值,之前在介紹為native方法設置解釋執行的入口時介紹過,result_handler對native_function執行的結果進行處理的具體邏輯。

處理參數iterate()函數的實現如下:

void iterate( uint64_t fingerprint ) {
    if (!is_static()) {
       // 當為實例方法時,需要傳遞receiver,也就是this參數
       pass_object();
       _jni_offset++;
       _offset++;
    }

    // fingerprint中包含有函數調用參數及返回類型等相關資訊
    SignatureIterator::iterate_parameters( fingerprint );
}

調用pass_object()函數為本地函數傳遞this參數。調用SignatureIterator::iterate_parameters()函數根據Java方法簽名向C/C++函數傳遞參數。調用的SignatureIterator::iterate_parameters()函數的實現如下:

void SignatureIterator::iterate_parameters( uint64_t fingerprint ) {
  uint64_t saved_fingerprint = fingerprint;

  // 當傳遞的參數太多時就無法使用方法指紋值來快速處理,只能通過遍歷Java方法簽名來處理
  if ( fingerprint == UCONST64(-1) ) { // 檢查處理參數太多的情況
     SignatureIterator::iterate_parameters();
     return;
  }

  _parameter_index = 0;
  // static_feature_size + result_feature_size的值為5
  fingerprint = fingerprint >> (static_feature_size + result_feature_size);  
  while ( 1 ) {
    switch ( fingerprint & parameter_feature_mask ) {
      case bool_parm:
        do_bool();
        _parameter_index += T_BOOLEAN_size;
        break;
      case byte_parm:
        do_byte();
        _parameter_index += T_BYTE_size;
        break;
      case char_parm:
        do_char();
        _parameter_index += T_CHAR_size;
        break;
      case short_parm:
        do_short();
        _parameter_index += T_SHORT_size;
        break;
      case int_parm:
        do_int();
        _parameter_index += T_INT_size;
        break;
      case obj_parm:
        do_object(0, 0);
        _parameter_index += T_OBJECT_size;
        break;
      case long_parm:
        do_long();
        _parameter_index += T_LONG_size;
        break;
      case float_parm:
        do_float();
        _parameter_index += T_FLOAT_size;
        break;
      case double_parm:
        do_double();
        _parameter_index += T_DOUBLE_size;
        break;
      case done_parm:
        return;
        break;
      default:
        ShouldNotReachHere();
        break;
    }
    // parameter_feature_size的值為4
    fingerprint >>= parameter_feature_size; 
  }
  _parameter_index = 0;
}

調用的do_float()、do_object()等函數的實現如下:

  void do_bool  ()                     { pass_int();    _jni_offset++; _offset++;       }
  void do_char  ()                     { pass_int();    _jni_offset++; _offset++;       }
  void do_float ()                     { pass_float();  _jni_offset++; _offset++;       }
  void do_double()                     { pass_double(); _jni_offset++; _offset += 2;    }
  void do_byte  ()                     { pass_int();    _jni_offset++; _offset++;       }
  void do_short ()                     { pass_int();    _jni_offset++; _offset++;       }
  void do_int   ()                     { pass_int();    _jni_offset++; _offset++;       }
  void do_long  ()                     { pass_long();   _jni_offset++; _offset += 2;    }
  void do_object(int begin, int end)   { pass_object(); _jni_offset++; _offset++;        }
  void do_array (int begin, int end)   { pass_object(); _jni_offset++; _offset++;        }

其中的_jni_offset表示參數對於本地函數的偏移量,而_offset表示參數對於Java方法的偏移量。對於Java方法來說,一個long或double會佔用2個slot,而在64位下,本地函數只需要一個slot即可。另外,如果是靜態方法,由於有JNIEnv*和jclass,對於實例方法有JNIEnv*,所以_jni_offset還需要加上2或1。

調用的pass_int()、pass_double()和pass_object()等函數的實現如下:

void InterpreterRuntime::SignatureHandlerGenerator::pass_int() {
  const Address src(from(), Interpreter::local_offset_in_bytes(offset()));

  switch (_num_int_args) { // 當為靜態方法時,_num_int_args的值為1,否則為0
  case 0:
    __ movl(c_rarg1, src);
    _num_int_args++;
    break;
  case 1:
    __ movl(c_rarg2, src);
    _num_int_args++;
    break;
  case 2:
    __ movl(c_rarg3, src);
    _num_int_args++;
    break;
  case 3:
    __ movl(c_rarg4, src);
    _num_int_args++;
    break;
  case 4:
    __ movl(c_rarg5, src);
    _num_int_args++;
    break;
  default: // 當傳遞的整數類型參數多於5個時,多出來的只能通過棧來傳遞
    __ movl(rax, src);
    __ movl(Address(to(), _stack_offset), rax); // 調用to()函數返回rsp
    _stack_offset += wordSize;
    break;
  }
}


void InterpreterRuntime::SignatureHandlerGenerator::pass_double() {
  const Address src(from(), Interpreter::local_offset_in_bytes(offset() + 1));

  if (_num_fp_args < Argument::n_float_register_parameters_c) {
    __ movdbl(as_XMMRegister(_num_fp_args++), src);
  } else {
    __ movptr(rax, src);
    __ movptr(Address(to(), _stack_offset), rax);
    _stack_offset += wordSize;
  }
}

// 在傳遞對象地址時,只需要使用整數類型的slot存儲地址即可
void InterpreterRuntime::SignatureHandlerGenerator::pass_object() {
  const Address src(from(), Interpreter::local_offset_in_bytes(offset()));

  switch (_num_int_args) {
  case 0:
    assert(offset() == 0, "argument register 1 can only be (non-null) receiver");
    __ lea(c_rarg1, src);
    _num_int_args++;
    break;
  case 1:
    __ lea(rax, src);
    __ xorl(c_rarg2, c_rarg2);
    __ cmpptr(src, 0);
    __ cmov(Assembler::notEqual, c_rarg2, rax);
    _num_int_args++;
    break;
  case 2:
    __ lea(rax, src);
    __ xorl(c_rarg3, c_rarg3);
    __ cmpptr(src, 0);
    __ cmov(Assembler::notEqual, c_rarg3, rax);
    _num_int_args++;
    break;
  case 3:
    __ lea(rax, src);
    __ xorl(c_rarg4, c_rarg4);
    __ cmpptr(src, 0);
    __ cmov(Assembler::notEqual, c_rarg4, rax);
    _num_int_args++;
    break;
  case 4:
    __ lea(rax, src);
    __ xorl(c_rarg5, c_rarg5);
    __ cmpptr(src, 0);
    __ cmov(Assembler::notEqual, c_rarg5, rax);
    _num_int_args++;
    break;
  default:
    __ lea(rax, src);
    __ xorl(temp(), temp());
    __ cmpptr(src, 0);
    // 如果傳遞的對象地址不為0,則將rax中的值存儲到temp()中
    __ cmov(Assembler::notEqual, temp(), rax);
    // 將temp()中的值存儲到棧幀中
    __ movptr(Address(to(), _stack_offset), temp());
    _stack_offset += wordSize;
    break;
  }
}

我們需要注意,因為要調用的本地函數是C/C++函數,所以需要遵守C/C++函數的調用約定,如果是整數或對象地址,則可以放到6個整數類型的暫存器中。靜態方法的JNI*和jclass參數需要通過c_rarg0和c_rarg1來傳遞,所以native方法的參數中的非浮點數類型只能使用c_rarg2、c_rarg3、c_rarg4和c_rarg5這幾個暫存器;如果是實例方法,則c_rarg0需要傳遞JNI*,然後就是c_rarg1傳遞receiver。當整數或對象多於6個時,要將剩下的參數從右向左壓入棧內。

下面看2個具體的小實例。 

為java.lang.Object.registerNatives()方法生成的signature_handler與result_handler的彙編程式碼如下:(為HotSpot VM添加選項 -XX:+PrintSignatureHandlers)

argument handler #0 for: static java.lang.Object.registerNatives()V (fingerprint = 349, 11 bytes generated)
  // 根據方法的返回類型獲取到對應的result_handler常式的地址並保存到%rax中
  // movabs的源操作數只能是立即數或標號(本質還是立即數),目的操作數是暫存器
  0x00007f386911c420: movabs $0x7f386900ecd8,%rax
  0x00007f386911c42a: retq   

 --- associated result handler ---
  // 由於registerNatives()方法不需要任何返回值,所以沒有任何執行邏輯
  // ret指令用於從子函數中返回。X86架構的Linux中是將函數的返回值設置到eax寄
  // 存器並返回的,設置的工作不是由ret來做,要自己做
  0x00007f386900ecd8: retq
 

由於registerNatives()方法沒有任何參數,所以不需要對參數進行處理。至於JNIEnv*和jclass參數,在之前介紹為native方法設置解釋執行入口時詳細介紹過。

再舉個例子,如下:

 private native void writeBytes(byte b[], int off, int len, boolean append) throws IOException;

生成的彙編如下:

argument handler #56 for: receiver java.io.FileOutputStream.writeBytes([BIIZ)V (fingerprint = 21146428, 44 bytes generated)
  // Java方法的第1個參數this是本地方法的第2個參數,所以要根據調用約定存儲在%rsi中
  0x00007fbfe4067a5a: lea    (%r14),%rsi
  // 將Java方法的第2個參數b存儲到%rax中
  0x00007fbfe4067a5d: lea    -0x8(%r14),%rax
  0x00007fbfe4067a61: xor    %edx,%edx // 清空%edx
  // cmp是比較指令,cmp的功能相當於減法指令,只是不保存結果
  0x00007fbfe4067a63: cmpq   $0x0,-0x8(%r14)
  // cmovne不相等時(也就是-0x8(%r14)中的值不為0時),將%rax中的值移動到%rdx中,
  // 也就是Java方法的第2個參數b是本地方法的第3個參數,根據調用約定存儲在%rdx中
  0x00007fbfe4067a6b: cmovne %rax,%rdx 

  // 傳遞Java方法的off參數
  0x00007fbfe4067a6f: mov    -0x10(%r14),%ecx
  // 傳遞Java方法的len參數
  0x00007fbfe4067a73: mov    -0x18(%r14),%r8d
  // 傳遞Java訪求的append參數
  0x00007fbfe4067a77: mov    -0x20(%r14),%r9d  
  
  0x00007fbfe4067a7b: movabs $0x7fbfe3f59cd8,%rax
  0x00007fbfe4067a85: retq   

 --- associated result handler ---
  // 當類型為int、long、void、float與double時,只執行retq
  // 即可,詳見下面的result handler
  0x00007fbfe3f59cd8: retq  

如上函數的參數不多於6個,所以正好能使用6個整數暫存器來傳參。

C/C++中的參數放入的順序如下:

  • 第一個參數:%rdi    c_rarg0
  • 第二個參數:%rsi    c_rarg1
  • 第三個參數:%rdx   c_rarg2
  • 第四個參數:%rcx   c_rarg3
  • 第五個參數:%r8    c_rarg4
  • 第六個參數:%r9    c_rarg5  

第1個參數為JNIEnv*,這在之前介紹為native方法設置解釋執行的入口時介紹過,從JavaThread::jni_environment中獲取JNIEnv實例的地址並保存到%rdi中。

(2)普通的解釋器

將普通的解釋器生成的常式保存到AbstractInterpreter::_slow_signature_handler中,所以在SignatureHandlerLibrary::add()函數中可直接能這個欄位中獲取常式地址。在HotSpot VM啟動時會調用如下函數:

void AbstractInterpreterGenerator::generate_all() {
  {
     CodeletMark cm(_masm, "slow signature handler");
     Interpreter::_slow_signature_handler = generate_slow_signature_handler();
  }
}

調用AbstractInterpreterGenerator::generate_slow_signature_handler()函數生成的彙編程式碼如下:

// rbx: method
// r14: pointer to locals

// %rcx指向了棧頂,其中的棧頂值是第1個需要通過棧來給本地函數傳遞的參數
0x00007fffe1005400: mov    %rsp,%rcx  // 為調用準備c_rarg3
// 0x70=14*wordSize,其中wordSize=8,這裡又從native棧幀上開闢了
// 14個slot,其中8個用來存儲浮點數,5個用來存儲整數,1個用來指示8個slot
// 中,哪些存儲了需要傳遞的方法參數,也就是需要傳遞給本地函數的浮點數
0x00007fffe1005403: sub    $0x70,%rsp

// 調用call_VM()函數生成的常式,這個常式調用InterpreterRuntime::slow_signature_handler()函數
0x00007fffe1005407: callq  0x00007fffe1005411
0x00007fffe100540c: jmpq   0x00007fffe1005492
0x00007fffe1005411: mov    %r14,%rdx      // 為調用準備參數c_rarg2
0x00007fffe1005414: mov    %rbx,%rsi      // 為調用準備參數c_rarg1
0x00007fffe1005417: lea    0x8(%rsp),%rax 
0x00007fffe100541c: mov    %r13,-0x38(%rbp)
0x00007fffe1005420: mov    %r15,%rdi      // 為調用準備參數c_rarg0

// 相關資訊保存到執行緒中
0x00007fffe1005423: mov    %rbp,0x200(%r15)
0x00007fffe100542a: mov    %rax,0x1f0(%r15)

// 如下彙編對函數進行調用,如果記憶體沒有對齊,則需要對齊處理後調用
0x00007fffe1005431: test   $0xf,%esp
0x00007fffe1005437: je     0x00007fffe100544f
0x00007fffe100543d: sub    $0x8,%rsp
0x00007fffe1005441: callq  0x00007ffff66aeed2
0x00007fffe1005446: add    $0x8,%rsp
0x00007fffe100544a: jmpq   0x00007fffe1005454

0x00007fffe100544f: callq  0x00007ffff66aeed2

// 將執行緒中保存的相關資訊重置
0x00007fffe1005454: movabs $0x0,%r10
0x00007fffe100545e: mov    %r10,0x1f0(%r15)
0x00007fffe1005465: movabs $0x0,%r10
0x00007fffe100546f: mov    %r10,0x200(%r15)

0x00007fffe1005476: cmpq   $0x0,0x8(%r15)
0x00007fffe100547e: je     0x00007fffe1005489
0x00007fffe1005484: jmpq   0x00007fffe1000420
0x00007fffe1005489: mov    -0x38(%rbp),%r13
0x00007fffe100548d: mov    -0x30(%rbp),%r14
0x00007fffe1005491: retq  
// 結束call_VM()函數的調用


// rax: result handler

// Do FP first so we can use c_rarg3 as temp
// 0x28等於5*wordSize
0x00007fffe1005492: mov    0x28(%rsp),%ecx  // float/double identifiers  

在執行InterpreterRuntime::slow_signature_handler()函數之前的棧狀態如下圖所示。

調用的InterpreterRuntime::slow_signature_handler()函數的實現如下:

IRT_ENTRY(address,InterpreterRuntime::slow_signature_handler(
 JavaThread*  thread,
 Method*      method,
 intptr_t*    from,
 intptr_t*    to
))
  methodHandle m(thread, (Method*)method);

  // 處理方法參數
  SlowSignatureHandler(m, (address)from, to + 1).iterate(UCONST64(-1));

  // 返回result_handler
  return Interpreter::result_handler(m->result_type());
IRT_END

其中from與to分別為

r14: pointer to locals
%rcx/c_rarg3: first stack arg - wordSize

調用的SlowSignatureHandler的構造函數如下:

SlowSignatureHandler(methodHandle method, address from, intptr_t* to) : NativeSignatureIterator(method) {
    _from = from;
    _to   = to;

    _int_args = to - (method->is_static() ? 14 : 15);
    _fp_args =  to - 9;
    _fp_identifiers = to - 10;
    *(int*) _fp_identifiers = 0;
    _num_int_args = (method->is_static() ? 1 : 0);
    _num_fp_args = 0;
}

NativeSignatureIterator(methodHandle method) : SignatureIterator(method->signature()) {
    _method = method;
    _offset = 0;
    _jni_offset = 0;

    const int JNIEnv_words = 1;
    const int mirror_words = 1;
    // 如果為靜態方法,則_prepended的值為2(JNI和mirror),否則值為1(JNI)
    _prepended = !is_static() ? JNIEnv_words : JNIEnv_words + mirror_words;
}

SignatureIterator::SignatureIterator(Symbol* signature) {
  _signature       = signature;
  _parameter_index = 0;
}

SignatureIterator類的繼承體系如下:

之前在介紹快速解釋器時使用的是InterpreterRuntime::SignatureHandlerGenerator,而慢速解釋器使用的是SlowSignatureHandler。

初始化好各個變數後就能在InterpreterRuntime::slow_signature_handler()函數中調用iterate()函數,然後在iterate()函數中調用SignatureIterator::iterate_parameters()函數,調用的pass_int()、pass_double()和pass_object()等函數是SlowSignatureHandler類中定義的系列函數,實現如下:

virtual void pass_int(){
    jint from_obj = *(jint *)(_from+Interpreter::local_offset_in_bytes(0));
    _from -= Interpreter::stackElementSize;

    if (_num_int_args < Argument::n_int_register_parameters_c-1) {
      *_int_args++ = from_obj;
      _num_int_args++;
    } else {
      *_to++ = from_obj;
    }
}

virtual void pass_long(){
    intptr_t from_obj = *(intptr_t*)(_from+Interpreter::local_offset_in_bytes(1));
    _from -= 2*Interpreter::stackElementSize;
    // n_int_register_parameters_c的值為6
    if (_num_int_args < Argument::n_int_register_parameters_c-1) {
      *_int_args++ = from_obj;
      _num_int_args++;
    } else {
      *_to++ = from_obj;
    }
}

virtual void pass_object(){
    intptr_t *from_addr = (intptr_t*)(_from + Interpreter::local_offset_in_bytes(0));
    _from -= Interpreter::stackElementSize;
    // n_int_register_parameters_c的值為6
    if (_num_int_args < Argument::n_int_register_parameters_c-1) {
      *_int_args++ = (*from_addr == 0) ? NULL : (intptr_t)from_addr;
      _num_int_args++;
    } else {
      *_to++ = (*from_addr == 0) ? NULL : (intptr_t) from_addr;
    }
}

通過如上函數的實現我們能看到,將需要通過暫存器傳遞的參數暫時存儲到native方法的棧中,過多的參數也會存儲到棧中。如下圖所示。

 

我們暫時將需要通過暫存器傳遞的參數保存到新開闢過的14個slot中,而需要通過棧傳遞的參數放到param n … param 1區域中即可。然後我們接著看AbstractInterpreterGenerator::generate_slow_signature_handler()函數中生成的彙編程式碼的邏輯,如下:

// (6 + 0) * wordSize
0x00007fffe1005496: test   $0x1,%ecx
0x00007fffe100549c: jne    0x00007fffe10054ad  // 如果不相等,則跳轉到-- d --
0x00007fffe10054a2: vmovss 0x30(%rsp),%xmm0 // 針對32位的移動
0x00007fffe10054a8: jmpq   0x00007fffe10054b3 // 跳轉到-- done --
// **** d ****
0x00007fffe10054ad: vmovsd 0x30(%rsp),%xmm0 // 針對64位的移動
// **** done ****

// (6 + 1) * wordSize
0x00007fffe10054b3: test   $0x2,%ecx
0x00007fffe10054b9: jne    0x00007fffe10054ca
0x00007fffe10054bf: vmovss 0x38(%rsp),%xmm1
0x00007fffe10054c5: jmpq   0x00007fffe10054d0
0x00007fffe10054ca: vmovsd 0x38(%rsp),%xmm1

// (6 + 2) * wordSize
0x00007fffe10054d0: test   $0x4,%ecx
0x00007fffe10054d6: jne    0x00007fffe10054e7
0x00007fffe10054dc: vmovss 0x40(%rsp),%xmm2
0x00007fffe10054e2: jmpq   0x00007fffe10054ed
0x00007fffe10054e7: vmovsd 0x40(%rsp),%xmm2

// (6 + 3) * wordSize
0x00007fffe10054ed: test   $0x8,%ecx
0x00007fffe10054f3: jne    0x00007fffe1005504
0x00007fffe10054f9: vmovss 0x48(%rsp),%xmm3
0x00007fffe10054ff: jmpq   0x00007fffe100550a
0x00007fffe1005504: vmovsd 0x48(%rsp),%xmm3

// (6 + 4) * wordSize
0x00007fffe100550a: test   $0x10,%ecx
0x00007fffe1005510: jne    0x00007fffe1005521
0x00007fffe1005516: vmovss 0x50(%rsp),%xmm4
0x00007fffe100551c: jmpq   0x00007fffe1005527
0x00007fffe1005521: vmovsd 0x50(%rsp),%xmm4

// (6 + 5) * wordSize
0x00007fffe1005527: test   $0x20,%ecx
0x00007fffe100552d: jne    0x00007fffe100553e
0x00007fffe1005533: vmovss 0x58(%rsp),%xmm5
0x00007fffe1005539: jmpq   0x00007fffe1005544
0x00007fffe100553e: vmovsd 0x58(%rsp),%xmm5

// (6 + 6) * wordSize
0x00007fffe1005544: test   $0x40,%ecx
0x00007fffe100554a: jne    0x00007fffe100555b
0x00007fffe1005550: vmovss 0x60(%rsp),%xmm6
0x00007fffe1005556: jmpq   0x00007fffe1005561
0x00007fffe100555b: vmovsd 0x60(%rsp),%xmm6

// (6 + 7) * wordSize
0x00007fffe1005561: test   $0x80,%ecx
0x00007fffe1005567: jne    0x00007fffe1005578
0x00007fffe100556d: vmovss 0x68(%rsp),%xmm7
0x00007fffe1005573: jmpq   0x00007fffe100557e
0x00007fffe1005578: vmovsd 0x68(%rsp),%xmm7  
  

其中的%ecx中存儲的是float/double identifiers,這是一個組合數字,也就是能夠指明8個浮點數slot中,哪些存儲了float值,哪些存儲了double值,然後分別使用vmovss和vmovsd移動到對應的暫存器中。

下面接著看AbstractInterpreterGenerator::generate_slow_signature_handler()函數中生成的彙編程式碼的邏輯,如下:

// 將Method::access_flags存儲到%ecx中
0x00007fffe100557e: mov    0x28(%rbx),%ecx 
// 是否含有JVM_ACC_STATIC標識
0x00007fffe1005581: test   $0x8,%ecx    
// 如果不含有,表示為實例方法,則將棧頂值存儲到c_rarg1,即%rsi中   
0x00007fffe1005587: cmove  (%rsp),%rsi     

// 將棧頂值存儲到c_rarg2、c_rarg3、c_rarg4及c_rarg5中
0x00007fffe100558c: mov    0x8(%rsp),%rdx
0x00007fffe1005591: mov    0x10(%rsp),%rcx
0x00007fffe1005596: mov    0x18(%rsp),%r8
0x00007fffe100559b: mov    0x20(%rsp),%r9

// 恢復%rsp
0x00007fffe10055a0: add    $0x70,%rsp
0x00007fffe10055a4: retq 

之前已經將棧中保存的浮點數存儲到了對應的暫存器中,現在需要將整數也保存到對應的暫存器中。

當將相關的值移動到暫存器後,新開始的14個slot就沒有用處了,直接更改%rsp的指向彈出這14個slot,這樣慢速解釋器為調用本地函數準備好了調用相關的參數。 

 

對於慢速解釋器來說,其對於所有的本地方法,生成的常式都是同一個,所以在這個常式中就必須檢查目標方法是否為靜態的、是否需要同步,然後根據不同的情況進入不同的路徑。還需要檢查參數數量和參數類型,然後準備棧參數。如果每個native方法的調用都涉及到這些邏輯,那麼執行的速度就會相對較慢一些。

對於快速解釋器來說,只是執行了必要的邏輯,所以執行的速度會相對快一些。

2、result_handler

無論是快速解釋器還是慢速解釋器,都會根據native方法的結果類型返回對應的result_handler(快速解釋器返回result_handler的邏輯在InterpreterRuntime::SignatureHandlerGenerator::generate()函數中,慢速解釋器在InterpreterRuntime::slow_signature_handler()函數中)。下面我們就來看一下result_handler,生成result_handler的程式碼如下:

 static const BasicType types[Interpreter::number_of_result_handlers] = {
    T_BOOLEAN,
    T_CHAR   ,
    T_BYTE   ,
    T_SHORT  ,
    T_INT    ,
    T_LONG   ,
    T_VOID   ,
    T_FLOAT  ,
    T_DOUBLE ,
    T_OBJECT
};

{
    CodeletMark cm(_masm, "result handlers for native calls");
    int is_generated[Interpreter::number_of_result_handlers]; // 10
    memset(is_generated, 0, sizeof(is_generated));

    for (int i = 0; i < Interpreter::number_of_result_handlers; i++) {
      BasicType type = types[i];
      if (!is_generated[Interpreter::BasicType_as_index(type)]++) {
    	int x = Interpreter::BasicType_as_index(type);
        Interpreter::_native_abi_to_tosca[x] = generate_result_handler_for(type);
      }
    }
}

會根據不同的方法返回類型生成不同的常式,這些常式都會保存到對應的_native_abi_to_tosca數組中,這個數組的定義如下:

static address    _native_abi_to_tosca[number_of_result_handlers];

調用的generate_result_handler_for()函數的實現如下:

address TemplateInterpreterGenerator::generate_result_handler_for(BasicType type) {
  switch (type) {
  case T_BOOLEAN: __ c2bool(rax);            break;
  case T_CHAR   : __ movzwl(rax, rax);       break;
  case T_BYTE   : __ sign_extend_byte(rax);  break;
  case T_SHORT  : __ sign_extend_short(rax); break;
  case T_INT    : /* nothing to do */        break;
  case T_LONG   : /* nothing to do */        break;
  case T_VOID   : /* nothing to do */        break;
  case T_FLOAT  : /* nothing to do */        break;
  case T_DOUBLE : /* nothing to do */        break;
  case T_OBJECT :
    // 對於返回類型為Object來說,會將結果存儲到棧上特定的位置
    __ movptr(rax, Address(rbp, frame::interpreter_frame_oop_temp_offset*wordSize));
    break;
  default       : ShouldNotReachHere();
  }
  __ ret(0);   

  return entry;
}

可以看到,向Interpreter::_native_abi_to_tosca數組中存儲了不同類型的入口。

(1)T_BOOLEAN

調用如下函數生成處理方法返回類型為boolean的常式:

void MacroAssembler::c2bool(Register x) {
  // implements x == 0 ? 0 : 1
  // note: must only look at least-significant byte of x
  //       since C-style booleans are stored in one byte
  //       only! (was bug)
  andl(x, 0xFF);
  setb(Assembler::notZero, x);
}

生成的彙編如下:

0x00007fffe100ecc0: and    $0xff,%eax
0x00007fffe100ecc6: setne  %al // 獲取ZF值後,取反,然後再放入%al中
0x00007fffe100ecc9: retq  

setxx系列指令根據標誌暫存器eflags的值,將操作數設置為0或1,如setne表示ZF=0時,也就是不相等時設置%al為1,否則設置為0。

(2)T_CHAR

生成的彙編如下:

0x00007fffe100ecca: movzwl %ax,%eax
0x00007fffe100eccd: retq

(3)T_BYTE

生成的彙編如下:

0x00007fffe100ecce: movsbl %al,%eax
0x00007fffe100ecd1: retq   

(4)T_SHORT

生成的彙編如下:

0x00007fffe100ecd2: movswl %ax,%eax
0x00007fffe100ecd5: retq

(5)T_INT、T_LONG、T_VOID、T_FLOAT與T_DOUBLE

只會生成一個retq指令,因為相關的值都根據調用約定快取到了特定的暫存器中。

(6)T_OBJECT

生成的彙編如下:

0x00007fffe100ecdb: mov    0x10(%rbp),%rax
0x00007fffe100ecdf: retq   

0x10(%rbp)在之前介紹為native方法設置解釋器入口時介紹過,這個slot處為oop temp,當native方法返回對象時,將結果存儲到這個slot中。

公眾號 深入剖析Java虛擬機HotSpot 已經更新虛擬機源程式碼剖析相關文章到60+,歡迎關注,如果有任何問題,可加作者微信mazhimazh,拉你入虛擬機群交流