From d87801bd50abb04be894628338164537d88654dc Mon Sep 17 00:00:00 2001 From: noneofyourbusiness Date: Sat, 2 Dec 2023 23:46:39 +0100 Subject: [PATCH] riscv64-asm.c: implement C extension add nop fix asm_emit_i immediate check (negative offsets were missing) fix check for IM12S remove non-existent instructions (example: slli64 is just slli with imm=0) --- riscv64-asm.c | 1040 ++++++++++++++++++++++++++++++++++++++----------- riscv64-tok.h | 94 ++++- 2 files changed, 888 insertions(+), 246 deletions(-) diff --git a/riscv64-asm.c b/riscv64-asm.c index 1e6bada8..e3a3e71b 100644 --- a/riscv64-asm.c +++ b/riscv64-asm.c @@ -19,6 +19,60 @@ ST_FUNC void gen_le32(int c); #define USING_GLOBALS #include "tcc.h" +enum { + OPT_REG, + OPT_IM12S, + OPT_IM32, +}; +#define C_ENCODE_RS1(register_index) ((register_index) << 7) +#define C_ENCODE_RS2(register_index) ((register_index) << 2) +#define ENCODE_RD(register_index) ((register_index) << 7) +#define ENCODE_RS1(register_index) ((register_index) << 15) +#define ENCODE_RS2(register_index) ((register_index) << 20) +#define NTH_BIT(b, n) ((b >> n) & 1) +#define OP_IM12S (1 << OPT_IM12S) +#define OP_IM32 (1 << OPT_IM32) +#define OP_REG (1 << OPT_REG) + +typedef struct Operand { + uint32_t type; + union { + uint8_t reg; + uint16_t regset; + ExprValue e; + }; +} Operand; + +static void asm_binary_opcode(TCCState* s1, int token); +static void asm_clobber(uint8_t *clobber_regs, const char *str); +static void asm_compute_constraints(ASMOperand *operands, int nb_operands, int nb_outputs, const uint8_t *clobber_regs, int *pout_reg); +static void asm_emit_b(int token, uint32_t opcode, const Operand *rs1, const Operand *rs2, const Operand *imm); +static void asm_emit_i(int token, uint32_t opcode, const Operand *rd, const Operand *rs1, const Operand *rs2); +static void asm_emit_j(int token, uint32_t opcode, const Operand *rd, const Operand *rs2); +static void asm_emit_opcode(uint32_t opcode); +static void asm_emit_r(int token, uint32_t opcode, const Operand *rd, const Operand *rs1, const Operand *rs2); +static void asm_emit_s(int token, uint32_t opcode, const Operand *rs1, const Operand *rs2, const Operand *imm); +static void asm_emit_u(int token, uint32_t opcode, const Operand *rd, const Operand *rs2); +static void asm_gen_code(ASMOperand *operands, int nb_operands, int nb_outputs, int is_output, uint8_t *clobber_regs, int out_reg); +static void asm_nullary_opcode(TCCState *s1, int token); +static void asm_opcode(TCCState *s1, int token); +static int asm_parse_regvar(int t); +static void asm_ternary_opcode(TCCState *s1, int token); +static void asm_unary_opcode(TCCState *s1, int token); +static void gen_expr32(ExprValue *pe); +static void parse_operand(TCCState *s1, Operand *op); +static void subst_asm_operand(CString *add_str, SValue *sv, int modifier); +/* C extension */ +static void asm_emit_ca(int token, uint16_t opcode, const Operand *rd, const Operand *rs2); +static void asm_emit_cb(int token, uint16_t opcode, const Operand *rs1, const Operand *imm); +static void asm_emit_ci(int token, uint16_t opcode, const Operand *rd, const Operand *imm); +static void asm_emit_ciw(int token, uint16_t opcode, const Operand *rd, const Operand *imm); +static void asm_emit_cj(int token, uint16_t opcode, const Operand *imm); +static void asm_emit_cl(int token, uint16_t opcode, const Operand *rd, const Operand *rs1, const Operand *imm); +static void asm_emit_cr(int token, uint16_t opcode, const Operand *rd, const Operand *rs2); +static void asm_emit_cs(int token, uint16_t opcode, const Operand *rs2, const Operand *rs1, const Operand *imm); +static void asm_emit_css(int token, uint16_t opcode, const Operand *rs2, const Operand *imm); + /* XXX: make it faster ? */ ST_FUNC void g(int c) { @@ -63,6 +117,9 @@ static void asm_emit_opcode(uint32_t opcode) { static void asm_nullary_opcode(TCCState *s1, int token) { + static const Operand nil = {.type = OP_REG}; + static const Operand zimm = {.type = OP_IM12S}; + switch (token) { // Sync instructions @@ -84,36 +141,27 @@ static void asm_nullary_opcode(TCCState *s1, int token) // Other + case TOK_ASM_nop: + asm_emit_i(token, (4 << 2) | 3, &nil, &nil, &zimm); + return; + case TOK_ASM_wfi: asm_emit_opcode((0x1C << 2) | 3 | (0x105 << 20)); return; + /* C extension */ + case TOK_ASM_c_ebreak: + asm_emit_cr(token, 2 | (9 << 12), &nil, &nil); + return; + case TOK_ASM_c_nop: + asm_emit_ci(token, 1, &nil, &zimm); + return; + default: expect("nullary instruction"); } } -enum { - OPT_REG, - OPT_IM12S, - OPT_IM32, -}; -#define OP_REG (1 << OPT_REG) -#define OP_IM32 (1 << OPT_IM32) -#define OP_IM12S (1 << OPT_IM12S) - -typedef struct Operand { - uint32_t type; - union { - uint8_t reg; - uint16_t regset; - ExprValue e; - }; -} Operand; - -static void asm_emit_i(int token, uint32_t opcode, const Operand* rd, const Operand* rs1, const Operand* rs2); -static void asm_emit_j(int token, uint32_t opcode, const Operand* rd, const Operand* rs2); - /* Parse a text containing operand and store the result in OP */ static void parse_operand(TCCState *s1, Operand *op) { @@ -134,27 +182,22 @@ static void parse_operand(TCCState *s1, Operand *op) asm_expr(s1, &e); op->type = OP_IM32; op->e = e; + /* compare against unsigned 12-bit maximum */ if (!op->e.sym) { - if ((int) op->e.v >= -2048 && (int) op->e.v < 2048) + if (op->e.v < 0x2000) op->type = OP_IM12S; } else expect("operand"); } -#define ENCODE_RS1(register_index) ((register_index) << 15) -#define ENCODE_RS2(register_index) ((register_index) << 20) -#define ENCODE_RD(register_index) ((register_index) << 7) - -// Note: Those all map to CSR--so they are pseudo-instructions. static void asm_unary_opcode(TCCState *s1, int token) { uint32_t opcode = (0x1C << 2) | 3 | (2 << 12); Operand op; + static const Operand nil = {.type = OP_REG}; + parse_operand(s1, &op); - if (op.type != OP_REG) { - expect("register"); - return; - } + /* Note: Those all map to CSR--so they are pseudo-instructions. */ opcode |= ENCODE_RD(op.reg); switch (token) { @@ -176,6 +219,18 @@ static void asm_unary_opcode(TCCState *s1, int token) case TOK_ASM_rdinstreth: asm_emit_opcode(opcode | (0xC82 << 20) | ENCODE_RD(op.reg)); return; + case TOK_ASM_c_j: + asm_emit_cj(token, 1 | (5 << 13), &op); + return; + case TOK_ASM_c_jal: /* RV32C-only */ + asm_emit_cj(token, 1 | (1 << 13), &op); + return; + case TOK_ASM_c_jalr: + asm_emit_cr(token, 2 | (9 << 12), &op, &nil); + return; + case TOK_ASM_c_jr: + asm_emit_cr(token, 2 | (8 << 12), &op, &nil); + return; default: expect("unary instruction"); } @@ -221,6 +276,100 @@ static void asm_binary_opcode(TCCState* s1, int token) case TOK_ASM_jal: asm_emit_j(token, 0x6f, ops, ops + 1); return; + + /* C extension */ + case TOK_ASM_c_add: + asm_emit_cr(token, 2 | (9 << 12), ops, ops + 1); + return; + case TOK_ASM_c_mv: + asm_emit_cr(token, 2 | (8 << 12), ops, ops + 1); + return; + + case TOK_ASM_c_addi16sp: + asm_emit_ci(token, 1 | (3 << 13), ops, ops + 1); + return; + case TOK_ASM_c_addi: + asm_emit_ci(token, 1, ops, ops + 1); + return; + case TOK_ASM_c_addiw: + asm_emit_ci(token, 1 | (1 << 13), ops, ops + 1); + return; + case TOK_ASM_c_fldsp: + asm_emit_ci(token, 2 | (1 << 13), ops, ops + 1); + return; + case TOK_ASM_c_flwsp: /* RV32FC-only */ + asm_emit_ci(token, 2 | (3 << 13), ops, ops + 1); + return; + case TOK_ASM_c_ldsp: + asm_emit_ci(token, 2 | (3 << 13), ops, ops + 1); + return; + case TOK_ASM_c_li: + asm_emit_ci(token, 1 | (2 << 13), ops, ops + 1); + return; + case TOK_ASM_c_lui: + asm_emit_ci(token, 1 | (3 << 13), ops, ops + 1); + return; + case TOK_ASM_c_lwsp: + asm_emit_ci(token, 2 | (2 << 13), ops, ops + 1); + return; + case TOK_ASM_c_slli: + asm_emit_ci(token, 2, ops, ops + 1); + return; + + case TOK_ASM_c_addi4spn: + asm_emit_ciw(token, 0, ops, ops + 1); + return; + +#define CA (1 | (3 << 10) | (4 << 13)) + case TOK_ASM_c_addw: + asm_emit_ca(token, CA | (1 << 5) | (1 << 12), ops, ops + 1); + return; + case TOK_ASM_c_and: + asm_emit_ca(token, CA | (3 << 5), ops, ops + 1); + return; + case TOK_ASM_c_or: + asm_emit_ca(token, CA | (2 << 5), ops, ops + 1); + return; + case TOK_ASM_c_sub: + asm_emit_ca(token, CA, ops, ops + 1); + return; + case TOK_ASM_c_subw: + asm_emit_ca(token, CA | (1 << 12), ops, ops + 1); + return; + case TOK_ASM_c_xor: + asm_emit_ca(token, CA | (1 << 5), ops, ops + 1); + return; +#undef CA + + case TOK_ASM_c_andi: + asm_emit_cb(token, 1 | (2 << 10) | (4 << 13), ops, ops + 1); + return; + case TOK_ASM_c_beqz: + asm_emit_cb(token, 1 | (6 << 13), ops, ops + 1); + return; + case TOK_ASM_c_bnez: + asm_emit_cb(token, 1 | (7 << 13), ops, ops + 1); + return; + case TOK_ASM_c_srai: + asm_emit_cb(token, 1 | (1 << 10) | (4 << 13), ops, ops + 1); + return; + case TOK_ASM_c_srli: + asm_emit_cb(token, 1 | (4 << 13), ops, ops + 1); + return; + + case TOK_ASM_c_sdsp: + asm_emit_css(token, 2 | (7 << 13), ops, ops + 1); + return; + case TOK_ASM_c_swsp: + asm_emit_css(token, 2 | (6 << 13), ops, ops + 1); + return; + case TOK_ASM_c_fswsp: /* RV32FC-only */ + asm_emit_css(token, 2 | (7 << 13), ops, ops + 1); + return; + case TOK_ASM_c_fsdsp: + asm_emit_css(token, 2 | (5 << 13), ops, ops + 1); + return; + default: expect("binary instruction"); } @@ -263,7 +412,7 @@ static void asm_emit_i(int token, uint32_t opcode, const Operand* rd, const Oper return; } if (rs2->type != OP_IM12S) { - tcc_error("'%s': Expected second source operand that is an immediate value between 0 and 4095", get_tok_str(token, NULL)); + tcc_error("'%s': Expected second source operand that is an immediate value between 0 and 8191", get_tok_str(token, NULL)); return; } /* I-type instruction: @@ -278,7 +427,7 @@ static void asm_emit_i(int token, uint32_t opcode, const Operand* rd, const Oper static void asm_emit_j(int token, uint32_t opcode, const Operand* rd, const Operand* rs2) { - uint64_t imm; + uint32_t imm; if (rd->type != OP_REG) { tcc_error("'%s': Expected destination operand that is a register", get_tok_str(token, NULL)); @@ -311,7 +460,7 @@ static void asm_emit_j(int token, uint32_t opcode, const Operand* rd, const Oper gen_le32(opcode | ENCODE_RD(rd->reg) | (((imm >> 20) & 1) << 31) | (((imm >> 1) & 0x3ff) << 21) | (((imm >> 11) & 1) << 20) | (((imm >> 12) & 0xff) << 12)); } -static void asm_shift_opcode(TCCState *s1, int token) +static void asm_ternary_opcode(TCCState *s1, int token) { Operand ops[3]; parse_operand(s1, &ops[0]); @@ -363,27 +512,7 @@ static void asm_shift_opcode(TCCState *s1, int token) case TOK_ASM_sraiw: asm_emit_i(token, (0x6 << 2) | 3 | (5 << 12), &ops[0], &ops[1], &ops[2]); return; - default: - expect("shift instruction"); - } -} -static void asm_data_processing_opcode(TCCState* s1, int token) -{ - Operand ops[3]; - parse_operand(s1, &ops[0]); - if (tok == ',') - next(); - else - expect("','"); - parse_operand(s1, &ops[1]); - if (tok == ',') - next(); - else - expect("','"); - parse_operand(s1, &ops[2]); - - switch (token) { // Arithmetic (RD,RS1,(RS2|IMM)); R-format, I-format or U-format case TOK_ASM_add: @@ -446,64 +575,26 @@ static void asm_data_processing_opcode(TCCState* s1, int token) asm_emit_i(token, 0x67 | (0 << 12), ops, ops + 1, ops + 2); return; - default: - expect("known data processing instruction"); - } -} + /* branch (RS1, RS2, IMM); B-format */ + case TOK_ASM_beq: + asm_emit_b(token, 0x63 | (0 << 12), ops, ops + 1, ops + 2); + return; + case TOK_ASM_bne: + asm_emit_b(token, 0x63 | (1 << 12), ops, ops + 1, ops + 2); + return; + case TOK_ASM_blt: + asm_emit_b(token, 0x63 | (4 << 12), ops, ops + 1, ops + 2); + return; + case TOK_ASM_bge: + asm_emit_b(token, 0x63 | (5 << 12), ops, ops + 1, ops + 2); + return; + case TOK_ASM_bltu: + asm_emit_b(token, 0x63 | (6 << 12), ops, ops + 1, ops + 2); + return; + case TOK_ASM_bgeu: + asm_emit_b(token, 0x63 | (7 << 12), ops, ops + 1, ops + 2); + return; -/* caller: Add funct3 to opcode */ -static void asm_emit_s(int token, uint32_t opcode, const Operand* rs1, const Operand* rs2, const Operand* imm) -{ - if (rs1->type != OP_REG) { - tcc_error("'%s': Expected first source operand that is a register", get_tok_str(token, NULL)); - return; - } - if (rs2->type != OP_REG) { - tcc_error("'%s': Expected second source operand that is a register", get_tok_str(token, NULL)); - return; - } - if (imm->type != OP_IM12S) { - tcc_error("'%s': Expected third operand that is an immediate value between 0 and 0xfff", get_tok_str(token, NULL)); - return; - } - { - uint16_t v = imm->e.v; - /* S-type instruction: - 31...25 imm[11:5] - 24...20 rs2 - 19...15 rs1 - 14...12 funct3 - 11...7 imm[4:0] - 6...0 opcode - opcode always fixed pos. */ - gen_le32(opcode | ENCODE_RS1(rs1->reg) | ENCODE_RS2(rs2->reg) | ((v & 0x1F) << 7) | ((v >> 5) << 25)); - } -} - -static void asm_data_transfer_opcode(TCCState* s1, int token) -{ - Operand ops[3]; - parse_operand(s1, &ops[0]); - if (ops[0].type != OP_REG) { - expect("register"); - return; - } - if (tok == ',') - next(); - else - expect("','"); - parse_operand(s1, &ops[1]); - if (ops[1].type != OP_REG) { - expect("register"); - return; - } - if (tok == ',') - next(); - else - expect("','"); - parse_operand(s1, &ops[2]); - - switch (token) { // Loads (RD,RS1,I); I-format case TOK_ASM_lb: @@ -544,69 +635,86 @@ static void asm_data_transfer_opcode(TCCState* s1, int token) asm_emit_s(token, (0x8 << 2) | 3 | (3 << 12), &ops[0], &ops[1], &ops[2]); return; + /* C extension */ + /* register-based loads and stores (RD, RS1, IMM); CL-format */ + case TOK_ASM_c_fld: + asm_emit_cl(token, 1 << 13, ops, ops + 1, ops + 2); + return; + case TOK_ASM_c_flw: /* RV32FC-only */ + asm_emit_cl(token, 3 << 13, ops, ops + 1, ops + 2); + return; + case TOK_ASM_c_fsd: + asm_emit_cs(token, 5 << 13, ops, ops + 1, ops + 2); + return; + case TOK_ASM_c_fsw: /* RV32FC-only */ + asm_emit_cs(token, 7 << 13, ops, ops + 1, ops + 2); + return; + case TOK_ASM_c_ld: + asm_emit_cl(token, 3 << 13, ops, ops + 1, ops + 2); + return; + case TOK_ASM_c_lw: + asm_emit_cl(token, 2 << 13, ops, ops + 1, ops + 2); + return; + case TOK_ASM_c_sd: + asm_emit_cs(token, 7 << 13, ops, ops + 1, ops + 2); + return; + case TOK_ASM_c_sw: + asm_emit_cs(token, 6 << 13, ops, ops + 1, ops + 2); + return; + default: - expect("known data transfer instruction"); + expect("ternary instruction"); } } -static void asm_branch_opcode(TCCState* s1, int token) +/* caller: Add funct3 to opcode */ +static void asm_emit_s(int token, uint32_t opcode, const Operand* rs1, const Operand* rs2, const Operand* imm) { - // Branch (RS1,RS2,IMM); SB-format - uint32_t opcode = (0x18 << 2) | 3; - uint32_t offset = 0; - Operand ops[3]; - parse_operand(s1, &ops[0]); - if (ops[0].type != OP_REG) { - expect("register"); + if (rs1->type != OP_REG) { + tcc_error("'%s': Expected first source operand that is a register", get_tok_str(token, NULL)); return; } - if (tok == ',') - next(); - else - expect("','"); - parse_operand(s1, &ops[1]); - if (ops[1].type != OP_REG) { - expect("register"); + if (rs2->type != OP_REG) { + tcc_error("'%s': Expected second source operand that is a register", get_tok_str(token, NULL)); return; } - if (tok == ',') - next(); - else - expect("','"); - parse_operand(s1, &ops[2]); + if (imm->type != OP_IM12S) { + tcc_error("'%s': Expected third operand that is an immediate value between 0 and 8191", get_tok_str(token, NULL)); + return; + } + { + uint16_t v = imm->e.v; + /* S-type instruction: + 31...25 imm[11:5] + 24...20 rs2 + 19...15 rs1 + 14...12 funct3 + 11...7 imm[4:0] + 6...0 opcode + opcode always fixed pos. */ + gen_le32(opcode | ENCODE_RS1(rs1->reg) | ENCODE_RS2(rs2->reg) | ((v & 0x1F) << 7) | ((v >> 5) << 25)); + } +} - if (ops[2].type != OP_IM12S) { - tcc_error("'%s': Expected third operand that is an immediate value between 0 and 0xfff", get_tok_str(token, NULL)); +static void asm_emit_b(int token, uint32_t opcode, const Operand *rs1, const Operand *rs2, const Operand *imm) +{ + uint32_t offset; + + if (rs1->type != OP_REG) { + tcc_error("'%s': Expected first source operand that is a register", get_tok_str(token, NULL)); return; } - offset = ops[2].e.v; - if (offset & 1) { - tcc_error("'%s': Expected third operand that is an even immediate value", get_tok_str(token, NULL)); + if (rs2->type != OP_REG) { + tcc_error("'%s': Expected destination operand that is a register", get_tok_str(token, NULL)); + return; + } + if (imm->type != OP_IM12S) { + tcc_error("'%s': Expected second source operand that is an immediate value between 0 and 8191", get_tok_str(token, NULL)); return; } - switch (token) { - case TOK_ASM_beq: - opcode |= 0 << 12; - break; - case TOK_ASM_bne: - opcode |= 1 << 12; - break; - case TOK_ASM_blt: - opcode |= 4 << 12; - break; - case TOK_ASM_bge: - opcode |= 5 << 12; - break; - case TOK_ASM_bltu: - opcode |= 6 << 12; - break; - case TOK_ASM_bgeu: - opcode |= 7 << 12; - break; - default: - expect("known branch instruction"); - } + offset = imm->e.v; + /* B-type instruction: 31 imm[12] 30...25 imm[10:5] @@ -616,19 +724,20 @@ static void asm_branch_opcode(TCCState* s1, int token) 8...11 imm[4:1] 7 imm[11] 6...0 opcode */ - asm_emit_opcode(opcode | ENCODE_RS1(ops[0].reg) | ENCODE_RS2(ops[1].reg) | (((offset >> 1) & 0xF) << 8) | (((offset >> 5) & 0x1f) << 25) | (((offset >> 11) & 1) << 7) | (((offset >> 12) & 1) << 31)); + asm_emit_opcode(opcode | ENCODE_RS1(rs1->reg) | ENCODE_RS2(rs2->reg) | (((offset >> 1) & 0xF) << 8) | (((offset >> 5) & 0x1f) << 25) | (((offset >> 11) & 1) << 7) | (((offset >> 12) & 1) << 31)); } ST_FUNC void asm_opcode(TCCState *s1, int token) { switch (token) { + case TOK_ASM_ebreak: + case TOK_ASM_ecall: case TOK_ASM_fence: case TOK_ASM_fence_i: - case TOK_ASM_ecall: - case TOK_ASM_ebreak: - case TOK_ASM_mrts: - case TOK_ASM_mrth: case TOK_ASM_hrts: + case TOK_ASM_mrth: + case TOK_ASM_mrts: + case TOK_ASM_nop: case TOK_ASM_wfi: asm_nullary_opcode(s1, token); return; @@ -648,68 +757,108 @@ ST_FUNC void asm_opcode(TCCState *s1, int token) asm_binary_opcode(s1, token); return; - case TOK_ASM_sll: - case TOK_ASM_slli: - case TOK_ASM_srl: - case TOK_ASM_srli: - case TOK_ASM_sra: - case TOK_ASM_srai: - case TOK_ASM_sllw: - case TOK_ASM_slld: - case TOK_ASM_slliw: - case TOK_ASM_sllid: - case TOK_ASM_srlw: - case TOK_ASM_srld: - case TOK_ASM_srliw: - case TOK_ASM_srlid: - case TOK_ASM_sraw: - case TOK_ASM_srad: - case TOK_ASM_sraiw: - case TOK_ASM_sraid: - asm_shift_opcode(s1, token); - return; - case TOK_ASM_add: case TOK_ASM_addi: - case TOK_ASM_sub: - case TOK_ASM_addw: case TOK_ASM_addiw: - case TOK_ASM_subw: - case TOK_ASM_xor: - case TOK_ASM_xori: - case TOK_ASM_or: - case TOK_ASM_ori: + case TOK_ASM_addw: case TOK_ASM_and: case TOK_ASM_andi: + case TOK_ASM_beq: + case TOK_ASM_bge: + case TOK_ASM_bgeu: + case TOK_ASM_blt: + case TOK_ASM_bltu: + case TOK_ASM_bne: + case TOK_ASM_jalr: + case TOK_ASM_lb: + case TOK_ASM_lbu: + case TOK_ASM_ld: + case TOK_ASM_lh: + case TOK_ASM_lhu: + case TOK_ASM_lw: + case TOK_ASM_lwu: + case TOK_ASM_or: + case TOK_ASM_ori: + case TOK_ASM_sb: + case TOK_ASM_sd: + case TOK_ASM_sh: + case TOK_ASM_sll: + case TOK_ASM_slli: + case TOK_ASM_slliw: + case TOK_ASM_sllw: case TOK_ASM_slt: case TOK_ASM_slti: - case TOK_ASM_sltu: case TOK_ASM_sltiu: - case TOK_ASM_jalr: - asm_data_processing_opcode(s1, token); - return; - - case TOK_ASM_lb: - case TOK_ASM_lh: - case TOK_ASM_lw: - case TOK_ASM_lbu: - case TOK_ASM_lhu: - case TOK_ASM_ld: - case TOK_ASM_lwu: - case TOK_ASM_sb: - case TOK_ASM_sh: + case TOK_ASM_sltu: + case TOK_ASM_sra: + case TOK_ASM_srai: + case TOK_ASM_sraiw: + case TOK_ASM_sraw: + case TOK_ASM_srl: + case TOK_ASM_srli: + case TOK_ASM_srliw: + case TOK_ASM_srlw: + case TOK_ASM_sub: + case TOK_ASM_subw: case TOK_ASM_sw: - case TOK_ASM_sd: - asm_data_transfer_opcode(s1, token); + case TOK_ASM_xor: + case TOK_ASM_xori: + asm_ternary_opcode(s1, token); return; - case TOK_ASM_beq: - case TOK_ASM_bne: - case TOK_ASM_blt: - case TOK_ASM_bge: - case TOK_ASM_bltu: - case TOK_ASM_bgeu: - asm_branch_opcode(s1, token); + /* C extension */ + case TOK_ASM_c_ebreak: + case TOK_ASM_c_nop: + asm_nullary_opcode(s1, token); + return; + + case TOK_ASM_c_j: + case TOK_ASM_c_jal: + case TOK_ASM_c_jalr: + case TOK_ASM_c_jr: + asm_unary_opcode(s1, token); + return; + + case TOK_ASM_c_add: + case TOK_ASM_c_addi16sp: + case TOK_ASM_c_addi4spn: + case TOK_ASM_c_addi: + case TOK_ASM_c_addiw: + case TOK_ASM_c_addw: + case TOK_ASM_c_and: + case TOK_ASM_c_andi: + case TOK_ASM_c_beqz: + case TOK_ASM_c_bnez: + case TOK_ASM_c_fldsp: + case TOK_ASM_c_flwsp: + case TOK_ASM_c_fsdsp: + case TOK_ASM_c_fswsp: + case TOK_ASM_c_ldsp: + case TOK_ASM_c_li: + case TOK_ASM_c_lui: + case TOK_ASM_c_lwsp: + case TOK_ASM_c_mv: + case TOK_ASM_c_or: + case TOK_ASM_c_sdsp: + case TOK_ASM_c_slli: + case TOK_ASM_c_srai: + case TOK_ASM_c_srli: + case TOK_ASM_c_sub: + case TOK_ASM_c_subw: + case TOK_ASM_c_swsp: + case TOK_ASM_c_xor: + asm_binary_opcode(s1, token); + return; + + case TOK_ASM_c_fld: + case TOK_ASM_c_flw: + case TOK_ASM_c_fsd: + case TOK_ASM_c_fsw: + case TOK_ASM_c_ld: + case TOK_ASM_c_lw: + case TOK_ASM_c_sd: + case TOK_ASM_c_sw: + asm_ternary_opcode(s1, token); return; default: @@ -756,19 +905,456 @@ ST_FUNC void asm_clobber(uint8_t *clobber_regs, const char *str) ST_FUNC int asm_parse_regvar (int t) { - if (t >= TOK_ASM_x0 && t <= TOK_ASM_pc) { /* register name */ - if (t >= TOK_ASM_zero && t <= TOK_ASM_t6) - return t - TOK_ASM_zero; - switch (t) { - case TOK_ASM_s0: - return 8; - case TOK_ASM_pc: - tcc_error("PC register not implemented."); - default: - return t - TOK_ASM_x0; - } - } else + /* PC register not implemented */ + if (t >= TOK_ASM_pc || t < TOK_ASM_x0) return -1; + + if (t < TOK_ASM_f0) + return t - TOK_ASM_x0; + + if (t < TOK_ASM_zero) + return t - TOK_ASM_f0; + + /* ABI mnemonic */ + if (t < TOK_ASM_ft0) + return t - TOK_ASM_zero; + + return t - TOK_ASM_ft0; +} + +/*************************************************************/ +/* C extension */ + +/* caller: Add funct6, funct2 into opcode */ +static void asm_emit_ca(int token, uint16_t opcode, const Operand *rd, const Operand *rs2) +{ + uint8_t dst, src; + + if (rd->type != OP_REG) { + tcc_error("'%s': Expected destination operand that is a register", get_tok_str(token, NULL)); + return; + } + + if (rs2->type != OP_REG) { + tcc_error("'%s': Expected source operand that is a register", get_tok_str(token, NULL)); + return; + } + + /* subtract index of x8 */ + dst = rd->reg - 8; + src = rs2->reg - 8; + + /* only registers {x,f}8 to {x,f}15 are valid (3-bit) */ + if (dst > 7) { + tcc_error("'%s': Expected destination operand that is a valid C-extension register", get_tok_str(token, NULL)); + return; + } + + if (src > 7) { + tcc_error("'%s': Expected source operand that is a valid C-extension register", get_tok_str(token, NULL)); + return; + } + + /* CA-type instruction: + 15...10 funct6 + 9...7 rd'/rs1' + 6..5 funct2 + 4...2 rs2' + 1...0 opcode */ + + gen_le16(opcode | C_ENCODE_RS2(src) | C_ENCODE_RS1(dst)); +} + +static void asm_emit_cb(int token, uint16_t opcode, const Operand *rs1, const Operand *imm) +{ + uint32_t offset; + uint8_t dst, src; + + if (rs1->type != OP_REG) { + tcc_error("'%s': Expected source operand that is a register", get_tok_str(token, NULL)); + return; + } + + if (imm->type != OP_IM12S && imm->type != OP_IM32) { + tcc_error("'%s': Expected source operand that is an immediate value", get_tok_str(token, NULL)); + return; + } + + offset = imm->e.v; + + if (offset & 1) { + tcc_error("'%s': Expected source operand that is an even immediate value", get_tok_str(token, NULL)); + return; + } + + src = rs1->reg - 8; + + if (src > 7) { + tcc_error("'%s': Expected source operand that is a valid C-extension register", get_tok_str(token, NULL)); + return; + } + + /* CB-type instruction: + 15...13 funct3 + 12...10 offset + 9..7 rs1' + 6...2 offset + 1...0 opcode */ + + /* non-branch also using CB: + 15...13 funct3 + 12 imm + 11..10 funct2 + 9...7 rd'/rs1' + 6..2 imm + 1...0 opcode */ + + switch (token) { + case TOK_ASM_c_beqz: + case TOK_ASM_c_bnez: + gen_le16(opcode | C_ENCODE_RS1(src) | ((NTH_BIT(offset, 5) | (((offset >> 1) & 3) << 1) | (((offset >> 6) & 3) << 3)) << 2) | ((((offset >> 3) & 3) | NTH_BIT(offset, 8)) << 10)); + return; + default: + gen_le16(opcode | C_ENCODE_RS1(src) | ((offset & 0x1f) << 2) | (NTH_BIT(offset, 5) << 12)); + return; + } +} + +static void asm_emit_ci(int token, uint16_t opcode, const Operand *rd, const Operand *imm) +{ + uint32_t immediate; + + if (rd->type != OP_REG) { + tcc_error("'%s': Expected destination operand that is a register", get_tok_str(token, NULL)); + return; + } + + if (imm->type != OP_IM12S && imm->type != OP_IM32) { + tcc_error("'%s': Expected source operand that is an immediate value", get_tok_str(token, NULL)); + return; + } + + immediate = imm->e.v; + + /* CI-type instruction: + 15...13 funct3 + 12 imm + 11...7 rd/rs1 + 6...2 imm + 1...0 opcode */ + + switch (token) { + case TOK_ASM_c_addi: + case TOK_ASM_c_addiw: + case TOK_ASM_c_li: + case TOK_ASM_c_slli: + gen_le16(opcode | ((immediate & 0x1f) << 2) | ENCODE_RD(rd->reg) | (NTH_BIT(immediate, 5) << 12)); + return; + case TOK_ASM_c_addi16sp: + gen_le16(opcode | NTH_BIT(immediate, 5) << 2 | (((immediate >> 7) & 3) << 3) | NTH_BIT(immediate, 6) << 5 | NTH_BIT(immediate, 4) << 6 | ENCODE_RD(rd->reg) | (NTH_BIT(immediate, 9) << 12)); + return; + case TOK_ASM_c_lui: + gen_le16(opcode | (((immediate >> 12) & 0x1f) << 2) | ENCODE_RD(rd->reg) | (NTH_BIT(immediate, 17) << 12)); + return; + case TOK_ASM_c_fldsp: + case TOK_ASM_c_ldsp: + gen_le16(opcode | (((immediate >> 6) & 7) << 2) | (((immediate >> 3) & 2) << 5) | ENCODE_RD(rd->reg) | (NTH_BIT(immediate, 5) << 12)); + return; + case TOK_ASM_c_flwsp: + case TOK_ASM_c_lwsp: + gen_le16(opcode | (((immediate >> 6) & 3) << 2) | (((immediate >> 2) & 7) << 4) | ENCODE_RD(rd->reg) | (NTH_BIT(immediate, 5) << 12)); + return; + case TOK_ASM_c_nop: + gen_le16(opcode); + return; + default: + expect("known instruction"); + } +} + +/* caller: Add funct3 into opcode */ +static void asm_emit_ciw(int token, uint16_t opcode, const Operand *rd, const Operand *imm) +{ + uint32_t nzuimm; + uint8_t dst; + + if (rd->type != OP_REG) { + tcc_error("'%s': Expected destination operand that is a register", get_tok_str(token, NULL)); + return; + } + + if (imm->type != OP_IM12S && imm->type != OP_IM32) { + tcc_error("'%s': Expected source operand that is an immediate value", get_tok_str(token, NULL)); + return; + } + + dst = rd->reg - 8; + + if (dst > 7) { + tcc_error("'%s': Expected destination operand that is a valid C-extension register", get_tok_str(token, NULL)); + return; + } + + nzuimm = imm->e.v; + + if (nzuimm > 0x3fc) { + tcc_error("'%s': Expected source operand that is an immediate value between 0 and 0x3ff", get_tok_str(token, NULL)); + return; + } + + if (nzuimm & 3) { + tcc_error("'%s': Expected source operand that is a non-zero immediate value divisible by 4", get_tok_str(token, NULL)); + return; + } + + /* CIW-type instruction: + 15...13 funct3 + 12...5 imm + 4...2 rd' + 1...0 opcode */ + + gen_le16(opcode | ENCODE_RS2(rd->reg) | ((NTH_BIT(nzuimm, 3) | (NTH_BIT(nzuimm, 2) << 1) | (((nzuimm >> 6) & 0xf) << 2) | (((nzuimm >> 4) & 3) << 6)) << 5)); +} + +/* caller: Add funct3 into opcode */ +static void asm_emit_cj(int token, uint16_t opcode, const Operand *imm) +{ + uint32_t offset; + + if (imm->type != OP_IM12S && imm->type != OP_IM32) { + tcc_error("'%s': Expected source operand that is an immediate value", get_tok_str(token, NULL)); + return; + } + + offset = imm->e.v; + + /* +-2 KiB range; max. 0x7fe min. 0xffe (-0x7fe) */ + if (offset > 0x1fff) { + tcc_error("'%s': Expected source operand that is an immediate value between 0 and 0x1fff", get_tok_str(token, NULL)); + return; + } + + if (offset & 1) { + tcc_error("'%s': Expected source operand that is an even immediate value", get_tok_str(token, NULL)); + return; + } + + /* CJ-type instruction: + 15...13 funct3 + 12...2 offset[11|4|9:8|10|6|7|3:1|5] + 1...0 opcode */ + + gen_le16(opcode | (NTH_BIT(offset, 5) << 2) | (((offset >> 1) & 7) << 3) | (NTH_BIT(offset, 7) << 6) | (NTH_BIT(offset, 6) << 7) | (NTH_BIT(offset, 10) << 8) | (((offset >> 8) & 3) << 9) | (NTH_BIT(offset, 4) << 11) | (NTH_BIT(offset, 11) << 12)); +} + +/* caller: Add funct3 into opcode */ +static void asm_emit_cl(int token, uint16_t opcode, const Operand *rd, const Operand *rs1, const Operand *imm) +{ + uint32_t offset; + uint8_t dst, src; + + if (rd->type != OP_REG) { + tcc_error("'%s': Expected destination operand that is a register", get_tok_str(token, NULL)); + return; + } + + if (rs1->type != OP_REG) { + tcc_error("'%s': Expected source operand that is a register", get_tok_str(token, NULL)); + return; + } + + if (imm->type != OP_IM12S && imm->type != OP_IM32) { + tcc_error("'%s': Expected source operand that is an immediate value", get_tok_str(token, NULL)); + return; + } + + dst = rd->reg - 8; + src = rs1->reg - 8; + + if (dst > 7) { + tcc_error("'%s': Expected destination operand that is a valid C-extension register", get_tok_str(token, NULL)); + return; + } + + if (src > 7) { + tcc_error("'%s': Expected source operand that is a valid C-extension register", get_tok_str(token, NULL)); + return; + } + + offset = imm->e.v; + + if (offset > 0xff) { + tcc_error("'%s': Expected source operand that is an immediate value between 0 and 0xff", get_tok_str(token, NULL)); + return; + } + + if (offset & 3) { + tcc_error("'%s': Expected source operand that is an immediate value divisible by 4", get_tok_str(token, NULL)); + return; + } + + /* CL-type instruction: + 15...13 funct3 + 12...10 imm + 9...7 rs1' + 6...5 imm + 4...2 rd' + 1...0 opcode */ + + switch (token) { + /* imm variant 1 */ + case TOK_ASM_c_flw: + case TOK_ASM_c_lw: + gen_le16(opcode | C_ENCODE_RS2(dst) | C_ENCODE_RS1(src) | (NTH_BIT(offset, 6) << 5) | (NTH_BIT(offset, 2) << 6) | (((offset >> 3) & 7) << 10)); + return; + /* imm variant 2 */ + case TOK_ASM_c_fld: + case TOK_ASM_c_ld: + gen_le16(opcode | C_ENCODE_RS2(dst) | C_ENCODE_RS1(src) | (((offset >> 6) & 3) << 5) | (((offset >> 3) & 7) << 10)); + return; + default: + expect("known instruction"); + } +} + +/* caller: Add funct4 into opcode */ +static void asm_emit_cr(int token, uint16_t opcode, const Operand *rd, const Operand *rs2) +{ + if (rd->type != OP_REG) { + tcc_error("'%s': Expected destination operand that is a register", get_tok_str(token, NULL)); + return; + } + + if (rs2->type != OP_REG) { + tcc_error("'%s': Expected source operand that is a register", get_tok_str(token, NULL)); + return; + } + + /* CR-type instruction: + 15...12 funct4 + 11..7 rd/rs1 + 6...2 rs2 + 1...0 opcode */ + + gen_le16(opcode | C_ENCODE_RS1(rd->reg) | C_ENCODE_RS2(rs2->reg)); +} + +/* caller: Add funct3 into opcode */ +static void asm_emit_cs(int token, uint16_t opcode, const Operand *rs2, const Operand *rs1, const Operand *imm) +{ + uint32_t offset; + uint8_t base, src; + + if (rs2->type != OP_REG) { + tcc_error("'%s': Expected destination operand that is a register", get_tok_str(token, NULL)); + return; + } + + if (rs1->type != OP_REG) { + tcc_error("'%s': Expected source operand that is a register", get_tok_str(token, NULL)); + return; + } + + if (imm->type != OP_IM12S && imm->type != OP_IM32) { + tcc_error("'%s': Expected source operand that is an immediate value", get_tok_str(token, NULL)); + return; + } + + base = rs1->reg - 8; + src = rs2->reg - 8; + + if (base > 7) { + tcc_error("'%s': Expected destination operand that is a valid C-extension register", get_tok_str(token, NULL)); + return; + } + + if (src > 7) { + tcc_error("'%s': Expected source operand that is a valid C-extension register", get_tok_str(token, NULL)); + return; + } + + offset = imm->e.v; + + if (offset > 0xff) { + tcc_error("'%s': Expected source operand that is an immediate value between 0 and 0xff", get_tok_str(token, NULL)); + return; + } + + if (offset & 3) { + tcc_error("'%s': Expected source operand that is an immediate value divisible by 4", get_tok_str(token, NULL)); + return; + } + + /* CS-type instruction: + 15...13 funct3 + 12...10 imm + 9...7 rs1' + 6...5 imm + 4...2 rs2' + 1...0 opcode */ + switch (token) { + /* imm variant 1 */ + case TOK_ASM_c_fsw: + case TOK_ASM_c_sw: + gen_le16(opcode | C_ENCODE_RS2(base) | C_ENCODE_RS1(src) | (NTH_BIT(offset, 6) << 5) | (NTH_BIT(offset, 2) << 6) | (((offset >> 3) & 7) << 10)); + return; + /* imm variant 2 */ + case TOK_ASM_c_fsd: + case TOK_ASM_c_sd: + gen_le16(opcode | C_ENCODE_RS2(base) | C_ENCODE_RS1(src) | (((offset >> 6) & 3) << 5) | (((offset >> 3) & 7) << 10)); + return; + default: + expect("known instruction"); + } +} + +/* caller: Add funct3 into opcode */ +static void asm_emit_css(int token, uint16_t opcode, const Operand *rs2, const Operand *imm) +{ + uint32_t offset; + + if (rs2->type != OP_REG) { + tcc_error("'%s': Expected destination operand that is a register", get_tok_str(token, NULL)); + return; + } + + if (imm->type != OP_IM12S && imm->type != OP_IM32) { + tcc_error("'%s': Expected source operand that is an immediate value", get_tok_str(token, NULL)); + return; + } + + offset = imm->e.v; + + if (offset > 0xff) { + tcc_error("'%s': Expected source operand that is an immediate value between 0 and 0xff", get_tok_str(token, NULL)); + return; + } + + if (offset & 3) { + tcc_error("'%s': Expected source operand that is an immediate value divisible by 4", get_tok_str(token, NULL)); + return; + } + + /* CSS-type instruction: + 15...13 funct3 + 12...7 imm + 6...2 rs2 + 1...0 opcode */ + + switch (token) { + /* imm variant 1 */ + case TOK_ASM_c_fswsp: + case TOK_ASM_c_swsp: + gen_le16(opcode | ENCODE_RS2(rs2->reg) | (((offset >> 6) & 3) << 7) | (((offset >> 2) & 0xf) << 9)); + return; + /* imm variant 2 */ + case TOK_ASM_c_fsdsp: + case TOK_ASM_c_sdsp: + gen_le16(opcode | ENCODE_RS2(rs2->reg) | (((offset >> 6) & 7) << 7) | (((offset >> 3) & 7) << 10)); + return; + default: + expect("known instruction"); + } } /*************************************************************/ diff --git a/riscv64-tok.h b/riscv64-tok.h index 986cffa9..c8a6e8e3 100644 --- a/riscv64-tok.h +++ b/riscv64-tok.h @@ -5,8 +5,11 @@ * The specifications are available under https://riscv.org/technical/specifications/ */ -/* register */ +#define DEF_ASM_WITH_SUFFIX(x, y) \ + DEF(TOK_ASM_ ## x ## _ ## y, #x "." #y) +/* register */ + /* integer */ DEF_ASM(x0) DEF_ASM(x1) DEF_ASM(x2) @@ -39,9 +42,42 @@ DEF_ASM(x29) DEF_ASM(x30) DEF_ASM(x31) + /* float */ + DEF_ASM(f0) + DEF_ASM(f1) + DEF_ASM(f2) + DEF_ASM(f3) + DEF_ASM(f4) + DEF_ASM(f5) + DEF_ASM(f6) + DEF_ASM(f7) + DEF_ASM(f8) + DEF_ASM(f9) + DEF_ASM(f10) + DEF_ASM(f11) + DEF_ASM(f12) + DEF_ASM(f13) + DEF_ASM(f14) + DEF_ASM(f15) + DEF_ASM(f16) + DEF_ASM(f17) + DEF_ASM(f18) + DEF_ASM(f19) + DEF_ASM(f20) + DEF_ASM(f21) + DEF_ASM(f22) + DEF_ASM(f23) + DEF_ASM(f24) + DEF_ASM(f25) + DEF_ASM(f26) + DEF_ASM(f27) + DEF_ASM(f28) + DEF_ASM(f29) + DEF_ASM(f30) + DEF_ASM(f31) -/* register macros */ - +/* register ABI mnemonics, refer to RISC-V ABI 1.0 */ + /* integer */ DEF_ASM(zero) DEF_ASM(ra) DEF_ASM(sp) @@ -50,7 +86,7 @@ DEF_ASM(t0) DEF_ASM(t1) DEF_ASM(t2) - DEF_ASM(fp) + DEF_ASM(s0) DEF_ASM(s1) DEF_ASM(a0) DEF_ASM(a1) @@ -74,14 +110,42 @@ DEF_ASM(t4) DEF_ASM(t5) DEF_ASM(t6) - - DEF_ASM(s0) // = x8 - + /* float */ + DEF_ASM(ft0) + DEF_ASM(ft1) + DEF_ASM(ft2) + DEF_ASM(ft3) + DEF_ASM(ft4) + DEF_ASM(ft5) + DEF_ASM(ft6) + DEF_ASM(ft7) + DEF_ASM(fs0) + DEF_ASM(fs1) + DEF_ASM(fa0) + DEF_ASM(fa1) + DEF_ASM(fa2) + DEF_ASM(fa3) + DEF_ASM(fa4) + DEF_ASM(fa5) + DEF_ASM(fa6) + DEF_ASM(fa7) + DEF_ASM(fs2) + DEF_ASM(fs3) + DEF_ASM(fs4) + DEF_ASM(fs5) + DEF_ASM(fs6) + DEF_ASM(fs7) + DEF_ASM(fs8) + DEF_ASM(fs9) + DEF_ASM(fs10) + DEF_ASM(fs11) + DEF_ASM(ft8) + DEF_ASM(ft9) + DEF_ASM(ft10) + DEF_ASM(ft11) + /* not in the ABI */ DEF_ASM(pc) -#define DEF_ASM_WITH_SUFFIX(x, y) \ - DEF(TOK_ASM_ ## x ## _ ## y, #x #y) - /* Loads */ DEF_ASM(lb) @@ -110,18 +174,12 @@ DEF_ASM(slli) DEF_ASM(srli) DEF_ASM(sllw) - DEF_ASM(slld) DEF_ASM(slliw) - DEF_ASM(sllid) DEF_ASM(srlw) - DEF_ASM(srld) DEF_ASM(srliw) - DEF_ASM(srlid) DEF_ASM(srai) DEF_ASM(sraw) - DEF_ASM(srad) DEF_ASM(sraiw) - DEF_ASM(sraid) /* Arithmetic */ @@ -236,11 +294,8 @@ /* Shifts */ DEF_ASM_WITH_SUFFIX(c, slli) - DEF_ASM_WITH_SUFFIX(c, slli64) DEF_ASM_WITH_SUFFIX(c, srli) - DEF_ASM_WITH_SUFFIX(c, srli64) DEF_ASM_WITH_SUFFIX(c, srai) - DEF_ASM_WITH_SUFFIX(c, srai64) /* Arithmetic */ DEF_ASM_WITH_SUFFIX(c, add) @@ -293,3 +348,4 @@ DEF_ASM(hrts) DEF_ASM(wfi) +#undef DEF_ASM_WITH_SUFFIX