/// \file Runner.cpp /// A runner top object of a pipeline structure, it is able to run the pipeline. // // This file is distributed under the MIT License. See LICENSE.md for details. // #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" #include "llvm/Support/Path.h" #include "llvm/Support/Progress.h" #include "revng/Pipeline/Context.h" #include "revng/Pipeline/Errors.h" #include "revng/Pipeline/GlobalTupleTreeDiff.h" #include "revng/Pipeline/Kind.h" #include "revng/Pipeline/Runner.h" #include "revng/Pipeline/Target.h" #include "revng/Support/Assert.h" using namespace std; using namespace llvm; using namespace pipeline; class PipelineExecutionEntry { public: Step *ToExecute; ContainerToTargetsMap Output; ContainerToTargetsMap Input; PipelineExecutionEntry(Step &ToExecute, ContainerToTargetsMap Output, ContainerToTargetsMap Input) : ToExecute(&ToExecute), Output(std::move(Output)), Input(std::move(Input)) {} }; static Error getObjectives(Runner &Runner, llvm::StringRef EndingStepName, const ContainerToTargetsMap &Targets, std::vector &ToExec) { ContainerToTargetsMap ToLoad = Targets; Step *CurrentStep = &(Runner[EndingStepName]); while (CurrentStep != nullptr and not ToLoad.empty()) { ContainerToTargetsMap Output = ToLoad; ToLoad = CurrentStep->analyzeGoals(ToLoad); ToExec.emplace_back(*CurrentStep, Output, ToLoad); CurrentStep = CurrentStep->hasPredecessor() ? &CurrentStep->getPredecessor() : nullptr; } if (not ToLoad.empty()) return make_error(Targets, ToLoad); reverse(ToExec.begin(), ToExec.end()); return Error::success(); } static void explainPipeline(const ContainerToTargetsMap &Targets, ArrayRef Requirements) { if (Requirements.empty()) return; ExplanationLogger << "OBJECTIVES requested\n"; indent(ExplanationLogger, 1); if (Requirements.size() <= 1) { ExplanationLogger << "Already satisfied\n"; return; } ExplanationLogger << Requirements.back().ToExecute->getName() << ":\n"; prettyPrintStatus(Targets, ExplanationLogger, 2); ExplanationLogger << DoLog; ExplanationLogger << "DEDUCED steps content to be produced: \n"; indent(ExplanationLogger, 1); ExplanationLogger << Requirements.back().ToExecute->getName() << ":\n"; prettyPrintStatus(Targets, ExplanationLogger, 2); for (size_t I = Requirements.size(); I != 0; I--) { StringRef StepName = Requirements[I - 1].ToExecute->getName(); const ContainerToTargetsMap &TargetsNeeded = Requirements[I - 1].Input; indent(ExplanationLogger, 1); ExplanationLogger << StepName << ":\n"; prettyPrintStatus(TargetsNeeded, ExplanationLogger, 2); } ExplanationLogger << DoLog; } Error Runner::getInvalidations(TargetInStepSet &Invalidated) const { for (const Step &NextS : *this) { if (not NextS.hasPredecessor()) continue; const Step &S = NextS.getPredecessor(); ContainerToTargetsMap &Inputs = Invalidated[S.getName()]; ContainerToTargetsMap &Outputs = Invalidated[NextS.getName()]; ContainerToTargetsMap Deduced = NextS.deduceResults(Inputs); NextS.containers().intersect(Deduced); Outputs.merge(Deduced); } return Error::success(); } Step &Runner::addStep(Step &&NewStep) { std::string Name = NewStep.getName().str(); auto Info = Steps.try_emplace(Name, std::move(NewStep)); revng_assert(Info.second, ("Duplicate_step: " + Name).c_str()); ReversePostOrderIndexes.emplace_back(&Info.first->second); return *ReversePostOrderIndexes.back(); } llvm::Error Runner::getInvalidations(const Target &Target, TargetInStepSet &Invalidations) const { for (const Step &Step : *this) for (const auto &Container : Step.containers()) { if (Container.second == nullptr) continue; if (Container.second->enumerate().contains(Target)) { Invalidations[Step.getName()].add(Container.first(), Target); } } if (llvm::Error Error = getInvalidations(Invalidations); !!Error) return Error; return llvm::Error::success(); } Error Runner::invalidate(const Target &Target) { llvm::StringMap Invalidations; if (llvm::Error Error = getInvalidations(Target, Invalidations); !!Error) return Error; return invalidate(Invalidations); } llvm::Expected PipelineFileMapping::parse(StringRef ToParse) { if (ToParse.count(':') < 1 or ToParse.count('/') < 1) { auto *Message = "could not parse %s\n" "Format is: file_path:step/container"; return createStringError(inconvertibleErrorCode(), Message, ToParse.str().c_str()); } auto [StoragePath, ContainerPath] = ToParse.rsplit(':'); auto Path = revng::FilePath::fromLocalStorage(StoragePath); auto [StepName, ContainerName] = ContainerPath.split('/'); return PipelineFileMapping(StepName, ContainerName, std::move(Path)); } Error PipelineFileMapping::load(Runner &LoadInto) const { if (not LoadInto.containsStep(Step)) return llvm::createStringError(llvm::inconvertibleErrorCode(), "No known step " + Step); if (not LoadInto[Step].containers().containsOrCanCreate(Container)) return llvm::createStringError(llvm::inconvertibleErrorCode(), "No known container " + Container); return LoadInto[Step].containers()[Container].load(Path); } Error PipelineFileMapping::store(Runner &LoadInto) const { if (not LoadInto.containsStep(Step)) { return llvm::createStringError(llvm::inconvertibleErrorCode(), "No known step " + Step); } if (not LoadInto[Step].containers().containsOrCanCreate(Container)) { return llvm::createStringError(llvm::inconvertibleErrorCode(), "No known container " + Container); } return LoadInto[Step].containers()[Container].store(Path); } Error Runner::store(const revng::DirectoryPath &DirPath) const { if (auto Error = DirPath.create(); Error) return Error; for (const auto &StepName : Steps.keys()) { if (llvm::Error Error = storeStepToDisk(StepName, DirPath); !!Error) { return Error; } } revng::DirectoryPath ContextDir = DirPath.getDirectory("context"); if (auto Error = ContextDir.create(); Error) return Error; return TheContext->store(ContextDir); } Error Runner::storeStepToDisk(llvm::StringRef StepName, const revng::DirectoryPath &DirPath) const { auto Step = Steps.find(StepName); if (Step == Steps.end()) return createStringError(inconvertibleErrorCode(), "Could not find a step named %s\n", StepName.str().c_str()); revng::DirectoryPath StepDir = DirPath.getDirectory(Step->first()); if (auto Error = StepDir.create(); Error) return Error; if (auto Error = Step->second.store(StepDir); !!Error) return Error; return Error::success(); } Error Runner::load(const revng::DirectoryPath &DirPath) { revng::DirectoryPath ContextDir = DirPath.getDirectory("context"); if (auto Error = TheContext->load(ContextDir); !!Error) return Error; for (auto &Step : Steps) { revng::DirectoryPath StepDir = DirPath.getDirectory(Step.first()); if (auto Error = Step.second.load(StepDir); !!Error) return Error; } return Error::success(); } std::vector Runner::getWrittenFiles(const revng::DirectoryPath &DirPath) const { std::vector Result; for (const llvm::StringMapEntry &StepEntry : Steps) { // Exclude the "begin" step from written files (since it's written // manually by the user) if (StepEntry.first() == begin()->getName()) continue; revng::DirectoryPath StepDir = DirPath.getDirectory(StepEntry.first()); const pipeline::Step &Step = StepEntry.second; append(Step.getWrittenFiles(StepDir), Result); } return Result; } llvm::Expected Runner::runAnalysis(llvm::StringRef AnalysisName, llvm::StringRef StepName, const ContainerToTargetsMap &Targets, TargetInStepSet &InvalidationsMap, const llvm::StringMap &Options) { GlobalsMap Before = getContext().getGlobals(); auto MaybeStep = Steps.find(StepName); if (MaybeStep == Steps.end()) { return createStringError(inconvertibleErrorCode(), "Could not find a step named %s\n", StepName.str().c_str()); } Task T(3, "Analysis execution"); T.advance("Produce step " + StepName, true); if (llvm::Error Error = run(StepName, Targets)) return std::move(Error); T.advance("Run analysis", true); if (llvm::Error Error = MaybeStep->second.runAnalysis(AnalysisName, Targets, Options); Error) { return std::move(Error); } T.advance("Apply diff produced by the analysis", true); const GlobalsMap &After = getContext().getGlobals(); DiffMap Map = Before.diff(After); for (const auto &GlobalNameDiffPair : Map) if (llvm::Error Error = apply(GlobalNameDiffPair.second, InvalidationsMap)) return std::move(Error); return std::move(Map); } /// Run all analysis in reverse post order (that is: parents first), llvm::Expected Runner::runAnalyses(const AnalysesList &List, TargetInStepSet &InvalidationsMap, const llvm::StringMap &Options) { GlobalsMap Before = getContext().getGlobals(); Task T(List.size() + 1, "Analysis list " + List.getName()); for (const AnalysisReference &Ref : List) { T.advance(Ref.getAnalysisName(), true); const Step &Step = getStep(Ref.getStepName()); const AnalysisWrapper &Analysis = Step.getAnalysis(Ref.getAnalysisName()); ContainerToTargetsMap Map; const std::vector &Containers = Analysis->getRunningContainersNames(); for (size_t I = 0; I < Containers.size(); I++) { for (const Kind *K : Analysis->getAcceptedKinds(I)) { Map.add(Containers[I], TargetsList::allTargets(getContext(), *K)); } } TargetInStepSet NewInvalidationsMap; auto Result = runAnalysis(Ref.getAnalysisName(), Step.getName(), Map, NewInvalidationsMap, Options); if (not Result) return Result.takeError(); for (auto &NewEntry : NewInvalidationsMap) InvalidationsMap[NewEntry.first()].merge(NewEntry.second); } T.advance("Computing analysis list diff", true); const GlobalsMap &After = getContext().getGlobals(); return Before.diff(After); } Error Runner::run(const State &ToProduce) { Task T(ToProduce.size(), "Multi-step pipeline run"); for (const auto &Request : ToProduce) { T.advance(Request.first(), true); if (llvm::Error Error = run(Request.first(), Request.second)) return Error; } return llvm::Error::success(); } Error Runner::run(llvm::StringRef EndingStepName, const ContainerToTargetsMap &Targets) { vector ToExec; if (llvm::Error Error = getObjectives(*this, EndingStepName, Targets, ToExec); Error) return Error; explainPipeline(Targets, ToExec); if (ToExec.size() <= 1) return Error::success(); for (PipelineExecutionEntry &StepGoalsPairs : llvm::drop_begin(ToExec)) { Step &Step = *StepGoalsPairs.ToExecute; if (llvm::Error Error = Step.checkPrecondition(); Error) return llvm::make_error(std::move(Error), "While scheduling step " + Step.getName() + ":"); } Task T(ToExec.size() - 1, "Produce steps required up to " + EndingStepName); for (PipelineExecutionEntry &StepGoalsPairs : llvm::drop_begin(ToExec)) { auto &[Step, PredictedOutput, Input] = StepGoalsPairs; T.advance(Step->getName(), true); Task T2(3, "Run step"); T2.advance("Clone and filter input containers", true); ::Step &Parent = Step->getPredecessor(); ContainerSet CurrentContainer = Parent.containers().cloneFiltered(Input); // Run the step T2.advance("Run the step", true); Step->run(std::move(CurrentContainer)); T2.advance("Extract the requested targets", true); if (VerifyLog.isEnabled()) { ContainerSet Produced = Step->containers().cloneFiltered(PredictedOutput); revng_check(Produced.enumerate().contains(PredictedOutput), "Not all the expected targets have been produced"); revng_check(Step->containers().enumerate().contains(PredictedOutput)); } } if (ExplanationLogger.isEnabled()) { ExplanationLogger << "PRODUCED\n"; indent(ExplanationLogger, 1); ExplanationLogger << EndingStepName << ":\n"; ToExec.back().ToExecute->containers().enumerate().dump(ExplanationLogger, 2, false); ExplanationLogger << DoLog; } return Error::success(); } Error Runner::invalidate(const TargetInStepSet &Invalidations) { for (const auto &Step : Invalidations) { llvm::StringRef StepName = Step.first(); const ContainerToTargetsMap &ToRemove = Step.second; if (llvm::Error Error = operator[](StepName).invalidate(ToRemove); Error) return Error; } return Error::success(); } void Runner::getCurrentState(State &Out) const { for (const auto &Step : Steps) { const auto &Under = Step.second; Out.insert({ Under.getName(), Under.containers().enumerate() }); } } void Runner::deduceAllPossibleTargets(State &Out) const { getCurrentState(Out); for (const auto &NextStep : *this) { if (not NextStep.hasPredecessor()) continue; const Step &Step = NextStep.getPredecessor(); auto Result = NextStep.deduceResults(Out[Step.getName()]); Out[NextStep.getName()].merge(std::move(Result)); } } const KindsRegistry &Runner::getKindsRegistry() const { return TheContext->getKindsRegistry(); } void Runner::getDiffInvalidations(const GlobalTupleTreeDiff &Diff, TargetInStepSet &Map) const { for (const Step &Step : llvm::drop_begin(*this)) { auto &StepInvalidations = Map[Step.getName()]; for (const auto &Container : Step.containers()) { if (not Container.second) continue; const TargetsList &ExistingTargets = Container.second->enumerate(); // for all targets that are not cached anywhere, mark them to be deleated // every time, since we must be conservative for (const Target &Target : ExistingTargets) { TargetInContainer ToFind(Target, Container.first().str()); if (Step.invalidationMetadataContains(Diff.getGlobalName(), ToFind)) continue; StepInvalidations[Container.first()].push_back(Target); } } for (const TupleTreePath *Path : Diff.getPaths()) { Step.registerTargetsDependingOn(Diff.getGlobalName(), *Path, Map); } } } llvm::Error Runner::apply(const GlobalTupleTreeDiff &Diff, TargetInStepSet &Map) { getDiffInvalidations(Diff, Map); if (auto Error = getInvalidations(Map); Error) return Error; return invalidate(Map); }