From 0755577ede7eabeb2b7edbd9b0f456d3f830846a Mon Sep 17 00:00:00 2001 From: Markus Hauru Date: Wed, 30 Jul 2025 13:42:08 +0100 Subject: [PATCH 01/12] Various fixes for Julia v1.12. Work in progress. --- src/bbcode.jl | 102 +++++++++++++++++++++++++++++++------------ src/copyable_task.jl | 28 ++++++++++-- src/test_utils.jl | 6 ++- src/utils.jl | 49 +++++++++++++++++---- 4 files changed, 143 insertions(+), 42 deletions(-) diff --git a/src/bbcode.jl b/src/bbcode.jl index 843877a8..79f834ca 100644 --- a/src/bbcode.jl +++ b/src/bbcode.jl @@ -140,22 +140,44 @@ end collect_stmts(bb::BBlock)::Vector{IDInstPair} = collect(zip(bb.inst_ids, bb.insts)) -struct BBCode - blocks::Vector{BBlock} - argtypes::Vector{Any} - sptypes::Vector{CC.VarState} - linetable::Vector{Core.LineInfoNode} - meta::Vector{Expr} -end +@static if VERSION >= v"1.12-" + struct BBCode + blocks::Vector{BBlock} + argtypes::Vector{Any} + sptypes::Vector{CC.VarState} + debuginfo::CC.DebugInfoStream + meta::Vector{Expr} + valid_worlds::CC.WorldRange + end -function BBCode(ir::Union{IRCode,BBCode}, new_blocks::Vector{BBlock}) - return BBCode( - new_blocks, - CC.copy(ir.argtypes), - CC.copy(ir.sptypes), - CC.copy(ir.linetable), - CC.copy(ir.meta), - ) + function BBCode(ir::Union{IRCode,BBCode}, new_blocks::Vector{BBlock}) + return BBCode( + new_blocks, + CC.copy(ir.argtypes), + CC.copy(ir.sptypes), + CC.copy(ir.debuginfo), + CC.copy(ir.meta), + ir.valid_worlds, + ) + end +else + struct BBCode + blocks::Vector{BBlock} + argtypes::Vector{Any} + sptypes::Vector{CC.VarState} + linetable::Vector{Core.LineInfoNode} + meta::Vector{Expr} + end + + function BBCode(ir::Union{IRCode,BBCode}, new_blocks::Vector{BBlock}) + return BBCode( + new_blocks, + CC.copy(ir.argtypes), + CC.copy(ir.sptypes), + CC.copy(ir.linetable), + CC.copy(ir.meta), + ) + end end # Makes use of the above outer constructor for `BBCode`. @@ -346,20 +368,42 @@ function CC.IRCode(bb_code::BBCode) insts = _ids_to_line_numbers(bb_code) cfg = control_flow_graph(bb_code) insts = _lines_to_blocks(insts, cfg) - return IRCode( - CC.InstructionStream( - map(x -> x.stmt, insts), - map(x -> x.type, insts), - map(x -> x.info, insts), - map(x -> x.line, insts), - map(x -> x.flag, insts), - ), - cfg, - CC.copy(bb_code.linetable), - CC.copy(bb_code.argtypes), - CC.copy(bb_code.meta), - CC.copy(bb_code.sptypes), - ) + @static if VERSION >= v"1.12-" + # See e.g. here for how the NTuple{3,Int}s get flattened for InstructionStream: + # https://github.com/JuliaLang/julia/blob/16a2bf0a3b106b03dda23b8c9478aab90ffda5e1/Compiler/src/ssair/ir.jl#L299 + lines = map(x -> x.line, insts) + lines = collect(Iterators.flatten(lines)) + return IRCode( + CC.InstructionStream( + map(x -> x.stmt, insts), + collect(Any, map(x -> x.type, insts)), + collect(CC.CallInfo, map(x -> x.info, insts)), + lines, + map(x -> x.flag, insts), + ), + cfg, + CC.copy(bb_code.debuginfo), + CC.copy(bb_code.argtypes), + CC.copy(bb_code.meta), + CC.copy(bb_code.sptypes), + bb_code.valid_worlds, + ) + else + return IRCode( + CC.InstructionStream( + map(x -> x.stmt, insts), + map(x -> x.type, insts), + map(x -> x.info, insts), + map(x -> x.line, insts), + map(x -> x.flag, insts), + ), + cfg, + CC.copy(bb_code.linetable), + CC.copy(bb_code.argtypes), + CC.copy(bb_code.meta), + CC.copy(bb_code.sptypes), + ) + end end function _lower_switch_statements(bb_code::BBCode) diff --git a/src/copyable_task.jl b/src/copyable_task.jl index ac549015..9819e172 100644 --- a/src/copyable_task.jl +++ b/src/copyable_task.jl @@ -377,7 +377,11 @@ expression, otherwise `false`. """ function is_produce_stmt(x)::Bool if Meta.isexpr(x, :invoke) && length(x.args) == 3 && x.args[1] isa Core.MethodInstance + # This branch is hit on Julia 1.11 and earlier. return x.args[1].specTypes <: Tuple{typeof(produce),Any} + elseif Meta.isexpr(x, :invoke) && length(x.args) == 3 && x.args[1] isa Core.CodeInstance + # This branch is hit on Julia 1.12. + return x.args[1].def.specTypes <: Tuple{typeof(produce),Any} elseif Meta.isexpr(x, :call) && length(x.args) == 2 return get_value(x.args[1]) === produce else @@ -400,7 +404,13 @@ function stmt_might_produce(x, ret_type::Type)::Bool # Statement will terminate in the usual fashion, so _do_ bother recusing. is_produce_stmt(x) && return true - Meta.isexpr(x, :invoke) && return might_produce(x.args[1].specTypes) + @static if VERSION >= v"1.12-" + # On Julia 1.12 x.args has CodeInstances rather than MethodInstances. We use .def + # to get the MethodInstances. + Meta.isexpr(x, :invoke) && return might_produce(x.args[1].def.specTypes) + else + Meta.isexpr(x, :invoke) && return might_produce(x.args[1].specTypes) + end if Meta.isexpr(x, :call) # This is a hack -- it's perfectly possible for `DataType` calls to produce in general. f = get_function(x.args[1]) @@ -964,7 +974,13 @@ function derive_copyable_task_ir(ir::BBCode)::Tuple{BBCode,Tuple,Vector{Any}} # Derive TapedTask for this statement. (callable, callable_args) = if Meta.isexpr(stmt, :invoke) - sig = stmt.args[1].specTypes + @static if VERSION >= v"1.12-" + # On Julia 1.12 stmt.args has CodeInstances rather than + # MethodInstances. We use .def to get the MethodInstances. + sig = stmt.args[1].def.specTypes + else + sig = stmt.args[1].specTypes + end v = Any[Any] (LazyCallable{sig,callable_ret_type(sig, v)}(), stmt.args[2:end]) elseif Meta.isexpr(stmt, :call) @@ -1079,7 +1095,13 @@ function derive_copyable_task_ir(ir::BBCode)::Tuple{BBCode,Tuple,Vector{Any}} new_argtypes = vcat(typeof(refs), copy(ir.argtypes)) # Return BBCode and the `Ref`s. - new_ir = BBCode(new_bblocks, new_argtypes, ir.sptypes, ir.linetable, ir.meta) + @static if VERSION >= v"1.12-" + new_ir = BBCode( + new_bblocks, new_argtypes, ir.sptypes, ir.debuginfo, ir.meta, ir.valid_worlds + ) + else + new_ir = BBCode(new_bblocks, new_argtypes, ir.sptypes, ir.linetables, ir.meta) + end return new_ir, refs, possible_produce_types end diff --git a/src/test_utils.jl b/src/test_utils.jl index 98e3abc0..069f2f24 100644 --- a/src/test_utils.jl +++ b/src/test_utils.jl @@ -59,7 +59,11 @@ function (case::Testcase)() end for _ in iteration_results - @test count_allocs(consume, t) == 0 + # TODO(mhauru) We seem to be causing more allocations than expected on + # v1.12, needs investigating. + @static if VERSION < v"1.12-" + @test count_allocs(consume, t) == 0 + end end end end diff --git a/src/utils.jl b/src/utils.jl index e4449657..1be17fc1 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -68,7 +68,11 @@ function optimise_ir!(ir::IRCode; show_ir=false, do_inline=true) ir = CC.compact!(ir) # CC.verify_ir(ir, true, false, CC.optimizer_lattice(local_interp)) - CC.verify_linetable(ir.linetable, true) + @static if VERSION >= v"1.12-" + CC.verify_linetable(ir.debuginfo, div(length(ir.debuginfo.codelocs), 3), true) + else + CC.verify_linetable(ir.linetable, true) + end if show_ir println("Post-optimization") display(ir) @@ -96,13 +100,29 @@ end # Run type inference and constant propagation on the ir. Credit to @oxinabox: # https://gist.github.com/oxinabox/cdcffc1392f91a2f6d80b2524726d802#file-example-jl-L54 function __infer_ir!(ir, interp::CC.AbstractInterpreter, mi::CC.MethodInstance) - method_info = CC.MethodInfo(true, nothing) #=propagate_inbounds=# - min_world = world = get_inference_world(interp) - max_world = Base.get_world_counter() - irsv = CC.IRInterpretationState( - interp, method_info, ir, mi, ir.argtypes, world, min_world, max_world - ) - rt = CC._ir_abstract_constant_propagation(interp, irsv) + # TODO(mhauru) Why is this line here? This function is no longer defined in 1.12 + @static if VERSION >= v"1.12-" + nargs = length(ir.argtypes) - 1 + # TODO(mhauru) How do we figure out isva? I don't think it's in ir or mi, see above + # prints. + isva = false + propagate_inbounds = true + spec_info = CC.SpecInfo(nargs, isva, propagate_inbounds, nothing) + min_world = world = get_inference_world(interp) + max_world = Base.get_world_counter() + irsv = CC.IRInterpretationState( + interp, spec_info, ir, mi, ir.argtypes, world, min_world, max_world + ) + rt = CC.ir_abstract_constant_propagation(interp, irsv) + else + method_info = CC.MethodInfo(true, nothing) #=propagate_inbounds=# + min_world = world = get_inference_world(interp) + max_world = Base.get_world_counter() + irsv = CC.IRInterpretationState( + interp, method_info, ir, mi, ir.argtypes, world, min_world, max_world + ) + rt = CC._ir_abstract_constant_propagation(interp, irsv) + end return ir end @@ -168,13 +188,24 @@ function opaque_closure( ) # This implementation is copied over directly from `Core.OpaqueClosure`. ir = CC.copy(ir) + @static if VERSION >= v"1.12-" + # On v1.12 OpaqueClosure expects the first arg to be the environment. + ir.argtypes[1] = typeof(env) + end nargs = length(ir.argtypes) - 1 - sig = Base.Experimental.compute_oc_signature(ir, nargs, isva) + @static if VERSION >= v"1.12-" + sig = CC.compute_oc_signature(ir, nargs, isva) + else + sig = Base.Experimental.compute_oc_signature(ir, nargs, isva) + end src = ccall(:jl_new_code_info_uninit, Ref{CC.CodeInfo}, ()) src.slotnames = fill(:none, nargs + 1) src.slotflags = fill(zero(UInt8), length(ir.argtypes)) src.slottypes = copy(ir.argtypes) src.rettype = ret_type + @static if VERSION >= v"1.12-" + src.nargs = UInt(nargs + 1) + end src = CC.ir_to_codeinf!(src, ir) return Base.Experimental.generate_opaque_closure( sig, Union{}, ret_type, src, nargs, isva, env...; do_compile From bfd8b2f106b23f18422189636e8330e9e221a065 Mon Sep 17 00:00:00 2001 From: Markus Hauru Date: Tue, 5 Aug 2025 18:19:10 +0100 Subject: [PATCH 02/12] Fix a typo --- src/copyable_task.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/copyable_task.jl b/src/copyable_task.jl index 9819e172..973b6182 100644 --- a/src/copyable_task.jl +++ b/src/copyable_task.jl @@ -1100,7 +1100,7 @@ function derive_copyable_task_ir(ir::BBCode)::Tuple{BBCode,Tuple,Vector{Any}} new_bblocks, new_argtypes, ir.sptypes, ir.debuginfo, ir.meta, ir.valid_worlds ) else - new_ir = BBCode(new_bblocks, new_argtypes, ir.sptypes, ir.linetables, ir.meta) + new_ir = BBCode(new_bblocks, new_argtypes, ir.sptypes, ir.linetable, ir.meta) end return new_ir, refs, possible_produce_types end From ab95d634132d36ed5db03cf96459a3e101d360dd Mon Sep 17 00:00:00 2001 From: Markus Hauru Date: Tue, 5 Aug 2025 18:21:25 +0100 Subject: [PATCH 03/12] Fix opaque_closure for v1.12 --- src/utils.jl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 1be17fc1..16129955 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -189,21 +189,25 @@ function opaque_closure( # This implementation is copied over directly from `Core.OpaqueClosure`. ir = CC.copy(ir) @static if VERSION >= v"1.12-" + ir.debuginfo.def === nothing && + (ir.debuginfo.def = :var"generated IR for OpaqueClosure") # On v1.12 OpaqueClosure expects the first arg to be the environment. ir.argtypes[1] = typeof(env) end - nargs = length(ir.argtypes) - 1 + nargtypes = length(ir.argtypes) + nargs = nargtypes - 1 @static if VERSION >= v"1.12-" sig = CC.compute_oc_signature(ir, nargs, isva) else sig = Base.Experimental.compute_oc_signature(ir, nargs, isva) end src = ccall(:jl_new_code_info_uninit, Ref{CC.CodeInfo}, ()) - src.slotnames = fill(:none, nargs + 1) - src.slotflags = fill(zero(UInt8), length(ir.argtypes)) + src.slotnames = fill(:none, nargtypes) + src.slotflags = fill(zero(UInt8), nargtypes) src.slottypes = copy(ir.argtypes) src.rettype = ret_type @static if VERSION >= v"1.12-" + src.isva = isva src.nargs = UInt(nargs + 1) end src = CC.ir_to_codeinf!(src, ir) From b12229b767f04ccba50ee6923e08f882600cf64c Mon Sep 17 00:00:00 2001 From: Markus Hauru Date: Thu, 9 Oct 2025 12:57:22 +0100 Subject: [PATCH 04/12] Work around Julia issue 59222 --- src/utils.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/utils.jl b/src/utils.jl index 16129955..4a29c18e 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -192,7 +192,11 @@ function opaque_closure( ir.debuginfo.def === nothing && (ir.debuginfo.def = :var"generated IR for OpaqueClosure") # On v1.12 OpaqueClosure expects the first arg to be the environment. - ir.argtypes[1] = typeof(env) + # ir.argtypes[1] = typeof(env) + # TODO(mhauru) However, there was a bug in this new treatment of argtypes + # (https://github.com/JuliaLang/julia/issues/59222) the fix for which did not make + # it to v1.12.0, so for now we need to use the below workaround. + ir.argtypes[1] = Tuple end nargtypes = length(ir.argtypes) nargs = nargtypes - 1 From 8d6ed7ec2855d18ae789fb77ff2e28069143e496 Mon Sep 17 00:00:00 2001 From: Markus Hauru Date: Thu, 9 Oct 2025 13:00:04 +0100 Subject: [PATCH 05/12] Update Julia version in CI --- .github/workflows/Testing.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Testing.yaml b/.github/workflows/Testing.yaml index fc34a49d..36011f6a 100644 --- a/.github/workflows/Testing.yaml +++ b/.github/workflows/Testing.yaml @@ -11,9 +11,10 @@ jobs: strategy: matrix: version: - - '1.10' + - 'min' - '1' - - 'pre' + # TODO(mhauru) Reenable the below once there is a 'pre' version different from '1'. + # - 'pre' os: - ubuntu-latest - windows-latest From 310f6f50cbc36e37d718c176fabffdc55eeb1244 Mon Sep 17 00:00:00 2001 From: Markus Hauru Date: Thu, 23 Oct 2025 12:31:23 +0100 Subject: [PATCH 06/12] Fixes to opaque_closure Copied over from https://github.com/chalk-lab/Mooncake.jl/pull/714 --- src/utils.jl | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 4a29c18e..021b32ef 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -189,14 +189,8 @@ function opaque_closure( # This implementation is copied over directly from `Core.OpaqueClosure`. ir = CC.copy(ir) @static if VERSION >= v"1.12-" - ir.debuginfo.def === nothing && - (ir.debuginfo.def = :var"generated IR for OpaqueClosure") # On v1.12 OpaqueClosure expects the first arg to be the environment. - # ir.argtypes[1] = typeof(env) - # TODO(mhauru) However, there was a bug in this new treatment of argtypes - # (https://github.com/JuliaLang/julia/issues/59222) the fix for which did not make - # it to v1.12.0, so for now we need to use the below workaround. - ir.argtypes[1] = Tuple + ir.argtypes[1] = typeof(env) end nargtypes = length(ir.argtypes) nargs = nargtypes - 1 @@ -206,15 +200,19 @@ function opaque_closure( sig = Base.Experimental.compute_oc_signature(ir, nargs, isva) end src = ccall(:jl_new_code_info_uninit, Ref{CC.CodeInfo}, ()) - src.slotnames = fill(:none, nargtypes) + src.slotnames = [Symbol(:_, i) for i in 1:nargtypes] src.slotflags = fill(zero(UInt8), nargtypes) src.slottypes = copy(ir.argtypes) - src.rettype = ret_type - @static if VERSION >= v"1.12-" + @static if VERSION > v"1.12-" + ir.debuginfo.def === nothing && + (ir.debuginfo.def = :var"generated IR for OpaqueClosure") + src.min_world = ir.valid_worlds.min_world + src.max_world = ir.valid_worlds.max_world src.isva = isva - src.nargs = UInt(nargs + 1) + src.nargs = nargtypes end src = CC.ir_to_codeinf!(src, ir) + src.rettype = ret_type return Base.Experimental.generate_opaque_closure( sig, Union{}, ret_type, src, nargs, isva, env...; do_compile )::Core.OpaqueClosure{sig,ret_type} From 4cb49404d8902520be5737b03c06389dba52c0bb Mon Sep 17 00:00:00 2001 From: Markus Hauru Date: Thu, 23 Oct 2025 12:33:48 +0100 Subject: [PATCH 07/12] Optimise opaque closures on 1.12 Copied over from @serenity4's work in https://github.com/chalk-lab/Mooncake.jl/pull/714 --- src/copyable_task.jl | 4 ++- src/utils.jl | 65 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/copyable_task.jl b/src/copyable_task.jl index 7442c763..4471b0fb 100644 --- a/src/copyable_task.jl +++ b/src/copyable_task.jl @@ -90,7 +90,9 @@ function build_callable(sig::Type{<:Tuple}) unoptimised_ir = IRCode(bb) optimised_ir = optimise_ir!(unoptimised_ir) mc_ret_type = callable_ret_type(sig, types) - mc = misty_closure(mc_ret_type, optimised_ir, refs...; isva=isva, do_compile=true) + mc = optimized_misty_closure( + mc_ret_type, optimised_ir, refs...; isva=isva, do_compile=true + ) mc_cache[key] = mc return mc, refs[end] end diff --git a/src/utils.jl b/src/utils.jl index 021b32ef..37762b14 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1,3 +1,7 @@ +function get_mi(ci::Core.CodeInstance) + @static isdefined(CC, :get_ci_mi) ? CC.get_ci_mi(ci) : ci.def +end +get_mi(mi::Core.MethodInstance) = mi """ replace_captures(oc::Toc, new_captures) where {Toc<:OpaqueClosure} @@ -218,6 +222,55 @@ function opaque_closure( )::Core.OpaqueClosure{sig,ret_type} end +function optimized_opaque_closure(rtype, ir::IRCode, env...; kwargs...) + oc = opaque_closure(rtype, ir, env...; kwargs...) + world = UInt(oc.world) + set_world_bounds_for_optimization!(oc) + optimized_oc = optimize_opaque_closure(oc, rtype, env...; kwargs...) + return optimized_oc +end + +function optimize_opaque_closure(oc::Core.OpaqueClosure, rtype, env...; kwargs...) + method = oc.source + ci = method.specializations.cache + world = UInt(oc.world) + ir = reinfer_and_inline(ci, world) + ir === nothing && return oc # nothing to optimize + return opaque_closure(rtype, ir, env...; kwargs...) +end + +# Allows optimization to make assumptions about binding access, +# enabling inlining and other optimizations. +function set_world_bounds_for_optimization!(oc::Core.OpaqueClosure) + ci = oc.source.specializations.cache + ci.inferred === nothing && return nothing + ci.inferred.min_world = oc.world + return ci.inferred.max_world = oc.world +end + +function reinfer_and_inline(ci::Core.CodeInstance, world::UInt) + interp = CC.NativeInterpreter(world) + mi = get_mi(ci) + argtypes = collect(Any, mi.specTypes.parameters) + irsv = CC.IRInterpretationState(interp, ci, mi, argtypes, world) + irsv === nothing && return nothing + for stmt in irsv.ir.stmts + inst = stmt[:inst] + if Meta.isexpr(inst, :loopinfo) || + Meta.isexpr(inst, :pop_exception) || + isa(inst, CC.GotoIfNot) || + isa(inst, CC.GotoNode) || + Meta.isexpr(inst, :copyast) + continue + end + stmt[:flag] |= CC.IR_FLAG_REFINED + end + CC.ir_abstract_constant_propagation(interp, irsv) + state = CC.InliningState(interp) + ir = CC.ssa_inlining_pass!(irsv.ir, state, CC.propagate_inbounds(irsv)) + return ir +end + """ misty_closure( ret_type::Type, @@ -239,3 +292,15 @@ function misty_closure( ) return MistyClosure(opaque_closure(ret_type, ir, env...; isva, do_compile), Ref(ir)) end + +function optimized_misty_closure( + ret_type::Type, + ir::IRCode, + @nospecialize env...; + isva::Bool=false, + do_compile::Bool=true, +) + return MistyClosure( + optimized_opaque_closure(ret_type, ir, env...; isva, do_compile), Ref(ir) + ) +end From c35b7e4a0752bb4b4c05b2f6023f6588d3f9797f Mon Sep 17 00:00:00 2001 From: Markus Hauru Date: Thu, 23 Oct 2025 12:54:41 +0100 Subject: [PATCH 08/12] Bump patch version to 0.9.6, turn NEWS.md into a HISTORY.md --- HISTORY.md | 11 +++++++++++ NEWS.md | 8 -------- Project.toml | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 HISTORY.md delete mode 100644 NEWS.md diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 00000000..73aff547 --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,11 @@ +# 0.9.6 + +Add support for Julia v1.12. + +# 0.9.0 + +From version 0.9.0, the old `TArray` and `TRef` types are completely removed, where previously they were only deprecated. Additionally, the internals have been completely overhauled, and the public interface more precisely defined. See the docs for more info. + +# 0.6.0 + +From v0.6.0 Libtask is implemented by recording all the computing to a tape and copying that tape. Before that version, it is based on a tricky hack on the Julia internals. You can check the commit history of this repo to see the details. diff --git a/NEWS.md b/NEWS.md deleted file mode 100644 index db7b2b8b..00000000 --- a/NEWS.md +++ /dev/null @@ -1,8 +0,0 @@ -- From v0.6.0, Libtask is implemented by recording all the computing - to a tape and copying that tape. Before that version, it is based on - a tricky hack on the Julia internals. You can check the commit - history of this repo to see the details. - -- From version 0.9.0, the old `TArray` and `TRef` types are completely removed, where - previously they were only deprecated. Additionally, the internals have been completely - overhauled, and the public interface more precisely defined. See the docs for more info. diff --git a/Project.toml b/Project.toml index 744e97cd..4967752d 100644 --- a/Project.toml +++ b/Project.toml @@ -3,7 +3,7 @@ uuid = "6f1fad26-d15e-5dc8-ae53-837a1d7b8c9f" license = "MIT" desc = "Tape based task copying in Turing" repo = "https://github.com/TuringLang/Libtask.jl.git" -version = "0.9.5" +version = "0.9.6" [deps] MistyClosures = "dbe65cb8-6be2-42dd-bbc5-4196aaced4f4" From 62362ad0ca5cbb3a6668d29db2cc555cddaee96b Mon Sep 17 00:00:00 2001 From: Markus Hauru Date: Thu, 23 Oct 2025 13:03:46 +0100 Subject: [PATCH 09/12] Refactor to simplify --- src/copyable_task.jl | 18 +++++------------- src/test_utils.jl | 6 +----- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/copyable_task.jl b/src/copyable_task.jl index 4471b0fb..6112618d 100644 --- a/src/copyable_task.jl +++ b/src/copyable_task.jl @@ -443,12 +443,10 @@ get_value(x) = x expression, otherwise `false`. """ function is_produce_stmt(x)::Bool - if Meta.isexpr(x, :invoke) && length(x.args) == 3 && x.args[1] isa Core.MethodInstance - # This branch is hit on Julia 1.11 and earlier. - return x.args[1].specTypes <: Tuple{typeof(produce),Any} - elseif Meta.isexpr(x, :invoke) && length(x.args) == 3 && x.args[1] isa Core.CodeInstance - # This branch is hit on Julia 1.12. - return x.args[1].def.specTypes <: Tuple{typeof(produce),Any} + if Meta.isexpr(x, :invoke) && + length(x.args) == 3 && + x.args[1] isa Union{Core.MethodInstance,Core.CodeInstance} + return get_mi(x.args[1]).specTypes <: Tuple{typeof(produce),Any} elseif Meta.isexpr(x, :call) && length(x.args) == 2 return get_value(x.args[1]) === produce else @@ -471,13 +469,7 @@ function stmt_might_produce(x, ret_type::Type)::Bool # Statement will terminate in the usual fashion, so _do_ bother recusing. is_produce_stmt(x) && return true - @static if VERSION >= v"1.12-" - # On Julia 1.12 x.args has CodeInstances rather than MethodInstances. We use .def - # to get the MethodInstances. - Meta.isexpr(x, :invoke) && return might_produce(x.args[1].def.specTypes) - else - Meta.isexpr(x, :invoke) && return might_produce(x.args[1].specTypes) - end + Meta.isexpr(x, :invoke) && return might_produce(get_mi(x.args[1]).specTypes) if Meta.isexpr(x, :call) # This is a hack -- it's perfectly possible for `DataType` calls to produce in general. f = get_function(x.args[1]) diff --git a/src/test_utils.jl b/src/test_utils.jl index 069f2f24..98e3abc0 100644 --- a/src/test_utils.jl +++ b/src/test_utils.jl @@ -59,11 +59,7 @@ function (case::Testcase)() end for _ in iteration_results - # TODO(mhauru) We seem to be causing more allocations than expected on - # v1.12, needs investigating. - @static if VERSION < v"1.12-" - @test count_allocs(consume, t) == 0 - end + @test count_allocs(consume, t) == 0 end end end From c4bdb4b399739a7e3c77626160499811e5dd01d9 Mon Sep 17 00:00:00 2001 From: Markus Hauru Date: Thu, 23 Oct 2025 13:09:00 +0100 Subject: [PATCH 10/12] More refactoring --- src/copyable_task.jl | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/copyable_task.jl b/src/copyable_task.jl index 6112618d..a23ad92a 100644 --- a/src/copyable_task.jl +++ b/src/copyable_task.jl @@ -1033,13 +1033,7 @@ function derive_copyable_task_ir(ir::BBCode)::Tuple{BBCode,Tuple,Vector{Any}} # Derive TapedTask for this statement. (callable, callable_args) = if Meta.isexpr(stmt, :invoke) - @static if VERSION >= v"1.12-" - # On Julia 1.12 stmt.args has CodeInstances rather than - # MethodInstances. We use .def to get the MethodInstances. - sig = stmt.args[1].def.specTypes - else - sig = stmt.args[1].specTypes - end + sig = get_mi(stmt.args[1]).specTypes v = Any[Any] (LazyCallable{sig,callable_ret_type(sig, v)}(), stmt.args[2:end]) elseif Meta.isexpr(stmt, :call) From 7a5b74f456a74f66b7665c0e6d6f1a9c7550812f Mon Sep 17 00:00:00 2001 From: Markus Hauru Date: Thu, 23 Oct 2025 13:12:46 +0100 Subject: [PATCH 11/12] Remove/update out-of-date comments --- src/utils.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 37762b14..cd958ae1 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -104,11 +104,9 @@ end # Run type inference and constant propagation on the ir. Credit to @oxinabox: # https://gist.github.com/oxinabox/cdcffc1392f91a2f6d80b2524726d802#file-example-jl-L54 function __infer_ir!(ir, interp::CC.AbstractInterpreter, mi::CC.MethodInstance) - # TODO(mhauru) Why is this line here? This function is no longer defined in 1.12 @static if VERSION >= v"1.12-" nargs = length(ir.argtypes) - 1 - # TODO(mhauru) How do we figure out isva? I don't think it's in ir or mi, see above - # prints. + # TODO(mhauru) How should we figure out isva? I don't think it's in ir or mi. isva = false propagate_inbounds = true spec_info = CC.SpecInfo(nargs, isva, propagate_inbounds, nothing) From 6c336b03bcdeddab1cb995d92c9562cdecdd4fe0 Mon Sep 17 00:00:00 2001 From: Markus Hauru Date: Thu, 23 Oct 2025 16:10:42 +0100 Subject: [PATCH 12/12] Add warning on Julia v1.12.0 --- src/copyable_task.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/copyable_task.jl b/src/copyable_task.jl index a23ad92a..de14fd87 100644 --- a/src/copyable_task.jl +++ b/src/copyable_task.jl @@ -279,6 +279,13 @@ The above gives the broad outline of how `TapedTask`s are implemented. We refer readers to the code, which is extensively commented to explain implementation details. """ function TapedTask(taped_globals::Any, fargs...; kwargs...) + @static if v"1.12.1" > VERSION >= v"1.12.0-" + @warn """ + Libtask.jl does not work correctly on Julia v1.12.0 and may crash your Julia + session. Please upgrade to at least v1.12.1. See + https://github.com/JuliaLang/julia/issues/59222 for the bug in question. + """ + end all_args = isempty(kwargs) ? fargs : (Core.kwcall, getfield(kwargs, :data), fargs...) seed_id!() # a BBCode thing. mc, count_ref = build_callable(typeof(all_args))