/* tc-riscv.c -- RISC-V assembler Copyright 2011-2016 Free Software Foundation, Inc. Contributed by Andrew Waterman (andrew@sifive.com). Based on MIPS target. This file is part of GAS. GAS is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GAS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING3. If not, see . */ #include "as.h" #include "config.h" #include "subsegs.h" #include "safe-ctype.h" #include "itbl-ops.h" #include "dwarf2dbg.h" #include "dw2gencfi.h" #include "elf/riscv.h" #include "opcode/riscv.h" #include /* Information about an instruction, including its format, operands and fixups. */ struct riscv_cl_insn { /* The opcode's entry in riscv_opcodes. */ const struct riscv_opcode *insn_mo; /* The encoded instruction bits. */ insn_t insn_opcode; /* The frag that contains the instruction. */ struct frag *frag; /* The offset into FRAG of the first instruction byte. */ long where; /* The relocs associated with the instruction, if any. */ fixS *fixp; }; #ifndef DEFAULT_ARCH #define DEFAULT_ARCH "riscv64" #endif static const char default_arch[] = DEFAULT_ARCH; unsigned xlen = 0; /* width of an x-register */ #define LOAD_ADDRESS_INSN (xlen == 64 ? "ld" : "lw") #define ADD32_INSN (xlen == 64 ? "addiw" : "addi") static unsigned elf_flags = 0; /* This is the set of options which the .option pseudo-op may modify. */ struct riscv_set_options { int pic; /* Generate position-independent code. */ int rvc; /* Generate RVC code. */ }; static struct riscv_set_options riscv_opts = { 0, /* pic */ 0, /* rvc */ }; static void riscv_set_rvc (bfd_boolean rvc_value) { if (rvc_value) elf_flags |= EF_RISCV_RVC; riscv_opts.rvc = rvc_value; } struct riscv_subset { const char *name; struct riscv_subset *next; }; static struct riscv_subset *riscv_subsets; static bfd_boolean riscv_subset_supports (const char *feature) { struct riscv_subset *s; char *p; unsigned xlen_required = strtoul (feature, &p, 10); if (xlen_required && xlen != xlen_required) return FALSE; for (s = riscv_subsets; s != NULL; s = s->next) if (strcasecmp (s->name, p) == 0) return TRUE; return FALSE; } static void riscv_add_subset (const char *subset) { struct riscv_subset *s = xmalloc (sizeof *s); s->name = xstrdup (subset); s->next = riscv_subsets; riscv_subsets = s; } /* Set which ISA and extensions are available. Formally, ISA strings must begin with RV32 or RV64, but we allow the prefix to be omitted. FIXME: Version numbers are not supported yet. */ static void riscv_set_arch (const char *p) { const char *all_subsets = "IMAFDC"; const char *extension = NULL; int rvc = 0; int i; if (strncasecmp (p, "RV32", 4) == 0) { xlen = 32; p += 4; } else if (strncasecmp (p, "RV64", 4) == 0) { xlen = 64; p += 4; } else if (strncasecmp (p, "RV", 2) == 0) p += 2; switch (TOUPPER(*p)) { case 'I': break; case 'G': p++; /* Fall through. */ case '\0': for (i = 0; all_subsets[i] != '\0'; i++) { const char subset[] = {all_subsets[i], '\0'}; riscv_add_subset (subset); } break; default: as_fatal ("`I' must be the first ISA subset name specified (got %c)", *p); } while (*p) { if (TOUPPER(*p) == 'X') { char *subset = xstrdup (p), *q = subset; while (*++q != '\0' && *q != '_') ; *q = '\0'; if (extension) as_fatal ("only one eXtension is supported (found %s and %s)", extension, subset); extension = subset; riscv_add_subset (subset); p += strlen (subset); free (subset); } else if (*p == '_') p++; else if ((all_subsets = strchr (all_subsets, *p)) != NULL) { const char subset[] = {*p, 0}; riscv_add_subset (subset); if (TOUPPER(*p) == 'C') rvc = 1; all_subsets++; p++; } else as_fatal ("unsupported ISA subset %c", *p); } if (rvc) { /* Override -m[no-]rvc setting if C was explicitly listed. */ riscv_set_rvc (TRUE); } else { /* Add RVC anyway. -m[no-]rvc toggles its availability. */ riscv_add_subset ("C"); } } /* Handle of the OPCODE hash table. */ static struct hash_control *op_hash = NULL; /* This array holds the chars that always start a comment. If the pre-processor is disabled, these aren't very useful */ const char comment_chars[] = "#"; /* This array holds the chars that only start a comment at the beginning of a line. If the line seems to have the form '# 123 filename' .line and .file directives will appear in the pre-processed output */ /* Note that input_file.c hand checks for '#' at the beginning of the first line of the input file. This is because the compiler outputs #NO_APP at the beginning of its output. */ /* Also note that C style comments are always supported. */ const char line_comment_chars[] = "#"; /* This array holds machine specific line separator characters. */ const char line_separator_chars[] = ";"; /* Chars that can be used to separate mant from exp in floating point nums */ const char EXP_CHARS[] = "eE"; /* Chars that mean this number is a floating point constant */ /* As in 0f12.456 */ /* or 0d1.2345e12 */ const char FLT_CHARS[] = "rRsSfFdDxXpP"; /* Macros for encoding relaxation state for RVC branches and far jumps. */ #define RELAX_BRANCH_ENCODE(uncond, rvc, length) \ ((relax_substateT) \ (0xc0000000 \ | ((uncond) ? 1 : 0) \ | ((rvc) ? 2 : 0) \ | ((length) << 2))) #define RELAX_BRANCH_P(i) (((i) & 0xf0000000) == 0xc0000000) #define RELAX_BRANCH_LENGTH(i) (((i) >> 2) & 0xF) #define RELAX_BRANCH_RVC(i) (((i) & 2) != 0) #define RELAX_BRANCH_UNCOND(i) (((i) & 1) != 0) /* Is the given value a sign-extended 32-bit value? */ #define IS_SEXT_32BIT_NUM(x) \ (((x) &~ (offsetT) 0x7fffffff) == 0 \ || (((x) &~ (offsetT) 0x7fffffff) == ~ (offsetT) 0x7fffffff)) /* Is the given value a zero-extended 32-bit value? Or a negated one? */ #define IS_ZEXT_32BIT_NUM(x) \ (((x) &~ (offsetT) 0xffffffff) == 0 \ || (((x) &~ (offsetT) 0xffffffff) == ~ (offsetT) 0xffffffff)) /* Change INSN's opcode so that the operand given by FIELD has value VALUE. INSN is a riscv_cl_insn structure and VALUE is evaluated exactly once. */ #define INSERT_OPERAND(FIELD, INSN, VALUE) \ INSERT_BITS ((INSN).insn_opcode, VALUE, OP_MASK_##FIELD, OP_SH_##FIELD) /* Determine if an instruction matches an opcode. */ #define OPCODE_MATCHES(OPCODE, OP) \ (((OPCODE) & MASK_##OP) == MATCH_##OP) static char *expr_end; /* The default target format to use. */ const char * riscv_target_format (void) { return xlen == 64 ? "elf64-littleriscv" : "elf32-littleriscv"; } /* Return the length of instruction INSN. */ static inline unsigned int insn_length (const struct riscv_cl_insn *insn) { return riscv_insn_length (insn->insn_opcode); } /* Initialise INSN from opcode entry MO. Leave its position unspecified. */ static void create_insn (struct riscv_cl_insn *insn, const struct riscv_opcode *mo) { insn->insn_mo = mo; insn->insn_opcode = mo->match; insn->frag = NULL; insn->where = 0; insn->fixp = NULL; } /* Install INSN at the location specified by its "frag" and "where" fields. */ static void install_insn (const struct riscv_cl_insn *insn) { char *f = insn->frag->fr_literal + insn->where; md_number_to_chars (f, insn->insn_opcode, insn_length (insn)); } /* Move INSN to offset WHERE in FRAG. Adjust the fixups accordingly and install the opcode in the new location. */ static void move_insn (struct riscv_cl_insn *insn, fragS *frag, long where) { insn->frag = frag; insn->where = where; if (insn->fixp != NULL) { insn->fixp->fx_frag = frag; insn->fixp->fx_where = where; } install_insn (insn); } /* Add INSN to the end of the output. */ static void add_fixed_insn (struct riscv_cl_insn *insn) { char *f = frag_more (insn_length (insn)); move_insn (insn, frag_now, f - frag_now->fr_literal); } static void add_relaxed_insn (struct riscv_cl_insn *insn, int max_chars, int var, relax_substateT subtype, symbolS *symbol, offsetT offset) { frag_grow (max_chars); move_insn (insn, frag_now, frag_more (0) - frag_now->fr_literal); frag_var (rs_machine_dependent, max_chars, var, subtype, symbol, offset, NULL); } /* Compute the length of a branch sequence, and adjust the stored length accordingly. If FRAGP is NULL, the worst-case length is returned. */ static unsigned relaxed_branch_length (fragS *fragp, asection *sec, int update) { int jump, rvc, length = 8; if (!fragp) return length; jump = RELAX_BRANCH_UNCOND (fragp->fr_subtype); rvc = RELAX_BRANCH_RVC (fragp->fr_subtype); length = RELAX_BRANCH_LENGTH (fragp->fr_subtype); /* Assume jumps are in range; the linker will catch any that aren't. */ length = jump ? 4 : 8; if (fragp->fr_symbol != NULL && S_IS_DEFINED (fragp->fr_symbol) && sec == S_GET_SEGMENT (fragp->fr_symbol)) { offsetT val = S_GET_VALUE (fragp->fr_symbol) + fragp->fr_offset; bfd_vma rvc_range = jump ? RVC_JUMP_REACH : RVC_BRANCH_REACH; val -= fragp->fr_address + fragp->fr_fix; if (rvc && (bfd_vma)(val + rvc_range/2) < rvc_range) length = 2; else if ((bfd_vma)(val + RISCV_BRANCH_REACH/2) < RISCV_BRANCH_REACH) length = 4; else if (!jump && rvc) length = 6; } if (update) fragp->fr_subtype = RELAX_BRANCH_ENCODE (jump, rvc, length); return length; } struct regname { const char *name; unsigned int num; }; enum reg_class { RCLASS_GPR, RCLASS_FPR, RCLASS_CSR, RCLASS_MAX }; static struct hash_control *reg_names_hash = NULL; #define ENCODE_REG_HASH(cls, n) \ ((void *)(uintptr_t)((n) * RCLASS_MAX + (cls) + 1)) #define DECODE_REG_CLASS(hash) (((uintptr_t)(hash) - 1) % RCLASS_MAX) #define DECODE_REG_NUM(hash) (((uintptr_t)(hash) - 1) / RCLASS_MAX) static void hash_reg_name (enum reg_class class, const char *name, unsigned n) { void *hash = ENCODE_REG_HASH (class, n); const char *retval = hash_insert (reg_names_hash, name, hash); if (retval != NULL) as_fatal (_("internal error: can't hash `%s': %s"), name, retval); } static void hash_reg_names (enum reg_class class, const char * const names[], unsigned n) { unsigned i; for (i = 0; i < n; i++) hash_reg_name (class, names[i], i); } static unsigned int reg_lookup_internal (const char *s, enum reg_class class) { struct regname *r = (struct regname *) hash_find (reg_names_hash, s); if (r == NULL || DECODE_REG_CLASS (r) != class) return -1; return DECODE_REG_NUM (r); } static bfd_boolean reg_lookup (char **s, enum reg_class class, unsigned int *regnop) { char *e; char save_c; int reg = -1; /* Find end of name. */ e = *s; if (is_name_beginner (*e)) ++e; while (is_part_of_name (*e)) ++e; /* Terminate name. */ save_c = *e; *e = '\0'; /* Look for the register. Advance to next token if one was recognized. */ if ((reg = reg_lookup_internal (*s, class)) >= 0) *s = e; *e = save_c; if (regnop) *regnop = reg; return reg >= 0; } static bfd_boolean arg_lookup (char **s, const char *const *array, size_t size, unsigned *regnop) { const char *p = strchr (*s, ','); size_t i, len = p ? (size_t)(p - *s) : strlen (*s); for (i = 0; i < size; i++) if (array[i] != NULL && strncmp (array[i], *s, len) == 0) { *regnop = i; *s += len; return TRUE; } return FALSE; } /* For consistency checking, verify that all bits are specified either by the match/mask part of the instruction definition, or by the operand list. */ static bfd_boolean validate_riscv_insn (const struct riscv_opcode *opc) { const char *p = opc->args; char c; insn_t used_bits = opc->mask; int insn_width = 8 * riscv_insn_length (opc->match); insn_t required_bits = ~0ULL >> (64 - insn_width); if ((used_bits & opc->match) != (opc->match & required_bits)) { as_bad (_("internal: bad RISC-V opcode (mask error): %s %s"), opc->name, opc->args); return FALSE; } #define USE_BITS(mask,shift) (used_bits |= ((insn_t)(mask) << (shift))) while (*p) switch (c = *p++) { case 'C': /* RVC */ switch (c = *p++) { case 'a': used_bits |= ENCODE_RVC_J_IMM(-1U); break; case 'c': break; /* RS1, constrained to equal sp */ case 'i': used_bits |= ENCODE_RVC_SIMM3(-1U); break; case 'j': used_bits |= ENCODE_RVC_IMM(-1U); break; case 'k': used_bits |= ENCODE_RVC_LW_IMM(-1U); break; case 'l': used_bits |= ENCODE_RVC_LD_IMM(-1U); break; case 'm': used_bits |= ENCODE_RVC_LWSP_IMM(-1U); break; case 'n': used_bits |= ENCODE_RVC_LDSP_IMM(-1U); break; case 'p': used_bits |= ENCODE_RVC_B_IMM(-1U); break; case 's': USE_BITS (OP_MASK_CRS1S, OP_SH_CRS1S); break; case 't': USE_BITS (OP_MASK_CRS2S, OP_SH_CRS2S); break; case 'u': used_bits |= ENCODE_RVC_IMM(-1U); break; case 'v': used_bits |= ENCODE_RVC_IMM(-1U); break; case 'w': break; /* RS1S, constrained to equal RD */ case 'x': break; /* RS2S, constrained to equal RD */ case 'K': used_bits |= ENCODE_RVC_ADDI4SPN_IMM(-1U); break; case 'L': used_bits |= ENCODE_RVC_ADDI16SP_IMM(-1U); break; case 'M': used_bits |= ENCODE_RVC_SWSP_IMM(-1U); break; case 'N': used_bits |= ENCODE_RVC_SDSP_IMM(-1U); break; case 'U': break; /* RS1, constrained to equal RD */ case 'V': USE_BITS (OP_MASK_CRS2, OP_SH_CRS2); break; case '<': used_bits |= ENCODE_RVC_IMM(-1U); break; case '>': used_bits |= ENCODE_RVC_IMM(-1U); break; case 'T': USE_BITS (OP_MASK_CRS2, OP_SH_CRS2); break; case 'D': USE_BITS (OP_MASK_CRS2S, OP_SH_CRS2S); break; default: as_bad (_("internal: bad RISC-V opcode (unknown operand type `C%c'): %s %s"), c, opc->name, opc->args); return FALSE; } break; case ',': break; case '(': break; case ')': break; case '<': USE_BITS (OP_MASK_SHAMTW, OP_SH_SHAMTW); break; case '>': USE_BITS (OP_MASK_SHAMT, OP_SH_SHAMT); break; case 'A': break; case 'D': USE_BITS (OP_MASK_RD, OP_SH_RD); break; case 'Z': USE_BITS (OP_MASK_RS1, OP_SH_RS1); break; case 'E': USE_BITS (OP_MASK_CSR, OP_SH_CSR); break; case 'I': break; case 'R': USE_BITS (OP_MASK_RS3, OP_SH_RS3); break; case 'S': USE_BITS (OP_MASK_RS1, OP_SH_RS1); break; case 'U': USE_BITS (OP_MASK_RS1, OP_SH_RS1); /* fallthru */ case 'T': USE_BITS (OP_MASK_RS2, OP_SH_RS2); break; case 'd': USE_BITS (OP_MASK_RD, OP_SH_RD); break; case 'm': USE_BITS (OP_MASK_RM, OP_SH_RM); break; case 's': USE_BITS (OP_MASK_RS1, OP_SH_RS1); break; case 't': USE_BITS (OP_MASK_RS2, OP_SH_RS2); break; case 'P': USE_BITS (OP_MASK_PRED, OP_SH_PRED); break; case 'Q': USE_BITS (OP_MASK_SUCC, OP_SH_SUCC); break; case 'o': case 'j': used_bits |= ENCODE_ITYPE_IMM(-1U); break; case 'a': used_bits |= ENCODE_UJTYPE_IMM(-1U); break; case 'p': used_bits |= ENCODE_SBTYPE_IMM(-1U); break; case 'q': used_bits |= ENCODE_STYPE_IMM(-1U); break; case 'u': used_bits |= ENCODE_UTYPE_IMM(-1U); break; case '[': break; case ']': break; case '0': break; default: as_bad (_("internal: bad RISC-V opcode " "(unknown operand type `%c'): %s %s"), c, opc->name, opc->args); return FALSE; } #undef USE_BITS if (used_bits != required_bits) { as_bad (_("internal: bad RISC-V opcode (bits 0x%lx undefined): %s %s"), ~(unsigned long)(used_bits & required_bits), opc->name, opc->args); return FALSE; } return TRUE; } struct percent_op_match { const char *str; bfd_reloc_code_real_type reloc; }; /* This function is called once, at assembler startup time. It should set up all the tables, etc. that the MD part of the assembler will need. */ void md_begin (void) { int i = 0; if (! bfd_set_arch_mach (stdoutput, bfd_arch_riscv, 0)) as_warn (_("Could not set architecture and machine")); op_hash = hash_new (); while (riscv_opcodes[i].name) { const char *name = riscv_opcodes[i].name; const char *hash_error = hash_insert (op_hash, name, (void *) &riscv_opcodes[i]); if (hash_error) { fprintf (stderr, _("internal error: can't hash `%s': %s\n"), riscv_opcodes[i].name, hash_error); /* Probably a memory allocation problem? Give up now. */ as_fatal (_("Broken assembler. No assembly attempted.")); } do { if (riscv_opcodes[i].pinfo != INSN_MACRO) { if (!validate_riscv_insn (&riscv_opcodes[i])) as_fatal (_("Broken assembler. No assembly attempted.")); } ++i; } while (riscv_opcodes[i].name && !strcmp (riscv_opcodes[i].name, name)); } reg_names_hash = hash_new (); hash_reg_names (RCLASS_GPR, riscv_gpr_names_numeric, NGPR); hash_reg_names (RCLASS_GPR, riscv_gpr_names_abi, NGPR); hash_reg_names (RCLASS_FPR, riscv_fpr_names_numeric, NFPR); hash_reg_names (RCLASS_FPR, riscv_fpr_names_abi, NFPR); #define DECLARE_CSR(name, num) hash_reg_name (RCLASS_CSR, #name, num); #include "opcode/riscv-opc.h" #undef DECLARE_CSR /* Set the default alignment for the text section. */ record_alignment (text_section, riscv_opts.rvc ? 1 : 2); } /* Output an instruction. IP is the instruction information. ADDRESS_EXPR is an operand of the instruction to be used with RELOC_TYPE. */ static void append_insn (struct riscv_cl_insn *ip, expressionS *address_expr, bfd_reloc_code_real_type reloc_type) { dwarf2_emit_insn (0); if (reloc_type != BFD_RELOC_UNUSED) { reloc_howto_type *howto; gas_assert(address_expr); if (reloc_type == BFD_RELOC_12_PCREL || reloc_type == BFD_RELOC_RISCV_JMP) { int j = reloc_type == BFD_RELOC_RISCV_JMP; int best_case = riscv_insn_length (ip->insn_opcode); unsigned worst_case = relaxed_branch_length (NULL, NULL, 0); add_relaxed_insn (ip, worst_case, best_case, RELAX_BRANCH_ENCODE (j, best_case == 2, worst_case), address_expr->X_add_symbol, address_expr->X_add_number); return; } else if (address_expr->X_op == O_constant) { switch (reloc_type) { case BFD_RELOC_32: ip->insn_opcode |= address_expr->X_add_number; goto append; case BFD_RELOC_RISCV_HI20: { insn_t imm = RISCV_CONST_HIGH_PART (address_expr->X_add_number); ip->insn_opcode |= ENCODE_UTYPE_IMM (imm); goto append; } case BFD_RELOC_RISCV_LO12_S: ip->insn_opcode |= ENCODE_STYPE_IMM (address_expr->X_add_number); goto append; case BFD_RELOC_RISCV_LO12_I: ip->insn_opcode |= ENCODE_ITYPE_IMM (address_expr->X_add_number); goto append; default: break; } } howto = bfd_reloc_type_lookup (stdoutput, reloc_type); if (howto == NULL) as_bad (_("Unsupported RISC-V relocation number %d"), reloc_type); ip->fixp = fix_new_exp (ip->frag, ip->where, bfd_get_reloc_size (howto), address_expr, FALSE, reloc_type); } append: add_fixed_insn (ip); install_insn (ip); } /* Build an instruction created by a macro expansion. This is passed a pointer to the count of instructions created so far, an expression, the name of the instruction to build, an operand format string, and corresponding arguments. */ static void macro_build (expressionS *ep, const char *name, const char *fmt, ...) { const struct riscv_opcode *mo; struct riscv_cl_insn insn; bfd_reloc_code_real_type r; va_list args; va_start (args, fmt); r = BFD_RELOC_UNUSED; mo = (struct riscv_opcode *) hash_find (op_hash, name); gas_assert (mo); /* Find a non-RVC variant of the instruction. append_insn will compress it if possible. */ while (riscv_insn_length (mo->match) < 4) mo++; gas_assert (strcmp (name, mo->name) == 0); create_insn (&insn, mo); for (;;) { switch (*fmt++) { case 'd': INSERT_OPERAND (RD, insn, va_arg (args, int)); continue; case 's': INSERT_OPERAND (RS1, insn, va_arg (args, int)); continue; case 't': INSERT_OPERAND (RS2, insn, va_arg (args, int)); continue; case '>': INSERT_OPERAND (SHAMT, insn, va_arg (args, int)); continue; case 'j': case 'u': case 'q': gas_assert (ep != NULL); r = va_arg (args, int); continue; case '\0': break; case ',': continue; default: as_fatal (_("internal error: invalid macro")); } break; } va_end (args); gas_assert (r == BFD_RELOC_UNUSED ? ep == NULL : ep != NULL); append_insn (&insn, ep, r); } /* Sign-extend 32-bit mode constants that have bit 31 set and all higher bits unset. */ static void normalize_constant_expr (expressionS *ex) { if (xlen > 32) return; if ((ex->X_op == O_constant || ex->X_op == O_symbol) && IS_ZEXT_32BIT_NUM (ex->X_add_number)) ex->X_add_number = (((ex->X_add_number & 0xffffffff) ^ 0x80000000) - 0x80000000); } /* Fail if an expression is not a constant. */ static void check_absolute_expr (struct riscv_cl_insn *ip, expressionS *ex) { if (ex->X_op == O_big) as_bad (_("unsupported large constant")); else if (ex->X_op != O_constant) as_bad (_("Instruction %s requires absolute expression"), ip->insn_mo->name); normalize_constant_expr (ex); } static symbolS * make_internal_label (void) { return (symbolS *) local_symbol_make (FAKE_LABEL_NAME, now_seg, (valueT) frag_now_fix(), frag_now); } /* Load an entry from the GOT. */ static void pcrel_access (int destreg, int tempreg, expressionS *ep, const char *lo_insn, const char *lo_pattern, bfd_reloc_code_real_type hi_reloc, bfd_reloc_code_real_type lo_reloc) { expressionS ep2; ep2.X_op = O_symbol; ep2.X_add_symbol = make_internal_label (); ep2.X_add_number = 0; macro_build (ep, "auipc", "d,u", tempreg, hi_reloc); macro_build (&ep2, lo_insn, lo_pattern, destreg, tempreg, lo_reloc); } static void pcrel_load (int destreg, int tempreg, expressionS *ep, const char *lo_insn, bfd_reloc_code_real_type hi_reloc, bfd_reloc_code_real_type lo_reloc) { pcrel_access (destreg, tempreg, ep, lo_insn, "d,s,j", hi_reloc, lo_reloc); } static void pcrel_store (int srcreg, int tempreg, expressionS *ep, const char *lo_insn, bfd_reloc_code_real_type hi_reloc, bfd_reloc_code_real_type lo_reloc) { pcrel_access (srcreg, tempreg, ep, lo_insn, "t,s,q", hi_reloc, lo_reloc); } /* PC-relative function call using AUIPC/JALR, relaxed to JAL. */ static void riscv_call (int destreg, int tempreg, expressionS *ep, bfd_reloc_code_real_type reloc) { macro_build (ep, "auipc", "d,u", tempreg, reloc); macro_build (NULL, "jalr", "d,s", destreg, tempreg); } /* Load an integer constant into a register. */ static void load_const (int reg, expressionS *ep) { int shift = RISCV_IMM_BITS; expressionS upper = *ep, lower = *ep; lower.X_add_number = (int32_t) ep->X_add_number << (32-shift) >> (32-shift); upper.X_add_number -= lower.X_add_number; if (ep->X_op != O_constant) { as_bad (_("unsupported large constant")); return; } if (xlen > 32 && !IS_SEXT_32BIT_NUM(ep->X_add_number)) { /* Reduce to a signed 32-bit constant using SLLI and ADDI. */ while (((upper.X_add_number >> shift) & 1) == 0) shift++; upper.X_add_number = (int64_t) upper.X_add_number >> shift; load_const(reg, &upper); macro_build (NULL, "slli", "d,s,>", reg, reg, shift); if (lower.X_add_number != 0) macro_build (&lower, "addi", "d,s,j", reg, reg, BFD_RELOC_RISCV_LO12_I); } else { /* Simply emit LUI and/or ADDI to build a 32-bit signed constant. */ int hi_reg = 0; if (upper.X_add_number != 0) { macro_build (ep, "lui", "d,u", reg, BFD_RELOC_RISCV_HI20); hi_reg = reg; } if (lower.X_add_number != 0 || hi_reg == 0) macro_build (ep, ADD32_INSN, "d,s,j", reg, hi_reg, BFD_RELOC_RISCV_LO12_I); } } /* Expand RISC-V assembly macros into one or more instructions. */ static void macro (struct riscv_cl_insn *ip, expressionS *imm_expr, bfd_reloc_code_real_type *imm_reloc) { int rd = (ip->insn_opcode >> OP_SH_RD) & OP_MASK_RD; int rs1 = (ip->insn_opcode >> OP_SH_RS1) & OP_MASK_RS1; int rs2 = (ip->insn_opcode >> OP_SH_RS2) & OP_MASK_RS2; int mask = ip->insn_mo->mask; switch (mask) { case M_LI: load_const (rd, imm_expr); break; case M_LA: case M_LLA: /* Load the address of a symbol into a register. */ if (!IS_SEXT_32BIT_NUM (imm_expr->X_add_number)) as_bad (_("offset too large")); if (imm_expr->X_op == O_constant) load_const (rd, imm_expr); else if (riscv_opts.pic && mask == M_LA) /* Global PIC symbol */ pcrel_load (rd, rd, imm_expr, LOAD_ADDRESS_INSN, BFD_RELOC_RISCV_GOT_HI20, BFD_RELOC_RISCV_PCREL_LO12_I); else /* Local PIC symbol, or any non-PIC symbol */ pcrel_load (rd, rd, imm_expr, "addi", BFD_RELOC_RISCV_PCREL_HI20, BFD_RELOC_RISCV_PCREL_LO12_I); break; case M_LA_TLS_GD: pcrel_load (rd, rd, imm_expr, "addi", BFD_RELOC_RISCV_TLS_GD_HI20, BFD_RELOC_RISCV_PCREL_LO12_I); break; case M_LA_TLS_IE: pcrel_load (rd, rd, imm_expr, LOAD_ADDRESS_INSN, BFD_RELOC_RISCV_TLS_GOT_HI20, BFD_RELOC_RISCV_PCREL_LO12_I); break; case M_LB: pcrel_load (rd, rd, imm_expr, "lb", BFD_RELOC_RISCV_PCREL_HI20, BFD_RELOC_RISCV_PCREL_LO12_I); break; case M_LBU: pcrel_load (rd, rd, imm_expr, "lbu", BFD_RELOC_RISCV_PCREL_HI20, BFD_RELOC_RISCV_PCREL_LO12_I); break; case M_LH: pcrel_load (rd, rd, imm_expr, "lh", BFD_RELOC_RISCV_PCREL_HI20, BFD_RELOC_RISCV_PCREL_LO12_I); break; case M_LHU: pcrel_load (rd, rd, imm_expr, "lhu", BFD_RELOC_RISCV_PCREL_HI20, BFD_RELOC_RISCV_PCREL_LO12_I); break; case M_LW: pcrel_load (rd, rd, imm_expr, "lw", BFD_RELOC_RISCV_PCREL_HI20, BFD_RELOC_RISCV_PCREL_LO12_I); break; case M_LWU: pcrel_load (rd, rd, imm_expr, "lwu", BFD_RELOC_RISCV_PCREL_HI20, BFD_RELOC_RISCV_PCREL_LO12_I); break; case M_LD: pcrel_load (rd, rd, imm_expr, "ld", BFD_RELOC_RISCV_PCREL_HI20, BFD_RELOC_RISCV_PCREL_LO12_I); break; case M_FLW: pcrel_load (rd, rs1, imm_expr, "flw", BFD_RELOC_RISCV_PCREL_HI20, BFD_RELOC_RISCV_PCREL_LO12_I); break; case M_FLD: pcrel_load (rd, rs1, imm_expr, "fld", BFD_RELOC_RISCV_PCREL_HI20, BFD_RELOC_RISCV_PCREL_LO12_I); break; case M_SB: pcrel_store (rs2, rs1, imm_expr, "sb", BFD_RELOC_RISCV_PCREL_HI20, BFD_RELOC_RISCV_PCREL_LO12_S); break; case M_SH: pcrel_store (rs2, rs1, imm_expr, "sh", BFD_RELOC_RISCV_PCREL_HI20, BFD_RELOC_RISCV_PCREL_LO12_S); break; case M_SW: pcrel_store (rs2, rs1, imm_expr, "sw", BFD_RELOC_RISCV_PCREL_HI20, BFD_RELOC_RISCV_PCREL_LO12_S); break; case M_SD: pcrel_store (rs2, rs1, imm_expr, "sd", BFD_RELOC_RISCV_PCREL_HI20, BFD_RELOC_RISCV_PCREL_LO12_S); break; case M_FSW: pcrel_store (rs2, rs1, imm_expr, "fsw", BFD_RELOC_RISCV_PCREL_HI20, BFD_RELOC_RISCV_PCREL_LO12_S); break; case M_FSD: pcrel_store (rs2, rs1, imm_expr, "fsd", BFD_RELOC_RISCV_PCREL_HI20, BFD_RELOC_RISCV_PCREL_LO12_S); break; case M_CALL: riscv_call (rd, rs1, imm_expr, *imm_reloc); break; default: as_bad (_("Macro %s not implemented"), ip->insn_mo->name); break; } } static const struct percent_op_match percent_op_utype[] = { {"%tprel_hi", BFD_RELOC_RISCV_TPREL_HI20}, {"%pcrel_hi", BFD_RELOC_RISCV_PCREL_HI20}, {"%tls_ie_pcrel_hi", BFD_RELOC_RISCV_TLS_GOT_HI20}, {"%tls_gd_pcrel_hi", BFD_RELOC_RISCV_TLS_GD_HI20}, {"%hi", BFD_RELOC_RISCV_HI20}, {0, 0} }; static const struct percent_op_match percent_op_itype[] = { {"%lo", BFD_RELOC_RISCV_LO12_I}, {"%tprel_lo", BFD_RELOC_RISCV_TPREL_LO12_I}, {"%pcrel_lo", BFD_RELOC_RISCV_PCREL_LO12_I}, {0, 0} }; static const struct percent_op_match percent_op_stype[] = { {"%lo", BFD_RELOC_RISCV_LO12_S}, {"%tprel_lo", BFD_RELOC_RISCV_TPREL_LO12_S}, {"%pcrel_lo", BFD_RELOC_RISCV_PCREL_LO12_S}, {0, 0} }; static const struct percent_op_match percent_op_rtype[] = { {"%tprel_add", BFD_RELOC_RISCV_TPREL_ADD}, {0, 0} }; /* Return true if *STR points to a relocation operator. When returning true, move *STR over the operator and store its relocation code in *RELOC. Leave both *STR and *RELOC alone when returning false. */ static bfd_boolean parse_relocation (char **str, bfd_reloc_code_real_type *reloc, const struct percent_op_match *percent_op) { for ( ; percent_op->str; percent_op++) if (strncasecmp (*str, percent_op->str, strlen (percent_op->str)) == 0) { int len = strlen (percent_op->str); if (!ISSPACE ((*str)[len]) && (*str)[len] != '(') continue; *str += strlen (percent_op->str); *reloc = percent_op->reloc; /* Check whether the output BFD supports this relocation. If not, issue an error and fall back on something safe. */ if (!bfd_reloc_type_lookup (stdoutput, percent_op->reloc)) { as_bad ("relocation %s isn't supported by the current ABI", percent_op->str); *reloc = BFD_RELOC_UNUSED; } return TRUE; } return FALSE; } static void my_getExpression (expressionS *ep, char *str) { char *save_in; save_in = input_line_pointer; input_line_pointer = str; expression (ep); expr_end = input_line_pointer; input_line_pointer = save_in; } /* Parse string STR as a 16-bit relocatable operand. Store the expression in *EP and the relocation, if any, in RELOC. Return the number of relocation operators used (0 or 1). On exit, EXPR_END points to the first character after the expression. */ static size_t my_getSmallExpression (expressionS *ep, bfd_reloc_code_real_type *reloc, char *str, const struct percent_op_match *percent_op) { size_t reloc_index; unsigned crux_depth, str_depth, regno; char *crux; /* First, check for integer registers. */ if (reg_lookup (&str, RCLASS_GPR, ®no)) { ep->X_op = O_register; ep->X_add_number = regno; return 0; } /* Search for the start of the main expression. End the loop with CRUX pointing to the start of the main expression and with CRUX_DEPTH containing the number of open brackets at that point. */ reloc_index = -1; str_depth = 0; do { reloc_index++; crux = str; crux_depth = str_depth; /* Skip over whitespace and brackets, keeping count of the number of brackets. */ while (*str == ' ' || *str == '\t' || *str == '(') if (*str++ == '(') str_depth++; } while (*str == '%' && reloc_index < 1 && parse_relocation (&str, reloc, percent_op)); my_getExpression (ep, crux); str = expr_end; /* Match every open bracket. */ while (crux_depth > 0 && (*str == ')' || *str == ' ' || *str == '\t')) if (*str++ == ')') crux_depth--; if (crux_depth > 0) as_bad ("unclosed '('"); expr_end = str; return reloc_index; } /* This routine assembles an instruction into its binary format. As a side effect, it sets the global variable imm_reloc to the type of relocation to do if one of the operands is an address expression. */ static const char * riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr, bfd_reloc_code_real_type *imm_reloc) { char *s; const char *args; char c = 0; struct riscv_opcode *insn; char *argsStart; unsigned int regno; char save_c = 0; int argnum; const struct percent_op_match *p; const char *error = "unrecognized opcode"; /* Parse the name of the instruction. Terminate the string if whitespace is found so that hash_find only sees the name part of the string. */ for (s = str; *s != '\0'; ++s) if (ISSPACE (*s)) { save_c = *s; *s++ = '\0'; break; } insn = (struct riscv_opcode *) hash_find (op_hash, str); argsStart = s; for ( ; insn && insn->name && strcmp (insn->name, str) == 0; insn++) { if (!riscv_subset_supports (insn->subset)) continue; create_insn (ip, insn); argnum = 1; imm_expr->X_op = O_absent; *imm_reloc = BFD_RELOC_UNUSED; p = percent_op_itype; for (args = insn->args;; ++args) { s += strspn (s, " \t"); switch (*args) { case '\0': /* End of args. */ if (insn->pinfo != INSN_MACRO) { if (!insn->match_func (insn, ip->insn_opcode)) break; if (riscv_insn_length (insn->match) == 2 && !riscv_opts.rvc) break; } if (*s != '\0') break; /* Successful assembly. */ error = NULL; goto out; case 'C': /* RVC */ switch (*++args) { case 's': /* RS1 x8-x15 */ if (!reg_lookup (&s, RCLASS_GPR, ®no) || !(regno >= 8 && regno <= 15)) break; INSERT_OPERAND (CRS1S, *ip, regno % 8); continue; case 'w': /* RS1 x8-x15, constrained to equal RD x8-x15. */ if (!reg_lookup (&s, RCLASS_GPR, ®no) || EXTRACT_OPERAND (CRS1S, ip->insn_opcode) + 8 != regno) break; continue; case 't': /* RS2 x8-x15 */ if (!reg_lookup (&s, RCLASS_GPR, ®no) || !(regno >= 8 && regno <= 15)) break; INSERT_OPERAND (CRS2S, *ip, regno % 8); continue; case 'x': /* RS2 x8-x15, constrained to equal RD x8-x15. */ if (!reg_lookup (&s, RCLASS_GPR, ®no) || EXTRACT_OPERAND (CRS2S, ip->insn_opcode) + 8 != regno) break; continue; case 'U': /* RS1, constrained to equal RD. */ if (!reg_lookup (&s, RCLASS_GPR, ®no) || EXTRACT_OPERAND (RD, ip->insn_opcode) != regno) break; continue; case 'V': /* RS2 */ if (!reg_lookup (&s, RCLASS_GPR, ®no)) break; INSERT_OPERAND (CRS2, *ip, regno); continue; case 'c': /* RS1, constrained to equal sp. */ if (!reg_lookup (&s, RCLASS_GPR, ®no) || regno != X_SP) break; continue; case '>': if (my_getSmallExpression (imm_expr, imm_reloc, s, p) || imm_expr->X_op != O_constant || imm_expr->X_add_number <= 0 || imm_expr->X_add_number >= 64) break; ip->insn_opcode |= ENCODE_RVC_IMM (imm_expr->X_add_number); rvc_imm_done: s = expr_end; imm_expr->X_op = O_absent; continue; case '<': if (my_getSmallExpression (imm_expr, imm_reloc, s, p) || imm_expr->X_op != O_constant || !VALID_RVC_IMM (imm_expr->X_add_number) || imm_expr->X_add_number <= 0 || imm_expr->X_add_number >= 32) break; ip->insn_opcode |= ENCODE_RVC_IMM (imm_expr->X_add_number); goto rvc_imm_done; case 'i': if (my_getSmallExpression (imm_expr, imm_reloc, s, p) || imm_expr->X_op != O_constant || imm_expr->X_add_number == 0 || !VALID_RVC_SIMM3 (imm_expr->X_add_number)) break; ip->insn_opcode |= ENCODE_RVC_SIMM3 (imm_expr->X_add_number); goto rvc_imm_done; case 'j': if (my_getSmallExpression (imm_expr, imm_reloc, s, p) || imm_expr->X_op != O_constant || imm_expr->X_add_number == 0 || !VALID_RVC_IMM (imm_expr->X_add_number)) break; ip->insn_opcode |= ENCODE_RVC_IMM (imm_expr->X_add_number); goto rvc_imm_done; case 'k': if (my_getSmallExpression (imm_expr, imm_reloc, s, p) || imm_expr->X_op != O_constant || !VALID_RVC_LW_IMM (imm_expr->X_add_number)) break; ip->insn_opcode |= ENCODE_RVC_LW_IMM (imm_expr->X_add_number); goto rvc_imm_done; case 'l': if (my_getSmallExpression (imm_expr, imm_reloc, s, p) || imm_expr->X_op != O_constant || !VALID_RVC_LD_IMM (imm_expr->X_add_number)) break; ip->insn_opcode |= ENCODE_RVC_LD_IMM (imm_expr->X_add_number); goto rvc_imm_done; case 'm': if (my_getSmallExpression (imm_expr, imm_reloc, s, p) || imm_expr->X_op != O_constant || !VALID_RVC_LWSP_IMM (imm_expr->X_add_number)) break; ip->insn_opcode |= ENCODE_RVC_LWSP_IMM (imm_expr->X_add_number); goto rvc_imm_done; case 'n': if (my_getSmallExpression (imm_expr, imm_reloc, s, p) || imm_expr->X_op != O_constant || !VALID_RVC_LDSP_IMM (imm_expr->X_add_number)) break; ip->insn_opcode |= ENCODE_RVC_LDSP_IMM (imm_expr->X_add_number); goto rvc_imm_done; case 'K': if (my_getSmallExpression (imm_expr, imm_reloc, s, p) || imm_expr->X_op != O_constant || !VALID_RVC_ADDI4SPN_IMM (imm_expr->X_add_number) || imm_expr->X_add_number == 0) break; ip->insn_opcode |= ENCODE_RVC_ADDI4SPN_IMM (imm_expr->X_add_number); goto rvc_imm_done; case 'L': if (my_getSmallExpression (imm_expr, imm_reloc, s, p) || imm_expr->X_op != O_constant || !VALID_RVC_ADDI16SP_IMM (imm_expr->X_add_number) || imm_expr->X_add_number == 0) break; ip->insn_opcode |= ENCODE_RVC_ADDI16SP_IMM (imm_expr->X_add_number); goto rvc_imm_done; case 'M': if (my_getSmallExpression (imm_expr, imm_reloc, s, p) || imm_expr->X_op != O_constant || !VALID_RVC_SWSP_IMM (imm_expr->X_add_number)) break; ip->insn_opcode |= ENCODE_RVC_SWSP_IMM (imm_expr->X_add_number); goto rvc_imm_done; case 'N': if (my_getSmallExpression (imm_expr, imm_reloc, s, p) || imm_expr->X_op != O_constant || !VALID_RVC_SDSP_IMM (imm_expr->X_add_number)) break; ip->insn_opcode |= ENCODE_RVC_SDSP_IMM (imm_expr->X_add_number); goto rvc_imm_done; case 'u': p = percent_op_utype; if (my_getSmallExpression (imm_expr, imm_reloc, s, p)) break; rvc_lui: if (imm_expr->X_op != O_constant || imm_expr->X_add_number <= 0 || imm_expr->X_add_number >= RISCV_BIGIMM_REACH || (imm_expr->X_add_number >= RISCV_RVC_IMM_REACH / 2 && (imm_expr->X_add_number < RISCV_BIGIMM_REACH - RISCV_RVC_IMM_REACH / 2))) break; ip->insn_opcode |= ENCODE_RVC_IMM (imm_expr->X_add_number); goto rvc_imm_done; case 'v': if (my_getSmallExpression (imm_expr, imm_reloc, s, p) || (imm_expr->X_add_number & (RISCV_IMM_REACH - 1)) || ((int32_t)imm_expr->X_add_number != imm_expr->X_add_number)) break; imm_expr->X_add_number = ((uint32_t) imm_expr->X_add_number) >> RISCV_IMM_BITS; goto rvc_lui; case 'p': goto branch; case 'a': goto jump; case 'D': /* Floating-point RS2 x8-x15. */ if (!reg_lookup (&s, RCLASS_FPR, ®no) || !(regno >= 8 && regno <= 15)) break; INSERT_OPERAND (CRS2S, *ip, regno % 8); continue; case 'T': /* Floating-point RS2. */ if (!reg_lookup (&s, RCLASS_FPR, ®no)) break; INSERT_OPERAND (CRS2, *ip, regno); continue; default: as_bad (_("bad RVC field specifier 'C%c'\n"), *args); } break; case ',': ++argnum; if (*s++ == *args) continue; s--; break; case '(': case ')': case '[': case ']': if (*s++ == *args) continue; break; case '<': /* Shift amount, 0 - 31. */ my_getExpression (imm_expr, s); check_absolute_expr (ip, imm_expr); if ((unsigned long) imm_expr->X_add_number > 31) as_warn (_("Improper shift amount (%lu)"), (unsigned long) imm_expr->X_add_number); INSERT_OPERAND (SHAMTW, *ip, imm_expr->X_add_number); imm_expr->X_op = O_absent; s = expr_end; continue; case '>': /* Shift amount, 0 - (XLEN-1). */ my_getExpression (imm_expr, s); check_absolute_expr (ip, imm_expr); if ((unsigned long) imm_expr->X_add_number >= xlen) as_warn (_("Improper shift amount (%lu)"), (unsigned long) imm_expr->X_add_number); INSERT_OPERAND (SHAMT, *ip, imm_expr->X_add_number); imm_expr->X_op = O_absent; s = expr_end; continue; case 'Z': /* CSRRxI immediate. */ my_getExpression (imm_expr, s); check_absolute_expr (ip, imm_expr); if ((unsigned long) imm_expr->X_add_number > 31) as_warn (_("Improper CSRxI immediate (%lu)"), (unsigned long) imm_expr->X_add_number); INSERT_OPERAND (RS1, *ip, imm_expr->X_add_number); imm_expr->X_op = O_absent; s = expr_end; continue; case 'E': /* Control register. */ if (reg_lookup (&s, RCLASS_CSR, ®no)) INSERT_OPERAND (CSR, *ip, regno); else { my_getExpression (imm_expr, s); check_absolute_expr (ip, imm_expr); if ((unsigned long) imm_expr->X_add_number > 0xfff) as_warn(_("Improper CSR address (%lu)"), (unsigned long) imm_expr->X_add_number); INSERT_OPERAND (CSR, *ip, imm_expr->X_add_number); imm_expr->X_op = O_absent; s = expr_end; } continue; case 'm': /* Rounding mode. */ if (arg_lookup (&s, riscv_rm, ARRAY_SIZE (riscv_rm), ®no)) { INSERT_OPERAND (RM, *ip, regno); continue; } break; case 'P': case 'Q': /* Fence predecessor/successor. */ if (arg_lookup (&s, riscv_pred_succ, ARRAY_SIZE (riscv_pred_succ), ®no)) { if (*args == 'P') INSERT_OPERAND (PRED, *ip, regno); else INSERT_OPERAND (SUCC, *ip, regno); continue; } break; case 'd': /* Destination register. */ case 's': /* Source register. */ case 't': /* Target register. */ if (reg_lookup (&s, RCLASS_GPR, ®no)) { c = *args; if (*s == ' ') ++s; /* Now that we have assembled one operand, we use the args string to figure out where it goes in the instruction. */ switch (c) { case 's': INSERT_OPERAND (RS1, *ip, regno); break; case 'd': INSERT_OPERAND (RD, *ip, regno); break; case 't': INSERT_OPERAND (RS2, *ip, regno); break; } continue; } break; case 'D': /* Floating point rd. */ case 'S': /* Floating point rs1. */ case 'T': /* Floating point rs2. */ case 'U': /* Floating point rs1 and rs2. */ case 'R': /* Floating point rs3. */ if (reg_lookup (&s, RCLASS_FPR, ®no)) { c = *args; if (*s == ' ') ++s; switch (c) { case 'D': INSERT_OPERAND (RD, *ip, regno); break; case 'S': INSERT_OPERAND (RS1, *ip, regno); break; case 'U': INSERT_OPERAND (RS1, *ip, regno); /* fallthru */ case 'T': INSERT_OPERAND (RS2, *ip, regno); break; case 'R': INSERT_OPERAND (RS3, *ip, regno); break; } continue; } break; case 'I': my_getExpression (imm_expr, s); if (imm_expr->X_op != O_big && imm_expr->X_op != O_constant) break; normalize_constant_expr (imm_expr); s = expr_end; continue; case 'A': my_getExpression (imm_expr, s); normalize_constant_expr (imm_expr); /* The 'A' format specifier must be a symbol. */ if (imm_expr->X_op != O_symbol) break; *imm_reloc = BFD_RELOC_32; s = expr_end; continue; case 'j': /* Sign-extended immediate. */ *imm_reloc = BFD_RELOC_RISCV_LO12_I; p = percent_op_itype; goto alu_op; case 'q': /* Store displacement. */ p = percent_op_stype; *imm_reloc = BFD_RELOC_RISCV_LO12_S; goto load_store; case 'o': /* Load displacement. */ p = percent_op_itype; *imm_reloc = BFD_RELOC_RISCV_LO12_I; goto load_store; case '0': /* AMO "displacement," which must be zero. */ p = percent_op_rtype; *imm_reloc = BFD_RELOC_UNUSED; load_store: /* Check whether there is only a single bracketed expression left. If so, it must be the base register and the constant must be zero. */ imm_expr->X_op = O_constant; imm_expr->X_add_number = 0; if (*s == '(' && strchr (s + 1, '(') == 0) continue; alu_op: /* If this value won't fit into a 16 bit offset, then go find a macro that will generate the 32 bit offset code pattern. */ if (!my_getSmallExpression (imm_expr, imm_reloc, s, p)) { normalize_constant_expr (imm_expr); if (imm_expr->X_op != O_constant || (*args == '0' && imm_expr->X_add_number != 0) || imm_expr->X_add_number >= (signed)RISCV_IMM_REACH/2 || imm_expr->X_add_number < -(signed)RISCV_IMM_REACH/2) break; } s = expr_end; continue; case 'p': /* PC-relative offset. */ branch: *imm_reloc = BFD_RELOC_12_PCREL; my_getExpression (imm_expr, s); s = expr_end; continue; case 'u': /* Upper 20 bits. */ p = percent_op_utype; if (!my_getSmallExpression (imm_expr, imm_reloc, s, p) && imm_expr->X_op == O_constant) { if (imm_expr->X_add_number < 0 || imm_expr->X_add_number >= (signed)RISCV_BIGIMM_REACH) as_bad (_("lui expression not in range 0..1048575")); *imm_reloc = BFD_RELOC_RISCV_HI20; imm_expr->X_add_number <<= RISCV_IMM_BITS; } s = expr_end; continue; case 'a': /* 20-bit PC-relative offset. */ jump: my_getExpression (imm_expr, s); s = expr_end; *imm_reloc = BFD_RELOC_RISCV_JMP; continue; case 'c': my_getExpression (imm_expr, s); s = expr_end; if (strcmp (s, "@plt") == 0) { *imm_reloc = BFD_RELOC_RISCV_CALL_PLT; s += 4; } else *imm_reloc = BFD_RELOC_RISCV_CALL; continue; default: as_fatal (_("internal error: bad argument type %c"), *args); } break; } s = argsStart; error = _("illegal operands"); } out: /* Restore the character we might have clobbered above. */ if (save_c) *(argsStart - 1) = save_c; return error; } void md_assemble (char *str) { struct riscv_cl_insn insn; expressionS imm_expr; bfd_reloc_code_real_type imm_reloc = BFD_RELOC_UNUSED; const char *error = riscv_ip (str, &insn, &imm_expr, &imm_reloc); if (error) { as_bad ("%s `%s'", error, str); return; } if (insn.insn_mo->pinfo == INSN_MACRO) macro (&insn, &imm_expr, &imm_reloc); else append_insn (&insn, &imm_expr, imm_reloc); } const char * md_atof (int type, char *litP, int *sizeP) { return ieee_md_atof (type, litP, sizeP, TARGET_BYTES_BIG_ENDIAN); } void md_number_to_chars (char *buf, valueT val, int n) { number_to_chars_littleendian (buf, val, n); } const char *md_shortopts = "O::g::G:"; enum options { OPTION_M32 = OPTION_MD_BASE, OPTION_M64, OPTION_MARCH, OPTION_PIC, OPTION_NO_PIC, OPTION_MSOFT_FLOAT, OPTION_MHARD_FLOAT, OPTION_MRVC, OPTION_MNO_RVC, OPTION_END_OF_ENUM }; struct option md_longopts[] = { {"m32", no_argument, NULL, OPTION_M32}, {"m64", no_argument, NULL, OPTION_M64}, {"march", required_argument, NULL, OPTION_MARCH}, {"fPIC", no_argument, NULL, OPTION_PIC}, {"fpic", no_argument, NULL, OPTION_PIC}, {"fno-pic", no_argument, NULL, OPTION_NO_PIC}, {"mrvc", no_argument, NULL, OPTION_MRVC}, {"mno-rvc", no_argument, NULL, OPTION_MNO_RVC}, {"msoft-float", no_argument, NULL, OPTION_MSOFT_FLOAT}, {"mhard-float", no_argument, NULL, OPTION_MHARD_FLOAT}, {NULL, no_argument, NULL, 0} }; size_t md_longopts_size = sizeof (md_longopts); enum float_mode { FLOAT_MODE_DEFAULT, FLOAT_MODE_SOFT, FLOAT_MODE_HARD }; static enum float_mode float_mode = FLOAT_MODE_DEFAULT; int md_parse_option (int c, const char *arg) { switch (c) { case OPTION_MRVC: riscv_set_rvc (TRUE); break; case OPTION_MNO_RVC: riscv_set_rvc (FALSE); break; case OPTION_MSOFT_FLOAT: float_mode = FLOAT_MODE_SOFT; break; case OPTION_MHARD_FLOAT: float_mode = FLOAT_MODE_HARD; break; case OPTION_M32: xlen = 32; break; case OPTION_M64: xlen = 64; break; case OPTION_MARCH: riscv_set_arch (arg); break; case OPTION_NO_PIC: riscv_opts.pic = FALSE; break; case OPTION_PIC: riscv_opts.pic = TRUE; break; default: return 0; } return 1; } void riscv_after_parse_args (void) { if (riscv_subsets == NULL) riscv_set_arch ("RVIMAFD"); if (xlen == 0) { if (strcmp (default_arch, "riscv32") == 0) xlen = 32; else if (strcmp (default_arch, "riscv64") == 0) xlen = 64; else as_bad ("unknown default architecture `%s'", default_arch); } } long md_pcrel_from (fixS *fixP) { return fixP->fx_where + fixP->fx_frag->fr_address; } /* Apply a fixup to the object file. */ void md_apply_fix (fixS *fixP, valueT *valP, segT seg ATTRIBUTE_UNUSED) { bfd_byte *buf = (bfd_byte *) (fixP->fx_frag->fr_literal + fixP->fx_where); /* Remember value for tc_gen_reloc. */ fixP->fx_addnumber = *valP; switch (fixP->fx_r_type) { case BFD_RELOC_RISCV_TLS_GOT_HI20: case BFD_RELOC_RISCV_TLS_GD_HI20: case BFD_RELOC_RISCV_TLS_DTPREL32: case BFD_RELOC_RISCV_TLS_DTPREL64: case BFD_RELOC_RISCV_TPREL_HI20: case BFD_RELOC_RISCV_TPREL_LO12_I: case BFD_RELOC_RISCV_TPREL_LO12_S: case BFD_RELOC_RISCV_TPREL_ADD: S_SET_THREAD_LOCAL (fixP->fx_addsy); /* Fall through. */ case BFD_RELOC_RISCV_GOT_HI20: case BFD_RELOC_RISCV_PCREL_HI20: case BFD_RELOC_RISCV_HI20: case BFD_RELOC_RISCV_LO12_I: case BFD_RELOC_RISCV_LO12_S: case BFD_RELOC_RISCV_ADD8: case BFD_RELOC_RISCV_ADD16: case BFD_RELOC_RISCV_ADD32: case BFD_RELOC_RISCV_ADD64: case BFD_RELOC_RISCV_SUB8: case BFD_RELOC_RISCV_SUB16: case BFD_RELOC_RISCV_SUB32: case BFD_RELOC_RISCV_SUB64: gas_assert (fixP->fx_addsy != NULL); /* Nothing needed to do. The value comes from the reloc entry. */ break; case BFD_RELOC_64: case BFD_RELOC_32: case BFD_RELOC_16: case BFD_RELOC_8: if (fixP->fx_addsy && fixP->fx_subsy) { fixP->fx_next = xmemdup (fixP, sizeof (*fixP), sizeof (*fixP)); fixP->fx_next->fx_addsy = fixP->fx_subsy; fixP->fx_next->fx_subsy = NULL; fixP->fx_next->fx_offset = 0; fixP->fx_subsy = NULL; switch (fixP->fx_r_type) { case BFD_RELOC_64: fixP->fx_r_type = BFD_RELOC_RISCV_ADD64; fixP->fx_next->fx_r_type = BFD_RELOC_RISCV_SUB64; break; case BFD_RELOC_32: fixP->fx_r_type = BFD_RELOC_RISCV_ADD32; fixP->fx_next->fx_r_type = BFD_RELOC_RISCV_SUB32; break; case BFD_RELOC_16: fixP->fx_r_type = BFD_RELOC_RISCV_ADD16; fixP->fx_next->fx_r_type = BFD_RELOC_RISCV_SUB16; break; case BFD_RELOC_8: fixP->fx_r_type = BFD_RELOC_RISCV_ADD8; fixP->fx_next->fx_r_type = BFD_RELOC_RISCV_SUB8; break; default: /* This case is unreachable. */ abort (); } } /* Fall through. */ case BFD_RELOC_RVA: /* If we are deleting this reloc entry, we must fill in the value now. This can happen if we have a .word which is not resolved when it appears but is later defined. */ if (fixP->fx_addsy == NULL) { gas_assert (fixP->fx_size <= sizeof (valueT)); md_number_to_chars ((char *) buf, *valP, fixP->fx_size); fixP->fx_done = 1; } break; case BFD_RELOC_RISCV_JMP: if (fixP->fx_addsy) { /* Fill in a tentative value to improve objdump readability. */ bfd_vma target = S_GET_VALUE (fixP->fx_addsy) + *valP; bfd_vma delta = target - md_pcrel_from (fixP); bfd_putl32 (bfd_getl32 (buf) | ENCODE_UJTYPE_IMM (delta), buf); } break; case BFD_RELOC_12_PCREL: if (fixP->fx_addsy) { /* Fill in a tentative value to improve objdump readability. */ bfd_vma target = S_GET_VALUE (fixP->fx_addsy) + *valP; bfd_vma delta = target - md_pcrel_from (fixP); bfd_putl32 (bfd_getl32 (buf) | ENCODE_SBTYPE_IMM (delta), buf); } break; case BFD_RELOC_RISCV_RVC_BRANCH: if (fixP->fx_addsy) { /* Fill in a tentative value to improve objdump readability. */ bfd_vma target = S_GET_VALUE (fixP->fx_addsy) + *valP; bfd_vma delta = target - md_pcrel_from (fixP); bfd_putl16 (bfd_getl16 (buf) | ENCODE_RVC_B_IMM (delta), buf); } break; case BFD_RELOC_RISCV_RVC_JUMP: if (fixP->fx_addsy) { /* Fill in a tentative value to improve objdump readability. */ bfd_vma target = S_GET_VALUE (fixP->fx_addsy) + *valP; bfd_vma delta = target - md_pcrel_from (fixP); bfd_putl16 (bfd_getl16 (buf) | ENCODE_RVC_J_IMM (delta), buf); } break; case BFD_RELOC_RISCV_PCREL_LO12_S: case BFD_RELOC_RISCV_PCREL_LO12_I: case BFD_RELOC_RISCV_CALL: case BFD_RELOC_RISCV_CALL_PLT: case BFD_RELOC_RISCV_ALIGN: break; default: /* We ignore generic BFD relocations we don't know about. */ if (bfd_reloc_type_lookup (stdoutput, fixP->fx_r_type) != NULL) as_fatal (_("internal error: bad relocation #%d"), fixP->fx_r_type); } } /* This structure is used to hold a stack of .option values. */ struct riscv_option_stack { struct riscv_option_stack *next; struct riscv_set_options options; }; static struct riscv_option_stack *riscv_opts_stack; /* Handle the .option pseudo-op. */ static void s_riscv_option (int x ATTRIBUTE_UNUSED) { char *name = input_line_pointer, ch; while (!is_end_of_line[(unsigned char) *input_line_pointer]) ++input_line_pointer; ch = *input_line_pointer; *input_line_pointer = '\0'; if (strcmp (name, "rvc") == 0) riscv_set_rvc (TRUE); else if (strcmp (name, "norvc") == 0) riscv_set_rvc (FALSE); else if (strcmp (name, "pic") == 0) riscv_opts.pic = TRUE; else if (strcmp (name, "nopic") == 0) riscv_opts.pic = FALSE; else if (strcmp (name, "soft-float") == 0) float_mode = FLOAT_MODE_SOFT; else if (strcmp (name, "hard-float") == 0) float_mode = FLOAT_MODE_HARD; else if (strcmp (name, "push") == 0) { struct riscv_option_stack *s; s = (struct riscv_option_stack *) xmalloc (sizeof *s); s->next = riscv_opts_stack; s->options = riscv_opts; riscv_opts_stack = s; } else if (strcmp (name, "pop") == 0) { struct riscv_option_stack *s; s = riscv_opts_stack; if (s == NULL) as_bad (_(".option pop with no .option push")); else { riscv_opts = s->options; riscv_opts_stack = s->next; free (s); } } else { as_warn (_("Unrecognized .option directive: %s\n"), name); } *input_line_pointer = ch; demand_empty_rest_of_line (); } /* Handle the .dtprelword and .dtpreldword pseudo-ops. They generate a 32-bit or 64-bit DTP-relative relocation (BYTES says which) for use in DWARF debug information. */ static void s_dtprel (int bytes) { expressionS ex; char *p; expression (&ex); if (ex.X_op != O_symbol) { as_bad (_("Unsupported use of %s"), (bytes == 8 ? ".dtpreldword" : ".dtprelword")); ignore_rest_of_line (); } p = frag_more (bytes); md_number_to_chars (p, 0, bytes); fix_new_exp (frag_now, p - frag_now->fr_literal, bytes, &ex, FALSE, (bytes == 8 ? BFD_RELOC_RISCV_TLS_DTPREL64 : BFD_RELOC_RISCV_TLS_DTPREL32)); demand_empty_rest_of_line (); } /* Handle the .bss pseudo-op. */ static void s_bss (int ignore ATTRIBUTE_UNUSED) { subseg_set (bss_section, 0); demand_empty_rest_of_line (); } /* Align to a given power of two. */ static void s_align (int bytes_p) { int fill_value = 0, fill_value_specified = 0; int min_text_alignment = riscv_opts.rvc ? 2 : 4; int alignment = get_absolute_expression(), bytes; if (bytes_p) { bytes = alignment; if (bytes < 1 || (bytes & (bytes-1)) != 0) as_bad (_("alignment not a power of 2: %d"), bytes); for (alignment = 0; bytes > 1; bytes >>= 1) alignment++; } bytes = 1 << alignment; if (alignment < 0 || alignment > 31) as_bad (_("unsatisfiable alignment: %d"), alignment); if (*input_line_pointer == ',') { ++input_line_pointer; fill_value = get_absolute_expression (); fill_value_specified = 1; } if (!fill_value_specified && subseg_text_p (now_seg) && bytes > min_text_alignment) { /* Emit the worst-case NOP string. The linker will delete any unnecessary NOPs. This allows us to support code alignment in spite of linker relaxations. */ bfd_vma i, worst_case_bytes = bytes - min_text_alignment; char *nops = frag_more (worst_case_bytes); for (i = 0; i < worst_case_bytes - 2; i += 4) md_number_to_chars (nops + i, RISCV_NOP, 4); if (i < worst_case_bytes) md_number_to_chars (nops + i, RVC_NOP, 2); expressionS ex; ex.X_op = O_constant; ex.X_add_number = worst_case_bytes; fix_new_exp (frag_now, nops - frag_now->fr_literal, 0, &ex, FALSE, BFD_RELOC_RISCV_ALIGN); } else if (alignment) frag_align (alignment, fill_value, 0); record_alignment (now_seg, alignment); demand_empty_rest_of_line (); } int md_estimate_size_before_relax (fragS *fragp, asection *segtype) { return (fragp->fr_var = relaxed_branch_length (fragp, segtype, FALSE)); } /* Translate internal representation of relocation info to BFD target format. */ arelent * tc_gen_reloc (asection *section ATTRIBUTE_UNUSED, fixS *fixp) { arelent *reloc = (arelent *) xmalloc (sizeof (arelent)); reloc->sym_ptr_ptr = (asymbol **) xmalloc (sizeof (asymbol *)); *reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy); reloc->address = fixp->fx_frag->fr_address + fixp->fx_where; reloc->addend = fixp->fx_addnumber; reloc->howto = bfd_reloc_type_lookup (stdoutput, fixp->fx_r_type); if (reloc->howto == NULL) { if ((fixp->fx_r_type == BFD_RELOC_16 || fixp->fx_r_type == BFD_RELOC_8) && fixp->fx_addsy != NULL && fixp->fx_subsy != NULL) { /* We don't have R_RISCV_8/16, but for this special case, we can use R_RISCV_ADD8/16 with R_RISCV_SUB8/16. */ return reloc; } as_bad_where (fixp->fx_file, fixp->fx_line, _("cannot represent %s relocation in object file"), bfd_get_reloc_code_name (fixp->fx_r_type)); return NULL; } return reloc; } int riscv_relax_frag (asection *sec, fragS *fragp, long stretch ATTRIBUTE_UNUSED) { if (RELAX_BRANCH_P (fragp->fr_subtype)) { offsetT old_var = fragp->fr_var; fragp->fr_var = relaxed_branch_length (fragp, sec, TRUE); return fragp->fr_var - old_var; } return 0; } /* Expand far branches to multi-instruction sequences. */ static void md_convert_frag_branch (fragS *fragp) { bfd_byte *buf; expressionS exp; fixS *fixp; insn_t insn; int rs1, reloc; buf = (bfd_byte *)fragp->fr_literal + fragp->fr_fix; exp.X_op = O_symbol; exp.X_add_symbol = fragp->fr_symbol; exp.X_add_number = fragp->fr_offset; gas_assert (fragp->fr_var == RELAX_BRANCH_LENGTH (fragp->fr_subtype)); if (RELAX_BRANCH_RVC (fragp->fr_subtype)) { switch (RELAX_BRANCH_LENGTH (fragp->fr_subtype)) { case 8: case 4: /* Expand the RVC branch into a RISC-V one. */ insn = bfd_getl16 (buf); rs1 = 8 + ((insn >> OP_SH_CRS1S) & OP_MASK_CRS1S); if ((insn & MASK_C_J) == MATCH_C_J) insn = MATCH_JAL; else if ((insn & MASK_C_JAL) == MATCH_C_JAL) insn = MATCH_JAL | (X_RA << OP_SH_RD); else if ((insn & MASK_C_BEQZ) == MATCH_C_BEQZ) insn = MATCH_BEQ | (rs1 << OP_SH_RS1); else if ((insn & MASK_C_BNEZ) == MATCH_C_BNEZ) insn = MATCH_BNE | (rs1 << OP_SH_RS1); else abort (); bfd_putl32 (insn, buf); break; case 6: /* Invert the branch condition. Branch over the jump. */ insn = bfd_getl16 (buf); insn ^= MATCH_C_BEQZ ^ MATCH_C_BNEZ; insn |= ENCODE_RVC_B_IMM (6); bfd_putl16 (insn, buf); buf += 2; goto jump; case 2: /* Just keep the RVC branch. */ reloc = RELAX_BRANCH_UNCOND (fragp->fr_subtype) ? BFD_RELOC_RISCV_RVC_JUMP : BFD_RELOC_RISCV_RVC_BRANCH; fixp = fix_new_exp (fragp, buf - (bfd_byte *)fragp->fr_literal, 2, &exp, FALSE, reloc); buf += 2; goto done; default: abort(); } } switch (RELAX_BRANCH_LENGTH (fragp->fr_subtype)) { case 8: gas_assert (!RELAX_BRANCH_UNCOND (fragp->fr_subtype)); /* Invert the branch condition. Branch over the jump. */ insn = bfd_getl32 (buf); insn ^= MATCH_BEQ ^ MATCH_BNE; insn |= ENCODE_SBTYPE_IMM (8); md_number_to_chars ((char *) buf, insn, 4); buf += 4; jump: /* Jump to the target. */ fixp = fix_new_exp (fragp, buf - (bfd_byte *)fragp->fr_literal, 4, &exp, FALSE, BFD_RELOC_RISCV_JMP); md_number_to_chars ((char *) buf, MATCH_JAL, 4); buf += 4; break; case 4: reloc = RELAX_BRANCH_UNCOND (fragp->fr_subtype) ? BFD_RELOC_RISCV_JMP : BFD_RELOC_12_PCREL; fixp = fix_new_exp (fragp, buf - (bfd_byte *)fragp->fr_literal, 4, &exp, FALSE, reloc); buf += 4; break; default: abort (); } done: fixp->fx_file = fragp->fr_file; fixp->fx_line = fragp->fr_line; gas_assert (buf == (bfd_byte *)fragp->fr_literal + fragp->fr_fix + fragp->fr_var); fragp->fr_fix += fragp->fr_var; } /* Relax a machine dependent frag. This returns the amount by which the current size of the frag should change. */ void md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED, segT asec ATTRIBUTE_UNUSED, fragS *fragp) { gas_assert (RELAX_BRANCH_P (fragp->fr_subtype)); md_convert_frag_branch (fragp); } void md_show_usage (FILE *stream) { fprintf (stream, _("\ RISC-V options:\n\ -m32 assemble RV32 code\n\ -m64 assemble RV64 code (default)\n\ -fpic generate position-independent code\n\ -fno-pic don't generate position-independent code (default)\n\ -msoft-float don't use F registers for floating-point values\n\ -mhard-float use F registers for floating-point values (default)\n\ -mno-rvc disable the C extension for compressed instructions (default)\n\ -mrvc enable the C extension for compressed instructions\n\ -march=ISA set the RISC-V architecture, RV64IMAFD by default\n\ ")); } /* Standard calling conventions leave the CFA at SP on entry. */ void riscv_cfi_frame_initial_instructions (void) { cfi_add_CFA_def_cfa_register (X_SP); } int tc_riscv_regname_to_dw2regnum (char *regname) { int reg; if ((reg = reg_lookup_internal (regname, RCLASS_GPR)) >= 0) return reg; if ((reg = reg_lookup_internal (regname, RCLASS_FPR)) >= 0) return reg + 32; as_bad (_("unknown register `%s'"), regname); return -1; } void riscv_elf_final_processing (void) { enum float_mode elf_float_mode = float_mode; elf_elfheader (stdoutput)->e_flags |= elf_flags; if (elf_float_mode == FLOAT_MODE_DEFAULT) { struct riscv_subset *subset; /* Assume soft-float unless D extension is present. */ elf_float_mode = FLOAT_MODE_SOFT; for (subset = riscv_subsets; subset != NULL; subset = subset->next) if (strcasecmp (subset->name, "D") == 0) elf_float_mode = FLOAT_MODE_HARD; } if (elf_float_mode == FLOAT_MODE_SOFT) elf_elfheader (stdoutput)->e_flags |= EF_RISCV_SOFT_FLOAT; } /* Parse the .sleb128 and .uleb128 pseudos. Only allow constant expressions, since these directives break relaxation when used with symbol deltas. */ static void s_riscv_leb128 (int sign) { expressionS exp; char *save_in = input_line_pointer; expression (&exp); if (exp.X_op != O_constant) as_bad (_("non-constant .%cleb128 is not supported"), sign ? 's' : 'u'); demand_empty_rest_of_line (); input_line_pointer = save_in; return s_leb128 (sign); } /* Pseudo-op table. */ static const pseudo_typeS riscv_pseudo_table[] = { /* RISC-V-specific pseudo-ops. */ {"option", s_riscv_option, 0}, {"half", cons, 2}, {"word", cons, 4}, {"dword", cons, 8}, {"dtprelword", s_dtprel, 4}, {"dtpreldword", s_dtprel, 8}, {"bss", s_bss, 0}, {"align", s_align, 0}, {"p2align", s_align, 0}, {"balign", s_align, 1}, {"uleb128", s_riscv_leb128, 0}, {"sleb128", s_riscv_leb128, 1}, { NULL, NULL, 0 }, }; void riscv_pop_insert (void) { extern void pop_insert (const pseudo_typeS *); pop_insert (riscv_pseudo_table); }