]> git.ipfire.org Git - thirdparty/valgrind.git/commitdiff
Add program to double-check VEX constant folding. BZ 506211 master
authorFlorian Krohm <flo2030@eich-krohm.de>
Sat, 12 Jul 2025 13:32:14 +0000 (13:32 +0000)
committerFlorian Krohm <flo2030@eich-krohm.de>
Sat, 12 Jul 2025 13:32:14 +0000 (13:32 +0000)
Using IR injection. Essentially:
- prepare input values for an IROp
- create an IRExpr for the IRop
- constant fold the expression
- make sure the result is an IRConst with the expected value

Only IROps with integer operands and result are supported.
No vector and floating point IROps. Maximum bit width is 64.

Part of fixing https://bugs.kde.org/show_bug.cgi?id=506211

20 files changed:
.gitignore
Makefile.am
VEX/priv/ir_inject.c
VEX/priv/ir_opt.c
VEX/pub/libvex.h
VEX/pub/libvex_ir.h
configure.ac
none/tests/iropt-test/Makefile.am [new file with mode: 0644]
none/tests/iropt-test/binary.c [new file with mode: 0644]
none/tests/iropt-test/filter_stderr [new file with mode: 0755]
none/tests/iropt-test/irops.tab [new file with mode: 0644]
none/tests/iropt-test/iropt-test-sec.stderr.exp [new file with mode: 0644]
none/tests/iropt-test/iropt-test-sec.vgtest [new file with mode: 0644]
none/tests/iropt-test/iropt-test.stderr.exp [new file with mode: 0644]
none/tests/iropt-test/iropt-test.vgtest [new file with mode: 0644]
none/tests/iropt-test/main.c [new file with mode: 0644]
none/tests/iropt-test/unary.c [new file with mode: 0644]
none/tests/iropt-test/util.c [new file with mode: 0644]
none/tests/iropt-test/valgrind.c [new file with mode: 0644]
none/tests/iropt-test/vtest.h [new file with mode: 0644]

index 32cedb0d7c6b269f26e962d14de3731bf855dd66..e4ddf5dc65ef08217ee5055accfa2088e4386c02 100644 (file)
 /none/tests/riscv64/integer
 /none/tests/riscv64/muldiv
 
 /none/tests/riscv64/integer
 /none/tests/riscv64/muldiv
 
+# /none/tests/iropt-test/
+/none/tests/iropt-test/*.dSYM
+/none/tests/iropt-test/*.stderr.diff*
+/none/tests/iropt-test/*.stderr.out
+/none/tests/iropt-test/*.stdout.diff*
+/none/tests/iropt-test/*.stdout.out
+/none/tests/iropt-test/.deps
+/none/tests/iropt-test/Makefile
+/none/tests/iropt-test/Makefile.in
+/none/tests/iropt-test/iropt-test
+/none/tests/iropt-test/iropt-test-sec
+
 # /none/tests/s390x/disasm-test/
 /none/tests/s390x/disasm-test/*.dSYM
 /none/tests/s390x/disasm-test/*.stderr.diff*
 # /none/tests/s390x/disasm-test/
 /none/tests/s390x/disasm-test/*.dSYM
 /none/tests/s390x/disasm-test/*.stderr.diff*
index e67356b5a0f2ca3db9e3113d5375e97f48db1703..a3150abb1481d7247b171dc6f26aecb9aaac2fd3 100644 (file)
@@ -34,6 +34,7 @@ SUBDIRS = \
        gdbserver_tests \
        memcheck/tests/vbit-test \
        none/tests/s390x/disasm-test \
        gdbserver_tests \
        memcheck/tests/vbit-test \
        none/tests/s390x/disasm-test \
+       none/tests/iropt-test \
        auxprogs \
        mpi \
        solaris \
        auxprogs \
        mpi \
        solaris \
index f642c834db4dd11150b73141e5e05e87e00a2c82..750bb588d3a64087012dc07ed6314ffb244d170d 100644 (file)
@@ -33,6 +33,7 @@
 #include "main_util.h"
 
 /* Convenience macros for readibility */
 #include "main_util.h"
 
 /* Convenience macros for readibility */
+#define mkU1(v)   IRExpr_Const(IRConst_U1(v))
 #define mkU8(v)   IRExpr_Const(IRConst_U8(v))
 #define mkU16(v)  IRExpr_Const(IRConst_U16(v))
 #define mkU32(v)  IRExpr_Const(IRConst_U32(v))
 #define mkU8(v)   IRExpr_Const(IRConst_U8(v))
 #define mkU16(v)  IRExpr_Const(IRConst_U16(v))
 #define mkU32(v)  IRExpr_Const(IRConst_U32(v))
@@ -321,6 +322,76 @@ vex_inject_ir_vbit(IRSB *irsb, IREndness endian)
    }
 }
 
    }
 }
 
+
+static void
+vex_inject_ir_iropt(IRSB *irsb, IREndness endian)
+{
+   IRICB_iropt_payload iricb = the_iricb.iropt;
+   IRExpr *opnd1, *opnd2, *expr;
+   ULong val1, val2;
+
+   val1 = *(ULong *)iricb.opnd1;
+   switch (iricb.t_opnd1) {
+   case Ity_I1:  opnd1 = mkU1(val1);  break;
+   case Ity_I8:  opnd1 = mkU8(val1);  break;
+   case Ity_I16: opnd1 = mkU16(val1); break;
+   case Ity_I32: opnd1 = mkU32(val1); break;
+   case Ity_I64: opnd1 = mkU64(val1); break;
+   default:
+      vpanic("unsupported type");
+   }
+
+   switch (iricb.num_operands) {
+   case 1:
+      expr = unop(iricb.op, opnd1);
+      break;
+
+   case 2:
+      val2 = *(ULong *)iricb.opnd2;
+      switch (iricb.t_opnd2) {
+      case Ity_I1:  opnd2 = mkU1(val2);  break;
+      case Ity_I8:  opnd2 = mkU8(val2);  break;
+      case Ity_I16: opnd2 = mkU16(val2); break;
+      case Ity_I32: opnd2 = mkU32(val2); break;
+      case Ity_I64: opnd2 = mkU64(val2); break;
+      default:
+         vpanic("unsupported type");
+      }
+      expr = binop(iricb.op, opnd1, opnd2);
+      break;
+
+   default:
+      vpanic("unsupported operator");
+   }
+
+   /* Make sure the expression gets folded ! */
+   IRExpr *env[1] = { 0 };
+   IRExpr *res = foldIRExpr(env, expr);
+
+   if (res->tag != Iex_Const) {
+      vex_printf("*** ");
+      ppIROp(iricb.op);
+      vex_printf(": not folded: result = ");
+      ppIRExpr(res);
+      vex_printf("\n");
+      *(ULong *)iricb.result = 0;     // whatever
+      return;
+   }
+
+   /* Store the folded result in the IRICB. We're only handling integers
+      up to 64-bit wide. */
+   switch (iricb.t_result) {
+   case Ity_I1:  *(ULong *)iricb.result = res->Iex.Const.con->Ico.U1;  break;
+   case Ity_I8:  *(ULong *)iricb.result = res->Iex.Const.con->Ico.U8;  break;
+   case Ity_I16: *(ULong *)iricb.result = res->Iex.Const.con->Ico.U16; break;
+   case Ity_I32: *(ULong *)iricb.result = res->Iex.Const.con->Ico.U32; break;
+   case Ity_I64: *(ULong *)iricb.result = res->Iex.Const.con->Ico.U64; break;
+   default:
+      vpanic("unsupported type");
+   }
+}
+
+
 void
 vex_inject_ir(IRSB *irsb, IREndness endian)
 {
 void
 vex_inject_ir(IRSB *irsb, IREndness endian)
 {
@@ -329,6 +400,10 @@ vex_inject_ir(IRSB *irsb, IREndness endian)
       vex_inject_ir_vbit(irsb, endian);
       break;
 
       vex_inject_ir_vbit(irsb, endian);
       break;
 
+   case IRICB_iropt:
+      vex_inject_ir_iropt(irsb, endian);
+      break;
+
    default:
       vpanic("unknown IRICB kind");
    }
    default:
       vpanic("unknown IRICB kind");
    }
