mirror of https://github.com/frida/tinycc
x86-64-asm: Support high registers %r8 - %r15
This requires correctly handling the REX prefix. As bonus we now also support the four 8bit registers spl,bpl,sil,dil, which are decoded as ah,ch,dh,bh in non-long-mode (and require a REX prefix as well).
This commit is contained in:
parent
8765826465
commit
4cb7047f0f
210
i386-asm.c
210
i386-asm.c
|
@ -72,6 +72,10 @@ enum {
|
||||||
OPT_DB, /* warning: value is hardcoded from TOK_ASM_xxx */
|
OPT_DB, /* warning: value is hardcoded from TOK_ASM_xxx */
|
||||||
OPT_SEG,
|
OPT_SEG,
|
||||||
OPT_ST,
|
OPT_ST,
|
||||||
|
#ifdef TCC_TARGET_X86_64
|
||||||
|
OPT_REG8_LOW, /* %spl,%bpl,%sil,%dil, encoded like ah,ch,dh,bh, but
|
||||||
|
with REX prefix, not used in insn templates */
|
||||||
|
#endif
|
||||||
OPT_IM8,
|
OPT_IM8,
|
||||||
OPT_IM8S,
|
OPT_IM8S,
|
||||||
OPT_IM16,
|
OPT_IM16,
|
||||||
|
@ -120,10 +124,12 @@ enum {
|
||||||
#define OP_INDIR (1 << OPT_INDIR)
|
#define OP_INDIR (1 << OPT_INDIR)
|
||||||
#ifdef TCC_TARGET_X86_64
|
#ifdef TCC_TARGET_X86_64
|
||||||
# define OP_REG64 (1 << OPT_REG64)
|
# define OP_REG64 (1 << OPT_REG64)
|
||||||
|
# define OP_REG8_LOW (1 << OPT_REG8_LOW)
|
||||||
# define OP_IM64 (1 << OPT_IM64)
|
# define OP_IM64 (1 << OPT_IM64)
|
||||||
# define OP_EA32 (OP_EA << 1)
|
# define OP_EA32 (OP_EA << 1)
|
||||||
#else
|
#else
|
||||||
# define OP_REG64 0
|
# define OP_REG64 0
|
||||||
|
# define OP_REG8_LOW 0
|
||||||
# define OP_IM64 0
|
# define OP_IM64 0
|
||||||
# define OP_EA32 0
|
# define OP_EA32 0
|
||||||
#endif
|
#endif
|
||||||
|
@ -272,6 +278,39 @@ static inline int get_reg_shift(TCCState *s1)
|
||||||
return shift;
|
return shift;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef TCC_TARGET_X86_64
|
||||||
|
static int asm_parse_high_reg(int *type)
|
||||||
|
{
|
||||||
|
int reg = -1;
|
||||||
|
if (tok >= TOK_IDENT && tok < tok_ident) {
|
||||||
|
const char *s = table_ident[tok - TOK_IDENT]->str;
|
||||||
|
char c;
|
||||||
|
if (*s++ != 'r')
|
||||||
|
return -1;
|
||||||
|
/* Don't allow leading '0'. */
|
||||||
|
if ((c = *s++) >= '1' && c <= '9')
|
||||||
|
reg = c - '0';
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
if ((c = *s) >= '0' && c <= '5')
|
||||||
|
s++, reg = reg * 10 + c - '0';
|
||||||
|
if (reg > 15)
|
||||||
|
return -1;
|
||||||
|
if ((c = *s) == 0)
|
||||||
|
*type = OP_REG64;
|
||||||
|
else if (c == 'b' && !s[1])
|
||||||
|
*type = OP_REG8;
|
||||||
|
else if (c == 'w' && !s[1])
|
||||||
|
*type = OP_REG16;
|
||||||
|
else if (c == 'd' && !s[1])
|
||||||
|
*type = OP_REG32;
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static int asm_parse_reg(int *type)
|
static int asm_parse_reg(int *type)
|
||||||
{
|
{
|
||||||
int reg = 0;
|
int reg = 0;
|
||||||
|
@ -281,12 +320,17 @@ static int asm_parse_reg(int *type)
|
||||||
next();
|
next();
|
||||||
if (tok >= TOK_ASM_eax && tok <= TOK_ASM_edi) {
|
if (tok >= TOK_ASM_eax && tok <= TOK_ASM_edi) {
|
||||||
reg = tok - TOK_ASM_eax;
|
reg = tok - TOK_ASM_eax;
|
||||||
|
*type = OP_REG32;
|
||||||
#ifdef TCC_TARGET_X86_64
|
#ifdef TCC_TARGET_X86_64
|
||||||
*type = OP_EA32;
|
|
||||||
} else if (tok >= TOK_ASM_rax && tok <= TOK_ASM_rdi) {
|
} else if (tok >= TOK_ASM_rax && tok <= TOK_ASM_rdi) {
|
||||||
reg = tok - TOK_ASM_rax;
|
reg = tok - TOK_ASM_rax;
|
||||||
|
*type = OP_REG64;
|
||||||
} else if (tok == TOK_ASM_rip) {
|
} else if (tok == TOK_ASM_rip) {
|
||||||
reg = 8;
|
reg = -2; /* Probably should use different escape code. */
|
||||||
|
*type = OP_REG64;
|
||||||
|
} else if ((reg = asm_parse_high_reg(type)) >= 0
|
||||||
|
&& (*type == OP_REG32 || *type == OP_REG64)) {
|
||||||
|
;
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
error_32:
|
error_32:
|
||||||
|
@ -345,6 +389,13 @@ static void parse_operand(TCCState *s1, Operand *op)
|
||||||
if (op->reg == 0)
|
if (op->reg == 0)
|
||||||
op->type |= OP_ST0;
|
op->type |= OP_ST0;
|
||||||
goto no_skip;
|
goto no_skip;
|
||||||
|
#ifdef TCC_TARGET_X86_64
|
||||||
|
} else if (tok >= TOK_ASM_spl && tok <= TOK_ASM_dil) {
|
||||||
|
op->type = OP_REG8 | OP_REG8_LOW;
|
||||||
|
op->reg = 4 + tok - TOK_ASM_spl;
|
||||||
|
} else if ((op->reg = asm_parse_high_reg(&op->type)) >= 0) {
|
||||||
|
;
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
reg_error:
|
reg_error:
|
||||||
tcc_error("unknown register %%%s", get_tok_str(tok, &tokc));
|
tcc_error("unknown register %%%s", get_tok_str(tok, &tokc));
|
||||||
|
@ -411,7 +462,7 @@ static void parse_operand(TCCState *s1, Operand *op)
|
||||||
op->shift = get_reg_shift(s1);
|
op->shift = get_reg_shift(s1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type & OP_EA32)
|
if (type & OP_REG32)
|
||||||
op->type |= OP_EA32;
|
op->type |= OP_EA32;
|
||||||
skip(')');
|
skip(')');
|
||||||
}
|
}
|
||||||
|
@ -475,7 +526,7 @@ static inline int asm_modrm(int reg, Operand *op)
|
||||||
#endif
|
#endif
|
||||||
gen_expr32(&op->e);
|
gen_expr32(&op->e);
|
||||||
#ifdef TCC_TARGET_X86_64
|
#ifdef TCC_TARGET_X86_64
|
||||||
} else if (op->reg == 8) {
|
} else if (op->reg == -2) {
|
||||||
ExprValue *pe = &op->e;
|
ExprValue *pe = &op->e;
|
||||||
g(0x05 + (reg << 3));
|
g(0x05 + (reg << 3));
|
||||||
gen_addrpc32(pe->sym ? VT_SYM : 0, pe->sym, pe->v);
|
gen_addrpc32(pe->sym ? VT_SYM : 0, pe->sym, pe->v);
|
||||||
|
@ -516,6 +567,69 @@ static inline int asm_modrm(int reg, Operand *op)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef TCC_TARGET_X86_64
|
||||||
|
#define REX_W 0x48
|
||||||
|
#define REX_R 0x44
|
||||||
|
#define REX_X 0x42
|
||||||
|
#define REX_B 0x41
|
||||||
|
|
||||||
|
static void asm_rex(int width64, Operand *ops, int nb_ops, int *op_type,
|
||||||
|
int regi, int rmi)
|
||||||
|
{
|
||||||
|
unsigned char rex = width64 ? 0x48 : 0;
|
||||||
|
int saw_high_8bit = 0;
|
||||||
|
int i;
|
||||||
|
if (rmi == -1) {
|
||||||
|
/* No mod/rm byte, but we might have a register op nevertheless
|
||||||
|
(we will add it to the opcode later). */
|
||||||
|
for(i = 0; i < nb_ops; i++) {
|
||||||
|
if (op_type[i] & (OP_REG | OP_ST)) {
|
||||||
|
if (ops[i].reg >= 8) {
|
||||||
|
rex |= REX_B;
|
||||||
|
ops[i].reg -= 8;
|
||||||
|
} else if (ops[i].type & OP_REG8_LOW)
|
||||||
|
rex |= 0x40;
|
||||||
|
else if (ops[i].type & OP_REG8 && ops[i].reg >= 4)
|
||||||
|
/* An 8 bit reg >= 4 without REG8 is ah/ch/dh/bh */
|
||||||
|
saw_high_8bit = ops[i].reg;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (regi != -1) {
|
||||||
|
if (ops[regi].reg >= 8) {
|
||||||
|
rex |= REX_R;
|
||||||
|
ops[regi].reg -= 8;
|
||||||
|
} else if (ops[regi].type & OP_REG8_LOW)
|
||||||
|
rex |= 0x40;
|
||||||
|
else if (ops[regi].type & OP_REG8 && ops[regi].reg >= 4)
|
||||||
|
/* An 8 bit reg >= 4 without REG8 is ah/ch/dh/bh */
|
||||||
|
saw_high_8bit = ops[regi].reg;
|
||||||
|
}
|
||||||
|
if (ops[rmi].type & (OP_REG | OP_MMX | OP_SSE | OP_CR | OP_EA)) {
|
||||||
|
if (ops[rmi].reg >= 8) {
|
||||||
|
rex |= REX_B;
|
||||||
|
ops[rmi].reg -= 8;
|
||||||
|
} else if (ops[rmi].type & OP_REG8_LOW)
|
||||||
|
rex |= 0x40;
|
||||||
|
else if (ops[rmi].type & OP_REG8 && ops[rmi].reg >= 4)
|
||||||
|
/* An 8 bit reg >= 4 without REG8 is ah/ch/dh/bh */
|
||||||
|
saw_high_8bit = ops[rmi].reg;
|
||||||
|
}
|
||||||
|
if (ops[rmi].type & OP_EA && ops[rmi].reg2 >= 8) {
|
||||||
|
rex |= REX_X;
|
||||||
|
ops[rmi].reg2 -= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rex) {
|
||||||
|
if (saw_high_8bit)
|
||||||
|
tcc_error("can't encode register %%%ch when REX prefix is required",
|
||||||
|
"acdb"[saw_high_8bit-4]);
|
||||||
|
g(rex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static void maybe_print_stats (void)
|
static void maybe_print_stats (void)
|
||||||
{
|
{
|
||||||
static int already = 1;
|
static int already = 1;
|
||||||
|
@ -558,13 +672,16 @@ static void maybe_print_stats (void)
|
||||||
ST_FUNC void asm_opcode(TCCState *s1, int opcode)
|
ST_FUNC void asm_opcode(TCCState *s1, int opcode)
|
||||||
{
|
{
|
||||||
const ASMInstr *pa;
|
const ASMInstr *pa;
|
||||||
int i, modrm_index, reg, v, op1, seg_prefix, pc;
|
int i, modrm_index, modreg_index, reg, v, op1, seg_prefix, pc;
|
||||||
int nb_ops, s;
|
int nb_ops, s;
|
||||||
Operand ops[MAX_OPERANDS], *pop;
|
Operand ops[MAX_OPERANDS], *pop;
|
||||||
int op_type[3]; /* decoded op type */
|
int op_type[3]; /* decoded op type */
|
||||||
int alltypes; /* OR of all operand types */
|
int alltypes; /* OR of all operand types */
|
||||||
int autosize;
|
int autosize;
|
||||||
int p66;
|
int p66;
|
||||||
|
#ifdef TCC_TARGET_X86_64
|
||||||
|
int rex64;
|
||||||
|
#endif
|
||||||
|
|
||||||
maybe_print_stats();
|
maybe_print_stats();
|
||||||
/* force synthetic ';' after prefix instruction, so we can handle */
|
/* force synthetic ';' after prefix instruction, so we can handle */
|
||||||
|
@ -775,6 +892,7 @@ ST_FUNC void asm_opcode(TCCState *s1, int opcode)
|
||||||
if (p66)
|
if (p66)
|
||||||
g(0x66);
|
g(0x66);
|
||||||
#ifdef TCC_TARGET_X86_64
|
#ifdef TCC_TARGET_X86_64
|
||||||
|
rex64 = 0;
|
||||||
if (s == 3 || (alltypes & OP_REG64)) {
|
if (s == 3 || (alltypes & OP_REG64)) {
|
||||||
/* generate REX prefix */
|
/* generate REX prefix */
|
||||||
int default64 = 0;
|
int default64 = 0;
|
||||||
|
@ -794,7 +912,7 @@ ST_FUNC void asm_opcode(TCCState *s1, int opcode)
|
||||||
&& opcode != TOK_ASM_popl && opcode != TOK_ASM_popq
|
&& opcode != TOK_ASM_popl && opcode != TOK_ASM_popq
|
||||||
&& opcode != TOK_ASM_call && opcode != TOK_ASM_jmp))
|
&& opcode != TOK_ASM_call && opcode != TOK_ASM_jmp))
|
||||||
&& !default64)
|
&& !default64)
|
||||||
g(0x48);
|
rex64 = 1;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -830,6 +948,50 @@ ST_FUNC void asm_opcode(TCCState *s1, int opcode)
|
||||||
/* fpu arith case */
|
/* fpu arith case */
|
||||||
v += ((opcode - pa->sym) / 6) << 3;
|
v += ((opcode - pa->sym) / 6) << 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* search which operand will be used for modrm */
|
||||||
|
modrm_index = -1;
|
||||||
|
modreg_index = -1;
|
||||||
|
if (pa->instr_type & OPC_MODRM) {
|
||||||
|
if (!nb_ops) {
|
||||||
|
/* A modrm opcode without operands is a special case (e.g. mfence).
|
||||||
|
It has a group and acts as if there's an register operand 0
|
||||||
|
(ax). */
|
||||||
|
i = 0;
|
||||||
|
ops[i].type = OP_REG;
|
||||||
|
ops[i].reg = 0;
|
||||||
|
goto modrm_found;
|
||||||
|
}
|
||||||
|
/* first look for an ea operand */
|
||||||
|
for(i = 0;i < nb_ops; i++) {
|
||||||
|
if (op_type[i] & OP_EA)
|
||||||
|
goto modrm_found;
|
||||||
|
}
|
||||||
|
/* then if not found, a register or indirection (shift instructions) */
|
||||||
|
for(i = 0;i < nb_ops; i++) {
|
||||||
|
if (op_type[i] & (OP_REG | OP_MMX | OP_SSE | OP_INDIR))
|
||||||
|
goto modrm_found;
|
||||||
|
}
|
||||||
|
#ifdef ASM_DEBUG
|
||||||
|
tcc_error("bad op table");
|
||||||
|
#endif
|
||||||
|
modrm_found:
|
||||||
|
modrm_index = i;
|
||||||
|
/* if a register is used in another operand then it is
|
||||||
|
used instead of group */
|
||||||
|
for(i = 0;i < nb_ops; i++) {
|
||||||
|
int t = op_type[i];
|
||||||
|
if (i != modrm_index &&
|
||||||
|
(t & (OP_REG | OP_MMX | OP_SSE | OP_CR | OP_TR | OP_DB | OP_SEG))) {
|
||||||
|
modreg_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef TCC_TARGET_X86_64
|
||||||
|
asm_rex (rex64, ops, nb_ops, op_type, modreg_index, modrm_index);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (pa->instr_type & OPC_REG) {
|
if (pa->instr_type & OPC_REG) {
|
||||||
/* mov $im, %reg case */
|
/* mov $im, %reg case */
|
||||||
if (v == 0xb0 && s >= 1)
|
if (v == 0xb0 && s >= 1)
|
||||||
|
@ -881,8 +1043,6 @@ ST_FUNC void asm_opcode(TCCState *s1, int opcode)
|
||||||
g(op1);
|
g(op1);
|
||||||
g(v);
|
g(v);
|
||||||
|
|
||||||
/* search which operand will used for modrm */
|
|
||||||
modrm_index = 0;
|
|
||||||
if (OPCT_IS(pa->instr_type, OPC_SHIFT)) {
|
if (OPCT_IS(pa->instr_type, OPC_SHIFT)) {
|
||||||
reg = (opcode - pa->sym) / NBWLX;
|
reg = (opcode - pa->sym) / NBWLX;
|
||||||
if (reg == 6)
|
if (reg == 6)
|
||||||
|
@ -897,40 +1057,10 @@ ST_FUNC void asm_opcode(TCCState *s1, int opcode)
|
||||||
|
|
||||||
pc = 0;
|
pc = 0;
|
||||||
if (pa->instr_type & OPC_MODRM) {
|
if (pa->instr_type & OPC_MODRM) {
|
||||||
if (!nb_ops) {
|
|
||||||
/* A modrm opcode without operands is a special case (e.g. mfence).
|
|
||||||
It has a group and acts as if there's an register operand 0
|
|
||||||
(ax). */
|
|
||||||
i = 0;
|
|
||||||
ops[i].type = OP_REG;
|
|
||||||
ops[i].reg = 0;
|
|
||||||
goto modrm_found;
|
|
||||||
}
|
|
||||||
/* first look for an ea operand */
|
|
||||||
for(i = 0;i < nb_ops; i++) {
|
|
||||||
if (op_type[i] & OP_EA)
|
|
||||||
goto modrm_found;
|
|
||||||
}
|
|
||||||
/* then if not found, a register or indirection (shift instructions) */
|
|
||||||
for(i = 0;i < nb_ops; i++) {
|
|
||||||
if (op_type[i] & (OP_REG | OP_MMX | OP_SSE | OP_INDIR))
|
|
||||||
goto modrm_found;
|
|
||||||
}
|
|
||||||
#ifdef ASM_DEBUG
|
|
||||||
tcc_error("bad op table");
|
|
||||||
#endif
|
|
||||||
modrm_found:
|
|
||||||
modrm_index = i;
|
|
||||||
/* if a register is used in another operand then it is
|
/* if a register is used in another operand then it is
|
||||||
used instead of group */
|
used instead of group */
|
||||||
for(i = 0;i < nb_ops; i++) {
|
if (modreg_index >= 0)
|
||||||
v = op_type[i];
|
reg = ops[modreg_index].reg;
|
||||||
if (i != modrm_index &&
|
|
||||||
(v & (OP_REG | OP_MMX | OP_SSE | OP_CR | OP_TR | OP_DB | OP_SEG))) {
|
|
||||||
reg = ops[i].reg;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pc = asm_modrm(reg, &ops[modrm_index]);
|
pc = asm_modrm(reg, &ops[modrm_index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -93,6 +93,14 @@
|
||||||
DEF_ASM(st)
|
DEF_ASM(st)
|
||||||
DEF_ASM(rip)
|
DEF_ASM(rip)
|
||||||
|
|
||||||
|
#ifdef TCC_TARGET_X86_64
|
||||||
|
/* The four low parts of sp/bp/si/di that exist only on
|
||||||
|
x86-64 (encoding aliased to ah,ch,dh,dh when not using REX). */
|
||||||
|
DEF_ASM(spl)
|
||||||
|
DEF_ASM(bpl)
|
||||||
|
DEF_ASM(sil)
|
||||||
|
DEF_ASM(dil)
|
||||||
|
#endif
|
||||||
/* generic two operands */
|
/* generic two operands */
|
||||||
DEF_BWLX(mov)
|
DEF_BWLX(mov)
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,22 @@ movq %dr6, %rax
|
||||||
movl %fs, %ecx
|
movl %fs, %ecx
|
||||||
movl %ebx, %fs
|
movl %ebx, %fs
|
||||||
|
|
||||||
|
#ifdef __x86_64__
|
||||||
|
movq %r8, %r9
|
||||||
|
movq %r10, %r11
|
||||||
|
movq %r12, %r13
|
||||||
|
movq %r14, %r15
|
||||||
|
movq %rax, %r9
|
||||||
|
movq %r15, %rsi
|
||||||
|
inc %r9b
|
||||||
|
dec %r10w
|
||||||
|
not %r11d
|
||||||
|
negq %r12
|
||||||
|
decb %r13b
|
||||||
|
incw %r14w
|
||||||
|
notl %r15d
|
||||||
|
#endif
|
||||||
|
|
||||||
movsbl 0x1000, %eax
|
movsbl 0x1000, %eax
|
||||||
movsbw 0x1000, %ax
|
movsbw 0x1000, %ax
|
||||||
movswl 0x1000, %eax
|
movswl 0x1000, %eax
|
||||||
|
@ -184,6 +200,24 @@ addl $0x123, (%ebp)
|
||||||
addl $0x123, (%esp)
|
addl $0x123, (%esp)
|
||||||
cmpl $0x123, (%esp)
|
cmpl $0x123, (%esp)
|
||||||
|
|
||||||
|
#ifdef __x86_64__
|
||||||
|
xor %bl,%ah
|
||||||
|
xor %bl,%r8b
|
||||||
|
xor %r9b,%bl
|
||||||
|
xor %sil,%cl
|
||||||
|
add %eax,(%r8d)
|
||||||
|
add %ebx,(%r9)
|
||||||
|
add %edx,(%r10d,%r11d)
|
||||||
|
add %ecx,(%r12,%r13)
|
||||||
|
add %esi,(%r14,%r15,4)
|
||||||
|
add %edi,0x1000(%rbx,%r12,8)
|
||||||
|
add %r11,0x1000(%ebp,%r9d,8)
|
||||||
|
movb $12, %ah
|
||||||
|
movb $13, %bpl
|
||||||
|
movb $14, %dil
|
||||||
|
movb $15, %r12b
|
||||||
|
#endif
|
||||||
|
|
||||||
add %eax, (%ebx)
|
add %eax, (%ebx)
|
||||||
add (%ebx), %eax
|
add (%ebx), %eax
|
||||||
|
|
||||||
|
@ -796,10 +830,10 @@ nop
|
||||||
movd %rdi, %xmm2
|
movd %rdi, %xmm2
|
||||||
movd (%rbx), %mm3
|
movd (%rbx), %mm3
|
||||||
movd (%rbx), %xmm3
|
movd (%rbx), %xmm3
|
||||||
movd %mm1, %rsi
|
movd %mm1, %r12
|
||||||
movd %xmm2, %rdi
|
movd %xmm2, %rdi
|
||||||
movd %mm3, (%rdx)
|
movd %mm3, (%r8)
|
||||||
movd %xmm3, (%rdx)
|
movd %xmm3, (%r13)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
movq (%ebp), %mm1
|
movq (%ebp), %mm1
|
||||||
|
|
Loading…
Reference in New Issue