aboutsummaryrefslogtreecommitdiff
path: root/gas
diff options
context:
space:
mode:
authorClaudiu Zissulescu <claziss@synopsys.com>2016-02-10 12:09:01 +0000
committerNick Clifton <nickc@redhat.com>2016-02-10 12:09:01 +0000
commit4670103e86f59a80259fd593a6949d693382e536 (patch)
treebc0ae65cc94a89c6fa81629a350abf6c9b243123 /gas
parent83da6e748c8f105f07e17f53aa6b99ed7867ff5f (diff)
downloadgdb-4670103e86f59a80259fd593a6949d693382e536.zip
gdb-4670103e86f59a80259fd593a6949d693382e536.tar.gz
gdb-4670103e86f59a80259fd593a6949d693382e536.tar.bz2
Add support for ARC instruction relaxation in the assembler.
gas/ 2016-01-26 Claudiu Zissulescu <claziss@synopsys.com> Janek van Oirschot <jvanoirs@synopsys.com> * config/tc-arc.h (TC_FRAG_TYPE, TC_PCREL_ADJUST, MAX_INSN_ARGS) (MAX_INSN_FLGS, MAX_FLAG_NAME_LENGHT, TC_GENERIC_RELAX_TABLE): Define. (arc_flags, arc_relax_type): New structure. * config/tc-arc.c (FRAG_MAX_GROWTH, RELAX_TABLE_ENTRY) (RELAX_TABLE_ENTRY_MAX): New define. (relaxation_state, md_relax_table, arc_relaxable_insns) (arc_num_relaxable_ins): New variable. (rlx_operand_type, arc_rlx_types): New enums. (arc_relaxable_ins): New structure. (OPTION_RELAX): New option. (arc_insn): New relax member. (arc_flags): Remove. (relax_insn_p): New function. (apply_fixups): Likewise. (relaxable_operand): Likewise. (may_relax_expr): Likewise. (relaxable_flag): Likewise. (arc_pcrel_adjust): Likewise. (md_estimate_size_before_relax): Implement. (md_convert_frag): Likewise. (md_parse_option): Handle new mrelax option. (md_show_usage): Likewise. (assemble_insn): Set relax member. (emit_insn0): New function. (emit_insn1): Likewise. (emit_insn): Handle relaxation case. * NEWS: Mention the new relaxation option. * doc/c-arc.texi (ARC Options): Document new mrelax option. gas/testsuite 2016-01-26 Claudiu Zissulescu <claziss@synopsys.com> * gas/arc/relax-avoid1.d: New file. * gas/arc/relax-avoid1.s: Likewise. * gas/arc/relax-avoid2.d: Likewise. * gas/arc/relax-avoid2.s: Likewise. * gas/arc/relax-avoid3.d: Likewise. * gas/arc/relax-avoid3.s: Likewise. * gas/arc/relax-b.d: Likewise. * gas/arc/relax-b.s: Likewise. include/opcode/ 2016-01-26 Claudiu Zissulescu <claziss@synopsys.com> Janek van Oirschot <jvanoirs@synopsys.com> * arc.h (arc_opcode arc_relax_opcodes, arc_num_relax_opcodes): Declare. opcodes/ 2016-01-26 Claudiu Zissulescu <claziss@synopsys.com> Janek van Oirschot <jvanoirs@synopsys.com> * arc-opc.c (arc_relax_opcodes, arc_num_relax_opcodes): New variable.
Diffstat (limited to 'gas')
-rw-r--r--gas/ChangeLog42
-rw-r--r--gas/NEWS2
-rw-r--r--gas/config/tc-arc.c2381
-rw-r--r--gas/config/tc-arc.h56
-rw-r--r--gas/doc/as.texinfo1
-rw-r--r--gas/doc/c-arc.texi6
-rw-r--r--gas/testsuite/gas/arc/relax-avoid1.d13
-rw-r--r--gas/testsuite/gas/arc/relax-avoid1.s11
-rw-r--r--gas/testsuite/gas/arc/relax-avoid2.d14
-rw-r--r--gas/testsuite/gas/arc/relax-avoid2.s4
-rw-r--r--gas/testsuite/gas/arc/relax-avoid3.d14
-rw-r--r--gas/testsuite/gas/arc/relax-avoid3.s5
-rw-r--r--gas/testsuite/gas/arc/relax-b.d19
-rw-r--r--gas/testsuite/gas/arc/relax-b.s11
14 files changed, 1622 insertions, 957 deletions
diff --git a/gas/ChangeLog b/gas/ChangeLog
index 34c301d..1d6a178 100644
--- a/gas/ChangeLog
+++ b/gas/ChangeLog
@@ -1,3 +1,45 @@
+2016-02-10 Claudiu Zissulescu <claziss@synopsys.com>
+ Janek van Oirschot <jvanoirs@synopsys.com>
+
+ * config/tc-arc.h (TC_FRAG_TYPE, TC_PCREL_ADJUST, MAX_INSN_ARGS)
+ (MAX_INSN_FLGS, MAX_FLAG_NAME_LENGHT, TC_GENERIC_RELAX_TABLE):
+ Define.
+ (arc_flags, arc_relax_type): New structure.
+ * config/tc-arc.c (FRAG_MAX_GROWTH, RELAX_TABLE_ENTRY)
+ (RELAX_TABLE_ENTRY_MAX): New define.
+ (relaxation_state, md_relax_table, arc_relaxable_insns)
+ (arc_num_relaxable_ins): New variable.
+ (rlx_operand_type, arc_rlx_types): New enums.
+ (arc_relaxable_ins): New structure.
+ (OPTION_RELAX): New option.
+ (arc_insn): New relax member.
+ (arc_flags): Remove.
+ (relax_insn_p): New function.
+ (apply_fixups): Likewise.
+ (relaxable_operand): Likewise.
+ (may_relax_expr): Likewise.
+ (relaxable_flag): Likewise.
+ (arc_pcrel_adjust): Likewise.
+ (md_estimate_size_before_relax): Implement.
+ (md_convert_frag): Likewise.
+ (md_parse_option): Handle new mrelax option.
+ (md_show_usage): Likewise.
+ (assemble_insn): Set relax member.
+ (emit_insn0): New function.
+ (emit_insn1): Likewise.
+ (emit_insn): Handle relaxation case.
+ * NEWS: Mention the new relaxation option.
+ * doc/c-arc.texi (ARC Options): Document new mrelax option.
+ * doc/as.texinfo (Target ARC Options): Likewise.
+ * testsuite/gas/arc/relax-avoid1.d: New file.
+ * testsuite/gas/arc/relax-avoid1.s: Likewise.
+ * testsuite/gas/arc/relax-avoid2.d: Likewise.
+ * testsuite/gas/arc/relax-avoid2.s: Likewise.
+ * testsuite/gas/arc/relax-avoid3.d: Likewise.
+ * testsuite/gas/arc/relax-avoid3.s: Likewise.
+ * testsuite/gas/arc/relax-b.d: Likewise.
+ * testsuite/gas/arc/relax-b.s: Likewise.
+
2016-02-08 Nick Clifton <nickc@redhat.com>
* config/tc-ia64.c (dot_prologue): Fix formatting.
diff --git a/gas/NEWS b/gas/NEWS
index 7175ef0..87bdbd9 100644
--- a/gas/NEWS
+++ b/gas/NEWS
@@ -10,6 +10,8 @@
* New command line option -mfence-as-lock-add=yes for x86 target to encode
lfence, mfence and sfence as "lock addl $0x0, (%[re]sp)".
+* Add assembly-time relaxation option for ARC cpus.
+
Changes in 2.26:
* Add a configure option --enable-compressed-debug-sections={all,gas} to
diff --git a/gas/config/tc-arc.c b/gas/config/tc-arc.c
index 20a48f7..f2d3a0e 100644
--- a/gas/config/tc-arc.c
+++ b/gas/config/tc-arc.c
@@ -31,9 +31,9 @@
/* Defines section. */
-#define MAX_FLAG_NAME_LENGHT 3
#define MAX_INSN_FIXUPS 2
#define MAX_CONSTR_STR 20
+#define FRAG_MAX_GROWTH 8
#ifdef DEBUG
# define pr_debug(fmt, args...) fprintf (stderr, fmt, ##args)
@@ -43,12 +43,51 @@
#define MAJOR_OPCODE(x) (((x) & 0xF8000000) >> 27)
#define SUB_OPCODE(x) (((x) & 0x003F0000) >> 16)
-#define LP_INSN(x) ((MAJOR_OPCODE (x) == 0x4) && \
+#define LP_INSN(x) ((MAJOR_OPCODE (x) == 0x4) && \
(SUB_OPCODE (x) == 0x28))
/* Equal to MAX_PRECISION in atof-ieee.c. */
#define MAX_LITTLENUMS 6
+/* Enum used to enumerate the relaxable ins operands. */
+enum rlx_operand_type
+{
+ EMPTY = 0,
+ REGISTER,
+ REGISTER_S, /* Register for short instruction(s). */
+ REGISTER_NO_GP, /* Is a register but not gp register specifically. */
+ REGISTER_DUP, /* Duplication of previous operand of type register. */
+ IMMEDIATE,
+ BRACKET
+};
+
+enum arc_rlx_types
+{
+ ARC_RLX_NONE = 0,
+ ARC_RLX_BL_S,
+ ARC_RLX_BL,
+ ARC_RLX_B_S,
+ ARC_RLX_B,
+ ARC_RLX_ADD_U3,
+ ARC_RLX_ADD_U6,
+ ARC_RLX_ADD_LIMM,
+ ARC_RLX_LD_U7,
+ ARC_RLX_LD_S9,
+ ARC_RLX_LD_LIMM,
+ ARC_RLX_MOV_U8,
+ ARC_RLX_MOV_S12,
+ ARC_RLX_MOV_LIMM,
+ ARC_RLX_SUB_U3,
+ ARC_RLX_SUB_U6,
+ ARC_RLX_SUB_LIMM,
+ ARC_RLX_MPY_U6,
+ ARC_RLX_MPY_LIMM,
+ ARC_RLX_MOV_RU6,
+ ARC_RLX_MOV_RLIMM,
+ ARC_RLX_ADD_RRU6,
+ ARC_RLX_ADD_RRLIMM,
+};
+
/* Macros section. */
#define regno(x) ((x) & 0x3F)
@@ -83,13 +122,17 @@ extern int target_big_endian;
const char *arc_target_format = DEFAULT_TARGET_FORMAT;
static int byte_order = DEFAULT_BYTE_ORDER;
+/* By default relaxation is disabled. */
+static int relaxation_state = 0;
+
extern int arc_get_mach (char *);
-/* Forward declaration. */
+/* Forward declarations. */
static void arc_lcomm (int);
static void arc_option (int);
static void arc_extra_reloc (int);
+
const pseudo_typeS md_pseudo_table[] =
{
/* Make sure that .word is 32 bits. */
@@ -121,6 +164,7 @@ enum options
OPTION_MCPU,
OPTION_CD,
+ OPTION_RELAX,
/* The following options are deprecated and provided here only for
compatibility reasons. */
@@ -162,6 +206,7 @@ struct option md_longopts[] =
{ "mEM", no_argument, NULL, OPTION_ARCEM },
{ "mHS", no_argument, NULL, OPTION_ARCHS },
{ "mcode-density", no_argument, NULL, OPTION_CD },
+ { "mrelax", no_argument, NULL, OPTION_RELAX },
/* The following options are deprecated and provided here only for
compatibility reasons. */
@@ -242,6 +287,8 @@ struct arc_insn
short. */
bfd_boolean has_limm; /* Boolean value: TRUE if limm field is
valid. */
+ bfd_boolean relax; /* Boolean value: TRUE if needs
+ relaxation. */
};
/* Structure to hold any last two instructions. */
@@ -257,6 +304,11 @@ static struct arc_last_insn
bfd_boolean has_delay_slot;
} arc_last_insns[2];
+/* Forward declaration. */
+static void assemble_insn
+ (const struct arc_opcode *, const expressionS *, int,
+ const struct arc_flags *, int, struct arc_insn *);
+
/* The cpu for which we are generating code. */
static unsigned arc_target = ARC_OPCODE_BASE;
static const char *arc_target_name = "<all>";
@@ -298,15 +350,6 @@ static const struct cpu_type
{ 0, 0, 0, 0, 0 }
};
-struct arc_flags
-{
- /* Name of the parsed flag. */
- char name[MAX_FLAG_NAME_LENGHT+1];
-
- /* The code of the parsed flag. Valid when is not zero. */
- unsigned char code;
-};
-
/* Used by the arc_reloc_op table. Order is important. */
#define O_gotoff O_md1 /* @gotoff relocation. */
#define O_gotpc O_md2 /* @gotpc relocation. */
@@ -371,6 +414,131 @@ static const struct arc_reloc_op_tag
static const int arc_num_reloc_op
= sizeof (arc_reloc_op) / sizeof (*arc_reloc_op);
+/* Structure for relaxable instruction that have to be swapped with a
+ smaller alternative instruction. */
+struct arc_relaxable_ins
+{
+ /* Mnemonic that should be checked. */
+ const char *mnemonic_r;
+
+ /* Operands that should be checked.
+ Indexes of operands from operand array. */
+ enum rlx_operand_type operands[6];
+
+ /* Flags that should be checked. */
+ unsigned flag_classes[5];
+
+ /* Mnemonic (smaller) alternative to be used later for relaxation. */
+ const char *mnemonic_alt;
+
+ /* Index of operand that generic relaxation has to check. */
+ unsigned opcheckidx;
+
+ /* Base subtype index used. */
+ enum arc_rlx_types subtype;
+};
+
+#define RELAX_TABLE_ENTRY(BITS, ISSIGNED, SIZE, NEXT) \
+ { (ISSIGNED) ? ((1 << ((BITS) - 1)) - 1) : ((1 << (BITS)) - 1), \
+ (ISSIGNED) ? -(1 << ((BITS) - 1)) : 0, \
+ (SIZE), \
+ (NEXT) } \
+
+#define RELAX_TABLE_ENTRY_MAX(ISSIGNED, SIZE, NEXT) \
+ { (ISSIGNED) ? 0x7FFFFFFF : 0xFFFFFFFF, \
+ (ISSIGNED) ? -(0x7FFFFFFF) : 0, \
+ (SIZE), \
+ (NEXT) } \
+
+
+/* ARC relaxation table. */
+const relax_typeS md_relax_table[] =
+{
+ /* Fake entry. */
+ {0, 0, 0, 0},
+
+ /* BL_S s13 ->
+ BL s25. */
+ RELAX_TABLE_ENTRY(13, 1, 2, ARC_RLX_BL),
+ RELAX_TABLE_ENTRY(25, 1, 4, ARC_RLX_NONE),
+
+ /* B_S s10 ->
+ B s25. */
+ RELAX_TABLE_ENTRY(10, 1, 2, ARC_RLX_B),
+ RELAX_TABLE_ENTRY(25, 1, 4, ARC_RLX_NONE),
+
+ /* ADD_S c,b, u3 ->
+ ADD<.f> a,b,u6 ->
+ ADD<.f> a,b,limm. */
+ RELAX_TABLE_ENTRY(3, 0, 2, ARC_RLX_ADD_U6),
+ RELAX_TABLE_ENTRY(6, 0, 4, ARC_RLX_ADD_LIMM),
+ RELAX_TABLE_ENTRY_MAX(0, 8, ARC_RLX_NONE),
+
+ /* LD_S a, [b, u7] ->
+ LD<zz><.x><.aa><.di> a, [b, s9] ->
+ LD<zz><.x><.aa><.di> a, [b, limm] */
+ RELAX_TABLE_ENTRY(7, 0, 2, ARC_RLX_LD_S9),
+ RELAX_TABLE_ENTRY(9, 1, 4, ARC_RLX_LD_LIMM),
+ RELAX_TABLE_ENTRY_MAX(1, 8, ARC_RLX_NONE),
+
+ /* MOV_S b, u8 ->
+ MOV<.f> b, s12 ->
+ MOV<.f> b, limm. */
+ RELAX_TABLE_ENTRY(8, 0, 2, ARC_RLX_MOV_S12),
+ RELAX_TABLE_ENTRY(8, 0, 4, ARC_RLX_MOV_LIMM),
+ RELAX_TABLE_ENTRY_MAX(0, 8, ARC_RLX_NONE),
+
+ /* SUB_S c, b, u3 ->
+ SUB<.f> a, b, u6 ->
+ SUB<.f> a, b, limm. */
+ RELAX_TABLE_ENTRY(3, 0, 2, ARC_RLX_SUB_U6),
+ RELAX_TABLE_ENTRY(6, 0, 4, ARC_RLX_SUB_LIMM),
+ RELAX_TABLE_ENTRY_MAX(0, 8, ARC_RLX_NONE),
+
+ /* MPY<.f> a, b, u6 ->
+ MPY<.f> a, b, limm. */
+ RELAX_TABLE_ENTRY(6, 0, 4, ARC_RLX_MPY_LIMM),
+ RELAX_TABLE_ENTRY_MAX(0, 8, ARC_RLX_NONE),
+
+ /* MOV<.f><.cc> b, u6 ->
+ MOV<.f><.cc> b, limm. */
+ RELAX_TABLE_ENTRY(6, 0, 4, ARC_RLX_MOV_RLIMM),
+ RELAX_TABLE_ENTRY_MAX(0, 8, ARC_RLX_NONE),
+
+ /* ADD<.f><.cc> b, b, u6 ->
+ ADD<.f><.cc> b, b, limm. */
+ RELAX_TABLE_ENTRY(6, 0, 4, ARC_RLX_ADD_RRLIMM),
+ RELAX_TABLE_ENTRY_MAX(0, 8, ARC_RLX_NONE),
+};
+
+/* Order of this table's entries matters! */
+const struct arc_relaxable_ins arc_relaxable_insns[] =
+{
+ { "bl", { IMMEDIATE }, { 0 }, "bl_s", 0, ARC_RLX_BL_S },
+ { "b", { IMMEDIATE }, { 0 }, "b_s", 0, ARC_RLX_B_S },
+ { "add", { REGISTER, REGISTER_DUP, IMMEDIATE }, { 5, 1, 0 }, "add",
+ 2, ARC_RLX_ADD_RRU6},
+ { "add", { REGISTER_S, REGISTER_S, IMMEDIATE }, { 0 }, "add_s", 2,
+ ARC_RLX_ADD_U3 },
+ { "add", { REGISTER, REGISTER, IMMEDIATE }, { 5, 0 }, "add", 2,
+ ARC_RLX_ADD_U6 },
+ { "ld", { REGISTER_S, BRACKET, REGISTER_S, IMMEDIATE, BRACKET },
+ { 0 }, "ld_s", 3, ARC_RLX_LD_U7 },
+ { "ld", { REGISTER, BRACKET, REGISTER_NO_GP, IMMEDIATE, BRACKET },
+ { 11, 4, 14, 17, 0 }, "ld", 3, ARC_RLX_LD_S9 },
+ { "mov", { REGISTER_S, IMMEDIATE }, { 0 }, "mov_s", 1, ARC_RLX_MOV_U8 },
+ { "mov", { REGISTER, IMMEDIATE }, { 5, 0 }, "mov", 1, ARC_RLX_MOV_S12 },
+ { "mov", { REGISTER, IMMEDIATE }, { 5, 1, 0 },"mov", 1, ARC_RLX_MOV_RU6 },
+ { "sub", { REGISTER_S, REGISTER_S, IMMEDIATE }, { 0 }, "sub_s", 2,
+ ARC_RLX_SUB_U3 },
+ { "sub", { REGISTER, REGISTER, IMMEDIATE }, { 5, 0 }, "sub", 2,
+ ARC_RLX_SUB_U6 },
+ { "mpy", { REGISTER, REGISTER, IMMEDIATE }, { 5, 0 }, "mpy", 2,
+ ARC_RLX_MPY_U6 },
+};
+
+const unsigned arc_num_relaxable_ins = ARRAY_SIZE (arc_relaxable_insns);
+
/* Flags to set in the elf header. */
static flagword arc_eflag = 0x00;
@@ -380,33 +548,6 @@ symbolS * GOT_symbol = 0;
/* Set to TRUE when we assemble instructions. */
static bfd_boolean assembling_insn = FALSE;
-/* Functions declaration. */
-
-static void assemble_tokens (const char *, expressionS *, int,
- struct arc_flags *, int);
-static const struct arc_opcode *find_opcode_match (const struct arc_opcode *,
- expressionS *, int *,
- struct arc_flags *,
- int, int *);
-static void assemble_insn (const struct arc_opcode *, const expressionS *,
- int, const struct arc_flags *, int,
- struct arc_insn *);
-static void emit_insn (struct arc_insn *);
-static unsigned insert_operand (unsigned, const struct arc_operand *,
- offsetT, char *, unsigned);
-static const struct arc_opcode *find_special_case_flag (const char *,
- int *,
- struct arc_flags *);
-static const struct arc_opcode *find_special_case (const char *,
- int *,
- struct arc_flags *,
- expressionS *, int *);
-static const struct arc_opcode *find_special_case_pseudo (const char *,
- int *,
- expressionS *,
- int *,
- struct arc_flags *);
-
/* Functions implementation. */
/* Like md_number_to_chars but used for limms. The 4-byte limm value,
@@ -916,6 +1057,926 @@ tokenize_flags (const char *str,
return -1;
}
+/* Apply the fixups in order. */
+
+static void
+apply_fixups (struct arc_insn *insn, fragS *fragP, int fix)
+{
+ int i;
+
+ for (i = 0; i < insn->nfixups; i++)
+ {
+ struct arc_fixup *fixup = &insn->fixups[i];
+ int size, pcrel, offset = 0;
+
+ /* FIXME! the reloc size is wrong in the BFD file.
+ When it is fixed please delete me. */
+ size = (insn->short_insn && !fixup->islong) ? 2 : 4;
+
+ if (fixup->islong)
+ offset = (insn->short_insn) ? 2 : 4;
+
+ /* Some fixups are only used internally, thus no howto. */
+ if ((int) fixup->reloc == 0)
+ as_fatal (_("Unhandled reloc type"));
+
+ if ((int) fixup->reloc < 0)
+ {
+ /* FIXME! the reloc size is wrong in the BFD file.
+ When it is fixed please enable me.
+ size = (insn->short_insn && !fixup->islong) ? 2 : 4; */
+ pcrel = fixup->pcrel;
+ }
+ else
+ {
+ reloc_howto_type *reloc_howto =
+ bfd_reloc_type_lookup (stdoutput,
+ (bfd_reloc_code_real_type) fixup->reloc);
+ gas_assert (reloc_howto);
+
+ /* FIXME! the reloc size is wrong in the BFD file.
+ When it is fixed please enable me.
+ size = bfd_get_reloc_size (reloc_howto); */
+ pcrel = reloc_howto->pc_relative;
+ }
+
+ pr_debug ("%s:%d: apply_fixups: new %s fixup (PCrel:%s) of size %d @ \
+offset %d + %d\n",
+ fragP->fr_file, fragP->fr_line,
+ (fixup->reloc < 0) ? "Internal" :
+ bfd_get_reloc_code_name (fixup->reloc),
+ pcrel ? "Y" : "N",
+ size, fix, offset);
+ fix_new_exp (fragP, fix + offset,
+ size, &fixup->exp, pcrel, fixup->reloc);
+
+ /* Check for ZOLs, and update symbol info if any. */
+ if (LP_INSN (insn->insn))
+ {
+ gas_assert (fixup->exp.X_add_symbol);
+ ARC_SET_FLAG (fixup->exp.X_add_symbol, ARC_FLAG_ZOL);
+ }
+ }
+}
+
+/* Actually output an instruction with its fixup. */
+
+static void
+emit_insn0 (struct arc_insn *insn, char *where, bfd_boolean relax)
+{
+ char *f = where;
+
+ pr_debug ("Emit insn : 0x%x\n", insn->insn);
+ pr_debug ("\tShort : 0x%d\n", insn->short_insn);
+ pr_debug ("\tLong imm: 0x%lx\n", insn->limm);
+
+ /* Write out the instruction. */
+ if (insn->short_insn)
+ {
+ if (insn->has_limm)
+ {
+ if (!relax)
+ f = frag_more (6);
+ md_number_to_chars (f, insn->insn, 2);
+ md_number_to_chars_midend (f + 2, insn->limm, 4);
+ dwarf2_emit_insn (6);
+ }
+ else
+ {
+ if (!relax)
+ f = frag_more (2);
+ md_number_to_chars (f, insn->insn, 2);
+ dwarf2_emit_insn (2);
+ }
+ }
+ else
+ {
+ if (insn->has_limm)
+ {
+ if (!relax)
+ f = frag_more (8);
+ md_number_to_chars_midend (f, insn->insn, 4);
+ md_number_to_chars_midend (f + 4, insn->limm, 4);
+ dwarf2_emit_insn (8);
+ }
+ else
+ {
+ if (!relax)
+ f = frag_more (4);
+ md_number_to_chars_midend (f, insn->insn, 4);
+ dwarf2_emit_insn (4);
+ }
+ }
+
+ if (!relax)
+ apply_fixups (insn, frag_now, (f - frag_now->fr_literal));
+}
+
+static void
+emit_insn1 (struct arc_insn *insn)
+{
+ /* How frag_var's args are currently configured:
+ - rs_machine_dependent, to dictate it's a relaxation frag.
+ - FRAG_MAX_GROWTH, maximum size of instruction
+ - 0, variable size that might grow...unused by generic relaxation.
+ - frag_now->fr_subtype, fr_subtype starting value, set previously.
+ - s, opand expression.
+ - 0, offset but it's unused.
+ - 0, opcode but it's unused. */
+ symbolS *s = make_expr_symbol (&insn->fixups[0].exp);
+ frag_now->tc_frag_data.pcrel = insn->fixups[0].pcrel;
+
+ if (frag_room () < FRAG_MAX_GROWTH)
+ {
+ /* Handle differently when frag literal memory is exhausted.
+ This is used because when there's not enough memory left in
+ the current frag, a new frag is created and the information
+ we put into frag_now->tc_frag_data is disregarded. */
+
+ struct arc_relax_type relax_info_copy;
+ relax_substateT subtype = frag_now->fr_subtype;
+
+ memcpy (&relax_info_copy, &frag_now->tc_frag_data,
+ sizeof (struct arc_relax_type));
+
+ frag_wane (frag_now);
+ frag_grow (FRAG_MAX_GROWTH);
+
+ memcpy (&frag_now->tc_frag_data, &relax_info_copy,
+ sizeof (struct arc_relax_type));
+
+ frag_var (rs_machine_dependent, FRAG_MAX_GROWTH, 0,
+ subtype, s, 0, 0);
+ }
+ else
+ frag_var (rs_machine_dependent, FRAG_MAX_GROWTH, 0,
+ frag_now->fr_subtype, s, 0, 0);
+}
+
+static void
+emit_insn (struct arc_insn *insn)
+{
+ if (insn->relax)
+ emit_insn1 (insn);
+ else
+ emit_insn0 (insn, NULL, FALSE);
+}
+
+/* Check whether a symbol involves a register. */
+
+static bfd_boolean
+contains_register (symbolS *sym)
+{
+ if (sym)
+ {
+ expressionS *ex = symbol_get_value_expression (sym);
+
+ return ((O_register == ex->X_op)
+ && !contains_register (ex->X_add_symbol)
+ && !contains_register (ex->X_op_symbol));
+ }
+
+ return FALSE;
+}
+
+/* Returns the register number within a symbol. */
+
+static int
+get_register (symbolS *sym)
+{
+ if (!contains_register (sym))
+ return -1;
+
+ expressionS *ex = symbol_get_value_expression (sym);
+ return regno (ex->X_add_number);
+}
+
+/* Return true if a RELOC is generic. A generic reloc is PC-rel of a
+ simple ME relocation (e.g. RELOC_ARC_32_ME, BFD_RELOC_ARC_PC32. */
+
+static bfd_boolean
+generic_reloc_p (extended_bfd_reloc_code_real_type reloc)
+{
+ if (!reloc)
+ return FALSE;
+
+ switch (reloc)
+ {
+ case BFD_RELOC_ARC_SDA_LDST:
+ case BFD_RELOC_ARC_SDA_LDST1:
+ case BFD_RELOC_ARC_SDA_LDST2:
+ case BFD_RELOC_ARC_SDA16_LD:
+ case BFD_RELOC_ARC_SDA16_LD1:
+ case BFD_RELOC_ARC_SDA16_LD2:
+ case BFD_RELOC_ARC_SDA16_ST2:
+ case BFD_RELOC_ARC_SDA32_ME:
+ return FALSE;
+ default:
+ return TRUE;
+ }
+}
+
+/* Allocates a tok entry. */
+
+static int
+allocate_tok (expressionS *tok, int ntok, int cidx)
+{
+ if (ntok > MAX_INSN_ARGS - 2)
+ return 0; /* No space left. */
+
+ if (cidx > ntok)
+ return 0; /* Incorect args. */
+
+ memcpy (&tok[ntok+1], &tok[ntok], sizeof (*tok));
+
+ if (cidx == ntok)
+ return 1; /* Success. */
+ return allocate_tok (tok, ntok - 1, cidx);
+}
+
+/* Search forward through all variants of an opcode looking for a
+ syntax match. */
+
+static const struct arc_opcode *
+find_opcode_match (const struct arc_opcode *first_opcode,
+ expressionS *tok,
+ int *pntok,
+ struct arc_flags *first_pflag,
+ int nflgs,
+ int *pcpumatch)
+{
+ const struct arc_opcode *opcode = first_opcode;
+ int ntok = *pntok;
+ int got_cpu_match = 0;
+ expressionS bktok[MAX_INSN_ARGS];
+ int bkntok;
+ expressionS emptyE;
+
+ memset (&emptyE, 0, sizeof (emptyE));
+ memcpy (bktok, tok, MAX_INSN_ARGS * sizeof (*tok));
+ bkntok = ntok;
+
+ do
+ {
+ const unsigned char *opidx;
+ const unsigned char *flgidx;
+ int tokidx = 0;
+ const expressionS *t = &emptyE;
+
+ pr_debug ("%s:%d: find_opcode_match: trying opcode 0x%08X ",
+ frag_now->fr_file, frag_now->fr_line, opcode->opcode);
+
+ /* Don't match opcodes that don't exist on this
+ architecture. */
+ if (!(opcode->cpu & arc_target))
+ goto match_failed;
+
+ if (is_code_density_p (opcode) && !(arc_features & ARC_CD))
+ goto match_failed;
+
+ got_cpu_match = 1;
+ pr_debug ("cpu ");
+
+ /* Check the operands. */
+ for (opidx = opcode->operands; *opidx; ++opidx)
+ {
+ const struct arc_operand *operand = &arc_operands[*opidx];
+
+ /* Only take input from real operands. */
+ if ((operand->flags & ARC_OPERAND_FAKE)
+ && !(operand->flags & ARC_OPERAND_BRAKET))
+ continue;
+
+ /* When we expect input, make sure we have it. */
+ if (tokidx >= ntok)
+ goto match_failed;
+
+ /* Match operand type with expression type. */
+ switch (operand->flags & ARC_OPERAND_TYPECHECK_MASK)
+ {
+ case ARC_OPERAND_IR:
+ /* Check to be a register. */
+ if ((tok[tokidx].X_op != O_register
+ || !is_ir_num (tok[tokidx].X_add_number))
+ && !(operand->flags & ARC_OPERAND_IGNORE))
+ goto match_failed;
+
+ /* If expect duplicate, make sure it is duplicate. */
+ if (operand->flags & ARC_OPERAND_DUPLICATE)
+ {
+ /* Check for duplicate. */
+ if (t->X_op != O_register
+ || !is_ir_num (t->X_add_number)
+ || (regno (t->X_add_number) !=
+ regno (tok[tokidx].X_add_number)))
+ goto match_failed;
+ }
+
+ /* Special handling? */
+ if (operand->insert)
+ {
+ const char *errmsg = NULL;
+ (*operand->insert)(0,
+ regno (tok[tokidx].X_add_number),
+ &errmsg);
+ if (errmsg)
+ {
+ if (operand->flags & ARC_OPERAND_IGNORE)
+ {
+ /* Missing argument, create one. */
+ if (!allocate_tok (tok, ntok - 1, tokidx))
+ goto match_failed;
+
+ tok[tokidx].X_op = O_absent;
+ ++ntok;
+ }
+ else
+ goto match_failed;
+ }
+ }
+
+ t = &tok[tokidx];
+ break;
+
+ case ARC_OPERAND_BRAKET:
+ /* Check if bracket is also in opcode table as
+ operand. */
+ if (tok[tokidx].X_op != O_bracket)
+ goto match_failed;
+ break;
+
+ case ARC_OPERAND_LIMM:
+ case ARC_OPERAND_SIGNED:
+ case ARC_OPERAND_UNSIGNED:
+ switch (tok[tokidx].X_op)
+ {
+ case O_illegal:
+ case O_absent:
+ case O_register:
+ goto match_failed;
+
+ case O_bracket:
+ /* Got an (too) early bracket, check if it is an
+ ignored operand. N.B. This procedure works only
+ when bracket is the last operand! */
+ if (!(operand->flags & ARC_OPERAND_IGNORE))
+ goto match_failed;
+ /* Insert the missing operand. */
+ if (!allocate_tok (tok, ntok - 1, tokidx))
+ goto match_failed;
+
+ tok[tokidx].X_op = O_absent;
+ ++ntok;
+ break;
+
+ case O_constant:
+ /* Check the range. */
+ if (operand->bits != 32
+ && !(operand->flags & ARC_OPERAND_NCHK))
+ {
+ offsetT min, max, val;
+ val = tok[tokidx].X_add_number;
+
+ if (operand->flags & ARC_OPERAND_SIGNED)
+ {
+ max = (1 << (operand->bits - 1)) - 1;
+ min = -(1 << (operand->bits - 1));
+ }
+ else
+ {
+ max = (1 << operand->bits) - 1;
+ min = 0;
+ }
+
+ if (val < min || val > max)
+ goto match_failed;
+
+ /* Check alignmets. */
+ if ((operand->flags & ARC_OPERAND_ALIGNED32)
+ && (val & 0x03))
+ goto match_failed;
+
+ if ((operand->flags & ARC_OPERAND_ALIGNED16)
+ && (val & 0x01))
+ goto match_failed;
+ }
+ else if (operand->flags & ARC_OPERAND_NCHK)
+ {
+ if (operand->insert)
+ {
+ const char *errmsg = NULL;
+ (*operand->insert)(0,
+ tok[tokidx].X_add_number,
+ &errmsg);
+ if (errmsg)
+ goto match_failed;
+ }
+ else
+ goto match_failed;
+ }
+ break;
+
+ case O_subtract:
+ /* Check if it is register range. */
+ if ((tok[tokidx].X_add_number == 0)
+ && contains_register (tok[tokidx].X_add_symbol)
+ && contains_register (tok[tokidx].X_op_symbol))
+ {
+ int regs;
+
+ regs = get_register (tok[tokidx].X_add_symbol);
+ regs <<= 16;
+ regs |= get_register (tok[tokidx].X_op_symbol);
+ if (operand->insert)
+ {
+ const char *errmsg = NULL;
+ (*operand->insert)(0,
+ regs,
+ &errmsg);
+ if (errmsg)
+ goto match_failed;
+ }
+ else
+ goto match_failed;
+ break;
+ }
+ default:
+ if (operand->default_reloc == 0)
+ goto match_failed; /* The operand needs relocation. */
+
+ /* Relocs requiring long immediate. FIXME! make it
+ generic and move it to a function. */
+ switch (tok[tokidx].X_md)
+ {
+ case O_gotoff:
+ case O_gotpc:
+ case O_pcl:
+ case O_tpoff:
+ case O_dtpoff:
+ case O_tlsgd:
+ case O_tlsie:
+ if (!(operand->flags & ARC_OPERAND_LIMM))
+ goto match_failed;
+ case O_absent:
+ if (!generic_reloc_p (operand->default_reloc))
+ goto match_failed;
+ default:
+ break;
+ }
+ break;
+ }
+ /* If expect duplicate, make sure it is duplicate. */
+ if (operand->flags & ARC_OPERAND_DUPLICATE)
+ {
+ if (t->X_op == O_illegal
+ || t->X_op == O_absent
+ || t->X_op == O_register
+ || (t->X_add_number != tok[tokidx].X_add_number))
+ goto match_failed;
+ }
+ t = &tok[tokidx];
+ break;
+
+ default:
+ /* Everything else should have been fake. */
+ abort ();
+ }
+
+ ++tokidx;
+ }
+ pr_debug ("opr ");
+
+ /* Check the flags. Iterate over the valid flag classes. */
+ int lnflg = nflgs;
+
+ for (flgidx = opcode->flags; *flgidx && lnflg; ++flgidx)
+ {
+ /* Get a valid flag class. */
+ const struct arc_flag_class *cl_flags = &arc_flag_classes[*flgidx];
+ const unsigned *flgopridx;
+
+ for (flgopridx = cl_flags->flags; *flgopridx; ++flgopridx)
+ {
+ const struct arc_flag_operand *flg_operand;
+ struct arc_flags *pflag = first_pflag;
+ int i;
+
+ flg_operand = &arc_flag_operands[*flgopridx];
+ for (i = 0; i < nflgs; i++, pflag++)
+ {
+ /* Match against the parsed flags. */
+ if (!strcmp (flg_operand->name, pflag->name))
+ {
+ /*TODO: Check if it is duplicated. */
+ pflag->code = *flgopridx;
+ lnflg--;
+ break; /* goto next flag class and parsed flag. */
+ }
+ }
+ }
+ }
+ /* Did I check all the parsed flags? */
+ if (lnflg)
+ goto match_failed;
+
+ pr_debug ("flg");
+ /* Possible match -- did we use all of our input? */
+ if (tokidx == ntok)
+ {
+ *pntok = ntok;
+ pr_debug ("\n");
+ return opcode;
+ }
+
+ match_failed:;
+ pr_debug ("\n");
+ /* Restore the original parameters. */
+ memcpy (tok, bktok, MAX_INSN_ARGS * sizeof (*tok));
+ ntok = bkntok;
+ }
+ while (++opcode - arc_opcodes < (int) arc_num_opcodes
+ && !strcmp (opcode->name, first_opcode->name));
+
+ if (*pcpumatch)
+ *pcpumatch = got_cpu_match;
+
+ return NULL;
+}
+
+/* Swap operand tokens. */
+
+static void
+swap_operand (expressionS *operand_array,
+ unsigned source,
+ unsigned destination)
+{
+ expressionS cpy_operand;
+ expressionS *src_operand;
+ expressionS *dst_operand;
+ size_t size;
+
+ if (source == destination)
+ return;
+
+ src_operand = &operand_array[source];
+ dst_operand = &operand_array[destination];
+ size = sizeof (expressionS);
+
+ /* Make copy of operand to swap with and swap. */
+ memcpy (&cpy_operand, dst_operand, size);
+ memcpy (dst_operand, src_operand, size);
+ memcpy (src_operand, &cpy_operand, size);
+}
+
+/* Check if *op matches *tok type.
+ Returns FALSE if they don't match, TRUE if they match. */
+
+static bfd_boolean
+pseudo_operand_match (const expressionS *tok,
+ const struct arc_operand_operation *op)
+{
+ offsetT min, max, val;
+ bfd_boolean ret;
+ const struct arc_operand *operand_real = &arc_operands[op->operand_idx];
+
+ ret = FALSE;
+ switch (tok->X_op)
+ {
+ case O_constant:
+ if (operand_real->bits == 32 && (operand_real->flags & ARC_OPERAND_LIMM))
+ ret = 1;
+ else if (!(operand_real->flags & ARC_OPERAND_IR))
+ {
+ val = tok->X_add_number + op->count;
+ if (operand_real->flags & ARC_OPERAND_SIGNED)
+ {
+ max = (1 << (operand_real->bits - 1)) - 1;
+ min = -(1 << (operand_real->bits - 1));
+ }
+ else
+ {
+ max = (1 << operand_real->bits) - 1;
+ min = 0;
+ }
+ if (min <= val && val <= max)
+ ret = TRUE;
+ }
+ break;
+
+ case O_symbol:
+ /* Handle all symbols as long immediates or signed 9. */
+ if (operand_real->flags & ARC_OPERAND_LIMM ||
+ ((operand_real->flags & ARC_OPERAND_SIGNED) && operand_real->bits == 9))
+ ret = TRUE;
+ break;
+
+ case O_register:
+ if (operand_real->flags & ARC_OPERAND_IR)
+ ret = TRUE;
+ break;
+
+ case O_bracket:
+ if (operand_real->flags & ARC_OPERAND_BRAKET)
+ ret = TRUE;
+ break;
+
+ default:
+ /* Unknown. */
+ break;
+ }
+ return ret;
+}
+
+/* Find pseudo instruction in array. */
+
+static const struct arc_pseudo_insn *
+find_pseudo_insn (const char *opname,
+ int ntok,
+ const expressionS *tok)
+{
+ const struct arc_pseudo_insn *pseudo_insn = NULL;
+ const struct arc_operand_operation *op;
+ unsigned int i;
+ int j;
+
+ for (i = 0; i < arc_num_pseudo_insn; ++i)
+ {
+ pseudo_insn = &arc_pseudo_insns[i];
+ if (strcmp (pseudo_insn->mnemonic_p, opname) == 0)
+ {
+ op = pseudo_insn->operand;
+ for (j = 0; j < ntok; ++j)
+ if (!pseudo_operand_match (&tok[j], &op[j]))
+ break;
+
+ /* Found the right instruction. */
+ if (j == ntok)
+ return pseudo_insn;
+ }
+ }
+ return NULL;
+}
+
+/* Assumes the expressionS *tok is of sufficient size. */
+
+static const struct arc_opcode *
+find_special_case_pseudo (const char *opname,
+ int *ntok,
+ expressionS *tok,
+ int *nflgs,
+ struct arc_flags *pflags)
+{
+ const struct arc_pseudo_insn *pseudo_insn = NULL;
+ const struct arc_operand_operation *operand_pseudo;
+ const struct arc_operand *operand_real;
+ unsigned i;
+ char construct_operand[MAX_CONSTR_STR];
+
+ /* Find whether opname is in pseudo instruction array. */
+ pseudo_insn = find_pseudo_insn (opname, *ntok, tok);
+
+ if (pseudo_insn == NULL)
+ return NULL;
+
+ /* Handle flag, Limited to one flag at the moment. */
+ if (pseudo_insn->flag_r != NULL)
+ *nflgs += tokenize_flags (pseudo_insn->flag_r, &pflags[*nflgs],
+ MAX_INSN_FLGS - *nflgs);
+
+ /* Handle operand operations. */
+ for (i = 0; i < pseudo_insn->operand_cnt; ++i)
+ {
+ operand_pseudo = &pseudo_insn->operand[i];
+ operand_real = &arc_operands[operand_pseudo->operand_idx];
+
+ if (operand_real->flags & ARC_OPERAND_BRAKET &&
+ !operand_pseudo->needs_insert)
+ continue;
+
+ /* Has to be inserted (i.e. this token does not exist yet). */
+ if (operand_pseudo->needs_insert)
+ {
+ if (operand_real->flags & ARC_OPERAND_BRAKET)
+ {
+ tok[i].X_op = O_bracket;
+ ++(*ntok);
+ continue;
+ }
+
+ /* Check if operand is a register or constant and handle it
+ by type. */
+ if (operand_real->flags & ARC_OPERAND_IR)
+ snprintf (construct_operand, MAX_CONSTR_STR, "r%d",
+ operand_pseudo->count);
+ else
+ snprintf (construct_operand, MAX_CONSTR_STR, "%d",
+ operand_pseudo->count);
+
+ tokenize_arguments (construct_operand, &tok[i], 1);
+ ++(*ntok);
+ }
+
+ else if (operand_pseudo->count)
+ {
+ /* Operand number has to be adjusted accordingly (by operand
+ type). */
+ switch (tok[i].X_op)
+ {
+ case O_constant:
+ tok[i].X_add_number += operand_pseudo->count;
+ break;
+
+ case O_symbol:
+ break;
+
+ default:
+ /* Ignored. */
+ break;
+ }
+ }
+ }
+
+ /* Swap operands if necessary. Only supports one swap at the
+ moment. */
+ for (i = 0; i < pseudo_insn->operand_cnt; ++i)
+ {
+ operand_pseudo = &pseudo_insn->operand[i];
+
+ if (operand_pseudo->swap_operand_idx == i)
+ continue;
+
+ swap_operand (tok, i, operand_pseudo->swap_operand_idx);
+
+ /* Prevent a swap back later by breaking out. */
+ break;
+ }
+
+ return (const struct arc_opcode *)
+ hash_find (arc_opcode_hash, pseudo_insn->mnemonic_r);
+}
+
+static const struct arc_opcode *
+find_special_case_flag (const char *opname,
+ int *nflgs,
+ struct arc_flags *pflags)
+{
+ unsigned int i;
+ const char *flagnm;
+ unsigned flag_idx, flag_arr_idx;
+ size_t flaglen, oplen;
+ const struct arc_flag_special *arc_flag_special_opcode;
+ const struct arc_opcode *opcode;
+
+ /* Search for special case instruction. */
+ for (i = 0; i < arc_num_flag_special; i++)
+ {
+ arc_flag_special_opcode = &arc_flag_special_cases[i];
+ oplen = strlen (arc_flag_special_opcode->name);
+
+ if (strncmp (opname, arc_flag_special_opcode->name, oplen) != 0)
+ continue;
+
+ /* Found a potential special case instruction, now test for
+ flags. */
+ for (flag_arr_idx = 0;; ++flag_arr_idx)
+ {
+ flag_idx = arc_flag_special_opcode->flags[flag_arr_idx];
+ if (flag_idx == 0)
+ break; /* End of array, nothing found. */
+
+ flagnm = arc_flag_operands[flag_idx].name;
+ flaglen = strlen (flagnm);
+ if (strcmp (opname + oplen, flagnm) == 0)
+ {
+ opcode = (const struct arc_opcode *)
+ hash_find (arc_opcode_hash,
+ arc_flag_special_opcode->name);
+
+ if (*nflgs + 1 > MAX_INSN_FLGS)
+ break;
+ memcpy (pflags[*nflgs].name, flagnm, flaglen);
+ pflags[*nflgs].name[flaglen] = '\0';
+ (*nflgs)++;
+ return opcode;
+ }
+ }
+ }
+ return NULL;
+}
+
+/* Used to find special case opcode. */
+
+static const struct arc_opcode *
+find_special_case (const char *opname,
+ int *nflgs,
+ struct arc_flags *pflags,
+ expressionS *tok,
+ int *ntok)
+{
+ const struct arc_opcode *opcode;
+
+ opcode = find_special_case_pseudo (opname, ntok, tok, nflgs, pflags);
+
+ if (opcode == NULL)
+ opcode = find_special_case_flag (opname, nflgs, pflags);
+
+ return opcode;
+}
+
+static void
+preprocess_operands (const struct arc_opcode *opcode,
+ expressionS *tok,
+ int ntok)
+{
+ int i;
+ size_t len;
+ const char *p;
+ unsigned j;
+ const struct arc_aux_reg *auxr;
+
+ for (i = 0; i < ntok; i++)
+ {
+ switch (tok[i].X_op)
+ {
+ case O_illegal:
+ case O_absent:
+ break; /* Throw and error. */
+
+ case O_symbol:
+ if (opcode->class != AUXREG)
+ break;
+ /* Convert the symbol to a constant if possible. */
+ p = S_GET_NAME (tok[i].X_add_symbol);
+ len = strlen (p);
+
+ auxr = &arc_aux_regs[0];
+ for (j = 0; j < arc_num_aux_regs; j++, auxr++)
+ if (len == auxr->length
+ && strcasecmp (auxr->name, p) == 0)
+ {
+ tok[i].X_op = O_constant;
+ tok[i].X_add_number = auxr->address;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+/* Given an opcode name, pre-tockenized set of argumenst and the
+ opcode flags, take it all the way through emission. */
+
+static void
+assemble_tokens (const char *opname,
+ expressionS *tok,
+ int ntok,
+ struct arc_flags *pflags,
+ int nflgs)
+{
+ bfd_boolean found_something = FALSE;
+ const struct arc_opcode *opcode;
+ int cpumatch = 1;
+
+ /* Search opcodes. */
+ opcode = (const struct arc_opcode *) hash_find (arc_opcode_hash, opname);
+
+ /* Couldn't find opcode conventional way, try special cases. */
+ if (!opcode)
+ opcode = find_special_case (opname, &nflgs, pflags, tok, &ntok);
+
+ if (opcode)
+ {
+ pr_debug ("%s:%d: assemble_tokens: %s trying opcode 0x%08X\n",
+ frag_now->fr_file, frag_now->fr_line, opcode->name,
+ opcode->opcode);
+
+ preprocess_operands (opcode, tok, ntok);
+
+ found_something = TRUE;
+ opcode = find_opcode_match (opcode, tok, &ntok, pflags, nflgs, &cpumatch);
+ if (opcode)
+ {
+ struct arc_insn insn;
+ assemble_insn (opcode, tok, ntok, pflags, nflgs, &insn);
+ emit_insn (&insn);
+ return;
+ }
+ }
+
+ if (found_something)
+ {
+ if (cpumatch)
+ as_bad (_("inappropriate arguments for opcode '%s'"), opname);
+ else
+ as_bad (_("opcode '%s' not supported for target %s"), opname,
+ arc_target_name);
+ }
+ else
+ as_bad (_("unknown opcode '%s'"), opname);
+}
+
/* The public interface to the instruction assembler. */
void
@@ -1171,6 +2232,72 @@ find_operand_for_reloc (extended_bfd_reloc_code_real_type reloc)
return NULL;
}
+/* Insert an operand value into an instruction. */
+
+static unsigned
+insert_operand (unsigned insn,
+ const struct arc_operand *operand,
+ offsetT val,
+ char *file,
+ unsigned line)
+{
+ offsetT min = 0, max = 0;
+
+ if (operand->bits != 32
+ && !(operand->flags & ARC_OPERAND_NCHK)
+ && !(operand->flags & ARC_OPERAND_FAKE))
+ {
+ if (operand->flags & ARC_OPERAND_SIGNED)
+ {
+ max = (1 << (operand->bits - 1)) - 1;
+ min = -(1 << (operand->bits - 1));
+ }
+ else
+ {
+ max = (1 << operand->bits) - 1;
+ min = 0;
+ }
+
+ if (val < min || val > max)
+ as_bad_value_out_of_range (_("operand"),
+ val, min, max, file, line);
+ }
+
+ pr_debug ("insert field: %ld <= %ld <= %ld in 0x%08x\n",
+ min, val, max, insn);
+
+ if ((operand->flags & ARC_OPERAND_ALIGNED32)
+ && (val & 0x03))
+ as_bad_where (file, line,
+ _("Unaligned operand. Needs to be 32bit aligned"));
+
+ if ((operand->flags & ARC_OPERAND_ALIGNED16)
+ && (val & 0x01))
+ as_bad_where (file, line,
+ _("Unaligned operand. Needs to be 16bit aligned"));
+
+ if (operand->insert)
+ {
+ const char *errmsg = NULL;
+
+ insn = (*operand->insert) (insn, val, &errmsg);
+ if (errmsg)
+ as_warn_where (file, line, "%s", errmsg);
+ }
+ else
+ {
+ if (operand->flags & ARC_OPERAND_TRUNCATE)
+ {
+ if (operand->flags & ARC_OPERAND_ALIGNED32)
+ val >>= 2;
+ if (operand->flags & ARC_OPERAND_ALIGNED16)
+ val >>= 1;
+ }
+ insn |= ((val & ((1 << operand->bits) - 1)) << operand->shift);
+ }
+ return insn;
+}
+
/* Apply a fixup to the object code. At this point all symbol values
should be fully resolved, and we attempt to completely resolve the
reloc. If we can not do that, we determine the correct reloc code
@@ -1477,16 +2604,33 @@ md_apply_fix (fixS *fixP,
fr_var starts with a value. */
int
-md_estimate_size_before_relax (fragS *fragP ATTRIBUTE_UNUSED,
- segT segment ATTRIBUTE_UNUSED)
+md_estimate_size_before_relax (fragS *fragP,
+ segT segment)
{
- int growth = 4;
+ int growth;
+
+ /* If the symbol is not located within the same section AND it's not
+ an absolute section, use the maximum. OR if the symbol is a
+ constant AND the insn is by nature not pc-rel, use the maximum.
+ OR if the symbol is being equated against another symbol, use the
+ maximum. OR if the symbol is weak use the maximum. */
+ if ((S_GET_SEGMENT (fragP->fr_symbol) != segment
+ && S_GET_SEGMENT (fragP->fr_symbol) != absolute_section)
+ || (symbol_constant_p (fragP->fr_symbol)
+ && !fragP->tc_frag_data.pcrel)
+ || symbol_equated_p (fragP->fr_symbol)
+ || S_IS_WEAK (fragP->fr_symbol))
+ {
+ while (md_relax_table[fragP->fr_subtype].rlx_more != ARC_RLX_NONE)
+ ++fragP->fr_subtype;
+ }
+
+ growth = md_relax_table[fragP->fr_subtype].rlx_length;
+ fragP->fr_var = growth;
- fragP->fr_var = 4;
pr_debug ("%s:%d: md_estimate_size_before_relax: %d\n",
fragP->fr_file, fragP->fr_line, growth);
- as_fatal (_("md_estimate_size_before_relax\n"));
return growth;
}
@@ -1570,12 +2714,40 @@ tc_gen_reloc (asection *section ATTRIBUTE_UNUSED,
void
md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED,
segT segment ATTRIBUTE_UNUSED,
- fragS *fragP ATTRIBUTE_UNUSED)
+ fragS *fragP)
{
+ const relax_typeS *table_entry;
+ char *dest;
+ const struct arc_opcode *opcode;
+ struct arc_insn insn;
+ int size, fix;
+ struct arc_relax_type *relax_arg = &fragP->tc_frag_data;
+
+ fix = (fragP->fr_fix < 0 ? 0 : fragP->fr_fix);
+ dest = fragP->fr_literal + fix;
+ table_entry = TC_GENERIC_RELAX_TABLE + fragP->fr_subtype;
+
pr_debug ("%s:%d: md_convert_frag, subtype: %d, fix: %d, var: %d\n",
fragP->fr_file, fragP->fr_line,
- fragP->fr_subtype, fragP->fr_fix, fragP->fr_var);
- abort ();
+ fragP->fr_subtype, fix, fragP->fr_var);
+
+ if (fragP->fr_subtype <= 0
+ && fragP->fr_subtype >= arc_num_relax_opcodes)
+ as_fatal (_("no relaxation found for this instruction."));
+
+ opcode = &arc_relax_opcodes[fragP->fr_subtype];
+
+ assemble_insn (opcode, relax_arg->tok, relax_arg->ntok, relax_arg->pflags,
+ relax_arg->nflg, &insn);
+
+ apply_fixups (&insn, fragP, fix);
+
+ size = insn.short_insn ? (insn.has_limm ? 6 : 2) : (insn.has_limm ? 8 : 4);
+ gas_assert (table_entry->rlx_length == size);
+ emit_insn0 (&insn, dest, TRUE);
+
+ fragP->fr_fix += table_entry->rlx_length;
+ fragP->fr_var = 0;
}
/* We have no need to default values of symbols. We could catch
@@ -1672,6 +2844,7 @@ arc_parse_name (const char *name,
-mcpu=<cpu name> Assemble for selected processor
-EB/-mbig-endian Big-endian
-EL/-mlittle-endian Little-endian
+ -mrelax Enable relaxation
The following CPU names are recognized:
arc700, av2em, av2hs. */
@@ -1748,6 +2921,10 @@ md_parse_option (int c, char *arg ATTRIBUTE_UNUSED)
arc_features |= ARC_CD;
break;
+ case OPTION_RELAX:
+ relaxation_state = 1;
+ break;
+
case OPTION_USER_MODE:
case OPTION_LD_EXT_MASK:
case OPTION_SWAP:
@@ -1797,829 +2974,263 @@ md_show_usage (FILE *stream)
-EB assemble code for a big-endian cpu\n"));
fprintf (stream, _("\
-EL assemble code for a little-endian cpu\n"));
-}
-
-static void
-preprocess_operands (const struct arc_opcode *opcode,
- expressionS *tok,
- int ntok)
-{
- int i;
- size_t len;
- const char *p;
- unsigned j;
- const struct arc_aux_reg *auxr;
-
- for (i = 0; i < ntok; i++)
- {
- switch (tok[i].X_op)
- {
- case O_illegal:
- case O_absent:
- break; /* Throw and error. */
-
- case O_symbol:
- if (opcode->class != AUXREG)
- break;
- /* Convert the symbol to a constant if possible. */
- p = S_GET_NAME (tok[i].X_add_symbol);
- len = strlen (p);
+ fprintf (stream, _("\
+ -mrelax Enable relaxation\n"));
- auxr = &arc_aux_regs[0];
- for (j = 0; j < arc_num_aux_regs; j++, auxr++)
- if (len == auxr->length
- && strcasecmp (auxr->name, p) == 0)
- {
- tok[i].X_op = O_constant;
- tok[i].X_add_number = auxr->address;
- break;
- }
- break;
- default:
- break;
- }
- }
}
-/* Given an opcode name, pre-tockenized set of argumenst and the
- opcode flags, take it all the way through emission. */
+/* Find the proper relocation for the given opcode. */
-static void
-assemble_tokens (const char *opname,
- expressionS *tok,
- int ntok,
- struct arc_flags *pflags,
- int nflgs)
+static extended_bfd_reloc_code_real_type
+find_reloc (const char *name,
+ const char *opcodename,
+ const struct arc_flags *pflags,
+ int nflg,
+ extended_bfd_reloc_code_real_type reloc)
{
- bfd_boolean found_something = FALSE;
- const struct arc_opcode *opcode;
- int cpumatch = 1;
-
- /* Search opcodes. */
- opcode = (const struct arc_opcode *) hash_find (arc_opcode_hash, opname);
-
- /* Couldn't find opcode conventional way, try special cases. */
- if (!opcode)
- opcode = find_special_case (opname, &nflgs, pflags, tok, &ntok);
+ unsigned int i;
+ int j;
+ bfd_boolean found_flag, tmp;
+ extended_bfd_reloc_code_real_type ret = BFD_RELOC_UNUSED;
- if (opcode)
+ for (i = 0; i < arc_num_equiv_tab; i++)
{
- pr_debug ("%s:%d: assemble_tokens: %s trying opcode 0x%08X\n",
- frag_now->fr_file, frag_now->fr_line, opcode->name,
- opcode->opcode);
-
- preprocess_operands (opcode, tok, ntok);
+ const struct arc_reloc_equiv_tab *r = &arc_reloc_equiv[i];
- found_something = TRUE;
- opcode = find_opcode_match (opcode, tok, &ntok, pflags, nflgs, &cpumatch);
- if (opcode)
+ /* Find the entry. */
+ if (strcmp (name, r->name))
+ continue;
+ if (r->mnemonic && (strcmp (r->mnemonic, opcodename)))
+ continue;
+ if (r->flags[0])
{
- struct arc_insn insn;
- assemble_insn (opcode, tok, ntok, pflags, nflgs, &insn);
- emit_insn (&insn);
- return;
+ if (!nflg)
+ continue;
+ found_flag = FALSE;
+ unsigned * psflg = (unsigned *)r->flags;
+ do
+ {
+ tmp = FALSE;
+ for (j = 0; j < nflg; j++)
+ if (!strcmp (pflags[j].name,
+ arc_flag_operands[*psflg].name))
+ {
+ tmp = TRUE;
+ break;
+ }
+ if (!tmp)
+ {
+ found_flag = FALSE;
+ break;
+ }
+ else
+ {
+ found_flag = TRUE;
+ }
+ ++ psflg;
+ } while (*psflg);
+
+ if (!found_flag)
+ continue;
}
- }
- if (found_something)
- {
- if (cpumatch)
- as_bad (_("inappropriate arguments for opcode '%s'"), opname);
- else
- as_bad (_("opcode '%s' not supported for target %s"), opname,
- arc_target_name);
+ if (reloc != r->oldreloc)
+ continue;
+ /* Found it. */
+ ret = r->newreloc;
+ break;
}
- else
- as_bad (_("unknown opcode '%s'"), opname);
-}
-/* Used to find special case opcode. */
-
-static const struct arc_opcode *
-find_special_case (const char *opname,
- int *nflgs,
- struct arc_flags *pflags,
- expressionS *tok,
- int *ntok)
-{
- const struct arc_opcode *opcode;
-
- opcode = find_special_case_pseudo (opname, ntok, tok, nflgs, pflags);
-
- if (opcode == NULL)
- opcode = find_special_case_flag (opname, nflgs, pflags);
-
- return opcode;
-}
-
-/* Swap operand tokens. */
-
-static void
-swap_operand (expressionS *operand_array,
- unsigned source,
- unsigned destination)
-{
- expressionS cpy_operand;
- expressionS *src_operand;
- expressionS *dst_operand;
- size_t size;
-
- if (source == destination)
- return;
-
- src_operand = &operand_array[source];
- dst_operand = &operand_array[destination];
- size = sizeof (expressionS);
-
- /* Make copy of operand to swap with and swap. */
- memcpy (&cpy_operand, dst_operand, size);
- memcpy (dst_operand, src_operand, size);
- memcpy (src_operand, &cpy_operand, size);
+ if (ret == BFD_RELOC_UNUSED)
+ as_bad (_("Unable to find %s relocation for instruction %s"),
+ name, opcodename);
+ return ret;
}
-/* Check if *op matches *tok type.
- Returns FALSE if they don't match, TRUE if they match. */
+/* All the symbol types that are allowed to be used for
+ relaxation. */
static bfd_boolean
-pseudo_operand_match (const expressionS *tok,
- const struct arc_operand_operation *op)
+may_relax_expr (expressionS tok)
{
- offsetT min, max, val;
- bfd_boolean ret;
- const struct arc_operand *operand_real = &arc_operands[op->operand_idx];
-
- ret = FALSE;
- switch (tok->X_op)
+ /* Check if we have unrelaxable relocs. */
+ switch (tok.X_md)
{
- case O_constant:
- if (operand_real->bits == 32 && (operand_real->flags & ARC_OPERAND_LIMM))
- ret = 1;
- else if (!(operand_real->flags & ARC_OPERAND_IR))
- {
- val = tok->X_add_number + op->count;
- if (operand_real->flags & ARC_OPERAND_SIGNED)
- {
- max = (1 << (operand_real->bits - 1)) - 1;
- min = -(1 << (operand_real->bits - 1));
- }
- else
- {
- max = (1 << operand_real->bits) - 1;
- min = 0;
- }
- if (min <= val && val <= max)
- ret = TRUE;
- }
- break;
-
- case O_symbol:
- /* Handle all symbols as long immediates or signed 9. */
- if (operand_real->flags & ARC_OPERAND_LIMM ||
- ((operand_real->flags & ARC_OPERAND_SIGNED) && operand_real->bits == 9))
- ret = TRUE;
- break;
-
- case O_register:
- if (operand_real->flags & ARC_OPERAND_IR)
- ret = TRUE;
- break;
-
- case O_bracket:
- if (operand_real->flags & ARC_OPERAND_BRAKET)
- ret = TRUE;
- break;
-
default:
- /* Unknown. */
break;
+ case O_plt:
+ return FALSE;
}
- return ret;
-}
-
-/* Find pseudo instruction in array. */
-
-static const struct arc_pseudo_insn *
-find_pseudo_insn (const char *opname,
- int ntok,
- const expressionS *tok)
-{
- const struct arc_pseudo_insn *pseudo_insn = NULL;
- const struct arc_operand_operation *op;
- unsigned int i;
- int j;
- for (i = 0; i < arc_num_pseudo_insn; ++i)
+ switch (tok.X_op)
{
- pseudo_insn = &arc_pseudo_insns[i];
- if (strcmp (pseudo_insn->mnemonic_p, opname) == 0)
- {
- op = pseudo_insn->operand;
- for (j = 0; j < ntok; ++j)
- if (!pseudo_operand_match (&tok[j], &op[j]))
- break;
+ case O_symbol:
+ case O_multiply:
+ case O_divide:
+ case O_modulus:
+ case O_add:
+ case O_subtract:
+ break;
- /* Found the right instruction. */
- if (j == ntok)
- return pseudo_insn;
- }
+ default:
+ return FALSE;
}
- return NULL;
+ return TRUE;
}
-/* Assumes the expressionS *tok is of sufficient size. */
+/* Checks if flags are in line with relaxable insn. */
-static const struct arc_opcode *
-find_special_case_pseudo (const char *opname,
- int *ntok,
- expressionS *tok,
- int *nflgs,
- struct arc_flags *pflags)
+static bfd_boolean
+relaxable_flag (const struct arc_relaxable_ins *ins,
+ const struct arc_flags *pflags,
+ int nflgs)
{
- const struct arc_pseudo_insn *pseudo_insn = NULL;
- const struct arc_operand_operation *operand_pseudo;
- const struct arc_operand *operand_real;
- unsigned i;
- char construct_operand[MAX_CONSTR_STR];
-
- /* Find whether opname is in pseudo instruction array. */
- pseudo_insn = find_pseudo_insn (opname, *ntok, tok);
+ unsigned flag_class,
+ flag,
+ flag_class_idx = 0,
+ flag_idx = 0;
- if (pseudo_insn == NULL)
- return NULL;
-
- /* Handle flag, Limited to one flag at the moment. */
- if (pseudo_insn->flag_r != NULL)
- *nflgs += tokenize_flags (pseudo_insn->flag_r, &pflags[*nflgs],
- MAX_INSN_FLGS - *nflgs);
+ const struct arc_flag_operand *flag_opand;
+ int i, counttrue = 0;
- /* Handle operand operations. */
- for (i = 0; i < pseudo_insn->operand_cnt; ++i)
+ /* Iterate through flags classes. */
+ while ((flag_class = ins->flag_classes[flag_class_idx]) != 0)
{
- operand_pseudo = &pseudo_insn->operand[i];
- operand_real = &arc_operands[operand_pseudo->operand_idx];
-
- if (operand_real->flags & ARC_OPERAND_BRAKET &&
- !operand_pseudo->needs_insert)
- continue;
-
- /* Has to be inserted (i.e. this token does not exist yet). */
- if (operand_pseudo->needs_insert)
+ /* Iterate through flags in flag class. */
+ while ((flag = arc_flag_classes[flag_class].flags[flag_idx])
+ != 0)
{
- if (operand_real->flags & ARC_OPERAND_BRAKET)
+ flag_opand = &arc_flag_operands[flag];
+ /* Iterate through flags in ins to compare. */
+ for (i = 0; i < nflgs; ++i)
{
- tok[i].X_op = O_bracket;
- ++(*ntok);
- continue;
+ if (strcmp (flag_opand->name, pflags[i].name) == 0)
+ ++counttrue;
}
- /* Check if operand is a register or constant and handle it
- by type. */
- if (operand_real->flags & ARC_OPERAND_IR)
- snprintf (construct_operand, MAX_CONSTR_STR, "r%d",
- operand_pseudo->count);
- else
- snprintf (construct_operand, MAX_CONSTR_STR, "%d",
- operand_pseudo->count);
-
- tokenize_arguments (construct_operand, &tok[i], 1);
- ++(*ntok);
- }
-
- else if (operand_pseudo->count)
- {
- /* Operand number has to be adjusted accordingly (by operand
- type). */
- switch (tok[i].X_op)
- {
- case O_constant:
- tok[i].X_add_number += operand_pseudo->count;
- break;
-
- case O_symbol:
- break;
-
- default:
- /* Ignored. */
- break;
- }
+ ++flag_idx;
}
- }
-
- /* Swap operands if necessary. Only supports one swap at the
- moment. */
- for (i = 0; i < pseudo_insn->operand_cnt; ++i)
- {
- operand_pseudo = &pseudo_insn->operand[i];
-
- if (operand_pseudo->swap_operand_idx == i)
- continue;
-
- swap_operand (tok, i, operand_pseudo->swap_operand_idx);
-
- /* Prevent a swap back later by breaking out. */
- break;
- }
-
- return (const struct arc_opcode *)
- hash_find (arc_opcode_hash, pseudo_insn->mnemonic_r);
-}
-
-static const struct arc_opcode *
-find_special_case_flag (const char *opname,
- int *nflgs,
- struct arc_flags *pflags)
-{
- unsigned int i;
- const char *flagnm;
- unsigned flag_idx, flag_arr_idx;
- size_t flaglen, oplen;
- const struct arc_flag_special *arc_flag_special_opcode;
- const struct arc_opcode *opcode;
-
- /* Search for special case instruction. */
- for (i = 0; i < arc_num_flag_special; i++)
- {
- arc_flag_special_opcode = &arc_flag_special_cases[i];
- oplen = strlen (arc_flag_special_opcode->name);
- if (strncmp (opname, arc_flag_special_opcode->name, oplen) != 0)
- continue;
-
- /* Found a potential special case instruction, now test for
- flags. */
- for (flag_arr_idx = 0;; ++flag_arr_idx)
- {
- flag_idx = arc_flag_special_opcode->flags[flag_arr_idx];
- if (flag_idx == 0)
- break; /* End of array, nothing found. */
-
- flagnm = arc_flag_operands[flag_idx].name;
- flaglen = strlen (flagnm);
- if (strcmp (opname + oplen, flagnm) == 0)
- {
- opcode = (const struct arc_opcode *)
- hash_find (arc_opcode_hash,
- arc_flag_special_opcode->name);
-
- if (*nflgs + 1 > MAX_INSN_FLGS)
- break;
- memcpy (pflags[*nflgs].name, flagnm, flaglen);
- pflags[*nflgs].name[flaglen] = '\0';
- (*nflgs)++;
- return opcode;
- }
- }
+ ++flag_class_idx;
+ flag_idx = 0;
}
- return NULL;
-}
-
-/* Check whether a symbol involves a register. */
-
-static int
-contains_register (symbolS *sym)
-{
- if (sym)
- {
- expressionS *ex = symbol_get_value_expression (sym);
- return ((O_register == ex->X_op)
- && !contains_register (ex->X_add_symbol)
- && !contains_register (ex->X_op_symbol));
- }
- else
- return 0;
-}
-
-/* Returns the register number within a symbol. */
-static int
-get_register (symbolS *sym)
-{
- if (!contains_register (sym))
- return -1;
-
- expressionS *ex = symbol_get_value_expression (sym);
- return regno (ex->X_add_number);
+ /* If counttrue == nflgs, then all flags have been found. */
+ return (counttrue == nflgs ? TRUE : FALSE);
}
-/* Allocates a tok entry. */
-
-static int
-allocate_tok (expressionS *tok, int ntok, int cidx)
-{
- if (ntok > MAX_INSN_ARGS - 2)
- return 0; /* No space left. */
-
- if (cidx > ntok)
- return 0; /* Incorect args. */
-
- memcpy (&tok[ntok+1], &tok[ntok], sizeof (*tok));
-
- if (cidx == ntok)
- return 1; /* Success. */
- return allocate_tok (tok, ntok - 1, cidx);
-}
-
-/* Return true if a RELOC is generic. A generic reloc is PC-rel of a
- simple ME relocation (e.g. RELOC_ARC_32_ME, BFD_RELOC_ARC_PC32. */
+/* Checks if operands are in line with relaxable insn. */
static bfd_boolean
-generic_reloc_p (extended_bfd_reloc_code_real_type reloc)
-{
- if (!reloc)
- return FALSE;
-
- switch (reloc)
- {
- case BFD_RELOC_ARC_SDA_LDST:
- case BFD_RELOC_ARC_SDA_LDST1:
- case BFD_RELOC_ARC_SDA_LDST2:
- case BFD_RELOC_ARC_SDA16_LD:
- case BFD_RELOC_ARC_SDA16_LD1:
- case BFD_RELOC_ARC_SDA16_LD2:
- case BFD_RELOC_ARC_SDA16_ST2:
- case BFD_RELOC_ARC_SDA32_ME:
- return FALSE;
- default:
- break;
- }
- return TRUE;
-}
-
-/* Search forward through all variants of an opcode looking for a
- syntax match. */
-
-static const struct arc_opcode *
-find_opcode_match (const struct arc_opcode *first_opcode,
- expressionS *tok,
- int *pntok,
- struct arc_flags *first_pflag,
- int nflgs,
- int *pcpumatch)
+relaxable_operand (const struct arc_relaxable_ins *ins,
+ const expressionS *tok,
+ int ntok)
{
- const struct arc_opcode *opcode = first_opcode;
- int ntok = *pntok;
- int got_cpu_match = 0;
- expressionS bktok[MAX_INSN_ARGS];
- int bkntok;
- expressionS emptyE;
+ const enum rlx_operand_type *operand = &ins->operands[0];
+ int i = 0;
- memset (&emptyE, 0, sizeof (emptyE));
- memcpy (bktok, tok, MAX_INSN_ARGS * sizeof (*tok));
- bkntok = ntok;
-
- do
+ while (*operand != EMPTY)
{
- const unsigned char *opidx;
- const unsigned char *flgidx;
- int tokidx = 0;
- const expressionS *t = &emptyE;
-
- pr_debug ("%s:%d: find_opcode_match: trying opcode 0x%08X ",
- frag_now->fr_file, frag_now->fr_line, opcode->opcode);
-
- /* Don't match opcodes that don't exist on this
- architecture. */
- if (!(opcode->cpu & arc_target))
- goto match_failed;
-
- if (is_code_density_p (opcode) && !(arc_features & ARC_CD))
- goto match_failed;
+ const expressionS *epr = &tok[i];
- got_cpu_match = 1;
- pr_debug ("cpu ");
+ if (i != 0 && i >= ntok)
+ return FALSE;
- /* Check the operands. */
- for (opidx = opcode->operands; *opidx; ++opidx)
+ switch (*operand)
{
- const struct arc_operand *operand = &arc_operands[*opidx];
+ case IMMEDIATE:
+ if (!(epr->X_op == O_multiply
+ || epr->X_op == O_divide
+ || epr->X_op == O_modulus
+ || epr->X_op == O_add
+ || epr->X_op == O_subtract
+ || epr->X_op == O_symbol))
+ return FALSE;
+ break;
- /* Only take input from real operands. */
- if ((operand->flags & ARC_OPERAND_FAKE)
- && !(operand->flags & ARC_OPERAND_BRAKET))
- continue;
+ case REGISTER_DUP:
+ if ((i <= 0)
+ || (epr->X_add_number != tok[i - 1].X_add_number))
+ return FALSE;
+ /* Fall through. */
+ case REGISTER:
+ if (epr->X_op != O_register)
+ return FALSE;
+ break;
- /* When we expect input, make sure we have it. */
- if (tokidx >= ntok)
- goto match_failed;
+ case REGISTER_S:
+ if (epr->X_op != O_register)
+ return FALSE;
- /* Match operand type with expression type. */
- switch (operand->flags & ARC_OPERAND_TYPECHECK_MASK)
+ switch (epr->X_add_number)
{
- case ARC_OPERAND_IR:
- /* Check to be a register. */
- if ((tok[tokidx].X_op != O_register
- || !is_ir_num (tok[tokidx].X_add_number))
- && !(operand->flags & ARC_OPERAND_IGNORE))
- goto match_failed;
-
- /* If expect duplicate, make sure it is duplicate. */
- if (operand->flags & ARC_OPERAND_DUPLICATE)
- {
- /* Check for duplicate. */
- if (t->X_op != O_register
- || !is_ir_num (t->X_add_number)
- || (regno (t->X_add_number) !=
- regno (tok[tokidx].X_add_number)))
- goto match_failed;
- }
-
- /* Special handling? */
- if (operand->insert)
- {
- const char *errmsg = NULL;
- (*operand->insert)(0,
- regno (tok[tokidx].X_add_number),
- &errmsg);
- if (errmsg)
- {
- if (operand->flags & ARC_OPERAND_IGNORE)
- {
- /* Missing argument, create one. */
- if (!allocate_tok (tok, ntok - 1, tokidx))
- goto match_failed;
-
- tok[tokidx].X_op = O_absent;
- ++ntok;
- }
- else
- goto match_failed;
- }
- }
-
- t = &tok[tokidx];
- break;
-
- case ARC_OPERAND_BRAKET:
- /* Check if bracket is also in opcode table as
- operand. */
- if (tok[tokidx].X_op != O_bracket)
- goto match_failed;
- break;
-
- case ARC_OPERAND_LIMM:
- case ARC_OPERAND_SIGNED:
- case ARC_OPERAND_UNSIGNED:
- switch (tok[tokidx].X_op)
- {
- case O_illegal:
- case O_absent:
- case O_register:
- goto match_failed;
-
- case O_bracket:
- /* Got an (too) early bracket, check if it is an
- ignored operand. N.B. This procedure works only
- when bracket is the last operand! */
- if (!(operand->flags & ARC_OPERAND_IGNORE))
- goto match_failed;
- /* Insert the missing operand. */
- if (!allocate_tok (tok, ntok - 1, tokidx))
- goto match_failed;
-
- tok[tokidx].X_op = O_absent;
- ++ntok;
- break;
-
- case O_constant:
- /* Check the range. */
- if (operand->bits != 32
- && !(operand->flags & ARC_OPERAND_NCHK))
- {
- offsetT min, max, val;
- val = tok[tokidx].X_add_number;
-
- if (operand->flags & ARC_OPERAND_SIGNED)
- {
- max = (1 << (operand->bits - 1)) - 1;
- min = -(1 << (operand->bits - 1));
- }
- else
- {
- max = (1 << operand->bits) - 1;
- min = 0;
- }
-
- if (val < min || val > max)
- goto match_failed;
-
- /* Check alignmets. */
- if ((operand->flags & ARC_OPERAND_ALIGNED32)
- && (val & 0x03))
- goto match_failed;
-
- if ((operand->flags & ARC_OPERAND_ALIGNED16)
- && (val & 0x01))
- goto match_failed;
- }
- else if (operand->flags & ARC_OPERAND_NCHK)
- {
- if (operand->insert)
- {
- const char *errmsg = NULL;
- (*operand->insert)(0,
- tok[tokidx].X_add_number,
- &errmsg);
- if (errmsg)
- goto match_failed;
- }
- else
- goto match_failed;
- }
- break;
-
- case O_subtract:
- /* Check if it is register range. */
- if ((tok[tokidx].X_add_number == 0)
- && contains_register (tok[tokidx].X_add_symbol)
- && contains_register (tok[tokidx].X_op_symbol))
- {
- int regs;
-
- regs = get_register (tok[tokidx].X_add_symbol);
- regs <<= 16;
- regs |= get_register (tok[tokidx].X_op_symbol);
- if (operand->insert)
- {
- const char *errmsg = NULL;
- (*operand->insert)(0,
- regs,
- &errmsg);
- if (errmsg)
- goto match_failed;
- }
- else
- goto match_failed;
- break;
- }
- default:
- if (operand->default_reloc == 0)
- goto match_failed; /* The operand needs relocation. */
-
- /* Relocs requiring long immediate. FIXME! make it
- generic and move it to a function. */
- switch (tok[tokidx].X_md)
- {
- case O_gotoff:
- case O_gotpc:
- case O_pcl:
- case O_tpoff:
- case O_dtpoff:
- case O_tlsgd:
- case O_tlsie:
- if (!(operand->flags & ARC_OPERAND_LIMM))
- goto match_failed;
- case O_absent:
- if (!generic_reloc_p (operand->default_reloc))
- goto match_failed;
- default:
- break;
- }
- break;
- }
- /* If expect duplicate, make sure it is duplicate. */
- if (operand->flags & ARC_OPERAND_DUPLICATE)
- {
- if (t->X_op == O_illegal
- || t->X_op == O_absent
- || t->X_op == O_register
- || (t->X_add_number != tok[tokidx].X_add_number))
- goto match_failed;
- }
- t = &tok[tokidx];
+ case 0: case 1: case 2: case 3:
+ case 12: case 13: case 14: case 15:
break;
-
default:
- /* Everything else should have been fake. */
- abort ();
+ return FALSE;
}
+ break;
- ++tokidx;
- }
- pr_debug ("opr ");
-
- /* Check the flags. Iterate over the valid flag classes. */
- int lnflg = nflgs;
-
- for (flgidx = opcode->flags; *flgidx && lnflg; ++flgidx)
- {
- /* Get a valid flag class. */
- const struct arc_flag_class *cl_flags = &arc_flag_classes[*flgidx];
- const unsigned *flgopridx;
-
- for (flgopridx = cl_flags->flags; *flgopridx; ++flgopridx)
- {
- const struct arc_flag_operand *flg_operand;
- struct arc_flags *pflag = first_pflag;
- int i;
+ case REGISTER_NO_GP:
+ if ((epr->X_op != O_register)
+ || (epr->X_add_number == 26)) /* 26 is the gp register. */
+ return FALSE;
+ break;
- flg_operand = &arc_flag_operands[*flgopridx];
- for (i = 0; i < nflgs; i++, pflag++)
- {
- /* Match against the parsed flags. */
- if (!strcmp (flg_operand->name, pflag->name))
- {
- /*TODO: Check if it is duplicated. */
- pflag->code = *flgopridx;
- lnflg--;
- break; /* goto next flag class and parsed flag. */
- }
- }
- }
- }
- /* Did I check all the parsed flags? */
- if (lnflg)
- goto match_failed;
+ case BRACKET:
+ if (epr->X_op != O_bracket)
+ return FALSE;
+ break;
- pr_debug ("flg");
- /* Possible match -- did we use all of our input? */
- if (tokidx == ntok)
- {
- *pntok = ntok;
- pr_debug ("\n");
- return opcode;
+ default:
+ /* Don't understand, bail out. */
+ return FALSE;
+ break;
}
- match_failed:;
- pr_debug ("\n");
- /* Restore the original parameters. */
- memcpy (tok, bktok, MAX_INSN_ARGS * sizeof (*tok));
- ntok = bkntok;
+ ++i;
+ operand = &ins->operands[i];
}
- while (++opcode - arc_opcodes < (int) arc_num_opcodes
- && !strcmp (opcode->name, first_opcode->name));
- if (*pcpumatch)
- *pcpumatch = got_cpu_match;
-
- return NULL;
+ return (i == ntok ? TRUE : FALSE);
}
-/* Find the proper relocation for the given opcode. */
+/* Return TRUE if this OPDCODE is a candidate for relaxation. */
-static extended_bfd_reloc_code_real_type
-find_reloc (const char *name,
- const char *opcodename,
- const struct arc_flags *pflags,
- int nflg,
- extended_bfd_reloc_code_real_type reloc)
+static bfd_boolean
+relax_insn_p (const struct arc_opcode *opcode,
+ const expressionS *tok,
+ int ntok,
+ const struct arc_flags *pflags,
+ int nflg)
{
- unsigned int i;
- int j;
- bfd_boolean found_flag, tmp;
- extended_bfd_reloc_code_real_type ret = BFD_RELOC_UNUSED;
+ unsigned i;
+ bfd_boolean rv = FALSE;
- for (i = 0; i < arc_num_equiv_tab; i++)
+ /* Check the relaxation table. */
+ for (i = 0; i < arc_num_relaxable_ins && relaxation_state; ++i)
{
- const struct arc_reloc_equiv_tab *r = &arc_reloc_equiv[i];
+ const struct arc_relaxable_ins *arc_rlx_ins = &arc_relaxable_insns[i];
- /* Find the entry. */
- if (strcmp (name, r->name))
- continue;
- if (r->mnemonic && (strcmp (r->mnemonic, opcodename)))
- continue;
- if (r->flags[0])
+ if ((strcmp (opcode->name, arc_rlx_ins->mnemonic_r) == 0)
+ && may_relax_expr (tok[arc_rlx_ins->opcheckidx])
+ && relaxable_operand (arc_rlx_ins, tok, ntok)
+ && relaxable_flag (arc_rlx_ins, pflags, nflg))
{
- if (!nflg)
- continue;
- found_flag = FALSE;
- unsigned * psflg = (unsigned *)r->flags;
- do
- {
- tmp = FALSE;
- for (j = 0; j < nflg; j++)
- if (!strcmp (pflags[j].name,
- arc_flag_operands[*psflg].name))
- {
- tmp = TRUE;
- break;
- }
- if (!tmp)
- {
- found_flag = FALSE;
- break;
- }
- else
- {
- found_flag = TRUE;
- }
- ++ psflg;
- } while (*psflg);
-
- if (!found_flag)
- continue;
+ rv = TRUE;
+ frag_now->fr_subtype = arc_relaxable_insns[i].subtype;
+ memcpy (&frag_now->tc_frag_data.tok, tok,
+ sizeof (expressionS) * ntok);
+ memcpy (&frag_now->tc_frag_data.pflags, pflags,
+ sizeof (struct arc_flags) * nflg);
+ frag_now->tc_frag_data.nflg = nflg;
+ frag_now->tc_frag_data.ntok = ntok;
+ break;
}
-
- if (reloc != r->oldreloc)
- continue;
- /* Found it. */
- ret = r->newreloc;
- break;
}
- if (ret == BFD_RELOC_UNUSED)
- as_bad (_("Unable to find %s relocation for instruction %s"),
- name, opcodename);
- return ret;
+ return rv;
}
/* Turn an opcode description and a set of arguments into
@@ -2866,6 +3477,8 @@ assemble_insn (const struct arc_opcode *opcode,
<< flg_operand->shift;
}
+ insn->relax = relax_insn_p (opcode, tok, ntok, pflags, nflg);
+
/* Short instruction? */
insn->short_insn = ARC_SHORT (opcode->mask) ? TRUE : FALSE;
@@ -2884,169 +3497,6 @@ assemble_insn (const struct arc_opcode *opcode,
_("A jump/branch instruction in delay slot."));
}
-/* Actually output an instruction with its fixup. */
-
-static void
-emit_insn (struct arc_insn *insn)
-{
- char *f;
- int i;
-
- pr_debug ("Emit insn : 0x%x\n", insn->insn);
- pr_debug ("\tShort : 0x%d\n", insn->short_insn);
- pr_debug ("\tLong imm: 0x%lx\n", insn->limm);
-
- /* Write out the instruction. */
- if (insn->short_insn)
- {
- if (insn->has_limm)
- {
- f = frag_more (6);
- md_number_to_chars (f, insn->insn, 2);
- md_number_to_chars_midend (f + 2, insn->limm, 4);
- dwarf2_emit_insn (6);
- }
- else
- {
- f = frag_more (2);
- md_number_to_chars (f, insn->insn, 2);
- dwarf2_emit_insn (2);
- }
- }
- else
- {
- if (insn->has_limm)
- {
- f = frag_more (8);
- md_number_to_chars_midend (f, insn->insn, 4);
- md_number_to_chars_midend (f + 4, insn->limm, 4);
- dwarf2_emit_insn (8);
- }
- else
- {
- f = frag_more (4);
- md_number_to_chars_midend (f, insn->insn, 4);
- dwarf2_emit_insn (4);
- }
- }
-
- /* Apply the fixups in order. */
- for (i = 0; i < insn->nfixups; i++)
- {
- struct arc_fixup *fixup = &insn->fixups[i];
- int size, pcrel, offset = 0;
-
- /*FIXME! the reloc size is wrong in the BFD file. When it will
- be fixed please delete me. */
- size = (insn->short_insn && !fixup->islong) ? 2 : 4;
-
- if (fixup->islong)
- offset = (insn->short_insn) ? 2 : 4;
-
- /* Some fixups are only used internally, thus no howto. */
- if ((int) fixup->reloc < 0)
- {
- /*FIXME! the reloc size is wrong in the BFD file. When it
- will be fixed please enable me.
- size = (insn->short_insn && !fixup->islong) ? 2 : 4; */
- pcrel = fixup->pcrel;
- }
- else
- {
- reloc_howto_type *reloc_howto =
- bfd_reloc_type_lookup (stdoutput,
- (bfd_reloc_code_real_type) fixup->reloc);
- gas_assert (reloc_howto);
- /*FIXME! the reloc size is wrong in the BFD file. When it
- will be fixed please enable me.
- size = bfd_get_reloc_size (reloc_howto); */
- pcrel = reloc_howto->pc_relative;
- }
-
- pr_debug ("%s:%d: emit_insn: new %s fixup (PCrel:%s) of size %d @ offset %d\n",
- frag_now->fr_file, frag_now->fr_line,
- (fixup->reloc < 0) ? "Internal" :
- bfd_get_reloc_code_name (fixup->reloc),
- pcrel ? "Y" : "N",
- size, offset);
- fix_new_exp (frag_now, f - frag_now->fr_literal + offset,
- size, &fixup->exp, pcrel, fixup->reloc);
-
- /* Check for ZOLs, and update symbol info if any. */
- if (LP_INSN (insn->insn))
- {
- gas_assert (fixup->exp.X_add_symbol);
- ARC_SET_FLAG (fixup->exp.X_add_symbol, ARC_FLAG_ZOL);
- }
- }
-}
-
-/* Insert an operand value into an instruction. */
-
-static unsigned
-insert_operand (unsigned insn,
- const struct arc_operand *operand,
- offsetT val,
- char *file,
- unsigned line)
-{
- offsetT min = 0, max = 0;
-
- if (operand->bits != 32
- && !(operand->flags & ARC_OPERAND_NCHK)
- && !(operand->flags & ARC_OPERAND_FAKE))
- {
- if (operand->flags & ARC_OPERAND_SIGNED)
- {
- max = (1 << (operand->bits - 1)) - 1;
- min = -(1 << (operand->bits - 1));
- }
- else
- {
- max = (1 << operand->bits) - 1;
- min = 0;
- }
-
- if (val < min || val > max)
- as_bad_value_out_of_range (_("operand"),
- val, min, max, file, line);
- }
-
- pr_debug ("insert field: %ld <= %ld <= %ld in 0x%08x\n",
- min, val, max, insn);
-
- if ((operand->flags & ARC_OPERAND_ALIGNED32)
- && (val & 0x03))
- as_bad_where (file, line,
- _("Unaligned operand. Needs to be 32bit aligned"));
-
- if ((operand->flags & ARC_OPERAND_ALIGNED16)
- && (val & 0x01))
- as_bad_where (file, line,
- _("Unaligned operand. Needs to be 16bit aligned"));
-
- if (operand->insert)
- {
- const char *errmsg = NULL;
-
- insn = (*operand->insert) (insn, val, &errmsg);
- if (errmsg)
- as_warn_where (file, line, "%s", errmsg);
- }
- else
- {
- if (operand->flags & ARC_OPERAND_TRUNCATE)
- {
- if (operand->flags & ARC_OPERAND_ALIGNED32)
- val >>= 2;
- if (operand->flags & ARC_OPERAND_ALIGNED16)
- val >>= 1;
- }
- insn |= ((val & ((1 << operand->bits) - 1)) << operand->shift);
- }
- return insn;
-}
-
void
arc_handle_align (fragS* fragP)
{
@@ -3208,3 +3658,20 @@ arc_frob_label (symbolS * sym)
dwarf2_emit_label (sym);
}
+
+/* Used because generic relaxation assumes a pc-rel value whilst we
+ also relax instructions that use an absolute value resolved out of
+ relative values (if that makes any sense). An example: 'add r1,
+ r2, @.L2 - .' The symbols . and @.L2 are relative to the section
+ but if they're in the same section we can subtract the section
+ offset relocation which ends up in a resolved value. So if @.L2 is
+ .text + 0x50 and . is .text + 0x10, we can say that .text + 0x50 -
+ .text + 0x40 = 0x10. */
+int
+arc_pcrel_adjust (fragS *fragP)
+{
+ if (!fragP->tc_frag_data.pcrel)
+ return fragP->fr_address + fragP->fr_fix;
+
+ return 0;
+}
diff --git a/gas/config/tc-arc.h b/gas/config/tc-arc.h
index ca5b152..acd007b 100644
--- a/gas/config/tc-arc.h
+++ b/gas/config/tc-arc.h
@@ -177,6 +177,14 @@ extern long md_pcrel_from_section (struct fix *, segT);
/* This hook is required to parse register names as operands. */
#define md_parse_name(name, exp, m, c) arc_parse_name (name, exp)
+/* Used within frags to pass some information to some relaxation
+ machine dependent values. */
+#define TC_FRAG_TYPE struct arc_relax_type
+
+/* Adjust non PC-rel values at relaxation time. */
+#define TC_PCREL_ADJUST(F) arc_pcrel_adjust (F)
+
+extern int arc_pcrel_adjust (fragS *);
extern bfd_boolean arc_parse_name (const char *, struct expressionS *);
extern int tc_arc_fix_adjustable (struct fix *);
extern void arc_handle_align (fragS *);
@@ -193,3 +201,51 @@ extern void arc_frob_label (symbolS *);
#define NOP_OPCODE_S 0x000078E0
#define NOP_OPCODE_L 0x264A7000 /* mov 0,0. */
+#define MAX_FLAG_NAME_LENGHT 3
+
+struct arc_flags
+{
+ /* Name of the parsed flag. */
+ char name[MAX_FLAG_NAME_LENGHT + 1];
+
+ /* The code of the parsed flag. Valid when is not zero. */
+ unsigned char code;
+};
+
+#ifndef MAX_INSN_ARGS
+#define MAX_INSN_ARGS 6
+#endif
+
+#ifndef MAX_INSN_FLGS
+#define MAX_INSN_FLGS 3
+#endif
+
+extern const relax_typeS md_relax_table[];
+#define TC_GENERIC_RELAX_TABLE md_relax_table
+
+/* Used to construct instructions at md_convert_frag stage of
+ relaxation. */
+struct arc_relax_type
+{
+ /* Dictates whether the pc-relativity should be kept in mind when
+ relax_frag is called or whether the pc-relativity should be
+ solved outside of relaxation. For clarification: BL(_S) and
+ B(_S) use pcrel == 1 and ADD with a solvable expression as 3rd
+ operand use pcrel == 0. */
+ unsigned char pcrel;
+
+ /* Expressions that dictate the operands. Used for re-assembling in
+ md_convert_frag. */
+ expressionS tok[MAX_INSN_ARGS];
+
+ /* Number of tok (i.e. number of operands). Used for re-assembling
+ in md_convert_frag. */
+ int ntok;
+
+ /* Flags of instruction. Used for re-assembling in
+ md_convert_frag. */
+ struct arc_flags pflags[MAX_INSN_FLGS];
+
+ /* Number of flags. Used for re-assembling in md_convert_frag. */
+ int nflg;
+};
diff --git a/gas/doc/as.texinfo b/gas/doc/as.texinfo
index aede5d3..90611a3 100644
--- a/gas/doc/as.texinfo
+++ b/gas/doc/as.texinfo
@@ -268,6 +268,7 @@ gcc(1), ld(1), and the Info entries for @file{binutils} and @file{ld}.
[@b{-mcpu=@var{cpu}}]
[@b{-mA6}|@b{-mARC600}|@b{-mARC601}|@b{-mA7}|@b{-mARC700}|@b{-mEM}|@b{-mHS}]
[@b{-mcode-density}]
+ [@b{-mrelax}]
[@b{-EB}|@b{-EL}]
@end ifset
@ifset ARM
diff --git a/gas/doc/c-arc.texi b/gas/doc/c-arc.texi
index 96abe83..4824027 100644
--- a/gas/doc/c-arc.texi
+++ b/gas/doc/c-arc.texi
@@ -85,6 +85,12 @@ default.
This option turns on Code Density instructions. Only valid for ARC EM
processors.
+@cindex @code{-mrelax} command line option, ARC
+@item -mrelax
+Enable support for assembly-time relaxation. The assembler will
+replace a longer version of an instruction with a shorter one,
+whenever it is possible.
+
@end table
@node ARC Syntax
diff --git a/gas/testsuite/gas/arc/relax-avoid1.d b/gas/testsuite/gas/arc/relax-avoid1.d
new file mode 100644
index 0000000..3d6d74e
--- /dev/null
+++ b/gas/testsuite/gas/arc/relax-avoid1.d
@@ -0,0 +1,13 @@
+#as: -mcpu=archs -mrelax
+#objdump: -dr
+
+.*: +file format .*arc.*
+
+
+Disassembly of section .text:
+
+00000000 <.text>:
+ 0: 78e0 nop_s
+ 2: 240a 0f80 0000 0000 mov r4,0
+ 6: R_ARC_32_ME .LC2
+ a: 78e0 nop_s
diff --git a/gas/testsuite/gas/arc/relax-avoid1.s b/gas/testsuite/gas/arc/relax-avoid1.s
new file mode 100644
index 0000000..82fbe63
--- /dev/null
+++ b/gas/testsuite/gas/arc/relax-avoid1.s
@@ -0,0 +1,11 @@
+ .section .rodata
+ .align 4
+.LC2:
+ .word 0x01
+ .word 0x02
+ .word 0x03
+
+ .section .text
+ .align 4
+ nop_s
+ mov r4,@.LC2
diff --git a/gas/testsuite/gas/arc/relax-avoid2.d b/gas/testsuite/gas/arc/relax-avoid2.d
new file mode 100644
index 0000000..fd602b4
--- /dev/null
+++ b/gas/testsuite/gas/arc/relax-avoid2.d
@@ -0,0 +1,14 @@
+#as: -mcpu=archs -mrelax
+#objdump: -dr
+
+.*: +file format .*arc.*
+
+
+Disassembly of section .text:
+
+00000000 <test>:
+ 0: 2000 0000 add r0,r0,r0
+
+00000004 <main>:
+ 4: 0802 0000 bl 0 <test>
+ 4: R_ARC_S25W_PCREL_PLT test
diff --git a/gas/testsuite/gas/arc/relax-avoid2.s b/gas/testsuite/gas/arc/relax-avoid2.s
new file mode 100644
index 0000000..703064d
--- /dev/null
+++ b/gas/testsuite/gas/arc/relax-avoid2.s
@@ -0,0 +1,4 @@
+test:
+ add r0,r0,r0
+main:
+ bl @test@plt
diff --git a/gas/testsuite/gas/arc/relax-avoid3.d b/gas/testsuite/gas/arc/relax-avoid3.d
new file mode 100644
index 0000000..7b177fb
--- /dev/null
+++ b/gas/testsuite/gas/arc/relax-avoid3.d
@@ -0,0 +1,14 @@
+#as: -mcpu=archs -mrelax
+#objdump: -dr
+
+.*: +file format .*arc.*
+
+
+Disassembly of section .text:
+
+00000000 <test>:
+ 0: 2000 0000 add r0,r0,r0
+
+00000004 <main>:
+ 4: 0001 0000 b 0 <test>
+ 4: R_ARC_S25H_PCREL test
diff --git a/gas/testsuite/gas/arc/relax-avoid3.s b/gas/testsuite/gas/arc/relax-avoid3.s
new file mode 100644
index 0000000..dc913a4
--- /dev/null
+++ b/gas/testsuite/gas/arc/relax-avoid3.s
@@ -0,0 +1,5 @@
+test:
+ add r0,r0,r0
+ .weak test
+main:
+ b test
diff --git a/gas/testsuite/gas/arc/relax-b.d b/gas/testsuite/gas/arc/relax-b.d
new file mode 100644
index 0000000..fd8dc47
--- /dev/null
+++ b/gas/testsuite/gas/arc/relax-b.d
@@ -0,0 +1,19 @@
+#as: -mcpu=archs -mrelax
+#objdump: -dr
+
+.*: +file format .*arc.*
+
+
+Disassembly of section .text:
+
+00000000 <foo-0x4>:
+ 0: 78e0 nop_s
+ 2: 78e0 nop_s
+
+00000004 <foo>:
+ 4: 2000 0000 add r0,r0,r0
+
+00000008 <bar>:
+ 8: ffff bl_s 4 <foo>
+ a: 2100 0041 add r1,r1,r1
+ e: f1fc b_s 4 <foo>
diff --git a/gas/testsuite/gas/arc/relax-b.s b/gas/testsuite/gas/arc/relax-b.s
new file mode 100644
index 0000000..3698b14
--- /dev/null
+++ b/gas/testsuite/gas/arc/relax-b.s
@@ -0,0 +1,11 @@
+ .text
+ nop_s
+ .align 4
+foo:
+ add r0,r0,r0
+
+ .align 4
+bar:
+ bl @foo
+ add r1,r1,r1
+ b @foo