Integrate with Julia's bounds checking infra - #288
Draft
maleadt wants to merge 10 commits into
Draft
Conversation
The `check_bounds` keyword on `load`/`store` (TileArray, TiledView and GatherScatterTileView) now defaults to `nothing`, meaning "inherit the caller's Julia bounds-checking context", matching how `Base.getindex` honors `@inbounds`. The explicit/inherited distinction has to survive to the emitter: only there is the bytecode version known, and an inherited `@inbounds` must silently fall back to checked below v13.4 (a permission, not a demand) while an explicit `false` keeps erroring. The intrinsics therefore take both the keyword and the resolved `Base.@_boundscheck` value, which `unchecked_view_access` combines. Entry points get `Base.@propagate_inbounds` so the context survives the convenience wrappers, per Base's one-layer rule.
Covers the policy truth table, the v13.3 fallback (every kernel must still compile), the v13.4 `inbounds` attribute for the inherited path, Base's one-layer propagation rule, all three view families, both `--check-bounds` overrides via subprocesses, and device execution. The pre-existing explicit-`check_bounds=false` tests now need a `--check-bounds=yes` guard: that flag forces checked access, which removes both the v13.3 error and the attribute.
Adds a "Bounds checking" section to the memory manual covering the inherited default, the v13.4 requirement and its silent fallback, `--check-bounds`, and the gather/scatter asymmetry; notes the diverging default against cuTile Python and the fallback in the compatibility table. Replaces BOUNDSCHECK.md, whose remaining content now lives in these pages and in the load/store docstrings.
`!` on a Bool and `~` on an integer both reach `not_int`, but its rewrite rules
were restricted to `Tile{...}` operands and so left the scalar form to fail in
codegen. The binary bitwise rules don't have this problem because they need no
type-directed constant.
cuTile Python documents `padding_mode` as ignored once an access is promised in
bounds ("no out-of-bound elements can occur"), and Tile IR in fact rejects a
view carrying both a padding value and the `inbounds` attribute. Drop the
padding request when the access is unchecked, however the promise was made.
Without this, `@inbounds` around a padded load turned a working kernel into an
opaque "failed to compile Tile IR program" — which the matmul, softmax and FMHA
examples would all have hit. It also fixes the same pre-existing failure on the
explicit `check_bounds=false` path.
The callers branch over the whole view construction rather than over the mode so
that both arms yield the same Julia type; a branch yielding the enum constant
per arm trips the structured-IR verifier.
Compares checked, `@inbounds` and explicit `check_bounds=false` on one kernel body. It also carries a dynamic Base range index, whose `@boundscheck` lowers to a device assert unless elided: no other example generates a Julia assertion, so the benchmark suite would otherwise not cover assertion-heavy kernels.
Search-and-replace slip when converting them to Base.@propagate_inbounds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tile IR's checked view access is not protective the way Julia's bounds checks are: a partially out-of-bounds tile is *defined* to pad on load and clip on store, and correct kernels rely on that for their edge tiles. That breaks the contract of the process-wide flag in both directions: `--check-bounds=no` would turn defined masking into undefined behavior, breaking exactly the correct kernels it promises to leave intact, and `--check-bounds=yes` has no error check to restore -- the padding drop is selected at the callsite, so its half-checked override (checked encoding with the padding already gone) was merely inconsistent. Only `@inbounds` and the explicit `check_bounds` keyword now control view accesses. `resolve_boundscheck!` classifies bounds-check statements by consumer: those feeding a view intrinsic's context slot resolve to the raw callsite context, while protective checks inlined from Base (lowered to device asserts) keep honoring the flag in both directions. The classification is reliable because `Expr(:boundscheck)` statements are `consistent=ALWAYS_FALSE` and thus never merged before the pass runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collect the places where kernel semantics deliberately diverge from host Julia -- exceptions lowering to device-side traps, non-throwing float-to- integer conversions, and bounds checks that mask rather than throw -- which were previously spread over kernels.md and memory.md or undocumented. The page is the Julia-facing counterpart of the cuTile Python comparison, which is retitled "Differences from cuTile Python" to match; kernels.md, memory.md and element_types.md now point at the new page instead of carrying the material themselves. Co-Authored-By: Claude Fable 5 <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.
This makes
@inboundsetc wire into thecheck_boundsarguments toct.loadandct.store, as suggested in #278.I'm not sure this is something we want though. Bounds checking in TileIR is semantic; it enables or disables the padding at the boundaries of tiles. In Julia, bounds checking is "protective", i.e., running with
--check-bounds=noisn't expected to change the behavior of the program, while disabling padding in cuTile kernels will very much change the program.As a middle ground, I've implemented it so that process-global
--check-boundsare ignored, but@inboundsis forwarded as thecheck_boundsargument to memory operations (a local kwarg taking precedence).Thoughts, @AntonOresten @vchuravy?