IslSimplifier — Predicate / Domain Gist
All symbols on this page apply to neuronx_cc
2.24.5133.0+58f8de22(cp310 wheel). The nativeislwrapper::*code lives inneuronxcc/starfish/lib/libBIR.so; the Python driver lives inneuronxcc/starfish/penguin/transforms/SimplifyPredicates.cpython-310-x86_64-linux-gnu.so. Other versions/ABIs differ.
Abstract
IslSimplifier is Penguin's polyhedral guard-and-domain simplifier. Given a predicated loop-nest statement, it pushes the statement's iteration domain and its conjunction of affine guard predicates into the Integer Set Library (ISL), runs an ISL gist against the loop-nest/parameter context to drop every predicate that the loop bounds already imply, and either re-installs the surviving (smaller) predicate set on the instruction or rewrites the loop bounds outright. It is the pass that turns a runtime mask like if (0 <= i < 10 && i < 7) into a tightened loop for i in 0..7 with no guard at all.
The single most important fact for a reimplementer is that the predicate/domain simplifier exists in two coexisting layers: a native C++ class islwrapper::IslSimplifier in libBIR.so — the authoritative implementation, carrying parameter-typed symbols — and a Cython driver/wrapper module neuronxcc/starfish/penguin/IslSimplifier.cpython-310-x86_64-linux-gnu.so, which supplies the CPython C-API glue that drives it. The native methods carry fully demangled, type-bearing symbols (simplify_predicate(bir::Instruction*), gist_convex_hull(bir::Instruction*), predicates_over_loopnest(bir::Instruction*, isl::set), predicates_over_loopnest_convex_hull(bir::Instruction*, isl::set, bool), shrink_domain(bir::InstLoop*, isl::set)). The actual ISL↔Penguin codecs live one class up the hierarchy, in the base islwrapper::IntegerSetAnalysis (also native C++ in libBIR.so), whose methods predicated_domain, convex_hull, apply_predicates, enumerate_affine_predicates, exract_cst_bounds, and simplify are likewise confirmed by mangled symbol. The Python pass SimplifyPredicates is only the driver that instantiates IslSimplifier and calls simplify_predicate / shrink_domain on predicated instructions.
Conceptually this is the ISL-gist pattern familiar from Polly and the isl tutorial: simplify set S given that context set C holds. gist(S, C) returns a set equal to S inside C but with all constraints already entailed by C stripped. Here S is domain ∩ predicates, C is the convex hull of the loop-nest domain (or, in the shrink path, a rebuilt loop "box"), and what survives the gist is exactly the non-redundant guards. The page covers both paths: the predicate path (gist_convex_hull → predicates_over_loopnest → predicates_over_loopnest_convex_hull) and the domain-shrink path (shrink_domain / shrink_domain_convex_hull).
For reimplementation, the contract is:
- The class split:
IslSimplifier(orchestration + statistics) overIntegerSetAnalysis(the AffinePredicate↔isl::setcodecs and domain construction). The simplifier never calls ISL directly except through the base-class helpers and a handful of stockisl::setverbs. - The two-pass predicate-simplify control flow in
gist_convex_hull, including theis_approxfilter and theNone/empty short-circuits. - The exact ISL op-set actually invoked, and where each runs:
gist(twice),convex_hull(twice),coalesce(once),compute_divs/remove_divs(over-approx only),is_empty,get_basic_sets,get_constraints,get_space,intersect_range, andBasicSet.universe. - The shrink-path domain narrowing: rebuild a canonical box, gist+coalesce, intersect predicates, re-extract per-axis
lb/ub/stride/tripcount, and clone the loop nest.
| Native class | islwrapper::IslSimplifier (in libBIR.so) |
| Base class | islwrapper::IntegerSetAnalysis (the codecs / domain builders) |
| Public entry | simplify_predicate(bir::Instruction*) @ thunk 0x17b250 |
| Predicate worker | predicates_over_loopnest_convex_hull(bir::Instruction*, isl::set, bool) @ thunk 0x181650 |
| Domain-shrink worker | shrink_domain(bir::InstLoop*, isl::set) @ thunk 0x17b8c0 |
| Python driver | SimplifyPredicates.cpython-310 (instantiates IslSimplifier) |
| Core ISL op | isl::set::gist(context) — redundant-constraint elimination |
| Statistics | eliminated_predicates, eliminated_loops, eliminated_iterations, strided_axes |
The Two Layers, Concretely
Both implementation layers ship, and a reimplementer needs to know which one owns what.
The native layer is islwrapper::IslSimplifier in libBIR.so. All five methods are present as parameter-typed mangled symbols, so the operand types (bir::Instruction*, isl::set, bir::InstLoop*) are authoritative and are what the tables on this page cite.
The Cython layer is neuronxcc/starfish/penguin/IslSimplifier.cpython-310-x86_64-linux-gnu.so: a top-level .so plus 64 per-function sidecars under the qualname prefix __pyx_…_9neuronxcc_8starfish_7penguin_13IslSimplifier. Its __Pyx_CreateStringTabAndInitStrings table interns the Python-visible method and counter names — gist_convex_hull (×4), eliminated_predicates / eliminated_loops / eliminated_iterations (×2 each), strided_axes (×3), simplify_predicate (×4), shrink_domain (×14). Every address on this page marked "(cython)" resolves inside this module: predicate_access_range @0x11600 → …13IslSimplifier_5predicate_access_rang_0x11600, shrink_domain_convex_hull @0x12c40 → …13IslSimplifier_1shrink_domain_convex__0x12c40, and shrink_domain @0x18f20.
The relationship is glue-over-implementation: Cython emits C-API sequences that drive the native methods, so a control-flow reconstruction from either side describes the same algorithm.
GOTCHA — the Cython qualnames (
gist_convex_hull,eliminated_predicates, …) live only in that module's Cython string table, not in the general string pools, so a corpus-wide string grep for them comes back empty. That is a search artifact, not evidence the names are absent.
The Class Hierarchy and Data Flow
Purpose
IslSimplifier derives from IntegerSetAnalysis. The split is the standard "policy over mechanism" division: the base class owns every conversion between Penguin's IR (loop axes, quasi-affine expressions, AffinePredicates) and ISL objects (isl::set, isl::aff, isl::constraint, isl::local_space); the derived class owns the simplification policy — which gist to run, when to over-approximate, what to do with the result, and the running statistics. A reimplementer should build IntegerSetAnalysis first; IslSimplifier is a thin strategy on top.
Base class islwrapper::IntegerSetAnalysis — the codecs
Every method below is a mangled symbol in libBIR.so; the addresses are export-thunk addresses in the cp310 build. The bodies themselves are imported — these per-symbol files are 6-byte PLT jumps — so the signatures are exact while the line-level bodies are not individually traced here.
| Method | Signature (demangled) | Role | Confidence |
|---|---|---|---|
predicated_domain | (bir::Instruction*, isl::set) @ 0x17e4d0 | forward: build domain ∩ predicates as an isl::set | CERTAIN |
combine_predicated_domain | (bir::Instruction*, isl::set) @ 0x17e8b0 | merge a predicated domain into an existing set | CERTAIN |
convex_hull | (std::vector<bir::LoopAxis*>&, isl::space) @ 0x17b5a0 | build the gist context: convex hull of the loop-nest domain in a space | CERTAIN |
enumerate_affine_predicates | (pelican::PelicanContext*, …) @ 0x17c400 | back: isl::constraint list → [AffinePredicate] | CERTAIN |
enumerate_predicates | (isl::local_space, std::vector<bir::…>&) @ 0x17c680 | lower-level constraint→predicate enumeration | CERTAIN |
apply_predicates | (pelican::PelicanContext*, vector<bir::Instruction*>&, vector<isl::constraint>&, vector<bir::LoopAxis*>, bool) @ 0x175180 | intersect a box/set with a predicate set (narrowing) | CERTAIN |
exract_cst_bounds | (isl::set, int, int, int, int) @ 0x17a180 | read tightened constant lb/ub per axis | CERTAIN |
extract_cst_floor / extract_cst_ceil / extract_cst_val | (isl::pw_aff) @ 0x17e1a0 / 0x176ab0 / 0x17bf30 | scalar bound extraction from piecewise-affines | CERTAIN |
add_loop_bounds | (isl::set, vector<bir::LoopAxis*>&) @ 0x17a050; (isl::basic_set, vector<Bound*>&) @ 0x175630 | inject IV bounds into a (basic) set | CERTAIN |
intersect_bound | (isl::set, int,int,int,int, vector<bir::LoopAxis*>&) @ 0x17ee50 | clip a set on one axis | CERTAIN |
build_aff / build_linear_expr / quasi_affine_expr | over bir::QuasiAffineExpr + isl::aff | Penguin expr → isl::aff | CERTAIN |
domain / domain_space / create_domain_space | (bir::Instruction*, …) | build the iteration isl::set / its isl::space | CERTAIN |
simplify | (isl::set) @ 0x17e0c0 | generic set simplify | CERTAIN |
extract_int | (isl::val) @ 0x17d450 | isl::val → C++ int | CERTAIN |
NOTE — the base-class name is
exract_cst_bounds— the binary symbol is misspelled, missing the secondt. A reimplementer matching symbols must use the misspelled form, not the naturalextract_cst_bounds. The neighboursextract_cst_floor/extract_cst_ceil/extract_cst_valare spelled correctly, so this is a one-off typo baked into the shippedlibBIR.so.
NOTE —
pelican::PelicanContext*threads throughenumerate_affine_predicatesandapply_predicates. Pelican is Penguin's compilation-context/codename layer; it resolves IV and SPMD-parameter names when turning anisl::constraintback into a namedAffinePredicate. The Cython-level analysis calls thiscu(compilation unit) withcu.spmd_ids; the native operand is thePelicanContext.
Top-level data flow (predicate simplification)
inst.predicates : [AffinePredicate] (exact only — is_approx dropped)
| IntegerSetAnalysis::predicated_domain(inst, domain) [forward: AP -> isl::set]
v
isl::set S = domain ∩ predicates
| context C = IntegerSetAnalysis::convex_hull(loop_axes, S.get_space())
v
S = S.gist(C) <<< ISL GIST: drop guards implied by C
| [+ S.compute_divs().remove_divs() if overapproximate]
v
bsets = S.get_basic_sets(); if len==1: S = bsets[0]
| cons = S.get_constraints()
v
[isl::constraint] --enumerate_affine_predicates(cons, PelicanContext, spmd_ids)-->
v
[AffinePredicate] (or None if any element infeasible -> empty domain)
| inst.resetPredicates(*R) ; inst.addPredicate(*R)
v
inst.predicates SMALLER -> IslSimplifier::eliminated_predicates += dropped
simplify_predicate — Public Entry
Purpose
The thin public driver. It records the predicate count, delegates the entire ISL pipeline to gist_convex_hull (which mutates inst's predicate list in place), then bumps the class statistic by the number of guards dropped and returns whether the count shrank — so a caller can iterate to a fixpoint or skip downstream rework.
Algorithm
bool IslSimplifier::simplify_predicate(bir::Instruction *inst): // sym @0x17b250
n = len(inst->predicates()); // count before
if (this->gist_convex_hull(inst)): // delegates the whole ISL pipeline
// gist_convex_hull mutated inst->predicates() in place
IslSimplifier::eliminated_predicates += (n - len(inst->predicates())); // CLASS stat
return n != len(inst->predicates()); // "did the guard count change?"
NOTE —
eliminated_predicatesis a class-level (static) accumulator, surfaced by Penguin'sStatisticssubsystem as the human-readable counter "Number of predicate eliminated". It tallies across every instruction in a compile. The companion shrink-path counters (eliminated_loops,eliminated_iterations,strided_axes) are the same shape.
gist_convex_hull — Predicate-Simplify Orchestrator
Purpose
The orchestrator (not itself an ISL caller). It (a) drops approximate predicates, (b) asks predicates_over_loopnest for the simplified predicate set, (c) wipes and re-installs them, then (d) runs a second pass against the now-tighter context and adds those. A None at either pass is the no-op / empty-domain short-circuit.
Algorithm
bool IslSimplifier::gist_convex_hull(bir::Instruction *inst): // sym @0x17c770
if (!inst->is_predicated()): // py line 157: unpredicated -> nothing
return false;
// keep EXACT affine predicates only; an is_approx guard is an over/under-
// approximation, not exact affine, and would poison the gist.
preds = [p for p in inst->predicates() if !p.is_approx]; // line 161
domain = this->domain; // line 162 (self.domain)
R1 = this->predicates_over_loopnest(inst, domain, preds, ...); // line 164 PASS 1
if (R1 == None):
return false; // nothing simplified
inst->resetPredicates(*R1); // line 168: clear, re-install simplified set
R2 = this->predicates_over_loopnest(inst, ...); // line 169 PASS 2
if (R2 == None):
return true; // second pass yielded nothing extra
inst->addPredicate(*R2); // line 173: add the delta
return true;
QUIRK — the two-pass structure is "simplify, commit, re-simplify against the now-tighter context, commit the delta." Pass 1's
resetPredicatesmakes the instruction's guard set strictly smaller; pass 2 then gists that tighter context, which can expose still more redundancy.R1==Nonereturnsfalse(truly nothing to do);R2==Nonereturnstrue(pass 1 already changed something). A reimplementer that runs a single pass will leave easy second-order redundancy on the table.
GOTCHA — the
is_approxfilter is silent and total. Any predicate the front-end flagged as an over- or under-approximation is excluded from the conjunction fed to ISL, so the gist operates only on exact affine constraints and never rewrites an approximate guard. If yourAffinePredicatemodel lacks anis_approxbit, you will feed non-exact constraints intogistand produce wrong (unsound) simplifications.
predicates_over_loopnest — Forward-Build Thunk
Purpose
A two-step thunk: build the isl::set for domain ∩ predicates, then hand it to the convex-hull worker. The forward conversion lives in the base class (predicated_domain); this method just sequences it and short-circuits on an empty build.
Algorithm
List? IslSimplifier::predicates_over_loopnest( // sym @0x17a000
bir::Instruction *inst, isl::set domain, preds,
bool overapproximate, bool approximate_predicates):
pd = this->predicated_domain(domain, preds); // line 116: base-class forward codec
if (pd == None): // domain ∩ preds empty/unbuildable
return None;
return this->predicates_over_loopnest_convex_hull( // lines 120-121
inst, pd, overapproximate, approximate_predicates);
The overapproximate / approximate_predicates flags steer the worker: the first allows a convex over-approximation (the compute_divs/remove_divs div-elimination, which may weaken a guard but makes it div-free); the second governs whether predicates flagged approximate are retained. predicated_domain itself — the AffinePredicate→isl::set direction — is IntegerSetAnalysis::predicated_domain(bir::Instruction*, isl::set) (@0x17e4d0), out of this method's hot path.
predicates_over_loopnest_convex_hull — The ISL Worker
Purpose
The only method that drives ISL set algebra directly. It builds the gist context (convex hull of the loop-nest domain in the predicate's space), gists the predicated domain against it (the redundant-guard eliminator), optionally over-approximates away existential divs, decomposes to a single convex basic set, extracts the surviving constraints, and converts them back to AffinePredicates.
Algorithm
List? IslSimplifier::predicates_over_loopnest_convex_hull( // sym @0x181650
bir::Instruction *inst, isl::set domain,
bool overapproximate, bool approximate_predicates):
ln = this->loopnest(inst); // line 125: loop-nest of this stmt
sp = domain.get_space(); // line 126: gist target space
ctx = this->convex_hull(sp, ...); // line 127: GIST CONTEXT
// = convex hull of loop-nest domain
// --- ISL GIST: drop every constraint of `domain` already implied by `ctx` ---
domain = this->try([&]{ return domain.gist(ctx); }); // line 129 (lambda4)
if (overapproximate): // line 133
// OVER-APPROX: materialise existential (floor/mod) divs, then project them
// out -> a div-free, possibly weaker, affine set.
domain = this->try([&]{ // line 134 (lambda5)
return domain.compute_divs().remove_divs(); });
bsets = domain.get_basic_sets(); // line 140: decompose to convex pieces
if (len(bsets) == 1): // after gist+hull, expect ONE piece
domain = bsets[0]; // take it so get_constraints is 1 conj
cons = domain.get_constraints(); // line 141: isl::constraint list
if (cons): // line 143
// back-convert: isl::constraint -> Penguin AffinePredicate, named via PelicanContext
result = enumerate_affine_predicates( // lines 147-148
cons, /*cu=*/this->cu, /*spmd_ids=*/this->cu->spmd_ids, ...);
result = list(result);
if (any(p is None for p in result)): // line 151: a None constraint = infeasible
return None; // -> empty/unsat domain
return result; // the simplified AffinePredicate list
return None; // cons empty: nothing to simplify
The ISL operations, and exactly where each runs
| ISL op | Site | Purpose | Confidence |
|---|---|---|---|
convex_hull(space) | worker @127 | build the gist context (hull of loop-nest domain in predicate space) | HIGH |
set::gist(context) | worker @129 | the core simplifier — strip guards entailed by the loop/param context | HIGH |
compute_divs() → remove_divs() | worker @134 (overapprox only) | materialise then project existential floor/mod divs → div-free over-approx | HIGH |
get_basic_sets() | worker @140 | decompose; expect a single convex piece after gist+hull | HIGH |
get_constraints() | worker @141 | extract the isl::constraint list of the convex result | HIGH |
get_space() | worker @126 | the space for the context / universe | HIGH |
set::gist(box) → coalesce() | shrink_domain @51 | second, distinct gist against a rebuilt box, then fuse pieces | HIGH |
set::convex_hull() | shrink_domain_convex_hull @24 | convexify a non-convex union before shrink | HIGH |
is_empty() | shrink_domain @32 | early-out: drop a fully-empty (gisted) domain | HIGH |
BasicSet::universe(space) | shrink_domain @47 | fresh full set to rebuild the loop box | HIGH |
intersect_range(valid) | predicate_access_range @97 | clip an access map's range to the valid address window | HIGH |
QUIRK —
gistappears twice in the module (predicate path @129 against the convex hull, shrink path @51 against the rebuilt box), andcoalesceappears exactly once (chained on the shrink-path gist).detect_equalities— the canonical ISL normalizer one would expect before reading constraints — is never called by name; equality detection is folded into ISL's ownget_constraintsnormalization. A reimplementer porting to a freshislbinding does not need an explicitdetect_equalitiescall to match behavior.
GOTCHA — every ISL call is wrapped in
this->try(λ), an exception-guarded apply. ISL operations (gist,convex_hull,compute_divs) can throw on resource-exhaustion or malformed sets;trycatches and yields a "skip this simplification" signal rather than aborting the compile. Omitting the guard turns a transient ISL failure into a crash. Thetryresult replacesdomain, so a caught failure must leavedomainin a usable (e.g. unchanged) state.
NOTE — the single-basic-set fast path (
if len(bsets)==1: domain = bsets[0]) is not just an optimization —get_constraintson a multi-piece union does not yield a single conjunction. Aftergistagainst a convex context the result is usually one convex basic set, so the common case is a clean single conjunction. When it is not one piece, the code reads constraints from the whole set; theany(p is None)check then guards against an infeasible component collapsing the predicate list toNone.
predicate_access_range — Access-Range-Driven Predicates
Purpose
The access-range variant of the predicate path. Instead of starting from the instruction's iteration domain, it starts from the iteration points whose tensor address lands inside a caller-supplied valid_range, derives the guard that exactly bounds the in-range accesses (e.g. masking an out-of-bounds tail), and installs it.
Algorithm
void IslSimplifier::predicate_access_range( // sym @0x11600 (cython)
bir::Instruction *inst, isl::set valid_range):
acc = this->access(inst); // line 96: access UnionMap { sN[ivs] -> tensor[addr] }
rng = acc.intersect_range(valid_range); // line 97: clip RANGE to the legal address window
R = this->predicates_over_loopnest( // line 99: simplify over the access-restricted
inst, /*domain=*/rng.domain(), ...); // DOMAIN (= access-range pre-image)
if (R != None):
inst->resetPredicates(*R); // ~line 100
inst->addPredicate(*R); // ~line 101
intersect_range is stock islpy/isl::map: it restricts the access relation's range to valid_range; the domain of the restricted map is then the set of iteration points that touch only valid addresses, which becomes the predicate domain. Net effect: the simplifier derives the exact loop-nest guard that keeps accesses inside the tensor's legal index window.
shrink_domain / shrink_domain_convex_hull — Domain Narrowing
Purpose
The domain-narrowing path (vs. predicate-simplify). Where the predicate path leaves a runtime guard, this path rewrites the loop bounds so the guard disappears. Native symbol: shrink_domain(bir::InstLoop*, isl::set) — note it takes a bir::InstLoop*, the loop instruction, not a generic Instruction*.
Algorithm — convex-hull pre-step
Loopnest IslSimplifier::shrink_domain_convex_hull( // sym @0x12c40 (cython)
bir::InstLoop *bottom_loop, isl::set domain, bool approximate_predicates):
domain = this->try([&]{ return domain.convex_hull(); }); // line 24: convexify union
return this->shrink_domain(bottom_loop, domain, // line 28
/*approximate_predicates=*/approximate_predicates);
Algorithm — the narrowing worker
Loopnest IslSimplifier::shrink_domain( // sym @0x17b8c0
bir::InstLoop *bottom_loop, isl::set domain,
fixed_axes, bool approximate_predicates):
axis = bottom_loop->front; // top axis of the nest
if (this->try([&]{ return domain.is_empty(); })): // line 32: empty -> bail
return ...;
lbs, ubs = exract_cst_bounds(domain, ...); // const lb/ub per axis (base-class codec)
box = isl::BasicSet::universe(space); // line 47: fresh full set over the IV space
box = box.add_loop_bounds(...) // line 48: inject loop-nest IV bounds
.add_param_bounds(...); // line 49: inject spmd/param bounds
// SECOND gist (distinct from the predicate path) + coalesce into minimal-piece union
domain = this->try([&]{ // line 51 (lambda2)
return domain.gist(box).coalesce(); });
narrowed = apply_predicates(box, predicates, ...); // line 65: intersect predicates -> narrow
new_axes = [];
for (old, (lo, hi) in zip(axes, zip(lbs, ubs))): // line 63 (per-axis genexpr)
a = shallowClone(old); // IRCloner.shallowClone
a.lb = lo; a.ub = hi; a.stride = ...; // tightened bounds from exract_cst_bounds
if (a.tripcount < old.tripcount):
IslSimplifier::eliminated_iterations += (old.tripcount - a.tripcount);
if (a.is_strided):
IslSimplifier::strided_axes += 1;
new_axes.append(a);
if (loop fully eliminated):
IslSimplifier::eliminated_loops += 1;
return new_loopnest(new_axes); // rebuilt, tighter nest
QUIRK —
shrink_domainrebuilds a fresh canonical box (BasicSet.universe+add_loop_bounds+add_param_bounds) and gists the domain against that, rather than reusing the predicate-path convex hull. The reason: the predicate path's context is the convex hull of the original domain (good for stripping redundant guards), but the shrink path needs a box in axis-canonical form so thatexract_cst_boundscan read one tightlb/ub/strideper axis. Gisting against a hull would leave the bounds entangled across axes; gisting against the box keeps them separable. This is why there are two distinct gists in the module, and whycoalesce(which only makes sense on the box-gisted, multi-piece-prone result) appears only here.
NOTE —
fixed_axespins axes that must not be reshaped (e.g. an axis whose extent is fixed by a downstream consumer); those are cloned with their original bounds.approximate_predicatesthreads down toapply_predicatesto decide whether approximate guards participate in the narrowing intersection.
This is the mechanism that converts a predicate such as 0 <= i < 10 ∧ i < 7 into an actual tightened loop bound for i in 0..7 with no residual runtime mask: gist the domain against the box, read the now-[0,7) axis bound, and clone the axis with the smaller ub. Axes whose tripcount drops feed eliminated_iterations; whole loops that vanish bump eliminated_loops; axes that become strided bump strided_axes.
The isl↔AffinePredicate Conversion
The forward and back conversions both live in the base IntegerSetAnalysis, and both are confirmed native symbols.
Forward (AffinePredicate → isl::set): predicated_domain(bir::Instruction*, isl::set) @0x17e4d0 builds domain ∩ (conjunction of exact predicates). The per-expression piece is build_aff / quasi_affine_expr / build_linear_expr turning a bir::QuasiAffineExpr into an isl::aff, and add_loop_bounds injecting the IV ranges.
Back (isl::constraint → AffinePredicate): enumerate_affine_predicates(pelican::PelicanContext*, …) @0x17c400 walks each surviving isl::constraint — an affine inequality a0 + Σ ai·xi ≥ 0 over loop IVs xi and SPMD-grid parameters — and emits one Penguin AffinePredicate. The PelicanContext resolves IV/parameter names; the SPMD-grid parameter dimensions (spmd_ids) supply the set's parameter space. A None element signals an infeasible constraint set (empty domain), which propagates up as the whole-pass None short-circuit.
GOTCHA — the back-conversion is where soundness lives.
gistcan legitimately enlarge a non-convex predicate union when over-approximating (theconvex_hullcontext andcompute_divs/remove_divsboth can weaken the set). The result is therefore a sound superset of the original guard — it never rejects an iteration the original would have admitted — but it may admit iterations the original guarded out, relying on the loop bounds to exclude them. A reimplementer must keep theoverapproximateflag honest: it is only valid where a weaker (superset) guard is acceptable because the surrounding loop/param context already excludes the difference.
Statistics
Four class-level (static) counters, registered with Penguin's Statistics subsystem and surfaced as human-readable lines:
| Counter | Display string | Bumped in | Confidence |
|---|---|---|---|
eliminated_predicates | "Number of predicate eliminated" | simplify_predicate (§ public entry) | HIGH |
eliminated_loops | "Number of loops eliminated" | shrink_domain (fully removed axis) | HIGH |
eliminated_iterations | "Number of iteration eliminated" | shrink_domain (tripcount drop) | HIGH |
strided_axes | "Number of strided axes" | shrink_domain (axis becomes strided) | HIGH |
The counters themselves are pinned by the Cython string table and by the consistent bump pattern at each site. The display wording in column two is [INFERRED]: those exact phrasings are not recoverable from the general string pools, and the superficially-similar strings that do appear elsewhere (in DeadStoreElimination, for instance) belong to unrelated counters. Match on the counter name, not on the rendered text.
Related Components
| Component | Relationship |
|---|---|
islwrapper::IntegerSetAnalysis (libBIR.so) | Base class — owns every AffinePredicate↔isl codec and domain builder that IslSimplifier orchestrates |
SimplifyPredicates.cpython-310 | Python pass that instantiates IslSimplifier and calls simplify_predicate / shrink_domain on predicated instructions |
TongaIslSimplifier / TongaSimplifyPredicate | Tonga-target-specialized siblings (access→address rewrite) |
pelican::PelicanContext | name/parameter resolution context threaded through the back-conversion |
Cross-References
- TongaIslSimplifier — the Tonga-specialized sibling that does access→address rewriting on top of the same ISL machinery
- ISL Dependence Graph — how Penguin builds the
isl::mapaccess relations and iteration domains this pass consumes - ISL Schedule-Tree Legality — the validation-only ISL use (the legality sibling of this transform-driving use)
- Predicate Bridge — the
AffinePredicateIR model and the bridge between Penguin guards and ISL constraints