/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*
gdbserver_tests \
memcheck/tests/vbit-test \
none/tests/s390x/disasm-test \
+ none/tests/iropt-test \
auxprogs \
mpi \
solaris \
#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))
}
}
+
+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)
{
vex_inject_ir_vbit(irsb, endian);
break;
+ case IRICB_iropt:
+ vex_inject_ir_iropt(irsb, endian);
+ break;
+
default:
vpanic("unknown IRICB kind");
}
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. */
typedef
enum {
IRICB_vbit,
+ IRICB_iropt,
}
IRICB_t;
}
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;
+ IRICB_iropt_payload iropt;
};
}
IRICB;
/* 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 );
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
--- /dev/null
+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
--- /dev/null
+/* -*- 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;
+ }
+}
--- /dev/null
+#!/bin/sh
+
+# Nothing to filter.
+sed 's/BLA/BLA/'
--- /dev/null
+/* -*- 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
--- /dev/null
+prog: iropt-test
+prereq: test -x iropt-test-sec
+vgopts: -q --vex-guest-chase=no
+
--- /dev/null
+prog: iropt-test
+#args: -v -v
+vgopts: -q --vex-guest-chase=no
+
--- /dev/null
+/* -*- 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;
+}
--- /dev/null
+/* -*- 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);
+}
--- /dev/null
+/* -*- 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__);
+ }
+}
--- /dev/null
+/* -*- 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");
+ }
+}
--- /dev/null
+/* -*- 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