LoongArch/GAS: Add support for branch relaxation

For the instructions of R_LARCH_B16/B21, if the immediate overflow,
add a B instruction and R_LARCH_B26 relocation.

For example:

.L1
  ...
  blt $t0, $t1, .L1
    R_LARCH_B16

change to:

.L1
  ...
  bge $t0, $t1, .L2
  b .L1
    R_LARCH_B26
.L2
This commit is contained in:
mengqinggang
2023-09-24 14:53:28 +08:00
committed by liuzhensong
parent 9847ba8f08
commit 1fb3cdd87e
6 changed files with 366 additions and 40 deletions

View File

@@ -106,6 +106,16 @@ const char *md_shortopts = "O::g::G:";
static const char default_arch[] = DEFAULT_ARCH;
/* 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)
enum options
{
OPTION_IGNORE = OPTION_MD_BASE,
@@ -953,11 +963,22 @@ append_fixed_insn (struct loongarch_cl_insn *insn)
move_insn (insn, frag_now, f - frag_now->fr_literal);
}
/* 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 reloc_type;
bfd_reloc_code_real_type r_type;
struct reloc_info *reloc_info = ip->reloc_info;
size_t i;
@@ -965,14 +986,40 @@ append_fixp_and_insn (struct loongarch_cl_insn *ip)
for (i = 0; i < ip->reloc_num; i++)
{
reloc_type = reloc_info[i].type;
howto = bfd_reloc_type_lookup (stdoutput, reloc_type);
if (howto == NULL)
as_fatal (_("no HOWTO loong relocation number %d"), reloc_type);
r_type = reloc_info[i].type;
ip->fixp[i] =
fix_new_exp (ip->frag, ip->where, bfd_get_reloc_size (howto),
&reloc_info[i].value, FALSE, reloc_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)
@@ -1487,14 +1534,6 @@ md_apply_fix (fixS *fixP, valueT *valP, segT seg ATTRIBUTE_UNUSED)
}
}
int
loongarch_relax_frag (asection *sec ATTRIBUTE_UNUSED,
fragS *fragp ATTRIBUTE_UNUSED,
long stretch ATTRIBUTE_UNUSED)
{
return 0;
}
int
md_estimate_size_before_relax (fragS *fragp ATTRIBUTE_UNUSED,
asection *segtype ATTRIBUTE_UNUSED)
@@ -1526,30 +1565,6 @@ tc_gen_reloc (asection *section ATTRIBUTE_UNUSED, fixS *fixp)
return reloc;
}
/* Convert a machine dependent frag. */
void
md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED, segT asec ATTRIBUTE_UNUSED,
fragS *fragp)
{
expressionS exp;
exp.X_op = O_symbol;
exp.X_add_symbol = fragp->fr_symbol;
exp.X_add_number = fragp->fr_offset;
bfd_byte *buf = (bfd_byte *)fragp->fr_literal + fragp->fr_fix;
fixS *fixp = fix_new_exp (fragp, buf - (bfd_byte *)fragp->fr_literal,
4, &exp, false, fragp->fr_subtype);
buf += 4;
fixp->fx_file = fragp->fr_file;
fixp->fx_line = fragp->fr_line;
fragp->fr_fix += fragp->fr_var;
gas_assert (fragp->fr_next == NULL
|| (fragp->fr_next->fr_address - fragp->fr_address
== fragp->fr_fix));
}
/* Standard calling conventions leave the CFA at SP on entry. */
void
loongarch_cfi_frame_initial_instructions (void)
@@ -1775,3 +1790,142 @@ 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 ATTRIBUTE_UNUSED,
fragS *fragp ATTRIBUTE_UNUSED,
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;
}
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 a machine dependent frag. This returns the amount by which
the current size of the frag should change. */
void
md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED, segT asec ATTRIBUTE_UNUSED,
fragS *fragp)
{
gas_assert (RELAX_BRANCH (fragp->fr_subtype));
loongarch_convert_frag_branch (fragp);
}

View File

