blob: b8634f782f88ec213fd4a846b8cb63805ea538be [file] [log] [blame]
/* tc-xtensa.c -- Assemble Xtensa instructions.
Copyright (C) 2003-2021 Free Software Foundation, Inc.
This file is part of GAS, the GNU Assembler.
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 GAS; see the file COPYING. If not, write to
the Free Software Foundation, 51 Franklin Street - Fifth Floor, Boston,
MA 02110-1301, USA. */
#include "as.h"
#include <limits.h>
#include "sb.h"
#include "safe-ctype.h"
#include "tc-xtensa.h"
#include "subsegs.h"
#include "xtensa-relax.h"
#include "dwarf2dbg.h"
#include "xtensa-istack.h"
#include "xtensa-config.h"
#include "elf/xtensa.h"
/* Provide default values for new configuration settings. */
#ifndef XTHAL_ABI_WINDOWED
#define XTHAL_ABI_WINDOWED 0
#endif
#ifndef XTHAL_ABI_CALL0
#define XTHAL_ABI_CALL0 1
#endif
#ifndef XTENSA_MARCH_EARLIEST
#define XTENSA_MARCH_EARLIEST 0
#endif
#ifndef uint32
#define uint32 unsigned int
#endif
#ifndef int32
#define int32 signed int
#endif
/* Notes:
Naming conventions (used somewhat inconsistently):
The xtensa_ functions are exported
The xg_ functions are internal
We also have a couple of different extensibility mechanisms.
1) The idiom replacement:
This is used when a line is first parsed to
replace an instruction pattern with another instruction
It is currently limited to replacements of instructions
with constant operands.
2) The xtensa-relax.c mechanism that has stronger instruction
replacement patterns. When an instruction's immediate field
does not fit the next instruction sequence is attempted.
In addition, "narrow" opcodes are supported this way. */
/* Define characters with special meanings to GAS. */
const char comment_chars[] = "#";
const char line_comment_chars[] = "#";
const char line_separator_chars[] = ";";
const char EXP_CHARS[] = "eE";
const char FLT_CHARS[] = "rRsSfFdDxXpP";
/* Flags to indicate whether the hardware supports the density and
absolute literals options. */
bool density_supported;
bool absolute_literals_supported;
static unsigned microarch_earliest;
static vliw_insn cur_vinsn;
unsigned xtensa_num_pipe_stages;
unsigned xtensa_fetch_width;
static enum debug_info_type xt_saved_debug_type = DEBUG_NONE;
/* Some functions are only valid in the front end. This variable
allows us to assert that we haven't crossed over into the
back end. */
static bool past_xtensa_end = false;
/* Flags for properties of the last instruction in a segment. */
#define FLAG_IS_A0_WRITER 0x1
#define FLAG_IS_BAD_LOOPEND 0x2
/* We define a special segment names ".literal" to place literals
into. The .fini and .init sections are special because they
contain code that is moved together by the linker. We give them
their own special .fini.literal and .init.literal sections. */
#define LITERAL_SECTION_NAME xtensa_section_rename (".literal")
#define LIT4_SECTION_NAME xtensa_section_rename (".lit4")
#define INIT_SECTION_NAME xtensa_section_rename (".init")
#define FINI_SECTION_NAME xtensa_section_rename (".fini")
/* This type is used for the directive_stack to keep track of the
state of the literal collection pools. If lit_prefix is set, it is
used to determine the literal section names; otherwise, the literal
sections are determined based on the current text section. The
lit_seg and lit4_seg fields cache these literal sections, with the
current_text_seg field used a tag to indicate whether the cached
values are valid. */
typedef struct lit_state_struct
{
char *lit_prefix;
segT current_text_seg;
segT lit_seg;
segT lit4_seg;
} lit_state;
static lit_state default_lit_sections;
/* We keep a list of literal segments. The seg_list type is the node
for this list. The literal_head pointer is the head of the list,
with the literal_head_h dummy node at the start. */
typedef struct seg_list_struct
{
struct seg_list_struct *next;
segT seg;
} seg_list;
static seg_list literal_head_h;
static seg_list *literal_head = &literal_head_h;
/* Lists of symbols. We keep a list of symbols that label the current
instruction, so that we can adjust the symbols when inserting alignment
for various instructions. We also keep a list of all the symbols on
literals, so that we can fix up those symbols when the literals are
later moved into the text sections. */
typedef struct sym_list_struct
{
struct sym_list_struct *next;
symbolS *sym;
} sym_list;
static sym_list *insn_labels = NULL;
static sym_list *free_insn_labels = NULL;
static sym_list *saved_insn_labels = NULL;
static sym_list *literal_syms;
/* Flags to determine whether to prefer const16 or l32r
if both options are available. */
int prefer_const16 = 0;
int prefer_l32r = 0;
/* Global flag to indicate when we are emitting literals. */
int generating_literals = 0;
/* The following PROPERTY table definitions are copied from
<elf/xtensa.h> and must be kept in sync with the code there. */
/* Flags in the property tables to specify whether blocks of memory
are literals, instructions, data, or unreachable. For
instructions, blocks that begin loop targets and branch targets are
designated. Blocks that do not allow density, instruction
reordering or transformation are also specified. Finally, for
branch targets, branch target alignment priority is included.
Alignment of the next block is specified in the current block
and the size of the current block does not include any fill required
to align to the next block. */
#define XTENSA_PROP_LITERAL 0x00000001
#define XTENSA_PROP_INSN 0x00000002
#define XTENSA_PROP_DATA 0x00000004
#define XTENSA_PROP_UNREACHABLE 0x00000008
/* Instruction only properties at beginning of code. */
#define XTENSA_PROP_INSN_LOOP_TARGET 0x00000010
#define XTENSA_PROP_INSN_BRANCH_TARGET 0x00000020
/* Instruction only properties about code. */
#define XTENSA_PROP_INSN_NO_DENSITY 0x00000040
#define XTENSA_PROP_INSN_NO_REORDER 0x00000080
/* Historically, NO_TRANSFORM was a property of instructions,
but it should apply to literals under certain circumstances. */
#define XTENSA_PROP_NO_TRANSFORM 0x00000100
/* Branch target alignment information. This transmits information
to the linker optimization about the priority of aligning a
particular block for branch target alignment: None, low priority,
high priority, or required. These only need to be checked in
instruction blocks marked as XTENSA_PROP_INSN_BRANCH_TARGET.
Common usage is
switch (GET_XTENSA_PROP_BT_ALIGN (flags))
case XTENSA_PROP_BT_ALIGN_NONE:
case XTENSA_PROP_BT_ALIGN_LOW:
case XTENSA_PROP_BT_ALIGN_HIGH:
case XTENSA_PROP_BT_ALIGN_REQUIRE:
*/
#define XTENSA_PROP_BT_ALIGN_MASK 0x00000600
/* No branch target alignment. */
#define XTENSA_PROP_BT_ALIGN_NONE 0x0
/* Low priority branch target alignment. */
#define XTENSA_PROP_BT_ALIGN_LOW 0x1
/* High priority branch target alignment. */
#define XTENSA_PROP_BT_ALIGN_HIGH 0x2
/* Required branch target alignment. */
#define XTENSA_PROP_BT_ALIGN_REQUIRE 0x3
#define SET_XTENSA_PROP_BT_ALIGN(flag, align) \
(((flag) & (~XTENSA_PROP_BT_ALIGN_MASK)) | \
(((align) << 9) & XTENSA_PROP_BT_ALIGN_MASK))
/* Alignment is specified in the block BEFORE the one that needs
alignment. Up to 5 bits. Use GET_XTENSA_PROP_ALIGNMENT(flags) to
get the required alignment specified as a power of 2. Use
SET_XTENSA_PROP_ALIGNMENT(flags, pow2) to set the required
alignment. Be careful of side effects since the SET will evaluate
flags twice. Also, note that the SIZE of a block in the property
table does not include the alignment size, so the alignment fill
must be calculated to determine if two blocks are contiguous.
TEXT_ALIGN is not currently implemented but is a placeholder for a
possible future implementation. */
#define XTENSA_PROP_ALIGN 0x00000800
#define XTENSA_PROP_ALIGNMENT_MASK 0x0001f000
#define SET_XTENSA_PROP_ALIGNMENT(flag, align) \
(((flag) & (~XTENSA_PROP_ALIGNMENT_MASK)) | \
(((align) << 12) & XTENSA_PROP_ALIGNMENT_MASK))
#define XTENSA_PROP_INSN_ABSLIT 0x00020000
/* Structure for saving instruction and alignment per-fragment data
that will be written to the object file. This structure is
equivalent to the actual data that will be written out to the file
but is easier to use. We provide a conversion to file flags
in frag_flags_to_number. */
typedef struct frag_flags_struct frag_flags;
struct frag_flags_struct
{
/* is_literal should only be used after xtensa_move_literals.
If you need to check if you are generating a literal fragment,
then use the generating_literals global. */
unsigned is_literal : 1;
unsigned is_insn : 1;
unsigned is_data : 1;
unsigned is_unreachable : 1;
/* is_specific_opcode implies no_transform. */
unsigned is_no_transform : 1;
struct
{
unsigned is_loop_target : 1;
unsigned is_branch_target : 1; /* Branch targets have a priority. */
unsigned bt_align_priority : 2;
unsigned is_no_density : 1;
/* no_longcalls flag does not need to be placed in the object file. */
unsigned is_no_reorder : 1;
/* Uses absolute literal addressing for l32r. */
unsigned is_abslit : 1;
} insn;
unsigned is_align : 1;
unsigned alignment : 5;
};
/* Structure for saving information about a block of property data
for frags that have the same flags. */
struct xtensa_block_info_struct
{
segT sec;
bfd_vma offset;
size_t size;
frag_flags flags;
struct xtensa_block_info_struct *next;
};
/* Structure for saving the current state before emitting literals. */
typedef struct emit_state_struct
{
const char *name;
segT now_seg;
subsegT now_subseg;
int generating_literals;
} emit_state;
/* Opcode placement information */
typedef unsigned long long bitfield;
#define bit_is_set(bit, bf) ((bf) & (0x01ll << (bit)))
#define set_bit(bit, bf) ((bf) |= (0x01ll << (bit)))
#define clear_bit(bit, bf) ((bf) &= ~(0x01ll << (bit)))
#define MAX_FORMATS 32
typedef struct op_placement_info_struct
{
int num_formats;
/* A number describing how restrictive the issue is for this
opcode. For example, an opcode that fits lots of different
formats has a high freedom, as does an opcode that fits
only one format but many slots in that format. The most
restrictive is the opcode that fits only one slot in one
format. */
int issuef;
xtensa_format narrowest;
char narrowest_size;
char narrowest_slot;
/* formats is a bitfield with the Nth bit set
if the opcode fits in the Nth xtensa_format. */
bitfield formats;
/* slots[N]'s Mth bit is set if the op fits in the
Mth slot of the Nth xtensa_format. */
bitfield slots[MAX_FORMATS];
/* A count of the number of slots in a given format
an op can fit (i.e., the bitcount of the slot field above). */
char slots_in_format[MAX_FORMATS];
} op_placement_info, *op_placement_info_table;
op_placement_info_table op_placement_table;
/* Extra expression types. */
#define O_pltrel O_md1 /* like O_symbol but use a PLT reloc */
#define O_hi16 O_md2 /* use high 16 bits of symbolic value */
#define O_lo16 O_md3 /* use low 16 bits of symbolic value */
#define O_pcrel O_md4 /* value is a PC-relative offset */
#define O_tlsfunc O_md5 /* TLS_FUNC/TLSDESC_FN relocation */
#define O_tlsarg O_md6 /* TLS_ARG/TLSDESC_ARG relocation */
#define O_tlscall O_md7 /* TLS_CALL relocation */
#define O_tpoff O_md8 /* TPOFF relocation */
#define O_dtpoff O_md9 /* DTPOFF relocation */
struct suffix_reloc_map
{
const char *suffix;
int length;
bfd_reloc_code_real_type reloc;
operatorT operator;
};
#define SUFFIX_MAP(str, reloc, op) { str, sizeof (str) - 1, reloc, op }
static struct suffix_reloc_map suffix_relocs[] =
{
SUFFIX_MAP ("l", BFD_RELOC_LO16, O_lo16),
SUFFIX_MAP ("h", BFD_RELOC_HI16, O_hi16),
SUFFIX_MAP ("plt", BFD_RELOC_XTENSA_PLT, O_pltrel),
SUFFIX_MAP ("pcrel", BFD_RELOC_32_PCREL, O_pcrel),
SUFFIX_MAP ("tlsfunc", BFD_RELOC_XTENSA_TLS_FUNC, O_tlsfunc),
SUFFIX_MAP ("tlsarg", BFD_RELOC_XTENSA_TLS_ARG, O_tlsarg),
SUFFIX_MAP ("tlscall", BFD_RELOC_XTENSA_TLS_CALL, O_tlscall),
SUFFIX_MAP ("tpoff", BFD_RELOC_XTENSA_TLS_TPOFF, O_tpoff),
SUFFIX_MAP ("dtpoff", BFD_RELOC_XTENSA_TLS_DTPOFF, O_dtpoff),
};
/* Directives. */
typedef enum
{
directive_none = 0,
directive_literal,
directive_density,
directive_transform,
directive_freeregs,
directive_longcalls,
directive_literal_prefix,
directive_schedule,
directive_absolute_literals,
directive_last_directive
} directiveE;
typedef struct
{
const char *name;
bool can_be_negated;
} directive_infoS;
const directive_infoS directive_info[] =
{
{ "none", false },
{ "literal", false },
{ "density", true },
{ "transform", true },
{ "freeregs", false },
{ "longcalls", true },
{ "literal_prefix", false },
{ "schedule", true },
{ "absolute-literals", true }
};
bool directive_state[] =
{
false, /* none */
false, /* literal */
false, /* density */
true, /* transform */
false, /* freeregs */
false, /* longcalls */
false, /* literal_prefix */
false, /* schedule */
false /* absolute_literals */
};
/* A circular list of all potential and actual literal pool locations
in a segment. */
struct litpool_frag
{
struct litpool_frag *next;
struct litpool_frag *prev;
fragS *fragP;
addressT addr;
short priority; /* 1, 2, or 3 -- 1 is highest */
short original_priority;
int literal_count;
};
/* Map a segment to its litpool_frag list. */
struct litpool_seg
{
struct litpool_seg *next;
asection *seg;
struct litpool_frag frag_list;
int frag_count; /* since last litpool location */
};
static struct litpool_seg litpool_seg_list;
/* Limit maximal size of auto litpool by half of the j range. */
#define MAX_AUTO_POOL_LITERALS 16384
/* Limit maximal size of explicit literal pool by l32r range. */
#define MAX_EXPLICIT_POOL_LITERALS 65536
#define MAX_POOL_LITERALS \
(auto_litpools ? MAX_AUTO_POOL_LITERALS : MAX_EXPLICIT_POOL_LITERALS)
/* Directive functions. */
static void xtensa_begin_directive (int);
static void xtensa_end_directive (int);
static void xtensa_literal_prefix (void);
static void xtensa_literal_position (int);
static void xtensa_literal_pseudo (int);
static void xtensa_frequency_pseudo (int);
static void xtensa_elf_cons (int);
static void xtensa_leb128 (int);
/* Parsing and Idiom Translation. */
static bfd_reloc_code_real_type xtensa_elf_suffix (char **, expressionS *);
/* Various Other Internal Functions. */
extern bool xg_is_single_relaxable_insn (TInsn *, TInsn *, bool);
static bool xg_build_to_insn (TInsn *, TInsn *, BuildInstr *);
static void xtensa_mark_literal_pool_location (void);
static addressT get_expanded_loop_offset (xtensa_opcode);
static fragS *get_literal_pool_location (segT);
static void set_literal_pool_location (segT, fragS *);
static void xtensa_set_frag_assembly_state (fragS *);
static void finish_vinsn (vliw_insn *);
static bool emit_single_op (TInsn *);
static int total_frag_text_expansion (fragS *);
static bool use_trampolines = true;
static void xtensa_check_frag_count (void);
static void xtensa_create_trampoline_frag (bool);
static void xtensa_maybe_create_trampoline_frag (void);
struct trampoline_frag;
static int init_trampoline_frag (fragS *);
static fixS *xg_append_jump (fragS *fragP, symbolS *sym, offsetT offset);
static void xtensa_maybe_create_literal_pool_frag (bool, bool);
static bool auto_litpools = false;
static int auto_litpool_limit = 0;
static bool xtensa_is_init_fini (segT seg);
/* Alignment Functions. */
static int get_text_align_power (unsigned);
static int get_text_align_max_fill_size (int, bool, bool);
static int branch_align_power (segT);
/* Helpers for xtensa_relax_frag(). */
static long relax_frag_add_nop (fragS *);
/* Accessors for additional per-subsegment information. */
static unsigned get_last_insn_flags (segT, subsegT);
static void set_last_insn_flags (segT, subsegT, unsigned, bool);
static float get_subseg_total_freq (segT, subsegT);
static float get_subseg_target_freq (segT, subsegT);
static void set_subseg_freq (segT, subsegT, float, float);
/* Segment list functions. */
static void xtensa_move_literals (void);
static void xtensa_reorder_segments (void);
static void xtensa_switch_to_literal_fragment (emit_state *);
static void xtensa_switch_to_non_abs_literal_fragment (emit_state *);
static void xtensa_switch_section_emit_state (emit_state *, segT, subsegT);
static void xtensa_restore_emit_state (emit_state *);
static segT cache_literal_section (bool);
/* op_placement_info functions. */
static void init_op_placement_info_table (void);
extern bool opcode_fits_format_slot (xtensa_opcode, xtensa_format, int);
static int xg_get_single_size (xtensa_opcode);
static xtensa_format xg_get_single_format (xtensa_opcode);
static int xg_get_single_slot (xtensa_opcode);
/* TInsn and IStack functions. */
static bool tinsn_has_symbolic_operands (const TInsn *);
static bool tinsn_has_invalid_symbolic_operands (const TInsn *);
static bool tinsn_has_complex_operands (const TInsn *);
static bool tinsn_to_insnbuf (TInsn *, xtensa_insnbuf);
static bool tinsn_check_arguments (const TInsn *);
static void tinsn_from_chars (TInsn *, char *, int);
static void tinsn_immed_from_frag (TInsn *, fragS *, int);
static int get_num_stack_text_bytes (IStack *);
static int get_num_stack_literal_bytes (IStack *);
static bool tinsn_to_slotbuf (xtensa_format, int, TInsn *, xtensa_insnbuf);
/* vliw_insn functions. */
static void xg_init_vinsn (vliw_insn *);
static void xg_copy_vinsn (vliw_insn *, vliw_insn *);
static void xg_clear_vinsn (vliw_insn *);
static bool vinsn_has_specific_opcodes (vliw_insn *);
static void xg_free_vinsn (vliw_insn *);
static bool vinsn_to_insnbuf
(vliw_insn *, char *, fragS *, bool);
static void vinsn_from_chars (vliw_insn *, char *);
/* Expression Utilities. */
bool expr_is_const (const expressionS *);
offsetT get_expr_const (const expressionS *);
void set_expr_const (expressionS *, offsetT);
bool expr_is_register (const expressionS *);
offsetT get_expr_register (const expressionS *);
void set_expr_symbol_offset (expressionS *, symbolS *, offsetT);
bool expr_is_equal (expressionS *, expressionS *);
static void copy_expr (expressionS *, const expressionS *);
/* Section renaming. */
static void build_section_rename (const char *);
/* ISA imported from bfd. */
extern xtensa_isa xtensa_default_isa;
extern int target_big_endian;
static xtensa_opcode xtensa_addi_opcode;
static xtensa_opcode xtensa_addmi_opcode;
static xtensa_opcode xtensa_call0_opcode;
static xtensa_opcode xtensa_call4_opcode;
static xtensa_opcode xtensa_call8_opcode;
static xtensa_opcode xtensa_call12_opcode;
static xtensa_opcode xtensa_callx0_opcode;
static xtensa_opcode xtensa_callx4_opcode;
static xtensa_opcode xtensa_callx8_opcode;
static xtensa_opcode xtensa_callx12_opcode;
static xtensa_opcode xtensa_const16_opcode;
static xtensa_opcode xtensa_entry_opcode;
static xtensa_opcode xtensa_extui_opcode;
static xtensa_opcode xtensa_movi_opcode;
static xtensa_opcode xtensa_movi_n_opcode;
static xtensa_opcode xtensa_isync_opcode;
static xtensa_opcode xtensa_j_opcode;
static xtensa_opcode xtensa_jx_opcode;
static xtensa_opcode xtensa_l32r_opcode;
static xtensa_opcode xtensa_loop_opcode;
static xtensa_opcode xtensa_loopnez_opcode;
static xtensa_opcode xtensa_loopgtz_opcode;
static xtensa_opcode xtensa_nop_opcode;
static xtensa_opcode xtensa_nop_n_opcode;
static xtensa_opcode xtensa_or_opcode;
static xtensa_opcode xtensa_ret_opcode;
static xtensa_opcode xtensa_ret_n_opcode;
static xtensa_opcode xtensa_retw_opcode;
static xtensa_opcode xtensa_retw_n_opcode;
static xtensa_opcode xtensa_rsr_lcount_opcode;
static xtensa_opcode xtensa_waiti_opcode;
static int config_max_slots = 0;
/* Command-line Options. */
bool use_literal_section = true;
enum flix_level produce_flix = FLIX_ALL;
static bool align_targets = true;
static bool warn_unaligned_branch_targets = false;
static bool has_a0_b_retw = false;
static bool workaround_a0_b_retw = false;
static bool workaround_b_j_loop_end = false;
static bool workaround_short_loop = false;
static bool maybe_has_short_loop = false;
static bool workaround_close_loop_end = false;
static bool maybe_has_close_loop_end = false;
static bool enforce_three_byte_loop_align = false;
static bool opt_linkrelax = true;
/* When workaround_short_loops is TRUE, all loops with early exits must
have at least 3 instructions. workaround_all_short_loops is a modifier
to the workaround_short_loop flag. In addition to the
workaround_short_loop actions, all straightline loopgtz and loopnez
must have at least 3 instructions. */
static bool workaround_all_short_loops = false;
/* Generate individual property section for every section.
This option is defined in BDF library. */
extern bool elf32xtensa_separate_props;
/* Xtensa ABI.
This option is defined in BDF library. */
extern int elf32xtensa_abi;
static void
xtensa_setup_hw_workarounds (int earliest, int latest)
{
if (earliest > latest)
as_fatal (_("illegal range of target hardware versions"));
/* Enable all workarounds for pre-T1050.0 hardware. */
if (earliest < 105000 || latest < 105000)
{
workaround_a0_b_retw |= true;
workaround_b_j_loop_end |= true;
workaround_short_loop |= true;
workaround_close_loop_end |= true;
workaround_all_short_loops |= true;
enforce_three_byte_loop_align = true;
}
}
enum
{
option_density = OPTION_MD_BASE,
option_no_density,
option_flix,
option_no_generate_flix,
option_no_flix,
option_relax,
option_no_relax,
option_link_relax,
option_no_link_relax,
option_generics,
option_no_generics,
option_transform,
option_no_transform,
option_text_section_literals,
option_no_text_section_literals,
option_absolute_literals,
option_no_absolute_literals,
option_align_targets,
option_no_align_targets,
option_warn_unaligned_targets,
option_longcalls,
option_no_longcalls,
option_workaround_a0_b_retw,
option_no_workaround_a0_b_retw,
option_workaround_b_j_loop_end,
option_no_workaround_b_j_loop_end,
option_workaround_short_loop,
option_no_workaround_short_loop,
option_workaround_all_short_loops,
option_no_workaround_all_short_loops,
option_workaround_close_loop_end,
option_no_workaround_close_loop_end,
option_no_workarounds,
option_rename_section_name,
option_prefer_l32r,
option_prefer_const16,
option_target_hardware,
option_trampolines,
option_no_trampolines,
option_auto_litpools,
option_no_auto_litpools,
option_auto_litpool_limit,
option_separate_props,
option_no_separate_props,
option_abi_windowed,
option_abi_call0,
};
const char *md_shortopts = "";
struct option md_longopts[] =
{
{ "density", no_argument, NULL, option_density },
{ "no-density", no_argument, NULL, option_no_density },
{ "flix", no_argument, NULL, option_flix },
{ "no-generate-flix", no_argument, NULL, option_no_generate_flix },
{ "no-allow-flix", no_argument, NULL, option_no_flix },
/* Both "relax" and "generics" are deprecated and treated as equivalent
to the "transform" option. */
{ "relax", no_argument, NULL, option_relax },
{ "no-relax", no_argument, NULL, option_no_relax },
{ "generics", no_argument, NULL, option_generics },
{ "no-generics", no_argument, NULL, option_no_generics },
{ "transform", no_argument, NULL, option_transform },
{ "no-transform", no_argument, NULL, option_no_transform },
{ "text-section-literals", no_argument, NULL, option_text_section_literals },
{ "no-text-section-literals", no_argument, NULL,
option_no_text_section_literals },
{ "absolute-literals", no_argument, NULL, option_absolute_literals },
{ "no-absolute-literals", no_argument, NULL, option_no_absolute_literals },
/* This option was changed from -align-target to -target-align
because it conflicted with the "-al" option. */
{ "target-align", no_argument, NULL, option_align_targets },
{ "no-target-align", no_argument, NULL, option_no_align_targets },
{ "warn-unaligned-targets", no_argument, NULL,
option_warn_unaligned_targets },
{ "longcalls", no_argument, NULL, option_longcalls },
{ "no-longcalls", no_argument, NULL, option_no_longcalls },
{ "no-workaround-a0-b-retw", no_argument, NULL,
option_no_workaround_a0_b_retw },
{ "workaround-a0-b-retw", no_argument, NULL, option_workaround_a0_b_retw },
{ "no-workaround-b-j-loop-end", no_argument, NULL,
option_no_workaround_b_j_loop_end },
{ "workaround-b-j-loop-end", no_argument, NULL,
option_workaround_b_j_loop_end },
{ "no-workaround-short-loops", no_argument, NULL,
option_no_workaround_short_loop },
{ "workaround-short-loops", no_argument, NULL,
option_workaround_short_loop },
{ "no-workaround-all-short-loops", no_argument, NULL,
option_no_workaround_all_short_loops },
{ "workaround-all-short-loop", no_argument, NULL,
option_workaround_all_short_loops },
{ "prefer-l32r", no_argument, NULL, option_prefer_l32r },
{ "prefer-const16", no_argument, NULL, option_prefer_const16 },
{ "no-workarounds", no_argument, NULL, option_no_workarounds },
{ "no-workaround-close-loop-end", no_argument, NULL,
option_no_workaround_close_loop_end },
{ "workaround-close-loop-end", no_argument, NULL,
option_workaround_close_loop_end },
{ "rename-section", required_argument, NULL, option_rename_section_name },
{ "link-relax", no_argument, NULL, option_link_relax },
{ "no-link-relax", no_argument, NULL, option_no_link_relax },
{ "target-hardware", required_argument, NULL, option_target_hardware },
{ "trampolines", no_argument, NULL, option_trampolines },
{ "no-trampolines", no_argument, NULL, option_no_trampolines },
{ "auto-litpools", no_argument, NULL, option_auto_litpools },
{ "no-auto-litpools", no_argument, NULL, option_no_auto_litpools },
{ "auto-litpool-limit", required_argument, NULL, option_auto_litpool_limit },
{ "separate-prop-tables", no_argument, NULL, option_separate_props },
{ "abi-windowed", no_argument, NULL, option_abi_windowed },
{ "abi-call0", no_argument, NULL, option_abi_call0 },
{ 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_density:
as_warn (_("--density option is ignored"));
return 1;
case option_no_density:
as_warn (_("--no-density option is ignored"));
return 1;
case option_link_relax:
opt_linkrelax = true;
return 1;
case option_no_link_relax:
opt_linkrelax = false;
return 1;
case option_flix:
produce_flix = FLIX_ALL;
return 1;
case option_no_generate_flix:
produce_flix = FLIX_NO_GENERATE;
return 1;
case option_no_flix:
produce_flix = FLIX_NONE;
return 1;
case option_generics:
as_warn (_("--generics is deprecated; use --transform instead"));
return md_parse_option (option_transform, arg);
case option_no_generics:
as_warn (_("--no-generics is deprecated; use --no-transform instead"));
return md_parse_option (option_no_transform, arg);
case option_relax:
as_warn (_("--relax is deprecated; use --transform instead"));
return md_parse_option (option_transform, arg);
case option_no_relax:
as_warn (_("--no-relax is deprecated; use --no-transform instead"));
return md_parse_option (option_no_transform, arg);
case option_longcalls:
directive_state[directive_longcalls] = true;
return 1;
case option_no_longcalls:
directive_state[directive_longcalls] = false;
return 1;
case option_text_section_literals:
use_literal_section = false;
return 1;
case option_no_text_section_literals:
use_literal_section = true;
return 1;
case option_absolute_literals:
if (!absolute_literals_supported)
{
as_fatal (_("--absolute-literals option not supported in this Xtensa configuration"));
return 0;
}
directive_state[directive_absolute_literals] = true;
return 1;
case option_no_absolute_literals:
directive_state[directive_absolute_literals] = false;
return 1;
case option_workaround_a0_b_retw:
workaround_a0_b_retw = true;
return 1;
case option_no_workaround_a0_b_retw:
workaround_a0_b_retw = false;
return 1;
case option_workaround_b_j_loop_end:
workaround_b_j_loop_end = true;
return 1;
case option_no_workaround_b_j_loop_end:
workaround_b_j_loop_end = false;
return 1;
case option_workaround_short_loop:
workaround_short_loop = true;
return 1;
case option_no_workaround_short_loop:
workaround_short_loop = false;
return 1;
case option_workaround_all_short_loops:
workaround_all_short_loops = true;
return 1;
case option_no_workaround_all_short_loops:
workaround_all_short_loops = false;
return 1;
case option_workaround_close_loop_end:
workaround_close_loop_end = true;
return 1;
case option_no_workaround_close_loop_end:
workaround_close_loop_end = false;
return 1;
case option_no_workarounds:
workaround_a0_b_retw = false;
workaround_b_j_loop_end = false;
workaround_short_loop = false;
workaround_all_short_loops = false;
workaround_close_loop_end = false;
return 1;
case option_align_targets:
align_targets = true;
return 1;
case option_no_align_targets:
align_targets = false;
return 1;
case option_warn_unaligned_targets:
warn_unaligned_branch_targets = true;
return 1;
case option_rename_section_name:
build_section_rename (arg);
return 1;
case 'Q':
/* -Qy, -Qn: SVR4 arguments controlling whether a .comment section
should be emitted or not. FIXME: Not implemented. */
return 1;
case option_prefer_l32r:
if (prefer_const16)
as_fatal (_("prefer-l32r conflicts with prefer-const16"));
prefer_l32r = 1;
return 1;
case option_prefer_const16:
if (prefer_l32r)
as_fatal (_("prefer-const16 conflicts with prefer-l32r"));
prefer_const16 = 1;
return 1;
case option_target_hardware:
{
int earliest, latest = 0;
char *end;
if (*arg == 0 || *arg == '-')
as_fatal (_("invalid target hardware version"));
earliest = strtol (arg, &end, 0);
if (*end == 0)
latest = earliest;
else if (*end == '-')
{
if (*++end == 0)
as_fatal (_("invalid target hardware version"));
latest = strtol (end, &end, 0);
}
if (*end != 0)
as_fatal (_("invalid target hardware version"));
xtensa_setup_hw_workarounds (earliest, latest);
return 1;
}
case option_transform:
/* This option has no affect other than to use the defaults,
which are already set. */
return 1;
case option_no_transform:
/* This option turns off all transformations of any kind.
However, because we want to preserve the state of other
directives, we only change its own field. Thus, before
you perform any transformation, always check if transform
is available. If you use the functions we provide for this
purpose, you will be ok. */
directive_state[directive_transform] = false;
return 1;
case option_trampolines:
use_trampolines = true;
return 1;
case option_no_trampolines:
use_trampolines = false;
return 1;
case option_auto_litpools:
auto_litpools = true;
use_literal_section = false;
if (auto_litpool_limit <= 0)
auto_litpool_limit = MAX_AUTO_POOL_LITERALS / 2;
return 1;
case option_no_auto_litpools:
auto_litpools = false;
auto_litpool_limit = -1;
return 1;
case option_auto_litpool_limit:
{
int value = 0;
char *end;
if (auto_litpool_limit < 0)
as_fatal (_("no-auto-litpools is incompatible with auto-litpool-limit"));
if (*arg == 0 || *arg == '-')
as_fatal (_("invalid auto-litpool-limit argument"));
value = strtol (arg, &end, 10);
if (*end != 0)
as_fatal (_("invalid auto-litpool-limit argument"));
if (value < 100 || value > 10000)
as_fatal (_("invalid auto-litpool-limit argument (range is 100-10000)"));
auto_litpool_limit = value;
auto_litpools = true;
use_literal_section = false;
return 1;
}
case option_separate_props:
elf32xtensa_separate_props = true;
return 1;
case option_no_separate_props:
elf32xtensa_separate_props = false;
return 1;
case option_abi_windowed:
elf32xtensa_abi = XTHAL_ABI_WINDOWED;
return 1;
case option_abi_call0:
elf32xtensa_abi = XTHAL_ABI_CALL0;
return 1;
default:
return 0;
}
}
void
md_show_usage (FILE *stream)
{
fputs ("\n\
Xtensa options:\n\
--[no-]text-section-literals\n\
[Do not] put literals in the text section\n\
--[no-]absolute-literals\n\
[Do not] default to use non-PC-relative literals\n\
--[no-]target-align [Do not] try to align branch targets\n\
--[no-]longcalls [Do not] emit 32-bit call sequences\n\
--[no-]transform [Do not] transform instructions\n\
--flix both allow hand-written and generate flix bundles\n\
--no-generate-flix allow hand-written but do not generate\n\
flix bundles\n\
--no-allow-flix neither allow hand-written nor generate\n\
flix bundles\n\
--rename-section old=new Rename section 'old' to 'new'\n\
--[no-]trampolines [Do not] generate trampolines (jumps to jumps)\n\
when jumps do not reach their targets\n\
--[no-]auto-litpools [Do not] automatically create literal pools\n\
--auto-litpool-limit=<value>\n\
(range 100-10000) Maximum number of blocks of\n\
instructions to emit between literal pool\n\
locations; implies --auto-litpools flag\n\
--[no-]separate-prop-tables\n\
[Do not] place Xtensa property records into\n\
individual property sections for each section.\n\
Default is to generate single property section.\n", stream);
}
/* Functions related to the list of current label symbols. */
static void
xtensa_add_insn_label (symbolS *sym)
{
sym_list *l;
if (!free_insn_labels)
l = XNEW (sym_list);
else
{
l = free_insn_labels;
free_insn_labels = l->next;
}
l->sym = sym;
l->next = insn_labels;
insn_labels = l;
}
static void
xtensa_clear_insn_labels (void)
{
sym_list **pl;
for (pl = &free_insn_labels; *pl != NULL; pl = &(*pl)->next)
;
*pl = insn_labels;
insn_labels = NULL;
}
static void
xtensa_move_labels (fragS *new_frag, valueT new_offset)
{
sym_list *lit;
for (lit = insn_labels; lit; lit = lit->next)
{
symbolS *lit_sym = lit->sym;
S_SET_VALUE (lit_sym, new_offset);
symbol_set_frag (lit_sym, new_frag);
}
}
/* Directive data and functions. */
typedef struct state_stackS_struct
{
directiveE directive;
bool negated;
bool old_state;
const char *file;
unsigned int line;
const void *datum;
struct state_stackS_struct *prev;
} state_stackS;
state_stackS *directive_state_stack;
const pseudo_typeS md_pseudo_table[] =
{
{ "align", s_align_bytes, 0 }, /* Defaulting is invalid (0). */
{ "literal_position", xtensa_literal_position, 0 },
{ "frame", s_ignore, 0 }, /* Formerly used for STABS debugging. */
{ "long", xtensa_elf_cons, 4 },
{ "word", xtensa_elf_cons, 4 },
{ "4byte", xtensa_elf_cons, 4 },
{ "short", xtensa_elf_cons, 2 },
{ "2byte", xtensa_elf_cons, 2 },
{ "sleb128", xtensa_leb128, 1},
{ "uleb128", xtensa_leb128, 0},
{ "begin", xtensa_begin_directive, 0 },
{ "end", xtensa_end_directive, 0 },
{ "literal", xtensa_literal_pseudo, 0 },
{ "frequency", xtensa_frequency_pseudo, 0 },
{ NULL, 0, 0 },
};
static bool
use_transform (void)
{
/* After md_end, you should be checking frag by frag, rather
than state directives. */
gas_assert (!past_xtensa_end);
return directive_state[directive_transform];
}
static bool
do_align_targets (void)
{
/* Do not use this function after md_end; just look at align_targets
instead. There is no target-align directive, so alignment is either
enabled for all frags or not done at all. */
gas_assert (!past_xtensa_end);
return align_targets && use_transform ();
}
static void
directive_push (directiveE directive, bool negated, const void *datum)
{
const char *file;
unsigned int line;
state_stackS *stack = XNEW (state_stackS);
file = as_where (&line);
stack->directive = directive;
stack->negated = negated;
stack->old_state = directive_state[directive];
stack->file = file;
stack->line = line;
stack->datum = datum;
stack->prev = directive_state_stack;
directive_state_stack = stack;
directive_state[directive] = !negated;
}
static void
directive_pop (directiveE *directive,
bool *negated,
const char **file,
unsigned int *line,
const void **datum)
{
state_stackS *top = directive_state_stack;
if (!directive_state_stack)
{
as_bad (_("unmatched .end directive"));
*directive = directive_none;
return;
}
directive_state[directive_state_stack->directive] = top->old_state;
*directive = top->directive;
*negated = top->negated;
*file = top->file;
*line = top->line;
*datum = top->datum;
directive_state_stack = top->prev;
free (top);
}
static void
directive_balance (void)
{
while (directive_state_stack)
{
directiveE directive;
bool negated;
const char *file;
unsigned int line;
const void *datum;
directive_pop (&directive, &negated, &file, &line, &datum);
as_warn_where ((char *) file, line,
_(".begin directive with no matching .end directive"));
}
}
static bool
inside_directive (directiveE dir)
{
state_stackS *top = directive_state_stack;
while (top && top->directive != dir)
top = top->prev;
return (top != NULL);
}
static void
get_directive (directiveE *directive, bool *negated)
{
int len;
unsigned i;
const char *directive_string;
if (!startswith (input_line_pointer, "no-"))
*negated = false;
else
{
*negated = true;
input_line_pointer += 3;
}
len = strspn (input_line_pointer,
"abcdefghijklmnopqrstuvwxyz_-/0123456789.");
/* This code is a hack to make .begin [no-][generics|relax] exactly
equivalent to .begin [no-]transform. We should remove it when
we stop accepting those options. */
if (startswith (input_line_pointer, "generics"))
{
as_warn (_("[no-]generics is deprecated; use [no-]transform instead"));
directive_string = "transform";
}
else if (startswith (input_line_pointer, "relax"))
{
as_warn (_("[no-]relax is deprecated; use [no-]transform instead"));
directive_string = "transform";
}
else
directive_string = input_line_pointer;
for (i = 0; i < sizeof (directive_info) / sizeof (*directive_info); ++i)
{
if (strncmp (directive_string, directive_info[i].name, len) == 0)
{
input_line_pointer += len;
*directive = (directiveE) i;
if (*negated && !directive_info[i].can_be_negated)
as_bad (_("directive %s cannot be negated"),
directive_info[i].name);
return;
}
}
as_bad (_("unknown directive"));
*directive = (directiveE) XTENSA_UNDEFINED;
}
static void
xtensa_begin_directive (int ignore ATTRIBUTE_UNUSED)
{
directiveE directive;
bool negated;
emit_state *state;
lit_state *ls;
get_directive (&directive, &negated);
if (directive == (directiveE) XTENSA_UNDEFINED)
{
discard_rest_of_line ();
return;
}
if (cur_vinsn.inside_bundle)
as_bad (_("directives are not valid inside bundles"));
switch (directive)
{
case directive_literal:
if (!inside_directive (directive_literal))
{
/* Previous labels go with whatever follows this directive, not with
the literal, so save them now. */
saved_insn_labels = insn_labels;
insn_labels = NULL;
}
as_warn (_(".begin literal is deprecated; use .literal instead"));
state = XNEW (emit_state);
xtensa_switch_to_literal_fragment (state);
directive_push (directive_literal, negated, state);
break;
case directive_literal_prefix:
/* Have to flush pending output because a movi relaxed to an l32r
might produce a literal. */
md_flush_pending_output ();
/* Check to see if the current fragment is a literal
fragment. If it is, then this operation is not allowed. */
if (generating_literals)
{
as_bad (_("cannot set literal_prefix inside literal fragment"));
return;
}
/* Allocate the literal state for this section and push
onto the directive stack. */
ls = XNEW (lit_state);
gas_assert (ls);
*ls = default_lit_sections;
directive_push (directive_literal_prefix, negated, ls);
/* Process the new prefix. */
xtensa_literal_prefix ();
break;
case directive_freeregs:
/* This information is currently unused, but we'll accept the statement
and just discard the rest of the line. This won't check the syntax,
but it will accept every correct freeregs directive. */
input_line_pointer += strcspn (input_line_pointer, "\n");
directive_push (directive_freeregs, negated, 0);
break;
case directive_schedule:
md_flush_pending_output ();
frag_var (rs_fill, 0, 0, frag_now->fr_subtype,
frag_now->fr_symbol, frag_now->fr_offset, NULL);
directive_push (directive_schedule, negated, 0);
xtensa_set_frag_assembly_state (frag_now);
break;
case directive_density:
as_warn (_(".begin [no-]density is ignored"));
break;
case directive_absolute_literals:
md_flush_pending_output ();
if (!absolute_literals_supported && !negated)
{
as_warn (_("Xtensa absolute literals option not supported; ignored"));
break;
}
xtensa_set_frag_assembly_state (frag_now);
directive_push (directive, negated, 0);
break;
default:
md_flush_pending_output ();
xtensa_set_frag_assembly_state (frag_now);
directive_push (directive, negated, 0);
break;
}
demand_empty_rest_of_line ();
}
static void
xtensa_end_directive (int ignore ATTRIBUTE_UNUSED)
{
directiveE begin_directive, end_directive;
bool begin_negated, end_negated;
const char *file;
unsigned int line;
emit_state *state;
emit_state **state_ptr;
lit_state *s;
if (cur_vinsn.inside_bundle)
as_bad (_("directives are not valid inside bundles"));
get_directive (&end_directive, &end_negated);
md_flush_pending_output ();
switch ((int) end_directive)
{
case XTENSA_UNDEFINED:
discard_rest_of_line ();
return;
case (int) directive_density:
as_warn (_(".end [no-]density is ignored"));
demand_empty_rest_of_line ();
break;
case (int) directive_absolute_literals:
if (!absolute_literals_supported && !end_negated)
{
as_warn (_("Xtensa absolute literals option not supported; ignored"));
demand_empty_rest_of_line ();
return;
}
break;
default:
break;
}
state_ptr = &state; /* use state_ptr to avoid type-punning warning */
directive_pop (&begin_directive, &begin_negated, &file, &line,
(const void **) state_ptr);
if (begin_directive != directive_none)
{
if (begin_directive != end_directive || begin_negated != end_negated)
{
as_bad (_("does not match begin %s%s at %s:%d"),
begin_negated ? "no-" : "",
directive_info[begin_directive].name, file, line);
}
else
{
switch (end_directive)
{
case directive_literal:
frag_var (rs_fill, 0, 0, 0, NULL, 0, NULL);
xtensa_restore_emit_state (state);
xtensa_set_frag_assembly_state (frag_now);
free (state);
if (!inside_directive (directive_literal))
{
/* Restore the list of current labels. */
xtensa_clear_insn_labels ();
insn_labels = saved_insn_labels;
}
break;
case directive_literal_prefix:
/* Restore the default collection sections from saved state. */
s = (lit_state *) state;
gas_assert (s);
default_lit_sections = *s;
/* Free the state storage. */
free (s->lit_prefix);
free (s);
break;
case directive_schedule:
case directive_freeregs:
break;
default:
xtensa_set_frag_assembly_state (frag_now);
break;
}
}
}
demand_empty_rest_of_line ();
}
/* Place an aligned literal fragment at the current location. */
static void
xtensa_literal_position (int ignore ATTRIBUTE_UNUSED)
{
md_flush_pending_output ();
if (inside_directive (directive_literal))
as_warn (_(".literal_position inside literal directive; ignoring"));
xtensa_mark_literal_pool_location ();
demand_empty_rest_of_line ();
xtensa_clear_insn_labels ();
}
/* Support .literal label, expr, ... */
static void
xtensa_literal_pseudo (int ignored ATTRIBUTE_UNUSED)
{
emit_state state;
char *p, *base_name;
char c;
if (inside_directive (directive_literal))
{
as_bad (_(".literal not allowed inside .begin literal region"));
ignore_rest_of_line ();
return;
}
md_flush_pending_output ();
/* Previous labels go with whatever follows this directive, not with
the literal, so save them now. */
saved_insn_labels = insn_labels;
insn_labels = NULL;
base_name = input_line_pointer;
xtensa_switch_to_literal_fragment (&state);
/* All literals are aligned to four-byte boundaries. */
frag_align (2, 0, 0);
record_alignment (now_seg, 2);
c = get_symbol_name (&base_name);
/* Just after name is now '\0'. */
p = input_line_pointer;
*p = c;
SKIP_WHITESPACE_AFTER_NAME ();
if (*input_line_pointer != ',' && *input_line_pointer != ':')
{
as_bad (_("expected comma or colon after symbol name; "
"rest of line ignored"));
ignore_rest_of_line ();
xtensa_restore_emit_state (&state);
return;
}
*p = 0;
colon (base_name);
*p = c;
input_line_pointer++; /* skip ',' or ':' */
xtensa_elf_cons (4);
xtensa_restore_emit_state (&state);
/* Restore the list of current labels. */
xtensa_clear_insn_labels ();
insn_labels = saved_insn_labels;
}
static void
xtensa_literal_prefix (void)
{
char *name;
int len;
/* Parse the new prefix from the input_line_pointer. */
SKIP_WHITESPACE ();
len = strspn (input_line_pointer,
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz_/0123456789.$");
/* Get a null-terminated copy of the name. */
name = xmemdup0 (input_line_pointer, len);
/* Skip the name in the input line. */
input_line_pointer += len;
default_lit_sections.lit_prefix = name;
/* Clear cached literal sections, since the prefix has changed. */
default_lit_sections.lit_seg = NULL;
default_lit_sections.lit4_seg = NULL;
}
/* Support ".frequency branch_target_frequency fall_through_frequency". */
static void
xtensa_frequency_pseudo (int ignored ATTRIBUTE_UNUSED)
{
float fall_through_f, target_f;
fall_through_f = (float) strtod (input_line_pointer, &input_line_pointer);
if (fall_through_f < 0)
{
as_bad (_("fall through frequency must be greater than 0"));
ignore_rest_of_line ();
return;
}
target_f = (float) strtod (input_line_pointer, &input_line_pointer);
if (target_f < 0)
{
as_bad (_("branch target frequency must be greater than 0"));
ignore_rest_of_line ();
return;
}
set_subseg_freq (now_seg, now_subseg, target_f + fall_through_f, target_f);
demand_empty_rest_of_line ();
}
/* Like normal .long/.short/.word, except support @plt, etc.
Clobbers input_line_pointer, checks end-of-line. */
static void
xtensa_elf_cons (int nbytes)
{
expressionS exp;
bfd_reloc_code_real_type reloc;
md_flush_pending_output ();
if (cur_vinsn.inside_bundle)
as_bad (_("directives are not valid inside bundles"));
if (is_it_end_of_statement ())
{
demand_empty_rest_of_line ();
return;
}
do
{
expression (&exp);
if (exp.X_op == O_symbol
&& *input_line_pointer == '@'
&& ((reloc = xtensa_elf_suffix (&input_line_pointer, &exp))
!= BFD_RELOC_NONE))
{
reloc_howto_type *reloc_howto =
bfd_reloc_type_lookup (stdoutput, reloc);
if (reloc == BFD_RELOC_UNUSED || !reloc_howto)
as_bad (_("unsupported relocation"));
else if ((reloc >= BFD_RELOC_XTENSA_SLOT0_OP
&& reloc <= BFD_RELOC_XTENSA_SLOT14_OP)
|| (reloc >= BFD_RELOC_XTENSA_SLOT0_ALT
&& reloc <= BFD_RELOC_XTENSA_SLOT14_ALT))
as_bad (_("opcode-specific %s relocation used outside "
"an instruction"), reloc_howto->name);
else if (nbytes != (int) bfd_get_reloc_size (reloc_howto))
as_bad (ngettext ("%s relocations do not fit in %d byte",
"%s relocations do not fit in %d bytes",
nbytes),
reloc_howto->name, nbytes);
else if (reloc == BFD_RELOC_XTENSA_TLS_FUNC
|| reloc == BFD_RELOC_XTENSA_TLS_ARG
|| reloc == BFD_RELOC_XTENSA_TLS_CALL)
as_bad (_("invalid use of %s relocation"), reloc_howto->name);
else
{
char *p = frag_more ((int) nbytes);
xtensa_set_frag_assembly_state (frag_now);
fix_new_exp (frag_now, p - frag_now->fr_literal,
nbytes, &exp, reloc_howto->pc_relative, reloc);
}
}
else
{
xtensa_set_frag_assembly_state (frag_now);
emit_expr (&exp, (unsigned int) nbytes);
}
}
while (*input_line_pointer++ == ',');
input_line_pointer--; /* Put terminator back into stream. */
demand_empty_rest_of_line ();
}
static bool is_leb128_expr;
static void
xtensa_leb128 (int sign)
{
is_leb128_expr = true;
s_leb128 (sign);
is_leb128_expr = false;
}
/* Parsing and Idiom Translation. */
/* Parse @plt, etc. and return the desired relocation. */
static bfd_reloc_code_real_type
xtensa_elf_suffix (char **str_p, expressionS *exp_p)
{
char ident[20];
char *str = *str_p;
char *str2;
int ch;
int len;
unsigned int i;
if (*str++ != '@')
return BFD_RELOC_NONE;
for (ch = *str, str2 = ident;
(str2 < ident + sizeof (ident) - 1
&& (ISALNUM (ch) || ch == '@'));
ch = *++str)
{
*str2++ = (ISLOWER (ch)) ? ch : TOLOWER (ch);
}
*str2 = '\0';
len = str2 - ident;
ch = ident[0];
for (i = 0; i < ARRAY_SIZE (suffix_relocs); i++)
if (ch == suffix_relocs[i].suffix[0]
&& len == suffix_relocs[i].length
&& memcmp (ident, suffix_relocs[i].suffix, suffix_relocs[i].length) == 0)
{
/* Now check for "identifier@suffix+constant". */
if (*str == '-' || *str == '+')
{
char *orig_line = input_line_pointer;
expressionS new_exp;
input_line_pointer = str;
expression (&new_exp);
if (new_exp.X_op == O_constant)
{
exp_p->X_add_number += new_exp.X_add_number;
str = input_line_pointer;
}
if (&input_line_pointer != str_p)
input_line_pointer = orig_line;
}
*str_p = str;
return suffix_relocs[i].reloc;
}
return BFD_RELOC_UNUSED;
}
/* Find the matching operator type. */
static operatorT
map_suffix_reloc_to_operator (bfd_reloc_code_real_type reloc)
{
operatorT operator = O_illegal;
unsigned int i;
for (i = 0; i < ARRAY_SIZE (suffix_relocs); i++)
{
if (suffix_relocs[i].reloc == reloc)
{
operator = suffix_relocs[i].operator;
break;
}
}
gas_assert (operator != O_illegal);
return operator;
}
/* Find the matching reloc type. */
static bfd_reloc_code_real_type
map_operator_to_reloc (unsigned char operator, bool is_literal)
{
unsigned int i;
bfd_reloc_code_real_type reloc = BFD_RELOC_UNUSED;
for (i = 0; i < ARRAY_SIZE (suffix_relocs); i++)
{
if (suffix_relocs[i].operator == operator)
{
reloc = suffix_relocs[i].reloc;
break;
}
}
if (is_literal)
{
if (reloc == BFD_RELOC_XTENSA_TLS_FUNC)
return BFD_RELOC_XTENSA_TLSDESC_FN;
else if (reloc == BFD_RELOC_XTENSA_TLS_ARG)
return BFD_RELOC_XTENSA_TLSDESC_ARG;
}
if (reloc == BFD_RELOC_UNUSED)
return BFD_RELOC_32;
return reloc;
}
static const char *
expression_end (const char *name)
{
while (1)
{
switch (*name)
{
case '}':
case ';':
case '\0':
case ',':
case ':':
return name;
case ' ':
case '\t':
++name;
continue;
default:
return 0;
}
}
}
#define ERROR_REG_NUM ((unsigned) -1)
static unsigned
tc_get_register (const char *prefix)
{
unsigned reg;
const char *next_expr;
const char *old_line_pointer;
SKIP_WHITESPACE ();
old_line_pointer = input_line_pointer;
if (*input_line_pointer == '$')
++input_line_pointer;
/* Accept "sp" as a synonym for "a1". */
if (input_line_pointer[0] == 's' && input_line_pointer[1] == 'p'
&& expression_end (input_line_pointer + 2))
{
input_line_pointer += 2;
return 1; /* AR[1] */
}
while (*input_line_pointer++ == *prefix++)
;
--input_line_pointer;
--prefix;
if (*prefix)
{
as_bad (_("bad register name: %s"), old_line_pointer);
return ERROR_REG_NUM;
}
if (!ISDIGIT ((unsigned char) *input_line_pointer))
{
as_bad (_("bad register number: %s"), input_line_pointer);
return ERROR_REG_NUM;
}
reg = 0;
while (ISDIGIT ((int) *input_line_pointer))
reg = reg * 10 + *input_line_pointer++ - '0';
if (!(next_expr = expression_end (input_line_pointer)))
{
as_bad (_("bad register name: %s"), old_line_pointer);
return ERROR_REG_NUM;
}
input_line_pointer = (char *) next_expr;
return reg;
}
static void
expression_maybe_register (xtensa_opcode opc, int opnd, expressionS *tok)
{
xtensa_isa isa = xtensa_default_isa;
/* Check if this is an immediate operand. */
if (xtensa_operand_is_register (isa, opc, opnd) == 0)
{
bfd_reloc_code_real_type reloc;
segT t = expression (tok);
if (t == absolute_section
&& xtensa_operand_is_PCrelative (isa, opc, opnd) == 1)
{
gas_assert (tok->X_op == O_constant);
tok->X_op = O_symbol;
tok->X_add_symbol = &abs_symbol;
}
if ((tok->X_op == O_constant || tok->X_op == O_symbol)
&& ((reloc = xtensa_elf_suffix (&input_line_pointer, tok))
!= BFD_RELOC_NONE))
{
switch (reloc)
{
case BFD_RELOC_LO16:
if (tok->X_op == O_constant)
{
tok->X_add_number &= 0xffff;
return;
}
break;
case BFD_RELOC_HI16:
if (tok->X_op == O_constant)
{
tok->X_add_number = ((unsigned) tok->X_add_number) >> 16;
return;
}
break;
case BFD_RELOC_UNUSED:
as_bad (_("unsupported relocation"));
return;
case BFD_RELOC_32_PCREL:
as_bad (_("pcrel relocation not allowed in an instruction"));
return;
default:
break;
}
tok->X_op = map_suffix_reloc_to_operator (reloc);
}
}
else
{
xtensa_regfile opnd_rf = xtensa_operand_regfile (isa, opc, opnd);
unsigned reg = tc_get_register (xtensa_regfile_shortname (isa, opnd_rf));
if (reg != ERROR_REG_NUM) /* Already errored */
{
uint32 buf = reg;
if (xtensa_operand_encode (isa, opc, opnd, &buf))
as_bad (_("register number out of range"));
}
tok->X_op = O_register;
tok->X_add_symbol = 0;
tok->X_add_number = reg;
}
}
/* Split up the arguments for an opcode or pseudo-op. */
static int
tokenize_arguments (char **args, char *str)
{
char *old_input_line_pointer;
bool saw_comma = false;
bool saw_arg = false;
bool saw_colon = false;
int num_args = 0;
char *arg_end, *arg;
int arg_len;
/* Save and restore input_line_pointer around this function. */
old_input_line_pointer = input_line_pointer;
input_line_pointer = str;
while (*input_line_pointer)
{
SKIP_WHITESPACE ();
switch (*input_line_pointer)
{
case '\0':
case '}':
goto fini;
case ':':
input_line_pointer++;
if (saw_comma || saw_colon || !saw_arg)
goto err;
saw_colon = true;
break;
case ',':
input_line_pointer++;
if (saw_comma || saw_colon || !saw_arg)
goto err;
saw_comma = true;
break;
default:
if (!saw_comma && !saw_colon && saw_arg)
goto err;
arg_end = input_line_pointer + 1;
while (!expression_end (arg_end))
arg_end += 1;
arg_len = arg_end - input_line_pointer;
arg = XNEWVEC (char, (saw_colon ? 1 : 0) + arg_len + 1);
args[num_args] = arg;
if (saw_colon)
*arg++ = ':';
strncpy (arg, input_line_pointer, arg_len);
arg[arg_len] = '\0';
input_line_pointer = arg_end;
num_args += 1;
saw_comma = false;
saw_colon = false;
saw_arg = true;
break;
}
}
fini:
if (saw_comma || saw_colon)
goto err;
input_line_pointer = old_input_line_pointer;
return num_args;
err:
if (saw_comma)
as_bad (_("extra comma"));
else if (saw_colon)
as_bad (_("extra colon"));
else if (!saw_arg)
as_bad (_("missing argument"));
else
as_bad (_("missing comma or colon"));
input_line_pointer = old_input_line_pointer;
return -1;
}
/* Parse the arguments to an opcode. Return TRUE on error. */
static bool
parse_arguments (TInsn *insn, int num_args, char **arg_strings)
{
expressionS *tok, *last_tok;
xtensa_opcode opcode = insn->opcode;
bool had_error = true;
xtensa_isa isa = xtensa_default_isa;
int n, num_regs = 0;
int opcode_operand_count;
int opnd_cnt, last_opnd_cnt;
unsigned int next_reg = 0;
char *old_input_line_pointer;
if (insn->insn_type == ITYPE_LITERAL)
opcode_operand_count = 1;
else
opcode_operand_count = xtensa_opcode_num_operands (isa, opcode);
tok = insn->tok;
memset (tok, 0, sizeof (*tok) * MAX_INSN_ARGS);
/* Save and restore input_line_pointer around this function. */
old_input_line_pointer = input_line_pointer;
last_tok = 0;
last_opnd_cnt = -1;
opnd_cnt = 0;
/* Skip invisible operands. */
while (xtensa_operand_is_visible (isa, opcode, opnd_cnt) == 0)
{
opnd_cnt += 1;
tok++;
}
for (n = 0; n < num_args; n++)
{
input_line_pointer = arg_strings[n];
if (*input_line_pointer == ':')
{
xtensa_regfile opnd_rf;
input_line_pointer++;
if (num_regs == 0)
goto err;
gas_assert (opnd_cnt > 0);
num_regs--;
opnd_rf = xtensa_operand_regfile (isa, opcode, last_opnd_cnt);
if (next_reg
!= tc_get_register (xtensa_regfile_shortname (isa, opnd_rf)))
as_warn (_("incorrect register number, ignoring"));
next_reg++;
}
else
{
if (opnd_cnt >= opcode_operand_count)
{
as_warn (_("too many arguments"));
goto err;
}
gas_assert (opnd_cnt < MAX_INSN_ARGS);
expression_maybe_register (opcode, opnd_cnt, tok);
next_reg = tok->X_add_number + 1;
if (tok->X_op == O_illegal || tok->X_op == O_absent)
goto err;
if (xtensa_operand_is_register (isa, opcode, opnd_cnt) == 1)
{
num_regs = xtensa_operand_num_regs (isa, opcode, opnd_cnt) - 1;
/* minus 1 because we are seeing one right now */
}
else
num_regs = 0;
last_tok = tok;
last_opnd_cnt = opnd_cnt;
demand_empty_rest_of_line ();
do
{
opnd_cnt += 1;
tok++;
}
while (xtensa_operand_is_visible (isa, opcode, opnd_cnt) == 0);
}
}
if (num_regs > 0 && ((int) next_reg != last_tok->X_add_number + 1))
goto err;
insn->ntok = tok - insn->tok;
had_error = false;
err:
input_line_pointer = old_input_line_pointer;
return had_error;
}
static int
get_invisible_operands (TInsn *insn)
{
xtensa_isa isa = xtensa_default_isa;
static xtensa_insnbuf slotbuf = NULL;
xtensa_format fmt;
xtensa_opcode opc = insn->opcode;
int slot, opnd, fmt_found;
unsigned val;
if (!slotbuf)
slotbuf = xtensa_insnbuf_alloc (isa);
/* Find format/slot where this can be encoded. */
fmt_found = 0;
slot = 0;
for (fmt = 0; fmt < xtensa_isa_num_formats (isa); fmt++)
{
for (slot = 0; slot < xtensa_format_num_slots (isa, fmt); slot++)
{
if (xtensa_opcode_encode (isa, fmt, slot, slotbuf, opc) == 0)
{
fmt_found = 1;
break;
}
}
if (fmt_found) break;
}
if (!fmt_found)
{
as_bad (_("cannot encode opcode \"%s\""), xtensa_opcode_name (isa, opc));
return -1;
}
/* First encode all the visible operands
(to deal with shared field operands). */
for (opnd = 0; opnd < insn->ntok; opnd++)
{
if (xtensa_operand_is_visible (isa, opc, opnd) == 1
&& (insn->tok[opnd].X_op == O_register
|| insn->tok[opnd].X_op == O_constant))
{
val = insn->tok[opnd].X_add_number;
xtensa_operand_encode (isa, opc, opnd, &val);
xtensa_operand_set_field (isa, opc, opnd, fmt, slot, slotbuf, val);
}
}
/* Then pull out the values for the invisible ones. */
for (opnd = 0; opnd < insn->ntok; opnd++)
{
if (xtensa_operand_is_visible (isa, opc, opnd) == 0)
{
xtensa_operand_get_field (isa, opc, opnd, fmt, slot, slotbuf, &val);
xtensa_operand_decode (isa, opc, opnd, &val);
insn->tok[opnd].X_add_number = val;
if (xtensa_operand_is_register (isa, opc, opnd) == 1)
insn->tok[opnd].X_op = O_register;
else
insn->tok[opnd].X_op = O_constant;
}
}
return 0;
}
static void
xg_reverse_shift_count (char **cnt_argp)
{
char *cnt_arg, *new_arg;
cnt_arg = *cnt_argp;
/* replace the argument with "31-(argument)" */
new_arg = concat ("31-(", cnt_arg, ")", (char *) NULL);
free (cnt_arg);
*cnt_argp = new_arg;
}
/* If "arg" is a constant expression, return non-zero with the value
in *valp. */
static int
xg_arg_is_constant (char *arg, offsetT *valp)
{
expressionS exp;
char *save_ptr = input_line_pointer;
input_line_pointer = arg;
expression (&exp);
input_line_pointer = save_ptr;
if (exp.X_op == O_constant)
{
*valp = exp.X_add_number;
return 1;
}
return 0;
}
static void
xg_replace_opname (char **popname, const char *newop)
{
free (*popname);
*popname = xstrdup (newop);
}
static int
xg_check_num_args (int *pnum_args,
int expected_num,
char *opname,
char **arg_strings)
{
int num_args = *pnum_args;
if (num_args < expected_num)
{
as_bad (_("not enough operands (%d) for '%s'; expected %d"),
num_args, opname, expected_num);
return -1;
}
if (num_args > expected_num)
{
as_warn (_("too many operands (%d) for '%s'; expected %d"),
num_args, opname, expected_num);
while (num_args-- > expected_num)
{
free (arg_strings[num_args]);
arg_strings[num_args] = 0;
}
*pnum_args = expected_num;
return -1;
}
return 0;
}
/* If the register is not specified as part of the opcode,
then get it from the operand and move it to the opcode. */
static int
xg_translate_sysreg_op (char **popname, int *pnum_args, char **arg_strings)
{
xtensa_isa isa = xtensa_default_isa;
xtensa_sysreg sr;
char *opname, *new_opname;
const char *sr_name;
int is_user, is_write;
opname = *popname;
if (*opname == '_')
opname += 1;
is_user = (opname[1] == 'u');
is_write = (opname[0] == 'w');
/* Opname == [rw]ur or [rwx]sr... */
if (xg_check_num_args (pnum_args, 2, opname, arg_strings))
return -1;
/* Check if the argument is a symbolic register name. */
sr = xtensa_sysreg_lookup_name (isa, arg_strings[1]);
/* Handle WSR to "INTSET" as a special case. */
if (sr == XTENSA_UNDEFINED && is_write && !is_user
&& !strcasecmp (arg_strings[1], "intset"))
sr = xtensa_sysreg_lookup_name (isa, "interrupt");
if (sr == XTENSA_UNDEFINED
|| (xtensa_sysreg_is_user (isa, sr) == 1) != is_user)
{
/* Maybe it's a register number.... */
offsetT val;
if (!xg_arg_is_constant (arg_strings[1], &val))
{
as_bad (_("invalid register '%s' for '%s' instruction"),
arg_strings[1], opname);
return -1;
}
sr = xtensa_sysreg_lookup (isa, val, is_user);
if (sr == XTENSA_UNDEFINED)
{
as_bad (_("invalid register number (%ld) for '%s' instruction"),
(long) val, opname);
return -1;
}
}
/* Remove the last argument, which is now part of the opcode. */
free (arg_strings[1]);
arg_strings[1] = 0;
*pnum_args = 1;
/* Translate the opcode. */
sr_name = xtensa_sysreg_name (isa, sr);
/* Another special case for "WSR.INTSET".... */
if (is_write && !is_user && !strcasecmp ("interrupt", sr_name))
sr_name = "intset";
new_opname = concat (*popname, ".", sr_name, (char *) NULL);
free (*popname);
*popname = new_opname;
return 0;
}
static int
xtensa_translate_old_userreg_ops (char **popname)
{
xtensa_isa isa = xtensa_default_isa;
xtensa_sysreg sr;
char *opname, *new_opname;
const char *sr_name;
bool has_underbar = false;
opname = *popname;
if (opname[0] == '_')
{
has_underbar = true;
opname += 1;
}
sr = xtensa_sysreg_lookup_name (isa, opname + 1);
if (sr != XTENSA_UNDEFINED)
{
/* The new default name ("nnn") is different from the old default
name ("URnnn"). The old default is handled below, and we don't
want to recognize [RW]nnn, so do nothing if the name is the (new)
default. */
static char namebuf[10];
sprintf (namebuf, "%d", xtensa_sysreg_number (isa, sr));
if (strcmp (namebuf, opname + 1) == 0)
return 0;
}
else
{
offsetT val;
char *end;
/* Only continue if the reg name is "URnnn". */
if (opname[1] != 'u' || opname[2] != 'r')
return 0;
val = strtoul (opname + 3, &end, 10);
if (*end != '\0')
return 0;
sr = xtensa_sysreg_lookup (isa, val, 1);
if (sr == XTENSA_UNDEFINED)
{
as_bad (_("invalid register number (%ld) for '%s'"),
(long) val, opname);
return -1;
}
}
/* Translate the opcode. */
sr_name = xtensa_sysreg_name (isa, sr);
new_opname = XNEWVEC (char, strlen (sr_name) + 6);
sprintf (new_opname, "%s%cur.%s", (has_underbar ? "_" : ""),
opname[0], sr_name);
free (*popname);
*popname = new_opname;
return 0;
}
static int
xtensa_translate_zero_immed (const char *old_op,
const char *new_op,
char **popname,
int *pnum_args,
char **arg_strings)
{
char *opname;
offsetT val;
opname = *popname;
gas_assert (opname[0] != '_');
if (strcmp (opname, old_op) != 0)
return 0;
if (xg_check_num_args (pnum_args, 3, opname, arg_strings))
return -1;
if (xg_arg_is_constant (arg_strings[1], &val) && val == 0)
{
xg_replace_opname (popname, new_op);
free (arg_strings[1]);
arg_strings[1] = arg_strings[2];
arg_strings[2] = 0;
*pnum_args = 2;
}
return 0;
}
/* If the instruction is an idiom (i.e., a built-in macro), translate it.
Returns non-zero if an error was found. */
static int
xg_translate_idioms (char **popname, int *pnum_args, char **arg_strings)
{
char *opname = *popname;
bool has_underbar = false;
if (*opname == '_')
{
has_underbar = true;
opname += 1;
}
if (strcmp (opname, "mov") == 0)
{
if (use_transform () && !has_underbar && density_supported)
xg_replace_opname (popname, "mov.n");
else
{
if (xg_check_num_args (pnum_args, 2, opname, arg_strings))
return -1;
xg_replace_opname (popname, (has_underbar ? "_or" : "or"));
arg_strings[2] = xstrdup (arg_strings[1]);
*pnum_args = 3;
}
return 0;
}
/* Without an operand, this is given a default immediate operand of 0. */
if ((strcmp (opname, "simcall") == 0 && microarch_earliest >= 280000))
{
if (*pnum_args == 0)
{
arg_strings[0] = (char *) xmalloc (2);
strcpy (arg_strings[0], "0");
*pnum_args = 1;
}
return 0;
}
if (strcmp (opname, "bbsi.l") == 0)
{
if (xg_check_num_args (pnum_args, 3, opname, arg_strings))
return -1;
xg_replace_opname (popname, (has_underbar ? "_bbsi" : "bbsi"));
if (target_big_endian)
xg_reverse_shift_count (&arg_strings[1]);
return 0;
}
if (strcmp (opname, "bbci.l") == 0)
{
if (xg_check_num_args (pnum_args, 3, opname, arg_strings))
return -1;
xg_replace_opname (popname, (has_underbar ? "_bbci" : "bbci"));
if (target_big_endian)
xg_reverse_shift_count (&arg_strings[1]);
return 0;
}
/* Don't do anything special with NOPs inside FLIX instructions. They
are handled elsewhere. Real NOP instructions are always available
in configurations with FLIX, so this should never be an issue but
check for it anyway. */
if (!cur_vinsn.inside_bundle && xtensa_nop_opcode == XTENSA_UNDEFINED
&& strcmp (opname, "nop") == 0)
{
if (use_transform () && !has_underbar && density_supported)
xg_replace_opname (popname, "nop.n");
else
{
if (xg_check_num_args (pnum_args, 0, opname, arg_strings))
return -1;
xg_replace_opname (popname, (has_underbar ? "_or" : "or"));
arg_strings[0] = xstrdup ("a1");
arg_strings[1] = xstrdup ("a1");
arg_strings[2] = xstrdup ("a1");
*pnum_args = 3;
}
return 0;
}
/* Recognize [RW]UR and [RWX]SR. */
if ((((opname[0] == 'r' || opname[0] == 'w')
&& (opname[1] == 'u' || opname[1] == 's'))
|| (opname[0] == 'x' && opname[1] == 's'))
&& opname[2] == 'r'
&& opname[3] == '\0')
return xg_translate_sysreg_op (popname, pnum_args, arg_strings);
/* Backward compatibility for RUR and WUR: Recognize [RW]UR<nnn> and
[RW]<name> if <name> is the non-default name of a user register. */
if ((opname[0] == 'r' || opname[0] == 'w')
&& xtensa_opcode_lookup (xtensa_default_isa, opname) == XTENSA_UNDEFINED)
return xtensa_translate_old_userreg_ops (popname);
/* Relax branches that don't allow comparisons against an immediate value
of zero to the corresponding branches with implicit zero immediates. */
if (!has_underbar && use_transform ())
{
if (xtensa_translate_zero_immed ("bnei", "bnez", popname,
pnum_args, arg_strings))
return -1;
if (xtensa_translate_zero_immed ("beqi", "beqz", popname,
pnum_args, arg_strings))
return -1;
if (xtensa_translate_zero_immed ("bgei", "bgez", popname,
pnum_args, arg_strings))
return -1;
if (xtensa_translate_zero_immed ("blti", "bltz", popname,
pnum_args, arg_strings))
return -1;
}
return 0;
}
/* Functions for dealing with the Xtensa ISA. */
/* Currently the assembler only allows us to use a single target per
fragment. Because of this, only one operand for a given
instruction may be symbolic. If there is a PC-relative operand,
the last one is chosen. Otherwise, the result is the number of the
last immediate operand, and if there are none of those, we fail and
return -1. */
static int
get_relaxable_immed (xtensa_opcode opcode)
{
int last_immed = -1;
int noperands, opi;
if (opcode == XTENSA_UNDEFINED)
return -1;
noperands = xtensa_opcode_num_operands (xtensa_default_isa, opcode);
for (opi = noperands - 1; opi >= 0; opi--)
{
if (xtensa_operand_is_visible (xtensa_default_isa, opcode, opi) == 0)
continue;
if (xtensa_operand_is_PCrelative (xtensa_default_isa, opcode, opi) == 1)
return opi;
if (last_immed == -1
&& xtensa_operand_is_register (xtensa_default_isa, opcode, opi) == 0)
last_immed = opi;
}
return last_immed;
}
static xtensa_opcode
get_opcode_from_buf (const char *buf, int slot)
{
static xtensa_insnbuf insnbuf = NULL;
static xtensa_insnbuf slotbuf = NULL;
xtensa_isa isa = xtensa_default_isa;
xtensa_format fmt;
if (!insnbuf)
{
insnbuf = xtensa_insnbuf_alloc (isa);
slotbuf = xtensa_insnbuf_alloc (isa);
}
xtensa_insnbuf_from_chars (isa, insnbuf, (const unsigned char *) buf, 0);
fmt = xtensa_format_decode (isa, insnbuf);
if (fmt == XTENSA_UNDEFINED)
return XTENSA_UNDEFINED;
if (slot >= xtensa_format_num_slots (isa, fmt))
return XTENSA_UNDEFINED;
xtensa_format_get_slot (isa, fmt, slot, insnbuf, slotbuf);
return xtensa_opcode_decode (isa, fmt, slot, slotbuf);
}
#ifdef TENSILICA_DEBUG
/* For debugging, print out the mapping of opcode numbers to opcodes. */
static void
xtensa_print_insn_table (void)
{
int num_opcodes, num_operands;
xtensa_opcode opcode;
xtensa_isa isa = xtensa_default_isa;
num_opcodes = xtensa_isa_num_opcodes (xtensa_default_isa);
for (opcode = 0; opcode < num_opcodes; opcode++)
{
int opn;
fprintf (stderr, "%d: %s: ", opcode, xtensa_opcode_name (isa, opcode));
num_operands = xtensa_opcode_num_operands (isa, opcode);
for (opn = 0; opn < num_operands; opn++)
{
if (xtensa_operand_is_visible (isa, opcode, opn) == 0)
continue;
if (xtensa_operand_is_register (isa, opcode, opn) == 1)
{
xtensa_regfile opnd_rf =
xtensa_operand_regfile (isa, opcode, opn);
fprintf (stderr, "%s ", xtensa_regfile_shortname (isa, opnd_rf));
}
else if (xtensa_operand_is_PCrelative (isa, opcode, opn) == 1)
fputs ("[lLr] ", stderr);
else
fputs ("i ", stderr);
}
fprintf (stderr, "\n");
}
}
static void
print_vliw_insn (xtensa_insnbuf vbuf)
{
xtensa_isa isa = xtensa_default_isa;
xtensa_format f = xtensa_format_decode (isa, vbuf);
xtensa_insnbuf sbuf = xtensa_insnbuf_alloc (isa);
int op;
fprintf (stderr, "format = %d\n", f);
for (op = 0; op < xtensa_format_num_slots (isa, f); op++)
{
xtensa_opcode opcode;
const char *opname;
int operands;
xtensa_format_get_slot (isa, f, op, vbuf, sbuf);
opcode = xtensa_opcode_decode (isa, f, op, sbuf);
opname = xtensa_opcode_name (isa, opcode);
fprintf (stderr, "op in slot %i is %s;\n", op, opname);
fprintf (stderr, " operands = ");
for (operands = 0;
operands < xtensa_opcode_num_operands (isa, opcode);
operands++)
{
unsigned int val;
if (xtensa_operand_is_visible (isa, opcode, operands) == 0)
continue;
xtensa_operand_get_field (isa, opcode, operands, f, op, sbuf, &val);
xtensa_operand_decode (isa, opcode, operands, &val);
fprintf (stderr, "%d ", val);
}
fprintf (stderr, "\n");
}
xtensa_insnbuf_free (isa, sbuf);
}
#endif /* TENSILICA_DEBUG */
static bool
is_direct_call_opcode (xtensa_opcode opcode)
{
xtensa_isa isa = xtensa_default_isa;
int n, num_operands;
if (xtensa_opcode_is_call (isa, opcode) != 1)
return false;
num_operands = xtensa_opcode_num_operands (isa, opcode);
for (n = 0; n < num_operands; n++)
{
if (xtensa_operand_is_register (isa, opcode, n) == 0
&& xtensa_operand_is_PCrelative (isa, opcode, n) == 1)
return true;
}
return false;
}
/* Convert from BFD relocation type code to slot and operand number.
Returns non-zero on failure. */
static int
decode_reloc (bfd_reloc_code_real_type reloc, int *slot, bool *is_alt)
{
if (reloc >= BFD_RELOC_XTENSA_SLOT0_OP
&& reloc <= BFD_RELOC_XTENSA_SLOT14_OP)
{
*slot = reloc - BFD_RELOC_XTENSA_SLOT0_OP;
*is_alt = false;
}
else if (reloc >= BFD_RELOC_XTENSA_SLOT0_ALT
&& reloc <= BFD_RELOC_XTENSA_SLOT14_ALT)
{
*slot = reloc - BFD_RELOC_XTENSA_SLOT0_ALT;
*is_alt = true;
}
else
return -1;
return 0;
}
/* Convert from slot number to BFD relocation type code for the
standard PC-relative relocations. Return BFD_RELOC_NONE on
failure. */
static bfd_reloc_code_real_type
encode_reloc (int slot)
{
if (slot < 0 || slot > 14)
return BFD_RELOC_NONE;
return BFD_RELOC_XTENSA_SLOT0_OP + slot;
}
/* Convert from slot numbers to BFD relocation type code for the
"alternate" relocations. Return BFD_RELOC_NONE on failure. */
static bfd_reloc_code_real_type
encode_alt_reloc (int slot)
{
if (slot < 0 || slot > 14)
return BFD_RELOC_NONE;
return BFD_RELOC_XTENSA_SLOT0_ALT + slot;
}
static void
xtensa_insnbuf_set_operand (xtensa_insnbuf slotbuf,
xtensa_format fmt,
int slot,
xtensa_opcode opcode,
int operand,
uint32 value,
const char *file,
unsigned int line)
{
uint32 valbuf = value;
if (xtensa_operand_encode (xtensa_default_isa, opcode, operand, &valbuf))
{
if (xtensa_operand_is_PCrelative (xtensa_default_isa, opcode, operand)
== 1)
as_bad_where ((char *) file, line,
_("operand %d of '%s' has out of range value '%u'"),
operand + 1,
xtensa_opcode_name (xtensa_default_isa, opcode),
value);
else
as_bad_where ((char *) file, line,
_("operand %d of '%s' has invalid value '%u'"),
operand + 1,
xtensa_opcode_name (xtensa_default_isa, opcode),
value);
return;
}
xtensa_operand_set_field (xtensa_default_isa, opcode, operand, fmt, slot,
slotbuf, valbuf);
}
static uint32
xtensa_insnbuf_get_operand (xtensa_insnbuf slotbuf,
xtensa_format fmt,
int slot,
xtensa_opcode opcode,
int opnum)
{
uint32 val = 0;
(void) xtensa_operand_get_field (xtensa_default_isa, opcode, opnum,
fmt, slot, slotbuf, &val);
(void) xtensa_operand_decode (xtensa_default_isa, opcode, opnum, &val);
return val;
}
/* Checks for rules from xtensa-relax tables. */
/* The routine xg_instruction_matches_option_term must return TRUE
when a given option term is true. The meaning of all of the option
terms is given interpretation by this function. */
static bool
xg_instruction_matches_option_term (TInsn *insn, const ReqOrOption *option)
{
if (strcmp (option->option_name, "realnop") == 0
|| startswith (option->option_name, "IsaUse"))
{
/* These conditions were evaluated statically when building the
relaxation table. There's no need to reevaluate them now. */
return true;
}
else if (strcmp (option->option_name, "FREEREG") == 0)
return insn->extra_arg.X_op == O_register;
else
{
as_fatal (_("internal error: unknown option name '%s'"),
option->option_name);
}
}
static bool
xg_instruction_matches_or_options (TInsn *insn,
const ReqOrOptionList *or_option)
{
const ReqOrOption *option;
/* Must match each of the AND terms. */
for (option = or_option; option != NULL; option = option->next)
{
if (xg_instruction_matches_option_term (insn, option))
return true;
}
return false;
}
static bool
xg_instruction_matches_options (TInsn *insn, const ReqOptionList *options)
{
const ReqOption *req_options;
/* Must match each of the AND terms. */
for (req_options = options;
req_options != NULL;
req_options = req_options->next)
{
/* Must match one of the OR clauses. */
if (!xg_instruction_matches_or_options (insn,
req_options->or_option_terms))
return false;
}
return true;
}
/* Return the transition rule that matches or NULL if none matches. */
static bool
xg_instruction_matches_rule (TInsn *insn, TransitionRule *rule)
{
PreconditionList *condition_l;
if (rule->opcode != insn->opcode)
return false;
for (condition_l = rule->conditions;
condition_l != NULL;
condition_l = condition_l->next)
{
expressionS *exp1;
expressionS *exp2;
Precondition *cond = condition_l->precond;
switch (cond->typ)
{
case OP_CONSTANT:
/* The expression must be the constant. */
gas_assert (cond->op_num < insn->ntok);
exp1 = &insn->tok[cond->op_num];
if (expr_is_const (exp1))
{
switch (cond->cmp)
{
case OP_EQUAL:
if (get_expr_const (exp1) != cond->op_data)
return false;
break;
case OP_NOTEQUAL:
if (get_expr_const (exp1) == cond->op_data)
return false;
break;
default:
return false;
}
}
else if (expr_is_register (exp1))
{
switch (cond->cmp)
{
case OP_EQUAL:
if (get_expr_register (exp1) != cond->op_data)
return false;
break;
case OP_NOTEQUAL:
if (get_expr_register (exp1) == cond->op_data)
return false;
break;
default:
return false;
}
}
else
return false;
break;
case OP_OPERAND:
gas_assert (cond->op_num < insn->ntok);
gas_assert (cond->op_data < insn->ntok);
exp1 = &insn->tok[cond->op_num];
exp2 = &insn->tok[cond->op_data];
switch (cond->cmp)
{
case OP_EQUAL:
if (!expr_is_equal (exp1, exp2))
return false;
break;
case OP_NOTEQUAL:
if (expr_is_equal (exp1, exp2))
return false;
break;
}
break;
case OP_LITERAL:
case OP_LABEL:
default:
return false;
}
}
if (!xg_instruction_matches_options (insn, rule->options))
return false;
return true;
}
static int
transition_rule_cmp (const TransitionRule *a, const TransitionRule *b)
{
bool a_greater = false;
bool b_greater = false;
ReqOptionList *l_a = a->options;
ReqOptionList *l_b = b->options;
/* We only care if they both are the same except for
a const16 vs. an l32r. */
while (l_a && l_b && ((l_a->next == NULL) == (l_b->next == NULL)))
{
ReqOrOptionList *l_or_a = l_a->or_option_terms;
ReqOrOptionList *l_or_b = l_b->or_option_terms;
while (l_or_a && l_or_b && ((l_a->next == NULL) == (l_b->next == NULL)))
{
if (l_or_a->is_true != l_or_b->is_true)
return 0;
if (strcmp (l_or_a->option_name, l_or_b->option_name) != 0)
{
/* This is the case we care about. */
if (strcmp (l_or_a->option_name, "IsaUseConst16") == 0
&& strcmp (l_or_b->option_name, "IsaUseL32R") == 0)
{
if (prefer_const16)
a_greater = true;
else
b_greater = true;
}
else if (strcmp (l_or_a->option_name, "IsaUseL32R") == 0
&& strcmp (l_or_b->option_name, "IsaUseConst16") == 0)
{
if (prefer_const16)
b_greater = true;
else
a_greater = true;
}
else
return 0;
}
l_or_a = l_or_a->next;
l_or_b = l_or_b->next;
}
if (l_or_a || l_or_b)
return 0;
l_a = l_a->next;
l_b = l_b->next;
}
if (l_a || l_b)
return 0;
/* Incomparable if the substitution was used differently in two cases. */
if (a_greater && b_greater)
return 0;
if (b_greater)
return 1;
if (a_greater)
return -1;
return 0;
}
static TransitionRule *
xg_instruction_match (TInsn *insn)
{
TransitionTable *table = xg_build_simplify_table (&transition_rule_cmp);
TransitionList *l;
gas_assert (insn->opcode < table->num_opcodes);
/* Walk through all of the possible transitions. */
for (l = table->table[insn->opcode]; l != NULL; l = l->next)
{
TransitionRule *rule = l->rule;
if (xg_instruction_matches_rule (insn, rule))
return rule;
}
return NULL;
}
/* Various Other Internal Functions. */
static bool
is_unique_insn_expansion (TransitionRule *r)
{
if (!r->to_instr || r->to_instr->next != NULL)
return false;
if (r->to_instr->typ != INSTR_INSTR)
return false;
return true;
}
/* Check if there is exactly one relaxation for INSN that converts it to
another instruction of equal or larger size. If so, and if TARG is
non-null, go ahead and generate the relaxed instruction into TARG. If
NARROW_ONLY is true, then only consider relaxations that widen a narrow
instruction, i.e., ignore relaxations that convert to an instruction of
equal size. In some contexts where this function is used, only
a single widening is allowed and the NARROW_ONLY argument is used to
exclude cases like ADDI being "widened" to an ADDMI, which may
later be relaxed to an ADDMI/ADDI pair. */
bool
xg_is_single_relaxable_insn (TInsn *insn, TInsn *targ, bool narrow_only)
{
TransitionTable *table = xg_build_widen_table (&transition_rule_cmp);
TransitionList *l;
TransitionRule *match = 0;
gas_assert (insn->insn_type == ITYPE_INSN);
gas_assert (insn->opcode < table->num_opcodes);
for (l = table->table[insn->opcode]; l != NULL; l = l->next)
{
TransitionRule *rule = l->rule;
if (xg_instruction_matches_rule (insn, rule)
&& is_unique_insn_expansion (rule)
&& (xg_get_single_size (insn->opcode) + (narrow_only ? 1 : 0)
<= xg_get_single_size (rule->to_instr->opcode)))
{
if (match)
return false;
match = rule;
}
}
if (!match)
return false;
if (targ)
xg_build_to_insn (targ, insn, match->to_instr);
return true;
}
/* Return the maximum number of bytes this opcode can expand to. */
static int
xg_get_max_insn_widen_size (xtensa_opcode opcode)
{
TransitionTable *table = xg_build_widen_table (&transition_rule_cmp);
TransitionList *l;
int max_size = xg_get_single_size (opcode);
gas_assert (opcode < table->num_opcodes);
for (l = table->table[opcode]; l != NULL; l = l->next)
{
TransitionRule *rule = l->rule;
BuildInstr *build_list;
int this_size = 0;
if (!rule)
continue;
build_list = rule->to_instr;
if (is_unique_insn_expansion (rule))
{
gas_assert (build_list->typ == INSTR_INSTR);
this_size = xg_get_max_insn_widen_size (build_list->opcode);
}
else
for (; build_list != NULL; build_list = build_list->next)
{
switch (build_list->typ)
{
case INSTR_INSTR:
this_size += xg_get_single_size (build_list->opcode);
break;
case INSTR_LITERAL_DEF:
case INSTR_LABEL_DEF:
default:
break;
}
}
if (this_size > max_size)
max_size = this_size;
}