Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
90561a0
DynComp: give tag 0 to unary operations whose result is a boolean, ma…
mernst Oct 10, 2026
6ff4084
DynComp: merge comparison tags with a dirty call; anchor tag-0 unop o…
mernst Oct 10, 2026
878a83c
DynComp test: make ctz_of_sum's operands independent before the call
mernst Oct 11, 2026
a37093c
Merge remote-tracking branch 'origin/master' into dyncomp-unop-tag0
mernst Oct 16, 2026
a060a96
DynComp: merge comparison tags with a dirty call
mernst Oct 17, 2026
c873fd5
Merge #104 (dyncomp-comparison-merge) into dyncomp-unop-tag0
mernst Oct 17, 2026
43533a8
DynComp: merge all tags with dirty calls; run fjalar-test in CI
mernst Oct 17, 2026
b9eae54
DynComp: reduce overhead of dirty merge calls; test Triop merges
mernst Oct 17, 2026
707c7c2
DynComp: skip merge calls on zero tags and constant arguments
mernst Oct 17, 2026
40c8dd7
Limit this PR to merging comparison tags with a dirty call
mernst Oct 17, 2026
977f953
Merge #104 (dyncomp-comparison-merge) into dyncomp-unop-tag0
mernst Oct 17, 2026
58fb6dc
Merge codespecs/fjalar master into dyncomp-comparison-merge
mernst Oct 17, 2026
1d9ac09
Merge codespecs/fjalar master into dyncomp-unop-tag0
mernst Oct 17, 2026
e2650a7
Merge #104 (dyncomp-comparison-merge) into dyncomp-unop-tag0
mernst Oct 17, 2026
f184dc5
Merge codespecs/fjalar master into dyncomp-comparison-merge
mernst Oct 18, 2026
2c40165
Merge codespecs/fjalar master into dyncomp-unop-tag0
mernst Oct 18, 2026
59d7edc
Merge dyncomp-comparison-merge into dyncomp-unop-tag0
mernst Oct 18, 2026
7484d7e
Test a comparison whose merge VEX removed under --dyncomp-interaction…
mernst Oct 18, 2026
2351dcb
Merge dyncomp-comparison-merge into dyncomp-unop-tag0
mernst Oct 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 121 additions & 72 deletions valgrind/fjalar/kvasir/dyncomp_translate.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -1724,6 +1740,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
Expand Down Expand Up @@ -1754,13 +1775,90 @@ 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_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_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
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_Cnt8x16: // only used by arm arm64
case Iop_Cnt8x8: // 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
// 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 IRExpr_Const(IRConst_UWord(0));

// ----------------------------------------------------------
// 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
Expand All @@ -1769,7 +1867,8 @@ IRExpr* expr2tags_Unop_DC ( DCEnv* dce, 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
Expand Down Expand Up @@ -1824,53 +1923,6 @@ IRExpr* expr2tags_Unop_DC ( DCEnv* dce, 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
Expand Down Expand Up @@ -1901,8 +1953,6 @@ IRExpr* expr2tags_Unop_DC ( DCEnv* dce, 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
Expand Down Expand Up @@ -1947,8 +1997,6 @@ IRExpr* expr2tags_Unop_DC ( DCEnv* dce, 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
Expand Down Expand Up @@ -2050,7 +2098,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;
}
}

/*
Expand Down Expand Up @@ -2374,7 +2423,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,
Expand Down
58 changes: 58 additions & 0 deletions valgrind/fjalar/tests/dyncomp-comparability-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/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, under both --dyncomp-interactions=all and
# --dyncomp-interactions=units.

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"

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:
# "<ppt> <variable> <comparability>".
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"
Loading
Loading