-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
Eager finalizer insertion #45272
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Eager finalizer insertion #45272
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -308,21 +308,17 @@ function finish_cfg_inline!(state::CFGInliningState) | |
| end | ||
| end | ||
|
|
||
| function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector{Any}, | ||
| linetable::Vector{LineInfoNode}, item::InliningTodo, | ||
| boundscheck::Symbol, todo_bbs::Vector{Tuple{Int, Int}}) | ||
| # Ok, do the inlining here | ||
| spec = item.spec::ResolvedInliningSpec | ||
| sparam_vals = item.mi.sparam_vals | ||
| def = item.mi.def::Method | ||
| function ir_inline_linetable!(linetable::Vector{LineInfoNode}, inlinee_ir::IRCode, | ||
| inlinee::Method, | ||
| inlined_at::Int32) | ||
| coverage = coverage_enabled(inlinee.module) | ||
| linetable_offset::Int32 = length(linetable) | ||
| # Append the linetable of the inlined function to our line table | ||
| inlined_at = compact.result[idx][:line] | ||
| topline::Int32 = linetable_offset + Int32(1) | ||
| coverage = coverage_enabled(def.module) | ||
| coverage_by_path = JLOptions().code_coverage == 3 | ||
| push!(linetable, LineInfoNode(def.module, def.name, def.file, def.line, inlined_at)) | ||
| oldlinetable = spec.ir.linetable | ||
| push!(linetable, LineInfoNode(inlinee.module, inlinee.name, inlinee.file, inlinee.line, inlined_at)) | ||
| oldlinetable = inlinee_ir.linetable | ||
| extra_coverage_line = 0 | ||
| for oldline in 1:length(oldlinetable) | ||
| entry = oldlinetable[oldline] | ||
| if !coverage && coverage_by_path && is_file_tracked(entry.file) | ||
|
|
@@ -341,8 +337,25 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector | |
| end | ||
| push!(linetable, newentry) | ||
| end | ||
| if coverage && spec.ir.stmts[1][:line] + linetable_offset != topline | ||
| insert_node_here!(compact, NewInstruction(Expr(:code_coverage_effect), Nothing, topline)) | ||
| if coverage && inlinee_ir.stmts[1][:line] + linetable_offset != topline | ||
| extra_coverage_line = topline | ||
| end | ||
| return linetable_offset, extra_coverage_line | ||
| end | ||
|
|
||
| function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector{Any}, | ||
| linetable::Vector{LineInfoNode}, item::InliningTodo, | ||
| boundscheck::Symbol, todo_bbs::Vector{Tuple{Int, Int}}) | ||
| # Ok, do the inlining here | ||
| spec = item.spec::ResolvedInliningSpec | ||
| sparam_vals = item.mi.sparam_vals | ||
| def = item.mi.def::Method | ||
| inlined_at = compact.result[idx][:line] | ||
| linetable_offset::Int32 = length(linetable) | ||
| topline::Int32 = linetable_offset + Int32(1) | ||
| linetable_offset, extra_coverage_line = ir_inline_linetable!(linetable, item.spec.ir, def, inlined_at) | ||
| if extra_coverage_line != 0 | ||
| insert_node_here!(compact, NewInstruction(Expr(:code_coverage_effect), Nothing, extra_coverage_line)) | ||
| end | ||
| if def.isva | ||
| nargs_def = Int(def.nargs::Int32) | ||
|
|
@@ -839,7 +852,7 @@ function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8) | |
| src === nothing && return compileable_specialization(et, match, effects) | ||
|
|
||
| et !== nothing && push!(et, mi) | ||
| return InliningTodo(mi, src, effects) | ||
| return InliningTodo(mi, retrieve_ir_for_inlining(mi, src), effects) | ||
| end | ||
|
|
||
| function resolve_todo((; fully_covered, atype, cases, #=bbs=#)::UnionSplit, state::InliningState, flag::UInt8) | ||
|
|
@@ -861,7 +874,8 @@ function validate_sparams(sparams::SimpleVector) | |
| end | ||
|
|
||
| function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, | ||
| flag::UInt8, state::InliningState) | ||
| flag::UInt8, state::InliningState, | ||
| do_resolve::Bool = true) | ||
| method = match.method | ||
| spec_types = match.spec_types | ||
|
|
||
|
|
@@ -895,26 +909,20 @@ function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, | |
| todo = InliningTodo(mi, match, argtypes) | ||
| # If we don't have caches here, delay resolving this MethodInstance | ||
| # until the batch inlining step (or an external post-processing pass) | ||
| state.mi_cache === nothing && return todo | ||
| do_resolve && state.mi_cache === nothing && return todo | ||
| return resolve_todo(todo, state, flag) | ||
| end | ||
|
|
||
| function InliningTodo(mi::MethodInstance, ir::IRCode, effects::Effects) | ||
| ir = copy(ir) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this have been a separate PR? Seems like an odd thing to have in the commit without any description except "redundant"
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This copy was moved somewhere else in a conflicting PR while this PR was pending, so after a rebase there ended up being a duplicated copy call that wasn't in the original PR
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is it in the commit message?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oversight during the squash merge.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, okay |
||
| return InliningTodo(mi, ResolvedInliningSpec(ir, effects)) | ||
| end | ||
|
|
||
| function InliningTodo(mi::MethodInstance, src::Union{CodeInfo, Vector{UInt8}}, effects::Effects) | ||
| if !isa(src, CodeInfo) | ||
| src = ccall(:jl_uncompress_ir, Any, (Any, Ptr{Cvoid}, Any), mi.def, C_NULL, src::Vector{UInt8})::CodeInfo | ||
| else | ||
| src = copy(src) | ||
| end | ||
| @timeit "inline IR inflation" begin | ||
| ir = inflate_ir!(src, mi)::IRCode | ||
| return InliningTodo(mi, ResolvedInliningSpec(ir, effects)) | ||
| end | ||
| function retrieve_ir_for_inlining(mi::MethodInstance, src::Array{UInt8, 1}) | ||
| src = ccall(:jl_uncompress_ir, Any, (Any, Ptr{Cvoid}, Any), mi.def, C_NULL, src::Vector{UInt8})::CodeInfo | ||
| return inflate_ir!(src, mi) | ||
| end | ||
| retrieve_ir_for_inlining(mi::MethodInstance, src::CodeInfo) = inflate_ir(src, mi)::IRCode | ||
| retrieve_ir_for_inlining(mi::MethodInstance, ir::IRCode) = copy(ir) | ||
|
|
||
| function handle_single_case!( | ||
| ir::IRCode, idx::Int, stmt::Expr, | ||
|
|
@@ -1196,7 +1204,7 @@ function process_simple!(ir::IRCode, idx::Int, state::InliningState, todo::Vecto | |
| end | ||
| end | ||
|
|
||
| if sig.f !== Core.invoke && is_builtin(sig) | ||
| if sig.f !== Core.invoke && sig.f !== Core.finalizer && is_builtin(sig) | ||
| # No inlining for builtins (other invoke/apply/typeassert) | ||
| return nothing | ||
| end | ||
|
|
@@ -1213,9 +1221,10 @@ function process_simple!(ir::IRCode, idx::Int, state::InliningState, todo::Vecto | |
| end | ||
|
|
||
| # TODO inline non-`isdispatchtuple`, union-split callsites? | ||
| function analyze_single_call!( | ||
| ir::IRCode, idx::Int, stmt::Expr, infos::Vector{MethodMatchInfo}, flag::UInt8, | ||
| sig::Signature, state::InliningState, todo::Vector{Pair{Int, Any}}) | ||
| function compute_inlining_cases( | ||
| infos::Vector{MethodMatchInfo}, flag::UInt8, | ||
| sig::Signature, state::InliningState, | ||
| do_resolve::Bool = true) | ||
| argtypes = sig.argtypes | ||
| cases = InliningCase[] | ||
| local any_fully_covered = false | ||
|
|
@@ -1232,7 +1241,7 @@ function analyze_single_call!( | |
| continue | ||
| end | ||
| for match in meth | ||
| handled_all_cases &= handle_match!(match, argtypes, flag, state, cases, true) | ||
| handled_all_cases &= handle_match!(match, argtypes, flag, state, cases, true, do_resolve) | ||
| any_fully_covered |= match.fully_covers | ||
| end | ||
| end | ||
|
|
@@ -1242,8 +1251,18 @@ function analyze_single_call!( | |
| filter!(case::InliningCase->isdispatchtuple(case.sig), cases) | ||
| end | ||
|
|
||
| handle_cases!(ir, idx, stmt, argtypes_to_type(argtypes), cases, | ||
| handled_all_cases & any_fully_covered, todo, state.params) | ||
| return cases, handled_all_cases & any_fully_covered | ||
| end | ||
|
|
||
| function analyze_single_call!( | ||
| ir::IRCode, idx::Int, stmt::Expr, infos::Vector{MethodMatchInfo}, flag::UInt8, | ||
| sig::Signature, state::InliningState, todo::Vector{Pair{Int, Any}}) | ||
|
|
||
| r = compute_inlining_cases(infos, flag, sig, state) | ||
| r === nothing && return nothing | ||
| cases, all_covered = r | ||
| handle_cases!(ir, idx, stmt, argtypes_to_type(sig.argtypes), cases, | ||
| all_covered, todo, state.params) | ||
| end | ||
|
|
||
| # similar to `analyze_single_call!`, but with constant results | ||
|
|
@@ -1295,14 +1314,15 @@ end | |
|
|
||
| function handle_match!( | ||
| match::MethodMatch, argtypes::Vector{Any}, flag::UInt8, state::InliningState, | ||
| cases::Vector{InliningCase}, allow_abstract::Bool = false) | ||
| cases::Vector{InliningCase}, allow_abstract::Bool = false, | ||
| do_resolve::Bool = true) | ||
| spec_types = match.spec_types | ||
| allow_abstract || isdispatchtuple(spec_types) || return false | ||
| # we may see duplicated dispatch signatures here when a signature gets widened | ||
| # during abstract interpretation: for the purpose of inlining, we can just skip | ||
| # processing this dispatch candidate | ||
| _any(case->case.sig === spec_types, cases) && return true | ||
| item = analyze_method!(match, argtypes, flag, state) | ||
| item = analyze_method!(match, argtypes, flag, state, do_resolve) | ||
| item === nothing && return false | ||
| push!(cases, InliningCase(spec_types, item)) | ||
| return true | ||
|
|
@@ -1417,6 +1437,48 @@ function assemble_inline_todo!(ir::IRCode, state::InliningState) | |
| continue | ||
| end | ||
|
|
||
| # Handle finalizer | ||
| if sig.f === Core.finalizer | ||
| if isa(info, FinalizerInfo) | ||
| # Only inline finalizers that are known nothrow and notls. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we have this restriction? Would it be invalid to inline finalizers and have them run in the original task environment? Obligatory GPU context: our finalizers access the TLS to inspect the current stream. On finalizer tasks, there's no such stream, so we perform a blocking free, whereas on 'regular' tasks the memory operation can be ordered against other operations on that stream. I had assumed that inlining finalizers would additionally get them to use the local stream, promoting blocking frees to asynchronously-ordered ones. Maybe that's too magical though.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's to prevent things like #42752 where finalizers unexpectedly mess with task local state. I think we're now conceptually moving into the direction of finalizers having their own pseudo-task (maybe even a real task eventually), so inlining a finalizer that looks at task state would not be legal since it changes the task environment. That said, for your case, you want to allow that to happen, even though it's not technically semantically legal. I think for that case, we could just put a notls effect override on the finalizer. |
||
| # This avoids having to set up state for finalizer isolation | ||
| (is_nothrow(info.effects) && is_notaskstate(info.effects)) || continue | ||
|
|
||
| info = info.info | ||
| if isa(info, MethodMatchInfo) | ||
| infos = MethodMatchInfo[info] | ||
| elseif isa(info, UnionSplitInfo) | ||
| infos = info.matches | ||
| else | ||
| continue | ||
| end | ||
|
|
||
| ft = argextype(stmt.args[2], ir) | ||
| has_free_typevars(ft) && return nothing | ||
| f = singleton_type(ft) | ||
| argtypes = Vector{Any}(undef, 2) | ||
| argtypes[1] = ft | ||
| argtypes[2] = argextype(stmt.args[3], ir) | ||
| sig = Signature(f, ft, argtypes) | ||
|
|
||
| cases, all_covered = compute_inlining_cases(infos, UInt8(0), sig, state, false) | ||
| length(cases) == 0 && continue | ||
| if all_covered && length(cases) == 1 | ||
| if isa(cases[1], InliningCase) | ||
| case1 = cases[1].item | ||
| if isa(case1, InliningTodo) | ||
| push!(stmt.args, true) | ||
| push!(stmt.args, case1.mi) | ||
| elseif isa(case1, InvokeCase) | ||
| push!(stmt.args, false) | ||
| push!(stmt.args, case1.invoke) | ||
| end | ||
| end | ||
| end | ||
| continue | ||
| end | ||
| end | ||
|
|
||
| # if inference arrived here with constant-prop'ed result(s), | ||
| # we can perform a specialized analysis for just this case | ||
| if isa(info, ConstCallInfo) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.