//===- IRSimilarityIdentifierTest.cpp - IRSimilarityIdentifier unit tests -===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // Tests for components for finding similarity such as the instruction mapper, // suffix tree usage, and structural analysis. // //===----------------------------------------------------------------------===// #include "llvm/Analysis/IRSimilarityIdentifier.h" #include "llvm/AsmParser/Parser.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" #include "llvm/Support/Allocator.h" #include "llvm/Support/SourceMgr.h" #include "gtest/gtest.h" using namespace llvm; using namespace IRSimilarity; static std::unique_ptr makeLLVMModule(LLVMContext &Context, StringRef ModuleStr) { SMDiagnostic Err; std::unique_ptr M = parseAssemblyString(ModuleStr, Err, Context); assert(M && "Bad LLVM IR?"); return M; } void getVectors(Module &M, IRInstructionMapper &Mapper, std::vector &InstrList, std::vector &UnsignedVec) { for (Function &F : M) for (BasicBlock &BB : F) Mapper.convertToUnsignedVec(BB, InstrList, UnsignedVec); } void getSimilarities( Module &M, std::vector> &SimilarityCandidates) { IRSimilarityIdentifier Identifier; SimilarityCandidates = Identifier.findSimilarity(M); } // Checks that different opcodes are mapped to different values TEST(IRInstructionMapper, OpcodeDifferentiation) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = add i32 %a, %b %1 = mul i32 %a, %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); // Check that the size of the unsigned vector and the instruction list are the // same as a safety check. ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); // Make sure that the unsigned vector is the expected size. ASSERT_TRUE(UnsignedVec.size() == 3); // Check whether the instructions are not mapped to the same value. ASSERT_TRUE(UnsignedVec[0] != UnsignedVec[1]); } // Checks that the same opcodes and types are mapped to the same values. TEST(IRInstructionMapper, OpcodeTypeSimilarity) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = add i32 %a, %b %1 = add i32 %b, %a ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); // Check whether the instructions are mapped to the same value. ASSERT_TRUE(UnsignedVec[0] == UnsignedVec[1]); } // Checks that the same opcode and different types are mapped to different // values. TEST(IRInstructionMapper, TypeDifferentiation) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b, i64 %c, i64 %d) { bb0: %0 = add i32 %a, %b %1 = add i64 %c, %d ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] != UnsignedVec[1]); } // Checks that different predicates map to different values. TEST(IRInstructionMapper, PredicateDifferentiation) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = icmp sge i32 %b, %a %1 = icmp slt i32 %a, %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] != UnsignedVec[1]); } // Checks that predicates where that can be considered the same when the // operands are swapped, i.e. greater than to less than are mapped to the same // unsigned integer. TEST(IRInstructionMapper, PredicateIsomorphism) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = icmp sgt i32 %a, %b %1 = icmp slt i32 %b, %a ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] == UnsignedVec[1]); } // Checks that the same predicate maps to the same value. TEST(IRInstructionMapper, PredicateSimilarity) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = icmp slt i32 %a, %b %1 = icmp slt i32 %b, %a ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] == UnsignedVec[1]); } // Checks that the same predicate maps to the same value for floating point // CmpInsts. TEST(IRInstructionMapper, FPPredicateSimilarity) { StringRef ModuleString = R"( define i32 @f(double %a, double %b) { bb0: %0 = fcmp olt double %a, %b %1 = fcmp olt double %b, %a ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] == UnsignedVec[1]); } // Checks that the different predicate maps to a different value for floating // point CmpInsts. TEST(IRInstructionMapper, FPPredicatDifference) { StringRef ModuleString = R"( define i32 @f(double %a, double %b) { bb0: %0 = fcmp olt double %a, %b %1 = fcmp oge double %b, %a ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] != UnsignedVec[1]); } // Checks that the zexts that have the same type parameters map to the same // unsigned integer. TEST(IRInstructionMapper, ZextTypeSimilarity) { StringRef ModuleString = R"( define i32 @f(i32 %a) { bb0: %0 = zext i32 %a to i64 %1 = zext i32 %a to i64 ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] == UnsignedVec[1]); } // Checks that the sexts that have the same type parameters map to the same // unsigned integer. TEST(IRInstructionMapper, SextTypeSimilarity) { StringRef ModuleString = R"( define i32 @f(i32 %a) { bb0: %0 = sext i32 %a to i64 %1 = sext i32 %a to i64 ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] == UnsignedVec[1]); } // Checks that the zexts that have the different type parameters map to the // different unsigned integers. TEST(IRInstructionMapper, ZextTypeDifference) { StringRef ModuleString = R"( define i32 @f(i32 %a, i8 %b) { bb0: %0 = zext i32 %a to i64 %1 = zext i8 %b to i32 ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] != UnsignedVec[1]); } // Checks that the sexts that have the different type parameters map to the // different unsigned integers. TEST(IRInstructionMapper, SextTypeDifference) { StringRef ModuleString = R"( define i32 @f(i32 %a, i8 %b) { bb0: %0 = sext i32 %a to i64 %1 = sext i8 %b to i32 ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] != UnsignedVec[1]); } // Checks that loads that have the same type are mapped to the same unsigned // integer. TEST(IRInstructionMapper, LoadSimilarType) { StringRef ModuleString = R"( define i32 @f(i32* %a, i32* %b) { bb0: %0 = load i32, i32* %a %1 = load i32, i32* %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] == UnsignedVec[1]); } // Checks that loads that have the different types are mapped to // different unsigned integers. TEST(IRInstructionMapper, LoadDifferentType) { StringRef ModuleString = R"( define i32 @f(i32* %a, i64* %b) { bb0: %0 = load i32, i32* %a %1 = load i64, i64* %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] != UnsignedVec[1]); } // Checks that loads that have the different aligns are mapped to different // unsigned integers. TEST(IRInstructionMapper, LoadDifferentAlign) { StringRef ModuleString = R"( define i32 @f(i32* %a, i32* %b) { bb0: %0 = load i32, i32* %a, align 4 %1 = load i32, i32* %b, align 8 ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] != UnsignedVec[1]); } // Checks that loads that have the different volatile settings are mapped to // different unsigned integers. TEST(IRInstructionMapper, LoadDifferentVolatile) { StringRef ModuleString = R"( define i32 @f(i32* %a, i32* %b) { bb0: %0 = load volatile i32, i32* %a %1 = load i32, i32* %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] != UnsignedVec[1]); } // Checks that loads that have the same volatile settings are mapped to // different unsigned integers. TEST(IRInstructionMapper, LoadSameVolatile) { StringRef ModuleString = R"( define i32 @f(i32* %a, i32* %b) { bb0: %0 = load volatile i32, i32* %a %1 = load volatile i32, i32* %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] == UnsignedVec[1]); } // Checks that loads that have the different atomicity settings are mapped to // different unsigned integers. TEST(IRInstructionMapper, LoadDifferentAtomic) { StringRef ModuleString = R"( define i32 @f(i32* %a, i32* %b) { bb0: %0 = load atomic i32, i32* %a unordered, align 4 %1 = load atomic i32, i32* %b monotonic, align 4 ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] != UnsignedVec[1]); } // Checks that loads that have the same atomicity settings are mapped to // different unsigned integers. TEST(IRInstructionMapper, LoadSameAtomic) { StringRef ModuleString = R"( define i32 @f(i32* %a, i32* %b) { bb0: %0 = load atomic i32, i32* %a unordered, align 4 %1 = load atomic i32, i32* %b unordered, align 4 ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] == UnsignedVec[1]); } // Checks that stores that have the same type are mapped to the same unsigned // integer. TEST(IRInstructionMapper, StoreSimilarType) { StringRef ModuleString = R"( define i32 @f(i32* %a, i32* %b) { bb0: store i32 1, i32* %a store i32 2, i32* %a ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] == UnsignedVec[1]); } // Checks that stores that have the different types are mapped to // different unsigned integers. TEST(IRInstructionMapper, StoreDifferentType) { StringRef ModuleString = R"( define i32 @f(i32* %a, i64* %b) { bb0: store i32 1, i32* %a store i64 1, i64* %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] != UnsignedVec[1]); } // Checks that stores that have the different aligns are mapped to different // unsigned integers. TEST(IRInstructionMapper, StoreDifferentAlign) { StringRef ModuleString = R"( define i32 @f(i32* %a, i32* %b) { bb0: store i32 1, i32* %a, align 4 store i32 1, i32* %b, align 8 ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] != UnsignedVec[1]); } // Checks that stores that have the different volatile settings are mapped to // different unsigned integers. TEST(IRInstructionMapper, StoreDifferentVolatile) { StringRef ModuleString = R"( define i32 @f(i32* %a, i32* %b) { bb0: store volatile i32 1, i32* %a store i32 1, i32* %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] != UnsignedVec[1]); } // Checks that stores that have the same volatile settings are mapped to // different unsigned integers. TEST(IRInstructionMapper, StoreSameVolatile) { StringRef ModuleString = R"( define i32 @f(i32* %a, i32* %b) { bb0: store volatile i32 1, i32* %a store volatile i32 1, i32* %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] == UnsignedVec[1]); } // Checks that loads that have the same atomicity settings are mapped to // different unsigned integers. TEST(IRInstructionMapper, StoreSameAtomic) { StringRef ModuleString = R"( define i32 @f(i32* %a, i32* %b) { bb0: store atomic i32 1, i32* %a unordered, align 4 store atomic i32 1, i32* %b unordered, align 4 ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] == UnsignedVec[1]); } // Checks that loads that have the different atomicity settings are mapped to // different unsigned integers. TEST(IRInstructionMapper, StoreDifferentAtomic) { StringRef ModuleString = R"( define i32 @f(i32* %a, i32* %b) { bb0: store atomic i32 1, i32* %a unordered, align 4 store atomic i32 1, i32* %b monotonic, align 4 ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(UnsignedVec.size() == 3); ASSERT_TRUE(UnsignedVec[0] != UnsignedVec[1]); } // In most cases, the illegal instructions we are collecting don't require any // sort of setup. In these cases, we can just only have illegal instructions, // and the mapper will create 0 length vectors, and we can check that. // In cases where we have legal instructions needed to set up the illegal // instruction, to check illegal instructions are assigned unsigned integers // from the maximum value decreasing to 0, it will be greater than a legal // instruction that comes after. So to check that we have an illegal // instruction, we place a legal instruction after an illegal instruction, and // check that the illegal unsigned integer is greater than the unsigned integer // of the legal instruction. // Checks that the branch is mapped to be illegal since there is extra checking // needed to ensure that a branch in one region is branching to an isomorphic // location in a different region. TEST(IRInstructionMapper, BranchIllegal) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = icmp slt i32 %a, %b br i1 %0, label %bb0, label %bb1 bb1: ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(0)); } // Checks that a PHINode is mapped to be illegal since there is extra checking // needed to ensure that a branch in one region is bin an isomorphic // location in a different region. TEST(IRInstructionMapper, PhiIllegal) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = phi i1 [ 0, %bb0 ], [ %0, %bb1 ] ret i32 0 bb1: ret i32 1 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(0)); } // Checks that an alloca instruction is mapped to be illegal. TEST(IRInstructionMapper, AllocaIllegal) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = alloca i32 ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(0)); } // Checks that an getelementptr instruction is mapped to be legal. And that // the operands in getelementpointer instructions are the exact same after the // first element operand, which only requires the same type. TEST(IRInstructionMapper, GetElementPtrSameEndOperands) { StringRef ModuleString = R"( %struct.RT = type { i8, [10 x [20 x i32]], i8 } %struct.ST = type { i32, double, %struct.RT } define i32 @f(%struct.ST* %s, i64 %a, i64 %b) { bb0: %0 = getelementptr inbounds %struct.ST, %struct.ST* %s, i64 %a, i32 0 %1 = getelementptr inbounds %struct.ST, %struct.ST* %s, i64 %b, i32 0 ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(3)); ASSERT_EQ(UnsignedVec[0], UnsignedVec[1]); } // Check that when the operands in getelementpointer instructions are not the // exact same after the first element operand, the instructions are mapped to // different values. TEST(IRInstructionMapper, GetElementPtrDifferentEndOperands) { StringRef ModuleString = R"( %struct.RT = type { i8, [10 x [20 x i32]], i8 } %struct.ST = type { i32, double, %struct.RT } define i32 @f(%struct.ST* %s, i64 %a, i64 %b) { bb0: %0 = getelementptr inbounds %struct.ST, %struct.ST* %s, i64 %a, i32 0 %1 = getelementptr inbounds %struct.ST, %struct.ST* %s, i64 %b, i32 2 ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(3)); ASSERT_NE(UnsignedVec[0], UnsignedVec[1]); } // Check that when the operands in getelementpointer instructions are not the // same initial base type, each instruction is mapped to a different value. TEST(IRInstructionMapper, GetElementPtrDifferentBaseType) { StringRef ModuleString = R"( %struct.RT = type { i8, [10 x [20 x i32]], i8 } %struct.ST = type { i32, double, %struct.RT } define i32 @f(%struct.ST* %s, %struct.RT* %r, i64 %a, i64 %b) { bb0: %0 = getelementptr inbounds %struct.ST, %struct.ST* %s, i64 %a %1 = getelementptr inbounds %struct.RT, %struct.RT* %r, i64 %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(3)); ASSERT_NE(UnsignedVec[0], UnsignedVec[1]); } // Check that when the operands in getelementpointer instructions do not have // the same inbounds modifier, they are not counted as the same. TEST(IRInstructionMapper, GetElementPtrDifferentInBounds) { StringRef ModuleString = R"( %struct.RT = type { i8, [10 x [20 x i32]], i8 } %struct.ST = type { i32, double, %struct.RT } define i32 @f(%struct.ST* %s, %struct.RT* %r, i64 %a, i64 %b) { bb0: %0 = getelementptr inbounds %struct.ST, %struct.ST* %s, i64 %a, i32 0 %1 = getelementptr %struct.ST, %struct.ST* %s, i64 %b, i32 0 ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(3)); ASSERT_NE(UnsignedVec[0], UnsignedVec[1]); } // Checks that a call instruction is mapped to be illegal. We have to perform // extra checks to ensure that both the name and function type are the same. TEST(IRInstructionMapper, CallIllegal) { StringRef ModuleString = R"( declare i32 @f1(i32, i32) define i32 @f(i32 %a, i32 %b) { bb0: %0 = call i32 @f1(i32 %a, i32 %b) ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(0)); } // Checks that an invoke instruction is mapped to be illegal. Invoke // instructions are considered to be illegal because of the change in the // control flow that is currently not recognized. TEST(IRInstructionMapper, InvokeIllegal) { StringRef ModuleString = R"( define i32 @f(i8 *%gep1, i32 %b) { then: invoke i32 undef(i8* undef) to label %invoke unwind label %lpad invoke: unreachable lpad: landingpad { i8*, i32 } catch i8* null unreachable })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(0)); } // Checks that an callbr instructions are considered to be illegal. Callbr // instructions are considered to be illegal because of the change in the // control flow that is currently not recognized. TEST(IRInstructionMapper, CallBrInstIllegal) { StringRef ModuleString = R"( define void @test() { fail: ret void } define i32 @f(i32 %a, i32 %b) { bb0: callbr void asm "xorl $0, $0; jmp ${1:l}", "r,X,~{dirflag},~{fpsr},~{flags}"(i32 %a, i8* blockaddress(@test, %fail)) to label %normal [label %fail] fail: ret i32 0 normal: ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(0)); } // Checks that an debuginfo intrinsics are mapped to be invisible. Since they // do not semantically change the program, they can be recognized as similar. TEST(IRInstructionMapper, DebugInfoInvisible) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { then: %0 = add i32 %a, %b call void @llvm.dbg.value(metadata !0) %1 = add i32 %a, %b ret i32 0 } declare void @llvm.dbg.value(metadata) !0 = distinct !{!"test\00", i32 10})"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(3)); } // The following are all exception handling intrinsics. We do not currently // handle these instruction because they are very context dependent. // Checks that an eh.typeid.for intrinsic is mapped to be illegal. TEST(IRInstructionMapper, ExceptionHandlingTypeIdIllegal) { StringRef ModuleString = R"( @_ZTIi = external constant i8* define i32 @f() { then: %0 = call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIi to i8*)) ret i32 0 } declare i32 @llvm.eh.typeid.for(i8*))"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(0)); } // Checks that an eh.exceptioncode intrinsic is mapped to be illegal. TEST(IRInstructionMapper, ExceptionHandlingExceptionCodeIllegal) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { entry: %0 = catchswitch within none [label %__except] unwind to caller __except: %1 = catchpad within %0 [i8* null] catchret from %1 to label %__except then: %2 = call i32 @llvm.eh.exceptioncode(token %1) ret i32 0 } declare i32 @llvm.eh.exceptioncode(token))"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(0)); } // Checks that an eh.unwind intrinsic is mapped to be illegal. TEST(IRInstructionMapper, ExceptionHandlingUnwindIllegal) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { entry: call void @llvm.eh.unwind.init() ret i32 0 } declare void @llvm.eh.unwind.init())"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(0)); } // Checks that an eh.exceptionpointer intrinsic is mapped to be illegal. TEST(IRInstructionMapper, ExceptionHandlingExceptionPointerIllegal) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { entry: %0 = call i8* @llvm.eh.exceptionpointer.p0i8(i32 0) ret i32 0 } declare i8* @llvm.eh.exceptionpointer.p0i8(i32))"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(0)); } // Checks that a catchpad instruction is mapped to an illegal value. TEST(IRInstructionMapper, CatchpadIllegal) { StringRef ModuleString = R"( declare void @llvm.donothing() nounwind readnone define void @function() personality i8 3 { entry: invoke void @llvm.donothing() to label %normal unwind label %exception exception: %cs1 = catchswitch within none [label %catchpad1] unwind to caller catchpad1: catchpad within %cs1 [] br label %normal normal: ret void })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(0)); } // Checks that a cleanuppad instruction is mapped to an illegal value. TEST(IRInstructionMapper, CleanuppadIllegal) { StringRef ModuleString = R"( declare void @llvm.donothing() nounwind readnone define void @function() personality i8 3 { entry: invoke void @llvm.donothing() to label %normal unwind label %exception exception: %cs1 = catchswitch within none [label %catchpad1] unwind to caller catchpad1: %clean = cleanuppad within none [] br label %normal normal: ret void })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(0)); } // The following three instructions are memory transfer and setting based, which // are considered illegal since is extra checking needed to handle the address // space checking. // Checks that a memset instruction is mapped to an illegal value. TEST(IRInstructionMapper, MemSetIllegal) { StringRef ModuleString = R"( declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) define i64 @function(i64 %x, i64 %z, i64 %n) { entry: %pool = alloca [59 x i64], align 4 %tmp = bitcast [59 x i64]* %pool to i8* call void @llvm.memset.p0i8.i64(i8* nonnull %tmp, i8 0, i64 236, i32 4, i1 false) %cmp3 = icmp eq i64 %n, 0 %a = add i64 %x, %z %c = add i64 %x, %z ret i64 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(6)); ASSERT_TRUE(UnsignedVec[2] < UnsignedVec[1]); } // Checks that a memcpy instruction is mapped to an illegal value. TEST(IRInstructionMapper, MemCpyIllegal) { StringRef ModuleString = R"( declare void @llvm.memcpy.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) define i64 @function(i64 %x, i64 %z, i64 %n) { entry: %pool = alloca [59 x i64], align 4 %tmp = bitcast [59 x i64]* %pool to i8* call void @llvm.memcpy.p0i8.i64(i8* nonnull %tmp, i8 0, i64 236, i32 4, i1 false) %cmp3 = icmp eq i64 %n, 0 %a = add i64 %x, %z %c = add i64 %x, %z ret i64 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(6)); ASSERT_TRUE(UnsignedVec[2] < UnsignedVec[1]); } // Checks that a memmove instruction is mapped to an illegal value. TEST(IRInstructionMapper, MemMoveIllegal) { StringRef ModuleString = R"( declare void @llvm.memmove.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) define i64 @function(i64 %x, i64 %z, i64 %n) { entry: %pool = alloca [59 x i64], align 4 %tmp = bitcast [59 x i64]* %pool to i8* call void @llvm.memmove.p0i8.i64(i8* nonnull %tmp, i8 0, i64 236, i32 4, i1 false) %cmp3 = icmp eq i64 %n, 0 %a = add i64 %x, %z %c = add i64 %x, %z ret i64 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(6)); ASSERT_TRUE(UnsignedVec[2] < UnsignedVec[1]); } // Checks that a variable argument instructions are mapped to an illegal value. // We exclude variable argument instructions since variable arguments // requires extra checking of the argument list. TEST(IRInstructionMapper, VarArgsIllegal) { StringRef ModuleString = R"( declare void @llvm.va_start(i8*) declare void @llvm.va_copy(i8*, i8*) declare void @llvm.va_end(i8*) define i32 @func1(i32 %a, double %b, i8* %v, ...) nounwind { entry: %a.addr = alloca i32, align 4 %b.addr = alloca double, align 8 %ap = alloca i8*, align 4 %c = alloca i32, align 4 store i32 %a, i32* %a.addr, align 4 store double %b, double* %b.addr, align 8 %ap1 = bitcast i8** %ap to i8* call void @llvm.va_start(i8* %ap1) store double %b, double* %b.addr, align 8 store double %b, double* %b.addr, align 8 %0 = va_arg i8** %ap, i32 store double %b, double* %b.addr, align 8 store double %b, double* %b.addr, align 8 call void @llvm.va_copy(i8* %v, i8* %ap1) store double %b, double* %b.addr, align 8 store double %b, double* %b.addr, align 8 call void @llvm.va_end(i8* %ap1) store i32 %0, i32* %c, align 4 %tmp = load i32, i32* %c, align 4 ret i32 %tmp })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(16)); ASSERT_TRUE(UnsignedVec[4] < UnsignedVec[3]); ASSERT_TRUE(UnsignedVec[7] < UnsignedVec[6]); ASSERT_TRUE(UnsignedVec[10] < UnsignedVec[9]); ASSERT_TRUE(UnsignedVec[13] < UnsignedVec[12]); } // Check the length of adding two illegal instructions one after th other. We // should find that only one element is added for each illegal range. TEST(IRInstructionMapper, RepeatedIllegalLength) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = add i32 %a, %b %1 = mul i32 %a, %b %2 = call i32 @f(i32 %a, i32 %b) %3 = call i32 @f(i32 %a, i32 %b) %4 = add i32 %a, %b %5 = mul i32 %a, %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); // Check that the size of the unsigned vector and the instruction list are the // same as a safety check. ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); // Make sure that the unsigned vector is the expected size. ASSERT_TRUE(UnsignedVec.size() == 6); } // A helper function that accepts an instruction list from a module made up of // two blocks of two legal instructions and terminator, and checks them for // instruction similarity. static bool longSimCandCompare(std::vector &InstrList, bool Structure = false) { std::vector::iterator Start, End; Start = InstrList.begin(); End = InstrList.begin(); std::advance(End, 1); IRSimilarityCandidate Cand1(0, 2, *Start, *End); Start = InstrList.begin(); End = InstrList.begin(); std::advance(Start, 3); std::advance(End, 4); IRSimilarityCandidate Cand2(3, 2, *Start, *End); if (Structure) return IRSimilarityCandidate::compareStructure(Cand1, Cand2); return IRSimilarityCandidate::isSimilar(Cand1, Cand2); } // Checks that two adds with commuted operands are considered to be the same // instructions. TEST(IRSimilarityCandidate, CheckIdenticalInstructions) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = add i32 %a, %b %1 = add i32 %b, %a ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); // Check to make sure that we have a long enough region. ASSERT_EQ(InstrList.size(), static_cast(3)); // Check that the instructions were added correctly to both vectors. ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); std::vector::iterator Start, End; Start = InstrList.begin(); End = InstrList.begin(); std::advance(End, 1); IRSimilarityCandidate Cand1(0, 2, *Start, *End); IRSimilarityCandidate Cand2(0, 2, *Start, *End); ASSERT_TRUE(IRSimilarityCandidate::isSimilar(Cand1, Cand2)); } // Checks that comparison instructions are found to be similar instructions // when the operands are flipped and the predicate is also swapped. TEST(IRSimilarityCandidate, PredicateIsomorphism) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = icmp sgt i32 %a, %b %1 = add i32 %b, %a br label %bb1 bb1: %2 = icmp slt i32 %a, %b %3 = add i32 %a, %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() > 5); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); std::vector::iterator Start, End; Start = InstrList.begin(); End = InstrList.begin(); std::advance(End, 1); IRSimilarityCandidate Cand1(0, 2, *Start, *End); Start = InstrList.begin(); End = InstrList.begin(); std::advance(Start, 3); std::advance(End, 4); IRSimilarityCandidate Cand2(3, 2, *Start, *End); ASSERT_TRUE(IRSimilarityCandidate::isSimilar(Cand1, Cand2)); } // Checks that IRSimilarityCandidates wrapping these two regions of instructions // are able to differentiate between instructions that have different opcodes. TEST(IRSimilarityCandidate, CheckRegionsDifferentInstruction) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = add i32 %a, %b %1 = add i32 %b, %a ret i32 0 bb1: %2 = sub i32 %a, %b %3 = add i32 %b, %a ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); // Check to make sure that we have a long enough region. ASSERT_EQ(InstrList.size(), static_cast(6)); // Check that the instructions were added correctly to both vectors. ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_FALSE(longSimCandCompare(InstrList)); } // Checks that IRSimilarityCandidates wrapping these two regions of instructions // are able to differentiate between instructions that have different types. TEST(IRSimilarityCandidate, CheckRegionsDifferentTypes) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b, i64 %c, i64 %d) { bb0: %0 = add i32 %a, %b %1 = add i32 %b, %a ret i32 0 bb1: %2 = add i64 %c, %d %3 = add i64 %d, %c ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); // Check to make sure that we have a long enough region. ASSERT_EQ(InstrList.size(), static_cast(6)); // Check that the instructions were added correctly to both vectors. ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_FALSE(longSimCandCompare(InstrList)); } // Check that debug instructions do not impact similarity. They are marked as // invisible. TEST(IRSimilarityCandidate, IdenticalWithDebug) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = add i32 %a, %b call void @llvm.dbg.value(metadata !0) %1 = add i32 %b, %a ret i32 0 bb1: %2 = add i32 %a, %b call void @llvm.dbg.value(metadata !1) %3 = add i32 %b, %a ret i32 0 bb2: %4 = add i32 %a, %b %5 = add i32 %b, %a ret i32 0 } declare void @llvm.dbg.value(metadata) !0 = distinct !{!"test\00", i32 10} !1 = distinct !{!"test\00", i32 11})"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); // Check to make sure that we have a long enough region. ASSERT_EQ(InstrList.size(), static_cast(9)); // Check that the instructions were added correctly to both vectors. ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(longSimCandCompare(InstrList)); } // Checks that IRSimilarityCandidates that include illegal instructions, are not // considered to be the same set of instructions. In these sets of instructions // the allocas are illegal. TEST(IRSimilarityCandidate, IllegalInCandidate) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = add i32 %a, %b %1 = add i32 %a, %b %2 = alloca i32 ret i32 0 bb1: %3 = add i32 %a, %b %4 = add i32 %a, %b %5 = alloca i32 ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); // Check to make sure that we have a long enough region. ASSERT_EQ(InstrList.size(), static_cast(6)); // Check that the instructions were added correctly to both vectors. ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); std::vector::iterator Start, End; Start = InstrList.begin(); End = InstrList.begin(); std::advance(End, 2); IRSimilarityCandidate Cand1(0, 3, *Start, *End); Start = InstrList.begin(); End = InstrList.begin(); std::advance(Start, 3); std::advance(End, 5); IRSimilarityCandidate Cand2(3, 3, *Start, *End); ASSERT_FALSE(IRSimilarityCandidate::isSimilar(Cand1, Cand2)); } // Checks that different structure, in this case, where we introduce a new // needed input in one region, is recognized as different. TEST(IRSimilarityCandidate, DifferentStructure) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = add i32 %a, %b %1 = add i32 %b, %a ret i32 0 bb1: %2 = add i32 %a, %b %3 = add i32 %b, %0 ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); // Check to make sure that we have a long enough region. ASSERT_EQ(InstrList.size(), static_cast(6)); // Check that the instructions were added correctly to both vectors. ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_FALSE(longSimCandCompare(InstrList, true)); } // Checks that comparison instructions are found to have the same structure // when the operands are flipped and the predicate is also swapped. TEST(IRSimilarityCandidate, PredicateIsomorphismStructure) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = icmp sgt i32 %a, %b %1 = add i32 %a, %b br label %bb1 bb1: %2 = icmp slt i32 %b, %a %3 = add i32 %a, %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() > 5); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(longSimCandCompare(InstrList, true)); } // Checks that different predicates are counted as diferent. TEST(IRSimilarityCandidate, PredicateDifference) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = icmp sge i32 %a, %b %1 = add i32 %b, %a br label %bb1 bb1: %2 = icmp slt i32 %b, %a %3 = add i32 %a, %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_TRUE(InstrList.size() > 5); ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_FALSE(longSimCandCompare(InstrList)); } // Checks that the same structure is recognized between two candidates. The // items %a and %b are used in the same way in both sets of instructions. TEST(IRSimilarityCandidate, SameStructure) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = add i32 %a, %b %1 = sub i32 %b, %a ret i32 0 bb1: %2 = add i32 %a, %b %3 = sub i32 %b, %a ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); // Check to make sure that we have a long enough region. ASSERT_EQ(InstrList.size(), static_cast(6)); // Check that the instructions were added correctly to both vectors. ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(longSimCandCompare(InstrList, true)); } // Checks that the same structure is recognized between two candidates. While // the input names are reversed, they still perform the same overall operation. TEST(IRSimilarityCandidate, DifferentNameSameStructure) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = add i32 %a, %b %1 = add i32 %b, %a ret i32 0 bb1: %2 = add i32 %b, %a %3 = add i32 %a, %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector InstrList; std::vector UnsignedVec; SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); getVectors(*M, Mapper, InstrList, UnsignedVec); // Check to make sure that we have a long enough region. ASSERT_EQ(InstrList.size(), static_cast(6)); // Check that the instructions were added correctly to both vectors. ASSERT_TRUE(InstrList.size() == UnsignedVec.size()); ASSERT_TRUE(longSimCandCompare(InstrList, true)); } // Checks that two sets of identical instructions are found to be the same. // Both sequences of adds have the same operand ordering, and the same // instructions, making them strcturally equivalent. TEST(IRSimilarityIdentifier, IdentitySimilarity) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = add i32 %a, %b %1 = sub i32 %b, %a br label %bb1 bb1: %2 = add i32 %a, %b %3 = sub i32 %b, %a ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector> SimilarityCandidates; getSimilarities(*M, SimilarityCandidates); ASSERT_TRUE(SimilarityCandidates.size() == 1); for (std::vector &Cands : SimilarityCandidates) { ASSERT_TRUE(Cands.size() == 2); unsigned InstIdx = 0; for (IRSimilarityCandidate &Cand : Cands) { ASSERT_TRUE(Cand.getStartIdx() == InstIdx); InstIdx += 3; } } } // Checks that incorrect sequences are not found as similar. In this case, // we have different sequences of instructions. TEST(IRSimilarityIdentifier, InstructionDifference) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b, i32 %c, i32 %d) { bb0: %0 = sub i32 %a, %b %1 = add i32 %b, %a br label %bb1 bb1: %2 = add i32 %c, %d %3 = sub i32 %d, %c ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector> SimilarityCandidates; getSimilarities(*M, SimilarityCandidates); ASSERT_TRUE(SimilarityCandidates.empty()); } // This test checks to see whether we can detect similarity for commutative // instructions where the operands have been reversed. TEST(IRSimilarityIdentifier, CommutativeSimilarity) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = add i32 %a, %b %1 = add i32 %b, %a br label %bb1 bb1: %2 = add i32 %a, %b %3 = add i32 %a, %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector> SimilarityCandidates; getSimilarities(*M, SimilarityCandidates); ASSERT_TRUE(SimilarityCandidates.size() == 1); for (std::vector &Cands : SimilarityCandidates) { ASSERT_TRUE(Cands.size() == 2); unsigned InstIdx = 0; for (IRSimilarityCandidate &Cand : Cands) { ASSERT_TRUE(Cand.getStartIdx() == InstIdx); InstIdx += 3; } } } // This test checks to see whether we can detect different structure in // commutative instructions. In this case, the second operand in the second // add is different. TEST(IRSimilarityIdentifier, NoCommutativeSimilarity) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = add i32 %a, %b %1 = add i32 %1, %b br label %bb1 bb1: %2 = add i32 %a, %b %3 = add i32 %2, %a ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector> SimilarityCandidates; getSimilarities(*M, SimilarityCandidates); ASSERT_TRUE(SimilarityCandidates.size() == 0); } // Check that we are not finding similarity in non commutative // instructions. That is, while the instruction and operands used are the same // in the two subtraction sequences, they are in a different order, and cannot // be counted as the same since a subtraction is not commutative. TEST(IRSimilarityIdentifier, NonCommutativeDifference) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = sub i32 %a, %b %1 = sub i32 %b, %a br label %bb1 bb1: %2 = sub i32 %a, %b %3 = sub i32 %a, %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector> SimilarityCandidates; getSimilarities(*M, SimilarityCandidates); ASSERT_TRUE(SimilarityCandidates.empty()); } // Check that we find similarity despite changing the register names. TEST(IRSimilarityIdentifier, MappingSimilarity) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b, i32 %c, i32 %d) { bb0: %0 = add i32 %a, %b %1 = sub i32 %b, %a br label %bb1 bb1: %2 = add i32 %c, %d %3 = sub i32 %d, %c ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector> SimilarityCandidates; getSimilarities(*M, SimilarityCandidates); ASSERT_TRUE(SimilarityCandidates.size() == 1); for (std::vector &Cands : SimilarityCandidates) { ASSERT_TRUE(Cands.size() == 2); unsigned InstIdx = 0; for (IRSimilarityCandidate &Cand : Cands) { ASSERT_TRUE(Cand.getStartIdx() == InstIdx); InstIdx += 3; } } } // Check that we find instances of swapped predicate isomorphism. That is, // for predicates that can be flipped, e.g. greater than to less than, // we can identify that instances of these different literal predicates, but are // the same within a single swap can be found. TEST(IRSimilarityIdentifier, PredicateIsomorphism) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = add i32 %a, %b %1 = icmp sgt i32 %b, %a br label %bb1 bb1: %2 = add i32 %a, %b %3 = icmp slt i32 %a, %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector> SimilarityCandidates; getSimilarities(*M, SimilarityCandidates); ASSERT_TRUE(SimilarityCandidates.size() == 1); for (std::vector &Cands : SimilarityCandidates) { ASSERT_TRUE(Cands.size() == 2); unsigned InstIdx = 0; for (IRSimilarityCandidate &Cand : Cands) { ASSERT_TRUE(Cand.getStartIdx() == InstIdx); InstIdx += 3; } } } // Checks that constants are detected as the same operand in each use in the // sequences of instructions. Also checks that we can find structural // equivalence using constants. In this case the 1 has the same use pattern as // %a. TEST(IRSimilarityIdentifier, ConstantMappingSimilarity) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = add i32 1, %b %1 = icmp sgt i32 %b, 1 br label %bb1 bb1: %2 = add i32 %a, %b %3 = icmp sgt i32 %b, %a ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector> SimilarityCandidates; getSimilarities(*M, SimilarityCandidates); ASSERT_TRUE(SimilarityCandidates.size() == 1); for (std::vector &Cands : SimilarityCandidates) { ASSERT_TRUE(Cands.size() == 2); unsigned InstIdx = 0; for (IRSimilarityCandidate &Cand : Cands) { ASSERT_TRUE(Cand.getStartIdx() == InstIdx); InstIdx += 3; } } } // Check that constants are uniquely identified. i.e. two different constants // are not considered the same. This means that this should not find any // structural similarity. TEST(IRSimilarityIdentifier, ConstantMappingDifference) { StringRef ModuleString = R"( define i32 @f(i32 %a, i32 %b) { bb0: %0 = add i32 1, %b %1 = icmp sgt i32 %b, 2 br label %bb1 bb1: %2 = add i32 %a, %b %3 = icmp slt i32 %a, %b ret i32 0 })"; LLVMContext Context; std::unique_ptr M = makeLLVMModule(Context, ModuleString); std::vector> SimilarityCandidates; getSimilarities(*M, SimilarityCandidates); ASSERT_TRUE(SimilarityCandidates.empty()); }