[PATCH v4 4/4] bpf: diagnose instructions above the selected ISA spec

Vineet Gupta vineet.gupta@linux.dev
Tue Sep 8 06:29:40 GMT 2026


Using an instruction that exists but is not available in the selected ISA
spec produces a misleading diagnostic:

  $ as -mdialect=pseudoc -misa-spec=v1 <<< 'gotol +1'
  Error: unrecognized instruction `gotol +1'
  Error: expected white space, got 'l +1'

`gotol' is recognized; it just requires v4.  The second line is noise: with
no template matching, md_assemble reports the error of whichever template
got furthest, which here is the unrelated `goto%w%d16'.

md_assemble skips templates whose version exceeds isa_spec, so an
instruction that is only spelled by such a template can never be told apart
from a typo.  Attempt those templates too, but only for the diagnostic: if
one matches the whole statement, remember it and keep scanning, since a
template within the ISA spec may still match.  Only when nothing usable
matches does the remembered one drive the error:

  Error: `gotol +1' requires ISA spec v4 or later

Parse errors from these extra attempts are not recorded, or they would
displace the diagnostics for genuinely unrecognized input.  brkpt is worded
separately, as xbpf is a superset of the BPF specs rather than a later
version of them.

gas/
	* config/tc-bpf.c (bpf_version_name): New function.
	(discard_deferred_symbols): New function, factored out of...
	(parse_error): ...here.  Do not record a message while
	matching_gated_insn.
	(matching_gated_insn): New variable.
	(md_assemble): Attempt templates above the selected ISA spec and
	report the ISA spec they require when nothing else matches.
	* testsuite/gas/bpf/isa-spec-gated.s: New test.
	* testsuite/gas/bpf/isa-spec-gated.d: New test.
	* testsuite/gas/bpf/isa-spec-gated.l: New test.
	* testsuite/gas/bpf/isa-spec-gated-pseudoc.s: New test.
	* testsuite/gas/bpf/isa-spec-gated-pseudoc.d: New test.
	* testsuite/gas/bpf/isa-spec-gated-pseudoc.l: New test.
	* testsuite/gas/bpf/bpf.exp: Run them.

Signed-off-by: Vineet Gupta <vineet.gupta@linux.dev>
---
 gas/config/tc-bpf.c                           | 92 ++++++++++++++++---
 gas/testsuite/gas/bpf/bpf.exp                 |  2 +
 .../gas/bpf/isa-spec-gated-pseudoc.d          |  4 +
 .../gas/bpf/isa-spec-gated-pseudoc.l          |  5 +
 .../gas/bpf/isa-spec-gated-pseudoc.s          |  8 ++
 gas/testsuite/gas/bpf/isa-spec-gated.d        |  4 +
 gas/testsuite/gas/bpf/isa-spec-gated.l        |  5 +
 gas/testsuite/gas/bpf/isa-spec-gated.s        |  7 ++
 8 files changed, 112 insertions(+), 15 deletions(-)
 create mode 100644 gas/testsuite/gas/bpf/isa-spec-gated-pseudoc.d
 create mode 100644 gas/testsuite/gas/bpf/isa-spec-gated-pseudoc.l
 create mode 100644 gas/testsuite/gas/bpf/isa-spec-gated-pseudoc.s
 create mode 100644 gas/testsuite/gas/bpf/isa-spec-gated.d
 create mode 100644 gas/testsuite/gas/bpf/isa-spec-gated.l
 create mode 100644 gas/testsuite/gas/bpf/isa-spec-gated.s

diff --git a/gas/config/tc-bpf.c b/gas/config/tc-bpf.c
index e2472546aa61..d0393472dde4 100644
--- a/gas/config/tc-bpf.c
+++ b/gas/config/tc-bpf.c
@@ -160,6 +160,22 @@ static int do_relax = 1;
 
 static int isa_spec = BPF_V4;
 
+/* Return the name of ISA spec VERSION, as spelled in -misa-spec=.  */
+
+static const char *
+bpf_version_name (int version)
+{
+  switch (version)
+    {
+    case BPF_V1: return "v1";
+    case BPF_V2: return "v2";
+    case BPF_V3: return "v3";
+    case BPF_V4: return "v4";
+    case BPF_XBPF: return "xbpf";
+    default: return "unknown";
+    }
+}
+
 /* The assembler supports two different dialects: "normal" syntax and
    "pseudoc" syntax.  The dialect to use can be configured using
    command-line options.  */
@@ -1401,15 +1417,36 @@ bpf_parse_name (const char *name, expressionS *exp, enum expr_mode mode)
   return true;
 }
 
