From 90561a0175dc81ce371f735f3067f221decd9278 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 10 Oct 2026 10:57:56 -0700 Subject: [PATCH 1/9] DynComp: give tag 0 to unary operations whose result is a boolean, mask, or count expr2tags_Unop_DC now takes the operation. Unary operations whose result is a boolean, or a mask or count computed from the operand's bits (CmpNEZ*, CmpwNEZ*, 32to1, 64to1, Clz*, Ctz*, PopCount*, GetMSBs*), give the result tag 0, as binary comparisons do. All other unary operations return the tag of the operand, as before. The operand's tag is passed to MC_(helperc_RETURN_0) rather than discarded. Returning a constant tag would let VEX remove the operand's tag expression as dead code, along with any merge it performed. For example, in 'char lt = (a < b)' the merge of a and b would be lost. --- valgrind/fjalar/kvasir/dyncomp_main.c | 12 ++++ valgrind/fjalar/kvasir/dyncomp_main.h | 1 + valgrind/fjalar/kvasir/dyncomp_translate.c | 79 ++++++++++++++++++++-- 3 files changed, 87 insertions(+), 5 deletions(-) diff --git a/valgrind/fjalar/kvasir/dyncomp_main.c b/valgrind/fjalar/kvasir/dyncomp_main.c index 6ef006b9..059dea2e 100644 --- a/valgrind/fjalar/kvasir/dyncomp_main.c +++ b/valgrind/fjalar/kvasir/dyncomp_main.c @@ -582,6 +582,18 @@ UInt MC_(helperc_MERGE_TAGS_RETURN_0) ( UInt tag1, UInt tag2 ) { } } +// Ignore the tag and return a value of 0. A unary operation whose +// result should have tag 0 passes the tag of its operand to this, +// rather than discarding that tag. Otherwise, VEX would remove the +// computation of that tag as dead code, and with it any merges that +// the computation performs (such as the merge for a comparison whose +// result the unary operation converts). +VG_REGPARM(1) +UInt MC_(helperc_RETURN_0) ( UInt tag ) { + (void)tag; + return 0; +} + /*------------------------------------------------------------------*/ /*--- Linked-lists of tags for garbage collection ---*/ /*------------------------------------------------------------------*/ diff --git a/valgrind/fjalar/kvasir/dyncomp_main.h b/valgrind/fjalar/kvasir/dyncomp_main.h index 4cf16d97..eb3c5fd1 100644 --- a/valgrind/fjalar/kvasir/dyncomp_main.h +++ b/valgrind/fjalar/kvasir/dyncomp_main.h @@ -301,6 +301,7 @@ extern VG_REGPARM(1) UInt MC_(helperc_CREATE_TAG) ( Addr static_id ); extern VG_REGPARM(2) UInt MC_(helperc_MERGE_TAGS) ( UInt, UInt ); extern VG_REGPARM(2) UInt MC_(helperc_MERGE_TAGS_RETURN_0) ( UInt, UInt ); +extern VG_REGPARM(1) UInt MC_(helperc_RETURN_0) ( UInt ); extern VG_REGPARM(3) UInt MC_(helperc_MERGE_3_TAGS) ( UInt, UInt, UInt ); extern VG_REGPARM(3) UInt MC_(helperc_MERGE_4_TAGS) ( UInt, UInt, UInt, UInt ); diff --git a/valgrind/fjalar/kvasir/dyncomp_translate.c b/valgrind/fjalar/kvasir/dyncomp_translate.c index d790f425..51f5ec76 100644 --- a/valgrind/fjalar/kvasir/dyncomp_translate.c +++ b/valgrind/fjalar/kvasir/dyncomp_translate.c @@ -1661,13 +1661,81 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, } static -IRExpr* expr2tags_Unop_DC ( DCEnv* dce, IRAtom* atom ) +IRExpr* expr2tags_Unop_DC ( DCEnv* dce, IROp op, IRAtom* atom ) { IRAtom* vatom = expr2tags_DC( dce, atom ); tl_assert(isOriginalAtom_DC(dce,atom)); - // Do nothing with unary ops. Just evaluate the - // sub-expression and return it: + switch (op) { + + // ---------------------------------------------------------- + // The result is a boolean, or a mask or count computed from + // the bits of the operand, rather than a value of the same kind + // as the operand. As for binary comparisons (see + // expr2tags_Binop_DC), the result gets tag 0. + // ---------------------------------------------------------- + + case Iop_32to1: + case Iop_64to1: + case Iop_Clz32: + case Iop_Clz64: + case Iop_ClzNat32: // only used by ppc + case Iop_ClzNat64: // only used by ppc + case Iop_Clz16x4: // only used by arm + case Iop_Clz16x8: // only used by arm ppc arm64 mips s390 + case Iop_Clz32x2: // only used by arm + case Iop_Clz32x4: // only used by arm ppc arm64 mips s390 + case Iop_Clz64x2: // only used by ppc mips s390 + case Iop_Clz8x16: // only used by arm ppc arm64 mips s390 + case Iop_Clz8x8: // only used by arm + case Iop_CmpNEZ128x1: // unused + case Iop_CmpNEZ16: // unused + case Iop_CmpNEZ16x16: // unused + case Iop_CmpNEZ16x2: // unused + case Iop_CmpNEZ16x4: // only used by arm + case Iop_CmpNEZ16x8: // only used by arm + case Iop_CmpNEZ32: // unused + case Iop_CmpNEZ32x2: // only used by arm + case Iop_CmpNEZ32x4: // only used by arm + case Iop_CmpNEZ32x8: // unused + case Iop_CmpNEZ64: // unused + case Iop_CmpNEZ64x2: // only used by arm + case Iop_CmpNEZ64x4: // unused + case Iop_CmpNEZ8: // unused + case Iop_CmpNEZ8x16: // only used by arm + case Iop_CmpNEZ8x32: // unused + case Iop_CmpNEZ8x4: // unused + case Iop_CmpNEZ8x8: // only used by arm + case Iop_CmpwNEZ32: // unused + case Iop_CmpwNEZ64: // only used by arm + case Iop_Ctz16x8: + case Iop_Ctz8x16: + case Iop_Ctz32: + case Iop_CtzNat32: + case Iop_Ctz32x4: + case Iop_Ctz64: + case Iop_CtzNat64: + case Iop_Ctz64x2: + case Iop_GetMSBs8x16: + case Iop_GetMSBs8x8: + case Iop_PopCount32: // only used by ppc + case Iop_PopCount64: // only used by ppc + // Do not simply return IRExpr_Const(IRConst_UWord(0)): see + // MC_(helperc_RETURN_0). + if (atom->tag == Iex_Const) { + return IRExpr_Const(IRConst_UWord(0)); + } + return mkIRExprCCall (Ity_Word, + 1 /*Int regparms*/, + "MC_(helperc_RETURN_0)", + &MC_(helperc_RETURN_0), + mkIRExprVec_1( vatom )); + + // ---------------------------------------------------------- + // Return the tag of the operand: + // ---------------------------------------------------------- + + default: // pgbovine: Actually, when you widen stuff, don't you want to // create new tags for the new bytes and merge them? // But you can't do that because you only have the word-sized @@ -1957,7 +2025,8 @@ IRExpr* expr2tags_Unop_DC ( DCEnv* dce, IRAtom* atom ) // Iop_Widen8Sto16x8: // only used by arm // Iop_Widen8Uto16x8: // only used by arm - return vatom; + return vatom; + } } /* @@ -2361,7 +2430,7 @@ IRExpr* expr2tags_DC ( DCEnv* dce, IRExpr* e ) ); case Iex_Unop: - return expr2tags_Unop_DC( dce, e->Iex.Unop.arg ); + return expr2tags_Unop_DC( dce, e->Iex.Unop.op, e->Iex.Unop.arg ); case Iex_Load: return expr2tags_LDle_DC( dce, e->Iex.Load.ty, From 6ff40840b9fcd14ae549bb1d113dc6249ff52d75 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 10 Oct 2026 12:18:10 -0700 Subject: [PATCH 2/9] DynComp: merge comparison tags with a dirty call; anchor tag-0 unop operands Comparisons merge their operands' tags with a dirty call, so VEX cannot remove the merge when a later operation discards the comparison's tag. Unary operations that give tag 0 anchor their operand's tag with a dirty MC_(helperc_TAG_NOP) call (except in the dataflow modes) and return constant 0, replacing MC_(helperc_RETURN_0). Iop_32to1 and Iop_64to1 return the operand's tag again, because they also extract data bits. Iop_Cnt* and Iop_Cls* give tag 0. Add a regression test, dyncomp-unop-tag0-test.sh. --- Makefile | 1 + valgrind/fjalar/kvasir/dyncomp_main.c | 12 -- valgrind/fjalar/kvasir/dyncomp_main.h | 1 - valgrind/fjalar/kvasir/dyncomp_translate.c | 136 ++++++++---------- .../fjalar/tests/dyncomp-unop-tag0-test.sh | 55 +++++++ valgrind/fjalar/tests/dyncomp-unop-tag0.c | 53 +++++++ valgrind/fjalar/tests/dyncomp-unop-tag0.goal | 12 ++ 7 files changed, 179 insertions(+), 91 deletions(-) create mode 100755 valgrind/fjalar/tests/dyncomp-unop-tag0-test.sh create mode 100644 valgrind/fjalar/tests/dyncomp-unop-tag0.c create mode 100644 valgrind/fjalar/tests/dyncomp-unop-tag0.goal diff --git a/Makefile b/Makefile index 284f4bad..21c49c9e 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,7 @@ test: # Fjalar tests that do not depend on Daikon fjalar-test: valgrind/fjalar/tests/library-compilation-unit-test.sh + valgrind/fjalar/tests/dyncomp-unop-tag0-test.sh # Kvasir tests daikon-test: ../daikon diff --git a/valgrind/fjalar/kvasir/dyncomp_main.c b/valgrind/fjalar/kvasir/dyncomp_main.c index 059dea2e..6ef006b9 100644 --- a/valgrind/fjalar/kvasir/dyncomp_main.c +++ b/valgrind/fjalar/kvasir/dyncomp_main.c @@ -582,18 +582,6 @@ UInt MC_(helperc_MERGE_TAGS_RETURN_0) ( UInt tag1, UInt tag2 ) { } } -// Ignore the tag and return a value of 0. A unary operation whose -// result should have tag 0 passes the tag of its operand to this, -// rather than discarding that tag. Otherwise, VEX would remove the -// computation of that tag as dead code, and with it any merges that -// the computation performs (such as the merge for a comparison whose -// result the unary operation converts). -VG_REGPARM(1) -UInt MC_(helperc_RETURN_0) ( UInt tag ) { - (void)tag; - return 0; -} - /*------------------------------------------------------------------*/ /*--- Linked-lists of tags for garbage collection ---*/ /*------------------------------------------------------------------*/ diff --git a/valgrind/fjalar/kvasir/dyncomp_main.h b/valgrind/fjalar/kvasir/dyncomp_main.h index eb3c5fd1..4cf16d97 100644 --- a/valgrind/fjalar/kvasir/dyncomp_main.h +++ b/valgrind/fjalar/kvasir/dyncomp_main.h @@ -301,7 +301,6 @@ extern VG_REGPARM(1) UInt MC_(helperc_CREATE_TAG) ( Addr static_id ); extern VG_REGPARM(2) UInt MC_(helperc_MERGE_TAGS) ( UInt, UInt ); extern VG_REGPARM(2) UInt MC_(helperc_MERGE_TAGS_RETURN_0) ( UInt, UInt ); -extern VG_REGPARM(1) UInt MC_(helperc_RETURN_0) ( UInt ); extern VG_REGPARM(3) UInt MC_(helperc_MERGE_3_TAGS) ( UInt, UInt, UInt ); extern VG_REGPARM(3) UInt MC_(helperc_MERGE_4_TAGS) ( UInt, UInt, UInt, UInt ); diff --git a/valgrind/fjalar/kvasir/dyncomp_translate.c b/valgrind/fjalar/kvasir/dyncomp_translate.c index 51f5ec76..f3236ed0 100644 --- a/valgrind/fjalar/kvasir/dyncomp_translate.c +++ b/valgrind/fjalar/kvasir/dyncomp_translate.c @@ -107,6 +107,36 @@ static void setHelperAnns_DC ( DCEnv* dce, IRDirty* di ) { di->fxState[1].repeatLen = 0; } +// Merge the tags vatom1 and vatom2, for an operation whose result +// gets tag 0 (such as a comparison). The merge is a dirty call +// rather than a clean one. VEX would remove a clean call as dead +// code whenever the operations that use the result discard its tag +// (for example, a shift discards the tag of its shift amount, and a +// unary operation that gives tag 0 discards the tag of its operand), +// and the merge would be lost. +static void mergeTagsReturn0_DC ( DCEnv* dce, + IRAtom* vatom1, IRAtom* vatom2 ) { + IRDirty* di = unsafeIRDirty_0_N(2, + "MC_(helperc_MERGE_TAGS_RETURN_0)", + &MC_(helperc_MERGE_TAGS_RETURN_0), + mkIRExprVec_2( vatom1, vatom2 )); + setHelperAnns_DC( dce, di ); + stmt_DC('V', dce, IRStmt_Dirty(di)); +} + +// 'Anchor' the tag vatom by passing it to a NOP dirty call, so that +// the IR optimizer does not delete the computation of vatom, along +// with any tag merges (clean helper calls) that it performs, even +// though the caller discards vatom. +static void anchorTag_DC ( DCEnv* dce, IRAtom* vatom ) { + IRDirty* di = unsafeIRDirty_0_N(1, + "MC_(helperc_TAG_NOP)", + &MC_(helperc_TAG_NOP), + mkIRExprVec_1( vatom )); + setHelperAnns_DC( dce, di ); + stmt_DC('V', dce, IRStmt_Dirty(di)); +} + // A PUT stores a value into the guest state void do_shadow_PUT_DC ( DCEnv* dce, Int offset, IRAtom* atom, IRAtom* vatom ) @@ -256,8 +286,6 @@ IRAtom* handleCCall_DC ( DCEnv* dce, IRAtom* first = expr2tags_DC(dce, exprvec[0]); Int i; IRAtom* cur; - IRDirty* di; - IRTemp datatag; for (i = 1; exprvec[i]; i++) { tl_assert(i < 32); @@ -271,19 +299,7 @@ IRAtom* handleCCall_DC ( DCEnv* dce, } else { /* merge the tags of first and current arguments */ cur = expr2tags_DC(dce, exprvec[i]); - - // (comment added 2006) - // TODO: Why is this dirty rather than clean? - pgbovine - // Because it has side effects? - smcc - datatag = newTemp(dce->mce, Ity_Word, DC); - di = unsafeIRDirty_1_N(datatag, - 2, - "MC_(helperc_MERGE_TAGS_RETURN_0)", - &MC_(helperc_MERGE_TAGS_RETURN_0), - mkIRExprVec_2( first, cur )); - - setHelperAnns_DC( dce, di ); - stmt_DC('V', dce, IRStmt_Dirty(di)); + mergeTagsReturn0_DC(dce, first, cur); } } // Return the tag of the first argument, if there is one @@ -1631,6 +1647,11 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, } } + if (helper == &MC_(helperc_MERGE_TAGS_RETURN_0)) { + mergeTagsReturn0_DC(dce, vatom1, vatom2); + return IRExpr_Const(IRConst_UWord(0)); + } + // Let's try a clean call. It seems to be correct // because of the fact that merging the same 2 things more than // once (in close proximity) doesn't hurt @@ -1675,8 +1696,6 @@ IRExpr* expr2tags_Unop_DC ( DCEnv* dce, IROp op, IRAtom* atom ) // expr2tags_Binop_DC), the result gets tag 0. // ---------------------------------------------------------- - case Iop_32to1: - case Iop_64to1: case Iop_Clz32: case Iop_Clz64: case Iop_ClzNat32: // only used by ppc @@ -1688,6 +1707,12 @@ IRExpr* expr2tags_Unop_DC ( DCEnv* dce, IROp op, IRAtom* atom ) case Iop_Clz64x2: // only used by ppc mips s390 case Iop_Clz8x16: // only used by arm ppc arm64 mips s390 case Iop_Clz8x8: // only used by arm + case Iop_Cls16x4: // only used by arm + case Iop_Cls16x8: // only used by arm arm64 + case Iop_Cls32x2: // only used by arm + case Iop_Cls32x4: // only used by arm arm64 + case Iop_Cls8x16: // only used by arm arm64 + case Iop_Cls8x8: // only used by arm case Iop_CmpNEZ128x1: // unused case Iop_CmpNEZ16: // unused case Iop_CmpNEZ16x16: // unused @@ -1708,6 +1733,8 @@ IRExpr* expr2tags_Unop_DC ( DCEnv* dce, IROp op, IRAtom* atom ) case Iop_CmpNEZ8x8: // only used by arm case Iop_CmpwNEZ32: // unused case Iop_CmpwNEZ64: // only used by arm + case Iop_Cnt8x16: // only used by arm arm64 + case Iop_Cnt8x8: // only used by arm case Iop_Ctz16x8: case Iop_Ctz8x16: case Iop_Ctz32: @@ -1720,16 +1747,19 @@ IRExpr* expr2tags_Unop_DC ( DCEnv* dce, IROp op, IRAtom* atom ) case Iop_GetMSBs8x8: case Iop_PopCount32: // only used by ppc case Iop_PopCount64: // only used by ppc - // Do not simply return IRExpr_Const(IRConst_UWord(0)): see - // MC_(helperc_RETURN_0). - if (atom->tag == Iex_Const) { - return IRExpr_Const(IRConst_UWord(0)); + // The result discards vatom, so anchor it to preserve the tag + // merges that computed it, such as the merge of a and b in + // 'ctz(a + b)'. In dyncomp_dataflow_only_mode and + // dyncomp_dataflow_comparisons_mode, no binary operation merges + // with a clean call (comparisons merge with a dirty call; see + // mergeTagsReturn0_DC), so the anchor is not worth the cost of + // a helper call. + if (atom->tag != Iex_Const + && !dyncomp_dataflow_only_mode + && !dyncomp_dataflow_comparisons_mode) { + anchorTag_DC(dce, vatom); } - return mkIRExprCCall (Ity_Word, - 1 /*Int regparms*/, - "MC_(helperc_RETURN_0)", - &MC_(helperc_RETURN_0), - mkIRExprVec_1( vatom )); + return IRExpr_Const(IRConst_UWord(0)); // ---------------------------------------------------------- // Return the tag of the operand: @@ -1744,7 +1774,8 @@ IRExpr* expr2tags_Unop_DC ( DCEnv* dce, IROp op, IRAtom* atom ) // we only consider the tag of the first bytes of each // operand anyways. // - // For documentation purposes, here is a list of all the unary ops. + // For documentation purposes, here is a list of all the unary ops + // that return the tag of the operand. // Iop_128HIto64 // Iop_128to64 // Iop_16HIto8 @@ -1799,53 +1830,6 @@ IRExpr* expr2tags_Unop_DC ( DCEnv* dce, IROp op, IRAtom* atom ) // Iop_BCDtoDPB: // only used by ppc // Iop_BCD128toI128S: // only used by ppc // Iop_CipherSV128: // only used by ppc - // Iop_Cls16x4: // only used by arm - // Iop_Cls16x8: // only used by arm arm64 - // Iop_Cls32x2: // only used by arm - // Iop_Cls32x4: // only used by arm arm64 - // Iop_Cls8x16: // only used by arm arm64 - // Iop_Cls8x8: // only used by arm - // Iop_Clz32 - // Iop_Clz64 - // Iop_ClzNat32 // only used by ppc - // Iop_ClzNat64 // only used by ppc - // Iop_Clz16x4: // only used by arm - // Iop_Clz16x8: // only used by arm ppc arm64 mips s390 - // Iop_Clz32x2: // only used by arm - // Iop_Clz32x4: // only used by arm ppc arm64 mips s390 - // Iop_Clz64x2: // only used by ppc mips s390 - // Iop_Clz8x16: // only used by arm ppc arm64 mips s390 - // Iop_Clz8x8: // only used by arm - // Iop_CmpNEZ128x1: // unused - // Iop_CmpNEZ16: // unused - // Iop_CmpNEZ16x16: // unused - // Iop_CmpNEZ16x2: // unused - // Iop_CmpNEZ16x4: // only used by arm - // Iop_CmpNEZ16x8: // only used by arm - // Iop_CmpNEZ32: // unused - // Iop_CmpNEZ32x2: // only used by arm - // Iop_CmpNEZ32x4: // only used by arm - // Iop_CmpNEZ32x8: // unused - // Iop_CmpNEZ64: // unused - // Iop_CmpNEZ64x2: // only used by arm - // Iop_CmpNEZ64x4: // unused - // Iop_CmpNEZ8: // unused - // Iop_CmpNEZ8x16: // only used by arm - // Iop_CmpNEZ8x32: // unused - // Iop_CmpNEZ8x4: // unused - // Iop_CmpNEZ8x8: // only used by arm - // Iop_CmpwNEZ32: // unused - // Iop_CmpwNEZ64: // only used by arm - // Iop_Cnt8x16: // only used by arm arm64 - // Iop_Cnt8x8: // only used by arm - // Iop_Ctz16x8: - // Iop_Ctz8x16: - // Iop_Ctz32: - // Iop_CtzNat32: - // Iop_Ctz32x4: - // Iop_Ctz64: - // Iop_CtzNat64: - // Iop_Ctz64x2: // Iop_D128HItoD64: // only used by ppc s390 // Iop_D128LOtoD64: // only used by ppc s390 // Iop_D32toD64: // only used by ppc s390 @@ -1876,8 +1860,6 @@ IRExpr* expr2tags_Unop_DC ( DCEnv* dce, IROp op, IRAtom* atom ) // Iop_F32toI32Ux2_RZ: // only used by arm mips // Iop_F32toI32Ux4_RZ: // only used by arm mips // Iop_F64toF128: // only used by s390 - // Iop_GetMSBs8x16 - // Iop_GetMSBs8x8 // Iop_I32StoD128: // only used by s390 // Iop_I32StoD64: // only used by s390 // Iop_I32StoF128: // only used by s390 @@ -1922,8 +1904,6 @@ IRExpr* expr2tags_Unop_DC ( DCEnv* dce, IROp op, IRAtom* atom ) // Iop_Not64: // Iop_NotV128: // Iop_NotV256: - // Iop_PopCount32: // only used by ppc - // Iop_PopCount64: // only used by ppc // Iop_PwAddL16Sx4: // only used by arm // Iop_PwAddL16Sx8: // only used by arm // Iop_PwAddL16Ux4: // only used by arm diff --git a/valgrind/fjalar/tests/dyncomp-unop-tag0-test.sh b/valgrind/fjalar/tests/dyncomp-unop-tag0-test.sh new file mode 100755 index 00000000..dd165c23 --- /dev/null +++ b/valgrind/fjalar/tests/dyncomp-unop-tag0-test.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +# Tests DynComp's comparability for unary operations whose result is a count +# or a mask computed from the bits of the operand. +# +# Such a result gets tag 0, so it is not comparable to the operand. The tag +# merges that computed the operand (such as the merge of a and b in +# 'ctz(a + b)') must still take effect, even though the unary operation +# discards the operand's tag. + +set -e + +test_dir="$(cd "$(dirname "$0")" && pwd)" +valgrind="${test_dir}/../../inst/bin/valgrind" + +if [ ! -x "${valgrind}" ]; then + echo "$0: Fjalar is not built; run \"make build\" at the top level." >&2 + exit 2 +fi + +output_dir="$(mktemp -d)" +trap 'rm -rf "${output_dir}"' EXIT + +# Fjalar does not read DWARF 5, and it recognizes a function's entry point +# only at the address in the debugging information, so the executable must not +# be position-independent. +gcc -gdwarf-4 -no-pie -O0 -o "${output_dir}/dyncomp-unop-tag0" \ + "${test_dir}/dyncomp-unop-tag0.c" + +decls="${output_dir}/dyncomp-unop-tag0.decls" +status=0 +"${valgrind}" --tool=fjalar --dyncomp --decls-only --decls-file="${decls}" \ + "${output_dir}/dyncomp-unop-tag0" \ + > "${output_dir}/dyncomp-unop-tag0.out" 2>&1 || status=$? + +if [ "${status}" -ne 0 ]; then + echo "$0: FAILED: Fjalar exited with status ${status}" >&2 + cat "${output_dir}/dyncomp-unop-tag0.out" >&2 + exit 1 +fi + +# One line per variable at each exit program point, other than main's: +# " ". +actual="${output_dir}/dyncomp-unop-tag0.comparability" +awk '/^ppt / { ppt = $2 } + /^ variable / { var = $2 } + /^ comparability / { if (ppt ~ /:::EXIT/ && ppt !~ /^\.\.main\(/) + print ppt, var, $2 }' "${decls}" > "${actual}" + +if ! diff -u "${test_dir}/dyncomp-unop-tag0.goal" "${actual}" >&2; then + echo "$0: FAILED: comparability differs from dyncomp-unop-tag0.goal" >&2 + exit 1 +fi + +echo "$0: PASSED" diff --git a/valgrind/fjalar/tests/dyncomp-unop-tag0.c b/valgrind/fjalar/tests/dyncomp-unop-tag0.c new file mode 100644 index 00000000..734ac321 --- /dev/null +++ b/valgrind/fjalar/tests/dyncomp-unop-tag0.c @@ -0,0 +1,53 @@ +/* + This file is part of Fjalar, a dynamic analysis framework for C/C++ + programs. + + Copyright (C) 2026 University of Washington Computer Science & Engineering Department, + Programming Languages and Software Engineering Group + + 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. +*/ + +// Functions whose results DynComp computes with unary operations that give +// tag 0: a count (Ctz) or a mask (GetMSBs) computed from the bits of the +// operand. The result of each function should not be comparable to its +// parameters, but the parameters that interact (via a comparison or an +// addition) should be comparable to each other. +// See dyncomp-unop-tag0-test.sh. + +#include + +// a and b are comparable; x and the result are comparable. +int shift_by_lt(int a, int b, int x) { + return x << (a < b); +} + +// a and b are comparable; the result is comparable to neither. +int ctz_of_sum(unsigned int a, unsigned int b) { + return __builtin_ctz(a + b); +} + +// The result is not comparable to x. +int ctz(unsigned int x) { + return __builtin_ctz(x); +} + +// The result is comparable to neither a nor b. +int movemask(long a, long b) { + __m128i v = _mm_set_epi64x(a, b); + return _mm_movemask_epi8(v); +} + +int main(void) { + int r = 0; + for (int i = 1; i < 5; i++) { + r += shift_by_lt(i, 3, 5); + r += ctz_of_sum(i, 2 * i); + r += ctz(i * 4); + r += movemask(i, -i); + } + return r == 12345; +} diff --git a/valgrind/fjalar/tests/dyncomp-unop-tag0.goal b/valgrind/fjalar/tests/dyncomp-unop-tag0.goal new file mode 100644 index 00000000..fab067b8 --- /dev/null +++ b/valgrind/fjalar/tests/dyncomp-unop-tag0.goal @@ -0,0 +1,12 @@ +..movemask():::EXIT0 a 1 +..movemask():::EXIT0 b 1 +..movemask():::EXIT0 return 2 +..ctz():::EXIT0 x 1 +..ctz():::EXIT0 return 2 +..ctz_of_sum():::EXIT0 a 1 +..ctz_of_sum():::EXIT0 b 1 +..ctz_of_sum():::EXIT0 return 2 +..shift_by_lt():::EXIT0 a 1 +..shift_by_lt():::EXIT0 b 1 +..shift_by_lt():::EXIT0 x 2 +..shift_by_lt():::EXIT0 return 2 From 878a83cbc704a11ad67c453e6735206afaabdd9f Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 10 Oct 2026 17:58:03 -0700 Subject: [PATCH 3/9] DynComp test: make ctz_of_sum's operands independent before the call --- valgrind/fjalar/tests/dyncomp-unop-tag0.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/valgrind/fjalar/tests/dyncomp-unop-tag0.c b/valgrind/fjalar/tests/dyncomp-unop-tag0.c index 734ac321..c557092d 100644 --- a/valgrind/fjalar/tests/dyncomp-unop-tag0.c +++ b/valgrind/fjalar/tests/dyncomp-unop-tag0.c @@ -43,9 +43,13 @@ int movemask(long a, long b) { int main(void) { int r = 0; + // j is computed independently of i, so ctz_of_sum's a and b become + // comparable only through the addition within ctz_of_sum. + unsigned int j = 7; for (int i = 1; i < 5; i++) { + j = j * 3 + 1; r += shift_by_lt(i, 3, 5); - r += ctz_of_sum(i, 2 * i); + r += ctz_of_sum(i, j); r += ctz(i * 4); r += movemask(i, -i); } From a060a96fe27f3bbb58a3a494a1353d96dd599f2f Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 17 Oct 2026 08:43:30 -0700 Subject: [PATCH 4/9] DynComp: merge comparison tags with a dirty call A clean call is removed as dead code when the operations that use its result discard the result's tag, and then the merge is lost. Add mergeTagsReturn0_DC, use it for binary comparisons and in handleCCall_DC, and add the test dyncomp-comparability-test.sh. --- Makefile | 1 + valgrind/fjalar/kvasir/dyncomp_translate.c | 37 ++++++++------ .../tests/dyncomp-comparability-test.sh | 51 +++++++++++++++++++ valgrind/fjalar/tests/dyncomp-comparability.c | 30 +++++++++++ .../fjalar/tests/dyncomp-comparability.goal | 4 ++ 5 files changed, 108 insertions(+), 15 deletions(-) create mode 100755 valgrind/fjalar/tests/dyncomp-comparability-test.sh create mode 100644 valgrind/fjalar/tests/dyncomp-comparability.c create mode 100644 valgrind/fjalar/tests/dyncomp-comparability.goal diff --git a/Makefile b/Makefile index 284f4bad..f560216f 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,7 @@ test: # Fjalar tests that do not depend on Daikon fjalar-test: valgrind/fjalar/tests/library-compilation-unit-test.sh + valgrind/fjalar/tests/dyncomp-comparability-test.sh # Kvasir tests daikon-test: ../daikon diff --git a/valgrind/fjalar/kvasir/dyncomp_translate.c b/valgrind/fjalar/kvasir/dyncomp_translate.c index d790f425..a9ed17e4 100644 --- a/valgrind/fjalar/kvasir/dyncomp_translate.c +++ b/valgrind/fjalar/kvasir/dyncomp_translate.c @@ -107,6 +107,22 @@ static void setHelperAnns_DC ( DCEnv* dce, IRDirty* di ) { di->fxState[1].repeatLen = 0; } +// Merge the tags vatom1 and vatom2, for an operation whose result +// gets tag 0 (such as a comparison). The merge is a dirty call +// rather than a clean one. VEX would remove a clean call as dead +// code whenever the operations that use the result discard its tag +// (for example, a shift discards the tag of its shift amount), and +// the merge would be lost. +static void mergeTagsReturn0_DC ( DCEnv* dce, + IRAtom* vatom1, IRAtom* vatom2 ) { + IRDirty* di = unsafeIRDirty_0_N(2, + "MC_(helperc_MERGE_TAGS_RETURN_0)", + &MC_(helperc_MERGE_TAGS_RETURN_0), + mkIRExprVec_2( vatom1, vatom2 )); + setHelperAnns_DC( dce, di ); + stmt_DC('V', dce, IRStmt_Dirty(di)); +} + // A PUT stores a value into the guest state void do_shadow_PUT_DC ( DCEnv* dce, Int offset, IRAtom* atom, IRAtom* vatom ) @@ -256,8 +272,6 @@ IRAtom* handleCCall_DC ( DCEnv* dce, IRAtom* first = expr2tags_DC(dce, exprvec[0]); Int i; IRAtom* cur; - IRDirty* di; - IRTemp datatag; for (i = 1; exprvec[i]; i++) { tl_assert(i < 32); @@ -271,19 +285,7 @@ IRAtom* handleCCall_DC ( DCEnv* dce, } else { /* merge the tags of first and current arguments */ cur = expr2tags_DC(dce, exprvec[i]); - - // (comment added 2006) - // TODO: Why is this dirty rather than clean? - pgbovine - // Because it has side effects? - smcc - datatag = newTemp(dce->mce, Ity_Word, DC); - di = unsafeIRDirty_1_N(datatag, - 2, - "MC_(helperc_MERGE_TAGS_RETURN_0)", - &MC_(helperc_MERGE_TAGS_RETURN_0), - mkIRExprVec_2( first, cur )); - - setHelperAnns_DC( dce, di ); - stmt_DC('V', dce, IRStmt_Dirty(di)); + mergeTagsReturn0_DC(dce, first, cur); } } // Return the tag of the first argument, if there is one @@ -1631,6 +1633,11 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, } } + if (helper == &MC_(helperc_MERGE_TAGS_RETURN_0)) { + mergeTagsReturn0_DC(dce, vatom1, vatom2); + return IRExpr_Const(IRConst_UWord(0)); + } + // Let's try a clean call. It seems to be correct // because of the fact that merging the same 2 things more than // once (in close proximity) doesn't hurt diff --git a/valgrind/fjalar/tests/dyncomp-comparability-test.sh b/valgrind/fjalar/tests/dyncomp-comparability-test.sh new file mode 100755 index 00000000..8a2782e3 --- /dev/null +++ b/valgrind/fjalar/tests/dyncomp-comparability-test.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +# Tests DynComp's comparability: whether the variables at each exit +# program point are comparable to each other. The expected comparability is +# in dyncomp-comparability.goal. + +set -e + +test_dir="$(cd "$(dirname "$0")" && pwd)" +valgrind="${test_dir}/../../inst/bin/valgrind" + +if [ ! -x "${valgrind}" ]; then + echo "$0: Fjalar is not built; run \"make build\" at the top level." >&2 + exit 2 +fi + +output_dir="$(mktemp -d)" +trap 'rm -rf "${output_dir}"' EXIT + +# Fjalar does not read DWARF 5, and it recognizes a function's entry point +# only at the address in the debugging information, so the executable must not +# be position-independent. +gcc -gdwarf-4 -no-pie -O0 -o "${output_dir}/dyncomp-comparability" \ + "${test_dir}/dyncomp-comparability.c" + +decls="${output_dir}/dyncomp-comparability.decls" +status=0 +"${valgrind}" --tool=fjalar --dyncomp --decls-only --decls-file="${decls}" \ + "${output_dir}/dyncomp-comparability" \ + > "${output_dir}/dyncomp-comparability.out" 2>&1 || status=$? + +if [ "${status}" -ne 0 ]; then + echo "$0: FAILED: Fjalar exited with status ${status}" >&2 + cat "${output_dir}/dyncomp-comparability.out" >&2 + exit 1 +fi + +# One line per variable at each exit program point, other than main's: +# " ". +actual="${output_dir}/dyncomp-comparability.comparability" +awk '/^ppt / { ppt = $2 } + /^ variable / { var = $2 } + /^ comparability / { if (ppt ~ /:::EXIT/ && ppt !~ /^\.\.main\(/) + print ppt, var, $2 }' "${decls}" > "${actual}" + +if ! diff -u "${test_dir}/dyncomp-comparability.goal" "${actual}" >&2; then + echo "$0: FAILED: comparability differs from dyncomp-comparability.goal" >&2 + exit 1 +fi + +echo "$0: PASSED" diff --git a/valgrind/fjalar/tests/dyncomp-comparability.c b/valgrind/fjalar/tests/dyncomp-comparability.c new file mode 100644 index 00000000..39005616 --- /dev/null +++ b/valgrind/fjalar/tests/dyncomp-comparability.c @@ -0,0 +1,30 @@ +/* + This file is part of Fjalar, a dynamic analysis framework for C/C++ + programs. + + Copyright (C) 2026 University of Washington Computer Science & Engineering Department, + Programming Languages and Software Engineering Group + + 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. +*/ + +// Functions whose parameters DynComp should make comparable, or not. +// See dyncomp-comparability-test.sh. + +// a and b are comparable, because they are compared, even though the shift +// discards the tag of the comparison's result; x and the result are +// comparable. +int shift_by_lt(int a, int b, int x) { + return x << (a < b); +} + +int main(void) { + int r = 0; + for (int i = 1; i < 5; i++) { + r += shift_by_lt(i, 3, 5); + } + return r == 12345; +} diff --git a/valgrind/fjalar/tests/dyncomp-comparability.goal b/valgrind/fjalar/tests/dyncomp-comparability.goal new file mode 100644 index 00000000..036485ff --- /dev/null +++ b/valgrind/fjalar/tests/dyncomp-comparability.goal @@ -0,0 +1,4 @@ +..shift_by_lt():::EXIT0 a 1 +..shift_by_lt():::EXIT0 b 1 +..shift_by_lt():::EXIT0 x 2 +..shift_by_lt():::EXIT0 return 2 From 43533a86678d271e0417709d2651872b8fee8bf0 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 17 Oct 2026 09:55:22 -0700 Subject: [PATCH 5/9] DynComp: merge all tags with dirty calls; run fjalar-test in CI Merging tags updates the union-find structure, so VEX must not remove a merge as dead code. Make every tag-merging helper call (MERGE_TAGS, MERGE_3_TAGS, MERGE_4_TAGS) a dirty call via the new mergeTags_DC, not only MERGE_TAGS_RETURN_0. Remove the comments that claimed a clean call is safe, and simplify the constant-argument handling in expr2tags_Binop_DC. Run "make fjalar-test" from test.sh, so that CI runs the Fjalar regression tests, including dyncomp-comparability-test.sh. --- test.sh | 3 + valgrind/fjalar/kvasir/dyncomp_translate.c | 191 +++++++++------------ 2 files changed, 80 insertions(+), 114 deletions(-) diff --git a/test.sh b/test.sh index 5cf30614..2dbf8f2b 100755 --- a/test.sh +++ b/test.sh @@ -47,6 +47,9 @@ make check-options make build +## Fjalar tests that do not depend on Daikon +make fjalar-test + make doc ## Valgrind tests diff --git a/valgrind/fjalar/kvasir/dyncomp_translate.c b/valgrind/fjalar/kvasir/dyncomp_translate.c index a9ed17e4..f84bc792 100644 --- a/valgrind/fjalar/kvasir/dyncomp_translate.c +++ b/valgrind/fjalar/kvasir/dyncomp_translate.c @@ -107,12 +107,26 @@ static void setHelperAnns_DC ( DCEnv* dce, IRDirty* di ) { di->fxState[1].repeatLen = 0; } +// Call a tag-merging helper, such as MC_(helperc_MERGE_TAGS), and +// return the resulting tag. Merging has a side effect: it updates +// the union-find structure that records comparability. Therefore, the +// call must be dirty rather than clean. VEX removes a clean call as +// dead code whenever the operations that use its result discard the +// result's tag (for example, a comparison with a constant, or a shift +// whose shift amount is the result), and then the merge would be lost. +static IRAtom* mergeTags_DC ( DCEnv* dce, Int regparms, + const HChar* hname, void* helper, + IRExpr** args ) { + IRTemp datatag = newTemp(dce->mce, Ity_Word, DC); + IRDirty* di = unsafeIRDirty_1_N(datatag, regparms, hname, helper, args); + setHelperAnns_DC( dce, di ); + stmt_DC('V', dce, IRStmt_Dirty(di)); + return mkexpr(datatag); +} + // Merge the tags vatom1 and vatom2, for an operation whose result -// gets tag 0 (such as a comparison). The merge is a dirty call -// rather than a clean one. VEX would remove a clean call as dead -// code whenever the operations that use the result discard its tag -// (for example, a shift discards the tag of its shift amount), and -// the merge would be lost. +// gets tag 0 (such as a comparison). See mergeTags_DC for why the +// call is dirty. static void mergeTagsReturn0_DC ( DCEnv* dce, IRAtom* vatom1, IRAtom* vatom2 ) { IRDirty* di = unsafeIRDirty_0_N(2, @@ -370,30 +384,30 @@ IRAtom* expr2tags_Qop_DC ( DCEnv* dce, // If we are running in units mode, then we should merge the // tags of the 3rd and 4th operands: if (dyncomp_units_mode) { - return mkIRExprCCall (Ity_Word, - 2 /*Int regparms*/, - "MC_(helperc_MERGE_TAGS)", - &MC_(helperc_MERGE_TAGS), - mkIRExprVec_2( vatom3, vatom4 )); + return mergeTags_DC (dce, + 2 /*Int regparms*/, + "MC_(helperc_MERGE_TAGS)", + &MC_(helperc_MERGE_TAGS), + mkIRExprVec_2( vatom3, vatom4 )); } // Ok, if we are running in the default mode, then we should // merge the tags of the 2nd, 3rd, and 4th operands: else { - return mkIRExprCCall (Ity_Word, - 3 /*Int regparms*/, - "MC_(helperc_MERGE_3_TAGS)", - &MC_(helperc_MERGE_3_TAGS), - mkIRExprVec_3( vatom2, vatom3, vatom4 )); + return mergeTags_DC (dce, + 3 /*Int regparms*/, + "MC_(helperc_MERGE_3_TAGS)", + &MC_(helperc_MERGE_3_TAGS), + mkIRExprVec_3( vatom2, vatom3, vatom4 )); } break; // Concatenation, like the binary Iop_V128HLtoV256, is an interaction. case Iop_64x4toV256: - return mkIRExprCCall (Ity_Word, - 3 /*Int regparms*/, - "MC_(helperc_MERGE_4_TAGS)", - &MC_(helperc_MERGE_4_TAGS), - mkIRExprVec_4( vatom1, vatom2, vatom3, vatom4 )); + return mergeTags_DC (dce, + 3 /*Int regparms*/, + "MC_(helperc_MERGE_4_TAGS)", + &MC_(helperc_MERGE_4_TAGS), + mkIRExprVec_4( vatom1, vatom2, vatom3, vatom4 )); // Unimplemented case Iop_Rotx32: // only used by mips @@ -474,16 +488,16 @@ IRAtom* expr2tags_Triop_DC ( DCEnv* dce, case Iop_Sub64Fx2: case Iop_Sub64Fx4: - return mkIRExprCCall (Ity_Word, - 2 /*Int regparms*/, - "MC_(helperc_MERGE_TAGS)", - &MC_(helperc_MERGE_TAGS), - // VERY IMPORTANT!!! We want to merge the - // tags of the 2nd and 3rd operands!!! - // Because the first one is a rounding - // mode (I think) - /* I32(rm) x F64 x F64 -> F64 */ - mkIRExprVec_2( vatom2, vatom3 )); + return mergeTags_DC (dce, + 2 /*Int regparms*/, + "MC_(helperc_MERGE_TAGS)", + &MC_(helperc_MERGE_TAGS), + // VERY IMPORTANT!!! We want to merge the + // tags of the 2nd and 3rd operands!!! + // Because the first one is a rounding + // mode (I think) + /* I32(rm) x F64 x F64 -> F64 */ + mkIRExprVec_2( vatom2, vatom3 )); case Iop_MulF32: // only used by arm mips s390 arm64 case Iop_DivF32: // only used by arm mips s390 arm64 @@ -507,16 +521,16 @@ IRAtom* expr2tags_Triop_DC ( DCEnv* dce, case Iop_Mul64Fx4: if (!dyncomp_units_mode) { - return mkIRExprCCall (Ity_Word, - 2 /*Int regparms*/, - "MC_(helperc_MERGE_TAGS)", - &MC_(helperc_MERGE_TAGS), - // VERY IMPORTANT!!! We want to merge the - // tags of the 2nd and 3rd operands!!! - // Because the first one is a rounding - // mode (I think) - /* I32(rm) x F64 x F64 -> F64 */ - mkIRExprVec_2( vatom2, vatom3 )); + return mergeTags_DC (dce, + 2 /*Int regparms*/, + "MC_(helperc_MERGE_TAGS)", + &MC_(helperc_MERGE_TAGS), + // VERY IMPORTANT!!! We want to merge the + // tags of the 2nd and 3rd operands!!! + // Because the first one is a rounding + // mode (I think) + /* I32(rm) x F64 x F64 -> F64 */ + mkIRExprVec_2( vatom2, vatom3 )); } // Else fall through ... break; @@ -1583,72 +1597,35 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, } if (helper) { - // Heuristic: - - // At least as observed in one trial, the dirty call - // version had MANY more calls of MC_(helperc_MERGE_TAGS) - // than the clean version, many of which were nonsensical - // merges of tag 0 with a valid tag but nonetheless with - // some valid merges as well. - // However, in 'z = x + y' of SuperSimpleTest, - // that interaction was correctly captured by the clean call. - - // The standard way to make a dirty helper call (I think): - // Tags are always word-sized - // datatag = newIRTemp(dce->bb->tyenv, Ity_Word); - // di = unsafeIRDirty_1_N(datatag, - // 2, - // hname, - // helper, - // mkIRExprVec_2( vatom1, vatom2 )); - - // setHelperAnns_DC( dce, di ); - // stmt( dce->bb, IRStmt_Dirty(di) ); - // return mkexpr(datatag); - - // If either argument is a constant and helper is equal to - // &MC_(helperc_MERGE_TAGS), then return the tag of the other - // argument. For &MC_(helperc_MERGE_TAGS_RETURN_0), simply - // return 0: + // If either argument is a constant, there is nothing to merge. + // For &MC_(helperc_MERGE_TAGS_RETURN_0), return 0. For + // &MC_(helperc_MERGE_TAGS), return the tag of the other argument. // (comment added 2006) // TODO: This doesn't happen THAT often, so there must be a // better way to tell whether we can make this optimization // without simply checking whether the atoms are consts - if (atom1->tag == Iex_Const) { - if (helper == &MC_(helperc_MERGE_TAGS)) { - return vatom2; - } - else if (helper == &MC_(helperc_MERGE_TAGS_RETURN_0)) { - return IRExpr_Const(IRConst_UWord(0)); - } - } - else if (atom2->tag == Iex_Const) { - if (helper == &MC_(helperc_MERGE_TAGS)) { - return vatom1; - } - else if (helper == &MC_(helperc_MERGE_TAGS_RETURN_0)) { - return IRExpr_Const(IRConst_UWord(0)); + if (helper == &MC_(helperc_MERGE_TAGS_RETURN_0)) { + if (atom1->tag != Iex_Const && atom2->tag != Iex_Const) { + mergeTagsReturn0_DC(dce, vatom1, vatom2); } + return IRExpr_Const(IRConst_UWord(0)); } - if (helper == &MC_(helperc_MERGE_TAGS_RETURN_0)) { - mergeTagsReturn0_DC(dce, vatom1, vatom2); - return IRExpr_Const(IRConst_UWord(0)); + tl_assert(helper == &MC_(helperc_MERGE_TAGS)); + if (atom1->tag == Iex_Const) { + return vatom2; + } + if (atom2->tag == Iex_Const) { + return vatom1; } - // Let's try a clean call. It seems to be correct - // because of the fact that merging the same 2 things more than - // once (in close proximity) doesn't hurt - // DO NOT use clean call unless it has NO side effects and - // is (nearly) purely functional like an IRExpr - // (from the point-of-view of IR, at least) - return mkIRExprCCall (Ity_Word, - 2 /*Int regparms*/, - hname, - helper, - mkIRExprVec_2( vatom1, vatom2 )); + return mergeTags_DC (dce, + 2 /*Int regparms*/, + hname, + helper, + mkIRExprVec_2( vatom1, vatom2 )); } // Hmmm, is this the desired behavior for a non-interaction? @@ -2172,18 +2149,6 @@ IRAtom* expr2tags_LDle_DC ( DCEnv* dce, IRType ty, IRAtom* addr, UInt bias ) // Merge the tags of the results of the // lower and upper 64-bit loads: - // Dirty call version: - // datatag = newIRTemp(dce->bb->tyenv, Ity_Word); - // di = unsafeIRDirty_1_N(datatag, - // 2, - // "MC_(helperc_MERGE_TAGS)", - // &MC_(helperc_MERGE_TAGS), - // mkIRExprVec_2( v64lo, v64hi )); - - // setHelperAnns_DC( dce, di ); - // stmt( dce->bb, IRStmt_Dirty(di) ); - // return mkexpr(datatag); - // (comment added 2005) // pgbovine TODO: Is this merge tags really necessary or too // premature? We should aim to do all of the merging on the @@ -2192,13 +2157,11 @@ IRAtom* expr2tags_LDle_DC ( DCEnv* dce, IRType ty, IRAtom* addr, UInt bias ) // on the memory level // On second thought, let's just go ahead and do it for now: - - // Clean call version: - return mkIRExprCCall (Ity_Word, - 2 /*Int regparms*/, - "MC_(helperc_MERGE_TAGS)", - &MC_(helperc_MERGE_TAGS), - mkIRExprVec_2( v64lo, v64hi )); + return mergeTags_DC (dce, + 2 /*Int regparms*/, + "MC_(helperc_MERGE_TAGS)", + &MC_(helperc_MERGE_TAGS), + mkIRExprVec_2( v64lo, v64hi )); default: From b9eae54d8ef2db49ff5477738fec434cfaa4e925 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 17 Oct 2026 10:26:11 -0700 Subject: [PATCH 6/9] DynComp: reduce overhead of dirty merge calls; test Triop merges - Look up the IP in MERGE_TAGS and val_uf_tag_union only when tracing. - Declare SP/IP reads on merge helpers only when tracing may be enabled. - Comment out the TAG_NOP anchors, which are unneeded now that merges are dirty calls. - Skip constant operands when merging tags for Qops and Triops. - Test comparability through a packed SSE2 add (a Triop). --- valgrind/fjalar/kvasir/dyncomp_main.c | 26 +- valgrind/fjalar/kvasir/dyncomp_translate.c | 273 +++++++++++------- valgrind/fjalar/kvasir/dyncomp_translate.h | 2 +- valgrind/fjalar/mc_translate.c | 5 +- valgrind/fjalar/tests/dyncomp-comparability.c | 18 ++ .../fjalar/tests/dyncomp-comparability.goal | 9 + 6 files changed, 209 insertions(+), 124 deletions(-) diff --git a/valgrind/fjalar/kvasir/dyncomp_main.c b/valgrind/fjalar/kvasir/dyncomp_main.c index 6ef006b9..a1f5962c 100644 --- a/valgrind/fjalar/kvasir/dyncomp_main.c +++ b/valgrind/fjalar/kvasir/dyncomp_main.c @@ -197,18 +197,20 @@ void val_uf_make_set_for_tag(UInt tag) { UInt val_uf_tag_union(UInt tag1, UInt tag2) { if (!IS_ZERO_TAG(tag1) && !IS_SECONDARY_UF_NULL(tag1) && !IS_ZERO_TAG(tag2) && !IS_SECONDARY_UF_NULL(tag2)) { - Addr eip = VG_(get_IP)(VG_(get_running_tid)()); - const HChar *eip_info; uf_object* tag1_obj, *tag2_obj; uf_object* leader; tag1_obj = GET_UF_OBJECT_PTR(tag1); tag2_obj = GET_UF_OBJECT_PTR(tag2); leader = uf_union(tag1_obj, tag2_obj); - // Describe this (probably live) address with current epoch - eip_info = VG_(describe_IP)(VG_(current_DiEpoch)(), eip, NULL); - DYNCOMP_TPRINTF("[DynComp-v1] Merging %u with %u to get %u at %s\n", - tag1, tag2, leader->tag, eip_info); + // This function is called on every merge, so look up the IP only when tracing. + if (dyncomp_print_trace_info) { + Addr eip = VG_(get_IP)(VG_(get_running_tid)()); + // Describe this (probably live) address with current epoch + const HChar *eip_info = VG_(describe_IP)(VG_(current_DiEpoch)(), eip, NULL); + DYNCOMP_TPRINTF("[DynComp-v1] Merging %u with %u to get %u at %s\n", + tag1, tag2, leader->tag, eip_info); + } return leader->tag; } @@ -499,15 +501,19 @@ UInt tag2_is_new ( UInt tag1, UInt tag2 ) { // of the merged set VG_REGPARM(2) UInt MC_(helperc_MERGE_TAGS) ( UInt tag1, UInt tag2 ) { - Addr eip = VG_(get_IP)(VG_(get_running_tid)()); - const HChar *eip_info; - // Describe this (probably live) address with current epoch - eip_info = VG_(describe_IP)(VG_(current_DiEpoch)(), eip, NULL); + const HChar *eip_info = NULL; if (dyncomp_profile_tags) { mergeTagsCount++; } + // This function is called on every merge, so look up the IP only when tracing. + if (dyncomp_print_trace_info) { + Addr eip = VG_(get_IP)(VG_(get_running_tid)()); + // Describe this (probably live) address with current epoch + eip_info = VG_(describe_IP)(VG_(current_DiEpoch)(), eip, NULL); + } + // Important special case - if one of the tags is 0, then // simply return the OTHER tag and don't do any merging. if IS_ZERO_TAG(tag1) { diff --git a/valgrind/fjalar/kvasir/dyncomp_translate.c b/valgrind/fjalar/kvasir/dyncomp_translate.c index f84bc792..2e70b988 100644 --- a/valgrind/fjalar/kvasir/dyncomp_translate.c +++ b/valgrind/fjalar/kvasir/dyncomp_translate.c @@ -107,6 +107,16 @@ static void setHelperAnns_DC ( DCEnv* dce, IRDirty* di ) { di->fxState[1].repeatLen = 0; } +// Set the annotations on a tag-merging helper. A merge helper reads the +// guest IP only to print trace output, so declare the reads only when +// tracing is or may become enabled. Declaring them forces VEX to write +// SP and IP back to the guest state before every merge. +static void setMergeHelperAnns_DC ( DCEnv* dce, IRDirty* di ) { + if (dyncomp_print_trace_info || dyncomp_delayed_trace) { + setHelperAnns_DC( dce, di ); + } +} + // Call a tag-merging helper, such as MC_(helperc_MERGE_TAGS), and // return the resulting tag. Merging has a side effect: it updates // the union-find structure that records comparability. Therefore, the @@ -119,7 +129,7 @@ static IRAtom* mergeTags_DC ( DCEnv* dce, Int regparms, IRExpr** args ) { IRTemp datatag = newTemp(dce->mce, Ity_Word, DC); IRDirty* di = unsafeIRDirty_1_N(datatag, regparms, hname, helper, args); - setHelperAnns_DC( dce, di ); + setMergeHelperAnns_DC( dce, di ); stmt_DC('V', dce, IRStmt_Dirty(di)); return mkexpr(datatag); } @@ -133,10 +143,51 @@ static void mergeTagsReturn0_DC ( DCEnv* dce, "MC_(helperc_MERGE_TAGS_RETURN_0)", &MC_(helperc_MERGE_TAGS_RETURN_0), mkIRExprVec_2( vatom1, vatom2 )); - setHelperAnns_DC( dce, di ); + setMergeHelperAnns_DC( dce, di ); stmt_DC('V', dce, IRStmt_Dirty(di)); } +// Merge the tags vatoms[0..n-1] of atoms[0..n-1], and return the +// resulting tag. As in expr2tags_Binop_DC, a constant atom has +// nothing to merge, so its tag is skipped. +static IRAtom* mergeNonConstTags_DC ( DCEnv* dce, Int n, + IRAtom** atoms, IRAtom** vatoms ) { + IRAtom* nonConst[4]; + Int i, k = 0; + tl_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) { + if (atoms[i]->tag != Iex_Const) { + nonConst[k++] = vatoms[i]; + } + } + switch (k) { + case 0: + return vatoms[n-1]; + case 1: + return nonConst[0]; + case 2: + return mergeTags_DC (dce, + 2 /*Int regparms*/, + "MC_(helperc_MERGE_TAGS)", + &MC_(helperc_MERGE_TAGS), + mkIRExprVec_2( nonConst[0], nonConst[1] )); + case 3: + return mergeTags_DC (dce, + 3 /*Int regparms*/, + "MC_(helperc_MERGE_3_TAGS)", + &MC_(helperc_MERGE_3_TAGS), + mkIRExprVec_3( nonConst[0], nonConst[1], + nonConst[2] )); + default: + return mergeTags_DC (dce, + 3 /*Int regparms*/, + "MC_(helperc_MERGE_4_TAGS)", + &MC_(helperc_MERGE_4_TAGS), + mkIRExprVec_4( nonConst[0], nonConst[1], + nonConst[2], nonConst[3] )); + } +} + // A PUT stores a value into the guest state void do_shadow_PUT_DC ( DCEnv* dce, Int offset, IRAtom* atom, IRAtom* vatom ) @@ -384,30 +435,24 @@ IRAtom* expr2tags_Qop_DC ( DCEnv* dce, // If we are running in units mode, then we should merge the // tags of the 3rd and 4th operands: if (dyncomp_units_mode) { - return mergeTags_DC (dce, - 2 /*Int regparms*/, - "MC_(helperc_MERGE_TAGS)", - &MC_(helperc_MERGE_TAGS), - mkIRExprVec_2( vatom3, vatom4 )); + return mergeNonConstTags_DC (dce, 2, + (IRAtom*[]){ atom3, atom4 }, + (IRAtom*[]){ vatom3, vatom4 }); } // Ok, if we are running in the default mode, then we should // merge the tags of the 2nd, 3rd, and 4th operands: else { - return mergeTags_DC (dce, - 3 /*Int regparms*/, - "MC_(helperc_MERGE_3_TAGS)", - &MC_(helperc_MERGE_3_TAGS), - mkIRExprVec_3( vatom2, vatom3, vatom4 )); + return mergeNonConstTags_DC (dce, 3, + (IRAtom*[]){ atom2, atom3, atom4 }, + (IRAtom*[]){ vatom2, vatom3, vatom4 }); } break; // Concatenation, like the binary Iop_V128HLtoV256, is an interaction. case Iop_64x4toV256: - return mergeTags_DC (dce, - 3 /*Int regparms*/, - "MC_(helperc_MERGE_4_TAGS)", - &MC_(helperc_MERGE_4_TAGS), - mkIRExprVec_4( vatom1, vatom2, vatom3, vatom4 )); + return mergeNonConstTags_DC (dce, 4, + (IRAtom*[]){ atom1, atom2, atom3, atom4 }, + (IRAtom*[]){ vatom1, vatom2, vatom3, vatom4 }); // Unimplemented case Iop_Rotx32: // only used by mips @@ -488,16 +533,14 @@ IRAtom* expr2tags_Triop_DC ( DCEnv* dce, case Iop_Sub64Fx2: case Iop_Sub64Fx4: - return mergeTags_DC (dce, - 2 /*Int regparms*/, - "MC_(helperc_MERGE_TAGS)", - &MC_(helperc_MERGE_TAGS), - // VERY IMPORTANT!!! We want to merge the - // tags of the 2nd and 3rd operands!!! - // Because the first one is a rounding - // mode (I think) - /* I32(rm) x F64 x F64 -> F64 */ - mkIRExprVec_2( vatom2, vatom3 )); + return mergeNonConstTags_DC (dce, 2, + // VERY IMPORTANT!!! We want to merge the + // tags of the 2nd and 3rd operands!!! + // Because the first one is a rounding + // mode (I think) + /* I32(rm) x F64 x F64 -> F64 */ + (IRAtom*[]){ atom2, atom3 }, + (IRAtom*[]){ vatom2, vatom3 }); case Iop_MulF32: // only used by arm mips s390 arm64 case Iop_DivF32: // only used by arm mips s390 arm64 @@ -521,16 +564,14 @@ IRAtom* expr2tags_Triop_DC ( DCEnv* dce, case Iop_Mul64Fx4: if (!dyncomp_units_mode) { - return mergeTags_DC (dce, - 2 /*Int regparms*/, - "MC_(helperc_MERGE_TAGS)", - &MC_(helperc_MERGE_TAGS), - // VERY IMPORTANT!!! We want to merge the - // tags of the 2nd and 3rd operands!!! - // Because the first one is a rounding - // mode (I think) - /* I32(rm) x F64 x F64 -> F64 */ - mkIRExprVec_2( vatom2, vatom3 )); + return mergeNonConstTags_DC (dce, 2, + // VERY IMPORTANT!!! We want to merge the + // tags of the 2nd and 3rd operands!!! + // Because the first one is a rounding + // mode (I think) + /* I32(rm) x F64 x F64 -> F64 */ + (IRAtom*[]){ atom2, atom3 }, + (IRAtom*[]){ vatom2, vatom3 }); } // Else fall through ... break; @@ -2117,22 +2158,24 @@ IRAtom* expr2tags_LDle_DC ( DCEnv* dce, IRType ty, IRAtom* addr, UInt bias ) IRAtom *v64hi, *v64lo; // IRDirty* di; // IRTemp datatag; - IRAtom *vaddr; - IRTemp addrTag; - IRDirty *diAddr; - - /* Compute the tag for the effective address, and throw the result - away, but anchor it to a dirty call so that Valgrind doesn't - optimize the merges away. */ - tl_assert(addr); - tl_assert(isOriginalAtom_DC(dce, addr)); - vaddr = expr2tags_DC(dce, addr); - tl_assert(isShadowAtom_DC(dce, vaddr)); - addrTag = newTemp(dce->mce, Ity_Word, DC); - diAddr = unsafeIRDirty_1_N(addrTag, 1, "MC_(helperc_TAG_NOP)", - &MC_(helperc_TAG_NOP), mkIRExprVec_1(vaddr)); - setHelperAnns_DC(dce, diAddr); - stmt_DC('V', dce, IRStmt_Dirty(diAddr)); + // IRAtom *vaddr; + // IRTemp addrTag; + // IRDirty *diAddr; + + // This anchor is disabled because every tag merge is a dirty call, + // which VEX never removes. It is needed only if merges are clean calls. +// /* Compute the tag for the effective address, and throw the result +// away, but anchor it to a dirty call so that Valgrind doesn't +// optimize the merges away. */ +// tl_assert(addr); +// tl_assert(isOriginalAtom_DC(dce, addr)); +// vaddr = expr2tags_DC(dce, addr); +// tl_assert(isShadowAtom_DC(dce, vaddr)); +// addrTag = newTemp(dce->mce, Ity_Word, DC); +// diAddr = unsafeIRDirty_1_N(addrTag, 1, "MC_(helperc_TAG_NOP)", +// &MC_(helperc_TAG_NOP), mkIRExprVec_1(vaddr)); +// setHelperAnns_DC(dce, diAddr); +// stmt_DC('V', dce, IRStmt_Dirty(diAddr)); switch (shadowTypeV(ty)) { case Ity_I8: @@ -2173,29 +2216,32 @@ static IRAtom* expr2tags_ITE_DC ( DCEnv* dce, IRAtom* cond, IRAtom* iftrue, IRAtom* iffalse ) { - IRAtom *vbitsC, *vbits0, *vbits1; - IRDirty* di; - IRTemp datatag; + IRAtom *vbits0, *vbits1; + // IRAtom *vbitsC; + // IRDirty* di; + // IRTemp datatag; tl_assert(isOriginalAtom_DC(dce, cond)); tl_assert(isOriginalAtom_DC(dce, iftrue)); tl_assert(isOriginalAtom_DC(dce, iffalse)); - // Generate a temp. 'datatag', which is the result of a NOP dirty - // call on vbitsC, in order to 'anchor' any possible tag merge - // clean helper calls in the expression which produced 'cond'. - // This prevents the IR optimizer from deleting all of these - // interactions from the parallel tag IR tree (or so we hope) - vbitsC = expr2tags_DC(dce, cond); - datatag = newTemp(dce->mce, Ity_Word, DC); - di = unsafeIRDirty_1_N(datatag, - 1, - "MC_(helperc_TAG_NOP)", - &MC_(helperc_TAG_NOP), - mkIRExprVec_1( vbitsC )); - - setHelperAnns_DC( dce, di ); - stmt_DC('V', dce, IRStmt_Dirty(di) ); + // This anchor is disabled because every tag merge is a dirty call, + // which VEX never removes. It is needed only if merges are clean calls. +// // Generate a temp. 'datatag', which is the result of a NOP dirty +// // call on vbitsC, in order to 'anchor' any possible tag merge +// // clean helper calls in the expression which produced 'cond'. +// // This prevents the IR optimizer from deleting all of these +// // interactions from the parallel tag IR tree (or so we hope) +// vbitsC = expr2tags_DC(dce, cond); +// datatag = newTemp(dce->mce, Ity_Word, DC); +// di = unsafeIRDirty_1_N(datatag, +// 1, +// "MC_(helperc_TAG_NOP)", +// &MC_(helperc_TAG_NOP), +// mkIRExprVec_1( vbitsC )); +// +// setHelperAnns_DC( dce, di ); +// stmt_DC('V', dce, IRStmt_Dirty(di) ); // Do the real work of generating tag IR trees for expr0 and exprX // and then making a parallel Mux which contains these two trees @@ -2209,31 +2255,33 @@ IRAtom* expr2tags_ITE_DC ( DCEnv* dce, return assignNew_DC(dce, Ity_Word, IRExpr_ITE(cond, vbits1, vbits0)); } -// (Very similar to expr2tags_ITE_DC) -// Generate and return temp 'datatag', which is the result of a NOP -// dirty call on the tag of 'guard', in order to 'anchor' any possible -// tag merge clean helper calls in the expression which produced -// 'guard'. This prevents the IR optimizer from deleting all of these -// interactions from the parallel tag IR tree (or so we hope) -IRAtom* do_shadow_cond_exit_DC (DCEnv* dce, IRExpr* guard) -{ - IRAtom *guardtag; - IRDirty* di; - IRTemp datatag; - - guardtag = expr2tags_DC(dce, guard); - datatag = newTemp(dce->mce, Ity_Word, DC); - di = unsafeIRDirty_1_N(datatag, - 1, - "MC_(helperc_TAG_NOP)", - &MC_(helperc_TAG_NOP), - mkIRExprVec_1( guardtag )); - - setHelperAnns_DC( dce, di ); - stmt_DC('V', dce, IRStmt_Dirty(di) ); - - return mkexpr(datatag); -} +// This anchor is disabled because every tag merge is a dirty call, +// which VEX never removes. It is needed only if merges are clean calls. +//// (Very similar to expr2tags_ITE_DC) +//// Generate and return temp 'datatag', which is the result of a NOP +//// dirty call on the tag of 'guard', in order to 'anchor' any possible +//// tag merge clean helper calls in the expression which produced +//// 'guard'. This prevents the IR optimizer from deleting all of these +//// interactions from the parallel tag IR tree (or so we hope) +//IRAtom* do_shadow_cond_exit_DC (DCEnv* dce, IRExpr* guard) +//{ +// IRAtom *guardtag; +// IRDirty* di; +// IRTemp datatag; +// +// guardtag = expr2tags_DC(dce, guard); +// datatag = newTemp(dce->mce, Ity_Word, DC); +// di = unsafeIRDirty_1_N(datatag, +// 1, +// "MC_(helperc_TAG_NOP)", +// &MC_(helperc_TAG_NOP), +// mkIRExprVec_1( guardtag )); +// +// setHelperAnns_DC( dce, di ); +// stmt_DC('V', dce, IRStmt_Dirty(di) ); +// +// return mkexpr(datatag); +//} /* --------- This is the main expression-handling function. --------- */ @@ -2363,11 +2411,12 @@ void do_shadow_STle_DC ( DCEnv* dce, { IROp mkAdd; IRType ty, tyAddr; - IRDirty *di, *diLo64, *diHi64, *diAddr; + IRDirty *di, *diLo64, *diHi64; + // IRDirty *diAddr; IRAtom *addrHi64; IRAtom *vdata; - IRAtom *vaddr; - IRTemp addrTag; + // IRAtom *vaddr; + // IRTemp addrTag; void* helper = NULL; const HChar* hname = NULL; Int pcOffset = -1; @@ -2402,18 +2451,20 @@ void do_shadow_STle_DC ( DCEnv* dce, } tl_assert(isShadowAtom_DC(dce,vdata)); - /* Compute the tag for the effective address, and throw the result - away, but anchor it to a dirty call so that Valgrind doesn't - optimize the merges away. */ - tl_assert(addr); - tl_assert(isOriginalAtom_DC(dce, addr)); - vaddr = expr2tags_DC(dce, addr); - tl_assert(isShadowAtom_DC(dce, vaddr)); - addrTag = newTemp(dce->mce, Ity_Word, DC); - diAddr = unsafeIRDirty_1_N(addrTag, 1, "MC_(helperc_TAG_NOP)", - &MC_(helperc_TAG_NOP), mkIRExprVec_1(vaddr)); - setHelperAnns_DC(dce, diAddr); - stmt_DC('V', dce, IRStmt_Dirty(diAddr)); + // This anchor is disabled because every tag merge is a dirty call, + // which VEX never removes. It is needed only if merges are clean calls. +// /* Compute the tag for the effective address, and throw the result +// away, but anchor it to a dirty call so that Valgrind doesn't +// optimize the merges away. */ +// tl_assert(addr); +// tl_assert(isOriginalAtom_DC(dce, addr)); +// vaddr = expr2tags_DC(dce, addr); +// tl_assert(isShadowAtom_DC(dce, vaddr)); +// addrTag = newTemp(dce->mce, Ity_Word, DC); +// diAddr = unsafeIRDirty_1_N(addrTag, 1, "MC_(helperc_TAG_NOP)", +// &MC_(helperc_TAG_NOP), mkIRExprVec_1(vaddr)); +// setHelperAnns_DC(dce, diAddr); +// stmt_DC('V', dce, IRStmt_Dirty(diAddr)); // Get the byte size of the REAL data (and not our tag vdata, which // is ALWAYS word-sized). This is very different from Memcheck's diff --git a/valgrind/fjalar/kvasir/dyncomp_translate.h b/valgrind/fjalar/kvasir/dyncomp_translate.h index a06d1fff..a077e7b4 100644 --- a/valgrind/fjalar/kvasir/dyncomp_translate.h +++ b/valgrind/fjalar/kvasir/dyncomp_translate.h @@ -35,7 +35,7 @@ void do_shadow_PUT_DC ( DCEnv* dce, Int offset, IRAtom* atom, IRAtom* vatom ); void do_shadow_PUTI_DC ( DCEnv* dce, IRPutI *puti ); void do_shadow_STle_DC ( DCEnv* dce, IRAtom* addr, IRAtom* data ); -IRAtom* do_shadow_cond_exit_DC (DCEnv* dce, IRExpr* guard); +// IRAtom* do_shadow_cond_exit_DC (DCEnv* dce, IRExpr* guard); void do_shadow_CAS_DC ( DCEnv* dce, IRCAS* cas ); diff --git a/valgrind/fjalar/mc_translate.c b/valgrind/fjalar/mc_translate.c index 46366055..04e97a9d 100644 --- a/valgrind/fjalar/mc_translate.c +++ b/valgrind/fjalar/mc_translate.c @@ -8908,8 +8908,9 @@ IRSB* MC_(instrument) ( VgCallbackClosure* closure, handle_possible_exit( &mce, st->Ist.Exit.jk ); // pgbovine //complainIfUndefined( &mce, st->Ist.Exit.guard, NULL ); // pgbovine #ifndef _NO_DYNCOMP - if (do_dyncomp) - do_shadow_cond_exit_DC( &dce, st->Ist.Exit.guard ); + // This DynComp anchor is disabled; see do_shadow_cond_exit_DC. + // if (do_dyncomp) + // do_shadow_cond_exit_DC( &dce, st->Ist.Exit.guard ); #endif /* _NO_DYNCOMP */ break; diff --git a/valgrind/fjalar/tests/dyncomp-comparability.c b/valgrind/fjalar/tests/dyncomp-comparability.c index 39005616..9d52bc01 100644 --- a/valgrind/fjalar/tests/dyncomp-comparability.c +++ b/valgrind/fjalar/tests/dyncomp-comparability.c @@ -14,6 +14,8 @@ // Functions whose parameters DynComp should make comparable, or not. // See dyncomp-comparability-test.sh. +#include + // a and b are comparable, because they are compared, even though the shift // discards the tag of the comparison's result; x and the result are // comparable. @@ -21,10 +23,26 @@ int shift_by_lt(int a, int b, int x) { return x << (a < b); } +// a, b, and c are comparable; d is not. The packed SSE2 add is a Triop +// (rounding mode, a, b) in VEX IR, unlike a scalar double add, which is a +// Binop. +int add_pd_lt(double a, double b, double c, double d) { + __m128d sum = _mm_add_pd(_mm_set1_pd(a), _mm_set1_pd(b)); + return _mm_cvtsd_f64(sum) < c; +} + +// a and c are comparable; d is not. One operand of the Triop is a constant. +int add_pd_const_lt(double a, double c, double d) { + __m128d sum = _mm_add_pd(_mm_set1_pd(a), _mm_set1_pd(2.0)); + return _mm_cvtsd_f64(sum) < c; +} + int main(void) { int r = 0; for (int i = 1; i < 5; i++) { r += shift_by_lt(i, 3, 5); + r += add_pd_lt(i, 2.0, 3.0, 4.0); + r += add_pd_const_lt(i, 3.0, 4.0); } return r == 12345; } diff --git a/valgrind/fjalar/tests/dyncomp-comparability.goal b/valgrind/fjalar/tests/dyncomp-comparability.goal index 036485ff..87a1ab07 100644 --- a/valgrind/fjalar/tests/dyncomp-comparability.goal +++ b/valgrind/fjalar/tests/dyncomp-comparability.goal @@ -1,3 +1,12 @@ +..add_pd_const_lt():::EXIT0 a 1 +..add_pd_const_lt():::EXIT0 c 1 +..add_pd_const_lt():::EXIT0 d 2 +..add_pd_const_lt():::EXIT0 return 3 +..add_pd_lt():::EXIT0 a 1 +..add_pd_lt():::EXIT0 b 1 +..add_pd_lt():::EXIT0 c 1 +..add_pd_lt():::EXIT0 d 2 +..add_pd_lt():::EXIT0 return 3 ..shift_by_lt():::EXIT0 a 1 ..shift_by_lt():::EXIT0 b 1 ..shift_by_lt():::EXIT0 x 2 From 707c7c221cfdc776b1feb846c70de38359801699 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 17 Oct 2026 10:45:36 -0700 Subject: [PATCH 7/9] DynComp: skip merge calls on zero tags and constant arguments - Guard the two-tag merge calls so that they run only when both tags are nonzero; otherwise, select the nonzero tag (merge2Tags_DC). - In handleCCall_DC, skip constant arguments and respect mcx_mask for the first argument, as the other operators do. - Use mergeNonConstTags_DC in expr2tags_Binop_DC, and remove the now-unused hname there. - Replace a stale TODO about skipping constant operands. --- valgrind/fjalar/kvasir/dyncomp_translate.c | 118 ++++++++++++--------- 1 file changed, 70 insertions(+), 48 deletions(-) diff --git a/valgrind/fjalar/kvasir/dyncomp_translate.c b/valgrind/fjalar/kvasir/dyncomp_translate.c index 2e70b988..0eff1fb6 100644 --- a/valgrind/fjalar/kvasir/dyncomp_translate.c +++ b/valgrind/fjalar/kvasir/dyncomp_translate.c @@ -117,6 +117,24 @@ static void setMergeHelperAnns_DC ( DCEnv* dce, IRDirty* di ) { } } +// Assign e, which has type ty, to a new shadow temporary, and return +// the temporary. (assignNew_DC always makes a word-sized temporary.) +static IRAtom* assignNewTy_DC ( DCEnv* dce, IRType ty, IRExpr* e ) { + IRTemp t = newTemp(dce->mce, ty, DC); + assign_DC('V', dce, t, e); + return mkexpr(t); +} + +// Return an Ity_I1 atom that is true iff the tag vatom is nonzero. +// The merge helpers take tags as UInt, so test only the low 32 bits. +static IRAtom* tagIsNonZero_DC ( DCEnv* dce, IRAtom* vatom ) { + IRAtom* tag32 = vatom; +#if VEX_HOST_WORDSIZE == 8 + tag32 = assignNewTy_DC(dce, Ity_I32, unop(Iop_64to32, vatom)); +#endif + return assignNewTy_DC(dce, Ity_I1, unop(Iop_CmpNEZ32, tag32)); +} + // Call a tag-merging helper, such as MC_(helperc_MERGE_TAGS), and // return the resulting tag. Merging has a side effect: it updates // the union-find structure that records comparability. Therefore, the @@ -124,25 +142,56 @@ static void setMergeHelperAnns_DC ( DCEnv* dce, IRDirty* di ) { // dead code whenever the operations that use its result discard the // result's tag (for example, a comparison with a constant, or a shift // whose shift amount is the result), and then the merge would be lost. -static IRAtom* mergeTags_DC ( DCEnv* dce, Int regparms, +// If guard is non-NULL, the call is made only when guard is true, and +// otherwise the result is meaningless. +static IRAtom* mergeTags_DC ( DCEnv* dce, IRAtom* guard, Int regparms, const HChar* hname, void* helper, IRExpr** args ) { IRTemp datatag = newTemp(dce->mce, Ity_Word, DC); IRDirty* di = unsafeIRDirty_1_N(datatag, regparms, hname, helper, args); + if (guard) { + di->guard = guard; + } setMergeHelperAnns_DC( dce, di ); stmt_DC('V', dce, IRStmt_Dirty(di)); return mkexpr(datatag); } +// Merge the tags vatom1 and vatom2, and return the resulting tag. +// If either tag is 0, MC_(helperc_MERGE_TAGS) merges nothing and +// returns the other tag, so skip the call and select the other tag. +static IRAtom* merge2Tags_DC ( DCEnv* dce, + IRAtom* vatom1, IRAtom* vatom2 ) { + IRAtom* nonZero1 = tagIsNonZero_DC(dce, vatom1); + IRAtom* nonZero2 = tagIsNonZero_DC(dce, vatom2); + IRAtom* bothNonZero + = assignNewTy_DC(dce, Ity_I1, binop(Iop_And1, nonZero1, nonZero2)); + IRAtom* merged = mergeTags_DC (dce, bothNonZero, + 2 /*Int regparms*/, + "MC_(helperc_MERGE_TAGS)", + &MC_(helperc_MERGE_TAGS), + mkIRExprVec_2( vatom1, vatom2 )); + IRAtom* nonZeroTag + = assignNew_DC(dce, Ity_Word, IRExpr_ITE(nonZero1, vatom1, vatom2)); + return assignNew_DC(dce, Ity_Word, + IRExpr_ITE(bothNonZero, merged, nonZeroTag)); +} + // Merge the tags vatom1 and vatom2, for an operation whose result // gets tag 0 (such as a comparison). See mergeTags_DC for why the -// call is dirty. +// call is dirty. If either tag is 0, there is nothing to merge, so +// the call is skipped. static void mergeTagsReturn0_DC ( DCEnv* dce, IRAtom* vatom1, IRAtom* vatom2 ) { + IRAtom* bothNonZero + = assignNewTy_DC(dce, Ity_I1, + binop(Iop_And1, tagIsNonZero_DC(dce, vatom1), + tagIsNonZero_DC(dce, vatom2))); IRDirty* di = unsafeIRDirty_0_N(2, "MC_(helperc_MERGE_TAGS_RETURN_0)", &MC_(helperc_MERGE_TAGS_RETURN_0), mkIRExprVec_2( vatom1, vatom2 )); + di->guard = bothNonZero; setMergeHelperAnns_DC( dce, di ); stmt_DC('V', dce, IRStmt_Dirty(di)); } @@ -166,20 +215,16 @@ static IRAtom* mergeNonConstTags_DC ( DCEnv* dce, Int n, case 1: return nonConst[0]; case 2: - return mergeTags_DC (dce, - 2 /*Int regparms*/, - "MC_(helperc_MERGE_TAGS)", - &MC_(helperc_MERGE_TAGS), - mkIRExprVec_2( nonConst[0], nonConst[1] )); + return merge2Tags_DC (dce, nonConst[0], nonConst[1]); case 3: - return mergeTags_DC (dce, + return mergeTags_DC (dce, NULL, 3 /*Int regparms*/, "MC_(helperc_MERGE_3_TAGS)", &MC_(helperc_MERGE_3_TAGS), mkIRExprVec_3( nonConst[0], nonConst[1], nonConst[2] )); default: - return mergeTags_DC (dce, + return mergeTags_DC (dce, NULL, 3 /*Int regparms*/, "MC_(helperc_MERGE_4_TAGS)", &MC_(helperc_MERGE_4_TAGS), @@ -334,11 +379,12 @@ IRAtom* handleCCall_DC ( DCEnv* dce, IRAtom** exprvec, IRCallee* cee ) { if (exprvec && exprvec[0]) { - IRAtom* first = expr2tags_DC(dce, exprvec[0]); + // The tag of the first non-constant, non-excluded argument + IRAtom* first = NULL; Int i; IRAtom* cur; - for (i = 1; exprvec[i]; i++) { + for (i = 0; exprvec[i]; i++) { tl_assert(i < 32); tl_assert(isOriginalAtom_DC(dce, exprvec[i])); /* Only take notice of this arg if the callee's mc-exclusion @@ -347,10 +393,16 @@ IRAtom* handleCCall_DC ( DCEnv* dce, /* the arg is to be excluded from definedness checking. Do nothing. (I guess we do nothing also just like mkLazyN) */ if (0) printf("excluding %s(%d)\n", cee->name, i); + } else if (exprvec[i]->tag == Iex_Const) { + // As in mergeNonConstTags_DC, a constant has nothing to merge. } else { /* merge the tags of first and current arguments */ cur = expr2tags_DC(dce, exprvec[i]); - mergeTagsReturn0_DC(dce, first, cur); + if (first == NULL) { + first = cur; + } else { + mergeTagsReturn0_DC(dce, first, cur); + } } } // Return the tag of the first argument, if there is one @@ -625,8 +677,7 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, IRAtom* vatom1 = expr2tags_DC( dce, atom1 ); IRAtom* vatom2 = expr2tags_DC( dce, atom2 ); - void* helper = 0; - const HChar* hname = 0; + void* helper = 0; if (dyncomp_profile_tags) { if ((atom1->tag == Iex_Const) || @@ -675,7 +726,6 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, if (!dyncomp_dataflow_comparisons_mode) { helper = &MC_(helperc_MERGE_TAGS); - hname = "MC_(helperc_MERGE_TAGS)"; } break; @@ -745,7 +795,6 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, if (!dyncomp_dataflow_comparisons_mode && !dyncomp_units_mode) { helper = &MC_(helperc_MERGE_TAGS); - hname = "MC_(helperc_MERGE_TAGS)"; } break; @@ -831,7 +880,6 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, if (!dyncomp_dataflow_comparisons_mode) { helper = &MC_(helperc_MERGE_TAGS); - hname = "MC_(helperc_MERGE_TAGS)"; } break; @@ -863,7 +911,6 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, if (!dyncomp_dataflow_comparisons_mode && !dyncomp_units_mode) { helper = &MC_(helperc_MERGE_TAGS); - hname = "MC_(helperc_MERGE_TAGS)"; } break; @@ -901,7 +948,6 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, if (!dyncomp_dataflow_comparisons_mode) { helper = &MC_(helperc_MERGE_TAGS); - hname = "MC_(helperc_MERGE_TAGS)"; } break; @@ -914,7 +960,6 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, if (!dyncomp_dataflow_comparisons_mode && !dyncomp_units_mode) { helper = &MC_(helperc_MERGE_TAGS); - hname = "MC_(helperc_MERGE_TAGS)"; } break; @@ -1003,7 +1048,6 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, if (!dyncomp_dataflow_comparisons_mode) { helper = &MC_(helperc_MERGE_TAGS); - hname = "MC_(helperc_MERGE_TAGS)"; } break; @@ -1036,7 +1080,6 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, if (!dyncomp_dataflow_comparisons_mode && !dyncomp_units_mode) { helper = &MC_(helperc_MERGE_TAGS); - hname = "MC_(helperc_MERGE_TAGS)"; } break; @@ -1122,7 +1165,6 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, if (!dyncomp_dataflow_comparisons_mode) { helper = &MC_(helperc_MERGE_TAGS); - hname = "MC_(helperc_MERGE_TAGS)"; } break; @@ -1262,7 +1304,6 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, case Iop_CmpGT64Sx4: helper = &MC_(helperc_MERGE_TAGS_RETURN_0); - hname = "MC_(helperc_MERGE_TAGS_RETURN_0)"; break; /* ------------------ 128-bit SIMD Integer. ------------------ */ @@ -1634,19 +1675,14 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, // In this mode, NOTHING is an interaction: if (dyncomp_dataflow_only_mode) { helper = 0; - hname = 0; } if (helper) { // If either argument is a constant, there is nothing to merge. + // Skipping it avoids a merge call that VEX cannot remove. // For &MC_(helperc_MERGE_TAGS_RETURN_0), return 0. For // &MC_(helperc_MERGE_TAGS), return the tag of the other argument. - // (comment added 2006) - // TODO: This doesn't happen THAT often, so there must be a - // better way to tell whether we can make this optimization - // without simply checking whether the atoms are consts - if (helper == &MC_(helperc_MERGE_TAGS_RETURN_0)) { if (atom1->tag != Iex_Const && atom2->tag != Iex_Const) { mergeTagsReturn0_DC(dce, vatom1, vatom2); @@ -1655,19 +1691,9 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, } tl_assert(helper == &MC_(helperc_MERGE_TAGS)); - if (atom1->tag == Iex_Const) { - return vatom2; - } - if (atom2->tag == Iex_Const) { - return vatom1; - } - - return mergeTags_DC (dce, - 2 /*Int regparms*/, - hname, - helper, - mkIRExprVec_2( vatom1, vatom2 )); - + return mergeNonConstTags_DC (dce, 2, + (IRAtom*[]){ atom1, atom2 }, + (IRAtom*[]){ vatom1, vatom2 }); } // Hmmm, is this the desired behavior for a non-interaction? // I think so ... @@ -2200,11 +2226,7 @@ IRAtom* expr2tags_LDle_DC ( DCEnv* dce, IRType ty, IRAtom* addr, UInt bias ) // on the memory level // On second thought, let's just go ahead and do it for now: - return mergeTags_DC (dce, - 2 /*Int regparms*/, - "MC_(helperc_MERGE_TAGS)", - &MC_(helperc_MERGE_TAGS), - mkIRExprVec_2( v64lo, v64hi )); + return merge2Tags_DC (dce, v64lo, v64hi); default: From 40c8dd7952883c091a1ca15262621c206c930b08 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 17 Oct 2026 11:39:08 -0700 Subject: [PATCH 8/9] Limit this PR to merging comparison tags with a dirty call Revert the changes that belong elsewhere or need a separate decision: making every merge a dirty call and disabling the TAG_NOP anchors, skipping constant helper-call arguments and looking up the IP only when tracing (both in #106), running make fjalar-test in CI, and the zero-tag guards and constant skipping for Qops and Triops. This reverts commits 707c7c22, b9eae548, and 43533a8a. --- test.sh | 3 - valgrind/fjalar/kvasir/dyncomp_main.c | 26 +- valgrind/fjalar/kvasir/dyncomp_translate.c | 460 ++++++++---------- valgrind/fjalar/kvasir/dyncomp_translate.h | 2 +- valgrind/fjalar/mc_translate.c | 5 +- valgrind/fjalar/tests/dyncomp-comparability.c | 18 - .../fjalar/tests/dyncomp-comparability.goal | 9 - 7 files changed, 225 insertions(+), 298 deletions(-) diff --git a/test.sh b/test.sh index 2dbf8f2b..5cf30614 100755 --- a/test.sh +++ b/test.sh @@ -47,9 +47,6 @@ make check-options make build -## Fjalar tests that do not depend on Daikon -make fjalar-test - make doc ## Valgrind tests diff --git a/valgrind/fjalar/kvasir/dyncomp_main.c b/valgrind/fjalar/kvasir/dyncomp_main.c index a1f5962c..6ef006b9 100644 --- a/valgrind/fjalar/kvasir/dyncomp_main.c +++ b/valgrind/fjalar/kvasir/dyncomp_main.c @@ -197,20 +197,18 @@ void val_uf_make_set_for_tag(UInt tag) { UInt val_uf_tag_union(UInt tag1, UInt tag2) { if (!IS_ZERO_TAG(tag1) && !IS_SECONDARY_UF_NULL(tag1) && !IS_ZERO_TAG(tag2) && !IS_SECONDARY_UF_NULL(tag2)) { + Addr eip = VG_(get_IP)(VG_(get_running_tid)()); + const HChar *eip_info; uf_object* tag1_obj, *tag2_obj; uf_object* leader; tag1_obj = GET_UF_OBJECT_PTR(tag1); tag2_obj = GET_UF_OBJECT_PTR(tag2); leader = uf_union(tag1_obj, tag2_obj); + // Describe this (probably live) address with current epoch + eip_info = VG_(describe_IP)(VG_(current_DiEpoch)(), eip, NULL); - // This function is called on every merge, so look up the IP only when tracing. - if (dyncomp_print_trace_info) { - Addr eip = VG_(get_IP)(VG_(get_running_tid)()); - // Describe this (probably live) address with current epoch - const HChar *eip_info = VG_(describe_IP)(VG_(current_DiEpoch)(), eip, NULL); - DYNCOMP_TPRINTF("[DynComp-v1] Merging %u with %u to get %u at %s\n", - tag1, tag2, leader->tag, eip_info); - } + DYNCOMP_TPRINTF("[DynComp-v1] Merging %u with %u to get %u at %s\n", + tag1, tag2, leader->tag, eip_info); return leader->tag; } @@ -501,19 +499,15 @@ UInt tag2_is_new ( UInt tag1, UInt tag2 ) { // of the merged set VG_REGPARM(2) UInt MC_(helperc_MERGE_TAGS) ( UInt tag1, UInt tag2 ) { - const HChar *eip_info = NULL; + Addr eip = VG_(get_IP)(VG_(get_running_tid)()); + const HChar *eip_info; + // Describe this (probably live) address with current epoch + eip_info = VG_(describe_IP)(VG_(current_DiEpoch)(), eip, NULL); if (dyncomp_profile_tags) { mergeTagsCount++; } - // This function is called on every merge, so look up the IP only when tracing. - if (dyncomp_print_trace_info) { - Addr eip = VG_(get_IP)(VG_(get_running_tid)()); - // Describe this (probably live) address with current epoch - eip_info = VG_(describe_IP)(VG_(current_DiEpoch)(), eip, NULL); - } - // Important special case - if one of the tags is 0, then // simply return the OTHER tag and don't do any merging. if IS_ZERO_TAG(tag1) { diff --git a/valgrind/fjalar/kvasir/dyncomp_translate.c b/valgrind/fjalar/kvasir/dyncomp_translate.c index 0eff1fb6..a9ed17e4 100644 --- a/valgrind/fjalar/kvasir/dyncomp_translate.c +++ b/valgrind/fjalar/kvasir/dyncomp_translate.c @@ -107,132 +107,22 @@ static void setHelperAnns_DC ( DCEnv* dce, IRDirty* di ) { di->fxState[1].repeatLen = 0; } -// Set the annotations on a tag-merging helper. A merge helper reads the -// guest IP only to print trace output, so declare the reads only when -// tracing is or may become enabled. Declaring them forces VEX to write -// SP and IP back to the guest state before every merge. -static void setMergeHelperAnns_DC ( DCEnv* dce, IRDirty* di ) { - if (dyncomp_print_trace_info || dyncomp_delayed_trace) { - setHelperAnns_DC( dce, di ); - } -} - -// Assign e, which has type ty, to a new shadow temporary, and return -// the temporary. (assignNew_DC always makes a word-sized temporary.) -static IRAtom* assignNewTy_DC ( DCEnv* dce, IRType ty, IRExpr* e ) { - IRTemp t = newTemp(dce->mce, ty, DC); - assign_DC('V', dce, t, e); - return mkexpr(t); -} - -// Return an Ity_I1 atom that is true iff the tag vatom is nonzero. -// The merge helpers take tags as UInt, so test only the low 32 bits. -static IRAtom* tagIsNonZero_DC ( DCEnv* dce, IRAtom* vatom ) { - IRAtom* tag32 = vatom; -#if VEX_HOST_WORDSIZE == 8 - tag32 = assignNewTy_DC(dce, Ity_I32, unop(Iop_64to32, vatom)); -#endif - return assignNewTy_DC(dce, Ity_I1, unop(Iop_CmpNEZ32, tag32)); -} - -// Call a tag-merging helper, such as MC_(helperc_MERGE_TAGS), and -// return the resulting tag. Merging has a side effect: it updates -// the union-find structure that records comparability. Therefore, the -// call must be dirty rather than clean. VEX removes a clean call as -// dead code whenever the operations that use its result discard the -// result's tag (for example, a comparison with a constant, or a shift -// whose shift amount is the result), and then the merge would be lost. -// If guard is non-NULL, the call is made only when guard is true, and -// otherwise the result is meaningless. -static IRAtom* mergeTags_DC ( DCEnv* dce, IRAtom* guard, Int regparms, - const HChar* hname, void* helper, - IRExpr** args ) { - IRTemp datatag = newTemp(dce->mce, Ity_Word, DC); - IRDirty* di = unsafeIRDirty_1_N(datatag, regparms, hname, helper, args); - if (guard) { - di->guard = guard; - } - setMergeHelperAnns_DC( dce, di ); - stmt_DC('V', dce, IRStmt_Dirty(di)); - return mkexpr(datatag); -} - -// Merge the tags vatom1 and vatom2, and return the resulting tag. -// If either tag is 0, MC_(helperc_MERGE_TAGS) merges nothing and -// returns the other tag, so skip the call and select the other tag. -static IRAtom* merge2Tags_DC ( DCEnv* dce, - IRAtom* vatom1, IRAtom* vatom2 ) { - IRAtom* nonZero1 = tagIsNonZero_DC(dce, vatom1); - IRAtom* nonZero2 = tagIsNonZero_DC(dce, vatom2); - IRAtom* bothNonZero - = assignNewTy_DC(dce, Ity_I1, binop(Iop_And1, nonZero1, nonZero2)); - IRAtom* merged = mergeTags_DC (dce, bothNonZero, - 2 /*Int regparms*/, - "MC_(helperc_MERGE_TAGS)", - &MC_(helperc_MERGE_TAGS), - mkIRExprVec_2( vatom1, vatom2 )); - IRAtom* nonZeroTag - = assignNew_DC(dce, Ity_Word, IRExpr_ITE(nonZero1, vatom1, vatom2)); - return assignNew_DC(dce, Ity_Word, - IRExpr_ITE(bothNonZero, merged, nonZeroTag)); -} - // Merge the tags vatom1 and vatom2, for an operation whose result -// gets tag 0 (such as a comparison). See mergeTags_DC for why the -// call is dirty. If either tag is 0, there is nothing to merge, so -// the call is skipped. +// gets tag 0 (such as a comparison). The merge is a dirty call +// rather than a clean one. VEX would remove a clean call as dead +// code whenever the operations that use the result discard its tag +// (for example, a shift discards the tag of its shift amount), and +// the merge would be lost. static void mergeTagsReturn0_DC ( DCEnv* dce, IRAtom* vatom1, IRAtom* vatom2 ) { - IRAtom* bothNonZero - = assignNewTy_DC(dce, Ity_I1, - binop(Iop_And1, tagIsNonZero_DC(dce, vatom1), - tagIsNonZero_DC(dce, vatom2))); IRDirty* di = unsafeIRDirty_0_N(2, "MC_(helperc_MERGE_TAGS_RETURN_0)", &MC_(helperc_MERGE_TAGS_RETURN_0), mkIRExprVec_2( vatom1, vatom2 )); - di->guard = bothNonZero; - setMergeHelperAnns_DC( dce, di ); + setHelperAnns_DC( dce, di ); stmt_DC('V', dce, IRStmt_Dirty(di)); } -// Merge the tags vatoms[0..n-1] of atoms[0..n-1], and return the -// resulting tag. As in expr2tags_Binop_DC, a constant atom has -// nothing to merge, so its tag is skipped. -static IRAtom* mergeNonConstTags_DC ( DCEnv* dce, Int n, - IRAtom** atoms, IRAtom** vatoms ) { - IRAtom* nonConst[4]; - Int i, k = 0; - tl_assert(n >= 1 && n <= 4); - for (i = 0; i < n; i++) { - if (atoms[i]->tag != Iex_Const) { - nonConst[k++] = vatoms[i]; - } - } - switch (k) { - case 0: - return vatoms[n-1]; - case 1: - return nonConst[0]; - case 2: - return merge2Tags_DC (dce, nonConst[0], nonConst[1]); - case 3: - return mergeTags_DC (dce, NULL, - 3 /*Int regparms*/, - "MC_(helperc_MERGE_3_TAGS)", - &MC_(helperc_MERGE_3_TAGS), - mkIRExprVec_3( nonConst[0], nonConst[1], - nonConst[2] )); - default: - return mergeTags_DC (dce, NULL, - 3 /*Int regparms*/, - "MC_(helperc_MERGE_4_TAGS)", - &MC_(helperc_MERGE_4_TAGS), - mkIRExprVec_4( nonConst[0], nonConst[1], - nonConst[2], nonConst[3] )); - } -} - // A PUT stores a value into the guest state void do_shadow_PUT_DC ( DCEnv* dce, Int offset, IRAtom* atom, IRAtom* vatom ) @@ -379,12 +269,11 @@ IRAtom* handleCCall_DC ( DCEnv* dce, IRAtom** exprvec, IRCallee* cee ) { if (exprvec && exprvec[0]) { - // The tag of the first non-constant, non-excluded argument - IRAtom* first = NULL; + IRAtom* first = expr2tags_DC(dce, exprvec[0]); Int i; IRAtom* cur; - for (i = 0; exprvec[i]; i++) { + for (i = 1; exprvec[i]; i++) { tl_assert(i < 32); tl_assert(isOriginalAtom_DC(dce, exprvec[i])); /* Only take notice of this arg if the callee's mc-exclusion @@ -393,16 +282,10 @@ IRAtom* handleCCall_DC ( DCEnv* dce, /* the arg is to be excluded from definedness checking. Do nothing. (I guess we do nothing also just like mkLazyN) */ if (0) printf("excluding %s(%d)\n", cee->name, i); - } else if (exprvec[i]->tag == Iex_Const) { - // As in mergeNonConstTags_DC, a constant has nothing to merge. } else { /* merge the tags of first and current arguments */ cur = expr2tags_DC(dce, exprvec[i]); - if (first == NULL) { - first = cur; - } else { - mergeTagsReturn0_DC(dce, first, cur); - } + mergeTagsReturn0_DC(dce, first, cur); } } // Return the tag of the first argument, if there is one @@ -487,24 +370,30 @@ IRAtom* expr2tags_Qop_DC ( DCEnv* dce, // If we are running in units mode, then we should merge the // tags of the 3rd and 4th operands: if (dyncomp_units_mode) { - return mergeNonConstTags_DC (dce, 2, - (IRAtom*[]){ atom3, atom4 }, - (IRAtom*[]){ vatom3, vatom4 }); + return mkIRExprCCall (Ity_Word, + 2 /*Int regparms*/, + "MC_(helperc_MERGE_TAGS)", + &MC_(helperc_MERGE_TAGS), + mkIRExprVec_2( vatom3, vatom4 )); } // Ok, if we are running in the default mode, then we should // merge the tags of the 2nd, 3rd, and 4th operands: else { - return mergeNonConstTags_DC (dce, 3, - (IRAtom*[]){ atom2, atom3, atom4 }, - (IRAtom*[]){ vatom2, vatom3, vatom4 }); + return mkIRExprCCall (Ity_Word, + 3 /*Int regparms*/, + "MC_(helperc_MERGE_3_TAGS)", + &MC_(helperc_MERGE_3_TAGS), + mkIRExprVec_3( vatom2, vatom3, vatom4 )); } break; // Concatenation, like the binary Iop_V128HLtoV256, is an interaction. case Iop_64x4toV256: - return mergeNonConstTags_DC (dce, 4, - (IRAtom*[]){ atom1, atom2, atom3, atom4 }, - (IRAtom*[]){ vatom1, vatom2, vatom3, vatom4 }); + return mkIRExprCCall (Ity_Word, + 3 /*Int regparms*/, + "MC_(helperc_MERGE_4_TAGS)", + &MC_(helperc_MERGE_4_TAGS), + mkIRExprVec_4( vatom1, vatom2, vatom3, vatom4 )); // Unimplemented case Iop_Rotx32: // only used by mips @@ -585,14 +474,16 @@ IRAtom* expr2tags_Triop_DC ( DCEnv* dce, case Iop_Sub64Fx2: case Iop_Sub64Fx4: - return mergeNonConstTags_DC (dce, 2, - // VERY IMPORTANT!!! We want to merge the - // tags of the 2nd and 3rd operands!!! - // Because the first one is a rounding - // mode (I think) - /* I32(rm) x F64 x F64 -> F64 */ - (IRAtom*[]){ atom2, atom3 }, - (IRAtom*[]){ vatom2, vatom3 }); + return mkIRExprCCall (Ity_Word, + 2 /*Int regparms*/, + "MC_(helperc_MERGE_TAGS)", + &MC_(helperc_MERGE_TAGS), + // VERY IMPORTANT!!! We want to merge the + // tags of the 2nd and 3rd operands!!! + // Because the first one is a rounding + // mode (I think) + /* I32(rm) x F64 x F64 -> F64 */ + mkIRExprVec_2( vatom2, vatom3 )); case Iop_MulF32: // only used by arm mips s390 arm64 case Iop_DivF32: // only used by arm mips s390 arm64 @@ -616,14 +507,16 @@ IRAtom* expr2tags_Triop_DC ( DCEnv* dce, case Iop_Mul64Fx4: if (!dyncomp_units_mode) { - return mergeNonConstTags_DC (dce, 2, - // VERY IMPORTANT!!! We want to merge the - // tags of the 2nd and 3rd operands!!! - // Because the first one is a rounding - // mode (I think) - /* I32(rm) x F64 x F64 -> F64 */ - (IRAtom*[]){ atom2, atom3 }, - (IRAtom*[]){ vatom2, vatom3 }); + return mkIRExprCCall (Ity_Word, + 2 /*Int regparms*/, + "MC_(helperc_MERGE_TAGS)", + &MC_(helperc_MERGE_TAGS), + // VERY IMPORTANT!!! We want to merge the + // tags of the 2nd and 3rd operands!!! + // Because the first one is a rounding + // mode (I think) + /* I32(rm) x F64 x F64 -> F64 */ + mkIRExprVec_2( vatom2, vatom3 )); } // Else fall through ... break; @@ -677,7 +570,8 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, IRAtom* vatom1 = expr2tags_DC( dce, atom1 ); IRAtom* vatom2 = expr2tags_DC( dce, atom2 ); - void* helper = 0; + void* helper = 0; + const HChar* hname = 0; if (dyncomp_profile_tags) { if ((atom1->tag == Iex_Const) || @@ -726,6 +620,7 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, if (!dyncomp_dataflow_comparisons_mode) { helper = &MC_(helperc_MERGE_TAGS); + hname = "MC_(helperc_MERGE_TAGS)"; } break; @@ -795,6 +690,7 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, if (!dyncomp_dataflow_comparisons_mode && !dyncomp_units_mode) { helper = &MC_(helperc_MERGE_TAGS); + hname = "MC_(helperc_MERGE_TAGS)"; } break; @@ -880,6 +776,7 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, if (!dyncomp_dataflow_comparisons_mode) { helper = &MC_(helperc_MERGE_TAGS); + hname = "MC_(helperc_MERGE_TAGS)"; } break; @@ -911,6 +808,7 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, if (!dyncomp_dataflow_comparisons_mode && !dyncomp_units_mode) { helper = &MC_(helperc_MERGE_TAGS); + hname = "MC_(helperc_MERGE_TAGS)"; } break; @@ -948,6 +846,7 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, if (!dyncomp_dataflow_comparisons_mode) { helper = &MC_(helperc_MERGE_TAGS); + hname = "MC_(helperc_MERGE_TAGS)"; } break; @@ -960,6 +859,7 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, if (!dyncomp_dataflow_comparisons_mode && !dyncomp_units_mode) { helper = &MC_(helperc_MERGE_TAGS); + hname = "MC_(helperc_MERGE_TAGS)"; } break; @@ -1048,6 +948,7 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, if (!dyncomp_dataflow_comparisons_mode) { helper = &MC_(helperc_MERGE_TAGS); + hname = "MC_(helperc_MERGE_TAGS)"; } break; @@ -1080,6 +981,7 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, if (!dyncomp_dataflow_comparisons_mode && !dyncomp_units_mode) { helper = &MC_(helperc_MERGE_TAGS); + hname = "MC_(helperc_MERGE_TAGS)"; } break; @@ -1165,6 +1067,7 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, if (!dyncomp_dataflow_comparisons_mode) { helper = &MC_(helperc_MERGE_TAGS); + hname = "MC_(helperc_MERGE_TAGS)"; } break; @@ -1304,6 +1207,7 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, case Iop_CmpGT64Sx4: helper = &MC_(helperc_MERGE_TAGS_RETURN_0); + hname = "MC_(helperc_MERGE_TAGS_RETURN_0)"; break; /* ------------------ 128-bit SIMD Integer. ------------------ */ @@ -1675,25 +1579,77 @@ IRAtom* expr2tags_Binop_DC ( DCEnv* dce, // In this mode, NOTHING is an interaction: if (dyncomp_dataflow_only_mode) { helper = 0; + hname = 0; } if (helper) { - // If either argument is a constant, there is nothing to merge. - // Skipping it avoids a merge call that VEX cannot remove. - // For &MC_(helperc_MERGE_TAGS_RETURN_0), return 0. For - // &MC_(helperc_MERGE_TAGS), return the tag of the other argument. + // Heuristic: + + // At least as observed in one trial, the dirty call + // version had MANY more calls of MC_(helperc_MERGE_TAGS) + // than the clean version, many of which were nonsensical + // merges of tag 0 with a valid tag but nonetheless with + // some valid merges as well. + // However, in 'z = x + y' of SuperSimpleTest, + // that interaction was correctly captured by the clean call. + + // The standard way to make a dirty helper call (I think): + // Tags are always word-sized + // datatag = newIRTemp(dce->bb->tyenv, Ity_Word); + // di = unsafeIRDirty_1_N(datatag, + // 2, + // hname, + // helper, + // mkIRExprVec_2( vatom1, vatom2 )); + + // setHelperAnns_DC( dce, di ); + // stmt( dce->bb, IRStmt_Dirty(di) ); + // return mkexpr(datatag); + + // If either argument is a constant and helper is equal to + // &MC_(helperc_MERGE_TAGS), then return the tag of the other + // argument. For &MC_(helperc_MERGE_TAGS_RETURN_0), simply + // return 0: - if (helper == &MC_(helperc_MERGE_TAGS_RETURN_0)) { - if (atom1->tag != Iex_Const && atom2->tag != Iex_Const) { - mergeTagsReturn0_DC(dce, vatom1, vatom2); + // (comment added 2006) + // TODO: This doesn't happen THAT often, so there must be a + // better way to tell whether we can make this optimization + // without simply checking whether the atoms are consts + + if (atom1->tag == Iex_Const) { + if (helper == &MC_(helperc_MERGE_TAGS)) { + return vatom2; + } + else if (helper == &MC_(helperc_MERGE_TAGS_RETURN_0)) { + return IRExpr_Const(IRConst_UWord(0)); + } + } + else if (atom2->tag == Iex_Const) { + if (helper == &MC_(helperc_MERGE_TAGS)) { + return vatom1; } + else if (helper == &MC_(helperc_MERGE_TAGS_RETURN_0)) { + return IRExpr_Const(IRConst_UWord(0)); + } + } + + if (helper == &MC_(helperc_MERGE_TAGS_RETURN_0)) { + mergeTagsReturn0_DC(dce, vatom1, vatom2); return IRExpr_Const(IRConst_UWord(0)); } - tl_assert(helper == &MC_(helperc_MERGE_TAGS)); - return mergeNonConstTags_DC (dce, 2, - (IRAtom*[]){ atom1, atom2 }, - (IRAtom*[]){ vatom1, vatom2 }); + // Let's try a clean call. It seems to be correct + // because of the fact that merging the same 2 things more than + // once (in close proximity) doesn't hurt + // DO NOT use clean call unless it has NO side effects and + // is (nearly) purely functional like an IRExpr + // (from the point-of-view of IR, at least) + return mkIRExprCCall (Ity_Word, + 2 /*Int regparms*/, + hname, + helper, + mkIRExprVec_2( vatom1, vatom2 )); + } // Hmmm, is this the desired behavior for a non-interaction? // I think so ... @@ -2184,24 +2140,22 @@ IRAtom* expr2tags_LDle_DC ( DCEnv* dce, IRType ty, IRAtom* addr, UInt bias ) IRAtom *v64hi, *v64lo; // IRDirty* di; // IRTemp datatag; - // IRAtom *vaddr; - // IRTemp addrTag; - // IRDirty *diAddr; - - // This anchor is disabled because every tag merge is a dirty call, - // which VEX never removes. It is needed only if merges are clean calls. -// /* Compute the tag for the effective address, and throw the result -// away, but anchor it to a dirty call so that Valgrind doesn't -// optimize the merges away. */ -// tl_assert(addr); -// tl_assert(isOriginalAtom_DC(dce, addr)); -// vaddr = expr2tags_DC(dce, addr); -// tl_assert(isShadowAtom_DC(dce, vaddr)); -// addrTag = newTemp(dce->mce, Ity_Word, DC); -// diAddr = unsafeIRDirty_1_N(addrTag, 1, "MC_(helperc_TAG_NOP)", -// &MC_(helperc_TAG_NOP), mkIRExprVec_1(vaddr)); -// setHelperAnns_DC(dce, diAddr); -// stmt_DC('V', dce, IRStmt_Dirty(diAddr)); + IRAtom *vaddr; + IRTemp addrTag; + IRDirty *diAddr; + + /* Compute the tag for the effective address, and throw the result + away, but anchor it to a dirty call so that Valgrind doesn't + optimize the merges away. */ + tl_assert(addr); + tl_assert(isOriginalAtom_DC(dce, addr)); + vaddr = expr2tags_DC(dce, addr); + tl_assert(isShadowAtom_DC(dce, vaddr)); + addrTag = newTemp(dce->mce, Ity_Word, DC); + diAddr = unsafeIRDirty_1_N(addrTag, 1, "MC_(helperc_TAG_NOP)", + &MC_(helperc_TAG_NOP), mkIRExprVec_1(vaddr)); + setHelperAnns_DC(dce, diAddr); + stmt_DC('V', dce, IRStmt_Dirty(diAddr)); switch (shadowTypeV(ty)) { case Ity_I8: @@ -2218,6 +2172,18 @@ IRAtom* expr2tags_LDle_DC ( DCEnv* dce, IRType ty, IRAtom* addr, UInt bias ) // Merge the tags of the results of the // lower and upper 64-bit loads: + // Dirty call version: + // datatag = newIRTemp(dce->bb->tyenv, Ity_Word); + // di = unsafeIRDirty_1_N(datatag, + // 2, + // "MC_(helperc_MERGE_TAGS)", + // &MC_(helperc_MERGE_TAGS), + // mkIRExprVec_2( v64lo, v64hi )); + + // setHelperAnns_DC( dce, di ); + // stmt( dce->bb, IRStmt_Dirty(di) ); + // return mkexpr(datatag); + // (comment added 2005) // pgbovine TODO: Is this merge tags really necessary or too // premature? We should aim to do all of the merging on the @@ -2226,7 +2192,13 @@ IRAtom* expr2tags_LDle_DC ( DCEnv* dce, IRType ty, IRAtom* addr, UInt bias ) // on the memory level // On second thought, let's just go ahead and do it for now: - return merge2Tags_DC (dce, v64lo, v64hi); + + // Clean call version: + return mkIRExprCCall (Ity_Word, + 2 /*Int regparms*/, + "MC_(helperc_MERGE_TAGS)", + &MC_(helperc_MERGE_TAGS), + mkIRExprVec_2( v64lo, v64hi )); default: @@ -2238,32 +2210,29 @@ static IRAtom* expr2tags_ITE_DC ( DCEnv* dce, IRAtom* cond, IRAtom* iftrue, IRAtom* iffalse ) { - IRAtom *vbits0, *vbits1; - // IRAtom *vbitsC; - // IRDirty* di; - // IRTemp datatag; + IRAtom *vbitsC, *vbits0, *vbits1; + IRDirty* di; + IRTemp datatag; tl_assert(isOriginalAtom_DC(dce, cond)); tl_assert(isOriginalAtom_DC(dce, iftrue)); tl_assert(isOriginalAtom_DC(dce, iffalse)); - // This anchor is disabled because every tag merge is a dirty call, - // which VEX never removes. It is needed only if merges are clean calls. -// // Generate a temp. 'datatag', which is the result of a NOP dirty -// // call on vbitsC, in order to 'anchor' any possible tag merge -// // clean helper calls in the expression which produced 'cond'. -// // This prevents the IR optimizer from deleting all of these -// // interactions from the parallel tag IR tree (or so we hope) -// vbitsC = expr2tags_DC(dce, cond); -// datatag = newTemp(dce->mce, Ity_Word, DC); -// di = unsafeIRDirty_1_N(datatag, -// 1, -// "MC_(helperc_TAG_NOP)", -// &MC_(helperc_TAG_NOP), -// mkIRExprVec_1( vbitsC )); -// -// setHelperAnns_DC( dce, di ); -// stmt_DC('V', dce, IRStmt_Dirty(di) ); + // Generate a temp. 'datatag', which is the result of a NOP dirty + // call on vbitsC, in order to 'anchor' any possible tag merge + // clean helper calls in the expression which produced 'cond'. + // This prevents the IR optimizer from deleting all of these + // interactions from the parallel tag IR tree (or so we hope) + vbitsC = expr2tags_DC(dce, cond); + datatag = newTemp(dce->mce, Ity_Word, DC); + di = unsafeIRDirty_1_N(datatag, + 1, + "MC_(helperc_TAG_NOP)", + &MC_(helperc_TAG_NOP), + mkIRExprVec_1( vbitsC )); + + setHelperAnns_DC( dce, di ); + stmt_DC('V', dce, IRStmt_Dirty(di) ); // Do the real work of generating tag IR trees for expr0 and exprX // and then making a parallel Mux which contains these two trees @@ -2277,33 +2246,31 @@ IRAtom* expr2tags_ITE_DC ( DCEnv* dce, return assignNew_DC(dce, Ity_Word, IRExpr_ITE(cond, vbits1, vbits0)); } -// This anchor is disabled because every tag merge is a dirty call, -// which VEX never removes. It is needed only if merges are clean calls. -//// (Very similar to expr2tags_ITE_DC) -//// Generate and return temp 'datatag', which is the result of a NOP -//// dirty call on the tag of 'guard', in order to 'anchor' any possible -//// tag merge clean helper calls in the expression which produced -//// 'guard'. This prevents the IR optimizer from deleting all of these -//// interactions from the parallel tag IR tree (or so we hope) -//IRAtom* do_shadow_cond_exit_DC (DCEnv* dce, IRExpr* guard) -//{ -// IRAtom *guardtag; -// IRDirty* di; -// IRTemp datatag; -// -// guardtag = expr2tags_DC(dce, guard); -// datatag = newTemp(dce->mce, Ity_Word, DC); -// di = unsafeIRDirty_1_N(datatag, -// 1, -// "MC_(helperc_TAG_NOP)", -// &MC_(helperc_TAG_NOP), -// mkIRExprVec_1( guardtag )); -// -// setHelperAnns_DC( dce, di ); -// stmt_DC('V', dce, IRStmt_Dirty(di) ); -// -// return mkexpr(datatag); -//} +// (Very similar to expr2tags_ITE_DC) +// Generate and return temp 'datatag', which is the result of a NOP +// dirty call on the tag of 'guard', in order to 'anchor' any possible +// tag merge clean helper calls in the expression which produced +// 'guard'. This prevents the IR optimizer from deleting all of these +// interactions from the parallel tag IR tree (or so we hope) +IRAtom* do_shadow_cond_exit_DC (DCEnv* dce, IRExpr* guard) +{ + IRAtom *guardtag; + IRDirty* di; + IRTemp datatag; + + guardtag = expr2tags_DC(dce, guard); + datatag = newTemp(dce->mce, Ity_Word, DC); + di = unsafeIRDirty_1_N(datatag, + 1, + "MC_(helperc_TAG_NOP)", + &MC_(helperc_TAG_NOP), + mkIRExprVec_1( guardtag )); + + setHelperAnns_DC( dce, di ); + stmt_DC('V', dce, IRStmt_Dirty(di) ); + + return mkexpr(datatag); +} /* --------- This is the main expression-handling function. --------- */ @@ -2433,12 +2400,11 @@ void do_shadow_STle_DC ( DCEnv* dce, { IROp mkAdd; IRType ty, tyAddr; - IRDirty *di, *diLo64, *diHi64; - // IRDirty *diAddr; + IRDirty *di, *diLo64, *diHi64, *diAddr; IRAtom *addrHi64; IRAtom *vdata; - // IRAtom *vaddr; - // IRTemp addrTag; + IRAtom *vaddr; + IRTemp addrTag; void* helper = NULL; const HChar* hname = NULL; Int pcOffset = -1; @@ -2473,20 +2439,18 @@ void do_shadow_STle_DC ( DCEnv* dce, } tl_assert(isShadowAtom_DC(dce,vdata)); - // This anchor is disabled because every tag merge is a dirty call, - // which VEX never removes. It is needed only if merges are clean calls. -// /* Compute the tag for the effective address, and throw the result -// away, but anchor it to a dirty call so that Valgrind doesn't -// optimize the merges away. */ -// tl_assert(addr); -// tl_assert(isOriginalAtom_DC(dce, addr)); -// vaddr = expr2tags_DC(dce, addr); -// tl_assert(isShadowAtom_DC(dce, vaddr)); -// addrTag = newTemp(dce->mce, Ity_Word, DC); -// diAddr = unsafeIRDirty_1_N(addrTag, 1, "MC_(helperc_TAG_NOP)", -// &MC_(helperc_TAG_NOP), mkIRExprVec_1(vaddr)); -// setHelperAnns_DC(dce, diAddr); -// stmt_DC('V', dce, IRStmt_Dirty(diAddr)); + /* Compute the tag for the effective address, and throw the result + away, but anchor it to a dirty call so that Valgrind doesn't + optimize the merges away. */ + tl_assert(addr); + tl_assert(isOriginalAtom_DC(dce, addr)); + vaddr = expr2tags_DC(dce, addr); + tl_assert(isShadowAtom_DC(dce, vaddr)); + addrTag = newTemp(dce->mce, Ity_Word, DC); + diAddr = unsafeIRDirty_1_N(addrTag, 1, "MC_(helperc_TAG_NOP)", + &MC_(helperc_TAG_NOP), mkIRExprVec_1(vaddr)); + setHelperAnns_DC(dce, diAddr); + stmt_DC('V', dce, IRStmt_Dirty(diAddr)); // Get the byte size of the REAL data (and not our tag vdata, which // is ALWAYS word-sized). This is very different from Memcheck's diff --git a/valgrind/fjalar/kvasir/dyncomp_translate.h b/valgrind/fjalar/kvasir/dyncomp_translate.h index a077e7b4..a06d1fff 100644 --- a/valgrind/fjalar/kvasir/dyncomp_translate.h +++ b/valgrind/fjalar/kvasir/dyncomp_translate.h @@ -35,7 +35,7 @@ void do_shadow_PUT_DC ( DCEnv* dce, Int offset, IRAtom* atom, IRAtom* vatom ); void do_shadow_PUTI_DC ( DCEnv* dce, IRPutI *puti ); void do_shadow_STle_DC ( DCEnv* dce, IRAtom* addr, IRAtom* data ); -// IRAtom* do_shadow_cond_exit_DC (DCEnv* dce, IRExpr* guard); +IRAtom* do_shadow_cond_exit_DC (DCEnv* dce, IRExpr* guard); void do_shadow_CAS_DC ( DCEnv* dce, IRCAS* cas ); diff --git a/valgrind/fjalar/mc_translate.c b/valgrind/fjalar/mc_translate.c index 04e97a9d..46366055 100644 --- a/valgrind/fjalar/mc_translate.c +++ b/valgrind/fjalar/mc_translate.c @@ -8908,9 +8908,8 @@ IRSB* MC_(instrument) ( VgCallbackClosure* closure, handle_possible_exit( &mce, st->Ist.Exit.jk ); // pgbovine //complainIfUndefined( &mce, st->Ist.Exit.guard, NULL ); // pgbovine #ifndef _NO_DYNCOMP - // This DynComp anchor is disabled; see do_shadow_cond_exit_DC. - // if (do_dyncomp) - // do_shadow_cond_exit_DC( &dce, st->Ist.Exit.guard ); + if (do_dyncomp) + do_shadow_cond_exit_DC( &dce, st->Ist.Exit.guard ); #endif /* _NO_DYNCOMP */ break; diff --git a/valgrind/fjalar/tests/dyncomp-comparability.c b/valgrind/fjalar/tests/dyncomp-comparability.c index 9d52bc01..39005616 100644 --- a/valgrind/fjalar/tests/dyncomp-comparability.c +++ b/valgrind/fjalar/tests/dyncomp-comparability.c @@ -14,8 +14,6 @@ // Functions whose parameters DynComp should make comparable, or not. // See dyncomp-comparability-test.sh. -#include - // a and b are comparable, because they are compared, even though the shift // discards the tag of the comparison's result; x and the result are // comparable. @@ -23,26 +21,10 @@ int shift_by_lt(int a, int b, int x) { return x << (a < b); } -// a, b, and c are comparable; d is not. The packed SSE2 add is a Triop -// (rounding mode, a, b) in VEX IR, unlike a scalar double add, which is a -// Binop. -int add_pd_lt(double a, double b, double c, double d) { - __m128d sum = _mm_add_pd(_mm_set1_pd(a), _mm_set1_pd(b)); - return _mm_cvtsd_f64(sum) < c; -} - -// a and c are comparable; d is not. One operand of the Triop is a constant. -int add_pd_const_lt(double a, double c, double d) { - __m128d sum = _mm_add_pd(_mm_set1_pd(a), _mm_set1_pd(2.0)); - return _mm_cvtsd_f64(sum) < c; -} - int main(void) { int r = 0; for (int i = 1; i < 5; i++) { r += shift_by_lt(i, 3, 5); - r += add_pd_lt(i, 2.0, 3.0, 4.0); - r += add_pd_const_lt(i, 3.0, 4.0); } return r == 12345; } diff --git a/valgrind/fjalar/tests/dyncomp-comparability.goal b/valgrind/fjalar/tests/dyncomp-comparability.goal index 87a1ab07..036485ff 100644 --- a/valgrind/fjalar/tests/dyncomp-comparability.goal +++ b/valgrind/fjalar/tests/dyncomp-comparability.goal @@ -1,12 +1,3 @@ -..add_pd_const_lt():::EXIT0 a 1 -..add_pd_const_lt():::EXIT0 c 1 -..add_pd_const_lt():::EXIT0 d 2 -..add_pd_const_lt():::EXIT0 return 3 -..add_pd_lt():::EXIT0 a 1 -..add_pd_lt():::EXIT0 b 1 -..add_pd_lt():::EXIT0 c 1 -..add_pd_lt():::EXIT0 d 2 -..add_pd_lt():::EXIT0 return 3 ..shift_by_lt():::EXIT0 a 1 ..shift_by_lt():::EXIT0 b 1 ..shift_by_lt():::EXIT0 x 2 From 7484d7e8fa87c489003fb2928dac9c04baecdb86 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 17 Oct 2026 18:14:44 -0700 Subject: [PATCH 9/9] Test a comparison whose merge VEX removed under --dyncomp-interactions=units --- .../tests/dyncomp-comparability-test.sh | 57 +++++++++++-------- valgrind/fjalar/tests/dyncomp-comparability.c | 20 +++++++ .../fjalar/tests/dyncomp-comparability.goal | 5 ++ 3 files changed, 57 insertions(+), 25 deletions(-) diff --git a/valgrind/fjalar/tests/dyncomp-comparability-test.sh b/valgrind/fjalar/tests/dyncomp-comparability-test.sh index 8a2782e3..08c1c8e0 100755 --- a/valgrind/fjalar/tests/dyncomp-comparability-test.sh +++ b/valgrind/fjalar/tests/dyncomp-comparability-test.sh @@ -2,7 +2,8 @@ # Tests DynComp's comparability: whether the variables at each exit # program point are comparable to each other. The expected comparability is -# in dyncomp-comparability.goal. +# in dyncomp-comparability.goal, under both --dyncomp-interactions=all and +# --dyncomp-interactions=units. set -e @@ -23,29 +24,35 @@ trap 'rm -rf "${output_dir}"' EXIT gcc -gdwarf-4 -no-pie -O0 -o "${output_dir}/dyncomp-comparability" \ "${test_dir}/dyncomp-comparability.c" -decls="${output_dir}/dyncomp-comparability.decls" -status=0 -"${valgrind}" --tool=fjalar --dyncomp --decls-only --decls-file="${decls}" \ - "${output_dir}/dyncomp-comparability" \ - > "${output_dir}/dyncomp-comparability.out" 2>&1 || status=$? - -if [ "${status}" -ne 0 ]; then - echo "$0: FAILED: Fjalar exited with status ${status}" >&2 - cat "${output_dir}/dyncomp-comparability.out" >&2 - exit 1 -fi - -# One line per variable at each exit program point, other than main's: -# " ". -actual="${output_dir}/dyncomp-comparability.comparability" -awk '/^ppt / { ppt = $2 } - /^ variable / { var = $2 } - /^ comparability / { if (ppt ~ /:::EXIT/ && ppt !~ /^\.\.main\(/) - print ppt, var, $2 }' "${decls}" > "${actual}" - -if ! diff -u "${test_dir}/dyncomp-comparability.goal" "${actual}" >&2; then - echo "$0: FAILED: comparability differs from dyncomp-comparability.goal" >&2 - exit 1 -fi +for interactions in all units; do + decls="${output_dir}/dyncomp-comparability.decls" + status=0 + "${valgrind}" --tool=fjalar --dyncomp \ + --dyncomp-interactions="${interactions}" \ + --decls-only --decls-file="${decls}" \ + "${output_dir}/dyncomp-comparability" \ + > "${output_dir}/dyncomp-comparability.out" 2>&1 || status=$? + + if [ "${status}" -ne 0 ]; then + echo "$0: FAILED: under --dyncomp-interactions=${interactions}," \ + "Fjalar exited with status ${status}" >&2 + cat "${output_dir}/dyncomp-comparability.out" >&2 + exit 1 + fi + + # One line per variable at each exit program point, other than main's: + # " ". + actual="${output_dir}/dyncomp-comparability.comparability" + awk '/^ppt / { ppt = $2 } + /^ variable / { var = $2 } + /^ comparability / { if (ppt ~ /:::EXIT/ && ppt !~ /^\.\.main\(/) + print ppt, var, $2 }' "${decls}" > "${actual}" + + if ! diff -u "${test_dir}/dyncomp-comparability.goal" "${actual}" >&2; then + echo "$0: FAILED: under --dyncomp-interactions=${interactions}," \ + "comparability differs from dyncomp-comparability.goal" >&2 + exit 1 + fi +done echo "$0: PASSED" diff --git a/valgrind/fjalar/tests/dyncomp-comparability.c b/valgrind/fjalar/tests/dyncomp-comparability.c index 39005616..08732563 100644 --- a/valgrind/fjalar/tests/dyncomp-comparability.c +++ b/valgrind/fjalar/tests/dyncomp-comparability.c @@ -21,10 +21,30 @@ int shift_by_lt(int a, int b, int x) { return x << (a < b); } +// a_lo and b_lo are comparable, and a_hi and b_hi are comparable, because +// the two 128-bit integers are compared as a compiler compares two __int128 +// values: a cmp instruction compares the low halves, an sbb instruction +// subtracts the high halves and the borrow, and a setl instruction reads the +// result. Because the cmp and the sbb are in the same superblock, VEX +// computes the borrow with a CmpLT64U of the low halves. Under +// --dyncomp-interactions=units, the tag of the borrow (the comparison's +// result, masked with 1) is 0, so nothing uses the result of the merge. +int less_wide(long a_lo, long a_hi, long b_lo, long b_hi) { + unsigned char result; + long hi = a_hi; + __asm__ volatile ("cmpq %3, %2\n\t" + "sbbq %4, %1\n\t" + "setl %0" + : "=q" (result), "+r" (hi) + : "r" (a_lo), "r" (b_lo), "r" (b_hi) : "cc"); + return result; +} + int main(void) { int r = 0; for (int i = 1; i < 5; i++) { r += shift_by_lt(i, 3, 5); } + r += less_wide(20, 21, 22, 23); return r == 12345; } diff --git a/valgrind/fjalar/tests/dyncomp-comparability.goal b/valgrind/fjalar/tests/dyncomp-comparability.goal index 036485ff..88baf480 100644 --- a/valgrind/fjalar/tests/dyncomp-comparability.goal +++ b/valgrind/fjalar/tests/dyncomp-comparability.goal @@ -1,3 +1,8 @@ +..less_wide():::EXIT0 a_lo 1 +..less_wide():::EXIT0 a_hi 2 +..less_wide():::EXIT0 b_lo 1 +..less_wide():::EXIT0 b_hi 2 +..less_wide():::EXIT0 return 3 ..shift_by_lt():::EXIT0 a 1 ..shift_by_lt():::EXIT0 b 1 ..shift_by_lt():::EXIT0 x 2