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