[Only for CI] Extensions - #2475
Draft
ThrudPrimrose wants to merge 3824 commits into
Draft
Conversation
__builtin_trap is a GCC/Clang extension lowering to ud2, so a fired guard surfaced as SIGILL and read as a bad-instruction/ISA fault -- it sent a real investigation down an AVX-512 path. std::abort is standard C++20, survives NDEBUG unlike assert(), needs no exceptions, and SIGABRT reads as deliberate. Cost is irrelevant: every site is on a failure branch. The assumption-guard dedup in assume_symbols_nonnegative recognises its own guards by searching tasklet code for the trap string, so it moves with the emission -- otherwise it would find none and re-emit every guard per run. Also drops a hardcoded interpreter path in a committed test (sys.executable).
…opagation Constant collection is a fixpoint over each control flow region, and it recursed into nested regions from inside the fixpoint loop. So every sweep of every region re-ran blockorder_topological_sort, which computes immediate dominators and branch merges, and a region nested d levels deep was re-analysed on every sweep at every enclosing level. Neither is needed. The CFG does not change while constants are being collected, so the block order is the same on every sweep. What a nested region computes is a function of its 'in' constants alone, so re-collecting it with unchanged inputs recomputes the same fixpoint. Cache the block order per region, and record the 'in' constants each nested region was last collected with so it is re-collected only when those change. The transfer and meet rules are untouched.
command_cache and precompiled_header already default true, but these two workflows are dominated by build time, so pin them rather than inherit a default that can drift.
…ol-promotion-squeezing
The recipe's last `SimplifyPass` runs BEFORE the terminal `LoopToMap` and the terminal vertical/horizontal fuse, so from that point on nothing reclaims arrays. `ArrayElimination` -- Simplify's array reclaimer -- would not have taken this shape anyway: its `_is_war_carrier` guard skips a candidate whenever the DESTINATION is read and written in the same state, which is every in-place stencil sweep. That guard is left alone. `RedundantArray` only ever redirects a transient's WRITERS into the destination, so it cannot expose a read to a later in-place write; the mirrored `RedundantSecondArray` fold -- redirecting a copy's READERS onto a WAR carrier, which is the TSVC s212 shape the guard exists for -- is deliberately not run here. It matched nothing on heat3d, jacobi_2d, seidel_2d, vadv, cavity_flow or channel_flow, and refusing costs a warning. heat3d is the case: its second sweep filled an (N-2)^3 transient that was then copied wholesale into `A[1:-1, 1:-1, 1:-1]`, and the buffer reached codegen as a heap allocation plus a full-size copy loop per timestep. `RedundantArray` (main, unchanged) removes it and lets the map write `A` directly -- transients 30 -> 29, heap buffers 1 -> 0, container-to-container copies 1 -> 0, one `new[]` and one of three `omp parallel for` regions gone from the generated code. Bit-exact against the same pipeline without the stage and against a numpy oracle. Wiring only, no new module. The slot is right after the terminal fuse and before the remat stage: fusion is the last thing that can strand a transient, the map shapes are final so the "no other reader" test is on the final graph, and a deleted buffer shortens the chains remat then walks. Four tests: the recipe carries the stage exactly once; the heat3d buffer and copy disappear (A/B against the same recipe with the stage skipped, so the test cannot go vacuous); re-entering the stage on the canonicalized SDFG is a no-op, which canonicalize needs since it is not yet a fixed point; and heat3d stays bit-exact. Three of the four fail with this commit reverted, four pass with it.
The --all-files ruff sweep deleted HAVE_ISL + is_domain_empty from wavefront_polyhedron and edmonds_karp from the graphlib flow shim. All three are re-exports reached as poly.<name> / edmondskarp.<name>, which ruff cannot see, so F401 fired; noqa marks them. The graphlib file was left as a docstring and nothing else. The two subprocess guard tests asserted only "child failed" / "marker absent", so an ImportError satisfied them -- both now require a BUILT marker first. Also drops two hardcoded developer paths from those subprocess scripts.
branch_mode changes the multidim pipeline by exactly three passes: LowerITEToFpFactor (integer ITE -> c*t + (1-c)*e), EliminateBranches (matches a ConditionalBlock) and LowerInterstateConditionalAssignments (consumes only what EliminateBranches mints). None can fire without a conditional, and neither the frontend nor base_pipeline makes one from straight-line source -- so on a branchless kernel both modes emit the same code and the fpfac phase recompiles and reruns identical C. exercises_branch_lowering reads the kernel source (recursing into the @dace.program helpers npbench pushes its conditionals into, decorators stripped), never a name list, so a kernel that gains an if/else picks the phase back up on its own. Over-broad on purpose: Compare / BoolOp / min / max / where= all count. Cross-check: the three kernels measured to emit DIFFERENT code under the two modes (s332, s481, s482) all keep their fpfac phases. 3644 -> 3238 collected (-406), 0 ids added, every removed id a *_fpfac one in the four *_simplify_multidim files. Measured A/B on 6 polybench kernels, serial, same box: 30 items/452.0s -> 22 items/389.4s (-13.9%). Also here: * Phase label is hostsimd_*, not the detected ISA. The label reaches the test ID, so an ISA-derived one renamed every SIMD case per runner and a before/after ID diff showed a rename instead of the real delta. Renames 351 ids once; count unchanged. * np/poly canonicalize corpora now uniquify sdfg.name per phase, like their simplify siblings -- the two phases shared a .dacecache dir. * xdist_group(kernel) so one kernel's phases can share the memoized _base on one worker. Needs --dist loadgroup in CI to take effect.
Both specializers cost a full recursive walk per call -- every state, every
nested SDFG, every interstate edge -- almost regardless of how many names they
carry. Callers loop, so N names cost N walks.
Add plural entry points that take a {name: value} map and walk once; the
singular forms become the one-entry case. Batching substitutes simultaneously,
which differs from the sequential loop only when a value names another key, so
_interacting detects that and falls back to the loop.
Switch the three looping callers (ConstantPropagation, canonicalize pipeline,
cloudsc benches) to one batched call.
Gate: guid-stripped serialized CloudSC SDFG is byte-identical to the
one-call-per-scalar loop at N=8.
{a: 'a'} named its own key, so the interaction guard fired and specialize_symbols
recursed on the same one-entry dict forever. A value naming its OWN key is a
degenerate substitution, identical batched or looped -- only another entry's key
counts.
Add the two tests the plural entry points were missing: batched == looped on a
three-scalar program (serialized graph and numerics), and an interacting mapping
still taking the sequential path.
…raphlib Five call sites still reached networkx directly instead of dace.graphlib: promote_constant_index_access and split_tasklets (both in the canonicalize / vectorization pipelines), vector_inference, experimental_cuda, and cuda. Add graphlib.weakly_connected_component(G, node): the weak component of a DIRECTED graph containing a node, i.e. what node_connected_component(G.to_undirected(as_view=True), node) computed at the two call sites that asked an undirected question. Exposing it this way keeps undirected graph types out of the backend protocol entirely -- rustworkx would otherwise need a whole parallel PyGraph handle class for two callers. Native on both backends, no networkx fallback. The rustworkx side deliberately scans weakly_connected_components on the PyDiGraph rather than to_undirected(), which renumbers node indices once the graph has holes from a remove_node. Result is returned in graph node order rather than as a bare set: the cuda.py caller's iteration reaches codegen and set order over id()-hashed DaCe nodes is not reproducible across processes. Give the rustworkx handle's in_edges/out_edges networkx's data= keyword. That also repairs dace/sdfg/graph.py's DiGraph.in_edges/out_edges, which pass it positionally and so raised TypeError under backend='rustworkx'. Convert VectorInferenceGraph from "is-a networkx.DiGraph" to "has-a graphlib graph" -- graphlib.DiGraph() is a backend-selecting factory, so there is no stable class left to subclass. cuda.py line 989 was calling nx.node_connected_component through the graphlib alias, which graphlib never exported -- a live AttributeError on extended that main (real networkx) does not have. Repointing it at the new wrapper restores parity rather than changing main's behaviour.
One canonicalize call on the CloudSC dwarf, compared against the un-transformed SDFG (simplify=False) on identical physical inputs under -O0 -fno-fast-math -ffp-contract=off, so every observed difference comes from the SDFG and not from the C++ compiler. Two arms: the Maps forced sequential (canonicalize's own reassociation only) and the Maps left as canonicalize produced them (adds OpenMP reduction order) -- the second is what proves the emitted parallelism is sound rather than merely compilable. The structural arm pins the end state: 234 Maps, 27 loops still sequential, and 234 '#pragma omp parallel for' in the generated host code. The last number is the one that matters -- a Map count alone does not prove parallelism reached the backend. Lives with the CloudSC corpus rather than under tests/canonicalize: that directory is auto-marked canonicalization by path, which would pull a multi-minute parse and a multi-hour canonicalize into the canonicalization workflow's 600s per-test timeout. The integration workflow gains --dist loadfile so each file's module-scoped CloudSC build is paid once instead of once per xdist worker, and its timeout goes to 7200s: measured on a loaded 16-core box the parse is 1245s and the canonicalize 4433s.
CollapseNoOpCast leads the canonicalize `clean` block. A Fortran kind coercion lands as `__out = dace.float64(__inp)` where `__out` already has that dtype; every downstream matcher reads tasklet bodies textually, and TrivialTaskletElimination's predicate is the exact string `out = in`, so it can never collapse the cast itself. Rewriting to the plain assignment first is what lets the Simplify five entries later fold the copy away. LoopToTranspose joins the `loop_to_x` lifts, gated like the other semantic lifts. Nothing else covers a hand-written permutation nest: LiftEinsum refuses a single tensor operand, and AssignmentAndCopyKernelToMemsetAndMemcpy explicitly REJECTS permutations because `_out = _in` reads the same for a copy, a broadcast and a transpose. Left unlifted it stays a strided element-wise copy. PowerOperatorExpansion joins the vectorizer's tasklet prep, before SplitTasklets. `**`/`pow`/`ipow` carry no ISA character, so a TileBinop holding one falls back to the per-lane pure loop; the unrolled product lowers to a native SIMD multiply. The tile emitter already carried the consumer for the `_out = _a * _a` shape this produces -- the producer was simply never constructed. Comments in vectorize_multi_dim, tile_binop and convert_tasklets_to_tile_ops claimed the expansion ran; corrected to what it actually does (literal integer exponent > 1 only). Also drop two things another pass demonstrably provides: BranchNormalizationPipeline -- BranchNormalization.apply_pass runs its own `while progress` fixed point over all control-flow regions recursively, which is what the outer MAX_ITERS loop existed to provide; and utils/iteration.py, whose `assert_no_lane_memlet_reads` gates on an `_iter_mask` array the multi-dim vectorizer never creates (it names them `_tile_iter_mask`), so it cannot fire.
fastgraph (rustworkx) and polyhedral (islpy) are both non-default, so their tests skip wherever the extra is absent. One job installs both and asserts they imported, so a missing extra fails loudly instead of turning the run into silent skips. py3.14, simplify on. canonicalization keeps polyhedral -- its 11 wavefront tests carry the canonicalization mark and would skip there otherwise. vectorization needs neither.
Last remaining loop over the singular specialize_symbol. Same N=6 set as the canonicalize pipeline, so the same 2.00x on that stage.
The node_connected_component defect was found by hand; this catches its whole class in one pass. dace.graphlib stands in for networkx (`from dace import graphlib as nx`), so a call site can name a symbol networkx has but graphlib never wrapped and nothing complains until that line executes. The new scan walks every module in dace/ that binds dace.graphlib and checks each attribute read against graphlib's exports. It fails on the parent commit naming exactly cuda.py:989, and passes here across 139 accesses -- that one call was the only instance in the tree. general-ci gains the fastgraph extra so tests/graphlib's rustworkx half actually runs instead of silently skipping on find_spec. No new workflow. graph.backend stays defaulted to networkx: this tests the opt-in backend, it does not ship it. Measured marginal cost 1.1 s per matrix cell (7.4 s -> 8.4 s wall), 6 cells.
…pAndReduce "Absorb" implied nesting into the second loop, which the pass explicitly does not do -- it only reorders a sibling state past it so FuseLoops sees an adjacent pair. The name also hid the pass: it was reported as the missing reorder-state-for-loop-fusion capability while sitting in the pipeline. AccumulatorToMapAndReduce lifted a GPU accumulator to buffer+Reduce so the Reduce libnode could tree-reduce. cuda.py and the experimental scope strategies now fold a device map-exit WCR with cub::BlockReduce directly under emit_tree_reductions, so the buffer was bought for nothing.
There is now ONE pass that forms perfect nests, and it does so in both
directions. Fission distributes `for i: {S1; S2}` when the statements are
independent; it is a no-op on `for i: {pre; for j: body; post}` whenever `pre`
feeds `body` -- the usual case, since that is why they share a nest -- because
the group analysis merges on any shared written container. Even when they ARE
independent, distributing yields three loops rather than the single 2-D nest a
GPU grid collapse needs. So `target='gpu'` additionally sinks the surviving
pre/post blocks into the inner loop under boundary guards, last in each fixpoint
round, giving fission first refusal.
sift_statements_into_perfect_nest keeps the matcher and the rewrite -- with the
S1 non-empty and S7 outer-axis-independence gates unchanged -- and loses its Pass
class; it is now driven through `sift_imperfect_nests`. The target policy moves
with it: the matcher is target-agnostic, `PerfectLoopNesting` decides.
The pipeline threads `target=` into the fission stage, so the GPU recipe gets the
sink and the CPU recipe is unchanged -- burying pre/post work under a
`j == <boundary>` guard would cost the outer level its sequential-fusion locality.
A WCR edge lowers to dst = dst OP src, so the destination's prior value is an input, but AccessSets classified read/write purely by edge degree and a WCR-written node with no outgoing edge came out write-only. DeadDataflowElimination does no analysis of its own -- AccessSets is its only liveness source -- so this is the one category where a read is invisible to it.
# Conflicts: # dace/transformation/passes/canonicalize/pipeline.py
# Conflicts: # .github/workflows/general-ci.yml
A numerical divergence in a 200-stage pipeline is only findable by binary search over the intermediate SDFGs, and re-running canonicalize per candidate costs 74 minutes on CloudSC. Dumps after every stage including no-ops -- a bisect needs a dense index, and 5 units rewrite the graph while reporting no change.
4 runners -> 1. Drop the {cmake, native} builder axis: native is not a CI
build path any more, and the split test parametrizes both builders itself
while native_{build,corpus}_test.py run unmarked in General Tests.
Drop the gpu-codegen job: it selected the whole readable suite's gpu-marked
tests under the experimental CUDA backend, which is exactly what the GPU
workflows' whole-tree -m gpu run already collects -- on a self-hosted gpu
runner that the GPU workflows themselves document as not serving CI.
Pin the caches the remaining path depends on (command_cache, PCH,
configure_cache) and install ninja-build, since the compdb replay only
engages when ninja is on PATH.
…imports The backslash-in-expression f-string needs Python 3.12; CI runs 3.11. The cloudsc perf files kept their pre-move import path.
Salvaged from the interrupted CloudSC bisection agent; its loop_to_map suite (46 tests incl. two new regression files) and a corpus spot-check are green. The CloudSC stage-088 bisection itself is unfinished.
…tial report) Single-entry job; report regenerable. Sweep was interrupted at 206/277 -- tables cover the completed subset only.
…axis
Each compiler now gets an OpenBLAS built by itself: the clang arms resolve `openblas
threads=openmp %clang` (links libomp) and the gcc arms `%gcc` (links libgomp), so no arm's BLAS
drags the OTHER OpenMP runtime into its process -- the co-residency that measured ~34x per region.
toolchain_env switches OPENBLAS_DIR per arm from CANON_PERF_OPENBLAS_{GCC,LLVM}, both fed by
`spack location -i`; blas_note applies the same override so the evidence line names the library
that actually produced the numbers (it was printing the process-wide fallback for every arm).
The mechanism relies on the loader, so the submit script now deliberately keeps BOTH openblas dirs
OFF LD_LIBRARY_PATH: dace links the full .so path, cmake bakes each build's dir into the kernel's
RUNPATH, and LD_LIBRARY_PATH would override RUNPATH and force one variant onto every arm.
Verified: a fresh clang-arm kernel carries the %clang prefix in its RUNPATH, and a 9-arm debug
sweep (job 4381744) records j5wfyon (libgomp) on the gcc arms and t2t72xo (libomp) on the llvm
arms, 16/16 kernels, zero errors.
llvm pin moves 22.1.7 -> 22.1.5 (the fuller +mlir build; both installed 22.1.5 have +polly), and
both doubly-installed packages get variant pins -- gcc@16.1.0 +graphite because its twin is
~graphite, which would kill the autopar arms; llvm@22.1.5 +mlir against its ~mlir twin.
emit_tree_reductions now rides the codegen axis by request: auto_optimize is measured as it ships
(legacy codegen, no tree reductions), canonicalize with tree reductions on the experimental
generator.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both full sweeps completed (4381745 loops, 4381746 array). canon-gcc reaches
7.24x over sequential C++ and 12.18x over numpy, ~2x auto_optimize, and the gain
is parallelism coverage rather than tuning.
Every GCC-arm loss traces to one of three classes, each confirmed against the
generated C++ and the responsible source site:
- lost parallelism: a codegen-inserted copy scheduled CPU_Multicore inside a
349k-trip loop (stockham_fft, 44x), a whole-array copy lowered to a
single-threaded memcpy because the size heuristic rejects symbolic counts
(va, 22x), a provably-false LoopToScan guard leaving dead parallel code, and
reduce_atomic on a thread-private scalar
- one parallel region per outer row in six 2-D nests, because the in_loop
guard in sequentialize_nested_parallel_scopes covers library nodes but not
maps (s115, 116x slower than sequential, ~100% team startup)
- the anti-dependence snapshot is a single-threaded memcpy costing 1.5x the
whole sequential kernel; ChunkAntiDependence would fix it but never matches,
because BreakAntiDependence puts the copy in a preceding state and state
fusion runs after the cpu_specialize band
A guard that merely never loses to sequential is worth +24% geomean on loops and
+16% on array. One 72-thread parallel region costs ~10us on this machine, which
is the constant behind every fixed-cost cluster in the data.
Also records three harness caveats: channel_flow times one converged iteration,
seq-cpp keeps blas=pure by design, and arm geomeans span different kernel sets.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Installing openblas for gcc@16.1.0 alongside the existing gcc@14.2.0 build made the bare `%gcc` spec ambiguous, so `spack location -i` resolved to nothing. The submit script swallowed that with `2>/dev/null || true`, which would have left CANON_PERF_OPENBLAS_GCC empty and run the gcc arms with no OPENBLAS_DIR at all -- a wrong measurement that still produces plausible numbers. Both the preflight and the submit path now pin %gcc@16.1.0 and %llvm@22.1.5, the versions the arms actually compile with, and an unresolvable spec is fatal rather than empty. Verified by debug job 4383341: gcc arms resolve to syyeqfx, llvm arms to t2t72xo, 16/16 kernels, 0 errors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
'ik,ik->i' and 'xyzk,xyzk->xyz' share every index between the two operands:
the batch indices reach the output, the rest is contracted, and neither
operand has a private index. That class is C[batch] = sum_k A[batch,k] *
B[batch,k] -- no BLAS-2/3 form expresses it. is_bmm() called it a batched
matmul anyway, so the contraction path minted a degenerate M=N=1 MatMul whose
operand views collapse under simplify into two equal 2-D shapes the MatMul
dispatch rejects (NotImplementedError). Classify it as not-a-BMM, next to the
existing 'ij,i->i' carve-out; the unbatched dot 'i,i->' keeps its BLAS path.
Three pure-path defects the reroute walks into, fixed here as well:
- a repeated operand (a, a) added a read node the dict comprehension then
dropped, leaving an isolated node that fails validation;
- the index letters named the map parameters, so a letter that matches an SDFG
symbol ('k' contracted over a [..., k] array) shadowed it and emitted the
self-referential bound k = 0:k -- zero iterations, silently wrong results;
- the accumulator init state was prepended to the SDFG rather than to the
state's own control-flow region, so an einsum inside a loop body raised
KeyError.
Fixes issues/einsum_rowdot_matmul_dispatch.md.
… gate floor Complex-typed chains>1 failed to compile: the UDRs in dace::scan::detail are invisible to unqualified lookup from the spliced multi-chain pragmas. Emit the declare-reduction at the tasklet's own block scope instead (GCC and clang both accept it with inscan). The corpus gate's constant fp64 atol=1e-11 is smaller than the scalar oracle's own rounding error at the paper preset (2.7e-11 vs float128), so it rejected a candidate 10x MORE accurate than the reference (scan_multi_5carry, canon arms only -- the arms that reproduce the oracle bit-for-bit passed). The absolute term becomes max(constant, 1e-12 * max|ref|), still elementwise; explicit atol overrides are never floored; integer gates stay exact; a non-finite reference keeps the constant (overflowing wavefront kernels would make the floor vacuous). Exact-int64 sweep over K x n x threads x seeded proves the lowering itself correct.
…ed benchmarks
Two recovery gaps: a rung tiling a parent window emitted T3*int_ceil(T2,T3) as
its bound, which the next sweep no longer recognized as T2, stalling 3-level
symbolic cascades half-collapsed (fix records Mod(span,K)==0 under the same
contract as the cascade-stride assumption); and dace.map-tiled nests were never
seen at all (pipeline runs map_roundtrip=False, matcher reads LoopRegions only)
-- now detected in-pass and round-tripped behind a probe-on-deepcopy gate that
declines if any map would come back as a loop (fires on 0/76 corpus kernels).
New corpus kernels jacobi2d_triple_tiled_{const,sym} and
heat3d_double_tiled_{const,sym}; untile/unroll tests now assert static
structure (collapse counts, one perfect chain, no leftover tile index or
remainder guard, generated-C++ loop counts, exact unroll body-copy counts),
not just values.
…join cost model moves to the specialization bands The canonical representation is the maximally parallel one; making a scope sequential again is a target decision. Copy/memset expansions now emit a parallel element map unconditionally -- a symbolic count is assumed big (only a provably-small constant keeps the libc call), and the expansion no longer reasons about its enclosing scope at all. The sequentialization that lived in canonicalize/finalize moves to cpu_specialization/SequentializeParallelScopes, the single home of the CPU fork/join cost model: it pins maps whose work per region cannot pay for a parallel region and scopes re-entered by a parallel map or a long loop (s115/s119 forked 768 teams per call; stockham_fft 349,525). The re-entry and short-loop rules live once, in libraries/standard/helper, consumed by the band. SpecializeCpuTransfers then hands sequentialized contiguous transfers their single memcpy/memset back, and runs again in codegen for the late-born explicit copies. The GPU counterpart moves to gpu_specialization/SequentializeNestedDeviceScopes, so canonical output keeps nested parallelism intact for both targets. Placed at the end of the pipeline: the verdict must read the final map shapes, and early Sequential pins would block map fusion.
…oMap guards NestedCall.add_state chained new states onto the visitor's cfg_target, which can disagree with the region owning last_state inside a loop or branch -- the interstate edge then joined a node the graph does not own. Use last_state.parent_graph. Einsum lifting and LoopToMap carry the matching guards for the shapes the corpus gate exposed.
…c GPU offload tests
Frontend minted loop iterators with explicit-None assumption kwargs, which sympy treats as distinct from omitted kwargs: two same-named symbols coexist and free_symbols/match silently give wrong answers. Omit None-valued kwargs at minting. Add opt-in validation walk and add_symbol check that raise on same-name different-assumption symbols (flag experimental.check_symbol_assumption_collisions; stays opt-in here until canonicalize assumption stamping moves to a registry).
Fusion removed the entry block and pinned start_block even when the region could derive it from its single source; BlockFusion pinned before remove_node, which clears the cached value. New helper pins only when underivable, and after removal.
LICM Python-parsed C++ tasklet code, got an empty symbol set, and hoisted tasklets out of the map defining their symbols; the resulting degenerate staging shape was then corrupted by whole-tree renames in ScalarFission and replicate_scope. Identifier-scan fallback for non-Python code, value-carrying write counts, and rename trees that stop at a different access node of the same container.
Clean-pattern passes counted scalar reuse only in other states; LoopToScan accepted carrier reads at foreign offsets; wavefront skew window widened with ISL containment check.
Bare a/b/o connectors in the sequential reduce expansion shadowed same-named parent arrays after codegen inlining; dotted structure member names reached copy/memset labels and produced invalid C++ function names.
C++ tasklet bodies lose their free symbols when rebuilt as Python AST, so such maps stay scalar; symbol definition map refuses names with WCR or multi-writer producers instead of resolving them to a stale seed.
Canonicalization stamps nonnegative on integer symbols while the rebuilt loop symbol is unassumed; match then binds the wildcard and reads per-iteration writes as invariant. Normalize same-named symbols to one instance before matching; only ever recognizes more a*i+b subsets.
Scope generation allocates arrays outside the tasklet/nested-SDFG calls that set calling_codegen, so idx helpers flushed there under the host key were re-emitted in the .cu as C++ redefinitions. Test regex updated to the 3-dim BlockReduce form with structural asserts on the register-partial protocol.
Entry canonicalize no longer short-unrolls constant-trip loops the tiler was called to widen; producer-only remainder bodies get ordering edges so topological emission drains; copy writes no longer count as supersedes in stage-global.
Assert parallel reduce entry points and reduction clauses instead of absence markers, pin the cost-model sequential default with the zero-threshold arm, wire the serialize path through explicit copies, and drop stale xfails.
Bare 'out' connector collides with caller array named 'out' after inlining.
insert_scatter_guard hard-required 1-D index arrays, crashing on ICON 2-D connectivity (edge_blk[jb,jc]). Classify a single contiguous varying dim per loop and guard that 1-D window instead; anything else stays un-lifted.
Guard states landed inside the loop's own owning sdfg (a LoopToMap-produced single-state wrapper), turning it multi-state and blocking InlineMultistateSDFG + MapCollapse from fusing the surrounding map nest, so MarkTileDims saw single-param maps below K. Place the guard in the outermost LoopRegion that natively defines the slice's fixed-dim symbols instead, leaving the inner wrapper trivial.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.