From 1752297cb1d7f3e112104f377d8ee5b99d55408b Mon Sep 17 00:00:00 2001 From: Florian Krohm Date: Sat, 12 Jul 2025 13:32:14 +0000 Subject: [PATCH] Add program to double-check VEX constant folding. BZ 506211 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 --- .gitignore | 12 + Makefile.am | 1 + VEX/priv/ir_inject.c | 75 +++++ VEX/priv/ir_opt.c | 5 + VEX/pub/libvex.h | 15 + VEX/pub/libvex_ir.h | 3 + configure.ac | 1 + none/tests/iropt-test/Makefile.am | 83 +++++ none/tests/iropt-test/binary.c | 315 ++++++++++++++++++ none/tests/iropt-test/filter_stderr | 4 + none/tests/iropt-test/irops.tab | 246 ++++++++++++++ .../iropt-test/iropt-test-sec.stderr.exp | 0 none/tests/iropt-test/iropt-test-sec.vgtest | 4 + none/tests/iropt-test/iropt-test.stderr.exp | 0 none/tests/iropt-test/iropt-test.vgtest | 4 + none/tests/iropt-test/main.c | 135 ++++++++ none/tests/iropt-test/unary.c | 229 +++++++++++++ none/tests/iropt-test/util.c | 106 ++++++ none/tests/iropt-test/valgrind.c | 92 +++++ none/tests/iropt-test/vtest.h | 91 +++++ 20 files changed, 1421 insertions(+) create mode 100644 none/tests/iropt-test/Makefile.am create mode 100644 none/tests/iropt-test/binary.c create mode 100755 none/tests/iropt-test/filter_stderr create mode 100644 none/tests/iropt-test/irops.tab create mode 100644 none/tests/iropt-test/iropt-test-sec.stderr.exp create mode 100644 none/tests/iropt-test/iropt-test-sec.vgtest create mode 100644 none/tests/iropt-test/iropt-test.stderr.exp create mode 100644 none/tests/iropt-test/iropt-test.vgtest create mode 100644 none/tests/iropt-test/main.c create mode 100644 none/tests/iropt-test/unary.c create mode 100644 none/tests/iropt-test/util.c create mode 100644 none/tests/iropt-test/valgrind.c create mode 100644 none/tests/iropt-test/vtest.h diff --git a/.gitignore b/.gitignore index 32cedb0d7c..e4ddf5dc65 100644 --- a/.gitignore +++ b/.gitignore @@ -2244,6 +2244,18 @@ /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* diff --git a/Makefile.am b/Makefile.am index e67356b5a0..a3150abb14 100644 --- a/Makefile.am +++ b/Makefile.am @@ -34,6 +34,7 @@ SUBDIRS = \ gdbserver_tests \ memcheck/tests/vbit-test \ none/tests/s390x/disasm-test \ + none/tests/iropt-test \ auxprogs \ mpi \ solaris \ diff --git a/VEX/priv/ir_inject.c b/VEX/priv/ir_inject.c index f642c834db..750bb588d3 100644 --- a/VEX/priv/ir_inject.c +++ b/VEX/priv/ir_inject.c @@ -33,6 +33,7 @@ #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)) @@ -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) { @@ -329,6 +400,10 @@ 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"); } diff --git a/VEX/priv/ir_opt.c b/VEX/priv/ir_opt.c index ee2c5a4a71..9a3f39c2c9 100644 --- a/VEX/priv/ir_opt.c +++ b/VEX/priv/ir_opt.c @@ -2610,6 +2610,11 @@ static IRExpr* fold_Expr ( IRExpr** env, IRExpr* 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. */ diff --git a/VEX/pub/libvex.h b/VEX/pub/libvex.h index 870747d354..de19e1ebf6 100644 --- a/VEX/pub/libvex.h +++ b/VEX/pub/libvex.h @@ -963,6 +963,7 @@ extern void LibVEX_ShowStats ( void ); typedef enum { IRICB_vbit, + IRICB_iropt, } IRICB_t; @@ -992,11 +993,25 @@ typedef } 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; diff --git a/VEX/pub/libvex_ir.h b/VEX/pub/libvex_ir.h index 803a1317ed..983cd50bca 100644 --- a/VEX/pub/libvex_ir.h +++ b/VEX/pub/libvex_ir.h @@ -2405,6 +2405,9 @@ extern IRExpr* deepCopyIRExpr ( 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 ); diff --git a/configure.ac b/configure.ac index a235f1a424..d64782728b 100755 --- a/configure.ac +++ b/configure.ac @@ -5757,6 +5757,7 @@ AC_CONFIG_FILES([ 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 diff --git a/none/tests/iropt-test/Makefile.am b/none/tests/iropt-test/Makefile.am new file mode 100644 index 0000000000..f4d1e9afa7 --- /dev/null +++ b/none/tests/iropt-test/Makefile.am @@ -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 index 0000000000..c3f36c1884 --- /dev/null +++ b/none/tests/iropt-test/binary.c @@ -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 . + + The GNU General Public License is contained in the file COPYING. +*/ + +#include // printf +#include // 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 index 0000000000..5337954d0f --- /dev/null +++ b/none/tests/iropt-test/filter_stderr @@ -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 index 0000000000..a849289cdd --- /dev/null +++ b/none/tests/iropt-test/irops.tab @@ -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 . + + 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 index 0000000000..e69de29bb2 diff --git a/none/tests/iropt-test/iropt-test-sec.vgtest b/none/tests/iropt-test/iropt-test-sec.vgtest new file mode 100644 index 0000000000..e8d4cc7025 --- /dev/null +++ b/none/tests/iropt-test/iropt-test-sec.vgtest @@ -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 index 0000000000..e69de29bb2 diff --git a/none/tests/iropt-test/iropt-test.vgtest b/none/tests/iropt-test/iropt-test.vgtest new file mode 100644 index 0000000000..8becafdf8c --- /dev/null +++ b/none/tests/iropt-test/iropt-test.vgtest @@ -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 index 0000000000..28b8d23e4b --- /dev/null +++ b/none/tests/iropt-test/main.c @@ -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 . + + The GNU General Public License is contained in the file COPYING. +*/ + +#include // assert +#include // printf +#include // malloc +#include // 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 index 0000000000..51ad51505b --- /dev/null +++ b/none/tests/iropt-test/unary.c @@ -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 . + + The GNU General Public License is contained in the file COPYING. +*/ + +#include // printf +#include // 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 index 0000000000..80e58570fe --- /dev/null +++ b/none/tests/iropt-test/util.c @@ -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 . + + The GNU General Public License is contained in the file COPYING. +*/ + +#include // fprintf +#include // exit +#include // va_list +#include // 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 index 0000000000..e482909b16 --- /dev/null +++ b/none/tests/iropt-test/valgrind.c @@ -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 . + + The GNU General Public License is contained in the file COPYING. +*/ + +#include // 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 index 0000000000..d9c1ddaae5 --- /dev/null +++ b/none/tests/iropt-test/vtest.h @@ -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 . + + 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 // uint64_t +#include // 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 -- 2.39.5