index ee2c5a4a719592a0e9de14f13e1419b2a6ba8af6..9a3f39c2c9d9f15539b4568c81b53fb0f8207495 100644 (file)
@@ -2610,6 +2610,11 @@ static IRExpr* fold_Expr ( IRExpr** env, IRExpr* e )
    return env == NULL ? e : fold_Expr_WRK(env, e);
 }
 
    return env == NULL ? e : fold_Expr_WRK(env, e);
 }
 
+IRExpr* foldIRExpr ( IRExpr** env, IRExpr* e )
+{
+   return fold_Expr(env, e);
+}
+
 /* Apply the subst to a simple 1-level expression -- guaranteed to be
    1-level due to previous flattening pass. */
 
 /* Apply the subst to a simple 1-level expression -- guaranteed to be
    1-level due to previous flattening pass. */
 
index 870747d354ae6985972b3a5a201526010151e68f..de19e1ebf6a8f8b0f1c2befdcd1da836508eb410 100644 (file)
@@ -963,6 +963,7 @@ extern void LibVEX_ShowStats ( void );
 typedef
    enum {
       IRICB_vbit,
 typedef
    enum {
       IRICB_vbit,
+      IRICB_iropt,
    }
    IRICB_t;
 
    }
    IRICB_t;
 
@@ -992,11 +993,25 @@ typedef
    }
    IRICB_vbit_payload;
 
    }
    IRICB_vbit_payload;
 
+typedef
+   struct {
+      IROp   op;            // the operation to perform
+      HWord  result;        // address of the result
+      HWord  opnd1;         // address of 1st operand
+      HWord  opnd2;         // address of 2nd operand
+      IRType t_result;      // type of result
+      IRType t_opnd1;       // type of 1st operand
+      IRType t_opnd2;       // type of 2nd operand
+      UInt   num_operands;
+   }
+   IRICB_iropt_payload;
+
 typedef
    struct {
       IRICB_t kind;
       union {
          IRICB_vbit_payload vbit;
 typedef
    struct {
       IRICB_t kind;
       union {
          IRICB_vbit_payload vbit;
+         IRICB_iropt_payload iropt;
       };
    }
    IRICB;
       };
    }
    IRICB;
index 803a1317ed9326650cc00c3aa30d80fdbcf56d53..983cd50bcaf0aa26f6618799187e665deaf44e9c 100644 (file)
@@ -2405,6 +2405,9 @@ extern IRExpr* deepCopyIRExpr ( const IRExpr* );
 /* Pretty-print an IRExpr. */
 extern void ppIRExpr ( const IRExpr* );
 
 /* Pretty-print an IRExpr. */
 extern void ppIRExpr ( const IRExpr* );
 
+/* Fold an IRExpr. Return folded result. */
+extern IRExpr* foldIRExpr ( IRExpr**, IRExpr* );
+
 /* NULL-terminated IRExpr vector constructors, suitable for
    use as arg lists in clean/dirty helper calls. */
 extern IRExpr** mkIRExprVec_0 ( void );
 /* NULL-terminated IRExpr vector constructors, suitable for
    use as arg lists in clean/dirty helper calls. */
 extern IRExpr** mkIRExprVec_0 ( void );
