From 81d46eba688daef6f81eb3503cc5527047e40ebe Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Wed, 23 Sep 2026 18:12:59 +0200 Subject: [PATCH] Keep inferred sources on Julia < 1.12. Before Julia 1.12, codegen itself looks up callees in our cache. If Julia discarded a callee's inferred source (for example, a non-inlineable `@nospecialize` helper with a non-compileable signature), `jl_compile_workqueue` re-inferred it with Julia's native interpreter, bypassing our method tables. Keep inferred sources on those versions. This also lets Julia 1.10 compile `@nospecialize` calls without a dynamic `jl_invoke`, as 1.11 and later already do. --- src/jlgen.jl | 7 ++++++- test/native.jl | 26 ++++++++------------------ 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/jlgen.jl b/src/jlgen.jl index c54d93fb7..4e77bb50a 100644 --- a/src/jlgen.jl +++ b/src/jlgen.jl @@ -252,7 +252,12 @@ end CC.may_optimize(interp::GPUInterpreter) = true CC.may_compress(interp::GPUInterpreter) = true -CC.may_discard_trees(interp::GPUInterpreter) = true +# before Julia 1.12, codegen looks up callees in our cache itself (see +# `compile_method_instance`), and re-infers those whose inferred source was discarded +# (non-inlineable functions with a non-compileable signature, e.g. `@nospecialize` helpers) +# with Julia's native interpreter, bypassing our method tables. keep the source of +# everything we infer there; later versions recover it using our interpreter. +CC.may_discard_trees(interp::GPUInterpreter) = VERSION >= v"1.12.0-DEV.1823" @static if VERSION <= v"1.12.0-DEV.1531" CC.verbose_stmt_info(interp::GPUInterpreter) = false end diff --git a/test/native.jl b/test/native.jl index 9b2a61b3b..a6ea29402 100644 --- a/test/native.jl +++ b/test/native.jl @@ -1860,25 +1860,15 @@ end kernel(a, b) = (unsafe_store!(b, nospecialize_child(a)); return) end - if VERSION >= v"1.11-" - # Preserve the inferred specialization despite @nospecialize. - @test @filecheck begin - @check_label "define {{.*}} @{{(julia|j)_kernel_[0-9]+}}" - @check_not "jl_invoke" - @check_not "apply_generic" - Native.code_llvm(mod.kernel, Tuple{Int,Ptr{Int}}; dump_module=true) - end - Native.code_execution(mod.kernel, Tuple{Int,Ptr{Int}}) - else - # 1.10 still emits a dynamic invoke for this @nospecialize call - @test_throws_message(InvalidIRError, - Native.code_execution(mod.kernel, Tuple{Int,Ptr{Int}})) do msg - occursin("invalid LLVM IR", msg) && - occursin(GPUCompiler.DYNAMIC_CALL, msg) && - occursin("call to nospecialize_child", msg) && - occursin(r"\[\d+\] kernel", msg) - end + # Preserve the inferred specialization despite @nospecialize (on 1.10, this relies on + # keeping the inferred source, see `may_discard_trees`) + @test @filecheck begin + @check_label "define {{.*}} @{{(julia|j)_kernel_[0-9]+}}" + @check_not "jl_invoke" + @check_not "apply_generic" + Native.code_llvm(mod.kernel, Tuple{Int,Ptr{Int}}; dump_module=true) end + Native.code_execution(mod.kernel, Tuple{Int,Ptr{Int}}) end @testset "dynamic call (apply)" begin