Skip to content

Commit

Permalink
Add a pattern which matches reordered binary operations
Browse files Browse the repository at this point in the history
During compilation to LLVM IR, binary operations which are both
commutative and associative may sometimes get reordered. This commit
enables effective matching of such reordered instructions.

Simply explained, when the operands differ in a compared reorderable
binary operation and all its users are operations of the same type,
the binary operation is skipped. The complete set of operands is then
matched during the comparison of the user.
  • Loading branch information
zacikpa authored and viktormalik committed Oct 30, 2023
1 parent ba0f4ca commit e376fd9
Show file tree
Hide file tree
Showing 11 changed files with 258 additions and 3 deletions.
3 changes: 2 additions & 1 deletion diffkemp/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ def make_argument_parser():
"relocations",
"type-casts",
"control-flow-only",
"inverse-conditions"]
"inverse-conditions",
"reordered-bin-ops"]

# Semantic patterns options.
compare_ap.add_argument("--enable-pattern",
Expand Down
4 changes: 4 additions & 0 deletions diffkemp/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def __init__(
type_casts=False,
control_flow_only=False,
inverse_conditions=True,
reordered_bin_ops=True,
):
"""
Create a configuration of built-in patterns.
Expand All @@ -39,6 +40,7 @@ def __init__(
:param type_casts: Changes in type casts.
:param control_flow_only: Consider control-flow changes only.
:param inverse_conditions: Inverted branch conditions.
:param reordered_bin_ops: Match reordered binary operations.
"""
self.settings = {
"struct-alignment": struct_alignment,
Expand All @@ -51,6 +53,7 @@ def __init__(
"type-casts": type_casts,
"control-flow-only": control_flow_only,
"inverse-conditions": inverse_conditions,
"reordered-bin-ops": reordered_bin_ops,
}
self.resolve_dependencies()

Expand Down Expand Up @@ -97,6 +100,7 @@ def as_ffi_struct(self):
ffi_struct.TypeCasts = self.settings["type-casts"]
ffi_struct.ControlFlowOnly = self.settings["control-flow-only"]
ffi_struct.InverseConditions = self.settings["inverse-conditions"]
ffi_struct.ReorderedBinOps = self.settings["reordered-bin-ops"]
return ffi_struct


Expand Down
1 change: 1 addition & 0 deletions diffkemp/simpll/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ struct BuiltinPatterns {
bool TypeCasts = false;
bool ControlFlowOnly = false;
bool InverseConditions = true;
bool ReorderedBinOps = true;
};

/// Tool configuration parsed from CLI options.
Expand Down
85 changes: 85 additions & 0 deletions diffkemp/simpll/DifferentialFunctionComparator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,11 @@ bool DifferentialFunctionComparator::maySkipInstruction(
ignoredInstructions.insert(Inst);
return true;
}
if (config.Patterns.ReorderedBinOps && isReorderableBinaryOp(Inst)
&& maySkipReorderableBinaryOp(Inst)) {
ignoredInstructions.insert(Inst);
return true;
}
if (isCast(Inst)) {
if (config.Patterns.TypeCasts) {
replacedInstructions.insert({Inst, Inst->getOperand(0)});
Expand Down Expand Up @@ -660,6 +665,17 @@ bool DifferentialFunctionComparator::maySkipLoad(const LoadInst *Load) const {
return false;
}

/// Check whether the given reorderable binary operator can be skipped.
/// It can only be skipped if all its users are binary operations
/// of the same kind.
bool DifferentialFunctionComparator::maySkipReorderableBinaryOp(
const Instruction *Op) const {
return std::all_of(Op->user_begin(), Op->user_end(), [Op](auto user) {
auto userOp = dyn_cast<BinaryOperator>(user);
return userOp && userOp->getOpcode() == Op->getOpcode();
});
}

bool mayIgnoreMacro(std::string macro) {
return ignoredMacroList.find(macro) != ignoredMacroList.end();
}
Expand Down Expand Up @@ -896,6 +912,30 @@ int DifferentialFunctionComparator::cmpBasicBlocks(
continue;
}

// If both instructions are reorderable binary operators, they may
// have skipped operands. Try to recursively collect all operands
// and compare their sets.
if (config.Patterns.ReorderedBinOps
&& isReorderableBinaryOp(&*InstL)
&& isReorderableBinaryOp(&*InstR)) {
Instruction::BinaryOps OpcodeL =
dyn_cast<BinaryOperator>(&*InstL)->getOpcode();
Instruction::BinaryOps OpcodeR =
dyn_cast<BinaryOperator>(&*InstR)->getOpcode();
std::multiset<int> SnSetL, SnSetR;
std::multiset<int64_t> ConstantsSetL, ConstantsSetR;
if (OpcodeL == OpcodeR
&& collectBinaryOperands(
&*InstL, OpcodeL, SnSetL, ConstantsSetL, sn_mapL)
&& collectBinaryOperands(
&*InstR, OpcodeR, SnSetR, ConstantsSetR, sn_mapR)
&& SnSetL == SnSetR && ConstantsSetL == ConstantsSetR) {
InstL++;
InstR++;
continue;
}
}

// If one of the instructions is a logical not, it is possible that
// it will be used in an inverse condition. Hence, we skip it here
// and mark that it may be inverse-matching the condition that
Expand Down Expand Up @@ -1874,3 +1914,48 @@ bool DifferentialFunctionComparator::isDependingOnReloc(

return false;
}

/// Recursively collect operands of reorderable binary operators.
/// Leafs must be constants or already synchronized values.
/// Return false if a non-synchronized non-constant leaf is found.
bool DifferentialFunctionComparator::collectBinaryOperands(
const Value *Val,
Instruction::BinaryOps Opcode,
std::multiset<int> &SNs,
std::multiset<int64_t> &Constants,
DenseMap<const Value *, int> &sn_map) const {
// Substitute an ignored value with its replacement when necessary
auto replacementIt = replacedInstructions.find(Val);
const Value *newVal = replacementIt == replacedInstructions.end()
? Val
: replacementIt->second;
// Constant leaf
if (auto Const = dyn_cast<ConstantInt>(newVal)) {
Constants.insert(Const->getSExtValue());
return true;
}
// Non-leaf; try to collect its operands recursively
auto BinOp = dyn_cast<BinaryOperator>(newVal);
if (BinOp && BinOp->getOpcode() == Opcode) {
std::multiset<int> tmpSNs;
std::multiset<int64_t> tmpConstants;
bool leftSuccess = collectBinaryOperands(
BinOp->getOperand(0), Opcode, tmpSNs, tmpConstants, sn_map);
bool rightSuccess = collectBinaryOperands(
BinOp->getOperand(1), Opcode, tmpSNs, tmpConstants, sn_map);
if (leftSuccess && rightSuccess) {
SNs.merge(tmpSNs);
Constants.merge(tmpConstants);
return true;
}
}
// Already synchronized leaf; do not match if it is the last value inserted
// into the synchronization map - it has just been compared as not equal
auto snMapIt = sn_map.find(newVal);
if (snMapIt != sn_map.end()
&& (unsigned)snMapIt->second != sn_map.size() - 1) {
SNs.insert(snMapIt->second);
return true;
}
return false;
}
14 changes: 14 additions & 0 deletions diffkemp/simpll/DifferentialFunctionComparator.h
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,11 @@ class DifferentialFunctionComparator : public FunctionComparator {
/// ignorable loads are stored inside the ignored instructions map.
bool maySkipLoad(const LoadInst *Load) const;

/// Check whether the given reorderable binary operator can be skipped.
/// It can only be skipped if all its users are binary operations
/// of the same kind.
bool maySkipReorderableBinaryOp(const Instruction *Op) const;

/// Retrive the replacement for the given value from the ignored
/// instructions map. Try to generate the replacement if a bitcast is given.
const Value *
Expand Down Expand Up @@ -265,6 +270,15 @@ class DifferentialFunctionComparator : public FunctionComparator {
/// (any instruction within it) access the same pointer and one of the
/// accesses is a store and the other one is a load.
bool isDependingOnReloc(const Instruction &Inst) const;

/// Recursively collect operands of reorderable binary operators.
/// Leafs must be constants or already synchronized values.
/// Return false if a non-synchronized non-constant leaf is found.
bool collectBinaryOperands(const Value *Val,
Instruction::BinaryOps Opcode,
std::multiset<int> &SNs,
std::multiset<int64_t> &Constants,
DenseMap<const Value *, int> &sn_map) const;
};

#endif // DIFFKEMP_SIMPLL_DIFFERENTIALFUNCTIONCOMPARATOR_H
7 changes: 6 additions & 1 deletion diffkemp/simpll/SimpLL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ cl::opt<bool>
InverseConditionsOpt("inverse-conditions",
cl::desc("Enable inverse conditions pattern."),
cl::cat(BuiltinPatternsCategory));
cl::opt<bool> ReorderedBinOpsOpt(
"reordered-bin-ops",
cl::desc("Enable reordered binary operations pattern."),
cl::cat(BuiltinPatternsCategory));

/// Add suffix to the file name.
/// \param File Original file name.
Expand Down Expand Up @@ -157,7 +161,8 @@ int main(int argc, const char **argv) {
.Relocations = RelocationsOpt,
.TypeCasts = TypeCastsOpt,
.ControlFlowOnly = ControlFlowOnlyOpt,
.InverseConditions = InverseConditionsOpt};
.InverseConditions = InverseConditionsOpt,
.ReorderedBinOps = ReorderedBinOpsOpt};

// Parse --fun option
auto FunName = parseFunOption();
Expand Down
20 changes: 19 additions & 1 deletion diffkemp/simpll/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <llvm/BinaryFormat/Dwarf.h>
#include <llvm/IR/DIBuilder.h>
#include <llvm/IR/DebugInfo.h>
#include <llvm/IR/Instruction.h>
#include <llvm/IR/Intrinsics.h>
#include <llvm/IR/Module.h>
#include <llvm/IR/Operator.h>
Expand Down Expand Up @@ -248,7 +249,7 @@ bool isLogicalNot(const Instruction *Inst) {
return false;

if (auto *BinOp = dyn_cast<BinaryOperator>(Inst)) {
if (BinOp->getOpcode() != llvm::Instruction::Xor)
if (BinOp->getOpcode() != Instruction::Xor)
return false;

if (auto constOp = dyn_cast<Constant>(BinOp->getOperand(1)))
Expand All @@ -257,6 +258,23 @@ bool isLogicalNot(const Instruction *Inst) {
return false;
}

/// Returns true if the given instruction is a reorderable binary operation,
/// i.e., it is commutative and associative. Note that IEEE 754 floating-point
/// addition/multiplication is NOT associative.
bool isReorderableBinaryOp(const Instruction *Inst) {
if (auto *BinOp = dyn_cast<BinaryOperator>(Inst)) {
static std::set<Instruction::BinaryOps> ReorderableOps = {
Instruction::Xor,
Instruction::Add,
Instruction::And,
Instruction::Or,
Instruction::Mul,
};
return (ReorderableOps.count(BinOp->getOpcode()) != 0);
}
return false;
}

/// Get value of the given constant as a string
std::string valueAsString(const Constant *Val) {
if (auto *IntVal = dyn_cast<ConstantInt>(Val)) {
Expand Down
5 changes: 5 additions & 0 deletions diffkemp/simpll/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ bool isZeroGEP(const Value *Val);
/// Returns true if the given instruction is a boolean negation operation
bool isLogicalNot(const Instruction *Inst);

/// Returns true if the given instruction is a reorderable binary operation,
/// i.e., it is commutative and associative. Note that IEEE 754 floating-point
/// addition/multiplication is NOT associative.
bool isReorderableBinaryOp(const Instruction *Inst);

/// Run simplification passes on the function
/// - simplify CFG
/// - dead code elimination
Expand Down
1 change: 1 addition & 0 deletions diffkemp/simpll/library/FFI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ BuiltinPatterns BuiltinPatternsFromC(builtin_patterns PatternsC) {
.TypeCasts = (bool)PatternsC.TypeCasts,
.ControlFlowOnly = (bool)PatternsC.ControlFlowOnly,
.InverseConditions = (bool)PatternsC.InverseConditions,
.ReorderedBinOps = (bool)PatternsC.ReorderedBinOps,
};
}

Expand Down
1 change: 1 addition & 0 deletions diffkemp/simpll/library/FFI.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ struct builtin_patterns {
int TypeCasts;
int ControlFlowOnly;
int InverseConditions;
int ReorderedBinOps;
};

struct config {
Expand Down
Loading

0 comments on commit e376fd9

Please sign in to comment.