@@ -0,0 +1,64 @@
#as:
#objdump: -dr
.*:[ ]+file format .*
Disassembly of section .text:
0* <.L1>:
[ ]+...
[ ]+48d158:[ ]+5c00098d[ ]+bne[ ]+\$t0, \$t1, 8[ ]+# 48d160 <.L1\+0x48d160>
[ ]+48d15c:[ ]+532ea7ed[ ]+b[ ]+-4772188[ ]+# 0 <.L1>
[ ]+48d15c: R_LARCH_B26[ ]+.L1
[ ]+48d160:[ ]+5800098d[ ]+beq[ ]+\$t0, \$t1, 8[ ]+# 48d168 <.L1\+0x48d168>
[ ]+48d164:[ ]+532e9fed[ ]+b[ ]+-4772196[ ]+# 0 <.L1>
[ ]+48d164: R_LARCH_B26[ ]+.L1
[ ]+48d168:[ ]+6400098d[ ]+bge[ ]+\$t0, \$t1, 8[ ]+# 48d170 <.L1\+0x48d170>
[ ]+48d16c:[ ]+532e97ed[ ]+b[ ]+-4772204[ ]+# 0 <.L1>
[ ]+48d16c: R_LARCH_B26[ ]+.L1
[ ]+48d170:[ ]+640009ac[ ]+bge[ ]+\$t1, \$t0, 8[ ]+# 48d178 <.L1\+0x48d178>
[ ]+48d174:[ ]+532e8fed[ ]+b[ ]+-4772212[ ]+# 0 <.L1>
[ ]+48d174: R_LARCH_B26[ ]+.L1
[ ]+48d178:[ ]+64000980[ ]+bgez[ ]+\$t0, 8[ ]+# 48d180 <.L1\+0x48d180>
[ ]+48d17c:[ ]+532e87ed[ ]+b[ ]+-4772220[ ]+# 0 <.L1>
[ ]+48d17c: R_LARCH_B26[ ]+.L1
[ ]+48d180:[ ]+6400080c[ ]+blez[ ]+\$t0, 8[ ]+# 48d188 <.L1\+0x48d188>
[ ]+48d184:[ ]+532e7fed[ ]+b[ ]+-4772228[ ]+# 0 <.L1>
[ ]+48d184: R_LARCH_B26[ ]+.L1
[ ]+48d188:[ ]+600009ac[ ]+blt[ ]+\$t1, \$t0, 8[ ]+# 48d190 <.L1\+0x48d190>
[ ]+48d18c:[ ]+532e77ed[ ]+b[ ]+-4772236[ ]+# 0 <.L1>
[ ]+48d18c: R_LARCH_B26[ ]+.L1
[ ]+48d190:[ ]+6000098d[ ]+blt[ ]+\$t0, \$t1, 8[ ]+# 48d198 <.L1\+0x48d198>
[ ]+48d194:[ ]+532e6fed[ ]+b[ ]+-4772244[ ]+# 0 <.L1>
[ ]+48d194: R_LARCH_B26[ ]+.L1
[ ]+48d198:[ ]+6000080c[ ]+bgtz[ ]+\$t0, 8[ ]+# 48d1a0 <.L1\+0x48d1a0>
[ ]+48d19c:[ ]+532e67ed[ ]+b[ ]+-4772252[ ]+# 0 <.L1>
[ ]+48d19c: R_LARCH_B26[ ]+.L1
[ ]+48d1a0:[ ]+60000980[ ]+bltz[ ]+\$t0, 8[ ]+# 48d1a8 <.L1\+0x48d1a8>
[ ]+48d1a4:[ ]+532e5fed[ ]+b[ ]+-4772260[ ]+# 0 <.L1>
[ ]+48d1a4: R_LARCH_B26[ ]+.L1
[ ]+48d1a8:[ ]+6c00098d[ ]+bgeu[ ]+\$t0, \$t1, 8[ ]+# 48d1b0 <.L1\+0x48d1b0>
[ ]+48d1ac:[ ]+532e57ed[ ]+b[ ]+-4772268[ ]+# 0 <.L1>
[ ]+48d1ac: R_LARCH_B26[ ]+.L1
[ ]+48d1b0:[ ]+6c0009ac[ ]+bgeu[ ]+\$t1, \$t0, 8[ ]+# 48d1b8 <.L1\+0x48d1b8>
[ ]+48d1b4:[ ]+532e4fed[ ]+b[ ]+-4772276[ ]+# 0 <.L1>
[ ]+48d1b4: R_LARCH_B26[ ]+.L1
[ ]+48d1b8:[ ]+680009ac[ ]+bltu[ ]+\$t1, \$t0, 8[ ]+# 48d1c0 <.L1\+0x48d1c0>
[ ]+48d1bc:[ ]+532e47ed[ ]+b[ ]+-4772284[ ]+# 0 <.L1>
[ ]+48d1bc: R_LARCH_B26[ ]+.L1
[ ]+48d1c0:[ ]+6800098d[ ]+bltu[ ]+\$t0, \$t1, 8[ ]+# 48d1c8 <.L1\+0x48d1c8>
[ ]+48d1c4:[ ]+532e3fed[ ]+b[ ]+-4772292[ ]+# 0 <.L1>
[ ]+48d1c4: R_LARCH_B26[ ]+.L1
[ ]+48d1c8:[ ]+44000980[ ]+bnez[ ]+\$t0, 8[ ]+# 48d1d0 <.L1\+0x48d1d0>
[ ]+48d1cc:[ ]+532e37ed[ ]+b[ ]+-4772300[ ]+# 0 <.L1>
[ ]+48d1cc: R_LARCH_B26[ ]+.L1
[ ]+48d1d0:[ ]+40000980[ ]+beqz[ ]+\$t0, 8[ ]+# 48d1d8 <.L1\+0x48d1d8>
[ ]+48d1d4:[ ]+532e2fed[ ]+b[ ]+-4772308[ ]+# 0 <.L1>
[ ]+48d1d4: R_LARCH_B26[ ]+.L1
[ ]+48d1d8:[ ]+48000900[ ]+bcnez[ ]+\$fcc0, 8[ ]+# 48d1e0 <.L1\+0x48d1e0>
[ ]+48d1dc:[ ]+532e27ed[ ]+b[ ]+-4772316[ ]+# 0 <.L1>
[ ]+48d1dc: R_LARCH_B26[ ]+.L1
[ ]+48d1e0:[ ]+48000800[ ]+bceqz[ ]+\$fcc0, 8[ ]+# 48d1e8 <.L1\+0x48d1e8>
[ ]+48d1e4:[ ]+532e1fed[ ]+b[ ]+-4772324[ ]+# 0 <.L1>
[ ]+48d1e4: R_LARCH_B26[ ]+.L1

