| /* tc-riscv.c -- RISC-V assembler |
| Copyright (C) 2011-2021 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 "bfd/elfxx-riscv.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; |
| }; |
| |
| /* All RISC-V CSR belong to one of these classes. */ |
| enum riscv_csr_class |
| { |
| CSR_CLASS_NONE, |
| |
| CSR_CLASS_I, |
| CSR_CLASS_I_32, /* rv32 only */ |
| CSR_CLASS_F, /* f-ext only */ |
| CSR_CLASS_DEBUG /* debug CSR */ |
| }; |
| |
| /* This structure holds all restricted conditions for a CSR. */ |
| struct riscv_csr_extra |
| { |
| /* Class to which this CSR belongs. Used to decide whether or |
| not this CSR is legal in the current -march context. */ |
| enum riscv_csr_class csr_class; |
| |
| /* CSR may have differnet numbers in the previous priv spec. */ |
| unsigned address; |
| |
| /* Record the CSR is defined/valid in which versions. */ |
| enum riscv_spec_class define_version; |
| |
| /* Record the CSR is aborted/invalid from which versions. If it isn't |
| aborted in the current version, then it should be CSR_CLASS_VDRAFT. */ |
| enum riscv_spec_class abort_version; |
| |
| /* The CSR may have more than one setting. */ |
| struct riscv_csr_extra *next; |
| }; |
| |
| #ifndef DEFAULT_ARCH |
| #define DEFAULT_ARCH "riscv64" |
| #endif |
| |
| #ifndef DEFAULT_RISCV_ATTR |
| #define DEFAULT_RISCV_ATTR 0 |
| #endif |
| |
| /* Let riscv_after_parse_args set the default value according to xlen. */ |
| #ifndef DEFAULT_RISCV_ARCH_WITH_EXT |
| #define DEFAULT_RISCV_ARCH_WITH_EXT NULL |
| #endif |
| |
| /* Need to sync the version with RISC-V compiler. */ |
| #ifndef DEFAULT_RISCV_ISA_SPEC |
| #define DEFAULT_RISCV_ISA_SPEC "2.2" |
| #endif |
| |
| #ifndef DEFAULT_RISCV_PRIV_SPEC |
| #define DEFAULT_RISCV_PRIV_SPEC "1.11" |
| #endif |
| |
| static const char default_arch[] = DEFAULT_ARCH; |
| static const char *default_arch_with_ext = DEFAULT_RISCV_ARCH_WITH_EXT; |
| static enum riscv_spec_class default_isa_spec = ISA_SPEC_CLASS_NONE; |
| static enum riscv_spec_class default_priv_spec = PRIV_SPEC_CLASS_NONE; |
| |
| static unsigned xlen = 0; /* The width of an x-register. */ |
| static unsigned abi_xlen = 0; /* The width of a pointer in the ABI. */ |
| static bool rve_abi = false; |
| enum float_abi |
| { |
| FLOAT_ABI_DEFAULT = -1, |
| FLOAT_ABI_SOFT, |
| FLOAT_ABI_SINGLE, |
| FLOAT_ABI_DOUBLE, |
| FLOAT_ABI_QUAD |
| }; |
| static enum float_abi float_abi = FLOAT_ABI_DEFAULT; |
| |
| #define LOAD_ADDRESS_INSN (abi_xlen == 64 ? "ld" : "lw") |
| #define ADD32_INSN (xlen == 64 ? "addiw" : "addi") |
| |
| static unsigned elf_flags = 0; |
| |
| /* Set the default_isa_spec. Return 0 if the spec isn't supported. |
| Otherwise, return 1. */ |
| |
| static int |
| riscv_set_default_isa_spec (const char *s) |
| { |
| enum riscv_spec_class class = ISA_SPEC_CLASS_NONE; |
| RISCV_GET_ISA_SPEC_CLASS (s, class); |
| if (class == ISA_SPEC_CLASS_NONE) |
| { |
| as_bad ("unknown default ISA spec `%s' set by " |
| "-misa-spec or --with-isa-spec", s); |
| return 0; |
| } |
| else |
| default_isa_spec = class; |
| return 1; |
| } |
| |
| /* Set the default_priv_spec. Find the privileged elf attributes when |
| the input string is NULL. Return 0 if the spec isn't supported. |
| Otherwise, return 1. */ |
| |
| static int |
| riscv_set_default_priv_spec (const char *s) |
| { |
| enum riscv_spec_class class = PRIV_SPEC_CLASS_NONE; |
| unsigned major, minor, revision; |
| obj_attribute *attr; |
| |
| RISCV_GET_PRIV_SPEC_CLASS (s, class); |
| if (class != PRIV_SPEC_CLASS_NONE) |
| { |
| default_priv_spec = class; |
| return 1; |
| } |
| |
| if (s != NULL) |
| { |
| as_bad (_("unknown default privileged spec `%s' set by " |
| "-mpriv-spec or --with-priv-spec"), s); |
| return 0; |
| } |
| |
| /* Set the default_priv_spec by the privileged elf attributes. */ |
| attr = elf_known_obj_attributes_proc (stdoutput); |
| major = (unsigned) attr[Tag_RISCV_priv_spec].i; |
| minor = (unsigned) attr[Tag_RISCV_priv_spec_minor].i; |
| revision = (unsigned) attr[Tag_RISCV_priv_spec_revision].i; |
| /* Version 0.0.0 is the default value and meningless. */ |
| if (major == 0 && minor == 0 && revision == 0) |
| return 1; |
| |
| riscv_get_priv_spec_class_from_numbers (major, minor, revision, &class); |
| if (class != PRIV_SPEC_CLASS_NONE) |
| { |
| default_priv_spec = class; |
| return 1; |
| } |
| |
| /* Still can not find the privileged spec class. */ |
| as_bad (_("unknown default privileged spec `%d.%d.%d' set by " |
| "privileged elf attributes"), major, minor, revision); |
| return 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. */ |
| int rve; /* Generate RVE code. */ |
| int relax; /* Emit relocs the linker is allowed to relax. */ |
| int arch_attr; /* Emit architecture and privileged elf attributes. */ |
| int csr_check; /* Enable the CSR checking. */ |
| }; |
| |
| static struct riscv_set_options riscv_opts = |
| { |
| 0, /* pic */ |
| 0, /* rvc */ |
| 0, /* rve */ |
| 1, /* relax */ |
| DEFAULT_RISCV_ATTR, /* arch_attr */ |
| 0, /* csr_check */ |
| }; |
| |
| static void |
| riscv_set_rvc (bool rvc_value) |
| { |
| if (rvc_value) |
| elf_flags |= EF_RISCV_RVC; |
| |
| riscv_opts.rvc = rvc_value; |
| } |
| |
| static void |
| riscv_set_rve (bool rve_value) |
| { |
| riscv_opts.rve = rve_value; |
| } |
| |
| static riscv_subset_list_t riscv_subsets; |
| |
| static bool |
| riscv_subset_supports (const char *feature) |
| { |
| struct riscv_subset_t *subset; |
| |
| if (riscv_opts.rvc && (strcasecmp (feature, "c") == 0)) |
| return true; |
| |
| return riscv_lookup_subset (&riscv_subsets, feature, &subset); |
| } |
| |
| static bool |
| riscv_multi_subset_supports (enum riscv_insn_class insn_class) |
| { |
| switch (insn_class) |
| { |
| case INSN_CLASS_I: return riscv_subset_supports ("i"); |
| case INSN_CLASS_C: return riscv_subset_supports ("c"); |
| case INSN_CLASS_A: return riscv_subset_supports ("a"); |
| case INSN_CLASS_M: return riscv_subset_supports ("m"); |
| case INSN_CLASS_F: return riscv_subset_supports ("f"); |
| case INSN_CLASS_D: return riscv_subset_supports ("d"); |
| case INSN_CLASS_Q: return riscv_subset_supports ("q"); |
| |
| case INSN_CLASS_F_AND_C: |
| return (riscv_subset_supports ("f") |
| && riscv_subset_supports ("c")); |
| case INSN_CLASS_D_AND_C: |
| return (riscv_subset_supports ("d") |
| && riscv_subset_supports ("c")); |
| |
| case INSN_CLASS_ZICSR: |
| return riscv_subset_supports ("zicsr"); |
| case INSN_CLASS_ZIFENCEI: |
| return riscv_subset_supports ("zifencei"); |
| case INSN_CLASS_ZIHINTPAUSE: |
| return riscv_subset_supports ("zihintpause"); |
| |
| case INSN_CLASS_ZBB: |
| return riscv_subset_supports ("zbb"); |
| |
| case INSN_CLASS_ZBA: |
| return riscv_subset_supports ("zba"); |
| |
| case INSN_CLASS_ZBC: |
| return riscv_subset_supports ("zbc"); |
| |
| case INSN_CLASS_ZBS: |
| return riscv_subset_supports ("zbs"); |
| |
| default: |
| as_fatal ("internal: unreachable"); |
| return false; |
| } |
| } |
| |
| /* Set which ISA and extensions are available. */ |
| |
| static void |
| riscv_set_arch (const char *s) |
| { |
| riscv_parse_subset_t rps; |
| rps.subset_list = &riscv_subsets; |
| rps.error_handler = as_bad; |
| rps.xlen = &xlen; |
| rps.isa_spec = default_isa_spec; |
| rps.check_unknown_prefixed_ext = true; |
| |
| if (s != NULL && strcmp (s, "") == 0) |
| { |
| as_bad (_("the architecture string of -march and elf architecture " |
| "attributes cannot be empty")); |
| return; |
| } |
| |
| riscv_release_subset_list (&riscv_subsets); |
| riscv_parse_subset (&rps, s); |
| |
| /* To support .option rvc and rve. */ |
| riscv_set_rvc (false); |
| if (riscv_subset_supports ("c")) |
| riscv_set_rvc (true); |
| riscv_set_rve (false); |
| if (riscv_subset_supports ("e")) |
| riscv_set_rve (true); |
| } |
| |
| /* Indicate -mabi option is explictly set. */ |
| static bool explicit_mabi = false; |
| |
| static void |
| riscv_set_abi (unsigned new_xlen, enum float_abi new_float_abi, bool rve) |
| { |
| abi_xlen = new_xlen; |
| float_abi = new_float_abi; |
| rve_abi = rve; |
| } |
| |
| /* If the -mabi option isn't set, then set the abi according to the |
| ISA string. Otherwise, check if there is any conflict. */ |
| |
| static void |
| riscv_set_abi_by_arch (void) |
| { |
| if (!explicit_mabi) |
| { |
| if (riscv_subset_supports ("q")) |
| riscv_set_abi (xlen, FLOAT_ABI_QUAD, false); |
| else if (riscv_subset_supports ("d")) |
| riscv_set_abi (xlen, FLOAT_ABI_DOUBLE, false); |
| else if (riscv_subset_supports ("e")) |
| riscv_set_abi (xlen, FLOAT_ABI_SOFT, true); |
| else |
| riscv_set_abi (xlen, FLOAT_ABI_SOFT, false); |
| } |
| else |
| { |
| gas_assert (abi_xlen != 0 && xlen != 0 && float_abi != FLOAT_ABI_DEFAULT); |
| if (abi_xlen > xlen) |
| as_bad ("can't have %d-bit ABI on %d-bit ISA", abi_xlen, xlen); |
| else if (abi_xlen < xlen) |
| as_bad ("%d-bit ABI not yet supported on %d-bit ISA", abi_xlen, xlen); |
| |
| if (riscv_subset_supports ("e") && !rve_abi) |
| as_bad ("only the ilp32e ABI is supported for e extension"); |
| |
| if (float_abi == FLOAT_ABI_SINGLE |
| && !riscv_subset_supports ("f")) |
| as_bad ("ilp32f/lp64f ABI can't be used when f extension " |
| "isn't supported"); |
| else if (float_abi == FLOAT_ABI_DOUBLE |
| && !riscv_subset_supports ("d")) |
| as_bad ("ilp32d/lp64d ABI can't be used when d extension " |
| "isn't supported"); |
| else if (float_abi == FLOAT_ABI_QUAD |
| && !riscv_subset_supports ("q")) |
| as_bad ("ilp32q/lp64q ABI can't be used when q extension " |
| "isn't supported"); |
| } |
| |
| /* Update the EF_RISCV_FLOAT_ABI field of elf_flags. */ |
| elf_flags &= ~EF_RISCV_FLOAT_ABI; |
| elf_flags |= float_abi << 1; |
| |
| if (rve_abi) |
| elf_flags |= EF_RISCV_RVE; |
| } |
| |
| /* Handle of the OPCODE hash table. */ |
| static htab_t op_hash = NULL; |
| |
| /* Handle of the type of .insn hash table. */ |
| static htab_t insn_type_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"; |
| |
| /* Indicate we are already assemble any instructions or not. */ |
| static bool start_assemble = false; |
| |
| /* Indicate ELF attributes are explicitly set. */ |
| static bool explicit_attr = false; |
| |
| /* Indicate CSR or priv instructions are explicitly used. */ |
| static bool explicit_priv_attr = false; |
| |
| /* 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; |
| |
| /* Create a new mapping symbol for the transition to STATE. */ |
| |
| static void |
| make_mapping_symbol (enum riscv_seg_mstate state, |
| valueT value, |
| fragS *frag) |
| { |
| const char *name; |
| switch (state) |
| { |
| case MAP_DATA: |
| name = "$d"; |
| break; |
| case MAP_INSN: |
| name = "$x"; |
| break; |
| default: |
| abort (); |
| } |
| |
| symbolS *symbol = symbol_new (name, now_seg, frag, value); |
| symbol_get_bfdsym (symbol)->flags |= (BSF_NO_FLAGS | BSF_LOCAL); |
| |
| /* If .fill or other data filling directive generates zero sized data, |
| or we are adding odd alignemnts, then the mapping symbol for the |
| following code will have the same value. */ |
| if (value == 0) |
| { |
| if (frag->tc_frag_data.first_map_symbol != NULL) |
| { |
| know (S_GET_VALUE (frag->tc_frag_data.first_map_symbol) |
| == S_GET_VALUE (symbol)); |
| /* Remove the old one. */ |
| symbol_remove (frag->tc_frag_data.first_map_symbol, |
| &symbol_rootP, &symbol_lastP); |
| } |
| frag->tc_frag_data.first_map_symbol = symbol; |
| } |
| if (frag->tc_frag_data.last_map_symbol != NULL) |
| { |
| /* The mapping symbols should be added in offset order. */ |
| know (S_GET_VALUE (frag->tc_frag_data.last_map_symbol) |
| <= S_GET_VALUE (symbol)); |
| /* Remove the old one. */ |
| if (S_GET_VALUE (frag->tc_frag_data.last_map_symbol) |
| == S_GET_VALUE (symbol)) |
| symbol_remove (frag->tc_frag_data.last_map_symbol, |
| &symbol_rootP, &symbol_lastP); |
| } |
| frag->tc_frag_data.last_map_symbol = symbol; |
| } |
| |
| /* Set the mapping state for frag_now. */ |
| |
| void |
| riscv_mapping_state (enum riscv_seg_mstate to_state, |
| int max_chars) |
| { |
| enum riscv_seg_mstate from_state = |
| seg_info (now_seg)->tc_segment_info_data.map_state; |
| |
| if (!SEG_NORMAL (now_seg) |
| /* For now I only add the mapping symbols to text sections. |
| Therefore, the dis-assembler only show the actual contents |
| distribution for text. Other sections will be shown as |
| data without the details. */ |
| || !subseg_text_p (now_seg)) |
| return; |
| |
| /* The mapping symbol should be emitted if not in the right |
| mapping state */ |
| if (from_state == to_state) |
| return; |
| |
| valueT value = (valueT) (frag_now_fix () - max_chars); |
| seg_info (now_seg)->tc_segment_info_data.map_state = to_state; |
| make_mapping_symbol (to_state, value, frag_now); |
| } |
| |
| /* Add the odd bytes of paddings for riscv_handle_align. */ |
| |
| static void |
| riscv_add_odd_padding_symbol (fragS *frag) |
| { |
| /* If there was already a mapping symbol, it should be |
| removed in the make_mapping_symbol. */ |
| make_mapping_symbol (MAP_DATA, frag->fr_fix, frag); |
| make_mapping_symbol (MAP_INSN, frag->fr_fix + 1, frag); |
| } |
| |
| /* Remove any excess mapping symbols generated for alignment frags in |
| SEC. We may have created a mapping symbol before a zero byte |
| alignment; remove it if there's a mapping symbol after the |
| alignment. */ |
| |
| static void |
| riscv_check_mapping_symbols (bfd *abfd ATTRIBUTE_UNUSED, |
| asection *sec, |
| void *dummy ATTRIBUTE_UNUSED) |
| { |
| segment_info_type *seginfo = seg_info (sec); |
| fragS *fragp; |
| |
| if (seginfo == NULL || seginfo->frchainP == NULL) |
| return; |
| |
| for (fragp = seginfo->frchainP->frch_root; |
| fragp != NULL; |
| fragp = fragp->fr_next) |
| { |
| symbolS *last = fragp->tc_frag_data.last_map_symbol; |
| fragS *next = fragp->fr_next; |
| |
| if (last == NULL || next == NULL) |
| continue; |
| |
| /* Check the last mapping symbol if it is at the boundary of |
| fragment. */ |
| if (S_GET_VALUE (last) < next->fr_address) |
| continue; |
| know (S_GET_VALUE (last) == next->fr_address); |
| |
| do |
| { |
| if (next->tc_frag_data.first_map_symbol != NULL) |
| { |
| /* The last mapping symbol overlaps with another one |
| which at the start of the next frag. */ |
| symbol_remove (last, &symbol_rootP, &symbol_lastP); |
| break; |
| } |
| |
| if (next->fr_next == NULL) |
| { |
| /* The last mapping symbol is at the end of the section. */ |
| know (next->fr_fix == 0 && next->fr_var == 0); |
| symbol_remove (last, &symbol_rootP, &symbol_lastP); |
| break; |
| } |
| |
| /* Since we may have empty frags without any mapping symbols, |
| keep looking until the non-empty frag. */ |
| if (next->fr_address != next->fr_next->fr_address) |
| break; |
| |
| next = next->fr_next; |
| } |
| while (next != NULL); |
| } |
| } |
| |
| /* The default target format to use. */ |
| |
| const char * |
| riscv_target_format (void) |
| { |
| if (target_big_endian) |
| return xlen == 64 ? "elf64-bigriscv" : "elf32-bigriscv"; |
| else |
| 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; |
| number_to_chars_littleendian (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) |
| && !S_IS_WEAK (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; |
| } |
| |
| /* Information about an opcode name, mnemonics and its value. */ |
| struct opcode_name_t |
| { |
| const char *name; |
| unsigned int val; |
| }; |
| |
| /* List for all supported opcode name. */ |
| static const struct opcode_name_t opcode_name_list[] = |
| { |
| {"C0", 0x0}, |
| {"C1", 0x1}, |
| {"C2", 0x2}, |
| |
| {"LOAD", 0x03}, |
| {"LOAD_FP", 0x07}, |
| {"CUSTOM_0", 0x0b}, |
| {"MISC_MEM", 0x0f}, |
| {"OP_IMM", 0x13}, |
| {"AUIPC", 0x17}, |
| {"OP_IMM_32", 0x1b}, |
| /* 48b 0x1f. */ |
| |
| {"STORE", 0x23}, |
| {"STORE_FP", 0x27}, |
| {"CUSTOM_1", 0x2b}, |
| {"AMO", 0x2f}, |
| {"OP", 0x33}, |
| {"LUI", 0x37}, |
| {"OP_32", 0x3b}, |
| /* 64b 0x3f. */ |
| |
| {"MADD", 0x43}, |
| {"MSUB", 0x47}, |
| {"NMADD", 0x4f}, |
| {"NMSUB", 0x4b}, |
| {"OP_FP", 0x53}, |
| /*reserved 0x57. */ |
| {"CUSTOM_2", 0x5b}, |
| /* 48b 0x5f. */ |
| |
| {"BRANCH", 0x63}, |
| {"JALR", 0x67}, |
| /*reserved 0x5b. */ |
| {"JAL", 0x6f}, |
| {"SYSTEM", 0x73}, |
| /*reserved 0x77. */ |
| {"CUSTOM_3", 0x7b}, |
| /* >80b 0x7f. */ |
| |
| {NULL, 0} |
| }; |
| |
| /* Hash table for lookup opcode name. */ |
| static htab_t opcode_names_hash = NULL; |
| |
| /* Initialization for hash table of opcode name. */ |
| |
| static void |
| init_opcode_names_hash (void) |
| { |
| const struct opcode_name_t *opcode; |
| |
| for (opcode = &opcode_name_list[0]; opcode->name != NULL; ++opcode) |
| if (str_hash_insert (opcode_names_hash, opcode->name, opcode, 0) != NULL) |
| as_fatal (_("internal: duplicate %s"), opcode->name); |
| } |
| |
| /* Find `s` is a valid opcode name or not, return the opcode name info |
| if found. */ |
| |
| static const struct opcode_name_t * |
| opcode_name_lookup (char **s) |
| { |
| char *e; |
| char save_c; |
| struct opcode_name_t *o; |
| |
| /* 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'; |
| |
| o = (struct opcode_name_t *) str_hash_find (opcode_names_hash, *s); |
| |
| /* Advance to next token if one was recognized. */ |
| if (o) |
| *s = e; |
| |
| *e = save_c; |
| expr_end = e; |
| |
| return o; |
| } |
| |
| enum reg_class |
| { |
| RCLASS_GPR, |
| RCLASS_FPR, |
| RCLASS_MAX, |
| |
| RCLASS_CSR |
| }; |
| |
| static htab_t reg_names_hash = NULL; |
| static htab_t csr_extra_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); |
| if (str_hash_insert (reg_names_hash, name, hash, 0) != NULL) |
| as_fatal (_("internal: duplicate %s"), name); |
| } |
| |
| 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); |
| } |
| |
| /* Init hash table csr_extra_hash to handle CSR. */ |
| |
| static void |
| riscv_init_csr_hash (const char *name, |
| unsigned address, |
| enum riscv_csr_class class, |
| enum riscv_spec_class define_version, |
| enum riscv_spec_class abort_version) |
| { |
| struct riscv_csr_extra *entry, *pre_entry; |
| bool need_enrty = true; |
| |
| pre_entry = NULL; |
| entry = (struct riscv_csr_extra *) str_hash_find (csr_extra_hash, name); |
| while (need_enrty && entry != NULL) |
| { |
| if (entry->csr_class == class |
| && entry->address == address |
| && entry->define_version == define_version |
| && entry->abort_version == abort_version) |
| need_enrty = false; |
| pre_entry = entry; |
| entry = entry->next; |
| } |
| |
| /* Duplicate CSR. */ |
| if (!need_enrty) |
| return; |
| |
| entry = XNEW (struct riscv_csr_extra); |
| entry->csr_class = class; |
| entry->address = address; |
| entry->define_version = define_version; |
| entry->abort_version = abort_version; |
| entry->next = NULL; |
| |
| if (pre_entry == NULL) |
| str_hash_insert (csr_extra_hash, name, entry, 0); |
| else |
| pre_entry->next = entry; |
| } |
| |
| /* Return the CSR address after checking the ISA dependency and |
| the privileged spec version. |
| |
| There are one warning and two errors for CSR, |
| |
| Invalid CSR: the CSR was defined, but isn't allowed for the current ISA |
| or the privileged spec, report warning only if -mcsr-check is set. |
| Unknown CSR: the CSR has never been defined, report error. |
| Improper CSR: the CSR number over the range (> 0xfff), report error. */ |
| |
| static unsigned int |
| riscv_csr_address (const char *csr_name, |
| struct riscv_csr_extra *entry) |
| { |
| struct riscv_csr_extra *saved_entry = entry; |
| enum riscv_csr_class csr_class = entry->csr_class; |
| bool need_check_version = true; |
| bool result = true; |
| |
| switch (csr_class) |
| { |
| case CSR_CLASS_I: |
| result = riscv_subset_supports ("i"); |
| break; |
| case CSR_CLASS_I_32: |
| result = (xlen == 32 && riscv_subset_supports ("i")); |
| break; |
| case CSR_CLASS_F: |
| result = riscv_subset_supports ("f"); |
| need_check_version = false; |
| break; |
| case CSR_CLASS_DEBUG: |
| need_check_version = false; |
| break; |
| default: |
| as_bad (_("internal: bad RISC-V CSR class (0x%x)"), csr_class); |
| } |
| |
| if (riscv_opts.csr_check && !result) |
| as_warn (_("invalid CSR `%s' for the current ISA"), csr_name); |
| |
| while (entry != NULL) |
| { |
| if (!need_check_version |
| || (default_priv_spec >= entry->define_version |
| && default_priv_spec < entry->abort_version)) |
| { |
| /* Find the CSR according to the specific version. */ |
| return entry->address; |
| } |
| entry = entry->next; |
| } |
| |
| /* Can not find the CSR address from the chosen privileged version, |
| so use the newly defined value. */ |
| if (riscv_opts.csr_check) |
| { |
| const char *priv_name = NULL; |
| RISCV_GET_PRIV_SPEC_NAME (priv_name, default_priv_spec); |
| if (priv_name != NULL) |
| as_warn (_("invalid CSR `%s' for the privileged spec `%s'"), |
| csr_name, priv_name); |
| } |
| |
| return saved_entry->address; |
| } |
| |
| /* Return -1 if the CSR has never been defined. Otherwise, return |
| the address. */ |
| |
| static unsigned int |
| reg_csr_lookup_internal (const char *s) |
| { |
| struct riscv_csr_extra *r = |
| (struct riscv_csr_extra *) str_hash_find (csr_extra_hash, s); |
| |
| if (r == NULL) |
| return -1U; |
| |
| return riscv_csr_address (s, r); |
| } |
| |
| static unsigned int |
| reg_lookup_internal (const char *s, enum reg_class class) |
| { |
| void *r; |
| |
| if (class == RCLASS_CSR) |
| return reg_csr_lookup_internal (s); |
| |
| r = str_hash_find (reg_names_hash, s); |
| if (r == NULL || DECODE_REG_CLASS (r) != class) |
| return -1; |
| |
| if (riscv_opts.rve && class == RCLASS_GPR && DECODE_REG_NUM (r) > 15) |
| return -1; |
| |
| return DECODE_REG_NUM (r); |
| } |
| |
| static bool |
| 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 bool |
| 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); |
| |
| if (len == 0) |
| return false; |
| |
| 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. The `length` could be 0, 4 or 8, 0 for auto detection. */ |
| |
| static bool |
| validate_riscv_insn (const struct riscv_opcode *opc, int length) |
| { |
| const char *p = opc->args; |
| char c; |
| insn_t used_bits = opc->mask; |
| int insn_width; |
| insn_t required_bits; |
| |
| if (length == 0) |
| insn_width = 8 * riscv_insn_length (opc->match); |
| else |
| insn_width = 8 * length; |
| |
| 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 'U': break; /* CRS1, constrained to equal RD. */ |
| case 'c': break; /* CRS1, constrained to equal sp. */ |
| case 'T': /* CRS2, floating point. */ |
| case 'V': USE_BITS (OP_MASK_CRS2, OP_SH_CRS2); break; |
| case 'S': /* CRS1S, floating point. */ |
| case 's': USE_BITS (OP_MASK_CRS1S, OP_SH_CRS1S); break; |
| case 'w': break; /* CRS1S, constrained to equal RD. */ |
| case 'D': /* CRS2S, floating point. */ |
| case 't': USE_BITS (OP_MASK_CRS2S, OP_SH_CRS2S); break; |
| case 'x': break; /* CRS2S, constrained to equal RD. */ |
| case 'z': break; /* CRS2S, constrained to be x0. */ |
| case '>': /* CITYPE immediate, compressed shift. */ |
| case 'u': /* CITYPE immediate, compressed lui. */ |
| case 'v': /* CITYPE immediate, li to compressed lui. */ |
| case 'o': /* CITYPE immediate, allow zero. */ |
| case 'j': used_bits |= ENCODE_CITYPE_IMM (-1U); break; |
| case 'L': used_bits |= ENCODE_CITYPE_ADDI16SP_IMM (-1U); break; |
| case 'm': used_bits |= ENCODE_CITYPE_LWSP_IMM (-1U); break; |
| case 'n': used_bits |= ENCODE_CITYPE_LDSP_IMM (-1U); break; |
| case '6': used_bits |= ENCODE_CSSTYPE_IMM (-1U); break; |
| case 'M': used_bits |= ENCODE_CSSTYPE_SWSP_IMM (-1U); break; |
| case 'N': used_bits |= ENCODE_CSSTYPE_SDSP_IMM (-1U); break; |
| case '8': used_bits |= ENCODE_CIWTYPE_IMM (-1U); break; |
| case 'K': used_bits |= ENCODE_CIWTYPE_ADDI4SPN_IMM (-1U); break; |
| /* CLTYPE and CSTYPE have the same immediate encoding. */ |
| case '5': used_bits |= ENCODE_CLTYPE_IMM (-1U); break; |
| case 'k': used_bits |= ENCODE_CLTYPE_LW_IMM (-1U); break; |
| case 'l': used_bits |= ENCODE_CLTYPE_LD_IMM (-1U); break; |
| case 'p': used_bits |= ENCODE_CBTYPE_IMM (-1U); break; |
| case 'a': used_bits |= ENCODE_CJTYPE_IMM (-1U); break; |
| case 'F': /* Compressed funct for .insn directive. */ |
| switch (c = *p++) |
| { |
| case '6': USE_BITS (OP_MASK_CFUNCT6, OP_SH_CFUNCT6); break; |
| case '4': USE_BITS (OP_MASK_CFUNCT4, OP_SH_CFUNCT4); break; |
| case '3': USE_BITS (OP_MASK_CFUNCT3, OP_SH_CFUNCT3); break; |
| case '2': USE_BITS (OP_MASK_CFUNCT2, OP_SH_CFUNCT2); break; |
| default: |
| as_bad (_("internal: bad RISC-V opcode " |
| "(unknown operand type `CF%c'): %s %s"), |
| c, opc->name, opc->args); |
| return false; |
| } |
| 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; /* Macro operand, must be symbol. */ |
| case 'B': break; /* Macro operand, must be symbol or constant. */ |
| case 'I': break; /* Macro operand, must be constant. */ |
| case 'D': /* RD, floating point. */ |
| case 'd': USE_BITS (OP_MASK_RD, OP_SH_RD); break; |
| case 'Z': /* RS1, CSR number. */ |
| case 'S': /* RS1, floating point. */ |
| case 's': USE_BITS (OP_MASK_RS1, OP_SH_RS1); break; |
| case 'U': /* RS1 and RS2 are the same, floating point. */ |
| USE_BITS (OP_MASK_RS1, OP_SH_RS1); |
| /* Fall through. */ |
| case 'T': /* RS2, floating point. */ |
| case 't': USE_BITS (OP_MASK_RS2, OP_SH_RS2); break; |
| case 'R': /* RS3, floating point. */ |
| case 'r': USE_BITS (OP_MASK_RS3, OP_SH_RS3); break; |
| case 'm': USE_BITS (OP_MASK_RM, OP_SH_RM); break; |
| case 'E': USE_BITS (OP_MASK_CSR, OP_SH_CSR); 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': /* ITYPE immediate, load displacement. */ |
| case 'j': used_bits |= ENCODE_ITYPE_IMM (-1U); break; |
| case 'a': used_bits |= ENCODE_JTYPE_IMM (-1U); break; |
| case 'p': used_bits |= ENCODE_BTYPE_IMM (-1U); break; |
| case 'q': used_bits |= ENCODE_STYPE_IMM (-1U); break; |
| case 'u': used_bits |= ENCODE_UTYPE_IMM (-1U); break; |
| case 'z': break; /* Zero immediate. */ |
| case '[': break; /* Unused operand. */ |
| case ']': break; /* Unused operand. */ |
| case '0': break; /* AMO displacement, must to zero. */ |
| case '1': break; /* Relaxation operand. */ |
| case 'F': /* Funct for .insn directive. */ |
| switch (c = *p++) |
| { |
| case '7': USE_BITS (OP_MASK_FUNCT7, OP_SH_FUNCT7); break; |
| case '3': USE_BITS (OP_MASK_FUNCT3, OP_SH_FUNCT3); break; |
| case '2': USE_BITS (OP_MASK_FUNCT2, OP_SH_FUNCT2); break; |
| default: |
| as_bad (_("internal: bad RISC-V opcode " |
| "(unknown operand type `F%c'): %s %s"), |
| c, opc->name, opc->args); |
| return false; |
| } |
| break; |
| case 'O': /* Opcode for .insn directive. */ |
| switch (c = *p++) |
| { |
| case '4': USE_BITS (OP_MASK_OP, OP_SH_OP); break; |
| case '2': USE_BITS (OP_MASK_OP2, OP_SH_OP2); break; |
| default: |
| as_bad (_("internal: bad RISC-V opcode " |
| "(unknown operand type `F%c'): %s %s"), |
| c, opc->name, opc->args); |
| return false; |
| } |
| 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; |
| }; |
| |
| /* Common hash table initialization function for instruction and .insn |
| directive. */ |
| |
| static htab_t |
| init_opcode_hash (const struct riscv_opcode *opcodes, |
| bool insn_directive_p) |
| { |
| int i = 0; |
| int length; |
| htab_t hash = str_htab_create (); |
| while (opcodes[i].name) |
| { |
| const char *name = opcodes[i].name; |
| if (str_hash_insert (hash, name, &opcodes[i], 0) != NULL) |
| as_fatal (_("internal: duplicate %s"), name); |
| |
| do |
| { |
| if (opcodes[i].pinfo != INSN_MACRO) |
| { |
| if (insn_directive_p) |
| length = ((name[0] == 'c') ? 2 : 4); |
| else |
| length = 0; /* Let assembler determine the length. */ |
| if (!validate_riscv_insn (&opcodes[i], length)) |
| as_fatal (_("internal: broken assembler. " |
| "No assembly attempted")); |
| } |
| else |
| gas_assert (!insn_directive_p); |
| ++i; |
| } |
| while (opcodes[i].name && !strcmp (opcodes[i].name, name)); |
| } |
| |
| return hash; |
| } |
| |
| /* 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) |
| { |
| unsigned long mach = xlen == 64 ? bfd_mach_riscv64 : bfd_mach_riscv32; |
| |
| if (! bfd_set_arch_mach (stdoutput, bfd_arch_riscv, mach)) |
| as_warn (_("could not set architecture and machine")); |
| |
| op_hash = init_opcode_hash (riscv_opcodes, false); |
| insn_type_hash = init_opcode_hash (riscv_insn_types, true); |
| |
| reg_names_hash = str_htab_create (); |
| 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); |
| /* Add "fp" as an alias for "s0". */ |
| hash_reg_name (RCLASS_GPR, "fp", 8); |
| |
| /* Create and insert CSR hash tables. */ |
| csr_extra_hash = str_htab_create (); |
| #define DECLARE_CSR(name, num, class, define_version, abort_version) \ |
| riscv_init_csr_hash (#name, num, class, define_version, abort_version); |
| #define DECLARE_CSR_ALIAS(name, num, class, define_version, abort_version) \ |
| DECLARE_CSR(name, num, class, define_version, abort_version); |
| #include "opcode/riscv-opc.h" |
| #undef DECLARE_CSR |
| |
| opcode_names_hash = str_htab_create (); |
| init_opcode_names_hash (); |
| |
| /* Set the default alignment for the text section. */ |
| record_alignment (text_section, riscv_opts.rvc ? 1 : 2); |
| } |
| |
| static insn_t |
| riscv_apply_const_reloc (bfd_reloc_code_real_type reloc_type, bfd_vma value) |
| { |
| switch (reloc_type) |
| { |
| case BFD_RELOC_32: |
| return value; |
| |
| case BFD_RELOC_RISCV_HI20: |
| return ENCODE_UTYPE_IMM (RISCV_CONST_HIGH_PART (value)); |
| |
| case BFD_RELOC_RISCV_LO12_S: |
| return ENCODE_STYPE_IMM (value); |
| |
| case BFD_RELOC_RISCV_LO12_I: |
| return ENCODE_ITYPE_IMM (value); |
| |
| default: |
| abort (); |
| } |
| } |
| |
| /* 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); |
| |
| if (now_seg == absolute_section) |
| { |
| as_bad (_("relaxable branches not supported in absolute section")); |
| return; |
| } |
| |
| 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 |
| { |
| howto = bfd_reloc_type_lookup (stdoutput, reloc_type); |
| if (howto == NULL) |
| as_bad (_("internal: 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); |
| |
| ip->fixp->fx_tcbit = riscv_opts.relax; |
| } |
| } |
| |
| add_fixed_insn (ip); |
| install_insn (ip); |
| |
| /* We need to start a new frag after any instruction that can be |
| optimized away or compressed by the linker during relaxation, to prevent |
| the assembler from computing static offsets across such an instruction. |
| This is necessary to get correct EH info. */ |
| if (reloc_type == BFD_RELOC_RISCV_HI20 |
| || reloc_type == BFD_RELOC_RISCV_PCREL_HI20 |
| || reloc_type == BFD_RELOC_RISCV_TPREL_HI20 |
| || reloc_type == BFD_RELOC_RISCV_TPREL_ADD) |
| { |
| frag_wane (frag_now); |
| frag_new (0); |
| } |
| } |
| |
| /* 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 *) str_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 'j': |
| case 'u': |
| case 'q': |
| gas_assert (ep != NULL); |
| r = va_arg (args, int); |
| continue; |
| |
| case '\0': |
| break; |
| case ',': |
| continue; |
| default: |
| as_fatal (_("internal: invalid macro")); |
| } |
| break; |
| } |
| va_end (args); |
| gas_assert (r == BFD_RELOC_UNUSED ? ep == NULL : ep != NULL); |
| |
| append_insn (&insn, ep, r); |
| } |
| |
| /* Build an instruction created by a macro expansion. Like md_assemble but |
| accept a printf-style format string and arguments. */ |
| |
| static void |
| md_assemblef (const char *format, ...) |
| { |
| char *buf = NULL; |
| va_list ap; |
| int r; |
| |
| va_start (ap, format); |
| |
| r = vasprintf (&buf, format, ap); |
| |
| if (r < 0) |
| as_fatal (_("internal: vasprintf failed")); |
| |
| md_assemble (buf); |
| free(buf); |
| |
| va_end (ap); |
| } |
| |
| /* 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 EX is not a constant. IP is the instruction using EX. |
| MAYBE_CSR is true if the symbol may be an unrecognized CSR name. */ |
| |
| static void |
| check_absolute_expr (struct riscv_cl_insn *ip, expressionS *ex, |
| bool maybe_csr) |
| { |
| if (ex->X_op == O_big) |
| as_bad (_("unsupported large constant")); |
| else if (maybe_csr && ex->X_op == O_symbol) |
| as_bad (_("unknown CSR `%s'"), |
| S_GET_NAME (ex->X_add_symbol)); |
| 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, frag_now, |
| frag_now_fix ()); |
| } |
| |
| /* 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) |
| { |
| /* Ensure the jalr is emitted to the same frag as the auipc. */ |
| frag_grow (8); |
| macro_build (ep, "auipc", "d,u", tempreg, reloc); |
| macro_build (NULL, "jalr", "d,s", destreg, tempreg); |
| /* See comment at end of append_insn. */ |
| frag_wane (frag_now); |
| frag_new (0); |
| } |
| |
| /* Load an integer constant into a register. */ |
| |
| static void |
| load_const (int reg, expressionS *ep) |
| { |
| int shift = RISCV_IMM_BITS; |
| bfd_vma upper_imm, sign = (bfd_vma) 1 << (RISCV_IMM_BITS - 1); |
| expressionS upper = *ep, lower = *ep; |
| lower.X_add_number = ((ep->X_add_number & (sign + sign - 1)) ^ sign) - sign; |
| 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); |
| |
| md_assemblef ("slli x%d, x%d, 0x%x", reg, reg, shift); |
| if (lower.X_add_number != 0) |
| md_assemblef ("addi x%d, x%d, %" BFD_VMA_FMT "d", reg, reg, |
| lower.X_add_number); |
| } |
| else |
| { |
| /* Simply emit LUI and/or ADDI to build a 32-bit signed constant. */ |
| int hi_reg = 0; |
| |
| if (upper.X_add_number != 0) |
| { |
| /* Discard low part and zero-extend upper immediate. */ |
| upper_imm = ((uint32_t)upper.X_add_number >> shift); |
| |
| md_assemblef ("lui x%d, 0x%" BFD_VMA_FMT "x", reg, upper_imm); |
| hi_reg = reg; |
| } |
| |
| if (lower.X_add_number != 0 || hi_reg == 0) |
| md_assemblef ("%s x%d, x%d, %" BFD_VMA_FMT "d", ADD32_INSN, reg, hi_reg, |
| lower.X_add_number); |
| } |
| } |
| |
| /* Zero extend and sign extend byte/half-word/word. */ |
| |
| static void |
| riscv_ext (int destreg, int srcreg, unsigned shift, bool sign) |
| { |
| if (sign) |
| { |
| md_assemblef ("slli x%d, x%d, 0x%x", destreg, srcreg, shift); |
| md_assemblef ("srai x%d, x%d, 0x%x", destreg, destreg, shift); |
| } |
| else |
| { |
| md_assemblef ("slli x%d, x%d, 0x%x", destreg, srcreg, shift); |
| md_assemblef ("srli x%d, x%d, 0x%x", destreg, destreg, shift); |
| } |
| } |
| |
| /* 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; |
| |
| case M_ZEXTH: |
| riscv_ext (rd, rs1, xlen - 16, false); |
| break; |
| |
| case M_ZEXTW: |
| riscv_ext (rd, rs1, xlen - 32, false); |
| break; |
| |
| case M_SEXTB: |
| riscv_ext (rd, rs1, xlen - 8, true); |
| break; |
| |
| case M_SEXTH: |
| riscv_ext (rd, rs1, xlen - 16, true); |
| break; |
| |
| default: |
| as_bad (_("internal: 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}, |
| {"%got_pcrel_hi", BFD_RELOC_RISCV_GOT_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} |
| }; |
| |
| static const struct percent_op_match percent_op_null[] = |
| { |
| {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 bool |
| 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 (*reloc != BFD_RELOC_UNUSED |
| && !bfd_reloc_type_lookup (stdoutput, *reloc)) |
| { |
| as_bad ("internal: 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. No callers can accept a reg, but |
| we need to avoid accidentally creating a useless undefined symbol below, |
| if this is an instruction pattern that can't match. A glibc build fails |
| if this is removed. */ |
| if (reg_lookup (&str, RCLASS_GPR, ®no)) |
| { |
| ep->X_op = O_register; |
| ep->X_add_number = regno; |
| expr_end = str; |
| 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; |
| } |
| |
| /* Parse opcode name, could be an mnemonics or number. */ |
| |
| static size_t |
| my_getOpcodeExpression (expressionS *ep, bfd_reloc_code_real_type *reloc, |
| char *str, const struct percent_op_match *percent_op) |
| { |
| const struct opcode_name_t *o = opcode_name_lookup (&str); |
| |
| if (o != NULL) |
| { |
| ep->X_op = O_constant; |
| ep->X_add_number = o->val; |
| return 0; |
| } |
| |
| return my_getSmallExpression (ep, reloc, str, percent_op); |
| } |
| |
| /* Detect and handle implicitly zero load-store offsets. For example, |
| "lw t0, (t1)" is shorthand for "lw t0, 0(t1)". Return TRUE iff such |
| an implicit offset was detected. */ |
| |
| static bool |
| riscv_handle_implicit_zero_offset (expressionS *ep, const char *s) |
| { |
| /* Check whether there is only a single bracketed expression left. |
| If so, it must be the base register and the constant must be zero. */ |
| if (*s == '(' && strchr (s + 1, '(') == 0) |
| { |
| ep->X_op = O_constant; |
| ep->X_add_number = 0; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* All RISC-V CSR instructions belong to one of these classes. */ |
| enum csr_insn_type |
| { |
| INSN_NOT_CSR, |
| INSN_CSRRW, |
| INSN_CSRRS, |
| INSN_CSRRC |
| }; |
| |
| /* Return which CSR instruction is checking. */ |
| |
| static enum csr_insn_type |
| riscv_csr_insn_type (insn_t insn) |
| { |
| if (((insn ^ MATCH_CSRRW) & MASK_CSRRW) == 0 |
| || ((insn ^ MATCH_CSRRWI) & MASK_CSRRWI) == 0) |
| return INSN_CSRRW; |
| else if (((insn ^ MATCH_CSRRS) & MASK_CSRRS) == 0 |
| || ((insn ^ MATCH_CSRRSI) & MASK_CSRRSI) == 0) |
| return INSN_CSRRS; |
| else if (((insn ^ MATCH_CSRRC) & MASK_CSRRC) == 0 |
| || ((insn ^ MATCH_CSRRCI) & MASK_CSRRCI) == 0) |
| return INSN_CSRRC; |
| else |
| return INSN_NOT_CSR; |
| } |
| |
| /* CSRRW and CSRRWI always write CSR. CSRRS, CSRRC, CSRRSI and CSRRCI write |
| CSR when RS1 isn't zero. The CSR is read only if the [11:10] bits of |
| CSR address is 0x3. */ |
| |
| static bool |
| riscv_csr_read_only_check (insn_t insn) |
| { |
| int csr = (insn & (OP_MASK_CSR << OP_SH_CSR)) >> OP_SH_CSR; |
| int rs1 = (insn & (OP_MASK_RS1 << OP_SH_RS1)) >> OP_SH_RS1; |
| int readonly = (((csr & (0x3 << 10)) >> 10) == 0x3); |
| enum csr_insn_type csr_insn = riscv_csr_insn_type (insn); |
| |
| if (readonly |
| && (((csr_insn == INSN_CSRRS |
| || csr_insn == INSN_CSRRC) |
| && rs1 != 0) |
| || csr_insn == INSN_CSRRW)) |
| return false; |
| |
| return true; |
| } |
| |
| /* Return True if it is a privileged instruction. Otherwise, return FALSE. |
| |
| uret is actually a N-ext instruction. So it is better to regard it as |
| an user instruction rather than the priv instruction. |
| |
| hret is used to return from traps in H-mode. H-mode is removed since |
| the v1.10 priv spec, but probably be added in the new hypervisor spec. |
| Therefore, hret should be controlled by the hypervisor spec rather than |
| priv spec in the future. |
| |
| dret is defined in the debug spec, so it should be checked in the future, |
| too. */ |
| |
| static bool |
| riscv_is_priv_insn (insn_t insn) |
| { |
| return (((insn ^ MATCH_SRET) & MASK_SRET) == 0 |
| || ((insn ^ MATCH_MRET) & MASK_MRET) == 0 |
| || ((insn ^ MATCH_SFENCE_VMA) & MASK_SFENCE_VMA) == 0 |
| || ((insn ^ MATCH_WFI) & MASK_WFI) == 0 |
| /* The sfence.vm is dropped in the v1.10 priv specs, but we still need to |
| check it here to keep the compatible. */ |
| || ((insn ^ MATCH_SFENCE_VM) & MASK_SFENCE_VM) == 0); |
| } |
| |
| /* 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, htab_t hash) |
| { |
| 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"; |
| /* Indicate we are assembling instruction with CSR. */ |
| bool insn_with_csr = false; |
| |
| /* Parse the name of the instruction. Terminate the string if whitespace |
| is found so that str_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 *) str_hash_find (hash, str); |
| |
| argsStart = s; |
| for ( ; insn && insn->name && strcmp (insn->name, str) == 0; insn++) |
| { |
| if ((insn->xlen_requirement != 0) && (xlen != insn->xlen_requirement)) |
| continue; |
| |
| if (!riscv_multi_subset_supports (insn->insn_class)) |
| 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; |
| |
| /* For .insn, insn->match and insn->mask are 0. */ |
| if (riscv_insn_length ((insn->match == 0 && insn->mask == 0) |
| ? ip->insn_opcode |
| : insn->match) == 2 |
| && !riscv_opts.rvc) |
| break; |
| |
| if (riscv_is_priv_insn (ip->insn_opcode)) |
| explicit_priv_attr = true; |
| |
| /* Check if we write a read-only CSR by the CSR |
| instruction. */ |
| if (insn_with_csr |
| && riscv_opts.csr_check |
| && !riscv_csr_read_only_check (ip->insn_opcode)) |
| { |
| /* Restore the character in advance, since we want to |
| report the detailed warning message here. */ |
| if (save_c) |
| *(argsStart - 1) = save_c; |
| as_warn (_("read-only CSR is written `%s'"), str); |
| insn_with_csr = false; |
| } |
| } |
| if (*s != '\0') |
| break; |
| /* Successful assembly. */ |
| error = NULL; |
| insn_with_csr = false; |
| 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 'z': /* RS2, constrained to equal x0. */ |
| if (!reg_lookup (&s, RCLASS_GPR, ®no) |
| || regno != 0) |
| break; |
| continue; |
| case '>': /* Shift amount, 0 - (XLEN-1). */ |
| if (my_getSmallExpression (imm_expr, imm_reloc, s, p) |
| || imm_expr->X_op != O_constant |
| || (unsigned long) imm_expr->X_add_number >= xlen) |
| break; |
| ip->insn_opcode |= ENCODE_CITYPE_IMM (imm_expr->X_add_number); |
| rvc_imm_done: |
| s = expr_end; |
| imm_expr->X_op = O_absent; |
| continue; |
| case '5': |
| 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 >= 32 |
| || !VALID_CLTYPE_IMM ((valueT) imm_expr->X_add_number)) |
| break; |
| ip->insn_opcode |= ENCODE_CLTYPE_IMM (imm_expr->X_add_number); |
| goto rvc_imm_done; |
| case '6': |
| 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 |
| || !VALID_CSSTYPE_IMM ((valueT) imm_expr->X_add_number)) |
| break; |
| ip->insn_opcode |= ENCODE_CSSTYPE_IMM (imm_expr->X_add_number); |
| goto rvc_imm_done; |
| case '8': |
| 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 >= 256 |
| || !VALID_CIWTYPE_IMM ((valueT) imm_expr->X_add_number)) |
| break; |
| ip->insn_opcode |= ENCODE_CIWTYPE_IMM (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_CITYPE_IMM ((valueT) imm_expr->X_add_number)) |
| break; |
| ip->insn_opcode |= ENCODE_CITYPE_IMM (imm_expr->X_add_number); |
| goto rvc_imm_done; |
| case 'k': |
| if (riscv_handle_implicit_zero_offset (imm_expr, s)) |
| continue; |
| if (my_getSmallExpression (imm_expr, imm_reloc, s, p) |
| || imm_expr->X_op != O_constant |
| || !VALID_CLTYPE_LW_IMM ((valueT) imm_expr->X_add_number)) |
| break; |
| ip->insn_opcode |= ENCODE_CLTYPE_LW_IMM (imm_expr->X_add_number); |
| goto rvc_imm_done; |
| case 'l': |
| if (riscv_handle_implicit_zero_offset (imm_expr, s)) |
| continue; |
| if (my_getSmallExpression (imm_expr, imm_reloc, s, p) |
| || imm_expr->X_op != O_constant |
| || !VALID_CLTYPE_LD_IMM ((valueT) imm_expr->X_add_number)) |
| break; |
| ip->insn_opcode |= ENCODE_CLTYPE_LD_IMM (imm_expr->X_add_number); |
| goto rvc_imm_done; |
| case 'm': |
| if (riscv_handle_implicit_zero_offset (imm_expr, s)) |
| continue; |
| if (my_getSmallExpression (imm_expr, imm_reloc, s, p) |
| || imm_expr->X_op != O_constant |
| || !VALID_CITYPE_LWSP_IMM ((valueT) imm_expr->X_add_number)) |
| break; |
| ip->insn_opcode |= |
| ENCODE_CITYPE_LWSP_IMM (imm_expr->X_add_number); |
| goto rvc_imm_done; |
| case 'n': |
| if (riscv_handle_implicit_zero_offset (imm_expr, s)) |
| continue; |
| if (my_getSmallExpression (imm_expr, imm_reloc, s, p) |
| || imm_expr->X_op != O_constant |
| || !VALID_CITYPE_LDSP_IMM ((valueT) imm_expr->X_add_number)) |
| break; |
| ip->insn_opcode |= |
| ENCODE_CITYPE_LDSP_IMM (imm_expr->X_add_number); |
| goto rvc_imm_done; |
| case 'o': |
| if (my_getSmallExpression (imm_expr, imm_reloc, s, p) |
| || imm_expr->X_op != O_constant |
| /* C.addiw, c.li, and c.andi allow zero immediate. |
| C.addi allows zero immediate as hint. Otherwise this |
| is same as 'j'. */ |
| || !VALID_CITYPE_IMM ((valueT) imm_expr->X_add_number)) |
| break; |
| ip->insn_opcode |= ENCODE_CITYPE_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 |
| || imm_expr->X_add_number == 0 |
| || !VALID_CIWTYPE_ADDI4SPN_IMM ((valueT) imm_expr->X_add_number)) |
| break; |
| ip->insn_opcode |= |
| ENCODE_CIWTYPE_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_CITYPE_ADDI16SP_IMM ((valueT) imm_expr->X_add_number)) |
| break; |
| ip->insn_opcode |= |
| ENCODE_CITYPE_ADDI16SP_IMM (imm_expr->X_add_number); |
| goto rvc_imm_done; |
| case 'M': |
| if (riscv_handle_implicit_zero_offset (imm_expr, s)) |
| continue; |
| if (my_getSmallExpression (imm_expr, imm_reloc, s, p) |
| || imm_expr->X_op != O_constant |
| || !VALID_CSSTYPE_SWSP_IMM ((valueT) imm_expr->X_add_number)) |
| break; |
| ip->insn_opcode |= |
| ENCODE_CSSTYPE_SWSP_IMM (imm_expr->X_add_number); |
| goto rvc_imm_done; |
| case 'N': |
| if (riscv_handle_implicit_zero_offset (imm_expr, s)) |
| continue; |
| if (my_getSmallExpression (imm_expr, imm_reloc, s, p) |
| || imm_expr->X_op != O_constant |
| || !VALID_CSSTYPE_SDSP_IMM ((valueT) imm_expr->X_add_number)) |
| break; |
| ip->insn_opcode |= |
| ENCODE_CSSTYPE_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_CITYPE_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 'S': /* Floating-point RS1 x8-x15. */ |
| if (!reg_lookup (&s, RCLASS_FPR, ®no) |
| || !(regno >= 8 && regno <= 15)) |
| break; |
| INSERT_OPERAND (CRS1S, *ip, regno % 8); |
| continue; |
| 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; |
| case 'F': |
| switch (*++args) |
| { |
| case '6': |
| 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) |
| { |
| as_bad (_("bad value for compressed funct6 " |
| "field, value must be 0...64")); |
| break; |
| } |
| INSERT_OPERAND (CFUNCT6, *ip, imm_expr->X_add_number); |
| imm_expr->X_op = O_absent; |
| s = expr_end; |
| continue; |
| |
| case '4': |
| 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 >= 16) |
| { |
| as_bad (_("bad value for compressed funct4 " |
| "field, value must be 0...15")); |
| break; |
| } |
| INSERT_OPERAND (CFUNCT4, *ip, imm_expr->X_add_number); |
| imm_expr->X_op = O_absent; |
| s = expr_end; |
| continue; |
| |
| case '3': |
| 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 >= 8) |
| { |
| as_bad (_("bad value for compressed funct3 " |
| "field, value must be 0...7")); |
| break; |
| } |
| INSERT_OPERAND (CFUNCT3, *ip, imm_expr->X_add_number); |
| imm_expr->X_op = O_absent; |
| s = expr_end; |
| continue; |
| |
| case '2': |
| 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 >= 4) |
| { |
| as_bad (_("bad value for compressed funct2 " |
| "field, value must be 0...3")); |
| break; |
| } |
| INSERT_OPERAND (CFUNCT2, *ip, imm_expr->X_add_number); |
| imm_expr->X_op = O_absent; |
| s = expr_end; |
| continue; |
| |
| default: |
| as_bad (_("internal: unknown compressed funct " |
| "field specifier `CF%c'"), *args); |
| } |
| break; |
| |
| default: |
| as_bad (_("internal: unknown compressed field " |
| "specifier `C%c'"), *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, false); |
| if ((unsigned long) imm_expr->X_add_number > 31) |
| as_bad (_("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, false); |
| if ((unsigned long) imm_expr->X_add_number >= xlen) |
| as_bad (_("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, false); |
| if ((unsigned long) imm_expr->X_add_number > 31) |
| as_bad (_("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. */ |
| insn_with_csr = true; |
| explicit_priv_attr = true; |
| if (reg_lookup (&s, RCLASS_CSR, ®no)) |
| INSERT_OPERAND (CSR, *ip, regno); |
| else |
| { |
| my_getExpression (imm_expr, s); |
| check_absolute_expr (ip, imm_expr, true); |
| if ((unsigned long) imm_expr->X_add_number > 0xfff) |
| as_bad (_("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. */ |
| case 'r': /* RS3 */ |
| 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; |
| case 'r': |
| INSERT_OPERAND (RS3, *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); |
| /* Fall through. */ |
| 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 'B': |
| my_getExpression (imm_expr, s); |
| normalize_constant_expr (imm_expr); |
| /* The 'B' format specifier must be a symbol or a constant. */ |
| if (imm_expr->X_op != O_symbol && imm_expr->X_op != O_constant) |
| break; |
| if (imm_expr->X_op == O_symbol) |
| *imm_reloc = BFD_RELOC_32; |
| s = expr_end; |
| continue; |
| |
| case 'j': /* Sign-extended immediate. */ |
| p = percent_op_itype; |
| *imm_reloc = BFD_RELOC_RISCV_LO12_I; |
| 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 '1': |
| /* This is used for TLS, where the fourth operand is |
| %tprel_add, to get a relocation applied to an add |
| instruction, for relaxation to use. */ |
| p = percent_op_rtype; |
| goto alu_op; |
| case '0': /* AMO displacement, which must be zero. */ |
| p = percent_op_null; |
| load_store: |
| if (riscv_handle_implicit_zero_offset (imm_expr, s)) |
| 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) |
| || (*args == '1') |
| || 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)) |
| { |
| if (imm_expr->X_op != O_constant) |
| break; |
| |
| 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; |
| |
| case 'O': |
| switch (*++args) |
| { |
| case '4': |
| if (my_getOpcodeExpression (imm_expr, imm_reloc, s, p) |
| || imm_expr->X_op != O_constant |
| || imm_expr->X_add_number < 0 |
| || imm_expr->X_add_number >= 128 |
| || (imm_expr->X_add_number & 0x3) != 3) |
| { |
| as_bad (_("bad value for opcode field, " |
| "value must be 0...127 and " |
| "lower 2 bits must be 0x3")); |
| break; |
| } |
| INSERT_OPERAND (OP, *ip, imm_expr->X_add_number); |
| imm_expr->X_op = O_absent; |
| s = expr_end; |
| continue; |
| |
| case '2': |
| if (my_getOpcodeExpression (imm_expr, imm_reloc, s, p) |
| || imm_expr->X_op != O_constant |
| || imm_expr->X_add_number < 0 |
| || imm_expr->X_add_number >= 3) |
| { |
| as_bad (_("bad value for opcode field, " |
| "value must be 0...2")); |
| break; |
| } |
| INSERT_OPERAND (OP2, *ip, imm_expr->X_add_number); |
| imm_expr->X_op = O_absent; |
| s = expr_end; |
| continue; |
| |
| default: |
| as_bad (_("internal: unknown opcode field " |
| "specifier `O%c'"), *args); |
| } |
| break; |
| |
| case 'F': |
| switch (*++args) |
| { |
| case '7': |
| 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 >= 128) |
| { |
| as_bad (_("bad value for funct7 field, " |
| "value must be 0...127")); |
| break; |
| } |
| INSERT_OPERAND (FUNCT7, *ip, imm_expr->X_add_number); |
| imm_expr->X_op = O_absent; |
| s = expr_end; |
| continue; |
| |
| case '3': |
| 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 >= 8) |
| { |
| as_bad (_("bad value for funct3 field, " |
| "value must be 0...7")); |
| break; |
| } |
| INSERT_OPERAND (FUNCT3, *ip, imm_expr->X_add_number); |
| imm_expr->X_op = O_absent; |
| s = expr_end; |
| continue; |
| |
| case '2': |
| 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 >= 4) |
| { |
| as_bad (_("bad value for funct2 field, " |
| "value must be 0...3")); |
| break; |
| } |
| INSERT_OPERAND (FUNCT2, *ip, imm_expr->X_add_number); |
| imm_expr->X_op = O_absent; |
| s = expr_end; |
| continue; |
| |
| default: |
| as_bad (_("internal: unknown funct field " |
| "specifier `F%c'\n"), *args); |
| } |
| break; |
| |
| case 'z': |
| if (my_getSmallExpression (imm_expr, imm_reloc, s, p) |
| || imm_expr->X_op != O_constant |
| || imm_expr->X_add_number != 0) |
| break; |
| s = expr_end; |
| imm_expr->X_op = O_absent; |
| continue; |
| |
| default: |
| as_fatal (_("internal: unknown argument type `%c'"), *args); |
| } |
| break; |
| } |
| s = argsStart; |
| error = _("illegal operands"); |
| insn_with_csr = false; |
| } |
| |
| out: |
| /* Restore the character we might have clobbered above. */ |
| if (save_c) |
| *(argsStart - 1) = save_c; |
| |
| return error; |
| } |
| |
| /* Similar to riscv_ip, but assembles an instruction according to the |
| hardcode values of .insn directive. */ |
| |
| static const char * |
| riscv_ip_hardcode (char *str, |
| struct riscv_cl_insn *ip, |
| expressionS *imm_expr, |
| const char *error) |
| { |
| struct riscv_opcode *insn; |
| insn_t values[2] = {0, 0}; |
| unsigned int num = 0; |
| |
| input_line_pointer = str; |
| do |
| { |
| expression (imm_expr); |
| if (imm_expr->X_op != O_constant) |
| { |
| /* The first value isn't constant, so it should be |
| .insn <type> <operands>. We have been parsed it |
| in the riscv_ip. */ |
| if (num == 0) |
| return error; |
| return _("values must be constant"); |
| } |
| values[num++] = (insn_t) imm_expr->X_add_number; |
| } |
| while (*input_line_pointer++ == ',' && num < 2); |
| |
| input_line_pointer--; |
| if (*input_line_pointer != '\0') |
| return _("unrecognized values"); |
| |
| insn = XNEW (struct riscv_opcode); |
| insn->match = values[num - 1]; |
| create_insn (ip, insn); |
| unsigned int bytes = riscv_insn_length (insn->match); |
| if (values[num - 1] >> (8 * bytes) != 0 |
| || (num == 2 && values[0] != bytes)) |
| return _("value conflicts with instruction length"); |
| |
| return NULL; |
| } |
| |
| void |
| md_assemble (char *str) |
| { |
| struct riscv_cl_insn insn; |
| expressionS imm_expr; |
| bfd_reloc_code_real_type imm_reloc = BFD_RELOC_UNUSED; |
| |
| /* The architecture and privileged elf attributes should be set |
| before assembling. */ |
| if (!start_assemble) |
| { |
| start_assemble = true; |
| |
| riscv_set_abi_by_arch (); |
| if (!riscv_set_default_priv_spec (NULL)) |
| return; |
| } |
| |
| riscv_mapping_state (MAP_INSN, 0); |
| |
| const char *error = riscv_ip (str, &insn, &imm_expr, &imm_reloc, op_hash); |
| |
| 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) |
| { |
| if (target_big_endian) |
| number_to_chars_bigendian (buf, val, n); |
| else |
| number_to_chars_littleendian (buf, val, n); |
| } |
| |
| const char *md_shortopts = "O::g::G:"; |
| |
| enum options |
| { |
| OPTION_MARCH = OPTION_MD_BASE, |
| OPTION_PIC, |
| OPTION_NO_PIC, |
| OPTION_MABI, |
| OPTION_RELAX, |
| OPTION_NO_RELAX, |
| OPTION_ARCH_ATTR, |
| OPTION_NO_ARCH_ATTR, |
| OPTION_CSR_CHECK, |
| OPTION_NO_CSR_CHECK, |
| OPTION_MISA_SPEC, |
| OPTION_MPRIV_SPEC, |
| OPTION_BIG_ENDIAN, |
| OPTION_LITTLE_ENDIAN, |
| OPTION_END_OF_ENUM |
| }; |
| |
| struct option md_longopts[] = |
| { |
| {"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}, |
| {"mabi", required_argument, NULL, OPTION_MABI}, |
| {"mrelax", no_argument, NULL, OPTION_RELAX}, |
| {"mno-relax", no_argument, NULL, OPTION_NO_RELAX}, |
| {"march-attr", no_argument, NULL, OPTION_ARCH_ATTR}, |
| {"mno-arch-attr", no_argument, NULL, OPTION_NO_ARCH_ATTR}, |
| {"mcsr-check", no_argument, NULL, OPTION_CSR_CHECK}, |
| {"mno-csr-check", no_argument, NULL, OPTION_NO_CSR_CHECK}, |
| {"misa-spec", required_argument, NULL, OPTION_MISA_SPEC}, |
| {"mpriv-spec", required_argument, NULL, OPTION_MPRIV_SPEC}, |
| {"mbig-endian", no_argument, NULL, OPTION_BIG_ENDIAN}, |
| {"mlittle-endian", no_argument, NULL, OPTION_LITTLE_ENDIAN}, |
| |
| {NULL, no_argument, NULL, 0} |
| }; |
| size_t md_longopts_size = sizeof (md_longopts); |
| |
| int |
| md_parse_option (int c, const char *arg) |
| { |
| switch (c) |
| { |
| case OPTION_MARCH: |
| default_arch_with_ext = arg; |
| break; |
| |
| case OPTION_NO_PIC: |
| riscv_opts.pic = false; |
| break; |
| |
| case OPTION_PIC: |
| riscv_opts.pic = true; |
| break; |
| |
| case OPTION_MABI: |
| if (strcmp (arg, "ilp32") == 0) |
| riscv_set_abi (32, FLOAT_ABI_SOFT, false); |
| else if (strcmp (arg, "ilp32e") == 0) |
| riscv_set_abi (32, FLOAT_ABI_SOFT, true); |
| else if (strcmp (arg, "ilp32f") == 0) |
| riscv_set_abi (32, FLOAT_ABI_SINGLE, false); |
| else if (strcmp (arg, "ilp32d") == 0) |
| riscv_set_abi (32, FLOAT_ABI_DOUBLE, false); |
| else if (strcmp (arg, "ilp32q") == 0) |
| riscv_set_abi (32, FLOAT_ABI_QUAD, false); |
| else if (strcmp (arg, "lp64") == 0) |
| riscv_set_abi (64, FLOAT_ABI_SOFT, false); |
| else if (strcmp (arg, "lp64f") == 0) |
| riscv_set_abi (64, FLOAT_ABI_SINGLE, false); |
| else if (strcmp (arg, "lp64d") == 0) |
| riscv_set_abi (64, FLOAT_ABI_DOUBLE, false); |
| else if (strcmp (arg, "lp64q") == 0) |
| riscv_set_abi (64, FLOAT_ABI_QUAD, false); |
| else |
| return 0; |
| explicit_mabi = true; |
| break; |
| |
| case OPTION_RELAX: |
| riscv_opts.relax = true; |
| break; |
| |
| case OPTION_NO_RELAX: |
| riscv_opts.relax = false; |
| break; |
| |
| case OPTION_ARCH_ATTR: |
| riscv_opts.arch_attr = true; |
| break; |
| |
| case OPTION_NO_ARCH_ATTR: |
| riscv_opts.arch_attr = false; |
| break; |
| |
| case OPTION_CSR_CHECK: |
| riscv_opts.csr_check = true; |
| break; |
| |
| case OPTION_NO_CSR_CHECK: |
| riscv_opts.csr_check = false; |
| break; |
| |
| case OPTION_MISA_SPEC: |
| return riscv_set_default_isa_spec (arg); |
| |
| case OPTION_MPRIV_SPEC: |
| return riscv_set_default_priv_spec (arg); |
| |
| case OPTION_BIG_ENDIAN: |
| target_big_endian = 1; |
| break; |
| |
| case OPTION_LITTLE_ENDIAN: |
| target_big_endian = 0; |
| break; |
| |
| default: |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| void |
| riscv_after_parse_args (void) |
| { |
| /* The --with-arch is optional for now, so we still need to set the xlen |
| according to the default_arch, which is set by the --target. */ |
| if (xlen == 0) |
| { |
| if (strcmp (default_arch, "riscv32") == |