HotSpot编译执行硬编码生成

发布时间 2023-07-29 23:52:20作者: ylc0x01

背景

在一个技术群里,有一个哥们对着hotspot的源码问了个问题:

image

源码

看一下对应的源码

// 来源:hotspot/src/cpu/x86/vm/assembler_x86.cpp
void Assembler::notl(Register dst) {
  int encode = prefix_and_encode(dst->encoding());
  emit_int8((unsigned char)0xF7);
  emit_int8((unsigned char)(0xD0 | encode));
}

// 来源:hotspot/src/cpu/x86/vm/register_x86.hpp
// Register是RegisterImpl的指针类型
typedef RegisterImpl* Register;
class RegisterImpl: public AbstractRegisterImpl {
    ...
    // encoding函数是返回RegisterImpl实例在内存中的首地址
    // 也就是代表的寄存器的首地址
    int encoding() const {
       ... 
       return (intptr_t)this; 
    }    
    ...
}

// 来源:hotspot/src/cpu/x86/vm/assembler_x86.cpp
// 该函数输出指令前缀并返回寄存器的地址
int Assembler::prefixq_and_encode(int reg_enc) {
  if (reg_enc < 8) {
    // 寄存器地址小于8,输出指令前缀
    prefix(REX_W);
  } else {
    // 寄存器地址 >=8,输出扩展指令前缀
    prefix(REX_WB);
    // 减8是因为只有8个通用寄存器
    reg_enc -= 8;
  }
  // 返回寄存器的首地址
  return reg_enc;
}

// 来源:hotspot/src/cpu/x86/vm/assembler_x86.cpp
// 输出8个位
void emit_int8(int8_t  x) { 
    code_section()->emit_int8(x); 
}
// 来源: hotspot/src/share/vm/asm/codeBuffer.hpp
// 汇编代码缓冲区,写入8bit
 void emit_int8 (int8_t  x)  { 
     // end()位子写入x
     *((int8_t*) end()) = x; 
     // end位置往后+8
     set_end(end() + sizeof(int8_t)); 
 }

从源码上看,理解到了HotSpot在编译执行时,生成汇编代码的实现动作。但是为什么not寄存器指令要输出 emit_int8((unsigned char)0xF7);emit_int8((unsigned char)(0xD0 | encode));

指令

看一下X86架构一条指令的结构:

image

  • Instruction Prefixes:指令前缀,非必须
  • Opcode:指令,必须
  • ModR/M:扩展指令,非必须
  • SIB:扩展指令增强,非必须
  • Displacement:偏移量,非必须
  • Immediate:立即数,非必须

当前not指令只用到了Opcode和ModR/M两个结构:

image

那输出的 0xF7,0D0 | encode到底是什么意思呢?

解析硬编码

先不考虑 |(或)运算

0xF7D0 = 0xF7 11 010 000

通过《Intel 开发手册》查表:

image

F7和010的组合代表 not 指令

image

11 和 000 ~ 111的组合代表具体的寄存器,或运算就是决定具体的寄存器

总结

更深入理解JVM(HotSpot)编译执行落地过程