mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
11f1e532c6
Add a `Dirty` boolean to the `ContainerBase` class. This will be set to true when the container mutates itself (e.g. remove, mergeBack, clear). The dirty bit is reset when running `PipelineManager.store`.
516 lines
17 KiB
C++
516 lines
17 KiB
C++
/// \file Runner.cpp
|
|
/// A step is composed of a list of pipes and a set of containers representing
|
|
/// the content of the pipeline before the execution of such pipes.
|
|
|
|
//
|
|
// This file is distributed under the MIT License. See LICENSE.md for details.
|
|
//
|
|
|
|
#include "llvm/Support/Error.h"
|
|
#include "llvm/Support/FileSystem.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Support/Progress.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
#include "revng/Pipeline/ContainerSet.h"
|
|
#include "revng/Pipeline/Context.h"
|
|
#include "revng/Pipeline/Errors.h"
|
|
#include "revng/Pipeline/Step.h"
|
|
#include "revng/Pipeline/Target.h"
|
|
#include "revng/Support/Assert.h"
|
|
#include "revng/Support/Debug.h"
|
|
#include "revng/Support/ZstdStream.h"
|
|
|
|
using namespace llvm;
|
|
using namespace std;
|
|
using namespace pipeline;
|
|
|
|
namespace pipeline {
|
|
|
|
class TargetInPipe {
|
|
public:
|
|
std::string SerializedTarget;
|
|
std::string PipeName;
|
|
|
|
llvm::Expected<llvm::SmallVector<TargetInContainer, 2>>
|
|
deserialize(const Context &Context, llvm::StringRef ContainerName) const;
|
|
|
|
static TargetInPipe fromTargetInContainer(const TargetInContainer &Target,
|
|
llvm::StringRef PipeName);
|
|
bool operator<(const TargetInPipe &Other) const {
|
|
const auto &Tied = std::tie(SerializedTarget, PipeName);
|
|
return Tied < std::tie(Other.SerializedTarget, Other.PipeName);
|
|
}
|
|
};
|
|
|
|
class ContainerInvalidationMetadata {
|
|
public:
|
|
using ValueType = std::pair<pipeline::TargetInPipe, std::vector<std::string>>;
|
|
using Vector = std::vector<ValueType>;
|
|
Vector Data;
|
|
|
|
void merge(ContainerInvalidationMetadata &&Other) {
|
|
for (ValueType &Entry : Other.Data)
|
|
Data.emplace_back(std::move(Entry));
|
|
}
|
|
|
|
public:
|
|
llvm::Expected<PathTargetBimap>
|
|
deserialize(const Context &Context,
|
|
const Global &Primitives,
|
|
llvm::StringRef PipeName,
|
|
llvm::StringRef ContainerName) const;
|
|
|
|
static ContainerInvalidationMetadata serialize(const PathTargetBimap &Map,
|
|
const Global &Primitives,
|
|
llvm::StringRef PipeName,
|
|
llvm::StringRef ContainerName);
|
|
};
|
|
|
|
class NamedPathTargetBimapVector {
|
|
public:
|
|
std::string GlobalName;
|
|
ContainerInvalidationMetadata Map;
|
|
};
|
|
} // namespace pipeline
|
|
|
|
LLVM_YAML_IS_SEQUENCE_VECTOR(ContainerInvalidationMetadata::ValueType);
|
|
|
|
namespace llvm {
|
|
namespace yaml {
|
|
|
|
// YAML traits for TargetInContainer
|
|
template<>
|
|
struct MappingTraits<pipeline::TargetInPipe> {
|
|
static void mapping(IO &IO, pipeline::TargetInPipe &TargetInContainer) {
|
|
IO.mapRequired("Target", TargetInContainer.SerializedTarget);
|
|
IO.mapRequired("PipeName", TargetInContainer.PipeName);
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct MappingTraits<pipeline::ContainerInvalidationMetadata> {
|
|
static void mapping(IO &Io,
|
|
pipeline::ContainerInvalidationMetadata &TargetMap) {
|
|
Io.mapRequired("Map", TargetMap.Data);
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct MappingTraits<ContainerInvalidationMetadata::Vector::value_type> {
|
|
static void
|
|
mapping(IO &Io,
|
|
pipeline::ContainerInvalidationMetadata::Vector::value_type
|
|
&TargetMap) {
|
|
Io.mapRequired("Target", TargetMap.first.SerializedTarget);
|
|
Io.mapRequired("PipeName", TargetMap.first.PipeName);
|
|
Io.mapRequired("ReadPaths", TargetMap.second);
|
|
}
|
|
};
|
|
|
|
} // namespace yaml
|
|
} // namespace llvm
|
|
|
|
LLVM_YAML_IS_SEQUENCE_VECTOR(pipeline::NamedPathTargetBimapVector);
|
|
|
|
namespace llvm {
|
|
namespace yaml {
|
|
template<>
|
|
struct MappingTraits<pipeline::NamedPathTargetBimapVector> {
|
|
static void mapping(IO &Io, pipeline::NamedPathTargetBimapVector &TargetMap) {
|
|
Io.mapRequired("GlobalName", TargetMap.GlobalName);
|
|
Io.mapRequired("Map", TargetMap.Map.Data);
|
|
}
|
|
};
|
|
} // namespace yaml
|
|
} // namespace llvm
|
|
|
|
llvm::Expected<llvm::SmallVector<TargetInContainer, 2>>
|
|
TargetInPipe::deserialize(const Context &Context,
|
|
llvm::StringRef ContainerName) const {
|
|
TargetsList Targets;
|
|
llvm::Error Error = parseTarget(Context,
|
|
SerializedTarget,
|
|
Context.getKindsRegistry(),
|
|
Targets);
|
|
if (Error)
|
|
return std::move(Error);
|
|
|
|
llvm::SmallVector<TargetInContainer, 2> Return;
|
|
for (const Target &Target : Targets) {
|
|
Return.emplace_back(std::move(Target), ContainerName.str());
|
|
}
|
|
return Return;
|
|
}
|
|
|
|
TargetInPipe
|
|
TargetInPipe::fromTargetInContainer(const TargetInContainer &Target,
|
|
llvm::StringRef PipeName) {
|
|
TargetInPipe ToReturn;
|
|
ToReturn.PipeName = PipeName;
|
|
ToReturn.SerializedTarget = Target.getTarget().toString();
|
|
|
|
return ToReturn;
|
|
}
|
|
|
|
ContainerInvalidationMetadata
|
|
ContainerInvalidationMetadata::serialize(const PathTargetBimap &Map,
|
|
const Global &Global,
|
|
llvm::StringRef PipeName,
|
|
llvm::StringRef ContainerName) {
|
|
ContainerInvalidationMetadata ToSerialize;
|
|
std::map<pipeline::TargetInPipe, std::vector<std::string>> TemporaryMap;
|
|
|
|
for (const auto &Content : Map) {
|
|
for (const TargetInContainer &Entry : Content.second) {
|
|
if (Entry.getContainerName() != ContainerName)
|
|
continue;
|
|
|
|
std::optional<std::string> AsString = Global.serializePath(Content.first);
|
|
revng_check(AsString.has_value());
|
|
TemporaryMap[TargetInPipe::fromTargetInContainer(Entry, PipeName)]
|
|
.push_back(*AsString);
|
|
}
|
|
}
|
|
|
|
for (const auto &Content : TemporaryMap) {
|
|
std::pair ToEmplace{ Content.first, Content.second };
|
|
ToSerialize.Data.emplace_back(std::move(ToEmplace));
|
|
}
|
|
|
|
return ToSerialize;
|
|
}
|
|
|
|
llvm::Expected<PathTargetBimap>
|
|
ContainerInvalidationMetadata::deserialize(const Context &Context,
|
|
const Global &Global,
|
|
llvm::StringRef PipeName,
|
|
llvm::StringRef ContainerName)
|
|
const {
|
|
|
|
PathTargetBimap ToReturn;
|
|
for (const ValueType &Entry : Data) {
|
|
if (Entry.first.PipeName != PipeName) {
|
|
continue;
|
|
}
|
|
|
|
llvm::Expected<SmallVector<TargetInContainer>>
|
|
MaybeTarget = Entry.first.deserialize(Context, ContainerName);
|
|
|
|
if (not MaybeTarget) {
|
|
return MaybeTarget.takeError();
|
|
}
|
|
|
|
for (auto &SerializedPath : Entry.second) {
|
|
std::optional<TupleTreePath>
|
|
MaybeParsedPath = Global.deserializePath(SerializedPath);
|
|
|
|
if (not MaybeParsedPath) {
|
|
return revng::createError("could not parse " + SerializedPath);
|
|
}
|
|
|
|
for (const TargetInContainer &Path : *MaybeTarget) {
|
|
ToReturn.insert(std::move(Path), std::move(*MaybeParsedPath));
|
|
}
|
|
}
|
|
}
|
|
|
|
return ToReturn;
|
|
}
|
|
|
|
std::pair<ContainerToTargetsMap, std::vector<PipeExecutionEntry>>
|
|
Step::analyzeGoals(const ContainerToTargetsMap &RequiredGoals) const {
|
|
|
|
ContainerToTargetsMap AlreadyAvailable;
|
|
ContainerToTargetsMap Targets = RequiredGoals;
|
|
removeSatisfiedGoals(Targets, AlreadyAvailable);
|
|
|
|
std::vector<PipeExecutionEntry> PipesExecutionEntries;
|
|
for (const PipeWrapper &Pipe :
|
|
llvm::make_range(Pipes.rbegin(), Pipes.rend())) {
|
|
PipesExecutionEntries.push_back(Pipe.Pipe->getRequirements(*TheContext,
|
|
Targets));
|
|
Targets = PipesExecutionEntries.back().Input;
|
|
}
|
|
std::reverse(PipesExecutionEntries.begin(), PipesExecutionEntries.end());
|
|
|
|
return std::make_pair(std::move(Targets), std::move(PipesExecutionEntries));
|
|
}
|
|
|
|
void Step::explainStartStep(const ContainerToTargetsMap &Targets,
|
|
size_t Indentation) const {
|
|
|
|
indent(ExplanationLogger, Indentation);
|
|
ExplanationLogger << "Now starting step " << getName()
|
|
<< " with the following inputs:\n";
|
|
indent(ExplanationLogger, Indentation + 1);
|
|
ExplanationLogger << getName() << ":\n";
|
|
prettyPrintStatus(Targets, ExplanationLogger, Indentation + 2);
|
|
ExplanationLogger << DoLog;
|
|
}
|
|
|
|
void Step::explainEndStep(const ContainerToTargetsMap &Targets,
|
|
size_t Indentation) const {
|
|
|
|
indent(ExplanationLogger, Indentation);
|
|
ExplanationLogger << "Step " << getName()
|
|
<< " completed\nThe following targets have been produced\n";
|
|
indent(ExplanationLogger, Indentation + 1);
|
|
ExplanationLogger << getName() << ":\n";
|
|
prettyPrintStatus(Targets, ExplanationLogger, Indentation + 2);
|
|
ExplanationLogger << DoLog;
|
|
}
|
|
|
|
void Step::explainExecutedPipe(const InvokableWrapperBase &Wrapper,
|
|
size_t Indentation) const {
|
|
ExplanationLogger << "Running " << Wrapper.getName() << " in step "
|
|
<< getName();
|
|
|
|
auto RunningContainersNames = Wrapper.getRunningContainersNames();
|
|
if (RunningContainersNames.empty()) {
|
|
ExplanationLogger << " with no arguments";
|
|
} else {
|
|
ExplanationLogger << " with the following containers:\n";
|
|
for (const std::string &ContainerName : RunningContainersNames)
|
|
ExplanationLogger << " " << ContainerName << "\n";
|
|
}
|
|
ExplanationLogger << DoLog;
|
|
}
|
|
|
|
ContainerSet Step::run(ContainerSet &&Input,
|
|
const std::vector<PipeExecutionEntry> &ExecutionInfos) {
|
|
ContainerToTargetsMap InputEnumeration = Input.enumerate();
|
|
explainStartStep(InputEnumeration);
|
|
|
|
Task T(Pipes.size() + 1, "Step " + getName());
|
|
for (const auto &[Pipe, Info] : llvm::zip(Pipes, ExecutionInfos)) {
|
|
T.advance(Pipe.Pipe->getName(), false);
|
|
explainExecutedPipe(*Pipe.Pipe);
|
|
|
|
ExecutionContext EC(*TheContext, &Pipe, Info.Output);
|
|
|
|
Pipe.Pipe->deduceResults(*TheContext, EC.getCurrentRequestedTargets());
|
|
|
|
cantFail(Pipe.Pipe->run(EC, Input));
|
|
llvm::cantFail(Input.verify());
|
|
EC.verify();
|
|
}
|
|
|
|
T.advance("Merging back", true);
|
|
explainEndStep(Input.enumerate());
|
|
Containers.mergeBack(std::move(Input));
|
|
InputEnumeration = deduceResults(InputEnumeration);
|
|
ContainerSet Cloned = Containers.cloneFiltered(InputEnumeration);
|
|
return Cloned;
|
|
}
|
|
|
|
void Step::pipeInvalidate(const GlobalTupleTreeDiff &Diff,
|
|
ContainerToTargetsMap &Map) const {
|
|
for (const auto &Pipe : Pipes) {
|
|
Pipe.Pipe->invalidate(Diff, Map, Containers);
|
|
}
|
|
}
|
|
|
|
llvm::Error Step::runAnalysis(llvm::StringRef AnalysisName,
|
|
const ContainerToTargetsMap &Targets,
|
|
const llvm::StringMap<std::string> &ExtraArgs) {
|
|
auto Stream = ExplanationLogger.getAsLLVMStream();
|
|
ContainerToTargetsMap Map = Containers.enumerate();
|
|
|
|
ContainerToTargetsMap CollapsedTargets = Targets;
|
|
|
|
revng_assert(Map.contains(CollapsedTargets),
|
|
"An analysis was requested, but not all targets are available");
|
|
|
|
AnalysisWrapper &TheAnalysis = getAnalysis(AnalysisName);
|
|
|
|
explainExecutedPipe(*TheAnalysis);
|
|
|
|
ContainerSet Cloned = Containers.cloneFiltered(Targets);
|
|
ExecutionContext EC(*TheContext, nullptr);
|
|
return TheAnalysis->run(EC, Cloned, ExtraArgs);
|
|
}
|
|
|
|
void Step::removeSatisfiedGoals(TargetsList &RequiredInputs,
|
|
const ContainerBase &CachedSymbols,
|
|
TargetsList &ToLoad) {
|
|
const TargetsList EnumeratedSymbols = CachedSymbols.enumerate();
|
|
const auto IsCached = [&ToLoad,
|
|
&EnumeratedSymbols](const Target &Target) -> bool {
|
|
bool MustBeLoaded = EnumeratedSymbols.contains(Target);
|
|
if (MustBeLoaded)
|
|
ToLoad.emplace_back(Target);
|
|
return MustBeLoaded;
|
|
};
|
|
|
|
llvm::erase_if(RequiredInputs, IsCached);
|
|
}
|
|
|
|
void Step::removeSatisfiedGoals(ContainerToTargetsMap &Targets,
|
|
ContainerToTargetsMap &ToLoad) const {
|
|
for (auto &RequiredInputsFromContainer : Targets) {
|
|
llvm::StringRef ContainerName = RequiredInputsFromContainer.first();
|
|
TargetsList &RequiredInputs = RequiredInputsFromContainer.second;
|
|
TargetsList &ToLoadFromCurrentContainer = ToLoad[ContainerName];
|
|
if (Containers.contains(ContainerName))
|
|
removeSatisfiedGoals(RequiredInputs,
|
|
Containers.at(ContainerName),
|
|
ToLoadFromCurrentContainer);
|
|
}
|
|
}
|
|
|
|
ContainerToTargetsMap Step::deduceResults(ContainerToTargetsMap Input) const {
|
|
for (const PipeWrapper &Pipe : Pipes)
|
|
Input = Pipe.Pipe->deduceResults(*TheContext, Input);
|
|
return Input;
|
|
}
|
|
|
|
Error Step::invalidate(const ContainerToTargetsMap &ToRemove) {
|
|
for (auto &Pipe : Pipes) {
|
|
Pipe.InvalidationMetadata.remove(ToRemove);
|
|
}
|
|
return containers().remove(ToRemove);
|
|
}
|
|
|
|
Error Step::store(const revng::DirectoryPath &DirPath) const {
|
|
if (auto Error = storeInvalidationMetadata(DirPath))
|
|
return Error;
|
|
return Containers.store(DirPath);
|
|
}
|
|
|
|
Error Step::checkPrecondition() const {
|
|
for (const PipeWrapper &Pipe : Pipes) {
|
|
if (llvm::Error Error = Pipe.Pipe->checkPrecondition(*TheContext)) {
|
|
std::string Message = "The precondition for pipe " + Pipe.Pipe->getName()
|
|
+ " failed:";
|
|
return llvm::make_error<AnnotatedError>(std::move(Error), Message);
|
|
}
|
|
}
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
Error Step::load(const revng::DirectoryPath &DirPath) {
|
|
auto MaybeBool = DirPath.exists();
|
|
if (not MaybeBool)
|
|
return MaybeBool.takeError();
|
|
|
|
if (not MaybeBool.get())
|
|
return llvm::Error::success();
|
|
|
|
if (auto Error = Containers.load(DirPath))
|
|
return Error;
|
|
|
|
return loadInvalidationMetadata(DirPath);
|
|
}
|
|
|
|
llvm::Error
|
|
Step::loadInvalidationMetadataImpl(const revng::DirectoryPath &Path,
|
|
ContainerSet::value_type &Container) {
|
|
auto FilePath = Path.getFile(Container.first().str() + ".cache.zst");
|
|
auto MaybeBool = FilePath.exists();
|
|
if (not MaybeBool)
|
|
return MaybeBool.takeError();
|
|
|
|
if (not MaybeBool.get())
|
|
return llvm::Error::success();
|
|
|
|
auto File = FilePath.getReadableFile();
|
|
if (not File)
|
|
return File.takeError();
|
|
|
|
llvm::MemoryBuffer &Buf = File.get()->buffer();
|
|
llvm::SmallVector<char> DecompressedData = zstdDecompress(Buf.getBuffer());
|
|
using Type = llvm::SmallVector<NamedPathTargetBimapVector, 2>;
|
|
auto Parsed = ::fromString<Type>({ DecompressedData.data(),
|
|
DecompressedData.size() });
|
|
if (not Parsed)
|
|
return Parsed.takeError();
|
|
|
|
for (PipeWrapper &Pipe : Pipes) {
|
|
for (NamedPathTargetBimapVector &Entry : *Parsed) {
|
|
Global *Global = llvm::cantFail(TheContext->getGlobals()
|
|
.get(Entry.GlobalName));
|
|
auto Parsed(Entry.Map.deserialize(*TheContext,
|
|
*Global,
|
|
Pipe.Pipe->getName(),
|
|
Container.first()));
|
|
if (not Parsed)
|
|
return Parsed.takeError();
|
|
Pipe.InvalidationMetadata.getPathCache(Global->getName())
|
|
.merge(std::move(*Parsed));
|
|
}
|
|
}
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
llvm::Error Step::loadInvalidationMetadata(const revng::DirectoryPath &Path) {
|
|
|
|
for (PipeWrapper &Pipe : Pipes) {
|
|
Pipe.InvalidationMetadata = {};
|
|
}
|
|
for (auto &Container : Containers) {
|
|
if (llvm::Error Error = loadInvalidationMetadataImpl(Path, Container))
|
|
return Error;
|
|
}
|
|
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
llvm::Error
|
|
Step::storeInvalidationMetadata(const revng::DirectoryPath &Path) const {
|
|
for (auto &Container : Containers) {
|
|
if (Container.second == nullptr)
|
|
continue;
|
|
|
|
if (not Container.second->isDirty())
|
|
continue;
|
|
|
|
using Type = llvm::SmallVector<NamedPathTargetBimapVector, 2>;
|
|
Type ToStore = {};
|
|
|
|
for (const Global *Global : TheContext->getGlobals()) {
|
|
NamedPathTargetBimapVector Entry;
|
|
Entry.GlobalName = Global->getName();
|
|
|
|
for (const PipeWrapper &Pipe : Pipes) {
|
|
auto &PathCache = Pipe.InvalidationMetadata.getPathCache();
|
|
if (PathCache.count(Entry.GlobalName) == 0)
|
|
continue;
|
|
|
|
using MetadataType = ContainerInvalidationMetadata;
|
|
auto Serialize = MetadataType::serialize;
|
|
MetadataType Serialized = Serialize(Pipe.InvalidationMetadata
|
|
.getPathCache(Entry.GlobalName),
|
|
*Global,
|
|
Pipe.Pipe->getName(),
|
|
Container.first());
|
|
Entry.Map.merge(std::move(Serialized));
|
|
}
|
|
ToStore.emplace_back(std::move(Entry));
|
|
}
|
|
|
|
auto File = Path.getFile(Container.first().str() + ".cache.zst")
|
|
.getWritableFile();
|
|
if (not File)
|
|
return File.takeError();
|
|
|
|
{
|
|
ZstdCompressedOstream OS(File->get()->os(), 5);
|
|
::serialize(OS, ToStore);
|
|
}
|
|
|
|
if (auto Error = File->get()->commit())
|
|
return Error;
|
|
}
|
|
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
std::vector<revng::FilePath>
|
|
Step::getWrittenFiles(const revng::DirectoryPath &DirPath) const {
|
|
std::vector<revng::FilePath> Result = Containers.getWrittenFiles(DirPath);
|
|
for (auto &Container : Containers)
|
|
Result.push_back(DirPath.getFile(Container.first().str() + ".cache.zst"));
|
|
return Result;
|
|
}
|