AS汇编器源码剖析-Arm操作码
Arm opcode定义
arm的全部指令定义在aarch64-tbl.h的aarch64_opcode_table,超过1000个。指令以struct aarch64_opcode的格式定义。以下代码是Add/subtract (with carry)部分的指令集定义。
structaarch64_opcodeaarch64_opcode_table[] =
{
/* Add/subtract (with carry). */
{"adc", 0x1a000000,0x7fe0fc00,addsub_carry, 0, CORE, OP3 (Rd, Rn, Rm), QL_I3SAMER,F_SF},
{"adcs", 0x3a000000,0x7fe0fc00,addsub_carry, 0, CORE, OP3 (Rd, Rn, Rm), QL_I3SAMER,F_SF},
{"sbc", 0x5a000000,0x7fe0fc00,addsub_carry, 0, CORE, OP3 (Rd, Rn, Rm), QL_I3SAMER,F_HAS_ALIAS |F_SF},
{"ngc", 0x5a0003e0,0x7fe0ffe0,addsub_carry, 0, CORE, OP2 (Rd, Rm), QL_I2SAME,F_ALIAS |F_SF},
{"sbcs", 0x7a000000,0x7fe0fc00,addsub_carry, 0, CORE, OP3 (Rd, Rn, Rm), QL_I3SAMER,F_HAS_ALIAS |F_SF},
{"ngcs", 0x7a0003e0, 0x7fe0ffe0,addsub_carry, 0, CORE, OP2 (Rd, Rm),QL_I2SAME, F_ALIAS |F_SF},
…}
structaarch64_opcode 定义在aarch64.h
structaarch64_opcode
{
/* 助记符 */
constchar *name;
/* 操作码,不包含操作数 */
aarch64_insnopcode;
/*opcode mask.掩码相当于操作码中所有有效位都置1的值*/
aarch64_insnmask;
/* 指令集类别,参见armv8 reference manual C4小节对指令的分类 */
enumaarch64_insn_class iclass;
/*Enumerator identifier. */
enumaarch64_op op;
/* 提供这个指令的组件 */
constaarch64_feature_set *avariant;
/* 操作数列表*/
enumaarch64_opnd operands[AARCH64_MAX_OPND_NUM];
/*操作数的限定序列,用来检查操作数是否符合约束*/
aarch64_opnd_qualifier_seq_tqualifiers_list[AARCH64_MAX_QLF_SEQ_NUM];
/* 指令相关的flag */
uint32_tflags;
};
先分析adc指令,对照结构体定义,每个成员相关对应信息如下:
"adc", | 助记符 |
0x1a000000 | 操作码 |
0x7fe0fc00 | 操作码掩码 |
addsub_carry, | 指令所属类 |
0 | opcode枚举 |
CORE | 提供这种指令的组件 |
OP3 (Rd, Rn, Rm) | 操作数编码数组 |
QL_I3SAMER, | 操作数的限定序列 |
Adc指令定义
在armv8 reference manual里的C4: A64Instruction Set encoding里,对指令集的编码进行的说明。Adc定义在C6.6.1,属于add、subtract(with carry)小节C4.5.3,信息如下:
ADC操作码opcode
0x1a000000 | 操作码 |
根据操作码的定义,有效部分不考虑操作数和其他部分,所以从bit21-30,bit10-15,都是属于这个class的有效bit。其他bit按照0来填充,所以操作码的21-30bit为0001 1010 000,bit10-15也是全0,就是0x1a000000。
特别注意的是bit31,这一位表示的是32/64位的区别,但是不管这一位是0/1,都是adc指令。所以,在时间的汇编器实现里,是通过sfflag来控制设置bit31,而不是直接把bit31作为opcode。
ADC操作码掩码
0x7fe0fc00 | 操作码掩码 |
操作码掩码包括21-30bit,bit10-15,全部为1表示就是0x7fe0fc00。同opcode解释,sf不占掩码。
指令的类class
addsub_carry, | 指令所属类 |
很明显,根据rfm定义就是addsub_carry,见C4.5.3定义。所有的分类都可以在C4里找到说明。在代码里定义如下,一共70类。
enum aarch64_insn_class
{
addsub_carry,
addsub_ext,
addsub_imm,
addsub_shift,
asimdall,
asimddiff,
asimdelem,
asimdext,
asimdimm,
asimdins,
asimdmisc,
asimdperm,
asimdsame,
asimdshf,
asimdtbl,
asisddiff,
asisdelem,
asisdlse,
asisdlsep,
asisdlso,
asisdlsop,
asisdmisc,
asisdone,
asisdpair,
asisdsame,
asisdshf,
bitfield,
branch_imm,
branch_reg,
compbranch,
condbranch,
condcmp_imm,
condcmp_reg,
condsel,
cryptoaes,
cryptosha2,
cryptosha3,
dp_1src,
dp_2src,
dp_3src,
exception,
extract,
float2fix,
float2int,
floatccmp,
floatcmp,
floatdp1,
floatdp2,
floatdp3,
floatimm,
floatsel,
ldst_immpost,
ldst_immpre,
ldst_imm9, /* immpost or immpre */
ldst_pos,
ldst_regoff,
ldst_unpriv,
ldst_unscaled,
ldstexcl,
ldstnapair_offs,
ldstpair_off,
ldstpair_indexed,
loadlit,
log_imm,
log_shift,
lse_atomic,
movewide,
pcreladdr,
ic_system,
testbranch,
}
Enumerator identifier
/*Enumerator identifier. */
enumaarch64_op op; 绝大部分指令的op都是0,这个域主要用来区分一些特殊的指令。普通指令里,只有add立即数使用了非0.
{"add", 0x11000000, 0x7f000000,addsub_imm, OP_ADD, CORE, OP3 (Rd_SP, Rn_SP, AIMM), QL_R2NIL, F_HAS_ALIAS |F_SF},
在parse_operands函数里使用了一次,代码如下。
case AARCH64_OPND_AIMM:
if(opcode->op == OP_ADD)
/* ADDmay have relocation types. */
po_misc_or_fail (parse_shifter_operand_reloc (&str, info,
SHIFTED_ARITH_IMM));
else
po_misc_or_fail (parse_shifter_operand (&str, info,
SHIFTED_ARITH_IMM));
提供指令的组件
CORE | 提供指令的组件 |
这个指令是CORE支持的,所以avariant是CORE枚举类型。有的指令是浮点单元支持的,所以是FP,有的是加密加速单元提供的,所以是CRYPTO。
arm有如下类型的组件提供指令
#defineCORE &aarch64_feature_v8
#defineFP &aarch64_feature_fp
#defineSIMD &aarch64_feature_simd
#defineCRYPTO &aarch64_feature_crypto
#defineCRC &aarch64_feature_crc
#defineLSE &aarch64_feature_lse
#defineLOR &aarch64_feature_lor
#defineRDMA &aarch64_feature_rdma
#defineFP_F16 &aarch64_feature_fp_f16
#defineSIMD_F16 &aarch64_feature_simd_f16
#defineRAS &aarch64_feature_ras
#defineSTAT_PROFILE &aarch64_feature_stat_profile
#defineARMV8_2 &aarch64_feature_v8_2
操作数
OP3 (Rd, Rn, Rm) | 操作数编码数组 |
OP3(Rd, Rn, Rm) 逐个展开如下
{AARCH64_OPND_Rd,AARCH64_OPND_Rn, AARCH64_OPND_Rm }
辅助宏定义如下
#defineOPND(x) AARCH64_OPND_##x
#defineOP0() {}
#defineOP1(a) {OPND(a)}
#defineOP2(a,b) {OPND(a), OPND(b)}
#defineOP3(a,b,c) {OPND(a), OPND(b), OPND(c)}
#defineOP4(a,b,c,d) {OPND(a), OPND(b), OPND(c), OPND(d)}
#defineOP5(a,b,c,d,e) {OPND(a), OPND(b), OPND(c), OPND(d), OPND(e)}
在enum aarch64_opnd里定义了arm支持的所有的操作数,其中有如下定义
{
AARCH64_OPND_Rd,/* Integer register as destination. */
AARCH64_OPND_Rn,/* Integer register as source. */
AARCH64_OPND_Rm,/* Integer register as source. */
}
操作数的限定序列
OP3 (Rd, Rn, Rm) | 操作数编码数组 |
QL_I3SAMER展开实际上是
{
{AARCH64_OPND_QLF_W,AARCH64_OPND_QLF_W, AARCH64_OPND_QLF_W }
{AARCH64_OPND_QLF_X,AARCH64_OPND_QLF_X,AARCH64_OPND_QLF_X }
}
具体的辅助宏定义如下
/*e.g. UDIV <Xd>, <Xn>, <Xm>. */
#defineQL_I3SAMER \
{ \
QLF3(W,W,W),\
QLF3(X,X,X),\
}
#defineQLF3(a,b,c) {QLF(a), QLF(b), QLF(c)}
#defineQLF(x) AARCH64_OPND_QLF_##x
操作数限定符定义全部在enum aarch64_opnd_qualifier
{
/* Indicating no further qualification on anoperand. */
AARCH64_OPND_QLF_NIL,
/* Qualifying an operand which is a general purpose(integer) register;
indicating the operand data size or a specificregister. */
AARCH64_OPND_QLF_W, /* Wn, WZR or WSP. */
AARCH64_OPND_QLF_X, /* Xn, XZR or XSP. */
其中,W表示word,32bit变量,X表示64bit变量。这里可以看出,sf不是固定的部分,也不能作为判断是否adc指令的依据,所以指令掩码不能包含sf位。根据手册上的说明可以清楚看出。
指令标志
F_SF标志的解释如下:
/* Instruction has the field of'sf'. */
#define F_SF (1 << 5)
很明显,这个说的是bit31,表明是32位还是64位。
F_HAS_ALIAS和F_ALIAS标志
adcs和adcs类似。sbc和sbcs差异是最后的标志不相同F_HAS_ALIAS| F_SF,
#define F_HAS_ALIAS (1 << 1)
这个指的是,当rn==‘11111’的时候,可以用NGC助记符代替。这个也就是NGC的来历。
再看NGC的定义
{"sbc", 0x5a000000, 0x7fe0fc00,addsub_carry, 0, CORE, OP3 (Rd, Rn, Rm), QL_I3SAMER, F_HAS_ALIAS | F_SF},
{"ngc", 0x5a0003e0, 0x7fe0ffe0,addsub_carry, 0, CORE, OP2 (Rd, Rm), QL_I2SAME, F_ALIAS | F_SF},
差别在于,Rn对应的bit变成了3e,也就是全1,操作数从OP3变成了OP2。另外,ngc的flga是F_ALIAS,表明这是个alias指令助记符,而sbc是F_HAS_ALIAS,可见sbc是真正的指令。
这个class一共4条指令,ADC, ADCS,SBC,SBCS ,最后一个s实际上是表示sign,影响S bit。