Skip to content
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

Merged
merged 5 commits into from
Feb 15, 2021
Merged

Conversation

Keno
Copy link
Member

@Keno Keno commented Feb 3, 2021

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).

@Keno Keno requested review from vtjnash and JeffBezanson February 3, 2021 23:45
@Keno Keno force-pushed the kf/partialopaque branch from 7ddb2be to d2ef8d0 Compare February 4, 2021 00:07
base/compiler/abstractinterpretation.jl Outdated Show resolved Hide resolved
base/compiler/typelattice.jl Show resolved Hide resolved
@Keno Keno force-pushed the kf/partialopaque branch from d2ef8d0 to 33929ec Compare February 4, 2021 19:13
return (widenconst(a) <: widenconst(b)) &&
⊑(a.env, b.env)
end
return widenconst(a) <: widenconst(b)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return widenconst(a) <: widenconst(b)
return widenconst(a) b

Comment on lines 129 to 134
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
Copy link
Member

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

Copy link
Member Author

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.

Copy link
Member Author

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.

Comment on lines +496 to +502
jl_serialize_value(s, NULL);
jl_serialize_value(s, jl_any_type);
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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);
Copy link
Member

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)

Copy link
Member Author

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{}
Copy link
Member

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?

Copy link
Member Author

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?

Copy link
Member

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.

Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for j = (i):length(params)
for j = (i):length(params)-1

Copy link
Member Author

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
Copy link
Member

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?

Copy link
Member

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.

Copy link
Member Author

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

@Keno Keno force-pushed the kf/partialopaque branch 2 times, most recently from cc4bb90 to 310ac38 Compare February 10, 2021 03:52
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)
Copy link
Member

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))])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

min?

Copy link
Member Author

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)
Copy link
Member

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?

Copy link
Member Author

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)
Copy link
Member

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)?

@@ -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)
Copy link
Member

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?

Copy link
Member Author

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

Comment on lines +1365 to +1367
function _opaque_closure_tfunc(@nospecialize(arg), @nospecialize(isva),
@nospecialize(lb), @nospecialize(ub), @nospecialize(source), env::Vector{Any},
linfo::MethodInstance)
Copy link
Member

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?

Suggested change
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

Copy link
Member

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 ?

Copy link
Member Author

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

Comment on lines 237 to 238
a.source == b.source || return false
a.parent == b.parent || return false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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
Copy link
Member

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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style nit:

Suggested change
} 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)) {
Copy link
Member

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?

Copy link
Member Author

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
Comment on lines 661 to 668
if (jl_is_method(mi->def.value) && mi->def.method->is_for_opaque_closure) {
jl_error("Cannot serialize MethodInstances for OpaqueClosure");
}
Copy link
Member

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?

Suggested change
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
Comment on lines 2399 to 2401
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);
Copy link
Member

@vtjnash vtjnash Feb 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style nit:

Suggested change
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);

Copy link
Member

@vtjnash vtjnash left a 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?

Keno added 5 commits February 12, 2021 20:27
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).
@Keno
Copy link
Member Author

Keno commented Feb 15, 2021

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.

@Keno Keno merged commit 88c0e25 into master Feb 15, 2021
@Keno Keno deleted the kf/partialopaque branch February 15, 2021 17:03
Comment on lines +107 to +113
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dead code

Suggested change
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

@vtjnash
Copy link
Member

vtjnash commented Feb 15, 2021

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 widenreturn, is in that PR).

@Keno
Copy link
Member Author

Keno commented Feb 15, 2021

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).

ElOceanografo pushed a commit to ElOceanografo/julia that referenced this pull request May 4, 2021
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).
antoine-levitt pushed a commit to antoine-levitt/julia that referenced this pull request May 9, 2021
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants