Skip to content

Integrate with Julia's bounds checking infra - #288

Draft
maleadt wants to merge 10 commits into
mainfrom
tb/boundscheck
Draft

Integrate with Julia's bounds checking infra#288
maleadt wants to merge 10 commits into
mainfrom
tb/boundscheck

Conversation

@maleadt

@maleadt maleadt commented Jul 29, 2026

Copy link
Copy Markdown
Member

This makes @inbounds etc wire into the check_bounds arguments to ct.load and ct.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=no isn'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-bounds are ignored, but @inbounds is forwarded as the check_bounds argument to memory operations (a local kwarg taking precedence).

Thoughts, @AntonOresten @vchuravy?

maleadt and others added 10 commits July 29, 2026 08:08
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant