aboutsummaryrefslogtreecommitdiff
path: root/gas/config/tc-riscv.c
diff options
context:
space:
mode:
Diffstat (limited to 'gas/config/tc-riscv.c')
-rw-r--r--gas/config/tc-riscv.c2408
1 files changed, 2408 insertions, 0 deletions
diff --git a/gas/config/tc-riscv.c b/gas/config/tc-riscv.c
new file mode 100644
index 0000000..592c95a
--- /dev/null
+++ b/gas/config/tc-riscv.c
@@ -0,0 +1,2408 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#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 <stdint.h>
+
+/* 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, &regno))
+ {
+ 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, &regno)
+ || !(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, &regno)
+ || EXTRACT_OPERAND (CRS1S, ip->insn_opcode) + 8 != regno)
+ break;
+ continue;
+ case 't': /* RS2 x8-x15 */
+ if (!reg_lookup (&s, RCLASS_GPR, &regno)
+ || !(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, &regno)
+ || EXTRACT_OPERAND (CRS2S, ip->insn_opcode) + 8 != regno)
+ break;
+ continue;
+ case 'U': /* RS1, constrained to equal RD. */
+ if (!reg_lookup (&s, RCLASS_GPR, &regno)
+ || EXTRACT_OPERAND (RD, ip->insn_opcode) != regno)
+ break;
+ continue;
+ case 'V': /* RS2 */
+ if (!reg_lookup (&s, RCLASS_GPR, &regno))
+ break;
+ INSERT_OPERAND (CRS2, *ip, regno);
+ continue;
+ case 'c': /* RS1, constrained to equal sp. */
+ if (!reg_lookup (&s, RCLASS_GPR, &regno)
+ || 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, &regno)
+ || !(regno >= 8 && regno <= 15))
+ break;
+ INSERT_OPERAND (CRS2S, *ip, regno % 8);
+ continue;
+ case 'T': /* Floating-point RS2. */
+ if (!reg_lookup (&s, RCLASS_FPR, &regno))
+ 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, &regno))
+ 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), &regno))
+ {
+ 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),
+ &regno))
+ {
+ 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, &regno))
+ {
+ 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, &regno))
+ {
+ 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;
+
+ 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);
+}