mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2025-01-06 12:09:26 +08:00
20eee7540b
Ignore .align at the start of a section may result in misalignment when partial linking. Manually add -mignore-start-align option without partial linking. Gcc -falign-functions add .align 5 to the start of a section, it causes some error message mismatch. Set these testcases to xfail on LoongArch target.
2175 lines
61 KiB
C
2175 lines
61 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";
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/* FIXME: expressionS use 'offsetT' as constant,
|
|
* we want this is 64-bit type. */
|
|
assert (8 <= sizeof (offsetT));
|
|
}
|
|
|
|
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 ();
|
|
}
|
|
|
|
static const pseudo_typeS loongarch_pseudo_table[] =
|
|
{
|
|
{ "dword", cons, 8 },
|
|
{ "word", cons, 4 },
|
|
{ "half", cons, 2 },
|
|
{ "dtprelword", s_dtprel, 4 },
|
|
{ "dtpreldword", s_dtprel, 8 },
|
|
{ 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 = symbol_find (now_seg->name);
|
|
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);
|
|
}
|