[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
This commit is contained in:
parent
446a84a7c6
commit
7908826693
4 changed files with 206 additions and 45 deletions
|
|
@ -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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,11 @@ static cl::opt<bool>
|
|||
cl::desc("Print the ISL abstract syntax tree"),
|
||||
cl::cat(PollyCategory));
|
||||
|
||||
static cl::opt<unsigned long>
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
130
polly/test/IstAstInfo/AstGenComputeOut.ll
Normal file
130
polly/test/IstAstInfo/AstGenComputeOut.ll
Normal file
|
|
@ -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<ast>' -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.
|
||||
Loading…
Reference in a new issue