| /* tc-arm.c -- Assemble for the ARM |
| Copyright 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 |
| Free Software Foundation, Inc. |
| Contributed by Richard Earnshaw (rwe@pegasus.esprit.ec.org) |
| Modified by David Taylor (dtaylor@armltd.co.uk) |
| |
| 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 2, 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, 59 Temple Place - Suite 330, Boston, MA |
| 02111-1307, USA. */ |
| |
| #include <ctype.h> |
| #include <string.h> |
| #define NO_RELOC 0 |
| #include "as.h" |
| |
| /* Need TARGET_CPU. */ |
| #include "config.h" |
| #include "subsegs.h" |
| #include "obstack.h" |
| #include "symbols.h" |
| #include "listing.h" |
| |
| #ifdef OBJ_ELF |
| #include "elf/arm.h" |
| #include "dwarf2dbg.h" |
| #endif |
| |
| /* Types of processor to assemble for. */ |
| #define ARM_1 0x00000001 |
| #define ARM_2 0x00000002 |
| #define ARM_3 0x00000004 |
| #define ARM_250 ARM_3 |
| #define ARM_6 0x00000008 |
| #define ARM_7 ARM_6 /* Same core instruction set. */ |
| #define ARM_8 ARM_6 /* Same core instruction set. */ |
| #define ARM_9 ARM_6 /* Same core instruction set. */ |
| #define ARM_CPU_MASK 0x0000000f |
| |
| /* The following bitmasks control CPU extensions (ARM7 onwards): */ |
| #define ARM_EXT_LONGMUL 0x00000010 /* Allow long multiplies. */ |
| #define ARM_EXT_HALFWORD 0x00000020 /* Allow half word loads. */ |
| #define ARM_EXT_THUMB 0x00000040 /* Allow BX instruction. */ |
| #define ARM_EXT_V5 0x00000080 /* Allow CLZ, etc. */ |
| #define ARM_EXT_V5E 0x00000100 /* "El Segundo". */ |
| #define ARM_EXT_XSCALE 0x00000200 /* Allow MIA etc. */ |
| |
| /* Architectures are the sum of the base and extensions. */ |
| #define ARM_ARCH_V3M ARM_EXT_LONGMUL |
| #define ARM_ARCH_V4 (ARM_ARCH_V3M | ARM_EXT_HALFWORD) |
| #define ARM_ARCH_V4T (ARM_ARCH_V4 | ARM_EXT_THUMB) |
| #define ARM_ARCH_V5 (ARM_ARCH_V4 | ARM_EXT_V5) |
| #define ARM_ARCH_V5T (ARM_ARCH_V5 | ARM_EXT_THUMB) |
| #define ARM_ARCH_V5TE (ARM_ARCH_V5T | ARM_EXT_V5E) |
| #define ARM_ARCH_XSCALE (ARM_ARCH_V5TE | ARM_EXT_XSCALE) |
| |
| /* Some useful combinations: */ |
| #define ARM_ANY 0x00ffffff |
| #define ARM_2UP (ARM_ANY - ARM_1) |
| #define ARM_ALL ARM_2UP /* Not arm1 only. */ |
| #define ARM_3UP 0x00fffffc |
| #define ARM_6UP 0x00fffff8 /* Includes ARM7. */ |
| |
| #define FPU_CORE 0x80000000 |
| #define FPU_FPA10 0x40000000 |
| #define FPU_FPA11 0x40000000 |
| #define FPU_NONE 0 |
| |
| /* Some useful combinations. */ |
| #define FPU_ALL 0xff000000 /* Note this is ~ARM_ANY. */ |
| #define FPU_MEMMULTI 0x7f000000 /* Not fpu_core. */ |
| |
| #ifndef CPU_DEFAULT |
| #if defined __XSCALE__ |
| #define CPU_DEFAULT (ARM_9 | ARM_ARCH_XSCALE) |
| #else |
| #if defined __thumb__ |
| #define CPU_DEFAULT (ARM_7 | ARM_ARCH_V4T) |
| #else |
| #define CPU_DEFAULT ARM_ALL |
| #endif |
| #endif |
| #endif |
| |
| #ifndef FPU_DEFAULT |
| #define FPU_DEFAULT FPU_ALL |
| #endif |
| |
| #define streq(a, b) (strcmp (a, b) == 0) |
| #define skip_whitespace(str) while (*(str) == ' ') ++(str) |
| |
| static unsigned long cpu_variant = CPU_DEFAULT | FPU_DEFAULT; |
| static int target_oabi = 0; |
| |
| #if defined OBJ_COFF || defined OBJ_ELF |
| /* Flags stored in private area of BFD structure. */ |
| static boolean uses_apcs_26 = false; |
| static boolean atpcs = false; |
| static boolean support_interwork = false; |
| static boolean uses_apcs_float = false; |
| static boolean pic_code = false; |
| #endif |
| |
| /* This array holds the chars that always start a comment. If the |
| pre-processor is disabled, these aren't very useful. */ |
| CONST char comment_chars[] = "@"; |
| |
| /* This array holds the chars that only start a comment at the beginning of |
| a line. If the line seems to have the form '# 123 filename' |
| .line and .file directives will appear in the pre-processed output. */ |
| /* Note that input_file.c hand checks for '#' at the beginning of the |
| first line of the input file. This is because the compiler outputs |
| #NO_APP at the beginning of its output. */ |
| /* Also note that comments like this one will always work. */ |
| CONST char line_comment_chars[] = "#"; |
| |
| CONST char line_separator_chars[] = ";"; |
| |
| /* Chars that can be used to separate mant |
| from exp in floating point numbers. */ |
| CONST char EXP_CHARS[] = "eE"; |
| |
| /* Chars that mean this number is a floating point constant. */ |
| /* As in 0f12.456 */ |
| /* or 0d1.2345e12 */ |
| |
| CONST char FLT_CHARS[] = "rRsSfFdDxXeEpP"; |
| |
| /* Prefix characters that indicate the start of an immediate |
| value. */ |
| #define is_immediate_prefix(C) ((C) == '#' || (C) == '$') |
| |
| #ifdef OBJ_ELF |
| /* Pre-defined "_GLOBAL_OFFSET_TABLE_" */ |
| symbolS * GOT_symbol; |
| #endif |
| |
| /* Size of relocation record. */ |
| CONST int md_reloc_size = 8; |
| |
| /* 0: assemble for ARM, |
| 1: assemble for Thumb, |
| 2: assemble for Thumb even though target CPU does not support thumb |
| instructions. */ |
| static int thumb_mode = 0; |
| |
| typedef struct arm_fix |
| { |
| int thumb_mode; |
| } arm_fix_data; |
| |
| struct arm_it |
| { |
| CONST char * error; |
| unsigned long instruction; |
| int suffix; |
| int size; |
| struct |
| { |
| bfd_reloc_code_real_type type; |
| expressionS exp; |
| int pc_rel; |
| } reloc; |
| }; |
| |
| struct arm_it inst; |
| |
| enum asm_shift_index |
| { |
| SHIFT_LSL = 0, |
| SHIFT_LSR, |
| SHIFT_ASR, |
| SHIFT_ROR, |
| SHIFT_RRX |
| }; |
| |
| struct asm_shift_properties |
| { |
| enum asm_shift_index index; |
| unsigned long bit_field; |
| unsigned int allows_0 : 1; |
| unsigned int allows_32 : 1; |
| }; |
| |
| static const struct asm_shift_properties shift_properties [] = |
| { |
| { SHIFT_LSL, 0, 1, 0}, |
| { SHIFT_LSR, 0x20, 0, 1}, |
| { SHIFT_ASR, 0x40, 0, 1}, |
| { SHIFT_ROR, 0x60, 0, 0}, |
| { SHIFT_RRX, 0x60, 0, 0} |
| }; |
| |
| struct asm_shift_name |
| { |
| const char * name; |
| const struct asm_shift_properties * properties; |
| }; |
| |
| static const struct asm_shift_name shift_names [] = |
| { |
| { "asl", shift_properties + SHIFT_LSL }, |
| { "lsl", shift_properties + SHIFT_LSL }, |
| { "lsr", shift_properties + SHIFT_LSR }, |
| { "asr", shift_properties + SHIFT_ASR }, |
| { "ror", shift_properties + SHIFT_ROR }, |
| { "rrx", shift_properties + SHIFT_RRX }, |
| { "ASL", shift_properties + SHIFT_LSL }, |
| { "LSL", shift_properties + SHIFT_LSL }, |
| { "LSR", shift_properties + SHIFT_LSR }, |
| { "ASR", shift_properties + SHIFT_ASR }, |
| { "ROR", shift_properties + SHIFT_ROR }, |
| { "RRX", shift_properties + SHIFT_RRX } |
| }; |
| |
| #define NO_SHIFT_RESTRICT 1 |
| #define SHIFT_RESTRICT 0 |
| |
| #define NUM_FLOAT_VALS 8 |
| |
| CONST char * fp_const[] = |
| { |
| "0.0", "1.0", "2.0", "3.0", "4.0", "5.0", "0.5", "10.0", 0 |
| }; |
| |
| /* Number of littlenums required to hold an extended precision number. */ |
| #define MAX_LITTLENUMS 6 |
| |
| LITTLENUM_TYPE fp_values[NUM_FLOAT_VALS][MAX_LITTLENUMS]; |
| |
| #define FAIL (-1) |
| #define SUCCESS (0) |
| |
| #define SUFF_S 1 |
| #define SUFF_D 2 |
| #define SUFF_E 3 |
| #define SUFF_P 4 |
| |
| #define CP_T_X 0x00008000 |
| #define CP_T_Y 0x00400000 |
| #define CP_T_Pre 0x01000000 |
| #define CP_T_UD 0x00800000 |
| #define CP_T_WB 0x00200000 |
| |
| #define CONDS_BIT 0x00100000 |
| #define LOAD_BIT 0x00100000 |
| #define TRANS_BIT 0x00200000 |
| |
| #define DOUBLE_LOAD_FLAG 0x00000001 |
| |
| struct asm_cond |
| { |
| CONST char * template; |
| unsigned long value; |
| }; |
| |
| /* This is to save a hash look-up in the common case. */ |
| #define COND_ALWAYS 0xe0000000 |
| |
| static CONST struct asm_cond conds[] = |
| { |
| {"eq", 0x00000000}, |
| {"ne", 0x10000000}, |
| {"cs", 0x20000000}, {"hs", 0x20000000}, |
| {"cc", 0x30000000}, {"ul", 0x30000000}, {"lo", 0x30000000}, |
| {"mi", 0x40000000}, |
| {"pl", 0x50000000}, |
| {"vs", 0x60000000}, |
| {"vc", 0x70000000}, |
| {"hi", 0x80000000}, |
| {"ls", 0x90000000}, |
| {"ge", 0xa0000000}, |
| {"lt", 0xb0000000}, |
| {"gt", 0xc0000000}, |
| {"le", 0xd0000000}, |
| {"al", 0xe0000000}, |
| {"nv", 0xf0000000} |
| }; |
| |
| /* Warning: If the top bit of the set_bits is set, then the standard |
| instruction bitmask is ignored, and the new bitmask is taken from |
| the set_bits: */ |
| struct asm_flg |
| { |
| CONST char * template; /* Basic flag string. */ |
| unsigned long set_bits; /* Bits to set. */ |
| }; |
| |
| static CONST struct asm_flg s_flag[] = |
| { |
| {"s", CONDS_BIT}, |
| {NULL, 0} |
| }; |
| |
| static CONST struct asm_flg ldr_flags[] = |
| { |
| {"d", DOUBLE_LOAD_FLAG}, |
| {"b", 0x00400000}, |
| {"t", TRANS_BIT}, |
| {"bt", 0x00400000 | TRANS_BIT}, |
| {"h", 0x801000b0}, |
| {"sh", 0x801000f0}, |
| {"sb", 0x801000d0}, |
| {NULL, 0} |
| }; |
| |
| static CONST struct asm_flg str_flags[] = |
| { |
| {"d", DOUBLE_LOAD_FLAG}, |
| {"b", 0x00400000}, |
| {"t", TRANS_BIT}, |
| {"bt", 0x00400000 | TRANS_BIT}, |
| {"h", 0x800000b0}, |
| {NULL, 0} |
| }; |
| |
| static CONST struct asm_flg byte_flag[] = |
| { |
| {"b", 0x00400000}, |
| {NULL, 0} |
| }; |
| |
| static CONST struct asm_flg cmp_flags[] = |
| { |
| {"s", CONDS_BIT}, |
| {"p", 0x0010f000}, |
| {NULL, 0} |
| }; |
| |
| static CONST struct asm_flg ldm_flags[] = |
| { |
| {"ed", 0x01800000}, |
| {"fd", 0x00800000}, |
| {"ea", 0x01000000}, |
| {"fa", 0x00000000}, |
| {"ib", 0x01800000}, |
| {"ia", 0x00800000}, |
| {"db", 0x01000000}, |
| {"da", 0x00000000}, |
| {NULL, 0} |
| }; |
| |
| static CONST struct asm_flg stm_flags[] = |
| { |
| {"ed", 0x00000000}, |
| {"fd", 0x01000000}, |
| {"ea", 0x00800000}, |
| {"fa", 0x01800000}, |
| {"ib", 0x01800000}, |
| {"ia", 0x00800000}, |
| {"db", 0x01000000}, |
| {"da", 0x00000000}, |
| {NULL, 0} |
| }; |
| |
| static CONST struct asm_flg lfm_flags[] = |
| { |
| {"fd", 0x00800000}, |
| {"ea", 0x01000000}, |
| {NULL, 0} |
| }; |
| |
| static CONST struct asm_flg sfm_flags[] = |
| { |
| {"fd", 0x01000000}, |
| {"ea", 0x00800000}, |
| {NULL, 0} |
| }; |
| |
| static CONST struct asm_flg round_flags[] = |
| { |
| {"p", 0x00000020}, |
| {"m", 0x00000040}, |
| {"z", 0x00000060}, |
| {NULL, 0} |
| }; |
| |
| /* The implementation of the FIX instruction is broken on some assemblers, |
| in that it accepts a precision specifier as well as a rounding specifier, |
| despite the fact that this is meaningless. To be more compatible, we |
| accept it as well, though of course it does not set any bits. */ |
| static CONST struct asm_flg fix_flags[] = |
| { |
| {"p", 0x00000020}, |
| {"m", 0x00000040}, |
| {"z", 0x00000060}, |
| {"sp", 0x00000020}, |
| {"sm", 0x00000040}, |
| {"sz", 0x00000060}, |
| {"dp", 0x00000020}, |
| {"dm", 0x00000040}, |
| {"dz", 0x00000060}, |
| {"ep", 0x00000020}, |
| {"em", 0x00000040}, |
| {"ez", 0x00000060}, |
| {NULL, 0} |
| }; |
| |
| static CONST struct asm_flg except_flag[] = |
| { |
| {"e", 0x00400000}, |
| {NULL, 0} |
| }; |
| |
| static CONST struct asm_flg cplong_flag[] = |
| { |
| {"l", 0x00400000}, |
| {NULL, 0} |
| }; |
| |
| struct asm_psr |
| { |
| CONST char * template; |
| boolean cpsr; |
| unsigned long field; |
| }; |
| |
| /* The bit that distnguishes CPSR and SPSR. */ |
| #define SPSR_BIT (1 << 22) |
| |
| /* How many bits to shift the PSR_xxx bits up by. */ |
| #define PSR_SHIFT 16 |
| |
| #define PSR_c (1 << 0) |
| #define PSR_x (1 << 1) |
| #define PSR_s (1 << 2) |
| #define PSR_f (1 << 3) |
| |
| static CONST struct asm_psr psrs[] = |
| { |
| {"CPSR", true, PSR_c | PSR_f}, |
| {"CPSR_all", true, PSR_c | PSR_f}, |
| {"SPSR", false, PSR_c | PSR_f}, |
| {"SPSR_all", false, PSR_c | PSR_f}, |
| {"CPSR_flg", true, PSR_f}, |
| {"CPSR_f", true, PSR_f}, |
| {"SPSR_flg", false, PSR_f}, |
| {"SPSR_f", false, PSR_f}, |
| {"CPSR_c", true, PSR_c}, |
| {"CPSR_ctl", true, PSR_c}, |
| {"SPSR_c", false, PSR_c}, |
| {"SPSR_ctl", false, PSR_c}, |
| {"CPSR_x", true, PSR_x}, |
| {"CPSR_s", true, PSR_s}, |
| {"SPSR_x", false, PSR_x}, |
| {"SPSR_s", false, PSR_s}, |
| /* Combinations of flags. */ |
| {"CPSR_fs", true, PSR_f | PSR_s}, |
| {"CPSR_fx", true, PSR_f | PSR_x}, |
| {"CPSR_fc", true, PSR_f | PSR_c}, |
| {"CPSR_sf", true, PSR_s | PSR_f}, |
| {"CPSR_sx", true, PSR_s | PSR_x}, |
| {"CPSR_sc", true, PSR_s | PSR_c}, |
| {"CPSR_xf", true, PSR_x | PSR_f}, |
| {"CPSR_xs", true, PSR_x | PSR_s}, |
| {"CPSR_xc", true, PSR_x | PSR_c}, |
| {"CPSR_cf", true, PSR_c | PSR_f}, |
| {"CPSR_cs", true, PSR_c | PSR_s}, |
| {"CPSR_cx", true, PSR_c | PSR_x}, |
| {"CPSR_fsx", true, PSR_f | PSR_s | PSR_x}, |
| {"CPSR_fsc", true, PSR_f | PSR_s | PSR_c}, |
| {"CPSR_fxs", true, PSR_f | PSR_x | PSR_s}, |
| {"CPSR_fxc", true, PSR_f | PSR_x | PSR_c}, |
| {"CPSR_fcs", true, PSR_f | PSR_c | PSR_s}, |
| {"CPSR_fcx", true, PSR_f | PSR_c | PSR_x}, |
| {"CPSR_sfx", true, PSR_s | PSR_f | PSR_x}, |
| {"CPSR_sfc", true, PSR_s | PSR_f | PSR_c}, |
| {"CPSR_sxf", true, PSR_s | PSR_x | PSR_f}, |
| {"CPSR_sxc", true, PSR_s | PSR_x | PSR_c}, |
| {"CPSR_scf", true, PSR_s | PSR_c | PSR_f}, |
| {"CPSR_scx", true, PSR_s | PSR_c | PSR_x}, |
| {"CPSR_xfs", true, PSR_x | PSR_f | PSR_s}, |
| {"CPSR_xfc", true, PSR_x | PSR_f | PSR_c}, |
| {"CPSR_xsf", true, PSR_x | PSR_s | PSR_f}, |
| {"CPSR_xsc", true, PSR_x | PSR_s | PSR_c}, |
| {"CPSR_xcf", true, PSR_x | PSR_c | PSR_f}, |
| {"CPSR_xcs", true, PSR_x | PSR_c | PSR_s}, |
| {"CPSR_cfs", true, PSR_c | PSR_f | PSR_s}, |
| {"CPSR_cfx", true, PSR_c | PSR_f | PSR_x}, |
| {"CPSR_csf", true, PSR_c | PSR_s | PSR_f}, |
| {"CPSR_csx", true, PSR_c | PSR_s | PSR_x}, |
| {"CPSR_cxf", true, PSR_c | PSR_x | PSR_f}, |
| {"CPSR_cxs", true, PSR_c | PSR_x | PSR_s}, |
| {"CPSR_fsxc", true, PSR_f | PSR_s | PSR_x | PSR_c}, |
| {"CPSR_fscx", true, PSR_f | PSR_s | PSR_c | PSR_x}, |
| {"CPSR_fxsc", true, PSR_f | PSR_x | PSR_s | PSR_c}, |
| {"CPSR_fxcs", true, PSR_f | PSR_x | PSR_c | PSR_s}, |
| {"CPSR_fcsx", true, PSR_f | PSR_c | PSR_s | PSR_x}, |
| {"CPSR_fcxs", true, PSR_f | PSR_c | PSR_x | PSR_s}, |
| {"CPSR_sfxc", true, PSR_s | PSR_f | PSR_x | PSR_c}, |
| {"CPSR_sfcx", true, PSR_s | PSR_f | PSR_c | PSR_x}, |
| {"CPSR_sxfc", true, PSR_s | PSR_x | PSR_f | PSR_c}, |
| {"CPSR_sxcf", true, PSR_s | PSR_x | PSR_c | PSR_f}, |
| {"CPSR_scfx", true, PSR_s | PSR_c | PSR_f | PSR_x}, |
| {"CPSR_scxf", true, PSR_s | PSR_c | PSR_x | PSR_f}, |
| {"CPSR_xfsc", true, PSR_x | PSR_f | PSR_s | PSR_c}, |
| {"CPSR_xfcs", true, PSR_x | PSR_f | PSR_c | PSR_s}, |
| {"CPSR_xsfc", true, PSR_x | PSR_s | PSR_f | PSR_c}, |
| {"CPSR_xscf", true, PSR_x | PSR_s | PSR_c | PSR_f}, |
| {"CPSR_xcfs", true, PSR_x | PSR_c | PSR_f | PSR_s}, |
| {"CPSR_xcsf", true, PSR_x | PSR_c | PSR_s | PSR_f}, |
| {"CPSR_cfsx", true, PSR_c | PSR_f | PSR_s | PSR_x}, |
| {"CPSR_cfxs", true, PSR_c | PSR_f | PSR_x | PSR_s}, |
| {"CPSR_csfx", true, PSR_c | PSR_s | PSR_f | PSR_x}, |
| {"CPSR_csxf", true, PSR_c | PSR_s | PSR_x | PSR_f}, |
| {"CPSR_cxfs", true, PSR_c | PSR_x | PSR_f | PSR_s}, |
| {"CPSR_cxsf", true, PSR_c | PSR_x | PSR_s | PSR_f}, |
| {"SPSR_fs", false, PSR_f | PSR_s}, |
| {"SPSR_fx", false, PSR_f | PSR_x}, |
| {"SPSR_fc", false, PSR_f | PSR_c}, |
| {"SPSR_sf", false, PSR_s | PSR_f}, |
| {"SPSR_sx", false, PSR_s | PSR_x}, |
| {"SPSR_sc", false, PSR_s | PSR_c}, |
| {"SPSR_xf", false, PSR_x | PSR_f}, |
| {"SPSR_xs", false, PSR_x | PSR_s}, |
| {"SPSR_xc", false, PSR_x | PSR_c}, |
| {"SPSR_cf", false, PSR_c | PSR_f}, |
| {"SPSR_cs", false, PSR_c | PSR_s}, |
| {"SPSR_cx", false, PSR_c | PSR_x}, |
| {"SPSR_fsx", false, PSR_f | PSR_s | PSR_x}, |
| {"SPSR_fsc", false, PSR_f | PSR_s | PSR_c}, |
| {"SPSR_fxs", false, PSR_f | PSR_x | PSR_s}, |
| {"SPSR_fxc", false, PSR_f | PSR_x | PSR_c}, |
| {"SPSR_fcs", false, PSR_f | PSR_c | PSR_s}, |
| {"SPSR_fcx", false, PSR_f | PSR_c | PSR_x}, |
| {"SPSR_sfx", false, PSR_s | PSR_f | PSR_x}, |
| {"SPSR_sfc", false, PSR_s | PSR_f | PSR_c}, |
| {"SPSR_sxf", false, PSR_s | PSR_x | PSR_f}, |
| {"SPSR_sxc", false, PSR_s | PSR_x | PSR_c}, |
| {"SPSR_scf", false, PSR_s | PSR_c | PSR_f}, |
| {"SPSR_scx", false, PSR_s | PSR_c | PSR_x}, |
| {"SPSR_xfs", false, PSR_x | PSR_f | PSR_s}, |
| {"SPSR_xfc", false, PSR_x | PSR_f | PSR_c}, |
| {"SPSR_xsf", false, PSR_x | PSR_s | PSR_f}, |
| {"SPSR_xsc", false, PSR_x | PSR_s | PSR_c}, |
| {"SPSR_xcf", false, PSR_x | PSR_c | PSR_f}, |
| {"SPSR_xcs", false, PSR_x | PSR_c | PSR_s}, |
| {"SPSR_cfs", false, PSR_c | PSR_f | PSR_s}, |
| {"SPSR_cfx", false, PSR_c | PSR_f | PSR_x}, |
| {"SPSR_csf", false, PSR_c | PSR_s | PSR_f}, |
| {"SPSR_csx", false, PSR_c | PSR_s | PSR_x}, |
| {"SPSR_cxf", false, PSR_c | PSR_x | PSR_f}, |
| {"SPSR_cxs", false, PSR_c | PSR_x | PSR_s}, |
| {"SPSR_fsxc", false, PSR_f | PSR_s | PSR_x | PSR_c}, |
| {"SPSR_fscx", false, PSR_f | PSR_s | PSR_c | PSR_x}, |
| {"SPSR_fxsc", false, PSR_f | PSR_x | PSR_s | PSR_c}, |
| {"SPSR_fxcs", false, PSR_f | PSR_x | PSR_c | PSR_s}, |
| {"SPSR_fcsx", false, PSR_f | PSR_c | PSR_s | PSR_x}, |
| {"SPSR_fcxs", false, PSR_f | PSR_c | PSR_x | PSR_s}, |
| {"SPSR_sfxc", false, PSR_s | PSR_f | PSR_x | PSR_c}, |
| {"SPSR_sfcx", false, PSR_s | PSR_f | PSR_c | PSR_x}, |
| {"SPSR_sxfc", false, PSR_s | PSR_x | PSR_f | PSR_c}, |
| {"SPSR_sxcf", false, PSR_s | PSR_x | PSR_c | PSR_f}, |
| {"SPSR_scfx", false, PSR_s | PSR_c | PSR_f | PSR_x}, |
| {"SPSR_scxf", false, PSR_s | PSR_c | PSR_x | PSR_f}, |
| {"SPSR_xfsc", false, PSR_x | PSR_f | PSR_s | PSR_c}, |
| {"SPSR_xfcs", false, PSR_x | PSR_f | PSR_c | PSR_s}, |
| {"SPSR_xsfc", false, PSR_x | PSR_s | PSR_f | PSR_c}, |
| {"SPSR_xscf", false, PSR_x | PSR_s | PSR_c | PSR_f}, |
| {"SPSR_xcfs", false, PSR_x | PSR_c | PSR_f | PSR_s}, |
| {"SPSR_xcsf", false, PSR_x | PSR_c | PSR_s | PSR_f}, |
| {"SPSR_cfsx", false, PSR_c | PSR_f | PSR_s | PSR_x}, |
| {"SPSR_cfxs", false, PSR_c | PSR_f | PSR_x | PSR_s}, |
| {"SPSR_csfx", false, PSR_c | PSR_s | PSR_f | PSR_x}, |
| {"SPSR_csxf", false, PSR_c | PSR_s | PSR_x | PSR_f}, |
| {"SPSR_cxfs", false, PSR_c | PSR_x | PSR_f | PSR_s}, |
| {"SPSR_cxsf", false, PSR_c | PSR_x | PSR_s | PSR_f}, |
| }; |
| |
| /* Functions called by parser. */ |
| /* ARM instructions. */ |
| static void do_arit PARAMS ((char *, unsigned long)); |
| static void do_cmp PARAMS ((char *, unsigned long)); |
| static void do_mov PARAMS ((char *, unsigned long)); |
| static void do_ldst PARAMS ((char *, unsigned long)); |
| static void do_ldmstm PARAMS ((char *, unsigned long)); |
| static void do_branch PARAMS ((char *, unsigned long)); |
| static void do_swi PARAMS ((char *, unsigned long)); |
| /* Pseudo Op codes. */ |
| static void do_adr PARAMS ((char *, unsigned long)); |
| static void do_adrl PARAMS ((char *, unsigned long)); |
| static void do_nop PARAMS ((char *, unsigned long)); |
| /* ARM 2. */ |
| static void do_mul PARAMS ((char *, unsigned long)); |
| static void do_mla PARAMS ((char *, unsigned long)); |
| /* ARM 3. */ |
| static void do_swap PARAMS ((char *, unsigned long)); |
| /* ARM 6. */ |
| static void do_msr PARAMS ((char *, unsigned long)); |
| static void do_mrs PARAMS ((char *, unsigned long)); |
| /* ARM 7M. */ |
| static void do_mull PARAMS ((char *, unsigned long)); |
| /* ARM THUMB. */ |
| static void do_bx PARAMS ((char *, unsigned long)); |
| |
| /* ARM_EXT_XScale. */ |
| static void do_mia PARAMS ((char *, unsigned long)); |
| static void do_mar PARAMS ((char *, unsigned long)); |
| static void do_mra PARAMS ((char *, unsigned long)); |
| static void do_pld PARAMS ((char *, unsigned long)); |
| static void do_ldrd PARAMS ((char *, unsigned long)); |
| |
| /* ARM_EXT_V5. */ |
| static void do_blx PARAMS ((char *, unsigned long)); |
| static void do_bkpt PARAMS ((char *, unsigned long)); |
| static void do_clz PARAMS ((char *, unsigned long)); |
| static void do_lstc2 PARAMS ((char *, unsigned long)); |
| static void do_cdp2 PARAMS ((char *, unsigned long)); |
| static void do_co_reg2 PARAMS ((char *, unsigned long)); |
| |
| static void do_t_blx PARAMS ((char *)); |
| static void do_t_bkpt PARAMS ((char *)); |
| |
| /* ARM_EXT_V5E. */ |
| static void do_smla PARAMS ((char *, unsigned long)); |
| static void do_smlal PARAMS ((char *, unsigned long)); |
| static void do_smul PARAMS ((char *, unsigned long)); |
| static void do_qadd PARAMS ((char *, unsigned long)); |
| static void do_co_reg2c PARAMS ((char *, unsigned long)); |
| |
| /* Coprocessor Instructions. */ |
| static void do_cdp PARAMS ((char *, unsigned long)); |
| static void do_lstc PARAMS ((char *, unsigned long)); |
| static void do_co_reg PARAMS ((char *, unsigned long)); |
| static void do_fp_ctrl PARAMS ((char *, unsigned long)); |
| static void do_fp_ldst PARAMS ((char *, unsigned long)); |
| static void do_fp_ldmstm PARAMS ((char *, unsigned long)); |
| static void do_fp_dyadic PARAMS ((char *, unsigned long)); |
| static void do_fp_monadic PARAMS ((char *, unsigned long)); |
| static void do_fp_cmp PARAMS ((char *, unsigned long)); |
| static void do_fp_from_reg PARAMS ((char *, unsigned long)); |
| static void do_fp_to_reg PARAMS ((char *, unsigned long)); |
| |
| static void fix_new_arm PARAMS ((fragS *, int, short, expressionS *, int, int)); |
| static int arm_reg_parse PARAMS ((char **)); |
| static CONST struct asm_psr * arm_psr_parse PARAMS ((char **)); |
| static void symbol_locate PARAMS ((symbolS *, CONST char *, segT, valueT, fragS *)); |
| static int add_to_lit_pool PARAMS ((void)); |
| static unsigned validate_immediate PARAMS ((unsigned)); |
| static unsigned validate_immediate_twopart PARAMS ((unsigned int, unsigned int *)); |
| static int validate_offset_imm PARAMS ((unsigned int, int)); |
| static void opcode_select PARAMS ((int)); |
| static void end_of_line PARAMS ((char *)); |
| static int reg_required_here PARAMS ((char **, int)); |
| static int psr_required_here PARAMS ((char **)); |
| static int co_proc_number PARAMS ((char **)); |
| static int cp_opc_expr PARAMS ((char **, int, int)); |
| static int cp_reg_required_here PARAMS ((char **, int)); |
| static int fp_reg_required_here PARAMS ((char **, int)); |
| static int cp_address_offset PARAMS ((char **)); |
| static int cp_address_required_here PARAMS ((char **)); |
| static int my_get_float_expression PARAMS ((char **)); |
| static int skip_past_comma PARAMS ((char **)); |
| static int walk_no_bignums PARAMS ((symbolS *)); |
| static int negate_data_op PARAMS ((unsigned long *, unsigned long)); |
| static int data_op2 PARAMS ((char **)); |
| static int fp_op2 PARAMS ((char **)); |
| static long reg_list PARAMS ((char **)); |
| static void thumb_load_store PARAMS ((char *, int, int)); |
| static int decode_shift PARAMS ((char **, int)); |
| static int ldst_extend PARAMS ((char **, int)); |
| static void thumb_add_sub PARAMS ((char *, int)); |
| static void insert_reg PARAMS ((int)); |
| static void thumb_shift PARAMS ((char *, int)); |
| static void thumb_mov_compare PARAMS ((char *, int)); |
| static void set_constant_flonums PARAMS ((void)); |
| static valueT md_chars_to_number PARAMS ((char *, int)); |
| static void insert_reg_alias PARAMS ((char *, int)); |
| static void output_inst PARAMS ((void)); |
| #ifdef OBJ_ELF |
| static bfd_reloc_code_real_type arm_parse_reloc PARAMS ((void)); |
| #endif |
| |
| /* ARM instructions take 4bytes in the object file, Thumb instructions |
| take 2: */ |
| #define INSN_SIZE 4 |
| |
| /* LONGEST_INST is the longest basic instruction name without |
| conditions or flags. ARM7M has 4 of length 5. El Segundo |
| has one basic instruction name of length 7 (SMLALxy). */ |
| #define LONGEST_INST 7 |
| |
| struct asm_opcode |
| { |
| /* Basic string to match. */ |
| CONST char * template; |
| |
| /* Basic instruction code. */ |
| unsigned long value; |
| |
| /* Compulsory suffix that must follow conds. If "", then the |
| instruction is not conditional and must have no suffix. */ |
| CONST char * comp_suffix; |
| |
| /* Bits to toggle if flag 'n' set. */ |
| CONST struct asm_flg * flags; |
| |
| /* Which CPU variants this exists for. */ |
| unsigned long variants; |
| |
| /* Function to call to parse args. */ |
| void (* parms) PARAMS ((char *, unsigned long)); |
| }; |
| |
| static CONST struct asm_opcode insns[] = |
| { |
| /* Intel XScale extensions to ARM V5 ISA. */ |
| {"mia", 0x0e200010, NULL, NULL, ARM_EXT_XSCALE, do_mia}, |
| {"miaph", 0x0e280010, NULL, NULL, ARM_EXT_XSCALE, do_mia}, |
| {"miabb", 0x0e2c0010, NULL, NULL, ARM_EXT_XSCALE, do_mia}, |
| {"miabt", 0x0e2d0010, NULL, NULL, ARM_EXT_XSCALE, do_mia}, |
| {"miatb", 0x0e2e0010, NULL, NULL, ARM_EXT_XSCALE, do_mia}, |
| {"miatt", 0x0e2f0010, NULL, NULL, ARM_EXT_XSCALE, do_mia}, |
| {"mar", 0x0c400000, NULL, NULL, ARM_EXT_XSCALE, do_mar}, |
| {"mra", 0x0c500000, NULL, NULL, ARM_EXT_XSCALE, do_mra}, |
| {"pld", 0xf450f000, "", NULL, ARM_EXT_XSCALE, do_pld}, |
| {"ldr", 0x000000d0, NULL, ldr_flags, ARM_ANY, do_ldrd}, |
| {"str", 0x000000f0, NULL, str_flags, ARM_ANY, do_ldrd}, |
| |
| /* ARM Instructions. */ |
| {"and", 0x00000000, NULL, s_flag, ARM_ANY, do_arit}, |
| {"eor", 0x00200000, NULL, s_flag, ARM_ANY, do_arit}, |
| {"sub", 0x00400000, NULL, s_flag, ARM_ANY, do_arit}, |
| {"rsb", 0x00600000, NULL, s_flag, ARM_ANY, do_arit}, |
| {"add", 0x00800000, NULL, s_flag, ARM_ANY, do_arit}, |
| {"adc", 0x00a00000, NULL, s_flag, ARM_ANY, do_arit}, |
| {"sbc", 0x00c00000, NULL, s_flag, ARM_ANY, do_arit}, |
| {"rsc", 0x00e00000, NULL, s_flag, ARM_ANY, do_arit}, |
| {"orr", 0x01800000, NULL, s_flag, ARM_ANY, do_arit}, |
| {"bic", 0x01c00000, NULL, s_flag, ARM_ANY, do_arit}, |
| {"tst", 0x01000000, NULL, cmp_flags, ARM_ANY, do_cmp}, |
| {"teq", 0x01200000, NULL, cmp_flags, ARM_ANY, do_cmp}, |
| {"cmp", 0x01400000, NULL, cmp_flags, ARM_ANY, do_cmp}, |
| {"cmn", 0x01600000, NULL, cmp_flags, ARM_ANY, do_cmp}, |
| {"mov", 0x01a00000, NULL, s_flag, ARM_ANY, do_mov}, |
| {"mvn", 0x01e00000, NULL, s_flag, ARM_ANY, do_mov}, |
| {"str", 0x04000000, NULL, str_flags, ARM_ANY, do_ldst}, |
| {"ldr", 0x04100000, NULL, ldr_flags, ARM_ANY, do_ldst}, |
| {"stm", 0x08000000, NULL, stm_flags, ARM_ANY, do_ldmstm}, |
| {"ldm", 0x08100000, NULL, ldm_flags, ARM_ANY, do_ldmstm}, |
| {"swi", 0x0f000000, NULL, NULL, ARM_ANY, do_swi}, |
| #ifdef TE_WINCE |
| {"bl", 0x0b000000, NULL, NULL, ARM_ANY, do_branch}, |
| {"b", 0x0a000000, NULL, NULL, ARM_ANY, do_branch}, |
| #else |
| {"bl", 0x0bfffffe, NULL, NULL, ARM_ANY, do_branch}, |
| {"b", 0x0afffffe, NULL, NULL, ARM_ANY, do_branch}, |
| #endif |
| |
| /* Pseudo ops. */ |
| {"adr", 0x028f0000, NULL, NULL, ARM_ANY, do_adr}, |
| {"adrl", 0x028f0000, NULL, NULL, ARM_ANY, do_adrl}, |
| {"nop", 0x01a00000, NULL, NULL, ARM_ANY, do_nop}, |
| |
| /* ARM 2 multiplies. */ |
| {"mul", 0x00000090, NULL, s_flag, ARM_2UP, do_mul}, |
| {"mla", 0x00200090, NULL, s_flag, ARM_2UP, do_mla}, |
| |
| /* ARM 3 - swp instructions. */ |
| {"swp", 0x01000090, NULL, byte_flag, ARM_3UP, do_swap}, |
| |
| /* ARM 6 Coprocessor instructions. */ |
| {"mrs", 0x010f0000, NULL, NULL, ARM_6UP, do_mrs}, |
| {"msr", 0x0120f000, NULL, NULL, ARM_6UP, do_msr}, |
| /* ScottB: our code uses 0x0128f000 for msr. |
| NickC: but this is wrong because the bits 16 through 19 are |
| handled by the PSR_xxx defines above. */ |
| |
| /* ARM 7M long multiplies - need signed/unsigned flags! */ |
| {"smull", 0x00c00090, NULL, s_flag, ARM_EXT_LONGMUL, do_mull}, |
| {"umull", 0x00800090, NULL, s_flag, ARM_EXT_LONGMUL, do_mull}, |
| {"smlal", 0x00e00090, NULL, s_flag, ARM_EXT_LONGMUL, do_mull}, |
| {"umlal", 0x00a00090, NULL, s_flag, ARM_EXT_LONGMUL, do_mull}, |
| |
| /* ARM THUMB interworking. */ |
| {"bx", 0x012fff10, NULL, NULL, ARM_EXT_THUMB, do_bx}, |
| |
| /* Floating point instructions. */ |
| {"wfs", 0x0e200110, NULL, NULL, FPU_ALL, do_fp_ctrl}, |
| {"rfs", 0x0e300110, NULL, NULL, FPU_ALL, do_fp_ctrl}, |
| {"wfc", 0x0e400110, NULL, NULL, FPU_ALL, do_fp_ctrl}, |
| {"rfc", 0x0e500110, NULL, NULL, FPU_ALL, do_fp_ctrl}, |
| {"ldf", 0x0c100100, "sdep", NULL, FPU_ALL, do_fp_ldst}, |
| {"stf", 0x0c000100, "sdep", NULL, FPU_ALL, do_fp_ldst}, |
| {"lfm", 0x0c100200, NULL, lfm_flags, FPU_MEMMULTI, do_fp_ldmstm}, |
| {"sfm", 0x0c000200, NULL, sfm_flags, FPU_MEMMULTI, do_fp_ldmstm}, |
| {"mvf", 0x0e008100, "sde", round_flags, FPU_ALL, do_fp_monadic}, |
| {"mnf", 0x0e108100, "sde", round_flags, FPU_ALL, do_fp_monadic}, |
| {"abs", 0x0e208100, "sde", round_flags, FPU_ALL, do_fp_monadic}, |
| {"rnd", 0x0e308100, "sde", round_flags, FPU_ALL, do_fp_monadic}, |
| {"sqt", 0x0e408100, "sde", round_flags, FPU_ALL, do_fp_monadic}, |
| {"log", 0x0e508100, "sde", round_flags, FPU_ALL, do_fp_monadic}, |
| {"lgn", 0x0e608100, "sde", round_flags, FPU_ALL, do_fp_monadic}, |
| {"exp", 0x0e708100, "sde", round_flags, FPU_ALL, do_fp_monadic}, |
| {"sin", 0x0e808100, "sde", round_flags, FPU_ALL, do_fp_monadic}, |
| {"cos", 0x0e908100, "sde", round_flags, FPU_ALL, do_fp_monadic}, |
| {"tan", 0x0ea08100, "sde", round_flags, FPU_ALL, do_fp_monadic}, |
| {"asn", 0x0eb08100, "sde", round_flags, FPU_ALL, do_fp_monadic}, |
| {"acs", 0x0ec08100, "sde", round_flags, FPU_ALL, do_fp_monadic}, |
| {"atn", 0x0ed08100, "sde", round_flags, FPU_ALL, do_fp_monadic}, |
| {"urd", 0x0ee08100, "sde", round_flags, FPU_ALL, do_fp_monadic}, |
| {"nrm", 0x0ef08100, "sde", round_flags, FPU_ALL, do_fp_monadic}, |
| {"adf", 0x0e000100, "sde", round_flags, FPU_ALL, do_fp_dyadic}, |
| {"suf", 0x0e200100, "sde", round_flags, FPU_ALL, do_fp_dyadic}, |
| {"rsf", 0x0e300100, "sde", round_flags, FPU_ALL, do_fp_dyadic}, |
| {"muf", 0x0e100100, "sde", round_flags, FPU_ALL, do_fp_dyadic}, |
| {"dvf", 0x0e400100, "sde", round_flags, FPU_ALL, do_fp_dyadic}, |
| {"rdf", 0x0e500100, "sde", round_flags, FPU_ALL, do_fp_dyadic}, |
| {"pow", 0x0e600100, "sde", round_flags, FPU_ALL, do_fp_dyadic}, |
| {"rpw", 0x0e700100, "sde", round_flags, FPU_ALL, do_fp_dyadic}, |
| {"rmf", 0x0e800100, "sde", round_flags, FPU_ALL, do_fp_dyadic}, |
| {"fml", 0x0e900100, "sde", round_flags, FPU_ALL, do_fp_dyadic}, |
| {"fdv", 0x0ea00100, "sde", round_flags, FPU_ALL, do_fp_dyadic}, |
| {"frd", 0x0eb00100, "sde", round_flags, FPU_ALL, do_fp_dyadic}, |
| {"pol", 0x0ec00100, "sde", round_flags, FPU_ALL, do_fp_dyadic}, |
| {"cmf", 0x0e90f110, NULL, except_flag, FPU_ALL, do_fp_cmp}, |
| {"cnf", 0x0eb0f110, NULL, except_flag, FPU_ALL, do_fp_cmp}, |
| /* The FPA10 data sheet suggests that the 'E' of cmfe/cnfe should not |
| be an optional suffix, but part of the instruction. To be compatible, |
| we accept either. */ |
| {"cmfe", 0x0ed0f110, NULL, NULL, FPU_ALL, do_fp_cmp}, |
| {"cnfe", 0x0ef0f110, NULL, NULL, FPU_ALL, do_fp_cmp}, |
| {"flt", 0x0e000110, "sde", round_flags, FPU_ALL, do_fp_from_reg}, |
| {"fix", 0x0e100110, NULL, fix_flags, FPU_ALL, do_fp_to_reg}, |
| |
| /* Generic copressor instructions. */ |
| {"cdp", 0x0e000000, NULL, NULL, ARM_2UP, do_cdp}, |
| {"ldc", 0x0c100000, NULL, cplong_flag, ARM_2UP, do_lstc}, |
| {"stc", 0x0c000000, NULL, cplong_flag, ARM_2UP, do_lstc}, |
| {"mcr", 0x0e000010, NULL, NULL, ARM_2UP, do_co_reg}, |
| {"mrc", 0x0e100010, NULL, NULL, ARM_2UP, do_co_reg}, |
| |
| /* ARM ISA extension 5. */ |
| /* Note: blx is actually 2 opcodes, so the .value is set dynamically. |
| And it's sometimes conditional and sometimes not. */ |
| {"blx", 0, NULL, NULL, ARM_EXT_V5, do_blx}, |
| {"clz", 0x016f0f10, NULL, NULL, ARM_EXT_V5, do_clz}, |
| {"bkpt", 0xe1200070, "", NULL, ARM_EXT_V5, do_bkpt}, |
| {"ldc2", 0xfc100000, "", cplong_flag, ARM_EXT_V5, do_lstc2}, |
| {"stc2", 0xfc000000, "", cplong_flag, ARM_EXT_V5, do_lstc2}, |
| {"cdp2", 0xfe000000, "", NULL, ARM_EXT_V5, do_cdp2}, |
| {"mcr2", 0xfe000010, "", NULL, ARM_EXT_V5, do_co_reg2}, |
| {"mrc2", 0xfe100010, "", NULL, ARM_EXT_V5, do_co_reg2}, |
| |
| /* ARM ISA extension 5E, El Segundo. */ |
| {"smlabb", 0x01000080, NULL, NULL, ARM_EXT_V5E, do_smla}, |
| {"smlatb", 0x010000a0, NULL, NULL, ARM_EXT_V5E, do_smla}, |
| {"smlabt", 0x010000c0, NULL, NULL, ARM_EXT_V5E, do_smla}, |
| {"smlatt", 0x010000e0, NULL, NULL, ARM_EXT_V5E, do_smla}, |
| |
| {"smlawb", 0x01200080, NULL, NULL, ARM_EXT_V5E, do_smla}, |
| {"smlawt", 0x012000c0, NULL, NULL, ARM_EXT_V5E, do_smla}, |
| |
| {"smlalbb",0x01400080, NULL, NULL, ARM_EXT_V5E, do_smlal}, |
| {"smlaltb",0x014000a0, NULL, NULL, ARM_EXT_V5E, do_smlal}, |
| {"smlalbt",0x014000c0, NULL, NULL, ARM_EXT_V5E, do_smlal}, |
| {"smlaltt",0x014000e0, NULL, NULL, ARM_EXT_V5E, do_smlal}, |
| |
| {"smulbb", 0x01600080, NULL, NULL, ARM_EXT_V5E, do_smul}, |
| {"smultb", 0x016000a0, NULL, NULL, ARM_EXT_V5E, do_smul}, |
| {"smulbt", 0x016000c0, NULL, NULL, ARM_EXT_V5E, do_smul}, |
| {"smultt", 0x016000e0, NULL, NULL, ARM_EXT_V5E, do_smul}, |
| |
| {"smulwb", 0x012000a0, NULL, NULL, ARM_EXT_V5E, do_smul}, |
| {"smulwt", 0x012000e0, NULL, NULL, ARM_EXT_V5E, do_smul}, |
| |
| {"qadd", 0x01000050, NULL, NULL, ARM_EXT_V5E, do_qadd}, |
| {"qdadd", 0x01400050, NULL, NULL, ARM_EXT_V5E, do_qadd}, |
| {"qsub", 0x01200050, NULL, NULL, ARM_EXT_V5E, do_qadd}, |
| {"qdsub", 0x01600050, NULL, NULL, ARM_EXT_V5E, do_qadd}, |
| |
| {"mcrr", 0x0c400000, NULL, NULL, ARM_EXT_V5E, do_co_reg2c}, |
| {"mrrc", 0x0c500000, NULL, NULL, ARM_EXT_V5E, do_co_reg2c}, |
| }; |
| |
| /* Defines for various bits that we will want to toggle. */ |
| #define INST_IMMEDIATE 0x02000000 |
| #define OFFSET_REG 0x02000000 |
| #define HWOFFSET_IMM 0x00400000 |
| #define SHIFT_BY_REG 0x00000010 |
| #define PRE_INDEX 0x01000000 |
| #define INDEX_UP 0x00800000 |
| #define WRITE_BACK 0x00200000 |
| #define LDM_TYPE_2_OR_3 0x00400000 |
| |
| #define LITERAL_MASK 0xf000f000 |
| #define COND_MASK 0xf0000000 |
| #define OPCODE_MASK 0xfe1fffff |
| #define DATA_OP_SHIFT 21 |
| |
| /* Codes to distinguish the arithmetic instructions. */ |
| #define OPCODE_AND 0 |
| #define OPCODE_EOR 1 |
| #define OPCODE_SUB 2 |
| #define OPCODE_RSB 3 |
| #define OPCODE_ADD 4 |
| #define OPCODE_ADC 5 |
| #define OPCODE_SBC 6 |
| #define OPCODE_RSC 7 |
| #define OPCODE_TST 8 |
| #define OPCODE_TEQ 9 |
| #define OPCODE_CMP 10 |
| #define OPCODE_CMN 11 |
| #define OPCODE_ORR 12 |
| #define OPCODE_MOV 13 |
| #define OPCODE_BIC 14 |
| #define OPCODE_MVN 15 |
| |
| static void do_t_nop PARAMS ((char *)); |
| static void do_t_arit PARAMS ((char *)); |
| static void do_t_add PARAMS ((char *)); |
| static void do_t_asr PARAMS ((char *)); |
| static void do_t_branch9 PARAMS ((char *)); |
| static void do_t_branch12 PARAMS ((char *)); |
| static void do_t_branch23 PARAMS ((char *)); |
| static void do_t_bx PARAMS ((char *)); |
| static void do_t_compare PARAMS ((char *)); |
| static void do_t_ldmstm PARAMS ((char *)); |
| static void do_t_ldr PARAMS ((char *)); |
| static void do_t_ldrb PARAMS ((char *)); |
| static void do_t_ldrh PARAMS ((char *)); |
| static void do_t_lds PARAMS ((char *)); |
| static void do_t_lsl PARAMS ((char *)); |
| static void do_t_lsr PARAMS ((char *)); |
| static void do_t_mov PARAMS ((char *)); |
| static void do_t_push_pop PARAMS ((char *)); |
| static void do_t_str PARAMS ((char *)); |
| static void do_t_strb PARAMS ((char *)); |
| static void do_t_strh PARAMS ((char *)); |
| static void do_t_sub PARAMS ((char *)); |
| static void do_t_swi PARAMS ((char *)); |
| static void do_t_adr PARAMS ((char *)); |
| |
| #define T_OPCODE_MUL 0x4340 |
| #define T_OPCODE_TST 0x4200 |
| #define T_OPCODE_CMN 0x42c0 |
| #define T_OPCODE_NEG 0x4240 |
| #define T_OPCODE_MVN 0x43c0 |
| |
| #define T_OPCODE_ADD_R3 0x1800 |
| #define T_OPCODE_SUB_R3 0x1a00 |
| #define T_OPCODE_ADD_HI 0x4400 |
| #define T_OPCODE_ADD_ST 0xb000 |
| #define T_OPCODE_SUB_ST 0xb080 |
| #define T_OPCODE_ADD_SP 0xa800 |
| #define T_OPCODE_ADD_PC 0xa000 |
| #define T_OPCODE_ADD_I8 0x3000 |
| #define T_OPCODE_SUB_I8 0x3800 |
| #define T_OPCODE_ADD_I3 0x1c00 |
| #define T_OPCODE_SUB_I3 0x1e00 |
| |
| #define T_OPCODE_ASR_R 0x4100 |
| #define T_OPCODE_LSL_R 0x4080 |
| #define T_OPCODE_LSR_R 0x40c0 |
| #define T_OPCODE_ASR_I 0x1000 |
| #define T_OPCODE_LSL_I 0x0000 |
| #define T_OPCODE_LSR_I 0x0800 |
| |
| #define T_OPCODE_MOV_I8 0x2000 |
| #define T_OPCODE_CMP_I8 0x2800 |
| #define T_OPCODE_CMP_LR 0x4280 |
| #define T_OPCODE_MOV_HR 0x4600 |
| #define T_OPCODE_CMP_HR 0x4500 |
| |
| #define T_OPCODE_LDR_PC 0x4800 |
| #define T_OPCODE_LDR_SP 0x9800 |
| #define T_OPCODE_STR_SP 0x9000 |
| #define T_OPCODE_LDR_IW 0x6800 |
| #define T_OPCODE_STR_IW 0x6000 |
| #define T_OPCODE_LDR_IH 0x8800 |
| #define T_OPCODE_STR_IH 0x8000 |
| #define T_OPCODE_LDR_IB 0x7800 |
| #define T_OPCODE_STR_IB 0x7000 |
| #define T_OPCODE_LDR_RW 0x5800 |
| #define T_OPCODE_STR_RW 0x5000 |
| #define T_OPCODE_LDR_RH 0x5a00 |
| #define T_OPCODE_STR_RH 0x5200 |
| #define T_OPCODE_LDR_RB 0x5c00 |
| #define T_OPCODE_STR_RB 0x5400 |
| |
| #define T_OPCODE_PUSH 0xb400 |
| #define T_OPCODE_POP 0xbc00 |
| |
| #define T_OPCODE_BRANCH 0xe7fe |
| |
| static int thumb_reg PARAMS ((char ** str, int hi_lo)); |
| |
| #define THUMB_SIZE 2 /* Size of thumb instruction. */ |
| #define THUMB_REG_LO 0x1 |
| #define THUMB_REG_HI 0x2 |
| #define THUMB_REG_ANY 0x3 |
| |
| #define THUMB_H1 0x0080 |
| #define THUMB_H2 0x0040 |
| |
| #define THUMB_ASR 0 |
| #define THUMB_LSL 1 |
| #define THUMB_LSR 2 |
| |
| #define THUMB_MOVE 0 |
| #define THUMB_COMPARE 1 |
| |
| #define THUMB_LOAD 0 |
| #define THUMB_STORE 1 |
| |
| #define THUMB_PP_PC_LR 0x0100 |
| |
| /* These three are used for immediate shifts, do not alter. */ |
| #define THUMB_WORD 2 |
| #define THUMB_HALFWORD 1 |
| #define THUMB_BYTE 0 |
| |
| struct thumb_opcode |
| { |
| /* Basic string to match. */ |
| CONST char * template; |
| |
| /* Basic instruction code. */ |
| unsigned long value; |
| |
| int size; |
| |
| /* Which CPU variants this exists for. */ |
| unsigned long variants; |
| |
| /* Function to call to parse args. */ |
| void (* parms) PARAMS ((char *)); |
| }; |
| |
| static CONST struct thumb_opcode tinsns[] = |
| { |
| {"adc", 0x4140, 2, ARM_EXT_THUMB, do_t_arit}, |
| {"add", 0x0000, 2, ARM_EXT_THUMB, do_t_add}, |
| {"and", 0x4000, 2, ARM_EXT_THUMB, do_t_arit}, |
| {"asr", 0x0000, 2, ARM_EXT_THUMB, do_t_asr}, |
| {"b", T_OPCODE_BRANCH, 2, ARM_EXT_THUMB, do_t_branch12}, |
| {"beq", 0xd0fe, 2, ARM_EXT_THUMB, do_t_branch9}, |
| {"bne", 0xd1fe, 2, ARM_EXT_THUMB, do_t_branch9}, |
| {"bcs", 0xd2fe, 2, ARM_EXT_THUMB, do_t_branch9}, |
| {"bhs", 0xd2fe, 2, ARM_EXT_THUMB, do_t_branch9}, |
| {"bcc", 0xd3fe, 2, ARM_EXT_THUMB, do_t_branch9}, |
| {"bul", 0xd3fe, 2, ARM_EXT_THUMB, do_t_branch9}, |
| {"blo", 0xd3fe, 2, ARM_EXT_THUMB, do_t_branch9}, |
| {"bmi", 0xd4fe, 2, ARM_EXT_THUMB, do_t_branch9}, |
| {"bpl", 0xd5fe, 2, ARM_EXT_THUMB, do_t_branch9}, |
| {"bvs", 0xd6fe, 2, ARM_EXT_THUMB, do_t_branch9}, |
| {"bvc", 0xd7fe, 2, ARM_EXT_THUMB, do_t_branch9}, |
| {"bhi", 0xd8fe, 2, ARM_EXT_THUMB, do_t_branch9}, |
| {"bls", 0xd9fe, 2, ARM_EXT_THUMB, do_t_branch9}, |
| {"bge", 0xdafe, 2, ARM_EXT_THUMB, do_t_branch9}, |
| {"blt", 0xdbfe, 2, ARM_EXT_THUMB, do_t_branch9}, |
| {"bgt", 0xdcfe, 2, ARM_EXT_THUMB, do_t_branch9}, |
| {"ble", 0xddfe, 2, ARM_EXT_THUMB, do_t_branch9}, |
| {"bal", 0xdefe, 2, ARM_EXT_THUMB, do_t_branch9}, |
| {"bic", 0x4380, 2, ARM_EXT_THUMB, do_t_arit}, |
| {"bl", 0xf7fffffe, 4, ARM_EXT_THUMB, do_t_branch23}, |
| {"blx", 0, 0, ARM_EXT_V5, do_t_blx}, |
| {"bkpt", 0xbe00, 2, ARM_EXT_V5, do_t_bkpt}, |
| {"bx", 0x4700, 2, ARM_EXT_THUMB, do_t_bx}, |
| {"cmn", T_OPCODE_CMN, 2, ARM_EXT_THUMB, do_t_arit}, |
| {"cmp", 0x0000, 2, ARM_EXT_THUMB, do_t_compare}, |
| {"eor", 0x4040, 2, ARM_EXT_THUMB, do_t_arit}, |
| {"ldmia", 0xc800, 2, ARM_EXT_THUMB, do_t_ldmstm}, |
| {"ldr", 0x0000, 2, ARM_EXT_THUMB, do_t_ldr}, |
| {"ldrb", 0x0000, 2, ARM_EXT_THUMB, do_t_ldrb}, |
| {"ldrh", 0x0000, 2, ARM_EXT_THUMB, do_t_ldrh}, |
| {"ldrsb", 0x5600, 2, ARM_EXT_THUMB, do_t_lds}, |
| {"ldrsh", 0x5e00, 2, ARM_EXT_THUMB, do_t_lds}, |
| {"ldsb", 0x5600, 2, ARM_EXT_THUMB, do_t_lds}, |
| {"ldsh", 0x5e00, 2, ARM_EXT_THUMB, do_t_lds}, |
| {"lsl", 0x0000, 2, ARM_EXT_THUMB, do_t_lsl}, |
| {"lsr", 0x0000, 2, ARM_EXT_THUMB, do_t_lsr}, |
| {"mov", 0x0000, 2, ARM_EXT_THUMB, do_t_mov}, |
| {"mul", T_OPCODE_MUL, 2, ARM_EXT_THUMB, do_t_arit}, |
| {"mvn", T_OPCODE_MVN, 2, ARM_EXT_THUMB, do_t_arit}, |
| {"neg", T_OPCODE_NEG, 2, ARM_EXT_THUMB, do_t_arit}, |
| {"orr", 0x4300, 2, ARM_EXT_THUMB, do_t_arit}, |
| {"pop", 0xbc00, 2, ARM_EXT_THUMB, do_t_push_pop}, |
| {"push", 0xb400, 2, ARM_EXT_THUMB, do_t_push_pop}, |
| {"ror", 0x41c0, 2, ARM_EXT_THUMB, do_t_arit}, |
| {"sbc", 0x4180, 2, ARM_EXT_THUMB, do_t_arit}, |
| {"stmia", 0xc000, 2, ARM_EXT_THUMB, do_t_ldmstm}, |
| {"str", 0x0000, 2, ARM_EXT_THUMB, do_t_str}, |
| {"strb", 0x0000, 2, ARM_EXT_THUMB, do_t_strb}, |
| {"strh", 0x0000, 2, ARM_EXT_THUMB, do_t_strh}, |
| {"swi", 0xdf00, 2, ARM_EXT_THUMB, do_t_swi}, |
| {"sub", 0x0000, 2, ARM_EXT_THUMB, do_t_sub}, |
| {"tst", T_OPCODE_TST, 2, ARM_EXT_THUMB, do_t_arit}, |
| /* Pseudo ops: */ |
| {"adr", 0x0000, 2, ARM_EXT_THUMB, do_t_adr}, |
| {"nop", 0x46C0, 2, ARM_EXT_THUMB, do_t_nop}, /* mov r8,r8 */ |
| }; |
| |
| struct reg_entry |
| { |
| CONST char * name; |
| int number; |
| }; |
| |
| #define int_register(reg) ((reg) >= 0 && (reg) <= 15) |
| #define cp_register(reg) ((reg) >= 32 && (reg) <= 47) |
| #define fp_register(reg) ((reg) >= 16 && (reg) <= 23) |
| |
| #define REG_PC 15 |
| #define REG_LR 14 |
| #define REG_SP 13 |
| |
| /* These are the standard names. Users can add aliases with .req. */ |
| static CONST struct reg_entry reg_table[] = |
| { |
| /* Processor Register Numbers. */ |
| {"r0", 0}, {"r1", 1}, {"r2", 2}, {"r3", 3}, |
| {"r4", 4}, {"r5", 5}, {"r6", 6}, {"r7", 7}, |
| {"r8", 8}, {"r9", 9}, {"r10", 10}, {"r11", 11}, |
| {"r12", 12}, {"r13", REG_SP},{"r14", REG_LR},{"r15", REG_PC}, |
| /* APCS conventions. */ |
| {"a1", 0}, {"a2", 1}, {"a3", 2}, {"a4", 3}, |
| {"v1", 4}, {"v2", 5}, {"v3", 6}, {"v4", 7}, {"v5", 8}, |
| {"v6", 9}, {"sb", 9}, {"v7", 10}, {"sl", 10}, |
| {"fp", 11}, {"ip", 12}, {"sp", REG_SP},{"lr", REG_LR},{"pc", REG_PC}, |
| /* ATPCS additions to APCS conventions. */ |
| {"wr", 7}, {"v8", 11}, |
| /* FP Registers. */ |
| {"f0", 16}, {"f1", 17}, {"f2", 18}, {"f3", 19}, |
| {"f4", 20}, {"f5", 21}, {"f6", 22}, {"f7", 23}, |
| {"c0", 32}, {"c1", 33}, {"c2", 34}, {"c3", 35}, |
| {"c4", 36}, {"c5", 37}, {"c6", 38}, {"c7", 39}, |
| {"c8", 40}, {"c9", 41}, {"c10", 42}, {"c11", 43}, |
| {"c12", 44}, {"c13", 45}, {"c14", 46}, {"c15", 47}, |
| {"cr0", 32}, {"cr1", 33}, {"cr2", 34}, {"cr3", 35}, |
| {"cr4", 36}, {"cr5", 37}, {"cr6", 38}, {"cr7", 39}, |
| {"cr8", 40}, {"cr9", 41}, {"cr10", 42}, {"cr11", 43}, |
| {"cr12", 44}, {"cr13", 45}, {"cr14", 46}, {"cr15", 47}, |
| /* ATPCS additions to float register names. */ |
| {"s0",16}, {"s1",17}, {"s2",18}, {"s3",19}, |
| {"s4",20}, {"s5",21}, {"s6",22}, {"s7",23}, |
| {"d0",16}, {"d1",17}, {"d2",18}, {"d3",19}, |
| {"d4",20}, {"d5",21}, {"d6",22}, {"d7",23}, |
| /* FIXME: At some point we need to add VFP register names. */ |
| /* Array terminator. */ |
| {NULL, 0} |
| }; |
| |
| #define BAD_ARGS _("Bad arguments to instruction") |
| #define BAD_PC _("r15 not allowed here") |
| #define BAD_FLAGS _("Instruction should not have flags") |
| #define BAD_COND _("Instruction is not conditional") |
| #define ERR_NO_ACCUM _("acc0 expected") |
| |
| static struct hash_control * arm_ops_hsh = NULL; |
| static struct hash_control * arm_tops_hsh = NULL; |
| static struct hash_control * arm_cond_hsh = NULL; |
| static struct hash_control * arm_shift_hsh = NULL; |
| static struct hash_control * arm_reg_hsh = NULL; |
| static struct hash_control * arm_psr_hsh = NULL; |
| |
| /* This table describes all the machine specific pseudo-ops the assembler |
| has to support. The fields are: |
| pseudo-op name without dot |
| function to call to execute this pseudo-op |
| Integer arg to pass to the function. */ |
| |
| static void s_req PARAMS ((int)); |
| static void s_align PARAMS ((int)); |
| static void s_bss PARAMS ((int)); |
| static void s_even PARAMS ((int)); |
| static void s_ltorg PARAMS ((int)); |
| static void s_arm PARAMS ((int)); |
| static void s_thumb PARAMS ((int)); |
| static void s_code PARAMS ((int)); |
| static void s_force_thumb PARAMS ((int)); |
| static void s_thumb_func PARAMS ((int)); |
| static void s_thumb_set PARAMS ((int)); |
| static void arm_s_text PARAMS ((int)); |
| static void arm_s_data PARAMS ((int)); |
| #ifdef OBJ_ELF |
| static void arm_s_section PARAMS ((int)); |
| static void s_arm_elf_cons PARAMS ((int)); |
| #endif |
| |
| static int my_get_expression PARAMS ((expressionS *, char **)); |
| |
| CONST pseudo_typeS md_pseudo_table[] = |
| { |
| /* Never called becasue '.req' does not start line. */ |
| { "req", s_req, 0 }, |
| { "bss", s_bss, 0 }, |
| { "align", s_align, 0 }, |
| { "arm", s_arm, 0 }, |
| { "thumb", s_thumb, 0 }, |
| { "code", s_code, 0 }, |
| { "force_thumb", s_force_thumb, 0 }, |
| { "thumb_func", s_thumb_func, 0 }, |
| { "thumb_set", s_thumb_set, 0 }, |
| { "even", s_even, 0 }, |
| { "ltorg", s_ltorg, 0 }, |
| { "pool", s_ltorg, 0 }, |
| /* Allow for the effect of section changes. */ |
| { "text", arm_s_text, 0 }, |
| { "data", arm_s_data, 0 }, |
| #ifdef OBJ_ELF |
| { "section", arm_s_section, 0 }, |
| { "section.s", arm_s_section, 0 }, |
| { "sect", arm_s_section, 0 }, |
| { "sect.s", arm_s_section, 0 }, |
| { "word", s_arm_elf_cons, 4 }, |
| { "long", s_arm_elf_cons, 4 }, |
| { "file", dwarf2_directive_file, 0 }, |
| { "loc", dwarf2_directive_loc, 0 }, |
| #else |
| { "word", cons, 4}, |
| #endif |
| { "extend", float_cons, 'x' }, |
| { "ldouble", float_cons, 'x' }, |
| { "packed", float_cons, 'p' }, |
| { 0, 0, 0 } |
| }; |
| |
| /* Stuff needed to resolve the label ambiguity |
| As: |
| ... |
| label: <insn> |
| may differ from: |
| ... |
| label: |
| <insn> |
| */ |
| |
| symbolS * last_label_seen; |
| static int label_is_thumb_function_name = false; |
| |
| /* Literal stuff. */ |
| |
| #define MAX_LITERAL_POOL_SIZE 1024 |
| |
| typedef struct literalS |
| { |
| struct expressionS exp; |
| struct arm_it * inst; |
| } literalT; |
| |
| literalT literals[MAX_LITERAL_POOL_SIZE]; |
| |
| /* Next free entry in the pool. */ |
| int next_literal_pool_place = 0; |
| |
| /* Next literal pool number. */ |
| int lit_pool_num = 1; |
| |
| symbolS * current_poolP = NULL; |
| |
| static int |
| add_to_lit_pool () |
| { |
| int lit_count = 0; |
| |
| if (current_poolP == NULL) |
| current_poolP = symbol_create (FAKE_LABEL_NAME, undefined_section, |
| (valueT) 0, &zero_address_frag); |
| |
| /* Check if this literal value is already in the pool: */ |
| while (lit_count < next_literal_pool_place) |
| { |
| if (literals[lit_count].exp.X_op == inst.reloc.exp.X_op |
| && inst.reloc.exp.X_op == O_constant |
| && (literals[lit_count].exp.X_add_number |
| == inst.reloc.exp.X_add_number) |
| && literals[lit_count].exp.X_unsigned == inst.reloc.exp.X_unsigned) |
| break; |
| |
| if (literals[lit_count].exp.X_op == inst.reloc.exp.X_op |
| && inst.reloc.exp.X_op == O_symbol |
| && (literals[lit_count].exp.X_add_number |
| == inst.reloc.exp.X_add_number) |
| && (literals[lit_count].exp.X_add_symbol |
| == inst.reloc.exp.X_add_symbol) |
| && (literals[lit_count].exp.X_op_symbol |
| == inst.reloc.exp.X_op_symbol)) |
| break; |
| |
| lit_count++; |
| } |
| |
| if (lit_count == next_literal_pool_place) /* New entry. */ |
| { |
| if (next_literal_pool_place >= MAX_LITERAL_POOL_SIZE) |
| { |
| inst.error = _("Literal Pool Overflow"); |
| return FAIL; |
| } |
| |
| literals[next_literal_pool_place].exp = inst.reloc.exp; |
| lit_count = next_literal_pool_place++; |
| } |
| |
| inst.reloc.exp.X_op = O_symbol; |
| inst.reloc.exp.X_add_number = (lit_count) * 4 - 8; |
| inst.reloc.exp.X_add_symbol = current_poolP; |
| |
| return SUCCESS; |
| } |
| |
| /* Can't use symbol_new here, so have to create a symbol and then at |
| a later date assign it a value. Thats what these functions do. */ |
| |
| static void |
| symbol_locate (symbolP, name, segment, valu, frag) |
| symbolS * symbolP; |
| CONST char * name; /* It is copied, the caller can modify. */ |
| segT segment; /* Segment identifier (SEG_<something>). */ |
| valueT valu; /* Symbol value. */ |
| fragS * frag; /* Associated fragment. */ |
| { |
| unsigned int name_length; |
| char * preserved_copy_of_name; |
| |
| name_length = strlen (name) + 1; /* +1 for \0. */ |
| obstack_grow (¬es, name, name_length); |
| preserved_copy_of_name = obstack_finish (¬es); |
| #ifdef STRIP_UNDERSCORE |
| if (preserved_copy_of_name[0] == '_') |
| preserved_copy_of_name++; |
| #endif |
| |
| #ifdef tc_canonicalize_symbol_name |
| preserved_copy_of_name = |
| tc_canonicalize_symbol_name (preserved_copy_of_name); |
| #endif |
| |
| S_SET_NAME (symbolP, preserved_copy_of_name); |
| |
| S_SET_SEGMENT (symbolP, segment); |
| S_SET_VALUE (symbolP, valu); |
| symbol_clear_list_pointers(symbolP); |
| |
| symbol_set_frag (symbolP, frag); |
| |
| /* Link to end of symbol chain. */ |
| { |
| extern int symbol_table_frozen; |
| if (symbol_table_frozen) |
| abort (); |
| } |
| |
| symbol_append (symbolP, symbol_lastP, & symbol_rootP, & symbol_lastP); |
| |
| obj_symbol_new_hook (symbolP); |
| |
| #ifdef tc_symbol_new_hook |
| tc_symbol_new_hook (symbolP); |
| #endif |
| |
| #ifdef DEBUG_SYMS |
| verify_symbol_chain (symbol_rootP, symbol_lastP); |
| #endif /* DEBUG_SYMS */ |
| } |
| |
| /* Check that an immediate is valid. |
| If so, convert it to the right format. */ |
| |
| static unsigned int |
| validate_immediate (val) |
| unsigned int val; |
| { |
| unsigned int a; |
| unsigned int i; |
| |
| #define rotate_left(v, n) (v << n | v >> (32 - n)) |
| |
| for (i = 0; i < 32; i += 2) |
| if ((a = rotate_left (val, i)) <= 0xff) |
| return a | (i << 7); /* 12-bit pack: [shift-cnt,const]. */ |
| |
| return FAIL; |
| } |
| |
| /* Check to see if an immediate can be computed as two seperate immediate |
| values, added together. We already know that this value cannot be |
| computed by just one ARM instruction. */ |
| |
| static unsigned int |
| validate_immediate_twopart (val, highpart) |
| unsigned int val; |
| unsigned int * highpart; |
| { |
| unsigned int a; |
| unsigned int i; |
| |
| for (i = 0; i < 32; i += 2) |
| if (((a = rotate_left (val, i)) & 0xff) != 0) |
| { |
| if (a & 0xff00) |
| { |
| if (a & ~ 0xffff) |
| continue; |
| * highpart = (a >> 8) | ((i + 24) << 7); |
| } |
| else if (a & 0xff0000) |
| { |
| if (a & 0xff000000) |
| continue; |
| * highpart = (a >> 16) | ((i + 16) << 7); |
| } |
| else |
| { |
| assert (a & 0xff000000); |
| * highpart = (a >> 24) | ((i + 8) << 7); |
| } |
| |
| return (a & 0xff) | (i << 7); |
| } |
| |
| return FAIL; |
| } |
| |
| static int |
| validate_offset_imm (val, hwse) |
| unsigned int val; |
| int hwse; |
| { |
| if ((hwse && val > 255) || val > 4095) |
| return FAIL; |
| return val; |
| } |
| |
| static void |
| s_req (a) |
| int a ATTRIBUTE_UNUSED; |
| { |
| as_bad (_("Invalid syntax for .req directive.")); |
| } |
| |
| static void |
| s_bss (ignore) |
| int ignore ATTRIBUTE_UNUSED; |
| { |
| /* We don't support putting frags in the BSS segment, we fake it by |
| marking in_bss, then looking at s_skip for clues. */ |
| subseg_set (bss_section, 0); |
| demand_empty_rest_of_line (); |
| } |
| |
| static void |
| s_even (ignore) |
| int ignore ATTRIBUTE_UNUSED; |
| { |
| /* Never make frag if expect extra pass. */ |
| if (!need_pass_2) |
| frag_align (1, 0, 0); |
| |
| record_alignment (now_seg, 1); |
| |
| demand_empty_rest_of_line (); |
| } |
| |
| static void |
| s_ltorg (ignored) |
| int ignored ATTRIBUTE_UNUSED; |
| { |
| int lit_count = 0; |
| char sym_name[20]; |
| |
| if (current_poolP == NULL) |
| return; |
| |
| /* Align pool as you have word accesses. |
| Only make a frag if we have to. */ |
| if (!need_pass_2) |
| frag_align (2, 0, 0); |
| |
| record_alignment (now_seg, 2); |
| |
| sprintf (sym_name, "$$lit_\002%x", lit_pool_num++); |
| |
| symbol_locate (current_poolP, sym_name, now_seg, |
| (valueT) frag_now_fix (), frag_now); |
| symbol_table_insert (current_poolP); |
| |
| ARM_SET_THUMB (current_poolP, thumb_mode); |
| |
| #if defined OBJ_COFF || defined OBJ_ELF |
| ARM_SET_INTERWORK (current_poolP, support_interwork); |
| #endif |
| |
| while (lit_count < next_literal_pool_place) |
| /* First output the expression in the instruction to the pool. */ |
| emit_expr (&(literals[lit_count++].exp), 4); /* .word */ |
| |
| next_literal_pool_place = 0; |
| current_poolP = NULL; |
| } |
| |
| /* Same as s_align_ptwo but align 0 => align 2. */ |
| |
| static void |
| s_align (unused) |
| int unused ATTRIBUTE_UNUSED; |
| { |
| register int temp; |
| register long temp_fill; |
| long max_alignment = 15; |
| |
| temp = get_absolute_expression (); |
| if (temp > max_alignment) |
| as_bad (_("Alignment too large: %d. assumed."), temp = max_alignment); |
| else if (temp < 0) |
| { |
| as_bad (_("Alignment negative. 0 assumed.")); |
| temp = 0; |
| } |
| |
| if (*input_line_pointer == ',') |
| { |
| input_line_pointer++; |
| temp_fill = get_absolute_expression (); |
| } |
| else |
| temp_fill = 0; |
| |
| if (!temp) |
| temp = 2; |
| |
| /* Only make a frag if we HAVE to. */ |
| if (temp && !need_pass_2) |
| frag_align (temp, (int) temp_fill, 0); |
| demand_empty_rest_of_line (); |
| |
| record_alignment (now_seg, temp); |
| } |
| |
| static void |
| s_force_thumb (ignore) |
| int ignore ATTRIBUTE_UNUSED; |
| { |
| /* If we are not already in thumb mode go into it, EVEN if |
| the target processor does not support thumb instructions. |
| This is used by gcc/config/arm/lib1funcs.asm for example |
| to compile interworking support functions even if the |
| target processor should not support interworking. */ |
| if (! thumb_mode) |
| { |
| thumb_mode = 2; |
| |
| record_alignment (now_seg, 1); |
| } |
| |
| demand_empty_rest_of_line (); |
| } |
| |
| static void |
| s_thumb_func (ignore) |
| int ignore ATTRIBUTE_UNUSED; |
| { |
| if (! thumb_mode) |
| opcode_select (16); |
| |
| /* The following label is the name/address of the start of a Thumb function. |
| We need to know this for the interworking support. */ |
| label_is_thumb_function_name = true; |
| |
| demand_empty_rest_of_line (); |
| } |
| |
| /* Perform a .set directive, but also mark the alias as |
| being a thumb function. */ |
| |
| static void |
| s_thumb_set (equiv) |
| int equiv; |
| { |
| /* XXX the following is a duplicate of the code for s_set() in read.c |
| We cannot just call that code as we need to get at the symbol that |
| is created. */ |
| register char * name; |
| register char delim; |
| register char * end_name; |
| register symbolS * symbolP; |
| |
| /* Especial apologies for the random logic: |
| This just grew, and could be parsed much more simply! |
| Dean - in haste. */ |
| name = input_line_pointer; |
| delim = get_symbol_end (); |
| end_name = input_line_pointer; |
| *end_name = delim; |
| |
| SKIP_WHITESPACE (); |
| |
| if (*input_line_pointer != ',') |
| { |
| *end_name = 0; |
| as_bad (_("Expected comma after name \"%s\""), name); |
| *end_name = delim; |
| ignore_rest_of_line (); |
| return; |
| } |
| |
| input_line_pointer++; |
| *end_name = 0; |
| |
| if (name[0] == '.' && name[1] == '\0') |
| { |
| /* XXX - this should not happen to .thumb_set. */ |
| abort (); |
| } |
| |
| if ((symbolP = symbol_find (name)) == NULL |
| && (symbolP = md_undefined_symbol (name)) == NULL) |
| { |
| #ifndef NO_LISTING |
| /* When doing symbol listings, play games with dummy fragments living |
| outside the normal fragment chain to record the file and line info |
| for this symbol. */ |
| if (listing & LISTING_SYMBOLS) |
| { |
| extern struct list_info_struct * listing_tail; |
| fragS * dummy_frag = (fragS *) xmalloc (sizeof (fragS)); |
| |
| memset (dummy_frag, 0, sizeof (fragS)); |
| dummy_frag->fr_type = rs_fill; |
| dummy_frag->line = listing_tail; |
| symbolP = symbol_new (name, undefined_section, 0, dummy_frag); |
| dummy_frag->fr_symbol = symbolP; |
| } |
| else |
| #endif |
| symbolP = symbol_new (name, undefined_section, 0, &zero_address_frag); |
| |
| #ifdef OBJ_COFF |
| /* "set" symbols are local unless otherwise specified. */ |
| SF_SET_LOCAL (symbolP); |
| #endif /* OBJ_COFF */ |
| } /* Make a new symbol. */ |
| |
| symbol_table_insert (symbolP); |
| |
| * end_name = delim; |
| |
| if (equiv |
| && S_IS_DEFINED (symbolP) |
| && S_GET_SEGMENT (symbolP) != reg_section) |
| as_bad (_("symbol `%s' already defined"), S_GET_NAME (symbolP)); |
| |
| pseudo_set (symbolP); |
| |
| demand_empty_rest_of_line (); |
| |
| /* XXX Now we come to the Thumb specific bit of code. */ |
| |
| THUMB_SET_FUNC (symbolP, 1); |
| ARM_SET_THUMB (symbolP, 1); |
| #if defined OBJ_ELF || defined OBJ_COFF |
| ARM_SET_INTERWORK (symbolP, support_interwork); |
| #endif |
| } |
| |
| /* If we change section we must dump the literal pool first. */ |
| |
| static void |
| arm_s_text (ignore) |
| int ignore; |
| { |
| if (now_seg != text_section) |
| s_ltorg (0); |
| |
| #ifdef OBJ_ELF |
| obj_elf_text (ignore); |
| #else |
| s_text (ignore); |
| #endif |
| } |
| |
| static void |
| arm_s_data (ignore) |
| int ignore; |
| { |
| if (flag_readonly_data_in_text) |
| { |
| if (now_seg != text_section) |
| s_ltorg (0); |
| } |
| else if (now_seg != data_section) |
| s_ltorg (0); |
| |
| #ifdef OBJ_ELF |
| obj_elf_data (ignore); |
| #else |
| s_data (ignore); |
| #endif |
| } |
| |
| #ifdef OBJ_ELF |
| static void |
| arm_s_section (ignore) |
| int ignore; |
| { |
| s_ltorg (0); |
| |
| obj_elf_section (ignore); |
| } |
| #endif |
| |
| static void |
| opcode_select (width) |
| int width; |
| { |
| switch (width) |
| { |
| case 16: |
| if (! thumb_mode) |
| { |
| if (! (cpu_variant & ARM_EXT_THUMB)) |
| as_bad (_("selected processor does not support THUMB opcodes")); |
| |
| thumb_mode = 1; |
| /* No need to force the alignment, since we will have been |
| coming from ARM mode, which is word-aligned. */ |
| record_alignment (now_seg, 1); |
| } |
| break; |
| |
| case 32: |
| if (thumb_mode) |
| { |
| if ((cpu_variant & ARM_ANY) == ARM_EXT_THUMB) |
| as_bad (_("selected processor does not support ARM opcodes")); |
| |
| thumb_mode = 0; |
| |
| if (!need_pass_2) |
| frag_align (2, 0, 0); |
| |
| record_alignment (now_seg, 1); |
| } |
| break; |
| |
| default: |
| as_bad (_("invalid instruction size selected (%d)"), width); |
| } |
| } |
| |
| static void |
| s_arm (ignore) |
| int ignore ATTRIBUTE_UNUSED; |
| { |
| opcode_select (32); |
| demand_empty_rest_of_line (); |
| } |
| |
| static void |
| s_thumb (ignore) |
| int ignore ATTRIBUTE_UNUSED; |
| { |
| opcode_select (16); |
| demand_empty_rest_of_line (); |
| } |
| |
| static void |
| s_code (unused) |
| int unused ATTRIBUTE_UNUSED; |
| { |
| register int temp; |
| |
| temp = get_absolute_expression (); |
| switch (temp) |
| { |
| case 16: |
| case 32: |
| opcode_select (temp); |
| break; |
| |
| default: |
| as_bad (_("invalid operand to .code directive (%d) (expecting 16 or 32)"), temp); |
| } |
| } |
| |
| static void |
| end_of_line (str) |
| char * str; |
| { |
| skip_whitespace (str); |
| |
| if (* str != '\0') |
| inst.error = _("Garbage following instruction"); |
| } |
| |
| static int |
| skip_past_comma (str) |
| char ** str; |
| { |
| char * p = * str, c; |
| int comma = 0; |
| |
| while ((c = *p) == ' ' || c == ',') |
| { |
| p++; |
| if (c == ',' && comma++) |
| return FAIL; |
| } |
| |
| if (c == '\0') |
| return FAIL; |
| |
| *str = p; |
| return comma ? SUCCESS : FAIL; |
| } |
| |
| /* A standard register must be given at this point. |
| SHIFT is the place to put it in inst.instruction. |
| Restores input start point on error. |
| Returns the reg#, or FAIL. */ |
| |
| static int |
| reg_required_here (str, shift) |
| char ** str; |
| int shift; |
| { |
| static char buff [128]; /* XXX */ |
| int reg; |
| char * start = * str; |
| |
| if ((reg = arm_reg_parse (str)) != FAIL && int_register (reg)) |
| { |
| if (shift >= 0) |
| inst.instruction |= reg << shift; |
| return reg; |
| } |
| |
| /* Restore the start point, we may have got a reg of the wrong class. */ |
| *str = start; |
| |
| /* In the few cases where we might be able to accept something else |
| this error can be overridden. */ |
| sprintf (buff, _("Register expected, not '%.100s'"), start); |
| inst.error = buff; |
| |
| return FAIL; |
| } |
| |
| static CONST struct asm_psr * |
| arm_psr_parse (ccp) |
| register char ** ccp; |
| { |
| char * start = * ccp; |
| char c; |
| char * p; |
| CONST struct asm_psr * psr; |
| |
| p = start; |
| |
| /* Skip to the end of the next word in the input stream. */ |
| do |
| { |
| c = *p++; |
| } |
| while (isalpha (c) || c == '_'); |
| |
| /* Terminate the word. */ |
| *--p = 0; |
| |
| /* CPSR's and SPSR's can now be lowercase. This is just a convenience |
| feature for ease of use and backwards compatibility. */ |
| if (!strncmp (start, "cpsr", 4)) |
| strncpy (start, "CPSR", 4); |
| else if (!strncmp (start, "spsr", 4)) |
| strncpy (start, "SPSR", 4); |
| |
| /* Now locate the word in the psr hash table. */ |
| psr = (CONST struct asm_psr *) hash_find (arm_psr_hsh, start); |
| |
| /* Restore the input stream. */ |
| *p = c; |
| |
| /* If we found a valid match, advance the |
| stream pointer past the end of the word. */ |
| *ccp = p; |
| |
| return psr; |
| } |
| |
| /* Parse the input looking for a PSR flag. */ |
| |
| static int |
| psr_required_here (str) |
| char ** str; |
| { |
| char * start = * str; |
| CONST struct asm_psr * psr; |
| |
| psr = arm_psr_parse (str); |
| |
| if (psr) |
| { |
| /* If this is the SPSR that is being modified, set the R bit. */ |
| if (! psr->cpsr) |
| inst.instruction |= SPSR_BIT; |
| |
| /* Set the psr flags in the MSR instruction. */ |
| inst.instruction |= psr->field << PSR_SHIFT; |
| |
| return SUCCESS; |
| } |
| |
| /* In the few cases where we might be able to accept |
| something else this error can be overridden. */ |
| inst.error = _("flag for {c}psr instruction expected"); |
| |
| /* Restore the start point. */ |
| *str = start; |
| return FAIL; |
| } |
| |
| static int |
| co_proc_number (str) |
| char ** str; |
| { |
| int processor, pchar; |
| |
| skip_whitespace (* str); |
| |
| /* The data sheet seems to imply that just a number on its own is valid |
| here, but the RISC iX assembler seems to accept a prefix 'p'. We will |
| accept either. */ |
| if (**str == 'p' || **str == 'P') |
| (*str)++; |
| |
| pchar = *(*str)++; |
| if (pchar >= '0' && pchar <= '9') |
| { |
| processor = pchar - '0'; |
| if (**str >= '0' && **str <= '9') |
| { |
| processor = processor * 10 + *(*str)++ - '0'; |
| if (processor > 15) |
| { |
| inst.error = _("Illegal co-processor number"); |
| return FAIL; |
| } |
| } |
| } |
| else |
| { |
| inst.error = _("Bad or missing co-processor number"); |
| return FAIL; |
| } |
| |
| inst.instruction |= processor << 8; |
| return SUCCESS; |
| } |
| |
| static int |
| cp_opc_expr (str, where, length) |
| char ** str; |
| int where; |
| int length; |
| { |
| expressionS expr; |
| |
| skip_whitespace (* str); |
| |
| memset (&expr, '\0', sizeof (expr)); |
| |
| if (my_get_expression (&expr, str)) |
| return FAIL; |
| if (expr.X_op != O_constant) |
| { |
| inst.error = _("bad or missing expression"); |
| return FAIL; |
| } |
| |
| if ((expr.X_add_number & ((1 << length) - 1)) != expr.X_add_number) |
| { |
| inst.error = _("immediate co-processor expression too large"); |
| return FAIL; |
| } |
| |
| inst.instruction |= expr.X_add_number << where; |
| return SUCCESS; |
| } |
| |
| static int |
| cp_reg_required_here (str, where) |
| char ** str; |
| int where; |
| { |
| int reg; |
| char * start = *str; |
| |
| if ((reg = arm_reg_parse (str)) != FAIL && cp_register (reg)) |
| { |
| reg &= 15; |
| inst.instruction |= reg << where; |
| return reg; |
| } |
| |
| /* In the few cases where we might be able to accept something else |
| this error can be overridden. */ |
| inst.error = _("Co-processor register expected"); |
| |
| /* Restore the start point. */ |
| *str = start; |
| return FAIL; |
| } |
| |
| static int |
| fp_reg_required_here (str, where) |
| char ** str; |
| int where; |
| { |
| int reg; |
| char * start = * str; |
| |
| if ((reg = arm_reg_parse (str)) != FAIL && fp_register (reg)) |
| { |
| reg &= 7; |
| inst.instruction |= reg << where; |
| return reg; |
| } |
| |
| /* In the few cases where we might be able to accept something else |
| this error can be overridden. */ |
| inst.error = _("Floating point register expected"); |
| |
| /* Restore the start point. */ |
| *str = start; |
| return FAIL; |
| } |
| |
| static int |
| cp_address_offset (str) |
| char ** str; |
| { |
| int offset; |
| |
| skip_whitespace (* str); |
| |
| if (! is_immediate_prefix (**str)) |
| { |
| inst.error = _("immediate expression expected"); |
| return FAIL; |
| } |
| |
| (*str)++; |
| |
| if (my_get_expression (& inst.reloc.exp, str)) |
| return FAIL; |
| |
| if (inst.reloc.exp.X_op == O_constant) |
| { |
| offset = inst.reloc.exp.X_add_number; |
| |
| if (offset & 3) |
| { |
| inst.error = _("co-processor address must be word aligned"); |
| return FAIL; |
| } |
| |
| if (offset > 1023 || offset < -1023) |
| { |
| inst.error = _("offset too large"); |
| return FAIL; |
| } |
| |
| if (offset >= 0) |
| inst.instruction |= INDEX_UP; |
| else |
| offset = -offset; |
| |
| inst.instruction |= offset >> 2; |
| } |
| else |
| inst.reloc.type = BFD_RELOC_ARM_CP_OFF_IMM; |
| |
| return SUCCESS; |
| } |
| |
| static int |
| cp_address_required_here (str) |
| char ** str; |
| { |
| char * p = * str; |
| int pre_inc = 0; |
| int write_back = 0; |
| |
| if (*p == '[') |
| { |
| int reg; |
| |
| p++; |
| skip_whitespace (p); |
| |
| if ((reg = reg_required_here (& p, 16)) == FAIL) |
| return FAIL; |
| |
| skip_whitespace (p); |
| |
| if (*p == ']') |
| { |
| p++; |
| |
| if (skip_past_comma (& p) == SUCCESS) |
| { |
| /* [Rn], #expr */ |
| write_back = WRITE_BACK; |
| |
| if (reg == REG_PC) |
| { |
| inst.error = _("pc may not be used in post-increment"); |
| return FAIL; |
| } |
| |
| if (cp_address_offset (& p) == FAIL) |
| return FAIL; |
| } |
| else |
| pre_inc = PRE_INDEX | INDEX_UP; |
| } |
| else |
| { |
| /* '['Rn, #expr']'[!] */ |
| |
| if (skip_past_comma (& p) == FAIL) |
| { |
| inst.error = _("pre-indexed expression expected"); |
| return FAIL; |
| } |
| |
| pre_inc = PRE_INDEX; |
| |
| if (cp_address_offset (& p) == FAIL) |
| return FAIL; |
| |
| skip_whitespace (p); |
| |
| if (*p++ != ']') |
| { |
| inst.error = _("missing ]"); |
| return FAIL; |
| } |
| |
| skip_whitespace (p); |
| |
| if (*p == '!') |
| { |
| if (reg == REG_PC) |
| { |
| inst.error = _("pc may not be used with write-back"); |
| return FAIL; |
| } |
| |
| p++; |
| write_back = WRITE_BACK; |
| } |
| } |
| } |
| else |
| { |
| if (my_get_expression (&inst.reloc.exp, &p)) |
| return FAIL; |
| |
| inst.reloc.type = BFD_RELOC_ARM_CP_OFF_IMM; |
| inst.reloc.exp.X_add_number -= 8; /* PC rel adjust. */ |
| inst.reloc.pc_rel = 1; |
| inst.instruction |= (REG_PC << 16); |
| pre_inc = PRE_INDEX; |
| } |
| |
| inst.instruction |= write_back | pre_inc; |
| *str = p; |
| return SUCCESS; |
| } |
| |
| static void |
| do_nop (str, flags) |
| char * str; |
| unsigned long flags; |
| { |
| /* Do nothing really. */ |
| inst.instruction |= flags; /* This is pointless. */ |
| end_of_line (str); |
| return; |
| } |
| |
| static void |
| do_mrs (str, flags) |
| char *str; |
| unsigned long flags; |
| { |
| int skip = 0; |
| |
| /* Only one syntax. */ |
| skip_whitespace (str); |
| |
| if (reg_required_here (&str, 12) == FAIL) |
| { |
| inst.error = BAD_ARGS; |
| return; |
| } |
| |
| if (skip_past_comma (&str) == FAIL) |
| { |
| inst.error = _("comma expected after register name"); |
| return; |
| } |
| |
| skip_whitespace (str); |
| |
| if ( strcmp (str, "CPSR") == 0 |
| || strcmp (str, "SPSR") == 0 |
| /* Lower case versions for backwards compatability. */ |
| || strcmp (str, "cpsr") == 0 |
| || strcmp (str, "spsr") == 0) |
| skip = 4; |
| |
| /* This is for backwards compatability with older toolchains. */ |
| else if ( strcmp (str, "cpsr_all") == 0 |
| || strcmp (str, "spsr_all") == 0) |
| skip = 8; |
| else |
| { |
| inst.error = _("{C|S}PSR expected"); |
| return; |
| } |
| |
| if (* str == 's' || * str == 'S') |
| inst.instruction |= SPSR_BIT; |
| str += skip; |
| |
| inst.instruction |= flags; |
| end_of_line (str); |
| } |
| |
| /* Two possible forms: |
| "{C|S}PSR_<field>, Rm", |
| "{C|S}PSR_f, #expression". */ |
| |
| static void |
| do_msr (str, flags) |
| char * str; |
| unsigned long flags; |
| { |
| skip_whitespace (str); |
| |
| if (psr_required_here (& str) == FAIL) |
| return; |
| |
| if (skip_past_comma (& str) == FAIL) |
| { |
| inst.error = _("comma missing after psr flags"); |
| return; |
| } |
| |
| skip_whitespace (str); |
| |
| if (reg_required_here (& str, 0) != FAIL) |
| { |
| inst.error = NULL; |
| inst.instruction |= flags; |
| end_of_line (str); |
| return; |
| } |
| |
| if (! is_immediate_prefix (* str)) |
| { |
| inst.error = |
| _("only a register or immediate value can follow a psr flag"); |
| return; |
| } |
| |
| str ++; |
| inst.error = NULL; |
| |
| if (my_get_expression (& inst.reloc.exp, & str)) |
| { |
| inst.error = |
| _("only a register or immediate value can follow a psr flag"); |
| return; |
| } |
| |
| #if 0 /* The first edition of the ARM architecture manual stated that |
| writing anything other than the flags with an immediate operation |
| had UNPREDICTABLE effects. This constraint was removed in the |
| second edition of the specification. */ |
| if ((cpu_variant & ARM_EXT_V5) != ARM_EXT_V5 |
| && inst.instruction & ((PSR_c | PSR_x | PSR_s) << PSR_SHIFT)) |
| { |
| inst.error = _("immediate value cannot be used to set this field"); |
| return; |
| } |
| #endif |
| |
| flags |= INST_IMMEDIATE; |
| |
| if (inst.reloc.exp.X_add_symbol) |
| { |
| inst.reloc.type = BFD_RELOC_ARM_IMMEDIATE; |
| inst.reloc.pc_rel = 0; |
| } |
| else |
| { |
| unsigned value = validate_immediate (inst.reloc.exp.X_add_number); |
| |
| if (value == (unsigned) FAIL) |
| { |
| inst.error = _("Invalid constant"); |
| return; |
| } |
| |
| inst.instruction |= value; |
| } |
| |
| inst.error = NULL; |
| inst.instruction |= flags; |
| end_of_line (str); |
| } |
| |
| /* Long Multiply Parser |
| UMULL RdLo, RdHi, Rm, Rs |
| SMULL RdLo, RdHi, Rm, Rs |
| UMLAL RdLo, RdHi, Rm, Rs |
| SMLAL RdLo, RdHi, Rm, Rs. */ |
| |
| static void |
| do_mull (str, flags) |
| char * str; |
| unsigned long flags; |
| { |
| int rdlo, rdhi, rm, rs; |
| |
| /* Only one format "rdlo, rdhi, rm, rs". */ |
| skip_whitespace (str); |
| |
| if ((rdlo = reg_required_here (&str, 12)) == FAIL) |
| { |
| inst.error = BAD_ARGS; |
| return; |
| } |
| |
| if (skip_past_comma (&str) == FAIL |
| || (rdhi = reg_required_here (&str, 16)) == FAIL) |
| { |
| inst.error = BAD_ARGS; |
| return; |
| } |
| |
| if (skip_past_comma (&str) == FAIL |
| || (rm = reg_required_here (&str, 0)) == FAIL) |
| { |
| inst.error = BAD_ARGS; |
| return; |
| } |
| |
| /* rdhi, rdlo and rm must all be different. */ |
| if (rdlo == rdhi || rdlo == rm || rdhi == rm) |
| as_tsktsk (_("rdhi, rdlo and rm must all be different")); |
| |
| if (skip_past_comma (&str) == FAIL |
| || (rs = reg_required_here (&str, 8)) == FAIL) |
| { |
| inst.error = BAD_ARGS; |
| return; |
| } |
| |
| if (rdhi == REG_PC || rdhi == REG_PC || rdhi == REG_PC || rdhi == REG_PC) |
| { |
| inst.error = BAD_PC; |
| return; |
| } |
| |
| inst.instruction |= flags; |
| end_of_line (str); |
| return; |
| } |
| |
| static void |
| do_mul (str, flags) |
| char * str; |
| unsigned long flags; |
| { |
| int rd, rm; |
| |
| /* Only one format "rd, rm, rs". */ |
| skip_whitespace (str); |
| |
| if ((rd = reg_required_here (&str, 16)) == FAIL) |
| { |
| inst.error = BAD_ARGS; |
| return; |
| } |
| |
| if (rd == REG_PC) |
| { |
| inst.error = BAD_PC; |
| return; |
| } |
| |
| if (skip_past_comma (&str) == FAIL |
| || (rm = reg_required_here (&str, 0)) == FAIL) |
| { |
| inst.error = BAD_ARGS; |
| return; |
| } |
| |
| if (rm == REG_PC) |
| { |
| inst.error = BAD_PC; |
| return; |
| } |
| |
| if (rm == rd) |
| as_tsktsk (_("rd and rm should be different in mul")); |
| |
| if (skip_past_comma (&str) == FAIL |
| || (rm = reg_required_here (&str, 8)) == FAIL) |
| { |
| inst.error = BAD_ARGS; |
| return; |
| } |
| |
| if (rm == REG_PC) |
| { |
| inst.error = BAD_PC; |
| return; |
| } |
| |
| inst.instruction |= flags; |
| end_of_line (str); |
| return; |
| } |
| |
| static void |
| do_mla (str, flags) |
| char * str; |
| unsigned long flags; |
| { |
| int rd, rm; |
| |
| /* Only one format "rd, rm, rs, rn". */ |
| skip_whitespace (str); |
| |
| if ((rd = reg_required_here (&str, 16)) == FAIL) |
| { |
| inst.error = BAD_ARGS; |
| return; |
| } |
| |
| if (rd == REG_PC) |
| { |
| inst.error = BAD_PC; |
| return; |
| } |
| |
| if (skip_past_comma (&str) == FAIL |
| || (rm = reg_required_here (&str, 0)) == FAIL) |
| { |
| inst.error = BAD_ARGS; |
| return; |
| } |
| |
| if (rm == REG_PC) |
| { |
| inst.error = BAD_PC; |
| return; |
| } |
| |
| if (rm == rd) |
| as_tsktsk (_("rd and rm should be different in mla")); |
| |
| if (skip_past_comma (&str) == FAIL |
| || (rd = reg_required_here (&str, 8)) == FAIL |
| || skip_past_comma (&str) == FAIL |
| || (rm = reg_required_here (&str, 12)) == FAIL) |
| { |
| inst.error = BAD_ARGS; |
| return; |
| } |
| |
| if (rd == REG_PC || rm == REG_PC) |
| { |
| inst.error = BAD_PC; |
| return; |
| } |
| |
| inst.instruction |= flags; |
| end_of_line (str); |
| return; |
| } |
| |
| /* Expects *str -> the characters "acc0", possibly with leading blanks. |
| Advances *str to the next non-alphanumeric. |
| Returns 0, or else FAIL (in which case sets inst.error). |
| |
| (In a future XScale, there may be accumulators other than zero. |
| At that time this routine and its callers can be upgraded to suit.) */ |
| |
| static int |
| accum0_required_here (str) |
| char ** str; |
| { |
| static char buff [128]; /* Note the address is taken. Hence, static. */ |
| char * p = * str; |
| char c; |
| int result = 0; /* The accum number. */ |
| |
| skip_whitespace (p); |
| |
| *str = p; /* Advance caller's string pointer too. */ |
| c = *p++; |
| while (isalnum (c)) |
| c = *p++; |
| |
| *--p = 0; /* Aap nul into input buffer at non-alnum. */ |
| |
| if (! ( streq (*str, "acc0") || streq (*str, "ACC0"))) |
| { |
| sprintf (buff, _("acc0 expected, not '%.100s'"), *str); |
| inst.error = buff; |
| result = FAIL; |
| } |
| |
| *p = c; /* Unzap. */ |
| *str = p; /* Caller's string pointer to after match. */ |
| return result; |
| } |
| |
| /* Expects **str -> after a comma. May be leading blanks. |
| Advances *str, recognizing a load mode, and setting inst.instruction. |
| Returns rn, or else FAIL (in which case may set inst.error |
| and not advance str) |
| |
| Note: doesn't know Rd, so no err checks that require such knowledge. */ |
| |
| static int |
| ld_mode_required_here (string) |
| char ** string; |
| { |
| char * str = * string; |
| int rn; |
| int pre_inc = 0; |
| |
| skip_whitespace (str); |
| |
| if (* str == '[') |
| { |
| str++; |
| |
| skip_whitespace (str); |
| |
| if ((rn = reg_required_here (& str, 16)) == FAIL) |
| return FAIL; |
| |
| skip_whitespace (str); |
| |
| if (* str == ']') |
| { |
| str ++; |
| |
| if (skip_past_comma (& str) == SUCCESS) |
| { |
| /* [Rn],... (post inc) */ |
| if (ldst_extend (& str, 1) == FAIL) |
| return FAIL; |
| } |
| else /* [Rn] */ |
| { |
| skip_whitespace (str); |
| |
| if (* str == '!') |
| { |
| str ++; |
| inst.instruction |= WRITE_BACK; |
| } |
| |
| inst.instruction |= INDEX_UP | HWOFFSET_IMM; |
| pre_inc = 1; |
| } |
| } |
| else /* [Rn,...] */ |
| { |
| if (skip_past_comma (& str) == FAIL) |
| { |
| inst.error = _("pre-indexed expression expected"); |
| return FAIL; |
| } |
| |
| pre_inc = 1; |
| |
| if (ldst_extend (& str, 1) == FAIL) |
| return FAIL; |
| |
| skip_whitespace (str); |
| |
| if (* str ++ != ']') |
| { |
| inst.error = _("missing ]"); |
| return FAIL; |
| } |
| |
| skip_whitespace (str); |
| |
| if (* str == '!') |
| { |
| str ++; |
| inst.instruction |= WRITE_BACK; |
| } |
| } |
| } |
| else if (* str == '=') /* ldr's "r,=label" syntax */ |
| /* We should never reach here, because <text> = <expression> is |
| caught gas/read.c read_a_source_file() as a .set operation. */ |
| return FAIL; |
| else /* PC +- 8 bit immediate offset. */ |
| { |
| if (my_get_expression (& inst.reloc.exp, & str)) |
| return FAIL; |
| |
| inst.instruction |= HWOFFSET_IMM; |