]> git.ipfire.org Git - thirdparty/valgrind.git/commitdiff
s390: The 2nd coming of disasm-test
authorFlorian Krohm <flo2030@eich-krohm.de>
Wed, 4 Feb 2026 16:03:30 +0000 (16:03 +0000)
committerFlorian Krohm <flo2030@eich-krohm.de>
Wed, 4 Feb 2026 16:03:30 +0000 (16:03 +0000)
With the advent of objdump-based disassembly there is no need anymore to
ensure that the disassembled insns out of valgrind match those from objdump.
This is now correct by construction.

In this patch the disasm-test functionality is changed to ensure that all
expected specification exceptions as specified in the Principles of
Operations are detected and no unexpected specification exceptions are found.

For a given opcode 2 sets of testcases will now be generated:
- one where every insn causes a specification exception
  This catches missed specification exceptions.
- one where no insn causes a specification exception
  This catches unexpected specification exceptions.

Changes:
Remove command line options --generate and --verify. Those were useful
in the early stages of development but not anymore.
Remove command line options --show-spec-exc and --no-show-miscompares
which are obsolete now.
Replace command line option --check-prereq with --check-march=ARCH.
Remove command line option --all-except-exrl. Add --exclude option which
is more general.
Add command line options --spec-exc and --no-spec-exc.
Remove functions check_objdump and disasm_same.
From verify_stats remove num_mismatch and num_verified members.
Rename verify_stats --> test_stats and add num_generated member.
Rename verify_disassembly --> verify_spec_exceptions.
No longer write .vex file. Write .spec-exc file instead.
Factor out functions run_opcode and choose_int_and_iterate.
New functions asm_detects_spec_exc and insn_bytes_as_string.

Opcode table in opcode.c:
- Remove all constraints that do not cause a specification exception.
- Change modelling of the "Rotate and ...." opcodes. Previously the
  i3, i4 and i5 fields are modelled as masks. That was a work-around in
  order to be able to specify interesting values and is no longer
  needed. Model those fields as integers instead.

Update disasm-test.vgtest.
Update README.
Copyright year updated.

12 files changed:
none/tests/s390x/disasm-test/README
none/tests/s390x/disasm-test/disasm-test.post.exp
none/tests/s390x/disasm-test/disasm-test.vgtest
none/tests/s390x/disasm-test/generate.c
none/tests/s390x/disasm-test/main.c
none/tests/s390x/disasm-test/main.h
none/tests/s390x/disasm-test/objdump.c
none/tests/s390x/disasm-test/objdump.h
none/tests/s390x/disasm-test/opcode.c
none/tests/s390x/disasm-test/verify.c
none/tests/s390x/disasm-test/vex.c
none/tests/s390x/disasm-test/vex.h

index ef8904323f9c6488ad8aac9a1eea061b8b4e0756..56a1391253093c639e7d814f6f0792736f3dde01 100644 (file)
@@ -1,8 +1,9 @@
 disasm-test
 
-The purpose of this program is to ensure that the disassembly as
-generated by s390_disasm matches what objdump -d produces for any
-given insn. As such the program runs as part of "make regtest".
+The purpose of this program is to ensure that
+(a) specification exceptions caused by invalid insn encodings as
+    described in Principles of Operations are detected
+(b) no unexpected specification exceptions are detected
 
 
 How it works
@@ -11,11 +12,9 @@ How it works
 2) Compile the C file.
 3) Run objdump -d on the object file and capture the result in a file.
    This file will be referred to as "the objdump file".
-4) Read the objdump file, extract the insn bytes and disassembly.
+4) Read the objdump file, extract the insn bytes.
 5) Feed the so-extracted insn bytes into VEX's decode-and-irgen
-   machinery with disassembly (= tracing frontend) being enabled.
-6) Intercept the so-disassembled text and compare it with the
-   disassembly in the objdump file.
+   machinery and observe whether specification exceptions are generated.
 
 
 Invocation
@@ -26,19 +25,6 @@ disasm-test --all
   For all opcodes known to disasm-test, generate testcases and
   verify the disassembly.
 
-disasm-test --all-except-exrl
-  As the option name suggests. This is how disasm-test is invoked
-  during regression testing.
-
-disasm-test --generate OPCODE_NAMEs
-  For each specified opcode, generate a C file with testcases,
-  compile it and create the objdump file.
-  Useful when adding new opcodes.
-
-disasm-test --verify OBJDUMP_FILEs
-  For each specified objdump file, verify that the disassembly via VEX
-  matches. Useful when adding new opcodes.
-
 disasm-test --run OPCODE_NAMEs
   Combines --generate and --verify. Useful when adding new opcodes.
 
@@ -52,6 +38,9 @@ Other non-debugging options
   Write out test generation summary. This option is only observed in
   combination with --all.
 
+--exclude OPCODE_NAMEs
+  Do not generate testcases for the names opcodes.
+
 --gcc=/path/to/gcc
   Specify which GCC to use. Default is: gcc on $PATH.
 
@@ -65,14 +54,11 @@ Other non-debugging options
   Keep generated files: .c file with testcases, object file, objdump
   file, and .vex file
 
---show-exc-spec
-  Show generated insns that cause specification exceptions.
-  Because insns causing specification exceptions are typically accepted
-  by GCC and objdump an objdump file may contain them. But comparing
-  them is pointless.
+--spec-exc
+  Generate only those testcases that cause specification exceptions.
 
---no-show-miscompares
-  Do not show miscomparing insns.
+--no-spec-exc
+  Generate only those testcases that do not cause specification exceptions.
 
 
 Debugging options
@@ -105,7 +91,8 @@ Why are we picking these values? Boundary cases are *always*
 interesting, as is 0. '1' is picked because it is odd and '2' is picked
 because it is even.
 
-Note: if an opcode has more than one GPR (without specification) choose
+FIXME: this is obsolete and the code should be changed accordingly
+Note: if an opcode has more than one GPR (without constraint) choose
 different registers. We do this because we want to catch bugs due to
 mixed up registers.
 E.g. If the testcase is "brxh r1,r2,8" and s390_disasm produces
@@ -116,58 +103,41 @@ need to be different. The same applies to ARs, FPRs and VRs.
 Adding a new opcode
 -------------------
 See extensive documentation at the top of file opcode.c
-Any opcode can be added. It is not necessary for the opcode to have
-extended mnemonics.
 
 
 Integration into regression testing
 -----------------------------------
 1) Observe the exit code
 
-   disasm-test --all --no-show-miscompares
-
-   There will be no output to stdout and stderr. If there are no
-   miscompares in the disassembly the exit code will be 0. Otherwise,
-   it will be 1.
-
-2) Observe stderr
-
    disasm-test --all
 
-   Miscomparing disassembly will be written to stderr. Exit code as
-   described above.
+   There will be no output to stdout and stderr. If there all expected
+   specification exceptions and no unexpected ones are found the exit
+   code will be 0. Otherwise, it will be 1.
 
-3) Observe testcase summary
+2) Observe testcase summary
 
-   disasm-test --all --no-show-miscompares --summary
+   disasm-test --all --summary
 
-   Will write information about #testcases as well as failing ones
-   to stdout. Exit code as described above.
+   Will write information about #testcases and observed specification
+   exceptions to stdout. Exit code as described above.
 
 
 Status
 ------
-All opcodes which are implemented in valgrind are considered.
+All opcodes described in 13th edition of "Principles of Operations"
+which are implemented in valgrind are considered.
 
 
 NOTES
 -----
-(1) Testcase generation for the "Rotate and ..." family of opcodes needs
-    to be improved. Several interesting cases involving the T- and Z-bit
-    are not considered.
-
-(2) Different versions of objdump produce slightly different disassembly.
-    In 2.38 base register 0 is written as "%r0". In current binutils git
-    that register will be output as "0".
-
-(3) Generated testcases may cause specification exceptions. This
-    happens iff a constraint involves more than one opcode field.
+(1) Not all constraints that cause specification exceptions can be
+    expressed in opcode.c. This happens iff a constraint involves more
+    than one opcode field.
     E.g.: for the VREP opcode the M4 value determines which I2 values
-    are allowed. This constraint cannot be expressed. However, the
-    disassembly for such insns is not compared. Use --show-spec-exc
-    to show those insns.
+    are allowed. This constraint cannot be expressed.
 
