mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-21 01:12:32 +08:00
a3f1e7c56a
Simplify assembly and disassembly of extended mnemonics with operands with constant ORed bits: Their instruction template already contains the respective constant operand bits, as they are significant to distinguish the extended from their base mnemonic. Operands are ORed into the instruction template. Therefore it is not necessary to OR the constant bits into the operand value during assembly in s390_insert_operand. Additionally the constant operand bits from the instruction template can be used to mask them from the operand value during disassembly in s390_print_insn_with_opcode. For now do so for non-length unsigned integer operands only. The separate instruction formats need to be retained, as their masks differ, which is relevant during disassembly to distinguish the base and extended mnemonics from each other. This affects the following extended mnemonics: - vfaebs, vfaehs, vfaefs - vfaezb, vfaezh, vfaezf - vfaezbs, vfaezhs, vfaezfs - vstrcbs, vstrchs, vstrcfs - vstrczb, vstrczh, vstrczf - vstrczbs, vstrczhs, vstrczfs - wcefb, wcdgb - wcelfb, wcdlgb - wcfeb, wcgdb - wclfeb, wclgdb - wfisb, wfidb, wfixb - wledb, wflrd, wflrx include/ * opcode/s390.h (S390_OPERAND_OR1, S390_OPERAND_OR2, S390_OPERAND_OR8): Remove. opcodes/ * s390-opc.c (U4_OR1_24, U4_OR2_24, U4_OR8_28): Remove. (INSTR_VRR_VVV0U1, INSTR_VRR_VVV0U2, INSTR_VRR_VVV0U3): Define as INSTR_VRR_VVV0U0 while retaining respective insn fmt mask. (INSTR_VRR_VV0UU8): Define as INSTR_VRR_VV0UU while retaining respective insn fmt mask. (INSTR_VRR_VVVU0VB1, INSTR_VRR_VVVU0VB2, INSTR_VRR_VVVU0VB3): Define as INSTR_VRR_VVVU0VB while retaining respective insn fmt mask. * s390-dis.c (s390_print_insn_with_opcode): Mask constant operand bits set in insn template of non-length unsigned integer operands. gas/ * config/tc-s390.c (s390_insert_operand): Do not OR constant operand value bits. Signed-off-by: Jens Remus <jremus@linux.ibm.com>
2862 lines
78 KiB
C
2862 lines
78 KiB
C
/* tc-s390.c -- Assemble for the S390
|
|
Copyright (C) 2000-2024 Free Software Foundation, Inc.
|
|
Contributed by Martin Schwidefsky (schwidefsky@de.ibm.com).
|
|
|
|
This file is part of GAS, the GNU Assembler.
|
|
|
|
GAS is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3, or (at your option)
|
|
any later version.
|
|
|
|
GAS is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GAS; see the file COPYING. If not, write to the Free
|
|
Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA
|
|
02110-1301, USA. */
|
|
|
|
#include "as.h"
|
|
#include "safe-ctype.h"
|
|
#include "subsegs.h"
|
|
#include "dwarf2dbg.h"
|
|
#include "dw2gencfi.h"
|
|
|
|
#include "opcode/s390.h"
|
|
#include "elf/s390.h"
|
|
|
|
/* The default architecture. */
|
|
#ifndef DEFAULT_ARCH
|
|
#define DEFAULT_ARCH "s390"
|
|
#endif
|
|
static const char *default_arch = DEFAULT_ARCH;
|
|
/* Either 32 or 64, selects file format. */
|
|
static int s390_arch_size = 0;
|
|
|
|
/* If no -march option was given default to the highest available CPU.
|
|
Since with S/390 a newer CPU always supports everything from its
|
|
predecessors this will accept every valid asm input. */
|
|
static unsigned int current_cpu = S390_OPCODE_MAXCPU - 1;
|
|
/* All facilities are enabled by default. */
|
|
static unsigned int current_flags = S390_INSTR_FLAG_FACILITY_MASK;
|
|
/* The mode mask default is picked in init_default_arch depending on
|
|
the current cpu. */
|
|
static unsigned int current_mode_mask = 0;
|
|
|
|
/* Set to TRUE if the highgprs flag in the ELF header needs to be set
|
|
for the output file. The default is picked in init_default_arch(). */
|
|
static bool set_highgprs_p = false;
|
|
|
|
/* Whether to use user friendly register names. Default is TRUE. */
|
|
#ifndef TARGET_REG_NAMES_P
|
|
#define TARGET_REG_NAMES_P true
|
|
#endif
|
|
|
|
static bool reg_names_p = TARGET_REG_NAMES_P;
|
|
|
|
/* Set to TRUE if we want to warn about zero base/index registers. */
|
|
static bool warn_areg_zero = false;
|
|
|
|
/* Whether to warn about register name type check mismatches. */
|
|
#ifndef S390_REGTYPE_CHECK
|
|
#define S390_REGTYPE_CHECK S390_REGTYPE_CHECK_RELAXED
|
|
#endif
|
|
|
|
enum s390_regtype_check {
|
|
S390_REGTYPE_CHECK_NONE = 0, /* No register name type checks. */
|
|
S390_REGTYPE_CHECK_RELAXED, /* Relaxed register name type checks. */
|
|
S390_REGTYPE_CHECK_STRICT /* Strict register name type checks. */
|
|
};
|
|
|
|
/* Whether to warn about register name type check mismatches. */
|
|
static enum s390_regtype_check warn_regtype_mismatch = S390_REGTYPE_CHECK;
|
|
|
|
/* Generic assembler global variables which must be defined by all
|
|
targets. */
|
|
|
|
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. */
|
|
const char FLT_CHARS[] = "dD";
|
|
|
|
/* The dwarf2 data alignment, adjusted for 32 or 64 bit. */
|
|
int s390_cie_data_alignment;
|
|
|
|
/* The target specific pseudo-ops which we support. */
|
|
|
|
/* Define the prototypes for the pseudo-ops */
|
|
static void s390_byte (int);
|
|
static void s390_elf_cons (int);
|
|
static void s390_insn (int);
|
|
static void s390_literals (int);
|
|
static void s390_machine (int);
|
|
static void s390_machinemode (int);
|
|
|
|
const pseudo_typeS md_pseudo_table[] =
|
|
{
|
|
{ "align", s_align_bytes, 0 },
|
|
/* Pseudo-ops which must be defined. */
|
|
{ "insn", s390_insn, 0 },
|
|
/* Pseudo-ops which must be overridden. */
|
|
{ "byte", s390_byte, 0 },
|
|
{ "short", s390_elf_cons, 2 },
|
|
{ "long", s390_elf_cons, 4 },
|
|
{ "quad", s390_elf_cons, 8 },
|
|
{ "ltorg", s390_literals, 0 },
|
|
{ "string", stringer, 8 + 1 },
|
|
{ "machine", s390_machine, 0 },
|
|
{ "machinemode", s390_machinemode, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
/* Register types. */
|
|
enum s390_register_type
|
|
{
|
|
S390_REGTYPE_AR, /* Access register. */
|
|
S390_REGTYPE_CR, /* Control register. */
|
|
S390_REGTYPE_FPR, /* Floating-point register. */
|
|
S390_REGTYPE_GR, /* General register. */
|
|
S390_REGTYPE_VR, /* Vector register. */
|
|
};
|
|
|
|
/* Given NAME, find the register number associated with that name, return
|
|
the integer value associated with the given name or -1 on failure. */
|
|
|
|
static int
|
|
reg_name_search (const char *name)
|
|
{
|
|
int val = -1;
|
|
|
|
if (strcasecmp (name, "lit") == 0)
|
|
return 13;
|
|
|
|
if (strcasecmp (name, "sp") == 0)
|
|
return 15;
|
|
|
|
if (name[0] != 'a' && name[0] != 'c' && name[0] != 'f'
|
|
&& name[0] != 'r' && name[0] != 'v')
|
|
return -1;
|
|
|
|
if (ISDIGIT (name[1]))
|
|
{
|
|
val = name[1] - '0';
|
|
if (ISDIGIT (name[2]))
|
|
val = val * 10 + name[2] - '0';
|
|
}
|
|
|
|
if ((name[0] != 'v' && val > 15) || val > 31)
|
|
val = -1;
|
|
|
|
return val;
|
|
}
|
|
|
|
|
|
/*
|
|
* Summary of register_name().
|
|
*
|
|
* in: Input_line_pointer points to 1st char of operand.
|
|
*
|
|
* out: A expressionS.
|
|
* The operand may have been a register: in this case, X_op == O_register,
|
|
* X_add_number is set to the register number, and truth is returned.
|
|
* Input_line_pointer->(next non-blank) char after operand, or is in its
|
|
* original state.
|
|
*/
|
|
|
|
static bool
|
|
register_name (expressionS *expressionP)
|
|
{
|
|
int reg_number;
|
|
char *name;
|
|
char *start;
|
|
char c;
|
|
|
|
/* Find the spelling of the operand. */
|
|
start = name = input_line_pointer;
|
|
if (name[0] == '%' && ISALPHA (name[1]))
|
|
name = ++input_line_pointer;
|
|
else
|
|
return false;
|
|
|
|
c = get_symbol_name (&name);
|
|
reg_number = reg_name_search (name);
|
|
|
|
/* Put back the delimiting char. */
|
|
(void) restore_line_pointer (c);
|
|
|
|
/* Look to see if it's in the register table. */
|
|
if (reg_number >= 0)
|
|
{
|
|
expressionP->X_op = O_register;
|
|
expressionP->X_add_number = reg_number;
|
|
switch (name[0])
|
|
{
|
|
case 'a':
|
|
expressionP->X_md = S390_REGTYPE_AR;
|
|
break;
|
|
case 'c':
|
|
expressionP->X_md = S390_REGTYPE_CR;
|
|
break;
|
|
case 'f':
|
|
expressionP->X_md = S390_REGTYPE_FPR;
|
|
break;
|
|
case 'r':
|
|
expressionP->X_md = S390_REGTYPE_GR;
|
|
break;
|
|
case 'v':
|
|
expressionP->X_md = S390_REGTYPE_VR;
|
|
break;
|
|
default:
|
|
expressionP->X_md = 0;
|
|
}
|
|
|
|
/* Make the rest nice. */
|
|
expressionP->X_add_symbol = NULL;
|
|
expressionP->X_op_symbol = NULL;
|
|
return true;
|
|
}
|
|
|
|
/* Reset the line as if we had not done anything. */
|
|
input_line_pointer = start;
|
|
return false;
|
|
}
|
|
|
|
/* Local variables. */
|
|
|
|
/* Opformat hash table. */
|
|
static htab_t s390_opformat_hash;
|
|
|
|
/* Opcode hash table. */
|
|
static htab_t s390_opcode_hash = NULL;
|
|
|
|
/* Flags to set in the elf header */
|
|
static flagword s390_flags = 0;
|
|
|
|
symbolS *GOT_symbol; /* Pre-defined "_GLOBAL_OFFSET_TABLE_" */
|
|
|
|
#ifndef WORKING_DOT_WORD
|
|
int md_short_jump_size = 4;
|
|
int md_long_jump_size = 4;
|
|
#endif
|
|
|
|
const char *md_shortopts = "A:m:kVQ:";
|
|
struct option md_longopts[] = {
|
|
{NULL, no_argument, NULL, 0}
|
|
};
|
|
size_t md_longopts_size = sizeof (md_longopts);
|
|
|
|
/* Initialize the default opcode arch and word size from the default
|
|
architecture name if not specified by an option. */
|
|
static void
|
|
init_default_arch (void)
|
|
{
|
|
/* Default architecture size. */
|
|
if (strcmp (default_arch, "s390") == 0)
|
|
{
|
|
if (s390_arch_size == 0)
|
|
s390_arch_size = 32;
|
|
}
|
|
else if (strcmp (default_arch, "s390x") == 0)
|
|
{
|
|
if (s390_arch_size == 0)
|
|
s390_arch_size = 64;
|
|
}
|
|
else
|
|
as_fatal (_("Invalid default architecture, broken assembler."));
|
|
|
|
/* Default current architecture mode. */
|
|
if (current_mode_mask == 0)
|
|
{
|
|
/* Default to z/Architecture mode if the CPU supports it. */
|
|
if (current_cpu < S390_OPCODE_Z900)
|
|
current_mode_mask = 1 << S390_OPCODE_ESA;
|
|
else
|
|
current_mode_mask = 1 << S390_OPCODE_ZARCH;
|
|
}
|
|
|
|
/* Determine whether the highgprs flag in the ELF header needs to be set. */
|
|
if ((s390_arch_size == 32) && (current_mode_mask & (1 << S390_OPCODE_ZARCH)))
|
|
set_highgprs_p = true;
|
|
}
|
|
|
|
/* Called by TARGET_FORMAT. */
|
|
const char *
|
|
s390_target_format (void)
|
|
{
|
|
/* We don't get a chance to initialize anything before we're called,
|
|
so handle that now. */
|
|
init_default_arch ();
|
|
|
|
return s390_arch_size == 64 ? "elf64-s390" : "elf32-s390";
|
|
}
|
|
|
|
/* Map a cpu string ARG as given with -march= or .machine to the respective
|
|
enum s390_opcode_cpu_val value. If ALLOW_EXTENSIONS is TRUE, the cpu name
|
|
can be followed by a list of cpu facility flags each beginning with the
|
|
character '+'. The active cpu flags are returned through *RET_FLAGS.
|
|
In case of an error, S390_OPCODE_MAXCPU is returned. */
|
|
|
|
static unsigned int
|
|
s390_parse_cpu (const char *arg,
|
|
unsigned int *ret_flags,
|
|
bool allow_extensions)
|
|
{
|
|
static struct
|
|
{
|
|
const char * name;
|
|
unsigned int name_len;
|
|
const char * alt_name;
|
|
unsigned int alt_name_len;
|
|
unsigned int flags;
|
|
} cpu_table[S390_OPCODE_MAXCPU] =
|
|
{
|
|
{ STRING_COMMA_LEN ("g5"), STRING_COMMA_LEN ("arch3"), 0 },
|
|
{ STRING_COMMA_LEN ("g6"), STRING_COMMA_LEN (""), 0 },
|
|
{ STRING_COMMA_LEN ("z900"), STRING_COMMA_LEN ("arch5"), 0 },
|
|
{ STRING_COMMA_LEN ("z990"), STRING_COMMA_LEN ("arch6"), 0 },
|
|
{ STRING_COMMA_LEN ("z9-109"), STRING_COMMA_LEN (""), 0 },
|
|
{ STRING_COMMA_LEN ("z9-ec"), STRING_COMMA_LEN ("arch7"), 0 },
|
|
{ STRING_COMMA_LEN ("z10"), STRING_COMMA_LEN ("arch8"), 0 },
|
|
{ STRING_COMMA_LEN ("z196"), STRING_COMMA_LEN ("arch9"), 0 },
|
|
{ STRING_COMMA_LEN ("zEC12"), STRING_COMMA_LEN ("arch10"),
|
|
S390_INSTR_FLAG_HTM },
|
|
{ STRING_COMMA_LEN ("z13"), STRING_COMMA_LEN ("arch11"),
|
|
S390_INSTR_FLAG_HTM | S390_INSTR_FLAG_VX },
|
|
{ STRING_COMMA_LEN ("z14"), STRING_COMMA_LEN ("arch12"),
|
|
S390_INSTR_FLAG_HTM | S390_INSTR_FLAG_VX },
|
|
{ STRING_COMMA_LEN ("z15"), STRING_COMMA_LEN ("arch13"),
|
|
S390_INSTR_FLAG_HTM | S390_INSTR_FLAG_VX },
|
|
{ STRING_COMMA_LEN ("z16"), STRING_COMMA_LEN ("arch14"),
|
|
S390_INSTR_FLAG_HTM | S390_INSTR_FLAG_VX }
|
|
};
|
|
static struct
|
|
{
|
|
const char *name;
|
|
unsigned int mask;
|
|
bool on;
|
|
} cpu_flags[] =
|
|
{
|
|
{ "htm", S390_INSTR_FLAG_HTM, true },
|
|
{ "nohtm", S390_INSTR_FLAG_HTM, false },
|
|
{ "vx", S390_INSTR_FLAG_VX, true },
|
|
{ "novx", S390_INSTR_FLAG_VX, false }
|
|
};
|
|
unsigned int icpu;
|
|
char *ilp_bak;
|
|
|
|
icpu = S390_OPCODE_MAXCPU;
|
|
if (startswith (arg, "all") && (arg[3] == 0 || arg[3] == '+'))
|
|
{
|
|
icpu = S390_OPCODE_MAXCPU - 1;
|
|
arg += 3;
|
|
}
|
|
else
|
|
{
|
|
for (icpu = 0; icpu < S390_OPCODE_MAXCPU; icpu++)
|
|
{
|
|
unsigned int l, l_alt;
|
|
|
|
l = cpu_table[icpu].name_len;
|
|
|
|
if (strncmp (arg, cpu_table[icpu].name, l) == 0
|
|
&& (arg[l] == 0 || arg[l] == '+'))
|
|
{
|
|
arg += l;
|
|
break;
|
|
}
|
|
|
|
l_alt = cpu_table[icpu].alt_name_len;
|
|
|
|
if (l_alt > 0
|
|
&& strncmp (arg, cpu_table[icpu].alt_name, l_alt) == 0
|
|
&& (arg[l_alt] == 0 || arg[l_alt] == '+'))
|
|
{
|
|
arg += l_alt;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (icpu == S390_OPCODE_MAXCPU)
|
|
return S390_OPCODE_MAXCPU;
|
|
|
|
ilp_bak = input_line_pointer;
|
|
if (icpu != S390_OPCODE_MAXCPU)
|
|
{
|
|
input_line_pointer = (char *) arg;
|
|
*ret_flags = (cpu_table[icpu].flags & S390_INSTR_FLAG_FACILITY_MASK);
|
|
|
|
while (*input_line_pointer == '+' && allow_extensions)
|
|
{
|
|
unsigned int iflag;
|
|
char *sym;
|
|
char c;
|
|
|
|
input_line_pointer++;
|
|
c = get_symbol_name (&sym);
|
|
for (iflag = 0; iflag < ARRAY_SIZE (cpu_flags); iflag++)
|
|
{
|
|
if (strcmp (sym, cpu_flags[iflag].name) == 0)
|
|
{
|
|
if (cpu_flags[iflag].on)
|
|
*ret_flags |= cpu_flags[iflag].mask;
|
|
else
|
|
*ret_flags &= ~cpu_flags[iflag].mask;
|
|
break;
|
|
}
|
|
}
|
|
if (iflag == ARRAY_SIZE (cpu_flags))
|
|
as_bad (_("no such machine extension `%s'"), sym - 1);
|
|
*input_line_pointer = c;
|
|
if (iflag == ARRAY_SIZE (cpu_flags))
|
|
break;
|
|
}
|
|
}
|
|
|
|
SKIP_WHITESPACE ();
|
|
|
|
if (*input_line_pointer != 0 && *input_line_pointer != '\n')
|
|
{
|
|
as_bad (_("junk at end of machine string, first unrecognized character"
|
|
" is `%c'"), *input_line_pointer);
|
|
icpu = S390_OPCODE_MAXCPU;
|
|
}
|
|
input_line_pointer = ilp_bak;
|
|
|
|
return icpu;
|
|
}
|
|
|
|
int
|
|
md_parse_option (int c, const char *arg)
|
|
{
|
|
switch (c)
|
|
{
|
|
/* -k: Ignore for FreeBSD compatibility. */
|
|
case 'k':
|
|
break;
|
|
case 'm':
|
|
if (arg != NULL && strcmp (arg, "regnames") == 0)
|
|
reg_names_p = true;
|
|
|
|
else if (arg != NULL && strcmp (arg, "no-regnames") == 0)
|
|
reg_names_p = false;
|
|
|
|
else if (arg != NULL && strcmp (arg, "warn-areg-zero") == 0)
|
|
warn_areg_zero = true;
|
|
|
|
else if (arg != NULL && strcmp (arg, "warn-regtype-mismatch=strict") == 0)
|
|
warn_regtype_mismatch = S390_REGTYPE_CHECK_STRICT;
|
|
|
|
else if (arg != NULL && strcmp (arg, "warn-regtype-mismatch=relaxed") == 0)
|
|
warn_regtype_mismatch = S390_REGTYPE_CHECK_RELAXED;
|
|
|
|
else if (arg != NULL && strcmp (arg, "warn-regtype-mismatch=no") == 0)
|
|
warn_regtype_mismatch = S390_REGTYPE_CHECK_NONE;
|
|
|
|
else if (arg != NULL && strcmp (arg, "no-warn-regtype-mismatch") == 0)
|
|
warn_regtype_mismatch = S390_REGTYPE_CHECK_NONE;
|
|
|
|
else if (arg != NULL && strcmp (arg, "31") == 0)
|
|
s390_arch_size = 32;
|
|
|
|
else if (arg != NULL && strcmp (arg, "64") == 0)
|
|
s390_arch_size = 64;
|
|
|
|
else if (arg != NULL && strcmp (arg, "esa") == 0)
|
|
current_mode_mask = 1 << S390_OPCODE_ESA;
|
|
|
|
else if (arg != NULL && strcmp (arg, "zarch") == 0)
|
|
current_mode_mask = 1 << S390_OPCODE_ZARCH;
|
|
|
|
else if (arg != NULL && startswith (arg, "arch="))
|
|
{
|
|
current_cpu = s390_parse_cpu (arg + 5, ¤t_flags, false);
|
|
if (current_cpu == S390_OPCODE_MAXCPU)
|
|
{
|
|
as_bad (_("invalid switch -m%s"), arg);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
as_bad (_("invalid switch -m%s"), arg);
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case 'A':
|
|
/* Option -A is deprecated. Still available for compatibility. */
|
|
if (arg != NULL && strcmp (arg, "esa") == 0)
|
|
current_cpu = S390_OPCODE_G5;
|
|
else if (arg != NULL && strcmp (arg, "esame") == 0)
|
|
current_cpu = S390_OPCODE_Z900;
|
|
else
|
|
as_bad (_("invalid architecture -A%s"), arg);
|
|
break;
|
|
|
|
/* -V: SVR4 argument to print version ID. */
|
|
case 'V':
|
|
print_version_id ();
|
|
break;
|
|
|
|
/* -Qy, -Qn: SVR4 arguments controlling whether a .comment section
|
|
should be emitted or not. FIXME: Not implemented. */
|
|
case 'Q':
|
|
break;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
md_show_usage (FILE *stream)
|
|
{
|
|
fprintf (stream, _("\
|
|
S390 options:\n\
|
|
-m31 generate 31-bit file format (31/32 bit word size)\n\
|
|
-m64 generate 64-bit file format (64 bit word size)\n\
|
|
-mesa assemble for Enterprise System Architecture/390\n\
|
|
-mzarch assemble for z/Architecture\n\
|
|
-march=<processor> assemble for processor <processor>\n\
|
|
-mregnames allow symbolic names for registers\n\
|
|
-mno-regnames do not allow symbolic names for registers\n\
|
|
-mwarn-areg-zero warn about base/index register zero\n\
|
|
-mwarn-regtype-mismatch=strict\n\
|
|
warn about register name type mismatches\n\
|
|
-mwarn-regtype-mismatch=relaxed\n\
|
|
warn about register name type mismatches,\n\
|
|
but allow FPR and VR to be used interchangeably\n\
|
|
-mno-warn-regtype-mismatch\n\
|
|
do not warn about register name type mismatches\n\
|
|
"));
|
|
fprintf (stream, _("\
|
|
-V print assembler version number\n\
|
|
-Qy, -Qn ignored\n"));
|
|
fprintf (stream, _("\
|
|
Deprecated S390 options:\n\
|
|
-Aesa assemble for processor IBM S/390 G5 (g5/arch3)\n\
|
|
-Aesame assemble for processor IBM zSeries 900 (z900/arch5)\n"));
|
|
}
|
|
|
|
/* Generate the hash table mapping mnemonics to struct s390_opcode.
|
|
This table is built at startup and whenever the CPU level is
|
|
changed using .machine. */
|
|
|
|
static void
|
|
s390_setup_opcodes (void)
|
|
{
|
|
const struct s390_opcode *op;
|
|
const struct s390_opcode *op_end;
|
|
bool dup_insn = false;
|
|
|
|
if (s390_opcode_hash != NULL)
|
|
htab_delete (s390_opcode_hash);
|
|
|
|
/* Insert the opcodes into a hash table. */
|
|
s390_opcode_hash = str_htab_create ();
|
|
|
|
op_end = s390_opcodes + s390_num_opcodes;
|
|
for (op = s390_opcodes; op < op_end; op++)
|
|
{
|
|
int use_opcode;
|
|
|
|
while (op < op_end - 1 && strcmp(op->name, op[1].name) == 0)
|
|
{
|
|
if (op->min_cpu <= current_cpu && (op->modes & current_mode_mask))
|
|
break;
|
|
op++;
|
|
}
|
|
|
|
if ((op->modes & current_mode_mask) == 0)
|
|
use_opcode = 0;
|
|
else if ((op->flags & S390_INSTR_FLAG_FACILITY_MASK) == 0)
|
|
{
|
|
/* Opcodes that do not belong to a specific facility are enabled if
|
|
present in the selected cpu. */
|
|
use_opcode = (op->min_cpu <= current_cpu);
|
|
}
|
|
else
|
|
{
|
|
unsigned int f;
|
|
|
|
/* Opcodes of a specific facility are enabled if the facility is
|
|
enabled. Note: only some facilities are represented as flags. */
|
|
f = (op->flags & S390_INSTR_FLAG_FACILITY_MASK);
|
|
use_opcode = ((f & current_flags) == f);
|
|
}
|
|
if (use_opcode
|
|
&& str_hash_insert (s390_opcode_hash, op->name, op, 0) != NULL)
|
|
{
|
|
as_bad (_("duplicate %s"), op->name);
|
|
dup_insn = true;
|
|
}
|
|
|
|
while (op < op_end - 1 && strcmp (op->name, op[1].name) == 0)
|
|
op++;
|
|
}
|
|
|
|
if (dup_insn)
|
|
abort ();
|
|
}
|
|
|
|
/* This function is called when the assembler starts up. It is called
|
|
after the options have been parsed and the output file has been
|
|
opened. */
|
|
|
|
void
|
|
md_begin (void)
|
|
{
|
|
const struct s390_opcode *op;
|
|
const struct s390_opcode *op_end;
|
|
|
|
/* Give a warning if the combination -m64 and -Aesa is used. */
|
|
if (s390_arch_size == 64 && current_cpu < S390_OPCODE_Z900)
|
|
as_warn (_("The 64-bit file format is used without z/Architecture instructions."));
|
|
|
|
s390_cie_data_alignment = -s390_arch_size / 8;
|
|
|
|
/* Set the ELF flags if desired. */
|
|
if (s390_flags)
|
|
bfd_set_private_flags (stdoutput, s390_flags);
|
|
|
|
/* Insert the opcode formats into a hash table. */
|
|
s390_opformat_hash = str_htab_create ();
|
|
|
|
op_end = s390_opformats + s390_num_opformats;
|
|
for (op = s390_opformats; op < op_end; op++)
|
|
if (str_hash_insert (s390_opformat_hash, op->name, op, 0) != NULL)
|
|
as_fatal (_("duplicate %s"), op->name);
|
|
|
|
s390_setup_opcodes ();
|
|
|
|
record_alignment (text_section, 2);
|
|
record_alignment (data_section, 2);
|
|
record_alignment (bss_section, 2);
|
|
}
|
|
|
|
/* Called after all assembly has been done. */
|
|
void
|
|
s390_md_finish (void)
|
|
{
|
|
if (s390_arch_size == 64)
|
|
bfd_set_arch_mach (stdoutput, bfd_arch_s390, bfd_mach_s390_64);
|
|
else
|
|
bfd_set_arch_mach (stdoutput, bfd_arch_s390, bfd_mach_s390_31);
|
|
}
|
|
|
|
static void
|
|
s390_bad_operand_out_of_range (int operand_number,
|
|
offsetT value,
|
|
offsetT min,
|
|
offsetT max,
|
|
const char *file,
|
|
unsigned line)
|
|
{
|
|
const char * err;
|
|
|
|
if (operand_number > 0)
|
|
{
|
|
/* xgettext:c-format. */
|
|
err =_("operand %d: operand out of range (%" PRId64
|
|
" is not between %" PRId64 " and %" PRId64 ")");
|
|
if (file)
|
|
as_bad_where (file, line, err, operand_number,
|
|
(int64_t) value, (int64_t) min, (int64_t) max);
|
|
else
|
|
as_bad (err, operand_number,
|
|
(int64_t) value, (int64_t) min, (int64_t) max);
|
|
}
|
|
else
|
|
{
|
|
/* xgettext:c-format. */
|
|
err = _("operand out of range (%" PRId64
|
|
" is not between %" PRId64 " and %" PRId64 ")");
|
|
if (file)
|
|
as_bad_where (file, line, err,
|
|
(int64_t) value, (int64_t) min, (int64_t) max);
|
|
else
|
|
as_bad (err, (int64_t) value, (int64_t) min, (int64_t) max);
|
|
}
|
|
}
|
|
|
|
/* Insert an operand value into an instruction. */
|
|
|
|
static void
|
|
s390_insert_operand (unsigned char *insn,
|
|
const struct s390_operand *operand,
|
|
offsetT val,
|
|
const char *file,
|
|
unsigned int line,
|
|
int operand_number)
|
|
{
|
|
addressT uval;
|
|
int offset;
|
|
|
|
if (operand->flags & (S390_OPERAND_SIGNED|S390_OPERAND_PCREL))
|
|
{
|
|
offsetT min, max;
|
|
|
|
max = ((offsetT) 1 << (operand->bits - 1)) - 1;
|
|
min = - ((offsetT) 1 << (operand->bits - 1));
|
|
/* Halve PCREL operands. */
|
|
if (operand->flags & S390_OPERAND_PCREL)
|
|
val >>= 1;
|
|
/* Check for underflow / overflow. */
|
|
if (val < min || val > max)
|
|
{
|
|
if (operand->flags & S390_OPERAND_PCREL)
|
|
{
|
|
val = (offsetT) ((addressT) val << 1);
|
|
min = (offsetT) ((addressT) min << 1);
|
|
max = (offsetT) ((addressT) max << 1);
|
|
}
|
|
|
|
s390_bad_operand_out_of_range (operand_number, val, min, max,
|
|
file, line);
|
|
|
|
return;
|
|
}
|
|
/* val is ok, now restrict it to operand->bits bits. */
|
|
uval = (addressT) val & ((((addressT) 1 << (operand->bits-1)) << 1) - 1);
|
|
/* val is restrict, now check for special case. */
|
|
if (operand->bits == 20 && operand->shift == 20)
|
|
uval = (uval >> 12) | ((uval & 0xfff) << 8);
|
|
}
|
|
else
|
|
{
|
|
addressT min, max;
|
|
|
|
max = (((addressT) 1 << (operand->bits - 1)) << 1) - 1;
|
|
min = (offsetT) 0;
|
|
uval = (addressT) val;
|
|
|
|
/* Vector register operands have an additional bit in the RXB
|
|
field. */
|
|
if (operand->flags & S390_OPERAND_VR)
|
|
max = (max << 1) | 1;
|
|
|
|
/* Length x in an instructions has real length x+1. */
|
|
if (operand->flags & S390_OPERAND_LENGTH)
|
|
uval--;
|
|
/* Check for underflow / overflow. */
|
|
if (uval < min || uval > max)
|
|
{
|
|
if (operand->flags & S390_OPERAND_LENGTH)
|
|
{
|
|
uval++;
|
|
min++;
|
|
max++;
|
|
}
|
|
|
|
s390_bad_operand_out_of_range (operand_number, val, min, max,
|
|
file, line);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (operand->flags & S390_OPERAND_VR)
|
|
{
|
|
/* Insert the extra bit into the RXB field. */
|
|
switch (operand->shift)
|
|
{
|
|
case 8:
|
|
insn[4] |= (uval & 0x10) >> 1;
|
|
break;
|
|
case 12:
|
|
insn[4] |= (uval & 0x10) >> 2;
|
|
break;
|
|
case 16:
|
|
insn[4] |= (uval & 0x10) >> 3;
|
|
break;
|
|
case 32:
|
|
insn[4] |= (uval & 0x10) >> 4;
|
|
break;
|
|
}
|
|
uval &= 0xf;
|
|
}
|
|
|
|
/* Duplicate the GPR/VR operand at bit pos 12 to 16. */
|
|
if (operand->flags & S390_OPERAND_CP16)
|
|
{
|
|
/* Copy GPR/VR operand at bit pos 12 to bit pos 16. */
|
|
insn[2] |= uval << 4;
|
|
|
|
if (operand->flags & S390_OPERAND_VR)
|
|
{
|
|
/* Copy the VR flag in the RXB field. */
|
|
insn[4] |= (insn[4] & 4) >> 1;
|
|
}
|
|
}
|
|
|
|
/* Insert fragments of the operand byte for byte. */
|
|
offset = operand->shift + operand->bits;
|
|
uval <<= (-offset) & 7;
|
|
insn += (offset - 1) / 8;
|
|
while (uval != 0)
|
|
{
|
|
*insn-- |= uval;
|
|
uval >>= 8;
|
|
}
|
|
}
|
|
|
|
struct map_tls
|
|
{
|
|
const char *string;
|
|
int length;
|
|
bfd_reloc_code_real_type reloc;
|
|
};
|
|
|
|
/* Parse tls marker and return the desired relocation. */
|
|
static bfd_reloc_code_real_type
|
|
s390_tls_suffix (char **str_p, expressionS *exp_p)
|
|
{
|
|
static struct map_tls mapping[] =
|
|
{
|
|
{ "tls_load", 8, BFD_RELOC_390_TLS_LOAD },
|
|
{ "tls_gdcall", 10, BFD_RELOC_390_TLS_GDCALL },
|
|
{ "tls_ldcall", 10, BFD_RELOC_390_TLS_LDCALL },
|
|
{ NULL, 0, BFD_RELOC_UNUSED }
|
|
};
|
|
struct map_tls *ptr;
|
|
char *orig_line;
|
|
char *str;
|
|
char *ident;
|
|
int len;
|
|
|
|
str = *str_p;
|
|
if (*str++ != ':')
|
|
return BFD_RELOC_UNUSED;
|
|
|
|
ident = str;
|
|
while (ISIDNUM (*str))
|
|
str++;
|
|
len = str - ident;
|
|
if (*str++ != ':')
|
|
return BFD_RELOC_UNUSED;
|
|
|
|
orig_line = input_line_pointer;
|
|
input_line_pointer = str;
|
|
expression (exp_p);
|
|
str = input_line_pointer;
|
|
if (&input_line_pointer != str_p)
|
|
input_line_pointer = orig_line;
|
|
|
|
if (exp_p->X_op != O_symbol)
|
|
return BFD_RELOC_UNUSED;
|
|
|
|
for (ptr = &mapping[0]; ptr->length > 0; ptr++)
|
|
if (len == ptr->length
|
|
&& strncasecmp (ident, ptr->string, ptr->length) == 0)
|
|
{
|
|
/* Found a matching tls suffix. */
|
|
*str_p = str;
|
|
return ptr->reloc;
|
|
}
|
|
return BFD_RELOC_UNUSED;
|
|
}
|
|
|
|
/* Structure used to hold suffixes. */
|
|
typedef enum
|
|
{
|
|
ELF_SUFFIX_NONE = 0,
|
|
ELF_SUFFIX_GOT,
|
|
ELF_SUFFIX_PLT,
|
|
ELF_SUFFIX_GOTENT,
|
|
ELF_SUFFIX_GOTOFF,
|
|
ELF_SUFFIX_GOTPLT,
|
|
ELF_SUFFIX_PLTOFF,
|
|
ELF_SUFFIX_TLS_GD,
|
|
ELF_SUFFIX_TLS_GOTIE,
|
|
ELF_SUFFIX_TLS_IE,
|
|
ELF_SUFFIX_TLS_LDM,
|
|
ELF_SUFFIX_TLS_LDO,
|
|
ELF_SUFFIX_TLS_LE
|
|
}
|
|
elf_suffix_type;
|
|
|
|
struct map_bfd
|
|
{
|
|
const char *string;
|
|
int length;
|
|
elf_suffix_type suffix;
|
|
};
|
|
|
|
|
|
/* Parse @got/@plt/@gotoff. and return the desired relocation. */
|
|
static elf_suffix_type
|
|
s390_elf_suffix (char **str_p, expressionS *exp_p)
|
|
{
|
|
static struct map_bfd mapping[] =
|
|
{
|
|
{ "got", 3, ELF_SUFFIX_GOT },
|
|
{ "got12", 5, ELF_SUFFIX_GOT },
|
|
{ "plt", 3, ELF_SUFFIX_PLT },
|
|
{ "gotent", 6, ELF_SUFFIX_GOTENT },
|
|
{ "gotoff", 6, ELF_SUFFIX_GOTOFF },
|
|
{ "gotplt", 6, ELF_SUFFIX_GOTPLT },
|
|
{ "pltoff", 6, ELF_SUFFIX_PLTOFF },
|
|
{ "tlsgd", 5, ELF_SUFFIX_TLS_GD },
|
|
{ "gotntpoff", 9, ELF_SUFFIX_TLS_GOTIE },
|
|
{ "indntpoff", 9, ELF_SUFFIX_TLS_IE },
|
|
{ "tlsldm", 6, ELF_SUFFIX_TLS_LDM },
|
|
{ "dtpoff", 6, ELF_SUFFIX_TLS_LDO },
|
|
{ "ntpoff", 6, ELF_SUFFIX_TLS_LE },
|
|
{ NULL, 0, ELF_SUFFIX_NONE }
|
|
};
|
|
|
|
struct map_bfd *ptr;
|
|
char *str = *str_p;
|
|
char *ident;
|
|
int len;
|
|
|
|
if (*str++ != '@')
|
|
return ELF_SUFFIX_NONE;
|
|
|
|
ident = str;
|
|
while (ISALNUM (*str))
|
|
str++;
|
|
len = str - ident;
|
|
|
|
for (ptr = &mapping[0]; ptr->length > 0; ptr++)
|
|
if (len == ptr->length
|
|
&& strncasecmp (ident, ptr->string, ptr->length) == 0)
|
|
{
|
|
if (exp_p->X_add_number != 0)
|
|
as_warn (_("identifier+constant@%s means identifier@%s+constant"),
|
|
ptr->string, ptr->string);
|
|
/* Now check for identifier@suffix+constant. */
|
|
if (*str == '-' || *str == '+')
|
|
{
|
|
char *orig_line = input_line_pointer;
|
|
expressionS new_exp;
|
|
|
|
input_line_pointer = str;
|
|
expression (&new_exp);
|
|
|
|
switch (new_exp.X_op)
|
|
{
|
|
case O_constant: /* X_add_number (a constant expression). */
|
|
exp_p->X_add_number += new_exp.X_add_number;
|
|
str = input_line_pointer;
|
|
break;
|
|
case O_symbol: /* X_add_symbol + X_add_number. */
|
|
/* this case is used for e.g. xyz@PLT+.Label. */
|
|
exp_p->X_add_number += new_exp.X_add_number;
|
|
exp_p->X_op_symbol = new_exp.X_add_symbol;
|
|
exp_p->X_op = O_add;
|
|
str = input_line_pointer;
|
|
break;
|
|
case O_uminus: /* (- X_add_symbol) + X_add_number. */
|
|
/* this case is used for e.g. xyz@PLT-.Label. */
|
|
exp_p->X_add_number += new_exp.X_add_number;
|
|
exp_p->X_op_symbol = new_exp.X_add_symbol;
|
|
exp_p->X_op = O_subtract;
|
|
str = input_line_pointer;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* If s390_elf_suffix has not been called with
|
|
&input_line_pointer as first parameter, we have
|
|
clobbered the input_line_pointer. We have to
|
|
undo that. */
|
|
if (&input_line_pointer != str_p)
|
|
input_line_pointer = orig_line;
|
|
}
|
|
*str_p = str;
|
|
return ptr->suffix;
|
|
}
|
|
|
|
return ELF_SUFFIX_NONE;
|
|
}
|
|
|
|
/* Structure used to hold a literal pool entry. */
|
|
struct s390_lpe
|
|
{
|
|
struct s390_lpe *next;
|
|
expressionS ex;
|
|
FLONUM_TYPE floatnum; /* used if X_op == O_big && X_add_number <= 0 */
|
|
LITTLENUM_TYPE bignum[4]; /* used if X_op == O_big && X_add_number > 0 */
|
|
int nbytes;
|
|
bfd_reloc_code_real_type reloc;
|
|
symbolS *sym;
|
|
};
|
|
|
|
static struct s390_lpe *lpe_free_list = NULL;
|
|
static struct s390_lpe *lpe_list = NULL;
|
|
static struct s390_lpe *lpe_list_tail = NULL;
|
|
static symbolS *lp_sym = NULL;
|
|
static int lp_count = 0;
|
|
static int lpe_count = 0;
|
|
|
|
static int
|
|
s390_exp_compare (expressionS *exp1, expressionS *exp2)
|
|
{
|
|
if (exp1->X_op != exp2->X_op)
|
|
return 0;
|
|
|
|
switch (exp1->X_op)
|
|
{
|
|
case O_constant: /* X_add_number must be equal. */
|
|
case O_register:
|
|
return exp1->X_add_number == exp2->X_add_number;
|
|
|
|
case O_big:
|
|
as_bad (_("Can't handle O_big in s390_exp_compare"));
|
|
return 0;
|
|
|
|
case O_symbol: /* X_add_symbol & X_add_number must be equal. */
|
|
case O_symbol_rva:
|
|
case O_uminus:
|
|
case O_bit_not:
|
|
case O_logical_not:
|
|
return (exp1->X_add_symbol == exp2->X_add_symbol)
|
|
&& (exp1->X_add_number == exp2->X_add_number);
|
|
|
|
case O_multiply: /* X_add_symbol,X_op_symbol&X_add_number must be equal. */
|
|
case O_divide:
|
|
case O_modulus:
|
|
case O_left_shift:
|
|
case O_right_shift:
|
|
case O_bit_inclusive_or:
|
|
case O_bit_or_not:
|
|
case O_bit_exclusive_or:
|
|
case O_bit_and:
|
|
case O_add:
|
|
case O_subtract:
|
|
case O_eq:
|
|
case O_ne:
|
|
case O_lt:
|
|
case O_le:
|
|
case O_ge:
|
|
case O_gt:
|
|
case O_logical_and:
|
|
case O_logical_or:
|
|
return (exp1->X_add_symbol == exp2->X_add_symbol)
|
|
&& (exp1->X_op_symbol == exp2->X_op_symbol)
|
|
&& (exp1->X_add_number == exp2->X_add_number);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Test for @lit and if it's present make an entry in the literal pool and
|
|
modify the current expression to be an offset into the literal pool. */
|
|
static elf_suffix_type
|
|
s390_lit_suffix (char **str_p, expressionS *exp_p, elf_suffix_type suffix)
|
|
{
|
|
bfd_reloc_code_real_type reloc;
|
|
char tmp_name[64];
|
|
char *str = *str_p;
|
|
char *ident;
|
|
struct s390_lpe *lpe;
|
|
int nbytes, len;
|
|
|
|
if (*str++ != ':')
|
|
return suffix; /* No modification. */
|
|
|
|
/* We look for a suffix of the form "@lit1", "@lit2", "@lit4" or "@lit8". */
|
|
ident = str;
|
|
while (ISALNUM (*str))
|
|
str++;
|
|
len = str - ident;
|
|
if (len != 4 || strncasecmp (ident, "lit", 3) != 0
|
|
|| (ident[3]!='1' && ident[3]!='2' && ident[3]!='4' && ident[3]!='8'))
|
|
return suffix; /* no modification */
|
|
nbytes = ident[3] - '0';
|
|
|
|
reloc = BFD_RELOC_UNUSED;
|
|
if (suffix == ELF_SUFFIX_GOT)
|
|
{
|
|
if (nbytes == 2)
|
|
reloc = BFD_RELOC_390_GOT16;
|
|
else if (nbytes == 4)
|
|
reloc = BFD_RELOC_32_GOT_PCREL;
|
|
else if (nbytes == 8)
|
|
reloc = BFD_RELOC_390_GOT64;
|
|
}
|
|
else if (suffix == ELF_SUFFIX_PLT)
|
|
{
|
|
if (nbytes == 4)
|
|
reloc = BFD_RELOC_390_PLT32;
|
|
else if (nbytes == 8)
|
|
reloc = BFD_RELOC_390_PLT64;
|
|
}
|
|
|
|
if (suffix != ELF_SUFFIX_NONE && reloc == BFD_RELOC_UNUSED)
|
|
as_bad (_("Invalid suffix for literal pool entry"));
|
|
|
|
/* Search the pool if the new entry is a duplicate. */
|
|
if (exp_p->X_op == O_big)
|
|
{
|
|
/* Special processing for big numbers. */
|
|
for (lpe = lpe_list; lpe != NULL; lpe = lpe->next)
|
|
{
|
|
if (lpe->ex.X_op == O_big)
|
|
{
|
|
if (exp_p->X_add_number <= 0 && lpe->ex.X_add_number <= 0)
|
|
{
|
|
if (memcmp (&generic_floating_point_number, &lpe->floatnum,
|
|
sizeof (FLONUM_TYPE)) == 0)
|
|
break;
|
|
}
|
|
else if (exp_p->X_add_number == lpe->ex.X_add_number)
|
|
{
|
|
if (memcmp (generic_bignum, lpe->bignum,
|
|
sizeof (LITTLENUM_TYPE)*exp_p->X_add_number) == 0)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Processing for 'normal' data types. */
|
|
for (lpe = lpe_list; lpe != NULL; lpe = lpe->next)
|
|
if (lpe->nbytes == nbytes && lpe->reloc == reloc
|
|
&& s390_exp_compare (exp_p, &lpe->ex) != 0)
|
|
break;
|
|
}
|
|
|
|
if (lpe == NULL)
|
|
{
|
|
/* A new literal. */
|
|
if (lpe_free_list != NULL)
|
|
{
|
|
lpe = lpe_free_list;
|
|
lpe_free_list = lpe_free_list->next;
|
|
}
|
|
else
|
|
{
|
|
lpe = XNEW (struct s390_lpe);
|
|
}
|
|
|
|
lpe->ex = *exp_p;
|
|
|
|
if (exp_p->X_op == O_big)
|
|
{
|
|
if (exp_p->X_add_number <= 0)
|
|
lpe->floatnum = generic_floating_point_number;
|
|
else if (exp_p->X_add_number <= 4)
|
|
memcpy (lpe->bignum, generic_bignum,
|
|
exp_p->X_add_number * sizeof (LITTLENUM_TYPE));
|
|
else
|
|
as_bad (_("Big number is too big"));
|
|
}
|
|
|
|
lpe->nbytes = nbytes;
|
|
lpe->reloc = reloc;
|
|
/* Literal pool name defined ? */
|
|
if (lp_sym == NULL)
|
|
{
|
|
sprintf (tmp_name, ".L\001%i", lp_count);
|
|
lp_sym = symbol_make (tmp_name);
|
|
}
|
|
|
|
/* Make name for literal pool entry. */
|
|
sprintf (tmp_name, ".L\001%i\002%i", lp_count, lpe_count);
|
|
lpe_count++;
|
|
lpe->sym = symbol_make (tmp_name);
|
|
|
|
/* Add to literal pool list. */
|
|
lpe->next = NULL;
|
|
if (lpe_list_tail != NULL)
|
|
{
|
|
lpe_list_tail->next = lpe;
|
|
lpe_list_tail = lpe;
|
|
}
|
|
else
|
|
lpe_list = lpe_list_tail = lpe;
|
|
}
|
|
|
|
/* Now change exp_p to the offset into the literal pool.
|
|
That's the expression: .L^Ax^By-.L^Ax */
|
|
exp_p->X_add_symbol = lpe->sym;
|
|
exp_p->X_op_symbol = lp_sym;
|
|
exp_p->X_op = O_subtract;
|
|
exp_p->X_add_number = 0;
|
|
|
|
*str_p = str;
|
|
|
|
/* We change the suffix type to ELF_SUFFIX_NONE, because
|
|
the difference of two local labels is just a number. */
|
|
return ELF_SUFFIX_NONE;
|
|
}
|
|
|
|
/* Like normal .long/.short/.word, except support @got, etc.
|
|
clobbers input_line_pointer, checks end-of-line. */
|
|
static void
|
|
s390_elf_cons (int nbytes /* 1=.byte, 2=.word, 4=.long */)
|
|
{
|
|
expressionS exp;
|
|
elf_suffix_type suffix;
|
|
|
|
if (is_it_end_of_statement ())
|
|
{
|
|
demand_empty_rest_of_line ();
|
|
return;
|
|
}
|
|
|
|
do
|
|
{
|
|
expression (&exp);
|
|
|
|
if (exp.X_op == O_symbol
|
|
&& *input_line_pointer == '@'
|
|
&& (suffix = s390_elf_suffix (&input_line_pointer, &exp)) != ELF_SUFFIX_NONE)
|
|
{
|
|
bfd_reloc_code_real_type reloc;
|
|
reloc_howto_type *reloc_howto;
|
|
int size;
|
|
char *where;
|
|
|
|
if (nbytes == 2)
|
|
{
|
|
static bfd_reloc_code_real_type tab2[] =
|
|
{
|
|
BFD_RELOC_UNUSED, /* ELF_SUFFIX_NONE */
|
|
BFD_RELOC_390_GOT16, /* ELF_SUFFIX_GOT */
|
|
BFD_RELOC_UNUSED, /* ELF_SUFFIX_PLT */
|
|
BFD_RELOC_UNUSED, /* ELF_SUFFIX_GOTENT */
|
|
BFD_RELOC_16_GOTOFF, /* ELF_SUFFIX_GOTOFF */
|
|
BFD_RELOC_UNUSED, /* ELF_SUFFIX_GOTPLT */
|
|
BFD_RELOC_390_PLTOFF16, /* ELF_SUFFIX_PLTOFF */
|
|
BFD_RELOC_UNUSED, /* ELF_SUFFIX_TLS_GD */
|
|
BFD_RELOC_UNUSED, /* ELF_SUFFIX_TLS_GOTIE */
|
|
BFD_RELOC_UNUSED, /* ELF_SUFFIX_TLS_IE */
|
|
BFD_RELOC_UNUSED, /* ELF_SUFFIX_TLS_LDM */
|
|
BFD_RELOC_UNUSED, /* ELF_SUFFIX_TLS_LDO */
|
|
BFD_RELOC_UNUSED /* ELF_SUFFIX_TLS_LE */
|
|
};
|
|
reloc = tab2[suffix];
|
|
}
|
|
else if (nbytes == 4)
|
|
{
|
|
static bfd_reloc_code_real_type tab4[] =
|
|
{
|
|
BFD_RELOC_UNUSED, /* ELF_SUFFIX_NONE */
|
|
BFD_RELOC_32_GOT_PCREL, /* ELF_SUFFIX_GOT */
|
|
BFD_RELOC_390_PLT32, /* ELF_SUFFIX_PLT */
|
|
BFD_RELOC_UNUSED, /* ELF_SUFFIX_GOTENT */
|
|
BFD_RELOC_32_GOTOFF, /* ELF_SUFFIX_GOTOFF */
|
|
BFD_RELOC_390_GOTPLT32, /* ELF_SUFFIX_GOTPLT */
|
|
BFD_RELOC_390_PLTOFF32, /* ELF_SUFFIX_PLTOFF */
|
|
BFD_RELOC_390_TLS_GD32, /* ELF_SUFFIX_TLS_GD */
|
|
BFD_RELOC_390_TLS_GOTIE32, /* ELF_SUFFIX_TLS_GOTIE */
|
|
BFD_RELOC_390_TLS_IE32, /* ELF_SUFFIX_TLS_IE */
|
|
BFD_RELOC_390_TLS_LDM32, /* ELF_SUFFIX_TLS_LDM */
|
|
BFD_RELOC_390_TLS_LDO32, /* ELF_SUFFIX_TLS_LDO */
|
|
BFD_RELOC_390_TLS_LE32 /* ELF_SUFFIX_TLS_LE */
|
|
};
|
|
reloc = tab4[suffix];
|
|
}
|
|
else if (nbytes == 8)
|
|
{
|
|
static bfd_reloc_code_real_type tab8[] =
|
|
{
|
|
BFD_RELOC_UNUSED, /* ELF_SUFFIX_NONE */
|
|
BFD_RELOC_390_GOT64, /* ELF_SUFFIX_GOT */
|
|
BFD_RELOC_390_PLT64, /* ELF_SUFFIX_PLT */
|
|
BFD_RELOC_UNUSED, /* ELF_SUFFIX_GOTENT */
|
|
BFD_RELOC_390_GOTOFF64, /* ELF_SUFFIX_GOTOFF */
|
|
BFD_RELOC_390_GOTPLT64, /* ELF_SUFFIX_GOTPLT */
|
|
BFD_RELOC_390_PLTOFF64, /* ELF_SUFFIX_PLTOFF */
|
|
BFD_RELOC_390_TLS_GD64, /* ELF_SUFFIX_TLS_GD */
|
|
BFD_RELOC_390_TLS_GOTIE64, /* ELF_SUFFIX_TLS_GOTIE */
|
|
BFD_RELOC_390_TLS_IE64, /* ELF_SUFFIX_TLS_IE */
|
|
BFD_RELOC_390_TLS_LDM64, /* ELF_SUFFIX_TLS_LDM */
|
|
BFD_RELOC_390_TLS_LDO64, /* ELF_SUFFIX_TLS_LDO */
|
|
BFD_RELOC_390_TLS_LE64 /* ELF_SUFFIX_TLS_LE */
|
|
};
|
|
reloc = tab8[suffix];
|
|
}
|
|
else
|
|
reloc = BFD_RELOC_UNUSED;
|
|
|
|
if (reloc != BFD_RELOC_UNUSED
|
|
&& (reloc_howto = bfd_reloc_type_lookup (stdoutput, reloc)))
|
|
{
|
|
size = bfd_get_reloc_size (reloc_howto);
|
|
if (size > nbytes)
|
|
as_bad (ngettext ("%s relocations do not fit in %d byte",
|
|
"%s relocations do not fit in %d bytes",
|
|
nbytes),
|
|
reloc_howto->name, nbytes);
|
|
where = frag_more (nbytes);
|
|
md_number_to_chars (where, 0, size);
|
|
/* To make fixup_segment do the pc relative conversion the
|
|
pcrel parameter on the fix_new_exp call needs to be FALSE. */
|
|
fix_new_exp (frag_now, where - frag_now->fr_literal,
|
|
size, &exp, false, reloc);
|
|
}
|
|
else
|
|
as_bad (_("relocation not applicable"));
|
|
}
|
|
else
|
|
emit_expr (&exp, (unsigned int) nbytes);
|
|
}
|
|
while (*input_line_pointer++ == ',');
|
|
|
|
input_line_pointer--; /* Put terminator back into stream. */
|
|
demand_empty_rest_of_line ();
|
|
}
|
|
|
|
static const char *
|
|
operand_type_str(const struct s390_operand * operand)
|
|
{
|
|
if (operand->flags & S390_OPERAND_BASE)
|
|
return _("base register");
|
|
else if (operand->flags & S390_OPERAND_DISP)
|
|
return _("displacement");
|
|
else if (operand->flags & S390_OPERAND_INDEX)
|
|
{
|
|
if (operand->flags & S390_OPERAND_VR)
|
|
return _("vector index register");
|
|
else
|
|
return _("index register");
|
|
}
|
|
else if (operand->flags & S390_OPERAND_LENGTH)
|
|
return _("length");
|
|
else if (operand->flags & S390_OPERAND_AR)
|
|
return _("access register");
|
|
else if (operand->flags & S390_OPERAND_CR)
|
|
return _("control register");
|
|
else if (operand->flags & S390_OPERAND_FPR)
|
|
return _("floating-point register");
|
|
else if (operand->flags & S390_OPERAND_GPR)
|
|
return _("general-purpose register");
|
|
else if (operand->flags & S390_OPERAND_VR)
|
|
return _("vector register");
|
|
else
|
|
{
|
|
if (operand->flags & S390_OPERAND_SIGNED)
|
|
return _("signed number");
|
|
else
|
|
return _("unsigned number");
|
|
}
|
|
}
|
|
|
|
/* Return true if all remaining operands in the opcode with
|
|
OPCODE_FLAGS can be skipped. */
|
|
static bool
|
|
skip_optargs_p (unsigned int opcode_flags, const unsigned char *opindex_ptr)
|
|
{
|
|
if ((opcode_flags & (S390_INSTR_FLAG_OPTPARM | S390_INSTR_FLAG_OPTPARM2))
|
|
&& opindex_ptr[0] != '\0'
|
|
&& opindex_ptr[1] == '\0')
|
|
return true;
|
|
|
|
if ((opcode_flags & S390_INSTR_FLAG_OPTPARM2)
|
|
&& opindex_ptr[0] != '\0'
|
|
&& opindex_ptr[1] != '\0'
|
|
&& opindex_ptr[2] == '\0')
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* We need to keep a list of fixups. We can't simply generate them as
|
|
we go, because that would require us to first create the frag, and
|
|
that would screw up references to ``.''. */
|
|
|
|
struct s390_fixup
|
|
{
|
|
expressionS exp;
|
|
int opindex;
|
|
bfd_reloc_code_real_type reloc;
|
|
};
|
|
|
|
#define MAX_INSN_FIXUPS (4)
|
|
|
|
/* This routine is called for each instruction to be assembled. */
|
|
|
|
static char *
|
|
md_gather_operands (char *str,
|
|
unsigned char *insn,
|
|
const struct s390_opcode *opcode)
|
|
{
|
|
struct s390_fixup fixups[MAX_INSN_FIXUPS];
|
|
const struct s390_operand *operand;
|
|
const unsigned char *opindex_ptr;
|
|
expressionS ex;
|
|
elf_suffix_type suffix;
|
|
bfd_reloc_code_real_type reloc;
|
|
int omitted_base_or_index;
|
|
int operand_number;
|
|
char *f;
|
|
int fc, i;
|
|
|
|
while (ISSPACE (*str))
|
|
str++;
|
|
|
|
/* Gather the operands. */
|
|
omitted_base_or_index = 0; /* Whether B in D(L,B) or X in D(X,B) were omitted. */
|
|
operand_number = 1; /* Current operand number in e.g. R1,I2,M3,D4(B4). */
|
|
fc = 0;
|
|
for (opindex_ptr = opcode->operands; *opindex_ptr != 0; opindex_ptr++)
|
|
{
|
|
char *hold;
|
|
|
|
operand = s390_operands + *opindex_ptr;
|
|
|
|
if ((opcode->flags & (S390_INSTR_FLAG_OPTPARM | S390_INSTR_FLAG_OPTPARM2))
|
|
&& *str == '\0')
|
|
{
|
|
/* Optional parameters might need to be ORed with a
|
|
value so calling s390_insert_operand is needed. */
|
|
s390_insert_operand (insn, operand, 0, NULL, 0, operand_number);
|
|
break;
|
|
}
|
|
|
|
if (omitted_base_or_index && (operand->flags & S390_OPERAND_INDEX))
|
|
{
|
|
/* Skip omitted optional index register operand in D(X,B) due to
|
|
D(,B) or D(B). Skip comma, if D(,B). */
|
|
if (*str == ',')
|
|
str++;
|
|
omitted_base_or_index = 0;
|
|
continue;
|
|
}
|
|
|
|
/* Gather the operand. */
|
|
hold = input_line_pointer;
|
|
input_line_pointer = str;
|
|
|
|
/* Parse the operand. */
|
|
if (! register_name (&ex))
|
|
{
|
|
expression (&ex);
|
|
resolve_register (&ex);
|
|
}
|
|
|
|
str = input_line_pointer;
|
|
input_line_pointer = hold;
|
|
|
|
/* Write the operand to the insn. */
|
|
if (ex.X_op == O_illegal)
|
|
as_bad (_("operand %d: illegal operand"), operand_number);
|
|
else if (ex.X_op == O_absent)
|
|
{
|
|
if (opindex_ptr[0] == '\0')
|
|
break;
|
|
as_bad (_("operand %d: missing %s operand"), operand_number,
|
|
operand_type_str(operand));
|
|
}
|
|
else if (ex.X_op == O_register || ex.X_op == O_constant)
|
|
{
|
|
s390_lit_suffix (&str, &ex, ELF_SUFFIX_NONE);
|
|
|
|
if (ex.X_op != O_register && ex.X_op != O_constant)
|
|
{
|
|
/* We need to generate a fixup for the
|
|
expression returned by s390_lit_suffix. */
|
|
if (fc >= MAX_INSN_FIXUPS)
|
|
as_fatal (_("operand %d: too many fixups"), operand_number);
|
|
fixups[fc].exp = ex;
|
|
fixups[fc].opindex = *opindex_ptr;
|
|
fixups[fc].reloc = BFD_RELOC_UNUSED;
|
|
++fc;
|
|
}
|
|
else
|
|
{
|
|
if ((operand->flags & S390_OPERAND_LENGTH)
|
|
&& ex.X_op != O_constant)
|
|
as_bad (_("operand %d: invalid length field specified"),
|
|
operand_number);
|
|
if ((operand->flags & S390_OPERAND_INDEX)
|
|
&& ex.X_add_number == 0
|
|
&& warn_areg_zero)
|
|
as_warn (_("operand %d: index register specified but zero"),
|
|
operand_number);
|
|
if ((operand->flags & S390_OPERAND_BASE)
|
|
&& ex.X_add_number == 0
|
|
&& warn_areg_zero)
|
|
as_warn (_("operand %d: base register specified but zero"),
|
|
operand_number);
|
|
if ((operand->flags & S390_OPERAND_GPR)
|
|
&& (operand->flags & S390_OPERAND_REG_PAIR)
|
|
&& (ex.X_add_number & 1))
|
|
as_bad (_("operand %d: odd numbered general purpose register "
|
|
"specified as register pair"), operand_number);
|
|
if ((operand->flags & S390_OPERAND_FPR)
|
|
&& (operand->flags & S390_OPERAND_REG_PAIR)
|
|
&& ex.X_add_number != 0 && ex.X_add_number != 1
|
|
&& ex.X_add_number != 4 && ex.X_add_number != 5
|
|
&& ex.X_add_number != 8 && ex.X_add_number != 9
|
|
&& ex.X_add_number != 12 && ex.X_add_number != 13)
|
|
as_bad (_("operand %d: invalid floating-point register (FPR) "
|
|
"pair (valid FPR pair operands are 0, 1, 4, 5, 8, 9, "
|
|
"12 or 13)"), operand_number);
|
|
if (warn_regtype_mismatch && ex.X_op == O_register
|
|
&& !(opcode->flags & S390_INSTR_FLAG_PSEUDO_MNEMONIC))
|
|
{
|
|
const char *expected_regtype = NULL;
|
|
|
|
if ((operand->flags & S390_OPERAND_AR)
|
|
&& ex.X_md != S390_REGTYPE_AR)
|
|
expected_regtype = _("access register");
|
|
else if ((operand->flags & S390_OPERAND_CR)
|
|
&& ex.X_md != S390_REGTYPE_CR)
|
|
expected_regtype = _("control register");
|
|
else if ((operand->flags & S390_OPERAND_FPR)
|
|
&& ex.X_md != S390_REGTYPE_FPR
|
|
&& (warn_regtype_mismatch == S390_REGTYPE_CHECK_STRICT
|
|
|| (ex.X_md != S390_REGTYPE_VR)))
|
|
expected_regtype = _("floating-point register");
|
|
else if ((operand->flags & S390_OPERAND_GPR)
|
|
&& ex.X_md != S390_REGTYPE_GR)
|
|
expected_regtype = _("general register");
|
|
else if ((operand->flags & S390_OPERAND_VR)
|
|
&& ex.X_md != S390_REGTYPE_VR
|
|
&& (warn_regtype_mismatch == S390_REGTYPE_CHECK_STRICT
|
|
|| (ex.X_md != S390_REGTYPE_FPR)))
|
|
expected_regtype = _("vector register");
|
|
|
|
if (expected_regtype)
|
|
{
|
|
if (operand->flags & S390_OPERAND_BASE)
|
|
as_warn (_("operand %d: expected %s name as base register"),
|
|
operand_number, expected_regtype);
|
|
else if (operand->flags & S390_OPERAND_INDEX)
|
|
as_warn (_("operand %d: expected %s name as index register"),
|
|
operand_number, expected_regtype);
|
|
else
|
|
as_warn (_("operand %d: expected %s name"),
|
|
operand_number, expected_regtype);
|
|
}
|
|
}
|
|
s390_insert_operand (insn, operand, ex.X_add_number, NULL, 0, operand_number);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
suffix = s390_elf_suffix (&str, &ex);
|
|
suffix = s390_lit_suffix (&str, &ex, suffix);
|
|
reloc = BFD_RELOC_UNUSED;
|
|
|
|
if (suffix == ELF_SUFFIX_GOT)
|
|
{
|
|
if ((operand->flags & S390_OPERAND_DISP) &&
|
|
(operand->bits == 12))
|
|
reloc = BFD_RELOC_390_GOT12;
|
|
else if ((operand->flags & S390_OPERAND_DISP) &&
|
|
(operand->bits == 20))
|
|
reloc = BFD_RELOC_390_GOT20;
|
|
else if ((operand->flags & S390_OPERAND_SIGNED)
|
|
&& (operand->bits == 16))
|
|
reloc = BFD_RELOC_390_GOT16;
|
|
else if ((operand->flags & S390_OPERAND_PCREL)
|
|
&& (operand->bits == 32))
|
|
reloc = BFD_RELOC_390_GOTENT;
|
|
}
|
|
else if (suffix == ELF_SUFFIX_PLT)
|
|
{
|
|
if ((operand->flags & S390_OPERAND_PCREL)
|
|
&& (operand->bits == 12))
|
|
reloc = BFD_RELOC_390_PLT12DBL;
|
|
else if ((operand->flags & S390_OPERAND_PCREL)
|
|
&& (operand->bits == 16))
|
|
reloc = BFD_RELOC_390_PLT16DBL;
|
|
else if ((operand->flags & S390_OPERAND_PCREL)
|
|
&& (operand->bits == 24))
|
|
reloc = BFD_RELOC_390_PLT24DBL;
|
|
else if ((operand->flags & S390_OPERAND_PCREL)
|
|
&& (operand->bits == 32))
|
|
reloc = BFD_RELOC_390_PLT32DBL;
|
|
}
|
|
else if (suffix == ELF_SUFFIX_GOTENT)
|
|
{
|
|
if ((operand->flags & S390_OPERAND_PCREL)
|
|
&& (operand->bits == 32))
|
|
reloc = BFD_RELOC_390_GOTENT;
|
|
}
|
|
else if (suffix == ELF_SUFFIX_GOTOFF)
|
|
{
|
|
if ((operand->flags & S390_OPERAND_SIGNED)
|
|
&& (operand->bits == 16))
|
|
reloc = BFD_RELOC_16_GOTOFF;
|
|
}
|
|
else if (suffix == ELF_SUFFIX_PLTOFF)
|
|
{
|
|
if ((operand->flags & S390_OPERAND_SIGNED)
|
|
&& (operand->bits == 16))
|
|
reloc = BFD_RELOC_390_PLTOFF16;
|
|
}
|
|
else if (suffix == ELF_SUFFIX_GOTPLT)
|
|
{
|
|
if ((operand->flags & S390_OPERAND_DISP)
|
|
&& (operand->bits == 12))
|
|
reloc = BFD_RELOC_390_GOTPLT12;
|
|
else if ((operand->flags & S390_OPERAND_SIGNED)
|
|
&& (operand->bits == 16))
|
|
reloc = BFD_RELOC_390_GOTPLT16;
|
|
else if ((operand->flags & S390_OPERAND_PCREL)
|
|
&& (operand->bits == 32))
|
|
reloc = BFD_RELOC_390_GOTPLTENT;
|
|
}
|
|
else if (suffix == ELF_SUFFIX_TLS_GOTIE)
|
|
{
|
|
if ((operand->flags & S390_OPERAND_DISP)
|
|
&& (operand->bits == 12))
|
|
reloc = BFD_RELOC_390_TLS_GOTIE12;
|
|
else if ((operand->flags & S390_OPERAND_DISP)
|
|
&& (operand->bits == 20))
|
|
reloc = BFD_RELOC_390_TLS_GOTIE20;
|
|
}
|
|
else if (suffix == ELF_SUFFIX_TLS_IE)
|
|
{
|
|
if ((operand->flags & S390_OPERAND_PCREL)
|
|
&& (operand->bits == 32))
|
|
reloc = BFD_RELOC_390_TLS_IEENT;
|
|
}
|
|
|
|
if (suffix != ELF_SUFFIX_NONE && reloc == BFD_RELOC_UNUSED)
|
|
as_bad (_("operand %d: invalid operand suffix"), operand_number);
|
|
/* We need to generate a fixup of type 'reloc' for this
|
|
expression. */
|
|
if (fc >= MAX_INSN_FIXUPS)
|
|
as_fatal (_("operand %d: too many fixups"), operand_number);
|
|
fixups[fc].exp = ex;
|
|
fixups[fc].opindex = *opindex_ptr;
|
|
fixups[fc].reloc = reloc;
|
|
++fc;
|
|
}
|
|
|
|
/* Check the next character. The call to expression has advanced
|
|
str past any whitespace. */
|
|
if (operand->flags & S390_OPERAND_DISP)
|
|
{
|
|
/* After a displacement a block in parentheses can start. */
|
|
if (*str != '(')
|
|
{
|
|
/* There is no opening parentheses. Check if operands of
|
|
parenthesized block can be skipped. Only index and base
|
|
register operands as well as optional operands may be
|
|
skipped. A length operand may not be skipped. */
|
|
operand = s390_operands + *(++opindex_ptr);
|
|
if (!(operand->flags & (S390_OPERAND_INDEX|S390_OPERAND_BASE)))
|
|
as_bad (_("operand %d: syntax error; missing '(' after displacement"),
|
|
operand_number);
|
|
|
|
/* Ok, skip all operands until S390_OPERAND_BASE. */
|
|
while (!(operand->flags & S390_OPERAND_BASE))
|
|
operand = s390_operands + *(++opindex_ptr);
|
|
|
|
/* If there is no further input and the remaining operands are
|
|
optional then have these optional operands processed. */
|
|
if (*str == '\0' && skip_optargs_p (opcode->flags, &opindex_ptr[1]))
|
|
continue;
|
|
|
|
/* If there is a next operand it must be separated by a comma. */
|
|
if (opindex_ptr[1] != '\0')
|
|
{
|
|
if (*str != ',')
|
|
{
|
|
/* There is no comma. Skip all operands and stop. */
|
|
while (opindex_ptr[1] != '\0')
|
|
{
|
|
operand = s390_operands + *(++opindex_ptr);
|
|
as_bad (_("operand %d: syntax error; expected ','"),
|
|
operand_number);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Comma. */
|
|
str++;
|
|
operand_number++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* We found an opening parentheses. */
|
|
str++;
|
|
for (f = str; *f != '\0'; f++)
|
|
if (*f == ',' || *f == ')')
|
|
break;
|
|
/* If there is no comma until the closing parenthesis ')' or
|
|
there is a comma right after the opening parenthesis '(',
|
|
we have to skip the omitted optional index or base register
|
|
operand:
|
|
- Index X in D(X,B), when D(,B) or D(B)
|
|
- Base B in D(L,B), when D(L) */
|
|
if (*f == ',' && f == str)
|
|
{
|
|
/* Comma directly after opening parenthesis '(' ? */
|
|
omitted_base_or_index = 1;
|
|
}
|
|
else
|
|
omitted_base_or_index = (*f != ',');
|
|
}
|
|
}
|
|
else if (operand->flags & S390_OPERAND_BASE)
|
|
{
|
|
/* After the base register the parenthesised block ends. */
|
|
if (*str != ')')
|
|
as_bad (_("operand %d: syntax error; missing ')' after base register"),
|
|
operand_number);
|
|
else
|
|
str++;
|
|
omitted_base_or_index = 0;
|
|
|
|
/* If there is no further input and the remaining operands are
|
|
optional then have these optional operands processed. */
|
|
if (*str == '\0' && skip_optargs_p (opcode->flags, &opindex_ptr[1]))
|
|
continue;
|
|
|
|
/* If there is a next operand it must be separated by a comma. */
|
|
if (opindex_ptr[1] != '\0')
|
|
{
|
|
if (*str != ',')
|
|
{
|
|
/* There is no comma. Skip all operands and stop. */
|
|
while (opindex_ptr[1] != '\0')
|
|
{
|
|
operand = s390_operands + *(++opindex_ptr);
|
|
as_bad (_("operand %d: syntax error; expected ','"),
|
|
operand_number);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Comma. */
|
|
str++;
|
|
operand_number++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* We can find an 'early' closing parentheses in e.g. D(L) instead
|
|
of D(L,B). In this case the base register has to be skipped.
|
|
Same if the base register has been explicilty omitted in e.g.
|
|
D(X,) or D(L,). */
|
|
if (*str == ')' || (str[0] == ',' && str[1] == ')'))
|
|
{
|
|
operand = s390_operands + *(++opindex_ptr);
|
|
|
|
if (!(operand->flags & S390_OPERAND_BASE))
|
|
as_bad (_("operand %d: syntax error; '%c' not allowed here"),
|
|
operand_number, *str);
|
|
if (*str == ',')
|
|
str++;
|
|
str++;
|
|
}
|
|
|
|
/* If there is no further input and the remaining operands are
|
|
optional then have these optional operands processed. */
|
|
if (*str == '\0' && skip_optargs_p (opcode->flags, &opindex_ptr[1]))
|
|
continue;
|
|
|
|
/* If there is a next operand it must be separated by a comma. */
|
|
if (opindex_ptr[1] != '\0')
|
|
{
|
|
if (*str != ',')
|
|
{
|
|
/* There is no comma. Skip all operands and stop. */
|
|
while (opindex_ptr[1] != '\0')
|
|
{
|
|
operand = s390_operands + *(++opindex_ptr);
|
|
as_bad (_("operand %d: syntax error; expected ','"),
|
|
operand_number);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Comma. */
|
|
str++;
|
|
if (!(operand->flags & (S390_OPERAND_INDEX
|
|
| S390_OPERAND_LENGTH)))
|
|
operand_number++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
while (ISSPACE (*str))
|
|
str++;
|
|
|
|
/* Check for tls instruction marker. */
|
|
reloc = s390_tls_suffix (&str, &ex);
|
|
if (reloc != BFD_RELOC_UNUSED)
|
|
{
|
|
/* We need to generate a fixup of type 'reloc' for this
|
|
instruction. */
|
|
if (fc >= MAX_INSN_FIXUPS)
|
|
as_fatal (_("too many fixups"));
|
|
fixups[fc].exp = ex;
|
|
fixups[fc].opindex = -1;
|
|
fixups[fc].reloc = reloc;
|
|
++fc;
|
|
}
|
|
|
|
if (*str != '\0')
|
|
{
|
|
char *linefeed;
|
|
|
|
if ((linefeed = strchr (str, '\n')) != NULL)
|
|
*linefeed = '\0';
|
|
as_bad (_("junk at end of line: `%s'"), str);
|
|
if (linefeed != NULL)
|
|
*linefeed = '\n';
|
|
}
|
|
|
|
/* Write out the instruction. */
|
|
f = frag_more (opcode->oplen);
|
|
memcpy (f, insn, opcode->oplen);
|
|
dwarf2_emit_insn (opcode->oplen);
|
|
|
|
/* Create any fixups. At this point we do not use a
|
|
bfd_reloc_code_real_type, but instead just use the
|
|
BFD_RELOC_UNUSED plus the operand index. This lets us easily
|
|
handle fixups for any operand type, although that is admittedly
|
|
not a very exciting feature. We pick a BFD reloc type in
|
|
md_apply_fix. */
|
|
for (i = 0; i < fc; i++)
|
|
{
|
|
fixS *fixP;
|
|
|
|
if (fixups[i].opindex < 0)
|
|
{
|
|
/* Create tls instruction marker relocation. */
|
|
fix_new_exp (frag_now, f - frag_now->fr_literal, opcode->oplen,
|
|
&fixups[i].exp, 0, fixups[i].reloc);
|
|
continue;
|
|
}
|
|
|
|
operand = s390_operands + fixups[i].opindex;
|
|
|
|
if (fixups[i].reloc != BFD_RELOC_UNUSED)
|
|
{
|
|
reloc_howto_type *reloc_howto;
|
|
int size;
|
|
|
|
reloc_howto = bfd_reloc_type_lookup (stdoutput, fixups[i].reloc);
|
|
if (!reloc_howto)
|
|
abort ();
|
|
|
|
size = ((reloc_howto->bitsize - 1) / 8) + 1;
|
|
|
|
if (size < 1 || size > 4)
|
|
abort ();
|
|
|
|
fixP = fix_new_exp (frag_now,
|
|
f - frag_now->fr_literal + (operand->shift/8),
|
|
size, &fixups[i].exp, reloc_howto->pc_relative,
|
|
fixups[i].reloc);
|
|
/* Turn off overflow checking in fixup_segment. This is necessary
|
|
because fixup_segment will signal an overflow for large 4 byte
|
|
quantities for GOT12 relocations. */
|
|
if ( fixups[i].reloc == BFD_RELOC_390_GOT12
|
|
|| fixups[i].reloc == BFD_RELOC_390_GOT20
|
|
|| fixups[i].reloc == BFD_RELOC_390_GOT16)
|
|
fixP->fx_no_overflow = 1;
|
|
|
|
if (operand->flags & S390_OPERAND_PCREL)
|
|
fixP->fx_pcrel_adjust = operand->shift / 8;
|
|
}
|
|
else
|
|
fixP = fix_new_exp (frag_now, f - frag_now->fr_literal, 4,
|
|
&fixups[i].exp,
|
|
(operand->flags & S390_OPERAND_PCREL) != 0,
|
|
((bfd_reloc_code_real_type)
|
|
(fixups[i].opindex + (int) BFD_RELOC_UNUSED)));
|
|
/* s390_insert_operand () does the range checking. */
|
|
if (operand->flags & S390_OPERAND_PCREL)
|
|
fixP->fx_no_overflow = 1;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
/* This routine is called for each instruction to be assembled. */
|
|
|
|
void
|
|
md_assemble (char *str)
|
|
{
|
|
const struct s390_opcode *opcode;
|
|
unsigned char insn[6];
|
|
char *s;
|
|
|
|
/* Get the opcode. */
|
|
for (s = str; *s != '\0' && ! ISSPACE (*s); s++)
|
|
;
|
|
if (*s != '\0')
|
|
*s++ = '\0';
|
|
|
|
/* Look up the opcode in the hash table. */
|
|
opcode = (struct s390_opcode *) str_hash_find (s390_opcode_hash, str);
|
|
if (opcode == (const struct s390_opcode *) NULL)
|
|
{
|
|
as_bad (_("Unrecognized opcode: `%s'"), str);
|
|
return;
|
|
}
|
|
else if (!(opcode->modes & current_mode_mask))
|
|
{
|
|
as_bad (_("Opcode %s not available in this mode"), str);
|
|
return;
|
|
}
|
|
memcpy (insn, opcode->opcode, sizeof (insn));
|
|
md_gather_operands (s, insn, opcode);
|
|
}
|
|
|
|
#ifndef WORKING_DOT_WORD
|
|
/* Handle long and short jumps. We don't support these */
|
|
void
|
|
md_create_short_jump (ptr, from_addr, to_addr, frag, to_symbol)
|
|
char *ptr;
|
|
addressT from_addr, to_addr;
|
|
fragS *frag;
|
|
symbolS *to_symbol;
|
|
{
|
|
abort ();
|
|
}
|
|
|
|
void
|
|
md_create_long_jump (ptr, from_addr, to_addr, frag, to_symbol)
|
|
char *ptr;
|
|
addressT from_addr, to_addr;
|
|
fragS *frag;
|
|
symbolS *to_symbol;
|
|
{
|
|
abort ();
|
|
}
|
|
#endif
|
|
|
|
/* Pseudo-op handling. */
|
|
|
|
void
|
|
s390_insn (int ignore ATTRIBUTE_UNUSED)
|
|
{
|
|
expressionS exp;
|
|
const struct s390_opcode *opformat;
|
|
unsigned char insn[6];
|
|
char *s;
|
|
|
|
/* Get the opcode format. */
|
|
s = input_line_pointer;
|
|
while (*s != '\0' && *s != ',' && ! ISSPACE (*s))
|
|
s++;
|
|
if (*s != ',')
|
|
as_bad (_("Invalid .insn format\n"));
|
|
*s++ = '\0';
|
|
|
|
/* Look up the opcode in the hash table. */
|
|
opformat = (struct s390_opcode *)
|
|
str_hash_find (s390_opformat_hash, input_line_pointer);
|
|
if (opformat == (const struct s390_opcode *) NULL)
|
|
{
|
|
as_bad (_("Unrecognized opcode format: `%s'"), input_line_pointer);
|
|
return;
|
|
}
|
|
input_line_pointer = s;
|
|
expression (&exp);
|
|
if (exp.X_op == O_constant)
|
|
{
|
|
if ( ( opformat->oplen == 6
|
|
&& (addressT) exp.X_add_number < (1ULL << 48))
|
|
|| ( opformat->oplen == 4
|
|
&& (addressT) exp.X_add_number < (1ULL << 32))
|
|
|| ( opformat->oplen == 2
|
|
&& (addressT) exp.X_add_number < (1ULL << 16)))
|
|
md_number_to_chars ((char *) insn, exp.X_add_number, opformat->oplen);
|
|
else
|
|
as_bad (_("Invalid .insn format\n"));
|
|
}
|
|
else if (exp.X_op == O_big)
|
|
{
|
|
if (exp.X_add_number > 0
|
|
&& opformat->oplen == 6
|
|
&& generic_bignum[3] == 0)
|
|
{
|
|
md_number_to_chars ((char *) insn, generic_bignum[2], 2);
|
|
md_number_to_chars ((char *) &insn[2], generic_bignum[1], 2);
|
|
md_number_to_chars ((char *) &insn[4], generic_bignum[0], 2);
|
|
}
|
|
else
|
|
as_bad (_("Invalid .insn format\n"));
|
|
}
|
|
else
|
|
as_bad (_("second operand of .insn not a constant\n"));
|
|
|
|
if (strcmp (opformat->name, "e") != 0 && *input_line_pointer++ != ',')
|
|
as_bad (_("missing comma after insn constant\n"));
|
|
|
|
if ((s = strchr (input_line_pointer, '\n')) != NULL)
|
|
*s = '\0';
|
|
input_line_pointer = md_gather_operands (input_line_pointer, insn,
|
|
opformat);
|
|
if (s != NULL)
|
|
*s = '\n';
|
|
demand_empty_rest_of_line ();
|
|
}
|
|
|
|
/* The .byte pseudo-op. This is similar to the normal .byte
|
|
pseudo-op, but it can also take a single ASCII string. */
|
|
|
|
static void
|
|
s390_byte (int ignore ATTRIBUTE_UNUSED)
|
|
{
|
|
if (*input_line_pointer != '\"')
|
|
{
|
|
cons (1);
|
|
return;
|
|
}
|
|
|
|
/* Gather characters. A real double quote is doubled. Unusual
|
|
characters are not permitted. */
|
|
++input_line_pointer;
|
|
while (1)
|
|
{
|
|
char c;
|
|
|
|
c = *input_line_pointer++;
|
|
|
|
if (c == '\"')
|
|
{
|
|
if (*input_line_pointer != '\"')
|
|
break;
|
|
++input_line_pointer;
|
|
}
|
|
|
|
FRAG_APPEND_1_CHAR (c);
|
|
}
|
|
|
|
demand_empty_rest_of_line ();
|
|
}
|
|
|
|
/* The .ltorg pseudo-op.This emits all literals defined since the last
|
|
.ltorg or the invocation of gas. Literals are defined with the
|
|
@lit suffix. */
|
|
|
|
static void
|
|
s390_literals (int ignore ATTRIBUTE_UNUSED)
|
|
{
|
|
struct s390_lpe *lpe;
|
|
|
|
if (lp_sym == NULL || lpe_count == 0)
|
|
return; /* Nothing to be done. */
|
|
|
|
/* Emit symbol for start of literal pool. */
|
|
S_SET_SEGMENT (lp_sym, now_seg);
|
|
S_SET_VALUE (lp_sym, (valueT) frag_now_fix ());
|
|
symbol_set_frag (lp_sym, frag_now);
|
|
|
|
while (lpe_list)
|
|
{
|
|
lpe = lpe_list;
|
|
lpe_list = lpe_list->next;
|
|
S_SET_SEGMENT (lpe->sym, now_seg);
|
|
S_SET_VALUE (lpe->sym, (valueT) frag_now_fix ());
|
|
symbol_set_frag (lpe->sym, frag_now);
|
|
|
|
/* Emit literal pool entry. */
|
|
if (lpe->reloc != BFD_RELOC_UNUSED)
|
|
{
|
|
reloc_howto_type *reloc_howto =
|
|
bfd_reloc_type_lookup (stdoutput, lpe->reloc);
|
|
int size = bfd_get_reloc_size (reloc_howto);
|
|
char *where;
|
|
|
|
if (size > lpe->nbytes)
|
|
as_bad (ngettext ("%s relocations do not fit in %d byte",
|
|
"%s relocations do not fit in %d bytes",
|
|
lpe->nbytes),
|
|
reloc_howto->name, lpe->nbytes);
|
|
where = frag_more (lpe->nbytes);
|
|
md_number_to_chars (where, 0, size);
|
|
fix_new_exp (frag_now, where - frag_now->fr_literal,
|
|
size, &lpe->ex, reloc_howto->pc_relative, lpe->reloc);
|
|
}
|
|
else
|
|
{
|
|
if (lpe->ex.X_op == O_big)
|
|
{
|
|
if (lpe->ex.X_add_number <= 0)
|
|
generic_floating_point_number = lpe->floatnum;
|
|
else
|
|
memcpy (generic_bignum, lpe->bignum,
|
|
lpe->ex.X_add_number * sizeof (LITTLENUM_TYPE));
|
|
}
|
|
emit_expr (&lpe->ex, lpe->nbytes);
|
|
}
|
|
|
|
lpe->next = lpe_free_list;
|
|
lpe_free_list = lpe;
|
|
}
|
|
lpe_list_tail = NULL;
|
|
lp_sym = NULL;
|
|
lp_count++;
|
|
lpe_count = 0;
|
|
}
|
|
|
|
#define MAX_HISTORY 100
|
|
|
|
/* The .machine pseudo op allows one to switch to a different CPU level in
|
|
the asm listing. The current CPU setting can be stored on a stack
|
|
with .machine push and restored with .machine pop. */
|
|
|
|
static void
|
|
s390_machine (int ignore ATTRIBUTE_UNUSED)
|
|
{
|
|
char *cpu_string;
|
|
static struct cpu_history
|
|
{
|
|
unsigned int cpu;
|
|
unsigned int flags;
|
|
} *cpu_history;
|
|
static int curr_hist;
|
|
|
|
SKIP_WHITESPACE ();
|
|
|
|
if (*input_line_pointer == '"')
|
|
{
|
|
int len;
|
|
cpu_string = demand_copy_C_string (&len);
|
|
}
|
|
else
|
|
{
|
|
char c;
|
|
|
|
cpu_string = input_line_pointer;
|
|
do
|
|
{
|
|
char * str;
|
|
|
|
c = get_symbol_name (&str);
|
|
c = restore_line_pointer (c);
|
|
if (c == '+')
|
|
++ input_line_pointer;
|
|
}
|
|
while (c == '+');
|
|
|
|
c = *input_line_pointer;
|
|
*input_line_pointer = 0;
|
|
cpu_string = xstrdup (cpu_string);
|
|
(void) restore_line_pointer (c);
|
|
}
|
|
|
|
if (cpu_string != NULL)
|
|
{
|
|
unsigned int new_cpu = current_cpu;
|
|
unsigned int new_flags = current_flags;
|
|
|
|
if (strcmp (cpu_string, "push") == 0)
|
|
{
|
|
if (cpu_history == NULL)
|
|
cpu_history = XNEWVEC (struct cpu_history, MAX_HISTORY);
|
|
|
|
if (curr_hist >= MAX_HISTORY)
|
|
as_bad (_(".machine stack overflow"));
|
|
else
|
|
{
|
|
cpu_history[curr_hist].cpu = current_cpu;
|
|
cpu_history[curr_hist].flags = current_flags;
|
|
curr_hist++;
|
|
}
|
|
}
|
|
else if (strcmp (cpu_string, "pop") == 0)
|
|
{
|
|
if (curr_hist <= 0)
|
|
as_bad (_(".machine stack underflow"));
|
|
else
|
|
{
|
|
curr_hist--;
|
|
new_cpu = cpu_history[curr_hist].cpu;
|
|
new_flags = cpu_history[curr_hist].flags;
|
|
}
|
|
}
|
|
else
|
|
new_cpu = s390_parse_cpu (cpu_string, &new_flags, true);
|
|
|
|
if (new_cpu == S390_OPCODE_MAXCPU)
|
|
as_bad (_("invalid machine `%s'"), cpu_string);
|
|
|
|
if (new_cpu != current_cpu || new_flags != current_flags)
|
|
{
|
|
current_cpu = new_cpu;
|
|
current_flags = new_flags;
|
|
s390_setup_opcodes ();
|
|
}
|
|
}
|
|
|
|
demand_empty_rest_of_line ();
|
|
}
|
|
|
|
/* The .machinemode pseudo op allows one to switch to a different
|
|
architecture mode in the asm listing. The current architecture
|
|
mode setting can be stored on a stack with .machinemode push and
|
|
restored with .machinemode pop. */
|
|
|
|
static void
|
|
s390_machinemode (int ignore ATTRIBUTE_UNUSED)
|
|
{
|
|
char *mode_string;
|
|
static unsigned int *mode_history;
|
|
static int curr_hist;
|
|
|
|
SKIP_WHITESPACE ();
|
|
|
|
{
|
|
char c;
|
|
|
|
c = get_symbol_name (&mode_string);
|
|
mode_string = xstrdup (mode_string);
|
|
(void) restore_line_pointer (c);
|
|
}
|
|
|
|
if (mode_string != NULL)
|
|
{
|
|
unsigned int old_mode_mask = current_mode_mask;
|
|
char *p;
|
|
|
|
for (p = mode_string; *p != 0; p++)
|
|
*p = TOLOWER (*p);
|
|
|
|
if (strcmp (mode_string, "push") == 0)
|
|
{
|
|
if (mode_history == NULL)
|
|
mode_history = XNEWVEC (unsigned int, MAX_HISTORY);
|
|
|
|
if (curr_hist >= MAX_HISTORY)
|
|
as_bad (_(".machinemode stack overflow"));
|
|
else
|
|
mode_history[curr_hist++] = current_mode_mask;
|
|
}
|
|
else if (strcmp (mode_string, "pop") == 0)
|
|
{
|
|
if (curr_hist <= 0)
|
|
as_bad (_(".machinemode stack underflow"));
|
|
else
|
|
current_mode_mask = mode_history[--curr_hist];
|
|
}
|
|
else
|
|
{
|
|
if (strcmp (mode_string, "esa") == 0)
|
|
current_mode_mask = 1 << S390_OPCODE_ESA;
|
|
else if (strcmp (mode_string, "zarch") == 0)
|
|
{
|
|
if (s390_arch_size == 32)
|
|
set_highgprs_p = true;
|
|
current_mode_mask = 1 << S390_OPCODE_ZARCH;
|
|
}
|
|
else if (strcmp (mode_string, "zarch_nohighgprs") == 0)
|
|
current_mode_mask = 1 << S390_OPCODE_ZARCH;
|
|
else
|
|
as_bad (_("invalid machine mode `%s'"), mode_string);
|
|
}
|
|
|
|
if (current_mode_mask != old_mode_mask)
|
|
s390_setup_opcodes ();
|
|
}
|
|
|
|
demand_empty_rest_of_line ();
|
|
}
|
|
|
|
#undef MAX_HISTORY
|
|
|
|
const char *
|
|
md_atof (int type, char *litp, int *sizep)
|
|
{
|
|
return ieee_md_atof (type, litp, sizep, true);
|
|
}
|
|
|
|
/* Align a section (I don't know why this is machine dependent). */
|
|
|
|
valueT
|
|
md_section_align (asection *seg, valueT addr)
|
|
{
|
|
int align = bfd_section_alignment (seg);
|
|
|
|
return ((addr + (1 << align) - 1) & -(1 << align));
|
|
}
|
|
|
|
/* We don't have any form of relaxing. */
|
|
|
|
int
|
|
md_estimate_size_before_relax (fragS *fragp ATTRIBUTE_UNUSED,
|
|
asection *seg ATTRIBUTE_UNUSED)
|
|
{
|
|
abort ();
|
|
return 0;
|
|
}
|
|
|
|
/* Convert a machine dependent frag. We never generate these. */
|
|
|
|
void
|
|
md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED,
|
|
asection *sec ATTRIBUTE_UNUSED,
|
|
fragS *fragp ATTRIBUTE_UNUSED)
|
|
{
|
|
abort ();
|
|
}
|
|
|
|
symbolS *
|
|
md_undefined_symbol (char *name)
|
|
{
|
|
if (*name == '_' && *(name + 1) == 'G'
|
|
&& strcmp (name, "_GLOBAL_OFFSET_TABLE_") == 0)
|
|
{
|
|
if (!GOT_symbol)
|
|
{
|
|
if (symbol_find (name))
|
|
as_bad (_("GOT already in symbol table"));
|
|
GOT_symbol = symbol_new (name, undefined_section,
|
|
&zero_address_frag, 0);
|
|
}
|
|
return GOT_symbol;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Functions concerning relocs. */
|
|
|
|
/* The location from which a PC relative jump should be calculated,
|
|
given a PC relative reloc. */
|
|
|
|
long
|
|
md_pcrel_from_section (fixS *fixp, segT sec ATTRIBUTE_UNUSED)
|
|
{
|
|
return fixp->fx_frag->fr_address + fixp->fx_where;
|
|
}
|
|
|
|
/* Here we decide which fixups can be adjusted to make them relative to
|
|
the beginning of the section instead of the symbol. Basically we need
|
|
to make sure that the dynamic relocations are done correctly, so in
|
|
some cases we force the original symbol to be used. */
|
|
int
|
|
tc_s390_fix_adjustable (fixS *fixP)
|
|
{
|
|
/* Don't adjust pc-relative references to merge sections. */
|
|
if (fixP->fx_pcrel
|
|
&& (S_GET_SEGMENT (fixP->fx_addsy)->flags & SEC_MERGE) != 0)
|
|
return 0;
|
|
|
|
/* adjust_reloc_syms doesn't know about the GOT. */
|
|
if ( fixP->fx_r_type == BFD_RELOC_16_GOTOFF
|
|
|| fixP->fx_r_type == BFD_RELOC_32_GOTOFF
|
|
|| fixP->fx_r_type == BFD_RELOC_390_GOTOFF64
|
|
|| fixP->fx_r_type == BFD_RELOC_390_PLTOFF16
|
|
|| fixP->fx_r_type == BFD_RELOC_390_PLTOFF32
|
|
|| fixP->fx_r_type == BFD_RELOC_390_PLTOFF64
|
|
|| fixP->fx_r_type == BFD_RELOC_390_PLT12DBL
|
|
|| fixP->fx_r_type == BFD_RELOC_390_PLT16DBL
|
|
|| fixP->fx_r_type == BFD_RELOC_390_PLT24DBL
|
|
|| fixP->fx_r_type == BFD_RELOC_390_PLT32
|
|
|| fixP->fx_r_type == BFD_RELOC_390_PLT32DBL
|
|
|| fixP->fx_r_type == BFD_RELOC_390_PLT64
|
|
|| fixP->fx_r_type == BFD_RELOC_390_GOT12
|
|
|| fixP->fx_r_type == BFD_RELOC_390_GOT20
|
|
|| fixP->fx_r_type == BFD_RELOC_390_GOT16
|
|
|| fixP->fx_r_type == BFD_RELOC_32_GOT_PCREL
|
|
|| fixP->fx_r_type == BFD_RELOC_390_GOT64
|
|
|| fixP->fx_r_type == BFD_RELOC_390_GOTENT
|
|
|| fixP->fx_r_type == BFD_RELOC_390_GOTPLT12
|
|
|| fixP->fx_r_type == BFD_RELOC_390_GOTPLT16
|
|
|| fixP->fx_r_type == BFD_RELOC_390_GOTPLT20
|
|
|| fixP->fx_r_type == BFD_RELOC_390_GOTPLT32
|
|
|| fixP->fx_r_type == BFD_RELOC_390_GOTPLT64
|
|
|| fixP->fx_r_type == BFD_RELOC_390_GOTPLTENT
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_LOAD
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_GDCALL
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_LDCALL
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_GD32
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_GD64
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_GOTIE12
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_GOTIE20
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_GOTIE32
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_GOTIE64
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_LDM32
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_LDM64
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_IE32
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_IE64
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_IEENT
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_LE32
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_LE64
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_LDO32
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_LDO64
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_DTPMOD
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_DTPOFF
|
|
|| fixP->fx_r_type == BFD_RELOC_390_TLS_TPOFF
|
|
|| fixP->fx_r_type == BFD_RELOC_VTABLE_INHERIT
|
|
|| fixP->fx_r_type == BFD_RELOC_VTABLE_ENTRY)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/* Return true if we must always emit a reloc for a type and false if
|
|
there is some hope of resolving it at assembly time. */
|
|
int
|
|
tc_s390_force_relocation (struct fix *fixp)
|
|
{
|
|
/* Ensure we emit a relocation for every reference to the global
|
|
offset table or to the procedure link table. */
|
|
switch (fixp->fx_r_type)
|
|
{
|
|
case BFD_RELOC_390_GOT12:
|
|
case BFD_RELOC_390_GOT20:
|
|
case BFD_RELOC_32_GOT_PCREL:
|
|
case BFD_RELOC_32_GOTOFF:
|
|
case BFD_RELOC_390_GOTOFF64:
|
|
case BFD_RELOC_390_PLTOFF16:
|
|
case BFD_RELOC_390_PLTOFF32:
|
|
case BFD_RELOC_390_PLTOFF64:
|
|
case BFD_RELOC_390_GOTPC:
|
|
case BFD_RELOC_390_GOT16:
|
|
case BFD_RELOC_390_GOTPCDBL:
|
|
case BFD_RELOC_390_GOT64:
|
|
case BFD_RELOC_390_GOTENT:
|
|
case BFD_RELOC_390_PLT32:
|
|
case BFD_RELOC_390_PLT12DBL:
|
|
case BFD_RELOC_390_PLT16DBL:
|
|
case BFD_RELOC_390_PLT24DBL:
|
|
case BFD_RELOC_390_PLT32DBL:
|
|
case BFD_RELOC_390_PLT64:
|
|
case BFD_RELOC_390_GOTPLT12:
|
|
case BFD_RELOC_390_GOTPLT16:
|
|
case BFD_RELOC_390_GOTPLT20:
|
|
case BFD_RELOC_390_GOTPLT32:
|
|
case BFD_RELOC_390_GOTPLT64:
|
|
case BFD_RELOC_390_GOTPLTENT:
|
|
return 1;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return generic_force_reloc (fixp);
|
|
}
|
|
|
|
/* Apply a fixup to the object code. This is called for all the
|
|
fixups we generated by the call to fix_new_exp, above. In the call
|
|
above we used a reloc code which was the largest legal reloc code
|
|
plus the operand index. Here we undo that to recover the operand
|
|
index. At this point all symbol values should be fully resolved,
|
|
and we attempt to completely resolve the reloc. If we can not do
|
|
that, we determine the correct reloc code and put it back in the
|
|
fixup. */
|
|
|
|
void
|
|
md_apply_fix (fixS *fixP, valueT *valP, segT seg ATTRIBUTE_UNUSED)
|
|
{
|
|
char *where;
|
|
valueT value = *valP;
|
|
|
|
where = fixP->fx_frag->fr_literal + fixP->fx_where;
|
|
|
|
if (fixP->fx_subsy != NULL)
|
|
as_bad_subtract (fixP);
|
|
|
|
if (fixP->fx_addsy != NULL)
|
|
{
|
|
if (fixP->fx_pcrel)
|
|
value += fixP->fx_frag->fr_address + fixP->fx_where;
|
|
}
|
|
else
|
|
fixP->fx_done = 1;
|
|
|
|
if ((int) fixP->fx_r_type >= (int) BFD_RELOC_UNUSED)
|
|
{
|
|
const struct s390_operand *operand;
|
|
int opindex;
|
|
|
|
opindex = (int) fixP->fx_r_type - (int) BFD_RELOC_UNUSED;
|
|
operand = &s390_operands[opindex];
|
|
|
|
if (fixP->fx_done)
|
|
{
|
|
/* Insert the fully resolved operand value. */
|
|
s390_insert_operand ((unsigned char *) where, operand,
|
|
(offsetT) value, fixP->fx_file, fixP->fx_line, 0);
|
|
return;
|
|
}
|
|
|
|
/* Determine a BFD reloc value based on the operand information.
|
|
We are only prepared to turn a few of the operands into
|
|
relocs. */
|
|
fixP->fx_offset = value;
|
|
if (operand->bits == 12 && operand->shift == 20 && !fixP->fx_pcrel)
|
|
{
|
|
fixP->fx_size = 2;
|
|
fixP->fx_where += 2;
|
|
fixP->fx_r_type = BFD_RELOC_390_12;
|
|
}
|
|
else if (operand->bits == 12 && operand->shift == 36 && !fixP->fx_pcrel)
|
|
{
|
|
fixP->fx_size = 2;
|
|
fixP->fx_where += 4;
|
|
fixP->fx_r_type = BFD_RELOC_390_12;
|
|
}
|
|
else if (operand->bits == 20 && operand->shift == 20 && !fixP->fx_pcrel)
|
|
{
|
|
fixP->fx_size = 4;
|
|
fixP->fx_where += 2;
|
|
fixP->fx_r_type = BFD_RELOC_390_20;
|
|
}
|
|
else if (operand->bits == 8 && operand->shift == 8 && !fixP->fx_pcrel)
|
|
{
|
|
fixP->fx_size = 1;
|
|
fixP->fx_where += 1;
|
|
fixP->fx_r_type = BFD_RELOC_8;
|
|
}
|
|
else if (operand->bits == 12 && operand->shift == 12
|
|
&& (operand->flags & S390_OPERAND_PCREL))
|
|
{
|
|
fixP->fx_size = 2;
|
|
fixP->fx_where += 1;
|
|
fixP->fx_offset += 1;
|
|
fixP->fx_pcrel_adjust = 1;
|
|
fixP->fx_r_type = BFD_RELOC_390_PC12DBL;
|
|
}
|
|
else if (operand->bits == 16 && operand->shift == 16)
|
|
{
|
|
fixP->fx_size = 2;
|
|
fixP->fx_where += 2;
|
|
if (operand->flags & S390_OPERAND_PCREL)
|
|
{
|
|
fixP->fx_r_type = BFD_RELOC_390_PC16DBL;
|
|
fixP->fx_offset += 2;
|
|
fixP->fx_pcrel_adjust = 2;
|
|
}
|
|
else if (fixP->fx_pcrel)
|
|
{
|
|
fixP->fx_r_type = BFD_RELOC_16_PCREL;
|
|
fixP->fx_offset += 2;
|
|
fixP->fx_pcrel_adjust = 2;
|
|
}
|
|
else
|
|
fixP->fx_r_type = BFD_RELOC_16;
|
|
}
|
|
else if (operand->bits == 16 && operand->shift == 32
|
|
&& (operand->flags & S390_OPERAND_PCREL))
|
|
{
|
|
fixP->fx_size = 2;
|
|
fixP->fx_where += 4;
|
|
fixP->fx_offset += 4;
|
|
fixP->fx_pcrel_adjust = 4;
|
|
fixP->fx_r_type = BFD_RELOC_390_PC16DBL;
|
|
}
|
|
else if (operand->bits == 24 && operand->shift == 24
|
|
&& (operand->flags & S390_OPERAND_PCREL))
|
|
{
|
|
fixP->fx_size = 3;
|
|
fixP->fx_where += 3;
|
|
fixP->fx_offset += 3;
|
|
fixP->fx_pcrel_adjust = 3;
|
|
fixP->fx_r_type = BFD_RELOC_390_PC24DBL;
|
|
}
|
|
else if (operand->bits == 32 && operand->shift == 16
|
|
&& (operand->flags & S390_OPERAND_PCREL))
|
|
{
|
|
fixP->fx_size = 4;
|
|
fixP->fx_where += 2;
|
|
fixP->fx_offset += 2;
|
|
fixP->fx_pcrel_adjust = 2;
|
|
fixP->fx_r_type = BFD_RELOC_390_PC32DBL;
|
|
}
|
|
else
|
|
{
|
|
const char *sfile;
|
|
unsigned int sline;
|
|
|
|
/* Use expr_symbol_where to see if this is an expression
|
|
symbol. */
|
|
if (expr_symbol_where (fixP->fx_addsy, &sfile, &sline))
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
_("unresolved expression that must be resolved"));
|
|
else
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
_("unsupported relocation type"));
|
|
fixP->fx_done = 1;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (fixP->fx_r_type)
|
|
{
|
|
case BFD_RELOC_8:
|
|
if (fixP->fx_pcrel)
|
|
abort ();
|
|
if (fixP->fx_done)
|
|
md_number_to_chars (where, value, 1);
|
|
break;
|
|
case BFD_RELOC_390_12:
|
|
case BFD_RELOC_390_GOT12:
|
|
case BFD_RELOC_390_GOTPLT12:
|
|
case BFD_RELOC_390_PC12DBL:
|
|
case BFD_RELOC_390_PLT12DBL:
|
|
if (fixP->fx_pcrel)
|
|
value += fixP->fx_pcrel_adjust;
|
|
|
|
if (fixP->fx_done)
|
|
{
|
|
unsigned short mop;
|
|
|
|
if (fixP->fx_pcrel)
|
|
value >>= 1;
|
|
|
|
mop = bfd_getb16 ((unsigned char *) where);
|
|
mop |= (unsigned short) (value & 0xfff);
|
|
bfd_putb16 ((bfd_vma) mop, (unsigned char *) where);
|
|
}
|
|
break;
|
|
|
|
case BFD_RELOC_390_20:
|
|
case BFD_RELOC_390_GOT20:
|
|
case BFD_RELOC_390_GOTPLT20:
|
|
if (fixP->fx_done)
|
|
{
|
|
unsigned int mop;
|
|
mop = bfd_getb32 ((unsigned char *) where);
|
|
mop |= (unsigned int) ((value & 0xfff) << 8 |
|
|
(value & 0xff000) >> 12);
|
|
bfd_putb32 ((bfd_vma) mop, (unsigned char *) where);
|
|
}
|
|
break;
|
|
|
|
case BFD_RELOC_16:
|
|
case BFD_RELOC_GPREL16:
|
|
case BFD_RELOC_16_GOT_PCREL:
|
|
case BFD_RELOC_16_GOTOFF:
|
|
if (fixP->fx_pcrel)
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
_("cannot emit PC relative %s relocation%s%s"),
|
|
bfd_get_reloc_code_name (fixP->fx_r_type),
|
|
fixP->fx_addsy != NULL ? " against " : "",
|
|
(fixP->fx_addsy != NULL
|
|
? S_GET_NAME (fixP->fx_addsy)
|
|
: ""));
|
|
if (fixP->fx_done)
|
|
md_number_to_chars (where, value, 2);
|
|
break;
|
|
case BFD_RELOC_390_GOT16:
|
|
case BFD_RELOC_390_PLTOFF16:
|
|
case BFD_RELOC_390_GOTPLT16:
|
|
if (fixP->fx_done)
|
|
md_number_to_chars (where, value, 2);
|
|
break;
|
|
case BFD_RELOC_390_PC16DBL:
|
|
case BFD_RELOC_390_PLT16DBL:
|
|
value += fixP->fx_pcrel_adjust;
|
|
if (fixP->fx_done)
|
|
md_number_to_chars (where, (offsetT) value >> 1, 2);
|
|
break;
|
|
|
|
case BFD_RELOC_390_PC24DBL:
|
|
case BFD_RELOC_390_PLT24DBL:
|
|
value += fixP->fx_pcrel_adjust;
|
|
if (fixP->fx_done)
|
|
{
|
|
unsigned int mop;
|
|
value >>= 1;
|
|
|
|
mop = bfd_getb32 ((unsigned char *) where - 1);
|
|
mop |= (unsigned int) (value & 0xffffff);
|
|
bfd_putb32 ((bfd_vma) mop, (unsigned char *) where - 1);
|
|
}
|
|
break;
|
|
|
|
case BFD_RELOC_32:
|
|
if (fixP->fx_pcrel)
|
|
fixP->fx_r_type = BFD_RELOC_32_PCREL;
|
|
else
|
|
fixP->fx_r_type = BFD_RELOC_32;
|
|
if (fixP->fx_done)
|
|
md_number_to_chars (where, value, 4);
|
|
break;
|
|
case BFD_RELOC_32_PCREL:
|
|
case BFD_RELOC_32_BASEREL:
|
|
fixP->fx_r_type = BFD_RELOC_32_PCREL;
|
|
if (fixP->fx_done)
|
|
md_number_to_chars (where, value, 4);
|
|
break;
|
|
case BFD_RELOC_32_GOT_PCREL:
|
|
case BFD_RELOC_390_PLTOFF32:
|
|
case BFD_RELOC_390_PLT32:
|
|
case BFD_RELOC_390_GOTPLT32:
|
|
if (fixP->fx_done)
|
|
md_number_to_chars (where, value, 4);
|
|
break;
|
|
case BFD_RELOC_390_PC32DBL:
|
|
case BFD_RELOC_390_PLT32DBL:
|
|
case BFD_RELOC_390_GOTPCDBL:
|
|
case BFD_RELOC_390_GOTENT:
|
|
case BFD_RELOC_390_GOTPLTENT:
|
|
value += fixP->fx_pcrel_adjust;
|
|
if (fixP->fx_done)
|
|
md_number_to_chars (where, (offsetT) value >> 1, 4);
|
|
break;
|
|
|
|
case BFD_RELOC_32_GOTOFF:
|
|
if (fixP->fx_done)
|
|
md_number_to_chars (where, value, sizeof (int));
|
|
break;
|
|
|
|
case BFD_RELOC_390_GOTOFF64:
|
|
if (fixP->fx_done)
|
|
md_number_to_chars (where, value, 8);
|
|
break;
|
|
|
|
case BFD_RELOC_390_GOT64:
|
|
case BFD_RELOC_390_PLTOFF64:
|
|
case BFD_RELOC_390_PLT64:
|
|
case BFD_RELOC_390_GOTPLT64:
|
|
if (fixP->fx_done)
|
|
md_number_to_chars (where, value, 8);
|
|
break;
|
|
|
|
case BFD_RELOC_64:
|
|
if (fixP->fx_pcrel)
|
|
fixP->fx_r_type = BFD_RELOC_64_PCREL;
|
|
else
|
|
fixP->fx_r_type = BFD_RELOC_64;
|
|
if (fixP->fx_done)
|
|
md_number_to_chars (where, value, 8);
|
|
break;
|
|
|
|
case BFD_RELOC_64_PCREL:
|
|
fixP->fx_r_type = BFD_RELOC_64_PCREL;
|
|
if (fixP->fx_done)
|
|
md_number_to_chars (where, value, 8);
|
|
break;
|
|
|
|
case BFD_RELOC_VTABLE_INHERIT:
|
|
case BFD_RELOC_VTABLE_ENTRY:
|
|
fixP->fx_done = 0;
|
|
return;
|
|
|
|
case BFD_RELOC_390_TLS_LOAD:
|
|
case BFD_RELOC_390_TLS_GDCALL:
|
|
case BFD_RELOC_390_TLS_LDCALL:
|
|
case BFD_RELOC_390_TLS_GD32:
|
|
case BFD_RELOC_390_TLS_GD64:
|
|
case BFD_RELOC_390_TLS_GOTIE12:
|
|
case BFD_RELOC_390_TLS_GOTIE20:
|
|
case BFD_RELOC_390_TLS_GOTIE32:
|
|
case BFD_RELOC_390_TLS_GOTIE64:
|
|
case BFD_RELOC_390_TLS_LDM32:
|
|
case BFD_RELOC_390_TLS_LDM64:
|
|
case BFD_RELOC_390_TLS_IE32:
|
|
case BFD_RELOC_390_TLS_IE64:
|
|
case BFD_RELOC_390_TLS_LE32:
|
|
case BFD_RELOC_390_TLS_LE64:
|
|
case BFD_RELOC_390_TLS_LDO32:
|
|
case BFD_RELOC_390_TLS_LDO64:
|
|
case BFD_RELOC_390_TLS_DTPMOD:
|
|
case BFD_RELOC_390_TLS_DTPOFF:
|
|
case BFD_RELOC_390_TLS_TPOFF:
|
|
S_SET_THREAD_LOCAL (fixP->fx_addsy);
|
|
/* Fully resolved at link time. */
|
|
break;
|
|
case BFD_RELOC_390_TLS_IEENT:
|
|
/* Fully resolved at link time. */
|
|
S_SET_THREAD_LOCAL (fixP->fx_addsy);
|
|
value += 2;
|
|
break;
|
|
|
|
default:
|
|
{
|
|
const char *reloc_name = bfd_get_reloc_code_name (fixP->fx_r_type);
|
|
|
|
if (reloc_name != NULL)
|
|
as_fatal (_("Gas failure, reloc type %s\n"), reloc_name);
|
|
else
|
|
as_fatal (_("Gas failure, reloc type #%i\n"), fixP->fx_r_type);
|
|
}
|
|
}
|
|
|
|
fixP->fx_offset = value;
|
|
}
|
|
}
|
|
|
|
/* Generate a reloc for a fixup. */
|
|
|
|
arelent *
|
|
tc_gen_reloc (asection *seg ATTRIBUTE_UNUSED, fixS *fixp)
|
|
{
|
|
bfd_reloc_code_real_type code;
|
|
arelent *reloc;
|
|
|
|
code = fixp->fx_r_type;
|
|
if (GOT_symbol && fixp->fx_addsy == GOT_symbol)
|
|
{
|
|
if ( (s390_arch_size == 32 && code == BFD_RELOC_32_PCREL)
|
|
|| (s390_arch_size == 64 && code == BFD_RELOC_64_PCREL))
|
|
code = BFD_RELOC_390_GOTPC;
|
|
if (code == BFD_RELOC_390_PC32DBL)
|
|
code = BFD_RELOC_390_GOTPCDBL;
|
|
}
|
|
|
|
reloc = XNEW (arelent);
|
|
reloc->sym_ptr_ptr = XNEW (asymbol *);
|
|
*reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy);
|
|
reloc->address = fixp->fx_frag->fr_address + fixp->fx_where;
|
|
reloc->howto = bfd_reloc_type_lookup (stdoutput, code);
|
|
if (reloc->howto == NULL)
|
|
{
|
|
as_bad_where (fixp->fx_file, fixp->fx_line,
|
|
_("cannot represent relocation type %s"),
|
|
bfd_get_reloc_code_name (code));
|
|
/* Set howto to a garbage value so that we can keep going. */
|
|
reloc->howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_32);
|
|
gas_assert (reloc->howto != NULL);
|
|
}
|
|
reloc->addend = fixp->fx_offset;
|
|
|
|
return reloc;
|
|
}
|
|
|
|
void
|
|
s390_cfi_frame_initial_instructions (void)
|
|
{
|
|
cfi_add_CFA_def_cfa (15, s390_arch_size == 64 ? 160 : 96);
|
|
}
|
|
|
|
int
|
|
tc_s390_regname_to_dw2regnum (char *regname)
|
|
{
|
|
int regnum = -1;
|
|
|
|
if (regname[0] != 'c' && regname[0] != 'a')
|
|
{
|
|
regnum = reg_name_search (regname);
|
|
if (regname[0] == 'f' && regnum != -1)
|
|
regnum += 16;
|
|
}
|
|
else if (strcmp (regname, "ap") == 0)
|
|
regnum = 32;
|
|
else if (strcmp (regname, "cc") == 0)
|
|
regnum = 33;
|
|
return regnum;
|
|
}
|
|
|
|
void
|
|
s390_elf_final_processing (void)
|
|
{
|
|
if (set_highgprs_p)
|
|
elf_elfheader (stdoutput)->e_flags |= EF_S390_HIGH_GPRS;
|
|
}
|