openjdk底层汇编指令调用(三)——编码
有了寄存器的编号实现,现在讨论如何实现指令编码。还是以AArch64
指令为例进行说明。假设指令为add x0,x1,x2
,根据指令手册可知
其机器码为
#二进制
10001011000 00010 000000 00001 00000#十六进制
0x8B020020
#小端编码需要反转
0x2000028B
回到源码文件assembler_aarch64.hpp
。该文件中定义了汇编器类(Assembler)。在英文中(雅思7分,口语8分,炫耀一下),汇编(assemble)的原意是生产线上组装零件的意思。根据计算机的知识,汇编器(assembler)的作用就是根据手册组装指令中的各个部分。根据手册中该指令描述可知,至少有几处是需要填充值。如果要形成完整的指令则需将这些部分组装(assemble)起来。
在源码中如何实现上述的add
指令呢?其实现原理是将手册中各部分用编码替代,然后进行拼装。assembler_aarch64.hpp
文件的如下代码实现了add x0,x1,x2
指令的编码
#define INSN(NAME) \void NAME(Register Rd, Register Rn, Register Rm) { \if (Rd == sp || Rn == sp) \NAME(Rd, Rn, Rm, ext::uxtx); \else \NAME(Rd, Rn, Rm, LSL); \}...INSN(add);
上述INST(add)
宏展开之后如下
void add(Register Rd, Register Rn, Register Rm) { if (Rd == sp || Rn == sp) add(Rd, Rn, Rm, ext::uxtx); else add(Rd, Rn, Rm, LSL);
}
很明显,如要实现add x0,x1,x2
的编码,需调用的add(Rd,Rn,Rm,LSL)
函数,其具体实现如下
// Add/subtract (shifted register)
#define INSN(NAME, size, op) \void NAME(Register Rd, Register Rn, Register Rm, \enum shift_kind kind, unsigned shift = 0) { \starti; \f(0, 21); \assert_cond(kind != ROR); \zrf(Rd, 0), zrf(Rn, 5), zrf(Rm, 16); \op_shifted_reg(0b01011, kind, shift, size, op); \}INSN(add, 1, 0b000);
宏展开代码为
void add(Register Rd, Register Rn, Register Rm, enum shift_kind kind, unsigned shift = 0) { Instruction_aarch64 do_not_use(this); set_current(&do_not_use); f(0, 21); ; zrf(Rd, 0), zrf(Rn, 5), zrf(Rm, 16); op_shifted_reg(0b01011, kind, shift, 1, 0b000);
}
这里首先要明白f
函数的作用:
/*
* 将val的值放入lsb(低位)至msb(高位)区间
*/
void f(unsigned val, int msb, int lsb) {current->f(val, msb, lsb);
}
因此f(0,21)表示在21位上设置为0,符合手册要求的格式
其他函数如zrf
和op_shifted_reg
均是在内部调用f
函数以实现在编码各位置上的赋值,这里对zrf
函数的实现不再赘述,简单分析一下op_shifted_reg
函数
因此op_shifted_reg
将22至31位上按照手册要求赋值。
通过add(Register Rd, Register Rn, Register Rm, enum shift_kind kind, unsigned shift = 0)
的调用可完成add x0,x1,x2
汇编指令的编码。其中Rm
用r1
替换,Rn
用r2
替换,Rd
用r0
替换。关于寄存器定义的内容参见寄存器定义。
指令编码完成后,如果需要执行该指令,则需将其放入内存的指令区域,然后让程序计数器(PC)指向该指令地址,CPU读取该指令执行即可。