From 89972e21f8971e86e0a9e32dc91ee868db195f80 Mon Sep 17 00:00:00 2001 From: Michael Kruse Date: Tue, 19 Sep 2017 22:53:20 +0000 Subject: [PATCH] [ForwardOpTree] Allow out-of-quota in examination part of forwardTree. Computing the reaching definition in forwardTree() can take a long time if the coefficients are large. When the forwarding is carried-out (doIt==true), forwardTree() must execute entirely or not at all to get a consistent output, which means we cannot just allow out-of-quota errors to happen in the middle of the processing. We introduce the class IslQuotaScope which allows to opt-in code that is conformant and has been tested with out-of-quota events. In case of ForwardOpTree, out-of-quota is allowed during the operand tree examination, but not during the transformation. The same forwardTree() recursion is used for examination and execution, meaning that the reaching definition has already been computed in the examination tree walk and cached for reuse in the transformation tree walk. This should fix the time-out of grtestutils.ll of the asop buildbot. If the compilation still takes too long, we can reduce the max-operations allows for -polly-optree. Differential Revision: https://reviews.llvm.org/D37984 llvm-svn: 313690 --- polly/include/polly/Support/GICHelper.h | 112 ++++++++++++++++++++---- polly/lib/Transform/ForwardOpTree.cpp | 42 ++++++--- 2 files changed, 122 insertions(+), 32 deletions(-) diff --git a/polly/include/polly/Support/GICHelper.h b/polly/include/polly/Support/GICHelper.h index 682af2a3c021..a30d1d01ee57 100644 --- a/polly/include/polly/Support/GICHelper.h +++ b/polly/include/polly/Support/GICHelper.h @@ -287,6 +287,71 @@ operator<<(llvm::DiagnosticInfoOptimizationBase &OS, return OS; } +/// Scope guard for code that allows arbitrary isl function to return an error +/// if the max-operations quota exceeds. +/// +/// This allows to opt-in code sections that have known long executions times. +/// code not in a hot path can continue to assume that no unexpected error +/// occurs. +/// +/// This is typically used inside a nested IslMaxOperationsGuard scope. The +/// IslMaxOperationsGuard defines the number of allowed base operations for some +/// code, IslQuotaScope defines where it is allowed to return an error result. +class IslQuotaScope { + isl_ctx *IslCtx; + int OldOnError; + +public: + IslQuotaScope() : IslCtx(nullptr) {} + IslQuotaScope(const IslQuotaScope &) = delete; + IslQuotaScope(IslQuotaScope &&Other) + : IslCtx(Other.IslCtx), OldOnError(Other.OldOnError) { + Other.IslCtx = nullptr; + } + const IslQuotaScope &operator=(IslQuotaScope &&Other) { + std::swap(this->IslCtx, Other.IslCtx); + std::swap(this->OldOnError, Other.OldOnError); + return *this; + } + + /// Enter a quota-aware scope. + /// + /// Should not be used directly. Use IslMaxOperationsGuard::enter() instead. + explicit IslQuotaScope(isl_ctx *IslCtx, unsigned long LocalMaxOps) + : IslCtx(IslCtx) { + assert(IslCtx); + assert(isl_ctx_get_max_operations(IslCtx) == 0 && "Incorrect nesting"); + if (LocalMaxOps == 0) { + this->IslCtx = nullptr; + return; + } + + OldOnError = isl_options_get_on_error(IslCtx); + isl_options_set_on_error(IslCtx, ISL_ON_ERROR_CONTINUE); + isl_ctx_reset_error(IslCtx); + isl_ctx_set_max_operations(IslCtx, LocalMaxOps); + } + + ~IslQuotaScope() { + if (!IslCtx) + return; + + assert(isl_ctx_get_max_operations(IslCtx) > 0 && "Incorrect nesting"); + assert(isl_options_get_on_error(IslCtx) == ISL_ON_ERROR_CONTINUE && + "Incorrect nesting"); + isl_ctx_set_max_operations(IslCtx, 0); + isl_options_set_on_error(IslCtx, OldOnError); + } + + /// Return whether the current quota has exceeded. + bool hasQuotaExceeded() const { + if (!IslCtx) + return false; + + return isl_ctx_last_error(IslCtx) == isl_error_quota; + } +}; + /// Scoped limit of ISL operations. /// /// Limits the number of ISL operations during the lifetime of this object. The @@ -307,8 +372,11 @@ private: /// scope. isl_ctx *IslCtx; - /// Old OnError setting; to reset to when the scope ends. - int OldOnError; + /// Maximum number of operations for the scope. + unsigned long LocalMaxOps; + + /// When AutoEnter is enabled, holds the IslQuotaScope object. + IslQuotaScope TopLevelScope; public: /// Enter a max operations scope. @@ -316,8 +384,14 @@ public: /// @param IslCtx The ISL context to set the operations limit for. /// @param LocalMaxOps Maximum number of operations allowed in the /// scope. If set to zero, no operations limit is enforced. - IslMaxOperationsGuard(isl_ctx *IslCtx, unsigned long LocalMaxOps) - : IslCtx(IslCtx) { + /// @param AutoEnter If true, automatically enters an IslQuotaScope such + /// that isl operations may return quota errors + /// immediately. If false, only starts the operations + /// counter, but isl does not return quota errors before + /// calling enter(). + IslMaxOperationsGuard(isl_ctx *IslCtx, unsigned long LocalMaxOps, + bool AutoEnter = true) + : IslCtx(IslCtx), LocalMaxOps(LocalMaxOps) { assert(IslCtx); assert(isl_ctx_get_max_operations(IslCtx) == 0 && "Nested max operations not supported"); @@ -328,26 +402,26 @@ public: return; } - // Save previous state. - OldOnError = isl_options_get_on_error(IslCtx); - - // Activate the new setting. - isl_ctx_set_max_operations(IslCtx, LocalMaxOps); isl_ctx_reset_operations(IslCtx); - isl_options_set_on_error(IslCtx, ISL_ON_ERROR_CONTINUE); + TopLevelScope = enter(AutoEnter); } - /// Leave the max operations scope. - ~IslMaxOperationsGuard() { + /// Enter a scope that can handle out-of-quota errors. + /// + /// @param AllowReturnNull Whether the scoped code can handle out-of-quota + /// errors. If false, returns a dummy scope object that + /// does nothing. + IslQuotaScope enter(bool AllowReturnNull = true) { + return AllowReturnNull && IslCtx ? IslQuotaScope(IslCtx, LocalMaxOps) + : IslQuotaScope(); + } + + /// Return whether the current quota has exceeded. + bool hasQuotaExceeded() const { if (!IslCtx) - return; + return false; - assert(isl_options_get_on_error(IslCtx) == ISL_ON_ERROR_CONTINUE && - "Unexpected change of the on_error setting"); - - // Return to the previous error setting. - isl_ctx_set_max_operations(IslCtx, 0); - isl_options_set_on_error(IslCtx, OldOnError); + return isl_ctx_last_error(IslCtx) == isl_error_quota; } }; diff --git a/polly/lib/Transform/ForwardOpTree.cpp b/polly/lib/Transform/ForwardOpTree.cpp index 6a68581d6899..d178185e9b42 100644 --- a/polly/lib/Transform/ForwardOpTree.cpp +++ b/polly/lib/Transform/ForwardOpTree.cpp @@ -131,6 +131,9 @@ enum ForwardingDecision { /// statements. The simplification pass can clean these up. class ForwardOpTreeImpl : ZoneAlgorithm { private: + /// Scope guard to limit the number of isl operations for this pass. + IslMaxOperationsGuard &MaxOpGuard; + /// How many instructions have been copied to other statements. int NumInstructionsCopied = 0; @@ -248,8 +251,8 @@ private: } public: - ForwardOpTreeImpl(Scop *S, LoopInfo *LI) - : ZoneAlgorithm("polly-optree", S, LI) {} + ForwardOpTreeImpl(Scop *S, LoopInfo *LI, IslMaxOperationsGuard &MaxOpGuard) + : ZoneAlgorithm("polly-optree", S, LI), MaxOpGuard(MaxOpGuard) {} /// Compute the zones of known array element contents. /// @@ -260,9 +263,8 @@ public: // Check that nothing strange occurs. collectCompatibleElts(); - isl_ctx_reset_error(IslCtx.get()); { - IslMaxOperationsGuard MaxOpGuard(IslCtx.get(), MaxOps); + IslQuotaScope QuotaScope = MaxOpGuard.enter(); computeCommon(); Known = computeKnown(true, true); @@ -273,7 +275,6 @@ public: if (!Known || !Translator) { assert(isl_ctx_last_error(IslCtx.get()) == isl_error_quota); - KnownOutOfQuota++; Known = nullptr; Translator = nullptr; DEBUG(dbgs() << "Known analysis exceeded max_operations\n"); @@ -402,7 +403,8 @@ public: Loop *DefLoop, isl::map DefToTarget, bool DoIt) { // Cannot do anything without successful known analysis. - if (Known.is_null()) + if (Known.is_null() || Translator.is_null() || UseToTarget.is_null() || + DefToTarget.is_null() || MaxOpGuard.hasQuotaExceeded()) return FD_NotApplicable; LoadInst *LI = dyn_cast(Inst); @@ -445,6 +447,8 @@ public: llvm_unreachable("Shouldn't return this"); } + IslQuotaScope QuotaScope = MaxOpGuard.enter(!DoIt); + // { DomainDef[] -> ValInst[] } isl::map ExpectedVal = makeValInst(Inst, UseStmt, UseLoop); @@ -699,6 +703,8 @@ public: DefLoop = LI->getLoopFor(Inst->getParent()); if (DefToTarget.is_null() && !Known.is_null()) { + IslQuotaScope QuotaScope = MaxOpGuard.enter(!DoIt); + // { UseDomain[] -> DefDomain[] } isl::map UseToDef = computeUseToDefFlowDependency(UseStmt, DefStmt); @@ -846,16 +852,26 @@ public: releaseMemory(); LoopInfo &LI = getAnalysis().getLoopInfo(); - Impl = llvm::make_unique(&S, &LI); - if (AnalyzeKnown) { - DEBUG(dbgs() << "Prepare forwarders...\n"); - Impl->computeKnownValues(); + { + IslMaxOperationsGuard MaxOpGuard(S.getIslCtx(), MaxOps, false); + Impl = llvm::make_unique(&S, &LI, MaxOpGuard); + + if (AnalyzeKnown) { + DEBUG(dbgs() << "Prepare forwarders...\n"); + Impl->computeKnownValues(); + } + + DEBUG(dbgs() << "Forwarding operand trees...\n"); + Impl->forwardOperandTrees(); + + if (MaxOpGuard.hasQuotaExceeded()) { + DEBUG(dbgs() << "Not all operations completed because of " + "max_operations exceeded\n"); + KnownOutOfQuota++; + } } - DEBUG(dbgs() << "Forwarding operand trees...\n"); - Impl->forwardOperandTrees(); - DEBUG(dbgs() << "\nFinal Scop:\n"); DEBUG(dbgs() << S);