-(4) In s390_decode_and_irgen the code peeks past the current insn:
+(2) In s390_decode_and_irgen the code peeks past the current insn:
 
     /* If next instruction is execute, stop here */
     if (dis_res->whatNext == Dis_Continue &&
@@ -180,9 +150,9 @@ NOTES
     Perhaps disable the peek-ahead when inside disasm-test by means
     of some global variable? Not pretty either, but explicit.
 
-(5) For D20XB and D12XB operands add a test with B == X and B != 0
+(3) For D20XB and D12XB operands add a test with B == X and B != 0
     Not exactly easy to do.
 
-(6) For objdump-based disassembly the offset in the EXRL insn is expected
-    to denote a decodable insn. This is obviously not the case here and
-    therefore causing a miscompare.
+(4) The offset in the EXRL insn is expected to denote a decodable insn.
+    This is obviously not the case here and therefore that opcode is
+    excluded.
index 2e0d1b893ddc69a87efb5d3435f9cad6197f1bb1..68c0b9adb6e1d61f273b67be0352647b7526c9c2 100644 (file)
@@ -1,4 +1,6 @@
-Total: 148757 tests generated
-Total: 148665 insns verified
-Total:      0 disassembly mismatches
-Total:     92 specification exceptions
+Looking for missed specification exceptions
+Total:  42131 tests generated
+Total:  42131 specification exceptions
+Looking for unexpected specification exceptions
+Total: 153637 tests generated
+Total:      0 specification exceptions
index e45b61ceb8c667def0e711b85baec188a57df391..807250ba13174a3316dc8353d4bc17257a85159b 100644 (file)
@@ -2,9 +2,25 @@
 # We do not want disasm-test to be run under the auspices of valgrind.
 # Therefore the real test here is run in the "post" command.
 #
-prereq: ./disasm-test --check-prereq
+# Excluded opcodes:
+# exrl - Offset is expected to denote a decodable insn; no such scaffolding
+# kma  - cannot express constraint that r3 must be different from r1 and r2
+# The following need to be fixed
+# vmsl - m6 does not cause a spec exc.
+# vgef - runs into an assert instead of spec. exc.
+# vgeg - runs into an assert instead of spec. exc.
+# vscef - runs into an assert instead of spec. exc.
+# vsceg - runs into an assert instead of spec. exc.
+# vstef - runs into an assert instead of spec. exc.
+# popcnt - s390_insn_assert is wrong; just "may not operate correctly"
+# srnmb - The compile-time checkable constraint cannot be expressed in opcode.c
+#         But the s390_insn_assert is wrong nevertheless because bits [0:55] are ignored.
+#         Need to mask d2 before checking  (d2 & 0xff)
+# vster - should be    s390_insn_assert(m3 >= 1 && m3 <= 3);   // 3 not 4
+
+prereq: ./disasm-test --check-march=arch14
 prog: /bin/true
-post: ./disasm-test --all-except-exrl --summary
+post: ./disasm-test --gcc-flags=-march=arch14 --all --exclude exrl kma vmsl vgef vgeg vscef vsceg vstef popcnt srnmb vster --summary
 vgopts: -q
 stderr_filter:
 stderrB_filter:
index abde6bfef9ebbe6780a5438c6ee94ea72b856e90..d42983ff8c2e5c6984c294e4738940cfe23a2349 100644 (file)
@@ -4,7 +4,7 @@
    This file is part of Valgrind, a dynamic binary instrumentation
    framework.
 
-   Copyright (C) 2024-2025  Florian Krohm
+   Copyright (C) 2024-2026  Florian Krohm
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
@@ -108,11 +108,6 @@ random_reg(opnd_t reg_kind, int r0_allowed)
 }
 
 
-#if 0
-/* These functions are currently unused. But may become useful in
-   alternate test generation strategies that use random values instead
-   of hardwired interesting ones. */
-
 static unsigned
 random_uint(unsigned num_bits)
 {
@@ -141,20 +136,6 @@ random_sint(unsigned num_bits)
 }
 
 
-static unsigned
-d12_value(void)
-{
-   return random_uint(12);
-}
-
-
-static int
-d20_value(void)
-{
-   return random_sint(20);
-}
-
-
 static unsigned
 uint_value(unsigned num_bits)
 {
@@ -171,7 +152,7 @@ sint_value(unsigned num_bits)
       fatal("integer operand > 32 bits not supported\n");
    return random_sint(num_bits);
 }
-#endif
+
 
 /* MASK is a bitvector. For an e.g. GPR rk the k'th bit will be set. The
    function returns a register number which has not been used and
@@ -193,6 +174,20 @@ unique_reg(opnd_t reg_kind, unsigned regno, unsigned *mask)
 }
 
 
+/* Is VALUE an allowed value for OPERAND (which has a constraint) */
+static int
+is_allowed_value(long long value, const opnd *operand)
+{
+   int num_val = operand->allowed_values[0];
+
+   for (int i = 1; i <= num_val; ++i)
+      if (value == operand->allowed_values[i])
+         return 1;
+
+   return 0;
+}
+
+
 /* Field */
 typedef struct {
    const opnd *operand;  // the operand to which this field belongs
@@ -203,11 +198,15 @@ typedef struct {
 } field;
 
 static void choose_reg_and_iterate(FILE *, const opcode *, const opnd *,
-                                   field [], unsigned);
+                                   field [], unsigned, int);
+static void choose_int_and_iterate(FILE *, const opcode *, const opnd *,
+                                   field [], unsigned, const long long *,
+                                   unsigned, int);
 
 /* Write out a single ASM statement for OPC. */
 static void
-write_asm_stmt(FILE *fp, const opcode *opc, const field fields[])
+write_asm_stmt(FILE *fp, const opcode *opc, const field fields[],
+               int gen_spec_exc_tests)
 {
    fprintf(fp, "  asm volatile(\"%s ", opc->name);
 
@@ -293,7 +292,8 @@ write_asm_stmt(FILE *fp, const opcode *opc, const field fields[])
          assert(0);
       }
    }
-   fprintf(fp, "\");\n");
+   fprintf(fp, "\");");
+   fprintf(fp, "   // %s spec. exception\n", gen_spec_exc_tests ? " " : "no");
 
    ++num_tests;
 }
@@ -302,11 +302,12 @@ write_asm_stmt(FILE *fp, const opcode *opc, const field fields[])
 /* IX identifies the element of the FIELDS array to which a value
    will be assigned in this iteration. */
 static void
-iterate(FILE *fp, const opcode *opc, field fields[], unsigned ix)
+iterate(FILE *fp, const opcode *opc, field fields[], unsigned ix,
+        int gen_spec_exc_tests)
 {
    /* All fields are assigned. Write out the asm stmt */
    if (ix == opc->num_fields) {
-      write_asm_stmt(fp, opc, fields);
+      write_asm_stmt(fp, opc, fields, gen_spec_exc_tests);
       return;
    }
 
@@ -318,19 +319,19 @@ iterate(FILE *fp, const opcode *opc, field fields[], unsigned ix)
       if (operand->name[0] == 'b' || operand->name[0] == 'x') {
          /* Choose r0 */
          f->assigned_value = 0;
-         iterate(fp, opc, fields, ix + 1);
+         iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
          /* Choose any GPR other than r0 */
          f->assigned_value = random_reg(operand->kind, /* r0_allowed */ 0);
-         iterate(fp, opc, fields, ix + 1);
+         iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
       } else {
-         choose_reg_and_iterate(fp, opc, operand, fields, ix);
+         choose_reg_and_iterate(fp, opc, operand, fields, ix, gen_spec_exc_tests);
       }
       break;
 
    case OPND_AR:
    case OPND_FPR:
    case OPND_VR:
-      choose_reg_and_iterate(fp, opc, operand, fields, ix);
+      choose_reg_and_iterate(fp, opc, operand, fields, ix, gen_spec_exc_tests);
       break;
 
    case OPND_D12B:
@@ -343,7 +344,7 @@ iterate(FILE *fp, const opcode *opc, field fields[], unsigned ix)
 
          for (int i = 0; i < sizeof values / sizeof *values; ++i) {
             f->assigned_value = values[i];
-            iterate(fp, opc, fields, ix + 1);
+            iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
          }
       } else if (f->is_length) {
          /* Choose these interesting values */
@@ -351,20 +352,20 @@ iterate(FILE *fp, const opcode *opc, field fields[], unsigned ix)
 
          for (int i = 0; i < sizeof values / sizeof *values; ++i) {
             f->assigned_value = values[i];
-            iterate(fp, opc, fields, ix + 1);
+            iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
          }
       } else if (f->is_vr) {
          /* v0 is not special AFAICT */
          f->assigned_value = random_reg(OPND_VR, /* r0_allowed */ 11);
-         iterate(fp, opc, fields, ix + 1);
+         iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
       } else {
          /* Base or index register */
          /* Choose r0 */
          f->assigned_value = 0;
-         iterate(fp, opc, fields, ix + 1);
+         iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
          /* Choose any GPR other than r0 */
          f->assigned_value = random_reg(OPND_GPR, /* r0_allowed */ 0);
-         iterate(fp, opc, fields, ix + 1);
+         iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
       }
       break;
 
@@ -378,60 +379,36 @@ iterate(FILE *fp, const opcode *opc, field fields[], unsigned ix)
 
          for (int i = 0; i < sizeof values / sizeof *values; ++i) {
             f->assigned_value = values[i];
-            iterate(fp, opc, fields, ix + 1);
+            iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
          }
       } else {
          /* base or index register */
          f->assigned_value = 0;
-         iterate(fp, opc, fields, ix + 1);
+         iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
          f->assigned_value = random_reg(OPND_GPR, /* r0_allowed */ 0);
-         iterate(fp, opc, fields, ix + 1);
+         iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
       }
       break;
 
    case OPND_SINT:
