From: Jose E. Marchesi Date: Thu, 27 Jul 2023 16:17:35 +0000 (+0200) Subject: bpf: gas: support relaxation of V4 jump instructions X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=249d4715e41061b6bd2d26df20ae274e6478f972;p=binutils-gdb.git 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 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. --- diff --git a/gas/ChangeLog b/gas/ChangeLog index 147c20c460d..1f3735d70af 100644 --- a/gas/ChangeLog +++ b/gas/ChangeLog @@ -1,3 +1,39 @@ +2023-07-28 Jose E. Marchesi + + 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 * 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 faa809c05a2..969116bc5ad 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 868a358cf54..6b43c77d5a0 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 1d683d55215..6e6a0003e17 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 00000000000..08b85a97b45 --- /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 00000000000..6b50973915d --- /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 00000000000..8be3d7a4288 --- /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 00000000000..cd46ea7024e --- /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 00000000000..dabbab84f7c --- /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