aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJose E. Marchesi <jose.marchesi@oracle.com>2023-07-27 18:17:35 +0200
committerJose E. Marchesi <jose.marchesi@oracle.com>2023-07-28 18:19:44 +0200
commit249d4715e41061b6bd2d26df20ae274e6478f972 (patch)
treeed3caa4b4c19c83c780084958869fadf6eb1a576
parent07d8d4bd2ad213281be502d6e56c19e0269b8967 (diff)
downloadgdb-249d4715e41061b6bd2d26df20ae274e6478f972.zip
gdb-249d4715e41061b6bd2d26df20ae274e6478f972.tar.gz
gdb-249d4715e41061b6bd2d26df20ae274e6478f972.tar.bz2
bpf: gas: support relaxation of V4 jump instructions
The BPF jump-always instruction (JA), like all other jump instructions in the ISA, get a signed 16-bit displacement target argument denoted in number of 64-bit words minus one. This can sometimes be overflown. The BPF V4 ISA thus introduced support for a jump-always instruction (JAL) that gets a signed 32-bit displacement instead. This patch makes the BPF assembler to perform the following relaxations when the disp16 field gets overflown, unless the option -mno-relax is specified: JA disp16 -> JAL disp32 Jxx disp16 -> Jxx +1; JA +1; JAL disp32 Documentation and tests added. Tested in bpf-unknown-none. gas/ChangeLog: 2023-07-28 Jose E. Marchesi <jose.marchesi@oracle.com> PR gas/30690 * config/tc-bpf.c (struct bpf_insn): Add fields is_relaxable and relaxed_exp. (enum options): Add OPTION_NO_RELAX. (md_longopts): Likewise for -mno-relax. (do_relax): New global. (md_parse_option): Handle OPTION_NO_RELAX. (RELAX_BRANCH_ENCODE): Define. (RELAX_BRANCH_P): Likewise. (RELAX_BRANCH_LENGTH): Likewise. (RELAX_BRANCH_CONST): Likewise. (RELAX_BRANCH_UNCOND): Likewise. (relaxed_branch_length): New function. (md_estimate_size_before_relax): Likewise. (read_insn_word): Likewise. (encode_int16): Likewise. (encode_int32): Likewise. (write_insn_bytes): Likewise. (md_convert_frag): Likewise. (encode_insn): Likewise. (install_insn_fixups): Likewise. (add_fixed_insn): Likewise. (add_relaxed_insn): Likewise. (md_assemble): Move instruction encoding logic to the above new functions. * testsuite/gas/bpf/jump-relax-ja.d: New test. * testsuite/gas/bpf/jump-relax-ja-be.d: Likewise. * testsuite/gas/bpf/jump-relax-ja.s: And corresponding source. * testsuite/gas/bpf/jump-relax-jump.d: New test. * testsuite/gas/bpf/jump-relax-jump-be.d: Likewise. * testsuite/gas/bpf/jump-relax-jump.s: And corresponding source. * testsuite/gas/bpf/bpf.exp: Run new tests. * doc/c-bpf.texi (BPF Options): Document -mno-relax.
-rw-r--r--gas/ChangeLog36
-rw-r--r--gas/config/tc-bpf.c1020
-rw-r--r--gas/doc/c-bpf.texi4
-rw-r--r--gas/testsuite/gas/bpf/bpf.exp6
-rw-r--r--gas/testsuite/gas/bpf/jump-relax-ja-be.d19
-rw-r--r--gas/testsuite/gas/bpf/jump-relax-ja.d19
-rw-r--r--gas/testsuite/gas/bpf/jump-relax-ja.s20
-rw-r--r--gas/testsuite/gas/bpf/jump-relax-jump.d25
-rw-r--r--gas/testsuite/gas/bpf/jump-relax-jump.s17
9 files changed, 858 insertions, 308 deletions
diff --git a/gas/ChangeLog b/gas/ChangeLog
index 147c20c..1f3735d 100644
--- a/gas/ChangeLog
+++ b/gas/ChangeLog
@@ -1,3 +1,39 @@
+2023-07-28 Jose E. Marchesi <jose.marchesi@oracle.com>
+
+ PR gas/30690
+ * config/tc-bpf.c (struct bpf_insn): Add fields is_relaxable and
+ relaxed_exp.
+ (enum options): Add OPTION_NO_RELAX.
+ (md_longopts): Likewise for -mno-relax.
+ (do_relax): New global.
+ (md_parse_option): Handle OPTION_NO_RELAX.
+ (RELAX_BRANCH_ENCODE): Define.
+ (RELAX_BRANCH_P): Likewise.
+ (RELAX_BRANCH_LENGTH): Likewise.
+ (RELAX_BRANCH_CONST): Likewise.
+ (RELAX_BRANCH_UNCOND): Likewise.
+ (relaxed_branch_length): New function.
+ (md_estimate_size_before_relax): Likewise.
+ (read_insn_word): Likewise.
+ (encode_int16): Likewise.
+ (encode_int32): Likewise.
+ (write_insn_bytes): Likewise.
+ (md_convert_frag): Likewise.
+ (encode_insn): Likewise.
+ (install_insn_fixups): Likewise.
+ (add_fixed_insn): Likewise.
+ (add_relaxed_insn): Likewise.
+ (md_assemble): Move instruction encoding logic to the above
+ new functions.
+ * testsuite/gas/bpf/jump-relax-ja.d: New test.
+ * testsuite/gas/bpf/jump-relax-ja-be.d: Likewise.
+ * testsuite/gas/bpf/jump-relax-ja.s: And corresponding source.
+ * testsuite/gas/bpf/jump-relax-jump.d: New test.
+ * testsuite/gas/bpf/jump-relax-jump-be.d: Likewise.
+ * testsuite/gas/bpf/jump-relax-jump.s: And corresponding source.
+ * testsuite/gas/bpf/bpf.exp: Run new tests.
+ * doc/c-bpf.texi (BPF Options): Document -mno-relax.
+
2023-07-26 Jose E. Marchesi <jose.marchesi@oracle.com>
* testsuite/gas/bpf/alu.s: Add test for NEGI and NEG32I.
diff --git a/gas/config/tc-bpf.c b/gas/config/tc-bpf.c
index faa809c..969116b 100644
--- a/gas/config/tc-bpf.c
+++ b/gas/config/tc-bpf.c
@@ -51,6 +51,9 @@ struct bpf_insn
unsigned int has_disp32 : 1;
unsigned int has_imm32 : 1;
unsigned int has_imm64 : 1;
+
+ unsigned int is_relaxable : 1;
+ expressionS *relaxed_exp;
};
const char comment_chars[] = ";#";
@@ -120,6 +123,7 @@ enum options
OPTION_XBPF,
OPTION_DIALECT,
OPTION_ISA_SPEC,
+ OPTION_NO_RELAX,
};
struct option md_longopts[] =
@@ -129,6 +133,7 @@ struct option md_longopts[] =
{ "mxbpf", no_argument, NULL, OPTION_XBPF },
{ "mdialect", required_argument, NULL, OPTION_DIALECT},
{ "misa-spec", required_argument, NULL, OPTION_ISA_SPEC},
+ { "mno-relax", no_argument, NULL, OPTION_NO_RELAX},
{ NULL, no_argument, NULL, 0 },
};
@@ -144,6 +149,11 @@ const char * md_shortopts = "";
static int set_target_endian = 0;
extern int target_big_endian;
+/* Whether to relax branch instructions. Default is yes. Can be
+ changed using the -mno-relax command line option. */
+
+static int do_relax = 1;
+
/* The ISA specification can be one of BPF_V1, BPF_V2, BPF_V3, BPF_V4
or BPF_XPBF. The ISA spec to use can be configured using
command-line options. It defaults to the latest BPF spec. */
@@ -203,6 +213,9 @@ md_parse_option (int c, const char * arg)
/* This is an alias for -misa-spec=xbpf. */
isa_spec = BPF_XBPF;
break;
+ case OPTION_NO_RELAX:
+ do_relax = 0;
+ break;
default:
return 0;
}
@@ -337,6 +350,151 @@ tc_gen_reloc (asection *sec ATTRIBUTE_UNUSED, fixS *fixP)
}
+/* Relaxations supported by this assembler. */
+
+#define RELAX_BRANCH_ENCODE(uncond, constant, length) \
+ ((relax_substateT) \
+ (0xc0000000 \
+ | ((uncond) ? 1 : 0) \
+ | ((constant) ? 2 : 0) \
+ | ((length) << 2)))
+
+#define RELAX_BRANCH_P(i) (((i) & 0xf0000000) == 0xc0000000)
+#define RELAX_BRANCH_LENGTH(i) (((i) >> 2) & 0xff)
+#define RELAX_BRANCH_CONST(i) (((i) & 2) != 0)
+#define RELAX_BRANCH_UNCOND(i) (((i) & 1) != 0)
+
+
+/* Compute the length of a branch seuqence, and adjust the stored
+ length accordingly. If FRAG is NULL, the worst-case length is
+ returned. */
+
+static unsigned
+relaxed_branch_length (fragS *fragp, asection *sec, int update)
+{
+ int length, uncond;
+
+ if (!fragp)
+ return 8 * 3;
+
+ uncond = RELAX_BRANCH_UNCOND (fragp->fr_subtype);
+ length = RELAX_BRANCH_LENGTH (fragp->fr_subtype);
+
+ if (uncond)
+ /* Length is the same for both JA and JAL. */
+ length = 8;
+ else
+ {
+ if (RELAX_BRANCH_CONST (fragp->fr_subtype))
+ {
+ int64_t val = fragp->fr_offset;
+
+ if (val < -32768 || val > 32767)
+ length = 8 * 3;
+ else
+ length = 8;
+ }
+ else 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;
+
+ /* Convert to 64-bit words, minus one. */
+ val = (val - 8) / 8;
+
+ /* See if it fits in the signed 16-bits field. */
+ if (val < -32768 || val > 32767)
+ length = 8 * 3;
+ else
+ length = 8;
+ }
+ else
+ /* Use short version, and let the linker relax instead, if
+ appropriate and if supported. */
+ length = 8;
+ }
+
+ if (update)
+ fragp->fr_subtype = RELAX_BRANCH_ENCODE (uncond,
+ RELAX_BRANCH_CONST (fragp->fr_subtype),
+ length);
+
+ return length;
+}
+
+/* Estimate the size of a variant frag before relaxing. */
+
+int
+md_estimate_size_before_relax (fragS *fragp, asection *sec)
+{
+ return (fragp->fr_var = relaxed_branch_length (fragp, sec, true));
+}
+
+/* Read a BPF instruction word from BUF. */
+
+static uint64_t
+read_insn_word (bfd_byte *buf)
+{
+ return bfd_getb64 (buf);
+}
+
+/* Write the given signed 16-bit value in the given BUFFER using the
+ target endianness. */
+
+static void
+encode_int16 (int16_t value, char *buffer)
+{
+ uint16_t val = value;
+
+ if (target_big_endian)
+ {
+ buffer[0] = (val >> 8) & 0xff;
+ buffer[1] = val & 0xff;
+ }
+ else
+ {
+ buffer[1] = (val >> 8) & 0xff;
+ buffer[0] = val & 0xff;
+ }
+}
+
+/* Write the given signed 32-bit value in the given BUFFER using the
+ target endianness. */
+
+static void
+encode_int32 (int32_t value, char *buffer)
+{
+ uint32_t val = value;
+
+ if (target_big_endian)
+ {
+ buffer[0] = (val >> 24) & 0xff;
+ buffer[1] = (val >> 16) & 0xff;
+ buffer[2] = (val >> 8) & 0xff;
+ buffer[3] = val & 0xff;
+ }
+ else
+ {
+ buffer[3] = (val >> 24) & 0xff;
+ buffer[2] = (val >> 16) & 0xff;
+ buffer[1] = (val >> 8) & 0xff;
+ buffer[0] = value & 0xff;
+ }
+}
+
+/* Write a BPF instruction to BUF. */
+
+static void
+write_insn_bytes (bfd_byte *buf, char *bytes)
+{
+ int i;
+
+ for (i = 0; i < 8; ++i)
+ md_number_to_chars ((char *) buf + i, (valueT) bytes[i], 1);
+}
+
/* *FRAGP has been relaxed to its final size, and now needs to have
the bytes inside it modified to conform to the new size.
@@ -347,17 +505,229 @@ tc_gen_reloc (asection *sec ATTRIBUTE_UNUSED, fixS *fixP)
void
md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED,
segT sec ATTRIBUTE_UNUSED,
- fragS *fragP ATTRIBUTE_UNUSED)
+ fragS *fragp ATTRIBUTE_UNUSED)
{
- as_fatal (_("convert_frag called"));
-}
+ bfd_byte *buf = (bfd_byte *) fragp->fr_literal + fragp->fr_fix;
+ expressionS exp;
+ fixS *fixp;
+ bpf_insn_word word;
+ int disp_is_known = 0;
+ int64_t disp_to_target = 0;
-int
-md_estimate_size_before_relax (fragS *fragP ATTRIBUTE_UNUSED,
- segT segment ATTRIBUTE_UNUSED)
-{
- as_fatal (_("estimate_size_before_relax called"));
- return 0;
+ uint64_t code;
+
+ gas_assert (RELAX_BRANCH_P (fragp->fr_subtype));
+
+ /* Expression to be used in any resulting relocation in the relaxed
+ instructions. */
+ exp.X_op = O_symbol;
+ exp.X_add_symbol = fragp->fr_symbol;
+ exp.X_add_number = fragp->fr_offset;
+
+ gas_assert (fragp->fr_var == RELAX_BRANCH_LENGTH (fragp->fr_subtype));
+
+ /* Read an instruction word from the instruction to be relaxed, and
+ get the code. */
+ word = read_insn_word (buf);
+ code = (word >> 60) & 0xf;
+
+ /* Determine whether the 16-bit displacement to the target is known
+ at this point. */
+ if (RELAX_BRANCH_CONST (fragp->fr_subtype))
+ {
+ /* XXX this loses the 32-bit value if the constant was
+ overflown! */
+ disp_to_target = fragp->fr_offset;
+ disp_is_known = 1;
+ }
+ else 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;
+ /* Convert to 64-bit blocks minus one. */
+ disp_to_target = (val - 8) / 8;
+ disp_is_known = 1;
+ }
+
+ /* Now relax particular jump instructions. */
+ if (code == BPF_CODE_JA)
+ {
+ /* Unconditional jump.
+ JA d16 -> JAL d32 */
+
+ gas_assert (RELAX_BRANCH_UNCOND (fragp->fr_subtype));
+
+ if (disp_is_known)
+ {
+ if (disp_to_target >= -32768 && disp_to_target <= 32767)
+ {
+ /* 16-bit disp is known and in range. Install a fixup
+ for the disp16 if the branch value is not constant.
+ This will be resolved by the assembler and units
+ converted. */
+
+ if (!RELAX_BRANCH_CONST (fragp->fr_subtype))
+ {
+ /* Install fixup for the JA. */
+ reloc_howto_type *reloc_howto
+ = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
+ if (!reloc_howto)
+ abort();
+
+ fixp = fix_new_exp (fragp, buf - (bfd_byte *) fragp->fr_literal,
+ bfd_get_reloc_size (reloc_howto),
+ &exp,
+ reloc_howto->pc_relative,
+ BFD_RELOC_BPF_DISP16);
+ fixp->fx_file = fragp->fr_file;
+ fixp->fx_line = fragp->fr_line;
+ }
+ }
+ else
+ {
+ /* 16-bit disp is known and not in range. Turn the JA
+ into a JAL with a 32-bit displacement. */
+ char bytes[8];
+
+ bytes[0] = ((BPF_CLASS_JMP32|BPF_CODE_JA|BPF_SRC_K) >> 56) & 0xff;
+ bytes[1] = (word >> 48) & 0xff;
+ bytes[2] = 0; /* disp16 high */
+ bytes[3] = 0; /* disp16 lo */
+ encode_int32 ((int32_t) disp_to_target, bytes + 4);
+
+ write_insn_bytes (buf, bytes);
+ }
+ }
+ else
+ {
+ /* The displacement to the target is not known. Do not
+ relax. The linker will maybe do it if it chooses to. */
+
+ reloc_howto_type *reloc_howto = NULL;
+
+ gas_assert (!RELAX_BRANCH_CONST (fragp->fr_subtype));
+
+ /* Install fixup for the JA. */
+ reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
+ if (!reloc_howto)
+ abort ();
+
+ fixp = fix_new_exp (fragp, buf - (bfd_byte *) fragp->fr_literal,
+ bfd_get_reloc_size (reloc_howto),
+ &exp,
+ reloc_howto->pc_relative,
+ BFD_RELOC_BPF_DISP16);
+ fixp->fx_file = fragp->fr_file;
+ fixp->fx_line = fragp->fr_line;
+ }
+
+ buf += 8;
+ }
+ else
+ {
+ /* Conditional jump.
+ JXX d16 -> JXX +1; JA +1; JAL d32 */
+
+ gas_assert (!RELAX_BRANCH_UNCOND (fragp->fr_subtype));
+
+ if (disp_is_known)
+ {
+ if (disp_to_target >= -32768 && disp_to_target <= 32767)
+ {
+ /* 16-bit disp is known and in range. Install a fixup
+ for the disp16 if the branch value is not constant.
+ This will be resolved by the assembler and units
+ converted. */
+
+ if (!RELAX_BRANCH_CONST (fragp->fr_subtype))
+ {
+ /* Install fixup for the branch. */
+ reloc_howto_type *reloc_howto
+ = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
+ if (!reloc_howto)
+ abort();
+
+ fixp = fix_new_exp (fragp, buf - (bfd_byte *) fragp->fr_literal,
+ bfd_get_reloc_size (reloc_howto),
+ &exp,
+ reloc_howto->pc_relative,
+ BFD_RELOC_BPF_DISP16);
+ fixp->fx_file = fragp->fr_file;
+ fixp->fx_line = fragp->fr_line;
+ }
+
+ buf += 8;
+ }
+ else
+ {
+ /* 16-bit disp is known and not in range. Turn the JXX
+ into a sequence JXX +1; JA +1; JAL d32. */
+
+ char bytes[8];
+
+ /* First, set the 16-bit offset in the current
+ instruction to 1. */
+
+ if (target_big_endian)
+ bfd_putb16 (1, buf + 2);
+ else
+ bfd_putl16 (1, buf + 2);
+ buf += 8;
+
+ /* Then, write the JA + 1 */
+
+ bytes[0] = 0x05; /* JA */
+ bytes[1] = 0x0;
+ encode_int16 (1, bytes + 2);
+ bytes[4] = 0x0;
+ bytes[5] = 0x0;
+ bytes[6] = 0x0;
+ bytes[7] = 0x0;
+ write_insn_bytes (buf, bytes);
+ buf += 8;
+
+ /* Finally, write the JAL to the target. */
+
+ bytes[0] = ((BPF_CLASS_JMP32|BPF_CODE_JA|BPF_SRC_K) >> 56) & 0xff;
+ bytes[1] = 0;
+ bytes[2] = 0;
+ bytes[3] = 0;
+ encode_int32 ((int32_t) disp_to_target, bytes + 4);
+ write_insn_bytes (buf, bytes);
+ buf += 8;
+ }
+ }
+ else
+ {
+ /* The displacement to the target is not known. Do not
+ relax. The linker will maybe do it if it chooses to. */
+
+ reloc_howto_type *reloc_howto = NULL;
+
+ gas_assert (!RELAX_BRANCH_CONST (fragp->fr_subtype));
+
+ /* Install fixup for the conditional jump. */
+ reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
+ if (!reloc_howto)
+ abort ();
+
+ fixp = fix_new_exp (fragp, buf - (bfd_byte *) fragp->fr_literal,
+ bfd_get_reloc_size (reloc_howto),
+ &exp,
+ reloc_howto->pc_relative,
+ BFD_RELOC_BPF_DISP16);
+ fixp->fx_file = fragp->fr_file;
+ fixp->fx_line = fragp->fr_line;
+ buf += 8;
+ }
+ }
+
+ gas_assert (buf == (bfd_byte *)fragp->fr_literal
+ + fragp->fr_fix + fragp->fr_var);
+
+ fragp->fr_fix += fragp->fr_var;
}
@@ -460,6 +830,327 @@ md_apply_fix (fixS *fixP, valueT *valP, segT seg ATTRIBUTE_UNUSED)
fixP->fx_addnumber = *valP;
}
+
+/* Instruction writing routines. */
+
+/* Encode a BPF instruction in the given buffer BYTES. Non-constant
+ immediates are encoded as zeroes. */
+
+static void
+encode_insn (struct bpf_insn *insn, char *bytes)
+{
+ uint8_t src, dst;
+
+ /* Zero all the bytes. */
+ memset (bytes, 0, 16);
+
+ /* First encode the opcodes. Note that we have to handle the
+ endianness groups of the BPF instructions: 8 | 4 | 4 | 16 |
+ 32. */
+ if (target_big_endian)
+ {
+ /* code */
+ bytes[0] = (insn->opcode >> 56) & 0xff;
+ /* regs */
+ bytes[1] = (insn->opcode >> 48) & 0xff;
+ /* offset16 */
+ bytes[2] = (insn->opcode >> 40) & 0xff;
+ bytes[3] = (insn->opcode >> 32) & 0xff;
+ /* imm32 */
+ bytes[4] = (insn->opcode >> 24) & 0xff;
+ bytes[5] = (insn->opcode >> 16) & 0xff;
+ bytes[6] = (insn->opcode >> 8) & 0xff;
+ bytes[7] = insn->opcode & 0xff;
+ }
+ else
+ {
+ /* code */
+ bytes[0] = (insn->opcode >> 56) & 0xff;
+ /* regs */
+ bytes[1] = (((((insn->opcode >> 48) & 0xff) & 0xf) << 4)
+ | (((insn->opcode >> 48) & 0xff) & 0xf));
+ /* offset16 */
+ bytes[3] = (insn->opcode >> 40) & 0xff;
+ bytes[2] = (insn->opcode >> 32) & 0xff;
+ /* imm32 */
+ bytes[7] = (insn->opcode >> 24) & 0xff;
+ bytes[6] = (insn->opcode >> 16) & 0xff;
+ bytes[5] = (insn->opcode >> 8) & 0xff;
+ bytes[4] = insn->opcode & 0xff;
+ }
+
+ /* Now the registers. */
+ src = insn->has_src ? insn->src : 0;
+ dst = insn->has_dst ? insn->dst : 0;
+
+ if (target_big_endian)
+ bytes[1] = ((dst & 0xf) << 4) | (src & 0xf);
+ else
+ bytes[1] = ((src & 0xf) << 4) | (dst & 0xf);
+
+ /* Now the immediates that are known to be constant. */
+
+ if (insn->has_imm32 && insn->imm32.X_op == O_constant)
+ encode_int32 (insn->imm32.X_add_number, bytes + 4);
+
+ if (insn->has_disp32 && insn->disp32.X_op == O_constant)
+ encode_int32 (insn->disp32.X_add_number, bytes + 4);
+
+ if (insn->has_offset16 && insn->offset16.X_op == O_constant)
+ encode_int16 (insn->offset16.X_add_number, bytes + 2);
+
+ if (insn->has_disp16 && insn->disp16.X_op == O_constant)
+ encode_int16 (insn->disp16.X_add_number, bytes + 2);
+
+ if (insn->has_imm64 && insn->imm64.X_op == O_constant)
+ {
+ uint64_t imm64 = insn->imm64.X_add_number;
+
+ if (target_big_endian)
+ {
+ bytes[12] = (imm64 >> 56) & 0xff;
+ bytes[13] = (imm64 >> 48) & 0xff;
+ bytes[14] = (imm64 >> 40) & 0xff;
+ bytes[15] = (imm64 >> 32) & 0xff;
+ bytes[4] = (imm64 >> 24) & 0xff;
+ bytes[5] = (imm64 >> 16) & 0xff;
+ bytes[6] = (imm64 >> 8) & 0xff;
+ bytes[7] = imm64 & 0xff;
+ }
+ else
+ {
+ bytes[15] = (imm64 >> 56) & 0xff;
+ bytes[14] = (imm64 >> 48) & 0xff;
+ bytes[13] = (imm64 >> 40) & 0xff;
+ bytes[12] = (imm64 >> 32) & 0xff;
+ bytes[7] = (imm64 >> 24) & 0xff;
+ bytes[6] = (imm64 >> 16) & 0xff;
+ bytes[5] = (imm64 >> 8) & 0xff;
+ bytes[4] = imm64 & 0xff;
+ }
+ }
+}
+
+/* Install the fixups in INSN in their proper location in the
+ specified FRAG at the location pointed by WHERE. */
+
+static void
+install_insn_fixups (struct bpf_insn *insn, fragS *frag, long where)
+{
+ if (insn->has_imm64)
+ {
+ switch (insn->imm64.X_op)
+ {
+ case O_symbol:
+ case O_subtract:
+ case O_add:
+ {
+ reloc_howto_type *reloc_howto;
+ int size;
+
+ reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_64);
+ if (!reloc_howto)
+ abort ();
+
+ size = bfd_get_reloc_size (reloc_howto);
+
+ fix_new_exp (frag, where,
+ size, &insn->imm64, reloc_howto->pc_relative,
+ BFD_RELOC_BPF_64);
+ break;
+ }
+ case O_constant:
+ /* Already handled in encode_insn. */
+ break;
+ default:
+ abort ();
+ }
+ }
+
+ if (insn->has_imm32)
+ {
+ switch (insn->imm32.X_op)
+ {
+ case O_symbol:
+ case O_subtract:
+ case O_add:
+ case O_uminus:
+ {
+ reloc_howto_type *reloc_howto;
+ int size;
+
+ reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_32);
+ if (!reloc_howto)
+ abort ();
+
+ size = bfd_get_reloc_size (reloc_howto);
+
+ fix_new_exp (frag, where + 4,
+ size, &insn->imm32, reloc_howto->pc_relative,
+ BFD_RELOC_32);
+ break;
+ }
+ case O_constant:
+ /* Already handled in encode_insn. */
+ break;
+ default:
+ abort ();
+ }
+ }
+
+ if (insn->has_disp32)
+ {
+ switch (insn->disp32.X_op)
+ {
+ case O_symbol:
+ case O_subtract:
+ case O_add:
+ {
+ reloc_howto_type *reloc_howto;
+ int size;
+ unsigned int bfd_reloc
+ = (insn->id == BPF_INSN_CALL
+ ? BFD_RELOC_BPF_DISPCALL32
+ : BFD_RELOC_BPF_DISP32);
+
+ reloc_howto = bfd_reloc_type_lookup (stdoutput, bfd_reloc);
+ if (!reloc_howto)
+ abort ();
+
+ size = bfd_get_reloc_size (reloc_howto);
+
+ fix_new_exp (frag, where,
+ size, &insn->disp32, reloc_howto->pc_relative,
+ bfd_reloc);
+ break;
+ }
+ case O_constant:
+ /* Already handled in encode_insn. */
+ break;
+ default:
+ abort ();
+ }
+ }
+
+ if (insn->has_offset16)
+ {
+ switch (insn->offset16.X_op)
+ {
+ case O_symbol:
+ case O_subtract:
+ case O_add:
+ {
+ reloc_howto_type *reloc_howto;
+ int size;
+
+ /* XXX we really need a new pc-rel offset in bytes
+ relocation for this. */
+ reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
+ if (!reloc_howto)
+ abort ();
+
+ size = bfd_get_reloc_size (reloc_howto);
+
+ fix_new_exp (frag, where,
+ size, &insn->offset16, reloc_howto->pc_relative,
+ BFD_RELOC_BPF_DISP16);
+ break;
+ }
+ case O_constant:
+ /* Already handled in encode_insn. */
+ break;
+ default:
+ abort ();
+ }
+ }
+
+ if (insn->has_disp16)
+ {
+ switch (insn->disp16.X_op)
+ {
+ case O_symbol:
+ case O_subtract:
+ case O_add:
+ {
+ reloc_howto_type *reloc_howto;
+ int size;
+
+ reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
+ if (!reloc_howto)
+ abort ();
+
+ size = bfd_get_reloc_size (reloc_howto);
+
+ fix_new_exp (frag, where,
+ size, &insn->disp16, reloc_howto->pc_relative,
+ BFD_RELOC_BPF_DISP16);
+ break;
+ }
+ case O_constant:
+ /* Already handled in encode_insn. */
+ break;
+ default:
+ abort ();
+ }
+ }
+
+}
+
+/* Add a new insn to the list of instructions. */
+
+static void
+add_fixed_insn (struct bpf_insn *insn)
+{
+ char *this_frag = frag_more (insn->size);
+ char bytes[16];
+ int i;
+
+ /* First encode the known parts of the instruction, including
+ opcodes and constant immediates, and write them to the frag. */
+ encode_insn (insn, bytes);
+ for (i = 0; i < insn->size; ++i)
+ md_number_to_chars (this_frag + i, (valueT) bytes[i], 1);
+
+ /* Now install the instruction fixups. */
+ install_insn_fixups (insn, frag_now,
+ this_frag - frag_now->fr_literal);
+}
+
+/* Add a new relaxable to the list of instructions. */
+
+static void
+add_relaxed_insn (struct bpf_insn *insn, expressionS *exp)
+{
+ char bytes[16];
+ int i;
+ char *this_frag;
+ unsigned worst_case = relaxed_branch_length (NULL, NULL, 0);
+ unsigned best_case = insn->size;
+
+ /* We only support relaxing branches, for the moment. */
+ relax_substateT subtype
+ = RELAX_BRANCH_ENCODE (insn->id == BPF_INSN_JAR,
+ exp->X_op == O_constant,
+ worst_case);
+
+ frag_grow (worst_case);
+ this_frag = frag_more (0);
+
+ /* First encode the known parts of the instruction, including
+ opcodes and constant immediates, and write them to the frag. */
+ encode_insn (insn, bytes);
+ for (i = 0; i < insn->size; ++i)
+ md_number_to_chars (this_frag + i, (valueT) bytes[i], 1);
+
+ /* Note that instruction fixups will be applied once the frag is
+ relaxed, in md_convert_frag. */
+ frag_var (rs_machine_dependent,
+ worst_case, best_case,
+ subtype, exp->X_add_symbol, exp->X_add_number /* offset */,
+ NULL);
+}
+
+
/* Parse an operand expression. Returns the first character that is
not part of the expression, or NULL in case of parse error.
@@ -781,6 +1472,7 @@ md_assemble (char *str ATTRIBUTE_UNUSED)
break;
}
insn.has_disp16 = 1;
+ insn.is_relaxable = 1;
p += 4;
}
else if (strncmp (p, "%d32", 4) == 0)
@@ -865,307 +1557,19 @@ md_assemble (char *str ATTRIBUTE_UNUSED)
#undef PARSE_ERROR
/* Generate the frags and fixups for the parsed instruction. */
- {
- char *this_frag = frag_more (insn.size);
- char bytes[16];
- uint8_t src, dst;
- int i;
-
- /* Zero all the bytes. */
- memset (bytes, 0, 16);
-
- /* First encode the opcodes. Note that we have to handle the
- endianness groups of the BPF instructions: 8 | 4 | 4 | 16 |
- 32. */
- if (target_big_endian)
- {
- /* code */
- bytes[0] = (insn.opcode >> 56) & 0xff;
- /* regs */
- bytes[1] = (insn.opcode >> 48) & 0xff;
- /* offset16 */
- bytes[2] = (insn.opcode >> 40) & 0xff;
- bytes[3] = (insn.opcode >> 32) & 0xff;
- /* imm32 */
- bytes[4] = (insn.opcode >> 24) & 0xff;
- bytes[5] = (insn.opcode >> 16) & 0xff;
- bytes[6] = (insn.opcode >> 8) & 0xff;
- bytes[7] = insn.opcode & 0xff;
- }
- else
- {
- /* code */
- bytes[0] = (insn.opcode >> 56) & 0xff;
- /* regs */
- bytes[1] = (((((insn.opcode >> 48) & 0xff) & 0xf) << 4)
- | (((insn.opcode >> 48) & 0xff) & 0xf));
- /* offset16 */
- bytes[3] = (insn.opcode >> 40) & 0xff;
- bytes[2] = (insn.opcode >> 32) & 0xff;
- /* imm32 */
- bytes[7] = (insn.opcode >> 24) & 0xff;
- bytes[6] = (insn.opcode >> 16) & 0xff;
- bytes[5] = (insn.opcode >> 8) & 0xff;
- bytes[4] = insn.opcode & 0xff;
- }
-
- /* Now the registers. */
- src = insn.has_src ? insn.src : 0;
- dst = insn.has_dst ? insn.dst : 0;
-
- if (target_big_endian)
- bytes[1] = ((dst & 0xf) << 4) | (src & 0xf);
- else
- bytes[1] = ((src & 0xf) << 4) | (dst & 0xf);
-
- /* Now the immediates. */
- if (insn.has_imm64)
- {
- switch (insn.imm64.X_op)
- {
- case O_constant:
- {
- uint64_t imm64 = insn.imm64.X_add_number;
-
- if (target_big_endian)
- {
- bytes[12] = (imm64 >> 56) & 0xff;
- bytes[13] = (imm64 >> 48) & 0xff;
- bytes[14] = (imm64 >> 40) & 0xff;
- bytes[15] = (imm64 >> 32) & 0xff;
- bytes[4] = (imm64 >> 24) & 0xff;
- bytes[5] = (imm64 >> 16) & 0xff;
- bytes[6] = (imm64 >> 8) & 0xff;
- bytes[7] = imm64 & 0xff;
- }
- else
- {
- bytes[15] = (imm64 >> 56) & 0xff;
- bytes[14] = (imm64 >> 48) & 0xff;
- bytes[13] = (imm64 >> 40) & 0xff;
- bytes[12] = (imm64 >> 32) & 0xff;
- bytes[7] = (imm64 >> 24) & 0xff;
- bytes[6] = (imm64 >> 16) & 0xff;
- bytes[5] = (imm64 >> 8) & 0xff;
- bytes[4] = imm64 & 0xff;
- }
- break;
- }
- case O_symbol:
- case O_subtract:
- case O_add:
- {
- reloc_howto_type *reloc_howto;
- int size;
-
- reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_64);
- if (!reloc_howto)
- abort ();
-
- size = bfd_get_reloc_size (reloc_howto);
-
- fix_new_exp (frag_now, this_frag - frag_now->fr_literal,
- size, &insn.imm64, reloc_howto->pc_relative,
- BFD_RELOC_BPF_64);
- break;
- }
- default:
- abort ();
- }
- }
-
- if (insn.has_imm32)
- {
- switch (insn.imm32.X_op)
- {
- case O_constant:
- {
- uint32_t imm32 = insn.imm32.X_add_number;
-
- if (target_big_endian)
- {
- bytes[4] = (imm32 >> 24) & 0xff;
- bytes[5] = (imm32 >> 16) & 0xff;
- bytes[6] = (imm32 >> 8) & 0xff;
- bytes[7] = imm32 & 0xff;
- }
- else
- {
- bytes[7] = (imm32 >> 24) & 0xff;
- bytes[6] = (imm32 >> 16) & 0xff;
- bytes[5] = (imm32 >> 8) & 0xff;
- bytes[4] = imm32 & 0xff;
- }
- break;
- }
- case O_symbol:
- case O_subtract:
- case O_add:
- case O_uminus:
- {
- reloc_howto_type *reloc_howto;
- int size;
-
- reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_32);
- if (!reloc_howto)
- abort ();
-
- size = bfd_get_reloc_size (reloc_howto);
-
- fix_new_exp (frag_now, this_frag - frag_now->fr_literal + 4,
- size, &insn.imm32, reloc_howto->pc_relative,
- BFD_RELOC_32);
- break;
- }
- default:
- abort ();
- }
- }
-
- if (insn.has_disp32)
- {
- switch (insn.disp32.X_op)
- {
- case O_constant:
- {
- uint32_t disp32 = insn.disp32.X_add_number;
-
- if (target_big_endian)
- {
- bytes[4] = (disp32 >> 24) & 0xff;
- bytes[5] = (disp32 >> 16) & 0xff;
- bytes[6] = (disp32 >> 8) & 0xff;
- bytes[7] = disp32 & 0xff;
- }
- else
- {
- bytes[7] = (disp32 >> 24) & 0xff;
- bytes[6] = (disp32 >> 16) & 0xff;
- bytes[5] = (disp32 >> 8) & 0xff;
- bytes[4] = disp32 & 0xff;
- }
- break;
- }
- case O_symbol:
- case O_subtract:
- case O_add:
- {
- reloc_howto_type *reloc_howto;
- int size;
- unsigned int bfd_reloc
- = (insn.id == BPF_INSN_CALL
- ? BFD_RELOC_BPF_DISPCALL32
- : BFD_RELOC_BPF_DISP32);
-
- reloc_howto = bfd_reloc_type_lookup (stdoutput, bfd_reloc);
- if (!reloc_howto)
- abort ();
-
- size = bfd_get_reloc_size (reloc_howto);
-
- fix_new_exp (frag_now, this_frag - frag_now->fr_literal,
- size, &insn.disp32, reloc_howto->pc_relative,
- bfd_reloc);
- break;
- }
- default:
- abort ();
- }
- }
-
- if (insn.has_offset16)
- {
- switch (insn.offset16.X_op)
- {
- case O_constant:
- {
- uint32_t offset16 = insn.offset16.X_add_number;
-
- if (target_big_endian)
- {
- bytes[2] = (offset16 >> 8) & 0xff;
- bytes[3] = offset16 & 0xff;
- }
- else
- {
- bytes[3] = (offset16 >> 8) & 0xff;
- bytes[2] = offset16 & 0xff;
- }
- break;
- }
- case O_symbol:
- case O_subtract:
- case O_add:
- {
- reloc_howto_type *reloc_howto;
- int size;
-
- reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
- if (!reloc_howto)
- abort ();
-
- size = bfd_get_reloc_size (reloc_howto);
-
- fix_new_exp (frag_now, this_frag - frag_now->fr_literal,
- size, &insn.offset16, reloc_howto->pc_relative,
- BFD_RELOC_BPF_DISP16);
- break;
- }
- default:
- abort ();
- }
- }
-
- if (insn.has_disp16)
- {
- switch (insn.disp16.X_op)
- {
- case O_constant:
- {
- uint32_t disp16 = insn.disp16.X_add_number;
-
- if (target_big_endian)
- {
- bytes[2] = (disp16 >> 8) & 0xff;
- bytes[3] = disp16 & 0xff;
- }
- else
- {
- bytes[3] = (disp16 >> 8) & 0xff;
- bytes[2] = disp16 & 0xff;
- }
- break;
- }
- case O_symbol:
- case O_subtract:
- case O_add:
- {
- reloc_howto_type *reloc_howto;
- int size;
-
- reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
- if (!reloc_howto)
- abort ();
+ if (do_relax && insn.is_relaxable)
+ {
+ expressionS *relaxable_exp = NULL;
- size = bfd_get_reloc_size (reloc_howto);
+ if (insn.has_disp16)
+ relaxable_exp = &insn.disp16;
+ else
+ abort ();
- fix_new_exp (frag_now, this_frag - frag_now->fr_literal,
- size, &insn.disp16, reloc_howto->pc_relative,
- BFD_RELOC_BPF_DISP16);
- break;
- }
- default:
- abort ();
- }
- }
-
- /* Emit bytes. */
- for (i = 0; i < insn.size; ++i)
- {
- md_number_to_chars (this_frag, (valueT) bytes[i], 1);
- this_frag += 1;
- }
- }
+ add_relaxed_insn (&insn, relaxable_exp);
+ }
+ else
+ add_fixed_insn (&insn);
/* Emit DWARF2 debugging information. */
dwarf2_emit_insn (insn.size);
diff --git a/gas/doc/c-bpf.texi b/gas/doc/c-bpf.texi
index 868a358..6b43c77 100644
--- a/gas/doc/c-bpf.texi
+++ b/gas/doc/c-bpf.texi
@@ -53,6 +53,10 @@ when assembling. The BPF ISA versions supported are @option{v1} @option{v2}, @o
The value @option{xbpf} can be specified to recognize extra
instructions that are used by GCC for testing purposes. But beware
this is not valid BPF.
+
+@cindex @option{-mno-relax} command-line options, BPF
+@item -mno-relax
+This option tells the assembler to not relax instructions.
@end table
Note that if no endianness option is specified in the command line,
diff --git a/gas/testsuite/gas/bpf/bpf.exp b/gas/testsuite/gas/bpf/bpf.exp
index 1d683d5..6e6a000 100644
--- a/gas/testsuite/gas/bpf/bpf.exp
+++ b/gas/testsuite/gas/bpf/bpf.exp
@@ -40,6 +40,9 @@ if {[istarget bpf*-*-*]} {
run_dump_test indcall-1
run_dump_test indcall-1-pseudoc
+ run_dump_test jump-relax-ja
+ run_dump_test jump-relax-jump
+
# Big-endian BPF tests
run_dump_test call-be
run_dump_test exit-be
@@ -59,4 +62,7 @@ if {[istarget bpf*-*-*]} {
run_dump_test atomic-v1-be
run_dump_test atomic-be
run_dump_test atomic-be-pseudoc
+
+ run_dump_test jump-relax-ja-be
+ run_dump_test jump-relax-jump-be
}
diff --git a/gas/testsuite/gas/bpf/jump-relax-ja-be.d b/gas/testsuite/gas/bpf/jump-relax-ja-be.d
new file mode 100644
index 0000000..08b85a9
--- /dev/null
+++ b/gas/testsuite/gas/bpf/jump-relax-ja-be.d
@@ -0,0 +1,19 @@
+#as: -EB -mdialect=normal
+#objdump: -dr -M dec
+#source: jump-relax-ja.s
+#name: Relaxation of unconditional branch (JA) instructions, big-endian
+
+.*: +file format .*bpf.*
+
+Disassembly of section .text:
+
+0+ <.*>:
+ 0: 05 00 80 00 00 00 00 00 ja -32768
+ 8: 05 00 7f ff 00 00 00 00 ja 32767
+ 10: 05 00 ff fd 00 00 00 00 ja -3
+ 18: 05 00 00 00 00 00 00 00 ja 0
+ 18: R_BPF_GNU_64_16 undefined
+ 20: 06 00 00 00 ff ff 7f ff jal -32769
+ 28: 06 00 00 00 00 00 80 00 jal 32768
+ 30: 06 00 00 00 00 00 80 01 jal 32769
+ 38: 06 00 00 00 00 00 80 01 jal 32769
diff --git a/gas/testsuite/gas/bpf/jump-relax-ja.d b/gas/testsuite/gas/bpf/jump-relax-ja.d
new file mode 100644
index 0000000..6b50973
--- /dev/null
+++ b/gas/testsuite/gas/bpf/jump-relax-ja.d
@@ -0,0 +1,19 @@
+#as: -EL -mdialect=normal
+#objdump: -dr -M dec
+#source: jump-relax-ja.s
+#name: Relaxation of unconditional branch (JA) instructions
+
+.*: +file format .*bpf.*
+
+Disassembly of section .text:
+
+0+ <.*>:
+ 0: 05 00 00 80 00 00 00 00 ja -32768
+ 8: 05 00 ff 7f 00 00 00 00 ja 32767
+ 10: 05 00 fd ff 00 00 00 00 ja -3
+ 18: 05 00 00 00 00 00 00 00 ja 0
+ 18: R_BPF_GNU_64_16 undefined
+ 20: 06 00 00 00 ff 7f ff ff jal -32769
+ 28: 06 00 00 00 00 80 00 00 jal 32768
+ 30: 06 00 00 00 01 80 00 00 jal 32769
+ 38: 06 00 00 00 01 80 00 00 jal 32769
diff --git a/gas/testsuite/gas/bpf/jump-relax-ja.s b/gas/testsuite/gas/bpf/jump-relax-ja.s
new file mode 100644
index 0000000..8be3d7a
--- /dev/null
+++ b/gas/testsuite/gas/bpf/jump-relax-ja.s
@@ -0,0 +1,20 @@
+ ;; The following two instructions have constant targets that
+ ;; fix in the JA 16-bit signed displacement operand. These
+ ;; are not relaxed.
+1: ja -32768
+ ja 32767
+ ;; The following instruction refers to a defined symbol that
+ ;; is on reach, so it should not be relaxed.
+ ja 1b
+ ;; The following instruction has an undefined symbol as a
+ ;; target. It is not to be relaxed.
+ ja undefined + 10
+ ;; The following instructions are relaxed to JAL instructions
+ ;; so they can fit their displacements.
+ ja -32769
+ ja 32768
+ ;; The following instructions refer to a defined symbol that
+ ;; is not on reach. They shall be relaxed to a JAL.
+ ja tail
+ tail = .text + 262160
+ ja tail
diff --git a/gas/testsuite/gas/bpf/jump-relax-jump.d b/gas/testsuite/gas/bpf/jump-relax-jump.d
new file mode 100644
index 0000000..cd46ea7
--- /dev/null
+++ b/gas/testsuite/gas/bpf/jump-relax-jump.d
@@ -0,0 +1,25 @@
+#as: -EL -mdialect=normal
+#objdump: -dr -M dec
+#source: jump-relax-jump.s
+#name: Relaxation of conditional branch instructions
+
+.*: +file format .*bpf.*
+
+Disassembly of section .text:
+
+0+ <.*>:
+ 0: 1d 21 00 80 00 00 00 00 jeq %r1,%r2,-32768
+ 8: ad 21 ff 7f 00 00 00 00 jlt %r1,%r2,32767
+ 10: bd 21 fd ff 00 00 00 00 jle %r1,%r2,-3
+ 18: 3d 21 01 00 00 00 00 00 jge %r1,%r2,1
+ 20: 05 00 01 00 00 00 00 00 ja 1
+ 28: 06 00 00 00 ff 7f ff ff jal -32769
+ 30: 2d 21 01 00 00 00 00 00 jgt %r1,%r2,1
+ 38: 05 00 01 00 00 00 00 00 ja 1
+ 40: 06 00 00 00 00 80 00 00 jal 32768
+ 48: 1d 21 01 00 00 00 00 00 jeq %r1,%r2,1
+ 50: 05 00 01 00 00 00 00 00 ja 1
+ 58: 06 00 00 00 01 80 00 00 jal 32769
+ 60: 2d 21 01 00 00 00 00 00 jgt %r1,%r2,1
+ 68: 05 00 01 00 00 00 00 00 ja 1
+ 70: 06 00 00 00 01 80 00 00 jal 32769
diff --git a/gas/testsuite/gas/bpf/jump-relax-jump.s b/gas/testsuite/gas/bpf/jump-relax-jump.s
new file mode 100644
index 0000000..dabbab8
--- /dev/null
+++ b/gas/testsuite/gas/bpf/jump-relax-jump.s
@@ -0,0 +1,17 @@
+ ;; The following two instructions have constant targets that
+ ;; fix in the jump 16-bit signed displacement operand.
+1: jeq %r1, %r2, -32768
+ jlt %r1, %r2, 32767
+ ;; The following instruction refers to a defined symbol that
+ ;; is on reach, so it should not be relaxed.
+ jle %r1, %r2, 1b
+ ;; The following instructions are relaxed to sequences
+ ;; involving unconditional jumps, so they can fi their
+ ;; displacements.
+ jge %r1, %r2, -32769
+ jgt %r1, %r2, 32768
+ ;; The following instructions refer to a defined symbol that
+ ;; is not on reach. They shall be relaxed.
+ jeq %r1, %r2, tail
+ tail = .text + 262160
+ jgt %r1, %r2, tail