View File

@@ -0,0 +1,33 @@
# Instruction and Relocation generating tests
.L1:
.fill 0x123456, 4, 0x0
# R_LARCH_B16
beq $r12, $r13, .L1
bne $r12, $r13, .L1
blt $r12, $r13, .L1
bgt $r12, $r13, .L1
bltz $r12, .L1
bgtz $r12, .L1
ble $r12, $r13, .L1
bge $r12, $r13, .L1
blez $r12, .L1
bgez $r12, .L1
bltu $r12, $r13, .L1
bgtu $r12, $r13, .L1
bleu $r12, $r13, .L1
bgeu $r12, $r13, .L1
# R_LARCH_B21
beqz $r12, .L1
bnez $r12, .L1
bceqz $fcc0, .L1
bcnez $fcc0, .L1

View File

@@ -0,0 +1,40 @@
#as:
#objdump: -dr
.*:[ ]+file format .*
Disassembly of section .text:
0* <.L1>:
[ ]+...
[ ]+20000:[ ]+5a00018d[ ]+beq[ ]+\$t0, \$t1, -131072[ ]+# 0 <.L1>
[ ]+20000: R_LARCH_B16[ ]+.L1
[ ]+20004:[ ]+5c00098d[ ]+bne[ ]+\$t0, \$t1, 8[ ]+# 2000c <.L1\+0x2000c>
[ ]+20008:[ ]+51fffbff[ ]+b[ ]+-131080[ ]+# 0 <.L1>
[ ]+20008: R_LARCH_B26[ ]+.L1
[ ]+2000c:[ ]+5c00098d[ ]+bne[ ]+\$t0, \$t1, 8[ ]+# 20014 <.L1\+0x20014>
[ ]+20010:[ ]+52000000[ ]+b[ ]+131072[ ]+# 40010 <.L2>
[ ]+20010: R_LARCH_B26[ ]+.L2
[ ]+20014:[ ]+59fffd8d[ ]+beq[ ]+\$t0, \$t1, 131068[ ]+# 40010 <.L2>
[ ]+20014: R_LARCH_B16[ ]+.L2
[ ]+...
0*40010 <.L2>:
[ ]+...
[ ]+440010:[ ]+40000190[ ]+beqz[ ]+\$t0, -4194304[ ]+# 40010 <.L2>
[ ]+440010: R_LARCH_B21[ ]+.L2
[ ]+440014:[ ]+44000980[ ]+bnez[ ]+\$t0, 8[ ]+# 44001c <.L2\+0x40000c>
[ ]+440018:[ ]+53fffbef[ ]+b[ ]+-4194312[ ]+# 40010 <.L2>
[ ]+440018: R_LARCH_B26[ ]+.L2
[ ]+44001c:[ ]+44000980[ ]+bnez[ ]+\$t0, 8[ ]+# 440024 <.L2\+0x400014>
[ ]+440020:[ ]+50000010[ ]+b[ ]+4194304[ ]+# 840020 <.L3>
[ ]+440020: R_LARCH_B26[ ]+.L3
[ ]+440024:[ ]+43fffd8f[ ]+beqz[ ]+\$t0, 4194300[ ]+# 840020 <.L3>
[ ]+440024: R_LARCH_B21[ ]+.L3
[ ]+...
0*840020 <.L3>:
[ ]+840020:[ ]+5800018d[ ]+beq[ ]+\$t0, \$t1, 0[ ]+# 840020 <.L3>
[ ]+840020: R_LARCH_B16[ ]+.L4
0*840024 <.L5>:
[ ]+840024:[ ]+40000180[ ]+beqz[ ]+\$t0, 0[ ]+# 840024 <.L5>
[ ]+840024: R_LARCH_B21[ ]+.L5