index a235f1a424649fa1686f15e74db5e9b402247e96..d64782728b372f51761e69c59b94d1b277a438af 100755 (executable)
@@ -5757,6 +5757,7 @@ AC_CONFIG_FILES([
    none/tests/arm64/Makefile
    none/tests/s390x/Makefile
    none/tests/s390x/disasm-test/Makefile
    none/tests/arm64/Makefile
    none/tests/s390x/Makefile
    none/tests/s390x/disasm-test/Makefile
+   none/tests/iropt-test/Makefile
    none/tests/mips32/Makefile
    none/tests/mips64/Makefile
    none/tests/nanomips/Makefile
    none/tests/mips32/Makefile
    none/tests/mips64/Makefile
    none/tests/nanomips/Makefile
diff --git a/none/tests/iropt-test/Makefile.am b/none/tests/iropt-test/Makefile.am
new file mode 100644 (file)
index 0000000..f4d1e9a
--- /dev/null
@@ -0,0 +1,83 @@
+include $(top_srcdir)/Makefile.all.am
+
+EXTRA_DIST = iropt-test.vgtest iropt-test.stderr.exp \
+             iropt-test-sec.vgtest iropt-test-sec.stderr.exp \
+             irops.tab
+
+dist_noinst_SCRIPTS = filter_stderr
+
+#----------------------------------------------------------------------------
+# Headers
+#----------------------------------------------------------------------------
+
+pkginclude_HEADERS =
+noinst_HEADERS = vtest.h
+
+#----------------------------------------------------------------------------
+# iropt_test
+#----------------------------------------------------------------------------
+
+noinst_PROGRAMS = iropt-test
+
+if VGCONF_HAVE_PLATFORM_SEC
+noinst_PROGRAMS += iropt-test-sec
+endif
+
+if VGCONF_OS_IS_DARWIN
+noinst_DSYMS = $(noinst_PROGRAMS)
+endif
+
+SOURCES = \
+       main.c \
+       unary.c \
+       binary.c \
+       util.c \
+       valgrind.c
+
+# The link flags for this are tricky, because we want to build it for
+# both the primary and secondary platforms, and add
+# "-Wl,-read_only_relocs -Wl,suppress" to whichever of those is x86-darwin,
+# if any.  Hence there's a double-nested conditional that adds to the
+# LDFLAGS in both cases.
+
+iropt_test_SOURCES      = $(SOURCES)
+iropt_test_CPPFLAGS     = $(AM_CPPFLAGS_PRI) \
+                          -I$(top_srcdir)/include  \
+                          -I$(top_srcdir)/memcheck \
+                          -I$(top_srcdir)/VEX/pub
+iropt_test_CFLAGS       = $(AM_CFLAGS_PRI) -fhosted
+iropt_test_DEPENDENCIES = $(top_builddir)/VEX/libvex-@VGCONF_ARCH_PRI@-@VGCONF_OS@.a
+iropt_test_LDADD        = $(top_builddir)/VEX/libvex-@VGCONF_ARCH_PRI@-@VGCONF_OS@.a
+iropt_test_LDFLAGS      = $(AM_CFLAGS_PRI) @LIB_UBSAN@
+# If there is no secondary platform, and the platforms include x86-darwin,
+# then the primary platform must be x86-darwin.  Hence:
+if ! VGCONF_HAVE_PLATFORM_SEC
+if VGCONF_PLATFORMS_INCLUDE_X86_DARWIN
+iropt_test_LDFLAGS      += -Wl,-read_only_relocs -Wl,suppress
+endif
+endif
+
+if VGCONF_HAVE_PLATFORM_SEC
+iropt_test_sec_SOURCES      = $(SOURCES)
+iropt_test_sec_CPPFLAGS     = $(AM_CPPFLAGS_SEC) \
+                              $(AM_CPPFLAGS_@VGCONF_PLATFORM_SEC_CAPS@) \
+                              -I$(top_srcdir)/include  \
+                              -I$(top_srcdir)/memcheck \
+                              -I$(top_srcdir)/VEX/pub
+iropt_test_sec_CFLAGS       = $(AM_CFLAGS_SEC) -fhosted \
+                              $(AM_CFLAGS_@VGCONF_PLATFORM_SEC_CAPS@)
+iropt_test_sec_DEPENDENCIES = $(top_builddir)/VEX/libvex-@VGCONF_ARCH_SEC@-@VGCONF_OS@.a \
+                              $(TOOL_LDADD_@VGCONF_PLATFORM_SEC_CAPS@)
+iropt_test_sec_LDADD        = $(top_builddir)/VEX/libvex-@VGCONF_ARCH_SEC@-@VGCONF_OS@.a \
+                              $(TOOL_LDADD_@VGCONF_PLATFORM_SEC_CAPS@)
+iropt_test_sec_LDFLAGS      = $(AM_CFLAGS_SEC) @LIB_UBSAN@ \
+                              $(TOOL_LDFLAGS_@VGCONF_PLATFORM_SEC_CAPS@)
+endif
+# If there is a secondary platform, and the platforms include x86-darwin,
+# then the primary platform must be amd64-darwin and the secondary platform
+# must be x86-darwin.  Hence:
+if VGCONF_HAVE_PLATFORM_SEC
+if VGCONF_PLATFORMS_INCLUDE_X86_DARWIN
+iropt_test_sec_LDFLAGS      += -Wl,-read_only_relocs -Wl,suppress
+endif
+endif
diff --git a/none/tests/iropt-test/binary.c b/none/tests/iropt-test/binary.c
new file mode 100644 (file)
index 0000000..c3f36c1
--- /dev/null
@@ -0,0 +1,315 @@
+/* -*- mode: C; c-basic-offset: 3; -*- */
+
+/*
+   This file is part of Valgrind, a dynamic binary instrumentation
+   framework.
+
+   Copyright (C) 2025  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
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+   The GNU General Public License is contained in the file COPYING.
+*/
+
+#include <stdio.h>      // printf
+#include <stdint.h>     // UINT64_MAX
+#include "vtest.h"
+
+static void check_result(const irop_t *, const test_data_t *);
+static void run_tests(const irop_t *, test_data_t *, unsigned, uint64_t *,
+                      unsigned, uint64_t *);
+static int  is_shift_op(IROp);
+
+
+void
+test_binary_op(const irop_t *op, test_data_t *data)
+{
+   opnd_t *opnd_l = &data->opnds[0];
+
+   switch (opnd_l->type) {
+   case Ity_I1: {
+      uint64_t values[] = { 0, 1 };
+
+      run_tests(op, data, NUM_EL(values), values, NUM_EL(values), values);
+      break;
+   }
+
+   case Ity_I8: {
+      uint64_t values[] = { 0, 1, 2, UINT8_MAX - 1, UINT8_MAX };
+      uint64_t shifts[] = { 0, 1, 2, 6, 7 };
+
+      if (is_shift_op(op->op))
+         run_tests(op, data, NUM_EL(values), values, NUM_EL(shifts), shifts);
+      else
+         run_tests(op, data, NUM_EL(values), values, NUM_EL(values), values);
+      break;
+   }
+
+   case Ity_I16: {
+      uint64_t values[] = { 0, 1, 2, UINT16_MAX - 1, UINT16_MAX };
+      uint64_t shifts[] = { 0, 1, 2, 14, 15 };
+
+      if (is_shift_op(op->op))
+         run_tests(op, data, NUM_EL(values), values, NUM_EL(shifts), shifts);
+      else
+         run_tests(op, data, NUM_EL(values), values, NUM_EL(values), values);
+      break;
+   }
+
+   case Ity_I32: {
+      uint64_t values[] = { 0, 1, 2, UINT32_MAX - 1, UINT32_MAX };
+      uint64_t shifts[] = { 0, 1, 2, 30, 31 };
+
+      if (is_shift_op(op->op))
+         run_tests(op, data, NUM_EL(values), values, NUM_EL(shifts), shifts);
+      else
+         run_tests(op, data, NUM_EL(values), values, NUM_EL(values), values);
+      break;
+   }
+
+   case Ity_I64: {
+      uint64_t values[] = { 0, 1, 2, UINT64_MAX - 1, UINT64_MAX };
+      uint64_t shifts[] = { 0, 1, 2, 62, 63 };
+
+      if (is_shift_op(op->op))
+         run_tests(op, data, NUM_EL(values), values, NUM_EL(shifts), shifts);
+      else
+         run_tests(op, data, NUM_EL(values), values, NUM_EL(values), values);
+      break;
+   }
+
+   default:
+      panic(__func__);
+   }
+}
+
+
+static void
+run_tests(const irop_t *op, test_data_t *data, unsigned num_val_l,
+          uint64_t *values_l, unsigned num_val_r, uint64_t *values_r)
+{
+   opnd_t *opnd_l = &data->opnds[0];
+   opnd_t *opnd_r = &data->opnds[1];
+
+   for (unsigned i = 0; i < num_val_l; ++i) {
+      opnd_l->value = values_l[i];
+      for (unsigned j = 0; j < num_val_r; ++j) {
+         opnd_r->value = values_r[j];
+
+         valgrind_execute_test(op, data);
+         check_result(op, data);
+      }
+   }
+}
+
+
+/* Check the result of a binary operation. */
+static void
+check_result(const irop_t *op, const test_data_t *data)
+{
+   uint64_t result = data->result.value;
+   uint64_t opnd_l = data->opnds[0].value;
+   uint64_t opnd_r = data->opnds[1].value;
+   uint64_t expected;
+
+   switch (op->op) {
+   case Iop_Add8:
+   case Iop_Add16:
+   case Iop_Add32:
+   case Iop_Add64:
+      expected = opnd_l + opnd_r;
+      break;
+
+   case Iop_Sub8:
+   case Iop_Sub16:
+   case Iop_Sub32:
+   case Iop_Sub64:
+      expected = opnd_l - opnd_r;
+      break;
+
+   case Iop_MullS32:
+      expected = (int64_t)(int32_t)opnd_l * (int64_t)(int32_t)opnd_r;
+      break;
+
+   case Iop_Shl32:
+      expected = opnd_l << opnd_r;
+      break;
+
+   case Iop_Shl64:
+      expected = opnd_l << opnd_r;
+      break;
+
+   case Iop_Shr32:
+      expected = opnd_l >> opnd_r;
+      break;
+
+   case Iop_Shr64:
+      expected = opnd_l >> opnd_r;
+      break;
+
+   case Iop_Sar32:
+      expected = ((int64_t)(opnd_l << 32) >> 32) >> opnd_r;
+      break;
+
+   case Iop_Sar64:
+      expected = (int64_t)opnd_l >> opnd_r;
+      break;
+
+   case Iop_Or1:
+   case Iop_Or8:
+   case Iop_Or16:
+   case Iop_Or32:
+   case Iop_Or64:
+      expected = opnd_l | opnd_r;
+      break;
+
+   case Iop_And1:
+   case Iop_And8:
+   case Iop_And16:
+   case Iop_And32:
+   case Iop_And64:
+      expected = opnd_l & opnd_r;
+      break;
+
+   case Iop_Xor8:
+   case Iop_Xor16:
+   case Iop_Xor32:
+   case Iop_Xor64:
+      expected = opnd_l ^ opnd_r;
+      break;
+
+   case Iop_CmpEQ8:
+   case Iop_CmpEQ16:
+   case Iop_CmpEQ32:
+   case Iop_CmpEQ64:
+//   case Iop_CasCmpEQ8:
+//   case Iop_CasCmpEQ16:
+//   case Iop_CasCmpEQ32:
+//   case Iop_CasCmpEQ64:
+      expected = opnd_l == opnd_r;
+      break;
+
+   case Iop_CmpNE8:
+//   case Iop_CmpNE16:
+   case Iop_CmpNE32:
+   case Iop_CmpNE64:
+   case Iop_CasCmpNE8:
+//   case Iop_CasCmpNE16:
+   case Iop_CasCmpNE32:
+   case Iop_CasCmpNE64:
+   case Iop_ExpCmpNE8:
+//   case Iop_ExpCmpNE16:
+   case Iop_ExpCmpNE32:
+   case Iop_ExpCmpNE64:
+      expected = opnd_l != opnd_r;
+      break;
+
+   case Iop_CmpLT32U:
+   case Iop_CmpLT64U:
+      expected = opnd_l < opnd_r;
+      break;
+
+   case Iop_CmpLT32S: {
+      int32_t opnd_ls = (int32_t)(opnd_l & UINT32_MAX);
+      int32_t opnd_rs = (int32_t)(opnd_r & UINT32_MAX);
+      expected = opnd_ls < opnd_rs;
+      break;
+   }
+
+   case Iop_CmpLT64S:
+      expected = (int64_t)opnd_l < (int64_t)opnd_r;
+      break;
+
+   case Iop_CmpLE32U:
+   case Iop_CmpLE64U:
+      expected = opnd_l <= opnd_r;
+      break;
+
+   case Iop_CmpLE32S: {
+      int32_t opnd_ls = (int32_t)(opnd_l & UINT32_MAX);
+      int32_t opnd_rs = (int32_t)(opnd_r & UINT32_MAX);
+      expected = opnd_ls <= opnd_rs;
+      break;
+   }
+
+   case Iop_CmpLE64S:
+      expected = (int64_t)opnd_l <= (int64_t)opnd_r;
+      break;
+
+   case Iop_CmpORD32S: {
+      int32_t opnd_ls = (int32_t)(opnd_l & UINT32_MAX);
+      int32_t opnd_rs = (int32_t)(opnd_r & UINT32_MAX);
+      expected = (opnd_ls < opnd_rs) ? 8 : (opnd_ls > opnd_rs) ? 4 : 2;
+      break;
+   }
+
+   case Iop_Max32U:
+      opnd_l &= UINT32_MAX;
+      opnd_r &= UINT32_MAX;
+      expected = opnd_l > opnd_r ? opnd_l : opnd_r;
+      break;
+
+   case Iop_32HLto64:
+      expected = (opnd_l << 32) | opnd_r;
+      break;
+
+   default:
+      panic("%s: operator %s not handled\n", __func__, op->name);
+   }
+
+   /* Truncate to width of result type */
+   switch (bitsof_irtype(op->result_type)) {
+   case 1:  expected &= 0x1;        break;
+   case 8:  expected &= UINT8_MAX;  break;
+   case 16: expected &= UINT16_MAX; break;
+   case 32: expected &= UINT32_MAX; break;
+   case 64: expected &= UINT64_MAX; break;
+   default:
+      panic(__func__);
+   }
+
+   if (verbose > 1) {
+      printf("expected:  value = ");
+      print_value(stdout, expected, bitsof_irtype(data->result.type));
+      printf("\n");
+   }
+
+   int ok = 1;
+   switch (data->result.type) {
+   case Ity_I1:  ok = result == expected; break;
+   case Ity_I8:  ok = result == expected; break;
+   case Ity_I16: ok = result == expected; break;
+   case Ity_I32: ok = result == expected; break;
+   case Ity_I64: ok = result == expected; break;
+   default:
+      panic(__func__);
+   }
+
+   if (! ok)
+      complain(op, data, expected);
+}
+
+
+static int
+is_shift_op(IROp op)
+{
+   switch (op) {
+   case Iop_Shl8: case Iop_Shl16: case Iop_Shl32: case Iop_Shl64:
+   case Iop_Shr8: case Iop_Shr16: case Iop_Shr32: case Iop_Shr64:
+   case Iop_Sar8: case Iop_Sar16: case Iop_Sar32: case Iop_Sar64:
+      return 1;
+   default:
+      return 0;
+   }
+}
diff --git a/none/tests/iropt-test/filter_stderr b/none/tests/iropt-test/filter_stderr
new file mode 100755 (executable)
index 0000000..5337954
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+# Nothing to filter.
+sed 's/BLA/BLA/'
diff --git a/none/tests/iropt-test/irops.tab b/none/tests/iropt-test/irops.tab
new file mode 100644 (file)
index 0000000..a849289
--- /dev/null
@@ -0,0 +1,246 @@
+/* -*- mode: C; c-basic-offset: 3; -*- */
+
+/*
+   This file is part of Valgrind, a dynamic binary instrumentation
+   framework.
+
+   Copyright (C) 2025  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
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+   The GNU General Public License is contained in the file COPYING.
+*/
+
+#define OPNAME(op) #op, Iop_##op
+
+/* Definition of IROps:
+   - no IROps having floating point operands or result
+   - no IROPs having vector operands or results (V128, V256)
+   - no IROPs having integer operands or results with more than 64 bit
+*/
+   // UNARY
+   { OPNAME(Not1),   Ity_I1,  1, Ity_I1,  },
+   { OPNAME(Not8),   Ity_I8,  1, Ity_I8,  },
+   { OPNAME(Not16),  Ity_I16, 1, Ity_I16, },
+   { OPNAME(Not32),  Ity_I32, 1, Ity_I32, },
+   { OPNAME(Not64),  Ity_I64, 1, Ity_I64, },
+
+   { OPNAME(1Uto8),  Ity_I8,  1, Ity_I1,  },
+// { OPNAME(1Uto16), Ity_I16, 1, Ity_I1,  }, // missing in libvex_ir.h
+   { OPNAME(1Uto32), Ity_I32, 1, Ity_I1,  },
+   { OPNAME(1Uto64), Ity_I64, 1, Ity_I1,  },
+   { OPNAME(1Sto8),  Ity_I8,  1, Ity_I1,  },
+   { OPNAME(1Sto16), Ity_I16, 1, Ity_I1,  },
+   { OPNAME(1Sto32), Ity_I32, 1, Ity_I1,  },
+   { OPNAME(1Sto64), Ity_I64, 1, Ity_I1,  },
+
+   { OPNAME(8Uto16), Ity_I16, 1, Ity_I8,  },
+   { OPNAME(8Uto32), Ity_I32, 1, Ity_I8,  },
+   { OPNAME(8Uto64), Ity_I64, 1, Ity_I8,  },
+   { OPNAME(8Sto16), Ity_I16, 1, Ity_I8,  },
+   { OPNAME(8Sto32), Ity_I32, 1, Ity_I8,  },
+   { OPNAME(8Sto64), Ity_I64, 1, Ity_I8,  },
+
+   { OPNAME(16Uto32), Ity_I32, 1, Ity_I16, },
+   { OPNAME(16Uto64), Ity_I64, 1, Ity_I16, },
+   { OPNAME(16Sto32), Ity_I32, 1, Ity_I16, },
+   { OPNAME(16Sto64), Ity_I64, 1, Ity_I16, },
+
+   { OPNAME(32Uto64), Ity_I64, 1, Ity_I32, },
+   { OPNAME(32Sto64), Ity_I64, 1, Ity_I32, },
+
+// { OPNAME(8to1),    Ity_I1, 1, Ity_I8,  }, // missing in libvex_ir.h
+// { OPNAME(16to1),   Ity_I1, 1, Ity_I16, }, // missing in libvex_ir.h
+   { OPNAME(16to8),   Ity_I8, 1, Ity_I16, },
+   { OPNAME(16HIto8), Ity_I8, 1, Ity_I16, },
+
+   { OPNAME(32to1),    Ity_I1,  1, Ity_I32, },
+   { OPNAME(32to8),    Ity_I8,  1, Ity_I32, },
+   { OPNAME(32to16),   Ity_I16, 1, Ity_I32, },
+   { OPNAME(32HIto16), Ity_I16, 1, Ity_I32, },
+
+   { OPNAME(64to1),    Ity_I1,  1, Ity_I64, },
+   { OPNAME(64to8),    Ity_I8,  1, Ity_I64, },
+   { OPNAME(64to16),   Ity_I16, 1, Ity_I64, },
+   { OPNAME(64to32),   Ity_I32, 1, Ity_I64, },
+   { OPNAME(64HIto32), Ity_I32, 1, Ity_I64, },
+
+// { OPNAME(128to64),   Ity_I64, 1, Ity_I128, },      // 128 bit
+// { OPNAME(128HIto64), Ity_I64, 1, Ity_I128, },      // 128 bit
+
+   // BINARY
+   { OPNAME(Add8),    Ity_I8,  2, Ity_I8,  Ity_I8  },
+// { OPNAME(Add16),   Ity_I16, 2, Ity_I16, Ity_I16 }, // no folding yet
+   { OPNAME(Add32),   Ity_I32, 2, Ity_I32, Ity_I32 },
+   { OPNAME(Add64),   Ity_I64, 2, Ity_I64, Ity_I64 },
+
+   { OPNAME(Sub8),    Ity_I8,  2, Ity_I8,  Ity_I8  },
+// { OPNAME(Sub16),   Ity_I16, 2, Ity_I16, Ity_I16 }, // no folding yet
+   { OPNAME(Sub32),   Ity_I32, 2, Ity_I32, Ity_I32 },
+   { OPNAME(Sub64),   Ity_I64, 2, Ity_I64, Ity_I64 },
+
+// { OPNAME(Mul8),    Ity_I8,  2, Ity_I8,  Ity_I8  },
+// { OPNAME(Mul16),   Ity_I16, 2, Ity_I16, Ity_I16 },
+// { OPNAME(Mul32),   Ity_I32, 2, Ity_I32, Ity_I32 },
+// { OPNAME(Mul64),   Ity_I64, 2, Ity_I64, Ity_I64 },
+
+// { OPNAME(MullU8),  Ity_I16,  2, Ity_I8,  Ity_I8  }, // no folding yet
+// { OPNAME(MullU16), Ity_I32,  2, Ity_I16, Ity_I16 }, // no folding yet
+// { OPNAME(MullU32), Ity_I64,  2, Ity_I32, Ity_I32 }, // no folding yet
+// { OPNAME(MullU64), Ity_I128, 2, Ity_I64, Ity_I64 }, // 128 bit
+
+// { OPNAME(MullS8),  Ity_I16,  2, Ity_I8,  Ity_I8  }, // no folding yet
+// { OPNAME(MullS16), Ity_I32,  2, Ity_I16, Ity_I16 }, // no folding yet
+   { OPNAME(MullS32), Ity_I64,  2, Ity_I32, Ity_I32 },
+// { OPNAME(MullS64), Ity_I128, 2, Ity_I64, Ity_I64 }, // 128 bit
+
+// { OPNAME(DivU32),  Ity_I32,  2, Ity_I32,  Ity_I32  }, // no folding yet
+// { OPNAME(DivU64),  Ity_I64,  2, Ity_I64,  Ity_I64  }, // no folding yet
+// { OPNAME(DivU128), Ity_I128, 2, Ity_I128, Ity_I128 }, // 128 bit
+
+// { OPNAME(DivS32),  Ity_I32,  2, Ity_I32,  Ity_I32  }, // no folding yet
+// { OPNAME(DivS64),  Ity_I64,  2, Ity_I64,  Ity_I64  }, // no folding yet
+// { OPNAME(DivS128), Ity_I128, 2, Ity_I128, Ity_I128 }, // 128 bit
+
+// { OPNAME(DivU32E),  Ity_I32,  2, Ity_I32,  Ity_I32  }, // semantics ?
+// { OPNAME(DivU64E),  Ity_I32,  2, Ity_I32,  Ity_I32  }, // semantics ?
+// { OPNAME(DivU128E), Ity_I128, 2, Ity_I128, Ity_I128 }, // 128 bit
+
+// { OPNAME(DivS32E),  Ity_I32,  2, Ity_I32,  Ity_I32  }, // semantics ?
+// { OPNAME(DivS64E),  Ity_I32,  2, Ity_I32,  Ity_I32  }, // semantics ?
+// { OPNAME(DivS128E), Ity_I128, 2, Ity_I128, Ity_I128 }, // 128 bit
+
+// { OPNAME(DivModU32to32),  Ity_I64,  2, Ity_I32, Ity_I64  }, // no folding yet
+// { OPNAME(DivModU64to32),  Ity_I64,  2, Ity_I32, Ity_I64  }, // no folding yet
+// { OPNAME(DivModU64to64),  Ity_I64,  2, Ity_I64, Ity_I128 }, // 128 bit
+// { OPNAME(DivModU128to64), Ity_I128, 2, Ity_I64, Ity_I128 }, // 128 bit
+
+// { OPNAME(DivModS32to32),  Ity_I64,  2, Ity_I32, Ity_I32  }, // no folding yet
+// { OPNAME(DivModS32to32),  Ity_I64,  2, Ity_I32, Ity_I64  }, // no folding yet
+// { OPNAME(DivModS64to64),  Ity_I64,  2, Ity_I64, Ity_I128 }, // 128 bit
+// { OPNAME(DivModU128to64), Ity_I128, 2, Ity_I64, Ity_I128 }, // 128 bit
+
+// { OPNAME(ModU128), Ity_I128, 2, Ity_I128, Ity_I128 }, // 128 bit
+// { OPNAME(ModS128), Ity_I128, 2, Ity_I128, Ity_I128 }, // 128 bit
+
+// { OPNAME(Shl8),  Ity_I8,  2, Ity_I8,  Ity_I8 },  // no folding yet
+// { OPNAME(Shl16), Ity_I16, 2, Ity_I16, Ity_I8 },  // no folding yet
+   { OPNAME(Shl32), Ity_I32, 2, Ity_I32, Ity_I8 },
+   { OPNAME(Shl64), Ity_I64, 2, Ity_I64, Ity_I8 },
+
+// { OPNAME(Shr8),  Ity_I8,  2, Ity_I8,  Ity_I8 },  // no folding yet
+// { OPNAME(Shr16), Ity_I16, 2, Ity_I16, Ity_I8 },  // no folding yet
+   { OPNAME(Shr32), Ity_I32, 2, Ity_I32, Ity_I8 },
+   { OPNAME(Shr64), Ity_I64, 2, Ity_I64, Ity_I8 },
+
+// { OPNAME(Sar8),  Ity_I8,  2, Ity_I8,  Ity_I8 },  // no folding yet
+// { OPNAME(Sar16), Ity_I16, 2, Ity_I16, Ity_I8 },  // no folding yet
+   { OPNAME(Sar32), Ity_I32, 2, Ity_I32, Ity_I8 },
+   { OPNAME(Sar64), Ity_I64, 2, Ity_I64, Ity_I8 },
+
+   { OPNAME(Or1),   Ity_I1,  2, Ity_I1,  Ity_I1  },
+   { OPNAME(Or8),   Ity_I8,  2, Ity_I8,  Ity_I8  },
+   { OPNAME(Or16),  Ity_I16, 2, Ity_I16, Ity_I16 },
+   { OPNAME(Or32),  Ity_I32, 2, Ity_I32, Ity_I32 },
+   { OPNAME(Or64),  Ity_I64, 2, Ity_I64, Ity_I64 },
+
+   { OPNAME(And1),  Ity_I1,  2, Ity_I1,  Ity_I1  },
+   { OPNAME(And8),  Ity_I8,  2, Ity_I8,  Ity_I8  },
+   { OPNAME(And16), Ity_I16, 2, Ity_I16, Ity_I16 },
+   { OPNAME(And32), Ity_I32, 2, Ity_I32, Ity_I32 },
+   { OPNAME(And64), Ity_I64, 2, Ity_I64, Ity_I64 },
+
+// { OPNAME(Xor1),  Ity_I1,  2, Ity_I1,  Ity_I1  }, // missing in libvex_ir.h
+   { OPNAME(Xor8),  Ity_I8,  2, Ity_I8,  Ity_I8  },
+   { OPNAME(Xor16), Ity_I16, 2, Ity_I16, Ity_I16 },
+   { OPNAME(Xor32), Ity_I32, 2, Ity_I32, Ity_I32 },
+   { OPNAME(Xor64), Ity_I64, 2, Ity_I64, Ity_I64 },
+
+// { OPNAME(CmpEQ8),  Ity_I1,  2, Ity_I8,  Ity_I8  }, // no folding yet
+// { OPNAME(CmpEQ16), Ity_I1,  2, Ity_I16, Ity_I16 }, // no folding yet
+   { OPNAME(CmpEQ32), Ity_I1,  2, Ity_I32, Ity_I32 },
+   { OPNAME(CmpEQ64), Ity_I1,  2, Ity_I64, Ity_I64 },
+
+   { OPNAME(CmpNE8),  Ity_I1,  2, Ity_I8,  Ity_I8  },
+// { OPNAME(CmpNE16), Ity_I1,  2, Ity_I16, Ity_I16 }, // no folding yet
+   { OPNAME(CmpNE32), Ity_I1,  2, Ity_I32, Ity_I32 },
+   { OPNAME(CmpNE64), Ity_I1,  2, Ity_I64, Ity_I64 },
+
+   { OPNAME(CmpLT32U), Ity_I1,  2, Ity_I32, Ity_I32 },
+   { OPNAME(CmpLT64U), Ity_I1,  2, Ity_I64, Ity_I64 },
+
+   { OPNAME(CmpLT32S), Ity_I1,  2, Ity_I32, Ity_I32 },
+   { OPNAME(CmpLT64S), Ity_I1,  2, Ity_I64, Ity_I64 },
+
+   { OPNAME(CmpLE32U), Ity_I1,  2, Ity_I32, Ity_I32 },
+   { OPNAME(CmpLE64U), Ity_I1,  2, Ity_I64, Ity_I64 },
+
+   { OPNAME(CmpLE32S), Ity_I1,  2, Ity_I32, Ity_I32 },
+   { OPNAME(CmpLE64S), Ity_I1,  2, Ity_I64, Ity_I64 },
+
+// { OPNAME(CasCmpEQ8),  Ity_I1,  2, Ity_I8,  Ity_I8  }, // no folding yet
+// { OPNAME(CasCmpEQ16), Ity_I1,  2, Ity_I16, Ity_I16 }, // no folding yet
+// { OPNAME(CasCmpEQ32), Ity_I1,  2, Ity_I32, Ity_I32 }, // no folding yet
+// { OPNAME(CasCmpEQ64), Ity_I1,  2, Ity_I64, Ity_I64 }, // no folding yet
+
+   { OPNAME(CasCmpNE8),  Ity_I1,  2, Ity_I8,  Ity_I8  },
+// { OPNAME(CasCmpNE16), Ity_I1,  2, Ity_I16, Ity_I16 }, // no folding yet
+   { OPNAME(CasCmpNE32), Ity_I1,  2, Ity_I32, Ity_I32 },
+   { OPNAME(CasCmpNE64), Ity_I1,  2, Ity_I64, Ity_I64 },
+
+   { OPNAME(ExpCmpNE8),  Ity_I1,  2, Ity_I8,  Ity_I8  },
+// { OPNAME(ExpCmpNE16), Ity_I1,  2, Ity_I16, Ity_I16 }, // no folding yet
+   { OPNAME(ExpCmpNE32), Ity_I1,  2, Ity_I32, Ity_I32 },
+   { OPNAME(ExpCmpNE64), Ity_I1,  2, Ity_I64, Ity_I64 },
+
+// { OPNAME(CmpORD32U), Ity_I32,  2, Ity_I32, Ity_I32 }, // no folding yet
+// { OPNAME(CmpORD64U), Ity_I64,  2, Ity_I64, Ity_I64 }, // no folding yet
+
+   { OPNAME(CmpORD32S), Ity_I32,  2, Ity_I32, Ity_I32 },
+// { OPNAME(CmpORD64S), Ity_I64,  2, Ity_I64, Ity_I64 }, // no folding yet
+
+   { OPNAME(CmpNEZ8),   Ity_I1,  1, Ity_I8  },
+// { OPNAME(CmpNEZ16),  Ity_I1,  1, Ity_I16 }, // no folding yet
+   { OPNAME(CmpNEZ32),  Ity_I1,  1, Ity_I32 },
+   { OPNAME(CmpNEZ64),  Ity_I1,  1, Ity_I64 },
+
+   { OPNAME(CmpwNEZ32), Ity_I32,  1, Ity_I32 },
+   { OPNAME(CmpwNEZ64), Ity_I64,  1, Ity_I64 },
+
+// { OPNAME(Left8),  Ity_I8,   1, Ity_I8  }, // no folding yet
+// { OPNAME(Left16), Ity_I16,  1, Ity_I16 }, // no folding yet
+   { OPNAME(Left32), Ity_I32,  1, Ity_I32 },
+   { OPNAME(Left64), Ity_I64,  1, Ity_I64 },
+
+   { OPNAME(Max32U), Ity_I32,  2, Ity_I32, Ity_I32 },
+
+// { OPNAME(Clz32),  Ity_I32,  1, Ity_I32 }, // deprecated, undefined behaviour
+// { OPNAME(Clz64),  Ity_I64,  1, Ity_I64 }, // deprecated, undefined behaviour
+
+// { OPNAME(Ctz32),  Ity_I32,  1, Ity_I32 }, // deprecated, undefined behaviour
+// { OPNAME(Ctz64),  Ity_I64,  1, Ity_I64 }, // deprecated, undefined behaviour
+
+// { OPNAME(ClzNat32), Ity_I32, 1, Ity_I32 }, // no folding yet
+// { OPNAME(ClzNat64), Ity_I64, 1, Ity_I64 }, // no folding yet
+
+// { OPNAME(CtzNat32), Ity_I32, 1, Ity_I32 }, // no folding yet
+// { OPNAME(CtzNat64), Ity_I64, 1, Ity_I64 }, // no folding yet
+
+// { OPNAME(PopCount32), Ity_I32, 1, Ity_I32 }, // no folding yet
+// { OPNAME(PopCount64), Ity_I64, 1, Ity_I64 }, // no folding yet
+
+// { OPNAME(8HLto16),   Ity_I16,  2, Ity_I8,  Ity_I8  }, // no folding yet
+// { OPNAME(16HLto32),  Ity_I32,  2, Ity_I16, Ity_I16 }, // no folding yet
+   { OPNAME(32HLto64),  Ity_I64,  2, Ity_I32, Ity_I32 },
+// { OPNAME(64HLto128), Ity_I128, 2, Ity_I64, Ity_I64 }, // 128 bit
diff --git a/none/tests/iropt-test/iropt-test-sec.stderr.exp b/none/tests/iropt-test/iropt-test-sec.stderr.exp
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/none/tests/iropt-test/iropt-test-sec.vgtest b/none/tests/iropt-test/iropt-test-sec.vgtest
new file mode 100644 (file)
index 0000000..e8d4cc7
--- /dev/null
@@ -0,0 +1,4 @@
+prog: iropt-test
+prereq: test -x iropt-test-sec
+vgopts: -q --vex-guest-chase=no
+
diff --git a/none/tests/iropt-test/iropt-test.stderr.exp b/none/tests/iropt-test/iropt-test.stderr.exp
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/none/tests/iropt-test/iropt-test.vgtest b/none/tests/iropt-test/iropt-test.vgtest
new file mode 100644 (file)
index 0000000..8becafd
--- /dev/null
@@ -0,0 +1,4 @@
+prog: iropt-test
+#args: -v -v
+vgopts: -q --vex-guest-chase=no
+
diff --git a/none/tests/iropt-test/main.c b/none/tests/iropt-test/main.c
new file mode 100644 (file)
index 0000000..28b8d23
--- /dev/null
@@ -0,0 +1,135 @@
+/* -*- mode: C; c-basic-offset: 3; -*- */
+
+/*
+   This file is part of Valgrind, a dynamic binary instrumentation
+   framework.
+
+   Copyright (C) 2025  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
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+   The GNU General Public License is contained in the file COPYING.
+*/
+
+#include <assert.h>    // assert
+#include <stdio.h>     // printf
+#include <stdlib.h>    // malloc
+#include <string.h>    // memset
+#include "valgrind.h"  // RUNNING_ON_VALGRIND
+#include "vtest.h"
+
+/* Table of IROps. */
+static irop_t irops[] = {
+   #include "irops.tab"
+};
+
+static void check_irops_table(void);
+static test_data_t *new_test_data(const irop_t *);
+
+int verbose = 0;
+
+
+int
+main(int argc, char *argv[])
+{
+   assert(sizeof(long long) == 8);
+
+   for (int i = 1; i < argc; ++i) {
+      if (strcmp(argv[i], "-v") == 0)
+         ++verbose;
+      else if (strcmp(argv[i], "--help") == 0) {
+        printf("\niropt-test [ -v | --help ]\n");
+        printf("\n\t -v    verbose mode; shows IROps being tested\n");
+        printf("\n\t -v -v verbose mode, extreme edition\n\n");
+        return 0;
+      } else {
+        printf("%s ?  Nothing happens.\n", argv[i]);
+        return 1;
+      }
+   }
+
+   if (! RUNNING_ON_VALGRIND) {
+     fprintf(stderr, "*** This program needs to run under valgrind.\n");
+     return 1;
+   }
+
+   check_irops_table();
+
+   setbuf(stdout, NULL);  // make stdout unbuffered
+
+   for (unsigned i = 0; i < NUM_EL(irops); ++i) {
+      const irop_t *op = irops +i;
+
+      if (verbose)
+         printf("\nTesting operator %s\n", op->name);
+
+      test_data_t *data = new_test_data(op);
+
+      IRICB iricb = new_iricb(op, data);
+
+      valgrind_vex_init_for_iri(&iricb);
+
+      switch (op->num_opnds) {
+      case 1:
+         test_unary_op(op, data);
+         break;
+
+      case 2:
+         test_binary_op(op, data);
+         break;
+
+      default:
+         panic("operator %s not handled", op->name);
+      }
+
+      free(data);
+   }
+
+   return 0;
+}
+
+
+static void
+check_irops_table(void)
+{
+   for (unsigned i = 0; i < sizeof irops / sizeof *irops; ++i) {
+      const irop_t *op = irops +i;
+
+      IRType t_res, t_opnd1, t_opnd2, t_opnd3, t_opnd4;
+
+      typeOfPrimop(op->op, &t_res, &t_opnd1, &t_opnd2, &t_opnd3, &t_opnd4);
+
+      if (op->result_type != t_res   ||
+          op->opnd1_type  != t_opnd1 ||
+          (op->num_opnds == 2 && op->opnd2_type  != t_opnd2))
+         fprintf(stderr, "%s: type mismatch\n", op->name);
+   }
+}
+
+
+static test_data_t *
+new_test_data(const irop_t *op)
+{
+   test_data_t *data = malloc(sizeof *data);
+
+   memset(data, 0x0, sizeof *data);  // initialise
+
+   data->result.type = op->result_type;
+
+   data->opnds[0].type = op->opnd1_type;
+   if (op->num_opnds > 1)
+      data->opnds[1].type = op->opnd2_type;
+
+   return data;
+}
diff --git a/none/tests/iropt-test/unary.c b/none/tests/iropt-test/unary.c
new file mode 100644 (file)
index 0000000..51ad515
--- /dev/null
@@ -0,0 +1,229 @@
+/* -*- mode: C; c-basic-offset: 3; -*- */
+
+/*
+   This file is part of Valgrind, a dynamic binary instrumentation
+   framework.
+
+   Copyright (C) 2025  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
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+   The GNU General Public License is contained in the file COPYING.
+*/
+
+#include <stdio.h>      // printf
+#include <stdint.h>     // UINT64_MAX
+#include "vtest.h"
+
+static void check_result(const irop_t *, const test_data_t *);
+static void run_tests(const irop_t *, test_data_t *, unsigned, uint64_t *);
+
+
+void
+test_unary_op(const irop_t *op, test_data_t *data)
+{
+   opnd_t *opnd = &data->opnds[0];
+
+   switch (opnd->type) {
+   case Ity_I1: {
+      uint64_t values[] = { 0, 1 };
+
+      run_tests(op, data, NUM_EL(values), values);
+      break;
+   }
+
+   case Ity_I8: {
+      uint64_t values[] = { 0, 1, 2, UINT8_MAX - 1, UINT8_MAX };
+
+      run_tests(op, data, NUM_EL(values), values);
+      break;
+   }
+
+   case Ity_I16: {
+      uint64_t values[] = { 0, 1, 2, UINT16_MAX - 1, UINT16_MAX,
+         /* and for the benefit of Iop_16HIto8: */
+         1 << 8, 2 << 8, UINT8_MAX << 8
+      };
+
+      run_tests(op, data, NUM_EL(values), values);
+      break;
+   }
+
+   case Ity_I32: {
+      uint64_t values[] = { 0, 1, 2, UINT32_MAX - 1, UINT32_MAX,
+         /* and for the benefit of Iop_32HIto16: */
+         1 << 16, 2 << 16, (uint64_t)UINT16_MAX << 16
+      };
+      run_tests(op, data, NUM_EL(values), values);
+      break;
+   }
+
+   case Ity_I64: {
+      uint64_t values[] = { 0, 1, 2, UINT64_MAX - 1, UINT64_MAX,
+         /* and for the benefit of Iop_64HIto32: */
+         (uint64_t)1 << 32, (uint64_t)2 << 32, (uint64_t)UINT32_MAX << 32
+      };
+      run_tests(op, data, NUM_EL(values), values);
+      break;
+   }
+
+   default:
+      panic(__func__);
+   }
+}
+
+
+static void
+run_tests(const irop_t *op, test_data_t *data, unsigned num_val,
+          uint64_t *values)
+{
+   opnd_t *opnd = &data->opnds[0];
+
+   for (unsigned i = 0; i < num_val; ++i) {
+      opnd->value = values[i];
+
+      valgrind_execute_test(op, data);
+      check_result(op, data);
+   }
+}
+
+
+/* Check the result of a unary operation. */
+static void
+check_result(const irop_t *op, const test_data_t *data)
+{
+   uint64_t result = data->result.value;
+   uint64_t opnd   = data->opnds[0].value;
+   uint64_t expected;
+
+   switch (op->op) {
+   case Iop_Not1:   expected = ~opnd & 0x1;        break;
+   case Iop_Not8:   expected = ~opnd & UINT8_MAX;  break;
+   case Iop_Not16:  expected = ~opnd & UINT16_MAX; break;
+   case Iop_Not32:  expected = ~opnd & UINT32_MAX; break;
+   case Iop_Not64:  expected = ~opnd & UINT64_MAX; break;
+
+   case Iop_1Uto8:  expected = opnd; break;
+// case Iop_1Uto16: expected = opnd; break;
+   case Iop_1Uto32: expected = opnd; break;
+   case Iop_1Uto64: expected = opnd; break;
+
+   case Iop_1Sto8:
+      expected = sign_extend(opnd, 1) & UINT8_MAX;
+      break;
+   case Iop_1Sto16:
+      expected = sign_extend(opnd, 1) & UINT16_MAX;
+      break;
+   case Iop_1Sto32:
+      expected = sign_extend(opnd, 1) & UINT32_MAX;
+      break;
+   case Iop_1Sto64:
+      expected = sign_extend(opnd, 1) & UINT64_MAX;
+      break;
+
+   case Iop_8Uto16: expected = opnd; break;
+   case Iop_8Uto32: expected = opnd; break;
+   case Iop_8Uto64: expected = opnd; break;
+
+   case Iop_8Sto16:
+      expected = sign_extend(opnd, 8) & UINT16_MAX;
+      break;
+   case Iop_8Sto32:
+      expected = sign_extend(opnd, 8) & UINT32_MAX;
+      break;
+   case Iop_8Sto64:
+      expected = sign_extend(opnd, 8) & UINT64_MAX;
+      break;
+
+   case Iop_16Uto32: expected = opnd; break;
+   case Iop_16Uto64: expected = opnd; break;
+
+   case Iop_16Sto32:
+      expected = sign_extend(opnd, 16) & UINT32_MAX;
+      break;
+   case Iop_16Sto64:
+      expected = sign_extend(opnd, 16) & UINT64_MAX;
+      break;
+
+   case Iop_32Uto64: expected = opnd; break;
+
+   case Iop_32Sto64:
+      expected = sign_extend(opnd, 32) & UINT64_MAX;
+      break;
+
+// case Iop_8to1:    expected = opnd & 0x1;       break;
+// case Iop_16to1:   expected = opnd & 0x1;       break;
+   case Iop_16to8:   expected = opnd & UINT8_MAX; break;
+   case Iop_16HIto8: expected = opnd >> 8;        break;
+      break;
+
+   case Iop_32to1:    expected = opnd & 0x1;        break;
+   case Iop_32to8:    expected = opnd & UINT8_MAX;  break;
+   case Iop_32to16:   expected = opnd & UINT16_MAX; break;
+   case Iop_32HIto16: expected = opnd >> 16;        break;
+
+   case Iop_64to1:    expected = opnd & 0x1;        break;
+   case Iop_64to8:    expected = opnd & UINT8_MAX;  break;
+   case Iop_64to16:   expected = opnd & UINT16_MAX; break;
+   case Iop_64to32:   expected = opnd & UINT32_MAX; break;
+   case Iop_64HIto32: expected = opnd >> 32;        break;
+
+   case Iop_CmpNEZ8:
+// case Iop_CmpNEZ16:
+   case Iop_CmpNEZ32:
+   case Iop_CmpNEZ64:
+      expected = opnd != 0;
+      break;
+
+   case Iop_CmpwNEZ32: expected = opnd == 0 ? 0 : UINT32_MAX; break;
+   case Iop_CmpwNEZ64: expected = opnd == 0 ? 0 : UINT64_MAX; break;
+
+// case Iop_Left8:
+// case Iop_Left16:
+   case Iop_Left32: {
+      int32_t opnd_s = (int32_t)opnd;
+      expected = (opnd_s | -opnd_s) & UINT32_MAX;
+      break;
+   }
+
+   case Iop_Left64: {
+      int64_t opnd_s = (int64_t)opnd;
+      expected = (opnd_s | -opnd_s) & UINT64_MAX;
+      break;
+   }
+
+   default:
+      panic("%s: operator %s not handled\n", __func__, op->name);
+   }
+
+   if (verbose > 1) {
+      printf("expected:  value = ");
+      print_value(stdout, expected, bitsof_irtype(data->result.type));
+      printf("\n");
+   }
+
+   int ok = 1;
+   switch (data->result.type) {
+   case Ity_I1:  ok = result == expected; break;
+   case Ity_I8:  ok = result == expected; break;
+   case Ity_I16: ok = result == expected; break;
+   case Ity_I32: ok = result == expected; break;
+   case Ity_I64: ok = result == expected; break;
+   default:
+      panic(__func__);
+   }
+
+   if (! ok)
+      complain(op, data, expected);
+}
diff --git a/none/tests/iropt-test/util.c b/none/tests/iropt-test/util.c
new file mode 100644 (file)
index 0000000..80e5857
--- /dev/null
@@ -0,0 +1,106 @@
+/* -*- mode: C; c-basic-offset: 3; -*- */
+
+/*
+   This file is part of Valgrind, a dynamic binary instrumentation
+   framework.
+
+   Copyright (C) 2025  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
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+   The GNU General Public License is contained in the file COPYING.
+*/
+
+#include <stdio.h>     // fprintf
+#include <stdlib.h>    // exit
+#include <stdarg.h>    // va_list
+#include <inttypes.h>  // PRIx...
+#include "vtest.h"
+
+
+/* Something bad happened. Cannot continue. */
+void __attribute__((noreturn))
+panic(const char *fmt, ...)
+{
+   va_list args;
+
+   va_start(args, fmt);
+   fprintf(stderr, "*** OOPS: ");
+   vfprintf(stderr, fmt, args);
+   fputc('\n', stderr);
+   va_end(args);
+   exit(1);
+}
+
+
+/* Issue a complaint because the result of an operation differs from what
+   was expected. */
+void
+complain(const irop_t *op, const test_data_t *data, uint64_t expected)
+{
+   fprintf(stderr, "*** Incorrect result for operator %s\n", op->name);
+
+   for (unsigned i = 0; i < op->num_opnds; ++i) {
+      fprintf(stderr, "    opnd %u:  ", i);
+      print_opnd(stderr, &data->opnds[i]);
+      fprintf(stderr, "\n");
+   }
+   fprintf(stderr, "    result:  ");
+   print_opnd(stderr, &data->result);
+   fprintf(stderr, "\n");
+   fprintf(stderr, "    expect:  ");
+   print_value(stderr, expected, bitsof_irtype(op->result_type));
+   fprintf(stderr, "\n");
+}
+
+
+void
+print_value(FILE *fp, uint64_t val, unsigned num_bits)
+{
+   switch (num_bits) {
+   case 1:  fprintf(fp, "%01"  PRIx64, val); break;
+   case 8:  fprintf(fp, "%02"  PRIx64, val); break;
+   case 16: fprintf(fp, "%04"  PRIx64, val); break;
+   case 32: fprintf(fp, "%08"  PRIx64, val); break;
+   case 64: fprintf(fp, "%016" PRIx64, val); break;
+   case 128:
+   case 256:
+      /* fall through */
+   default:
+      panic("%s: num_bits = %u", __func__, num_bits);
+   }
+}
+
+
+void
+print_opnd(FILE *fp, const opnd_t *opnd)
+{
+   fprintf(fp, "value = ");
+   print_value(fp, opnd->value, bitsof_irtype(opnd->type));
+}
+
+
+unsigned
+bitsof_irtype(IRType ty)
+{
+   switch (ty) {
+   case Ity_I1:  return 1;
+   case Ity_I8:  return 8;
+   case Ity_I16: return 16;
+   case Ity_I32: return 32;
+   case Ity_I64: return 64;
+   default:
+      panic(__func__);
+   }
+}
diff --git a/none/tests/iropt-test/valgrind.c b/none/tests/iropt-test/valgrind.c
new file mode 100644 (file)
index 0000000..e482909
--- /dev/null
@@ -0,0 +1,92 @@
+/* -*- mode: C; c-basic-offset: 3; -*- */
+
+/*
+   This file is part of Valgrind, a dynamic binary instrumentation
+   framework.
+
+   Copyright (C) 2025  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
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+   The GNU General Public License is contained in the file COPYING.
+*/
+
+#include <string.h>    // memset
+#include "valgrind.h"  // VALGRIND_VEX_INJECT_IR
+#include "vtest.h"
+
+
+/* Return a completely initialised control block */
+IRICB
+new_iricb(const irop_t *op, test_data_t *data)
+{
+   IRICB_iropt_payload cb;
+
+   memset(&cb, 0x0, sizeof cb);
+
+   cb.op = op->op;
+   cb.result = (HWord)&data->result.value;
+   cb.opnd1  = (HWord)&data->opnds[0].value;
+   cb.opnd2  = (HWord)&data->opnds[1].value;
+   cb.t_result = data->result.type;
+   cb.t_opnd1  = data->opnds[0].type;
+   cb.t_opnd2  = data->opnds[1].type;
+
+   cb.num_operands = op->num_opnds;
+
+   return (IRICB) { .kind = IRICB_iropt, .iropt = cb };
+}
+
+
+/* Insert a client request that will initialize VEX for IR injection */
+void
+valgrind_vex_init_for_iri(IRICB *cb)
+{
+   VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__VEX_INIT_FOR_IRI, cb, 0,0,0,0);
+}
+
+
+/* Insert a special opcode that will cause VEX to inject an IR stmt based
+   on the information passed in the IRICB (in valgrind_vex_init_for_iri). */
+static void
+valgrind_vex_inject_ir(void)
+{
+   VALGRIND_VEX_INJECT_IR();
+}
+
+
+/* Execute the test under valgrind. Well, yes, we're not really executing
+   it here, just preparing for it... */
+void
+valgrind_execute_test(const irop_t *op, test_data_t *data)
+{
+   if (verbose > 1)
+      printf("---------- Running a test\n");
+
+   for (unsigned i = 0; i < op->num_opnds; ++i) {
+      if (verbose > 1) {
+         printf("opnd #%u:   ", i);
+         print_opnd(stdout, &data->opnds[i]);
+         printf("\n");
+      }
+   }
+
+   valgrind_vex_inject_ir();
+
+   if (verbose > 1) {
+      printf("result:    ");
+      print_opnd(stdout, &data->result);
+      printf("\n");
+   }
+}
diff --git a/none/tests/iropt-test/vtest.h b/none/tests/iropt-test/vtest.h
new file mode 100644 (file)
index 0000000..d9c1dda
--- /dev/null
@@ -0,0 +1,91 @@
+/* -*- mode: C; c-basic-offset: 3; -*- */
+
+/*
+   This file is part of Valgrind, a dynamic binary instrumentation
+   framework.
+
+   Copyright (C) 2025  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
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+   The GNU General Public License is contained in the file COPYING.
+*/
+
+#ifndef VTEST_H
+#define VTEST_H
+
+/* Main header file for the iropt tester */
+
+#include <stdint.h>   // uint64_t
+#include <stdio.h>    // FILE
+#include "libvex.h"   // IROp
+
+/* Everything we want to know about an IROp */
+typedef struct {
+   const char *name;
+   IROp op;
+   IRType result_type;
+   unsigned num_opnds;
+   IRType opnd1_type;
+   IRType opnd2_type;
+} irop_t;
+
+
+/* The maximum number of input operands */
+#define MAX_OPERANDS 2
+
+/* An operand of an IROp (also used for the result) */
+typedef struct {
+   IRType   type;
+   uint64_t value;
+} opnd_t;
+
+
+/* Carries the data needed to execute and evaluate a test. I.e.
+   inputs and result. */
+typedef struct {
+   opnd_t result;
+   opnd_t opnds[MAX_OPERANDS];
+} test_data_t;
+
+
+/* Convenience macros */
+#define NUM_EL(x) (sizeof x / sizeof *(x))
+
+/* Sign-extend VAL which is NUM_BITS wide to 64 bit */
+#define sign_extend(val, num_bits) \
+        ((int64_t)((val) << (64 - (num_bits))) >> (64 - (num_bits)))
+
+
+/* Function prototypes */
+void print_opnd(FILE *, const opnd_t *);
+void print_value(FILE *, uint64_t, unsigned);
+
+void test_unary_op(const irop_t *, test_data_t *);
+void test_binary_op(const irop_t *, test_data_t *);
+
+void valgrind_vex_init_for_iri(IRICB *);
+void valgrind_execute_test(const irop_t *, test_data_t *);
+
+IRICB new_iricb(const irop_t *, test_data_t *);
+
+void panic(const char *, ...) __attribute__((noreturn));
+void complain(const irop_t *, const test_data_t *, uint64_t expected);
+
+unsigned bitsof_irtype(IRType);
+
+/* Exported variables */
+extern int verbose;
+
+#endif // VTEST_H