#pragma once // // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include #include #include #include #include #include #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/iterator_range.h" #include "llvm/Support/Error.h" #include "revng/ADT/STLExtras.h" #include "revng/Pipeline/Container.h" #include "revng/Pipeline/ContainerEnumerator.h" #include "revng/Pipeline/ContainerSet.h" #include "revng/Pipeline/Contract.h" #include "revng/Pipeline/ExecutionContext.h" #include "revng/Pipeline/Global.h" #include "revng/Pipeline/Invokable.h" #include "revng/Pipeline/Target.h" #include "revng/Support/Debug.h" inline Logger<> InvalidationLog("invalidation"); namespace pipeline { template concept HasCheckPrecondition = requires(T &V, const pipeline::Context &Context) { { V.checkPrecondition(Context) }; }; /// Represents the requested (not expected, which means that it contains only /// the targets the user care about, not all those that will be generated as a /// side effect) input and output of a given invocation of a pipe. class PipeExecutionEntry { public: ContainerToTargetsMap Output; ContainerToTargetsMap Input; PipeExecutionEntry(ContainerToTargetsMap Output, ContainerToTargetsMap Input) : Output(std::move(Output)), Input(std::move(Input)) {} }; namespace detail { template concept HasContract = requires(T P) { { llvm::ArrayRef(P.getContract()) }; }; template concept Pipe = Invokable and (IsContainer and ...) and HasContract; template constexpr bool checkPipe(auto (C::*)(First, Rest...)) requires Pipe { return true; } template constexpr size_t countArgs(auto (C::*)(First, Rest...)) { return sizeof...(Rest); } template const char *getNameOfContainerImpl(size_t Index) { if (Index == 0) return std::decay_t::Name; if constexpr (sizeof...(Types) == 0) return ""; else return getNameOfContainerImpl(Index - 1); } template const char *getNameOfContainer(auto (C::*)(Context, Rest...), size_t Index) { return getNameOfContainerImpl(Index); } template class PipeWrapperImpl; // TODO: Rename, there are 3 layers of wrappers around a pipe and they are // getting confusing class PipeWrapperBase : public InvokableWrapperBase { public: template using ImplType = PipeWrapperImpl; public: virtual PipeExecutionEntry getRequirements(const Context &Context, const ContainerToTargetsMap &Target) const = 0; virtual ContainerToTargetsMap deduceResults(const Context &Context, ContainerToTargetsMap &Target, bool ForInvalidation = false) const = 0; virtual bool areRequirementsMet(const Context &Context, const ContainerToTargetsMap &Input) const = 0; virtual std::unique_ptr clone(std::vector NewRunningContainersNames = {}) const = 0; virtual llvm::Error checkPrecondition(const Context &Context) const = 0; virtual size_t getContainerArgumentsCount() const = 0; virtual llvm::StringRef getContainerName(size_t Index) const = 0; virtual ~PipeWrapperBase() = default; }; /// A pipe must be type erased somehow to become compatible with a pipeline, /// a PipeWrapperImpl takes care of this issue, it can be constructed from /// any pipeline type, and it will expose the contract and run method of that /// enforcer. template class PipeWrapperImpl : public PipeWrapperBase { private: static constexpr bool CheckPipe = checkPipe(&PipeType::run); static_assert(CheckPipe); InvokableWrapperImpl Invokable; public: PipeWrapperImpl(PipeType ActualPipe, std::vector RunningContainersNames) : Invokable(std::move(ActualPipe), std::move(RunningContainersNames)) {} PipeWrapperImpl(const PipeWrapperImpl &ActualPipe, std::vector RunningContainersNames) : Invokable(ActualPipe.Invokable, std::move(RunningContainersNames)) {} PipeWrapperImpl(PipeWrapperImpl &&ActualPipe, std::vector RunningContainersNames) : Invokable(std::move(ActualPipe.Invokable), std::move(RunningContainersNames)) {} public: bool areRequirementsMet(const Context &Context, const ContainerToTargetsMap &Input) const override { const auto &Contracts = Invokable.getPipe().getContract(); if (Contracts.size() == 0) return true; ContainerToTargetsMap ToCheck = Input; for (const auto &Contract : Contracts) { if (Contract.forwardMatches(ToCheck, Invokable.getRunningContainersNames())) return true; Contract.deduceResults(Context, ToCheck, Invokable.getRunningContainersNames()); } return false; } PipeExecutionEntry getRequirements(const Context &Context, const ContainerToTargetsMap &Target) const override { revng_log(InvalidationLog, "Computing requirements for " << this->Invokable.getName()); LoggerIndent<> Imdent(InvalidationLog); const auto &Contracts = Invokable.getPipe().getContract(); ContainerToTargetsMap Input = Target; std::set> ThisPipeOutputs; for (const auto &Contract : llvm::reverse(Contracts)) { Input = Contract .deduceRequirements(Context, Input, Invokable.getRunningContainersNames()); for (const auto &[ContainerIndex, Kind] : Contract.getOutputs()) { auto RunningContainersNames = Invokable.getRunningContainersNames(); if (not Invokable.isContainerArgumentConst(ContainerIndex)) { revng_log(InvalidationLog, RunningContainersNames[ContainerIndex] << " " << Kind->name().str() << " allowed"); ThisPipeOutputs.insert({ RunningContainersNames[ContainerIndex], Kind->id() }); } } } if (InvalidationLog.isEnabled()) { InvalidationLog << "Input:\n"; Input.dump(InvalidationLog); InvalidationLog << DoLog; InvalidationLog << "Output (pre-filter):\n"; Target.dump(InvalidationLog); } ContainerToTargetsMap Output = Target; // NOTE: do not iterate over the StringMap, the second entry of the pair is // a copy (somehow) std::vector Keys; for (llvm::StringRef Key : Output.keys()) Keys.push_back(Key.str()); for (const std::string &ContainerName : Keys) { auto &TargetList = Output.at(ContainerName); TargetList.erase_if([&](const class Target &T) -> bool { return not ThisPipeOutputs.contains({ ContainerName, T.getKind().id() }); }); if (TargetList.empty()) Output.erase(ContainerName); } if (InvalidationLog.isEnabled()) { InvalidationLog << "Output:\n"; Output.dump(InvalidationLog); InvalidationLog << DoLog; } return PipeExecutionEntry(Output, Input); } ContainerToTargetsMap deduceResults(const Context &Context, ContainerToTargetsMap &Target, bool ForInvalidation = false) const override { const auto &Contracts = Invokable.getPipe().getContract(); for (const auto &Contract : Contracts) { Contract.deduceResults(Context, Target, Invokable.getRunningContainersNames(), ForInvalidation); } return Target; } std::unique_ptr clone(std::vector NewContainersNames = {}) const override { if (NewContainersNames.empty()) return std::make_unique(*this); return std::make_unique(*this, std::move(NewContainersNames)); } llvm::Error checkPrecondition(const Context &Context) const override { if constexpr (HasCheckPrecondition) { return Invokable.getPipe().checkPrecondition(Context); } else { return llvm::Error::success(); } } size_t getContainerArgumentsCount() const override { return countArgs(&PipeType::run); } llvm::StringRef getContainerName(size_t Index) const override { return getNameOfContainer(&PipeType::run, Index); } public: void dump(std::ostream &OS, size_t Indentation) const override debug_function { Invokable.dump(OS, Indentation); } llvm::Error run(ExecutionContext &Context, ContainerSet &Containers, const llvm::StringMap &ExtraArgs) override { return Invokable.run(Context, Containers, ExtraArgs); } void invalidate(const GlobalTupleTreeDiff &Diff, ContainerToTargetsMap &Map, const ContainerSet &Containers) const override { return Invokable.invalidate(Diff, Map, Containers); } std::vector getOptionsNames() const override { return Invokable.getOptionsNames(); } std::vector getOptionsTypes() const override { return Invokable.getOptionsTypes(); } std::vector getRunningContainersNames() const override { return Invokable.getRunningContainersNames(); } bool isContainerArgumentConst(size_t ArgumentIndex) const override { return Invokable.isContainerArgumentConst(ArgumentIndex); } std::string getName() const override { return Invokable.getName(); } }; } // namespace detail class InvalidationMetadata { private: llvm::StringMap PathCache; public: void registerTargetsDependingOn(const Context &Context, llvm::StringRef GlobalName, const TupleTreePath &Path, ContainerToTargetsMap &Out, Logger<> &Log) const; void remove(const ContainerToTargetsMap &Map); const llvm::StringMap &getPathCache() const { return PathCache; } llvm::StringMap &getPathCache() { return PathCache; } const PathTargetBimap &getPathCache(llvm::StringRef GlobalName) const { revng_assert(PathCache.find(GlobalName) != PathCache.end()); return PathCache.find(GlobalName)->second; } PathTargetBimap &getPathCache(llvm::StringRef GlobalName) { return PathCache[GlobalName]; } void dump(const pipeline::Context &Context, unsigned Indentation = 0) const; }; // Due to invokable wrapper not being controllable by this file we need to have // a extra wrapper that carries along the invalidation metadata too. struct PipeWrapper { public: using WrapperType = InvokableWrapper; WrapperType Pipe; InvalidationMetadata InvalidationMetadata; public: template static PipeWrapper make(PipeType Pipe, std::vector RunningContainersNames) { return WrapperType::make(Pipe, std::move(RunningContainersNames)); } template static PipeWrapper make(std::vector RunningContainersNames) { return WrapperType::make(std::move(RunningContainersNames)); } PipeWrapper(const InvokableWrapper &Other) : Pipe(Other) {} PipeWrapper(const PipeWrapper &Other, std::vector RunningContainersNames) : Pipe(Other.Pipe, RunningContainersNames) {} template static PipeWrapper bind(ContainerNames &&...Names) { return WrapperType::bind(Names...); } template static PipeWrapper bind(PipeType &&E, ContainerNames &&...Names) { return WrapperType::bind(E, Names...); } }; } // namespace pipeline