//===- MLRegAllocPriorityAdvisor.cpp - ML priority advisor-----------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // Implementation of the ML priority advisor and reward injection pass // //===----------------------------------------------------------------------===// #include "AllocationOrder.h" #include "RegAllocGreedy.h" #include "RegAllocPriorityAdvisor.h" #include "llvm/Analysis/AliasAnalysis.h" #include "llvm/Analysis/InteractiveModelRunner.h" #include "llvm/Analysis/MLModelRunner.h" #include "llvm/Analysis/ReleaseModeModelRunner.h" #include "llvm/Analysis/TensorSpec.h" #include "llvm/CodeGen/CalcSpillWeights.h" #include "llvm/CodeGen/LiveRegMatrix.h" #include "llvm/CodeGen/MachineBlockFrequencyInfo.h" #include "llvm/CodeGen/MachineFunction.h" #include "llvm/CodeGen/MachineLoopInfo.h" #include "llvm/CodeGen/MachineRegisterInfo.h" #include "llvm/CodeGen/Passes.h" #include "llvm/CodeGen/RegisterClassInfo.h" #include "llvm/CodeGen/SlotIndexes.h" #include "llvm/CodeGen/VirtRegMap.h" #include "llvm/InitializePasses.h" #include "llvm/Pass.h" #include "llvm/PassRegistry.h" #include "llvm/Support/CommandLine.h" #if defined(LLVM_HAVE_TFLITE) #include "llvm/Analysis/ModelUnderTrainingRunner.h" #include "llvm/Analysis/NoInferenceModelRunner.h" #include "llvm/Analysis/Utils/TrainingLogger.h" #include "llvm/IR/Module.h" #endif using namespace llvm; static cl::opt InteractiveChannelBaseName( "regalloc-priority-interactive-channel-base", cl::Hidden, cl::desc( "Base file path for the interactive mode. The incoming filename should " "have the name .in, while " "the outgoing name should be " ".out")); using CompiledModelType = NoopSavedModelImpl; // Options that only make sense in development mode #ifdef LLVM_HAVE_TFLITE #include "RegAllocScore.h" #include "llvm/Analysis/Utils/TFUtils.h" static cl::opt TrainingLog( "regalloc-priority-training-log", cl::Hidden, cl::desc("Training log for the register allocator priority model")); static cl::opt ModelUnderTraining( "regalloc-priority-model", cl::Hidden, cl::desc("The model being trained for register allocation priority")); #endif // #ifdef LLVM_HAVE_TFLITE namespace llvm { static const std::vector PerLiveRangeShape{1}; #define RA_PRIORITY_FEATURES_LIST(M) \ M(int64_t, li_size, PerLiveRangeShape, "size") \ M(int64_t, stage, PerLiveRangeShape, "stage") \ M(float, weight, PerLiveRangeShape, "weight") #define DecisionName "priority" static const TensorSpec DecisionSpec = TensorSpec::createSpec(DecisionName, {1}); // Named features index. enum FeatureIDs { #define _FEATURE_IDX(_, name, __, ___) name, RA_PRIORITY_FEATURES_LIST(_FEATURE_IDX) #undef _FEATURE_IDX FeatureCount }; class MLPriorityAdvisor : public RegAllocPriorityAdvisor { public: MLPriorityAdvisor(const MachineFunction &MF, const RAGreedy &RA, SlotIndexes *const Indexes, MLModelRunner *Runner); protected: const RegAllocPriorityAdvisor &getDefaultAdvisor() const { return static_cast(DefaultAdvisor); } // The assumption is that if the Runner could not be constructed, we emit-ed // error, and we shouldn't be asking for it here. const MLModelRunner &getRunner() const { return *Runner; } float getPriorityImpl(const LiveInterval &LI) const; unsigned getPriority(const LiveInterval &LI) const override; private: const DefaultPriorityAdvisor DefaultAdvisor; MLModelRunner *const Runner; }; #define _DECL_FEATURES(type, name, shape, _) \ TensorSpec::createSpec(#name, shape), static const std::vector InputFeatures{ {RA_PRIORITY_FEATURES_LIST(_DECL_FEATURES)}, }; #undef _DECL_FEATURES // =================================== // Release (AOT) - specifics // =================================== class ReleaseModePriorityAdvisorAnalysis final : public RegAllocPriorityAdvisorAnalysis { public: ReleaseModePriorityAdvisorAnalysis() : RegAllocPriorityAdvisorAnalysis(AdvisorMode::Release) {} // support for isa<> and dyn_cast. static bool classof(const RegAllocPriorityAdvisorAnalysis *R) { return R->getAdvisorMode() == AdvisorMode::Release; } private: void getAnalysisUsage(AnalysisUsage &AU) const override { AU.setPreservesAll(); AU.addRequired(); RegAllocPriorityAdvisorAnalysis::getAnalysisUsage(AU); } std::unique_ptr getAdvisor(const MachineFunction &MF, const RAGreedy &RA) override { if (!Runner) { if (InteractiveChannelBaseName.empty()) Runner = std::make_unique>( MF.getFunction().getContext(), InputFeatures, DecisionName); else Runner = std::make_unique( MF.getFunction().getContext(), InputFeatures, DecisionSpec, InteractiveChannelBaseName + ".out", InteractiveChannelBaseName + ".in"); } return std::make_unique( MF, RA, &getAnalysis().getSI(), Runner.get()); } std::unique_ptr Runner; }; // =================================== // Development mode-specifics // =================================== // // Features we log #ifdef LLVM_HAVE_TFLITE static const TensorSpec Reward = TensorSpec::createSpec("reward", {1}); #define _DECL_TRAIN_FEATURES(type, name, shape, _) \ TensorSpec::createSpec(std::string("action_") + #name, shape), static const std::vector TrainingInputFeatures{ {RA_PRIORITY_FEATURES_LIST(_DECL_TRAIN_FEATURES) TensorSpec::createSpec("action_discount", {1}), TensorSpec::createSpec("action_step_type", {1}), TensorSpec::createSpec("action_reward", {1})}}; #undef _DECL_TRAIN_FEATURES class DevelopmentModePriorityAdvisor : public MLPriorityAdvisor { public: DevelopmentModePriorityAdvisor(const MachineFunction &MF, const RAGreedy &RA, SlotIndexes *const Indexes, MLModelRunner *Runner, Logger *Log) : MLPriorityAdvisor(MF, RA, Indexes, Runner), Log(Log) {} private: unsigned getPriority(const LiveInterval &LI) const override; Logger *const Log; }; class DevelopmentModePriorityAdvisorAnalysis final : public RegAllocPriorityAdvisorAnalysis { public: DevelopmentModePriorityAdvisorAnalysis() : RegAllocPriorityAdvisorAnalysis(AdvisorMode::Development) {} // support for isa<> and dyn_cast. static bool classof(const RegAllocPriorityAdvisorAnalysis *R) { return R->getAdvisorMode() == AdvisorMode::Development; } void logRewardIfNeeded(const MachineFunction &MF, llvm::function_ref GetReward) override { if (!Log || !Log->hasAnyObservationForContext(MF.getName())) return; // The function pass manager would run all the function passes for a // function, so we assume the last context belongs to this function. If // this invariant ever changes, we can implement at that time switching // contexts. At this point, it'd be an error if (Log->currentContext() != MF.getName()) { MF.getFunction().getContext().emitError( "The training log context shouldn't have had changed."); } if (Log->hasObservationInProgress()) Log->logReward(GetReward()); } private: void getAnalysisUsage(AnalysisUsage &AU) const override { AU.setPreservesAll(); AU.addRequired(); RegAllocPriorityAdvisorAnalysis::getAnalysisUsage(AU); } // Save all the logs (when requested). bool doInitialization(Module &M) override { LLVMContext &Ctx = M.getContext(); if (ModelUnderTraining.empty() && TrainingLog.empty()) { Ctx.emitError("Regalloc development mode should be requested with at " "least logging enabled and/or a training model"); return false; } if (ModelUnderTraining.empty()) Runner = std::make_unique(Ctx, InputFeatures); else Runner = ModelUnderTrainingRunner::createAndEnsureValid( Ctx, ModelUnderTraining, DecisionName, TrainingInputFeatures); if (!Runner) { Ctx.emitError("Regalloc: could not set up the model runner"); return false; } if (TrainingLog.empty()) return false; std::error_code EC; auto OS = std::make_unique(TrainingLog, EC); if (EC) { M.getContext().emitError(EC.message() + ":" + TrainingLog); return false; } std::vector LFS = InputFeatures; if (auto *MUTR = dyn_cast(Runner.get())) append_range(LFS, MUTR->extraOutputsForLoggingSpecs()); // We always log the output; in particular, if we're not evaluating, we // don't have an output spec json file. That's why we handle the // 'normal' output separately. LFS.push_back(DecisionSpec); Log = std::make_unique(std::move(OS), LFS, Reward, /*IncludeReward*/ true); return false; } std::unique_ptr getAdvisor(const MachineFunction &MF, const RAGreedy &RA) override { if (!Runner) return nullptr; if (Log) { Log->switchContext(MF.getName()); } return std::make_unique( MF, RA, &getAnalysis().getSI(), Runner.get(), Log.get()); } std::unique_ptr Runner; std::unique_ptr Log; }; #endif //#ifdef LLVM_HAVE_TFLITE } // namespace llvm RegAllocPriorityAdvisorAnalysis *llvm::createReleaseModePriorityAdvisor() { return llvm::isEmbeddedModelEvaluatorValid() || !InteractiveChannelBaseName.empty() ? new ReleaseModePriorityAdvisorAnalysis() : nullptr; } MLPriorityAdvisor::MLPriorityAdvisor(const MachineFunction &MF, const RAGreedy &RA, SlotIndexes *const Indexes, MLModelRunner *Runner) : RegAllocPriorityAdvisor(MF, RA, Indexes), DefaultAdvisor(MF, RA, Indexes), Runner(std::move(Runner)) { assert(this->Runner); Runner->switchContext(MF.getName()); } float MLPriorityAdvisor::getPriorityImpl(const LiveInterval &LI) const { const unsigned Size = LI.getSize(); LiveRangeStage Stage = RA.getExtraInfo().getStage(LI); *Runner->getTensor(0) = static_cast(Size); *Runner->getTensor(1) = static_cast(Stage); *Runner->getTensor(2) = static_cast(LI.weight()); return Runner->evaluate(); } unsigned MLPriorityAdvisor::getPriority(const LiveInterval &LI) const { return static_cast(getPriorityImpl(LI)); } #ifdef LLVM_HAVE_TFLITE RegAllocPriorityAdvisorAnalysis *llvm::createDevelopmentModePriorityAdvisor() { return new DevelopmentModePriorityAdvisorAnalysis(); } unsigned DevelopmentModePriorityAdvisor::getPriority(const LiveInterval &LI) const { double Prio = 0; if (isa(getRunner())) { Prio = MLPriorityAdvisor::getPriorityImpl(LI); } else { Prio = getDefaultAdvisor().getPriority(LI); } if (TrainingLog.empty()) return Prio; // TODO(mtrofin): when we support optional rewards, this can go away. In the // meantime, we log the "pretend" reward (0) for the previous observation // before starting a new one. if (Log->hasObservationInProgress()) Log->logReward(0.0); Log->startObservation(); size_t CurrentFeature = 0; for (; CurrentFeature < InputFeatures.size(); ++CurrentFeature) { Log->logTensorValue(CurrentFeature, reinterpret_cast( getRunner().getTensorUntyped(CurrentFeature))); } if (auto *MUTR = dyn_cast(&getRunner())) { for (size_t I = 0; I < MUTR->extraOutputsForLoggingSpecs().size(); ++I, ++CurrentFeature) Log->logTensorValue( CurrentFeature, reinterpret_cast(MUTR->getUntypedExtraOutputValue(I))); } float Ret = static_cast(Prio); Log->logTensorValue(CurrentFeature, reinterpret_cast(&Ret)); Log->endObservation(); return static_cast(Prio); } #endif // #ifdef LLVM_HAVE_TFLITE