-   case OPND_PCREL:
-      if (operand->allowed_values == NULL) {
-         /* No constraint: Choose these interesting values */
-         const long long values[] = {
-            0, 1, 2, -1, -2, (1LL << (operand->num_bits - 1)) - 1,
-            -(1LL << (operand->num_bits - 1))
-         };
-
-         for (int i = 0; i < sizeof values / sizeof *values; ++i) {
-            f->assigned_value = values[i];
-            iterate(fp, opc, fields, ix + 1);
-         }
-      } else {
-         /* Constraint. Choose only allowed values */
-         unsigned num_val = operand->allowed_values[0];
-         for (int i = 1; i <= num_val; ++i) {
-            f->assigned_value = operand->allowed_values[i];
-            iterate(fp, opc, fields, ix + 1);
-         }
-      }
+   case OPND_PCREL: {
+      const long long values[] = {
+         0, 1, 2, -1, -2, (1LL << (operand->num_bits - 1)) - 1,
+         -(1LL << (operand->num_bits - 1))
+      };
+      choose_int_and_iterate(fp, opc, operand, fields, ix, values,
+                             sizeof values / sizeof *values, gen_spec_exc_tests);
       break;
+   }
 
-   case OPND_UINT:
-      if (operand->allowed_values == NULL) {
-         /* No constraint: Choose these interesting values */
-         const long long values[] = {
-            0, 1, 2, (1LL << operand->num_bits) - 1
-         };
-
-         for (int i = 0; i < sizeof values / sizeof *values; ++i) {
-            f->assigned_value = values[i];
-            iterate(fp, opc, fields, ix + 1);
-         }
-      } else {
-         /* Constraint. Choose only allowed values */
-         unsigned num_val = operand->allowed_values[0];
-         for (int i = 1; i <= num_val; ++i) {
-            f->assigned_value = operand->allowed_values[i];
-            iterate(fp, opc, fields, ix + 1);
-         }
-      }
+   case OPND_UINT: {
+      const long long values[] = {
+         0, 1, 2, (1LL << operand->num_bits) - 1
+      };
+      choose_int_and_iterate(fp, opc, operand, fields, ix, values,
+                             sizeof values / sizeof *values, gen_spec_exc_tests);
       break;
+   }
 
    case OPND_MASK:
       if (operand->allowed_values == NULL) {
@@ -439,14 +416,32 @@ iterate(FILE *fp, const opcode *opc, field fields[], unsigned ix)
          unsigned maxval = (1u << operand->num_bits) - 1;
          for (int val = 0; val <= maxval; ++val) {
             f->assigned_value = val;
-            iterate(fp, opc, fields, ix + 1);
+            iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
          }
       } else {
-         /* Constraint. Choose only allowed values */
-         unsigned num_val = operand->allowed_values[0];
-         for (int i = 1; i <= num_val; ++i) {
-            f->assigned_value = operand->allowed_values[i];
-            iterate(fp, opc, fields, ix + 1);
+         if (gen_spec_exc_tests) {
+            /* Choose only disallowed values */
+            if (asm_detects_spec_exc(operand)) {
+               /* Pick an allowed value to avoid an asm error message */
+               f->assigned_value = operand->allowed_values[1];
+               iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
+            } else {
+               unsigned maxval = (1 << operand->num_bits) - 1;
+               /* Enumerate all possible disallowed values for the operand */
+               for (int val = 0; val <= maxval; ++val) {
+                  if (! is_allowed_value(val, operand)) {
+                     f->assigned_value = val;
+                     iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
+                  }
+               }
+            }
+         } else {
+            /* Constraint. Choose only allowed values */
+            unsigned num_val = operand->allowed_values[0];
+            for (int i = 1; i <= num_val; ++i) {
+               f->assigned_value = operand->allowed_values[i];
+               iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
+            }
          }
       }
       break;
@@ -459,7 +454,7 @@ iterate(FILE *fp, const opcode *opc, field fields[], unsigned ix)
 
 
 static void
-generate(FILE *fp, const opcode *opc)
+generate(FILE *fp, const opcode *opc, int gen_spec_exc_tests)
 {
    /* Array of opcode fields to which we need to assign values. */
    field fields[opc->num_fields];
@@ -531,12 +526,12 @@ generate(FILE *fp, const opcode *opc)
    }
    assert(ix == opc->num_fields);
 
-   iterate(fp, opc, fields, 0);
+   iterate(fp, opc, fields, 0, gen_spec_exc_tests);
 }
 
 
 unsigned
-generate_tests(const opcode *opc)
+generate_tests(const opcode *opc, int gen_spec_exc_tests)
 {
    srand(42);
 
@@ -545,8 +540,8 @@ generate_tests(const opcode *opc)
 
    num_tests = 0;
 
-   char file[strlen(opc->name) + 3];
-   sprintf(file, "%s.c", opc->name);
+   char file[strlen(opc->name) + 10];  // large enough
+   sprintf(file, "%s-%s.c", opc->name, gen_spec_exc_tests ? "se" : "no-se");
 
    FILE *fp = fopen(file, "w");
    if (fp == NULL) {
@@ -558,18 +553,18 @@ generate_tests(const opcode *opc)
    fprintf(fp, "main(void)\n");
    fprintf(fp, "{\n");
    fprintf(fp, "  asm volatile(\"%s\");\n", MARK);
-   generate(fp, opc);
+   generate(fp, opc, gen_spec_exc_tests);
    fprintf(fp, "  asm volatile(\"%s\");\n", MARK);
    fprintf(fp, "}\n");
    fclose(fp);
 
    if (verbose)
-      printf("...%u testcases generated for '%s'\n", num_tests,
-             opc->name);
+      printf("...%u testcases generated for '%s'\n", num_tests, opc->name);
 
-   run_cmd("%s -c %s %s.c", gcc, gcc_flags, opc->name);
-   run_cmd("%s --disassemble=%s %s.o > %s.dump", objdump, FUNCTION,
-           opc->name, opc->name);
+   const char *ext = gen_spec_exc_tests ? "se" : "no-se";
+   run_cmd("%s -c %s %s", gcc, gcc_flags, file);
+   run_cmd("%s --disassemble=%s %s-%s.o > %s-%s.dump", objdump, FUNCTION,
+           opc->name, ext, opc->name, ext);
 
    return num_tests;
 }
@@ -577,20 +572,87 @@ generate_tests(const opcode *opc)
 
 static void
 choose_reg_and_iterate(FILE *fp, const opcode *opc, const opnd *operand,
-                       field fields[], unsigned ix)
+                       field fields[], unsigned ix, int gen_spec_exc_tests)
 {
    field *f = fields + ix;
 
    if (operand->allowed_values == NULL) {
       /* No constraint. Pick register at random. */
       f->assigned_value = random_reg(operand->kind, /* r0_allowed */ 1);
-      iterate(fp, opc, fields, ix + 1);
+      iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
    } else {
-      /* Constraint. Choose only allowed values */
-      unsigned num_val = operand->allowed_values[0];
-      for (int i = 1; i <= num_val; ++i) {
-         f->assigned_value = operand->allowed_values[i];
-         iterate(fp, opc, fields, ix + 1);
+      if (gen_spec_exc_tests) {
+         /* Choose only disallowed values */
+         if (asm_detects_spec_exc(operand)) {
+            /* Pick an allowed value to avoid an asm error message */
+            f->assigned_value = operand->allowed_values[1];
+            iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
+         } else {
+            unsigned maxval = (1 << operand->num_bits) - 1;
+            /* Enumerate all possible disallowed values for the operand */
+            for (int val = 0; val <= maxval; ++val) {
+               if (! is_allowed_value(val, operand)) {
+                  f->assigned_value = val;
+                  iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
+               }
+            }
+         }
+     } else {
+         /* Choose only allowed values */
+         unsigned num_val = operand->allowed_values[0];
+         for (int i = 1; i <= num_val; ++i) {
+            f->assigned_value = operand->allowed_values[i];
+            iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
+         }
+      }
+   }
+}
+
+
+static void
+choose_int_and_iterate(FILE *fp, const opcode *opc, const opnd *operand,
+                       field fields[], unsigned ix, const long long *values,
+                       unsigned num_values, int gen_spec_exc_tests)
+{
+   field *f = fields + ix;
+
+   if (operand->allowed_values == NULL) {
+      /* No constraint: Choose the passed-in interesting values */
+      for (int i = 0; i < num_values; ++i) {
+         f->assigned_value = values[i];
+         iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
+      }
+   } else {
+      if (gen_spec_exc_tests) {
+         /* Choose only disallowed values */
+         if (operand->num_bits <= 4) {
+            unsigned maxval = (1 << operand->num_bits) - 1;
+            /* Enumerate all possible disallowed values for the operand */
+            for (int val = 0; val <= maxval; ++val) {
+               if (! is_allowed_value(val, operand)) {
+                  f->assigned_value = val;
+                  iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
+               }
+            }
+         } else {
+            /* Choose 4 random values */
+            for (int count = 0; count < 4; ) {
+               long long val =
+                  operand->is_unsigned ? uint_value(operand->num_bits)
+                                       : sint_value(operand->num_bits);
+               if (is_allowed_value(val, operand)) continue;
+               ++count;
+               f->assigned_value = val;
+               iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
+            }
+         }
+      } else {
+         /* Choose only allowed values */
+         unsigned num_val = operand->allowed_values[0];
+         for (int i = 1; i <= num_val; ++i) {
+            f->assigned_value = operand->allowed_values[i];
+            iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests);
+         }
       }
    }
 }
