diff --git a/src/determinism.jl b/src/determinism.jl index 50f945a1..3a44fb7a 100644 --- a/src/determinism.jl +++ b/src/determinism.jl @@ -69,6 +69,24 @@ end ## compile-unit deduplication +# Repoint the operands of the metadata graph rooted at `md` according to `replacement`. +function repoint!(@nospecialize(md), replacement::Dict{LLVM.Metadata,LLVM.Metadata}, + visited::Set{LLVM.Metadata}) + md isa LLVM.MDNode || return + md in visited && return + push!(visited, md) + # iterate a copy, as replacing an operand re-uniques the node + for (i, op) in enumerate(collect(md.operands)) + op isa LLVM.MDNode || continue + repl = get(replacement, op, nothing) + if repl !== nothing + md.operands[i] = repl + else + repoint!(op, replacement, visited) + end + end +end + # LLVM does not merge distinct `DICompileUnit`s when linking. Group compile units by their # uniqued operands, repoint references to the first unit in each group, and shrink # `llvm.dbg.cu` to those canonical units. @@ -95,34 +113,18 @@ function dedup_compile_units!(mod::LLVM.Module) # Metadata forms a graph, so walk every attachment reachable from module values. visited = Set{LLVM.Metadata}() - function repoint!(@nospecialize(md)) - md isa LLVM.MDNode || return - md in visited && return - push!(visited, md) - # iterate a copy, as replacing an operand re-uniques the node - for (i, op) in enumerate(collect(md.operands)) - op isa LLVM.MDNode || continue - repl = get(replacement, op, nothing) - if repl !== nothing - md.operands[i] = repl - else - repoint!(op) - end - end - end - for f in mod.functions sp = f.subprogram - sp === nothing || repoint!(sp) + sp === nothing || repoint!(sp, replacement, visited) for bb in f.blocks, inst in bb.instructions for (kind, md) in inst.metadata - repoint!(md) + repoint!(md, replacement, visited) end end end for gv in mod.globals for (kind, md) in gv.metadata - repoint!(md) + repoint!(md, replacement, visited) end end diff --git a/src/irgen.jl b/src/irgen.jl index 7ded0af6..679e8b23 100644 --- a/src/irgen.jl +++ b/src/irgen.jl @@ -823,6 +823,22 @@ end # once LLVM supports this pattern, consider going back to passing the state by reference, # so that the julia.gpu.state_getter` can be simplified to return an opaque pointer. +# add the functions using `val` (looking through constant expressions) to `additions`, +# unless they are already part of `worklist` +function add_using_functions!(additions, worklist, val) + for use in val.uses + user = use.user + if user isa Instruction + f = user.parent.parent + in(f, worklist) || push!(additions, f) + elseif user isa ConstantExpr + add_using_functions!(additions, worklist, user) + else + error("Don't know how to check uses of $user. Please file an issue.") + end + end +end + # add a state argument to every function in the module, starting from the kernel entry point struct AddKernelState job::CompilerJob @@ -852,22 +868,8 @@ function (self::AddKernelState)(mod::LLVM.Module) # iteratively discover functions that use the intrinsic or any function calling it worklist_length = length(worklist) additions = LLVM.Function[] - function check_user(val) - if val isa Instruction - bb = val.parent - new_f = bb.parent - in(new_f, worklist) || push!(additions, new_f) - elseif val isa ConstantExpr - # constant expressions don't have a parent; we need to look up their uses - for use in val.uses - check_user(use.user) - end - else - error("Don't know how to check uses of $val. Please file an issue.") - end - end - for f in worklist, use in f.uses - check_user(use.user) + for f in worklist + add_using_functions!(additions, worklist, f) end for f in additions push!(worklist, f) @@ -957,77 +959,78 @@ function (self::AddKernelState)(mod::LLVM.Module) erase!(f) end - # update uses of the new function, modifying call sites to include the kernel state - function rewrite_uses!(f, ft) - # update uses - @dispose builder=IRBuilder() begin - for use in collect(f.uses) - val = use.user - if val isa LLVM.CallBase && val.called_operand == f - # NOTE: we don't rewrite calls using Julia's jlcall calling convention, - # as those have a fixed argument list, passing actual arguments - # in an array of objects. that doesn't matter, for now, since - # GPU back-ends don't support such calls anyhow. but if we ever - # want to support kernel state passing on more capable back-ends, - # we'll need to update the argument array instead. - if val.callconv == 37 || val.callconv == 38 - # TODO: update for LLVM 15 when JuliaLang/julia#45088 is merged. - continue - end - - # forward the state argument - position!(builder, LLVM.before(val)) - state_arg = call!(builder, state_intr_ft, state_intr, Value[], "state") - new_val = if val isa LLVM.CallInst - call!(builder, ft, f, Value[state_arg, val.arguments...], - val.operand_bundles) - else - # TODO: invoke and callbr - error("Rewrite of $(typeof(val))-based calls is not implemented: $val") - end - new_val.callconv = val.callconv - - replace_uses!(val, new_val) - @assert isempty(val.uses) - erase!(val) - elseif val isa LLVM.CallBase - # the function is being passed as an argument. to avoid having to - # rewrite the target function, instead case the rewritten function to - # the old stateless type. - # XXX: we won't have to do this with opaque pointers. - position!(builder, LLVM.before(val)) - target_ft = val.called_type - new_args = Value[] - for (param_typ, arg) in zip(target_ft.parameters, val.arguments) - push!(new_args, arg.value_type != param_typ ? - const_bitcast(arg, param_typ) : arg) - end - new_val = call!(builder, val.called_type, val.called_operand, new_args, - val.operand_bundles) - new_val.callconv = val.callconv - - replace_uses!(val, new_val) - @assert isempty(val.uses) - erase!(val) - elseif val isa LLVM.StoreInst - # the function is being stored, which again we'll permit like before. - elseif val isa ConstantExpr - rewrite_uses!(val, ft) - else - error("Cannot rewrite $(typeof(val)) use of function: $val") - end - end - end - end + # update uses of the new functions, modifying call sites to include the kernel state for f in values(workmap) ft = f.function_type - rewrite_uses!(f, ft) + rewrite_state_uses!(f, ft, state_intr, state_intr_ft) end return true end AddKernelStatePass(job) = ModulePass("AddKernelStatePass", AddKernelState(job)) +# modify the call sites of `f` (of type `ft`) to pass the kernel state +function rewrite_state_uses!(f, ft, state_intr, state_intr_ft) + @dispose builder=IRBuilder() begin + for use in collect(f.uses) + val = use.user + if val isa LLVM.CallBase && val.called_operand == f + # NOTE: we don't rewrite calls using Julia's jlcall calling convention, + # as those have a fixed argument list, passing actual arguments + # in an array of objects. that doesn't matter, for now, since + # GPU back-ends don't support such calls anyhow. but if we ever + # want to support kernel state passing on more capable back-ends, + # we'll need to update the argument array instead. + if val.callconv == 37 || val.callconv == 38 + # TODO: update for LLVM 15 when JuliaLang/julia#45088 is merged. + continue + end + + # forward the state argument + position!(builder, LLVM.before(val)) + state_arg = call!(builder, state_intr_ft, state_intr, Value[], "state") + new_val = if val isa LLVM.CallInst + call!(builder, ft, f, Value[state_arg, val.arguments...], + val.operand_bundles) + else + # TODO: invoke and callbr + error("Rewrite of $(typeof(val))-based calls is not implemented: $val") + end + new_val.callconv = val.callconv + + replace_uses!(val, new_val) + @assert isempty(val.uses) + erase!(val) + elseif val isa LLVM.CallBase + # the function is being passed as an argument. to avoid having to + # rewrite the target function, instead case the rewritten function to + # the old stateless type. + # XXX: we won't have to do this with opaque pointers. + position!(builder, LLVM.before(val)) + target_ft = val.called_type + new_args = Value[] + for (param_typ, arg) in zip(target_ft.parameters, val.arguments) + push!(new_args, arg.value_type != param_typ ? + const_bitcast(arg, param_typ) : arg) + end + new_val = call!(builder, val.called_type, val.called_operand, new_args, + val.operand_bundles) + new_val.callconv = val.callconv + + replace_uses!(val, new_val) + @assert isempty(val.uses) + erase!(val) + elseif val isa LLVM.StoreInst + # the function is being stored, which again we'll permit like before. + elseif val isa ConstantExpr + rewrite_state_uses!(val, ft, state_intr, state_intr_ft) + else + error("Cannot rewrite $(typeof(val)) use of function: $val") + end + end + end +end + # lower calls to the state getter intrinsic. this is a two-step process, so that the state # argument can be added before optimization, and that optimization can introduce new uses # before the intrinsic getting lowered late during optimization. @@ -1365,22 +1368,8 @@ function add_input_arguments!(@nospecialize(job::CompilerJob), mod::LLVM.Module, # iteratively discover functions that use an intrinsic or any function calling it worklist_length = length(worklist) additions = Set{LLVM.Function}() - function scan_uses(val) - for use in val.uses - candidate = use.user - if isa(candidate, Instruction) - bb = candidate.parent - new_f = bb.parent - in(new_f, worklist) || push!(additions, new_f) - elseif isa(candidate, ConstantExpr) - scan_uses(candidate) - else - error("Don't know how to check uses of $candidate. Please file an issue.") - end - end - end for f in worklist - scan_uses(f) + add_using_functions!(additions, worklist, f) end for f in additions push!(worklist, f) @@ -1445,46 +1434,9 @@ function add_input_arguments!(@nospecialize(job::CompilerJob), mod::LLVM.Module, remove_dead_constant_users!(f) end - # update other uses of the old function, modifying call sites to pass the arguments - function rewrite_uses!(f, new_f) - # update uses - @dispose builder=IRBuilder() begin - for use in collect(f.uses) - val = use.user - if val isa LLVM.CallInst || val isa LLVM.InvokeInst || val isa LLVM.CallBrInst - callee_f = val.parent.parent - # forward the arguments - position!(builder, LLVM.before(val)) - new_val = if val isa LLVM.CallInst - call!(builder, new_f.function_type, new_f, - Value[val.arguments..., callee_f.parameters[end-nargs+1:end]...], - val.operand_bundles) - else - # TODO: invoke and callbr - error("Rewrite of $(typeof(val))-based calls is not implemented: $val") - end - new_val.callconv = val.callconv - - replace_uses!(val, new_val) - @assert isempty(val.uses) - erase!(val) - elseif val isa LLVM.ConstantExpr && val.opcode == LLVM.Opcode.BitCast - # XXX: why isn't this caught by the value materializer above? - target = val.operands[1] - @assert target == f - new_val = LLVM.const_bitcast(new_f, val.value_type) - rewrite_uses!(val, new_val) - # we can't simply replace this constant expression, as it may be used - # as a call, taking arguments (so we need to rewrite it to pass the input - # arguments). the old constant is dropped below, once it's unused. - else - error("Cannot rewrite unknown use of function: $val") - end - end - end - end + # update other uses of the old functions, modifying call sites to pass the arguments for (f, new_f) in workmap - rewrite_uses!(f, new_f) + rewrite_input_uses!(f, new_f, nargs) remove_dead_constant_users!(f) @assert isempty(f.uses) replace_metadata_uses!(f, new_f) @@ -1512,3 +1464,41 @@ function add_input_arguments!(@nospecialize(job::CompilerJob), mod::LLVM.Module, return mod.functions[entry_fn] end + +# modify the call sites of `f` to call `new_f`, passing on the trailing `nargs` arguments +function rewrite_input_uses!(f, new_f, nargs) + @dispose builder=IRBuilder() begin + for use in collect(f.uses) + val = use.user + if val isa LLVM.CallInst || val isa LLVM.InvokeInst || val isa LLVM.CallBrInst + callee_f = val.parent.parent + # forward the arguments + position!(builder, LLVM.before(val)) + new_val = if val isa LLVM.CallInst + call!(builder, new_f.function_type, new_f, + Value[val.arguments..., callee_f.parameters[end-nargs+1:end]...], + val.operand_bundles) + else + # TODO: invoke and callbr + error("Rewrite of $(typeof(val))-based calls is not implemented: $val") + end + new_val.callconv = val.callconv + + replace_uses!(val, new_val) + @assert isempty(val.uses) + erase!(val) + elseif val isa LLVM.ConstantExpr && val.opcode == LLVM.Opcode.BitCast + # XXX: why isn't this caught by the value materializer above? + target = val.operands[1] + @assert target == f + new_val = LLVM.const_bitcast(new_f, val.value_type) + rewrite_input_uses!(val, new_val, nargs) + # we can't simply replace this constant expression, as it may be used + # as a call, taking arguments (so we need to rewrite it to pass the input + # arguments). the old constant is dropped by the caller, once it's unused. + else + error("Cannot rewrite unknown use of function: $val") + end + end + end +end diff --git a/src/jlgen.jl b/src/jlgen.jl index e713cb49..940c64e3 100644 --- a/src/jlgen.jl +++ b/src/jlgen.jl @@ -798,7 +798,7 @@ function compile_method_instance(@nospecialize(job::CompilerJob)) # generate IR ts_mod = ThreadSafeModule("start") - native_code, llvm_mod = GC.@preserve lookup_cb let + GC.@preserve lookup_cb begin # configure the module ts_mod() do mod mod.triple = llvm_triple(job.config.target) @@ -855,8 +855,6 @@ function compile_method_instance(@nospecialize(job::CompilerJob)) # borrow it, and hand out a copy once we're done with the native code. LLVM.unsafe_module(llvm_ts_mod) end - - native_code, llvm_mod end # Older Julia merges the per-CodeInstance modules in pointer order; restore emission diff --git a/src/metal.jl b/src/metal.jl index a687e58d..30e54013 100644 --- a/src/metal.jl +++ b/src/metal.jl @@ -228,29 +228,25 @@ function is_boxed_constant(@nospecialize(gv::LLVM.GlobalVariable)) (length(els) >= 2 && is_pad(els[1]) && is_hdr(els[2])) end -# Are all (transitive constant) users of `gv` instructions, i.e. does the box escape only +# Are all (transitive constant) users of `v` instructions, i.e. does the box escape only # through function bodies? A box reachable from another global's *initializer* is the # `jl_true`/`jl_false` slot→box indirection, which lives fine in the constant space # (`add_global_address_spaces!`); demoting it would strand the slot's constant pointer. Only # the isbits-union interior boxes — whose payload address flows through a body `phi`/`select` # — need demotion, and those have instruction users exclusively. -function box_used_only_by_instructions(@nospecialize(gv::LLVM.GlobalVariable)) - ok = true - function walk(@nospecialize(v)) - for u in v.users - if u isa LLVM.Instruction - # a body use: fine - elseif u isa LLVM.GlobalVariable - ok = false # initializer reference (slot→box) - elseif u isa LLVM.Constant - walk(u) - else - ok = false - end +function box_used_only_by_instructions(@nospecialize(v::LLVM.Value)) + for u in v.users + if u isa LLVM.Instruction + # a body use: fine + elseif u isa LLVM.GlobalVariable + return false # initializer reference (slot→box) + elseif u isa LLVM.Constant + box_used_only_by_instructions(u) || return false + else + return false end end - walk(gv) - return ok + return true end # Demote materialized boxed-constant globals to per-function stack allocas. @@ -1276,6 +1272,26 @@ function lower_fences!(@nospecialize(job::CompilerJob{MetalCompilerTarget}), mod return true end +# Replace the names matching `codegen_name` in the strings of the metadata graph rooted at +# `md`, by `normalize`d names or, in the root node itself, by `replacement`. +function normalize_metadata!(@nospecialize(md), visited::Set{LLVM.MDNode}, codegen_name::Regex, + normalize, replacement=nothing) + md isa LLVM.MDNode || return + md in visited && return + push!(visited, md) + # (a copy, as replacing an operand can re-unique the node) + for (i, op) in enumerate(collect(md.operands)) + if op isa LLVM.MDString + str = convert(String, op) + occursin(codegen_name, str) || continue + new = replacement === nothing ? normalize(str) : replacement + new == str || (md.operands[i] = LLVM.MDString(new)) + elseif op isa LLVM.MDNode + normalize_metadata!(op, visited, codegen_name, normalize) + end + end +end + # Julia names each generated LLVM function `julia__`, where the counter is # drawn from a process-global codegen sequence and so differs from one session to the next. # Left anywhere in the emitted bitcode it makes the AIR (and the metallib wrapping it) @@ -1313,22 +1329,6 @@ function normalize_julia_symbol_names!(mod::LLVM.Module) # domain), so walk it, replacing every string that carries a name. Only nodes reachable # from a function or an instruction are visited, which is where inlining leaves its traces. visited = Set{LLVM.MDNode}() - function normalize_metadata!(@nospecialize(md), replacement=nothing) - md isa LLVM.MDNode || return - md in visited && return - push!(visited, md) - # (a copy, as replacing an operand can re-unique the node) - for (i, op) in enumerate(collect(md.operands)) - if op isa LLVM.MDString - str = convert(String, op) - occursin(codegen_name, str) || continue - new = replacement === nothing ? normalize(str) : replacement - new == str || (md.operands[i] = LLVM.MDString(new)) - elseif op isa LLVM.MDNode - normalize_metadata!(op) - end - end - end # the instruction metadata that can name a function: a debug location points at the # subprogram it came from (orphaned once that function is inlined away), and Julia's alias @@ -1345,7 +1345,7 @@ function normalize_julia_symbol_names!(mod::LLVM.Module) for f in mod.functions isdeclaration(f) && continue sp = f.subprogram - sp === nothing || normalize_metadata!(sp, f.name) + sp === nothing || normalize_metadata!(sp, visited, codegen_name, normalize, f.name) end for f in mod.functions isdeclaration(f) && continue @@ -1355,7 +1355,8 @@ function normalize_julia_symbol_names!(mod::LLVM.Module) rename!(inst) md = inst.metadata for kind in md_kinds - haskey(md, kind) && normalize_metadata!(md[kind]) + haskey(md, kind) && + normalize_metadata!(md[kind], visited, codegen_name, normalize) end end end @@ -1441,6 +1442,23 @@ function downgrade(input::Vector{UInt8}, version::VersionNumber) end +function remapType(src) + # TODO: shouldn't we recurse into structs here, making sure the parent object's + # address space matches the contained one? doesn't matter right now as we + # only use LLVMPtr (i.e. no rewriting of contained pointers needed) in the + # device addrss space (i.e. no mismatch between parent and field possible) + dst = if src isa LLVM.PointerType && src.addrspace == 0 + if supports_typed_pointers(context()) + LLVM.PointerType(remapType(src.element_type), #=device=# 1) + else + LLVM.PointerType(#=device=# 1) + end + else + src + end + return dst +end + # generic pointer removal # # every pointer argument (i.e. byref objs) to a kernel needs an address space attached. @@ -1479,23 +1497,6 @@ function add_parameter_address_spaces!(@nospecialize(job::CompilerJob), mod::LLV identity_word[arg.idx] end - function remapType(src) - # TODO: shouldn't we recurse into structs here, making sure the parent object's - # address space matches the contained one? doesn't matter right now as we - # only use LLVMPtr (i.e. no rewriting of contained pointers needed) in the - # device addrss space (i.e. no mismatch between parent and field possible) - dst = if src isa LLVM.PointerType && src.addrspace == 0 - if supports_typed_pointers(context()) - LLVM.PointerType(remapType(src.element_type), #=device=# 1) - else - LLVM.PointerType(#=device=# 1) - end - else - src - end - return dst - end - # generate the new function type & definition new_types = LLVMType[] for (i, param) in enumerate(ft.parameters) @@ -1687,21 +1688,21 @@ function add_global_address_spaces!(@nospecialize(job::CompilerJob), mod::LLVM.M # determine which functions we need to update function_worklist = Set{LLVM.Function}() - function check_user(val) + pending_users = LLVM.Value[] + for gv in keys(global_map) + append!(pending_users, gv.users) + end + while !isempty(pending_users) + val = pop!(pending_users) if val isa LLVM.Instruction bb = val.parent f = bb.parent push!(function_worklist, f) elseif val isa LLVM.ConstantExpr - for user in val.users - check_user(user) - end + append!(pending_users, val.users) end end - for gv in keys(global_map), user in gv.users - check_user(user) - end # update functions that use the global # Clones are appended in processing order, so don't iterate the pointer-hashed worklist. @@ -2966,13 +2967,6 @@ function lower_is_fpclass!(builder::IRBuilder, call::LLVM.CallBase) sub!(builder, magnitude, LLVM.ConstantInt(ityp, 1)), normal - 1) test_zero() = compare(LLVM.IntPredicate.EQ, magnitude, 0) - negative = nothing - function with_sign(test, neg) - if negative === nothing - negative = compare(LLVM.IntPredicate.SLT, bits, 0) - end - and!(builder, test, neg ? negative : not!(builder, negative)) - end # combine the tested classes, as encoded by the `FPClassTest` mask bits terms = LLVM.Value[] @@ -2984,12 +2978,18 @@ function lower_is_fpclass!(builder::IRBuilder, call::LLVM.CallBase) elseif tested(1) push!(terms, test_qnan()) end + negative = nothing for (test, negbit, posbit) in ((test_inf, 2, 9), (test_normal, 3, 8), (test_subnormal, 4, 7), (test_zero, 5, 6)) if tested(negbit) && tested(posbit) push!(terms, test()) elseif tested(negbit) || tested(posbit) - push!(terms, with_sign(test(), tested(negbit))) + term = test() + if negative === nothing + negative = compare(LLVM.IntPredicate.SLT, bits, 0) + end + sign = tested(negbit) ? negative : not!(builder, negative) + push!(terms, and!(builder, term, sign)) end end return isempty(terms) ? LLVM.ConstantInt(LLVM.Int1Type(), 0) : diff --git a/src/reflection.jl b/src/reflection.jl index 1d7db645..9fbc7a60 100644 --- a/src/reflection.jl +++ b/src/reflection.jl @@ -298,7 +298,7 @@ function code_native(io::IO, @nospecialize(job::CompilerJob); raw::Bool=false, dump_module::Bool=false) config = CompilerConfig(job.config; strip=!raw, only_entry=!dump_module, validate=false) asm = JuliaContext() do ctx - asm, meta = compile(:asm, CompilerJob(job; config)) + local asm, meta = compile(:asm, CompilerJob(job; config)) dispose(meta.ir) asm end @@ -506,11 +506,11 @@ Evaluates the expression `ex` and dumps all intermediate forms of code to the di `@device_code_*` macros for back-ends with other compilation stages. """ macro device_code(ex...) - localUnique = 1 + localUnique = Ref(1) function hook(@nospecialize(job); dir::AbstractString) job isa CompilerJob || throw(unsupported_reflection(:device_code, job)) name = job.source.def.name - fn = "$(name)_$(localUnique)" + fn = "$(name)_$(localUnique[])" mkpath(dir) open(joinpath(dir, "$fn.lowered.jl"), "w") do io @@ -535,7 +535,7 @@ macro device_code(ex...) code_native(io, job; dump_module=true, raw=true) end - localUnique += 1 + localUnique[] += 1 end emit_hooked_compilation(hook, ex...) end diff --git a/src/relocation.jl b/src/relocation.jl index 31bfab2a..23243660 100644 --- a/src/relocation.jl +++ b/src/relocation.jl @@ -594,22 +594,23 @@ function collect_cglobal_relocations!(@nospecialize(job::CompilerJob), mod::LLVM return changed end -function has_unresolved_cglobal_loads(mod::LLVM.Module, relocs::Relocations) - # also through merged addresses that `redirect_word_addresses!` had to leave alone - function has_load(value, seen=Set{LLVM.Value}()) - for use in value.uses - val = use.user - val isa LLVM.LoadInst && return true - if val isa LLVM.ConstantExpr || - (val isa LLVM.Instruction && forwarded_addresses(val) !== nothing) - val in seen && continue - push!(seen, val) - has_load(val, seen) && return true - end +# is `value` loaded from, also through merged addresses that `redirect_word_addresses!` had +# to leave alone? +function has_load(value, seen=Set{LLVM.Value}()) + for use in value.uses + val = use.user + val isa LLVM.LoadInst && return true + if val isa LLVM.ConstantExpr || + (val isa LLVM.Instruction && forwarded_addresses(val) !== nothing) + val in seen && continue + push!(seen, val) + has_load(val, seen) && return true end - return false end + return false +end +function has_unresolved_cglobal_loads(mod::LLVM.Module, relocs::Relocations) for value in [collect(mod.functions); collect(mod.globals)] is_cglobal_candidate(value, relocs) || continue has_load(value) && return true diff --git a/src/rtlib.jl b/src/rtlib.jl index e38bd00c..dd389b84 100644 --- a/src/rtlib.jl +++ b/src/rtlib.jl @@ -31,7 +31,7 @@ function LLVM.call!(builder, rt::Runtime.RuntimeMethodInstance, args=LLVM.Value[ # get or create a function prototype f = get!(mod.functions, rt.llvm_name) do - ft = if job === nothing + local ft = if job === nothing convert(LLVM.FunctionType, rt) else runtime_function_type(job, rt) diff --git a/src/spirv.jl b/src/spirv.jl index b71896c0..f23c1779 100644 --- a/src/spirv.jl +++ b/src/spirv.jl @@ -345,8 +345,8 @@ end error("Unsupported SPIR-V back-end $(repr(target.backend)); expected :llvm or :khronos.") end - dump_output() = let path = tempname(cleanup=false) * ".spv" - write(path, spirv) + dump_output(output) = let path = tempname(cleanup=false) * ".spv" + write(path, output) path end @@ -354,7 +354,7 @@ end message = spirv_validate(spirv) if message !== nothing error("""Failed to validate generated SPIR-V: $message - If you think this is a bug, please file an issue and attach $(dump_input()) and $(dump_output()).""") + If you think this is a bug, please file an issue and attach $(dump_input()) and $(dump_output(spirv)).""") end end @@ -362,7 +362,7 @@ end optimized = spirv_optimize(spirv) if optimized === nothing error("""Failed to optimize generated SPIR-V. - If you think this is a bug, please file an issue and attach $(dump_input()) and $(dump_output()).""") + If you think this is a bug, please file an issue and attach $(dump_input()) and $(dump_output(spirv)).""") end spirv = optimized end @@ -376,7 +376,7 @@ source_code(target::SPIRVCompilerTarget) = "spirv" function code_native(io::IO, job::CompilerJob{SPIRVCompilerTarget}; raw::Bool=false, dump_module::Bool=false) config = CompilerConfig(job.config; strip=!raw, only_entry=!dump_module, validate=false) obj = JuliaContext() do ctx - obj, meta = compile(:obj, CompilerJob(job; config)) + local obj, meta = compile(:obj, CompilerJob(job; config)) dispose(meta.ir) obj end @@ -766,7 +766,7 @@ function call_spirv_builtin!(builder::IRBuilder, mod::LLVM.Module, name::String, fn = "_Z$(length(name))$(name)$(mangled)" ft = LLVM.FunctionType(T_ret, LLVMType[arg.value_type for arg in args]) f = declare!(mod, fn, ft) do - f = LLVM.Function(mod, fn, ft) + local f = LLVM.Function(mod, fn, ft) # (not `argmemonly` or `readonly`: they order other memory) for attr in (:mustprogress, :nounwind, :willreturn) push!(f.function_attributes, EnumAttribute(attr)) diff --git a/src/validation.jl b/src/validation.jl index f446711e..fa44e630 100644 --- a/src/validation.jl +++ b/src/validation.jl @@ -270,12 +270,12 @@ function check_ir(job, mod::LLVM.Module, relocs::Relocations=Relocations(); comp explain = if compiled === nothing nothing else - emitted = nothing + emitted = Ref{Dict{String,Any}}() function (inst, error) error[1] == DYNAMIC_CALL || return explanation = try - emitted === nothing && (emitted = emitted_functions(mod, compiled)) - explain_error(inst, error, emitted, job.world) + isassigned(emitted) || (emitted[] = emitted_functions(mod, compiled)) + explain_error(inst, error, emitted[], job.world) catch e @safe_debug "Explaining an IR error failed" exception=(e, catch_backtrace()) nothing @@ -723,8 +723,8 @@ function explain_error(inst::LLVM.Instruction, (kind, bt, meta)::IRError, emitte entry === nothing && return nothing src = compiled_source(entry) src === nothing && return nothing - mi = entry.ci.def - mi isa MethodInstance || (mi = mi.def) # an `ABIOverride` + def = entry.ci.def + mi = def isa MethodInstance ? def : def.def # an `ABIOverride` if 0 < pc <= length(src.code) description = describe_call(src, mi, pc, meta, world) diff --git a/test/runtests.jl b/test/runtests.jl index 597e1279..44fd5629 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,5 @@ using ParallelTestRunner +using Test import GPUCompiler, LLVM using GPUCompiler, LLVM using SPIRV_LLVM_Backend_jll, SPIRV_LLVM_Translator_jll, SPIRV_Tools_jll @@ -26,6 +27,13 @@ end testsuite = find_tests(@__DIR__) args = parse_args(ARGS) +# captured variables that get boxed (`Core.Box`) lose their type +if isdefined(Test, :detect_closure_boxes) + testsuite["closure_boxes"] = quote + @test Test.detect_closure_boxes(GPUCompiler, GPUCompiler.Runtime) == [] + end +end + if filter_tests!(testsuite, args) helperkeys = String[] for key in collect(keys(testsuite))