第22篇-虛擬機位元組碼指令之類型轉換

  • 2021 年 9 月 14 日
  • 筆記

Java虛擬機規範中定義的類型轉換相關的位元組碼指令如下表所示。

0x85

i2l

將棧頂int型數值強制轉換成long型數值並將結果壓入棧頂

0x86

i2f

將棧頂int型數值強制轉換成float型數值並將結果壓入棧頂

0x87

i2d

將棧頂int型數值強制轉換成double型數值並將結果壓入棧頂

0x88

l2i

將棧頂long型數值強制轉換成int型數值並將結果壓入棧頂

0x89

l2f

將棧頂long型數值強制轉換成float型數值並將結果壓入棧頂

0x8a

l2d

將棧頂long型數值強制轉換成double型數值並將結果壓入棧頂

0x8b

f2i

將棧頂float型數值強制轉換成int型數值並將結果壓入棧頂

0x8c

f2l

將棧頂float型數值強制轉換成long型數值並將結果壓入棧頂

0x8d

f2d

將棧頂float型數值強制轉換成double型數值並將結果壓入棧頂

0x8e

d2i

將棧頂double型數值強制轉換成int型數值並將結果壓入棧頂

0x8f

d2l

將棧頂double型數值強制轉換成long型數值並將結果壓入棧頂

0x90

d2f

將棧頂double型數值強制轉換成float型數值並將結果壓入棧頂

0x91

i2b

將棧頂int型數值強制轉換成byte型數值並將結果壓入棧頂

0x92

i2c

將棧頂int型數值強制轉換成char型數值並將結果壓入棧頂

0x93

i2s

將棧頂int型數值強制轉換成short型數值並將結果壓入棧頂

上表位元組碼指令的模板定義如下:

def(Bytecodes::_i2l   , ____|____|____|____, itos, ltos, convert ,  _           );
def(Bytecodes::_i2f   , ____|____|____|____, itos, ftos, convert ,  _           );
def(Bytecodes::_i2d   , ____|____|____|____, itos, dtos, convert ,  _           );
def(Bytecodes::_l2i   , ____|____|____|____, ltos, itos, convert ,  _           );
def(Bytecodes::_l2f   , ____|____|____|____, ltos, ftos, convert ,  _           );
def(Bytecodes::_l2d   , ____|____|____|____, ltos, dtos, convert ,  _           );
def(Bytecodes::_f2i   , ____|____|____|____, ftos, itos, convert ,  _           );
def(Bytecodes::_f2l   , ____|____|____|____, ftos, ltos, convert ,  _           );
def(Bytecodes::_f2d   , ____|____|____|____, ftos, dtos, convert ,  _           );
def(Bytecodes::_d2i   , ____|____|____|____, dtos, itos, convert ,  _           );
def(Bytecodes::_d2l   , ____|____|____|____, dtos, ltos, convert ,  _           );
def(Bytecodes::_d2f   , ____|____|____|____, dtos, ftos, convert ,  _           );
def(Bytecodes::_i2b   , ____|____|____|____, itos, itos, convert ,  _           );
def(Bytecodes::_i2c   , ____|____|____|____, itos, itos, convert ,  _           );
def(Bytecodes::_i2s   , ____|____|____|____, itos, itos, convert ,  _           ); 

相關位元組碼轉換指令的生成函數為TemplateTable::convert(),此函數的實現如下:

void TemplateTable::convert() {
  static const int64_t is_nan = 0x8000000000000000L;

  // Conversion
  switch (bytecode()) {
  case Bytecodes::_i2l:
    __ movslq(rax, rax);
    break;
  case Bytecodes::_i2f:
    __ cvtsi2ssl(xmm0, rax);
    break;
  case Bytecodes::_i2d:
    __ cvtsi2sdl(xmm0, rax);
    break;
  case Bytecodes::_i2b:
    __ movsbl(rax, rax);
    break;
  case Bytecodes::_i2c:
    __ movzwl(rax, rax);
    break;
  case Bytecodes::_i2s:
    __ movswl(rax, rax);
    break;
  case Bytecodes::_l2i:
    __ movl(rax, rax);
    break;
  case Bytecodes::_l2f:
    __ cvtsi2ssq(xmm0, rax);
    break;
  case Bytecodes::_l2d:
    __ cvtsi2sdq(xmm0, rax);
    break;
  case Bytecodes::_f2i:
  {
    Label L;
    __ cvttss2sil(rax, xmm0);
    __ cmpl(rax, 0x80000000); // NaN or overflow/underflow?
    __ jcc(Assembler::notEqual, L);
    __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::f2i), 1);
    __ bind(L);
  }
    break;
  case Bytecodes::_f2l:
  {
    Label L;
    __ cvttss2siq(rax, xmm0);
    // NaN or overflow/underflow?
    __ cmp64(rax, ExternalAddress((address) &is_nan));
    __ jcc(Assembler::notEqual, L);
    __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::f2l), 1);
    __ bind(L);
  }
    break;
  case Bytecodes::_f2d:
    __ cvtss2sd(xmm0, xmm0);
    break;
  case Bytecodes::_d2i:
  {
    Label L;
    __ cvttsd2sil(rax, xmm0);
    __ cmpl(rax, 0x80000000); // NaN or overflow/underflow?
    __ jcc(Assembler::notEqual, L);
    __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::d2i), 1);
    __ bind(L);
  }
    break;
  case Bytecodes::_d2l:
  {
    Label L;
    __ cvttsd2siq(rax, xmm0);
    // NaN or overflow/underflow?
    __ cmp64(rax, ExternalAddress((address) &is_nan));
    __ jcc(Assembler::notEqual, L);
    __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::d2l), 1);
    __ bind(L);
  }
    break;
  case Bytecodes::_d2f:
    __ cvtsd2ss(xmm0, xmm0);
    break;
  default:
    ShouldNotReachHere();
  }
}

