From 4e4bcdcd74838ec4c54ed49b60c1cc83304c64cf Mon Sep 17 00:00:00 2001 From: oscarddssmith Date: Thu, 5 Nov 2026 13:36:51 -0700 Subject: [PATCH] Keep compiled entries pointing at the functions after sanitizing names `irgen` sanitizes the names of defined functions after `compiled` has recorded them, so entries for functions like `julia_f+_0` named functions that no longer exist. Record the names actually given and update the entries. Sanitized names can also collide, e.g. `julia_+_0` and `julia_-_0` from Julia 1.14, which numbers functions per compilation. LLVM would then uniquify the second name with a `.` suffix, which is not a safe name, so pick a free `_N` suffix instead. That keeps consumers that sanitize `compiled` names themselves, like Enzyme, finding the right function. Co-authored-by: Tim Besard Co-Authored-By: Claude Opus 5.5 --- src/irgen.jl | 18 +++++++++++++++--- test/native.jl | 24 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/irgen.jl b/src/irgen.jl index 1456c008..072dfdf4 100644 --- a/src/irgen.jl +++ b/src/irgen.jl @@ -51,14 +51,26 @@ function irgen(@nospecialize(job::CompilerJob)) end end - # sanitize defined functions (Julia doesn't when using the external codegen policy) + # sanitize defined functions (Julia doesn't when using the external codegen policy). + # Sanitized names can collide, e.g. `julia_+_0` and `julia_-_0` from Julia 1.14, which + # numbers functions per compilation. Uniquify them ourselves, as LLVM would add a `.`. + renamed = Dict{String,String}() for val in collect(mod.functions) isdeclaration(val) && continue old_name = val.name new_name = safe_name(old_name) - if old_name != new_name - val.name = new_name + old_name == new_name && continue + unique_name, i = new_name, 0 + while haskey(mod.functions, unique_name) + i += 1 + unique_name = "$(new_name)_$i" end + val.name = unique_name + renamed[old_name] = unique_name + end + for (mi, entry) in compiled + compiled[mi] = (; entry..., func = get(renamed, entry.func, entry.func), + specfunc = get(renamed, entry.specfunc, entry.specfunc)) end # rename and process the entry point diff --git a/test/native.jl b/test/native.jl index e7959e0c..a387305f 100644 --- a/test/native.jl +++ b/test/native.jl @@ -430,6 +430,30 @@ end end end + @testset "sanitized function names" begin + # `+` and `*` both sanitize to `_`, so the callees' names collide wherever Julia + # numbers functions per compilation. + mod = @eval module $(gensym()) + @noinline var"f+"(x) = x + 1 + @noinline var"f*"(x) = x * 3 + kernel(x) = var"f+"(x) - var"f*"(x) + end + + job, _ = Native.create_job(mod.kernel, (Int,); validate=false) + JuliaContext() do ctx + ir, meta = GPUCompiler.compile(:llvm, job) + + specfuncs = Dict(mi.def.name => entry.specfunc for (mi, entry) in meta.compiled) + @test allunique(values(specfuncs)) + for name in values(specfuncs) + @test haskey(ir.functions, name) + @test name == GPUCompiler.safe_name(name) + end + @test occursin("mul", string(ir.functions[specfuncs[Symbol("f*")]])) + @test !occursin("mul", string(ir.functions[specfuncs[Symbol("f+")]])) + end + end + @testset "cached compilation" begin mod = @eval module $(gensym()) @noinline child(i) = i