diff options
Diffstat (limited to 'backend/include/rcc/IR')
| -rw-r--r-- | backend/include/rcc/IR/Constants.h | 177 | ||||
| -rw-r--r-- | backend/include/rcc/IR/DataLayout.h | 82 | ||||
| -rw-r--r-- | backend/include/rcc/IR/PassInstrumentation.h | 121 | ||||
| -rw-r--r-- | backend/include/rcc/IR/PassManager.h | 532 |
4 files changed, 912 insertions, 0 deletions
diff --git a/backend/include/rcc/IR/Constants.h b/backend/include/rcc/IR/Constants.h new file mode 100644 index 0000000..9f894f2 --- /dev/null +++ b/backend/include/rcc/IR/Constants.h @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_IR_CONSTANTS_H +#define RCC_IR_CONSTANTS_H + +#include "rcc/ADT/APFloat.h" +#include "rcc/ADT/APInt.h" +#include "rcc/Support/Error.h" + +#include <cstddef> +#include <cstdint> +#include <optional> +#include <string> +#include <vector> + +namespace rcc { + +enum class GenericConstantTypeKind : std::uint8_t { + Invalid, + Void, + Label, + Token, + Metadata, + Integer, + Byte, + Floating, + Pointer, + Array, + Vector, + Struct, + Function, + Named, +}; + +struct GenericConstantType final { + GenericConstantTypeKind Kind = GenericConstantTypeKind::Invalid; + std::uint32_t BitWidth = 0; + FloatSemantics Semantics = FloatSemantics::IEEEHalf; + std::uint32_t AddressSpace = 0; + std::uint64_t ElementCount = 0; + bool Scalable = false; + bool Packed = false; + bool VarArg = false; + bool NumericName = false; + std::string Name; + std::vector<GenericConstantType> Elements; + + [[nodiscard]] static Expected<GenericConstantType> integer(std::uint32_t BitWidth); + [[nodiscard]] static GenericConstantType primitive(GenericConstantTypeKind Kind) noexcept; + [[nodiscard]] static GenericConstantType byte() noexcept; + [[nodiscard]] static Expected<GenericConstantType> floating(FloatSemantics Semantics); + [[nodiscard]] static GenericConstantType pointer(std::uint32_t AddressSpace = 0) noexcept; + [[nodiscard]] static Expected<GenericConstantType> array(std::uint64_t ElementCount, + GenericConstantType Element); + [[nodiscard]] static Expected<GenericConstantType> + vector(std::uint64_t ElementCount, GenericConstantType Element, bool Scalable = false); + [[nodiscard]] static Expected<GenericConstantType> + structure(std::vector<GenericConstantType> Elements, bool Packed = false); + [[nodiscard]] static Expected<GenericConstantType> + function(GenericConstantType Return, std::vector<GenericConstantType> Parameters, + bool VarArg = false); + [[nodiscard]] static Expected<GenericConstantType> named(std::string Name, + bool NumericName = false); + + [[nodiscard]] const GenericConstantType &resolved() const noexcept; + [[nodiscard]] Expected<void> verify() const; + + friend bool operator==(const GenericConstantType &Left, + const GenericConstantType &Right) = default; +}; + +enum class GenericConstantKind : std::uint8_t { + Invalid, + Integer, + Floating, + NullPointer, + ZeroInitializer, + Undef, + Poison, + Aggregate, + ByteString, + GlobalAddress, + BlockAddress, + DSOLocalEquivalent, + NoCFI, + PointerAuth, + Expression, +}; + +enum class GenericConstantExpressionOpcode : std::uint8_t { + Invalid, + Trunc, + BitCast, + AddressSpaceCast, + IntToPtr, + PtrToAddr, + PtrToInt, + Add, + Sub, + Xor, + GetElementPtr, + ShuffleVector, + InsertElement, + ExtractElement, + Splat, +}; + +enum class GenericConstantExpressionFlag : std::uint8_t { + NoUnsignedWrap, + NoSignedWrap, + InBounds, + NoUnsignedSignedWrap, + GEPNoUnsignedWrap, + Count, +}; + +class GenericConstantExpressionFlags final { +public: + constexpr GenericConstantExpressionFlags() noexcept = default; + + [[nodiscard]] constexpr bool has(GenericConstantExpressionFlag Flag) const noexcept { + const auto Index = static_cast<std::uint8_t>(Flag); + if (Index >= static_cast<std::uint8_t>(GenericConstantExpressionFlag::Count)) + return false; + return (Mask & (std::uint8_t{1} << Index)) != 0; + } + + constexpr void set(GenericConstantExpressionFlag Flag) noexcept { + const auto Index = static_cast<std::uint8_t>(Flag); + if (Index < static_cast<std::uint8_t>(GenericConstantExpressionFlag::Count)) + Mask |= std::uint8_t{1} << Index; + } + + [[nodiscard]] constexpr std::uint8_t rawValue() const noexcept { + return Mask; + } + + friend constexpr bool operator==(GenericConstantExpressionFlags Left, + GenericConstantExpressionFlags Right) noexcept = default; + +private: + std::uint8_t Mask = 0; +}; + +struct GenericConstantData final { + GenericConstantType Type; + GenericConstantKind Kind = GenericConstantKind::Invalid; + std::optional<APInt> IntegerValue; + std::optional<APFloat> FloatingValue; + std::vector<std::byte> ByteValue; + std::vector<GenericConstantData> Elements; + std::string Symbol; + std::string Block; + bool NumericSymbol = false; + bool NumericBlock = false; + GenericConstantExpressionOpcode ExpressionOpcode = GenericConstantExpressionOpcode::Invalid; + GenericConstantExpressionFlags ExpressionFlags; + std::optional<GenericConstantType> SourceElementType; + std::optional<APInt> InRangeStart; + std::optional<APInt> InRangeEnd; + + [[nodiscard]] static Expected<GenericConstantData> integer(APInt Value); + [[nodiscard]] static Expected<GenericConstantData> floating(APFloat Value); + [[nodiscard]] static Expected<GenericConstantData> byteString(GenericConstantType Type, + std::vector<std::byte> Bytes); + + [[nodiscard]] Expected<std::string> toLLVMString() const; + [[nodiscard]] Expected<void> verify() const; + [[nodiscard]] bool referencesSymbol() const noexcept; + + friend bool operator==(const GenericConstantData &Left, + const GenericConstantData &Right) = default; +}; + +} // namespace rcc + +#endif // RCC_IR_CONSTANTS_H diff --git a/backend/include/rcc/IR/DataLayout.h b/backend/include/rcc/IR/DataLayout.h new file mode 100644 index 0000000..0ca3f07 --- /dev/null +++ b/backend/include/rcc/IR/DataLayout.h @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_IR_DATALAYOUT_H +#define RCC_IR_DATALAYOUT_H + +#include "rcc/IR/Constants.h" +#include "rcc/Support/Error.h" + +#include <cstddef> +#include <cstdint> +#include <string> +#include <string_view> +#include <vector> + +namespace rcc { + +class DataLayout final { +public: + [[nodiscard]] static Expected<DataLayout> parse(std::string_view LayoutString); + + [[nodiscard]] Expected<std::uint64_t> getTypeAllocSize(const GenericConstantType &Type) const; + [[nodiscard]] Expected<std::uint64_t> getABITypeAlign(const GenericConstantType &Type) const; + [[nodiscard]] Expected<std::uint64_t> getPrefTypeAlign(const GenericConstantType &Type) const; + [[nodiscard]] Expected<std::uint32_t> getPointerSizeInBits(std::uint32_t AddressSpace = 0) const; + +private: + struct AlignmentSpec final { + std::uint64_t ABI = 1; + std::uint64_t Preferred = 1; + }; + + struct PrimitiveSpec final { + char Kind = '\0'; + std::uint32_t BitWidth = 0; + AlignmentSpec Alignment; + }; + + struct PointerSpec final { + std::uint32_t AddressSpace = 0; + std::uint32_t SizeInBits = 0; + AlignmentSpec Alignment; + std::string AddressSpaceName; + }; + + struct TypeLayout final { + std::uint64_t StoreSize = 0; + std::uint64_t AllocationSize = 0; + std::uint64_t ABIAlignment = 1; + std::uint64_t PreferredAlignment = 1; + }; + + [[nodiscard]] static Expected<std::uint64_t> parseUnsignedField(std::string_view Text, + std::string_view Description); + [[nodiscard]] static Expected<std::uint64_t> alignmentBytes(std::string_view Text, bool AllowZero, + std::string_view Description); + [[nodiscard]] static std::vector<std::string_view> split(std::string_view Text, char Separator); + + void setPrimitiveSpec(PrimitiveSpec Entry); + void setPointerSpec(PointerSpec Entry); + [[nodiscard]] AlignmentSpec naturalAlignment(std::uint64_t StoreSize) const noexcept; + [[nodiscard]] AlignmentSpec scalarAlignment(char Kind, std::uint32_t BitWidth, + std::uint64_t StoreSize) const noexcept; + [[nodiscard]] AlignmentSpec integerAlignment(std::uint32_t BitWidth, + std::uint64_t StoreSize) const noexcept; + [[nodiscard]] AlignmentSpec floatingAlignment(std::uint32_t BitWidth, + std::uint64_t StoreSize) const noexcept; + [[nodiscard]] AlignmentSpec vectorAlignment(std::uint32_t BitWidth, + std::uint64_t StoreSize) const noexcept; + [[nodiscard]] Expected<PointerSpec> getPointerSpec(std::uint32_t AddressSpace) const; + [[nodiscard]] Expected<TypeLayout> getTypeLayout(const GenericConstantType &Type, + std::vector<std::string> &NamedTypeStack, + std::size_t Depth) const; + + bool Explicit = false; + std::vector<PrimitiveSpec> PrimitiveSpecs; + std::vector<PointerSpec> PointerSpecs; + AlignmentSpec AggregateAlignment{1, 8}; +}; + +} // namespace rcc + +#endif // RCC_IR_DATALAYOUT_H diff --git a/backend/include/rcc/IR/PassInstrumentation.h b/backend/include/rcc/IR/PassInstrumentation.h new file mode 100644 index 0000000..b0a673a --- /dev/null +++ b/backend/include/rcc/IR/PassInstrumentation.h @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_IR_PASSINSTRUMENTATION_H +#define RCC_IR_PASSINSTRUMENTATION_H + +#include "rcc/Support/Error.h" + +#include <cstdint> +#include <functional> +#include <mutex> +#include <string_view> +#include <utility> +#include <vector> + +namespace rcc { + +enum class InstrumentationPhase : std::uint8_t { + Before, + After, +}; + +template <typename IRUnitT, typename PassResultT> class PassInstrumentationCallbacks final { +public: + using BeforeCallback = std::function<void(std::string_view, const IRUnitT &)>; + using AfterCallback = std::function<void(std::string_view, const IRUnitT &, const PassResultT &)>; + using FailureCallback = std::function<void(std::string_view, const IRUnitT &, const Error &)>; + using BeforeAnalysisCallback = std::function<void(std::string_view, const IRUnitT &)>; + using AfterAnalysisCallback = + std::function<void(std::string_view, const IRUnitT &, bool, std::uint64_t)>; + using AnalysisFailureCallback = + std::function<void(std::string_view, const IRUnitT &, const Error &)>; + + void addBeforeCallback(BeforeCallback Callback) { + addCallback(Before, std::move(Callback)); + } + + void addAfterCallback(AfterCallback Callback) { + addCallback(After, std::move(Callback)); + } + + void addFailureCallback(FailureCallback Callback) { + addCallback(Failure, std::move(Callback)); + } + + void addBeforeAnalysisCallback(BeforeAnalysisCallback Callback) { + addCallback(BeforeAnalysis, std::move(Callback)); + } + + void addAfterAnalysisCallback(AfterAnalysisCallback Callback) { + addCallback(AfterAnalysis, std::move(Callback)); + } + + void addAnalysisFailureCallback(AnalysisFailureCallback Callback) { + addCallback(AnalysisFailure, std::move(Callback)); + } + + void runBefore(std::string_view Name, const IRUnitT &Unit) const { + for (const BeforeCallback &Callback : snapshot(Before)) + Callback(Name, Unit); + } + + void runAfter(std::string_view Name, const IRUnitT &Unit, const PassResultT &Result) const { + for (const AfterCallback &Callback : snapshot(After)) + Callback(Name, Unit, Result); + } + + void runFailure(std::string_view Name, const IRUnitT &Unit, const Error &FailureValue) const { + for (const FailureCallback &Callback : snapshot(Failure)) + Callback(Name, Unit, FailureValue); + } + + void runBeforeAnalysis(std::string_view Name, const IRUnitT &Unit) const { + for (const BeforeAnalysisCallback &Callback : snapshot(BeforeAnalysis)) + Callback(Name, Unit); + } + + void runAfterAnalysis(std::string_view Name, const IRUnitT &Unit, bool CacheHit, + std::uint64_t Nanoseconds) const { + for (const AfterAnalysisCallback &Callback : snapshot(AfterAnalysis)) + Callback(Name, Unit, CacheHit, Nanoseconds); + } + + void runAnalysisFailure(std::string_view Name, const IRUnitT &Unit, + const Error &FailureValue) const { + for (const AnalysisFailureCallback &Callback : snapshot(AnalysisFailure)) + Callback(Name, Unit, FailureValue); + } + + [[nodiscard]] bool empty() const { + std::scoped_lock Lock(Mutex); + return Before.empty() && After.empty() && Failure.empty() && BeforeAnalysis.empty() && + AfterAnalysis.empty() && AnalysisFailure.empty(); + } + +private: + template <typename CallbackT> + void addCallback(std::vector<CallbackT> &Callbacks, CallbackT Callback) { + if (!Callback) + return; + std::scoped_lock Lock(Mutex); + Callbacks.push_back(std::move(Callback)); + } + + template <typename CallbackT> + [[nodiscard]] std::vector<CallbackT> snapshot(const std::vector<CallbackT> &Callbacks) const { + std::scoped_lock Lock(Mutex); + return Callbacks; + } + + mutable std::mutex Mutex; + std::vector<BeforeCallback> Before; + std::vector<AfterCallback> After; + std::vector<FailureCallback> Failure; + std::vector<BeforeAnalysisCallback> BeforeAnalysis; + std::vector<AfterAnalysisCallback> AfterAnalysis; + std::vector<AnalysisFailureCallback> AnalysisFailure; +}; + +} // namespace rcc + +#endif // RCC_IR_PASSINSTRUMENTATION_H diff --git a/backend/include/rcc/IR/PassManager.h b/backend/include/rcc/IR/PassManager.h new file mode 100644 index 0000000..568cf97 --- /dev/null +++ b/backend/include/rcc/IR/PassManager.h @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_IR_PASSMANAGER_H +#define RCC_IR_PASSMANAGER_H + +#include "rcc/Support/Error.h" + +#include <algorithm> +#include <chrono> +#include <concepts> +#include <cstddef> +#include <cstdint> +#include <functional> +#include <memory> +#include <optional> +#include <ranges> +#include <set> +#include <span> +#include <string> +#include <string_view> +#include <unordered_map> +#include <unordered_set> +#include <utility> +#include <vector> + +namespace rcc { + +class TargetDescription; + +using AnalysisID = const void *; + +class PreservedAnalyses final { +public: + [[nodiscard]] static PreservedAnalyses all() noexcept { + PreservedAnalyses Result; + Result.PreserveAll = true; + return Result; + } + + [[nodiscard]] static PreservedAnalyses none() noexcept { + return {}; + } + + void preserve(AnalysisID ID) { + if (ID != nullptr) + Preserved.insert(ID); + } + + template <typename AnalysisT> void preserve() { + preserve(analysisID<AnalysisT>()); + } + + [[nodiscard]] bool preserves(AnalysisID ID) const noexcept { + return PreserveAll || (ID != nullptr && Preserved.contains(ID)); + } + + [[nodiscard]] bool preservesAll() const noexcept { + return PreserveAll; + } + + template <typename AnalysisT> [[nodiscard]] static AnalysisID analysisID() noexcept { + static const std::uint8_t Token = 0; + return &Token; + } + +private: + bool PreserveAll = false; + std::unordered_set<AnalysisID> Preserved; +}; + +enum class PassThreadSafety : std::uint8_t { + NotThreadSafe, + ThreadSafe, +}; + +struct PassParameter final { + std::string Name; + std::string Description; + std::optional<std::string> DefaultValue; + bool Required = false; +}; + +template <typename IRUnitT, typename InstrumentationT> struct AnalysisManagerInstrumentation { + static void beforeAnalysis(InstrumentationT *, std::string_view, const IRUnitT &) {} + static void afterAnalysis(InstrumentationT *, std::string_view, const IRUnitT &, bool, + std::uint64_t) {} + static void analysisFailure(InstrumentationT *, std::string_view, const IRUnitT &, const Error &, + std::uint64_t) {} + + template <typename InstructionT> + [[nodiscard]] static Expected<bool> requestAction(InstrumentationT *, std::string_view, + std::string_view, const IRUnitT &, InstructionT, + std::uint64_t) { + return true; + } +}; + +template <typename ChangeT> struct AnalysisRegistration final { + AnalysisID ID = nullptr; + std::string Name; + std::vector<AnalysisID> Dependencies; + ChangeT InvalidatedBy = ChangeT::all(); + PassThreadSafety ThreadSafety = PassThreadSafety::NotThreadSafe; +}; + +template <typename IRUnitT, typename PassT, typename OptionsT> struct PassManagerCallbacks; + +template <typename IRUnitT, typename PassT, typename OptionsT> class PassManager { +public: + using PassList = std::vector<std::unique_ptr<PassT>>; + using Callbacks = PassManagerCallbacks<IRUnitT, PassT, OptionsT>; + + explicit PassManager(OptionsT Options = {}) : Options(Options) {} + PassManager(const PassManager &) = delete; + PassManager &operator=(const PassManager &) = delete; + PassManager(PassManager &&) = default; + PassManager &operator=(PassManager &&) = default; + ~PassManager() = default; + + [[nodiscard]] Expected<void> addPass(std::unique_ptr<PassT> Pass) { + if (!Pass) + return makeUnexpected(ErrorCode::InvalidArgument, "pass manager cannot add a null pass"); + auto Validation = Callbacks::validate(*Pass); + if (!Validation) + return std::unexpected(Validation.error()); + Passes.push_back(std::move(Pass)); + return {}; + } + + template <typename... Args> + [[nodiscard]] Expected<void> run(IRUnitT &Unit, Args &&...Arguments) const { + return Callbacks::run(Passes, Options, Unit, std::forward<Args>(Arguments)...); + } + + template <typename... Args> + [[nodiscard]] Expected<void> runFunctions(Args &&...Arguments) const + requires requires(const PassList &InputPasses, const OptionsT &InputOptions, + Args &&...InputArguments) { + Callbacks::runFunctions(InputPasses, InputOptions, std::forward<Args>(InputArguments)...); + } + { + return Callbacks::runFunctions(Passes, Options, std::forward<Args>(Arguments)...); + } + + [[nodiscard]] std::size_t size() const noexcept { + return Passes.size(); + } + + [[nodiscard]] PassList takePasses() && noexcept { + return std::move(Passes); + } + +private: + PassList Passes; + OptionsT Options; +}; + +template <typename IRUnitT, typename RegistrationT, typename ChangeT, typename InstrumentationT> +class AnalysisManager { +public: + using Self = AnalysisManager<IRUnitT, RegistrationT, ChangeT, InstrumentationT>; + + AnalysisManager() = default; + AnalysisManager(const AnalysisManager &) = delete; + AnalysisManager &operator=(const AnalysisManager &) = delete; + AnalysisManager(AnalysisManager &&) = delete; + AnalysisManager &operator=(AnalysisManager &&) = delete; + ~AnalysisManager() = default; + + template <typename AnalysisT> [[nodiscard]] static AnalysisID id() noexcept { + return PreservedAnalyses::analysisID<AnalysisT>(); + } + + template <typename AnalysisT> + [[nodiscard]] Expected<void> registerAnalysis(RegistrationT Registration = {}) { + using ResultT = typename AnalysisT::Result; + if (Registration.ID == nullptr) + Registration.ID = id<AnalysisT>(); + if (Registration.Name.empty()) { + if constexpr (requires { + { AnalysisT::name() } -> std::convertible_to<std::string_view>; + }) + Registration.Name = std::string(AnalysisT::name()); + else + Registration.Name = "unnamed-analysis"; + } + + ErasedRunner Runner = [](IRUnitT &Unit, const TargetDescription &Target, + Self &Manager) -> Expected<std::shared_ptr<void>> { + auto Computed = AnalysisT::run(Unit, Target, Manager); + if (!Computed) + return std::unexpected(Computed.error()); + return std::shared_ptr<void>(new ResultT(std::move(*Computed)), + [](void *Value) { delete static_cast<ResultT *>(Value); }); + }; + ErasedInvalidator Invalidator = [](void *Value, const IRUnitT &Unit, + const PreservedAnalyses &Preserved) -> bool { + if constexpr (requires(ResultT &Result, const IRUnitT &InputUnit, + const PreservedAnalyses &Analyses) { + { Result.invalidate(InputUnit, Analyses) } -> std::convertible_to<bool>; + }) + return static_cast<ResultT *>(Value)->invalidate(Unit, Preserved); + return true; + }; + return registerAnalysisImpl(std::move(Registration), std::move(Runner), std::move(Invalidator)); + } + + template <typename AnalysisT> + [[nodiscard]] Expected<typename AnalysisT::Result *> getResult(IRUnitT &Unit, + const TargetDescription &Target) { + const AnalysisID Key = id<AnalysisT>(); + if (!isRegistered(Key)) { + auto Registered = registerAnalysis<AnalysisT>(); + if (!Registered) + return std::unexpected(Registered.error()); + } + auto Result = getResultImpl(Key, Unit, Target); + if (!Result) + return std::unexpected(Result.error()); + return static_cast<typename AnalysisT::Result *>(*Result); + } + + [[nodiscard]] Expected<void *> getResultByID(AnalysisID ID, IRUnitT &Unit, + const TargetDescription &Target) { + return getResultImpl(ID, Unit, Target); + } + + void invalidate(IRUnitT &Unit, const PreservedAnalyses &Preserved, ChangeT Changes) noexcept { + const auto UnitPosition = Cache.find(&Unit); + if (UnitPosition == Cache.end() || Preserved.preservesAll() || Changes.empty()) + return; + + std::set<AnalysisID> Invalid; + for (auto &[ID, Entry] : UnitPosition->second) { + if (Preserved.preserves(ID)) + continue; + const auto Registration = Registry.find(ID); + if (Registration == Registry.end() || + Registration->second.Info.InvalidatedBy.intersects(Changes)) { + const bool ShouldInvalidate = + Registration == Registry.end() || !Entry.Value || !Registration->second.Invalidator || + Registration->second.Invalidator(Entry.Value.get(), Unit, Preserved); + if (ShouldInvalidate) + Invalid.insert(ID); + } + } + + bool Added = true; + while (Added) { + Added = false; + for (const auto &[ID, Entry] : UnitPosition->second) { + (void)Entry; + if (Invalid.contains(ID) || Preserved.preserves(ID)) + continue; + const auto Registration = Registry.find(ID); + if (Registration != Registry.end() && + std::ranges::any_of( + Registration->second.Info.Dependencies, + [&Invalid](AnalysisID Dependency) { return Invalid.contains(Dependency); })) + Added = Invalid.insert(ID).second || Added; + } + } + + for (const AnalysisID ID : Invalid) + UnitPosition->second.erase(ID); + if (UnitPosition->second.empty()) + Cache.erase(UnitPosition); + } + + void invalidate(IRUnitT &Unit, const PreservedAnalyses &Preserved) noexcept { + invalidate(Unit, Preserved, ChangeT::all()); + } + + [[nodiscard]] bool hasCached(const IRUnitT &Unit, AnalysisID ID) const noexcept { + const auto UnitPosition = Cache.find(const_cast<IRUnitT *>(&Unit)); + if (UnitPosition == Cache.end()) + return false; + const auto AnalysisPosition = UnitPosition->second.find(ID); + return AnalysisPosition != UnitPosition->second.end() && AnalysisPosition->second.Value; + } + + [[nodiscard]] std::size_t cachedResultCount(const IRUnitT &Unit) const noexcept { + const auto Position = Cache.find(const_cast<IRUnitT *>(&Unit)); + return Position == Cache.end() ? 0 : Position->second.size(); + } + + [[nodiscard]] bool isRegistered(AnalysisID ID) const noexcept { + return ID != nullptr && Registry.contains(ID); + } + + void clear(IRUnitT &Unit) noexcept { + Cache.erase(&Unit); + ActiveComputations.erase(&Unit); + } + + void clearAll() noexcept { + Cache.clear(); + ActiveComputations.clear(); + } + + [[nodiscard]] Expected<std::unique_ptr<Self>> forkEmpty() const { + if (!ActiveComputations.empty()) + return makeUnexpected(ErrorCode::InvalidArgument, + "cannot fork an analysis manager during analysis computation"); + auto Result = std::make_unique<Self>(); + Result->Registry = Registry; + return Result; + } + + [[nodiscard]] Expected<void> replaceRegistrations(Self &&Source) { + if (!Cache.empty() || !ActiveComputations.empty() || !Source.Cache.empty() || + !Source.ActiveComputations.empty()) + return makeUnexpected(ErrorCode::InvalidArgument, + "analysis registrations can only be replaced on empty managers"); + Registry = std::move(Source.Registry); + return {}; + } + + [[nodiscard]] Expected<void> + validateParallelRequirements(std::span<const AnalysisID> RequiredAnalyses) const { + std::vector<AnalysisID> Worklist(RequiredAnalyses.begin(), RequiredAnalyses.end()); + std::unordered_set<AnalysisID> Visited; + for (std::size_t Index = 0; Index < Worklist.size(); ++Index) { + const AnalysisID ID = Worklist[Index]; + if (ID == nullptr || !Visited.insert(ID).second) + continue; + const auto Position = Registry.find(ID); + if (Position == Registry.end()) + return makeUnexpected(ErrorCode::InvalidArgument, + "parallel pass requires an unregistered analysis"); + if (Position->second.Info.ThreadSafety != PassThreadSafety::ThreadSafe) + return makeUnexpected(ErrorCode::InvalidArgument, "parallel pipeline requires analysis '" + + Position->second.Info.Name + + "' to be marked thread-safe"); + Worklist.insert(Worklist.end(), Position->second.Info.Dependencies.begin(), + Position->second.Info.Dependencies.end()); + } + return {}; + } + + void setInstrumentation(InstrumentationT *NewInstrumentation) noexcept { + Instrumentation = NewInstrumentation; + } + + void setActivePass(std::string Name) { + ActivePass = std::move(Name); + } + + template <typename InstructionT> + [[nodiscard]] Expected<bool> requestAction(std::string_view Rule, const IRUnitT &Unit, + InstructionT Instruction, std::uint64_t Cost = 1) { + if (Rule.empty() || Cost == 0) + return makeUnexpected(ErrorCode::InvalidArgument, + "pass action requires a name and nonzero cost"); + if (Instrumentation == nullptr) + return true; + return AnalysisManagerInstrumentation<IRUnitT, InstrumentationT>::requestAction( + Instrumentation, ActivePass.empty() ? "unscoped" : ActivePass, Rule, Unit, Instruction, + Cost); + } + + [[nodiscard]] Expected<bool> requestAction(std::string_view Rule, const IRUnitT &Unit, + std::uint64_t Cost = 1) { + if (Rule.empty() || Cost == 0) + return makeUnexpected(ErrorCode::InvalidArgument, + "pass action requires a name and nonzero cost"); + if (Instrumentation == nullptr) + return true; + return AnalysisManagerInstrumentation<IRUnitT, InstrumentationT>::requestAction( + Instrumentation, ActivePass.empty() ? "unscoped" : ActivePass, Rule, Unit, nullptr, Cost); + } + + [[nodiscard]] const std::string &activePass() const noexcept { + return ActivePass; + } + +private: + using ErasedRunner = + std::function<Expected<std::shared_ptr<void>>(IRUnitT &, const TargetDescription &, Self &)>; + using ErasedInvalidator = std::function<bool(void *, const IRUnitT &, const PreservedAnalyses &)>; + + struct RegisteredAnalysis final { + RegistrationT Info; + ErasedRunner Runner; + ErasedInvalidator Invalidator; + }; + + struct CacheEntry final { + std::shared_ptr<void> Value; + AnalysisID ID = nullptr; + }; + + [[nodiscard]] static bool hasDuplicateAnalysisIDs(std::span<const AnalysisID> IDs) { + std::unordered_set<AnalysisID> Unique; + for (const AnalysisID ID : IDs) { + if (ID == nullptr || !Unique.insert(ID).second) + return true; + } + return false; + } + + [[nodiscard]] Expected<void> registerAnalysisImpl(RegistrationT Registration, ErasedRunner Runner, + ErasedInvalidator Invalidator) { + if (Registration.ID == nullptr || Registration.Name.empty() || !Runner || !Invalidator) + return makeUnexpected(ErrorCode::InvalidArgument, "analysis registration is incomplete"); + if (hasDuplicateAnalysisIDs(Registration.Dependencies)) + return makeUnexpected(ErrorCode::InvalidArgument, + "analysis dependencies must be non-null and unique"); + if (std::ranges::find(Registration.Dependencies, Registration.ID) != + Registration.Dependencies.end()) + return makeUnexpected(ErrorCode::InvalidArgument, + "analysis cannot directly depend on itself"); + if (Registry.contains(Registration.ID)) + return makeUnexpected(ErrorCode::InvalidArgument, "analysis ID is already registered"); + if (std::ranges::any_of(Registry, [&Registration](const auto &Entry) { + return Entry.second.Info.Name == Registration.Name; + })) + return makeUnexpected(ErrorCode::InvalidArgument, "analysis name is already registered"); + const AnalysisID ID = Registration.ID; + Registry.emplace( + ID, RegisteredAnalysis{std::move(Registration), std::move(Runner), std::move(Invalidator)}); + return {}; + } + + [[nodiscard]] Expected<void *> getResultImpl(AnalysisID ID, IRUnitT &Unit, + const TargetDescription &Target) { + if (ID == nullptr) + return makeUnexpected(ErrorCode::InvalidArgument, "analysis ID cannot be null"); + auto &UnitCache = Cache[&Unit]; + if (const auto Cached = UnitCache.find(ID); Cached != UnitCache.end() && Cached->second.Value) { + const auto Registration = Registry.find(ID); + const std::string_view Name = Registration == Registry.end() + ? std::string_view{"unknown-analysis"} + : std::string_view{Registration->second.Info.Name}; + if (Instrumentation != nullptr) + AnalysisManagerInstrumentation<IRUnitT, InstrumentationT>::afterAnalysis( + Instrumentation, Name, Unit, true, 0); + return Cached->second.Value.get(); + } + + const auto RegistrationPosition = Registry.find(ID); + if (RegistrationPosition == Registry.end()) + return makeUnexpected(ErrorCode::InvalidArgument, "analysis ID is not registered"); + const RegisteredAnalysis &Registration = RegistrationPosition->second; + auto &Stack = ActiveComputations[&Unit]; + if (std::ranges::find(Stack, ID) != Stack.end()) { + Error Failure(ErrorCode::InvalidArgument, + "analysis dependency cycle reaches '" + Registration.Info.Name + "'"); + if (Instrumentation != nullptr) + AnalysisManagerInstrumentation<IRUnitT, InstrumentationT>::analysisFailure( + Instrumentation, Registration.Info.Name, Unit, Failure, 0); + return std::unexpected(std::move(Failure)); + } + + if (Instrumentation != nullptr) + AnalysisManagerInstrumentation<IRUnitT, InstrumentationT>::beforeAnalysis( + Instrumentation, Registration.Info.Name, Unit); + const auto Start = std::chrono::steady_clock::now(); + Stack.push_back(ID); + for (const AnalysisID Dependency : Registration.Info.Dependencies) { + if (!Registry.contains(Dependency)) { + Stack.pop_back(); + if (Stack.empty()) + ActiveComputations.erase(&Unit); + return makeUnexpected(ErrorCode::InvalidArgument, + "analysis depends on an unregistered analysis"); + } + auto DependencyResult = getResultImpl(Dependency, Unit, Target); + if (!DependencyResult) { + Stack.pop_back(); + if (Stack.empty()) + ActiveComputations.erase(&Unit); + Error Failure = DependencyResult.error(); + std::string Message = "analysis '" + Registration.Info.Name + "' failed: "; + Message.append(Failure.message()); + Failure = Error(Failure.code(), std::move(Message)); + if (Instrumentation != nullptr) + AnalysisManagerInstrumentation<IRUnitT, InstrumentationT>::analysisFailure( + Instrumentation, Registration.Info.Name, Unit, Failure, elapsedNanoseconds(Start)); + return std::unexpected(std::move(Failure)); + } + } + + auto Computed = Registration.Runner(Unit, Target, *this); + Stack.pop_back(); + if (Stack.empty()) + ActiveComputations.erase(&Unit); + const std::uint64_t Duration = elapsedNanoseconds(Start); + if (!Computed) { + Error Failure = Computed.error(); + std::string Message = "analysis '" + Registration.Info.Name + "' failed: "; + Message.append(Failure.message()); + Failure = Error(Failure.code(), std::move(Message)); + if (Instrumentation != nullptr) + AnalysisManagerInstrumentation<IRUnitT, InstrumentationT>::analysisFailure( + Instrumentation, Registration.Info.Name, Unit, Failure, Duration); + return std::unexpected(std::move(Failure)); + } + if (!*Computed) { + Error Failure(ErrorCode::InternalError, + "analysis '" + Registration.Info.Name + "' returned a null result"); + if (Instrumentation != nullptr) + AnalysisManagerInstrumentation<IRUnitT, InstrumentationT>::analysisFailure( + Instrumentation, Registration.Info.Name, Unit, Failure, Duration); + return std::unexpected(std::move(Failure)); + } + + void *Result = Computed->get(); + UnitCache.emplace(ID, CacheEntry{std::move(*Computed), ID}); + if (Instrumentation != nullptr) + AnalysisManagerInstrumentation<IRUnitT, InstrumentationT>::afterAnalysis( + Instrumentation, Registration.Info.Name, Unit, false, Duration); + return Result; + } + + [[nodiscard]] static std::uint64_t + elapsedNanoseconds(std::chrono::steady_clock::time_point Start) noexcept { + const auto Elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>( + std::chrono::steady_clock::now() - Start); + return Elapsed.count() <= 0 ? 0 : static_cast<std::uint64_t>(Elapsed.count()); + } + + std::unordered_map<AnalysisID, RegisteredAnalysis> Registry; + std::unordered_map<IRUnitT *, std::unordered_map<AnalysisID, CacheEntry>> Cache; + std::unordered_map<IRUnitT *, std::vector<AnalysisID>> ActiveComputations; + InstrumentationT *Instrumentation = nullptr; + std::string ActivePass; +}; + +} // namespace rcc + +#endif // RCC_IR_PASSMANAGER_H |
