diff --git a/arm64-asm.c b/arm64-asm.c index 4fca8c09..3b9a46de 100644 --- a/arm64-asm.c +++ b/arm64-asm.c @@ -86,6 +86,13 @@ enum { #define TREG_X30 30 #define TREG_SP 31 +#ifdef TCC_TARGET_PE +#define ARM64_FREG_BASE 19 +#else +#define ARM64_FREG_BASE 20 +#endif +#define ARM64_FREG_LAST (ARM64_FREG_BASE + 7) + typedef struct Operand { uint32_t type; int8_t reg; @@ -147,8 +154,8 @@ static void emit_instr32(uint32_t val) ind += 4; } -/* Parse ARM64 register from token */ -static int arm64_parse_regvar(int t) +/* Parse ARM64 register token for assembler syntax. */ +static int arm64_parse_asm_reg(int t) { /* X registers (64-bit) */ if (t >= TOK_ASM_x0 && t <= TOK_ASM_x30) @@ -249,6 +256,23 @@ static int parse_barrier_option_name(int t) return -1; } +static int arm64_parse_regvar(int t) +{ + if (t >= TOK_ASM_x0 && t <= TOK_ASM_x30) + return t - TOK_ASM_x0; + if (t >= TOK_ASM_v0 && t <= TOK_ASM_v7) + return ARM64_FREG_BASE + (t - TOK_ASM_v0); + if (t >= TOK_ASM_d0 && t <= TOK_ASM_d7) + return ARM64_FREG_BASE + (t - TOK_ASM_d0); + if (t >= TOK_ASM_s0 && t <= TOK_ASM_s7) + return ARM64_FREG_BASE + (t - TOK_ASM_s0); + if (t >= TOK_ASM_h0 && t <= TOK_ASM_h7) + return ARM64_FREG_BASE + (t - TOK_ASM_h0); + if (t >= TOK_ASM_b0 && t <= TOK_ASM_b7) + return ARM64_FREG_BASE + (t - TOK_ASM_b0); + return -1; +} + /* Parse a single operand */ static void parse_operand(TCCState *s1, Operand *op) { @@ -269,7 +293,7 @@ static void parse_operand(TCCState *s1, Operand *op) } /* Register */ - reg = arm64_parse_regvar(tok); + reg = arm64_parse_asm_reg(tok); if (reg >= 0) { op->type = OP_REG; op->reg = reg; @@ -332,7 +356,7 @@ static void parse_addr_operand(TCCState *s1, Operand *op) op->reg_tok = 0; skip('['); - reg = arm64_parse_regvar(tok); + reg = arm64_parse_asm_reg(tok); if (reg < 0 || reg >= 32) { tcc_error("invalid register in address operand"); return; @@ -573,6 +597,98 @@ static void gen_mov_reg(int rd, int rm, int is_64bit) emit_instr32(instr); } +static int arm64_asm_encode_bimm64(uint64_t x) +{ + int neg, rep, pos, len; + + neg = x & 1; + if (neg) + x = ~x; + if (!x) + return -1; + + if (x >> 2 == (x & ((((uint64_t)1) << (64 - 2)) - 1))) + rep = 2, x &= (((uint64_t)1) << 2) - 1; + else if (x >> 4 == (x & ((((uint64_t)1) << (64 - 4)) - 1))) + rep = 4, x &= (((uint64_t)1) << 4) - 1; + else if (x >> 8 == (x & ((((uint64_t)1) << (64 - 8)) - 1))) + rep = 8, x &= (((uint64_t)1) << 8) - 1; + else if (x >> 16 == (x & ((((uint64_t)1) << (64 - 16)) - 1))) + rep = 16, x &= (((uint64_t)1) << 16) - 1; + else if (x >> 32 == (x & ((((uint64_t)1) << (64 - 32)) - 1))) + rep = 32, x &= (((uint64_t)1) << 32) - 1; + else + rep = 64; + + pos = 0; + if (!(x & ((((uint64_t)1) << 32) - 1))) + x >>= 32, pos += 32; + if (!(x & ((((uint64_t)1) << 16) - 1))) + x >>= 16, pos += 16; + if (!(x & ((((uint64_t)1) << 8) - 1))) + x >>= 8, pos += 8; + if (!(x & ((((uint64_t)1) << 4) - 1))) + x >>= 4, pos += 4; + if (!(x & ((((uint64_t)1) << 2) - 1))) + x >>= 2, pos += 2; + if (!(x & ((((uint64_t)1) << 1) - 1))) + x >>= 1, pos += 1; + + len = 0; + if (!(~x & ((((uint64_t)1) << 32) - 1))) + x >>= 32, len += 32; + if (!(~x & ((((uint64_t)1) << 16) - 1))) + x >>= 16, len += 16; + if (!(~x & ((((uint64_t)1) << 8) - 1))) + x >>= 8, len += 8; + if (!(~x & ((((uint64_t)1) << 4) - 1))) + x >>= 4, len += 4; + if (!(~x & ((((uint64_t)1) << 2) - 1))) + x >>= 2, len += 2; + if (!(~x & ((((uint64_t)1) << 1) - 1))) + x >>= 1, len += 1; + + if (x) + return -1; + if (neg) { + pos = (pos + len) & (rep - 1); + len = rep - len; + } + return ((0x1000 & rep << 6) | (((rep - 1) ^ 31) << 1 & 63) | + ((rep - pos) & (rep - 1)) << 6 | (len - 1)); +} + +static void gen_logical_imm(uint32_t opcode, int rd, int rn, + uint64_t imm, int is_64bit) +{ + uint32_t instr; + uint64_t val; + int enc; + + if (is_64bit) + val = imm; + else { + val = (uint32_t)imm; + val |= val << 32; + } + + enc = arm64_asm_encode_bimm64(val); + if (enc < 0) { + tcc_error("logical immediate out of range"); + return; + } + + instr = opcode; + if (is_64bit) + instr |= ARM64_SF(1); + instr |= ARM64_N(enc >> 12); + instr |= ARM64_IMM_R(enc >> 6); + instr |= ARM64_IMM_S(enc); + instr |= ARM64_RN(rn); + instr |= ARM64_RD(rd); + emit_instr32(instr); +} + /* return the constraint priority (we allocate first the lowest numbered constraints) */ static inline int constraint_priority(const char *str) @@ -593,22 +709,35 @@ static inline int constraint_priority(const char *str) pr = 1; break; case 'w': - pr = 2; - break; case 'f': case 'x': + case 'y': pr = 3; break; case 'm': + case 'Q': pr = 4; break; case 'i': + case 'S': pr = 5; break; + case 'U': + if (str[0] == 'm' && str[1] == 'p') { + str += 2; + pr = 4; + break; + } + tcc_warning("unknown constraint '%c'", c); + pr = 0; + break; case 'I': case 'J': case 'K': case 'L': + case 'M': + case 'N': + case 'Z': pr = 6; break; case 'n': @@ -642,28 +771,14 @@ static int is_valid_add_imm(int64_t val) static int is_valid_logical_imm(int64_t val, int bits) { - uint64_t uval = val; - int i, shift; + uint64_t uval; - if (uval == 0) - return 1; - - for (shift = 0; shift < bits; shift += 2) { - uint64_t mask = ((uint64_t)1 << (bits - shift)) - 1; - if ((uval & mask) == uval) - return 1; + uval = (uint64_t)val; + if (bits == 32) { + uval = (uint32_t)uval; + uval |= uval << 32; } - - for (i = 0; i < 6; i++) { - uint64_t pattern = uval & 0x3F; - if (pattern == 0 || pattern == 0x3F) { - uint64_t shifted = uval >> (i * 2); - if ((shifted & (((uint64_t)1 << (bits - i * 2)) - 1)) == 0) - return 1; - } - } - - return 0; + return arm64_asm_encode_bimm64(uval) >= 0; } static int is_valid_movw_imm(int64_t val) @@ -682,6 +797,53 @@ static int is_valid_movw_imm(int64_t val) return 0; } +static int arm64_memory_is_base_only(const SValue *sv) +{ + int r; + + r = sv->r; + if ((r & VT_VALMASK) == VT_CONST) + return 0; + if ((r & VT_VALMASK) == VT_LOCAL) + return 0; + if ((r & VT_VALMASK) == VT_LLOCAL) + return 1; + if (r & VT_LVAL) + return (r & VT_VALMASK) < VT_CONST; + return 0; +} + +static int arm64_memory_is_pair_suitable(const SValue *sv) +{ + int64_t offset; + + if (arm64_memory_is_base_only(sv)) + return 1; + if ((sv->r & VT_VALMASK) != VT_LOCAL) + return 0; + + offset = (int64_t)sv->c.i; + return (offset & 7) == 0 && offset >= -512 && offset <= 504; +} + +static int arm64_prepare_memory_operand(ASMOperand *op, uint8_t *regs_allocated) +{ + int reg; + + if ((op->vt->r & VT_VALMASK) != VT_LLOCAL) + return 1; + + for (reg = 0; reg < 31; reg++) { + if (!(regs_allocated[reg] & REG_IN_MASK)) { + regs_allocated[reg] |= REG_IN_MASK; + op->reg = reg; + op->is_memory = 1; + return 1; + } + } + return 0; +} + static int operand_is_sp(const Operand *op) { return op->reg_tok == TOK_ASM_sp; @@ -752,23 +914,23 @@ static void gen_shift(int rd, int rn, int rm_or_imm, int shift_type, int is_imm, if (is_imm) { /* Shift by immediate */ switch (shift_type) { - case 0: /* LSL - UBFM alias: immr = (width - shift) & 0x3F, imms = width - 1 */ + case 0: /* LSL - UBFM alias */ if (rm_or_imm < 0 || rm_or_imm >= width) { tcc_error("shift immediate out of range"); return; } - instr = is_64bit ? ARM64_LSL_IMM : (ARM64_LSL_IMM & ~(1U << 31)); - instr |= ((width - rm_or_imm) & 0x3F) << 16; /* immr */ - instr |= (width - 1) << 10; /* imms */ + instr = is_64bit ? ARM64_LSL_IMM : ARM64_LSR_IMM_32; + instr |= ARM64_IMM_R((width - rm_or_imm) & (width - 1)); + instr |= ARM64_IMM_S(width - rm_or_imm - 1); break; case 1: /* LSR - UBFM alias: immr = shift, imms = width - 1 */ if (rm_or_imm < 0 || rm_or_imm >= width) { tcc_error("shift immediate out of range"); return; } - instr = is_64bit ? ARM64_LSL_IMM : (ARM64_LSL_IMM & ~(1U << 31)); - instr |= (rm_or_imm & 0x3F) << 16; /* immr */ - instr |= (width - 1) << 10; /* imms */ + instr = is_64bit ? ARM64_LSL_IMM : ARM64_LSR_IMM_32; + instr |= ARM64_IMM_R(rm_or_imm); + instr |= ARM64_IMM_S(width - 1); break; case 2: /* ASR - SBFM alias: immr = shift, imms = width - 1 */ if (rm_or_imm < 0 || rm_or_imm >= width) { @@ -776,18 +938,19 @@ static void gen_shift(int rd, int rn, int rm_or_imm, int shift_type, int is_imm, return; } instr = is_64bit ? ARM64_ASR_IMM : (ARM64_ASR_IMM & ~(1U << 31)); - instr |= (rm_or_imm & 0x3F) << 16; /* immr */ - instr |= (width - 1) << 10; /* imms */ + instr |= ARM64_IMM_R(rm_or_imm); + instr |= ARM64_IMM_S(width - 1); break; - case 3: /* ROR - EXTR alias: Rm = shift, Rn = source, Rd = dest */ + case 3: /* ROR - EXTR alias: Rm = source, Rn = source, imms = shift */ if (rm_or_imm < 0 || rm_or_imm >= width) { tcc_error("shift immediate out of range"); return; } instr = is_64bit ? ARM64_EXTR64 : ARM64_EXTR; - instr |= ARM64_RM(rm_or_imm); /* Rm = shift amount */ - instr |= ARM64_RN(rn); /* Rn = source */ - instr |= ARM64_RD(rd); /* Rd = dest */ + instr |= ARM64_RM(rn); + instr |= ARM64_IMM_S(rm_or_imm); + instr |= ARM64_RN(rn); + instr |= ARM64_RD(rd); emit_instr32(instr); return; default: @@ -868,13 +1031,24 @@ static void asm_shift(TCCState *s1, int token) if (tok == ',') { next(); - /* Parse shift immediate - skip # prefix like arm-asm.c does */ - if (tok == '#' || tok == '$') - next(); parse_operand(s1, &op3); - shift_amount = op3.e.v; is_64bit = (op1.reg_type & REG_X); - gen_shift(rd, rn, shift_amount, shift_type, 1, is_64bit); + if (is_64bit != !!(op2.reg_type & REG_X)) { + tcc_error("mismatched register widths"); + return; + } + if (op3.type & OP_REG) { + if (is_64bit != !!(op3.reg_type & REG_X)) { + tcc_error("mismatched register widths"); + return; + } + gen_shift(rd, rn, op3.reg, shift_type, 0, is_64bit); + } else if (op3.type & OP_IM) { + shift_amount = op3.e.v; + gen_shift(rd, rn, shift_amount, shift_type, 1, is_64bit); + } else { + tcc_error("shift requires immediate or register operand"); + } } else { tcc_error("shift requires immediate or register operand"); return; @@ -1059,14 +1233,26 @@ static void asm_data_proc(TCCState *s1, int token) if (tok == ',') { next(); parse_operand(s1, &op3); + is_64bit = (op1.reg_type & REG_X); + if (is_64bit != !!(op2.reg_type & REG_X)) { + tcc_error("mismatched register widths"); + return; + } if (op3.type & OP_IM) { - is_64bit = (op1.reg_type & REG_X); if (token == TOK_ASM_add || token == TOK_ASM_adds) gen_add_imm(rd, rn, op3.e.v, is_64bit, token == TOK_ASM_adds); else if (token == TOK_ASM_sub || token == TOK_ASM_subs) gen_sub_imm(rd, rn, op3.e.v, is_64bit, token == TOK_ASM_subs); + else if (token == TOK_ASM_and) + gen_logical_imm(ARM64_AND_IMM, rd, rn, op3.e.v, is_64bit); + else if (token == TOK_ASM_ands) + gen_logical_imm(ARM64_ANDS_IMM, rd, rn, op3.e.v, is_64bit); + else if (token == TOK_ASM_orr) + gen_logical_imm(ARM64_ORR_IMM_BASE, rd, rn, op3.e.v, is_64bit); + else if (token == TOK_ASM_eor) + gen_logical_imm(ARM64_EOR_IMM, rd, rn, op3.e.v, is_64bit); else tcc_error("immediate operand not valid for this instruction"); } else { @@ -1075,7 +1261,6 @@ static void asm_data_proc(TCCState *s1, int token) return; } rm = op3.reg; - is_64bit = (op1.reg_type & REG_X); if (is_64bit != !!(op2.reg_type & REG_X) || is_64bit != !!(op3.reg_type & REG_X)) tcc_error("mismatched register widths"); gen_dp_reg(opcode, rd, rn, rm, is_64bit); @@ -1539,10 +1724,17 @@ ST_FUNC void asm_opcode(TCCState *s1, int opcode) /* Substitute assembler operand */ ST_FUNC void subst_asm_operand(CString *add_str, SValue *sv, int modifier) { - int r, reg, size, val; + int r, reg, size, fp_reg, align; + int64_t val; + uint64_t uval; r = sv->r; if ((r & VT_VALMASK) == VT_CONST) { + if ((modifier == 'w' || modifier == 'x') && !(r & VT_LVAL) + && !(r & VT_SYM) && sv->c.i == 0) { + cstr_cat(add_str, modifier == 'w' ? "wzr" : "xzr", -1); + return; + } if (!(r & VT_LVAL) && modifier != 'c' && modifier != 'n' && modifier != 'P') cstr_ccat(add_str, '#'); @@ -1557,10 +1749,16 @@ ST_FUNC void subst_asm_operand(CString *add_str, SValue *sv, int modifier) goto no_offset; cstr_ccat(add_str, '+'); } - val = sv->c.i; - if (modifier == 'n') - val = -val; - cstr_printf(add_str, "%d", (int) sv->c.i); + uval = sv->c.i; + if (modifier == 'n') { + val = -(int64_t)uval; + cstr_printf(add_str, "%lld", (long long)val); + } else if (uval > 0x7fffffffffffffffULL) { + cstr_printf(add_str, "0x%llx", (unsigned long long)uval); + } else { + val = (int64_t)uval; + cstr_printf(add_str, "%lld", (long long)val); + } no_offset:; } else if ((r & VT_VALMASK) == VT_LOCAL) { cstr_printf(add_str, "[x29,#%d]", (int) sv->c.i); @@ -1575,6 +1773,31 @@ ST_FUNC void subst_asm_operand(CString *add_str, SValue *sv, int modifier) if (reg >= VT_CONST) tcc_internal_error(""); + if (ARM64_FREG_BASE <= reg && reg <= ARM64_FREG_LAST) { + fp_reg = reg - ARM64_FREG_BASE; + size = type_size(&sv->type, &align); + + if (modifier == 0) + modifier = size <= 4 ? 's' + : size == 8 ? 'd' + : 'q'; + + switch (modifier) { + case 'b': + case 'h': + case 's': + case 'd': + case 'q': + case 'Z': + cstr_printf(add_str, "%c%d", modifier == 'Z' ? 'z' : modifier, + fp_reg); + return; + default: + tcc_error("invalid operand modifier for SIMD/FP register"); + return; + } + } + /* choose register operand size */ if ((sv->type.t & VT_BTYPE) == VT_BYTE || (sv->type.t & VT_BTYPE) == VT_BOOL) @@ -1584,12 +1807,14 @@ ST_FUNC void subst_asm_operand(CString *add_str, SValue *sv, int modifier) else size = 8; - if (modifier == 'b') { - size = 1; - } else if (modifier == 'w') { - size = 2; - } else if (modifier == 'k') { + if (modifier == 'x') { + size = 8; + } else if (modifier == 'w' || modifier == 'k') { size = 4; + } else if (modifier == 'b') { + size = 1; + } else if (modifier == 'h') { + size = 2; } else if (modifier == 'q') { size = 8; } @@ -1609,7 +1834,8 @@ ST_FUNC void asm_gen_code(ASMOperand *operands, int nb_operands, { uint8_t regs_allocated[NB_ASM_REGS]; ASMOperand *op; - int i, reg; + int i, reg, saved_count, stack_size, stack_off; + int saved_regs[12]; static const uint8_t reg_saved[] = { 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 @@ -1622,44 +1848,28 @@ ST_FUNC void asm_gen_code(ASMOperand *operands, int nb_operands, regs_allocated[op->reg] = 1; } + saved_count = 0; + for (i = 0; i < sizeof(reg_saved) / sizeof(reg_saved[0]); i++) { + reg = reg_saved[i]; + if (regs_allocated[reg]) + saved_regs[saved_count++] = reg; + } + stack_size = ((saved_count + 1) / 2) * 16; + if (!is_output) { - int saved_count = 0; - int first_saved = -1; - - for (i = 0; i < sizeof(reg_saved)/sizeof(reg_saved[0]); i++) { - reg = reg_saved[i]; - if (regs_allocated[reg]) { - if (first_saved < 0) - first_saved = i; - saved_count++; - } - } - if (saved_count > 0) { - int stack_size = ((saved_count + 1) / 2) * 16; gen_sub_imm(31, 31, stack_size, 1, 0); - for (i = first_saved; i < sizeof(reg_saved)/sizeof(reg_saved[0]); i += 2) { - int reg1 = reg_saved[i]; - int reg2 = (i + 1 < sizeof(reg_saved)/sizeof(reg_saved[0])) ? reg_saved[i + 1] : -1; - - if (regs_allocated[reg1]) { - if (reg2 >= 0 && regs_allocated[reg2]) { - uint32_t instr = ARM64_STP_X; - int offset = ((i - first_saved) / 2) * 8; - instr |= ARM64_IMM7(offset >> 3); - instr |= ARM64_RT2(reg2); - instr |= ARM64_RN(reg1); - instr |= ARM64_RD(31); - emit_instr32(instr); - } else { - uint32_t instr = ARM64_STR_X; - int offset = (i - first_saved) * 8; - instr |= ARM64_IMM12(offset >> 3); - instr |= ARM64_RN(reg1); - instr |= ARM64_RT(31); - emit_instr32(instr); - } + for (i = stack_off = 0; i < saved_count; ) { + if (i + 1 < saved_count) { + gen_ldst_pair(ARM64_STP_X, saved_regs[i], saved_regs[i + 1], + TREG_SP, stack_off, 3); + stack_off += 16; + i += 2; + } else { + gen_ldst_imm(ARM64_STR_X, saved_regs[i], TREG_SP, + stack_off, 3); + i++; } } } @@ -1700,38 +1910,20 @@ ST_FUNC void asm_gen_code(ASMOperand *operands, int nb_operands, } } - for (i = sizeof(reg_saved)/sizeof(reg_saved[0]) - 1; i >= 0; i--) { - int reg1 = reg_saved[i]; - int reg2 = (i > 0) ? reg_saved[i - 1] : -1; - - if (regs_allocated[reg1]) { - if (reg2 >= 0 && regs_allocated[reg2] && i > 0) { - uint32_t instr = ARM64_LDP_X; - int pair_idx = i - 1; - int offset = (pair_idx / 2) * 8; - instr |= ARM64_IMM7(offset >> 3); - instr |= ARM64_RT2(reg2); - instr |= ARM64_RN(reg1); - instr |= ARM64_RD(31); - emit_instr32(instr); - i--; + if (saved_count > 0) { + for (i = stack_off = 0; i < saved_count; ) { + if (i + 1 < saved_count) { + gen_ldst_pair(ARM64_LDP_X, saved_regs[i], saved_regs[i + 1], + TREG_SP, stack_off, 3); + stack_off += 16; + i += 2; } else { - uint32_t instr = ARM64_LDR_X; - int offset = i * 8; - instr |= ARM64_IMM12(offset >> 3); - instr |= ARM64_RN(reg1); - instr |= ARM64_RT(31); - emit_instr32(instr); + gen_ldst_imm(ARM64_LDR_X, saved_regs[i], TREG_SP, + stack_off, 3); + i++; } } - } - - for (i = 0; i < sizeof(reg_saved)/sizeof(reg_saved[0]); i++) { - reg = reg_saved[i]; - if (regs_allocated[reg]) { - gen_add_imm(31, 31, 16, 1, 0); - break; - } + gen_add_imm(31, 31, stack_size, 1, 0); } } } @@ -1839,14 +2031,15 @@ ST_FUNC void asm_compute_constraints(ASMOperand *operands, } goto try_next; case 'w': - for (reg = 0; reg < 31; reg++) { + case 'f': + for (reg = ARM64_FREG_BASE; reg <= ARM64_FREG_LAST; reg++) { if (!is_reg_allocated(reg)) goto reg_found; } goto try_next; - case 'f': case 'x': - for (reg = 32; reg < 64; reg++) { + case 'y': + for (reg = ARM64_FREG_BASE; reg <= ARM64_FREG_LAST; reg++) { if (!is_reg_allocated(reg)) goto reg_found; } @@ -1854,19 +2047,29 @@ ST_FUNC void asm_compute_constraints(ASMOperand *operands, case 'm': case 'g': if (j < nb_outputs || c == 'm') { - if ((op->vt->r & VT_VALMASK) == VT_LLOCAL) { - for (reg = 0; reg < 31; reg++) { - if (!(regs_allocated[reg] & REG_IN_MASK)) - goto reg_found1; - } + if (!arm64_prepare_memory_operand(op, regs_allocated)) goto try_next; - reg_found1: - regs_allocated[reg] |= REG_IN_MASK; - op->reg = reg; - op->is_memory = 1; - } } break; + case 'Q': + if (!arm64_memory_is_base_only(op->vt)) + goto try_next; + if (!arm64_prepare_memory_operand(op, regs_allocated)) + goto try_next; + break; + case 'S': + if ((op->vt->r & (VT_VALMASK | VT_LVAL | VT_SYM)) != (VT_CONST | VT_SYM)) + goto try_next; + break; + case 'U': + if (str[0] != 'm' || str[1] != 'p') + goto try_next; + str += 2; + if (!arm64_memory_is_pair_suitable(op->vt)) + goto try_next; + if (!arm64_prepare_memory_operand(op, regs_allocated)) + goto try_next; + break; case 'i': if (!((op->vt->r & (VT_VALMASK | VT_LVAL)) == VT_CONST)) goto try_next; @@ -1878,6 +2081,11 @@ ST_FUNC void asm_compute_constraints(ASMOperand *operands, goto try_next; break; case 'J': + if (!((op->vt->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST)) + goto try_next; + if (!is_valid_add_imm(-op->vt->c.i)) + goto try_next; + break; case 'K': if (!((op->vt->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST)) goto try_next; @@ -1885,11 +2093,24 @@ ST_FUNC void asm_compute_constraints(ASMOperand *operands, goto try_next; break; case 'L': + if (!((op->vt->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST)) + goto try_next; + if (!is_valid_logical_imm(op->vt->c.i, 64)) + goto try_next; + break; + case 'M': + case 'N': if (!((op->vt->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST)) goto try_next; if (!is_valid_movw_imm(op->vt->c.i)) goto try_next; break; + case 'Z': + if (!((op->vt->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST)) + goto try_next; + if (op->vt->c.i != 0) + goto try_next; + break; case 'n': if (!((op->vt->r & (VT_VALMASK | VT_LVAL)) == VT_CONST)) goto try_next; @@ -1920,7 +2141,7 @@ ST_FUNC void asm_compute_constraints(ASMOperand *operands, if (op->reg >= 0 && (op->vt->r & VT_VALMASK) == VT_LLOCAL && !op->is_memory) { - for (reg = 0; reg < NB_ASM_REGS; reg++) { + for (reg = 0; reg < 31; reg++) { if (!(regs_allocated[reg] & REG_OUT_MASK)) goto reg_found2; } diff --git a/arm64-tok.h b/arm64-tok.h index 210f6b26..4e299614 100644 --- a/arm64-tok.h +++ b/arm64-tok.h @@ -573,7 +573,7 @@ #define ARM64_SUB_REG 0x4B000000U #define ARM64_SUBS_REG 0x6B000000U #define ARM64_AND_REG 0x0A000000U -#define ARM64_ANDS_REG 0x2A000000U +#define ARM64_ANDS_REG 0x6A000000U #define ARM64_ORR_REG 0x2A000000U #define ARM64_EOR_REG 0x4A000000U #define ARM64_MUL_REG 0x1B000000U /* Base opcode, Rm/Rn/Rd must be filled in */ @@ -730,8 +730,11 @@ #define ARM64_ADR 0x10000000U /* Logical immediate */ -#define ARM64_ORR_IMM 0x320003E0U #define ARM64_AND_IMM 0x12000000U +#define ARM64_ORR_IMM_BASE 0x32000000U +#define ARM64_EOR_IMM 0x52000000U +#define ARM64_ANDS_IMM 0x72000000U +#define ARM64_ORR_IMM 0x320003E0U /* ORR immediate alias with Rn = XZR/WZR */ /* ------------------------------------------------------------------ */ /* ARM64 instruction encoding helper macros */ @@ -755,6 +758,7 @@ #define ARM64_SIZE(s) (((uint32_t)(s) & 3) << 30) #define ARM64_SF(s) (((uint32_t)(s) & 1) << 31) #define ARM64_S(v) (((uint32_t)(v) & 1) << 29) +#define ARM64_N(v) (((uint32_t)(v) & 1) << 22) #define ARM64_SH(v) (((uint32_t)(v) & 1) << 22) /* Condition code encoding */ diff --git a/tccasm.c b/tccasm.c index ea44e5b3..8202f7f2 100644 --- a/tccasm.c +++ b/tccasm.c @@ -1178,7 +1178,10 @@ static void subst_asm_operands(ASMOperand *operands, int nb_operands, modifier = 0; if (*str == 'c' || *str == 'n' || *str == 'b' || *str == 'w' || *str == 'h' || *str == 'k' || - *str == 'q' || *str == 'l' || + *str == 'q' || *str == 'l' || +#ifdef TCC_TARGET_ARM64 + *str == 'x' || *str == 's' || *str == 'd' || *str == 'Z' || +#endif #ifdef TCC_TARGET_RISCV64 *str == 'z' || #endif @@ -1251,7 +1254,12 @@ static void parse_asm_operands(ASMOperand *operands, int *nb_operands_ptr, if ((vtop->r & VT_LVAL) && ((vtop->r & VT_VALMASK) == VT_LLOCAL || (vtop->r & VT_VALMASK) < VT_CONST) && - !strchr(op->constraint, 'm')) { + !strchr(op->constraint, 'm') +#ifdef TCC_TARGET_ARM64 + && !strchr(op->constraint, 'Q') + && !strstr(op->constraint, "Ump") +#endif + ) { gv(RC_INT); } } diff --git a/tests/Makefile b/tests/Makefile index c466295a..97d4e40e 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -52,6 +52,9 @@ ifeq ($(ARCH),arm) # of functions via bit masking comes out as 1. Just disable thumb. test.ref: CFLAGS+=-marm endif +ifeq ($(ARCH),arm64) + TESTS += arm64-ext-asm +endif ifeq ($(ARCH)$(CONFIG_WIN32),i386) # tcctest.c:get_asm_string uses a construct that is checked too strictly # by GCC in 32bit mode when PIC is enabled. @@ -261,6 +264,10 @@ endif # test assembler with tcc compiled by itself asmtest2: MAYBE_RUN_TCC = $(RUN_TCC) +arm64-ext-asm: $(TOPSRC)/tests/asm/test-asm-arm64-ext.c + @echo ------------ $@ ------------ + $(TCC) -run $(TOPSRC)/tests/asm/test-asm-arm64-ext.c + # Check that code generated by libtcc is binary compatible with # that generated by CC abitest-cc.exe: abitest.c diff --git a/tests/asm/test-asm-arm64-ext-fixed.c b/tests/asm/test-asm-arm64-ext-fixed.c deleted file mode 100644 index 6924f1a1..00000000 --- a/tests/asm/test-asm-arm64-ext-fixed.c +++ /dev/null @@ -1,236 +0,0 @@ -/* - * ARM64 Extended Inline Assembly Tests - * Tests for GCC-style extended inline assembly with operands, constraints, and clobbers - */ - -#include -#include -#include - -/* Test 1: Basic output operand */ -void test_basic_output(void) -{ - int x; - asm("mov %0, #42" : "=r"(x)); - assert(x == 42); - printf("Test 1 (basic output): PASSED\n"); -} - -/* Test 2: Input operand */ -void test_input_operand(void) -{ - int x = 10; - int y; - asm("add %0, %1, #5" : "=r"(y) : "r"(x)); - assert(y == 15); - printf("Test 2 (input operand): PASSED\n"); -} - -/* Test 3: Read-write operand */ -void test_read_write_operand(void) -{ - int x = 10; - asm("add %0, %0, #1" : "+r"(x)); - assert(x == 11); - printf("Test 3 (read-write operand): PASSED\n"); -} - -/* Test 4: Memory operand - load */ -void test_memory_load(void) -{ - int x = 42; - int y; - asm("ldr %0, [%1]" : "=r"(y) : "r"(&x)); - assert(y == 42); - printf("Test 4 (memory load): PASSED\n"); -} - -/* Test 5: Memory operand - store */ -void test_memory_store(void) -{ - int x; - int val = 123; - asm("str %1, [%0]" : : "r"(&x), "r"(val)); - assert(x == 123); - printf("Test 5 (memory store): PASSED\n"); -} - -/* Test 6: Clobber list */ -void test_clobber_list(void) -{ - int x = 10; - int y = 20; - int result; - asm("add %0, %1, %2" - : "=r"(result) - : "r"(x), "r"(y) - : "cc"); - assert(result == 30); - printf("Test 6 (clobber list): PASSED\n"); -} - -/* Test 7: Multiple outputs */ -void test_multiple_outputs(void) -{ - int a, b; - asm("mov %0, #1; mov %1, #2" - : "=r"(a), "=r"(b)); - assert(a == 1 && b == 2); - printf("Test 7 (multiple outputs): PASSED\n"); -} - -/* Test 8: Constraint reference */ -void test_constraint_reference(void) -{ - int x = 10; - int y; - asm("add %0, %1, #5" : "=r"(y) : "0"(x)); - assert(y == 15); - printf("Test 8 (constraint reference): PASSED\n"); -} - -/* Test 9: Early clobber */ -void test_early_clobber(void) -{ - int x = 10; - int y; - asm("mov %0, #42" : "=&r"(y) : "r"(x)); - assert(y == 42); - printf("Test 9 (early clobber): PASSED\n"); -} - -/* Test 10: 32-bit register (w register) */ -void test_w_register(void) -{ - uint32_t x = 100; - uint32_t y; - asm("add %w0, %w1, #50" : "=w"(y) : "w"(x)); - assert(y == 150); - printf("Test 10 (w register): PASSED\n"); -} - -/* Test 11: Immediate constraint 'I' (12-bit immediate) */ -void test_immediate_i_constraint(void) -{ - int x = 100; - int y; - asm("add %0, %1, #200" : "=r"(y) : "r"(x)); - assert(y == 300); - printf("Test 11 (immediate I constraint): PASSED\n"); -} - -/* Test 12: Register constraint */ -void test_general_operand_constraint(void) -{ - int x = 50; - int y; - asm("add %0, %1, #10" : "=r"(y) : "r"(x)); - assert(y == 60); - printf("Test 12 (general operand constraint): PASSED\n"); -} - -/* Test 13: Multiple inputs and outputs */ -void test_multiple_io(void) -{ - int a = 10, b = 20; - int sum, diff; - asm("add %0, %1, %2" : "=r"(sum) : "r"(a), "r"(b)); - asm("sub %0, %1, %2" : "=r"(diff) : "r"(a), "r"(b)); - assert(sum == 30 && diff == -10); - printf("Test 13 (multiple IO): PASSED\n"); -} - -/* Test 14: Register variable preservation - SKIPPED for now */ -void test_regvar_preservation(void) -{ - printf("Test 14 (regvar preservation): SKIPPED\n"); -} - -/* Test 15: Complex arithmetic */ -void test_complex_arithmetic(void) -{ - int a = 100, b = 50, c = 25; - int result; - asm("add %0, %1, %2; sub %0, %0, %3" - : "=&r"(result) - : "r"(a), "r"(b), "r"(c)); - assert(result == 125); - printf("Test 15 (complex arithmetic): PASSED\n"); -} - -/* Test 16: Named operand (GCC extension) */ -void test_named_operand(void) -{ - int input = 10; - int output; - asm("add %[out], %[in], #5" - : [out] "=r"(output) - : [in] "r"(input)); - assert(output == 15); - printf("Test 16 (named operand): PASSED\n"); -} - -/* Test 17: Memory clobber */ -void test_memory_clobber(void) -{ - int x = 10; - asm volatile("" : : : "memory"); - assert(x == 10); - printf("Test 17 (memory clobber): PASSED\n"); -} - -/* Test 18: Condition flags clobber */ -void test_cc_clobber(void) -{ - int x = 100; - int y; - asm("adds %0, %1, #0" : "=r"(y) : "r"(x) : "cc"); - assert(y == 100); - printf("Test 18 (cc clobber): PASSED\n"); -} - -/* Test 19: Large immediate with movz/movk */ -void test_large_immediate(void) -{ - uint64_t val; - asm("movz %0, #0x1234, lsl #16; movk %0, #0x5678" : "=r"(val)); - assert(val == 0x12345678ULL); - printf("Test 19 (large immediate): PASSED\n"); -} - -/* Test 20: Bitwise operations - SKIPPED (and imm not implemented) */ -void test_bitwise_ops(void) -{ - printf("Test 20 (bitwise ops): SKIPPED\n"); -} - -int main(void) -{ - printf("ARM64 Extended Inline Assembly Tests\n"); - printf("=====================================\n\n"); - - test_basic_output(); - test_input_operand(); - test_read_write_operand(); - test_memory_load(); - test_memory_store(); - test_clobber_list(); - test_multiple_outputs(); - test_constraint_reference(); - test_early_clobber(); - test_w_register(); - test_immediate_i_constraint(); - test_general_operand_constraint(); - test_multiple_io(); - test_regvar_preservation(); - test_complex_arithmetic(); - test_named_operand(); - test_memory_clobber(); - test_cc_clobber(); - test_large_immediate(); - test_bitwise_ops(); - - printf("\n=====================================\n"); - printf("All tests completed!\n"); - return 0; -} diff --git a/tests/asm/test-asm-arm64-ext.c b/tests/asm/test-asm-arm64-ext.c index b41f1006..87533fcb 100644 --- a/tests/asm/test-asm-arm64-ext.c +++ b/tests/asm/test-asm-arm64-ext.c @@ -7,6 +7,21 @@ #include #include +struct pair64 { + uint64_t a; + uint64_t b; +}; + +static int arm64_symbol_target(void) +{ + return 7; +} + +static void test_symbolic_address_constraint_compile_only(void) +{ + asm volatile("" : : "S"(arm64_symbol_target)); +} + /* Test 1: Basic output operand */ void test_basic_output(void) { @@ -40,7 +55,7 @@ void test_memory_load(void) { int x = 42; int y; - asm("ldr %0, [%1]" : "=r"(y) : "r"(&x)); + asm("ldr %w0, [%1]" : "=r"(y) : "r"(&x)); assert(y == 42); printf("Test 4 (memory load): PASSED\n"); } @@ -50,7 +65,7 @@ void test_memory_store(void) { int x; int val = 123; - asm("str %1, [%0]" : : "r"(&x), "r"(val)); + asm("str %w1, [%0]" : : "r"(&x), "r"(val)); assert(x == 123); printf("Test 5 (memory store): PASSED\n"); } @@ -99,14 +114,14 @@ void test_early_clobber(void) printf("Test 9 (early clobber): PASSED\n"); } -/* Test 10: 32-bit register (w register) */ +/* Test 10: 32-bit register modifier */ void test_w_register(void) { uint32_t x = 100; uint32_t y; - asm("add %w0, %w1, #50" : "=w"(y) : "w"(x)); + asm("add %w0, %w1, #50" : "=r"(y) : "r"(x)); assert(y == 150); - printf("Test 10 (w register): PASSED\n"); + printf("Test 10 (w register modifier): PASSED\n"); } /* Test 11: Immediate constraint 'I' (12-bit immediate) */ @@ -204,6 +219,145 @@ void test_bitwise_ops(void) printf("Test 20 (bitwise ops): SKIPPED\n"); } +/* Test 21: Register shift operands */ +void test_register_shift_operands(void) +{ + uint64_t val = 3; + uint64_t amount = 4; + uint64_t shifted; + uint64_t rotated; + + asm("lsl %0, %1, %2" : "=r"(shifted) : "r"(val), "r"(amount)); + asm("ror %0, %1, %2" : "=r"(rotated) + : "r"(0x0123456789abcdefULL), "r"(8ULL)); + assert(shifted == 48); + assert(rotated == 0xef0123456789abcdULL); + printf("Test 21 (register shifts): PASSED\n"); +} + +/* Test 22: ROR immediate alias of EXTR */ +void test_ror_immediate(void) +{ + uint64_t rotated; + + asm("ror %0, %1, #8" : "=r"(rotated) : "r"(0x0123456789abcdefULL)); + assert(rotated == 0xef0123456789abcdULL); + printf("Test 22 (ror immediate): PASSED\n"); +} + +/* Test 23: FP/SIMD register constraint and modifier */ +void test_fp_register_operand(void) +{ + double x = 3.5; + double y; + + asm("ldr %d0, [%1]" : "=w"(y) : "r"(&x)); + assert(y == x); + printf("Test 23 (fp register operand): PASSED\n"); +} + +/* Test 24: Zero constraint */ +void test_zero_constraint(void) +{ + uint64_t x = 41; + uint64_t y; + + asm("add %0, %1, %x2" : "=r"(y) : "r"(x), "Z"(0)); + assert(y == x); + printf("Test 24 (Z constraint): PASSED\n"); +} + +/* Test 25: 32-bit logical immediate constraint */ +void test_logical_imm32_constraint(void) +{ + uint32_t y; + + asm("orr %w0, wzr, %1" : "=r"(y) : "K"(0xff)); + assert(y == 0xff); + printf("Test 25 (K constraint): PASSED\n"); +} + +/* Test 26: 64-bit logical immediate constraint */ +void test_logical_imm64_constraint(void) +{ + uint64_t y; + + asm("orr %0, xzr, %1" : "=r"(y) : "L"(0xff00ff00ff00ff00ULL)); + assert(y == 0xff00ff00ff00ff00ULL); + printf("Test 26 (L constraint): PASSED\n"); +} + +/* Test 27: 32-bit MOV pseudo immediate constraint */ +void test_mov_imm32_constraint(void) +{ + uint32_t y; + + asm("mov %w0, %1" : "=r"(y) : "M"(0x1234)); + assert(y == 0x1234); + printf("Test 27 (M constraint): PASSED\n"); +} + +/* Test 28: 64-bit MOV pseudo immediate constraint */ +void test_mov_imm64_constraint(void) +{ + uint64_t y; + + asm("mov %0, %1" : "=r"(y) : "N"(0x12340000ULL)); + assert(y == 0x12340000ULL); + printf("Test 28 (N constraint): PASSED\n"); +} + +/* Test 29: x FP/SIMD register constraint */ +void test_x_constraint_fp(void) +{ + double x = 6.25; + double y; + + asm("ldr %d0, [%1]" : "=x"(y) : "r"(&x)); + assert(y == x); + printf("Test 29 (x constraint): PASSED\n"); +} + +/* Test 30: y FP/SIMD register constraint */ +void test_y_constraint_fp(void) +{ + double x = 7.25; + double y; + + asm("ldr %d0, [%1]" : "=y"(y) : "r"(&x)); + assert(y == x); + printf("Test 30 (y constraint): PASSED\n"); +} + +/* Test 31: Q memory constraint */ +static int arm64_q_load(int *ptr) +{ + int y; + + asm("ldr %w0, %1" : "=r"(y) : "Q"(*ptr)); + return y; +} + +void test_q_memory_constraint(void) +{ + int x = 91; + assert(arm64_q_load(&x) == 91); + printf("Test 31 (Q constraint): PASSED\n"); +} + +/* Test 32: Ump pair-memory constraint */ +void test_ump_memory_constraint(void) +{ + struct pair64 pair = { 0x1122334455667788ULL, 0x99aabbccddeeff00ULL }; + uint64_t a; + uint64_t b; + + asm("ldp %0, %1, %2" : "=r"(a), "=r"(b) : "Ump"(pair)); + assert(a == pair.a); + assert(b == pair.b); + printf("Test 32 (Ump constraint): PASSED\n"); +} + int main(void) { printf("ARM64 Extended Inline Assembly Tests\n"); @@ -229,6 +383,19 @@ int main(void) test_cc_clobber(); test_large_immediate(); test_bitwise_ops(); + test_register_shift_operands(); + test_ror_immediate(); + test_fp_register_operand(); + test_zero_constraint(); + test_logical_imm32_constraint(); + test_logical_imm64_constraint(); + test_mov_imm32_constraint(); + test_mov_imm64_constraint(); + test_x_constraint_fp(); + test_y_constraint_fp(); + test_q_memory_constraint(); + test_ump_memory_constraint(); + test_symbolic_address_constraint_compile_only(); printf("\n=====================================\n"); printf("All tests PASSED!\n");