From ae520af9f8e94f81ae71373cf1ffaab31d085f80 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 10 Jan 2025 13:11:32 +0100 Subject: [PATCH] Updates for Julia 1.12 (#656) --- src/jlgen.jl | 70 +++++++++++++++++++++++++------------------- src/validation.jl | 23 ++++++++++++++- test/gcn_tests.jl | 16 +++++----- test/native_tests.jl | 16 +++++----- test/ptx_tests.jl | 28 +++++++++--------- test/spirv_tests.jl | 2 +- 6 files changed, 93 insertions(+), 62 deletions(-) diff --git a/src/jlgen.jl b/src/jlgen.jl index d2f0fdfc..ce8aeedf 100644 --- a/src/jlgen.jl +++ b/src/jlgen.jl @@ -480,8 +480,32 @@ end # HAS_INTEGRATED_CACHE ## codegen/inference integration function ci_cache_populate(interp, cache, mi, min_world, max_world) - if VERSION >= v"1.12.0-DEV.15" - inferred_ci = CC.typeinf_ext_toplevel(interp, mi, CC.SOURCE_MODE_FORCE_SOURCE) # or SOURCE_MODE_FORCE_SOURCE_UNCACHED? + codeinfos = Pair{CodeInstance, CodeInfo}[] + @static if VERSION >= v"1.12.0-DEV.1434" + # see typeinfer.jl: typeinf_ext_toplevel + ci = CC.typeinf_ext(interp, mi, CC.SOURCE_MODE_NOT_REQUIRED) + inspected = IdSet{CodeInstance}() + tocompile = CodeInstance[ci] + while !isempty(tocompile) + callee = pop!(tocompile) + callee in inspected && continue + push!(inspected, callee) + # now make sure everything has source code, if desired + mi = CC.get_ci_mi(callee) + def = mi.def + if CC.use_const_api(callee) + src = CC.codeinfo_for_const(interp, mi, ci.rettype_const) + else + # TODO: typeinf_code could return something with different edges/ages/owner/abi (needing an update to callee), which we don't handle here + src = CC.typeinf_code(interp, mi, true) + end + if src isa CodeInfo + CC.collectinvokes!(tocompile, src) + push!(codeinfos, callee => src) + end + end + elseif VERSION >= v"1.12.0-DEV.15" + inferred_ci = CC.typeinf_ext_toplevel(interp, mi, CC.SOURCE_MODE_FORCE_SOURCE) @assert inferred_ci !== nothing "Inference of $mi failed" # inference should have populated our cache @@ -512,13 +536,13 @@ function ci_cache_populate(interp, cache, mi, min_world, max_world) end end - return ci::CodeInstance + return codeinfos end function ci_cache_lookup(cache, mi, min_world, max_world) wvc = WorldView(cache, min_world, max_world) ci = CC.get(wvc, mi, nothing) - if ci !== nothing && ci.inferred === nothing + if VERSION < v"1.12.0-DEV.1434" && ci !== nothing && ci.inferred === nothing # if for some reason we did end up with a codeinfo without inferred source, e.g., # because of calling `Base.return_types` which only sets rettyp, pretend we didn't # run inference so that we re-infer now and not during codegen (which is disallowed) @@ -543,23 +567,6 @@ end CompilationPolicyExtern = 1 end -# HACK: in older versions of Julia, `jl_create_native` doesn't take a world argument -# but instead always generates code for the current world. note that this doesn't -# actually change the world age, but just spoofs the counter `jl_create_native` reads. -# XXX: Base.get_world_counter is supposed to be monotonically increasing and is runtime global. -macro in_world(world, ex) - quote - actual_world = Base.get_world_counter() - world_counter = cglobal(:jl_world_counter, Csize_t) - unsafe_store!(world_counter, $(esc(world))) - try - $(esc(ex)) - finally - unsafe_store!(world_counter, actual_world) - end - end -end - """ precompile(job::CompilerJob) @@ -574,10 +581,7 @@ function Base.precompile(@nospecialize(job::CompilerJob)) # populate the cache interp = get_interpreter(job) cache = CC.code_cache(interp) - if ci_cache_lookup(cache, job.source, job.world, job.world) === nothing - ci_cache_populate(interp, cache, job.source, job.world, job.world) - return ci_cache_lookup(cache, job.source, job.world, job.world) !== nothing - end + ci_cache_populate(interp, cache, job.source, job.world, job.world) return true end @@ -589,10 +593,7 @@ function compile_method_instance(@nospecialize(job::CompilerJob)) # populate the cache interp = get_interpreter(job) cache = CC.code_cache(interp) - if ci_cache_lookup(cache, job.source, job.world, job.world) === nothing - ci_cache_populate(interp, cache, job.source, job.world, job.world) - @assert ci_cache_lookup(cache, job.source, job.world, job.world) !== nothing - end + populated = ci_cache_populate(interp, cache, job.source, job.world, job.world) # create a callback to look-up function in our cache, # and keep track of the method instances we needed. @@ -639,7 +640,16 @@ function compile_method_instance(@nospecialize(job::CompilerJob)) Metadata(ConstantInt(DEBUG_METADATA_VERSION())) end - native_code = if VERSION >= v"1.12.0-DEV.1667" + native_code = if VERSION >= v"1.12.0-DEV.1823" + codeinfos = Any[] + for (ci, src) in populated + # each item in the list should be a CodeInstance followed by a CodeInfo + # indicating something to compile + push!(codeinfos, ci::CodeInstance) + push!(codeinfos, src::CodeInfo) + end + @ccall jl_emit_native(codeinfos::Vector{Any}, ts_mod::LLVM.API.LLVMOrcThreadSafeModuleRef, Ref(params)::Ptr{Base.CodegenParams}, #=extern linkage=# false::Cint)::Ptr{Cvoid} + elseif VERSION >= v"1.12.0-DEV.1667" ccall(:jl_create_native, Ptr{Cvoid}, (Vector{MethodInstance}, LLVM.API.LLVMOrcThreadSafeModuleRef, Ptr{Base.CodegenParams}, Cint, Cint, Cint, Csize_t, Ptr{Cvoid}), [job.source], ts_mod, Ref(params), CompilationPolicyExtern, #=imaging mode=# 0, #=external linkage=# 0, job.world, Base.unsafe_convert(Ptr{Nothing}, lookup_cb)) diff --git a/src/validation.jl b/src/validation.jl index f3ec3075..bdbf7861 100644 --- a/src/validation.jl +++ b/src/validation.jl @@ -250,9 +250,30 @@ function check_ir!(job, errors::Vector{IRError}, inst::LLVM.CallInst) @safe_debug "Decoding arguments to jl_reresolve_binding_value_seqcst failed" inst bb=LLVM.parent(inst) push!(errors, (DELAYED_BINDING, bt, nothing)) end + elseif startswith(fn, "tojlinvoke") + try + fun, args, nargs = arguments(inst) + fun = first(operands(fun::ConstantExpr))::ConstantInt + fun = convert(Int, fun) + fun = Ptr{Cvoid}(fun) + fun = Base.unsafe_pointer_to_objref(fun)::Base.Function + push!(errors, (DYNAMIC_CALL, bt, fun)) + # XXX: an invoke trampoline happens when codegen doesn't have access to code + # which suggests a GPUCompiler.jl bug. throw an error instead? + catch e + @safe_debug "Decoding arguments to jl_invoke failed" inst bb = LLVM.parent(inst) + push!(errors, (DYNAMIC_CALL, bt, nothing)) + end elseif fn == "jl_invoke" || fn == "ijl_invoke" + # most invokes are contained in a trampoline handled above, + # but some direct ones remain (e.g., with `@nospecialize`) + # XXX: this shouldn't be true on 1.12+ anymore; jl_invoke is always trampolined + caller = LLVM.parent(LLVM.parent(inst)) + if startswith(LLVM.name(caller), "tojlinvoke") + return + end try - f, args, nargs, meth = arguments(inst) + fun, args, nargs, meth = arguments(inst) meth = first(operands(meth::ConstantExpr))::ConstantInt meth = convert(Int, meth) meth = Ptr{Cvoid}(meth) diff --git a/test/gcn_tests.jl b/test/gcn_tests.jl index 0fb64f24..fe408337 100644 --- a/test/gcn_tests.jl +++ b/test/gcn_tests.jl @@ -53,8 +53,8 @@ end end asm = sprint(io->GCN.code_native(io, parent, Tuple{Int64}; dump_module=true)) - @test occursin(r"s_add_u32.*julia_child_.*@rel32@", asm) - @test occursin(r"s_addc_u32.*julia_child_.*@rel32@", asm) + @test occursin(r"s_add_u32.*(julia|j)_child_.*@rel32@", asm) + @test occursin(r"s_addc_u32.*(julia|j)_child_.*@rel32@", asm) end @testset "kernel functions" begin @@ -89,10 +89,10 @@ end end asm = sprint(io->GCN.code_native(io, mod.parent1, Tuple{Int}; dump_module=true)) - @test occursin(r"\.type.*julia_[[:alnum:]_.]*child_\d*,@function", asm) + @test occursin(r"\.type.*(julia|j)_[[:alnum:]_.]*child_\d*,@function", asm) asm = sprint(io->GCN.code_native(io, mod.parent2, Tuple{Int}; dump_module=true)) - @test occursin(r"\.type.*julia_[[:alnum:]_.]*child_\d*,@function", asm) + @test occursin(r"\.type.*(julia|j)_[[:alnum:]_.]*child_\d*,@function", asm) end @testset "child function reuse bis" begin @@ -115,12 +115,12 @@ end end asm = sprint(io->GCN.code_native(io, mod.parent1, Tuple{Int}; dump_module=true)) - @test occursin(r"\.type.*julia_[[:alnum:]_.]*child1_\d*,@function", asm) - @test occursin(r"\.type.*julia_[[:alnum:]_.]*child2_\d*,@function", asm) + @test occursin(r"\.type.*(julia|j)_[[:alnum:]_.]*child1_\d*,@function", asm) + @test occursin(r"\.type.*(julia|j)_[[:alnum:]_.]*child2_\d*,@function", asm) asm = sprint(io->GCN.code_native(io, mod.parent2, Tuple{Int}; dump_module=true)) - @test occursin(r"\.type.*julia_[[:alnum:]_.]*child1_\d*,@function", asm) - @test occursin(r"\.type.*julia_[[:alnum:]_.]*child2_\d*,@function", asm) + @test occursin(r"\.type.*(julia|j)_[[:alnum:]_.]*child1_\d*,@function", asm) + @test occursin(r"\.type.*(julia|j)_[[:alnum:]_.]*child2_\d*,@function", asm) end @testset "indirect sysimg function use" begin diff --git a/test/native_tests.jl b/test/native_tests.jl index 34f44472..d4a07442 100644 --- a/test/native_tests.jl +++ b/test/native_tests.jl @@ -16,10 +16,10 @@ using Test @test contains(ir, "MethodInstance for identity") ir = sprint(io->GPUCompiler.code_llvm(io, job)) - @test contains(ir, "julia_identity") + @test contains(ir, r"(julia|j)_identity") asm = sprint(io->GPUCompiler.code_native(io, job)) - @test contains(asm, "julia_identity") + @test contains(asm, r"(julia|j)_identity") end @testset "compilation" begin @@ -187,7 +187,7 @@ end ir = sprint(io->Native.code_llvm(io, valid_kernel, Tuple{}; optimize=false, dump_module=true)) # module should contain our function + a generic call wrapper - @test occursin(r"define\ .* void\ @.*julia_valid_kernel.*\(\)"x, ir) + @test occursin(r"define\ .* void\ @.*(julia|j)_valid_kernel.*\(\)"x, ir) @test !occursin("define %jl_value_t* @jlcall_", ir) # there should be no debug metadata @@ -208,7 +208,7 @@ end parent(i) = child(i) ir = sprint(io->Native.code_llvm(io, parent, Tuple{Int})) - @test occursin(r"call .+ @julia.+child.+", ir) + @test occursin(r"call .+ @(julia|j).+child.+", ir) end @testset "sysimg" begin @@ -295,18 +295,18 @@ end end ir = sprint(io->Native.code_llvm(io, mod.g, Tuple{Int64}; dump_module=true, kernel=true)) - @test occursin(r"^define.*julia_f_expensive"m, ir) + @test occursin(r"^define.*(julia|j)_f_expensive"m, ir) ir = sprint(io->Native.code_llvm(io, mod.g, Tuple{Int64}; dump_module=true, kernel=true, always_inline=true)) - @test !occursin(r"^define.*julia_f_expensive"m, ir) + @test !occursin(r"^define.*(julia|j)_f_expensive"m, ir) ir = sprint(io->Native.code_llvm(io, mod.h, Tuple{Int64}; dump_module=true, kernel=true, always_inline=true)) - @test !occursin(r"^define.*julia_f_expensive"m, ir) + @test !occursin(r"^define.*(julia|j)_f_expensive"m, ir) ir = sprint(io->Native.code_llvm(io, mod.h, Tuple{Int64}; dump_module=true, kernel=true)) - @test occursin(r"^define.*julia_f_expensive"m, ir) + @test occursin(r"^define.*(julia|j)_f_expensive"m, ir) end @testset "function attributes" begin diff --git a/test/ptx_tests.jl b/test/ptx_tests.jl index bb89c6be..70f67bbb 100644 --- a/test/ptx_tests.jl +++ b/test/ptx_tests.jl @@ -27,8 +27,8 @@ end end ir = sprint(io->PTX.code_llvm(io, mod.kernel, Tuple{mod.Aggregate})) - @test occursin(r"@julia_kernel\w*\(({ i64 }|\[1 x i64\])\* ", ir) || - occursin(r"@julia_kernel\w*\(ptr ", ir) + @test occursin(r"@(julia|j)_kernel\w*\(({ i64 }|\[1 x i64\])\* ", ir) || + occursin(r"@(julia|j)_kernel\w*\(ptr ", ir) ir = sprint(io->PTX.code_llvm(io, mod.kernel, Tuple{mod.Aggregate}; kernel=true)) @test occursin(r"@_Z6kernel9Aggregate\(.*({ i64 }|\[1 x i64\]) ", ir) @@ -89,7 +89,7 @@ end end ir = sprint(io->PTX.code_llvm(io, mod.kernel, Tuple{})) - @test occursin(r"@julia_kernel\w*\(\)", ir) + @test occursin(r"@(julia|j)_kernel\w*\(\)", ir) ir = sprint(io->PTX.code_llvm(io, mod.kernel, Tuple{}; kernel=true)) @test occursin("@_Z6kernel([1 x i64] %state)", ir) @@ -117,10 +117,10 @@ end @test occursin(r"@_Z6kernelP5Int64\(\[1 x i64\] %state", ir) # child1 doesn't use the state - @test occursin(r"@julia_child1\w*\((i64|i8\*|ptr)", ir) + @test occursin(r"@(julia|j)_child1\w*\((i64|i8\*|ptr)", ir) # child2 does - @test occursin(r"@julia_child2\w*\(\[1 x i64\] %state", ir) + @test occursin(r"@(julia|j)_child2\w*\(\[1 x i64\] %state", ir) # can't have the unlowered intrinsic @test !occursin("julia.gpu.state_getter", ir) @@ -149,7 +149,7 @@ end end asm = sprint(io->PTX.code_native(io, mod.parent, Tuple{Int64})) - @test occursin(r"call.uni\s+julia_child_"m, asm) + @test occursin(r"call.uni\s+(julia|j)_child_"m, asm) end @testset "kernel functions" begin @@ -167,8 +167,8 @@ end asm = sprint(io->PTX.code_native(io, mod.entry, Tuple{Int64}; kernel=true, dump_module=true)) @test occursin(".visible .entry _Z5entry5Int64", asm) - @test !occursin(".visible .func julia_nonentry", asm) - @test occursin(".func julia_nonentry", asm) + @test !occursin(r"\.visible \.func (julia|j)_nonentry", asm) + @test occursin(r"\.func (julia|j)_nonentry", asm) @testset "property_annotations" begin asm = sprint(io->PTX.code_native(io, mod.entry, Tuple{Int64}; kernel=true)) @@ -214,10 +214,10 @@ end end asm = sprint(io->PTX.code_native(io, mod.parent1, Tuple{Int})) - @test occursin(".func julia_child_", asm) + @test occursin(r"\.func (julia|j)_child_", asm) asm = sprint(io->PTX.code_native(io, mod.parent2, Tuple{Int})) - @test occursin(".func julia_child_", asm) + @test occursin(r"\.func (julia|j)_child_", asm) end @testset "child function reuse bis" begin @@ -241,12 +241,12 @@ end end asm = sprint(io->PTX.code_native(io, mod.parent1, Tuple{Int})) - @test occursin(".func julia_child1_", asm) - @test occursin(".func julia_child2_", asm) + @test occursin(r"\.func (julia|j)_child1_", asm) + @test occursin(r"\.func (julia|j)_child2_", asm) asm = sprint(io->PTX.code_native(io, mod.parent2, Tuple{Int})) - @test occursin(".func julia_child1_", asm) - @test occursin(".func julia_child2_", asm) + @test occursin(r"\.func (julia|j)_child1_", asm) + @test occursin(r"\.func (julia|j)_child2_", asm) end @testset "indirect sysimg function use" begin diff --git a/test/spirv_tests.jl b/test/spirv_tests.jl index 1774e0ae..9de9a2cc 100644 --- a/test/spirv_tests.jl +++ b/test/spirv_tests.jl @@ -89,7 +89,7 @@ end end asm = sprint(io->SPIRV.code_native(io, kernel, Tuple{Bool}; kernel=true)) - @test occursin(r"OpFunctionCall %void %julia_error", asm) + @test occursin(r"OpFunctionCall %void %(julia|j)_error", asm) end end