+/* Discard the symbols deferred by a parse that we are not committing to.
+   They may potentially be reused in the future from the orphan list.  */
+
+static void
+discard_deferred_symbols (void)
+{
+  while (deferred_sym_rootP)
+    {
+      symbolS *sym = deferred_sym_rootP;
+      symbol_remove (sym, &deferred_sym_rootP, &deferred_sym_lastP);
+      symbol_append (sym, orphan_sym_lastP, &orphan_sym_rootP,
+		     &orphan_sym_lastP);
+    }
+}
+
 /* Collect a parse error message.  */
 
 static int partial_match_length = 0;
 static char *errmsg = NULL;
 
+/* Set while attempting to match a template that is not available in the
+   selected ISA spec.  Such templates are tried only to improve diagnostics,
+   so their parse errors must not compete with those of the templates that
+   are actually available.  */
+static bool matching_gated_insn = false;
+
 static void
 parse_error (int length, const char *fmt, ...)
 {
-  if (length > partial_match_length)
+  if (!matching_gated_insn && length > partial_match_length)
     {
       va_list args;
 
@@ -1420,15 +1457,7 @@ parse_error (int length, const char *fmt, ...)
       partial_match_length = length;
     }
 
-  /* Discard deferred symbols from the failed parse.  They may potentially
-     be reused in the future from the orphan list.  */
-  while (deferred_sym_rootP)
-    {
-      symbolS *sym = deferred_sym_rootP;
-      symbol_remove (sym, &deferred_sym_rootP, &deferred_sym_lastP);
-      symbol_append (sym, orphan_sym_lastP, &orphan_sym_rootP,
-		     &orphan_sym_lastP);
-    }
+  discard_deferred_symbols ();
 }
 
 /* Assemble a machine instruction in STR and emit the frags/bytes it
@@ -1460,11 +1489,13 @@ md_assemble (char *str ATTRIBUTE_UNUSED)
   unsigned int idx = 0;
   struct bpf_insn insn;
   const struct bpf_opcode *opcode;
+  const struct bpf_opcode *gated_opcode = NULL;
 
   /* Initialize the global diagnostic variables.  See the parse_error
      function above.  */
   partial_match_length = 0;
   errmsg = NULL;
+  matching_gated_insn = false;
   g_curr_insn_str = str;
 
 #define PARSE_ERROR(...) parse_error (s > str ? s - str : 0, __VA_ARGS__)
@@ -1476,9 +1507,14 @@ md_assemble (char *str ATTRIBUTE_UNUSED)
       const char *template
         = (asm_dialect == DIALECT_PSEUDOC ? opcode->pseudoc : opcode->normal);
 
-      /* Do not try to match opcodes with a higher version than the
-         selected ISA spec.  */
-      if (opcode->version > isa_spec)
+      /* Opcodes with a higher version than the selected ISA spec cannot be
+         used, but still try to match them: if one matches we can tell the
+         user which ISA spec the instruction needs, rather than leaving them
+         with a parse error for whatever unrelated template happened to get
+         furthest.  Remember the first such match in GATED_OPCODE and keep
+         looking for a usable template.  */
+      matching_gated_insn = (opcode->version > isa_spec);
+      if (matching_gated_insn && gated_opcode != NULL)
         continue;
 
       memset (&insn, 0, sizeof (struct bpf_insn));
@@ -1722,16 +1758,42 @@ md_assemble (char *str ATTRIBUTE_UNUSED)
           while (is_whitespace (*s))
             s += 1;
           if (is_end_of_stmt (*s))
-            /* We parsed an instruction successfully.  */
-            break;
+            {
+              if (!matching_gated_insn)
+                /* We parsed an instruction successfully.  */
+                break;
+
+              /* The instruction exists, but not in the selected ISA spec.
+                 Remember it for the diagnostic below and carry on: some
+                 other template may still match within the ISA spec.  */
+              gated_opcode = opcode;
+              discard_deferred_symbols ();
+              continue;
+            }
           PARSE_ERROR ("extra junk at end of line");
         }
     }
 
+  matching_gated_insn = false;
+
   /* Mark that we are no longer parsing an instruction, bpf_parse_name does
      not interfere with symbols in e.g. assembler directives.  */
   parsing_insn_operands = false;
 
