Skip to content

Reject allocations of objects that contain references - #949

Open
maleadt wants to merge 2 commits into
tb/throw-argumentsfrom
tb/reject-reference-allocations
Open

maleadt wants to merge 2 commits into
tb/throw-argumentsfrom
tb/reject-reference-allocations

Conversation

@maleadt

@maleadt maleadt commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

There is no device GC, and gc_pool_alloc does not provide the object header expected by Julia's runtime. Only statically typed uses of plain-data objects work, such as an escaping Ref{Int}, a mutable struct with bits fields, or a boxed bits value. Objects that reference other objects also get GC orderings (unordered, release) on their loads and stores, which Metal and SPIR-V cannot express. After AllocOpt, GPULowerGCFrame now resolves the type of each remaining allocation. For types that are not pointer-free, it emits a marker that check_ir reports with a backtrace:

InvalidIRError: compiling MethodInstance for refs(::MtlDeviceVector{Int32, 1}) resulted in invalid LLVM IR
Reason: unsupported allocation of an object with references (Holder)
Stacktrace:
 [1] Holder
   @ ./example.jl:3
 [2] refs
   @ ./example.jl:7

referenced_object now also looks through cast instructions. Metal.jl's :table relocations produce those around the type operand.

This depends on #940, which removes dead exception objects before this check. On main with only this change, kernels containing Bool(x), Int32(::Float32), or throw(DomainError(x, LazyString(...))) fail to compile on Julia 1.11 and 1.12 for both Metal and SPIR-V (Tuple{DataType, Int64}, Tuple{DataType, Float32}, LazyString).

Scope

The check runs for every target that does not use the Julia runtime, including PTX and GCN. The suites below cover Metal, oneAPI, OpenCL, and KernelAbstractions (POCL); the CUDA.jl and AMDGPU.jl suites have not been run with this change.

Known gaps

  • A failing type assertion on an isbits-union value still boxes integer members for jl_type_error through the box_* runtime. That box contains plain data on the failure path, so it is allowed.
  • Enzyme's interpreter does not run julia_ir_passes. Enzyme-differentiated GPU code with dead exception objects containing references, from throw paths not covered by a back-end override, is now rejected instead of compiled. Fixing this requires a follow-up in Enzyme that calls the interpreter-independent GPUCompiler.run_julia_ir_passes.

@maleadt
maleadt added this pull request to stack #951 September 25, 2026 06:14
@maleadt
maleadt force-pushed the tb/reject-reference-allocations branch from ea2a75e to 89ef241 Compare September 25, 2026 06:55
@maleadt

maleadt commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

check_allocation! crashes instead of rejecting or handling an allocation whose type operand is a small type tag:

using Metal
k(a) = (Base.donotdelete(Core.svec(a[1])); nothing)
Metal.code_llvm(devnull, k, Tuple{MtlDeviceVector{Float32,1}}; kernel = true)
# signal 11: referenced_object at src/relocation.jl:934, from check_allocation! (LowerGCFrame)

Reproduced with Julia 1.12.7 on this branch through the #951 stack and Metal.jl #977. GPUCompiler 2.8.2 compiles the same kernel.

The allocation is:

call ptr @julia.gc_alloc_obj(ptr %current_task, i64 16, ptr inttoptr (i64 144 to ptr))

Here, 144 is SimpleVector’s small type tag—an index into jl_small_typeof, as used for tags below jl_max_tags << 4—not a type address. The inttoptr branch in referenced_object passes it to unsafe_pointer_to_objref; dispatching on the resulting value then segfaults.

Small tags need to be resolved through jl_small_typeof instead.

I found this with a kernel that forwards keyword arguments through a non-inlined kwargs... wrapper, which allocates an svec.

@codecov

codecov Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 36 lines in your changes missing coverage. Please review.
✅ Project coverage is 0.00%. Comparing base (946c1d7) to head (634b0b2).

Files with missing lines Patch % Lines
src/optim.jl 0.00% 21 Missing ⚠️
src/relocation.jl 0.00% 11 Missing ⚠️
src/validation.jl 0.00% 4 Missing ⚠️
Additional details and impacted files
@@                  Coverage Diff                   @@
##           tb/throw-arguments    #949       +/-   ##
======================================================
- Coverage               86.28%   0.00%   -86.29%     
======================================================
  Files                      29      29               
  Lines                    5783    5682      -101     
======================================================
- Hits                     4990       0     -4990     
- Misses                    793    5682     +4889     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

There is no garbage collector on the device, and `gc_pool_alloc` does not provide the
object header expected by Julia's runtime. Only statically typed uses of objects holding
plain data work. Objects that reference other objects get GC orderings on their loads and
stores, which not all back-ends can express, and typically come from GPU-incompatible
code. After `AllocOpt`, `GPULowerGCFrame` now marks allocations of such objects, and
`check_ir` reports them with a backtrace.
Julia 1.10+ refers to some types by a small tag instead of their
address: an offset into `jl_small_typeof`, below `jl_max_tags << 4`.
Codegen emits such a tag as the type operand of `julia.gc_alloc_obj`,
e.g., since 1.12 when allocating a `Core.svec` inline, where
`check_allocation!` passed it to `referenced_object`. That treated
every `inttoptr` constant as an object address and dereferenced the
tag, crashing the compiler.

Look small tags up in `jl_small_typeof` instead, as `jl_to_typeof`
does, and treat unused table entries and non-constant operands as
unknown. Larger constants are still object addresses, as embedded by
1.10's codegen or by baking relocations. Such allocations of a
`SimpleVector` are now reported as allocations of an object with
references.
@maleadt
maleadt force-pushed the tb/reject-reference-allocations branch from 634b0b2 to e5adcf7 Compare September 28, 2026 16:52
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