Skip to content

Aggregate codegen errors and lower Julia throws#248

Merged
maleadt merged 10 commits into
mainfrom
tb/deferred-codegen-errors
Jul 10, 2026
Merged

Aggregate codegen errors and lower Julia throws#248
maleadt merged 10 commits into
mainfrom
tb/deferred-codegen-errors

Conversation

@maleadt

@maleadt maleadt commented Jun 3, 2026

Copy link
Copy Markdown
Member

This changes codegen to collect independent errors instead of stopping at the first one. Failed values are replaced with poison values so compilation can continue, while follow-on errors caused by those values are suppressed. The final error reports the useful root causes in source order.

It also handles Julia throw calls directly. Throws that are unavoidable become compile-time diagnostics, while throws in conditional control flow become runtime Tile IR assertions. Static exception messages are preserved when possible, and exception objects are removed before Tile IR codegen.

Taking the example from #237:

using CUDACore; import cuTile as ct

function fill_normal!(A::ct.TileArray{Float32, 1}, n::Int)
    bid = ct.bid(1)
    tile = randn(Float32, (n,))
    ct.store(A; index=bid, tile=tile)
    return
end
julia> @cuda backend=ct blocks=64 fill_normal!(CuArray{Float32}(undef, 65536), 1024)
ERROR: CodegenErrors: kernel compilation produced 2 errors:
Reason: fill() shape must be a compile-time constant
Stacktrace:
 [1] _full(value::cuTile.IntTile{Tuple{}, UInt32}, ::Type{cuTile.IntTile{Tuple{}, UInt32}}, shape::Tuple{Int64})
   @ cuTile ~/Julia/pkg/cuTile/src/language/operations.jl:724 [inlined]
 [2] fill(v::cuTile.IntTile{Tuple{}, UInt32}, dims::Tuple{Int64})
   @ cuTile ~/Julia/pkg/cuTile/src/language/overlays.jl:164 [inlined]
 [3] randn_f32class_tile(stream::Int64, ::Type{Float32}, dims::Tuple{Int64})
   @ cuTile ~/Julia/pkg/cuTile/src/language/random.jl:427 [inlined]
 [4] randn_tile(stream::Int64, ::Type{Float32}, dims::Tuple{Int64})
   @ cuTile ~/Julia/pkg/cuTile/src/language/random.jl:486 [inlined]
 [5] randn(::Type{Float32}, dims::Tuple{Int64})
   @ cuTile ~/Julia/pkg/cuTile/src/language/random.jl:576 [inlined]
 [6] fill_normal!(A::cuTile.TileArray{Float32, 1, cuTile.ArraySpec{1, 128, true, (0,), (16,)}()}, n::Int64)
   @ Main REPL[3]:3 [inlined]

Reason: iota() shape must be a compile-time constant
Stacktrace:
 [1] counter_tile(c_base::cuTile.IntTile{Tuple{}, UInt32}, dims::Tuple{Int64})
   @ cuTile ~/Julia/pkg/cuTile/src/language/random.jl:226 [inlined]
 [2] randn_f32class_tile(stream::Int64, ::Type{Float32}, dims::Tuple{Int64})
   @ cuTile ~/Julia/pkg/cuTile/src/language/random.jl:428 [inlined]
 [3] randn_tile(stream::Int64, ::Type{Float32}, dims::Tuple{Int64})
   @ cuTile ~/Julia/pkg/cuTile/src/language/random.jl:486 [inlined]
 [4] randn(::Type{Float32}, dims::Tuple{Int64})
   @ cuTile ~/Julia/pkg/cuTile/src/language/random.jl:576 [inlined]
 [5] fill_normal!(A::cuTile.TileArray{Float32, 1, cuTile.ArraySpec{1, 128, true, (0,), (16,)}()}, n::Int64)
   @ Main REPL[3]:3 [inlined]

An unconditional throw is reported during compilation:

using CUDACore
import cuTile as ct

function unavailable_kernel()
    throw(ArgumentError("this kernel configuration is not supported"))
end
julia> @cuda backend=ct blocks=1 unavailable_kernel()
ERROR: CodegenErrors: kernel compilation produced 1 error:
Reason: this kernel configuration is not supported
Stacktrace:
 [1] unavailable_kernel()
   @ Main REPL[2]:2 [inlined]

A conditional throw is preserved as a runtime assertion:

function require_positive(n::Int)
    n > 0 || throw(ArgumentError("n must be positive"))
    return
end
julia> ct.code_tiled(require_positive, Tuple{Int})
cuda_tile.module @kernels {
  entry @require_positive(%arg0: tile<i64>, %arg1: tile<i32>) {
    %cst_0_i64 = constant <i64: 0> : tile<i64>
    %0 = cmpi less_than %cst_0_i64, %arg0, signed : tile<i64> -> tile<i1>
    if %0 {
    } else {
      %false = constant <i1: false> : tile<i1>
      assert %false, "n must be positive" : tile<i1>
    }
    return
  }
}

julia> @cuda backend=ct blocks=1 require_positive(1);

julia> @cuda backend=ct blocks=1 require_positive(-1);
REPL[9]:2: tile block: [0, 0, 0], position: []: n must be positive

@maleadt maleadt changed the title Initial sketch of deferred error reporting. Don't crash on the first codegen error encountered Jun 3, 2026
@maleadt

maleadt commented Jun 3, 2026

Copy link
Copy Markdown
Member Author

Errors probably can be ordered better; the randn one happens on line 3 while the Any is in a line below.

@maleadt maleadt marked this pull request as ready for review July 10, 2026 11:03
@maleadt maleadt force-pushed the tb/deferred-codegen-errors branch from b153d0a to dbfb5e0 Compare July 10, 2026 11:03
@maleadt maleadt changed the title Don't crash on the first codegen error encountered Aggregate codegen errors and lower Julia throws Jul 10, 2026
maleadt and others added 10 commits July 10, 2026 18:11
A statement that consumes a poisoned value but emits fine used to break
the taint chain, so failures further down the dataflow surfaced as fresh
root causes. Also check operands statically in the diagnostic boundary:
emitters that throw before reading their arguments (e.g. rejecting a
statement on its type) never set the dynamic touched_poison flag.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A control-flow op whose carried result inferred as Any records a
diagnostic before its regions are emitted, so it cannot know yet whether
the body will surface the actual cause. Bracket the carry collection and
drop the carry diagnostic again when region emission recorded further
errors; keep it when the body is clean (e.g. branch arms yielding
different tile shapes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deep-inlining-first ordering put symptoms ahead of causes (PR feedback:
the randn error on line 3 sorted after the store error on line 4), and
full-stack dedup kept every inlining path of the same problem. Keep
recording order — emission order is program order — and dedup on
(message, kernel line).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A non-IRError escaping statement emission is a compiler bug, not a kernel
diagnostic. Abort immediately, but point the report at the offending
kernel statement and ask for an issue; the original exception renders as
the cause via the exception chain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bytecode-version requirements of FP8-E8M0/FP4 dtypes, the unsupported-
dtype fallback, and the load/store latency range are all triggerable
from kernel code, so they belong in the collected diagnostics rather
than aborting as ArgumentError/ErrorException (which would now be
misreported as internal compiler errors).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@maleadt maleadt force-pushed the tb/deferred-codegen-errors branch from f32e669 to 5322388 Compare July 10, 2026 16:11
@maleadt maleadt merged commit e431927 into main Jul 10, 2026
1 check passed
@maleadt maleadt deleted the tb/deferred-codegen-errors branch July 10, 2026 17:04
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