| /* tc-alpha.c - Processor-specific code for the DEC Alpha AXP CPU. |
| Copyright (C) 1989-2021 Free Software Foundation, Inc. |
| Contributed by Carnegie Mellon University, 1993. |
| Written by Alessandro Forin, based on earlier gas-1.38 target CPU files. |
| Modified by Ken Raeburn for gas-2.x and ECOFF support. |
| Modified by Richard Henderson for ELF support. |
| Modified by Klaus K"ampf for EVAX (OpenVMS/Alpha) support. |
| |
| 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. */ |
| |
| /* Mach Operating System |
| Copyright (c) 1993 Carnegie Mellon University |
| All Rights Reserved. |
| |
| Permission to use, copy, modify and distribute this software and its |
| documentation is hereby granted, provided that both the copyright |
| notice and this permission notice appear in all copies of the |
| software, derivative works or modified versions, and any portions |
| thereof, and that both notices appear in supporting documentation. |
| |
| CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS |
| CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR |
| ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. |
| |
| Carnegie Mellon requests users of this software to return to |
| |
| Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU |
| School of Computer Science |
| Carnegie Mellon University |
| Pittsburgh PA 15213-3890 |
| |
| any improvements or extensions that they make and grant Carnegie the |
| rights to redistribute these changes. */ |
| |
| #include "as.h" |
| #include "subsegs.h" |
| #include "ecoff.h" |
| |
| #include "opcode/alpha.h" |
| |
| #ifdef OBJ_ELF |
| #include "elf/alpha.h" |
| #endif |
| |
| #ifdef OBJ_EVAX |
| #include "vms.h" |
| #include "vms/egps.h" |
| #endif |
| |
| #include "dwarf2dbg.h" |
| #include "dw2gencfi.h" |
| #include "safe-ctype.h" |
| |
| /* Local types. */ |
| |
| #define TOKENIZE_ERROR -1 |
| #define TOKENIZE_ERROR_REPORT -2 |
| #define MAX_INSN_FIXUPS 2 |
| #define MAX_INSN_ARGS 5 |
| |
| /* Used since new relocation types are introduced in this |
| file (DUMMY_RELOC_LITUSE_*) */ |
| typedef int extended_bfd_reloc_code_real_type; |
| |
| struct alpha_fixup |
| { |
| expressionS exp; |
| /* bfd_reloc_code_real_type reloc; */ |
| extended_bfd_reloc_code_real_type reloc; |
| #ifdef OBJ_EVAX |
| /* The symbol of the item in the linkage section. */ |
| symbolS *xtrasym; |
| |
| /* The symbol of the procedure descriptor. */ |
| symbolS *procsym; |
| #endif |
| }; |
| |
| struct alpha_insn |
| { |
| unsigned insn; |
| int nfixups; |
| struct alpha_fixup fixups[MAX_INSN_FIXUPS]; |
| long sequence; |
| }; |
| |
| enum alpha_macro_arg |
| { |
| MACRO_EOA = 1, |
| MACRO_IR, |
| MACRO_PIR, |
| MACRO_OPIR, |
| MACRO_CPIR, |
| MACRO_FPR, |
| MACRO_EXP |
| }; |
| |
| struct alpha_macro |
| { |
| const char *name; |
| void (*emit) (const expressionS *, int, const void *); |
| const void * arg; |
| enum alpha_macro_arg argsets[16]; |
| }; |
| |
| /* Extra expression types. */ |
| |
| #define O_pregister O_md1 /* O_register, in parentheses. */ |
| #define O_cpregister O_md2 /* + a leading comma. */ |
| |
| /* The alpha_reloc_op table below depends on the ordering of these. */ |
| #define O_literal O_md3 /* !literal relocation. */ |
| #define O_lituse_addr O_md4 /* !lituse_addr relocation. */ |
| #define O_lituse_base O_md5 /* !lituse_base relocation. */ |
| #define O_lituse_bytoff O_md6 /* !lituse_bytoff relocation. */ |
| #define O_lituse_jsr O_md7 /* !lituse_jsr relocation. */ |
| #define O_lituse_tlsgd O_md8 /* !lituse_tlsgd relocation. */ |
| #define O_lituse_tlsldm O_md9 /* !lituse_tlsldm relocation. */ |
| #define O_lituse_jsrdirect O_md10 /* !lituse_jsrdirect relocation. */ |
| #define O_gpdisp O_md11 /* !gpdisp relocation. */ |
| #define O_gprelhigh O_md12 /* !gprelhigh relocation. */ |
| #define O_gprellow O_md13 /* !gprellow relocation. */ |
| #define O_gprel O_md14 /* !gprel relocation. */ |
| #define O_samegp O_md15 /* !samegp relocation. */ |
| #define O_tlsgd O_md16 /* !tlsgd relocation. */ |
| #define O_tlsldm O_md17 /* !tlsldm relocation. */ |
| #define O_gotdtprel O_md18 /* !gotdtprel relocation. */ |
| #define O_dtprelhi O_md19 /* !dtprelhi relocation. */ |
| #define O_dtprello O_md20 /* !dtprello relocation. */ |
| #define O_dtprel O_md21 /* !dtprel relocation. */ |
| #define O_gottprel O_md22 /* !gottprel relocation. */ |
| #define O_tprelhi O_md23 /* !tprelhi relocation. */ |
| #define O_tprello O_md24 /* !tprello relocation. */ |
| #define O_tprel O_md25 /* !tprel relocation. */ |
| |
| #define DUMMY_RELOC_LITUSE_ADDR (BFD_RELOC_UNUSED + 1) |
| #define DUMMY_RELOC_LITUSE_BASE (BFD_RELOC_UNUSED + 2) |
| #define DUMMY_RELOC_LITUSE_BYTOFF (BFD_RELOC_UNUSED + 3) |
| #define DUMMY_RELOC_LITUSE_JSR (BFD_RELOC_UNUSED + 4) |
| #define DUMMY_RELOC_LITUSE_TLSGD (BFD_RELOC_UNUSED + 5) |
| #define DUMMY_RELOC_LITUSE_TLSLDM (BFD_RELOC_UNUSED + 6) |
| #define DUMMY_RELOC_LITUSE_JSRDIRECT (BFD_RELOC_UNUSED + 7) |
| |
| #define USER_RELOC_P(R) ((R) >= O_literal && (R) <= O_tprel) |
| |
| /* Macros for extracting the type and number of encoded register tokens. */ |
| |
| #define is_ir_num(x) (((x) & 32) == 0) |
| #define is_fpr_num(x) (((x) & 32) != 0) |
| #define regno(x) ((x) & 31) |
| |
| /* Something odd inherited from the old assembler. */ |
| |
| #define note_gpreg(R) (alpha_gprmask |= (1 << (R))) |
| #define note_fpreg(R) (alpha_fprmask |= (1 << (R))) |
| |
| /* Predicates for 16- and 32-bit ranges */ |
| /* XXX: The non-shift version appears to trigger a compiler bug when |
| cross-assembling from x86 w/ gcc 2.7.2. */ |
| |
| #if 1 |
| #define range_signed_16(x) \ |
| (((offsetT) (x) >> 15) == 0 || ((offsetT) (x) >> 15) == -1) |
| #define range_signed_32(x) \ |
| (((offsetT) (x) >> 31) == 0 || ((offsetT) (x) >> 31) == -1) |
| #else |
| #define range_signed_16(x) ((offsetT) (x) >= -(offsetT) 0x8000 && \ |
| (offsetT) (x) <= (offsetT) 0x7FFF) |
| #define range_signed_32(x) ((offsetT) (x) >= -(offsetT) 0x80000000 && \ |
| (offsetT) (x) <= (offsetT) 0x7FFFFFFF) |
| #endif |
| |
| /* Macros for sign extending from 16- and 32-bits. */ |
| /* XXX: The cast macros will work on all the systems that I care about, |
| but really a predicate should be found to use the non-cast forms. */ |
| |
| #if 1 |
| #define sign_extend_16(x) ((short) (x)) |
| #define sign_extend_32(x) ((int) (x)) |
| #else |
| #define sign_extend_16(x) ((offsetT) (((x) & 0xFFFF) ^ 0x8000) - 0x8000) |
| #define sign_extend_32(x) ((offsetT) (((x) & 0xFFFFFFFF) \ |
| ^ 0x80000000) - 0x80000000) |
| #endif |
| |
| /* Macros to build tokens. */ |
| |
| #define set_tok_reg(t, r) (memset (&(t), 0, sizeof (t)), \ |
| (t).X_op = O_register, \ |
| (t).X_add_number = (r)) |
| #define set_tok_preg(t, r) (memset (&(t), 0, sizeof (t)), \ |
| (t).X_op = O_pregister, \ |
| (t).X_add_number = (r)) |
| #define set_tok_cpreg(t, r) (memset (&(t), 0, sizeof (t)), \ |
| (t).X_op = O_cpregister, \ |
| (t).X_add_number = (r)) |
| #define set_tok_freg(t, r) (memset (&(t), 0, sizeof (t)), \ |
| (t).X_op = O_register, \ |
| (t).X_add_number = (r) + 32) |
| #define set_tok_sym(t, s, a) (memset (&(t), 0, sizeof (t)), \ |
| (t).X_op = O_symbol, \ |
| (t).X_add_symbol = (s), \ |
| (t).X_add_number = (a)) |
| #define set_tok_const(t, n) (memset (&(t), 0, sizeof (t)), \ |
| (t).X_op = O_constant, \ |
| (t).X_add_number = (n)) |
| |
| /* Generic assembler global variables which must be defined by all |
| targets. */ |
| |
| /* Characters which always start a comment. */ |
| const char comment_chars[] = "#"; |
| |
| /* Characters which start a comment at the beginning of a line. */ |
| const char line_comment_chars[] = "#"; |
| |
| /* Characters which may be used to separate multiple commands on a |
| single line. */ |
| const char line_separator_chars[] = ";"; |
| |
| /* Characters which are used to indicate an exponent in a floating |
| point number. */ |
| const char EXP_CHARS[] = "eE"; |
| |
| /* Characters which mean that a number is a floating point constant, |
| as in 0d1.0. */ |
| /* XXX: Do all of these really get used on the alpha?? */ |
| const char FLT_CHARS[] = "rRsSfFdDxXpP"; |
| |
| #ifdef OBJ_EVAX |
| const char *md_shortopts = "Fm:g+1h:HG:"; |
| #else |
| const char *md_shortopts = "Fm:gG:"; |
| #endif |
| |
| struct option md_longopts[] = |
| { |
| #define OPTION_32ADDR (OPTION_MD_BASE) |
| { "32addr", no_argument, NULL, OPTION_32ADDR }, |
| #define OPTION_RELAX (OPTION_32ADDR + 1) |
| { "relax", no_argument, NULL, OPTION_RELAX }, |
| #ifdef OBJ_ELF |
| #define OPTION_MDEBUG (OPTION_RELAX + 1) |
| #define OPTION_NO_MDEBUG (OPTION_MDEBUG + 1) |
| { "mdebug", no_argument, NULL, OPTION_MDEBUG }, |
| { "no-mdebug", no_argument, NULL, OPTION_NO_MDEBUG }, |
| #endif |
| #ifdef OBJ_EVAX |
| #define OPTION_REPLACE (OPTION_RELAX + 1) |
| #define OPTION_NOREPLACE (OPTION_REPLACE+1) |
| { "replace", no_argument, NULL, OPTION_REPLACE }, |
| { "noreplace", no_argument, NULL, OPTION_NOREPLACE }, |
| #endif |
| { NULL, no_argument, NULL, 0 } |
| }; |
| |
| size_t md_longopts_size = sizeof (md_longopts); |
| |
| #ifdef OBJ_EVAX |
| #define AXP_REG_R0 0 |
| #define AXP_REG_R16 16 |
| #define AXP_REG_R17 17 |
| #undef AXP_REG_T9 |
| #define AXP_REG_T9 22 |
| #undef AXP_REG_T10 |
| #define AXP_REG_T10 23 |
| #undef AXP_REG_T11 |
| #define AXP_REG_T11 24 |
| #undef AXP_REG_T12 |
| #define AXP_REG_T12 25 |
| #define AXP_REG_AI 25 |
| #undef AXP_REG_FP |
| #define AXP_REG_FP 29 |
| |
| #undef AXP_REG_GP |
| #define AXP_REG_GP AXP_REG_PV |
| |
| #endif /* OBJ_EVAX */ |
| |
| /* The cpu for which we are generating code. */ |
| static unsigned alpha_target = AXP_OPCODE_BASE; |
| static const char *alpha_target_name = "<all>"; |
| |
| /* The hash table of instruction opcodes. */ |
| static htab_t alpha_opcode_hash; |
| |
| /* The hash table of macro opcodes. */ |
| static htab_t alpha_macro_hash; |
| |
| #ifdef OBJ_ECOFF |
| /* The $gp relocation symbol. */ |
| static symbolS *alpha_gp_symbol; |
| |
| /* XXX: what is this, and why is it exported? */ |
| valueT alpha_gp_value; |
| #endif |
| |
| /* The current $gp register. */ |
| static int alpha_gp_register = AXP_REG_GP; |
| |
| /* A table of the register symbols. */ |
| static symbolS *alpha_register_table[64]; |
| |
| /* Constant sections, or sections of constants. */ |
| #ifdef OBJ_ECOFF |
| static segT alpha_lita_section; |
| #endif |
| #ifdef OBJ_EVAX |
| segT alpha_link_section; |
| #endif |
| #ifndef OBJ_EVAX |
| static segT alpha_lit8_section; |
| #endif |
| |
| /* Symbols referring to said sections. */ |
| #ifdef OBJ_ECOFF |
| static symbolS *alpha_lita_symbol; |
| #endif |
| #ifdef OBJ_EVAX |
| static symbolS *alpha_link_symbol; |
| #endif |
| #ifndef OBJ_EVAX |
| static symbolS *alpha_lit8_symbol; |
| #endif |
| |
| /* Literal for .litX+0x8000 within .lita. */ |
| #ifdef OBJ_ECOFF |
| static offsetT alpha_lit8_literal; |
| #endif |
| |
| /* Is the assembler not allowed to use $at? */ |
| static int alpha_noat_on = 0; |
| |
| /* Are macros enabled? */ |
| static int alpha_macros_on = 1; |
| |
| /* Are floats disabled? */ |
| static int alpha_nofloats_on = 0; |
| |
| /* Are addresses 32 bit? */ |
| static int alpha_addr32_on = 0; |
| |
| /* Symbol labelling the current insn. When the Alpha gas sees |
| foo: |
| .quad 0 |
| and the section happens to not be on an eight byte boundary, it |
| will align both the symbol and the .quad to an eight byte boundary. */ |
| static symbolS *alpha_insn_label; |
| #if defined(OBJ_ELF) || defined (OBJ_EVAX) |
| static symbolS *alpha_prologue_label; |
| #endif |
| |
| #ifdef OBJ_EVAX |
| /* Symbol associate with the current jsr instruction. */ |
| static symbolS *alpha_linkage_symbol; |
| #endif |
| |
| /* Whether we should automatically align data generation pseudo-ops. |
| .align 0 will turn this off. */ |
| static int alpha_auto_align_on = 1; |
| |
| /* The known current alignment of the current section. */ |
| static int alpha_current_align; |
| |
| /* These are exported to ECOFF code. */ |
| unsigned long alpha_gprmask, alpha_fprmask; |
| |
| /* Whether the debugging option was seen. */ |
| static int alpha_debug; |
| |
| #ifdef OBJ_ELF |
| /* Whether we are emitting an mdebug section. */ |
| int alpha_flag_mdebug = -1; |
| #endif |
| |
| #ifdef OBJ_EVAX |
| /* Whether to perform the VMS procedure call optimization. */ |
| int alpha_flag_replace = 1; |
| #endif |
| |
| /* Don't fully resolve relocations, allowing code movement in the linker. */ |
| static int alpha_flag_relax; |
| |
| /* What value to give to bfd_set_gp_size. */ |
| static int g_switch_value = 8; |
| |
| #ifdef OBJ_EVAX |
| /* Collect information about current procedure here. */ |
| struct alpha_evax_procs |
| { |
| symbolS *symbol; /* Proc pdesc symbol. */ |
| int pdsckind; |
| int framereg; /* Register for frame pointer. */ |
| int framesize; /* Size of frame. */ |
| int rsa_offset; |
| int ra_save; |
| int fp_save; |
| long imask; |
| long fmask; |
| int type; |
| int prologue; |
| symbolS *handler; |
| int handler_data; |
| }; |
| |
| /* Linked list of .linkage fixups. */ |
| struct alpha_linkage_fixups *alpha_linkage_fixup_root; |
| static struct alpha_linkage_fixups *alpha_linkage_fixup_tail; |
| |
| /* Current procedure descriptor. */ |
| static struct alpha_evax_procs *alpha_evax_proc; |
| static struct alpha_evax_procs alpha_evax_proc_data; |
| |
| static int alpha_flag_hash_long_names = 0; /* -+ */ |
| static int alpha_flag_show_after_trunc = 0; /* -H */ |
| |
| /* If the -+ switch is given, then a hash is appended to any name that is |
| longer than 64 characters, else longer symbol names are truncated. */ |
| |
| #endif |
| |
| #ifdef RELOC_OP_P |
| /* A table to map the spelling of a relocation operand into an appropriate |
| bfd_reloc_code_real_type type. The table is assumed to be ordered such |
| that op-O_literal indexes into it. */ |
| |
| #define ALPHA_RELOC_TABLE(op) \ |
| (&alpha_reloc_op[ ((!USER_RELOC_P (op)) \ |
| ? (abort (), 0) \ |
| : (int) (op) - (int) O_literal) ]) |
| |
| #define DEF(NAME, RELOC, REQ, ALLOW) \ |
| { #NAME, sizeof(#NAME)-1, O_##NAME, RELOC, REQ, ALLOW} |
| |
| static const struct alpha_reloc_op_tag |
| { |
| const char *name; /* String to lookup. */ |
| size_t length; /* Size of the string. */ |
| operatorT op; /* Which operator to use. */ |
| extended_bfd_reloc_code_real_type reloc; |
| unsigned int require_seq : 1; /* Require a sequence number. */ |
| unsigned int allow_seq : 1; /* Allow a sequence number. */ |
| } |
| alpha_reloc_op[] = |
| { |
| DEF (literal, BFD_RELOC_ALPHA_ELF_LITERAL, 0, 1), |
| DEF (lituse_addr, DUMMY_RELOC_LITUSE_ADDR, 1, 1), |
| DEF (lituse_base, DUMMY_RELOC_LITUSE_BASE, 1, 1), |
| DEF (lituse_bytoff, DUMMY_RELOC_LITUSE_BYTOFF, 1, 1), |
| DEF (lituse_jsr, DUMMY_RELOC_LITUSE_JSR, 1, 1), |
| DEF (lituse_tlsgd, DUMMY_RELOC_LITUSE_TLSGD, 1, 1), |
| DEF (lituse_tlsldm, DUMMY_RELOC_LITUSE_TLSLDM, 1, 1), |
| DEF (lituse_jsrdirect, DUMMY_RELOC_LITUSE_JSRDIRECT, 1, 1), |
| DEF (gpdisp, BFD_RELOC_ALPHA_GPDISP, 1, 1), |
| DEF (gprelhigh, BFD_RELOC_ALPHA_GPREL_HI16, 0, 0), |
| DEF (gprellow, BFD_RELOC_ALPHA_GPREL_LO16, 0, 0), |
| DEF (gprel, BFD_RELOC_GPREL16, 0, 0), |
| DEF (samegp, BFD_RELOC_ALPHA_BRSGP, 0, 0), |
| DEF (tlsgd, BFD_RELOC_ALPHA_TLSGD, 0, 1), |
| DEF (tlsldm, BFD_RELOC_ALPHA_TLSLDM, 0, 1), |
| DEF (gotdtprel, BFD_RELOC_ALPHA_GOTDTPREL16, 0, 0), |
| DEF (dtprelhi, BFD_RELOC_ALPHA_DTPREL_HI16, 0, 0), |
| DEF (dtprello, BFD_RELOC_ALPHA_DTPREL_LO16, 0, 0), |
| DEF (dtprel, BFD_RELOC_ALPHA_DTPREL16, 0, 0), |
| DEF (gottprel, BFD_RELOC_ALPHA_GOTTPREL16, 0, 0), |
| DEF (tprelhi, BFD_RELOC_ALPHA_TPREL_HI16, 0, 0), |
| DEF (tprello, BFD_RELOC_ALPHA_TPREL_LO16, 0, 0), |
| DEF (tprel, BFD_RELOC_ALPHA_TPREL16, 0, 0), |
| }; |
| |
| #undef DEF |
| |
| static const int alpha_num_reloc_op |
| = sizeof (alpha_reloc_op) / sizeof (*alpha_reloc_op); |
| #endif /* RELOC_OP_P */ |
| |
| /* Maximum # digits needed to hold the largest sequence #. */ |
| #define ALPHA_RELOC_DIGITS 25 |
| |
| /* Structure to hold explicit sequence information. */ |
| struct alpha_reloc_tag |
| { |
| fixS *master; /* The literal reloc. */ |
| #ifdef OBJ_EVAX |
| struct symbol *sym; /* Linkage section item symbol. */ |
| struct symbol *psym; /* Pdesc symbol. */ |
| #endif |
| fixS *slaves; /* Head of linked list of lituses. */ |
| segT segment; /* Segment relocs are in or undefined_section. */ |
| long sequence; /* Sequence #. */ |
| unsigned n_master; /* # of literals. */ |
| unsigned n_slaves; /* # of lituses. */ |
| unsigned saw_tlsgd : 1; /* True if ... */ |
| unsigned saw_tlsldm : 1; |
| unsigned saw_lu_tlsgd : 1; |
| unsigned saw_lu_tlsldm : 1; |
| unsigned multi_section_p : 1; /* True if more than one section was used. */ |
| char string[1]; /* Printable form of sequence to hash with. */ |
| }; |
| |
| /* Hash table to link up literals with the appropriate lituse. */ |
| static htab_t alpha_literal_hash; |
| |
| /* Sequence numbers for internal use by macros. */ |
| static long next_sequence_num = -1; |
| |
| /* A table of CPU names and opcode sets. */ |
| |
| static const struct cpu_type |
| { |
| const char *name; |
| unsigned flags; |
| } |
| cpu_types[] = |
| { |
| /* Ad hoc convention: cpu number gets palcode, process code doesn't. |
| This supports usage under DU 4.0b that does ".arch ev4", and |
| usage in MILO that does -m21064. Probably something more |
| specific like -m21064-pal should be used, but oh well. */ |
| |
| { "21064", AXP_OPCODE_BASE|AXP_OPCODE_EV4 }, |
| { "21064a", AXP_OPCODE_BASE|AXP_OPCODE_EV4 }, |
| { "21066", AXP_OPCODE_BASE|AXP_OPCODE_EV4 }, |
| { "21068", AXP_OPCODE_BASE|AXP_OPCODE_EV4 }, |
| { "21164", AXP_OPCODE_BASE|AXP_OPCODE_EV5 }, |
| { "21164a", AXP_OPCODE_BASE|AXP_OPCODE_EV5|AXP_OPCODE_BWX }, |
| { "21164pc", (AXP_OPCODE_BASE|AXP_OPCODE_EV5|AXP_OPCODE_BWX |
| |AXP_OPCODE_MAX) }, |
| { "21264", (AXP_OPCODE_BASE|AXP_OPCODE_EV6|AXP_OPCODE_BWX |
| |AXP_OPCODE_MAX|AXP_OPCODE_CIX) }, |
| { "21264a", (AXP_OPCODE_BASE|AXP_OPCODE_EV6|AXP_OPCODE_BWX |
| |AXP_OPCODE_MAX|AXP_OPCODE_CIX) }, |
| { "21264b", (AXP_OPCODE_BASE|AXP_OPCODE_EV6|AXP_OPCODE_BWX |
| |AXP_OPCODE_MAX|AXP_OPCODE_CIX) }, |
| |
| { "ev4", AXP_OPCODE_BASE }, |
| { "ev45", AXP_OPCODE_BASE }, |
| { "lca45", AXP_OPCODE_BASE }, |
| { "ev5", AXP_OPCODE_BASE }, |
| { "ev56", AXP_OPCODE_BASE|AXP_OPCODE_BWX }, |
| { "pca56", AXP_OPCODE_BASE|AXP_OPCODE_BWX|AXP_OPCODE_MAX }, |
| { "ev6", AXP_OPCODE_BASE|AXP_OPCODE_BWX|AXP_OPCODE_MAX|AXP_OPCODE_CIX }, |
| { "ev67", AXP_OPCODE_BASE|AXP_OPCODE_BWX|AXP_OPCODE_MAX|AXP_OPCODE_CIX }, |
| { "ev68", AXP_OPCODE_BASE|AXP_OPCODE_BWX|AXP_OPCODE_MAX|AXP_OPCODE_CIX }, |
| |
| { "all", AXP_OPCODE_BASE }, |
| { 0, 0 } |
| }; |
| |
| /* Some instruction sets indexed by lg(size). */ |
| static const char * const sextX_op[] = { "sextb", "sextw", "sextl", NULL }; |
| static const char * const insXl_op[] = { "insbl", "inswl", "insll", "insql" }; |
| static const char * const insXh_op[] = { NULL, "inswh", "inslh", "insqh" }; |
| static const char * const extXl_op[] = { "extbl", "extwl", "extll", "extql" }; |
| static const char * const extXh_op[] = { NULL, "extwh", "extlh", "extqh" }; |
| static const char * const mskXl_op[] = { "mskbl", "mskwl", "mskll", "mskql" }; |
| static const char * const mskXh_op[] = { NULL, "mskwh", "msklh", "mskqh" }; |
| static const char * const stX_op[] = { "stb", "stw", "stl", "stq" }; |
| static const char * const ldXu_op[] = { "ldbu", "ldwu", NULL, NULL }; |
| |
| static void assemble_insn (const struct alpha_opcode *, const expressionS *, int, struct alpha_insn *, extended_bfd_reloc_code_real_type); |
| static void emit_insn (struct alpha_insn *); |
| static void assemble_tokens (const char *, const expressionS *, int, int); |
| #ifdef OBJ_EVAX |
| static const char *s_alpha_section_name (void); |
| static symbolS *add_to_link_pool (symbolS *, offsetT); |
| #endif |
| |
| static struct alpha_reloc_tag * |
| get_alpha_reloc_tag (long sequence) |
| { |
| char buffer[ALPHA_RELOC_DIGITS]; |
| struct alpha_reloc_tag *info; |
| |
| sprintf (buffer, "!%ld", sequence); |
| |
| info = (struct alpha_reloc_tag *) str_hash_find (alpha_literal_hash, buffer); |
| if (! info) |
| { |
| size_t len = strlen (buffer); |
| |
| info = (struct alpha_reloc_tag *) |
| xcalloc (sizeof (struct alpha_reloc_tag) + len, 1); |
| |
| info->segment = now_seg; |
| info->sequence = sequence; |
| strcpy (info->string, buffer); |
| str_hash_insert (alpha_literal_hash, info->string, info, 0); |
| #ifdef OBJ_EVAX |
| info->sym = 0; |
| info->psym = 0; |
| #endif |
| } |
| |
| return info; |
| } |
| |
| #ifndef OBJ_EVAX |
| |
| static void |
| alpha_adjust_relocs (bfd *abfd ATTRIBUTE_UNUSED, |
| asection *sec, |
| void * ptr ATTRIBUTE_UNUSED) |
| { |
| segment_info_type *seginfo = seg_info (sec); |
| fixS **prevP; |
| fixS *fixp; |
| fixS *next; |
| fixS *slave; |
| |
| /* If seginfo is NULL, we did not create this section; don't do |
| anything with it. By using a pointer to a pointer, we can update |
| the links in place. */ |
| if (seginfo == NULL) |
| return; |
| |
| /* If there are no relocations, skip the section. */ |
| if (! seginfo->fix_root) |
| return; |
| |
| /* First rebuild the fixup chain without the explicit lituse and |
| gpdisp_lo16 relocs. */ |
| prevP = &seginfo->fix_root; |
| for (fixp = seginfo->fix_root; fixp; fixp = next) |
| { |
| next = fixp->fx_next; |
| fixp->fx_next = (fixS *) 0; |
| |
| switch (fixp->fx_r_type) |
| { |
| case BFD_RELOC_ALPHA_LITUSE: |
| if (fixp->tc_fix_data.info->n_master == 0) |
| as_bad_where (fixp->fx_file, fixp->fx_line, |
| _("No !literal!%ld was found"), |
| fixp->tc_fix_data.info->sequence); |
| #ifdef RELOC_OP_P |
| if (fixp->fx_offset == LITUSE_ALPHA_TLSGD) |
| { |
| if (! fixp->tc_fix_data.info->saw_tlsgd) |
| as_bad_where (fixp->fx_file, fixp->fx_line, |
| _("No !tlsgd!%ld was found"), |
| fixp->tc_fix_data.info->sequence); |
| } |
| else if (fixp->fx_offset == LITUSE_ALPHA_TLSLDM) |
| { |
| if (! fixp->tc_fix_data.info->saw_tlsldm) |
| as_bad_where (fixp->fx_file, fixp->fx_line, |
| _("No !tlsldm!%ld was found"), |
| fixp->tc_fix_data.info->sequence); |
| } |
| #endif |
| break; |
| |
| case BFD_RELOC_ALPHA_GPDISP_LO16: |
| if (fixp->tc_fix_data.info->n_master == 0) |
| as_bad_where (fixp->fx_file, fixp->fx_line, |
| _("No ldah !gpdisp!%ld was found"), |
| fixp->tc_fix_data.info->sequence); |
| break; |
| |
| case BFD_RELOC_ALPHA_ELF_LITERAL: |
| if (fixp->tc_fix_data.info |
| && (fixp->tc_fix_data.info->saw_tlsgd |
| || fixp->tc_fix_data.info->saw_tlsldm)) |
| break; |
| /* FALLTHRU */ |
| |
| default: |
| *prevP = fixp; |
| prevP = &fixp->fx_next; |
| break; |
| } |
| } |
| |
| /* Go back and re-chain dependent relocations. They are currently |
| linked through the next_reloc field in reverse order, so as we |
| go through the next_reloc chain, we effectively reverse the chain |
| once again. |
| |
| Except if there is more than one !literal for a given sequence |
| number. In that case, the programmer and/or compiler is not sure |
| how control flows from literal to lituse, and we can't be sure to |
| get the relaxation correct. |
| |
| ??? Well, actually we could, if there are enough lituses such that |
| we can make each literal have at least one of each lituse type |
| present. Not implemented. |
| |
| Also suppress the optimization if the !literals/!lituses are spread |
| in different segments. This can happen with "interesting" uses of |
| inline assembly; examples are present in the Linux kernel semaphores. */ |
| |
| for (fixp = seginfo->fix_root; fixp; fixp = next) |
| { |
| next = fixp->fx_next; |
| switch (fixp->fx_r_type) |
| { |
| case BFD_RELOC_ALPHA_TLSGD: |
| case BFD_RELOC_ALPHA_TLSLDM: |
| if (!fixp->tc_fix_data.info) |
| break; |
| if (fixp->tc_fix_data.info->n_master == 0) |
| break; |
| else if (fixp->tc_fix_data.info->n_master > 1) |
| { |
| as_bad_where (fixp->fx_file, fixp->fx_line, |
| _("too many !literal!%ld for %s"), |
| fixp->tc_fix_data.info->sequence, |
| (fixp->fx_r_type == BFD_RELOC_ALPHA_TLSGD |
| ? "!tlsgd" : "!tlsldm")); |
| break; |
| } |
| |
| fixp->tc_fix_data.info->master->fx_next = fixp->fx_next; |
| fixp->fx_next = fixp->tc_fix_data.info->master; |
| fixp = fixp->fx_next; |
| /* Fall through. */ |
| |
| case BFD_RELOC_ALPHA_ELF_LITERAL: |
| if (fixp->tc_fix_data.info |
| && fixp->tc_fix_data.info->n_master == 1 |
| && ! fixp->tc_fix_data.info->multi_section_p) |
| { |
| for (slave = fixp->tc_fix_data.info->slaves; |
| slave != (fixS *) 0; |
| slave = slave->tc_fix_data.next_reloc) |
| { |
| slave->fx_next = fixp->fx_next; |
| fixp->fx_next = slave; |
| } |
| } |
| break; |
| |
| case BFD_RELOC_ALPHA_GPDISP_HI16: |
| if (fixp->tc_fix_data.info->n_slaves == 0) |
| as_bad_where (fixp->fx_file, fixp->fx_line, |
| _("No lda !gpdisp!%ld was found"), |
| fixp->tc_fix_data.info->sequence); |
| else |
| { |
| slave = fixp->tc_fix_data.info->slaves; |
| slave->fx_next = next; |
| fixp->fx_next = slave; |
| } |
| break; |
| |
| default: |
| break; |
| } |
| } |
| } |
| |
| /* Before the relocations are written, reorder them, so that user |
| supplied !lituse relocations follow the appropriate !literal |
| relocations, and similarly for !gpdisp relocations. */ |
| |
| void |
| alpha_before_fix (void) |
| { |
| if (alpha_literal_hash) |
| bfd_map_over_sections (stdoutput, alpha_adjust_relocs, NULL); |
| } |
| |
| #endif |
| |
| #ifdef DEBUG_ALPHA |
| static void |
| debug_exp (expressionS tok[], int ntok) |
| { |
| int i; |
| |
| fprintf (stderr, "debug_exp: %d tokens", ntok); |
| for (i = 0; i < ntok; i++) |
| { |
| expressionS *t = &tok[i]; |
| const char *name; |
| |
| switch (t->X_op) |
| { |
| default: name = "unknown"; break; |
| case O_illegal: name = "O_illegal"; break; |
| case O_absent: name = "O_absent"; break; |
| case O_constant: name = "O_constant"; break; |
| case O_symbol: name = "O_symbol"; break; |
| case O_symbol_rva: name = "O_symbol_rva"; break; |
| case O_register: name = "O_register"; break; |
| case O_big: name = "O_big"; break; |
| case O_uminus: name = "O_uminus"; break; |
| case O_bit_not: name = "O_bit_not"; break; |
| case O_logical_not: name = "O_logical_not"; break; |
| case O_multiply: name = "O_multiply"; break; |
| case O_divide: name = "O_divide"; break; |
| case O_modulus: name = "O_modulus"; break; |
| case O_left_shift: name = "O_left_shift"; break; |
| case O_right_shift: name = "O_right_shift"; break; |
| case O_bit_inclusive_or: name = "O_bit_inclusive_or"; break; |
| case O_bit_or_not: name = "O_bit_or_not"; break; |
| case O_bit_exclusive_or: name = "O_bit_exclusive_or"; break; |
| case O_bit_and: name = "O_bit_and"; break; |
| case O_add: name = "O_add"; break; |
| case O_subtract: name = "O_subtract"; break; |
| case O_eq: name = "O_eq"; break; |
| case O_ne: name = "O_ne"; break; |
| case O_lt: name = "O_lt"; break; |
| case O_le: name = "O_le"; break; |
| case O_ge: name = "O_ge"; break; |
| case O_gt: name = "O_gt"; break; |
| case O_logical_and: name = "O_logical_and"; break; |
| case O_logical_or: name = "O_logical_or"; break; |
| case O_index: name = "O_index"; break; |
| case O_pregister: name = "O_pregister"; break; |
| case O_cpregister: name = "O_cpregister"; break; |
| case O_literal: name = "O_literal"; break; |
| case O_lituse_addr: name = "O_lituse_addr"; break; |
| case O_lituse_base: name = "O_lituse_base"; break; |
| case O_lituse_bytoff: name = "O_lituse_bytoff"; break; |
| case O_lituse_jsr: name = "O_lituse_jsr"; break; |
| case O_lituse_tlsgd: name = "O_lituse_tlsgd"; break; |
| case O_lituse_tlsldm: name = "O_lituse_tlsldm"; break; |
| case O_lituse_jsrdirect: name = "O_lituse_jsrdirect"; break; |
| case O_gpdisp: name = "O_gpdisp"; break; |
| case O_gprelhigh: name = "O_gprelhigh"; break; |
| case O_gprellow: name = "O_gprellow"; break; |
| case O_gprel: name = "O_gprel"; break; |
| case O_samegp: name = "O_samegp"; break; |
| case O_tlsgd: name = "O_tlsgd"; break; |
| case O_tlsldm: name = "O_tlsldm"; break; |
| case O_gotdtprel: name = "O_gotdtprel"; break; |
| case O_dtprelhi: name = "O_dtprelhi"; break; |
| case O_dtprello: name = "O_dtprello"; break; |
| case O_dtprel: name = "O_dtprel"; break; |
| case O_gottprel: name = "O_gottprel"; break; |
| case O_tprelhi: name = "O_tprelhi"; break; |
| case O_tprello: name = "O_tprello"; break; |
| case O_tprel: name = "O_tprel"; break; |
| } |
| |
| fprintf (stderr, ", %s(%s, %s, %d)", name, |
| (t->X_add_symbol) ? S_GET_NAME (t->X_add_symbol) : "--", |
| (t->X_op_symbol) ? S_GET_NAME (t->X_op_symbol) : "--", |
| (int) t->X_add_number); |
| } |
| fprintf (stderr, "\n"); |
| fflush (stderr); |
| } |
| #endif |
| |
| /* Parse the arguments to an opcode. */ |
| |
| static int |
| tokenize_arguments (char *str, |
| expressionS tok[], |
| int ntok) |
| { |
| expressionS *end_tok = tok + ntok; |
| char *old_input_line_pointer; |
| int saw_comma = 0, saw_arg = 0; |
| #ifdef DEBUG_ALPHA |
| expressionS *orig_tok = tok; |
| #endif |
| #ifdef RELOC_OP_P |
| char *p; |
| const struct alpha_reloc_op_tag *r; |
| int c, i; |
| size_t len; |
| int reloc_found_p = 0; |
| #endif |
| |
| memset (tok, 0, sizeof (*tok) * ntok); |
| |
| /* Save and restore input_line_pointer around this function. */ |
| old_input_line_pointer = input_line_pointer; |
| input_line_pointer = str; |
| |
| #ifdef RELOC_OP_P |
| /* ??? Wrest control of ! away from the regular expression parser. */ |
| is_end_of_line[(unsigned char) '!'] = 1; |
| #endif |
| |
| while (tok < end_tok && *input_line_pointer) |
| { |
| SKIP_WHITESPACE (); |
| switch (*input_line_pointer) |
| { |
| case '\0': |
| goto fini; |
| |
| #ifdef RELOC_OP_P |
| case '!': |
| /* A relocation operand can be placed after the normal operand on an |
| assembly language statement, and has the following form: |
| !relocation_type!sequence_number. */ |
| if (reloc_found_p) |
| { |
| /* Only support one relocation op per insn. */ |
| as_bad (_("More than one relocation op per insn")); |
| goto err_report; |
| } |
| |
| if (!saw_arg) |
| goto err; |
| |
| ++input_line_pointer; |
| SKIP_WHITESPACE (); |
| c = get_symbol_name (&p); |
| |
| /* Parse !relocation_type. */ |
| len = input_line_pointer - p; |
| if (len == 0) |
| { |
| as_bad (_("No relocation operand")); |
| goto err_report; |
| } |
| |
| r = &alpha_reloc_op[0]; |
| for (i = alpha_num_reloc_op - 1; i >= 0; i--, r++) |
| if (len == r->length && memcmp (p, r->name, len) == 0) |
| break; |
| if (i < 0) |
| { |
| as_bad (_("Unknown relocation operand: !%s"), p); |
| goto err_report; |
| } |
| |
| *input_line_pointer = c; |
| SKIP_WHITESPACE_AFTER_NAME (); |
| if (*input_line_pointer != '!') |
| { |
| if (r->require_seq) |
| { |
| as_bad (_("no sequence number after !%s"), p); |
| goto err_report; |
| } |
| |
| tok->X_add_number = 0; |
| } |
| else |
| { |
| if (! r->allow_seq) |
| { |
| as_bad (_("!%s does not use a sequence number"), p); |
| goto err_report; |
| } |
| |
| input_line_pointer++; |
| |
| /* Parse !sequence_number. */ |
| expression (tok); |
| if (tok->X_op != O_constant || tok->X_add_number <= 0) |
| { |
| as_bad (_("Bad sequence number: !%s!%s"), |
| r->name, input_line_pointer); |
| goto err_report; |
| } |
| } |
| |
| tok->X_op = r->op; |
| reloc_found_p = 1; |
| ++tok; |
| break; |
| #endif /* RELOC_OP_P */ |
| |
| case ',': |
| ++input_line_pointer; |
| if (saw_comma || !saw_arg) |
| goto err; |
| saw_comma = 1; |
| break; |
| |
| case '(': |
| { |
| char *hold = input_line_pointer++; |
| |
| /* First try for parenthesized register ... */ |
| expression (tok); |
| if (*input_line_pointer == ')' && tok->X_op == O_register) |
| { |
| tok->X_op = (saw_comma ? O_cpregister : O_pregister); |
| saw_comma = 0; |
| saw_arg = 1; |
| ++input_line_pointer; |
| ++tok; |
| break; |
| } |
| |
| /* ... then fall through to plain expression. */ |
| input_line_pointer = hold; |
| } |
| /* Fall through. */ |
| |
| default: |
| if (saw_arg && !saw_comma) |
| goto err; |
| |
| expression (tok); |
| if (tok->X_op == O_illegal || tok->X_op == O_absent) |
| goto err; |
| |
| saw_comma = 0; |
| saw_arg = 1; |
| ++tok; |
| break; |
| } |
| } |
| |
| fini: |
| if (saw_comma) |
| goto err; |
| input_line_pointer = old_input_line_pointer; |
| |
| #ifdef DEBUG_ALPHA |
| debug_exp (orig_tok, ntok - (end_tok - tok)); |
| #endif |
| #ifdef RELOC_OP_P |
| is_end_of_line[(unsigned char) '!'] = 0; |
| #endif |
| |
| return ntok - (end_tok - tok); |
| |
| err: |
| #ifdef RELOC_OP_P |
| is_end_of_line[(unsigned char) '!'] = 0; |
| #endif |
| input_line_pointer = old_input_line_pointer; |
| return TOKENIZE_ERROR; |
| |
| #ifdef RELOC_OP_P |
| err_report: |
| is_end_of_line[(unsigned char) '!'] = 0; |
| #endif |
| input_line_pointer = old_input_line_pointer; |
| return TOKENIZE_ERROR_REPORT; |
| } |
| |
| /* Search forward through all variants of an opcode looking for a |
| syntax match. */ |
| |
| static const struct alpha_opcode * |
| find_opcode_match (const struct alpha_opcode *first_opcode, |
| const expressionS *tok, |
| int *pntok, |
| int *pcpumatch) |
| { |
| const struct alpha_opcode *opcode = first_opcode; |
| int ntok = *pntok; |
| int got_cpu_match = 0; |
| |
| do |
| { |
| const unsigned char *opidx; |
| int tokidx = 0; |
| |
| /* Don't match opcodes that don't exist on this architecture. */ |
| if (!(opcode->flags & alpha_target)) |
| goto match_failed; |
| |
| got_cpu_match = 1; |
| |
| for (opidx = opcode->operands; *opidx; ++opidx) |
| { |
| const struct alpha_operand *operand = &alpha_operands[*opidx]; |
| |
| /* Only take input from real operands. */ |
| if (operand->flags & AXP_OPERAND_FAKE) |
| continue; |
| |
| /* When we expect input, make sure we have it. */ |
| if (tokidx >= ntok) |
| { |
| if ((operand->flags & AXP_OPERAND_OPTIONAL_MASK) == 0) |
| goto match_failed; |
| continue; |
| } |
| |
| /* Match operand type with expression type. */ |
| switch (operand->flags & AXP_OPERAND_TYPECHECK_MASK) |
| { |
| case AXP_OPERAND_IR: |
| if (tok[tokidx].X_op != O_register |
| || !is_ir_num (tok[tokidx].X_add_number)) |
| goto match_failed; |
| break; |
| case AXP_OPERAND_FPR: |
| if (tok[tokidx].X_op != O_register |
| || !is_fpr_num (tok[tokidx].X_add_number)) |
| goto match_failed; |
| break; |
| case AXP_OPERAND_IR | AXP_OPERAND_PARENS: |
| if (tok[tokidx].X_op != O_pregister |
| || !is_ir_num (tok[tokidx].X_add_number)) |
| goto match_failed; |
| break; |
| case AXP_OPERAND_IR | AXP_OPERAND_PARENS | AXP_OPERAND_COMMA: |
| if (tok[tokidx].X_op != O_cpregister |
| || !is_ir_num (tok[tokidx].X_add_number)) |
| goto match_failed; |
| break; |
| |
| case AXP_OPERAND_RELATIVE: |
| case AXP_OPERAND_SIGNED: |
| case AXP_OPERAND_UNSIGNED: |
| switch (tok[tokidx].X_op) |
| { |
| case O_illegal: |
| case O_absent: |
| case O_register: |
| case O_pregister: |
| case O_cpregister: |
| goto match_failed; |
| |
| default: |
| break; |
| } |
| break; |
| |
| default: |
| /* Everything else should have been fake. */ |
| abort (); |
| } |
| ++tokidx; |
| } |
| |
| /* Possible match -- did we use all of our input? */ |
| if (tokidx == ntok) |
| { |
| *pntok = ntok; |
| return opcode; |
| } |
| |
| match_failed:; |
| } |
| while (++opcode - alpha_opcodes < (int) alpha_num_opcodes |
| && !strcmp (opcode->name, first_opcode->name)); |
| |
| if (*pcpumatch) |
| *pcpumatch = got_cpu_match; |
| |
| return NULL; |
| } |
| |
| /* Given an opcode name and a pre-tokenized set of arguments, assemble |
| the insn, but do not emit it. |
| |
| Note that this implies no macros allowed, since we can't store more |
| than one insn in an insn structure. */ |
| |
| static void |
| assemble_tokens_to_insn (const char *opname, |
| const expressionS *tok, |
| int ntok, |
| struct alpha_insn *insn) |
| { |
| const struct alpha_opcode *opcode; |
| |
| /* Search opcodes. */ |
| opcode = (const struct alpha_opcode *) str_hash_find (alpha_opcode_hash, |
| opname); |
| if (opcode) |
| { |
| int cpumatch; |
| opcode = find_opcode_match (opcode, tok, &ntok, &cpumatch); |
| if (opcode) |
| { |
| assemble_insn (opcode, tok, ntok, insn, BFD_RELOC_UNUSED); |
| return; |
| } |
| else if (cpumatch) |
| as_bad (_("inappropriate arguments for opcode `%s'"), opname); |
| else |
| as_bad (_("opcode `%s' not supported for target %s"), opname, |
| alpha_target_name); |
| } |
| else |
| as_bad (_("unknown opcode `%s'"), opname); |
| } |
| |
| /* Build a BFD section with its flags set appropriately for the .lita, |
| .lit8, or .lit4 sections. */ |
| |
| static void |
| create_literal_section (const char *name, |
| segT *secp, |
| symbolS **symp) |
| { |
| segT current_section = now_seg; |
| int current_subsec = now_subseg; |
| segT new_sec; |
| |
| *secp = new_sec = subseg_new (name, 0); |
| subseg_set (current_section, current_subsec); |
| bfd_set_section_alignment (new_sec, 4); |
| bfd_set_section_flags (new_sec, (SEC_RELOC | SEC_ALLOC | SEC_LOAD |
| | SEC_READONLY | SEC_DATA)); |
| |
| S_CLEAR_EXTERNAL (*symp = section_symbol (new_sec)); |
| } |
| |
| /* Load a (partial) expression into a target register. |
| |
| If poffset is not null, after the call it will either contain |
| O_constant 0, or a 16-bit offset appropriate for any MEM format |
| instruction. In addition, pbasereg will be modified to point to |
| the base register to use in that MEM format instruction. |
| |
| In any case, *pbasereg should contain a base register to add to the |
| expression. This will normally be either AXP_REG_ZERO or |
| alpha_gp_register. Symbol addresses will always be loaded via $gp, |
| so "foo($0)" is interpreted as adding the address of foo to $0; |
| i.e. "ldq $targ, LIT($gp); addq $targ, $0, $targ". Odd, perhaps, |
| but this is what OSF/1 does. |
| |
| If explicit relocations of the form !literal!<number> are allowed, |
| and used, then explicit_reloc with be an expression pointer. |
| |
| Finally, the return value is nonzero if the calling macro may emit |
| a LITUSE reloc if otherwise appropriate; the return value is the |
| sequence number to use. */ |
| |
| static long |
| load_expression (int targreg, |
| const expressionS *exp, |
| int *pbasereg, |
| expressionS *poffset, |
| const char *opname) |
| { |
| long emit_lituse = 0; |
| offsetT addend = exp->X_add_number; |
| int basereg = *pbasereg; |
| struct alpha_insn insn; |
| expressionS newtok[3]; |
| |
| switch (exp->X_op) |
| { |
| case O_symbol: |
| { |
| #ifdef OBJ_ECOFF |
| offsetT lit; |
| |
| /* Attempt to reduce .lit load by splitting the offset from |
| its symbol when possible, but don't create a situation in |
| which we'd fail. */ |
| if (!range_signed_32 (addend) && |
| (alpha_noat_on || targreg == AXP_REG_AT)) |
| { |
| lit = add_to_literal_pool (exp->X_add_symbol, addend, |
| alpha_lita_section, 8); |
| addend = 0; |
| } |
| else |
| lit = add_to_literal_pool (exp->X_add_symbol, 0, |
| alpha_lita_section, 8); |
| |
| if (lit >= 0x8000) |
| as_fatal (_("overflow in literal (.lita) table")); |
| |
| /* Emit "ldq r, lit(gp)". */ |
| |
| if (basereg != alpha_gp_register && targreg == basereg) |
| { |
| if (alpha_noat_on) |
| as_bad (_("macro requires $at register while noat in effect")); |
| if (targreg == AXP_REG_AT) |
| as_bad (_("macro requires $at while $at in use")); |
| |
| set_tok_reg (newtok[0], AXP_REG_AT); |
| } |
| else |
| set_tok_reg (newtok[0], targreg); |
| |
| set_tok_sym (newtok[1], alpha_lita_symbol, lit); |
| set_tok_preg (newtok[2], alpha_gp_register); |
| |
| assemble_tokens_to_insn ("ldq", newtok, 3, &insn); |
| |
| gas_assert (insn.nfixups == 1); |
| insn.fixups[0].reloc = BFD_RELOC_ALPHA_LITERAL; |
| insn.sequence = emit_lituse = next_sequence_num--; |
| #endif /* OBJ_ECOFF */ |
| #ifdef OBJ_ELF |
| /* Emit "ldq r, gotoff(gp)". */ |
| |
| if (basereg != alpha_gp_register && targreg == basereg) |
| { |
| if (alpha_noat_on) |
| as_bad (_("macro requires $at register while noat in effect")); |
| if (targreg == AXP_REG_AT) |
| as_bad (_("macro requires $at while $at in use")); |
| |
| set_tok_reg (newtok[0], AXP_REG_AT); |
| } |
| else |
| set_tok_reg (newtok[0], targreg); |
| |
| /* XXX: Disable this .got minimizing optimization so that we can get |
| better instruction offset knowledge in the compiler. This happens |
| very infrequently anyway. */ |
| if (1 |
| || (!range_signed_32 (addend) |
| && (alpha_noat_on || targreg == AXP_REG_AT))) |
| { |
| newtok[1] = *exp; |
| addend = 0; |
| } |
| else |
| set_tok_sym (newtok[1], exp->X_add_symbol, 0); |
| |
| set_tok_preg (newtok[2], alpha_gp_register); |
| |
| assemble_tokens_to_insn ("ldq", newtok, 3, &insn); |
| |
| gas_assert (insn.nfixups == 1); |
| insn.fixups[0].reloc = BFD_RELOC_ALPHA_ELF_LITERAL; |
| insn.sequence = emit_lituse = next_sequence_num--; |
| #endif /* OBJ_ELF */ |
| #ifdef OBJ_EVAX |
| /* Find symbol or symbol pointer in link section. */ |
| |
| if (exp->X_add_symbol == alpha_evax_proc->symbol) |
| { |
| /* Linkage-relative expression. */ |
| set_tok_reg (newtok[0], targreg); |
| |
| if (range_signed_16 (addend)) |
| { |
| set_tok_const (newtok[1], addend); |
| addend = 0; |
| } |
| else |
| { |
| set_tok_const (newtok[1], 0); |
| } |
| set_tok_preg (newtok[2], basereg); |
| assemble_tokens_to_insn ("lda", newtok, 3, &insn); |
| } |
| else |
| { |
| const char *symname = S_GET_NAME (exp->X_add_symbol); |
| const char *ptr1, *ptr2; |
| int symlen = strlen (symname); |
| |
| if ((symlen > 4 && |
| strcmp (ptr2 = &symname [symlen - 4], "..lk") == 0)) |
| { |
| /* Access to an item whose address is stored in the linkage |
| section. Just read the address. */ |
| set_tok_reg (newtok[0], targreg); |
| |
| newtok[1] = *exp; |
| newtok[1].X_op = O_subtract; |
| newtok[1].X_op_symbol = alpha_evax_proc->symbol; |
| |
| set_tok_preg (newtok[2], basereg); |
| assemble_tokens_to_insn ("ldq", newtok, 3, &insn); |
| alpha_linkage_symbol = exp->X_add_symbol; |
| |
| if (poffset) |
| set_tok_const (*poffset, 0); |
| |
| if (alpha_flag_replace && targreg == 26) |
| { |
| /* Add a NOP fixup for 'ldX $26,YYY..NAME..lk'. */ |
| char *ensymname; |
| symbolS *ensym; |
| |
| /* Build the entry name as 'NAME..en'. */ |
| ptr1 = strstr (symname, "..") + 2; |
| if (ptr1 > ptr2) |
| ptr1 = symname; |
| ensymname = XNEWVEC (char, ptr2 - ptr1 + 5); |
| memcpy (ensymname, ptr1, ptr2 - ptr1); |
| memcpy (ensymname + (ptr2 - ptr1), "..en", 5); |
| |
| gas_assert (insn.nfixups + 1 <= MAX_INSN_FIXUPS); |
| insn.fixups[insn.nfixups].reloc = BFD_RELOC_ALPHA_NOP; |
| ensym = symbol_find_or_make (ensymname); |
| free (ensymname); |
| symbol_mark_used (ensym); |
| /* The fixup must be the same as the BFD_RELOC_ALPHA_BOH |
| case in emit_jsrjmp. See B.4.5.2 of the OpenVMS Linker |
| Utility Manual. */ |
| insn.fixups[insn.nfixups].exp.X_op = O_symbol; |
| insn.fixups[insn.nfixups].exp.X_add_symbol = ensym; |
| insn.fixups[insn.nfixups].exp.X_add_number = 0; |
| insn.fixups[insn.nfixups].xtrasym = alpha_linkage_symbol; |
| insn.fixups[insn.nfixups].procsym = alpha_evax_proc->symbol; |
| insn.nfixups++; |
| |
| /* ??? Force bsym to be instantiated now, as it will be |
| too late to do so in tc_gen_reloc. */ |
| symbol_get_bfdsym (exp->X_add_symbol); |
| } |
| else if (alpha_flag_replace && targreg == 27) |
| { |
| /* Add a lda fixup for 'ldX $27,YYY.NAME..lk+8'. */ |
| char *psymname; |
| symbolS *psym; |
| |
| /* Extract NAME. */ |
| ptr1 = strstr (symname, "..") + 2; |
| if (ptr1 > ptr2) |
| ptr1 = symname; |
| psymname = xmemdup0 (ptr1, ptr2 - ptr1); |
| |
| gas_assert (insn.nfixups + 1 <= MAX_INSN_FIXUPS); |
| insn.fixups[insn.nfixups].reloc = BFD_RELOC_ALPHA_LDA; |
| psym = symbol_find_or_make (psymname); |
| free (psymname); |
| symbol_mark_used (psym); |
| insn.fixups[insn.nfixups].exp.X_op = O_subtract; |
| insn.fixups[insn.nfixups].exp.X_add_symbol = psym; |
| insn.fixups[insn.nfixups].exp.X_op_symbol = alpha_evax_proc->symbol; |
| insn.fixups[insn.nfixups].exp.X_add_number = 0; |
| insn.fixups[insn.nfixups].xtrasym = alpha_linkage_symbol; |
| insn.fixups[insn.nfixups].procsym = alpha_evax_proc->symbol; |
| insn.nfixups++; |
| } |
| |
| emit_insn (&insn); |
| return 0; |
| } |
| else |
| { |
| /* Not in the linkage section. Put the value into the linkage |
| section. */ |
| symbolS *linkexp; |
| |
| if (!range_signed_32 (addend)) |
| addend = sign_extend_32 (addend); |
| linkexp = add_to_link_pool (exp->X_add_symbol, 0); |
| set_tok_reg (newtok[0], targreg); |
| set_tok_sym (newtok[1], linkexp, 0); |
| set_tok_preg (newtok[2], basereg); |
| assemble_tokens_to_insn ("ldq", newtok, 3, &insn); |
| } |
| } |
| #endif /* OBJ_EVAX */ |
| |
| emit_insn (&insn); |
| |
| #ifndef OBJ_EVAX |
| if (basereg != alpha_gp_register && basereg != AXP_REG_ZERO) |
| { |
| /* Emit "addq r, base, r". */ |
| |
| set_tok_reg (newtok[1], basereg); |
| set_tok_reg (newtok[2], targreg); |
| assemble_tokens ("addq", newtok, 3, 0); |
| } |
| #endif |
| basereg = targreg; |
| } |
| break; |
| |
| case O_constant: |
| break; |
| |
| case O_subtract: |
| /* Assume that this difference expression will be resolved to an |
| absolute value and that that value will fit in 16 bits. */ |
| |
| set_tok_reg (newtok[0], targreg); |
| newtok[1] = *exp; |
| set_tok_preg (newtok[2], basereg); |
| assemble_tokens (opname, newtok, 3, 0); |
| |
| if (poffset) |
| set_tok_const (*poffset, 0); |
| return 0; |
| |
| case O_big: |
| if (exp->X_add_number > 0) |
| as_bad (_("bignum invalid; zero assumed")); |
| else |
| as_bad (_("floating point number invalid; zero assumed")); |
| addend = 0; |
| break; |
| |
| default: |
| as_bad (_("can't handle expression")); |
| addend = 0; |
| break; |
| } |
| |
| if (!range_signed_32 (addend)) |
| { |
| #ifdef OBJ_EVAX |
| symbolS *litexp; |
| #else |
| offsetT lit; |
| long seq_num = next_sequence_num--; |
| #endif |
| |
| /* For 64-bit addends, just put it in the literal pool. */ |
| #ifdef OBJ_EVAX |
| /* Emit "ldq targreg, lit(basereg)". */ |
| litexp = add_to_link_pool (section_symbol (absolute_section), addend); |
| set_tok_reg (newtok[0], targreg); |
| set_tok_sym (newtok[1], litexp, 0); |
| set_tok_preg (newtok[2], alpha_gp_register); |
| assemble_tokens ("ldq", newtok, 3, 0); |
| #else |
| |
| if (alpha_lit8_section == NULL) |
| { |
| create_literal_section (".lit8", |
| &alpha_lit8_section, |
| &alpha_lit8_symbol); |
| |
| #ifdef OBJ_ECOFF |
| alpha_lit8_literal = add_to_literal_pool (alpha_lit8_symbol, 0x8000, |
| alpha_lita_section, 8); |
| if (alpha_lit8_literal >= 0x8000) |
| as_fatal (_("overflow in literal (.lita) table")); |
| #endif |
| } |
| |
| lit = add_to_literal_pool (NULL, addend, alpha_lit8_section, 8) - 0x8000; |
| if (lit >= 0x8000) |
| as_fatal (_("overflow in literal (.lit8) table")); |
| |
| /* Emit "lda litreg, .lit8+0x8000". */ |
| |
| if (targreg == basereg) |
| { |
| if (alpha_noat_on) |
| as_bad (_("macro requires $at register while noat in effect")); |
| if (targreg == AXP_REG_AT) |
| as_bad (_("macro requires $at while $at in use")); |
| |
| set_tok_reg (newtok[0], AXP_REG_AT); |
| } |
| else |
| set_tok_reg (newtok[0], targreg); |
| #ifdef OBJ_ECOFF |
| set_tok_sym (newtok[1], alpha_lita_symbol, alpha_lit8_literal); |
| #endif |
| #ifdef OBJ_ELF |
| set_tok_sym (newtok[1], alpha_lit8_symbol, 0x8000); |
| #endif |
| set_tok_preg (newtok[2], alpha_gp_register); |
| |
| assemble_tokens_to_insn ("ldq", newtok, 3, &insn); |
| |
| gas_assert (insn.nfixups == 1); |
| #ifdef OBJ_ECOFF |
| insn.fixups[0].reloc = BFD_RELOC_ALPHA_LITERAL; |
| #endif |
| #ifdef OBJ_ELF |
| insn.fixups[0].reloc = BFD_RELOC_ALPHA_ELF_LITERAL; |
| #endif |
| insn.sequence = seq_num; |
| |
| emit_insn (&insn); |
| |
| /* Emit "ldq litreg, lit(litreg)". */ |
| |
| set_tok_const (newtok[1], lit); |
| set_tok_preg (newtok[2], newtok[0].X_add_number); |
| |
| assemble_tokens_to_insn ("ldq", newtok, 3, &insn); |
| |
| gas_assert (insn.nfixups < MAX_INSN_FIXUPS); |
| insn.fixups[insn.nfixups].reloc = DUMMY_RELOC_LITUSE_BASE; |
| insn.fixups[insn.nfixups].exp.X_op = O_absent; |
| insn.nfixups++; |
| insn.sequence = seq_num; |
| emit_lituse = 0; |
| |
| emit_insn (&insn); |
| |
| /* Emit "addq litreg, base, target". */ |
| |
| if (basereg != AXP_REG_ZERO) |
| { |
| set_tok_reg (newtok[1], basereg); |
| set_tok_reg (newtok[2], targreg); |
| assemble_tokens ("addq", newtok, 3, 0); |
| } |
| #endif /* !OBJ_EVAX */ |
| |
| if (poffset) |
| set_tok_const (*poffset, 0); |
| *pbasereg = targreg; |
| } |
| else |
| { |
| offsetT low, high, extra, tmp; |
| |
| /* For 32-bit operands, break up the addend. */ |
| |
| low = sign_extend_16 (addend); |
| tmp = addend - low; |
| high = sign_extend_16 (tmp >> 16); |
| |
| if (tmp - (high << 16)) |
| { |
| extra = 0x4000; |
| tmp -= 0x40000000; |
| high = sign_extend_16 (tmp >> 16); |
| } |
| else |
| extra = 0; |
| |
| set_tok_reg (newtok[0], targreg); |
| set_tok_preg (newtok[2], basereg); |
| |
| if (extra) |
| { |
| /* Emit "ldah r, extra(r). */ |
| set_tok_const (newtok[1], extra); |
| assemble_tokens ("ldah", newtok, 3, 0); |
| set_tok_preg (newtok[2], basereg = targreg); |
| } |
| |
| if (high) |
| { |
| /* Emit "ldah r, high(r). */ |
| set_tok_const (newtok[1], high); |
| assemble_tokens ("ldah", newtok, 3, 0); |
| basereg = targreg; |
| set_tok_preg (newtok[2], basereg); |
| } |
| |
| if ((low && !poffset) || (!poffset && basereg != targreg)) |
| { |
| /* Emit "lda r, low(base)". */ |
| set_tok_const (newtok[1], low); |
| assemble_tokens ("lda", newtok, 3, 0); |
| basereg = targreg; |
| low = 0; |
| } |
| |
| if (poffset) |
| set_tok_const (*poffset, low); |
| *pbasereg = basereg; |
| } |
| |
| return emit_lituse; |
| } |
| |
| /* The lda macro differs from the lda instruction in that it handles |
| most simple expressions, particularly symbol address loads and |
| large constants. */ |
| |
| static void |
| emit_lda (const expressionS *tok, |
| int ntok, |
| const void * unused ATTRIBUTE_UNUSED) |
| { |
| int basereg; |
| |
| if (ntok == 2) |
| basereg = (tok[1].X_op == O_constant ? AXP_REG_ZERO : alpha_gp_register); |
| else |
| basereg = tok[2].X_add_number; |
| |
| (void) load_expression (tok[0].X_add_number, &tok[1], &basereg, NULL, "lda"); |
| } |
| |
| /* The ldah macro differs from the ldah instruction in that it has $31 |
| as an implied base register. */ |
| |
| static void |
| emit_ldah (const expressionS *tok, |
| int ntok ATTRIBUTE_UNUSED, |
| const void * unused ATTRIBUTE_UNUSED) |
| { |
| expressionS newtok[3]; |
| |
| newtok[0] = tok[0]; |
| newtok[1] = tok[1]; |
| set_tok_preg (newtok[2], AXP_REG_ZERO); |
| |
| assemble_tokens ("ldah", newtok, 3, 0); |
| } |
| |
| /* Called internally to handle all alignment needs. This takes care |
| of eliding calls to frag_align if'n the cached current alignment |
| says we've already got it, as well as taking care of the auto-align |
| feature wrt labels. */ |
| |
| static void |
| alpha_align (int n, |
| char *pfill, |
| symbolS *label, |
| int force ATTRIBUTE_UNUSED) |
| { |
| if (alpha_current_align >= n) |
| return; |
| |
| if (pfill == NULL) |
| { |
| if (subseg_text_p (now_seg)) |
| frag_align_code (n, 0); |
| else |
| frag_align (n, 0, 0); |
| } |
| else |
| frag_align (n, *pfill, 0); |
| |
| alpha_current_align = n; |
| |
| if (label != NULL && S_GET_SEGMENT (label) == now_seg) |
| { |
| symbol_set_frag (label, frag_now); |
| S_SET_VALUE (label, (valueT) frag_now_fix ()); |
| } |
| |
| record_alignment (now_seg, n); |
| |
| /* ??? If alpha_flag_relax && force && elf, record the requested alignment |
| in a reloc for the linker to see. */ |
| } |
| |
| /* Actually output an instruction with its fixup. */ |
| |
| static void |
| emit_insn (struct alpha_insn *insn) |
| { |
| char *f; |
| int i; |
| |
| /* Take care of alignment duties. */ |
| if (alpha_auto_align_on && alpha_current_align < 2) |
| alpha_align (2, (char *) NULL, alpha_insn_label, 0); |
| if (alpha_current_align > 2) |
| alpha_current_align = 2; |
| alpha_insn_label = NULL; |
| |
| /* Write out the instruction. */ |
| f = frag_more (4); |
| md_number_to_chars (f, insn->insn, 4); |
| |
| #ifdef OBJ_ELF |
| dwarf2_emit_insn (4); |
| #endif |
| |
| /* Apply the fixups in order. */ |
| for (i = 0; i < insn->nfixups; ++i) |
| { |
| const struct alpha_operand *operand = (const struct alpha_operand *) 0; |
| struct alpha_fixup *fixup = &insn->fixups[i]; |
| struct alpha_reloc_tag *info = NULL; |
| int size, pcrel; |
| fixS *fixP; |
| |
| /* Some fixups are only used internally and so have no howto. */ |
| if ((int) fixup->reloc < 0) |
| { |
| operand = &alpha_operands[-(int) fixup->reloc]; |
| size = 4; |
| pcrel = ((operand->flags & AXP_OPERAND_RELATIVE) != 0); |
| } |
| else if (fixup->reloc > BFD_RELOC_UNUSED |
| || fixup->reloc == BFD_RELOC_ALPHA_GPDISP_HI16 |
| || fixup->reloc == BFD_RELOC_ALPHA_GPDISP_LO16) |
| { |
| size = 2; |
| pcrel = 0; |
| } |
| else |
| { |
| reloc_howto_type *reloc_howto = |
| bfd_reloc_type_lookup (stdoutput, |
| (bfd_reloc_code_real_type) fixup->reloc); |
| gas_assert (reloc_howto); |
| |
| size = bfd_get_reloc_size (reloc_howto); |
| |
| switch (fixup->reloc) |
| { |
| #ifdef OBJ_EVAX |
| case BFD_RELOC_ALPHA_NOP: |
| case BFD_RELOC_ALPHA_BSR: |
| case BFD_RELOC_ALPHA_LDA: |
| case BFD_RELOC_ALPHA_BOH: |
| break; |
| #endif |
| default: |
| gas_assert (size >= 1 && size <= 4); |
| } |
| |
| pcrel = reloc_howto->pc_relative; |
| } |
| |
| fixP = fix_new_exp (frag_now, f - frag_now->fr_literal, size, |
| &fixup->exp, pcrel, (bfd_reloc_code_real_type) fixup->reloc); |
| |
| /* Turn off complaints that the addend is too large for some fixups, |
| and copy in the sequence number for the explicit relocations. */ |
| switch (fixup->reloc) |
| { |
| case BFD_RELOC_ALPHA_HINT: |
| case BFD_RELOC_GPREL32: |
| case BFD_RELOC_GPREL16: |
| case BFD_RELOC_ALPHA_GPREL_HI16: |
| case BFD_RELOC_ALPHA_GPREL_LO16: |
| case BFD_RELOC_ALPHA_GOTDTPREL16: |
| case BFD_RELOC_ALPHA_DTPREL_HI16: |
| case BFD_RELOC_ALPHA_DTPREL_LO16: |
| case BFD_RELOC_ALPHA_DTPREL16: |
| case BFD_RELOC_ALPHA_GOTTPREL16: |
| case BFD_RELOC_ALPHA_TPREL_HI16: |
| case BFD_RELOC_ALPHA_TPREL_LO16: |
| case BFD_RELOC_ALPHA_TPREL16: |
| fixP->fx_no_overflow = 1; |
| break; |
| |
| case BFD_RELOC_ALPHA_GPDISP_HI16: |
| fixP->fx_no_overflow = 1; |
| fixP->fx_addsy = section_symbol (now_seg); |
| fixP->fx_offset = 0; |
| |
| info = get_alpha_reloc_tag (insn->sequence); |
| if (++info->n_master > 1) |
| as_bad (_("too many ldah insns for !gpdisp!%ld"), insn->sequence); |
| if (info->segment != now_seg) |
| as_bad (_("both insns for !gpdisp!%ld must be in the same section"), |
| insn->sequence); |
| fixP->tc_fix_data.info = info; |
| break; |
| |
| case BFD_RELOC_ALPHA_GPDISP_LO16: |
| fixP->fx_no_overflow = 1; |
| |
| info = get_alpha_reloc_tag (insn->sequence); |
| if (++info->n_slaves > 1) |
| as_bad (_("too many lda insns for !gpdisp!%ld"), insn->sequence); |
| if (info->segment != now_seg) |
| as_bad (_("both insns for !gpdisp!%ld must be in the same section"), |
| insn->sequence); |
| fixP->tc_fix_data.info = info; |
| info->slaves = fixP; |
| break; |
| |
| case BFD_RELOC_ALPHA_LITERAL: |
| case BFD_RELOC_ALPHA_ELF_LITERAL: |
| fixP->fx_no_overflow = 1; |
| |
| if (insn->sequence == 0) |
| break; |
| info = get_alpha_reloc_tag (insn->sequence); |
| info->master = fixP; |
| info->n_master++; |
| if (info->segment != now_seg) |
| info->multi_section_p = 1; |
| fixP->tc_fix_data.info = info; |
| break; |
| |
| #ifdef RELOC_OP_P |
| case DUMMY_RELOC_LITUSE_ADDR: |
| fixP->fx_offset = LITUSE_ALPHA_ADDR; |
| goto do_lituse; |
| case DUMMY_RELOC_LITUSE_BASE: |
| fixP->fx_offset = LITUSE_ALPHA_BASE; |
| goto do_lituse; |
| case DUMMY_RELOC_LITUSE_BYTOFF: |
| fixP->fx_offset = LITUSE_ALPHA_BYTOFF; |
| goto do_lituse; |
| case DUMMY_RELOC_LITUSE_JSR: |
| fixP->fx_offset = LITUSE_ALPHA_JSR; |
| goto do_lituse; |
| case DUMMY_RELOC_LITUSE_TLSGD: |
| fixP->fx_offset = LITUSE_ALPHA_TLSGD; |
| goto do_lituse; |
| case DUMMY_RELOC_LITUSE_TLSLDM: |
| fixP->fx_offset = LITUSE_ALPHA_TLSLDM; |
| goto do_lituse; |
| case DUMMY_RELOC_LITUSE_JSRDIRECT: |
| fixP->fx_offset = LITUSE_ALPHA_JSRDIRECT; |
| goto do_lituse; |
| do_lituse: |
| fixP->fx_addsy = section_symbol (now_seg); |
| fixP->fx_r_type = BFD_RELOC_ALPHA_LITUSE; |
| |
| info = get_alpha_reloc_tag (insn->sequence); |
| if (fixup->reloc == DUMMY_RELOC_LITUSE_TLSGD) |
| info->saw_lu_tlsgd = 1; |
| else if (fixup->reloc == DUMMY_RELOC_LITUSE_TLSLDM) |
| info->saw_lu_tlsldm = 1; |
| if (++info->n_slaves > 1) |
| { |
| if (info->saw_lu_tlsgd) |
| as_bad (_("too many lituse insns for !lituse_tlsgd!%ld"), |
| insn->sequence); |
| else if (info->saw_lu_tlsldm) |
| as_bad (_("too many lituse insns for !lituse_tlsldm!%ld"), |
| insn->sequence); |
| } |
| fixP->tc_fix_data.info = info; |
| fixP->tc_fix_data.next_reloc = info->slaves; |
| info->slaves = fixP; |
| if (info->segment != now_seg) |
| info->multi_section_p = 1; |
| break; |
| |
| case BFD_RELOC_ALPHA_TLSGD: |
| fixP->fx_no_overflow = 1; |
| |
| if (insn->sequence == 0) |
| break; |
| info = get_alpha_reloc_tag (insn->sequence); |
| if (info->saw_tlsgd) |
| as_bad (_("duplicate !tlsgd!%ld"), insn->sequence); |
| else if (info->saw_tlsldm) |
| as_bad (_("sequence number in use for !tlsldm!%ld"), |
| insn->sequence); |
| else |
| info->saw_tlsgd = 1; |
| fixP->tc_fix_data.info = info; |
| break; |
| |
| case BFD_RELOC_ALPHA_TLSLDM: |
| fixP->fx_no_overflow = 1; |
| |
| if (insn->sequence == 0) |
| break; |
| info = get_alpha_reloc_tag (insn->sequence); |
| if (info->saw_tlsldm) |
| as_bad (_("duplicate !tlsldm!%ld"), insn->sequence); |
| else if (info->saw_tlsgd) |
| as_bad (_("sequence number in use for !tlsgd!%ld"), |
| insn->sequence); |
| else |
| info->saw_tlsldm = 1; |
| fixP->tc_fix_data.info = info; |
| break; |
| #endif |
| #ifdef OBJ_EVAX |
| case BFD_RELOC_ALPHA_NOP: |
| case BFD_RELOC_ALPHA_LDA: |
| case BFD_RELOC_ALPHA_BSR: |
| case BFD_RELOC_ALPHA_BOH: |
| info = get_alpha_reloc_tag (next_sequence_num--); |
| fixP->tc_fix_data.info = info; |
| fixP->tc_fix_data.info->sym = fixup->xtrasym; |
| fixP->tc_fix_data.info->psym = fixup->procsym; |
| break; |
| #endif |
| |
| default: |
| if ((int) fixup->reloc < 0) |
| { |
| if (operand->flags & AXP_OPERAND_NOOVERFLOW) |
| fixP->fx_no_overflow = 1; |
| } |
| break; |
| } |
| } |
| } |
| |
| /* Insert an operand value into an instruction. */ |
| |
| static unsigned |
| insert_operand (unsigned insn, |
| const struct alpha_operand *operand, |
| offsetT val, |
| const char *file, |
| unsigned line) |
| { |
| if (!(operand->flags & AXP_OPERAND_NOOVERFLOW)) |
| { |
| offsetT min, max; |
| |
| if (operand->flags & AXP_OPERAND_SIGNED) |
| { |
| max = (1 << (operand->bits - 1)) - 1; |
| min = -(1 << (operand->bits - 1)); |
| } |
| else |
| { |
| max = (1 << operand->bits) - 1; |
| min = 0; |
| } |
| |
| if (val < min || val > max) |
| as_bad_value_out_of_range (_("operand"), val, min, max, file, line); |
| } |
| |
| if (operand->insert) |
| { |
| const char *errmsg = NULL; |
| |
| insn = (*operand->insert) (insn, val, &errmsg); |
| if (errmsg) |
| as_warn ("%s", errmsg); |
| } |
| else |
| insn |= ((val & ((1 << operand->bits) - 1)) << operand->shift); |
| |
| return insn; |
| } |
| |
| /* Turn an opcode description and a set of arguments into |
| an instruction and a fixup. */ |
| |
| static void |
| assemble_insn (const struct alpha_opcode *opcode, |
| const expressionS *tok, |
| int ntok, |
| struct alpha_insn *insn, |
| extended_bfd_reloc_code_real_type reloc) |
| { |
| const struct alpha_operand *reloc_operand = NULL; |
| const expressionS *reloc_exp = NULL; |
| const unsigned char *argidx; |
| unsigned image; |
| int tokidx = 0; |
| |
| memset (insn, 0, sizeof (*insn)); |
| image = opcode->opcode; |
| |
| for (argidx = opcode->operands; *argidx; ++argidx) |
| { |
| const struct alpha_operand *operand = &alpha_operands[*argidx]; |
| const expressionS *t = (const expressionS *) 0; |
| |
| if (operand->flags & AXP_OPERAND_FAKE) |
| { |
| /* Fake operands take no value and generate no fixup. */ |
| image = insert_operand (image, operand, 0, NULL, 0); |
| continue; |
| } |
| |
| if (tokidx >= ntok) |
| { |
| switch (operand->flags & AXP_OPERAND_OPTIONAL_MASK) |
| { |
| case AXP_OPERAND_DEFAULT_FIRST: |
| t = &tok[0]; |
| break; |
| case AXP_OPERAND_DEFAULT_SECOND: |
| t = &tok[1]; |
| break; |
| case AXP_OPERAND_DEFAULT_ZERO: |
| { |
| static expressionS zero_exp; |
| t = &zero_exp; |
| zero_exp.X_op = O_constant; |
| zero_exp.X_unsigned = 1; |
| } |
| break; |
| default: |
| abort (); |
| } |
| } |
| else |
| t = &tok[tokidx++]; |
| |
| switch (t->X_op) |
| { |
| case O_register: |
| case O_pregister: |
| case O_cpregister: |
| image = insert_operand (image, operand, regno (t->X_add_number), |
| NULL, 0); |
| break; |
| |
| case O_constant: |
| image = insert_operand (image, operand, t->X_add_number, NULL, 0); |
| gas_assert (reloc_operand == NULL); |
| reloc_operand = operand; |
| reloc_exp = t; |
| break; |
| |
| default: |
| /* This is only 0 for fields that should contain registers, |
| which means this pattern shouldn't have matched. */ |
| if (operand->default_reloc == 0) |
| abort (); |
| |
| /* There is one special case for which an insn receives two |
| relocations, and thus the user-supplied reloc does not |
| override the operand reloc. */ |
| if (operand->default_reloc == BFD_RELOC_ALPHA_HINT) |
| { |
| struct alpha_fixup *fixup; |
| |
| if (insn->nfixups >= MAX_INSN_FIXUPS) |
| as_fatal (_("too many fixups")); |
| |
| fixup = &insn->fixups[insn->nfixups++]; |
| fixup->exp = *t; |
| fixup->reloc = BFD_RELOC_ALPHA_HINT; |
| } |
| else |
| { |
| if (reloc == BFD_RELOC_UNUSED) |
| reloc = operand->default_reloc; |
| |
| gas_assert (reloc_operand == NULL); |
| reloc_operand = operand; |
| reloc_exp = t; |
| } |
| break; |
| } |
| } |
| |
| if (reloc != BFD_RELOC_UNUSED) |
| { |
| struct alpha_fixup *fixup; |
| |
| if (insn->nfixups >= MAX_INSN_FIXUPS) |
| as_fatal (_("too many fixups")); |
| |
| /* ??? My but this is hacky. But the OSF/1 assembler uses the same |
| relocation tag for both ldah and lda with gpdisp. Choose the |
| correct internal relocation based on the opcode. */ |
| if (reloc == BFD_RELOC_ALPHA_GPDISP) |
| { |
| if (strcmp (opcode->name, "ldah") == 0) |
| reloc = BFD_RELOC_ALPHA_GPDISP_HI16; |
| else if (strcmp (opcode->name, "lda") == 0) |
| reloc = BFD_RELOC_ALPHA_GPDISP_LO16; |
| else |
| as_bad (_("invalid relocation for instruction")); |
| } |
| |
| /* If this is a real relocation (as opposed to a lituse hint), then |
| the relocation width should match the operand width. |
| Take care of -MDISP in operand table. */ |
| else if (reloc < BFD_RELOC_UNUSED && reloc > 0) |
| { |
| reloc_howto_type *reloc_howto |
| = bfd_reloc_type_lookup (stdoutput, |
| (bfd_reloc_code_real_type) reloc); |
| if (reloc_operand == NULL |
| || reloc_howto->bitsize != reloc_operand->bits) |
| { |
| as_bad (_("invalid relocation for field")); |
| return; |
| } |
| } |
| |
| fixup = &insn->fixups[insn->nfixups++]; |
| if (reloc_exp) |
| fixup->exp = *reloc_exp; |
| else |
| fixup->exp.X_op = O_absent; |
| fixup->reloc = reloc; |
| } |
| |
| insn->insn = image; |
| } |
| |
| /* Handle all "simple" integer register loads -- ldq, ldq_l, ldq_u, |
| etc. They differ from the real instructions in that they do simple |
| expressions like the lda macro. */ |
| |
| static void |
| emit_ir_load (const expressionS *tok, |
| int ntok, |
| const void * opname) |
| { |
| int basereg; |
| long lituse; |
| expressionS newtok[3]; |
| struct alpha_insn insn; |
| const char *symname |
| = tok[1].X_add_symbol ? S_GET_NAME (tok[1].X_add_symbol): ""; |
| int symlen = strlen (symname); |
| |
| if (ntok == 2) |
| basereg = (tok[1].X_op == O_constant ? AXP_REG_ZERO : alpha_gp_register); |
| else |
| basereg = tok[2].X_add_number; |
| |
| lituse = load_expression (tok[0].X_add_number, &tok[1], |
| &basereg, &newtok[1], (const char *) opname); |
| |
| if (basereg == alpha_gp_register && |
| (symlen > 4 && strcmp (&symname [symlen - 4], "..lk") == 0)) |
| return; |
| |
| newtok[0] = tok[0]; |
| set_tok_preg (newtok[2], basereg); |
| |
| assemble_tokens_to_insn ((const char *) opname, newtok, 3, &insn); |
| |
| if (lituse) |
| { |
| gas_assert (insn.nfixups < MAX_INSN_FIXUPS); |
| insn.fixups[insn.nfixups].reloc = DUMMY_RELOC_LITUSE_BASE; |
| insn.fixups[insn.nfixups].exp.X_op = O_absent; |
| insn.nfixups++; |
| insn.sequence = lituse; |
| } |
| |
| emit_insn (&insn); |
| } |
| |
| /* Handle fp register loads, and both integer and fp register stores. |
| Again, we handle simple expressions. */ |
| |
| static void |
| emit_loadstore (const expressionS *tok, |
| int ntok, |
| const void * opname) |
| { |
| int basereg; |
| long lituse; |
| expressionS newtok[3]; |
| struct alpha_insn insn; |
| |
| if (ntok == 2) |
| basereg = (tok[1].X_op == O_constant ? AXP_REG_ZERO : alpha_gp_register); |
| else |
| basereg = tok[2].X_add_number; |
| |
| if (tok[1].X_op != O_constant || !range_signed_16 (tok[1].X_add_number)) |
| { |
| if (alpha_noat_on) |
| as_bad (_("macro requires $at register while noat in effect")); |
| |
| lituse = load_expression (AXP_REG_AT, &tok[1], |
| &basereg, &newtok[1], (const char *) opname); |
| } |
| else |
| { |
| newtok[1] = tok[1]; |
| lituse = 0; |
| } |
| |
| newtok[0] = tok[0]; |
| set_tok_preg (newtok[2], basereg); |
| |
| assemble_tokens_to_insn ((const char *) opname, newtok, 3, &insn); |
| |
| if (lituse) |
| { |
| gas_assert (insn.nfixups < MAX_INSN_FIXUPS); |
| insn.fixups[insn.nfixups].reloc = DUMMY_RELOC_LITUSE_BASE; |
| insn.fixups[insn.nfixups].exp.X_op = O_absent; |
| insn.nfixups++; |
| insn.sequence = lituse; |
| } |
| |
| emit_insn (&insn); |
| } |
| |
| /* Load a half-word or byte as an unsigned value. */ |
| |
| static void |
| emit_ldXu (const expressionS *tok, |
| int ntok, |
| const void * vlgsize) |
| { |
| if (alpha_target & AXP_OPCODE_BWX) |
| emit_ir_load (tok, ntok, ldXu_op[(long) vlgsize]); |
| else |
| { |
| expressionS newtok[3]; |
| struct alpha_insn insn; |
| int basereg; |
| long lituse; |
| |
| if (alpha_noat_on) |
| as_bad (_("macro requires $at register while noat in effect")); |
| |
| if (ntok == 2) |
| basereg = (tok[1].X_op == O_constant |
| ? AXP_REG_ZERO : alpha_gp_register); |
| else |
| basereg = tok[2].X_add_number; |
| |
| /* Emit "lda $at, exp". */ |
| lituse = load_expression (AXP_REG_AT, &tok[1], &basereg, NULL, "lda"); |
| |
| /* Emit "ldq_u targ, 0($at)". */ |
| newtok[0] = tok[0]; |
| set_tok_const (newtok[1], 0); |
| set_tok_preg (newtok[2], basereg); |
| assemble_tokens_to_insn ("ldq_u", newtok, 3, &insn); |
| |
| if (lituse) |
| { |
| gas_assert (insn.nfixups < MAX_INSN_FIXUPS); |
| insn.fixups[insn.nfixups].reloc = DUMMY_RELOC_LITUSE_BASE; |
| insn.fixups[insn.nfixups].exp.X_op = O_absent; |
| insn.nfixups++; |
| insn.sequence = lituse; |
| } |
| |
| emit_insn (&insn); |
| |
| /* Emit "extXl targ, $at, targ". */ |
| set_tok_reg (newtok[1], basereg); |
| newtok[2] = newtok[0]; |
| assemble_tokens_to_insn (extXl_op[(long) vlgsize], newtok, 3, &insn); |
| |
| if (lituse) |
| { |
| gas_assert (insn.nfixups < MAX_INSN_FIXUPS); |
| insn.fixups[insn.nfixups].reloc = DUMMY_RELOC_LITUSE_BYTOFF; |
| insn.fixups[insn.nfixups].exp.X_op = O_absent; |
| insn.nfixups++; |
| insn.sequence = lituse; |
| } |
| |
| emit_insn (&insn); |
| } |
| } |
| |
| /* Load a half-word or byte as a signed value. */ |
| |
| static void |
| emit_ldX (const expressionS *tok, |
| int ntok, |
| const void * vlgsize) |
| { |
| emit_ldXu (tok, ntok, vlgsize); |
| assemble_tokens (sextX_op[(long) vlgsize], tok, 1, 1); |
| } |
| |
| /* Load an integral value from an unaligned address as an unsigned |
| value. */ |
| |
| static void |
| emit_uldXu (const expressionS *tok, |
| int ntok, |
| const void * vlgsize) |
| { |
| long lgsize = (long) vlgsize; |
| expressionS newtok[3]; |
| |
| if (alpha_noat_on) |
| as_bad (_("macro requires $at register while noat in effect")); |
| |
| /* Emit "lda $at, exp". */ |
| memcpy (newtok, tok, sizeof (expressionS) * ntok); |
| newtok[0].X_add_number = AXP_REG_AT; |
| assemble_tokens ("lda", newtok, ntok, 1); |
| |
| /* Emit "ldq_u $t9, 0($at)". */ |
| set_tok_reg (newtok[0], AXP_REG_T9); |
| set_tok_const (newtok[1], 0); |
| set_tok_preg (newtok[2], AXP_REG_AT); |
| assemble_tokens ("ldq_u", newtok, 3, 1); |
| |
| /* Emit "ldq_u $t10, size-1($at)". */ |
| set_tok_reg (newtok[0], AXP_REG_T10); |
| set_tok_const (newtok[1], (1 << lgsize) - 1); |
| assemble_tokens ("ldq_u", newtok, 3, 1); |
| |
| /* Emit "extXl $t9, $at, $t9". */ |
| set_tok_reg (newtok[0], AXP_REG_T9); |
| set_tok_reg (newtok[1], AXP_REG_AT); |
| set_tok_reg (newtok[2], AXP_REG_T9); |
| assemble_tokens (extXl_op[lgsize], newtok, 3, 1); |
| |
| /* Emit "extXh $t10, $at, $t10". */ |
| set_tok_reg (newtok[0], AXP_REG_T10); |
| set_tok_reg (newtok[2], AXP_REG_T10); |
| assemble_tokens (extXh_op[lgsize], newtok, 3, 1); |
| |
| /* Emit "or $t9, $t10, targ". */ |
| set_tok_reg (newtok[0], AXP_REG_T9); |
| set_tok_reg (newtok[1], AXP_REG_T10); |
| newtok[2] = tok[0]; |
| assemble_tokens ("or", newtok, 3, 1); |
| } |
| |
| /* Load an integral value from an unaligned address as a signed value. |
| Note that quads should get funneled to the unsigned load since we |
| don't have to do the sign extension. */ |
| |
| static void |
| emit_uldX (const expressionS *tok, |
| int ntok, |
| const void * vlgsize) |
| { |
| emit_uldXu (tok, ntok, vlgsize); |
| assemble_tokens (sextX_op[(long) vlgsize], tok, 1, 1); |
| } |
| |
| /* Implement the ldil macro. */ |
| |
| static void |
| emit_ldil (const expressionS *tok, |
| int ntok, |
| const void * unused ATTRIBUTE_UNUSED) |
| { |
| expressionS newtok[2]; |
| |
| memcpy (newtok, tok, sizeof (newtok)); |
| newtok[1].X_add_number = sign_extend_32 (tok[1].X_add_number); |
| |
| assemble_tokens ("lda", newtok, ntok, 1); |
| } |
| |
| /* Store a half-word or byte. */ |
| |
| static void |
| emit_stX (const expressionS *tok, |
| int ntok, |
| const void * vlgsize) |
| { |
| int lgsize = (int) (long) vlgsize; |
| |
| if (alpha_target & AXP_OPCODE_BWX) |
| emit_loadstore (tok, ntok, stX_op[lgsize]); |
| else |
| { |
| expressionS newtok[3]; |
| struct alpha_insn insn; |
| int basereg; |
| long lituse; |
| |
| if (alpha_noat_on) |
| as_bad (_("macro requires $at register while noat in effect")); |
| |
| if (ntok == 2) |
| basereg = (tok[1].X_op == O_constant |
| ? AXP_REG_ZERO : alpha_gp_register); |
| else |
| basereg = tok[2].X_add_number; |
| |
| /* Emit "lda $at, exp". */ |
| lituse = load_expression (AXP_REG_AT, &tok[1], &basereg, NULL, "lda"); |
| |
| /* Emit "ldq_u $t9, 0($at)". */ |
| set_tok_reg (newtok[0], AXP_REG_T9); |
| set_tok_const (newtok[1], 0); |
| set_tok_preg (newtok[2], basereg); |
| assemble_tokens_to_insn ("ldq_u", newtok, 3, &insn); |
| |
| if (lituse) |
| { |
| gas_assert (insn.nfixups < MAX_INSN_FIXUPS); |
| insn.fixups[insn.nfixups].reloc = DUMMY_RELOC_LITUSE_BASE; |
| insn.fixups[insn.nfixups].exp.X_op = O_absent; |
| insn.nfixups++; |
| insn.sequence = lituse; |
| } |
| |
| emit_insn (&insn); |
| |
| /* Emit "insXl src, $at, $t10". */ |
| newtok[0] = tok[0]; |
| set_tok_reg (newtok[1], basereg); |
| set_tok_reg (newtok[2], AXP_REG_T10); |
| assemble_tokens_to_insn (insXl_op[lgsize], newtok, 3, &insn); |
| |
| if (lituse) |
| { |
| gas_assert (insn.nfixups < MAX_INSN_FIXUPS); |
| insn.fixups[insn.nfixups].reloc = DUMMY_RELOC_LITUSE_BYTOFF; |
| insn.fixups[insn.nfixups].exp.X_op = O_absent; |
| insn.nfixups++; |
| insn.sequence = lituse; |
| } |
| |
| emit_insn (&insn); |
| |
| /* Emit "mskXl $t9, $at, $t9". */ |
| set_tok_reg (newtok[0], AXP_REG_T9); |
| newtok[2] = newtok[0]; |
| assemble_tokens_to_insn (mskXl_op[lgsize], newtok, 3, &insn); |
| |
| if (lituse) |
| { |
| gas_assert (insn.nfixups < MAX_INSN_FIXUPS); |
| insn.fixups[insn.nfixups].reloc = DUMMY_RELOC_LITUSE_BYTOFF; |
| insn.fixups[insn.nfixups].exp.X_op = O_absent; |
| insn.nfixups++; |
| insn.sequence = lituse; |
| } |
| |
| emit_insn (&insn); |
| |
| /* Emit "or $t9, $t10, $t9". */ |
| set_tok_reg (newtok[1], AXP_REG_T10); |
| assemble_tokens ("or", newtok, 3, 1); |
| |
| /* Emit "stq_u $t9, 0($at). */ |
| set_tok_const(newtok[1], 0); |
| set_tok_preg (newtok[2], AXP_REG_AT); |
| assemble_tokens_to_insn ("stq_u", newtok, 3, &insn); |
| |
| if (lituse) |
| { |
| gas_assert (insn.nfixups < MAX_INSN_FIXUPS); |
| insn.fixups[insn.nfixups].reloc = DUMMY_RELOC_LITUSE_BASE; |
| insn.fixups[insn.nfixups].exp.X_op = O_absent; |
| insn.nfixups++; |
| insn.sequence = lituse; |
| } |
| |
| emit_insn (&insn); |
| } |
| } |
| |
| /* Store an integer to an unaligned address. */ |
| |
| static void |
| emit_ustX (const expressionS *tok, |
| int ntok, |
| const void * vlgsize) |
| { |
| int lgsize = (int) (long) vlgsize; |
| expressionS newtok[3]; |
| |
| /* Emit "lda $at, exp". */ |
| memcpy (newtok, tok, sizeof (expressionS) * ntok); |
| newtok[0].X_add_number = AXP_REG_AT; |
| assemble_tokens ("lda", newtok, ntok, 1); |
| |
| /* Emit "ldq_u $9, 0($at)". */ |
| set_tok_reg (newtok[0], AXP_REG_T9); |
| set_tok_const (newtok[1], 0); |
| set_tok_preg (newtok[2], AXP_REG_AT); |
| assemble_tokens ("ldq_u", newtok, 3, 1); |
| |
| /* Emit "ldq_u $10, size-1($at)". */ |
| set_tok_reg (newtok[0], AXP_REG_T10); |
| set_tok_const (newtok[1], (1 << lgsize) - 1); |
| assemble_tokens ("ldq_u", newtok, 3, 1); |
| |
| /* Emit "insXl src, $at, $t11". */ |
| newtok[0] = tok[0]; |
| set_tok_reg (newtok[1], AXP_REG_AT); |
| set_tok_reg (newtok[2], AXP_REG_T11); |
| assemble_tokens (insXl_op[lgsize], newtok, 3, 1); |
| |
| /* Emit "insXh src, $at, $t12". */ |
| set_tok_reg (newtok[2], AXP_REG_T12); |
| assemble_tokens (insXh_op[lgsize], newtok, 3, 1); |
| |
| /* Emit "mskXl $t9, $at, $t9". */ |
| set_tok_reg (newtok[0], AXP_REG_T9); |
| newtok[2] = newtok[0]; |
| assemble_tokens (mskXl_op[lgsize], newtok, 3, 1); |
| |
| /* Emit "mskXh $t10, $at, $t10". */ |
| set_tok_reg (newtok[0], AXP_REG_T10); |
| newtok[2] = newtok[0]; |
| assemble_tokens (mskXh_op[lgsize], newtok, 3, 1); |
| |
| /* Emit "or $t9, $t11, $t9". */ |
| set_tok_reg (newtok[0], AXP_REG_T9); |
| set_tok_reg (newtok[1], AXP_REG_T11); |
| newtok[2] = newtok[0]; |
| assemble_tokens ("or", newtok, 3, 1); |
| |
| /* Emit "or $t10, $t12, $t10". */ |
| set_tok_reg (newtok[0], AXP_REG_T10); |
| set_tok_reg (newtok[1], AXP_REG_T12); |
| newtok[2] = newtok[0]; |
| assemble_tokens ("or", newtok, 3, 1); |
| |
| /* Emit "stq_u $t10, size-1($at)". */ |
| set_tok_reg (newtok[0], AXP_REG_T10); |
| set_tok_const (newtok[1], (1 << lgsize) - 1); |
| set_tok_preg (newtok[2], AXP_REG_AT); |
| assemble_tokens ("stq_u", newtok, 3, 1); |
| |
| /* Emit "stq_u $t9, 0($at)". */ |
| set_tok_reg (newtok[0], AXP_REG_T9); |
| set_tok_const (newtok[1], 0); |
| assemble_tokens ("stq_u", newtok, 3, 1); |
| } |
| |
| /* Sign extend a half-word or byte. The 32-bit sign extend is |
| implemented as "addl $31, $r, $t" in the opcode table. */ |
| |
| static void |
| emit_sextX (const expressionS *tok, |
| int ntok, |
| const void * vlgsize) |
| { |
| long lgsize = (long) vlgsize; |
| |
| if (alpha_target & AXP_OPCODE_BWX) |
| assemble_tokens (sextX_op[lgsize], tok, ntok, 0); |
| else |
| { |
| int bitshift = 64 - 8 * (1 << lgsize); |
| expressionS newtok[3]; |
| |
| /* Emit "sll src,bits,dst". */ |
| newtok[0] = tok[0]; |
| set_tok_const (newtok[1], bitshift); |
| newtok[2] = tok[ntok - 1]; |
| assemble_tokens ("sll", newtok, 3, 1); |
| |
| /* Emit "sra dst,bits,dst". */ |
| newtok[0] = newtok[2]; |
| assemble_tokens ("sra", newtok, 3, 1); |
| } |
| } |
| |
| /* Implement the division and modulus macros. */ |
| |
| #ifdef OBJ_EVAX |
| |
| /* Make register usage like in normal procedure call. |
| Don't clobber PV and RA. */ |
| |
| static void |
| emit_division (const expressionS *tok, |
| int ntok, |
| const void * symname) |
| { |
| /* DIVISION and MODULUS. Yech. |
| |
| Convert |
| OP x,y,result |
| to |
| mov x,R16 # if x != R16 |
| mov y,R17 # if y != R17 |
| lda AT,__OP |
| jsr AT,(AT),0 |
| mov R0,result |
| |
| with appropriate optimizations if R0,R16,R17 are the registers |
| specified by the compiler. */ |
| |
| int xr, yr, rr; |
| symbolS *sym; |
| expressionS newtok[3]; |
| |
| xr = regno (tok[0].X_add_number); |
| yr = regno (tok[1].X_add_number); |
| |
| if (ntok < 3) |
| rr = xr; |
| else |
| rr = regno (tok[2].X_add_number); |
| |
| /* Move the operands into the right place. */ |
| if (yr == AXP_REG_R16 && xr == AXP_REG_R17) |
| { |
| /* They are in exactly the wrong order -- swap through AT. */ |
| if (alpha_noat_on) |
| as_bad (_("macro requires $at register while noat in effect")); |
| |
| set_tok_reg (newtok[0], AXP_REG_R16); |
| set_tok_reg (newtok[1], AXP_REG_AT); |
| assemble_tokens ("mov", newtok, 2, 1); |
| |
| set_tok_reg (newtok[0], AXP_REG_R17); |
| set_tok_reg (newtok[1], AXP_REG_R16); |
| assemble_tokens ("mov", newtok, 2, 1); |
| |
| set_tok_reg (newtok[0], AXP_REG_AT); |
| set_tok_reg (newtok[1], AXP_REG_R17); |
| assemble_tokens ("mov", newtok, 2, 1); |
| } |
| else |
| { |
| if (yr == AXP_REG_R16) |
| { |
| set_tok_reg (newtok[0], AXP_REG_R16); |
| set_tok_reg (newtok[1], AXP_REG_R17); |
| assemble_tokens ("mov", newtok, 2, 1); |
| } |
| |
| if (xr != AXP_REG_R16) |
| { |
| set_tok_reg (newtok[0], xr); |
| set_tok_reg (newtok[1], AXP_REG_R16); |
| assemble_tokens ("mov", newtok, 2, 1); |
| } |
| |
| if (yr != AXP_REG_R16 && yr != AXP_REG_R17) |
| { |
| set_tok_reg (newtok[0], yr); |
| set_tok_reg (newtok[1], AXP_REG_R17); |
| assemble_tokens ("mov", newtok, 2, 1); |
| } |
| } |
| |
| sym = symbol_find_or_make ((const char *) symname); |
| |
| set_tok_reg (newtok[0], AXP_REG_AT); |
| set_tok_sym (newtok[1], sym, 0); |
| assemble_tokens ("lda", newtok, 2, 1); |
| |
| /* Call the division routine. */ |
| set_tok_reg (newtok[0], AXP_REG_AT); |
| set_tok_cpreg (newtok[1], AXP_REG_AT); |
| set_tok_const (newtok[2], 0); |
| assemble_tokens ("jsr", newtok, 3, 1); |
| |
| /* Move the result to the right place. */ |
| if (rr != AXP_REG_R0) |
| { |
| set_tok_reg (newtok[0], AXP_REG_R0); |
| set_tok_reg (newtok[1], rr); |
| assemble_tokens ("mov", newtok, 2, 1); |
| } |
| } |
| |
| #else /* !OBJ_EVAX */ |
| |
| static void |
| emit_division (const expressionS *tok, |
| int ntok, |
| const void * symname) |
| { |
| /* DIVISION and MODULUS. Yech. |
| Convert |
| OP x,y,result |
| to |
| lda pv,__OP |
| mov x,t10 |
| mov y,t11 |
| jsr t9,(pv),__OP |
| mov t12,result |
| |
| with appropriate optimizations if t10,t11,t12 are the registers |
| specified by the compiler. */ |
| |
| int xr, yr, rr; |
| symbolS *sym; |
| expressionS newtok[3]; |
| |
| xr = regno (tok[0].X_add_number); |
| yr = regno (tok[1].X_add_number); |
| |
| if (ntok < 3) |
| rr = xr; |
| else |
| rr = regno (tok[2].X_add_number); |
| |
| sym = symbol_find_or_make ((const char *) symname); |
| |
| /* Move the operands into the right place. */ |
| if (yr == AXP_REG_T10 && xr == AXP_REG_T11) |
| { |
| /* They are in exactly the wrong order -- swap through AT. */ |
| if (alpha_noat_on) |
| as_bad (_("macro requires $at register while noat in effect")); |
| |
| set_tok_reg (newtok[0], AXP_REG_T10); |
| set_tok_reg (newtok[1], AXP_REG_AT); |
| assemble_tokens ("mov", newtok, 2, 1); |
| |
| set_tok_reg (newtok[0], AXP_REG_T11); |
| set_tok_reg (newtok[1], AXP_REG_T10); |
| assemble_tokens ("mov", newtok, 2, 1); |
| |
| set_tok_reg (newtok[0], AXP_REG_AT); |
| set_tok_reg (newtok[1], AXP_REG_T11); |
| assemble_tokens ("mov", newtok, 2, 1); |
| } |
| else |
| { |
| if (yr == AXP_REG_T10) |
| { |
| set_tok_reg (newtok[0], AXP_REG_T10); |
| set_tok_reg (newtok[1], AXP_REG_T11); |
| assemble_tokens ("mov", newtok, 2, 1); |
| } |
| |
| if (xr != AXP_REG_T10) |
| { |
| set_tok_reg (newtok[0], xr); |
| set_tok_reg (newtok[1], AXP_REG_T10); |
| assemble_tokens ("mov", newtok, 2, 1); |
| } |
| |
| if (yr != AXP_REG_T10 && yr != AXP_REG_T11) |
| { |
| set_tok_reg (newtok[0], yr); |
| set_tok_reg (newtok[1], AXP_REG_T11); |
| assemble_tokens ("mov", newtok, 2, 1); |
| } |
| } |
| |
| /* Call the division routine. */ |
| set_tok_reg (newtok[0], AXP_REG_T9); |
| set_tok_sym (newtok[1], sym, 0); |
| assemble_tokens ("jsr", newtok, 2, 1); |
| |
| /* Reload the GP register. */ |
| #ifdef OBJ_AOUT |
| FIXME |
| #endif |
| #if defined(OBJ_ECOFF) || defined(OBJ_ELF) |
| set_tok_reg (newtok[0], alpha_gp_register); |
| set_tok_const (newtok[1], 0); |
| set_tok_preg (newtok[2], AXP_REG_T9); |
| assemble_tokens ("ldgp", newtok, 3, 1); |
| #endif |
| |
| /* Move the result to the right place. */ |
| if (rr != AXP_REG_T12) |
| { |
| set_tok_reg (newtok[0], AXP_REG_T12); |
| set_tok_reg (newtok[1], rr); |
| assemble_tokens ("mov", newtok, 2, 1); |
| } |
| } |
| |
| #endif /* !OBJ_EVAX */ |
| |
| /* The jsr and jmp macros differ from their instruction counterparts |
| in that they can load the target address and default most |
| everything. */ |
| |
| static void |
| emit_jsrjmp (const expressionS *tok, |
| int ntok, |
| const void * vopname) |
| { |
| const char *opname = (const char *) vopname; |
| struct alpha_insn insn; |
| expressionS newtok[3]; |
| int r, tokidx = 0; |
| long lituse = 0; |
| |
| if (tokidx < ntok && tok[tokidx].X_op == O_register) |
| r = regno (tok[tokidx++].X_add_number); |
| else |
| r = strcmp (opname, "jmp") == 0 ? AXP_REG_ZERO : AXP_REG_RA; |
| |
| set_tok_reg (newtok[0], r); |
| |
| if (tokidx < ntok && |
| (tok[tokidx].X_op == O_pregister || tok[tokidx].X_op == O_cpregister)) |
| r = regno (tok[tokidx++].X_add_number); |
| #ifdef OBJ_EVAX |
| /* Keep register if jsr $n.<sym>. */ |
| #else |
| else |
| { |
| int basereg = alpha_gp_register; |
| lituse = load_expression (r = AXP_REG_PV, &tok[tokidx], |
| &basereg, NULL, opname); |
| } |
| #endif |
| |
| set_tok_cpreg (newtok[1], r); |
| |
| #ifndef OBJ_EVAX |
| if (tokidx < ntok) |
| newtok[2] = tok[tokidx]; |
| else |
| #endif |
| set_tok_const (newtok[2], 0); |
| |
| assemble_tokens_to_insn (opname, newtok, 3, &insn); |
| |
| if (lituse) |
| { |
| gas_assert (insn.nfixups < MAX_INSN_FIXUPS); |
| insn.fixups[insn.nfixups].reloc = DUMMY_RELOC_LITUSE_JSR; |
| insn.fixups[insn.nfixups].exp.X_op = O_absent; |
| insn.nfixups++; |
| insn.sequence = lituse; |
| } |
| |
| #ifdef OBJ_EVAX |
| if (alpha_flag_replace |
| && r == AXP_REG_RA |
| && tok[tokidx].X_add_symbol |
| && alpha_linkage_symbol) |
| { |
| /* Create a BOH reloc for 'jsr $27,NAME'. */ |
| const char *symname = S_GET_NAME (tok[tokidx].X_add_symbol); |
| int symlen = strlen (symname); |
| char *ensymname; |
| |
| /* Build the entry name as 'NAME..en'. */ |
| ensymname = XNEWVEC (char, symlen + 5); |
| memcpy (ensymname, symname, symlen); |
| memcpy (ensymname + symlen, "..en", 5); |
| |
| gas_assert (insn.nfixups < MAX_INSN_FIXUPS); |
| if (insn.nfixups > 0) |
| { |
| memmove (&insn.fixups[1], &insn.fixups[0], |
| sizeof(struct alpha_fixup) * insn.nfixups); |
| } |
| |
| /* The fixup must be the same as the BFD_RELOC_ALPHA_NOP |
| case in load_expression. See B.4.5.2 of the OpenVMS |
| Linker Utility Manual. */ |
| insn.fixups[0].reloc = BFD_RELOC_ALPHA_BOH; |
| insn.fixups[0].exp.X_op = O_symbol; |
| insn.fixups[0].exp.X_add_symbol = symbol_find_or_make (ensymname); |
| insn.fixups[0].exp.X_add_number = 0; |
| insn.fixups[0].xtrasym = alpha_linkage_symbol; |
| insn.fixups[0].procsym = alpha_evax_proc->symbol; |
| insn.nfixups++; |
| alpha_linkage_symbol = 0; |
| free (ensymname); |
| } |
| #endif |
| |
| emit_insn (&insn); |
| } |
| |
| /* The ret and jcr instructions differ from their instruction |
| counterparts in that everything can be defaulted. */ |
| |
| static void |
| emit_retjcr (const expressionS *tok, |
| int ntok, |
| const void * vopname) |
| { |
| const char *opname = (const char *) vopname; |
| expressionS newtok[3]; |
| int r, tokidx = 0; |
| |
| if (tokidx < ntok && tok[tokidx].X_op == O_register) |
| r = regno (tok[tokidx++].X_add_number); |
| else |
| r = AXP_REG_ZERO; |
| |
| set_tok_reg (newtok[0], r); |
| |
| if (tokidx < ntok && |
| (tok[tokidx].X_op == O_pregister || tok[tokidx].X_op == O_cpregister)) |
| r = regno (tok[tokidx++].X_add_number); |
| else |
| r = AXP_REG_RA; |
| |
| set_tok_cpreg (newtok[1], r); |
| |
| if (tokidx < ntok) |
| newtok[2] = tok[tokidx]; |
| else |
| set_tok_const (newtok[2], strcmp (opname, "ret") == 0); |
| |
| assemble_tokens (opname, newtok, 3, 0); |
| } |
| |
| /* Implement the ldgp macro. */ |
| |
| static void |
| emit_ldgp (const expressionS *tok ATTRIBUTE_UNUSED, |
| int ntok ATTRIBUTE_UNUSED, |
| const void * unused ATTRIBUTE_UNUSED) |
| { |
| #ifdef OBJ_AOUT |
| FIXME |
| #endif |
| #if defined(OBJ_ECOFF) || defined(OBJ_ELF) |
| /* from "ldgp r1,n(r2)", generate "ldah r1,X(R2); lda r1,Y(r1)" |
| with appropriate constants and relocations. */ |
| struct alpha_insn insn; |
| expressionS newtok[3]; |
| expressionS addend; |
| |
| #ifdef OBJ_ECOFF |
| if (regno (tok[2].X_add_number) == AXP_REG_PV) |
| ecoff_set_gp_prolog_size (0); |
| #endif |
| |
| newtok[0] = tok[0]; |
| set_tok_const (newtok[1], 0); |
| newtok[2] = tok[2]; |
| |
| assemble_tokens_to_insn ("ldah", newtok, 3, &insn); |
| |
| addend = tok[1]; |
| |
| #ifdef OBJ_ECOFF |
| if (addend.X_op != O_constant) |
| as_bad (_("can not resolve expression")); |
| addend.X_op = O_symbol; |
| addend.X_add_symbol = alpha_gp_symbol; |
| #endif |
| |
| insn.nfixups = 1; |
| insn.fixups[0].exp = addend; |
| insn.fixups[0].reloc = BFD_RELOC_ALPHA_GPDISP_HI16; |
| insn.sequence = next_sequence_num; |
| |
| emit_insn (&insn); |
| |
| set_tok_preg (newtok[2], tok[0].X_add_number); |
| |
| assemble_tokens_to_insn ("lda", newtok, 3, &insn); |
| |
| #ifdef OBJ_ECOFF |
| addend.X_add_number += 4; |
| #endif |
| |
| insn.nfixups = 1; |
| insn.fixups[0].exp = addend; |
| insn.fixups[0].reloc = BFD_RELOC_ALPHA_GPDISP_LO16; |
| insn.sequence = next_sequence_num--; |
| |
| emit_insn (&insn); |
| #endif /* OBJ_ECOFF || OBJ_ELF */ |
| } |
| |
| /* The macro table. */ |
| |
| static const struct alpha_macro alpha_macros[] = |
| { |
| /* Load/Store macros. */ |
| { "lda", emit_lda, NULL, |
| { MACRO_IR, MACRO_EXP, MACRO_OPIR, MACRO_EOA } }, |
| { "ldah", emit_ldah, NULL, |
| { MACRO_IR, MACRO_EXP, MACRO_EOA } }, |
| |
| { "ldl", emit_ir_load, "ldl", |
| { MACRO_IR, MACRO_EXP, MACRO_OPIR, MACRO_EOA } }, |
| { "ldl_l", emit_ir_load, "ldl_l", |
| { MACRO_IR, MACRO_EXP, MACRO_OPIR, MACRO_EOA } }, |
| { "ldq", emit_ir_load, "ldq", |
| { MACRO_IR, MACRO_EXP, MACRO_OPIR, MACRO_EOA } }, |
| { "ldq_l", emit_ir_load, "ldq_l", |
| { MACRO_IR, MACRO_EXP, MACRO_OPIR, MACRO_EOA } }, |
| { "ldq_u", emit_ir_load, "ldq_u", |
| { MACRO_IR, MACRO_EXP, MACRO_OPIR, MACRO_EOA } }, |
| { "ldf", emit_loadstore, "ldf", |
| { MACRO_FPR, MACRO_EXP, MACRO_OPIR, MACRO_EOA } }, |
| { "ldg", emit_loadstore, "ldg", |
| { MACRO_FPR, MACRO_EXP, MACRO_OPIR, MACRO_EOA } }, |
| { "lds", emit_loadstore, "lds", |
| { MACRO_FPR, MACRO_EXP, MACRO_OPIR, MACRO_EOA } }, |
| { "ldt", emit_loadstore, "ldt", |
| { MACRO_FPR, MACRO_EXP, MACRO_OPIR, MACRO_EOA } }, |
| |
| { "ldb", emit_ldX, (void *) 0, |
| { MACRO_IR, MACRO_EXP, MACRO_OPIR, MACRO_EOA } }, |
| { "ldbu", emit_ldXu, (void *) 0, |
| { MACRO_IR, MACRO_EXP, MACRO_OPIR, MACRO_EOA } }, |
| { "ldw", emit_ldX, (void *) 1, |
| { MACRO_IR, MACRO_EXP, MACRO_OPIR, MACRO_EOA } }, |
| { "ldwu", emit_ldXu, (void *) 1, |
| { MACRO_IR, MACRO_EXP, MACRO_OPIR, MACRO_EOA } }, |
| |
| { "uldw", emit_uldX, (void *) 1, |
| { MACRO_IR, MACRO_EXP, MACRO_OPIR, MACRO_EOA } }, |
| { "uldwu", emit_uldXu, (void *)
|