From b74be2acf032ddb768861376d4acb05f83111523 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Wed, 21 Oct 2026 14:01:32 +0200 Subject: [PATCH 1/2] Keep more of the compiler concretely typed Like in the previous commits, code that operated on values inference couldn't type (names returned by `safe_name`, debug info file names, the signature in `classify_arguments`, entries of the `compiled` dictionary, runtime function declarations, loaded relocation targets, JLL library paths, and the result of a sibling compiler) was compiled with edges to the abstract signatures of generic Base methods. Packages that extend those (e.g. DataFrames' dependencies or FilePathsBase) then made Julia discard the precompiled code when loading GPUCompiler. Assert the known types, and hide the remaining dynamic call. --- src/debug.jl | 4 +++- src/determinism.jl | 4 ++-- src/driver.jl | 8 +++++++- src/irgen.jl | 2 +- src/mangling.jl | 4 ++-- src/relocation.jl | 2 +- src/rtlib.jl | 2 +- src/utils.jl | 3 +++ src/validation.jl | 1 + 9 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/debug.jl b/src/debug.jl index 881bad94d..382865a20 100644 --- a/src/debug.jl +++ b/src/debug.jl @@ -21,7 +21,9 @@ function backtrace(inst::LLVM.Instruction, bt = StackTraces.StackFrame[]) name = replace(something(scope.name, ""), r";$"=>"") isempty(name) && (name = "unknown") file = scope.file - path = file === nothing ? "unknown" : joinpath(file.directory, file.filename) + path = file === nothing ? "unknown" : + joinpath(file.directory::Union{Nothing,String}, + file.filename::Union{Nothing,String}) line = loc.line push!(bt, StackTraces.StackFrame(name, path, line)) end diff --git a/src/determinism.jl b/src/determinism.jl index 893e4307f..cf39af62b 100644 --- a/src/determinism.jl +++ b/src/determinism.jl @@ -78,12 +78,12 @@ function dedup_compile_units!(mod::LLVM.Module) cus = mds["llvm.dbg.cu"].operands length(cus) <= 1 && return false - canonical = Dict{Tuple,LLVM.Metadata}() + canonical = Dict{Vector{Union{Nothing,LLVM.Metadata}},LLVM.Metadata}() canonical_cus = LLVM.Metadata[] replacement = Dict{LLVM.Metadata,LLVM.Metadata}() for cu in cus cu === nothing && continue - key = Tuple(cu.operands) + key = Union{Nothing,LLVM.Metadata}[cu.operands...] canon = get!(canonical, key, cu) if canon == cu push!(canonical_cus, cu) diff --git a/src/driver.jl b/src/driver.jl index 13c3f81dc..1065ca3ca 100644 --- a/src/driver.jl +++ b/src/driver.jl @@ -282,7 +282,13 @@ const __llvm_initialized = Ref(false) # which was loaded in a newer world. dyn_ir, dyn_meta = @invokelatest deferred_codegen(dyn_job, job) dyn_entry_fn = dyn_meta.entry.name - merge!(compiled, dyn_meta.compiled) + # the sibling compiler's result isn't inferred + dyn_compiled = dyn_meta.compiled + if dyn_compiled isa Dict{Any,Any} + merge!(compiled, dyn_compiled) + else + Base.inferencebarrier(merge!)(compiled, dyn_compiled) + end @assert dyn_ir.context == ir.context link_relocatable!(ir, relocations, dyn_ir, dyn_meta.relocations) diff --git a/src/irgen.jl b/src/irgen.jl index 87a5572e3..7ded0af64 100644 --- a/src/irgen.jl +++ b/src/irgen.jl @@ -565,7 +565,7 @@ end # is not passed at the LLVM level. function classify_arguments(@nospecialize(job::CompilerJob), codegen_ft::LLVM.FunctionType; post_optimization::Bool=false) - source_sig = job.source.specTypes + source_sig = job.source.specTypes::DataType source_types = collect(Any, source_sig.parameters) source_argnames = Base.method_argnames(job.source.def) diff --git a/src/mangling.jl b/src/mangling.jl index 35a53df25..5f58e7a00 100644 --- a/src/mangling.jl +++ b/src/mangling.jl @@ -71,7 +71,7 @@ function mangle_param(@nospecialize(t), substitutions = Any[], top = false) # check if we already know this base type str = find_substitution(t.name.wrapper) if str === nothing - tn = safe_name(t) + tn = safe_name(t)::String str = "$(length(tn))$tn" push!(substitutions, t.name.wrapper) end @@ -171,7 +171,7 @@ function mangle_param(@nospecialize(t), substitutions = Any[], top = false) bits = string(reinterpret(UInt64, t); base=16) "Ld$(bits)E" else - tn = safe_name(t) # TODO: actually does support digits... + tn = safe_name(t)::String # TODO: actually does support digits... if startswith(tn, r"\d") # C++ classes cannot start with a digit, so mangling doesn't support it tn = "_$(tn)" diff --git a/src/relocation.jl b/src/relocation.jl index 59da580f8..31bfab2ae 100644 --- a/src/relocation.jl +++ b/src/relocation.jl @@ -83,7 +83,7 @@ end function resolve_relocation_target(target::CGlobalRef) if target.library === nothing # `jl_cglobal` accepts the symbol directly and does the process-wide `jl_dlfind`. - address = ccall(:jl_cglobal, Any, (Any, Any), target.symbol, UInt) + address = ccall(:jl_cglobal, Any, (Any, Any), target.symbol, UInt)::Ptr{UInt} return unsafe_load(address + target.offset) end handle = Libdl.dlopen(target.library) diff --git a/src/rtlib.jl b/src/rtlib.jl index 6c2d16a59..e38bd00c6 100644 --- a/src/rtlib.jl +++ b/src/rtlib.jl @@ -37,7 +37,7 @@ function LLVM.call!(builder, rt::Runtime.RuntimeMethodInstance, args=LLVM.Value[ runtime_function_type(job, rt) end LLVM.Function(mod, rt.llvm_name, ft) - end + end::LLVM.Function ft = f.function_type if !isdeclaration(f) && (rt.name !== :gc_pool_alloc && rt.name !== :report_exception) # XXX: uses of the gc_pool_alloc intrinsic can be introduced _after_ the runtime diff --git a/src/utils.jl b/src/utils.jl index 3fc15d791..02758d794 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -72,6 +72,9 @@ end struct ExternalBackend library::String # path to the shared library, as exported by its JLL prefix::String # e.g. "NVPTX" for `NVPTXCompile` + + # JLLs are loaded lazily, so the type of their library paths isn't inferred + ExternalBackend(library, prefix) = new(library::String, prefix::String) end function api(backend::ExternalBackend, name::String) diff --git a/src/validation.jl b/src/validation.jl index 87e144ed4..2b0ea8d9b 100644 --- a/src/validation.jl +++ b/src/validation.jl @@ -656,6 +656,7 @@ function emitted_functions(mod::LLVM.Module, compiled) for entry in values(compiled) for name in (entry.func, entry.specfunc) name === nothing && continue + name = name::String # `compiled` holds entries of any type emitted[name] = entry # functions may have been renamed since, but not their debug information f = get(functions, name, nothing) From 03a1ab5567f201568c0e0645be0dc149cbc1f3b8 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Wed, 21 Oct 2026 15:06:34 +0200 Subject: [PATCH 2/2] Metal: Type the words of argument type names concretely `julia_type_words` recurses through `@nospecialize`d arguments, so its result wasn't inferred, and building the type names of struct arguments for their metadata was compiled with edges to generic concatenation methods (`vcat` of values of unknown type, `Base.tail(::Any)`), which ChainRulesCore invalidates. With it loaded first, that discarded the precompiled `finish_ir!` of the Metal back-end, and the first Metal kernel (e.g. the one `fill!` launches) took ~2 s longer to compile. Declare the result type and collect the words as `String`s. --- src/metal.jl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/metal.jl b/src/metal.jl index a7b63cadb..06a019f78 100644 --- a/src/metal.jl +++ b/src/metal.jl @@ -2389,18 +2389,19 @@ function msl_identifier(@nospecialize(T)) str = String(strip(replace(julia_type_words(T), r"[^A-Za-z0-9_]+" => "_"), '_')) return isempty(str) || isdigit(str[1]) ? "_" * str : str end -function julia_type_words(@nospecialize(x)) +# (recursive, so the result type is declared for inference) +function julia_type_words(@nospecialize(x))::String if x isa DataType - join([String(nameof(x)); map(julia_type_words, x.parameters)], "_") + join([String(nameof(x)); String[julia_type_words(p) for p in x.parameters]], "_") elseif x isa Union # (sorted, as Julia orders union members by module name, which can be generated) - join(["Union"; sort!(map(julia_type_words, Base.uniontypes(x)))], "_") + join(["Union"; sort!(String[julia_type_words(T) for T in Base.uniontypes(x)])], "_") elseif x isa UnionAll julia_type_words(Base.unwrap_unionall(x)) elseif x isa TypeVar String(x.name) elseif x isa Tuple - join(map(julia_type_words, x), "_") + join(String[julia_type_words(y) for y in x], "_") elseif x isa Union{Number,Symbol,AbstractChar,AbstractString} string(x) else