[Only for CI] Extensions - #2475
Draft
ThrudPrimrose wants to merge 4743 commits into
Draft
Conversation
ThrudPrimrose
force-pushed
the
extended
branch
from
August 12, 2026 11:16
269d9cf to
f9c1368
Compare
ThrudPrimrose
added a commit
that referenced
this pull request
Aug 20, 2026
…skip_gpu_outputs; restore OMP thread default; fix extras - codegen.py: restore scalar_emission conditional form; pass skip_gpu_outputs=True so GPU map outputs are not scalarized (the unconditional preserve_abi run in PR #2475 introduced broad CPU regressions). - conftest.py: do not force OMP_NUM_THREADS=1; keep setdefault so callers control it. - pyproject.toml: re-add fastgraph/polyhedral extras and testing z3-solver/matplotlib that were dropped when moving from setup.py.
pad (constant mode), fill_diagonal, diagflat, diff, ediff1d and meshgrid were the last of the shape-and-stencil numpy calls still reaching the pyobject callback path. meshgrid pins the 'xy' against 'ij' axis order: the two spellings differ only in the first two axes, which a stencil gets wrong silently. The edge modes of pad read a mirrored or clamped index, which is a different kernel, so they are refused by name rather than lowered wrong.
hipSPARSE and hipTensor mirror their CUDA counterparts call for call, so each shares one expansion body through a dialect table; the tile-op headers named no NVIDIA intrinsic and only assumed a 32-lane warp.
Four conflicts, all in main's scalar-fission work. Resolved per hunk, not per file: extended carries 502/263/623 changed lines in these files against main's 9/17/12, so taking either whole side would have discarded real work. scalar_fission.py and its test keep extended's shape. Extended factors the memlet rename into rename_node_memlets, which renames the whole memlet PATH rather than the single edge main renames, and the test refactors main's two new functions through assert_memlets_name_an_endpoint -- both strict supersets of what main added. analysis.py and redundant_array.py take main's text. The code is byte-identical on both sides; only a comment and a docstring differ, so upstream's wording wins and the two files stop conflicting on the next merge.
A compile-time-unrolled Python loop replays its body, so every copy minted the same for_<lineno> label and validation rejected the graph with "Found multiple blocks with the same name". _add_loop_region added the region without asking for a unique name, unlike every other block the frontend creates. Both loop paths now pass the label the region actually received down to _recursive_visit, so the inner state names follow it too.
ExpandReduceGPUAuto built its tasklets with connectors named a, b and o, so reducing an array that is itself called `a` produced "Connector name 'a' is already used as a symbol, constant, or array name" and the graph never validated. Namespaced all three to __a_in / __b_in / __o_out. Renaming only the dst_conn/src_conn keyword forms is not enough: three add_edge(node, 'o', ...) positional calls kept the old spelling and turned the failure into a dangling connector instead. The patch covers both spellings.
Replace the name-only merge in _independent_groups with an SMT-backed live-range check. Two chains that touch the same written container are kept together only when prove_disjoint_access_boxes cannot show their subsets are independent across loop iterations. This avoids the CloudSC miscompile where disjoint live ranges on the same transient were forced into one group. Also fixes _container_per_iter_only to inspect the subset on the node's side of an edge (it previously looked at the source subset on a copy edge) and switches grouping sets to OrderedSet for PYTHONHASHSEED stability.
A scalar promoted to __sym_<name> lands directly in sdfg.symbols without touching the visitor's scope map, so a range over the promoted slice's own shape was reported as an undefined variable. Accept a symbol declared on the SDFG as defined. numpy.where with a symbolic branch (2 * N) reached dtype_to_typeclass with a sympy expression, whose type() is sympy.Mul and is in no dtype map. Infer the branch dtype with symbolic.symtype and spell the branch with symstr, since sympy prints ** and / that C++ does not accept. The closure qualname sanitizer only replaced attribute and subscript punctuation, but a hoisted qualname can be a whole call expression whose parentheses, spaces and operators then failed name validation. Replace every non-identifier character, and materialize the hoisted value when it evaluates to a numpy view, which the calling convention refuses.
…a ROCm host The GPU runtime environment asked CMake for find_package(CUDA) and put cuda_runtime.h in the host frame no matter the backend, which aborted the configure of every CUB-backed libnode on a ROCm-only machine; the canonicalize perf tail named the CUDA vendor row a second time, so every rocBLAS lowering fell back to pure there; and the K=1 tile-op GPU header reached cuda_fp16.h and the two FP16x2 min/max intrinsics ROCm does not ship.
Insertion order is why DaCe uses this container everywhere: it stands in for a canonical order so codegen does not move with PYTHONHASHSEED. That order is a property of how a set was built, not of what it holds, but the upstream class is a Sequence, so == between two of them compared element order. Invariant checks were therefore asserting on the order the two sides happened to be accumulated in, and MapFusionVertical's partition self-check failed for a nest whose intermediates were classified in a different order than the exit edges reached them, refusing a legal fusion through its safety net. dace.ordered.OrderedSet overrides __eq__ to compare membership when the other side is a set, and keeps the order-sensitive meaning when it is a real sequence such as a list. Iteration order is untouched.
tiled_producer_subset() sizes the reduced intermediate with size(), which only a Range offers, so a SubsetUnion producer raised AttributeError from inside the matcher instead of being refused by a guard. The equivalent check already ran a few lines later; this moves it to the first use of the subset.
The trapezoid rewrite makes a triangular pair interchangeable, but the new outer loop is left a min/int_floor bound that LoopToMap will not take. On a nest whose levels were both DOALL that traded two Maps for one Map plus a sequential loop, which unit stride does not pay for. Count the parallel levels before and after the bubble and revert when the move loses one; TSVC s1232 still permutes, since its outer level was never parallel. The adversarial test that pinned the old "a triangular bound must refuse the swap" contract predates the trapezoid rewrite. It now asserts the stronger property the rewrite is supposed to have: the bounds change direction with the loops, and the interchanged nest enumerates the same iteration set at sizes the single N=8 case cannot distinguish. The s1232 test asserts the rewritten bounds the same way instead of resting on order and a stopwatch alone.
Scalar-to-symbol promotion wrote the promoted value into an AccessNode with a host tasklet without checking storage, so an argmax on GPU produced a host write to a GPU_Global container that validation rejected; the CUB workspace test asserted a .cu extension the HIP backend never emits.
The producer-reads/consumer-writes hazard guard is already in place (5cbfd5b); add a direct seidel_2d-shaped test so the anti-dependence refusal stays pinned.
The assertion that the two halves recombine into `first_nodes` had to convert both sides to plain `set`s to dodge the order-sensitive `==` that `OrderedSet` used to inherit; with set equality in place it compares the containers as they are. `new_first_nodes` was a plain `set`, so the order it fed back into `nodes_to_scan` and `first_nodes` moved with `PYTHONHASHSEED`.
# Conflicts: # dace/libraries/blas/nodes/axpy.py # dace/libraries/blas/nodes/ger.py
The closure evaluator copied any numpy view it produced, so an array reached through an attribute chain came back as a fresh object on every call. `combine_nested_closures` dedups by `id(evaluator())` against `array_mapping`, which is keyed on the object the closure was registered with, so the dedup never matched and each nested convertible holding the same array minted another `__g_<name>_N` argument. pyFV3's D_SW then asked for a `__g_self__del6_u_1` that no caller supplies. Copying it was also wrong on its own terms: closure arrays are resolved before parsing, so nothing yet knows whether the kernel writes the array, and a write to the copy is discarded in silence. Only a value hoisted out of an expression is a temporary nobody else holds -- that one is materialized once, which is also what keeps its identity stable.
`multidim_gbar.cuh` moved to the gpu* aliases, but `types.h` includes it before `DACE_DFI` and `dace::vec` exist, so `cudacommon.cuh` cannot be pulled in alongside it and every CUDA build failed on an undefined `gpuError_t`. It belongs in `dace.h`, after both.
`-DCUDA_HOST_COMPILER` was emitted but nothing in `codegen/CMakeLists.txt` ever read it, so `compiler.cpu.executable` could not steer nvcc at all and host and device objects were free to disagree on the ABI. CMake now consumes it before `enable_language(CUDA)`, and the CUDA targets forward the host compiler unconditionally, the way the CPU target already pins `CMAKE_CXX_COMPILER`.
The refusal covered every mix of an index array and an integer index, which took a working case with it: the indirection numbers dimensions against the full-rank array, so only a dropped axis BEFORE the index array shifts that numbering. A trailing one leaves it alone, and A[2:4, ind[1], 3] += B computes the same values as numpy.
…LoopLocalMemoryReduction, helpers, and writeset_underapproximation D2 determinism sites: - transient_reuse.py:50: bucket order decides which array survives a reuse merge. - loop_local_memory_reduction.py:229,231,234: [0][0] index selection feeds the modulo buffer size. - helpers.py:1019,1038: forward/reverse scan sets in isolate_nested_sdfg. D3 writeset_underapproximation.py:600-613: loop-nest root order drives per-loop-nest processing order into approximation_dict. Unfrozen and converted despite earlier freeze note; the bug is real and the change is mechanical.
…e fully writes `OffloadToAccelerator` staged every signature array down to the device before the kernels that use it, including arrays nothing ever reads. For a write-only output that is a full host-to-device transfer on every call whose result is immediately overwritten and discarded. The copy is only dead when BOTH hold. Nothing may read the container -- no access node with an out-edge and no interstate edge naming it, since an interstate read has no node for an access-node walk to find -- and the device must write all of it, because the copy-out sends the whole device buffer back and it is the copy-in that currently makes untouched elements round-trip unchanged. Proving the second is where this gets subtle, and a covering subset does not do it. An indirect write carries the whole array as its subset because that is where it MIGHT land, while its volume says how much it actually writes: `write_subset_dynamic`'s `A[x[i], y[j]]` spans `0:20, 0:20` and writes 256 of the 400 elements. Trusting the subset alone drops its copy-in and returns uninitialised memory for the 144 the scatter misses -- which is how that existing test caught this. `written_in_full` therefore requires the subset to cover AND the volume to equal the descriptor's total size, and refuses a dynamic or write-conflict-resolution memlet outright. Three tests: the elision, a partial-write control, and a structural one asserting the indirect write over-approximates its subset and keeps its copy-in. Only the first fails when the elision is removed, so the other two are controls rather than passengers. This restores the property `GPUTransformSDFG` had and the pass did not, which went untested when that transformation was removed.
The wrong product only appears on the self-hosted runner, and a bare np.allclose assert throws away everything needed to tell a layout error from a corrupted broadcast.
…ing it nvcc does not fail on a host without a GPU: -arch=native warns once and substitutes a default architecture older than sm_53, where <cuda_fp16.h> declares no __half operators and no half2 intrinsics, so a generated fp16 kernel dies on undefined __hadd2 and ambiguous __half conversions. The architecture also stopped being a CMake CACHE entry, which had pinned the first configure's choice and silently ignored every later compiler.cuda.cuda_arch in the same build folder.
A toolkit that cannot build sm_80 still drops to the oldest architecture it has that can compile the runtime's fp16.
An expansion bakes its tasklet text once, and prepare() ran before the dialect scope was entered, so a node that spells its own element transform had no way to ask which dialect it was rendering for. ArgReduce always chose std::abs, which is C++ only, and every argmax over a transformed element then failed the C rendering at the self-containment check rather than at anything the caller could act on. Measured on the llr-focus40 roster: the C forms go from 37 of 40 to 38, with tsvc_2_s318 rendering in both dialects instead of C++ alone.
…nd kernel-local transients Swapping GPUTransformSDFG for OffloadToAccelerator turned five General-CI tests red, all for the same reason: the new pass judged three things by the wrong question. A Stream descriptor has no subset to collect, so asking which arrays an edge uses raised instead of answering "none", and single-sided placement tried to give it a device twin. Both now treat a Stream as data that stays where it is, and apply_gpu_transformations no longer refuses an SDFG that holds one. A library node was called device-wide on the strength of the node type alone, but only the chosen expansion knows whether it emits device code. Expansions now declare runs_inside_kernel, the CUDA block-reduction ones set it, and the taskloop classifier asks the expansion instead of the node. A transient live only inside a kernel scope kept host storage, which registered IllegalCopy as a target that then emitted nothing -- tripping the codegen assertion that every registered target is used. register_kernel_local_transients moves those to Register once, after placement, for transients that never escape their kernel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dace::float16 converts implicitly both to and from the built-in floats, so 1.0 / x with x an fp16 gives nvcc two equally good operators -- the built-in one converting x up, and the class's own converting the literal down -- and it rejects the expression as ambiguous. A nested fp64 program called from an fp16 one hits this on every literal it divides by. The C++ unparser now prints an untyped numeric literal as a cast to the dtype of the operand beside it, but only when that operand is one of the class-typed floats; every other typeclass is a built-in whose usual arithmetic conversions are unambiguous, and its literals are printed unchanged. The cast is what Python and NumPy compute here too, where a weak scalar takes the array's dtype. The readable CPU generator renders some statements itself rather than leaving them to unparse_tasklet, so it now carries the dtypes of the operands it splices in -- a surviving connector under its own name, an inlined one under its access text. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A dynamic map range spells its bounds with the map's connector names, and
used_symbols treats a connector as locally defined, so such a bound never
reached the scope arglist. On the host that is harmless -- the connector is a
local variable in the same function -- but a kernel is a separate function in
a separate translation unit, so the device code named an identifier nothing
declared ("kidia" is undefined, on CloudSC). The kernel arglist now includes
the dynamic-range connectors as by-value scalars, excluding the stream, which
the launch wrapper appends itself.
The host side had the mirror bug: when a connector reuses its container's own
name, defining it again emitted "int kfdia = kfdia;", shadowing the variable
that held the value with a self-initialised copy. That definition is now
skipped, since the value is already in scope under that name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A boundary memlet must name the container on the parent side of its edge, and after versioning it does not: ScalarFission renames the inner end of a staging copy and the memlet follows the inner name, so nesting that subgraph left the parent referencing data it no longer had, with a connector minted around the moved descriptor. Both nest_state_subgraph and the map-boundary tasklet insertion now re-anchor such an edge on the AccessNode outside the scope, and only where the access is a single element -- a wider memlet does not carry which outer element it moves, so it is left alone rather than guessed at. Two neighbouring cases in nest_state_subgraph fall out of the same question. An AccessNode a boundary edge already carries needs no second, referenced-in- full interface: the extra one reads an in-scope allocation nothing writes, and pruning the unused boundary connector took the ordering edge with it, emitting the map before its input was assigned. And an input edge already ending on the whole container needs no access node inside, where the name -> name copy is a no-op whose write turns an input connector into a written descriptor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two tests pulled OpenMP into the interpreter at collection time -- one loading libgomp.so.1 through ctypes at module level, one importing a module that imports torch -- which is what made a later dace build in the same process find the wrong runtime. The ctypes load is gone and the torch-bearing import moved into the test body. Casting ITE arms early, in resolve_mixed_dtype_binops, duplicated what CastScalarIteLiteralArms already does after the types are settled, where the information to do it correctly exists. The early pass is back to leaving ITEs alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eight tests guarded themselves with a skipif on nvcc but carried no gpu marker, so the GPU CI selection never picked them up and they only ever ran on CPU runners, where they skipped. The marker makes them run where the hardware is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Twenty kernel tests carried a marker that skipped them unless --run-simple was passed, on the grounds that a harder sibling covered the same ground. Nothing in CI passed that flag, so the sweep they were meant to join never included them and they had been skipping for their whole existence. All twenty pass. The marker, its option and the skipping hook are gone. Where the note said which test is the harder canonical one, it stays as a comment, since that is the part worth keeping: it tells the next reader what already covers the case. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The nested-boundary test covers the CUDA side and needs a device, so the half of the fix that lives in the readable CPU generator -- carrying the dtypes of the operands it splices in, since it renders those statements itself -- had no test that CI runs on a CPU runner. Three tests on one map that computes 1.0 / (a + 1.0) in fp16: the literals are emitted as dace::float16 casts, an fp64 version of the same program keeps its literals bare, and the compiled result matches the numpy fp16 oracle exactly. The assertions are on the generated text, because a host compile does not reproduce the bug -- dace::float16 is a struct there and g++ still ranks its conversions, so the ambiguity is nvcc's __half specifically. The run only shows the cast did not move the answer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The perf tests asserted that canonicalize is never slower than auto_optimize, which twelve of the corpus kernels have never satisfied, so every selection in CI filtered them out with "not perf" and nothing ran them at all. An assertion no job evaluates does not defend anything. They now assert a ratchet: each kernel that is currently slower carries its measured ratio, and the test fails when a kernel exceeds its own number by more than the run-to-run slack, or when a kernel that was not slower becomes so. Nothing is skipped, and a regression on any kernel is still a red. The job is advisory for now. The baselines were measured on a loaded machine and several are wrong by a factor of two; the flag comes off once they are re-measured on an idle one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The anchoring fix shipped with no test: the whole surrounding suite passed identically with it reverted, so nothing would have caught it coming back. Five tests now build the shape that produces it -- a loop-invariant scalar staged in through the map entry under the same name on both sides, run through ScalarFission, which versions the two ends apart and leaves the boundary memlet on the inner name. Reverted, they fail as the three distinct artifacts: a memlet naming an array the parent SDFG no longer has, a second interface that turns a read-only container into one the map writes every iteration, and a no-op copy inside the nest. The fifth covers the guard that declines to split a locally named memlet whose outer container is wider than one element. Failure mode (b) is pinned structurally rather than by a wrong answer. It produces the wrong graph reliably, but the miscomputation the commit describes needs the connector to be pruned as well, and pruning does not fire on a shape this pass produces on its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…form MPR -> CPF throughout: dace/codegen/mpr.py becomes codegen/cpf.py, dace/mpr_lowering.py becomes cpf_lowering.py, dace.mpr() becomes dace.cpf(), and tests/codegen/mpr moves with them. The rendering is the canonical parallel form of an SDFG, and "maximal" claimed something the pass never promised. Names only -- no lowering, dialect or emitted text changes.
Both backends declare ``FutureValue<T, Iter = T*>`` and take the iterator by
``const Iter``, so leaving it defaulted binds ``T* const`` -- which the
``const T*`` the scan wrapper is handed cannot convert to. rocPRIM rejects it
("would lose const qualifier") and the whole translation unit fails to
compile on gfx942; CUB happens not to reach the same instantiation, so the
break has only ever shown on HIP.
Spell ``const T*`` instead, from one helper rather than the two call sites
that each built the type string. That accepts a seed that is const and one
that is not, on both backends.
The perf marker covered two different things: the vectorization ratchet, which is a gate, and the canonicalization corpus sweep, which is a measurement harness -- a few hundred kernels times eight arms, each built and timed over two dataset presets with warm-ups and repetitions, resumable from a result directory because a full sweep is hours. The new CI perf job selected both and would have run the sweep on every push: 336 cases where it means 55. The sweep now carries long as well as perf, which every CI selection already excludes, and the job asks for "perf and not long". Nothing that was running stops running -- the marker kept both out of CI until this job existed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each of the four recipes this branch ships for CloudSC gets a leg that re-drives the dwarf's own physical inputs and compares every output array against the same oracle: the un-transformed simplify=False SDFG run sequentially under the IEEE build. Canonicalize for the host; vectorize on top of that; canonicalize for the device followed by CloudSC's own offload recipe; and the generic device move over the host-canonical graph. The two device recipes are deliberately different, so a red device leg separates the offload from the target preset that fed it. Every leg also asserts structurally, because a numeric comparison alone passes for the wrong reason: the host legs check that Maps exist and that "#pragma omp parallel for" reached the generated code, the vectorize leg that tile ops were emitted, the device legs that something is scheduled on the device and that __global__ kernels came out. Tolerances are the parallel-arm bound the sibling canonicalize test already uses, and the device legs build with the strict FP flags, so the residual is reassociation and device libm, nothing else. No CI job selects it yet. It is slow enough that the runner it goes on has to be sized off a measured canonicalize, and that measurement is not in yet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The four-way test had no job selecting it. It gets two, split by where the hardware is: cloudsc-host-pipelines on an ubuntu runner takes the host legs -- parse and simplify the dwarf, canonicalize for the CPU and check the numbers, then vectorize that same graph and check them again -- and test_cscs_gh200_cloudsc_pipelines takes the device legs on the CSCS GH200, which is the only runner in this repo with a GPU. The broad GPU legs deselect the file by PATH rather than by the integration marker. The marker would also have dropped cloudsc_offload_numeric_test, which those legs run today inside their per-test timeout; only this file needs a whole-dwarf canonicalize per case and a budget to match. Budgets are deliberately loose -- four hours per test, four hours of SLURM -- because a canonicalize of the dwarf is measured in tens of minutes, and a gate that dies on the clock reports nothing about the numbers it was meant to check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
hipCUB's DeviceReduce static_asserts a std random_access_iterator_tag that thrust's transform_iterator does not report, so the strided argmax did not compile on gfx942; a single-element WCR stage-out now lifts to an explicit copy instead of dace::CopyND::Accumulate.
rocPRIM's arg_index_iterator, which DeviceReduce::ArgMax wraps its input in, static_asserts that iterator_category IS std::random_access_iterator_tag. Both libraries' transform iterators report a thrust tag instead once thrust is in the unit, and DaCe puts it there unconditionally via thrust::complex, so every strided or transformed ArgReduce failed to compile on gfx942 (s318). Declaring the tag ourselves is independent of what else the unit includes.
OffloadToAccelerator returned None whatever it did, so apply_gpu_transformations did too, and a Pipeline read every offload as "nothing changed". It now returns the containers left in device storage. Two tests asserted the old GPUTransformSDFG application count, which no longer exists; they assert the postcondition instead -- something is placed, and a map or library node carries a GPU schedule. The predicate is GPU_RESIDENT_STORAGES, not dtypes.GPU_STORAGES, which holds only GPU_Shared. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Eleven jobs of four to eight minutes each paid a checkout and an environment sync apiece. CPF's three legs become one job of three steps; HPCAgent-Bench's two tags become one job that still writes and summarizes a CSV per tag; and General Tests grows an "extensions" job holding cpu-codegen and the rustworkx graphlib suite. layout and canon-perf are fused separately, into "advisory", because both are non-blocking and continue-on-error is a job-level setting -- putting either in with a real gate would swallow that gate's failures. LibNodes stays on its own runner: it builds MKL and TBLIS first, and folding it in would push the shared job past half an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Left implicit it is the one copy the readable generator has no explicit form for, so cpu.py fell back to dace::CopyND::Accumulate and CPF refused the render (segment_reduce_ragged). Multi-element and GPU-resident WCR edges stay unlifted -- a memcpy expansion would turn the accumulate into an overwrite.
The test put its parallelism in a map nested inside another, and asserted a tasklet specialized to block 1. Offloading assigns GPU_Device at a host level and Sequential below one, so the inner map became a serial loop and the kernel ran in a single block: the guard was emitted, never taken, and three elements kept the unspecialized value. One flat map of two blocks' worth instead, so blockIdx 1 and threadIdx 2, 5, 8 all exist. Same assertions, and the expected array is built from the block size the config actually carries rather than from a literal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The selection criterion is what a standalone unit can compile, not the name "pure": Auto gives a contiguous copy one memcpy where a mapped tasklet spent an element loop. It stays an allowlist because environments is empty for Reduce's OpenMP, FindFirst's OpenMP and Scan's CUDA, all of which name a dace:: symbol. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Plain `inline` there is ill-formed C: an inline function with external linkage may not reference an identifier with internal linkage (C11 6.7.4p3), and every body calls the `static` <array>_idx helpers. 14 of 38 rendered llr forms were rejected by gcc -Werror for it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Code generation writes an `assert((step) > 0 && ...)` guard on every non-unit step map straight into the stream, so no call-site table discovers it and the declaration was never pulled in. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A symbolic size is assumed large and must stay a parallel map; only a small compile-time-constant copy takes the single libc call. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.