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);