From 4cb7047f0fa000c83ca5592d9a64a2cbc96d8fdc Mon Sep 17 00:00:00 2001 From: Michael Matz Date: Thu, 4 Aug 2016 04:47:03 +0200 Subject: [PATCH] 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). --- i386-asm.c | 210 +++++++++++++++++++++++++++++++++++++++--------- i386-tok.h | 8 ++ tests/asmtest.S | 40 ++++++++- 3 files changed, 215 insertions(+), 43 deletions(-) diff --git a/i386-asm.c b/i386-asm.c index b686665..f41efbb 100644 --- a/i386-asm.c +++ b/i386-asm.c @@ -72,6 +72,10 @@ enum { OPT_DB, /* warning: value is hardcoded from TOK_ASM_xxx */ OPT_SEG, 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_IM8S, OPT_IM16, @@ -120,10 +124,12 @@ enum { #define OP_INDIR (1 << OPT_INDIR) #ifdef TCC_TARGET_X86_64 # define OP_REG64 (1 << OPT_REG64) +# define OP_REG8_LOW (1 << OPT_REG8_LOW) # define OP_IM64 (1 << OPT_IM64) # define OP_EA32 (OP_EA << 1) #else # define OP_REG64 0 +# define OP_REG8_LOW 0 # define OP_IM64 0 # define OP_EA32 0 #endif @@ -272,6 +278,39 @@ static inline int get_reg_shift(TCCState *s1) 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) { int reg = 0; @@ -281,12 +320,17 @@ static int asm_parse_reg(int *type) next(); if (tok >= TOK_ASM_eax && tok <= TOK_ASM_edi) { reg = tok - TOK_ASM_eax; + *type = OP_REG32; #ifdef TCC_TARGET_X86_64 - *type = OP_EA32; } else if (tok >= TOK_ASM_rax && tok <= TOK_ASM_rdi) { reg = tok - TOK_ASM_rax; + *type = OP_REG64; } 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 } else { error_32: @@ -345,6 +389,13 @@ static void parse_operand(TCCState *s1, Operand *op) if (op->reg == 0) op->type |= OP_ST0; 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 { reg_error: 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); } } - if (type & OP_EA32) + if (type & OP_REG32) op->type |= OP_EA32; skip(')'); } @@ -475,7 +526,7 @@ static inline int asm_modrm(int reg, Operand *op) #endif gen_expr32(&op->e); #ifdef TCC_TARGET_X86_64 - } else if (op->reg == 8) { + } else if (op->reg == -2) { ExprValue *pe = &op->e; g(0x05 + (reg << 3)); 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; } +#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 int already = 1; @@ -558,13 +672,16 @@ static void maybe_print_stats (void) ST_FUNC void asm_opcode(TCCState *s1, int opcode) { 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; Operand ops[MAX_OPERANDS], *pop; int op_type[3]; /* decoded op type */ int alltypes; /* OR of all operand types */ int autosize; int p66; +#ifdef TCC_TARGET_X86_64 + int rex64; +#endif maybe_print_stats(); /* force synthetic ';' after prefix instruction, so we can handle */ @@ -775,6 +892,7 @@ ST_FUNC void asm_opcode(TCCState *s1, int opcode) if (p66) g(0x66); #ifdef TCC_TARGET_X86_64 + rex64 = 0; if (s == 3 || (alltypes & OP_REG64)) { /* generate REX prefix */ 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_call && opcode != TOK_ASM_jmp)) && !default64) - g(0x48); + rex64 = 1; } #endif @@ -830,6 +948,50 @@ ST_FUNC void asm_opcode(TCCState *s1, int opcode) /* fpu arith case */ 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) { /* mov $im, %reg case */ if (v == 0xb0 && s >= 1) @@ -881,8 +1043,6 @@ ST_FUNC void asm_opcode(TCCState *s1, int opcode) g(op1); g(v); - /* search which operand will used for modrm */ - modrm_index = 0; if (OPCT_IS(pa->instr_type, OPC_SHIFT)) { reg = (opcode - pa->sym) / NBWLX; if (reg == 6) @@ -897,40 +1057,10 @@ ST_FUNC void asm_opcode(TCCState *s1, int opcode) pc = 0; 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++) { - v = op_type[i]; - if (i != modrm_index && - (v & (OP_REG | OP_MMX | OP_SSE | OP_CR | OP_TR | OP_DB | OP_SEG))) { - reg = ops[i].reg; - break; - } - } + if (modreg_index >= 0) + reg = ops[modreg_index].reg; pc = asm_modrm(reg, &ops[modrm_index]); } diff --git a/i386-tok.h b/i386-tok.h index cf65897..8c25af0 100644 --- a/i386-tok.h +++ b/i386-tok.h @@ -93,6 +93,14 @@ DEF_ASM(st) 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 */ DEF_BWLX(mov) diff --git a/tests/asmtest.S b/tests/asmtest.S index ef5f83e..80fb362 100644 --- a/tests/asmtest.S +++ b/tests/asmtest.S @@ -76,6 +76,22 @@ movq %dr6, %rax movl %fs, %ecx 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 movsbw 0x1000, %ax movswl 0x1000, %eax @@ -184,6 +200,24 @@ addl $0x123, (%ebp) addl $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 (%ebx), %eax @@ -796,10 +830,10 @@ nop movd %rdi, %xmm2 movd (%rbx), %mm3 movd (%rbx), %xmm3 - movd %mm1, %rsi + movd %mm1, %r12 movd %xmm2, %rdi - movd %mm3, (%rdx) - movd %xmm3, (%rdx) + movd %mm3, (%r8) + movd %xmm3, (%r13) #endif movq (%ebp), %mm1