diff --git a/lib/IRCanonicalization/TwosComplementArithmeticNormalizationPass.cpp b/lib/IRCanonicalization/TwosComplementArithmeticNormalizationPass.cpp index fc7e8917f..8f0591053 100644 --- a/lib/IRCanonicalization/TwosComplementArithmeticNormalizationPass.cpp +++ b/lib/IRCanonicalization/TwosComplementArithmeticNormalizationPass.cpp @@ -2,7 +2,9 @@ // Copyright rev.ng Labs Srl. See LICENSE.md for details. // +#include "llvm/IR/DerivedTypes.h" #include "llvm/IR/IRBuilder.h" +#include "llvm/IR/Instructions.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/MDBuilder.h" #include "llvm/IR/PatternMatch.h" @@ -28,115 +30,258 @@ public: using TANP = TwosComplementArithmeticNormalizationPass; +class UnaryMinusBuilder { + + OpaqueFunctionsPool Pool; + llvm::IRBuilder<> Builder; + +public: + UnaryMinusBuilder(llvm::Function &F) : + Pool(F.getParent(), false), Builder(F.getContext()) { + initUnaryMinusPool(Pool); + } + + void SetInsertPoint(llvm::Instruction *I) { Builder.SetInsertPoint(I); } + + llvm::CallInst *operator()(llvm::Type *IntType, llvm::APInt Value) { + revng_assert(llvm::isa(IntType)); + llvm::Function *Func = Pool.get(IntType, IntType, IntType, "unary_minus"); + auto ConstInt = llvm::ConstantInt::getSigned(IntType, + Value.abs().getLimitedValue()); + return Builder.CreateCall(Func, { ConstInt }); + } +}; + +class BinaryNotBuilder { + + OpaqueFunctionsPool Pool; + llvm::IRBuilder<> Builder; + +public: + BinaryNotBuilder(llvm::Function &F) : + Pool(F.getParent(), false), Builder(F.getContext()) { + initBinaryNotPool(Pool); + } + + void SetInsertPoint(llvm::Instruction *I) { Builder.SetInsertPoint(I); } + + llvm::CallInst *operator()(llvm::Type *IntType, llvm::Value *Val) { + revng_assert(isa(IntType)); + llvm::Function *Func = Pool.get(IntType, IntType, IntType, "binary_not"); + return Builder.CreateCall(Func, { Val }); + } +}; + +static bool isSignedComparison(llvm::ICmpInst::Predicate P) { + return P == llvm::ICmpInst::Predicate::ICMP_SGE + or P == llvm::ICmpInst::Predicate::ICMP_SGT + or P == llvm::ICmpInst::Predicate::ICMP_SLE + or P == llvm::ICmpInst::Predicate::ICMP_SLT; +} + +static bool isEqualityComparison(llvm::ICmpInst::Predicate P) { + return P == llvm::ICmpInst::Predicate::ICMP_EQ + or P == llvm::ICmpInst::Predicate::ICMP_NE; +} + bool TANP::runOnFunction(llvm::Function &F) { using namespace llvm; using namespace PatternMatch; - IRBuilder<> Builder(F.getContext()); + UnaryMinusBuilder BuildUnaryMinus{ F }; + BinaryNotBuilder BuildBinaryNot{ F }; + llvm::IRBuilder<> Builder{ F.getContext() }; - OpaqueFunctionsPool UnaryMinusPool(F.getParent(), false); - initUnaryMinusPool(UnaryMinusPool); - - OpaqueFunctionsPool BinaryNotPool(F.getParent(), false); - initBinaryNotPool(BinaryNotPool); - - auto BuildUnaryMinus = - [&Builder, &UnaryMinusPool](const Value *Val, const APInt *Int) -> Value * { - const auto IntType = Val->getType(); - auto Func = UnaryMinusPool.get(IntType, IntType, IntType, "unary_minus"); - auto IntValue = Int->abs().getLimitedValue(); - auto Value = ConstantInt::getSigned(IntType, IntValue); - auto Call = Builder.CreateCall(Func, { Value }); - return Call; - }; + bool Changed = false; SmallVector DeadInsts; for (BasicBlock &BB : F) { for (Instruction &I : BB) { Value *Val = nullptr; const APInt *Int = nullptr; - Value *NewV = nullptr; - ICmpInst::Predicate Pred; - Builder.SetInsertPoint(&I); - if (match(&I, m_Add(m_Value(Val), m_APInt(Int))) && Int->isNegative()) { + Value *NewV = nullptr; + if ((match(&I, m_Xor(m_Value(Val), m_APInt(Int))) + or match(&I, m_Xor(m_APInt(Int), m_Value(Val)))) + and Int->isAllOnesValue()) { + BuildBinaryNot.SetInsertPoint(&I); + NewV = BuildBinaryNot(I.getType(), Val); + + } else if (match(&I, m_Add(m_Value(Val), m_APInt(Int))) + and Int->isNegative()) { + Builder.SetInsertPoint(&I); NewV = Builder.CreateSub(Val, ConstantInt::get(I.getType(), ~(*Int) + 1)); } else if (match(&I, m_Sub(m_Value(Val), m_APInt(Int))) - && Int->isNegative()) { + and Int->isNegative()) { + Builder.SetInsertPoint(&I); NewV = Builder.CreateAdd(Val, ConstantInt::get(I.getType(), ~(*Int) + 1)); } else if ((match(&I, m_Mul(m_Value(Val), m_APInt(Int))) - || match(&I, m_Mul(m_APInt(Int), m_Value(Val)))) - && Int->isNegative()) { + or match(&I, m_Mul(m_APInt(Int), m_Value(Val)))) + and Int->isNegative()) { const auto IntType = Val->getType(); if (Int->isSignBitSet() - && Int->isSignedIntN(IntType->getIntegerBitWidth())) { - auto Call = BuildUnaryMinus(Val, Int); - NewV = Builder.CreateMul(Val, Call); + and Int->isSignedIntN(IntType->getIntegerBitWidth())) { + BuildUnaryMinus.SetInsertPoint(&I); + auto UnaryMinus = BuildUnaryMinus(Val->getType(), *Int); + Builder.SetInsertPoint(UnaryMinus->getNextNonDebugInstruction()); + NewV = Builder.CreateMul(Val, UnaryMinus); } } else if (match(&I, m_SDiv(m_Value(Val), m_APInt(Int))) - && Int->isNegative()) { + and Int->isNegative()) { const auto IntType = Val->getType(); if (Int->isSignBitSet() - && Int->isSignedIntN(IntType->getIntegerBitWidth())) { - auto Call = BuildUnaryMinus(Val, Int); - NewV = Builder.CreateSDiv(Val, Call); + and Int->isSignedIntN(IntType->getIntegerBitWidth())) { + BuildUnaryMinus.SetInsertPoint(&I); + auto UnaryMinus = BuildUnaryMinus(Val->getType(), *Int); + Builder.SetInsertPoint(UnaryMinus->getNextNonDebugInstruction()); + NewV = Builder.CreateSDiv(Val, UnaryMinus); } } else if (match(&I, m_SDiv(m_APInt(Int), m_Value(Val))) - && Int->isNegative()) { + and Int->isNegative()) { const auto IntType = Val->getType(); if (Int->isSignBitSet() - && Int->isSignedIntN(IntType->getIntegerBitWidth())) { - auto Call = BuildUnaryMinus(Val, Int); - NewV = Builder.CreateSDiv(Call, Val); + and Int->isSignedIntN(IntType->getIntegerBitWidth())) { + BuildUnaryMinus.SetInsertPoint(&I); + auto UnaryMinus = BuildUnaryMinus(Val->getType(), *Int); + Builder.SetInsertPoint(UnaryMinus->getNextNonDebugInstruction()); + NewV = Builder.CreateSDiv(UnaryMinus, Val); } } else if (match(&I, m_SRem(m_Value(Val), m_APInt(Int))) - && Int->isNegative()) { + and Int->isNegative()) { const auto IntType = Val->getType(); if (Int->isSignBitSet() - && Int->isSignedIntN(IntType->getIntegerBitWidth())) { - auto Call = BuildUnaryMinus(Val, Int); - NewV = Builder.CreateSRem(Val, Call); + and Int->isSignedIntN(IntType->getIntegerBitWidth())) { + BuildUnaryMinus.SetInsertPoint(&I); + auto UnaryMinus = BuildUnaryMinus(Val->getType(), *Int); + Builder.SetInsertPoint(UnaryMinus->getNextNonDebugInstruction()); + NewV = Builder.CreateSRem(Val, UnaryMinus); } } else if (match(&I, m_SRem(m_APInt(Int), m_Value(Val))) - && Int->isNegative()) { + and Int->isNegative()) { const auto IntType = Val->getType(); if (Int->isSignBitSet() - && Int->isSignedIntN(IntType->getIntegerBitWidth())) { - auto Call = BuildUnaryMinus(Val, Int); - NewV = Builder.CreateSRem(Call, Val); + and Int->isSignedIntN(IntType->getIntegerBitWidth())) { + BuildUnaryMinus.SetInsertPoint(&I); + auto UnaryMinus = BuildUnaryMinus(Val->getType(), *Int); + Builder.SetInsertPoint(UnaryMinus->getNextNonDebugInstruction()); + NewV = Builder.CreateSRem(UnaryMinus, Val); } - } else if ((match(&I, m_Xor(m_Value(Val), m_APInt(Int))) - || match(&I, m_Xor(m_APInt(Int), m_Value(Val)))) - && Int->isAllOnesValue()) { - const auto IntType = I.getType(); - auto Func = BinaryNotPool.get(IntType, IntType, IntType, "binary_not"); - NewV = Builder.CreateCall(Func, { Val }); - } else if (match(&I, m_ICmp(Pred, m_Value(Val), m_APInt(Int))) - && Pred == ICmpInst::Predicate::ICMP_EQ) { + + } else if (ICmpInst::Predicate Pred; + match(&I, m_ICmp(Pred, m_Value(Val), m_APInt(Int)))) { + const auto IntType = Val->getType(); - if (Int->isSignBitSet() - && Int->isSignedIntN(IntType->getIntegerBitWidth())) { - auto Call = BuildUnaryMinus(Val, Int); - NewV = Builder.CreateICmp(Pred, Val, Call); + llvm::Value *LHS = nullptr; + const APInt *RHS = nullptr; + if (match(Val, m_Add(m_Value(LHS), m_APInt(RHS)))) { + bool Overflow = false; + APInt NewInt = isSignedComparison(Pred) ? + Int->ssub_ov(*RHS, Overflow) : + Int->usub_ov(*RHS, Overflow); + Builder.SetInsertPoint(I.getNextNonDebugInstruction()); + NewV = Builder.CreateICmp(Pred, + LHS, + ConstantInt::get(IntType, NewInt)); + + if (not isEqualityComparison(Pred) and Overflow) { + // Here we don't have overflow, and it's not an equality comparison, + // so we have to handle wraparound + llvm::ICmpInst::Predicate P; + switch (Pred) { + case llvm::ICmpInst::Predicate::ICMP_SGE: + case llvm::ICmpInst::Predicate::ICMP_SGT: { + P = llvm::ICmpInst::Predicate::ICMP_SLT; + } break; + + case llvm::ICmpInst::Predicate::ICMP_UGE: + case llvm::ICmpInst::Predicate::ICMP_UGT: { + P = llvm::ICmpInst::Predicate::ICMP_ULT; + } break; + + case llvm::ICmpInst::Predicate::ICMP_SLE: + case llvm::ICmpInst::Predicate::ICMP_SLT: { + P = llvm::ICmpInst::Predicate::ICMP_SGT; + } break; + + case llvm::ICmpInst::Predicate::ICMP_ULE: + case llvm::ICmpInst::Predicate::ICMP_ULT: { + P = llvm::ICmpInst::Predicate::ICMP_UGT; + } break; + + default: + revng_abort(); + } + auto *NotRHS = ConstantInt::get(IntType, ~*RHS); + NewV = Builder.CreateAnd(NewV, Builder.CreateICmp(P, LHS, NotRHS)); + } + } else if (match(Val, m_Sub(m_Value(LHS), m_APInt(RHS)))) { + bool Overflow = false; + APInt NewInt = isSignedComparison(Pred) ? + Int->ssub_ov(-*RHS, Overflow) : + Int->usub_ov(-*RHS, Overflow); + Builder.SetInsertPoint(I.getNextNonDebugInstruction()); + NewV = Builder.CreateICmp(Pred, + LHS, + ConstantInt::get(IntType, NewInt)); + + if (not isEqualityComparison(Pred) and Overflow) { + // Here we don't have overflow, and it's not an equality comparison, + // so we have to handle wraparound + llvm::ICmpInst::Predicate P; + switch (Pred) { + case llvm::ICmpInst::Predicate::ICMP_SGE: + case llvm::ICmpInst::Predicate::ICMP_SGT: { + P = llvm::ICmpInst::Predicate::ICMP_SLT; + } break; + + case llvm::ICmpInst::Predicate::ICMP_UGE: + case llvm::ICmpInst::Predicate::ICMP_UGT: { + P = llvm::ICmpInst::Predicate::ICMP_ULT; + } break; + + case llvm::ICmpInst::Predicate::ICMP_SLE: + case llvm::ICmpInst::Predicate::ICMP_SLT: { + P = llvm::ICmpInst::Predicate::ICMP_SGT; + } break; + + case llvm::ICmpInst::Predicate::ICMP_ULE: + case llvm::ICmpInst::Predicate::ICMP_ULT: { + P = llvm::ICmpInst::Predicate::ICMP_UGT; + } break; + + default: + revng_abort(); + } + auto *NotMinusRHS = ConstantInt::get(IntType, ~-*RHS); + NewV = Builder.CreateAnd(NewV, + Builder.CreateICmp(P, LHS, NotMinusRHS)); + } + } else if (Int->isSignBitSet() + and Int->isSignedIntN(IntType->getIntegerBitWidth())) { + BuildUnaryMinus.SetInsertPoint(&I); + auto UnaryMinus = BuildUnaryMinus(IntType, *Int); + Builder.SetInsertPoint(UnaryMinus->getNextNonDebugInstruction()); + NewV = Builder.CreateICmp(Pred, Val, UnaryMinus); } } if (NewV) { + Changed = true; I.replaceAllUsesWith(NewV); DeadInsts.emplace_back(&I); } @@ -144,9 +289,9 @@ bool TANP::runOnFunction(llvm::Function &F) { } for (auto *I : DeadInsts) - I->eraseFromParent(); + llvm::RecursivelyDeleteTriviallyDeadInstructions(I); - return not DeadInsts.empty(); + return Changed; } char TANP::ID = 0; diff --git a/tests/unit/llvm-lit-tests/TwosComplementArithmeticNormalization.ll b/tests/unit/llvm-lit-tests/TwosComplementArithmeticNormalization.ll index 6ea7e2959..da972c543 100644 --- a/tests/unit/llvm-lit-tests/TwosComplementArithmeticNormalization.ll +++ b/tests/unit/llvm-lit-tests/TwosComplementArithmeticNormalization.ll @@ -113,5 +113,170 @@ define i32 @twoscomplement_norm_srem_unary_minus_2(i32 %0) !revng.tags !0 { ret i32 %2 } +define i1 @twoscomplement_norm_move_const_add_eq(i16 %0) !revng.tags !0 { + ; CHECK: %2 = icmp eq i16 %0, 1 + %2 = add i16 %0, 2 + %3 = icmp eq i16 %2, 3 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_sub_eq(i16 %0) !revng.tags !0 { + ; CHECK: %2 = icmp eq i16 %0, 5 + %2 = sub i16 %0, 2 + %3 = icmp eq i16 %2, 3 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_add_ne(i16 %0) !revng.tags !0 { + ; CHECK: %2 = icmp ne i16 %0, 1 + %2 = add i16 %0, 2 + %3 = icmp ne i16 %2, 3 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_sub_ne(i16 %0) !revng.tags !0 { + ; CHECK: %2 = icmp ne i16 %0, 5 + %2 = sub i16 %0, 2 + %3 = icmp ne i16 %2, 3 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_unary_minus(i8 %0) !revng.tags !0 { + ; CHECK: %2 = call i8 @unary_minus(i8 127) + ; CHECK-NEXT: %3 = icmp eq i8 %0, %2 + %2 = add i8 %0, 1 + %3 = icmp eq i8 %2, 130 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_slt_ov(i8 %0) !revng.tags !0 { + ; CHECK: %2 = call i8 @unary_minus(i8 120) + ; CHECK-NEXT: %3 = icmp slt i8 %0, %2 + %2 = sub i8 %0, 10 + %3 = icmp slt i8 %2, 126 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_sle_ov(i8 %0) !revng.tags !0 { + ; CHECK: %2 = call i8 @unary_minus(i8 120) + ; CHECK-NEXT: %3 = icmp sle i8 %0, %2 + %2 = sub i8 %0, 10 + %3 = icmp sle i8 %2, 126 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_sgt_ov(i8 %0) !revng.tags !0 { + ; CHECK: %2 = call i8 @unary_minus(i8 120) + ; CHECK-NEXT: %3 = icmp sgt i8 %0, %2 + %2 = sub i8 %0, 10 + %3 = icmp sgt i8 %2, 126 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_sge_ov(i8 %0) !revng.tags !0 { + ; CHECK: %2 = call i8 @unary_minus(i8 120) + ; CHECK-NEXT: %3 = icmp sge i8 %0, %2 + %2 = sub i8 %0, 10 + %3 = icmp sge i8 %2, 126 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_slt_unary_minus(i16 %0) !revng.tags !0 { + ; CHECK: %2 = call i16 @unary_minus.2(i16 1) + ; CHECK-NEXT: %3 = icmp slt i16 %0, %2 + %2 = sub i16 %0, 1 + %3 = icmp slt i16 %2, 65534 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_sle_unary_minus(i16 %0) !revng.tags !0 { + ; CHECK: %2 = call i16 @unary_minus.2(i16 1) + ; CHECK-NEXT: %3 = icmp sle i16 %0, %2 + %2 = sub i16 %0, 1 + %3 = icmp sle i16 %2, 65534 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_sgt_unary_minus(i16 %0) !revng.tags !0 { + ; CHECK: %2 = call i16 @unary_minus.2(i16 1) + ; CHECK-NEXT: %3 = icmp sgt i16 %0, %2 + %2 = sub i16 %0, 1 + %3 = icmp sgt i16 %2, 65534 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_sge_unary_minus(i16 %0) !revng.tags !0 { + ; CHECK: %2 = call i16 @unary_minus.2(i16 1) + ; CHECK-NEXT: %3 = icmp sge i16 %0, %2 + %2 = sub i16 %0, 1 + %3 = icmp sge i16 %2, 65534 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_ult_ov(i16 %0) !revng.tags !0 { + ; CHECK: %2 = icmp ult i16 %0, 1 + %2 = sub i16 %0, 2 + %3 = icmp ult i16 %2, 65535 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_ule_ov(i16 %0) !revng.tags !0 { + ; CHECK: %2 = icmp ule i16 %0, 1 + %2 = sub i16 %0, 2 + %3 = icmp ule i16 %2, 65535 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_ugt_ov(i16 %0) !revng.tags !0 { + ; CHECK: %2 = icmp ugt i16 %0, 1 + %2 = sub i16 %0, 2 + %3 = icmp ugt i16 %2, 65535 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_uge_ov(i16 %0) !revng.tags !0 { + ; CHECK: %2 = icmp uge i16 %0, 1 + %2 = sub i16 %0, 2 + %3 = icmp uge i16 %2, 65535 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_ult(i16 %0) !revng.tags !0 { + ; CHECK: %2 = icmp ult i16 %0, 5 + %2 = sub i16 %0, 2 + %3 = icmp ult i16 %2, 3 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_ule(i16 %0) !revng.tags !0 { + ; CHECK: %2 = icmp ule i16 %0, 5 + %2 = sub i16 %0, 2 + %3 = icmp ule i16 %2, 3 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_ugt(i16 %0) !revng.tags !0 { + ; CHECK: %2 = icmp ugt i16 %0, 5 + %2 = sub i16 %0, 2 + %3 = icmp ugt i16 %2, 3 + ret i1 %3 +} + +define i1 @twoscomplement_norm_move_const_uge(i16 %0) !revng.tags !0 { + ; CHECK: %2 = icmp uge i16 %0, 5 + %2 = sub i16 %0, 2 + %3 = icmp uge i16 %2, 3 + ret i1 %3 +} + +define i1 @neg_neg(i16 %0) !revng.tags !0 { + ; CHECK: %2 = call i16 @unary_minus.2(i16 7) + ; CHECK-NEXT: %3 = icmp ult i16 %0, %2 + ; CHECK-NEXT: %4 = icmp ugt i16 %0, 3 + ; CHECK-NEXT: %5 = and i1 %3, %4 + %2 = add i16 %0, -4 + %3 = icmp ult i16 %2, -11 + ret i1 %3 +} !0 = !{!"Isolated"}