| /* 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, ®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); |
| } |