-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Add PartialOpaque
lattice element for OpaqueClosure
#39512
Conversation
7ddb2be
to
d2ef8d0
Compare
d2ef8d0
to
33929ec
Compare
base/compiler/typelattice.jl
Outdated
return (widenconst(a) <: widenconst(b)) && | ||
⊑(a.env, b.env) | ||
end | ||
return widenconst(a) <: widenconst(b) |
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.
return widenconst(a) <: widenconst(b) | |
return widenconst(a) ⊑ b |
base/compiler/utilities.jl
Outdated
if isdefined(linfo, :uninferred) | ||
c = copy(linfo.uninferred::CodeInfo) | ||
else | ||
# user code might throw errors – ignore them | ||
c = get_staged(linfo) | ||
# For opaque closures, cache the generated code info to make sure |
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.
Since this is a correctness issue, and there's many places it gets called, get_staged
must be handling this itself
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.
I figured it was only a correctness issue if it escaped into inference, which should always happen here. Doing the copy in C
is kinda annoying, but I guess I'll just move it there.
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.
Actually, I guess I can just move this into the get_staged
julia-side function, which avoids duplicating the copy logic on the C side. The other callers from C just use it to generate code directly, which doesn't have the correctness issues.
jl_serialize_value(s, NULL); | ||
jl_serialize_value(s, jl_any_type); |
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.
These are part of the ABI of the code pointers also in this struct. Dropping this means we also need to clear the jl_fptr_const_return flag.
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 part isn't changed - it skips the entire codeinstance if the return type is PartialOpaque.
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.
Ah, that would make more sense. Looks like you're missing a return
above to actually implement that?
@@ -2422,6 +2451,7 @@ static jl_method_t *jl_lookup_method(jl_methtable_t *mt, jl_datatype_t *sig, siz | |||
|
|||
static jl_method_t *jl_recache_method(jl_method_t *m) | |||
{ | |||
assert(!m->is_for_opaque_closure); |
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.
Seems like this assert isn't handled on serialization? (there's nothing stopping the user from referencing this from other places via reflection or perhaps .roots
from generated code)
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.
I'll add an error case if you try to serialize it.
else | ||
# non-constant function, but the number of arguments is known | ||
# and the ft is not a Builtin or IntrinsicFunction | ||
if typeintersect(widenconst(ft), Builtin) != Union{} | ||
if typeintersect(widenconst(ft), Union{Builtin, Core.OpaqueClosure}) != Union{} |
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.
Seems like we should consider making it a Builtin?
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.
Potentially? It's kinda weird to call it a builtin though. Maybe add something above builtin?
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.
I think it is perhaps accurate enough calling it a Builtin—there is no source code since that is in C and uses a JL_CALLABLE handle for the initial invoke
, and there's otherwise about a dozen spots in compiler similar to this one that we need to audit. OTOH, in C we assume that Builtin must have exactly one entry in the method table cache (which will be false for the OpaqueClosure), so perhaps we need to audit each anyways.
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.
Do we still need to audit these, or did you already look through the other places where we treat it specially?
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.
I left this as is for the moment. It still seems wrong to be to call it a Builtin
. We could perhaps insert a new abstract type above Builtin
though.
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.
I don't think that is necessarily useful. We might want to make an alias name for this Union in Core.Compiler, but currently this is the only usage site. I was inquiring instead whether any of the other places that we refer to Builtin though need to use this Union instead.
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.
I looked through those and I didn't think any needed to be adjusted.
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.
Ah, good. Thanks for confirming
rt = unwrapva(params[max(i-1, length(params))]) | ||
if closure.isva | ||
if length(params) > i-1 | ||
for j = (i):length(params) |
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.
for j = (i):length(params) | |
for j = (i):length(params)-1 |
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.
Am I missing something? I thought length(params)
was correct.
src/dump.c
Outdated
@@ -380,11 +380,11 @@ static void jl_serialize_module(jl_serializer_state *s, jl_module_t *m) | |||
write_uint8(s->s, m->infer); | |||
} | |||
|
|||
static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int as_literal) JL_GC_DISABLED | |||
static inline int jl_serialize_generic(jl_serializer_state *s, jl_value_t *v) JL_GC_DISABLED |
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.
Why is this declared inline?
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.
Also could use a comment saying what the return value means.
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.
It grew after I added the inline annotation. It's probably too big now to be inline
cc4bb90
to
310ac38
Compare
base/compiler/utilities.jl
Outdated
if has_opaque_closure(mi) | ||
# For opaque closures, cache the generated code info to make sure | ||
# that OpaqueClosure method identity is stable | ||
mi.uninferred = copy(ci) |
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.
We already have code to do this in C (jl_copy_ast). Let's keep it there, since this would be unsound otherwise.
@assert isa(argt, DataType) && argt.name === typename(Tuple) | ||
params = argt.parameters | ||
for i = 2:closure.source.nargs | ||
rt = unwrapva(params[max(i-1, length(params))]) |
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.
min
?
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.
whoops, thanks.
ret = Any[] | ||
cc = widenconst(closure) | ||
argt = unwrap_unionall(cc).parameters[1] | ||
@assert isa(argt, DataType) && argt.name === typename(Tuple) |
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.
Is the idea we'll refuse to form the PartialOpaque
if this condition doesn't hold?
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.
I guess we could just do [Vararg{Any}]
otherwise. Let me do that.
return CallMeta(Any, nothing) | ||
end | ||
|
||
function most_general_argtypes(closure::PartialOpaque) |
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 incomplete copy of matching_cache_argtypes
. Can we use that instead (refactoring as needed)?
base/compiler/inferenceresult.jl
Outdated
@@ -3,7 +3,7 @@ | |||
function is_argtype_match(@nospecialize(given_argtype), | |||
@nospecialize(cache_argtype), | |||
overridden_by_const::Bool) | |||
if isa(given_argtype, Const) || isa(given_argtype, PartialStruct) | |||
if isa(given_argtype, Const) || isa(given_argtype, PartialStruct) || isa(given_argtype, PartialOpaque) |
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.
Why would a PartialOpaque ever be equal with OpaqueClosure
?
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.
You're right, it shouldn't be
function _opaque_closure_tfunc(@nospecialize(arg), @nospecialize(isva), | ||
@nospecialize(lb), @nospecialize(ub), @nospecialize(source), env::Vector{Any}, | ||
linfo::MethodInstance) |
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.
Alternatively, this could be written more compactly as. Thoughts?
function _opaque_closure_tfunc(@nospecialize(arg), @nospecialize(isva), | |
@nospecialize(lb), @nospecialize(ub), @nospecialize(source), env::Vector{Any}, | |
linfo::MethodInstance) | |
function _opaque_closure_tfunc(arg::Any, isva::Any, lb::Any, ub::Any, source::Any, | |
env::Vector{Any}, linfo::MethodInstance) | |
@nospecialize |
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.
@nospecialized
is declared at the top of this file, so I guess we even don't need these annotations in tfuncs.jl ?
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.
I like the explicit nospecialize annotations. Otherwise I always forget to put them back
base/compiler/typelattice.jl
Outdated
a.source == b.source || return false | ||
a.parent == b.parent || return false |
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.
a.source == b.source || return false | |
a.parent == b.parent || return false | |
a.source === b.source || return false | |
a.parent === b.parent || return false |
return 0; | ||
} | ||
|
||
static void jl_serialize_code_instance(jl_serializer_state *s, jl_code_instance_t *codeinst, int skip_partial_opaque) JL_GC_DISABLED |
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 you add some comments to this function? It is a bit more complex now.
src/dump.c
Outdated
jl_typeis(codeinst->rettype_const, jl_partial_opaque_type)) { | ||
if (skip_partial_opaque) { | ||
jl_serialize_code_instance(s, codeinst->next, skip_partial_opaque); | ||
} else { |
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.
style nit:
} else { | |
} | |
else { |
|
||
int write_ret_type = validate || codeinst->min_world == 0; | ||
if (write_ret_type && codeinst->rettype_const && | ||
jl_typeis(codeinst->rettype_const, jl_partial_opaque_type)) { |
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.
If this happens, I think we should also see this same rettype_const object in Method.roots
and possibly elsewhere, from when we compressed slottypes
? We might want to improve that eventually, but seems like we need to just make this valid to store? What's inhibiting that currently?
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 PartialOpaque will have a reference to the Method
object that we don't have a good identity for.
src/dump.c
Outdated
if (jl_is_method(mi->def.value) && mi->def.method->is_for_opaque_closure) { | ||
jl_error("Cannot serialize MethodInstances for OpaqueClosure"); | ||
} |
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.
Seems like this means we also would be prohibited from inferring with them (since that would add backedges to them). Can we fix this?
if (jl_is_method(mi->def.value) && mi->def.method->is_for_opaque_closure) { | |
jl_error("Cannot serialize MethodInstances for OpaqueClosure"); | |
} | |
if (jl_is_method(mi->def.value) && mi->def.method->is_for_opaque_closure) { | |
jl_error("unimplemented: serialization of MethodInstances for OpaqueClosure"); | |
} |
src/jltypes.c
Outdated
jl_partial_opaque_type = jl_new_datatype(jl_symbol("PartialOpaque"), core, jl_any_type, jl_emptysvec, | ||
jl_perm_symsvec(5, "typ", "env", "isva", "parent", "source"), | ||
jl_svec(5, jl_type_type, jl_any_type, jl_bool_type, jl_method_instance_type, jl_method_type), 0, 0, 5); |
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.
style nit:
jl_partial_opaque_type = jl_new_datatype(jl_symbol("PartialOpaque"), core, jl_any_type, jl_emptysvec, | |
jl_perm_symsvec(5, "typ", "env", "isva", "parent", "source"), | |
jl_svec(5, jl_type_type, jl_any_type, jl_bool_type, jl_method_instance_type, jl_method_type), 0, 0, 5); | |
jl_partial_opaque_type = jl_new_datatype(jl_symbol("PartialOpaque"), core, jl_any_type, jl_emptysvec, | |
jl_perm_symsvec(5, "typ", "env", "isva", "parent", "source"), | |
jl_svec(5, jl_type_type, jl_any_type, jl_bool_type, jl_method_instance_type, jl_method_type), | |
0, 0, 5); |
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.
A few comments inline. Do we want to also implement tmerge
now? The fallback will already be correct (it falls back to tmerge on widenconst), but seems suboptimal?
This adds a lattice element for tracking OpaqueClosures in inference, but does not yet do anything with it. The reason I'm separating this out is that just the introduction of the lattice element raises some tricky issues. In particular, the lattice element refers back to the OpaqueClosure method, which we currently don't support in the serializer. I played with several ways of adding support for that, but in the end it all ended up super complicated for questionable benefit, so in this PR, CodeInstances that get inferred to `PartialOpaque` get omitted during serialization (i.e. they will be reinfered upon loading the .ji).
310ac38
to
b4cdce7
Compare
Alright, I think this is in a decent state to merge. There's much still to be done for inference/optimizations, so we can include any further tweaks in upcoming rounds. |
function has_opaque_closure(c::CodeInfo) | ||
for i = 1:length(c.code) | ||
stmt = c.code[i] | ||
(isa(stmt, Expr) && stmt.head === :new_opaque_closure) && return true | ||
end | ||
return false | ||
end |
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.
dead code
function has_opaque_closure(c::CodeInfo) | |
for i = 1:length(c.code) | |
stmt = c.code[i] | |
(isa(stmt, Expr) && stmt.head === :new_opaque_closure) && return true | |
end | |
return false | |
end |
Glad to see this in! I wish you had merged #39606 first though, since that might need to be backported and has some minor conflicts with this (and one fix needed to the lattice here, for |
I don't think #39606 is backportable as a whole anyway, since it touches 1.7 only code. I didn't think we had fully discussed the parameterization issue there yet, or has that been worked out (let's talk about that over there). |
This adds a lattice element for tracking OpaqueClosures in inference, but does not yet do anything with it. The reason I'm separating this out is that just the introduction of the lattice element raises some tricky issues. In particular, the lattice element refers back to the OpaqueClosure method, which we currently don't support in the serializer. I played with several ways of adding support for that, but in the end it all ended up super complicated for questionable benefit, so in this PR, CodeInstances that get inferred to `PartialOpaque` get omitted during serialization (i.e. they will be reinfered upon loading the .ji).
This adds a lattice element for tracking OpaqueClosures in inference, but does not yet do anything with it. The reason I'm separating this out is that just the introduction of the lattice element raises some tricky issues. In particular, the lattice element refers back to the OpaqueClosure method, which we currently don't support in the serializer. I played with several ways of adding support for that, but in the end it all ended up super complicated for questionable benefit, so in this PR, CodeInstances that get inferred to `PartialOpaque` get omitted during serialization (i.e. they will be reinfered upon loading the .ji).
This adds a lattice element for tracking OpaqueClosures in inference,
but does not yet do anything with it. The reason I'm separating
this out is that just the introduction of the lattice element
raises some tricky issues. In particular, the lattice element
refers back to the OpaqueClosure method, which we currently
don't support in the serializer. I played with several ways
of adding support for that, but in the end it all ended up
super complicated for questionable benefit, so in this PR,
CodeInstances that get inferred to
PartialOpaque
getomitted during serialization (i.e. they will be reinfered
upon loading the .ji).