Conversation
ea2a75e to
89ef241
Compare
|
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 Small tags need to be resolved through I found this with a kernel that forwards keyword arguments through a non-inlined |
Codecov Report❌ Patch coverage is
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. 🚀 New features to boost your workflow:
|
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.
634b0b2 to
e5adcf7
Compare
There is no device GC, and
gc_pool_allocdoes not provide the object header expected by Julia's runtime. Only statically typed uses of plain-data objects work, such as an escapingRef{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. AfterAllocOpt,GPULowerGCFramenow resolves the type of each remaining allocation. For types that are not pointer-free, it emits a marker thatcheck_irreports with a backtrace:referenced_objectnow also looks through cast instructions. Metal.jl's:tablerelocations produce those around the type operand.This depends on #940, which removes dead exception objects before this check. On
mainwith only this change, kernels containingBool(x),Int32(::Float32), orthrow(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
jl_type_errorthrough thebox_*runtime. That box contains plain data on the failure path, so it is allowed.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-independentGPUCompiler.run_julia_ir_passes.