/// \file Pipeline.cpp // // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/LegacyPassManager.h" #include "llvm/InitializePasses.h" #include "llvm/Pass.h" #include "llvm/Support/Casting.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/YAMLTraits.h" #include "llvm/Transforms/Utils/Cloning.h" #include "revng/Pipeline/AllRegistries.h" #include "revng/Pipeline/ContainerEnumerator.h" #include "revng/Pipeline/ContainerFactory.h" #include "revng/Pipeline/ContainerFactorySet.h" #include "revng/Pipeline/ContainerSet.h" #include "revng/Pipeline/Context.h" #include "revng/Pipeline/Contract.h" #include "revng/Pipeline/Errors.h" #include "revng/Pipeline/GenericLLVMPipe.h" #include "revng/Pipeline/Invokable.h" #include "revng/Pipeline/Kind.h" #include "revng/Pipeline/LLVMContainer.h" #include "revng/Pipeline/LLVMContainerFactory.h" #include "revng/Pipeline/LLVMKind.h" #include "revng/Pipeline/Loader.h" #include "revng/Pipeline/Runner.h" #include "revng/Pipeline/Target.h" #include "revng/Support/Assert.h" #define BOOST_TEST_MODULE Pipeline bool init_unit_test(); #include "boost/test/unit_test.hpp" #include "revng/UnitTestHelpers/UnitTestHelpers.h" using namespace pipeline; using namespace std; using namespace llvm; static auto Root = defineRootRank<"Root">(); static auto FunctionRank = defineRank<"Function", std::string>(Root); class RootKindType : public LLVMKind { public: RootKindType() : LLVMKind("RootKind", Root) { revng_assert(depth() == 0); }; std::optional symbolToTarget(const llvm::Function &Symbol) const override { if (Symbol.getName() == "root") return Target(*this); return std::nullopt; } void appendAllTargets(const pipeline::Context &Ctx, pipeline::TargetsList &Out) const override { Out.push_back(Target(*this)); } ~RootKindType() override {} }; static RootKindType RootKind; static SingleElementKind RootKind2("RootKind2", RootKind, Root, {}, {}); static SingleElementKind RootKind3("RootKind3", Root, {}, {}); class SingleFunctionKind : public LLVMKind { public: using LLVMKind::LLVMKind; std::optional symbolToTarget(const llvm::Function &Symbol) const override { if (Symbol.getName() == "f1") return Target("f1", *this); if (Symbol.getName() == "f2") return Target("f2", *this); return std::nullopt; } void appendAllTargets(const pipeline::Context &Ctx, pipeline::TargetsList &Out) const override { Out.push_back(Target("f1", *this)); Out.push_back(Target("f2", *this)); } ~SingleFunctionKind() override {} }; static SingleFunctionKind FunctionKind("FunctionKind", FunctionRank); static std::string CName = "ContainerName"; class MapContainer : public Container { public: static inline const llvm::StringRef MIMEType = "application/x.test.map"; MapContainer(std::map Map, llvm::StringRef Name) : Container(Name), Map(std::move(Map)) {} MapContainer(llvm::StringRef Name) : Container(Name), Map() {} ~MapContainer() override = default; unique_ptr cloneFiltered(const TargetsList &Container) const final { return make_unique(Map, this->name()); } static std::vector possibleKinds() { return { &FunctionKind, &RootKind }; } bool contains(const Target &T) const { return Map.contains(T); } TargetsList enumerate() const final { TargetsList ToReturn; for (const auto &Target : Map) ToReturn.push_back(Target.first); return ToReturn; } bool remove(const TargetsList &Targets) override { bool RemovedAll = true; for (const auto &Target : Targets) RemovedAll = remove(Target) && RemovedAll; return RemovedAll; } bool remove(const Target &Target) { if (!Map.contains(Target)) return false; Map.erase(Target); return true; } llvm::Error extractOne(llvm::raw_ostream &OS, const Target &Target) const override { revng_abort(); return llvm::Error::success(); } static char ID; auto &get(Target Target) { return Map[std::move(Target)]; } const auto &get(const Target &Target) const { return Map.find(std::move(Target))->second; } auto &getMap() const { return Map; } auto &getMap() { return Map; } llvm::Error storeToDisk(llvm::StringRef Path) const override { SavedData = Map; return llvm::Error::success(); } llvm::Error loadFromDisk(llvm::StringRef Path) override { Map = SavedData; return llvm::Error::success(); } llvm::Error serialize(llvm::raw_ostream &OS) const final { return llvm::Error::success(); } llvm::Error deserialize(const llvm::MemoryBuffer &Buffer) final { return llvm::Error::success(); } /// Must reset the state of the container to the just built state void clear() final {} private: std::map Map; mutable std::map SavedData; private: void mergeBackImpl(MapContainer &&Container) override { Container.Map.merge(std::move(this->Map)); this->Map = std::move(Container.Map); } }; char MapContainer::ID; static const Target ExampleTarget({}, RootKind); struct Fixture { Fixture() { Rank::init(); Kind::init(); } }; BOOST_AUTO_TEST_SUITE(PipelineTestSuite, *boost::unit_test::fixture()) BOOST_AUTO_TEST_CASE(ContainerIsa) { std::map Map; Map[ExampleTarget] = 1; auto Ptr = make_unique(std::move(Map), "dont_care"); ContainerBase *BasePtr = Ptr.get(); BOOST_TEST(llvm::isa(BasePtr)); BOOST_TEST(llvm::cast(BasePtr) != nullptr); BOOST_TEST(Ptr->get(ExampleTarget) == 1); } static ContainerFactory getMapFactoryContainer() { return ContainerFactory::create(); } BOOST_AUTO_TEST_CASE(ContainersCanBeCreated) { ContainerSet Containers; auto Factory = getMapFactoryContainer(); Containers.add(CName, Factory, Factory("RandomName")); BOOST_TEST(Containers.contains(CName)); auto &Container = llvm::cast(Containers[CName]); Container.get(ExampleTarget) = 1; BOOST_TEST(cast(Containers.at(CName)).get(ExampleTarget) == 1); } class TestPipe { public: static constexpr auto Name = "TestPipe"; std::vector getContract() const { return { ContractGroup(RootKind, 0, RootKind2, 0, InputPreservation::Preserve) }; } void run(const Context &, const MapContainer &Source, MapContainer &Target) { Source.enumerate().dump(); auto SrcCopy = Source; for (const auto &Element : SrcCopy.getMap()) if (&Element.first.getKind() == &RootKind) { llvm::outs() << "Invoked\n"; pipeline::Target NewTar(RootKind2); NewTar.dump(); Target.get(NewTar) = Element.second; Target.enumerate().dump(); } Target.enumerate().dump(); } }; BOOST_AUTO_TEST_CASE(PipeCanBeWrapper) { Context Ctx; MapContainer Map("RandomName"); Map.get({ {}, RootKind }) = 1; TestPipe Enf; Enf.run(Ctx, Map, Map); BOOST_TEST(Map.get({ {}, RootKind2 }) == 1); } BOOST_AUTO_TEST_CASE(InputOutputContractExactPassForward) { ContainerToTargetsMap Targets; Targets[CName].emplace_back(Target({}, RootKind)); ContractGroup Contract1(RootKind); Contract1.deduceResults(Context(), Targets, { CName }); BOOST_TEST((&Targets[CName][0].getKind() == &RootKind)); } BOOST_AUTO_TEST_CASE(InputOutputContractExactExactForward) { ContainerToTargetsMap Targets; Targets[CName].emplace_back(Target({}, RootKind)); ContractGroup Contract1(RootKind, 0, RootKind2, 0); Contract1.deduceResults(Context(), Targets, { CName }); BOOST_TEST((&Targets[CName][0].getKind() == &RootKind2)); } BOOST_AUTO_TEST_CASE(InputOutputContractExactKillForward) { ContainerToTargetsMap Targets; Targets[CName].emplace_back(Target({}, RootKind)); ContractGroup Contract1(RootKind, 0, RootKind3, 0, InputPreservation::Erase); Contract1.deduceResults(Context(), Targets, { CName }); BOOST_TEST((Targets[CName].size() == 1)); BOOST_TEST((&Targets[CName][0].getKind() == &RootKind3)); } BOOST_AUTO_TEST_CASE(InputOutputContractMultiLine) { ContainerToTargetsMap Targets; Targets["third"].emplace_back(Target({}, RootKind)); Contract FirstPart(RootKind2, 0, RootKind, 2, InputPreservation::Preserve); Contract SecondPart(RootKind3, 1, RootKind, 2, InputPreservation::Preserve); ContractGroup Contract({ FirstPart, SecondPart }); auto Res = Contract.deduceRequirements(Context(), Targets, { "first", "second", "third" }); BOOST_TEST(Res["first"].size() == 1U); BOOST_TEST(Res["second"].size() == 1U); } BOOST_AUTO_TEST_CASE(InputOutputContractDerivedPassForward) { ContainerToTargetsMap Targets; Targets[CName].emplace_back(Target({}, RootKind2)); ContractGroup Contract1(RootKind, 0, RootKind2, 0); Contract1.deduceResults(Context(), Targets, { CName }); BOOST_TEST((&Targets[CName][0].getKind() == &RootKind2)); } BOOST_AUTO_TEST_CASE(InputOutputContractDerivedExactForward) { ContainerToTargetsMap Targets; Targets[CName].emplace_back(Target({}, RootKind)); ContractGroup Contract1(RootKind, 0, RootKind, 0); Contract1.deduceResults(Context(), Targets, { CName }); BOOST_TEST((&Targets[CName][0].getKind() == &RootKind)); BOOST_TEST((Targets[CName].size() == 1)); } BOOST_AUTO_TEST_CASE(InputOutputContractExactPassBackward) { ContainerToTargetsMap Targets; Targets[CName].emplace_back(Target({}, RootKind)); ContractGroup Contract1(RootKind); auto Res = Contract1.deduceRequirements(Context(), Targets, { CName }); BOOST_TEST((&Res[CName][0].getKind() == &RootKind)); } BOOST_AUTO_TEST_CASE(InputOutputContractExactExactBackward) { ContainerToTargetsMap Targets; Targets[CName].emplace_back(Target({}, RootKind2)); ContractGroup Contract1(RootKind, 0, RootKind2, 0); auto Res = Contract1.deduceRequirements(Context(), Targets, { CName }); BOOST_TEST((&Res[CName][0].getKind() == &RootKind)); } BOOST_AUTO_TEST_CASE(InputOutputContractDerivedPassBackward) { ContainerToTargetsMap Targets; Targets[CName].emplace_back(Target({}, RootKind2)); ContractGroup Contract1(RootKind); auto Res = Contract1.deduceRequirements(Context(), Targets, { CName }); BOOST_TEST((&Res[CName][0].getKind() == &RootKind2)); } BOOST_AUTO_TEST_CASE(InputOutputContractDerivedExactBackward) { ContainerToTargetsMap Targets; Targets[CName].emplace_back(Target({}, RootKind2)); ContractGroup Contract1(RootKind, 0, RootKind2, 0); auto Res = Contract1.deduceRequirements(Context(), Targets, { CName }); BOOST_TEST((&Res[CName][0].getKind() == &RootKind)); } BOOST_AUTO_TEST_CASE(InputOutputContractExactExactFineGrainedBackward) { ContainerToTargetsMap Targets; Targets.add(CName, { "f1" }, FunctionKind); ContractGroup Contract1(RootKind, 0, FunctionKind, 0); auto Res = Contract1.deduceRequirements(Context(), Targets, { CName }); BOOST_TEST((&Res[CName][0].getKind() == &RootKind)); BOOST_TEST((Res[CName][0].getPathComponents().size() == 0)); } BOOST_AUTO_TEST_CASE(InputOutputContractExactExactFineGrainedForward) { ContainerToTargetsMap Targets; Targets[CName].emplace_back(Target({}, RootKind)); ContractGroup Contract1(RootKind, 0, FunctionKind, 0); Contract1.deduceResults(Context(), Targets, { CName }); BOOST_TEST((&Targets[CName][0].getKind() == &FunctionKind)); BOOST_TEST((Targets[CName][0].getPathComponents().size() == 1)); } static void checkIfContains(auto &TargetRange, const Kind &K) { const auto ToFind = [&K](const Target &Target) { return &Target.getKind() == &K; }; BOOST_TEST(llvm::find_if(TargetRange, ToFind) != TargetRange.end()); } BOOST_AUTO_TEST_CASE(InputOutputContractMupltipleInputTest) { ContainerToTargetsMap Targets; Targets[CName].emplace_back(Target(RootKind2)); Targets[CName].emplace_back(Target(RootKind)); ContractGroup Contract1(RootKind, 0, RootKind2, 0); auto Res = Contract1.deduceRequirements(Context(), Targets, { CName }); const auto &ProducedResults = Res[CName]; checkIfContains(ProducedResults, RootKind); } BOOST_AUTO_TEST_CASE(InputOutputContractPreserved) { ContainerToTargetsMap Targets; Targets[CName].emplace_back(Target({}, RootKind2)); Targets[CName].emplace_back(Target({}, RootKind)); ContractGroup Contract1(RootKind, 0, RootKind2, 0, InputPreservation::Preserve); Contract1.deduceResults(Context(), Targets, { CName }); const auto &ProducedResults = Targets[CName]; checkIfContains(ProducedResults, RootKind2); checkIfContains(ProducedResults, RootKind); checkIfContains(ProducedResults, RootKind2); } BOOST_AUTO_TEST_CASE(InputOutputContractPreservedBackwardMain) { ContainerToTargetsMap Targets; Targets[CName].emplace_back(Target({}, RootKind2)); ContractGroup Contract1(RootKind, 0, RootKind2, 0, InputPreservation::Preserve); auto Res = Contract1.deduceRequirements(Context(), Targets, { CName }); BOOST_TEST((&Res[CName][0].getKind() == &RootKind)); } BOOST_AUTO_TEST_CASE(InputOutputContractPreservedBackwardSecondary) { ContainerToTargetsMap Targets; Targets[CName].emplace_back(Target({}, RootKind)); ContractGroup Contract1(RootKind, 0, RootKind2, 0, InputPreservation::Preserve); auto Res = Contract1.deduceRequirements(Context(), Targets, { CName }); BOOST_TEST((&Res[CName][0].getKind() == &RootKind)); } BOOST_AUTO_TEST_CASE(StepCanCloneAndRun) { Context Ctx; Runner Pip(Ctx); ContainerSet Containers; auto Factory = getMapFactoryContainer(); Containers.add(CName, Factory, Factory("dont_care")); Step Step("first_step", "", std::move(Containers), PipeWrapper::bind(CName, CName)); ContainerToTargetsMap Targets; Targets[CName].emplace_back(RootKind2); Containers = ContainerSet(); auto Factory2 = getMapFactoryContainer(); Containers.add(CName, Factory, Factory("dont_care")); cast(Containers[CName]).get(Target({}, RootKind)) = 1; auto Result = Step.run(Ctx, std::move(Containers)); auto &Cont = cast(Result.at(CName)); BOOST_TEST(Cont.get(Target({}, RootKind2)) == 1); } BOOST_AUTO_TEST_CASE(PipelineCanBeManuallyExectued) { ContainerFactorySet Registry; Registry.registerDefaultConstructibleFactory(CName); Context Ctx; Runner Pip(Ctx); Pip.addStep(Step("first_step", "", Registry.createEmpty(), PipeWrapper::bind(CName, CName))); auto Containers = Registry.createEmpty(); auto &C1 = Containers.getOrCreate(CName); C1.get(Target(RootKind)) = 1; auto Res = Pip["first_step"].run(Ctx, std::move(Containers)); BOOST_TEST(cast(Res.at(CName)).get(Target(RootKind2)) == 1); const auto &StartingContainer = Pip["first_step"] .containers() .getOrCreate(CName); auto Val = StartingContainer.get(Target(RootKind2)); BOOST_TEST(Val == 1); } BOOST_AUTO_TEST_CASE(SingleElementPipelineCanBeRunned) { Context Ctx; Runner Pip(Ctx); ContainerSet Content; auto Factory = getMapFactoryContainer(); Content.add(CName, Factory, Factory("dont_care")); auto &C1 = cast(Content[CName]); C1.get(Target(RootKind)) = 1; Step StepToAdd("first_step", "", std::move(Content)); Pip.addStep(std::move(StepToAdd)); ContainerSet &BCI = Pip["first_step"].containers(); BOOST_TEST(cast(BCI.at(CName)).get(Target(RootKind)) == 1); ContainerSet Containers2; Containers2.add(CName, Factory, make_unique("dont_care")); Pip.addStep(Step("End", "", std::move(Containers2), Pip["first_step"], PipeWrapper::bind(CName, CName))); ContainerToTargetsMap Targets; Targets[CName].emplace_back(Target(RootKind2)); auto Error = Pip.run("End", Targets); BOOST_TEST(!Error); ContainerSet &BC = Pip["End"].containers(); BOOST_TEST(cast(BC.at(CName)).get(Target(RootKind2)) == 1); } class FineGranerPipe { public: static constexpr auto Name = "FinedGranedPipe"; std::vector getContract() const { return { ContractGroup(RootKind, 0, FunctionKind, 1, InputPreservation::Preserve) }; } void run(Context &, const MapContainer &Source, MapContainer &Target) { for (const auto &Element : Source.getMap()) { if (&Element.first.getKind() != &RootKind) continue; auto PathComponents = Element.first.getPathComponents(); PathComponents.emplace_back("f1"); Target.get({ std::move(PathComponents), FunctionKind }) = Element.second; PathComponents = Element.first.getPathComponents(); PathComponents.emplace_back("f2"); Target.get({ std::move(PathComponents), FunctionKind }) = Element.second; } } }; class CopyPipe { public: static constexpr auto Name = "CopyPipe"; std::vector getContract() const { return { ContractGroup(FunctionKind, 0, FunctionKind, 1, InputPreservation::Preserve) }; } void run(Context &, const MapContainer &Source, MapContainer &Target) { for (const auto &Element : Source.getMap()) if (&Element.first.getKind() == &FunctionKind) Target.get(Element.first) = Element.second; } }; BOOST_AUTO_TEST_CASE(SingleElementPipelineBackwardFinedGrained) { Context Ctx; Runner Pipeline(Ctx); Pipeline.addDefaultConstructibleFactory(CName); const std::string Name = "first_step"; Pipeline.emplaceStep("", Name, ""); Pipeline.emplaceStep(Name, "End", "", PipeWrapper::bind(CName, CName)); auto &Container(Pipeline[Name].containers().getOrCreate(CName)); Container.get(Target(RootKind)) = 1; ContainerToTargetsMap Targets; Targets.add(CName, { "f1" }, FunctionKind); auto Error = Pipeline.run("End", Targets); BOOST_TEST(!Error); auto &FinalContainer = Pipeline["End"].containers().get(CName); Target FinalTarget({ "f1" }, FunctionKind); auto Val = FinalContainer.get(FinalTarget); BOOST_TEST(Val == 1); } BOOST_AUTO_TEST_CASE(DifferentNamesAreNotCompatible) { Target Target1({ "f1Wrong" }, FunctionKind); Target Target2({ "f1" }, FunctionKind); BOOST_TEST((Target1 != Target2)); BOOST_TEST((Target2 != Target1)); } BOOST_AUTO_TEST_CASE(DifferentNamesRootOnlyAreNotCompatibleSet) { Target Target1({ "f1Wrong" }, FunctionKind); Target Target2({ "f1" }, FunctionKind); TargetsList TargetList; TargetList.push_back(Target1); BOOST_TEST(not TargetList.contains(Target2)); BOOST_TEST(TargetList.contains(Target1)); } BOOST_AUTO_TEST_CASE(SingleElementPipelineFailure) { Context Ctx; Runner Pipeline(Ctx); Pipeline.addDefaultConstructibleFactory(CName); const std::string Name = "first_step"; Pipeline.emplaceStep("", Name, "", PipeWrapper::bind(CName, CName)); Pipeline.emplaceStep(Name, "End", ""); auto &Container(Pipeline[Name].containers().getOrCreate(CName)); Container.get(Target(RootKind)) = 1; ContainerToTargetsMap Targets; Targets.add(CName, {}, RootKind2); auto Error = Pipeline.run("End", Targets); BOOST_TEST(!!Error); consumeError(std::move(Error)); } static void makeF(llvm::Module &M, llvm::StringRef FName) { auto VoidType = llvm::Type::getVoidTy(M.getContext()); auto *FType = llvm::FunctionType::get(VoidType, {}); auto F = M.getOrInsertFunction(FName, FType); auto *Fun = llvm::dyn_cast(F.getCallee()); auto *BB = llvm::BasicBlock::Create(M.getContext(), "bb", Fun); llvm::IRBuilder<> Builder(BB); Builder.SetInsertPoint(BB); Builder.CreateRet(nullptr); } struct FunctionInserterPass : public llvm::ModulePass { static char ID; FunctionInserterPass() : llvm::ModulePass(ID) {} bool runOnModule(llvm::Module &M) override { M.getFunction("root")->eraseFromParent(); makeF(M, "f1"); return true; } }; char FunctionInserterPass::ID = '_'; struct IdentityPass : public llvm::ModulePass { static char ID; IdentityPass() : llvm::ModulePass(ID) {} bool runOnModule(llvm::Module &M) override { return true; } }; char IdentityPass::ID = '_'; static llvm::RegisterPass X2("IdentityPass", "IdentityPass"); struct LLVMPassFunctionCreator { static constexpr auto Name = "Function Creator"; std::vector getContract() const { return { ContractGroup(RootKind, 0, FunctionKind) }; } void registerPasses(llvm::legacy::PassManager &Manager) { Manager.add(new FunctionInserterPass()); } }; struct LLVMPassFunctionIdentity { static constexpr auto Name = "Identity"; std::vector getContract() const { return { ContractGroup(FunctionKind) }; } void registerPasses(llvm::legacy::PassManager &Manager) { Manager.add(new IdentityPass()); } }; BOOST_AUTO_TEST_CASE(SingleElementLLVMPipelineBackwardFinedGrained) { llvm::LLVMContext C; Context Ctx; Runner Pipeline(Ctx); Pipeline.addContainerFactory(CName, ContainerFactory::fromGlobal(&Ctx, &C)); const std::string Name = "first_step"; Pipeline.emplaceStep("", Name, ""); Pipeline .emplaceStep(Name, "End", "", LLVMContainer::wrapLLVMPasses(CName, LLVMPassFunctionCreator(), LLVMPassFunctionIdentity())); auto &C1(Pipeline[Name].containers().getOrCreate(CName)); makeF(C1.getModule(), "root"); ContainerToTargetsMap Targets; Targets.add(CName, Target({ "f1" }, FunctionKind)); auto Error = Pipeline.run("End", Targets); BOOST_TEST(!Error); const auto &Final = Pipeline["End"].containers().get(CName); const auto *F = Final.getModule().getFunction("f1"); BOOST_TEST(F != nullptr); } BOOST_AUTO_TEST_CASE(LLVMPurePipe) { llvm::LLVMContext C; Context Ctx; Runner Pipeline(Ctx); Pipeline.addContainerFactory(CName, ContainerFactory::fromGlobal(&Ctx, &C)); const std::string Name = "first_step"; PureLLVMPassWrapper IdentityPass("IdentityPass"); Pipeline.emplaceStep("", Name, ""); Pipeline.emplaceStep(Name, "End", "", LLVMContainer::wrapLLVMPasses(CName, LLVMPassFunctionCreator(), IdentityPass)); auto &C1 = Pipeline[Name].containers().getOrCreate(CName); makeF(C1.getModule(), "root"); ContainerToTargetsMap Targets; Targets.add(CName, Target({ "f1" }, FunctionKind)); auto Error = Pipeline.run("End", Targets); BOOST_TEST(!Error); const auto &Final = Pipeline["End"].containers().get(CName); const auto *F = Final.getModule().getFunction("f1"); BOOST_TEST(F != nullptr); } BOOST_AUTO_TEST_CASE(SingleElementPipelineForwardFinedGrained) { Context Ctx; Runner Pipeline(Ctx); Pipeline.addDefaultConstructibleFactory(CName); const std::string Name = "first_step"; Pipeline.emplaceStep("", Name, ""); Pipeline.emplaceStep(Name, "End", "", PipeWrapper::bind(CName, CName)); auto &C1 = Pipeline[Name].containers().getOrCreate(CName); C1.get(Target({}, RootKind)) = 1; auto &C2 = Pipeline["End"].containers().getOrCreate(CName); const auto T = Target({ "f1" }, FunctionKind); const auto T2 = Target({ "f2" }, FunctionKind); C2.get(T) = 1; C2.get(T2) = 1; llvm::StringMap Invalidations; Invalidations[Name].add(CName, {}, RootKind); auto Error = Pipeline.getInvalidations(Invalidations); BOOST_TEST(!Error); const auto &EndContainerInvalidations = Invalidations["End"][CName]; BOOST_TEST(not EndContainerInvalidations.empty()); BOOST_TEST((EndContainerInvalidations == TargetsList({ T, T2 }))); } BOOST_AUTO_TEST_CASE(SingleElementPipelineInvalidation) { Context Ctx; Runner Pipeline(Ctx); Pipeline.addDefaultConstructibleFactory(CName); const std::string Name = "first_step"; Pipeline.emplaceStep("", Name, ""); Pipeline.emplaceStep(Name, "End", "", PipeWrapper::bind(CName, CName)); auto &C1 = Pipeline[Name].containers().getOrCreate(CName); C1.get(Target({}, RootKind)) = 1; auto &C2 = Pipeline["End"].containers().getOrCreate(CName); const auto T = Target({ "f1" }, FunctionKind); const auto T2 = Target({ "f2" }, FunctionKind); C2.get(T) = 1; C2.get(T2) = 1; Target ToKill({}, RootKind); llvm::StringMap Invalidations; auto Error = Pipeline.getInvalidations(ToKill, Invalidations); BOOST_TEST(!Error); const auto &QuantifOfInvalidated = Invalidations["End"][CName] .front() .getPathComponents(); BOOST_TEST((QuantifOfInvalidated.front() == "f1")); } BOOST_AUTO_TEST_CASE(SingleElementPipelineWithRemove) { Context Ctx; Runner Pipeline(Ctx); Pipeline.addDefaultConstructibleFactory(CName); const std::string Name = "first_step"; Pipeline.emplaceStep("", Name, "", PipeWrapper::bind(CName, CName)); Pipeline.emplaceStep(Name, "End", ""); auto &C1 = Pipeline[Name].containers().getOrCreate(CName); C1.get(Target(RootKind)) = 1; Target ToKill(RootKind); auto Error = Pipeline.invalidate(ToKill); BOOST_TEST(!Error); auto &C2 = Pipeline[Name].containers().getOrCreate(CName); auto IsIn = C2.contains(ToKill); BOOST_TEST(IsIn == false); } BOOST_AUTO_TEST_CASE(LoaderTest) { StepDeclaration SDeclaration{ "FirstStep", { { "FineGranerPipe", { CName, CName } } } }; BranchDeclaration BDeclaration{ "", { std::move(SDeclaration) } }; PipelineDeclaration PDeclaration{ "revng-test", { { CName, "MapContainer" } }, { std::move(BDeclaration) } }; auto Ctx = Context::fromRegistry(Registry::registerAllKinds()); Loader Loader(Ctx); Loader.addDefaultConstructibleContainer("MapContainer"); Loader.registerPipe("FineGranerPipe"); auto MaybePipeline = Loader.load(PDeclaration); BOOST_TEST(!!MaybePipeline); auto &Pipeline = *MaybePipeline; const std::string Name = "FirstStep"; BOOST_TEST((Pipeline[Name].getName() == Name)); BOOST_TEST((Pipeline["begin"].getName() == "begin")); ContainerToTargetsMap Targets; Targets.add(CName, { "f1" }, FunctionKind); auto &C1 = Pipeline["begin"].containers().getOrCreate(CName); C1.get(Target(RootKind)) = 1; auto Error = Pipeline.run(Name, Targets); BOOST_TEST(!Error); auto &FinalContainer = Pipeline[Name].containers().get(CName); Target FinalTarget({ "f1" }, FunctionKind); auto Val = FinalContainer.get(FinalTarget); BOOST_TEST(Val == 1); } static const std::string Pipeline(R"(--- Component: revng-test Containers: - Name: ContainerName Type: MapContainer Branches: - Steps: - Name: FirstStep Pipes: - Type: FineGranerPipe UsedContainers: - ContainerName - ContainerName )"); BOOST_AUTO_TEST_CASE(LoaderTestFromYaml) { Context Ctx; Loader Loader(Ctx); Loader.addDefaultConstructibleContainer("MapContainer"); Loader.registerPipe("FineGranerPipe"); auto MaybePipeline = Loader.load(Pipeline); BOOST_TEST(!!MaybePipeline); } static const std::string PipelineTree(R"(--- Component: revng-test Containers: - Name: ContainerName Type: MapContainer Branches: - Steps: - Name: FirstStep Pipes: - Type: FineGranerPipe UsedContainers: - ContainerName - ContainerName )"); static const std::string PipelineTree2(R"(--- Component: revng-test Containers: Branches: - From: FirstStep Steps: - Name: SecondStep )"); static const std::string PipelineTree3(R"(--- Component: revng-test Containers: Branches: - From: FirstStep Steps: - Name: ThirdStep )"); BOOST_AUTO_TEST_CASE(LoaderTestFromYamlTree) { Context Ctx; Loader Loader(Ctx); Loader.addDefaultConstructibleContainer("MapContainer"); Loader.registerPipe("FineGranerPipe"); vector ToLoad({ PipelineTree2, PipelineTree3, PipelineTree }); auto MaybePipeline = Loader.load(ToLoad); BOOST_TEST(!!MaybePipeline); } BOOST_AUTO_TEST_CASE(LoaderTestFromYamlLLVM) { llvm::LLVMContext C; Context Ctx; Loader Loader(Ctx); Loader.addContainerFactory("LLVMContainer", ContainerFactory::fromGlobal(&Ctx, &C)); auto *Name = "CreateFunctionPass"; Loader.registerLLVMPass(Name); Name = "IdentityPass"; Loader.registerLLVMPass(Name); std::string LLVMPipeline(R"(--- Component: revng-test Containers: - Name: CustomName Type: LLVMContainer Branches: - Steps: - Name: FirstStep Pipes: - Type: LLVMPipe UsedContainers: - CustomName Passes: - CreateFunctionPass - IdentityPass )"); auto MaybePipeline = Loader.load(LLVMPipeline); BOOST_TEST(!!MaybePipeline); } static std::string getCurrentPath() { llvm::SmallVector ToReturn; llvm::sys::fs::current_path(ToReturn); return std::string(ToReturn.begin(), ToReturn.end()); } BOOST_AUTO_TEST_CASE(SingleElementPipelineStoreToDisk) { Context Ctx; Runner Pipeline(Ctx); Pipeline.addDefaultConstructibleFactory(CName); const std::string Name = "first_step"; Pipeline.emplaceStep("", Name, "", PipeWrapper::bind(CName, CName)); Pipeline.emplaceStep(Name, "End", ""); auto &C1 = Pipeline[Name].containers().getOrCreate(CName); C1.get(Target({}, RootKind)) = 1; BOOST_TEST((!Pipeline.storeToDisk(getCurrentPath()))); auto &Container(Pipeline[Name].containers().getOrCreate(CName)); BOOST_TEST((Container.get(Target({}, RootKind)) == 1)); Container.get(Target({}, RootKind)) = 2; BOOST_TEST((Container.get(Target({}, RootKind)) == 2)); BOOST_TEST((!Pipeline.loadFromDisk(getCurrentPath()))); BOOST_TEST(Pipeline[Name].containers().containsOrCanCreate(CName)); BOOST_TEST(not Pipeline[Name].containers().contains(CName)); } BOOST_AUTO_TEST_CASE(SingleElementPipelineStoreToDiskWithOverrides) { Context Ctx; Loader Loader(Ctx); Loader.addDefaultConstructibleContainer("MapContainer"); Loader.registerPipe("FineGranerPipe"); auto MaybePipeline = Loader.load(Pipeline); BOOST_TEST(!!MaybePipeline); auto &Pipeline = *MaybePipeline; auto *Mapping = "DontCareSourceFile:FirstStep/ContainerName"; auto MaybeMapping = PipelineFileMapping::parse(Mapping); BOOST_TEST(!!MaybeMapping); const std::string Name = "FirstStep"; auto &Container(Pipeline[Name].containers().getOrCreate(CName)); Container.get(Target({}, RootKind)) = 1; BOOST_TEST((!MaybeMapping->storeToDisk(Pipeline))); Container.get(Target({}, RootKind)) = 2; BOOST_TEST((Container.get(Target({}, RootKind)) == 2)); BOOST_TEST((!MaybeMapping->loadFromDisk(Pipeline))); BOOST_TEST((Container.get(Target({}, RootKind)) == 1)); } class EnumerableContainerExample : public EnumerableContainer { public: static char ID; static inline const llvm::StringRef MIMEType = ""; EnumerableContainerExample(Context &Ctx, llvm::StringRef Name) : EnumerableContainer(Ctx, Name) {} unique_ptr cloneFiltered(const TargetsList &Container) const final { return make_unique(*this); } ~EnumerableContainerExample() override = default; llvm::Error storeToDisk(llvm::StringRef Path) const override { return llvm::Error::success(); } llvm::Error loadFromDisk(llvm::StringRef Path) override { return llvm::Error::success(); } llvm::Error serialize(llvm::raw_ostream &OS) const final { return llvm::Error::success(); } llvm::Error deserialize(const llvm::MemoryBuffer &Buffer) final { return llvm::Error::success(); } llvm::Error extractOne(llvm::raw_ostream &OS, const Target &Target) const override { revng_abort(); return llvm::Error::success(); } /// Must reset the state of the container to the just built state void clear() final {} std::set Targets; private: void mergeBackImpl(EnumerableContainerExample &&Container) override {} }; char EnumerableContainerExample::ID; class ExampleContainerInpsector : public ContainerEnumerator { public: ExampleContainerInpsector() : ContainerEnumerator(RootKind) {} TargetsList enumerate(const Context &Ctx, const EnumerableContainerExample &Container) const final { TargetsList ToReturn; llvm::copy(Container.Targets, back_inserter(ToReturn)); return ToReturn; } bool contains(const Target &Target, const EnumerableContainerExample &Container) const { return Container.Targets.contains(Target); } bool remove(const Context &Ctx, const TargetsList &Targets, EnumerableContainerExample &Container) const { bool ErasedAll = true; for (const Target &Target : Targets) ErasedAll = remove(Target, Container) and ErasedAll; return ErasedAll; } bool remove(const Target &Target, EnumerableContainerExample &Container) const { if (not contains(Target, Container)) return false; Container.Targets.erase(Target); return true; } }; static ExampleContainerInpsector Example; BOOST_AUTO_TEST_CASE(EnumerableContainersTest) { Context Ctx; EnumerableContainerExample Example(Ctx, "dont_care"); Target T({}, RootKind); Example.Targets.insert(T); BOOST_TEST(Example.contains(T)); BOOST_TEST(Example.remove(TargetsList({ T }))); BOOST_TEST(not Example.contains(T)); } class LLVMInspectorExample : public LLVMKind { public: using LLVMKind::LLVMKind; std::optional symbolToTarget(const llvm::Function &Symbol) const override { return Target({ Symbol.getName() }, FunctionKind); } void appendAllTargets(const pipeline::Context &Ctx, pipeline::TargetsList &Out) const override { Out.push_back(Target("f1", *this)); Out.push_back(Target("f2", *this)); } }; class LLVMRootInspectorExample : public LLVMKind { public: using LLVMKind::LLVMKind; std::optional symbolToTarget(const llvm::Function &Symbol) const override { return Target({}, RootKind); } void appendAllTargets(const pipeline::Context &Ctx, pipeline::TargetsList &Out) const override { Out.push_back(Target(*this)); } }; static LLVMInspectorExample ExampleLLVMInspector("dc", FunctionRank); static LLVMRootInspectorExample ExampleLLVMRootInspector("dc2", Root); BOOST_AUTO_TEST_CASE(LLVMKindTest) { llvm::LLVMContext C; using Cont = LLVMContainer; Context Ctx; Runner Pipeline(Ctx); Pipeline.addContainerFactory(CName, ContainerFactory::fromGlobal(&Ctx, &C)); Pipeline.emplaceStep("", "first_step", ""); Pipeline.emplaceStep("first_step", "End", "", Cont::wrapLLVMPasses(CName, LLVMPassFunctionCreator())); makeF(Pipeline["first_step"] .containers() .getOrCreate(CName) .getModule(), "root"); ContainerToTargetsMap Targets; Targets.add(CName, Target({ "f1" }, FunctionKind)); auto Error = Pipeline.run("End", Targets); BOOST_TEST(!Error); const auto &Final = Pipeline["End"].containers().get(CName); const auto *F = Final.getModule().getFunction("f1"); BOOST_TEST(F != nullptr); } class InspectorKindExample : public LLVMKind { public: InspectorKindExample() : LLVMKind("ExampleName", FunctionRank) {} std::optional symbolToTarget(const llvm::Function &Symbol) const final { return Target({ Symbol.getName() }, *this); } void appendAllTargets(const pipeline::Context &Ctx, pipeline::TargetsList &Out) const override { Out.push_back(Target("f1", *this)); Out.push_back(Target("f2", *this)); } ~InspectorKindExample() override {} }; static InspectorKindExample InspKindExample; BOOST_AUTO_TEST_CASE(InspectorKindTest) { Context Ctx; llvm::LLVMContext C; using Cont = LLVMContainer; auto Factory = ContainerFactory::fromGlobal(&Ctx, &C); auto Container = Factory("dont_care"); makeF(cast(*Container).getModule(), "root"); Target RootF({ "root" }, InspKindExample); BOOST_TEST(Container->enumerate().contains(RootF)); } BOOST_AUTO_TEST_CASE(MultiStepInvalidationTest) { Context Ctx; Runner Pipeline(Ctx); auto CName2 = CName + "2"; Pipeline.addDefaultConstructibleFactory(CName); Pipeline.addDefaultConstructibleFactory(CName2); const std::string Name = "first_step"; const std::string SecondName = "second_step"; Pipeline.emplaceStep("", Name, ""); Pipeline.emplaceStep(Name, SecondName, "", PipeWrapper::bind(CName, CName)); Pipeline.emplaceStep(SecondName, "End", "", PipeWrapper::bind(CName, CName2)); auto &C1 = Pipeline[Name].containers().getOrCreate(CName); auto &C1End = Pipeline["End"].containers().getOrCreate(CName); auto &C2End = Pipeline["End"].containers().getOrCreate(CName2); const auto T = Target(RootKind); C1.get(T) = 1; const auto ToProduce = Target({ "f1" }, FunctionKind); ContainerToTargetsMap Map; Map[CName2].emplace_back(ToProduce); cantFail(Pipeline.run("End", Map)); BOOST_TEST(C1.get(T) == 1); BOOST_TEST(C1End.get(T) == 1); BOOST_TEST(C2End.get(ToProduce) == 1); pipeline::InvalidationMap Invalidations; Invalidations[Name][CName].push_back(T); // C2End.enumerate().dump(); llvm::outs() << "Invalidations\n"; auto Error = Pipeline.getInvalidations(Invalidations); // Invalidations[Name].dump(); // Invalidations[SecondName].dump(); // Invalidations["End"].dump(); BOOST_TEST(!Error); Error = Pipeline.invalidate(Invalidations); BOOST_TEST(!Error); BOOST_TEST(C1.get(T) == 0); BOOST_TEST(C1End.get(T) == 0); BOOST_TEST(C2End.get(ToProduce) == 0); } class ArgumentTestAnalysis { public: constexpr static const char *Name = "dont_care"; constexpr static std::tuple Options = { pipeline::Option("first", 10), pipeline::Option("second", "something"), pipeline::Option("third", static_cast(32)) }; std::vector> AcceptedKinds = { { &RootKind } }; void run(const Context &Ctx, const MapContainer &Cont, int First, std::string Second, uint64_t Third) { BOOST_TEST(First == 10); BOOST_TEST(Second == "something"); BOOST_TEST(Third == 32); } }; BOOST_AUTO_TEST_CASE(PipeOptions) { pipeline::AnalysisWrapperImpl W(ArgumentTestAnalysis(), { "container_name" }); Context Ctx; ContainerSet Set; auto Factory = ContainerFactory::create(); Set.add("container_name", Factory); Set["container_name"]; if (auto Error = W.run(Ctx, Set, {}); Error) BOOST_FAIL("unreachable"); } BOOST_AUTO_TEST_SUITE_END()