From 2acc07fb438d7c7ffaccc06da0d4b9d2ec416d24 Mon Sep 17 00:00:00 2001 From: Shikhar Jain Date: Sun, 19 Jul 2026 11:37:32 +0530 Subject: [PATCH] [Polly] Guard ISL ast gen compute out (#201859) Bounds the maximum ISL operations allowed for ISL AST Generation. Fixes: https://github.com/llvm/llvm-project/issues/201801 (cherry picked from commit 790882669323c7255cb761755bb2d37d3b344452) --- polly/include/polly/DependenceInfo.h | 12 +- polly/lib/Analysis/DependenceInfo.cpp | 61 +++++----- polly/lib/CodeGen/IslAst.cpp | 48 +++++--- polly/test/IstAstInfo/AstGenComputeOut.ll | 130 ++++++++++++++++++++++ 4 files changed, 206 insertions(+), 45 deletions(-) create mode 100644 polly/test/IstAstInfo/AstGenComputeOut.ll diff --git a/polly/include/polly/DependenceInfo.h b/polly/include/polly/DependenceInfo.h index c4d7b033e024..19730c4cd217 100644 --- a/polly/include/polly/DependenceInfo.h +++ b/polly/include/polly/DependenceInfo.h @@ -119,11 +119,13 @@ public: /// @param MinDistancePtr If not nullptr, the minimal dependence distance will /// be returned at the address of that pointer /// - /// @return Returns true, if executing parallel the outermost dimension of - /// @p Schedule is valid according to the dependences @p Deps. - bool isParallel(__isl_keep isl_union_map *Schedule, - __isl_take isl_union_map *Deps, - __isl_give isl_pw_aff **MinDistancePtr = nullptr) const; + /// @return isl::boolean::true if executing parallel the outermost dimension + /// of @p Schedule is valid according to the dependences @p Deps, + /// isl::boolean::false if it is not, and isl::boolean::error() if the + /// result could not be computed (e.g. the ISL operation quota was + /// exhausted during AST generation). + isl::boolean isKnownParallel(isl::union_map Schedule, isl::union_map Deps, + isl::pw_aff *MinDistancePtr = nullptr) const; /// Check if a new schedule is valid. /// diff --git a/polly/lib/Analysis/DependenceInfo.cpp b/polly/lib/Analysis/DependenceInfo.cpp index 0f208ec74634..e9016ebcb3d2 100644 --- a/polly/lib/Analysis/DependenceInfo.cpp +++ b/polly/lib/Analysis/DependenceInfo.cpp @@ -712,51 +712,58 @@ bool Dependences::isValidSchedule( // dimension, then the loop is parallel. The distance is zero in the current // dimension if it is a subset of a map with equal values for the current // dimension. -bool Dependences::isParallel(__isl_keep isl_union_map *Schedule, - __isl_take isl_union_map *Deps, - __isl_give isl_pw_aff **MinDistancePtr) const { - isl_set *Deltas, *Distance; - isl_map *ScheduleDeps; - unsigned Dimension; - bool IsParallel; +isl::boolean Dependences::isKnownParallel(isl::union_map Schedule, + isl::union_map Deps, + isl::pw_aff *MinDistancePtr) const { + // A null input means an upstream ISL operation failed (e.g. the operation + // quota was exhausted). We cannot compute a result, so report the error + // state rather than silently propagating the null through the computation. + if (Schedule.is_null() || Deps.is_null()) + return isl::boolean::error(); - Deps = isl_union_map_apply_range(Deps, isl_union_map_copy(Schedule)); - Deps = isl_union_map_apply_domain(Deps, isl_union_map_copy(Schedule)); + Deps = Deps.apply_range(Schedule); + Deps = Deps.apply_domain(Schedule); - if (isl_union_map_is_empty(Deps)) { - isl_union_map_free(Deps); + isl::boolean UnionMapIsEmpty = Deps.is_empty(); + if (UnionMapIsEmpty.is_error()) + return isl::boolean::error(); + if (UnionMapIsEmpty.is_true()) return true; - } - ScheduleDeps = isl_map_from_union_map(Deps); - Dimension = isl_map_dim(ScheduleDeps, isl_dim_out) - 1; + isl::map ScheduleDeps = isl::map::from_union_map(Deps); + isl::size NumberOfDimensions = ScheduleDeps.dim(isl::dim::out); + if (NumberOfDimensions.is_error()) + return isl::boolean::error(); + if (unsigned(NumberOfDimensions) == 0) + return false; + unsigned Dimension = unsigned(NumberOfDimensions) - 1; for (unsigned i = 0; i < Dimension; i++) - ScheduleDeps = isl_map_equate(ScheduleDeps, isl_dim_out, i, isl_dim_in, i); + ScheduleDeps = ScheduleDeps.equate(isl::dim::out, i, isl::dim::in, i); - Deltas = isl_map_deltas(ScheduleDeps); - Distance = isl_set_universe(isl_set_get_space(Deltas)); + isl::set Deltas = ScheduleDeps.deltas(); + isl::set Distance = isl::set::universe(Deltas.get_space()); // [0, ..., 0, +] - All zeros and last dimension larger than zero for (unsigned i = 0; i < Dimension; i++) - Distance = isl_set_fix_si(Distance, isl_dim_set, i, 0); + Distance = Distance.fix_si(isl::dim::set, i, 0); - Distance = isl_set_lower_bound_si(Distance, isl_dim_set, Dimension, 1); - Distance = isl_set_intersect(Distance, Deltas); + Distance = Distance.lower_bound_si(isl::dim::set, Dimension, 1); + Distance = Distance.intersect(Deltas); - IsParallel = isl_set_is_empty(Distance); - if (IsParallel || !MinDistancePtr) { - isl_set_free(Distance); + isl::boolean IsParallel = Distance.is_empty(); + if (IsParallel.is_error()) + return isl::boolean::error(); + if (IsParallel.is_true() || !MinDistancePtr) return IsParallel; - } - Distance = isl_set_project_out(Distance, isl_dim_set, 0, Dimension); - Distance = isl_set_coalesce(Distance); + Distance = Distance.project_out(isl::dim::set, 0, Dimension); + Distance = Distance.coalesce(); // This last step will compute a expression for the minimal value in the // distance polyhedron Distance with regards to the first (outer most) // dimension. - *MinDistancePtr = isl_pw_aff_coalesce(isl_set_dim_min(Distance, 0)); + *MinDistancePtr = Distance.dim_min(0).coalesce(); return false; } diff --git a/polly/lib/CodeGen/IslAst.cpp b/polly/lib/CodeGen/IslAst.cpp index 20b6440ebffb..500d3783772f 100644 --- a/polly/lib/CodeGen/IslAst.cpp +++ b/polly/lib/CodeGen/IslAst.cpp @@ -88,6 +88,11 @@ static cl::opt cl::desc("Print the ISL abstract syntax tree"), cl::cat(PollyCategory)); +static cl::opt + AstGenComputeout("polly-astgen-computeout", + cl::desc("Bound the AST generation by a maximal number of " + "ISL operations [0 means un-bounded]"), + cl::Hidden, cl::init(3000000), cl::cat(PollyCategory)); STATISTIC(ScopsProcessed, "Number of SCoPs processed"); STATISTIC(ScopsBeneficial, "Number of beneficial SCoPs"); STATISTIC(BeneficialAffineLoops, "Number of beneficial affine loops"); @@ -206,28 +211,30 @@ static isl_printer *cbPrintFor(__isl_take isl_printer *Printer, static bool astScheduleDimIsParallel(const isl::ast_build &Build, const Dependences *D, IslAstUserPayload *NodeInfo) { - if (!D->hasValidDependences()) + if (!D || !D->hasValidDependences()) return false; isl::union_map Schedule = Build.get_schedule(); isl::union_map Dep = D->getDependences( Dependences::TYPE_RAW | Dependences::TYPE_WAW | Dependences::TYPE_WAR); - if (!D->isParallel(Schedule.get(), Dep.release())) { + isl::boolean IsParallel = D->isKnownParallel(Schedule, Dep); + if (IsParallel.is_error()) + return false; + if (IsParallel.is_false()) { isl::union_map DepsAll = D->getDependences(Dependences::TYPE_RAW | Dependences::TYPE_WAW | Dependences::TYPE_WAR | Dependences::TYPE_TC_RED); - // TODO: We will need to change isParallel to stop the unwrapping - isl_pw_aff *MinimalDependenceDistanceIsl = nullptr; - D->isParallel(Schedule.get(), DepsAll.release(), - &MinimalDependenceDistanceIsl); - NodeInfo->MinimalDependenceDistance = - isl::manage(MinimalDependenceDistanceIsl); + isl::pw_aff MinimalDependenceDistance; + isl::boolean IsParallelWithDistance = + D->isKnownParallel(Schedule, DepsAll, &MinimalDependenceDistance); + if (IsParallelWithDistance.is_false()) + NodeInfo->MinimalDependenceDistance = MinimalDependenceDistance; return false; } isl::union_map RedDeps = D->getDependences(Dependences::TYPE_TC_RED); - if (!D->isParallel(Schedule.get(), RedDeps.release())) + if (D->isKnownParallel(Schedule, RedDeps).is_false()) NodeInfo->IsReductionParallel = true; if (!NodeInfo->IsReductionParallel) @@ -237,7 +244,7 @@ static bool astScheduleDimIsParallel(const isl::ast_build &Build, if (!MaRedPair.second) continue; isl::union_map MaRedDeps = isl::manage_copy(MaRedPair.second); - if (!D->isParallel(Schedule.get(), MaRedDeps.release())) + if (D->isKnownParallel(Schedule, MaRedDeps).is_false()) NodeInfo->BrokenReductions.insert(MaRedPair.first); } return true; @@ -317,6 +324,8 @@ astBuildAfterMark(__isl_take isl_ast_node *Node, assert(isl_ast_node_get_type(Node) == isl_ast_node_mark); AstBuildUserInfo *BuildInfo = (AstBuildUserInfo *)User; auto *Id = isl_ast_node_mark_get_id(Node); + if (!Id) + return Node; if (strcmp(isl_id_get_name(Id), "SIMD") == 0) BuildInfo->InSIMD = false; isl_id_free(Id); @@ -548,10 +557,23 @@ void IslAst::init(const Dependences &D) { } RunCondition = buildRunCondition(S, isl::manage_copy(Build)); + // Apply IslMaxOperationsGuard on the API that starts the process of AST + // generation from the schedule tree. This is to avoid a timeout when the + // schedule tree is too big and complex. - Root = isl::manage( - isl_ast_build_node_from_schedule(Build, S.getScheduleTree().release())); - walkAstForStatistics(Root); + { + IslMaxOperationsGuard MaxOpGuard(Ctx.get(), AstGenComputeout); + Root = isl::manage( + isl_ast_build_node_from_schedule(Build, S.getScheduleTree().release())); + if (MaxOpGuard.hasQuotaExceeded()) { + POLLY_DEBUG( + dbgs() << "AST generation for SCoP in function '" + << S.getFunction().getName() + << "' exceeded operation limit (operations). Skipping.\n"); + } + } + if (!Root.is_null()) + walkAstForStatistics(Root); isl_ast_build_free(Build); } diff --git a/polly/test/IstAstInfo/AstGenComputeOut.ll b/polly/test/IstAstInfo/AstGenComputeOut.ll new file mode 100644 index 000000000000..f43a9d69fe2f --- /dev/null +++ b/polly/test/IstAstInfo/AstGenComputeOut.ll @@ -0,0 +1,130 @@ +; This test checks that Polly's ISL AST generation aborts gracefully when the +; ISL operation quota (set via -polly-astgen-computeout) is exhausted, instead +; of running for an unbounded amount of time. +; +; The SCoP is constructed to be deliberately expensive for AST generation: +; - a large iteration space (the outer loop runs with trip count 65536, see +; the exit test 'icmp eq i64 %phi, 65536' in bb49), combined with +; - a long chain of conditionals (bb9, bb15, bb18, ... bb48), where one +; branch of every conditional flows into the common block bb7. +; Because bb7 is reached from all of these predecessors, its domain becomes the +; union of every reaching condition, which pushes ISL AST generation into a +; high-dimensional, blow-up search space. Without a bound this does not finish +; in reasonable time. +; This test case has the same characteristics as the one in +; PR https://github.com/llvm/llvm-project/pull/203073, but is effectively a +; "larger problem". The complexity of its structure made this test case bail +; out of the DeLICM phase, yet it previously got stuck in the ISL AST +; generation phase. +; +; The operation limit of polly-astgen-computeout is set to 1 -- the smallest +; value that still arms the guard (0 means unbounded) -- so the quota trips +; as early as possible and the test does not depend on an arbitrary tuned cutoff value. + +; RUN: opt %loadNPMPolly %s -passes='polly-custom' -polly-process-unprofitable \ +; RUN: -polly-astgen-computeout=1 -debug-only=polly-ast \ +; RUN: -disable-output 2>&1 | FileCheck %s + +define void @eggs(i32 %arg) { +bb: + br label %bb1 + +bb1: ; preds = %bb49, %bb + %phi = phi i64 [ 1, %bb ], [ %add, %bb49 ] + br i1 true, label %bb2, label %bb5 + +bb2: ; preds = %bb1 + %icmp = icmp eq i32 %arg, 0 + br i1 %icmp, label %bb3, label %bb5 + +bb3: ; preds = %bb2 + %trunc = trunc i64 %phi to i32 + %and = and i32 %trunc, 1 + %icmp4 = icmp eq i32 %and, 0 + br i1 %icmp4, label %bb9, label %bb5 + +bb5: ; preds = %bb3, %bb2, %bb1 + %phi6 = phi i8 [ 0, %bb2 ], [ 1, %bb1 ], [ 0, %bb3 ] + br label %bb7 + +bb7: ; preds = %bb48, %bb45, %bb42, %bb39, %bb36, %bb33, %bb30, %bb27, %bb24, %bb21, %bb18, %bb15, %bb9, %bb5 + %phi8 = phi i8 [ 1, %bb5 ], [ 0, %bb9 ], [ 0, %bb48 ], [ 0, %bb15 ], [ 0, %bb18 ], [ 0, %bb21 ], [ 0, %bb24 ], [ 0, %bb27 ], [ 0, %bb30 ], [ 0, %bb33 ], [ 0, %bb36 ], [ 0, %bb39 ], [ 0, %bb42 ], [ 0, %bb45 ] + store i8 0, ptr null, align 1 + br label %bb49 + +bb9: ; preds = %bb3 + %and10 = and i32 %trunc, 2 + %icmp11 = icmp eq i32 %and10, 0 + %and12 = and i32 %trunc, 4 + %icmp13 = icmp eq i32 %and12, 0 + %and14 = and i1 %icmp11, %icmp13 + br i1 %and14, label %bb15, label %bb7 + +bb15: ; preds = %bb9 + %and16 = and i32 %trunc, 8 + %icmp17 = icmp eq i32 %and16, 0 + br i1 %icmp17, label %bb18, label %bb7 + +bb18: ; preds = %bb15 + %and19 = and i32 %trunc, 16 + %icmp20 = icmp eq i32 %and19, 0 + br i1 %icmp20, label %bb21, label %bb7 + +bb21: ; preds = %bb18 + %and22 = and i32 %trunc, 32 + %icmp23 = icmp eq i32 %and22, 0 + br i1 %icmp23, label %bb24, label %bb7 + +bb24: ; preds = %bb21 + %and25 = and i32 %trunc, 64 + %icmp26 = icmp eq i32 %and25, 0 + br i1 %icmp26, label %bb27, label %bb7 + +bb27: ; preds = %bb24 + %and28 = and i32 %trunc, 128 + %icmp29 = icmp eq i32 %and28, 0 + br i1 %icmp29, label %bb30, label %bb7 + +bb30: ; preds = %bb27 + %and31 = and i32 %trunc, 256 + %icmp32 = icmp eq i32 %and31, 0 + br i1 %icmp32, label %bb33, label %bb7 + +bb33: ; preds = %bb30 + %and34 = and i32 %trunc, 512 + %icmp35 = icmp eq i32 %and34, 0 + br i1 %icmp35, label %bb36, label %bb7 + +bb36: ; preds = %bb33 + %and37 = and i32 %trunc, 1024 + %icmp38 = icmp eq i32 %and37, 0 + br i1 %icmp38, label %bb39, label %bb7 + +bb39: ; preds = %bb36 + %and40 = and i32 %trunc, 2048 + %icmp41 = icmp eq i32 %and40, 0 + br i1 %icmp41, label %bb42, label %bb7 + +bb42: ; preds = %bb39 + %and43 = and i32 %trunc, 4096 + %icmp44 = icmp eq i32 %and43, 0 + br i1 %icmp44, label %bb45, label %bb7 + +bb45: ; preds = %bb42 + %and46 = and i32 %trunc, 8192 + %icmp47 = icmp eq i32 %and46, 0 + br i1 %icmp47, label %bb48, label %bb7 + +bb48: ; preds = %bb45 + br i1 false, label %bb49, label %bb7 + +bb49: ; preds = %bb48, %bb7 + %add = add i64 %phi, 1 + %icmp50 = icmp eq i64 %phi, 65536 + br i1 %icmp50, label %bb51, label %bb1 + +bb51: ; preds = %bb49 + ret void +} + +; CHECK: AST generation for SCoP in function 'eggs' exceeded operation limit (operations). Skipping.