+  if (opcode == NULL && gated_opcode != NULL)
+    {
+      /* XBPF is a superset of the BPF specs rather than a later version of
+         them, so `or later' would be misleading for its own instructions.  */
+      if (gated_opcode->version == BPF_XBPF)
+        as_bad (_("`%s' requires ISA spec xbpf"), str);
+      else
+        as_bad (_("`%s' requires ISA spec %s or later"),
+                str, bpf_version_name (gated_opcode->version));
+      free (errmsg);
+      errmsg = NULL;
+      return;
+    }
+
   if (opcode == NULL)
     {
       as_bad (_("unrecognized instruction `%s'"), str);
diff --git a/gas/testsuite/gas/bpf/bpf.exp b/gas/testsuite/gas/bpf/bpf.exp
index 9884b36678c2..055d957e916f 100644
--- a/gas/testsuite/gas/bpf/bpf.exp
+++ b/gas/testsuite/gas/bpf/bpf.exp
@@ -40,6 +40,8 @@ if {[istarget bpf*-*-*]} {
     run_dump_test jump32-pseudoc
     run_dump_test jump-gotol-signed-pseudoc
     run_dump_test jump-gotol-cond-pseudoc
+    run_dump_test isa-spec-gated
+    run_dump_test isa-spec-gated-pseudoc
     run_dump_test atomic-v1
     run_dump_test atomic
     run_dump_test atomic-pseudoc
diff --git a/gas/testsuite/gas/bpf/isa-spec-gated-pseudoc.d b/gas/testsuite/gas/bpf/isa-spec-gated-pseudoc.d
new file mode 100644
index 000000000000..7f2bb3eb32b9
--- /dev/null
+++ b/gas/testsuite/gas/bpf/isa-spec-gated-pseudoc.d
@@ -0,0 +1,4 @@
+#as: -EL -mdialect=pseudoc -misa-spec=v1
+#source: isa-spec-gated-pseudoc.s
+#error_output: isa-spec-gated-pseudoc.l
+#name: eBPF instructions above the selected ISA spec, pseudoc syntax
diff --git a/gas/testsuite/gas/bpf/isa-spec-gated-pseudoc.l b/gas/testsuite/gas/bpf/isa-spec-gated-pseudoc.l
new file mode 100644
index 000000000000..0e9b93f6d788
--- /dev/null
+++ b/gas/testsuite/gas/bpf/isa-spec-gated-pseudoc.l
@@ -0,0 +1,5 @@
+.*: Assembler messages:
+.*:5: Error: `gotol \+1' requires ISA spec v4 or later
+.*:6: Error: `r1 =bswap16 r1' requires ISA spec v4 or later
+.*:7: Error: `r1 s/=r2' requires ISA spec v4 or later
+.*:8: Error: `if w1==w2 goto 1' requires ISA spec v3 or later
diff --git a/gas/testsuite/gas/bpf/isa-spec-gated-pseudoc.s b/gas/testsuite/gas/bpf/isa-spec-gated-pseudoc.s
new file mode 100644
index 000000000000..c17aa0678d59
--- /dev/null
+++ b/gas/testsuite/gas/bpf/isa-spec-gated-pseudoc.s
@@ -0,0 +1,8 @@
+        # Likewise for the pseudo-C dialect.  PR gas/34558: `gotol' below v4
+        # used to assemble silently as `goto' plus a relocation against an
+        # undefined symbol `l'.
+        .text
+        gotol +1
+        r1 = bswap16 r1
+        r1 s/= r2
+        if w1 == w2 goto 1
diff --git a/gas/testsuite/gas/bpf/isa-spec-gated.d b/gas/testsuite/gas/bpf/isa-spec-gated.d
new file mode 100644
index 000000000000..137e868bae99
--- /dev/null
+++ b/gas/testsuite/gas/bpf/isa-spec-gated.d
@@ -0,0 +1,4 @@
+#as: -EL -misa-spec=v1
+#source: isa-spec-gated.s
+#error_output: isa-spec-gated.l
+#name: eBPF instructions above the selected ISA spec, normal syntax
diff --git a/gas/testsuite/gas/bpf/isa-spec-gated.l b/gas/testsuite/gas/bpf/isa-spec-gated.l
new file mode 100644
index 000000000000..5a65d79ff998
--- /dev/null
+++ b/gas/testsuite/gas/bpf/isa-spec-gated.l
@@ -0,0 +1,5 @@
+.*: Assembler messages:
+.*:4: Error: `jal 1' requires ISA spec v4 or later
+.*:5: Error: `movs %r1,%r2,8' requires ISA spec v4 or later
+.*:6: Error: `aadd \[%r1\+8\],%r2' requires ISA spec v3 or later
+.*:7: Error: `jeq32 %r1,%r2,1' requires ISA spec v3 or later
diff --git a/gas/testsuite/gas/bpf/isa-spec-gated.s b/gas/testsuite/gas/bpf/isa-spec-gated.s
new file mode 100644
index 000000000000..acc17dea3341
--- /dev/null
+++ b/gas/testsuite/gas/bpf/isa-spec-gated.s
@@ -0,0 +1,7 @@
+        # Instructions that exist, but not in the selected ISA spec, must be
+        # diagnosed as such rather than as a generic parse failure.
+        .text
+        jal 1
+        movs %r1, %r2, 8
+        aadd [%r1+8], %r2
+        jeq32 %r1, %r2, 1
-- 
2.53.0-Meta



More information about the Binutils mailing list