如_i2l指令將棧頂int型數值強制轉換成long型數值並將結果壓入棧頂,其對應的彙編程式碼如下:

movslq %eax,%rax  // 將一個雙字擴展後送到一個四字中

對於浮點數float或long轉int或long類型相對複雜,下面看一個float轉int類型的f2i指令。

// 把標量單精度數轉換為佔用雙字的標量整數
0x00007fffe1019189: vcvttss2si %xmm0,%eax
// 和0x80000000進行比較,如果不相等,則跳轉到L
0x00007fffe101918d: cmp    $0x80000000,%eax
0x00007fffe1019193: jne    0x00007fffe10191bc

// 如果棧頂指針已經按16位元組對齊,則可直接調用調用SharedRuntime::f2i()函數,否則
// 將棧頂指令按16位元組對齊後再調用

0x00007fffe1019199: test   $0xf,%esp
0x00007fffe101919f: je     0x00007fffe10191b7
0x00007fffe10191a5: sub    $0x8,%rsp
// 調用SharedRuntime::f2i()函數
0x00007fffe10191a9: callq  0x00007ffff6a0f946
0x00007fffe10191ae: add    $0x8,%rsp
0x00007fffe10191b2: jmpq   0x00007fffe10191bc
// 調用SharedRuntime::f2i()函數
0x00007fffe10191b7: callq  0x00007ffff6a0f946 

---- L ----

生成的彙編指令vcvttss2si的意思為把標量單精度數轉換為佔用雙字的標量整數,名稱的由來解讀如下:

cvt:convert,轉換;

t:truncation,截斷;

ss:scalar single,標量單精度數;

2:to;

si:scalar integer,標量整數。

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

JRT_LEAF(jint, SharedRuntime::f2i(jfloat  x))
  if (g_isnan(x))  // 如果為非數字值,直接返回0
    return 0;
  if (x >= (jfloat) max_jint)
    return max_jint;
  if (x <= (jfloat) min_jint)
    return min_jint;
  return (jint) x;
JRT_END

C++函數調用時,需要一個參數x,在GNU / Linux上遵循System V AMD64 ABI的調用約定。暫存器RDI,RSI,RDX,RCX,R8和R9是用於整數和存儲器地址的參數和XMM0,XMM1,XMM2,XMM3,XMM4,XMM5,XMM6和XMM7用於浮點參數,所以將用xmm0做為第1個參數,這個參數恰好是棧頂用來快取浮點數的暫存器,所以默認不用任何操作。 

返回值存儲到%rax中,由於tos_out為itos,%rax暫存器用來做棧頂快取,所以也不需要做額外的操作。

 

推薦閱讀:

第1篇-關於JVM運行時,開篇說的簡單些

第2篇-JVM虛擬機這樣來調用Java主類的main()方法

第3篇-CallStub新棧幀的創建

第4篇-JVM終於開始調用Java主類的main()方法啦

第5篇-調用Java方法後彈出棧幀及處理返回結果

第6篇-Java方法新棧幀的創建

第7篇-為Java方法創建棧幀

第8篇-dispatch_next()函數分派位元組碼

第9篇-位元組碼指令的定義

第10篇-初始化模板表

第11篇-認識Stub與StubQueue

第12篇-認識CodeletMark

第13篇-通過InterpreterCodelet存儲機器指令片段

第14篇-生成重要的常式

第15章-解釋器及解釋器生成器

第16章-虛擬機中的彙編器

第17章-x86-64暫存器

第18章-x86指令集之常用指令

第19篇-載入與存儲指令(1)

第20篇-載入與存儲指令之ldc與_fast_aldc指令(2)

第21篇-虛擬機位元組碼之運算指令

如果有問題可直接評論留言或加作者微信mazhimazh

關注公眾號,有HotSpot VM源碼剖析系列文章!

 

 

  

 

Tags: