/*************************************************************//* * RISCV64 assembler for TCC * */ #ifdef TARGET_DEFS_ONLY #define CONFIG_TCC_ASM/* 32 general purpose + 32 floating point registers */#define NB_ASM_REGS 64 ST_FUNC void g(int c);ST_FUNC void gen_le16(int c);ST_FUNC void gen_le32(int c); /*************************************************************/#else/*************************************************************/#define USING_GLOBALS#include "tcc.h" enum { OPT_REG, OPT_IM12S, OPT_IM32,};// Registers go from 0 to 31. We use next bit to choose general/float#define REG_FLOAT_MASK 0x20#define REG_IS_FLOAT(register_index) ((register_index) & REG_FLOAT_MASK)#define REG_VALUE(register_index) ((register_index) & (REG_FLOAT_MASK-1))#define C_ENCODE_RS1(register_index) (REG_VALUE(register_index) << 7)#define C_ENCODE_RS2(register_index) (REG_VALUE(register_index) << 2)#define ENCODE_RD(register_index) (REG_VALUE(register_index) << 7)#define ENCODE_RS1(register_index) (REG_VALUE(register_index) << 15)#define ENCODE_RS2(register_index) (REG_VALUE(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 const Operand zero = { OP_REG, { 0 }};static const Operand ra = { OP_REG, { 1 }};static const Operand zimm = { OP_IM12S }; static void asm_binary_opcode(TCCState* s1, int token);ST_FUNC void asm_clobber(uint8_t *clobber_regs, const char *str);ST_FUNC void asm_compute_constraints(ASMOperand *operands, int nb_operands, int nb_outputs, const uint8_t *clobber_regs, int *pout_reg);static void asm_emit_a(int token, uint32_t opcode, const Operand *rs1, const Operand *rs2, const Operand *rd1, int aq, int rl);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_emit_f(int token, uint32_t opcode, const Operand *rd, const Operand *rs1, const Operand *rs2);static void asm_emit_fb(int token, uint32_t opcode, const Operand *rd, const Operand *rs);static void asm_emit_fq(int token, uint32_t opcode, const Operand *rd, const Operand *rs1, const Operand *rs2, const Operand *rs3);ST_FUNC 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);ST_FUNC void asm_opcode(TCCState *s1, int token);static int asm_parse_csrvar(int t);ST_FUNC 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 asm_branch_opcode(TCCState *s1, int token, int argc);ST_FUNC void gen_expr32(ExprValue *pe);static void parse_operand(TCCState *s1, Operand *op);static void parse_branch_offset_operand(TCCState *s1, Operand *op);static void parse_operands(TCCState *s1, Operand *ops, int count);static void parse_mem_access_operands(TCCState *s1, Operand* ops);ST_FUNC 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){ int ind1; if (nocode_wanted) return; ind1 = ind + 1; if (ind1 > cur_text_section->data_allocated) section_realloc(cur_text_section, ind1); cur_text_section->data[ind] = c; ind = ind1;} ST_FUNC void gen_le16 (int i){ g(i); g(i>>8);} ST_FUNC void gen_le32 (int i){ int ind1; if (nocode_wanted) return; ind1 = ind + 4; if (ind1 > cur_text_section->data_allocated) section_realloc(cur_text_section, ind1); cur_text_section->data[ind++] = i & 0xFF; cur_text_section->data[ind++] = (i >> 8) & 0xFF; cur_text_section->data[ind++] = (i >> 16) & 0xFF; cur_text_section->data[ind++] = (i >> 24) & 0xFF;} ST_FUNC void gen_expr32(ExprValue *pe){ gen_le32(pe->v);} static void asm_emit_opcode(uint32_t opcode) { gen_le32(opcode);} static void asm_nullary_opcode(TCCState *s1, int token){ switch (token) { // Sync instructions case TOK_ASM_fence_i: // I asm_emit_opcode((0x3 << 2) | 3| (1 << 12)); return; // System calls case TOK_ASM_ecall: // I (pseudo) asm_emit_opcode((0x1C << 2) | 3 | (0 << 12)); return; case TOK_ASM_ebreak: // I (pseudo) asm_emit_opcode((0x1C << 2) | 3 | (0 << 12) | (1 << 20)); return; // Other case TOK_ASM_nop: asm_emit_i(token, (4 << 2) | 3, &zero, &zero, &zimm); return; case TOK_ASM_wfi: asm_emit_opcode((0x1C << 2) | 3 | (0x105 << 20)); return; /* Pseudoinstructions */ case TOK_ASM_ret: /* jalr zero, x1, 0 */ asm_emit_opcode( 0x67 | (0 << 12) | ENCODE_RS1(1) ); return; /* C extension */ case TOK_ASM_c_ebreak: asm_emit_cr(token, 2 | (9 << 12), &zero, &zero); return; case TOK_ASM_c_nop: asm_emit_ci(token, 1, &zero, &zimm); return; default: expect("nullary instruction"); }} /* Parse a text containing operand and store the result in OP */static void parse_operand(TCCState *s1, Operand *op){ ExprValue e = {0}; Sym label = {0}; int8_t reg; op->type = 0; if ((reg = asm_parse_regvar(tok)) != -1) { next(); // skip register name op->type = OP_REG; op->reg = (uint8_t) reg; return; } else if (tok == '$') { /* constant value */ next(); // skip '#' or '$' } else if ((e.v = asm_parse_csrvar(tok)) != -1) { next(); } else { 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 >= -0x1000 && (int) op->e.v < 0x1000) op->type = OP_IM12S; } else if (op->e.sym->type.t & (VT_EXTERN | VT_STATIC)) { /* see also: "RISC-V ABIs Specification" V1.0 section 5.2 recommends using a GOT for "possibly-undefined weak symbols" section 5.3: "Medium position independent code model" if this is a non-local symbol: use a GOT non-local: outside of a pc-relative +- 2 GiB range */ label.type.t = VT_VOID | VT_STATIC; /* use the medium PIC model: GOT, auipc, lw */ if (op->e.sym->type.t & VT_STATIC) greloca(cur_text_section, op->e.sym, ind, R_RISCV_PCREL_HI20, 0); else greloca(cur_text_section, op->e.sym, ind, R_RISCV_GOT_HI20, 0); put_extern_sym(&label, cur_text_section, ind, 0); greloca(cur_text_section, &label, ind+4, R_RISCV_PCREL_LO12_I, 0); op->type = OP_IM12S; op->e.v = 0; } else { expect("operand"); }} static void parse_branch_offset_operand(TCCState *s1, Operand *op){ ExprValue e = {0}; 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 >= -0x1000 && (int) op->e.v < 0x1000) op->type = OP_IM12S; } else if (op->e.sym->type.t & (VT_EXTERN | VT_STATIC)) { /* For extern/static symbols, always use far-branch expansion since linker relaxation (R_RISCV_RELAX) is not implemented. */ op->type = OP_IM32; op->e.v = 0; } else { expect("operand"); }} static void parse_jump_offset_operand(TCCState *s1, Operand *op){ ExprValue e = {0}; 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 >= -0x1000 && (int) op->e.v < 0x1000) op->type = OP_IM12S; } else if (op->e.sym->type.t & (VT_EXTERN | VT_STATIC)) { greloca(cur_text_section, op->e.sym, ind, R_RISCV_JAL, 0); op->type = OP_IM12S; op->e.v = 0; } else { expect("operand"); }} static void parse_operands(TCCState *s1, Operand* ops, int count){ int i; for (i = 0; i < count; i++) { if ( i != 0 ) skip(','); parse_operand(s1, &ops[i]); }} /* parse `X, imm(Y)` to {X, Y, imm} operands */static void parse_mem_access_operands(TCCState *s1, Operand* ops){ Operand op; parse_operand(s1, &ops[0]); skip(','); if ( tok == '(') { /* `X, (Y)` case*/ next(); parse_operand(s1, &ops[1]); skip(')'); ops[2] = zimm; } else { parse_operand(s1, &ops[2]); if ( tok == '('){ /* `X, imm(Y)` case*/ next(); parse_operand(s1, &ops[1]); skip(')'); } else { /* `X, Y` case*/ /* we parsed Y thinking it was imm, swap and default imm to zero */ op = ops[2]; ops[1] = ops[2]; ops[2] = op; ops[2] = zimm; } }} /* This is special: First operand is optional */static void asm_jal_opcode(TCCState *s1, int token){ Operand ops[2]; if (token == TOK_ASM_j ){ ops[0] = zero; // j offset } else if (asm_parse_regvar(tok) == -1) { ops[0] = ra; // jal offset } else { // jal reg, offset parse_operand(s1, &ops[0]); if ( tok == ',') next(); else expect("','"); } parse_jump_offset_operand(s1, &ops[1]); asm_emit_j(token, 0x6f, &ops[0], &ops[1]);} /* This is special: It can be a pseudointruction or a instruction */static void asm_jalr_opcode(TCCState *s1, int token){ Operand ops[3]; Operand op; parse_operand(s1, &ops[0]); if ( tok == ',') next(); else { /* no more operands, it's the pseudoinstruction: * jalr rs * Expand to: * jalr ra, 0(rs) */ asm_emit_i(token, 0x67 | (0 << 12), &ra, &ops[0], &zimm); return; } if ( tok == '(') { /* `X, (Y)` case*/ next(); parse_operand(s1, &ops[1]); skip(')'); ops[2] = zimm; } else { parse_operand(s1, &ops[2]); if ( tok == '('){ /* `X, imm(Y)` case*/ next(); parse_operand(s1, &ops[1]); skip(')'); } else { /* `X, Y` case*/ /* we parsed Y thinking it was imm, swap and default imm to zero */ op = ops[2]; ops[1] = ops[2]; ops[2] = op; ops[2] = zimm; } } /* jalr(RD, RS1, IMM); I-format */ asm_emit_i(token, 0x67 | (0 << 12), &ops[0], &ops[1], &ops[2]);} static void asm_unary_opcode(TCCState *s1, int token){ uint32_t opcode = (0x1C << 2) | 3 | (2 << 12); Operand op; parse_operands(s1, &op, 1); /* Note: Those all map to CSR--so they are pseudo-instructions. */ opcode |= ENCODE_RD(op.reg); switch (token) { /* pseudoinstructions */ case TOK_ASM_rdcycle: asm_emit_opcode(opcode | (0xC00 << 20)); return; case TOK_ASM_rdcycleh: asm_emit_opcode(opcode | (0xC80 << 20)); return; case TOK_ASM_rdtime: asm_emit_opcode(opcode | (0xC01 << 20) | ENCODE_RD(op.reg)); return; case TOK_ASM_rdtimeh: asm_emit_opcode(opcode | (0xC81 << 20) | ENCODE_RD(op.reg)); return; case TOK_ASM_rdinstret: asm_emit_opcode(opcode | (0xC02 << 20) | ENCODE_RD(op.reg)); return; case TOK_ASM_rdinstreth: asm_emit_opcode(opcode | (0xC82 << 20) | ENCODE_RD(op.reg)); return; case TOK_ASM_frflags: asm_emit_opcode(opcode | (0x001 << 20) | ENCODE_RD(op.reg)); return; case TOK_ASM_frrm: asm_emit_opcode(opcode | (0x002 << 20) | ENCODE_RD(op.reg)); return; case TOK_ASM_frcsr: asm_emit_opcode(opcode | (0x003 << 20) | ENCODE_RD(op.reg)); return; case TOK_ASM_jr: /* jalr zero, 0(rs)*/ asm_emit_i(token, 0x67 | (0 << 12), &zero, &op, &zimm); return; case TOK_ASM_call: /* auipc ra, 0 */ greloca(cur_text_section, op.e.sym, ind, R_RISCV_CALL, 0); asm_emit_opcode(3 | (5 << 2) | ENCODE_RD(1)); /* jalr zero, 0(ra) */ asm_emit_opcode(0x67 | (0 << 12) | ENCODE_RS1(1)); return; case TOK_ASM_tail: /* auipc x6, 0 */ greloca(cur_text_section, op.e.sym, ind, R_RISCV_CALL, 0); asm_emit_opcode(3 | (5 << 2) | ENCODE_RD(6)); /* jalr zero, 0(x6) */ asm_emit_opcode(0x67 | (0 << 12) | ENCODE_RS1(6)); return; /* C extension */ 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, &zero); return; case TOK_ASM_c_jr: asm_emit_cr(token, 2 | (8 << 12), &op, &zero); return; default: expect("unary instruction"); }} static void asm_emit_u(int token, uint32_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)); } if (rs2->type != OP_IM12S && rs2->type != OP_IM32) { tcc_error("'%s': Expected second source operand that is an immediate value", get_tok_str(token, NULL)); } else if (rs2->e.v >= 0x100000) { tcc_error("'%s': Expected second source operand that is an immediate value between 0 and 0xfffff", get_tok_str(token, NULL)); } /* U-type instruction: 31...12 imm[31:12] 11...7 rd 6...0 opcode */ gen_le32(opcode | ENCODE_RD(rd->reg) | (rs2->e.v << 12));} static int parse_fence_operand(){ int t = tok; if ( tok == TOK_ASM_or ){ // we are in a fence instruction, parse as output read t = TOK_ASM_or_fence; } next(); return t - (TOK_ASM_w_fence - 1);} static void asm_fence_opcode(TCCState *s1, int token){ // `fence` is both an instruction and a pseudoinstruction: // `fence` expands to `fence iorw, iorw` int succ = 0xF, pred = 0xF; if (tok != TOK_LINEFEED && tok != ';' && tok != CH_EOF){ pred = parse_fence_operand(); if ( pred > 0xF || pred < 0) { tcc_error("'%s': Expected first operand that is a valid predecessor operand", get_tok_str(token, NULL)); } skip(','); succ = parse_fence_operand(); if ( succ > 0xF || succ < 0) { tcc_error("'%s': Expected second operand that is a valid successor operand", get_tok_str(token, NULL)); } } asm_emit_opcode((0x3 << 2) | 3 | (0 << 12) | succ<<20 | pred<<24);}Lines 1–500 of 3059. Load more
/*************************************************************/ /* * RISCV64 assembler for TCC * */ #ifdef TARGET_DEFS_ONLY #define CONFIG_TCC_ASM /* 32 general purpose + 32 floating point registers */ #define NB_ASM_REGS 64 ST_FUNC void g(int c); ST_FUNC void gen_le16(int c); ST_FUNC void gen_le32(int c); /*************************************************************/ #else /*************************************************************/ #define USING_GLOBALS #include "tcc.h" enum { OPT_REG, OPT_IM12S, OPT_IM32, }; // Registers go from 0 to 31. We use next bit to choose general/float #define REG_FLOAT_MASK 0x20 #define REG_IS_FLOAT(register_index) ((register_index) & REG_FLOAT_MASK) #define REG_VALUE(register_index) ((register_index) & (REG_FLOAT_MASK-1)) #define C_ENCODE_RS1(register_index) (REG_VALUE(register_index) << 7) #define C_ENCODE_RS2(register_index) (REG_VALUE(register_index) << 2) #define ENCODE_RD(register_index) (REG_VALUE(register_index) << 7) #define ENCODE_RS1(register_index) (REG_VALUE(register_index) << 15) #define ENCODE_RS2(register_index) (REG_VALUE(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 const Operand zero = { OP_REG, { 0 }}; static const Operand ra = { OP_REG, { 1 }}; static const Operand zimm = { OP_IM12S }; static void asm_binary_opcode(TCCState* s1, int token); ST_FUNC void asm_clobber(uint8_t *clobber_regs, const char *str); ST_FUNC void asm_compute_constraints(ASMOperand *operands, int nb_operands, int nb_outputs, const uint8_t *clobber_regs, int *pout_reg); static void asm_emit_a(int token, uint32_t opcode, const Operand *rs1, const Operand *rs2, const Operand *rd1, int aq, int rl); 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_emit_f(int token, uint32_t opcode, const Operand *rd, const Operand *rs1, const Operand *rs2); static void asm_emit_fb(int token, uint32_t opcode, const Operand *rd, const Operand *rs); static void asm_emit_fq(int token, uint32_t opcode, const Operand *rd, const Operand *rs1, const Operand *rs2, const Operand *rs3); ST_FUNC 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); ST_FUNC void asm_opcode(TCCState *s1, int token); static int asm_parse_csrvar(int t); ST_FUNC 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 asm_branch_opcode(TCCState *s1, int token, int argc); ST_FUNC void gen_expr32(ExprValue *pe); static void parse_operand(TCCState *s1, Operand *op); static void parse_branch_offset_operand(TCCState *s1, Operand *op); static void parse_operands(TCCState *s1, Operand *ops, int count); static void parse_mem_access_operands(TCCState *s1, Operand* ops); ST_FUNC 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) { int ind1; if (nocode_wanted) return; ind1 = ind + 1; if (ind1 > cur_text_section->data_allocated) section_realloc(cur_text_section, ind1); cur_text_section->data[ind] = c; ind = ind1; } ST_FUNC void gen_le16 (int i) { g(i); g(i>>8); } ST_FUNC void gen_le32 (int i) { int ind1; if (nocode_wanted) return; ind1 = ind + 4; if (ind1 > cur_text_section->data_allocated) section_realloc(cur_text_section, ind1); cur_text_section->data[ind++] = i & 0xFF; cur_text_section->data[ind++] = (i >> 8) & 0xFF; cur_text_section->data[ind++] = (i >> 16) & 0xFF; cur_text_section->data[ind++] = (i >> 24) & 0xFF; } ST_FUNC void gen_expr32(ExprValue *pe) { gen_le32(pe->v); } static void asm_emit_opcode(uint32_t opcode) { gen_le32(opcode); } static void asm_nullary_opcode(TCCState *s1, int token) { switch (token) { // Sync instructions case TOK_ASM_fence_i: // I asm_emit_opcode((0x3 << 2) | 3| (1 << 12)); return; // System calls case TOK_ASM_ecall: // I (pseudo) asm_emit_opcode((0x1C << 2) | 3 | (0 << 12)); return; case TOK_ASM_ebreak: // I (pseudo) asm_emit_opcode((0x1C << 2) | 3 | (0 << 12) | (1 << 20)); return; // Other case TOK_ASM_nop: asm_emit_i(token, (4 << 2) | 3, &zero, &zero, &zimm); return; case TOK_ASM_wfi: asm_emit_opcode((0x1C << 2) | 3 | (0x105 << 20)); return; /* Pseudoinstructions */ case TOK_ASM_ret: /* jalr zero, x1, 0 */ asm_emit_opcode( 0x67 | (0 << 12) | ENCODE_RS1(1) ); return; /* C extension */ case TOK_ASM_c_ebreak: asm_emit_cr(token, 2 | (9 << 12), &zero, &zero); return; case TOK_ASM_c_nop: asm_emit_ci(token, 1, &zero, &zimm); return; default: expect("nullary instruction"); } } /* Parse a text containing operand and store the result in OP */ static void parse_operand(TCCState *s1, Operand *op) { ExprValue e = {0}; Sym label = {0}; int8_t reg; op->type = 0; if ((reg = asm_parse_regvar(tok)) != -1) { next(); // skip register name op->type = OP_REG; op->reg = (uint8_t) reg; return; } else if (tok == '$') { /* constant value */ next(); // skip '#' or '$' } else if ((e.v = asm_parse_csrvar(tok)) != -1) { next(); } else { 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 >= -0x1000 && (int) op->e.v < 0x1000) op->type = OP_IM12S; } else if (op->e.sym->type.t & (VT_EXTERN | VT_STATIC)) { /* see also: "RISC-V ABIs Specification" V1.0 section 5.2 recommends using a GOT for "possibly-undefined weak symbols" section 5.3: "Medium position independent code model" if this is a non-local symbol: use a GOT non-local: outside of a pc-relative +- 2 GiB range */ label.type.t = VT_VOID | VT_STATIC; /* use the medium PIC model: GOT, auipc, lw */ if (op->e.sym->type.t & VT_STATIC) greloca(cur_text_section, op->e.sym, ind, R_RISCV_PCREL_HI20, 0); else greloca(cur_text_section, op->e.sym, ind, R_RISCV_GOT_HI20, 0); put_extern_sym(&label, cur_text_section, ind, 0); greloca(cur_text_section, &label, ind+4, R_RISCV_PCREL_LO12_I, 0); op->type = OP_IM12S; op->e.v = 0; } else { expect("operand"); } } static void parse_branch_offset_operand(TCCState *s1, Operand *op){ ExprValue e = {0}; 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 >= -0x1000 && (int) op->e.v < 0x1000) op->type = OP_IM12S; } else if (op->e.sym->type.t & (VT_EXTERN | VT_STATIC)) { /* For extern/static symbols, always use far-branch expansion since linker relaxation (R_RISCV_RELAX) is not implemented. */ op->type = OP_IM32; op->e.v = 0; } else { expect("operand"); } } static void parse_jump_offset_operand(TCCState *s1, Operand *op){ ExprValue e = {0}; 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 >= -0x1000 && (int) op->e.v < 0x1000) op->type = OP_IM12S; } else if (op->e.sym->type.t & (VT_EXTERN | VT_STATIC)) { greloca(cur_text_section, op->e.sym, ind, R_RISCV_JAL, 0); op->type = OP_IM12S; op->e.v = 0; } else { expect("operand"); } } static void parse_operands(TCCState *s1, Operand* ops, int count){ int i; for (i = 0; i < count; i++) { if ( i != 0 ) skip(','); parse_operand(s1, &ops[i]); } } /* parse `X, imm(Y)` to {X, Y, imm} operands */ static void parse_mem_access_operands(TCCState *s1, Operand* ops){ Operand op; parse_operand(s1, &ops[0]); skip(','); if ( tok == '(') { /* `X, (Y)` case*/ next(); parse_operand(s1, &ops[1]); skip(')'); ops[2] = zimm; } else { parse_operand(s1, &ops[2]); if ( tok == '('){ /* `X, imm(Y)` case*/ next(); parse_operand(s1, &ops[1]); skip(')'); } else { /* `X, Y` case*/ /* we parsed Y thinking it was imm, swap and default imm to zero */ op = ops[2]; ops[1] = ops[2]; ops[2] = op; ops[2] = zimm; } } } /* This is special: First operand is optional */ static void asm_jal_opcode(TCCState *s1, int token){ Operand ops[2]; if (token == TOK_ASM_j ){ ops[0] = zero; // j offset } else if (asm_parse_regvar(tok) == -1) { ops[0] = ra; // jal offset } else { // jal reg, offset parse_operand(s1, &ops[0]); if ( tok == ',') next(); else expect("','"); } parse_jump_offset_operand(s1, &ops[1]); asm_emit_j(token, 0x6f, &ops[0], &ops[1]); } /* This is special: It can be a pseudointruction or a instruction */ static void asm_jalr_opcode(TCCState *s1, int token){ Operand ops[3]; Operand op; parse_operand(s1, &ops[0]); if ( tok == ',') next(); else { /* no more operands, it's the pseudoinstruction: * jalr rs * Expand to: * jalr ra, 0(rs) */ asm_emit_i(token, 0x67 | (0 << 12), &ra, &ops[0], &zimm); return; } if ( tok == '(') { /* `X, (Y)` case*/ next(); parse_operand(s1, &ops[1]); skip(')'); ops[2] = zimm; } else { parse_operand(s1, &ops[2]); if ( tok == '('){ /* `X, imm(Y)` case*/ next(); parse_operand(s1, &ops[1]); skip(')'); } else { /* `X, Y` case*/ /* we parsed Y thinking it was imm, swap and default imm to zero */ op = ops[2]; ops[1] = ops[2]; ops[2] = op; ops[2] = zimm; } } /* jalr(RD, RS1, IMM); I-format */ asm_emit_i(token, 0x67 | (0 << 12), &ops[0], &ops[1], &ops[2]); } static void asm_unary_opcode(TCCState *s1, int token) { uint32_t opcode = (0x1C << 2) | 3 | (2 << 12); Operand op; parse_operands(s1, &op, 1); /* Note: Those all map to CSR--so they are pseudo-instructions. */ opcode |= ENCODE_RD(op.reg); switch (token) { /* pseudoinstructions */ case TOK_ASM_rdcycle: asm_emit_opcode(opcode | (0xC00 << 20)); return; case TOK_ASM_rdcycleh: asm_emit_opcode(opcode | (0xC80 << 20)); return; case TOK_ASM_rdtime: asm_emit_opcode(opcode | (0xC01 << 20) | ENCODE_RD(op.reg)); return; case TOK_ASM_rdtimeh: asm_emit_opcode(opcode | (0xC81 << 20) | ENCODE_RD(op.reg)); return; case TOK_ASM_rdinstret: asm_emit_opcode(opcode | (0xC02 << 20) | ENCODE_RD(op.reg)); return; case TOK_ASM_rdinstreth: asm_emit_opcode(opcode | (0xC82 << 20) | ENCODE_RD(op.reg)); return; case TOK_ASM_frflags: asm_emit_opcode(opcode | (0x001 << 20) | ENCODE_RD(op.reg)); return; case TOK_ASM_frrm: asm_emit_opcode(opcode | (0x002 << 20) | ENCODE_RD(op.reg)); return; case TOK_ASM_frcsr: asm_emit_opcode(opcode | (0x003 << 20) | ENCODE_RD(op.reg)); return; case TOK_ASM_jr: /* jalr zero, 0(rs)*/ asm_emit_i(token, 0x67 | (0 << 12), &zero, &op, &zimm); return; case TOK_ASM_call: /* auipc ra, 0 */ greloca(cur_text_section, op.e.sym, ind, R_RISCV_CALL, 0); asm_emit_opcode(3 | (5 << 2) | ENCODE_RD(1)); /* jalr zero, 0(ra) */ asm_emit_opcode(0x67 | (0 << 12) | ENCODE_RS1(1)); return; case TOK_ASM_tail: /* auipc x6, 0 */ greloca(cur_text_section, op.e.sym, ind, R_RISCV_CALL, 0); asm_emit_opcode(3 | (5 << 2) | ENCODE_RD(6)); /* jalr zero, 0(x6) */ asm_emit_opcode(0x67 | (0 << 12) | ENCODE_RS1(6)); return; /* C extension */ 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, &zero); return; case TOK_ASM_c_jr: asm_emit_cr(token, 2 | (8 << 12), &op, &zero); return; default: expect("unary instruction"); } } static void asm_emit_u(int token, uint32_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)); } if (rs2->type != OP_IM12S && rs2->type != OP_IM32) { tcc_error("'%s': Expected second source operand that is an immediate value", get_tok_str(token, NULL)); } else if (rs2->e.v >= 0x100000) { tcc_error("'%s': Expected second source operand that is an immediate value between 0 and 0xfffff", get_tok_str(token, NULL)); } /* U-type instruction: 31...12 imm[31:12] 11...7 rd 6...0 opcode */ gen_le32(opcode | ENCODE_RD(rd->reg) | (rs2->e.v << 12)); } static int parse_fence_operand(){ int t = tok; if ( tok == TOK_ASM_or ){ // we are in a fence instruction, parse as output read t = TOK_ASM_or_fence; } next(); return t - (TOK_ASM_w_fence - 1); } static void asm_fence_opcode(TCCState *s1, int token){ // `fence` is both an instruction and a pseudoinstruction: // `fence` expands to `fence iorw, iorw` int succ = 0xF, pred = 0xF; if (tok != TOK_LINEFEED && tok != ';' && tok != CH_EOF){ pred = parse_fence_operand(); if ( pred > 0xF || pred < 0) { tcc_error("'%s': Expected first operand that is a valid predecessor operand", get_tok_str(token, NULL)); } skip(','); succ = parse_fence_operand(); if ( succ > 0xF || succ < 0) { tcc_error("'%s': Expected second operand that is a valid successor operand", get_tok_str(token, NULL)); } } asm_emit_opcode((0x3 << 2) | 3 | (0 << 12) | succ<<20 | pred<<24); }