index 6d8f8353b82e435f8b07a26faba08309a70f9ec3..286c9ef4030671ea00bb9610be8bf49404f70c55 100644 (file)
@@ -4,7 +4,7 @@
    This file is part of Valgrind, a dynamic binary instrumentation
    framework.
 
-   Copyright (C) 2024-2025  Florian Krohm
+   Copyright (C) 2024-2026  Florian Krohm
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
@@ -25,7 +25,7 @@
 #include <stddef.h>           // NULL
 #include <stdlib.h>           // exit, malloc
 #include <stdio.h>            // vfprintf
-#include <ctype.h>            // isdigit
+#include <ctype.h>            // isalpha
 #include <stdarg.h>           // va_list
 #include <string.h>           // strchr
 #include <assert.h>           // assert
 #include "vex.h"              // vex_init
 #include "main.h"
 
-int verbose, debug, show_spec_exc, show_miscompares;
+int verbose, debug;
 
 const char *gcc = "gcc";          // path to GCC
 const char *objdump = "objdump";  // path to objdump
 const char *gcc_flags = "-march=arch14";
 
-#define MIN_OBJDUMP_VERSION 2044000  /* 2.44 */
-
 #define CHECK_CLO(x, s) (strncmp(x, s, sizeof s - 1) == 0)
 
 static const char usage[] =
    "Usage:\n\n"
-   "disasm-test --generate OPCODES\n"
-   "    Generate testcases for the given opcodes and prepare objdump files.\n\n"
-   "disasm-test --verify FILES\n"
-   "    Read specified objdump files and compare with VEX disassembly.\n\n"
    "disasm-test --run OPCODES\n"
-   "    Generate testcases for the given opcodes and compare the disassembly.\n\n"
+   "    Generate testcases for the given opcodes and check for missed and unexpected specification exceptions.\n\n"
    "disasm-test --all\n"
-   "    For all opcodes generate testcases and compare the disassembly.\n\n"
+   "    For all opcodes generate testcases and check for missed and unexpected specification exceptions.\n\n"
    "disasm-test --unit-test\n"
    "    Run unit tests. All other command line options are ignored.\n\n"
    "Additional options:\n"
    "    --verbose\n"
    "    --debug\n"
+   "    --spec-exc        - Only generate testcases causing spec. exceptions\n"
+   "    --no-spec-exc     - Do not generate testcases causing spec. exceptions\n"
+   "    --exclude OPCODES - Do not generate testcases for named opcodes\n"
    "    --gcc=/path/to/gcc\n"
    "    --gcc-flags=FLAGS\n"
    "    --objdump=/path/to/objdump\n"
    "    --keep-temp - Do not remove temporary files\n"
    "    --summary   - Output test generation summary (with --all)\n"
    "    --unit-test - Run unit tests\n"
-   "    --show-spec-exc - Show insns causing specification exceptions\n"
-   "    --no-show-miscompares - Do not show disassembly miscompares\n"
-   "    --check-prereq - Check prerequisites (e.g. objdump version)\n"
+   "    --check-march=ARCH - Check whether GCC supports -march=ARCH\n"
    ;
 
-static void remove_temp_files(const char *);
+static int  check_march_level(const char *);
+static void remove_temp_files(const char *, int);
 static int  opcode_has_errors(const opcode *);
-static int  check_objdump(void);
+static int  is_excluded_opcode(const opcode *);
+static int  skip_opcode(const opcode *, int);
+static test_stats run_opcode(opcode *, int);
 
 static int keep_temp = 0;
 static int summary = 0;
+static const char **excluded_opcodes;
+static unsigned num_excluded_opcodes;
 
+#define GEN_SPEC_EXC    1
+#define GEN_NO_SPEC_EXC 2
 
