diff options
author | mengqinggang <mengqinggang@loongson.cn> | 2023-09-24 14:53:28 +0800 |
---|---|---|
committer | liuzhensong <liuzhensong@loongson.cn> | 2023-10-10 16:34:33 +0800 |
commit | 1fb3cdd87ec61715a5684925fb6d6a6cf53bb97c (patch) | |
tree | 29ff87c34597a975906f77e6bfaeedf8e433c37e /gas/config | |
parent | 9847ba8f085a7da0d85e90718ca987ab76637cbe (diff) | |
download | gdb-1fb3cdd87ec61715a5684925fb6d6a6cf53bb97c.zip gdb-1fb3cdd87ec61715a5684925fb6d6a6cf53bb97c.tar.gz gdb-1fb3cdd87ec61715a5684925fb6d6a6cf53bb97c.tar.bz2 |
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
Diffstat (limited to 'gas/config')
-rw-r--r-- | gas/config/tc-loongarch.c | 236 |
1 files changed, 195 insertions, 41 deletions
diff --git a/gas/config/tc-loongarch.c b/gas/config/tc-loongarch.c index d1d21fa..b563982 100644 --- a/gas/config/tc-loongarch.c +++ b/gas/config/tc-loongarch.c @@ -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); - - ip->fixp[i] = - fix_new_exp (ip->frag, ip->where, bfd_get_reloc_size (howto), - &reloc_info[i].value, FALSE, reloc_type); + 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) @@ -1488,14 +1535,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); +} |