Skip to content

Commit ec9edb7

Browse files
committed
Performance enhancements
1 parent 999f5bc commit ec9edb7

File tree

4 files changed

+162
-40
lines changed

4 files changed

+162
-40
lines changed

src/Libtask.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module Libtask
44
using Mooncake
55
using Mooncake: BBCode, BBlock, ID, new_inst, stmt, seed_id!, terminator
66
using Mooncake: IDGotoIfNot, IDGotoNode, IDPhiNode, Switch
7+
using Mooncake.BasicBlockCode: collect_stmts, characterise_used_ids
78

89
# We'll emit `MistyClosure`s rather than `OpaqueClosure`s.
910
using MistyClosures

src/copyable_task.jl

Lines changed: 81 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
const dynamic_scope = ScopedValue{Any}(0)
2-
31
"""
4-
get_dynamic_scope()
2+
get_dynamic_scope(T::Type)
53
64
Returns the dynamic scope associated to `Libtask`. If called from inside a `TapedTask`, this
75
will return whatever is contained in its `dynamic_scope` field.
86
7+
The type `T` is required for optimal performance. If you know that the result of this
8+
operation must return a specific type, specific `T`. If you do not know what type it will
9+
return, pass `Any` -- this will typically yield type instabilities, but will run correctly.
10+
911
See also [`set_dynamic_scope!`](@ref).
1012
"""
11-
get_dynamic_scope() = dynamic_scope[]
13+
get_dynamic_scope(::Type{T}) where {T} = typeassert(task_local_storage(:task_variable), T)
1214

1315
__v::Int = 5
1416

@@ -25,16 +27,21 @@ See also: [`Libtask.consume`](@ref)
2527
return x
2628
end
2729

28-
function callable_ret_type(sig)
29-
return Union{Base.code_ircode_by_type(sig)[1][2],ProducedValue}
30+
function callable_ret_type(sig, types)
31+
produce_type = Union{}
32+
for t in types
33+
p = isconcretetype(t) ? ProducedValue{t} : ProducedValue{T} where {T<:t}
34+
produce_type = CC.tmerge(p, produce_type)
35+
end
36+
return Union{Base.code_ircode_by_type(sig)[1][2],produce_type}
3037
end
3138

