blob: f7e0c929aa04cc08a177abf22b8490566b6a15f1 [file] [log] [blame]
/* 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, &regno))
{
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, &regno)
|| !(regno >= 8 && regno <= 15))
break;
INSERT_OPERAND (CRS1S, *ip, regno % 8);
continue;
case 'w': /* RS1 x8-x15, constrained to equal RD x8-x15. */
if (!reg_lookup (&s, RCLASS_GPR, &regno)
|| EXTRACT_OPERAND (CRS1S, ip->insn_opcode) + 8 != regno)
break;
continue;
case 't': /* RS2 x8-x15. */
if (!reg_lookup (&s, RCLASS_GPR, &regno)
|| !(regno >= 8 && regno <= 15))
break;
INSERT_OPERAND (CRS2S, *ip, regno % 8);
continue;
case 'x': /* RS2 x8-x15, constrained to equal RD x8-x15. */
if (!reg_lookup (&s, RCLASS_GPR, &regno)
|| EXTRACT_OPERAND (CRS2S, ip->insn_opcode) + 8 != regno)
break;
continue;
case 'U': /* RS1, constrained to equal RD. */
if (!reg_lookup (&s, RCLASS_GPR, &regno)
|| EXTRACT_OPERAND (RD, ip->insn_opcode) != regno)
break;
continue;
case 'V': /* RS2 */
if (!reg_lookup (&s, RCLASS_GPR, &regno))
break;
INSERT_OPERAND (CRS2, *ip, regno);
continue;
case 'c': /* RS1, constrained to equal sp. */
if (!reg_lookup (&s, RCLASS_GPR, &regno)
|| regno != X_SP)
break;
continue;
case 'z': /* RS2, constrained to equal x0. */
if (!reg_lookup (&s, RCLASS_GPR, &regno)
|| 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, &regno)
|| !(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, &regno)
|| !(regno >= 8 && regno <= 15))
break;
INSERT_OPERAND (CRS2S, *ip, regno % 8);
continue;
case 'T': /* Floating-point RS2. */
if (!reg_lookup (&s, RCLASS_FPR, &regno))
break;
INSERT_OPERAND (CRS2, *ip, regno);
continue;
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, &regno))
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), &regno))
{
INSERT_OPERAND (RM, *ip, regno);
continue;
}
break;
case 'P':
case 'Q': /* Fence predecessor/successor. */
if (arg_lookup (&s, riscv_pred_succ, ARRAY_SIZE (riscv_pred_succ),
&regno))
{
if (*args == 'P')
INSERT_OPERAND (PRED, *ip, regno);
else
INSERT_OPERAND (SUCC, *ip, regno);
continue;
}
break;
case 'd': /* Destination register. */
case 's': /* Source register. */
case 't': /* Target register. */
case 'r': /* RS3 */
if (reg_lookup (&s, RCLASS_GPR, &regno))
{
c = *args;
if (*s == ' ')
++s;
/* Now that we have assembled one operand, we use the args
string to figure out where it goes in the instruction. */
switch (c)
{
case 's':
INSERT_OPERAND (RS1, *ip, regno);
break;
case 'd':
INSERT_OPERAND (RD, *ip, regno);
break;
case 't':
INSERT_OPERAND (RS2, *ip, regno);
break;
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, &regno))
{
c = *args;
if (*s == ' ')
++s;
switch (c)
{
case 'D':
INSERT_OPERAND (RD, *ip, regno);
break;
case 'S':
INSERT_OPERAND (RS1, *ip, regno);
break;
case 'U':
INSERT_OPERAND (RS1, *ip, regno);
/* 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 (