mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-27 03:51:15 +08:00
8864c4afdf
In some cases we may want to use different options only for certain assembly, so the .option directive is added to control the assembler options. .option can accept 4 parameters: push pop Pushes or pops the current option stack. They limit the scope of option changes so that they do not affect other parts of the assembly file. relax norelax Enables or disables relaxation.
2295 lines
64 KiB
C
2295 lines
64 KiB
C
/* tc-loongarch.c -- Assemble for the LoongArch ISA
|
|
|
|
Copyright (C) 2021-2024 Free Software Foundation, Inc.
|
|
Contributed by Loongson Ltd.
|
|
|
|
This file is part of GAS.
|
|
|
|
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 of the license, 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 this program; see the file COPYING3. If not,
|
|
see <http://www.gnu.org/licenses/>. */
|
|
|
|
#include "as.h"
|
|
#include "subsegs.h"
|
|
#include "dw2gencfi.h"
|
|
#include "loongarch-lex.h"
|
|
#include "elf/loongarch.h"
|
|
#include "opcode/loongarch.h"
|
|
#include "obj-elf.h"
|
|
#include "bfd/elfxx-loongarch.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
|
|
/* All information about an instruction during assemble. */
|
|
struct loongarch_cl_insn
|
|
{
|
|
/* First split string. */
|
|
const char *name;
|
|
const char *arg_strs[MAX_ARG_NUM_PLUS_2];
|
|
size_t arg_num;
|
|
|
|
/* Second analyze name_str and each actual args string to match the insn
|
|
in 'loongarch-opc.c'. And actual args may need be relocated.
|
|
We get length of insn. If 'insn_length == 0 && insn_mo->macro != NULL',
|
|
it's a macro insntruction and we call 'md_assemble' recursively
|
|
after expanding it. */
|
|
int match_now;
|
|
int all_match;
|
|
|
|
const struct loongarch_opcode *insn;
|
|
size_t insn_length;
|
|
|
|
offsetT args[MAX_ARG_NUM_PLUS_2];
|
|
struct reloc_info reloc_info[MAX_RELOC_NUMBER_A_INSN];
|
|
size_t reloc_num;
|
|
|
|
/* For relax reserved. We not support relax now.
|
|
'insn_length < relax_max_length' means need to relax.
|
|
And 'insn_length == relax_max_length' means no need to relax. */
|
|
size_t relax_max_length;
|
|
relax_substateT subtype;
|
|
|
|
/* Then we get the binary representation of insn
|
|
and write it in to section. */
|
|
insn_t insn_bin;
|
|
|
|
/* The frag that contains the instruction. */
|
|
struct frag *frag;
|
|
/* The offset into FRAG of the first instruction byte. */
|
|
long where;
|
|
/* The relocs associated with the instruction, if any. */
|
|
fixS *fixp[MAX_RELOC_NUMBER_A_INSN];
|
|
/* Represents macros or instructions expanded from macro.
|
|
For la.local -> la.pcrel or la.pcrel -> pcalau12i + addi.d, la.pcrel,
|
|
pcalau12i and addi.d are expanded from macro.
|
|
The first bit represents expanded from one register macro (e.g.
|
|
la.local $t0, symbol) and emit R_LARCH_RELAX relocations.
|
|
The second bit represents expanded from two registers macro (e.g.
|
|
la.local $t0, $t1, symbol) and not emit R_LARCH_RELAX relocations.
|
|
|
|
The macros or instructions expanded from macros do not output register
|
|
deprecated warning. */
|
|
unsigned int expand_from_macro;
|
|
};
|
|
|
|
#ifndef DEFAULT_ARCH
|
|
#define DEFAULT_ARCH "loongarch64"
|
|
#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 C style comments are always supported. */
|
|
const char line_comment_chars[] = "#";
|
|
|
|
/* This array holds machine specific line separator characters. */
|
|
const char line_separator_chars[] = ";";
|
|
|
|
/* Chars that can be used to separate mant from exp in floating point nums. */
|
|
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[] = "rRsSfFdDxXpP";
|
|
|
|
const char *md_shortopts = "O::g::G:";
|
|
|
|
static const char default_arch[] = DEFAULT_ARCH;
|
|
|
|
static bool call36 = 0;
|
|
|
|
/* The lowest 4-bit is the bytes of instructions. */
|
|
#define RELAX_BRANCH_16 0xc0000014
|
|
#define RELAX_BRANCH_21 0xc0000024
|
|
#define RELAX_BRANCH_26 0xc0000048
|
|
|
|
#define RELAX_BRANCH(x) \
|
|
(((x) & 0xf0000000) == 0xc0000000)
|
|
#define RELAX_BRANCH_ENCODE(x) \
|
|
(BFD_RELOC_LARCH_B16 == (x) ? RELAX_BRANCH_16 : RELAX_BRANCH_21)
|
|
|
|
#define ALIGN_MAX_ADDEND(n, max) ((max << 8) | n)
|
|
#define ALIGN_MAX_NOP_BYTES(addend) ((1 << (addend & 0xff)) - 4)
|
|
#define FRAG_AT_START_OF_SECTION(frag) \
|
|
(0 == frag->fr_address && 0 == frag->fr_fix)
|
|
|
|
enum options
|
|
{
|
|
OPTION_IGNORE = OPTION_MD_BASE,
|
|
|
|
OPTION_ABI,
|
|
OPTION_FLOAT_ABI,
|
|
OPTION_FLOAT_ISA,
|
|
|
|
OPTION_LA_LOCAL_WITH_ABS,
|
|
OPTION_LA_GLOBAL_WITH_PCREL,
|
|
OPTION_LA_GLOBAL_WITH_ABS,
|
|
|
|
OPTION_RELAX,
|
|
OPTION_NO_RELAX,
|
|
|
|
OPTION_THIN_ADD_SUB,
|
|
OPTION_IGNORE_START_ALIGN,
|
|
|
|
OPTION_END_OF_ENUM,
|
|
};
|
|
|
|
struct option md_longopts[] =
|
|
{
|
|
{ "mabi", required_argument, NULL, OPTION_ABI },
|
|
|
|
{ "mfpu", required_argument, NULL, OPTION_FLOAT_ISA },
|
|
|
|
{ "mla-local-with-abs", no_argument, NULL, OPTION_LA_LOCAL_WITH_ABS },
|
|
{ "mla-global-with-pcrel", no_argument, NULL, OPTION_LA_GLOBAL_WITH_PCREL },
|
|
{ "mla-global-with-abs", no_argument, NULL, OPTION_LA_GLOBAL_WITH_ABS },
|
|
|
|
{ "mrelax", no_argument, NULL, OPTION_RELAX },
|
|
{ "mno-relax", no_argument, NULL, OPTION_NO_RELAX },
|
|
{ "mthin-add-sub", no_argument, NULL, OPTION_THIN_ADD_SUB},
|
|
{ "mignore-start-align", no_argument, NULL, OPTION_IGNORE_START_ALIGN},
|
|
|
|
{ NULL, no_argument, NULL, 0 }
|
|
};
|
|
|
|
size_t md_longopts_size = sizeof (md_longopts);
|
|
|
|
int
|
|
md_parse_option (int c, const char *arg)
|
|
{
|
|
int ret = 1;
|
|
char lp64[256] = "";
|
|
char ilp32[256] = "";
|
|
unsigned char *suf = (unsigned char *)arg;
|
|
|
|
lp64['s'] = lp64['S'] = EF_LOONGARCH_ABI_SOFT_FLOAT;
|
|
lp64['f'] = lp64['F'] = EF_LOONGARCH_ABI_SINGLE_FLOAT;
|
|
lp64['d'] = lp64['D'] = EF_LOONGARCH_ABI_DOUBLE_FLOAT;
|
|
|
|
ilp32['s'] = ilp32['S'] = EF_LOONGARCH_ABI_SOFT_FLOAT;
|
|
ilp32['f'] = ilp32['F'] = EF_LOONGARCH_ABI_SINGLE_FLOAT;
|
|
ilp32['d'] = ilp32['D'] = EF_LOONGARCH_ABI_DOUBLE_FLOAT;
|
|
|
|
switch (c)
|
|
{
|
|
case OPTION_ABI:
|
|
if (strncasecmp (arg, "lp64", 4) == 0 && lp64[suf[4]] != 0)
|
|
{
|
|
LARCH_opts.ase_ilp32 = 1;
|
|
LARCH_opts.ase_lp64 = 1;
|
|
LARCH_opts.ase_lsx = 1;
|
|
LARCH_opts.ase_lasx = 1;
|
|
LARCH_opts.ase_lvz = 1;
|
|
LARCH_opts.ase_lbt = 1;
|
|
LARCH_opts.ase_abi = lp64[suf[4]];
|
|
}
|
|
else if (strncasecmp (arg, "ilp32", 5) == 0 && ilp32[suf[5]] != 0)
|
|
{
|
|
LARCH_opts.ase_abi = ilp32[suf[5]];
|
|
LARCH_opts.ase_ilp32 = 1;
|
|
}
|
|
else
|
|
ret = 0;
|
|
break;
|
|
|
|
case OPTION_FLOAT_ISA:
|
|
if (strcasecmp (arg, "soft") == 0)
|
|
LARCH_opts.ase_nf = 1;
|
|
else if (strcasecmp (arg, "single") == 0)
|
|
LARCH_opts.ase_sf = 1;
|
|
else if (strcasecmp (arg, "double") == 0)
|
|
{
|
|
LARCH_opts.ase_sf = 1;
|
|
LARCH_opts.ase_df = 1;
|
|
}
|
|
else
|
|
ret = 0;
|
|
break;
|
|
|
|
case OPTION_LA_LOCAL_WITH_ABS:
|
|
LARCH_opts.ase_labs = 1;
|
|
break;
|
|
|
|
case OPTION_LA_GLOBAL_WITH_PCREL:
|
|
LARCH_opts.ase_gpcr = 1;
|
|
break;
|
|
|
|
case OPTION_LA_GLOBAL_WITH_ABS:
|
|
LARCH_opts.ase_gabs = 1;
|
|
break;
|
|
|
|
case OPTION_RELAX:
|
|
LARCH_opts.relax = 1;
|
|
break;
|
|
|
|
case OPTION_NO_RELAX:
|
|
LARCH_opts.relax = 0;
|
|
break;
|
|
|
|
case OPTION_THIN_ADD_SUB:
|
|
LARCH_opts.thin_add_sub = 1;
|
|
break;
|
|
|
|
case OPTION_IGNORE_START_ALIGN:
|
|
LARCH_opts.ignore_start_align = 1;
|
|
break;
|
|
|
|
case OPTION_IGNORE:
|
|
break;
|
|
|
|
default:
|
|
ret = 0;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static const char *const *r_abi_names = NULL;
|
|
static const char *const *f_abi_names = NULL;
|
|
static struct htab *r_htab = NULL;
|
|
static struct htab *r_deprecated_htab = NULL;
|
|
static struct htab *f_htab = NULL;
|
|
static struct htab *f_deprecated_htab = NULL;
|
|
static struct htab *fc_htab = NULL;
|
|
static struct htab *fcn_htab = NULL;
|
|
static struct htab *c_htab = NULL;
|
|
static struct htab *cr_htab = NULL;
|
|
static struct htab *v_htab = NULL;
|
|
static struct htab *x_htab = NULL;
|
|
|
|
void
|
|
loongarch_after_parse_args ()
|
|
{
|
|
/* Set default ABI/ISA LP64D. */
|
|
if (!LARCH_opts.ase_ilp32)
|
|
{
|
|
if (strcmp (default_arch, "loongarch64") == 0)
|
|
{
|
|
LARCH_opts.ase_abi = EF_LOONGARCH_ABI_DOUBLE_FLOAT;
|
|
LARCH_opts.ase_ilp32 = 1;
|
|
LARCH_opts.ase_lp64 = 1;
|
|
LARCH_opts.ase_lsx = 1;
|
|
LARCH_opts.ase_lasx = 1;
|
|
LARCH_opts.ase_lvz = 1;
|
|
LARCH_opts.ase_lbt = 1;
|
|
}
|
|
else if (strcmp (default_arch, "loongarch32") == 0)
|
|
{
|
|
LARCH_opts.ase_abi = EF_LOONGARCH_ABI_DOUBLE_FLOAT;
|
|
LARCH_opts.ase_ilp32 = 1;
|
|
}
|
|
else
|
|
as_bad ("unknown default architecture `%s'", default_arch);
|
|
}
|
|
|
|
LARCH_opts.ase_abi |= EF_LOONGARCH_OBJABI_V1;
|
|
/* Set default ISA double-float. */
|
|
if (!LARCH_opts.ase_nf
|
|
&& !LARCH_opts.ase_sf
|
|
&& !LARCH_opts.ase_df)
|
|
{
|
|
LARCH_opts.ase_sf = 1;
|
|
LARCH_opts.ase_df = 1;
|
|
}
|
|
|
|
size_t i;
|
|
|
|
assert(LARCH_opts.ase_ilp32);
|
|
|
|
/* Init ilp32/lp64 registers names. */
|
|
if (!r_htab)
|
|
r_htab = str_htab_create (), str_hash_insert (r_htab, "", 0, 0);
|
|
if (!r_deprecated_htab)
|
|
r_deprecated_htab = str_htab_create (),
|
|
str_hash_insert (r_deprecated_htab, "", 0, 0);
|
|
|
|
r_abi_names = loongarch_r_normal_name;
|
|
for (i = 0; i < ARRAY_SIZE (loongarch_r_normal_name); i++)
|
|
str_hash_insert (r_htab, loongarch_r_normal_name[i], (void *) (i + 1), 0);
|
|
|
|
/* Init ilp32/lp64 registers alias. */
|
|
r_abi_names = loongarch_r_alias;
|
|
for (i = 0; i < ARRAY_SIZE (loongarch_r_alias); i++)
|
|
str_hash_insert (r_htab, loongarch_r_alias[i], (void *) (i + 1), 0);
|
|
|
|
for (i = 0; i < ARRAY_SIZE (loongarch_r_alias_1); i++)
|
|
str_hash_insert (r_htab, loongarch_r_alias_1[i], (void *) (i + 1), 0);
|
|
|
|
for (i = 0; i < ARRAY_SIZE (loongarch_r_alias_deprecated); i++)
|
|
str_hash_insert (r_deprecated_htab, loongarch_r_alias_deprecated[i],
|
|
(void *) (i + 1), 0);
|
|
|
|
if (!cr_htab)
|
|
cr_htab = str_htab_create (), str_hash_insert (cr_htab, "", 0, 0);
|
|
|
|
for (i = 0; i < ARRAY_SIZE (loongarch_cr_normal_name); i++)
|
|
str_hash_insert (cr_htab, loongarch_cr_normal_name[i], (void *) (i + 1), 0);
|
|
|
|
/* Init single/double float registers names. */
|
|
if (LARCH_opts.ase_sf || LARCH_opts.ase_df)
|
|
{
|
|
if (!f_htab)
|
|
f_htab = str_htab_create (), str_hash_insert (f_htab, "", 0, 0);
|
|
if (!f_deprecated_htab)
|
|
f_deprecated_htab = str_htab_create (),
|
|
str_hash_insert (f_deprecated_htab, "", 0, 0);
|
|
|
|
f_abi_names = loongarch_f_normal_name;
|
|
for (i = 0; i < ARRAY_SIZE (loongarch_f_normal_name); i++)
|
|
str_hash_insert (f_htab, loongarch_f_normal_name[i], (void *) (i + 1),
|
|
0);
|
|
|
|
/* Init float-ilp32/lp64 registers alias. */
|
|
f_abi_names = loongarch_f_alias;
|
|
for (i = 0; i < ARRAY_SIZE (loongarch_f_alias); i++)
|
|
str_hash_insert (f_htab, loongarch_f_alias[i],
|
|
(void *) (i + 1), 0);
|
|
for (i = 0; i < ARRAY_SIZE (loongarch_f_alias_deprecated); i++)
|
|
str_hash_insert (f_deprecated_htab, loongarch_f_alias_deprecated[i],
|
|
(void *) (i + 1), 0);
|
|
|
|
if (!fc_htab)
|
|
fc_htab = str_htab_create (), str_hash_insert (fc_htab, "", 0, 0);
|
|
|
|
for (i = 0; i < ARRAY_SIZE (loongarch_fc_normal_name); i++)
|
|
str_hash_insert (fc_htab, loongarch_fc_normal_name[i], (void *) (i + 1),
|
|
0);
|
|
|
|
if (!fcn_htab)
|
|
fcn_htab = str_htab_create (), str_hash_insert (fcn_htab, "", 0, 0);
|
|
|
|
for (i = 0; i < ARRAY_SIZE (loongarch_fc_numeric_name); i++)
|
|
str_hash_insert (fcn_htab, loongarch_fc_numeric_name[i], (void *) (i + 1),
|
|
0);
|
|
|
|
if (!c_htab)
|
|
c_htab = str_htab_create (), str_hash_insert (c_htab, "", 0, 0);
|
|
|
|
for (i = 0; i < ARRAY_SIZE (loongarch_c_normal_name); i++)
|
|
str_hash_insert (c_htab, loongarch_c_normal_name[i], (void *) (i + 1),
|
|
0);
|
|
|
|
}
|
|
|
|
/* Init lsx registers names. */
|
|
if (LARCH_opts.ase_lsx)
|
|
{
|
|
if (!v_htab)
|
|
v_htab = str_htab_create (), str_hash_insert (v_htab, "", 0, 0);
|
|
for (i = 0; i < ARRAY_SIZE (loongarch_v_normal_name); i++)
|
|
str_hash_insert (v_htab, loongarch_v_normal_name[i], (void *) (i + 1),
|
|
0);
|
|
}
|
|
|
|
/* Init lasx registers names. */
|
|
if (LARCH_opts.ase_lasx)
|
|
{
|
|
if (!x_htab)
|
|
x_htab = str_htab_create (), str_hash_insert (x_htab, "", 0, 0);
|
|
for (i = 0; i < ARRAY_SIZE (loongarch_x_normal_name); i++)
|
|
str_hash_insert (x_htab, loongarch_x_normal_name[i], (void *) (i + 1),
|
|
0);
|
|
}
|
|
|
|
}
|
|
|
|
const char *
|
|
loongarch_target_format ()
|
|
{
|
|
return LARCH_opts.ase_lp64 ? "elf64-loongarch" : "elf32-loongarch";
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
unsigned int sec_id;
|
|
symbolS *s;
|
|
} align_sec_sym;
|
|
|
|
static htab_t align_hash;
|
|
|
|
static hashval_t
|
|
align_sec_sym_hash (const void *entry)
|
|
{
|
|
const align_sec_sym *e = entry;
|
|
return (hashval_t) (e->sec_id);
|
|
}
|
|
|
|
static int
|
|
align_sec_sym_eq (const void *entry1, const void *entry2)
|
|
{
|
|
const align_sec_sym *e1 = entry1, *e2 = entry2;
|
|
return e1->sec_id == e2->sec_id;
|
|
}
|
|
|
|
/* Make align symbol be in same section with alignment directive.
|
|
If the symbol is only created at the first time to handle alignment
|
|
directive. This means that all other sections may use this symbol.
|
|
If the section of this symbol is discarded, there may be problems. */
|
|
|
|
static symbolS *get_align_symbol (segT sec)
|
|
{
|
|
align_sec_sym search = { sec->id, NULL };
|
|
align_sec_sym *pentry = htab_find (align_hash, &search);
|
|
if (pentry)
|
|
return pentry->s;
|
|
|
|
/* If we not find the symbol in this section. Create and insert it. */
|
|
symbolS *s = (symbolS *)local_symbol_make (".Lla-relax-align", sec,
|
|
&zero_address_frag, 0);
|
|
align_sec_sym entry = { sec->id, s };
|
|
align_sec_sym **slot = (align_sec_sym **) htab_find_slot (align_hash,
|
|
&entry, INSERT);
|
|
if (slot == NULL)
|
|
return NULL;
|
|
*slot = (align_sec_sym *) xmalloc (sizeof (align_sec_sym));
|
|
if (*slot == NULL)
|
|
return NULL;
|
|
**slot = entry;
|
|
return entry.s;
|
|
}
|
|
|
|
void
|
|
md_begin ()
|
|
{
|
|
const struct loongarch_opcode *it;
|
|
struct loongarch_ase *ase;
|
|
for (ase = loongarch_ASEs; ase->enabled; ase++)
|
|
for (it = ase->opcodes; it->name; it++)
|
|
{
|
|
if (loongarch_check_format (it->format) != 0)
|
|
as_fatal (_("insn name: %s\tformat: %s\tsyntax error"),
|
|
it->name, it->format);
|
|
if (it->mask == 0 && it->macro == 0)
|
|
as_fatal (_("insn name: %s\nformat: %s\nwe want macro but "
|
|
"macro is NULL"),
|
|
it->name, it->format);
|
|
if (it->macro
|
|
&& loongarch_check_macro (it->format, it->macro) != 0)
|
|
as_fatal (_("insn name: %s\nformat: %s\nmacro: %s\tsyntax error"),
|
|
it->name, it->format, it->macro);
|
|
}
|
|
|
|
align_hash = htab_create (10, align_sec_sym_hash, align_sec_sym_eq, free);
|
|
|
|
/* FIXME: expressionS use 'offsetT' as constant,
|
|
* we want this is 64-bit type. */
|
|
assert (8 <= sizeof (offsetT));
|
|
}
|
|
|
|
/* Called just before the assembler exits. */
|
|
|
|
void
|
|
loongarch_md_end (void)
|
|
{
|
|
htab_delete (align_hash);
|
|
}
|
|
|
|
unsigned long
|
|
loongarch_mach (void)
|
|
{
|
|
return LARCH_opts.ase_lp64 ? bfd_mach_loongarch64 : bfd_mach_loongarch32;
|
|
}
|
|
|
|
static const expressionS const_0 = { .X_op = O_constant, .X_add_number = 0 };
|
|
|
|
/* Handle the .dtprelword and .dtpreldword pseudo-ops. They generate
|
|
a 32-bit or 64-bit DTP-relative relocation (BYTES says which) for
|
|
use in DWARF debug information. */
|
|
|
|
static void
|
|
s_dtprel (int bytes)
|
|
{
|
|
expressionS ex;
|
|
char *p;
|
|
|
|
expression (&ex);
|
|
|
|
if (ex.X_op != O_symbol)
|
|
{
|
|
as_bad (_("Unsupported use of %s"),
|
|
(bytes == 8 ? ".dtpreldword" : ".dtprelword"));
|
|
ignore_rest_of_line ();
|
|
}
|
|
|
|
p = frag_more (bytes);
|
|
md_number_to_chars (p, 0, bytes);
|
|
fix_new_exp (frag_now, p - frag_now->fr_literal, bytes, &ex, FALSE,
|
|
(bytes == 8
|
|
? BFD_RELOC_LARCH_TLS_DTPREL64
|
|
: BFD_RELOC_LARCH_TLS_DTPREL32));
|
|
|
|
demand_empty_rest_of_line ();
|
|
}
|
|
|
|
struct LARCH_option_stack
|
|
{
|
|
struct LARCH_option_stack *next;
|
|
struct loongarch_ASEs_option options;
|
|
};
|
|
|
|
static struct LARCH_option_stack *LARCH_opts_stack = NULL;
|
|
|
|
/* Handle the .option pseudo-op.
|
|
The alignment of .align is done by R_LARCH_ALIGN at link time.
|
|
If the .align directive is within the range controlled by
|
|
.option norelax, that is, relax is turned off, R_LARCH_ALIGN
|
|
cannot be generated, which may cause ld to be unable to handle
|
|
the alignment. */
|
|
static void
|
|
s_loongarch_option (int x ATTRIBUTE_UNUSED)
|
|
{
|
|
char *name = input_line_pointer, ch;
|
|
while (!is_end_of_line[(unsigned char) *input_line_pointer])
|
|
++input_line_pointer;
|
|
ch = *input_line_pointer;
|
|
*input_line_pointer = '\0';
|
|
|
|
if (strcmp (name, "relax") == 0)
|
|
LARCH_opts.relax = 1;
|
|
else if (strcmp (name, "norelax") == 0)
|
|
LARCH_opts.relax = 0;
|
|
else if (strcmp (name, "push") == 0)
|
|
{
|
|
struct LARCH_option_stack *s;
|
|
|
|
s = XNEW (struct LARCH_option_stack);
|
|
s->next = LARCH_opts_stack;
|
|
s->options = LARCH_opts;
|
|
LARCH_opts_stack = s;
|
|
}
|
|
else if (strcmp (name, "pop") == 0)
|
|
{
|
|
struct LARCH_option_stack *s;
|
|
|
|
s = LARCH_opts_stack;
|
|
if (s == NULL)
|
|
as_bad (_(".option pop with no .option push"));
|
|
else
|
|
{
|
|
LARCH_opts_stack = s->next;
|
|
LARCH_opts = s->options;
|
|
free (s);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
as_warn (_("unrecognized .option directive: %s"), name);
|
|
}
|
|
*input_line_pointer = ch;
|
|
demand_empty_rest_of_line ();
|
|
}
|
|
|
|
static const pseudo_typeS loongarch_pseudo_table[] =
|
|
{
|
|
{ "dword", cons, 8 },
|
|
{ "word", cons, 4 },
|
|
{ "half", cons, 2 },
|
|
{ "dtprelword", s_dtprel, 4 },
|
|
{ "dtpreldword", s_dtprel, 8 },
|
|
{ "option", s_loongarch_option, 0},
|
|
{ NULL, NULL, 0 },
|
|
};
|
|
|
|
void
|
|
loongarch_pop_insert (void)
|
|
{
|
|
pop_insert (loongarch_pseudo_table);
|
|
}
|
|
|
|
#define INTERNAL_LABEL_SPECIAL 10
|
|
static unsigned long internal_label_count[INTERNAL_LABEL_SPECIAL] = { 0 };
|
|
|
|
static const char *
|
|
loongarch_internal_label_name (unsigned long label, int augend)
|
|
{
|
|
static char symbol_name_build[24];
|
|
unsigned long want_label;
|
|
char *p;
|
|
|
|
want_label = internal_label_count[label] + augend;
|
|
|
|
p = symbol_name_build;
|
|
#ifdef LOCAL_LABEL_PREFIX
|
|
*p++ = LOCAL_LABEL_PREFIX;
|
|
#endif
|
|
*p++ = 'L';
|
|
for (; label; label /= 10)
|
|
*p++ = label % 10 + '0';
|
|
/* Make sure internal label never belong to normal label namespace. */
|
|
*p++ = ':';
|
|
for (; want_label; want_label /= 10)
|
|
*p++ = want_label % 10 + '0';
|
|
*p++ = '\0';
|
|
return symbol_name_build;
|
|
}
|
|
|
|
static void
|
|
setup_internal_label_here (unsigned long label)
|
|
{
|
|
assert (label < INTERNAL_LABEL_SPECIAL);
|
|
internal_label_count[label]++;
|
|
colon (loongarch_internal_label_name (label, 0));
|
|
}
|
|
|
|
void
|
|
get_internal_label (expressionS *label_expr, unsigned long label,
|
|
int augend /* 0 for previous, 1 for next. */)
|
|
{
|
|
assert (label < INTERNAL_LABEL_SPECIAL);
|
|
as_fatal (_("internal error: we have no internal label yet"));
|
|
label_expr->X_op = O_symbol;
|
|
label_expr->X_add_symbol =
|
|
symbol_find_or_make (loongarch_internal_label_name (label, augend));
|
|
label_expr->X_add_number = 0;
|
|
}
|
|
|
|
static int
|
|
is_internal_label (const char *c_str)
|
|
{
|
|
do
|
|
{
|
|
if (*c_str != ':')
|
|
break;
|
|
c_str++;
|
|
if (!('0' <= *c_str && *c_str <= '9'))
|
|
break;
|
|
while ('0' <= *c_str && *c_str <= '9')
|
|
c_str++;
|
|
if (*c_str != 'b' && *c_str != 'f')
|
|
break;
|
|
c_str++;
|
|
return *c_str == '\0';
|
|
}
|
|
while (0);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
is_label (const char *c_str)
|
|
{
|
|
if (is_internal_label (c_str))
|
|
return 1;
|
|
else if ('0' <= *c_str && *c_str <= '9')
|
|
{
|
|
/* [0-9]+[bf] */
|
|
while ('0' <= *c_str && *c_str <= '9')
|
|
c_str++;
|
|
return *c_str == 'b' || *c_str == 'f';
|
|
}
|
|
else if (is_name_beginner (*c_str))
|
|
{
|
|
/* [a-zA-Z\._\$][0-9a-zA-Z\._\$]* */
|
|
c_str++;
|
|
while (is_part_of_name (*c_str))
|
|
c_str++;
|
|
return *c_str == '\0';
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
is_label_with_addend (const char *c_str)
|
|
{
|
|
if (is_internal_label (c_str))
|
|
return 1;
|
|
else if ('0' <= *c_str && *c_str <= '9')
|
|
{
|
|
/* [0-9]+[bf] */
|
|
while ('0' <= *c_str && *c_str <= '9')
|
|
c_str++;
|
|
if (*c_str == 'b' || *c_str == 'f')
|
|
c_str++;
|
|
else
|
|
return 0;
|
|
return *c_str == '\0'
|
|
|| ((*c_str == '-' || *c_str == '+')
|
|
&& is_unsigned (c_str + 1));
|
|
}
|
|
else if (is_name_beginner (*c_str))
|
|
{
|
|
/* [a-zA-Z\._\$][0-9a-zA-Z\._\$]* */
|
|
c_str++;
|
|
while (is_part_of_name (*c_str))
|
|
c_str++;
|
|
return *c_str == '\0'
|
|
|| ((*c_str == '-' || *c_str == '+')
|
|
&& is_unsigned (c_str + 1));
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int32_t
|
|
loongarch_args_parser_can_match_arg_helper (char esc_ch1, char esc_ch2,
|
|
const char *bit_field,
|
|
const char *arg, void *context)
|
|
{
|
|
struct loongarch_cl_insn *ip = context;
|
|
offsetT imm, ret = 0;
|
|
size_t reloc_num_we_have = MAX_RELOC_NUMBER_A_INSN - ip->reloc_num;
|
|
size_t reloc_num = 0;
|
|
|
|
if (!ip->match_now)
|
|
return 0;
|
|
|
|
switch (esc_ch1)
|
|
{
|
|
case 'l':
|
|
switch (esc_ch2)
|
|
{
|
|
default:
|
|
ip->match_now = is_label (arg);
|
|
if (!ip->match_now && is_label_with_addend (arg))
|
|
as_fatal (_("This label shouldn't be with addend."));
|
|
break;
|
|
case 'a':
|
|
ip->match_now = is_label_with_addend (arg);
|
|
break;
|
|
}
|
|
break;
|
|
/* This is used for TLS, where the fourth operand is %le_add_r,
|
|
to get a relocation applied to an add instruction, for relaxation to use.
|
|
Two conditions, ip->match_now and reloc_num, are used to check tls insn
|
|
to prevent cases like add.d $a0,$a0,$a0,8. */
|
|
case 't':
|
|
ip->match_now = loongarch_parse_expr (arg, ip->reloc_info + ip->reloc_num,
|
|
reloc_num_we_have, &reloc_num, &imm) == 0;
|
|
|
|
if (!ip->match_now)
|
|
break;
|
|
|
|
bfd_reloc_code_real_type tls_reloc_type = BFD_RELOC_LARCH_TLS_LE_ADD_R;
|
|
|
|
if (reloc_num
|
|
&& (ip->reloc_info[ip->reloc_num].type == tls_reloc_type))
|
|
{
|
|
ip->reloc_num += reloc_num;
|
|
ip->reloc_info[ip->reloc_num].type = BFD_RELOC_LARCH_RELAX;
|
|
ip->reloc_info[ip->reloc_num].value = const_0;
|
|
ip->reloc_num++;
|
|
}
|
|
else
|
|
ip->match_now = 0;
|
|
break;
|
|
case 's':
|
|
case 'u':
|
|
ip->match_now =
|
|
loongarch_parse_expr (arg, ip->reloc_info + ip->reloc_num,
|
|
reloc_num_we_have, &reloc_num, &imm) == 0;
|
|
|
|
if (!ip->match_now)
|
|
break;
|
|
|
|
ret = imm;
|
|
if (reloc_num)
|
|
{
|
|
bfd_reloc_code_real_type reloc_type = BFD_RELOC_NONE;
|
|
reloc_num_we_have -= reloc_num;
|
|
if (reloc_num_we_have == 0)
|
|
as_fatal (_("expr too huge") /* Want one more reloc. */);
|
|
if (esc_ch1 == 'u')
|
|
{
|
|
if (strncmp (bit_field, "10:12", strlen ("10:12")) == 0)
|
|
reloc_type = BFD_RELOC_LARCH_SOP_POP_32_U_10_12;
|
|
}
|
|
else if (esc_ch1 == 's')
|
|
{
|
|
if (strncmp (bit_field, "10:16<<2", strlen ("10:16<<2")) == 0)
|
|
reloc_type = BFD_RELOC_LARCH_SOP_POP_32_S_10_16_S2;
|
|
else if (strncmp (bit_field, "0:5|10:16<<2",
|
|
strlen ("0:5|10:16<<2")) == 0)
|
|
reloc_type = BFD_RELOC_LARCH_SOP_POP_32_S_0_5_10_16_S2;
|
|
else if (strncmp (bit_field, "0:10|10:16<<2",
|
|
strlen ("0:10|10:16<<2")) == 0)
|
|
reloc_type = BFD_RELOC_LARCH_SOP_POP_32_S_0_10_10_16_S2;
|
|
else if (strncmp (bit_field, "10:12", strlen ("10:12")) == 0)
|
|
reloc_type = BFD_RELOC_LARCH_SOP_POP_32_S_10_12;
|
|
else if (strncmp (bit_field, "5:20", strlen ("5:20")) == 0)
|
|
reloc_type = BFD_RELOC_LARCH_SOP_POP_32_S_5_20;
|
|
else if (strncmp (bit_field, "10:16", strlen ("10:16")) == 0)
|
|
reloc_type = BFD_RELOC_LARCH_SOP_POP_32_S_10_16;
|
|
else if (strncmp (bit_field, "10:5", strlen ("10:5")) == 0)
|
|
reloc_type = BFD_RELOC_LARCH_SOP_POP_32_S_10_5;
|
|
}
|
|
if (reloc_type == BFD_RELOC_NONE)
|
|
as_fatal (
|
|
_("not support reloc bit-field\nfmt: %c%c %s\nargs: %s"),
|
|
esc_ch1, esc_ch2, bit_field, arg);
|
|
|
|
if (ip->reloc_info[0].type >= BFD_RELOC_LARCH_B16
|
|
&& ip->reloc_info[0].type <= BFD_RELOC_LARCH_TLS_DESC_PCREL20_S2)
|
|
{
|
|
/* As we compact stack-relocs, it is no need for pop operation.
|
|
But break out until here in order to check the imm field.
|
|
May be reloc_num > 1 if implement relax? */
|
|
ip->reloc_num += reloc_num;
|
|
reloc_type = ip->reloc_info[0].type;
|
|
|
|
if (LARCH_opts.relax
|
|
&& (BFD_RELOC_LARCH_TLS_LE_HI20_R == reloc_type
|
|
|| BFD_RELOC_LARCH_TLS_LE_LO12_R == reloc_type
|
|
|| BFD_RELOC_LARCH_TLS_LE_HI20 == reloc_type
|
|
|| BFD_RELOC_LARCH_TLS_LE_LO12 == reloc_type
|
|
|| BFD_RELOC_LARCH_TLS_LE64_LO20 == reloc_type
|
|
|| BFD_RELOC_LARCH_TLS_LE64_HI12 == reloc_type
|
|
|| BFD_RELOC_LARCH_CALL36 == reloc_type))
|
|
{
|
|
ip->reloc_info[ip->reloc_num].type = BFD_RELOC_LARCH_RELAX;
|
|
ip->reloc_info[ip->reloc_num].value = const_0;
|
|
ip->reloc_num++;
|
|
}
|
|
|
|
/* Only one register macros (used in normal code model)
|
|
emit R_LARCH_RELAX.
|
|
LARCH_opts.ase_labs and LARCH_opts.ase_gabs are used
|
|
to generate the code model of absolute addresses, and
|
|
we do not relax this code model. */
|
|
if (LARCH_opts.relax && (ip->expand_from_macro & 1)
|
|
&& !(LARCH_opts.ase_labs | LARCH_opts.ase_gabs)
|
|
&& (BFD_RELOC_LARCH_PCALA_HI20 == reloc_type
|
|
|| BFD_RELOC_LARCH_PCALA_LO12 == reloc_type
|
|
|| BFD_RELOC_LARCH_GOT_PC_HI20 == reloc_type
|
|
|| BFD_RELOC_LARCH_GOT_PC_LO12 == reloc_type
|
|
|| BFD_RELOC_LARCH_TLS_LD_PC_HI20 == reloc_type
|
|
|| BFD_RELOC_LARCH_TLS_GD_PC_HI20 == reloc_type
|
|
|| BFD_RELOC_LARCH_TLS_DESC_PC_HI20 == reloc_type
|
|
|| BFD_RELOC_LARCH_TLS_DESC_PC_LO12 == reloc_type
|
|
|| BFD_RELOC_LARCH_TLS_DESC_LD == reloc_type
|
|
|| BFD_RELOC_LARCH_TLS_DESC_CALL == reloc_type
|
|
|| BFD_RELOC_LARCH_TLS_IE_PC_HI20 == reloc_type
|
|
|| BFD_RELOC_LARCH_TLS_IE_PC_LO12 == reloc_type))
|
|
{
|
|
ip->reloc_info[ip->reloc_num].type = BFD_RELOC_LARCH_RELAX;
|
|
ip->reloc_info[ip->reloc_num].value = const_0;
|
|
ip->reloc_num++;
|
|
}
|
|
break;
|
|
}
|
|
reloc_num++;
|
|
ip->reloc_num += reloc_num;
|
|
ip->reloc_info[ip->reloc_num - 1].type = reloc_type;
|
|
ip->reloc_info[ip->reloc_num - 1].value = const_0;
|
|
}
|
|
break;
|
|
case 'r':
|
|
imm = (intptr_t) str_hash_find (r_htab, arg);
|
|
ip->match_now = 0 < imm;
|
|
ret = imm - 1;
|
|
if (ip->match_now)
|
|
break;
|
|
/* Handle potential usage of deprecated register aliases. */
|
|
imm = (intptr_t) str_hash_find (r_deprecated_htab, arg);
|
|
ip->match_now = 0 < imm;
|
|
ret = imm - 1;
|
|
/* !ip->expand_from_macro: avoiding duplicate output warnings,
|
|
only the first macro output warning. */
|
|
if (ip->match_now && !ip->expand_from_macro)
|
|
as_warn (_("register alias %s is deprecated, use %s instead"),
|
|
arg, r_abi_names[ret]);
|
|
break;
|
|
case 'f':
|
|
switch (esc_ch2)
|
|
{
|
|
case 'c':
|
|
imm = (intptr_t) str_hash_find (fc_htab, arg);
|
|
if (0 >= imm)
|
|
{
|
|
imm = (intptr_t) str_hash_find (fcn_htab, arg);
|
|
}
|
|
break;
|
|
default:
|
|
imm = (intptr_t) str_hash_find (f_htab, arg);
|
|
}
|
|
ip->match_now = 0 < imm;
|
|
ret = imm - 1;
|
|
if (ip->match_now && !ip->expand_from_macro)
|
|
break;
|
|
/* Handle potential usage of deprecated register aliases. */
|
|
imm = (intptr_t) str_hash_find (f_deprecated_htab, arg);
|
|
ip->match_now = 0 < imm;
|
|
ret = imm - 1;
|
|
if (ip->match_now)
|
|
as_warn (_("register alias %s is deprecated, use %s instead"),
|
|
arg, f_abi_names[ret]);
|
|
break;
|
|
case 'c':
|
|
switch (esc_ch2)
|
|
{
|
|
case 'r':
|
|
imm = (intptr_t) str_hash_find (cr_htab, arg);
|
|
break;
|
|
default:
|
|
imm = (intptr_t) str_hash_find (c_htab, arg);
|
|
}
|
|
ip->match_now = 0 < imm;
|
|
ret = imm - 1;
|
|
break;
|
|
case 'v':
|
|
imm = (intptr_t) str_hash_find (v_htab, arg);
|
|
ip->match_now = 0 < imm;
|
|
ret = imm - 1;
|
|
break;
|
|
case 'x':
|
|
imm = (intptr_t) str_hash_find (x_htab, arg);
|
|
ip->match_now = 0 < imm;
|
|
ret = imm - 1;
|
|
break;
|
|
case '\0':
|
|
ip->all_match = ip->match_now;
|
|
ip->insn_length =
|
|
ip->insn->mask ? loongarch_insn_length (ip->insn->match) : 0;
|
|
/* FIXME: now we have no relax insn. */
|
|
ip->relax_max_length = ip->insn_length;
|
|
break;
|
|
default:
|
|
as_fatal (_("unknown escape"));
|
|
}
|
|
|
|
do
|
|
{
|
|
/* Check imm overflow. */
|
|
int bit_width, bits_needed_s, bits_needed_u;
|
|
char *t;
|
|
|
|
if (!ip->match_now)
|
|
break;
|
|
|
|
if (0 < reloc_num)
|
|
break;
|
|
|
|
bit_width = loongarch_get_bit_field_width (bit_field, &t);
|
|
|
|
if (bit_width == -1)
|
|
/* No specify bit width. */
|
|
break;
|
|
|
|
imm = ret;
|
|
if (t[0] == '<' && t[1] == '<')
|
|
{
|
|
int i = strtol (t += 2, &t, 10), j;
|
|
for (j = i; 0 < j; j--, imm >>= 1)
|
|
if (imm & 1)
|
|
as_fatal (_("require imm low %d bit is 0."), i);
|
|
}
|
|
|
|
if (*t == '+')
|
|
imm -= strtol (t, &t, 10);
|
|
|
|
bits_needed_s = loongarch_bits_imm_needed (imm, 1);
|
|
bits_needed_u = loongarch_bits_imm_needed (imm, 0);
|
|
|
|
if ((esc_ch1 == 's' && bit_width < bits_needed_s)
|
|
|| (esc_ch1 != 's' && bit_width < bits_needed_u))
|
|
/* How to do after we detect overflow. */
|
|
as_fatal (_("Immediate overflow.\n"
|
|
"format: %c%c%s\n"
|
|
"arg: %s"),
|
|
esc_ch1, esc_ch2, bit_field, arg);
|
|
}
|
|
while (0);
|
|
|
|
if (esc_ch1 != '\0')
|
|
{
|
|
ip->args[ip->arg_num] = ret;
|
|
ip->arg_num++;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
get_loongarch_opcode (struct loongarch_cl_insn *insn)
|
|
{
|
|
const struct loongarch_opcode *it;
|
|
struct loongarch_ase *ase;
|
|
for (ase = loongarch_ASEs; ase->enabled; ase++)
|
|
{
|
|
if (!*ase->enabled || (ase->include && !*ase->include)
|
|
|| (ase->exclude && *ase->exclude))
|
|
continue;
|
|
|
|
if (!ase->name_hash_entry)
|
|
{
|
|
ase->name_hash_entry = str_htab_create ();
|
|
for (it = ase->opcodes; it->name; it++)
|
|
{
|
|
if ((!it->include || (it->include && *it->include))
|
|
&& (!it->exclude || (it->exclude && !(*it->exclude)))
|
|
&& !(it->pinfo & INSN_DIS_ALIAS))
|
|
str_hash_insert (ase->name_hash_entry, it->name,
|
|
(void *) it, 0);
|
|
}
|
|
}
|
|
|
|
if ((it = str_hash_find (ase->name_hash_entry, insn->name)) == NULL)
|
|
continue;
|
|
|
|
do
|
|
{
|
|
insn->insn = it;
|
|
insn->match_now = 1;
|
|
insn->all_match = 0;
|
|
insn->arg_num = 0;
|
|
insn->reloc_num = 0;
|
|
insn->insn_bin = (loongarch_foreach_args
|
|
(it->format, insn->arg_strs,
|
|
loongarch_args_parser_can_match_arg_helper,
|
|
insn));
|
|
if (insn->all_match && !(it->include && !*it->include)
|
|
&& !(it->exclude && *it->exclude))
|
|
{
|
|
insn->insn_bin |= it->match;
|
|
return;
|
|
}
|
|
it++;
|
|
}
|
|
while (it->name && strcasecmp (it->name, insn->name) == 0);
|
|
}
|
|
}
|
|
|
|
static int
|
|
check_this_insn_before_appending (struct loongarch_cl_insn *ip)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (strncmp (ip->name, "la.abs", 6) == 0)
|
|
{
|
|
ip->reloc_info[ip->reloc_num].type = BFD_RELOC_LARCH_MARK_LA;
|
|
ip->reloc_info[ip->reloc_num].value = const_0;
|
|
ip->reloc_num++;
|
|
}
|
|
else if (ip->insn->mask == 0xffff8000
|
|
/* amcas.b rd, rk, rj */
|
|
&& ((ip->insn_bin & 0xfff80000) == 0x38580000
|
|
/* amswap.w rd, rk, rj */
|
|
|| (ip->insn_bin & 0xfff00000) == 0x38600000
|
|
/* ammax_db.wu rd, rk, rj */
|
|
|| (ip->insn_bin & 0xffff0000) == 0x38700000
|
|
/* ammin_db.wu rd, rk, rj */
|
|
|| (ip->insn_bin & 0xffff0000) == 0x38710000))
|
|
{
|
|
/* For AMO insn amswap.[wd], amadd.[wd], etc. */
|
|
if (ip->args[0] != 0
|
|
&& (ip->args[0] == ip->args[1] || ip->args[0] == ip->args[2]))
|
|
as_bad (_("automic memory operations insns require rd != rj"
|
|
" && rd != rk when rd isn't r0"));
|
|
}
|
|
else if ((ip->insn->mask == 0xffe08000
|
|
/* bstrins.w rd, rj, msbw, lsbw */
|
|
&& (ip->insn_bin & 0xffe00000) == 0x00600000)
|
|
|| (ip->insn->mask == 0xffc00000
|
|
/* bstrins.d rd, rj, msbd, lsbd */
|
|
&& (ip->insn_bin & 0xff800000) == 0x00800000))
|
|
{
|
|
/* For bstr(ins|pick).[wd]. */
|
|
if (ip->args[2] < ip->args[3])
|
|
as_bad (_("bstr(ins|pick).[wd] require msbd >= lsbd"));
|
|
}
|
|
else if (ip->insn->mask != 0 && (ip->insn_bin & 0xfe0003c0) == 0x04000000
|
|
/* csrxchg rd, rj, csr_num */
|
|
&& (strcmp ("csrxchg", ip->name) == 0
|
|
|| strcmp ("gcsrxchg", ip->name) == 0))
|
|
as_bad (_("g?csrxchg require rj != r0 && rj != r1"));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
install_insn (const struct loongarch_cl_insn *insn)
|
|
{
|
|
char *f = insn->frag->fr_literal + insn->where;
|
|
if (0 < insn->insn_length)
|
|
md_number_to_chars (f, insn->insn_bin, insn->insn_length);
|
|
}
|
|
|
|
static void
|
|
move_insn (struct loongarch_cl_insn *insn, fragS *frag, long where)
|
|
{
|
|
size_t i;
|
|
insn->frag = frag;
|
|
insn->where = where;
|
|
for (i = 0; i < insn->reloc_num; i++)
|
|
{
|
|
if (insn->fixp[i])
|
|
{
|
|
insn->fixp[i]->fx_frag = frag;
|
|
insn->fixp[i]->fx_where = where;
|
|
}
|
|
}
|
|
install_insn (insn);
|
|
}
|
|
|
|
/* Add INSN to the end of the output. */
|
|
static void
|
|
append_fixed_insn (struct loongarch_cl_insn *insn)
|
|
{
|
|
/* Ensure the jirl is emitted to the same frag as the pcaddu18i. */
|
|
if (BFD_RELOC_LARCH_CALL36 == insn->reloc_info[0].type)
|
|
frag_grow (8);
|
|
|
|
char *f = frag_more (insn->insn_length);
|
|
move_insn (insn, frag_now, f - frag_now->fr_literal);
|
|
|
|
if (call36)
|
|
{
|
|
if (strcmp (insn->name, "jirl") == 0)
|
|
{
|
|
/* See comment at end of append_fixp_and_insn. */
|
|
frag_wane (frag_now);
|
|
frag_new (0);
|
|
}
|
|
call36 = 0;
|
|
}
|
|
|
|
if (BFD_RELOC_LARCH_CALL36 == insn->reloc_info[0].type)
|
|
call36 = 1;
|
|
}
|
|
|
|
/* Add instructions based on the worst-case scenario firstly. */
|
|
static void
|
|
append_relaxed_branch_insn (struct loongarch_cl_insn *insn, int max_chars,
|
|
int var, relax_substateT subtype, symbolS *symbol, offsetT offset)
|
|
{
|
|
frag_grow (max_chars);
|
|
move_insn (insn, frag_now, frag_more (0) - frag_now->fr_literal);
|
|
frag_var (rs_machine_dependent, max_chars, var,
|
|
subtype, symbol, offset, NULL);
|
|
}
|
|
|
|
static void
|
|
append_fixp_and_insn (struct loongarch_cl_insn *ip)
|
|
{
|
|
reloc_howto_type *howto;
|
|
bfd_reloc_code_real_type r_type;
|
|
struct reloc_info *reloc_info = ip->reloc_info;
|
|
size_t i;
|
|
|
|
dwarf2_emit_insn (0);
|
|
|
|
for (i = 0; i < ip->reloc_num; i++)
|
|
{
|
|
r_type = reloc_info[i].type;
|
|
|
|
if (r_type != BFD_RELOC_UNUSED)
|
|
{
|
|
|
|
gas_assert (&(reloc_info[i].value));
|
|
if (BFD_RELOC_LARCH_B16 == r_type || BFD_RELOC_LARCH_B21 == r_type)
|
|
{
|
|
int min_bytes = 4; /* One branch instruction. */
|
|
unsigned max_bytes = 8; /* Branch and jump instructions. */
|
|
|
|
if (now_seg == absolute_section)
|
|
{
|
|
as_bad (_("relaxable branches not supported in absolute section"));
|
|
return;
|
|
}
|
|
|
|
append_relaxed_branch_insn (ip, max_bytes, min_bytes,
|
|
RELAX_BRANCH_ENCODE (r_type),
|
|
reloc_info[i].value.X_add_symbol,
|
|
reloc_info[i].value.X_add_number);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
howto = bfd_reloc_type_lookup (stdoutput, r_type);
|
|
if (howto == NULL)
|
|
as_fatal (_("no HOWTO loong relocation number %d"), r_type);
|
|
|
|
ip->fixp[i] = fix_new_exp (ip->frag, ip->where,
|
|
bfd_get_reloc_size (howto),
|
|
&reloc_info[i].value, FALSE, r_type);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ip->insn_length < ip->relax_max_length)
|
|
as_fatal (_("Internal error: not support relax now"));
|
|
else
|
|
append_fixed_insn (ip);
|
|
|
|
/* We need to start a new frag after any instruction that can be
|
|
optimized away or compressed by the linker during relaxation, to prevent
|
|
the assembler from computing static offsets across such an instruction.
|
|
|
|
This is necessary to get correct .eh_frame FDE DW_CFA_advance_loc info.
|
|
If one cfi_insn_data's two symbols are not in the same frag, it will
|
|
generate ADD and SUB relocations pairs to calculate DW_CFA_advance_loc.
|
|
(gas/dw2gencfi.c: output_cfi_insn:
|
|
if (symbol_get_frag (to) == symbol_get_frag (from)))
|
|
|
|
For macro instructions, only the first instruction expanded from macro
|
|
need to start a new frag.
|
|
Since the relocations of the normal code model and the extreme code model
|
|
of the old LE instruction sequence are the same, it is impossible to
|
|
distinguish which code model it is based on relocation alone, so the
|
|
extreme code model has to be relaxed. */
|
|
if (LARCH_opts.relax
|
|
&& (BFD_RELOC_LARCH_PCALA_HI20 == reloc_info[0].type
|
|
|| BFD_RELOC_LARCH_GOT_PC_HI20 == reloc_info[0].type
|
|
|| BFD_RELOC_LARCH_TLS_LE_HI20_R == reloc_info[0].type
|
|
|| BFD_RELOC_LARCH_TLS_LE_ADD_R == reloc_info[0].type
|
|
|| BFD_RELOC_LARCH_TLS_LD_PC_HI20 == reloc_info[0].type
|
|
|| BFD_RELOC_LARCH_TLS_GD_PC_HI20 == reloc_info[0].type
|
|
|| BFD_RELOC_LARCH_TLS_DESC_PC_HI20 == reloc_info[0].type
|
|
|| BFD_RELOC_LARCH_TLS_IE_PC_HI20 == reloc_info[0].type
|
|
|| BFD_RELOC_LARCH_TLS_LE_HI20 == reloc_info[0].type
|
|
|| BFD_RELOC_LARCH_TLS_LE_LO12 == reloc_info[0].type
|
|
|| BFD_RELOC_LARCH_TLS_LE64_LO20 == reloc_info[0].type
|
|
|| BFD_RELOC_LARCH_TLS_LE64_HI12 == reloc_info[0].type))
|
|
{
|
|
frag_wane (frag_now);
|
|
frag_new (0);
|
|
}
|
|
}
|
|
|
|
/* Ask helper for returning a malloced c_str or NULL. */
|
|
static char *
|
|
assember_macro_helper (const char *const args[], void *context_ptr)
|
|
{
|
|
struct loongarch_cl_insn *insn = context_ptr;
|
|
char *ret = NULL;
|
|
if ( strcmp (insn->name, "li.w") == 0 || strcmp (insn->name, "li.d") == 0)
|
|
{
|
|
char args_buf[50], insns_buf[200];
|
|
const char *arg_strs[6];
|
|
uint32_t hi32, lo32;
|
|
|
|
/* We pay attention to sign extend beacause it is chance of reduce insn.
|
|
The exception is 12-bit and hi-12-bit unsigned,
|
|
we need a 'ori' or a 'lu52i.d' accordingly. */
|
|
char all0_bit_vec, sign_bit_vec, allf_bit_vec, paritial_is_sext_of_prev;
|
|
|
|
lo32 = insn->args[1] & 0xffffffff;
|
|
hi32 = insn->args[1] >> 32;
|
|
|
|
if (strcmp (insn->name, "li.w") == 0)
|
|
{
|
|
if (hi32 != 0 && hi32 != 0xffffffff)
|
|
as_fatal (_("li overflow: hi32:0x%x lo32:0x%x"), hi32, lo32);
|
|
hi32 = lo32 & 0x80000000 ? 0xffffffff : 0;
|
|
}
|
|
|
|
if (strcmp (insn->name, "li.d") == 0 && !LARCH_opts.ase_lp64)
|
|
as_fatal (_("we can't li.d on 32bit-arch"));
|
|
|
|
snprintf (args_buf, sizeof (args_buf), "0x%x,0x%x,0x%x,0x%x,%s",
|
|
(hi32 >> 20) & 0xfff, hi32 & 0xfffff, (lo32 >> 12) & 0xfffff,
|
|
lo32 & 0xfff, args[0]);
|
|
loongarch_split_args_by_comma (args_buf, arg_strs);
|
|
|
|
all0_bit_vec =
|
|
((((hi32 & 0xfff00000) == 0) << 3) | (((hi32 & 0x000fffff) == 0) << 2)
|
|
| (((lo32 & 0xfffff000) == 0) << 1) | ((lo32 & 0x00000fff) == 0));
|
|
sign_bit_vec =
|
|
((((hi32 & 0x80000000) != 0) << 3) | (((hi32 & 0x00080000) != 0) << 2)
|
|
| (((lo32 & 0x80000000) != 0) << 1) | ((lo32 & 0x00000800) != 0));
|
|
allf_bit_vec =
|
|
((((hi32 & 0xfff00000) == 0xfff00000) << 3)
|
|
| (((hi32 & 0x000fffff) == 0x000fffff) << 2)
|
|
| (((lo32 & 0xfffff000) == 0xfffff000) << 1)
|
|
| ((lo32 & 0x00000fff) == 0x00000fff));
|
|
paritial_is_sext_of_prev =
|
|
(all0_bit_vec ^ allf_bit_vec) & (all0_bit_vec ^ (sign_bit_vec << 1));
|
|
|
|
static const char *const li_32bit[] =
|
|
{
|
|
"lu12i.w %5,%3&0x80000?%3-0x100000:%3;ori %5,%5,%4;",
|
|
"lu12i.w %5,%3&0x80000?%3-0x100000:%3;",
|
|
"addi.w %5,$r0,%4&0x800?%4-0x1000:%4;",
|
|
"or %5,$r0,$r0;",
|
|
};
|
|
static const char *const li_hi_32bit[] =
|
|
{
|
|
"lu32i.d %5,%2&0x80000?%2-0x100000:%2;"
|
|
"lu52i.d %5,%5,%1&0x800?%1-0x1000:%1;",
|
|
"lu52i.d %5,%5,%1&0x800?%1-0x1000:%1;",
|
|
"lu32i.d %5,%2&0x80000?%2-0x100000:%2;",
|
|
"",
|
|
};
|
|
do
|
|
{
|
|
insns_buf[0] = '\0';
|
|
if (paritial_is_sext_of_prev == 0x7)
|
|
{
|
|
strcat (insns_buf, "lu52i.d %5,$r0,%1&0x800?%1-0x1000:%1;");
|
|
break;
|
|
}
|
|
if ((all0_bit_vec & 0x3) == 0x2)
|
|
strcat (insns_buf, "ori %5,$r0,%4;");
|
|
else
|
|
strcat (insns_buf, li_32bit[paritial_is_sext_of_prev & 0x3]);
|
|
strcat (insns_buf, li_hi_32bit[paritial_is_sext_of_prev >> 2]);
|
|
}
|
|
while (0);
|
|
|
|
ret = loongarch_expand_macro (insns_buf, arg_strs, NULL, NULL,
|
|
sizeof (args_buf));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Accept instructions separated by ';'
|
|
* assuming 'not starting with space and not ending with space' or pass in
|
|
* empty c_str. */
|
|
static void
|
|
loongarch_assemble_INSNs (char *str, unsigned int expand_from_macro)
|
|
{
|
|
char *rest;
|
|
size_t len_str = strlen(str);
|
|
|
|
for (rest = str; *rest != ';' && *rest != '\0'; rest++);
|
|
if (*rest == ';')
|
|
*rest++ = '\0';
|
|
|
|
if (*str == ':')
|
|
{
|
|
str++;
|
|
setup_internal_label_here (strtol (str, &str, 10));
|
|
str++;
|
|
}
|
|
|
|
do
|
|
{
|
|
if (*str == '\0')
|
|
break;
|
|
|
|
struct loongarch_cl_insn the_one = { 0 };
|
|
the_one.name = str;
|
|
the_one.expand_from_macro = expand_from_macro;
|
|
|
|
for (; *str && *str != ' '; str++)
|
|
;
|
|
if (*str == ' ')
|
|
*str++ = '\0';
|
|
|
|
loongarch_split_args_by_comma (str, the_one.arg_strs);
|
|
get_loongarch_opcode (&the_one);
|
|
|
|
if (!the_one.all_match)
|
|
{
|
|
char *ss = loongarch_cat_splited_strs (the_one.arg_strs);
|
|
as_bad (_("no match insn: %s\t%s"), the_one.name, ss ? ss : "");
|
|
free(ss);
|
|
return;
|
|
}
|
|
|
|
if (check_this_insn_before_appending (&the_one) != 0)
|
|
break;
|
|
|
|
append_fixp_and_insn (&the_one);
|
|
|
|
/* Expanding macro instructions. */
|
|
if (the_one.insn_length == 0 && the_one.insn->macro)
|
|
{
|
|
unsigned int new_expand_from_macro = 0;
|
|
if (2 == the_one.arg_num)
|
|
new_expand_from_macro |= 1;
|
|
else if (3 == the_one.arg_num)
|
|
new_expand_from_macro |= 2;
|
|
|
|
char *c_str = loongarch_expand_macro (the_one.insn->macro,
|
|
the_one.arg_strs,
|
|
assember_macro_helper,
|
|
&the_one, len_str);
|
|
/* The first instruction expanded from macro. */
|
|
loongarch_assemble_INSNs (c_str, new_expand_from_macro);
|
|
free (c_str);
|
|
}
|
|
}
|
|
while (0);
|
|
|
|
/* The rest instructions expanded from macro, split by semicolon(;),
|
|
assembly one by one. */
|
|
if (*rest != '\0')
|
|
loongarch_assemble_INSNs (rest, expand_from_macro);
|
|
}
|
|
|
|
void
|
|
md_assemble (char *str)
|
|
{
|
|
loongarch_assemble_INSNs (str, 0);
|
|
}
|
|
|
|
const char *
|
|
md_atof (int type, char *litP, int *sizeP)
|
|
{
|
|
return ieee_md_atof (type, litP, sizeP, FALSE);
|
|
}
|
|
|
|
void
|
|
md_number_to_chars (char *buf, valueT val, int n)
|
|
{
|
|
number_to_chars_littleendian (buf, val, n);
|
|
}
|
|
|
|
/* The location from which a PC relative jump should be calculated,
|
|
given a PC relative reloc. */
|
|
long
|
|
md_pcrel_from (fixS *fixP ATTRIBUTE_UNUSED)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void fix_reloc_insn (fixS *fixP, bfd_vma reloc_val, char *buf)
|
|
{
|
|
reloc_howto_type *howto;
|
|
insn_t insn;
|
|
howto = bfd_reloc_type_lookup (stdoutput, fixP->fx_r_type);
|
|
|
|
insn = bfd_getl32 (buf);
|
|
|
|
if (!loongarch_adjust_reloc_bitsfield (NULL, howto, &reloc_val))
|
|
as_bad_where (fixP->fx_file, fixP->fx_line, "Reloc overflow");
|
|
|
|
insn = (insn & (insn_t)howto->src_mask)
|
|
| ((insn & (~(insn_t)howto->dst_mask)) | reloc_val);
|
|
|
|
bfd_putl32 (insn, buf);
|
|
}
|
|
|
|
void
|
|
md_apply_fix (fixS *fixP, valueT *valP, segT seg ATTRIBUTE_UNUSED)
|
|
{
|
|
static int64_t stack_top;
|
|
static int last_reloc_is_sop_push_pcrel_1 = 0;
|
|
int last_reloc_is_sop_push_pcrel = last_reloc_is_sop_push_pcrel_1;
|
|
segT sub_segment;
|
|
last_reloc_is_sop_push_pcrel_1 = 0;
|
|
|
|
char *buf = fixP->fx_frag->fr_literal + fixP->fx_where;
|
|
switch (fixP->fx_r_type)
|
|
{
|
|
case BFD_RELOC_LARCH_SOP_PUSH_TLS_TPREL:
|
|
case BFD_RELOC_LARCH_SOP_PUSH_TLS_GD:
|
|
case BFD_RELOC_LARCH_SOP_PUSH_TLS_GOT:
|
|
case BFD_RELOC_LARCH_TLS_LE_HI20:
|
|
case BFD_RELOC_LARCH_TLS_LE_LO12:
|
|
case BFD_RELOC_LARCH_TLS_LE64_LO20:
|
|
case BFD_RELOC_LARCH_TLS_LE64_HI12:
|
|
case BFD_RELOC_LARCH_TLS_IE_PC_HI20:
|
|
case BFD_RELOC_LARCH_TLS_IE_PC_LO12:
|
|
case BFD_RELOC_LARCH_TLS_IE64_PC_LO20:
|
|
case BFD_RELOC_LARCH_TLS_IE64_PC_HI12:
|
|
case BFD_RELOC_LARCH_TLS_IE_HI20:
|
|
case BFD_RELOC_LARCH_TLS_IE_LO12:
|
|
case BFD_RELOC_LARCH_TLS_IE64_LO20:
|
|
case BFD_RELOC_LARCH_TLS_IE64_HI12:
|
|
case BFD_RELOC_LARCH_TLS_LD_PC_HI20:
|
|
case BFD_RELOC_LARCH_TLS_LD_HI20:
|
|
case BFD_RELOC_LARCH_TLS_GD_PC_HI20:
|
|
case BFD_RELOC_LARCH_TLS_GD_HI20:
|
|
case BFD_RELOC_LARCH_TLS_DESC_PC_HI20:
|
|
case BFD_RELOC_LARCH_TLS_DESC_PC_LO12:
|
|
case BFD_RELOC_LARCH_TLS_DESC64_PC_LO20:
|
|
case BFD_RELOC_LARCH_TLS_DESC64_PC_HI12:
|
|
case BFD_RELOC_LARCH_TLS_DESC_HI20:
|
|
case BFD_RELOC_LARCH_TLS_DESC_LO12:
|
|
case BFD_RELOC_LARCH_TLS_DESC64_LO20:
|
|
case BFD_RELOC_LARCH_TLS_DESC64_HI12:
|
|
case BFD_RELOC_LARCH_TLS_LE_ADD_R:
|
|
case BFD_RELOC_LARCH_TLS_LE_HI20_R:
|
|
case BFD_RELOC_LARCH_TLS_LE_LO12_R:
|
|
/* Add tls lo (got_lo reloc type). */
|
|
if (fixP->fx_addsy == NULL)
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
_("Relocation against a constant"));
|
|
S_SET_THREAD_LOCAL (fixP->fx_addsy);
|
|
break;
|
|
|
|
case BFD_RELOC_LARCH_SOP_PUSH_PCREL:
|
|
if (fixP->fx_addsy == NULL)
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
_("Relocation against a constant"));
|
|
|
|
last_reloc_is_sop_push_pcrel_1 = 1;
|
|
if (S_GET_SEGMENT (fixP->fx_addsy) == seg)
|
|
stack_top = (S_GET_VALUE (fixP->fx_addsy) + fixP->fx_offset
|
|
- (fixP->fx_where + fixP->fx_frag->fr_address));
|
|
else
|
|
stack_top = 0;
|
|
break;
|
|
|
|
case BFD_RELOC_LARCH_TLS_DESC_LD:
|
|
case BFD_RELOC_LARCH_TLS_DESC_CALL:
|
|
break;
|
|
|
|
case BFD_RELOC_LARCH_SOP_POP_32_S_10_5:
|
|
case BFD_RELOC_LARCH_SOP_POP_32_S_10_12:
|
|
case BFD_RELOC_LARCH_SOP_POP_32_U_10_12:
|
|
case BFD_RELOC_LARCH_SOP_POP_32_S_10_16:
|
|
case BFD_RELOC_LARCH_SOP_POP_32_S_10_16_S2:
|
|
case BFD_RELOC_LARCH_SOP_POP_32_S_5_20:
|
|
case BFD_RELOC_LARCH_SOP_POP_32_U:
|
|
case BFD_RELOC_LARCH_SOP_POP_32_S_0_5_10_16_S2:
|
|
case BFD_RELOC_LARCH_SOP_POP_32_S_0_10_10_16_S2:
|
|
if (!last_reloc_is_sop_push_pcrel)
|
|
break;
|
|
|
|
fix_reloc_insn (fixP, (bfd_vma)stack_top, buf);
|
|
break;
|
|
|
|
/* LARCH only has R_LARCH_64/32, not has R_LARCH_24/16/8.
|
|
For BFD_RELOC_64/32, if fx_addsy and fx_subsy not null, wer need
|
|
generate BFD_RELOC_LARCH_ADD64/32 and BFD_RELOC_LARCH_SUB64/32 here.
|
|
Then will parse howto table bfd_reloc_code_real_type to generate
|
|
R_LARCH_ADD64/32 and R_LARCH_SUB64/32 reloc at tc_gen_reloc function.
|
|
If only fx_addsy not null, skip here directly, then generate
|
|
R_LARCH_64/32.
|
|
|
|
For BFD_RELOC_24/16/8, if fx_addsy and fx_subsy not null, wer need
|
|
generate BFD_RELOC_LARCH_ADD24/16/8 and BFD_RELOC_LARCH_SUB24/16/8 here.
|
|
Then will parse howto table bfd_reloc_code_real_type to generate
|
|
R_LARCH_ADD24/16/8 and R_LARCH_SUB24/16/8 reloc at tc_gen_reloc
|
|
function. If only fx_addsy not null, we generate
|
|
BFD_RELOC_LARCH_ADD24/16/8 only, then generate R_LARCH_24/16/8.
|
|
To avoid R_LARCH_ADDxx add extra value, we write 0 first
|
|
(use md_number_to_chars (buf, 0, fixP->fx_size)). */
|
|
case BFD_RELOC_64:
|
|
case BFD_RELOC_32:
|
|
if (fixP->fx_pcrel)
|
|
{
|
|
switch (fixP->fx_r_type)
|
|
{
|
|
case BFD_RELOC_64:
|
|
fixP->fx_r_type = BFD_RELOC_LARCH_64_PCREL;
|
|
break;
|
|
case BFD_RELOC_32:
|
|
fixP->fx_r_type = BFD_RELOC_LARCH_32_PCREL;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If symbol in .eh_frame the address may be adjusted, and contents of
|
|
.eh_frame will be adjusted, so use pc-relative relocation for FDE
|
|
initial location.
|
|
The Option of mthin-add-sub does not affect the generation of
|
|
R_LARCH_32_PCREL relocation in .eh_frame. */
|
|
if (fixP->fx_r_type == BFD_RELOC_32
|
|
&& fixP->fx_addsy && fixP->fx_subsy
|
|
&& (sub_segment = S_GET_SEGMENT (fixP->fx_subsy))
|
|
&& strcmp (sub_segment->name, ".eh_frame") == 0
|
|
&& S_GET_VALUE (fixP->fx_subsy)
|
|
== fixP->fx_frag->fr_address + fixP->fx_where)
|
|
{
|
|
fixP->fx_r_type = BFD_RELOC_LARCH_32_PCREL;
|
|
fixP->fx_subsy = NULL;
|
|
break;
|
|
}
|
|
|
|
if (fixP->fx_addsy && fixP->fx_subsy)
|
|
{
|
|
fixP->fx_next = xmemdup (fixP, sizeof (*fixP), sizeof (*fixP));
|
|
fixP->fx_next->fx_addsy = fixP->fx_subsy;
|
|
fixP->fx_next->fx_subsy = NULL;
|
|
fixP->fx_next->fx_offset = 0;
|
|
fixP->fx_subsy = NULL;
|
|
|
|
switch (fixP->fx_r_type)
|
|
{
|
|
case BFD_RELOC_64:
|
|
fixP->fx_r_type = BFD_RELOC_LARCH_ADD64;
|
|
fixP->fx_next->fx_r_type = BFD_RELOC_LARCH_SUB64;
|
|
break;
|
|
case BFD_RELOC_32:
|
|
fixP->fx_r_type = BFD_RELOC_LARCH_ADD32;
|
|
fixP->fx_next->fx_r_type = BFD_RELOC_LARCH_SUB32;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
md_number_to_chars (buf, 0, fixP->fx_size);
|
|
}
|
|
|
|
if (fixP->fx_addsy == NULL)
|
|
{
|
|
fixP->fx_done = 1;
|
|
md_number_to_chars (buf, *valP, fixP->fx_size);
|
|
}
|
|
break;
|
|
|
|
case BFD_RELOC_24:
|
|
case BFD_RELOC_16:
|
|
case BFD_RELOC_8:
|
|
if (fixP->fx_addsy)
|
|
{
|
|
fixP->fx_next = xmemdup (fixP, sizeof (*fixP), sizeof (*fixP));
|
|
fixP->fx_next->fx_addsy = fixP->fx_subsy;
|
|
fixP->fx_next->fx_subsy = NULL;
|
|
fixP->fx_next->fx_offset = 0;
|
|
fixP->fx_subsy = NULL;
|
|
|
|
switch (fixP->fx_r_type)
|
|
{
|
|
case BFD_RELOC_24:
|
|
fixP->fx_r_type = BFD_RELOC_LARCH_ADD24;
|
|
fixP->fx_next->fx_r_type = BFD_RELOC_LARCH_SUB24;
|
|
break;
|
|
case BFD_RELOC_16:
|
|
fixP->fx_r_type = BFD_RELOC_LARCH_ADD16;
|
|
fixP->fx_next->fx_r_type = BFD_RELOC_LARCH_SUB16;
|
|
break;
|
|
case BFD_RELOC_8:
|
|
fixP->fx_r_type = BFD_RELOC_LARCH_ADD8;
|
|
fixP->fx_next->fx_r_type = BFD_RELOC_LARCH_SUB8;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
md_number_to_chars (buf, 0, fixP->fx_size);
|
|
|
|
if (fixP->fx_next->fx_addsy == NULL)
|
|
fixP->fx_next->fx_done = 1;
|
|
}
|
|
|
|
if (fixP->fx_addsy == NULL)
|
|
{
|
|
fixP->fx_done = 1;
|
|
md_number_to_chars (buf, *valP, fixP->fx_size);
|
|
}
|
|
break;
|
|
|
|
case BFD_RELOC_LARCH_CFA:
|
|
if (fixP->fx_addsy && fixP->fx_subsy)
|
|
{
|
|
fixP->fx_next = xmemdup (fixP, sizeof (*fixP), sizeof (*fixP));
|
|
fixP->fx_next->fx_addsy = fixP->fx_subsy;
|
|
fixP->fx_next->fx_subsy = NULL;
|
|
fixP->fx_next->fx_offset = 0;
|
|
fixP->fx_subsy = NULL;
|
|
|
|
unsigned int subtype;
|
|
offsetT loc;
|
|
subtype = bfd_get_8 (NULL, &((fragS *)
|
|
(fixP->fx_frag->fr_opcode))->fr_literal[fixP->fx_where]);
|
|
loc = fixP->fx_frag->fr_fix - (subtype & 7);
|
|
switch (subtype)
|
|
{
|
|
case DW_CFA_advance_loc1:
|
|
fixP->fx_where = loc + 1;
|
|
fixP->fx_next->fx_where = loc + 1;
|
|
fixP->fx_r_type = BFD_RELOC_LARCH_ADD8;
|
|
fixP->fx_next->fx_r_type = BFD_RELOC_LARCH_SUB8;
|
|
md_number_to_chars (buf+1, 0, fixP->fx_size);
|
|
break;
|
|
|
|
case DW_CFA_advance_loc2:
|
|
fixP->fx_size = 2;
|
|
fixP->fx_next->fx_size = 2;
|
|
fixP->fx_where = loc + 1;
|
|
fixP->fx_next->fx_where = loc + 1;
|
|
fixP->fx_r_type = BFD_RELOC_LARCH_ADD16;
|
|
fixP->fx_next->fx_r_type = BFD_RELOC_LARCH_SUB16;
|
|
md_number_to_chars (buf+1, 0, fixP->fx_size);
|
|
break;
|
|
|
|
case DW_CFA_advance_loc4:
|
|
fixP->fx_size = 4;
|
|
fixP->fx_next->fx_size = 4;
|
|
fixP->fx_where = loc;
|
|
fixP->fx_next->fx_where = loc;
|
|
fixP->fx_r_type = BFD_RELOC_LARCH_ADD32;
|
|
fixP->fx_next->fx_r_type = BFD_RELOC_LARCH_SUB32;
|
|
md_number_to_chars (buf+1, 0, fixP->fx_size);
|
|
break;
|
|
|
|
default:
|
|
if (subtype < 0x80 && (subtype & 0x40))
|
|
{
|
|
/* DW_CFA_advance_loc. */
|
|
fixP->fx_frag = (fragS *) fixP->fx_frag->fr_opcode;
|
|
fixP->fx_next->fx_frag = fixP->fx_frag;
|
|
fixP->fx_r_type = BFD_RELOC_LARCH_ADD6;
|
|
fixP->fx_next->fx_r_type = BFD_RELOC_LARCH_SUB6;
|
|
md_number_to_chars (buf, 0x40, fixP->fx_size);
|
|
}
|
|
else
|
|
as_fatal (_("internal: bad CFA value #%d"), subtype);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case BFD_RELOC_LARCH_B16:
|
|
case BFD_RELOC_LARCH_B21:
|
|
case BFD_RELOC_LARCH_B26:
|
|
if (fixP->fx_addsy == NULL)
|
|
{
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
_ ("Relocation against a constant."));
|
|
}
|
|
if (S_GET_SEGMENT (fixP->fx_addsy) == seg
|
|
&& !S_FORCE_RELOC (fixP->fx_addsy, 1))
|
|
{
|
|
int64_t sym_addend = S_GET_VALUE (fixP->fx_addsy) + fixP->fx_offset;
|
|
int64_t pc = fixP->fx_where + fixP->fx_frag->fr_address;
|
|
fix_reloc_insn (fixP, sym_addend - pc, buf);
|
|
|
|
/* If relax, symbol value may change at link time, so reloc need to
|
|
be saved. */
|
|
if (!LARCH_opts.relax)
|
|
fixP->fx_done = 1;
|
|
}
|
|
break;
|
|
|
|
/* Because ADD_ULEB128/SUB_ULEB128 always occur in pairs.
|
|
So just deal with one is ok.
|
|
case BFD_RELOC_LARCH_ADD_ULEB128: */
|
|
case BFD_RELOC_LARCH_SUB_ULEB128:
|
|
{
|
|
unsigned int len = 0;
|
|
len = loongarch_get_uleb128_length ((bfd_byte *)buf);
|
|
bfd_byte *endp = (bfd_byte*) buf + len -1;
|
|
/* Clean the uleb128 value to 0. Do not reduce the length. */
|
|
memset (buf, 0x80, len - 1);
|
|
*endp = 0;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Estimate the size of a frag before relaxing. */
|
|
|
|
int
|
|
md_estimate_size_before_relax (fragS *fragp, asection *sec)
|
|
{
|
|
/* align pseudo instunctions. */
|
|
if (rs_align_code == fragp->fr_subtype)
|
|
{
|
|
offsetT nop_bytes;
|
|
if (NULL == fragp->fr_symbol)
|
|
nop_bytes = fragp->fr_offset;
|
|
else
|
|
nop_bytes = ALIGN_MAX_NOP_BYTES (fragp->fr_offset);
|
|
|
|
/* Normally, nop_bytes should be >= 4. */
|
|
gas_assert (nop_bytes > 0);
|
|
|
|
if (FRAG_AT_START_OF_SECTION (fragp)
|
|
&& 0 == ((1 << sec->alignment_power) % (nop_bytes + 4)))
|
|
return (fragp->fr_var = 0);
|
|
else
|
|
return (fragp->fr_var = nop_bytes);
|
|
}
|
|
|
|
/* branch instructions and other instructions.
|
|
branch instructions may become 8 bytes after relaxing. */
|
|
return (fragp->fr_var = 4);
|
|
}
|
|
|
|
/* Translate internal representation of relocation info to BFD target
|
|
format. */
|
|
arelent *
|
|
tc_gen_reloc (asection *section ATTRIBUTE_UNUSED, fixS *fixp)
|
|
{
|
|
arelent *reloc = (arelent *) xmalloc (sizeof (arelent));
|
|
|
|
reloc->sym_ptr_ptr = (asymbol **) xmalloc (sizeof (asymbol *));
|
|
*reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy);
|
|
reloc->address = fixp->fx_frag->fr_address + fixp->fx_where;
|
|
reloc->addend = fixp->fx_offset;
|
|
|
|
reloc->howto = bfd_reloc_type_lookup (stdoutput, fixp->fx_r_type);
|
|
if (reloc->howto == NULL)
|
|
{
|
|
as_bad_where (fixp->fx_file, fixp->fx_line,
|
|
_("cannot represent %s relocation in object file"),
|
|
bfd_get_reloc_code_name (fixp->fx_r_type));
|
|
return NULL;
|
|
}
|
|
|
|
return reloc;
|
|
}
|
|
|
|
/* Standard calling conventions leave the CFA at SP on entry. */
|
|
void
|
|
loongarch_cfi_frame_initial_instructions (void)
|
|
{
|
|
cfi_add_CFA_def_cfa_register (3 /* $sp */);
|
|
}
|
|
|
|
void
|
|
loongarch_pre_output_hook (void)
|
|
{
|
|
const frchainS *frch;
|
|
segT s;
|
|
|
|
if (!LARCH_opts.relax)
|
|
return;
|
|
|
|
/* Save the current segment info. */
|
|
segT seg = now_seg;
|
|
subsegT subseg = now_subseg;
|
|
|
|
for (s = stdoutput->sections; s; s = s->next)
|
|
for (frch = seg_info (s)->frchainP; frch; frch = frch->frch_next)
|
|
{
|
|
fragS *frag;
|
|
|
|
for (frag = frch->frch_root; frag; frag = frag->fr_next)
|
|
{
|
|
if (frag->fr_type == rs_cfa)
|
|
{
|
|
expressionS exp;
|
|
expressionS *symval;
|
|
|
|
symval = symbol_get_value_expression (frag->fr_symbol);
|
|
exp.X_op = O_subtract;
|
|
exp.X_add_symbol = symval->X_add_symbol;
|
|
exp.X_add_number = 0;
|
|
exp.X_op_symbol = symval->X_op_symbol;
|
|
|
|
/* We must set the segment before creating a frag after all
|
|
frag chains have been chained together. */
|
|
subseg_set (s, frch->frch_subseg);
|
|
|
|
fix_new_exp (frag, (int) frag->fr_offset, 1, &exp, 0,
|
|
BFD_RELOC_LARCH_CFA);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Restore the original segment info. */
|
|
subseg_set (seg, subseg);
|
|
}
|
|
|
|
void
|
|
tc_loongarch_parse_to_dw2regnum (expressionS *exp)
|
|
{
|
|
expression_and_evaluate (exp);
|
|
}
|
|
|
|
void
|
|
md_show_usage (FILE *stream)
|
|
{
|
|
fprintf (stream, _("LARCH options:\n"));
|
|
/* FIXME */
|
|
fprintf (stream, _("\
|
|
-mthin-add-sub Convert a pair of R_LARCH_ADD32/64 and R_LARCH_SUB32/64 to\n\
|
|
R_LARCH_32/64_PCREL as much as possible\n\
|
|
The option does not affect the generation of R_LARCH_32_PCREL\n\
|
|
relocations in .eh_frame\n\
|
|
-mignore-start-align Ignore .align if it is at the start of a section. This option\n\
|
|
can't be used when partial linking (ld -r).\n"));
|
|
}
|
|
|
|
static void
|
|
loongarch_make_nops (char *buf, bfd_vma bytes)
|
|
{
|
|
bfd_vma i = 0;
|
|
|
|
/* Fill with 4-byte NOPs. */
|
|
for ( ; i < bytes; i += 4)
|
|
number_to_chars_littleendian (buf + i, LARCH_NOP, 4);
|
|
}
|
|
|
|
/* Called from md_do_align. Used to create an alignment frag in a
|
|
code section by emitting a worst-case NOP sequence that the linker
|
|
will later relax to the correct number of NOPs. We can't compute
|
|
the correct alignment now because of other linker relaxations. */
|
|
|
|
bool
|
|
loongarch_frag_align_code (int n, int max)
|
|
{
|
|
char *nops;
|
|
expressionS ex;
|
|
symbolS *s = NULL;
|
|
|
|
/* When not relaxing, loongarch_handle_align handles code alignment. */
|
|
if (!LARCH_opts.relax)
|
|
return false;
|
|
|
|
bfd_vma align_bytes = (bfd_vma) 1 << n;
|
|
bfd_vma worst_case_bytes = align_bytes - 4;
|
|
bfd_vma addend = worst_case_bytes;
|
|
bool align_max = max > 0 && (bfd_vma) max < worst_case_bytes;
|
|
|
|
/* If we are moving to a smaller alignment than the instruction size, then no
|
|
alignment is required. */
|
|
if (align_bytes <= 4)
|
|
return true;
|
|
|
|
/* If max <= 0, ignore max.
|
|
If max >= worst_case_bytes, max has no effect.
|
|
Similar to gas/write.c relax_segment function rs_align_code case:
|
|
if (fragP->fr_subtype != 0 && offset > fragP->fr_subtype). */
|
|
if (align_max)
|
|
{
|
|
s = get_align_symbol (now_seg);
|
|
if (!s)
|
|
as_fatal (_("internal error: cannot get align symbol"));
|
|
addend = ALIGN_MAX_ADDEND (n, max);
|
|
}
|
|
|
|
if (LARCH_opts.ignore_start_align)
|
|
{
|
|
frag_grow (worst_case_bytes);
|
|
/* Use relaxable frag for .align.
|
|
If .align at the start of section, do nothing. Section alignment can
|
|
ensure correct alignment.
|
|
If .align is not at the start of a section, reserve NOP instructions
|
|
and R_LARCH_ALIGN relocation. */
|
|
nops = frag_var (rs_machine_dependent, worst_case_bytes, worst_case_bytes,
|
|
rs_align_code, s, addend, NULL);
|
|
}
|
|
else
|
|
{
|
|
nops = frag_more (worst_case_bytes);
|
|
if (align_max)
|
|
{
|
|
ex.X_add_symbol = s;
|
|
ex.X_op = O_symbol;
|
|
}
|
|
else
|
|
ex.X_op = O_constant;
|
|
|
|
ex.X_add_number = addend;
|
|
|
|
fix_new_exp (frag_now, nops - frag_now->fr_literal, 0,
|
|
&ex, false, BFD_RELOC_LARCH_ALIGN);
|
|
}
|
|
|
|
/* Default write NOP for aligned bytes. */
|
|
loongarch_make_nops (nops, worst_case_bytes);
|
|
|
|
/* We need to start a new frag after the alignment which may be removed by
|
|
the linker, to prevent the assembler from computing static offsets.
|
|
This is necessary to get correct EH info. */
|
|
frag_wane (frag_now);
|
|
frag_new (0);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Fill in an rs_align_code fragment. We want to fill 'andi $r0,$r0,0'. */
|
|
void
|
|
loongarch_handle_align (fragS *fragp)
|
|
{
|
|
/* char nop_opcode; */
|
|
char *p;
|
|
int bytes, size, excess;
|
|
valueT opcode;
|
|
|
|
if (fragp->fr_type != rs_align_code)
|
|
return;
|
|
|
|
struct loongarch_cl_insn nop =
|
|
{ .name = "andi", .arg_strs = { "$r0", "$r0", "0", NULL } };
|
|
|
|
get_loongarch_opcode (&nop);
|
|
gas_assert (nop.all_match);
|
|
|
|
p = fragp->fr_literal + fragp->fr_fix;
|
|
opcode = nop.insn_bin;
|
|
size = 4;
|
|
|
|
bytes = fragp->fr_next->fr_address - fragp->fr_address - fragp->fr_fix;
|
|
excess = bytes % size;
|
|
|
|
gas_assert (excess < 4);
|
|
fragp->fr_fix += excess;
|
|
|
|
while (excess-- != 0)
|
|
*p++ = 0;
|
|
|
|
md_number_to_chars (p, opcode, size);
|
|
fragp->fr_var = size;
|
|
}
|
|
|
|
/* Scan uleb128 subtraction expressions and insert fixups for them.
|
|
e.g., .uleb128 .L1 - .L0
|
|
Because relaxation may change the value of the subtraction, we
|
|
must resolve them at link-time. */
|
|
|
|
static void
|
|
loongarch_insert_uleb128_fixes (bfd *abfd ATTRIBUTE_UNUSED,
|
|
asection *sec, void *xxx ATTRIBUTE_UNUSED)
|
|
{
|
|
segment_info_type *seginfo = seg_info (sec);
|
|
struct frag *fragP;
|
|
|
|
subseg_set (sec, 0);
|
|
|
|
for (fragP = seginfo->frchainP->frch_root;
|
|
fragP; fragP = fragP->fr_next)
|
|
{
|
|
expressionS *exp, *exp_dup;
|
|
|
|
if (fragP->fr_type != rs_leb128 || fragP->fr_symbol == NULL)
|
|
continue;
|
|
|
|
exp = symbol_get_value_expression (fragP->fr_symbol);
|
|
|
|
if (exp->X_op != O_subtract)
|
|
continue;
|
|
|
|
/* FIXME: Skip for .sleb128. */
|
|
if (fragP->fr_subtype != 0)
|
|
continue;
|
|
|
|
exp_dup = xmemdup (exp, sizeof (*exp), sizeof (*exp));
|
|
exp_dup->X_op = O_symbol;
|
|
exp_dup->X_op_symbol = NULL;
|
|
|
|
exp_dup->X_add_symbol = exp->X_add_symbol;
|
|
fix_new_exp (fragP, fragP->fr_fix, 0,
|
|
exp_dup, 0, BFD_RELOC_LARCH_ADD_ULEB128);
|
|
|
|
/* From binutils/testsuite/binutils-all/dw5.S
|
|
section .debug_rnglists
|
|
.uleb128 .Letext0-.Ltext0 Range length (*.LLRL2)
|
|
Offset Info Type Symbol's Value Symbol's Name + Addend
|
|
0000000000000015 0000000200000079 R_LARCH_ADD_ULEB128 0000000000000000 .text + 2
|
|
0000000000000015 000000020000007a R_LARCH_SUB_ULEB128 0000000000000000 .text + 0. */
|
|
|
|
/* Only the ADD_ULEB128 has X_add_number (Addend)? */
|
|
exp_dup->X_add_number = 0;
|
|
exp_dup->X_add_symbol = exp->X_op_symbol;
|
|
fix_new_exp (fragP, fragP->fr_fix, 0,
|
|
exp_dup, 0, BFD_RELOC_LARCH_SUB_ULEB128);
|
|
}
|
|
}
|
|
|
|
void
|
|
loongarch_md_finish (void)
|
|
{
|
|
/* Insert relocations for uleb128 directives, so the values can be recomputed
|
|
at link time. */
|
|
if (LARCH_opts.relax)
|
|
bfd_map_over_sections (stdoutput, loongarch_insert_uleb128_fixes, NULL);
|
|
}
|
|
|
|
void
|
|
loongarch_elf_final_processing (void)
|
|
{
|
|
elf_elfheader (stdoutput)->e_flags = LARCH_opts.ase_abi;
|
|
}
|
|
|
|
/* Compute the length of a branch sequence, and adjust the stored length
|
|
accordingly. If FRAGP is NULL, the worst-case length is returned. */
|
|
static unsigned
|
|
loongarch_relaxed_branch_length (fragS *fragp, asection *sec, int update)
|
|
{
|
|
int length = 4;
|
|
|
|
if (!fragp)
|
|
return 8;
|
|
|
|
if (fragp->fr_symbol != NULL
|
|
&& S_IS_DEFINED (fragp->fr_symbol)
|
|
&& !S_IS_WEAK (fragp->fr_symbol)
|
|
&& sec == S_GET_SEGMENT (fragp->fr_symbol))
|
|
{
|
|
offsetT val = S_GET_VALUE (fragp->fr_symbol) + fragp->fr_offset;
|
|
|
|
val -= fragp->fr_address + fragp->fr_fix;
|
|
|
|
if (RELAX_BRANCH_16 == fragp->fr_subtype
|
|
&& OUT_OF_RANGE (val, 16, 2))
|
|
{
|
|
length = 8;
|
|
if (update)
|
|
fragp->fr_subtype = RELAX_BRANCH_26;
|
|
}
|
|
|
|
if (RELAX_BRANCH_21 == fragp->fr_subtype
|
|
&& OUT_OF_RANGE (val, 21, 2))
|
|
{
|
|
length = 8;
|
|
if (update)
|
|
fragp->fr_subtype = RELAX_BRANCH_26;
|
|
}
|
|
|
|
if (RELAX_BRANCH_26 == fragp->fr_subtype)
|
|
length = 8;
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
int
|
|
loongarch_relax_frag (asection *sec, fragS *fragp,
|
|
long stretch ATTRIBUTE_UNUSED)
|
|
{
|
|
if (RELAX_BRANCH (fragp->fr_subtype))
|
|
{
|
|
offsetT old_var = fragp->fr_var;
|
|
fragp->fr_var = loongarch_relaxed_branch_length (fragp, sec, true);
|
|
return fragp->fr_var - old_var;
|
|
}
|
|
else if (rs_align_code == fragp->fr_subtype)
|
|
{
|
|
offsetT nop_bytes;
|
|
if (NULL == fragp->fr_symbol)
|
|
nop_bytes = fragp->fr_offset;
|
|
else
|
|
nop_bytes = ALIGN_MAX_NOP_BYTES (fragp->fr_offset);
|
|
|
|
/* Normally, nop_bytes should be >= 4. */
|
|
gas_assert (nop_bytes > 0);
|
|
|
|
offsetT old_var = fragp->fr_var;
|
|
/* If .align at the start of a section, do nothing. Section alignment
|
|
* can ensure correct alignment. */
|
|
if (FRAG_AT_START_OF_SECTION (fragp)
|
|
&& 0 == ((1 << sec->alignment_power) % (nop_bytes + 4)))
|
|
fragp->fr_var = 0;
|
|
else
|
|
fragp->fr_var = nop_bytes;
|
|
return fragp->fr_var - old_var;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Expand far branches to multi-instruction sequences.
|
|
Branch instructions:
|
|
beq, bne, blt, bgt, bltz, bgtz, ble, bge, blez, bgez
|
|
bltu, bgtu, bleu, bgeu
|
|
beqz, bnez, bceqz, bcnez. */
|
|
|
|
static void
|
|
loongarch_convert_frag_branch (fragS *fragp)
|
|
{
|
|
bfd_byte *buf;
|
|
expressionS exp;
|
|
fixS *fixp;
|
|
insn_t insn;
|
|
|
|
buf = (bfd_byte *)fragp->fr_literal + fragp->fr_fix;
|
|
|
|
exp.X_op = O_symbol;
|
|
exp.X_add_symbol = fragp->fr_symbol;
|
|
exp.X_add_number = fragp->fr_offset;
|
|
|
|
gas_assert ((fragp->fr_subtype & 0xf) == fragp->fr_var);
|
|
|
|
/* blt $t0, $t1, .L1
|
|
nop
|
|
change to:
|
|
bge $t0, $t1, .L2
|
|
b .L1
|
|
.L2:
|
|
nop */
|
|
switch (fragp->fr_subtype)
|
|
{
|
|
case RELAX_BRANCH_26:
|
|
insn = bfd_getl32 (buf);
|
|
/* Invert the branch condition. */
|
|
if (LARCH_FLOAT_BRANCH == (insn & LARCH_BRANCH_OPCODE_MASK))
|
|
insn ^= LARCH_FLOAT_BRANCH_INVERT_BIT;
|
|
else
|
|
insn ^= LARCH_BRANCH_INVERT_BIT;
|
|
insn |= ENCODE_BRANCH16_IMM (8); /* Set target to PC + 8. */
|
|
bfd_putl32 (insn, buf);
|
|
buf += 4;
|
|
|
|
/* Add the B instruction and jump to the original target. */
|
|
bfd_putl32 (LARCH_B, buf);
|
|
fixp = fix_new_exp (fragp, buf - (bfd_byte *)fragp->fr_literal,
|
|
4, &exp, false, BFD_RELOC_LARCH_B26);
|
|
buf += 4;
|
|
break;
|
|
case RELAX_BRANCH_21:
|
|
fixp = fix_new_exp (fragp, buf - (bfd_byte *)fragp->fr_literal,
|
|
4, &exp, false, BFD_RELOC_LARCH_B21);
|
|
buf += 4;
|
|
break;
|
|
case RELAX_BRANCH_16:
|
|
fixp = fix_new_exp (fragp, buf - (bfd_byte *)fragp->fr_literal,
|
|
4, &exp, false, BFD_RELOC_LARCH_B16);
|
|
buf += 4;
|
|
break;
|
|
|
|
default:
|
|
abort();
|
|
}
|
|
|
|
fixp->fx_file = fragp->fr_file;
|
|
fixp->fx_line = fragp->fr_line;
|
|
|
|
gas_assert (buf == (bfd_byte *)fragp->fr_literal
|
|
+ fragp->fr_fix + fragp->fr_var);
|
|
|
|
fragp->fr_fix += fragp->fr_var;
|
|
}
|
|
|
|
/* Relax .align frag. */
|
|
|
|
static void
|
|
loongarch_convert_frag_align (fragS *fragp, asection *sec)
|
|
{
|
|
bfd_byte *buf = (bfd_byte *)fragp->fr_literal + fragp->fr_fix;
|
|
|
|
offsetT nop_bytes;
|
|
if (NULL == fragp->fr_symbol)
|
|
nop_bytes = fragp->fr_offset;
|
|
else
|
|
nop_bytes = ALIGN_MAX_NOP_BYTES (fragp->fr_offset);
|
|
|
|
/* Normally, nop_bytes should be >= 4. */
|
|
gas_assert (nop_bytes > 0);
|
|
|
|
if (!(FRAG_AT_START_OF_SECTION (fragp)
|
|
&& 0 == ((1 << sec->alignment_power) % (nop_bytes + 4))))
|
|
{
|
|
expressionS exp;
|
|
exp.X_op = O_symbol;
|
|
exp.X_add_symbol = fragp->fr_symbol;
|
|
exp.X_add_number = fragp->fr_offset;
|
|
|
|
fixS *fixp = fix_new_exp (fragp, buf - (bfd_byte *)fragp->fr_literal,
|
|
nop_bytes, &exp, false, BFD_RELOC_LARCH_ALIGN);
|
|
fixp->fx_file = fragp->fr_file;
|
|
fixp->fx_line = fragp->fr_line;
|
|
|
|
buf += nop_bytes;
|
|
}
|
|
|
|
gas_assert (buf == (bfd_byte *)fragp->fr_literal
|
|
+ fragp->fr_fix + fragp->fr_var);
|
|
|
|
fragp->fr_fix += fragp->fr_var;
|
|
}
|
|
|
|
/* Relax a machine dependent frag. */
|
|
|
|
void
|
|
md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED, segT asec, fragS *fragp)
|
|
{
|
|
gas_assert (RELAX_BRANCH (fragp->fr_subtype)
|
|
|| rs_align_code == fragp->fr_subtype);
|
|
if (RELAX_BRANCH (fragp->fr_subtype))
|
|
loongarch_convert_frag_branch (fragp);
|
|
else if (rs_align_code == fragp->fr_subtype)
|
|
loongarch_convert_frag_align (fragp, asec);
|
|
}
|