3239
function build_callable(sig::Type{<:Tuple})
3340
ir = Base.code_ircode_by_type(sig)[1][1]
34-
bb, refs = derive_copyable_task_ir(BBCode(ir))
41+
bb, refs, types = derive_copyable_task_ir(BBCode(ir))
3542
unoptimised_ir = IRCode(bb)
3643
optimised_ir = Mooncake.optimise_ir!(unoptimised_ir)
37-
mc_ret_type = callable_ret_type(sig)
44+
mc_ret_type = callable_ret_type(sig, types)
3845
mc = Mooncake.misty_closure(mc_ret_type, optimised_ir, refs...; do_compile=true)
3946
return mc, refs[end]
4047
end
@@ -200,7 +207,8 @@ called, it start execution from the entry point. If `consume` has previously bee
200207
`nothing` will be returned.
201208
"""
202209
@inline function consume(t::TapedTask)
203-
v = with(() -> t.mc(t.fargs...), dynamic_scope => t.dynamic_scope)
210+
task_local_storage(:task_variable, t.dynamic_scope)
211+
v = t.mc.oc(t.fargs...)
204212
return v isa ProducedValue ? v[] : nothing
205213
end
206214

@@ -285,6 +293,7 @@ end
285293
struct ProducedValue{T}
286294
x::T
287295
end
296+
ProducedValue(::Type{T}) where {T} = ProducedValue{Type{T}}(T)
288297

289298
@inline Base.getindex(x::ProducedValue) = x.x
290299

@@ -318,7 +327,35 @@ inc_args(x::Core.PiNode) = Core.PiNode(__inc(x.val), __inc(x.typ))
318327
__inc(x::Argument) = Argument(x.n + 1)
319328
__inc(x) = x
320329

321-
function derive_copyable_task_ir(ir::BBCode)::Tuple{BBCode,Tuple}
330+
const TypeInfo = Tuple{Vector{Any},Dict{ID,Type}}
331+
332+
"""
333+
_typeof(x)
334+
335+
Central definition of typeof, which is specific to the use-required in this package.
336+
"""
337+
_typeof(x) = Base._stable_typeof(x)
338+
_typeof(x::Tuple) = Tuple{tuple_map(_typeof, x)...}
339+
_typeof(x::NamedTuple{names}) where {names} = NamedTuple{names,_typeof(Tuple(x))}
340+
341+
"""
342+
get_type(info::ADInfo, x)
343+
344+
Returns the static / inferred type associated to `x`.
345+
"""
346+
get_type(info::TypeInfo, x::Argument) = info[1][x.n - 1]
347+
get_type(info::TypeInfo, x::ID) = CC.widenconst(info[2][x])
348+
get_type(::TypeInfo, x::QuoteNode) = _typeof(x.value)
349+
get_type(::TypeInfo, x) = _typeof(x)
350+
function get_type(::TypeInfo, x::GlobalRef)
351+
return isconst(x) ? _typeof(getglobal(x.mod, x.name)) : x.binding.ty
352+
end
353+
function get_type(::TypeInfo, x::Expr)
354+
x.head === :boundscheck && return Bool
355+
return error("Unrecognised expression $x found in argument slot.")
356+
end
357+
358+
function derive_copyable_task_ir(ir::BBCode)::Tuple{BBCode,Tuple,Vector{Any}}
322359

323360
# The location from which all state can be retrieved. Since we're using `OpaqueClosure`s
324361
# to implement `TapedTask`s, this appears via the first argument.
@@ -338,13 +375,17 @@ function derive_copyable_task_ir(ir::BBCode)::Tuple{BBCode,Tuple}
338375
ssa_id_to_ref_index_map = Dict{ID,Int}()
339376
ref_index_to_ssa_id_map = Dict{Int,ID}()
340377
ref_index_to_type_map = Dict{Int,Type}()
378+
id_to_type_map = Dict{ID,Type}()
379+
is_used_dict = characterise_used_ids(collect_stmts(ir))
341380
n = 0
342381
for bb in ir.blocks
343382
for (id, stmt) in zip(bb.inst_ids, bb.insts)
383+
id_to_type_map[id] = CC.widenconst(stmt.type)
344384
stmt.stmt isa IDGotoNode && continue
345385
stmt.stmt isa IDGotoIfNot && continue
346386
stmt.stmt === nothing && continue
347387
stmt.stmt isa ReturnNode && continue
388+
is_used_dict[id] || continue
348389
n += 1
349390
ssa_id_to_ref_index_map[id] = n
350391
ref_index_to_ssa_id_map[n] = id
@@ -447,6 +488,9 @@ function derive_copyable_task_ir(ir::BBCode)::Tuple{BBCode,Tuple}
447488
# A set of blocks from which we might wish to resume computation.
448489
resume_block_ids = Vector{ID}()
449490

491+
# A list onto which we'll push the type of any statement which might produce.
492+
possible_produce_types = Any[]
493+
450494
# This where most of the action happens.
451495
#
452496
# For each split of each block, we must
@@ -582,10 +626,13 @@ function derive_copyable_task_ir(ir::BBCode)::Tuple{BBCode,Tuple}
582626
push!(inst_pairs, (id, inst))
583627

584628
# If we know it is not possible for this statement to contain any calls
585-
# to produce, then simply write out the result to its `Ref`.
586-
out_ind = ssa_id_to_ref_index_map[id]
587-
set_ref = Expr(:call, set_ref_at!, refs_id, out_ind, id)
588-
push!(inst_pairs, (ID(), new_inst(set_ref)))
629+
# to produce, then simply write out the result to its `Ref`. If it is
630+
# never used, then there is no need to store it.
631+
if is_used_dict[id]
632+
out_ind = ssa_id_to_ref_index_map[id]
633+
set_ref = Expr(:call, set_ref_at!, refs_id, out_ind, id)
634+
push!(inst_pairs, (ID(), new_inst(set_ref)))
635+
end
589636
elseif Meta.isexpr(stmt, :boundscheck)
590637
push!(inst_pairs, (id, inst))
591638
elseif Meta.isexpr(stmt, :code_coverage_effect)
@@ -677,6 +724,10 @@ function derive_copyable_task_ir(ir::BBCode)::Tuple{BBCode,Tuple}
677724
# computation is resumed from the statement _after_ this produce statement,
678725
# and to return whatever this produce statement returns.
679726

727+
# Log the result type of this statement.
728+
arg = stmt.args[Meta.isexpr(stmt, :invoke) ? 3 : 2]
729+
push!(possible_produce_types, get_type((ir.argtypes, id_to_type_map), arg))
730+
680731
# When this TapedTask is next called, we should resume from the first
681732
# statement of the next split.
682733
resume_id = splits_ids[n + 1]
@@ -733,6 +784,11 @@ function derive_copyable_task_ir(ir::BBCode)::Tuple{BBCode,Tuple}
733784
# which involves calls that might produce, in order to get a sense of what
734785
# the resulting code looks like prior to digging into the code below.
735786

787+
# At present, we're not able to properly infer the values which might
788+
# potentially be produced by a call-which-might-produce. Consequently, we
789+
# have to assume they can produce anything.
790+
push!(possible_produce_types, Any)
791+
736792
# Create a new basic block from the existing statements, since all new
737793
# statement need to live in their own basic blocks.
738794
callable_block_id = ID()
@@ -742,7 +798,8 @@ function derive_copyable_task_ir(ir::BBCode)::Tuple{BBCode,Tuple}
742798
# Derive TapedTask for this statement.
743799
(callable, callable_args) = if Meta.isexpr(stmt, :invoke)
744800
sig = stmt.args[1].specTypes
745-
(LazyCallable{sig,callable_ret_type(sig)}(), stmt.args[2:end])
801+
v = Any[Any]
802+
(LazyCallable{sig,callable_ret_type(sig, v)}(), stmt.args[2:end])
746803
elseif Meta.isexpr(stmt, :call)
747804
(DynamicCallable(), stmt.args)
748805
else
@@ -815,8 +872,12 @@ function derive_copyable_task_ir(ir::BBCode)::Tuple{BBCode,Tuple}
815872
# `ProducedValue`. In this case, we must first push the result to the `Ref`
816873
# associated to the call, and goto the next split.
817874
next_block_id = splits_ids[n + 1] # safe since the last split ends with a terminator
818-
result_ref_ind = ssa_id_to_ref_index_map[id]
819-
set_ref = Expr(:call, set_ref_at!, refs_id, result_ref_ind, result_id)
875+
if is_used_dict[id]
876+
result_ref_ind = ssa_id_to_ref_index_map[id]
877+
set_ref = Expr(:call, set_ref_at!, refs_id, result_ref_ind, result_id)
878+
else
879+
set_ref = nothing
880+
end
820881
not_produced_block_inst_pairs = Mooncake.IDInstPair[
821882
(ID(), new_inst(set_ref))
822883
(ID(), new_inst(IDGotoNode(next_block_id)))
@@ -851,13 +912,12 @@ function derive_copyable_task_ir(ir::BBCode)::Tuple{BBCode,Tuple}
851912
new_argtypes = vcat(typeof(refs), copy(ir.argtypes))
852913

853914
# Return BBCode and the `Ref`s.
854-
return BBCode(new_bblocks, new_argtypes, ir.sptypes, ir.linetable, ir.meta), refs
915+
new_ir = BBCode(new_bblocks, new_argtypes, ir.sptypes, ir.linetable, ir.meta)
916+
return new_ir, refs, possible_produce_types
855917
end
856918

857919
# Helper used in `derive_copyable_task_ir`.
858-
@inline function get_ref_at(refs::R, n::Int) where {R<:Tuple}
859-
return refs[n][]
860-
end
920+
@inline get_ref_at(refs::R, n::Int) where {R<:Tuple} = refs[n][]
861921

862922
# Helper used in `derive_copyable_task_ir`.
863923
@inline function set_ref_at!(refs::R, n::Int, val) where {R<:Tuple}

0 commit comments

Comments
 (0)