From 29e082cd1ba43ddf03bf6ad9f8d54f52fe6b0c8b Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Thu, 9 Jul 2026 15:47:32 +0200 Subject: [PATCH 1/2] Resolve CPU references lazily. Since JuliaLang/julia#61527 (1.14.0-DEV.2614), the LowerPTLS pass may emit calls to `jl_get_pgcstack_resolved`, a symbol that only exists within the JIT and cannot be looked up using `jl_cglobal`, causing the ResolveCPUReferences pass to error on such modules (as seen on Windows CI, where the TLS getter cannot be lowered to a TP access). As this pass only rewrites loads from `jl_` bindings, and such JIT-only symbols are only ever called, resolve binding addresses lazily, i.e., only when actually replacing a load. Co-Authored-By: Claude Fable 5 --- src/mcgen.jl | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/mcgen.jl b/src/mcgen.jl index 2c5e85c3..3fb4a5c6 100644 --- a/src/mcgen.jl +++ b/src/mcgen.jl @@ -35,10 +35,17 @@ function (self::ResolveCPUReferences)(mod::LLVM.Module) for f in functions(mod) fn = LLVM.name(f) if isdeclaration(f) && !LLVM.isintrinsic(f) && startswith(fn, "jl_") - # eagerly resolve the address of the binding - address = ccall(:jl_cglobal, Any, (Any, Any), fn, UInt) - dereferenced = unsafe_load(address) - dereferenced = LLVM.ConstantInt(dereferenced) + # lazily resolve the address of the binding; some symbols only exist + # within the JIT (e.g. `jl_get_pgcstack_resolved`) and cannot be looked up, + # but such symbols are only ever called, not loaded from. + dereferenced = nothing + function resolve_binding() + if dereferenced === nothing + address = ccall(:jl_cglobal, Any, (Any, Any), fn, UInt) + dereferenced = LLVM.ConstantInt(unsafe_load(address)) + end + dereferenced + end function replace_bindings!(value) changed = false @@ -49,7 +56,7 @@ function (self::ResolveCPUReferences)(mod::LLVM.Module) changed |= replace_bindings!(val) elseif isa(val, LLVM.LoadInst) # resolve - replace_uses!(val, dereferenced) + replace_uses!(val, resolve_binding()) erase!(val) # FIXME: iterator invalidation? changed = true From 08514a8d6876085f7a1ff775911ab7b76cc9dc2c Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Thu, 9 Jul 2026 15:47:32 +0200 Subject: [PATCH 2/2] Silence expected output from the native test suite. - suppress the unbound-typevar warning emitted when defining the test method (does not work on 1.12, where `redirect_stderr` does not capture C-level output); - suppress FileCheck diagnostics from always_inline tests that are known to be broken on 1.13+. Also add a regression test for resolving CPU references in modules that call JIT-private `jl_` symbols (JuliaLang/julia#61527). Co-Authored-By: Claude Fable 5 --- test/native.jl | 48 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/test/native.jl b/test/native.jl index 6aebba91..997d334e 100644 --- a/test/native.jl +++ b/test/native.jl @@ -324,8 +324,11 @@ end end @testset "unbound typevars" begin - mod = @eval module $(gensym()) - invalid_kernel() where {unbound} = return + # suppress the warning Julia emits when defining a method with an unbound typevar + mod = redirect_stderr(devnull) do + @eval module $(gensym()) + invalid_kernel() where {unbound} = return + end end @test_throws KernelError Native.code_llvm(devnull, mod.invalid_kernel, Tuple{}) end @@ -456,20 +459,27 @@ end Native.code_llvm(mod.g, Tuple{Int64}; dump_module=true, kernel=true) end - @test @filecheck(begin - @check_not "@{{(julia|j)_expensive_[0-9]+}}" - Native.code_llvm(mod.g, Tuple{Int64}; dump_module=true, kernel=true, always_inline=true) - end) broken=broken + # suppress FileCheck diagnostics when the failure is known and expected + quiet(f) = broken ? redirect_stderr(f, devnull) : f() + + @test quiet() do + @filecheck begin + @check_not "@{{(julia|j)_expensive_[0-9]+}}" + Native.code_llvm(mod.g, Tuple{Int64}; dump_module=true, kernel=true, always_inline=true) + end + end broken=broken @test @filecheck begin @check "@{{(julia|j)_expensive_[0-9]+}}" Native.code_llvm(mod.h, Tuple{Int64}; dump_module=true, kernel=true) end - @test @filecheck(begin - @check_not "@{{(julia|j)_expensive_[0-9]+}}" - Native.code_llvm(mod.h, Tuple{Int64}; dump_module=true, kernel=true, always_inline=true) - end) broken=broken + @test quiet() do + @filecheck begin + @check_not "@{{(julia|j)_expensive_[0-9]+}}" + Native.code_llvm(mod.h, Tuple{Int64}; dump_module=true, kernel=true, always_inline=true) + end + end broken=broken end @testset "function attributes" begin @@ -495,6 +505,24 @@ end end end +@testset "CPU reference resolution" begin + # JIT-private symbols like `jl_get_pgcstack_resolved` (JuliaLang/julia#61527) cannot + # be looked up using `jl_cglobal`, so we should only resolve bindings that are + # actually loaded from, leaving called functions alone. + job, _ = Native.create_job(identity, (Nothing,)) + JuliaContext() do ctx + mod = parse(LLVM.Module, """ + declare void @jl_get_pgcstack_resolved() + + define void @entry() { + call void @jl_get_pgcstack_resolved() + ret void + }""") + GPUCompiler.prepare_execution!(job, mod) + @test haskey(functions(mod), "jl_get_pgcstack_resolved") + end +end + end ############################################################################################