// SPDX-License-Identifier: GPL-3.0-or-later #include "rcc/CodeGen/MachinePass.h" #include "rcc/CodeGen/CompilationUnit.h" #include "rcc/CodeGen/MachineBasicBlock.h" #include "rcc/CodeGen/MachineFunction.h" #include "rcc/CodeGen/MachineFunctionCloner.h" #include "rcc/CodeGen/MachineInstr.h" #include "rcc/CodeGen/MachineOperand.h" #include "rcc/CodeGen/MachineVerifier.h" #include "rcc/CodeGen/TargetDescription.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace rcc { thread_local MachinePassInstrumentation::BufferedEvents *MachinePassInstrumentation::ActiveBuffer = nullptr; namespace { using Clock = std::chrono::steady_clock; [[nodiscard]] std::uint64_t elapsedNanoseconds(Clock::time_point Start) noexcept { const auto Elapsed = std::chrono::duration_cast(Clock::now() - Start); if (Elapsed.count() <= 0) return 0; return static_cast(Elapsed.count()); } [[nodiscard]] Error prefixedError(const Error &Source, std::string Prefix) { Prefix.append(Source.message()); return Error(Source.code(), std::move(Prefix)); } [[nodiscard]] bool hasDuplicateAnalysisIDs(std::span IDs) { std::unordered_set Unique; for (const AnalysisID ID : IDs) { if (ID == nullptr || !Unique.insert(ID).second) return true; } return false; } [[nodiscard]] Expected validateParameters(std::span Parameters) { std::unordered_set Names; for (const PassParameter &Parameter : Parameters) { if (Parameter.Name.empty()) return makeUnexpected(ErrorCode::InvalidArgument, "pass parameter name cannot be empty"); if (!Names.insert(Parameter.Name).second) return makeUnexpected(ErrorCode::InvalidArgument, "pass parameter names must be unique"); if (Parameter.Required && Parameter.DefaultValue) { return makeUnexpected(ErrorCode::InvalidArgument, "a required pass parameter cannot have a default value"); } } return {}; } void combineFingerprint(std::uint64_t &State, std::uint64_t Value) noexcept { State ^= Value + 0x9e3779b97f4a7c15ULL + (State << 6U) + (State >> 2U); } [[nodiscard]] std::uint64_t machineFunctionFingerprint(const MachineFunction &Function) noexcept { std::uint64_t Result = 0xcbf29ce484222325ULL; combineFingerprint(Result, Function.stableID().value()); combineFingerprint(Result, Function.properties().rawValue()); combineFingerprint(Result, Function.blockCount()); combineFingerprint(Result, Function.allInstructions().size()); combineFingerprint(Result, Function.allMemOperands().size()); for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; Block = Block->next()) { combineFingerprint(Result, Block->stableID().value()); combineFingerprint(Result, Block->instructionCount()); combineFingerprint(Result, Block->successors().size()); for (const MachineSuccessor &Successor : Block->successors()) { combineFingerprint(Result, Successor.Block == nullptr ? 0 : Successor.Block->stableID().value()); combineFingerprint(Result, Successor.Probability.rawValue()); } for (const MachineBasicBlockLiveIn &LiveIn : Block->liveIns()) { combineFingerprint(Result, LiveIn.PhysReg.number()); combineFingerprint(Result, LiveIn.Lanes.rawValue()); } for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; Instruction = Instruction->next()) { combineFingerprint(Result, Instruction->stableID().value()); combineFingerprint(Result, Instruction->opcode().value()); combineFingerprint(Result, Instruction->flags().rawValue()); combineFingerprint(Result, Instruction->operandCount()); combineFingerprint(Result, Instruction->memOperands().size()); for (const MachineOperand &Operand : Instruction->operands()) { combineFingerprint(Result, static_cast(Operand.kind())); combineFingerprint(Result, Operand.targetFlags()); combineFingerprint(Result, Operand.registerFlags().rawValue()); combineFingerprint(Result, Operand.subReg().value()); if (const Register *Reg = Operand.getIf()) combineFingerprint(Result, Reg->opaqueValue()); else if (const std::int64_t *Immediate = Operand.getIf()) combineFingerprint(Result, static_cast(*Immediate)); } } } return Result; } [[nodiscard]] Expected verifyCompilationUnit(const CompilationUnit &Unit, const TargetDescription &Target) { auto UnitVerification = Unit.verify(); if (!UnitVerification) return std::unexpected(UnitVerification.error()); for (std::size_t Index = 0; Index < Unit.functionCount(); ++Index) { const MachineFunction *Function = Unit.functionAt(Index); if (Function == nullptr) return makeUnexpected(ErrorCode::InternalError, "compilation unit function order contains a null entry"); auto FunctionVerification = MachineVerifier::verify(*Function, Target); if (!FunctionVerification) return std::unexpected(FunctionVerification.error()); } return {}; } } // namespace Expected MachineAnalysisManager::registerAnalysisImpl(MachineAnalysisRegistration Registration, ErasedRunner Runner, ErasedInvalidator Invalidator) { if (Registration.ID == nullptr || Registration.Name.empty() || !Runner || !Invalidator) { return makeUnexpected(ErrorCode::InvalidArgument, "machine analysis registration is incomplete"); } if (hasDuplicateAnalysisIDs(Registration.Dependencies)) { return makeUnexpected(ErrorCode::InvalidArgument, "machine analysis dependencies must be non-null and unique"); } if (std::ranges::find(Registration.Dependencies, Registration.ID) != Registration.Dependencies.end()) { return makeUnexpected(ErrorCode::InvalidArgument, "machine analysis cannot directly depend on itself"); } if (Registry.contains(Registration.ID)) return makeUnexpected(ErrorCode::InvalidArgument, "machine 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, "machine analysis name is already registered"); } Registry.emplace(Registration.ID, RegisteredAnalysis{std::move(Registration), std::move(Runner), std::move(Invalidator)}); return {}; } bool MachineAnalysisManager::isRegistered(AnalysisID ID) const noexcept { return ID != nullptr && Registry.contains(ID); } bool MachineAnalysisManager::hasCached(const MachineFunction &Function, AnalysisID ID) const noexcept { const auto FunctionPosition = Cache.find(const_cast(&Function)); if (FunctionPosition == Cache.end()) return false; const auto AnalysisPosition = FunctionPosition->second.find(ID); return AnalysisPosition != FunctionPosition->second.end() && AnalysisPosition->second.Value; } std::size_t MachineAnalysisManager::cachedResultCount(const MachineFunction &Function) const noexcept { const auto Position = Cache.find(const_cast(&Function)); return Position == Cache.end() ? 0 : Position->second.size(); } Expected MachineAnalysisManager::materializeDependencies(const RegisteredAnalysis &Registration, MachineFunction &Function, const TargetDescription &Target) { for (const AnalysisID Dependency : Registration.Info.Dependencies) { if (!Registry.contains(Dependency)) { return makeUnexpected(ErrorCode::InvalidArgument, "machine analysis depends on an unregistered analysis"); } auto Result = getResultImpl(Dependency, Function, Target); if (!Result) return std::unexpected(Result.error()); } return {}; } Expected MachineAnalysisManager::getResultImpl(AnalysisID ID, MachineFunction &Function, const TargetDescription &Target) { if (ID == nullptr) return makeUnexpected(ErrorCode::InvalidArgument, "machine analysis ID cannot be null"); auto &FunctionCache = Cache[&Function]; if (const auto Cached = FunctionCache.find(ID); Cached != FunctionCache.end() && Cached->second.Value) { const auto Registration = Registry.find(ID); const std::string_view Name = Registration == Registry.end() ? std::string_view{"unknown-machine-analysis"} : std::string_view{Registration->second.Info.Name}; if (Instrumentation != nullptr) Instrumentation->afterAnalysis(Name, Function, true, 0); return Cached->second.Value.get(); } const auto RegistrationPosition = Registry.find(ID); if (RegistrationPosition == Registry.end()) return makeUnexpected(ErrorCode::InvalidArgument, "machine analysis ID is not registered"); const RegisteredAnalysis &Registration = RegistrationPosition->second; auto &Stack = ActiveComputations[&Function]; if (std::ranges::find(Stack, ID) != Stack.end()) { Error Failure(ErrorCode::InvalidArgument, "machine analysis dependency cycle reaches '" + Registration.Info.Name + "'"); if (Instrumentation != nullptr) Instrumentation->analysisFailure(Registration.Info.Name, Function, Failure, 0); return std::unexpected(std::move(Failure)); } if (Instrumentation != nullptr) Instrumentation->beforeAnalysis(Registration.Info.Name, Function); const Clock::time_point Start = Clock::now(); Stack.push_back(ID); auto Dependencies = materializeDependencies(Registration, Function, Target); if (!Dependencies) { Stack.pop_back(); if (Stack.empty()) ActiveComputations.erase(&Function); Error Failure = prefixedError(Dependencies.error(), "machine analysis '" + Registration.Info.Name + "' failed: "); if (Instrumentation != nullptr) { Instrumentation->analysisFailure(Registration.Info.Name, Function, Failure, elapsedNanoseconds(Start)); } return std::unexpected(std::move(Failure)); } auto Computed = Registration.Runner(Function, Target, *this); Stack.pop_back(); if (Stack.empty()) ActiveComputations.erase(&Function); const std::uint64_t Duration = elapsedNanoseconds(Start); if (!Computed) { Error Failure = prefixedError(Computed.error(), "machine analysis '" + Registration.Info.Name + "' failed: "); if (Instrumentation != nullptr) Instrumentation->analysisFailure(Registration.Info.Name, Function, Failure, Duration); return std::unexpected(std::move(Failure)); } if (!*Computed) { Error Failure(ErrorCode::InternalError, "machine analysis '" + Registration.Info.Name + "' returned a null result"); if (Instrumentation != nullptr) Instrumentation->analysisFailure(Registration.Info.Name, Function, Failure, Duration); return std::unexpected(std::move(Failure)); } void *Result = Computed->get(); FunctionCache.emplace(ID, CacheEntry{std::move(*Computed), ID}); if (Instrumentation != nullptr) Instrumentation->afterAnalysis(Registration.Info.Name, Function, false, Duration); return Result; } void MachineAnalysisManager::invalidate(MachineFunction &Function, const PreservedAnalyses &Preserved, MachinePassChanges Changes) noexcept { const auto FunctionPosition = Cache.find(&Function); if (FunctionPosition == Cache.end() || Preserved.preservesAll() || Changes.empty()) return; std::unordered_set Invalid; for (auto &[ID, Entry] : FunctionPosition->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(), Function, Preserved); if (ShouldInvalidate) Invalid.insert(ID); } } bool Added = true; while (Added) { Added = false; for (const auto &[ID, Entry] : FunctionPosition->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) FunctionPosition->second.erase(ID); if (FunctionPosition->second.empty()) Cache.erase(FunctionPosition); } void MachineAnalysisManager::clear(MachineFunction &Function) noexcept { Cache.erase(&Function); ActiveComputations.erase(&Function); } void MachineAnalysisManager::clearAll() noexcept { Cache.clear(); ActiveComputations.clear(); } Expected> MachineAnalysisManager::forkEmpty() const { if (!ActiveComputations.empty()) { return makeUnexpected(ErrorCode::InvalidArgument, "cannot fork a machine analysis manager during analysis computation"); } auto Result = std::make_unique(); Result->Registry = Registry; return Result; } Expected MachineAnalysisManager::validateParallelRequirements( std::span RequiredAnalyses) const { std::vector Worklist(RequiredAnalyses.begin(), RequiredAnalyses.end()); std::unordered_set 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 machine pass requires an unregistered analysis"); } if (Position->second.Info.ThreadSafety != PassThreadSafety::ThreadSafe) { return makeUnexpected(ErrorCode::InvalidArgument, "parallel machine 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 {}; } Expected MachineAnalysisManager::requestAction(std::string_view Action, const MachineFunction &Function, std::uint64_t Cost) { if (Action.empty() || Cost == 0) return makeUnexpected(ErrorCode::InvalidArgument, "machine action requires a name and nonzero cost"); if (Instrumentation == nullptr) return true; return Instrumentation->requestAction(Action, Function, Cost); } Expected CompilationUnitAnalysisManager::registerAnalysisImpl( CompilationUnitAnalysisRegistration Registration, ErasedRunner Runner, ErasedInvalidator Invalidator) { if (Registration.ID == nullptr || Registration.Name.empty() || !Runner || !Invalidator) { return makeUnexpected(ErrorCode::InvalidArgument, "compilation-unit analysis registration is incomplete"); } if (hasDuplicateAnalysisIDs(Registration.Dependencies)) { return makeUnexpected(ErrorCode::InvalidArgument, "compilation-unit analysis dependencies must be non-null and unique"); } if (std::ranges::find(Registration.Dependencies, Registration.ID) != Registration.Dependencies.end()) { return makeUnexpected(ErrorCode::InvalidArgument, "compilation-unit analysis cannot directly depend on itself"); } if (Registry.contains(Registration.ID)) { return makeUnexpected(ErrorCode::InvalidArgument, "compilation-unit 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, "compilation-unit analysis name is already registered"); } Registry.emplace(Registration.ID, RegisteredAnalysis{std::move(Registration), std::move(Runner), std::move(Invalidator)}); return {}; } bool CompilationUnitAnalysisManager::isRegistered(AnalysisID ID) const noexcept { return ID != nullptr && Registry.contains(ID); } bool CompilationUnitAnalysisManager::hasCached(const CompilationUnit &Unit, AnalysisID ID) const noexcept { const auto UnitPosition = Cache.find(const_cast(&Unit)); if (UnitPosition == Cache.end()) return false; const auto AnalysisPosition = UnitPosition->second.find(ID); return AnalysisPosition != UnitPosition->second.end() && AnalysisPosition->second.Value; } Expected CompilationUnitAnalysisManager::materializeDependencies(const RegisteredAnalysis &Registration, CompilationUnit &Unit, const TargetDescription &Target) { for (const AnalysisID Dependency : Registration.Info.Dependencies) { if (!Registry.contains(Dependency)) { return makeUnexpected(ErrorCode::InvalidArgument, "compilation-unit analysis depends on an unregistered analysis"); } auto Result = getResultImpl(Dependency, Unit, Target); if (!Result) return std::unexpected(Result.error()); } return {}; } Expected CompilationUnitAnalysisManager::getResultImpl(AnalysisID ID, CompilationUnit &Unit, const TargetDescription &Target) { if (ID == nullptr) return makeUnexpected(ErrorCode::InvalidArgument, "compilation-unit 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-compilation-unit-analysis"} : std::string_view{Registration->second.Info.Name}; if (Instrumentation != nullptr) Instrumentation->afterUnitAnalysis(Name, Unit, true, 0); return Cached->second.Value.get(); } const auto RegistrationPosition = Registry.find(ID); if (RegistrationPosition == Registry.end()) { return makeUnexpected(ErrorCode::InvalidArgument, "compilation-unit 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, "compilation-unit analysis dependency cycle reaches '" + Registration.Info.Name + "'"); if (Instrumentation != nullptr) Instrumentation->unitAnalysisFailure(Registration.Info.Name, Unit, Failure, 0); return std::unexpected(std::move(Failure)); } if (Instrumentation != nullptr) Instrumentation->beforeUnitAnalysis(Registration.Info.Name, Unit); const Clock::time_point Start = Clock::now(); Stack.push_back(ID); auto Dependencies = materializeDependencies(Registration, Unit, Target); if (!Dependencies) { Stack.pop_back(); if (Stack.empty()) ActiveComputations.erase(&Unit); Error Failure = prefixedError(Dependencies.error(), "compilation-unit analysis '" + Registration.Info.Name + "' failed: "); if (Instrumentation != nullptr) { Instrumentation->unitAnalysisFailure(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 = prefixedError(Computed.error(), "compilation-unit analysis '" + Registration.Info.Name + "' failed: "); if (Instrumentation != nullptr) Instrumentation->unitAnalysisFailure(Registration.Info.Name, Unit, Failure, Duration); return std::unexpected(std::move(Failure)); } if (!*Computed) { Error Failure(ErrorCode::InternalError, "compilation-unit analysis '" + Registration.Info.Name + "' returned a null result"); if (Instrumentation != nullptr) Instrumentation->unitAnalysisFailure(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) Instrumentation->afterUnitAnalysis(Registration.Info.Name, Unit, false, Duration); return Result; } void CompilationUnitAnalysisManager::invalidate(CompilationUnit &Unit, const PreservedAnalyses &Preserved, MachinePassChanges Changes) noexcept { const auto UnitPosition = Cache.find(&Unit); if (UnitPosition == Cache.end() || Preserved.preservesAll() || Changes.empty()) return; std::unordered_set 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 InvalidID : Invalid) UnitPosition->second.erase(InvalidID); if (UnitPosition->second.empty()) Cache.erase(UnitPosition); } void CompilationUnitAnalysisManager::clear(CompilationUnit &Unit) noexcept { Cache.erase(&Unit); ActiveComputations.erase(&Unit); } void CompilationUnitAnalysisManager::clearAll() noexcept { Cache.clear(); ActiveComputations.clear(); } void MachinePassInstrumentation::addBeforeCallback(BeforeCallback Callback) { if (!Callback) return; std::scoped_lock Lock(Mutex); Before.push_back(std::move(Callback)); } void MachinePassInstrumentation::addAfterCallback(AfterCallback Callback) { if (!Callback) return; std::scoped_lock Lock(Mutex); After.push_back(std::move(Callback)); } void MachinePassInstrumentation::addFailureCallback(FailureCallback Callback) { if (!Callback) return; std::scoped_lock Lock(Mutex); Failure.push_back(std::move(Callback)); } void MachinePassInstrumentation::addBeforeAnalysisCallback(BeforeAnalysisCallback Callback) { if (!Callback) return; std::scoped_lock Lock(Mutex); BeforeAnalysis.push_back(std::move(Callback)); } void MachinePassInstrumentation::addAfterAnalysisCallback(AfterAnalysisCallback Callback) { if (!Callback) return; std::scoped_lock Lock(Mutex); AfterAnalysis.push_back(std::move(Callback)); } void MachinePassInstrumentation::addAnalysisFailureCallback(AnalysisFailureCallback Callback) { if (!Callback) return; std::scoped_lock Lock(Mutex); AnalysisFailure.push_back(std::move(Callback)); } void MachinePassInstrumentation::addIRPrintCallback(IRPrintCallback Callback) { if (!Callback) return; std::scoped_lock Lock(Mutex); IRPrint.push_back(std::move(Callback)); } void MachinePassInstrumentation::addChangeCallback(ChangeCallback Callback) { if (!Callback) return; std::scoped_lock Lock(Mutex); Change.push_back(std::move(Callback)); } void MachinePassInstrumentation::setActionFilter(ActionFilter NewFilter) { std::scoped_lock Lock(Mutex); Filter = std::move(NewFilter); } void MachinePassInstrumentation::setPerFunctionFuel(std::optional Fuel) { std::scoped_lock Lock(Mutex); PerFunctionFuel = Fuel; ConsumedFuel.clear(); } void MachinePassInstrumentation::addBeforeUnitCallback(BeforeUnitCallback Callback) { if (!Callback) return; std::scoped_lock Lock(Mutex); BeforeUnit.push_back(std::move(Callback)); } void MachinePassInstrumentation::addAfterUnitCallback(AfterUnitCallback Callback) { if (!Callback) return; std::scoped_lock Lock(Mutex); AfterUnit.push_back(std::move(Callback)); } void MachinePassInstrumentation::addUnitFailureCallback(UnitFailureCallback Callback) { if (!Callback) return; std::scoped_lock Lock(Mutex); UnitFailure.push_back(std::move(Callback)); } void MachinePassInstrumentation::addBeforeUnitAnalysisCallback( BeforeUnitAnalysisCallback Callback) { if (!Callback) return; std::scoped_lock Lock(Mutex); BeforeUnitAnalysis.push_back(std::move(Callback)); } void MachinePassInstrumentation::addAfterUnitAnalysisCallback(AfterUnitAnalysisCallback Callback) { if (!Callback) return; std::scoped_lock Lock(Mutex); AfterUnitAnalysis.push_back(std::move(Callback)); } void MachinePassInstrumentation::addUnitAnalysisFailureCallback( UnitAnalysisFailureCallback Callback) { if (!Callback) return; std::scoped_lock Lock(Mutex); UnitAnalysisFailure.push_back(std::move(Callback)); } InstrumentationStatistic &MachinePassInstrumentation::statistic(std::string_view Name, bool IsAnalysis) const { const std::string Key = std::string(IsAnalysis ? "analysis:" : "pass:") + std::string(Name); auto [Position, Inserted] = Statistics.try_emplace(Key); if (Inserted) { Position->second.Name = std::string(Name); Position->second.IsAnalysis = IsAnalysis; } return Position->second; } bool MachinePassInstrumentation::bufferFunctionEvent( const MachineFunction &Function, std::function Replay) const { if (ActiveBuffer == nullptr) return false; std::shared_ptr Snapshot; auto Cloned = cloneMachineFunction( Function, *ActiveBuffer->Target, MachineFunctionCloneOptions{.NewStableID = Function.stableID(), .NewName = std::nullopt}); if (Cloned) { Snapshot = std::shared_ptr(std::move(Cloned->ClonedFunction)); } else { Snapshot = std::shared_ptr(&Function, [](const MachineFunction *) noexcept {}); } ActiveBuffer->Events.emplace_back( [Snapshot = std::move(Snapshot), Replay = std::move(Replay)]() { Replay(*Snapshot); }); return true; } void MachinePassInstrumentation::beginBuffering(BufferedEvents &Buffer, const TargetDescription &Target) const noexcept { Buffer.Target = &Target; Buffer.Events.clear(); ActiveBuffer = &Buffer; } void MachinePassInstrumentation::endBuffering() const noexcept { ActiveBuffer = nullptr; } void MachinePassInstrumentation::replayBufferedEvents(BufferedEvents &Buffer) const { for (const std::function &Event : Buffer.Events) Event(); Buffer.Events.clear(); Buffer.Target = nullptr; } void MachinePassInstrumentation::before(std::string_view Name, const MachineFunction &Function) const { if (bufferFunctionEvent(Function, [this, Name = std::string(Name)](const MachineFunction &Input) { before(Name, Input); })) { return; } std::vector Callbacks; std::vector Printers; { std::scoped_lock Lock(Mutex); Callbacks = Before; Printers = IRPrint; } for (const BeforeCallback &Callback : Callbacks) Callback(Name, Function); for (const IRPrintCallback &Printer : Printers) Printer(InstrumentationPhase::Before, Name, Function); } void MachinePassInstrumentation::after(std::string_view Name, const MachineFunction &Function, const MachinePassResult &Result, std::uint64_t Nanoseconds) const { if (bufferFunctionEvent(Function, [this, Name = std::string(Name), Result, Nanoseconds](const MachineFunction &Input) { after(Name, Input, Result, Nanoseconds); })) { return; } std::vector Callbacks; std::vector Printers; { std::scoped_lock Lock(Mutex); InstrumentationStatistic &Record = statistic(Name, false); ++Record.Invocations; Record.Changes += Result.Changed ? 1U : 0U; Record.TotalNanoseconds += Nanoseconds; Callbacks = After; Printers = IRPrint; } for (const AfterCallback &Callback : Callbacks) Callback(Name, Function, Result); for (const IRPrintCallback &Printer : Printers) Printer(InstrumentationPhase::After, Name, Function); } void MachinePassInstrumentation::failure(std::string_view Name, const MachineFunction &Function, const Error &ErrorValue, std::uint64_t Nanoseconds) const { if (bufferFunctionEvent(Function, [this, Name = std::string(Name), ErrorValue, Nanoseconds](const MachineFunction &Input) { failure(Name, Input, ErrorValue, Nanoseconds); })) { return; } std::vector Callbacks; { std::scoped_lock Lock(Mutex); InstrumentationStatistic &Record = statistic(Name, false); ++Record.Invocations; ++Record.Failures; Record.TotalNanoseconds += Nanoseconds; Callbacks = Failure; } for (const FailureCallback &Callback : Callbacks) Callback(Name, Function, ErrorValue); } void MachinePassInstrumentation::beforeAnalysis(std::string_view Name, const MachineFunction &Function) const { if (bufferFunctionEvent(Function, [this, Name = std::string(Name)](const MachineFunction &Input) { beforeAnalysis(Name, Input); })) { return; } std::vector Callbacks; { std::scoped_lock Lock(Mutex); Callbacks = BeforeAnalysis; } for (const BeforeAnalysisCallback &Callback : Callbacks) Callback(Name, Function); } void MachinePassInstrumentation::afterAnalysis(std::string_view Name, const MachineFunction &Function, bool CacheHit, std::uint64_t Nanoseconds) const { if (bufferFunctionEvent(Function, [this, Name = std::string(Name), CacheHit, Nanoseconds](const MachineFunction &Input) { afterAnalysis(Name, Input, CacheHit, Nanoseconds); })) { return; } std::vector Callbacks; { std::scoped_lock Lock(Mutex); InstrumentationStatistic &Record = statistic(Name, true); ++Record.Invocations; Record.CacheHits += CacheHit ? 1U : 0U; Record.TotalNanoseconds += Nanoseconds; Callbacks = AfterAnalysis; } for (const AfterAnalysisCallback &Callback : Callbacks) Callback(Name, Function, CacheHit, Nanoseconds); } void MachinePassInstrumentation::analysisFailure(std::string_view Name, const MachineFunction &Function, const Error &ErrorValue, std::uint64_t Nanoseconds) const { if (bufferFunctionEvent(Function, [this, Name = std::string(Name), ErrorValue, Nanoseconds](const MachineFunction &Input) { analysisFailure(Name, Input, ErrorValue, Nanoseconds); })) { return; } std::vector Callbacks; { std::scoped_lock Lock(Mutex); InstrumentationStatistic &Record = statistic(Name, true); ++Record.Invocations; ++Record.Failures; Record.TotalNanoseconds += Nanoseconds; Callbacks = AnalysisFailure; } for (const AnalysisFailureCallback &Callback : Callbacks) Callback(Name, Function, ErrorValue); } void MachinePassInstrumentation::change(std::string_view Name, const MachineFunction &Function, MachinePassChanges Changes, bool FingerprintChanged) const { if (bufferFunctionEvent(Function, [this, Name = std::string(Name), Changes, FingerprintChanged](const MachineFunction &Input) { change(Name, Input, Changes, FingerprintChanged); })) { return; } std::vector Callbacks; { std::scoped_lock Lock(Mutex); Callbacks = Change; } for (const ChangeCallback &Callback : Callbacks) Callback(Name, Function, Changes, FingerprintChanged); } Expected MachinePassInstrumentation::requestAction(std::string_view Action, const MachineFunction &Function, std::uint64_t Cost) { std::scoped_lock ActionLock(ActionMutex); ActionFilter CurrentFilter; { std::scoped_lock Lock(Mutex); CurrentFilter = Filter; } if (CurrentFilter && !CurrentFilter(Action, Function, Cost)) return false; std::scoped_lock Lock(Mutex); if (!PerFunctionFuel) return true; std::uint64_t &Consumed = ConsumedFuel[Function.stableID().value()]; if (Consumed > *PerFunctionFuel || Cost > *PerFunctionFuel - Consumed) return false; Consumed += Cost; return true; } void MachinePassInstrumentation::beforeUnit(std::string_view Name, const CompilationUnit &Unit) const { std::vector Callbacks; { std::scoped_lock Lock(Mutex); Callbacks = BeforeUnit; } for (const BeforeUnitCallback &Callback : Callbacks) Callback(Name, Unit); } void MachinePassInstrumentation::afterUnit(std::string_view Name, const CompilationUnit &Unit, const CompilationUnitPassResult &Result, std::uint64_t Nanoseconds) const { std::vector Callbacks; { std::scoped_lock Lock(Mutex); InstrumentationStatistic &Record = statistic("unit:" + std::string(Name), false); ++Record.Invocations; Record.Changes += Result.Changed ? 1U : 0U; Record.TotalNanoseconds += Nanoseconds; Callbacks = AfterUnit; } for (const AfterUnitCallback &Callback : Callbacks) Callback(Name, Unit, Result); } void MachinePassInstrumentation::unitFailure(std::string_view Name, const CompilationUnit &Unit, const Error &ErrorValue, std::uint64_t Nanoseconds) const { std::vector Callbacks; { std::scoped_lock Lock(Mutex); InstrumentationStatistic &Record = statistic("unit:" + std::string(Name), false); ++Record.Invocations; ++Record.Failures; Record.TotalNanoseconds += Nanoseconds; Callbacks = UnitFailure; } for (const UnitFailureCallback &Callback : Callbacks) Callback(Name, Unit, ErrorValue); } void MachinePassInstrumentation::beforeUnitAnalysis(std::string_view Name, const CompilationUnit &Unit) const { std::vector Callbacks; { std::scoped_lock Lock(Mutex); Callbacks = BeforeUnitAnalysis; } for (const BeforeUnitAnalysisCallback &Callback : Callbacks) Callback(Name, Unit); } void MachinePassInstrumentation::afterUnitAnalysis(std::string_view Name, const CompilationUnit &Unit, bool CacheHit, std::uint64_t Nanoseconds) const { std::vector Callbacks; { std::scoped_lock Lock(Mutex); InstrumentationStatistic &Record = statistic("unit:" + std::string(Name), true); ++Record.Invocations; Record.CacheHits += CacheHit ? 1U : 0U; Record.TotalNanoseconds += Nanoseconds; Callbacks = AfterUnitAnalysis; } for (const AfterUnitAnalysisCallback &Callback : Callbacks) Callback(Name, Unit, CacheHit, Nanoseconds); } void MachinePassInstrumentation::unitAnalysisFailure(std::string_view Name, const CompilationUnit &Unit, const Error &ErrorValue, std::uint64_t Nanoseconds) const { std::vector Callbacks; { std::scoped_lock Lock(Mutex); InstrumentationStatistic &Record = statistic("unit:" + std::string(Name), true); ++Record.Invocations; ++Record.Failures; Record.TotalNanoseconds += Nanoseconds; Callbacks = UnitAnalysisFailure; } for (const UnitAnalysisFailureCallback &Callback : Callbacks) Callback(Name, Unit, ErrorValue); } std::vector MachinePassInstrumentation::statistics() const { std::vector Result; { std::scoped_lock Lock(Mutex); Result.reserve(Statistics.size()); for (const auto &[Key, Record] : Statistics) { (void)Key; Result.push_back(Record); } } std::ranges::sort( Result, [](const InstrumentationStatistic &Left, const InstrumentationStatistic &Right) { if (Left.IsAnalysis != Right.IsAnalysis) return Left.IsAnalysis < Right.IsAnalysis; return Left.Name < Right.Name; }); return Result; } void MachinePassInstrumentation::clearStatistics() { std::scoped_lock Lock(Mutex); Statistics.clear(); ConsumedFuel.clear(); } Expected MachinePassManager::validateContract(const MachinePassContract &Contract) const { if (Contract.Name.empty()) return makeUnexpected(ErrorCode::InvalidArgument, "machine pass must have a non-empty name"); if (Contract.Sets.intersects(Contract.Clears)) { return makeUnexpected(ErrorCode::InvalidArgument, "machine pass cannot set and clear the same property"); } if (hasDuplicateAnalysisIDs(Contract.RequiredAnalyses)) { return makeUnexpected(ErrorCode::InvalidArgument, "machine pass required analyses must be non-null and unique"); } return validateParameters(Contract.Parameters); } Expected MachinePassManager::addPass(std::unique_ptr Pass) { if (!Pass) return makeUnexpected(ErrorCode::InvalidArgument, "machine pass manager cannot add a null pass"); auto Validation = validateContract(Pass->contract()); if (!Validation) return std::unexpected(Validation.error()); Passes.push_back(std::move(Pass)); return {}; } Expected MachinePassManager::run(MachineFunction &Function, const TargetDescription &Target, MachineAnalysisManager &Analyses, MachinePassInstrumentation *Instrumentation) const { Analyses.setInstrumentation(Instrumentation); for (const std::unique_ptr &Pass : Passes) { const MachinePassContract &Contract = Pass->contract(); const std::string_view Name = Contract.Name; if (const MachineFunctionProperties Missing = Contract.Requires.missingFrom(Function.properties()); !Missing.empty()) { Error Failure(ErrorCode::VerificationError, "pass '" + std::string(Name) + "' requires properties: " + Missing.toString()); if (Instrumentation != nullptr) Instrumentation->failure(Name, Function, Failure); Analyses.setInstrumentation(nullptr); return std::unexpected(std::move(Failure)); } if (Options.VerifyBefore) { auto Verification = MachineVerifier::verify(Function, Target); if (!Verification) { Error Failure = prefixedError(Verification.error(), "pass '" + std::string(Name) + "' received invalid MachineIR: "); if (Instrumentation != nullptr) Instrumentation->failure(Name, Function, Failure); Analyses.setInstrumentation(nullptr); return std::unexpected(std::move(Failure)); } } for (const AnalysisID Required : Contract.RequiredAnalyses) { auto Result = Analyses.getResultImpl(Required, Function, Target); if (!Result) { Error Failure = prefixedError(Result.error(), "pass '" + std::string(Name) + "' could not materialize a required analysis: "); if (Instrumentation != nullptr) Instrumentation->failure(Name, Function, Failure); Analyses.setInstrumentation(nullptr); return std::unexpected(std::move(Failure)); } } const std::uint64_t FingerprintBefore = Options.DetectUnreportedChanges ? machineFunctionFingerprint(Function) : 0; if (Instrumentation != nullptr) Instrumentation->before(Name, Function); const Clock::time_point Start = Clock::now(); auto PassResult = Pass->run(Function, Target, Analyses); const std::uint64_t Duration = elapsedNanoseconds(Start); if (!PassResult) { Error Failure = prefixedError(PassResult.error(), "pass '" + std::string(Name) + "' failed: "); if (Instrumentation != nullptr) Instrumentation->failure(Name, Function, Failure, Duration); Analyses.clear(Function); Analyses.setInstrumentation(nullptr); return std::unexpected(std::move(Failure)); } if (PassResult->Changed != !PassResult->Changes.empty()) { Error Failure(ErrorCode::VerificationError, "pass '" + std::string(Name) + "' reported inconsistent changed state and modification categories"); if (Instrumentation != nullptr) Instrumentation->failure(Name, Function, Failure, Duration); Analyses.clear(Function); Analyses.setInstrumentation(nullptr); return std::unexpected(std::move(Failure)); } if (!Contract.DeclaredChanges.contains(PassResult->Changes)) { Error Failure(ErrorCode::VerificationError, "pass '" + std::string(Name) + "' reported a modification category absent from its registration"); if (Instrumentation != nullptr) Instrumentation->failure(Name, Function, Failure, Duration); Analyses.clear(Function); Analyses.setInstrumentation(nullptr); return std::unexpected(std::move(Failure)); } bool FingerprintChanged = PassResult->Changed; if (Options.DetectUnreportedChanges) { FingerprintChanged = FingerprintBefore != machineFunctionFingerprint(Function); if (FingerprintChanged && !PassResult->Changed) { Error Failure(ErrorCode::VerificationError, "pass '" + std::string(Name) + "' changed MachineIR without reporting a modification"); if (Instrumentation != nullptr) Instrumentation->failure(Name, Function, Failure, Duration); Analyses.clear(Function); Analyses.setInstrumentation(nullptr); return std::unexpected(std::move(Failure)); } } if (Instrumentation != nullptr) Instrumentation->change(Name, Function, PassResult->Changes, FingerprintChanged); auto Transition = Function.properties().applyTransition(Contract.Requires, Contract.Sets, Contract.Clears); if (!Transition) { Error Failure = prefixedError(Transition.error(), "pass '" + std::string(Name) + "' produced an invalid property transition: "); if (Instrumentation != nullptr) Instrumentation->failure(Name, Function, Failure, Duration); Analyses.clear(Function); Analyses.setInstrumentation(nullptr); return std::unexpected(std::move(Failure)); } Analyses.invalidate(Function, PassResult->Preserved, PassResult->Changes); if (Options.VerifyEach) { MachineVerifierOptions VerifyOptions; VerifyOptions.Stage = Contract.VerificationStage; auto Verification = MachineVerifier::verify(Function, Target, VerifyOptions); if (!Verification) { Error Failure = prefixedError(Verification.error(), "pass '" + std::string(Name) + "' left invalid MachineIR: "); if (Instrumentation != nullptr) Instrumentation->failure(Name, Function, Failure, Duration); Analyses.clear(Function); Analyses.setInstrumentation(nullptr); return std::unexpected(std::move(Failure)); } } if (Instrumentation != nullptr) Instrumentation->after(Name, Function, *PassResult, Duration); } Analyses.setInstrumentation(nullptr); return {}; } Expected MachinePassManager::runFunctions(CompilationUnit &Unit, const TargetDescription &Target, const MachineAnalysisManager &AnalysisPrototype, std::size_t ThreadCount, MachinePassInstrumentation *Instrumentation) const { if (ThreadCount == 0) return makeUnexpected(ErrorCode::InvalidArgument, "machine pass thread count must be nonzero"); auto UnitVerification = verifyCompilationUnit(Unit, Target); if (!UnitVerification) return std::unexpected(UnitVerification.error()); const std::size_t FunctionCount = Unit.functionCount(); if (FunctionCount == 0) return {}; const std::size_t WorkerCount = std::min(ThreadCount, FunctionCount); if (WorkerCount > 1) { for (const std::unique_ptr &Pass : Passes) { if (Pass->contract().ThreadSafety != PassThreadSafety::ThreadSafe) { return makeUnexpected(ErrorCode::InvalidArgument, "parallel machine pipeline contains a pass not marked thread-safe"); } auto AnalysisSafety = AnalysisPrototype.validateParallelRequirements(Pass->contract().RequiredAnalyses); if (!AnalysisSafety) return std::unexpected(AnalysisSafety.error()); } } std::vector> Managers; Managers.reserve(FunctionCount); for (std::size_t Index = 0; Index < FunctionCount; ++Index) { auto Manager = AnalysisPrototype.forkEmpty(); if (!Manager) return std::unexpected(Manager.error()); Managers.push_back(std::move(*Manager)); } std::vector> Failures(FunctionCount); std::vector EventBuffers(FunctionCount); std::atomic NextFunction = 0; const auto Worker = [&]() { while (true) { const std::size_t Index = NextFunction.fetch_add(1, std::memory_order_relaxed); if (Index >= FunctionCount) return; MachineFunction *Function = Unit.functionAt(Index); if (Function == nullptr) { Failures[Index] = Error(ErrorCode::InternalError, "compilation unit contains a null function during parallel run"); continue; } if (Instrumentation != nullptr) Instrumentation->beginBuffering(EventBuffers[Index], Target); auto Result = run(*Function, Target, *Managers[Index], Instrumentation); if (Instrumentation != nullptr) Instrumentation->endBuffering(); if (!Result) Failures[Index] = Result.error(); } }; std::vector Workers; Workers.reserve(WorkerCount); for (std::size_t Index = 0; Index < WorkerCount; ++Index) Workers.emplace_back(Worker); for (std::thread &WorkerThread : Workers) WorkerThread.join(); for (std::size_t Index = 0; Index < FunctionCount; ++Index) { if (Instrumentation != nullptr) Instrumentation->replayBufferedEvents(EventBuffers[Index]); if (Failures[Index]) { return std::unexpected( prefixedError(*Failures[Index], "function '" + std::string(Unit.functionAt(Index)->name()) + "' failed: ")); } } return {}; } Expected CompilationUnitPassManager::addPass(std::unique_ptr Pass) { if (!Pass) { return makeUnexpected(ErrorCode::InvalidArgument, "compilation-unit pass manager cannot add a null pass"); } const CompilationUnitPassContract &Contract = Pass->contract(); if (Contract.Name.empty()) { return makeUnexpected(ErrorCode::InvalidArgument, "compilation-unit pass must have a non-empty name"); } if (hasDuplicateAnalysisIDs(Contract.RequiredAnalyses)) { return makeUnexpected(ErrorCode::InvalidArgument, "compilation-unit pass required analyses must be non-null and unique"); } auto Parameters = validateParameters(Contract.Parameters); if (!Parameters) return std::unexpected(Parameters.error()); Passes.push_back(std::move(Pass)); return {}; } Expected CompilationUnitPassManager::run(CompilationUnit &Unit, const TargetDescription &Target, CompilationUnitAnalysisManager &UnitAnalyses, MachineAnalysisManager &FunctionAnalyses, MachinePassInstrumentation *Instrumentation) const { UnitAnalyses.setInstrumentation(Instrumentation); FunctionAnalyses.setInstrumentation(Instrumentation); for (const std::unique_ptr &Pass : Passes) { const CompilationUnitPassContract &Contract = Pass->contract(); const std::string_view Name = Contract.Name; if (Options.VerifyBefore) { auto Verification = verifyCompilationUnit(Unit, Target); if (!Verification) { Error Failure = prefixedError(Verification.error(), "compilation-unit pass '" + std::string(Name) + "' received invalid MachineIR: "); if (Instrumentation != nullptr) Instrumentation->unitFailure(Name, Unit, Failure); UnitAnalyses.setInstrumentation(nullptr); FunctionAnalyses.setInstrumentation(nullptr); return std::unexpected(std::move(Failure)); } } for (const AnalysisID Required : Contract.RequiredAnalyses) { auto Result = UnitAnalyses.getResultImpl(Required, Unit, Target); if (!Result) { Error Failure = prefixedError(Result.error(), "compilation-unit pass '" + std::string(Name) + "' could not materialize a required analysis: "); if (Instrumentation != nullptr) Instrumentation->unitFailure(Name, Unit, Failure); UnitAnalyses.setInstrumentation(nullptr); FunctionAnalyses.setInstrumentation(nullptr); return std::unexpected(std::move(Failure)); } } if (Instrumentation != nullptr) Instrumentation->beforeUnit(Name, Unit); const Clock::time_point Start = Clock::now(); auto Result = Pass->run(Unit, Target, UnitAnalyses, FunctionAnalyses); const std::uint64_t Duration = elapsedNanoseconds(Start); if (!Result) { Error Failure = prefixedError(Result.error(), "compilation-unit pass '" + std::string(Name) + "' failed: "); if (Instrumentation != nullptr) Instrumentation->unitFailure(Name, Unit, Failure, Duration); UnitAnalyses.clearAll(); FunctionAnalyses.clearAll(); UnitAnalyses.setInstrumentation(nullptr); FunctionAnalyses.setInstrumentation(nullptr); return std::unexpected(std::move(Failure)); } if (Result->Changed != !Result->Changes.empty() || !Contract.DeclaredChanges.contains(Result->Changes)) { Error Failure(ErrorCode::VerificationError, "compilation-unit pass '" + std::string(Name) + "' reported inconsistent or undeclared modifications"); if (Instrumentation != nullptr) Instrumentation->unitFailure(Name, Unit, Failure, Duration); UnitAnalyses.clearAll(); FunctionAnalyses.clearAll(); UnitAnalyses.setInstrumentation(nullptr); FunctionAnalyses.setInstrumentation(nullptr); return std::unexpected(std::move(Failure)); } UnitAnalyses.invalidate(Unit, Result->PreservedUnitAnalyses, Result->Changes); for (std::size_t Index = 0; Index < Unit.functionCount(); ++Index) { MachineFunction *Function = Unit.functionAt(Index); if (Function != nullptr) { FunctionAnalyses.invalidate(*Function, Result->PreservedFunctionAnalyses, Result->Changes); } } if (Options.VerifyEach) { auto Verification = verifyCompilationUnit(Unit, Target); if (!Verification) { Error Failure = prefixedError(Verification.error(), "compilation-unit pass '" + std::string(Name) + "' left invalid MachineIR: "); if (Instrumentation != nullptr) Instrumentation->unitFailure(Name, Unit, Failure, Duration); UnitAnalyses.clearAll(); FunctionAnalyses.clearAll(); UnitAnalyses.setInstrumentation(nullptr); FunctionAnalyses.setInstrumentation(nullptr); return std::unexpected(std::move(Failure)); } } if (Instrumentation != nullptr) Instrumentation->afterUnit(Name, Unit, *Result, Duration); } UnitAnalyses.setInstrumentation(nullptr); FunctionAnalyses.setInstrumentation(nullptr); return {}; } } // namespace rcc