View File

@@ -0,0 +1,23 @@
# Immediate boundary value tests
.L1:
.fill 0x8000, 4, 0
beq $r12, $r13, .L1 # min imm -0x20000
beq $r12, $r13, .L1 # out of range
beq $r12, $r13, .L2 # out of range
beq $r12, $r13, .L2 # max imm 0x1fffc
.fill 0x7ffe, 4, 0
.L2:
.fill 0x100000, 4, 0
beqz $r12, .L2 # min imm -0x400000
beqz $r12, .L2 # out of range
beqz $r12, .L3 # out of range
beqz $r12, .L3 # max imm 0x3ffffc
.fill 0xffffe, 4, 0
.L3:
# 0 imm
.L4:
beq $r12, $r13, .L4
.L5:
beqz $r12, .L5

View File

@@ -29,6 +29,18 @@ extern "C"
#endif
#define LARCH_NOP 0x03400000
#define LARCH_B 0x50000000
/* BCEQZ/BCNEZ. */
#define LARCH_FLOAT_BRANCH 0x48000000
#define LARCH_BRANCH_OPCODE_MASK 0xfc000000
#define LARCH_BRANCH_INVERT_BIT 0x04000000
#define LARCH_FLOAT_BRANCH_INVERT_BIT 0x00000100
#define ENCODE_BRANCH16_IMM(x) (((x) >> 2) << 10)
#define OUT_OF_RANGE(value, bits, align) \
((value) < (-(1 << ((bits) - 1) << align)) \
|| (value) > ((((1 << ((bits) - 1)) - 1) << align)))
typedef uint32_t insn_t;