-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
trimming: Support finalizers #58014
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
trimming: Support finalizers #58014
Conversation
I was thinking perhaps |
01c2a07 to
fc4b732
Compare
Compiler/src/typeinfer.jl
Outdated
| if ftyp === typeof(Core.finalizer) && length(stmt.args) == 3 | ||
| finalizer_fn = argextype(stmt.args[2], ci, sptypes) | ||
| object = argextype(stmt.args[3], ci, sptypes) | ||
| atype = argtypes_to_type(Any[finalizer_fn, object]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we instead use the existing CallInfo pieces? E.g. We already have a FinalizerInfo
julia/Compiler/src/stmtinfo.jl
Line 455 in b265dba
| struct FinalizerInfo <: CallInfo |
I am thinking something like:
indirect(::CallInfo) = false
indirect(::FinalizerInfo) = true
if indirect(callinfo)
enqueue(workqueue, callinfo.call)
end
and then we could add TaskInfo and so forth.
One note is
julia/Compiler/src/stmtinfo.jl
Line 460 in b265dba
| add_edges_impl(::Vector{Any}, ::FinalizerInfo) = nothing |
Maybe we "just" need to change that to actually add the edge?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC the optimizer destroys the CallInfo so we unfortunately can't forward this edge from inference in the usual way
|
I am still not convinced of the utility of a new builtin like If we truly need it, could we merge it into |
I'm not super pleased about it either - but I also don't know of an alternative for const atexit_handlers = Callable[]
push!(atexit_handlers, f) # we know the identity of `f` on the `push!`
run_atexit() = for handler in atexit_handlers
handler() # we know longer know the identity of the handlers here
endThis is pretty challenging, and also very common in practice. Even if we add this "virtual edge" to the runtime (via (that happens to not be the case for
Perhaps. That's what I called it in the first prototype, but @JeffBezanson has been keeping the |
7ecc4f6 to
0e29daa
Compare
|
So the problem is two-fold?
My expectations was that we could just turn these missing edges in inference into real edges there, e.g. teach it to look at the Task constructor and treat it as if it is a call, and that ought to add it to its real edge list? And that one could use the precompile built-in to express such a virtual edge. For cases like atexit, the problem is more that we have two "separate" inference entries, and their is indeed no information flow possible. I agree that in that case we need to mark these functions as entrypoints, but that seems independent for me from how we handle these calls when they are visible in inference? |
We could do something more like you describe, but it seemed likely to end up being more complicated than is useful. We don't actually want to compile most "real edges" here, we only want to compile "invoke edges" which are often derived from inlined or de-virtualized copies instead. Those edges also run under a different interpreter than the parent (NativeInterp @ current-world, never the inherited one), so it is inconvenient to have to pass around multiple interpreters inside of each NativeInterp in the off-chance that someone is running a trim build and will want to recover that information later. Since this code is trim-relevant, we already know the code to precisely compute the call signature is already required (we need to check that every dispatch is covered or is a builtin function not on the disallow-list), so checking that this is one of a set of specific functions is already visible to the code here. The tricky case is ones like |
|
@JeffBezanson this should be ready to go from my end I'm happy to remove the AFAIU, that's only immediately useful for |
7ba5c33 to
d6c982d
Compare
| t = @_gc_preserve_begin a | ||
| p = unsafe_convert(Ptr{Cvoid}, a) | ||
| T = eltype(a) | ||
| memset(p, 0x0, (sizeof(T) * length(a)) % UInt) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like an odd place to put this optimization. Is it really that helpful? If so, should it be added to fill!? Or we could add a proper zero! function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see now this was for bootstrapping. Why isn't fill! available here though; isn't array.jl loaded first?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fill! implementation we need here is from multidimensional.jl since it's for a Memory:
julia> @which fill!(m, 0)
fill!(A::AbstractArray{T}, x) where T
@ Base multidimensional.jl:1141There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah. Seems to me that should be in abstractarray.jl.
|
If we're going to have predeclare_call (and I think we should) then we should use it to handle finalizer, Task, and atexit. Is there a compelling reason to make finalizers special but not other cases? |
That's only true for fianlizer/atexit that will run with invoke latest, but not Task which inherits the world?
The reason I am pushing back is that I don't really like "poluting" the IR for trim, especially when the information is right next to it. The only case where that isn't the case is |
|
OK I'm kind of sympathetic to the "polluted IR" argument. Having to manually add each case to the compiler when it could just be one line of code as needed seems so unfortunate though. Are we sure there won't be more cases? I guess with |
d6c982d to
8bf1bca
Compare
This adds the changes required to guess the MethodInstance that the runtime will eventually invoke for this call, enqueue it for codegen, and then verify its presence in the verifier.
Otherwise this will match `ftyp === Builtin`, which is pretty silly since that could just as well be any other built-in.
8bf1bca to
ce56baa
Compare
|
I've decided to split off the This should be ready to merge once CI passes. |
51dfef3 to
0578395
Compare
|
Can you add a mention of this to NEWS (or rather, expand upon the trim entry in HISTORY) when you make the other followups also? |
Should cover all the post-review comments from #58014 ~~Also disables the eager compilation of finalizers unless `--trim` is enabled, but I'm happy to re-enable that.~~ edit: re-enabled
This is sufficient to get finalizers working. Before merging this though I propose generalizing it by adding a new builtin
calledge(or something) that we'd use like so:which says that if a call to
finalizeris reachable, then so isf(o)etc. The implementation would be basically identical to what's here currently.