-/* Return code: 0  no disassembly mismatches
-   Return code: 1  at least one disassembly mismatch
 
-   Specification exceptions do not influence the return code. */
+/* Return code: 0  No missed or unexpected specification exceptions
+   Return code: 1  False missed and/or unexpected specification exceptions found */
 int
 main(int argc, char *argv[])
 {
-   int all = 0, verify = 0, generate = 0, unit_test = 0, all_except_exrl = 0;
-   int num_clargs = 0;
-   int run = 0, check_prereq = 0;
-   const char *clargs[argc];
+   int all = 0, unit_test = 0, run = 0;
+   int num_to_run = 0, check_march = 0;
+   int mode = 0;
+   const char *to_run[argc];
+   const char *arch;
 
    assert(sizeof(long long) == 8);
 
@@ -95,39 +97,29 @@ main(int argc, char *argv[])
    setlinebuf(stdout);
    setlinebuf(stderr);
 
-   show_miscompares = 1;
-
    /* Collect options and arguments */
    for (int i = 1; i < argc; ++i) {
       const char *clo = argv[i];
 
-      if (CHECK_CLO(clo, "--verify")) {
-         verify = 1;
-      } else if (CHECK_CLO(clo, "--generate")) {
-         generate = 1;
-      } else if (CHECK_CLO(clo, "--all-except-exrl")) {
-         all = 1;
-         all_except_exrl = 1;
-      } else if (CHECK_CLO(clo, "--all")) {
+      if (CHECK_CLO(clo, "--all")) {
          all = 1;
       } else if (CHECK_CLO(clo, "--verbose")) {
          verbose = 1;
       } else if (CHECK_CLO(clo, "--debug")) {
          debug = 1;
+      } else if (CHECK_CLO(clo, "--spec-exc")) {
+         mode |= GEN_SPEC_EXC;
+      } else if (CHECK_CLO(clo, "--no-spec-exc")) {
+         mode |= GEN_NO_SPEC_EXC;
       } else if (CHECK_CLO(clo, "--summary")) {
          summary = 1;
       } else if (CHECK_CLO(clo, "--unit-test")) {
          unit_test = 1;
-      } else if (CHECK_CLO(clo, "--show-spec-exc")) {
-         show_spec_exc = 1;
-      } else if (CHECK_CLO(clo, "--no-show-miscompares")) {
-         show_miscompares = 0;
       } else if (CHECK_CLO(clo, "--keep-temp")) {
          keep_temp = 1;
-      } else if (CHECK_CLO(clo, "--run")) {
-         run = 1;
-      } else if (CHECK_CLO(clo, "--check-prereq")) {
-         check_prereq = 1;
+      } else if (CHECK_CLO(clo, "--check-march=")) {
+         check_march = 1;
+         arch = strchr(clo, '=') + 1;
       } else if (CHECK_CLO(clo, "--help")) {
          printf("%s\n", usage);
          return 0;
@@ -137,154 +129,273 @@ main(int argc, char *argv[])
          gcc_flags = strchr(clo, '=') + 1;
       } else if (CHECK_CLO(clo, "--objdump=")) {
          objdump = strchr(clo, '=') + 1;
+      } else if (CHECK_CLO(clo, "--run")) {
+         run = 1;
+         num_to_run = 0;
+         int j;
+         for (j = i + 1; j < argc; ++j) {
+            if (! isalpha(argv[j][0]))
+               break;
+            if (get_opcode_by_name(argv[j]) == NULL)
+               error("'%s' is not a recognised opcode\n", argv[j]);
+            else
+               to_run[num_to_run++] = argv[j];
+         }
+         if (num_to_run == 0)
+            error("Missing opcode(s) for --run\n");
+         i = j - 1;
+      } else if (CHECK_CLO(clo, "--exclude")) {
+         num_excluded_opcodes = 0;
+         excluded_opcodes = mallock(argc * sizeof(char *));
+         int j;
+         for (j = i + 1; j < argc; ++j) {
+            if (argv[j][0] == '-')
+               break;
+            excluded_opcodes[num_excluded_opcodes++] = argv[j];
+         }
+         if (num_excluded_opcodes == 0)
+            error("Missing opcode(s) for --exclude\n");
+         i = j - 1;
       } else {
          if (strncmp(clo, "--", 2) == 0)
             fatal("Invalid command line option '%s'\n", clo);
-         clargs[num_clargs++] = clo;
+         else
+            fatal("Excess arguments on command line\n");
       }
    }
 
-   if (check_prereq)
-      return check_objdump();
+   if (check_march)
+      return check_march_level(arch);
 
    /* Check consistency of command line options */
-   if (verify + generate + run + all + unit_test == 0)
-      fatal("One of --verify, --generate, --run, --all, or --unit-test "
-            "is required\n");
-   if (verify + generate + run + all + unit_test != 1)
-      fatal("At most one of --verify, --generate, --run, --all, or "
-            " --unit-test can be given\n");
+   if (run + all + unit_test == 0)
+      fatal("One of --run, --all, or --unit-test is required\n");
+   if (run + all + unit_test != 1)
+      fatal("At most one of --run, --all, or  --unit-test can be given\n");
+
+   /* Nothing specified: look for both false positives and false negatives */
+   if (mode == 0)
+      mode = GEN_SPEC_EXC | GEN_NO_SPEC_EXC;
 
    vex_init();
 
-   if (generate) {
-      if (num_clargs == 0)
-         fatal("Missing opcode name[s]\n");
+   if (run) {
+      int rc = 0;
+
+      if (mode & GEN_SPEC_EXC) {
+         unsigned num_tests = 0, num_spec_exc = 0;
+         if (verbose)
+            printf("Looking for missed specification exceptions\n");
 
-      for (int i = 0; i < num_clargs; ++i) {
-         const char *name = clargs[i];
+         for (int i = 0; i < num_to_run; ++i) {
+            const char *name = to_run[i];
 
-         opcode *opc = get_opcode_by_name(name);
+            opcode *opc = get_opcode_by_name(name);  // never NULL
 
-         if (opc == NULL) {
-            error("'%s' is not a recognised opcode\n", name);
-         } else if (opcode_has_errors(opc)) {
-            error("Opcode '%s' ignored due to syntax errors\n", name);
-         } else {
-            generate_tests(opc);
-            release_opcode(opc);
+            test_stats stats = run_opcode(opc, /* gen-spec-exc-tests */ 1);
+            num_tests    += stats.num_generated;
+            num_spec_exc += stats.num_spec_exc;
          }
+         rc += num_tests != num_spec_exc;
       }
-      return 0;
-   }
+      if (mode & GEN_NO_SPEC_EXC) {
+         unsigned num_tests = 0, num_spec_exc = 0;
+         if (verbose)
+            printf("Looking for unexpected specification exceptions\n");
 
-   if (verify) {
-      if (num_clargs == 0)
-         fatal("Missing file name[s]\n");
+         for (int i = 0; i < num_to_run; ++i) {
+            const char *name = to_run[i];
 
-      int num_mismatch = 0;
+            opcode *opc = get_opcode_by_name(name);  // never NULL
 
-      for (int i = 0; i < num_clargs; ++i) {
-         verify_stats stats = verify_disassembly(clargs[i]);
-         num_mismatch += stats.num_mismatch;
+            test_stats stats = run_opcode(opc, /* gen-spec-exc-tests */ 0);
+            num_tests    += stats.num_generated;
+            num_spec_exc += stats.num_spec_exc;
+         }
+         rc += num_spec_exc != 0;
       }
-      return num_mismatch != 0;
+      return rc;
    }
 
-   if (run) {
-      if (num_clargs == 0)
-         fatal("Missing opcode name[s]\n");
-
-      unsigned num_mismatch = 0;
-
-      for (int i = 0; i < num_clargs; ++i) {
-         const char *name = clargs[i];
-
-         opcode *opc = get_opcode_by_name(name);
+   if (all) {
+      int rc = 0;
+
+      if (mode & GEN_SPEC_EXC) {
+         unsigned num_tests = 0, num_spec_exc = 0;
+         if (verbose || summary)
+            printf("Looking for missed specification exceptions\n");
+         for (int i = 0; i < num_opcodes; ++i) {
+            opcode *opc = get_opcode_by_index(i); // never NULL
+
+            test_stats stats = run_opcode(opc, /* gen-spec-exc-tests */ 1);
+            num_tests    += stats.num_generated;
+            num_spec_exc += stats.num_spec_exc;
+         }
+         if (verbose || summary) {
+            printf("Total: %6u tests generated\n", num_tests);
+            printf("Total: %6u specification exceptions\n", num_spec_exc);
+         }
+         rc += num_tests != num_spec_exc;
+      }
+      if (mode & GEN_NO_SPEC_EXC) {
+         unsigned num_tests = 0, num_spec_exc = 0;
+         if (verbose || summary)
+            printf("Looking for unexpected specification exceptions\n");
+         for (int i = 0; i < num_opcodes; ++i) {
+            opcode *opc = get_opcode_by_index(i); // never NULL
+
+            test_stats stats = run_opcode(opc, /* gen-spec-exc-tests */ 0);
+            num_tests    += stats.num_generated;
+            num_spec_exc += stats.num_spec_exc;
+         }
+         if (verbose || summary) {
+            printf("Total: %6u tests generated\n", num_tests);
+            printf("Total: %6u specification exceptions\n", num_spec_exc);
+         }
+         rc += num_spec_exc != 0;
+      }
+      return rc != 0;
+   }
 
-         if (opc == NULL) {
-            error("'%s' is not a recognised opcode\n", name);
-         } else if (opcode_has_errors(opc)) {
-            error("Opcode '%s' ignored due to syntax errors\n", name);
-         } else {
-            generate_tests(opc);
+   if (unit_test)
+      run_unit_tests();
 
-            char file[strlen(name) + 10];    // large enough
-            sprintf(file, "%s.dump", name);
+   return 0;
+}
 
-            verify_stats stats = verify_disassembly(file);
-            num_mismatch += stats.num_mismatch;
 
-            if (! keep_temp)
-               remove_temp_files(name);
-            release_opcode(opc);
-         }
-      }
-      return num_mismatch != 0;
+static test_stats
+run_opcode(opcode *opc, int gen_spec_exc_tests)
+{
+   test_stats stats = { 0, 0 };  // return value
+
+   if (opcode_has_errors(opc)) {
+      error("Opcode '%s' ignored due to syntax errors\n", opc->name);
+   } else if (skip_opcode(opc, gen_spec_exc_tests)) {
+      if (verbose)
+         printf("Opcode '%s' skipped\n", opc->name);
+   } else if (is_excluded_opcode(opc)) {
+      if (verbose)
+         printf("Opcode '%s' excluded via command line\n", opc->name);
+   } else {
+      unsigned num_generated = generate_tests(opc, gen_spec_exc_tests);
+
+      stats = verify_spec_exceptions(opc, gen_spec_exc_tests);
+      stats.num_generated = num_generated;
+
+      if (! keep_temp)
+         remove_temp_files(opc->name, gen_spec_exc_tests);
+      release_opcode(opc);
    }
+   return stats;
+}
 
-   if (all) {
-      if (num_clargs != 0)
-         fatal("Excess arguments on command line\n");
 
-      unsigned num_tests, num_verified, num_mismatch, num_spec_exc;
-      num_tests = num_verified = num_mismatch = num_spec_exc = 0;
+/* The GNU assembler detects certain insns that would cause a
+   specification exception, namely:
+   - an invalid register identifying a GPR pair is used
+   - an invalid register identifying a FPR pair is used
+   We do not want the assembler to complain and therefore need
+   to construct testcases accordingly. */
+int
+asm_detects_spec_exc(const opnd *operand)
+{
+   assert(operand->allowed_values);
 
-      for (int i = 0; i < num_opcodes; ++i) {
-         opcode *opc = get_opcode_by_index(i); // never NULL
+   if (operand->kind == OPND_GPR) {
+      /* Check for constraint on a GPR pair. */
+      int expected[8] = { 0,2,4,6,8,10,12,14 };
+      if (operand->allowed_values[0] != 8) return 0;
 
-         if (all_except_exrl && strcmp(opc->name, "exrl") == 0)
-            continue;
+      for (int i = 1; i <= 8; ++i) {
+         if (operand->allowed_values[i] != expected[i - 1])
+            return 0;
+      }
+      return 1;
+   }
 
-         if (opcode_has_errors(opc)) {
-            error("Opcode '%s' ignored due to syntax errors\n",
-                  opc->name);
-            continue;
-         }
-         num_tests += generate_tests(opc);
+   if (operand->kind == OPND_FPR) {
+      /* Check for constraint on an FPR pair. */
+      int expected[8] = { 0,1,4,5,8,9,12,13 };
+      if (operand->allowed_values[0] != 8) return 0;
 
-         char file[strlen(opc->name) + 10];
-         sprintf(file, "%s.dump", opc->name);
+      for (int i = 1; i <= 8; ++i) {
+         if (operand->allowed_values[i] != expected[i - 1])
+            return 0;
+      }
+      return 1;
+   }
 
-         verify_stats stats = verify_disassembly(file);
+   return 0;
+}
 
-         num_verified += stats.num_verified;
-         num_mismatch += stats.num_mismatch;
-         num_spec_exc += stats.num_spec_exc;
 
-         if (! keep_temp)
-            remove_temp_files(opc->name);
-         release_opcode(opc);
-      }
-      if (verbose || summary) {
-         printf("Total: %6u tests generated\n", num_tests);
-         printf("Total: %6u insns verified\n", num_verified);
-         printf("Total: %6u disassembly mismatches\n", num_mismatch);
-         printf("Total: %6u specification exceptions\n", num_spec_exc);
+static int
+skip_opcode(const opcode *opc, int gen_spec_exc_tests)
+{
+   if (gen_spec_exc_tests) {
+      /* Looking for false negatives. I.e. insns that cause spec. exception.
+         That means:
+         a) opcode must have at least one constraint (necessary condition)
+         b) at least one of those constraints is such that a constraint
+            violation is not detected by the assembler (sufficient condition)
+      */
+      for (int i = 0; i < opc->num_opnds; ++i) {
+         const opnd *operand = opc->opnds + i;
+
+         if (operand->allowed_values)
+            if (! asm_detects_spec_exc(operand))
+               return 0;
       }
-      return num_mismatch != 0;
+      return 1;
+   } else {
+      /* Looking for false positives. I.e. opcodes that should not cause
+         a spec. exception. That is:
+         a) opcodes without constraints
+         b) opcodes with satisfied constraints */
+      return 0;
    }
+}
 
-   if (unit_test)
-      run_unit_tests();
 
-   return 0;
+static int
+check_march_level(const char *arch)
+{
+   const char *cmd = "%s -c -march=%s /dev/null 2> /dev/null";
+   char buf[strlen(gcc) + strlen(cmd) + strlen(arch) + 10];
+   sprintf(buf, cmd, gcc, arch);
+
+   int rc = system(buf);
+   return rc == 0 ? 0 : 1;
 }
 
 
 static void
-remove_temp_files(const char *op)
+remove_temp_files(const char *op, int gen_spec_exc_tests)
 {
-   char file[strlen(op) + 10];    // large enough
-   static const char *suffix[] = { ".c", ".o", ".dump", ".vex" };
+   char file[strlen(op) + 20];    // large enough
+   static const char *suffix[] = { ".c", ".o", ".dump", ".spec-exc" };
 
    for (int i = 0; i < sizeof suffix / sizeof *suffix; ++i) {
-      sprintf(file, "%s%s", op, suffix[i]);
+      sprintf(file, "%s-%s%s", op, gen_spec_exc_tests ? "se" : "no-se",
+              suffix[i]);
       unlink(file);
    }
 }
 
 
+static int
+is_excluded_opcode(const opcode *opc)
+{
+   if (excluded_opcodes) {
+      for (int i = 0; i < num_excluded_opcodes; ++i)
+         if (strcmp(opc->name, excluded_opcodes[i]) == 0)
+            return 1;
+   }
+   return 0;
+}
+
+
 /* A few convenience utilities */
 void
 error(const char *fmt, ...)
@@ -349,53 +460,3 @@ opcode_has_errors(const opcode *opc)
    }
    return 0;
 }
-
-
-/* Objdump version 2.44 or later is required, Return 0, if that's
-   the case. */
-static int
-check_objdump(void)
-{
-   unsigned need = strlen(objdump) + 3 + 1;
-   char *cmd = mallock(need);
-
-   sprintf(cmd, "%s -V", objdump);
-   FILE *fp = popen(cmd, "r");
-
-   /* The version number is expected on the first line and its
-      format ought to be one of X or X.Y or X.Y.Z where X,Y,Z are
-      positive integers. */
-   int c, rc = 1;
-   while ((c = fgetc(fp)) != EOF) {
-      if (c == '\n') break;
-      if (! isdigit(c)) continue;
-
-      /* Version number is expected to be X or X.Y or X.Y.Z */
-      char buf[32];  // assumed large enough
-      int ix = 0;
-      do {
-         buf[ix++] = c;
-         c = fgetc(fp);
-      } while (isdigit(c) || c == '.');
-      buf[ix] = '\0';
-
-      unsigned version = 0, v1, v2, v3;
-      if (sscanf(buf, "%u.%u.%u", &v1,&v2,&v3) == 3) {
-         version = v1*1000000 + v2*1000 + v3;
-      } else if (sscanf(buf, "%u.%u", &v1,&v2) == 2) {
-         version = v1*1000000 + v2*1000;
-      } else if (sscanf(buf, "%u", &v1) == 1) {
-         version = v1*1000000;
-      } else {
-         error("Could not determine objdump version\n");
-         break;
-      }
-      if (version >= MIN_OBJDUMP_VERSION)
-         rc = 0;
-      break;
-   }
-   pclose(fp);
-   free(cmd);
-
-   return rc;
-}
index cdf75d2416edfc952eac98813be50404de4ad641..155bc0cb492e5f69989dfe008f1fe4a191e75c1a 100644 (file)
@@ -4,7 +4,7 @@
    This file is part of Valgrind, a dynamic binary instrumentation
    framework.
 
-   Copyright (C) 2024-2025  Florian Krohm
+   Copyright (C) 2024-2026  Florian Krohm
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
@@ -66,22 +66,22 @@ typedef struct {
 } opcode;
 
 typedef struct {
-   unsigned num_verified;
-   unsigned num_mismatch;
+   unsigned num_generated;
    unsigned num_spec_exc;
-} verify_stats;
+} test_stats;
 
 __attribute__((format(printf, 1, 2)))
 void error(const char *, ...);
 __attribute__((noreturn)) __attribute__((format(printf, 1, 2)))
 void fatal(const char *, ...);
 
-verify_stats verify_disassembly(const char *);
-unsigned generate_tests(const opcode *);
+test_stats verify_spec_exceptions(const opcode *, int);
+unsigned generate_tests(const opcode *, int);
 opcode  *get_opcode_by_name(const char *);
 opcode  *get_opcode_by_index(unsigned);
 void     release_opcode(opcode *);
 void     run_unit_tests(void);
+int      asm_detects_spec_exc(const opnd *);
 
 void *mallock(unsigned);
 char *strsave(const char *);
@@ -89,8 +89,6 @@ char *strnsave(const char *, unsigned);
 
 extern int verbose;
 extern int debug;
-extern int show_spec_exc;
-extern int show_miscompares;
 extern unsigned num_opcodes;
 extern const char *gcc;
 extern const char *gcc_flags;
index cabb0af7cc797800fe975413c0132874ae392580..40208ab5dabe3860477382ea29e823395cdab608 100644 (file)
@@ -4,7 +4,7 @@
    This file is part of Valgrind, a dynamic binary instrumentation
    framework.
 
-   Copyright (C) 2024-2025  Florian Krohm
+   Copyright (C) 2024-2026  Florian Krohm
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
index ac6f72e2776464b414bb816fa9bf2c65ca5b7027..33c601cd17441ceb2b4584244217ab42328e456e 100644 (file)
@@ -4,7 +4,7 @@
    This file is part of Valgrind, a dynamic binary instrumentation
    framework.
 
-   Copyright (C) 2024-2025  Florian Krohm
+   Copyright (C) 2024-2026  Florian Krohm
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
@@ -26,7 +26,7 @@
 #define OBJDUMP_H
 
 /* An opcode which marks the begin and end of a sequence of insns
-   in a testcase whose disassembly should be verified. */
+   in a testcase. */
 #define MARK "csch"
 
 /* Name of the C function containing the generated insns. */
index 12af90f6a71c4dc0f87ea53d8413b515824cc16d..cb863cfa5a15beafb02a7f5dda793de093669a84 100644 (file)
@@ -4,7 +4,7 @@
    This file is part of Valgrind, a dynamic binary instrumentation
    framework.
 
-   Copyright (C) 2024-2025  Florian Krohm
+   Copyright (C) 2024-2026  Florian Krohm
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
 
    If a set of allowed values is specified for an operand this
    implies that any other value would cause a specification exception.
-   UNLESS otherwise noted.
 */
 
-/* List of for hardware facilities.
+/* List of hardware facilities.
    The abbreviated name appears as structured comment in below opcode list to
-   indicate that the opcode is only available when the facility is installed.
+   indicate that
+   a) the opcode is only available when the facility is installed OR
+   b) the opcode semantics depend on the presence of the facility. E.g. a
+      certain opcode field is ignored when the facility is not present.
 
    dflt  --> deflate-conversion facility
    dfp   --> decimal floating point facility
@@ -396,16 +398,16 @@ static const char *opcodes[] = {
    "cvdy   r1,d20(x2,b2)",
    // cvdg not implemented
 
-   "cu24   r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14},m3:{0,1}",  // extr3  no spec exc for m3
+   "cu24   r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14},m3",  // extr3
 
-   "cu21   r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14},m3:{0,1}",  // no spec exc for m3
+   "cu21   r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14},m3",  // extr3
 
-   "cu42   r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14}",           // extr3
-   "cu41   r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14}",           // extr3
+   "cu42   r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14}",     // extr3
+   "cu41   r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14}",     // extr3
 
-   "cu12   r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14},m3:{0,1}",  // no spec exc for m3
+   "cu12   r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14},m3",  // extr3
 
-   "cu14   r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14},m3:{0,1}",  // extr3  no spec exc for m3
+   "cu14   r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14},m3",  // extr3
 
    "cpya   a1,a2",
 
@@ -692,7 +694,7 @@ static const char *opcodes[] = {
    "nnrk   r1,r2,r3",           // mi3
    "nngrk  r1,r2,r3",           // mi3
 
-   "niai   i1:u4{0..3},i2:u4{0..3}",  // exhi   no spec exc for i1,i2
+   "niai   i1:u4,i2:u4",        // exhi
 
    // ntstg not implemented
 
@@ -730,12 +732,12 @@ static const char *opcodes[] = {
    "pcc",                       // msa4
 
    // plo   not implemented
-   "ppa    r1,r2,m3:{1,15}",    // ppa     no spec exception for m3
+   "ppa    r1,r2,m3",           // ppa
 
    "ppno   r1,r2",              // msa5
    "prno   r1,r2",              // msa5
 
-   "popcnt r1,r2,m3:{0,8}",     // popc    no spec exception for m3
+   "popcnt r1,r2,m3",           // popc
 
    "pfd    m1,d20(x2,b2)",      // gie
    "pfdrl  m1,ri2:s32",         // gie
@@ -743,29 +745,15 @@ static const char *opcodes[] = {
    "rll    r1,r3,d20(b2)",
    "rllg   r1,r3,d20(b2)",
 
-   // Rotate and .... opcodes require special handling
-   //
-   // For rosbg and friends
-   // - Bit #0 of i3 is the T-bit and bit #1 of i3 ought to be 0.
-   // - i5 is optional and will not be written when 0
-   //
-   // For risbg and friends
-   // - Bit #0 of i4 is the Z-bit and bit #1 of i4 ought to be 0.
-   // - i5 is optional and will not be written when 0
-   //
-   // This implies that we need to model i3, i4 and i5 as masks so
-   // we can manipulate their value when disassembling or suppress
-   // the mask altogether. Note that we limit the set of allowed values
-   // for those masks to avoid excessively large numbers of testcases.
-   "rnsbg  r1,r2,m3:u8{0,1,2,63,128,129,191},m4:u6{0,1,2,63},m5:u6{0,1,2,63}",  // gie
-   "rxsbg  r1,r2,m3:u8{0,1,2,63,128,129,191},m4:u6{0,1,2,63},m5:u6{0,1,2,63}",  // gie
-   "rosbg  r1,r2,m3:u8{0,1,2,63,128,129,191},m4:u6{0,1,2,63},m5:u6{0,1,2,63}",  // gie
-
-   "risbg  r1,r2,m3:u6{0,1,2,63},m4:u8{0,1,2,63,128,129,191},m5:u6{0,1,2,63}",  // gie
-   "risbgn r1,r2,m3:u6{0,1,2,63},m4:u8{0,1,2,63,128,129,191},m5:u6{0,1,2,63}",  // mi1
-
-   "risbhg r1,r2,m3:u5{0,1,2,31},m4:u8{0,1,2,31,128,129,159},m5:u6{0,1,2,63}",  // hiwo
-   "risblg r1,r2,m3:u5{0,1,2,31},m4:u8{0,1,2,31,128,129,159},m5:u6{0,1,2,63}",  // hiwo
+   "rnsbg  r1,r2,i3:u8,i4:u6,i5:u6",  // gie
+   "rxsbg  r1,r2,i3:u8,i4:u6,i5:u6",  // gie
+   "rosbg  r1,r2,i3:u8,i4:u6,i5:u6",  // gie
+
+   "risbg  r1,r2,i3:u6,i4:u8,i5:u6",  // gie
+   "risbgn r1,r2,i3:u6,i4:u8,i5:u6",  // mi1
+
+   "risbhg r1,r2,i3:u5,i4:u8,i5:u6",  // hiwo
+   "risblg r1,r2,i3:u5,i4:u8,i5:u6",  // hiwo
 
    "srst   r1,r2",
 
@@ -924,10 +912,10 @@ static const char *opcodes[] = {
 
    "tre    r1:{0,2,4,6,8,10,12,14},r2",
 
-   "troo   r1:{0,2,4,6,8,10,12,14},r2,m3:{0,1}",  // extr2 no spec exception on m3
-   "trot   r1:{0,2,4,6,8,10,12,14},r2,m3:{0,1}",  // extr2 no spec exception on m3
-   "trto   r1:{0,2,4,6,8,10,12,14},r2,m3:{0,1}",  // extr2 no spec exception on m3
-   "trtt   r1:{0,2,4,6,8,10,12,14},r2,m3:{0,1}",  // extr2 no spec exception on m3
+   "troo   r1:{0,2,4,6,8,10,12,14},r2,m3",  // extr2
+   "trot   r1:{0,2,4,6,8,10,12,14},r2,m3",  // extr2
+   "trto   r1:{0,2,4,6,8,10,12,14},r2,m3",  // extr2
+   "trtt   r1:{0,2,4,6,8,10,12,14},r2,m3",  // extr2
 
    // unpk  not implemented
    // unpka not implemented
@@ -1203,7 +1191,7 @@ static const char *opcodes[] = {
    "vgeg    v1,d12(v2,b2),m3:{0,1}",
    "vgbm    v1,i2:u16",
    "vgm     v1,i2:u8,i3:u8,m4:{0..3}",
-   "vl      v1,d12(x2,b2),m3:{0,3,4}",    // no spec. exc
+   "vl      v1,d12(x2,b2),m3",
    "vlr     v1,v2",
    "vlrep   v1,d12(x2,b2),m3:{0..3}",
    "vlebrh  v1,d12(x2,b2),m3:{0..7}",           // vxe2
@@ -1233,11 +1221,11 @@ static const char *opcodes[] = {
    "vmrh    v1,v2,v3,m4:{0..3}",
    "vmrl    v1,v2,v3,m4:{0..3}",
    "vpk     v1,v2,v3,m4:{1..3}",
-   "vpks    v1,v2,v3,m4:{1..3},m5:{0,1}", // no spec. exception for m5
-   "vpkls   v1,v2,v3,m4:{1..3},m5:{0,1}", // no spec. exception for m5
+   "vpks    v1,v2,v3,m4:{1..3},m5",
+   "vpkls   v1,v2,v3,m4:{1..3},m5",
    "vperm   v1,v2,v3,v4",
-   "vpdi    v1,v2,v3,m4:{0,1,4,5}",       // no spec. exception for m4
-//   "vrep    v1,v3,i2:u16,m4:{0..3}",
+   "vpdi    v1,v2,v3,m4",
+//   "vrep    v1,v3,i2:u16,m4:{0..3}",  // cannot express constraint
    "vrepi   v1,i2:s16,m3:{0..3}",
    "vscef   v1,d12(v2,b2),m3:{0..3}",
    "vsceg   v1,d12(v2,b2),m3:{0,1}",
@@ -1274,9 +1262,9 @@ static const char *opcodes[] = {
    "vcksm   v1,v2,v3",
    "vec     v1,v2,m3:{0..3}",
    "vecl    v1,v2,m3:{0..3}",
-   "vceq    v1,v2,v3,m4:{0..3},m5:{0,1}",  // no spec. exception for m5
-   "vch     v1,v2,v3,m4:{0..3},m5:{0,1}",  // no spec. exception for m5
-   "vchl    v1,v2,v3,m4:{0..3},m5:{0,1}",  // no spec. exception for m5
+   "vceq    v1,v2,v3,m4:{0..3},m5",
+   "vch     v1,v2,v3,m4:{0..3},m5",
+   "vchl    v1,v2,v3,m4:{0..3},m5",
    "vclz    v1,v2,m3:{0..3}",
    "vctz    v1,v2,m3:{0..3}",
    "vx      v1,v2,v3",
@@ -1302,7 +1290,7 @@ static const char *opcodes[] = {
    "vmle    v1,v2,v3,m4:{0..2}",
    "vmo     v1,v2,v3,m4:{0..2}",
    "vmlo    v1,v2,v3,m4:{0..2}",
-   "vmsl    v1,v2,v3,v4,m5:{3},m6:{0,4,8,12}",   // vxe  no spec. exception for m6
+   "vmsl    v1,v2,v3,v4,m5:{3},m6",              // vxe
    "vnn     v1,v2,v3",                           // vxe
    "vno     v1,v2,v3",
    "vnx     v1,v2,v3",                           // vxe
@@ -1320,8 +1308,8 @@ static const char *opcodes[] = {
    "vesrl   v1,v3,d12(b2),m4:{0..3}",
    "vsl     v1,v2,v3",
    "vslb    v1,v2,v3",
-   "vsld    v1,v2,v3,i4:u8{0..7}",   // vxe2  spec exc.
-   "vsldb   v1,v2,v3,i4:u8{0..15}",  // no spec. exception - otherwise unpredictable
+   "vsld    v1,v2,v3,i4:u8{0..7}",   // vxe2
+   "vsldb   v1,v2,v3,i4:u8",
    "vsra    v1,v2,v3",
    "vsrab   v1,v2,v3",
    "vsrd    v1,v2,v3,i4:u8{0..7}",   // vxe2
@@ -1384,11 +1372,11 @@ static const char *opcodes[] = {
    //   "nnpa",            // cannot express constraint   // nnpa
 
    // sortl   not implemented
-   "vclfnh  v1,v2,m3:{3},m4:{0}",    // nnpa  no spec exc. for m3, m4 but IEEE exc.
-   "vclfnl  v1,v2,m3:{3},m4:{0}",    // nnpa  no spec exc. for m3, m4 but IEEE exc.
-   "vcrnf   v1,v2,v3,m4:{0},m5:{2}", // nnpa  no spec exc. for m4, m5 but IEEE exc.
-   "vcfn    v1,v2,m3:{1},m4:{0}",    // nnpa  no spec exc. for m3, m4 but IEEE exc.
-   "vcnf    v1,v2,m3:{0},m4:{1}",    // nnpa  no spec exc. for m3, m4 but IEEE exc.
+   "vclfnh  v1,v2,m3,m4",    // nnpa
+   "vclfnl  v1,v2,m3,m4",    // nnpa
+   "vcrnf   v1,v2,v3,m4,m5", // nnpa
+   "vcfn    v1,v2,m3,m4",    // nnpa
+   "vcnf    v1,v2,m3,m4",    // nnpa
 };
 
 unsigned num_opcodes = sizeof opcodes / sizeof *opcodes;
index b80e270e307ff7abcc8841ac6dace8776d15e9a9..d1686ccdcc6c66bd340763f942fc5afa748d600b 100644 (file)
@@ -4,7 +4,7 @@
    This file is part of Valgrind, a dynamic binary instrumentation
    framework.
 
-   Copyright (C) 2024-2025  Florian Krohm
+   Copyright (C) 2024-2026  Florian Krohm
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
 #include "main.h"           // verbose
 #include "vex.h"            // vex_disasm
 
-static int disasm_same(const char *, const char *, unsigned);
 
+/* Watch out: returned string is allocated in a static buffer which will
+   be overwritten in the next invocation. */
+static const char *
+insn_bytes_as_string(const unsigned char *bytes, unsigned num_bytes)
+{
+   static char buf[4 + 1 + 4 + 1 + 4 + 1];
+
+   char *p = buf;
+   for (int j = 0; j < num_bytes; j += 2)
+      p += sprintf(p, "%02X%02X ", bytes[j], bytes[j + 1]);
+   *--p = '\0';
+
+   return buf;
+}
 
-/* Return number of disassembly mismatches. */
-verify_stats
-verify_disassembly(const char *file)
+
+test_stats
+verify_spec_exceptions(const opcode *opc, int gen_spec_exc_tests)
 {
-   verify_stats stats = { 0, 0, 0 };  // return value
+   test_stats stats = { 0, 0 };  // return value
+
+   char file[strlen(opc->name) + 15];    // large enough
+   sprintf(file, "%s-%s.dump", opc->name, gen_spec_exc_tests ? "se" : "no-se");
 
    objdump_file *ofile = read_objdump(file);
    if (ofile == NULL)
@@ -45,139 +61,45 @@ verify_disassembly(const char *file)
    if (verbose)
       printf("...verifying %u insns in '%s'\n", ofile->num_lines, file);
 
-   const char *p = strchr(file, '.');
-   char vex_file[strlen(file) + 5];
+   char se_file[strlen(opc->name) + 15];  // large enough
+   sprintf(se_file, "%s-%s.spec-exc", opc->name,
+           gen_spec_exc_tests ? "se" : "no-se");
 
-   if (p == NULL) {
-      sprintf(vex_file, "%s.vex", file);
-   } else {
-      int len = p - file;
-      strncpy(vex_file, file, len);
-      strcpy(vex_file + len, ".vex");
-   }
-   FILE *fpvex = fopen(vex_file, "w");
-   if (fpvex == NULL)
-      error("%s: fopen failed\n", vex_file);
+   FILE *fpse = fopen(se_file, "w");
+   if (fpse == NULL)
+      error("%s: fopen failed\n", se_file);
 
    for (int i = 0; i < ofile->num_lines; ++i) {
       const objdump_line *oline = ofile->lines + i;
       int spec_exc = 0;
       const char *disassembly_from_vex =
          vex_disasm(oline->insn_bytes, &spec_exc);
+      const char *insn_bytes =
+         insn_bytes_as_string(oline->insn_bytes, oline->insn_len);
 
-      if (spec_exc) {
-         ++stats.num_spec_exc;
-
-         if (show_spec_exc) {
-            fprintf(stderr, "*** specification exception for insn ");
-            for (int j = 0; j < oline->insn_len; ++j)
-               fprintf(stderr, "%02X", oline->insn_bytes[j]);
-            fprintf(stderr, " in %s\n", file);
-         }
-         /* Instructions causing specification exceptions are not
-            compared */
+      if (disassembly_from_vex == NULL) {
+         error("Disasm failed for %s\n", insn_bytes);
          continue;
       }
 
-      if (disassembly_from_vex == NULL)
-         disassembly_from_vex = "MISSING disassembly from VEX";
-      if (fpvex)
-         fprintf(fpvex, "%s\n", disassembly_from_vex);
-
-      /* Compare disassembled insns */
-      ++stats.num_verified;
-      if (! disasm_same(oline->disassembled_insn, disassembly_from_vex,
-                        oline->address)) {
-         ++stats.num_mismatch;
-         if (show_miscompares) {
-            int n = fprintf(stderr, "*** mismatch VEX: |%s|",
-                            disassembly_from_vex);
-            fprintf(stderr, "%*c", 50 - n, ' ');
-            fprintf(stderr, "objdump: |%s|\n", oline->disassembled_insn);
-         }
+      if (spec_exc) {
+         ++stats.num_spec_exc;
+
+         if (fpse)
+            fprintf(fpse, "%s   %s\n", insn_bytes, disassembly_from_vex);
+         if (! gen_spec_exc_tests)
+            error("Unexpected spec. exc. detected for %s   %s\n", insn_bytes,
+                  disassembly_from_vex);
+      } else {
+         if (gen_spec_exc_tests)
+            error("Spec. exc. not detected for %s   %s\n", insn_bytes,
+                  disassembly_from_vex);
       }
    }
-   if (fpvex)
-      fclose(fpvex);
-   release_objdump(ofile);
 
-   if (verbose) {
-      printf("...%u insns verified\n", stats.num_verified);
-      printf("...%u disassembly mismatches\n", stats.num_mismatch);
-      printf("...%u specification exceptions\n", stats.num_spec_exc);
-   }
+   if (fpse)
+      fclose(fpse);
+   release_objdump(ofile);
 
    return stats;
 }
-
-
-/* Compare two disassembled insns ignoring white space. Return 1 if
-   equal. */
-static int
-disasm_same(const char *from_objdump, const char *from_vex,
-            unsigned address)
-{
-   const char *p1 = from_objdump;
-   const char *p2 = from_vex;
-
-   while (42) {
-      while (isspace(*p1))
-         ++p1;
-      while (isspace(*p2))
-         ++p2;
-      if (*p1 == '\0' && *p2 == '\0')
-         return 1;
-      if (*p1 == '\0' || *p2 == '\0')
-         return 0;
-
-      if (*p1 == *p2) {
-         ++p1;
-         ++p2;
-         continue;
-      }
-
-      /* Consider the case where the VEX disassembly has ".+integer"
-         or ".-integer" and the objdump disassembly has an hex address
-         possibly followed by a symbolic address, e.g. <main+0xe>. */
-      if (*p2++ != '.') return 0;
-
-      long long offset_in_bytes = 0;
-      unsigned long long target_address = 0;
-
-      while (isxdigit(*p1)) {
-         target_address *= 16;
-         if (isdigit(*p1))
-            target_address += *p1 - '0';
-         else {
-            int c = tolower(*p1);
-            if (c >= 'a' && c <= 'f')
-               target_address += 10 + c - 'a';
-            else
-               return 0;  // error
-         }
-         ++p1;
-      }
-      while (isspace(*p1))
-         ++p1;
-      if (*p1 == '<') {
-         while (*p1++ != '>')
-            ;
-      }
-
-      int is_negative = 0;
-      if (*p2 == '-') {
-         is_negative = 1;
-         ++p2;
-      } else if (*p2 == '+')
-         ++p2;
-      while (isdigit(*p2)) {
-         offset_in_bytes *= 10;
-         offset_in_bytes += *p2 - '0';
-         ++p2;
-      }
-      if (is_negative)
-         offset_in_bytes *= -1;
-
-      if (address + offset_in_bytes != target_address) return 0;
-   }
-}
index bb850b8eb1e56daf705970dfd2b73d1a663e35b1..3e98aa3b6af4445dacc7213059ecd3f8d100eaa3 100644 (file)
@@ -4,7 +4,7 @@
    This file is part of Valgrind, a dynamic binary instrumentation
    framework.
 
-   Copyright (C) 2024-2025  Florian Krohm
+   Copyright (C) 2024-2026  Florian Krohm
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
@@ -147,5 +147,21 @@ vex_disasm(const unsigned char *codebuf, int *spec_exc)
       *spec_exc = 1;
    }
 
+   if (last_vex_string) {
+      /* Compress a sequence of spaces with a single blank. */
+      int space_seen = 0;
+      char *p, *q;
+      for (p = q = last_vex_string; *p; ++p) {
+         if (isspace(*p)) {
+            if (space_seen) continue;
+            space_seen = 1;
+         } else {
+            space_seen = 0;
+         }
+         *q++ = *p;
+      }
+      *q = '\0';
+   }
+
    return last_vex_string;
 }
index 627c1e55e1875c355698580083f6448eb4b1a519..da46fb80836a1a0099f4f950ea91f6a67e75629d 100644 (file)
@@ -4,7 +4,7 @@
    This file is part of Valgrind, a dynamic binary instrumentation
    framework.
 
-   Copyright (C) 2024-2025  Florian Krohm
+   Copyright (C) 2024-2026  Florian Krohm
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as