Skip to content

Commit

Permalink
subtyping: apply performance optimization in more places
Browse files Browse the repository at this point in the history
also need to address some bugs in the optimizer since  supertype(T=typeof(Union{})) is awkwardly equal to T,
even though they are also expected to be distinguishable,
and similarly, we might get tripped up to discover that `Tuple{T}` where `isconcretetype(T)` has does subtypes
and so these optimizations were being too aggressive

also consider the whole Vararg tail, looking for obvious conflicts

this skips validation for malformed types with free type vars
  • Loading branch information
vtjnash committed May 28, 2019
1 parent abda71e commit a485f15
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 36 deletions.
4 changes: 2 additions & 2 deletions base/operators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,11 @@ isless(x::AbstractFloat, y::Real ) = (!isnan(x) & (isnan(y) | signless(x

function ==(T::Type, S::Type)
@_pure_meta
T<:S && S<:T
return ccall(:jl_types_equal, Cint, (Any, Any), T, S) != 0
end
function !=(T::Type, S::Type)
@_pure_meta
!(T == S)
return !(T == S)
end
==(T::TypeVar, S::Type) = false
==(T::Type, S::TypeVar) = false
Expand Down
1 change: 0 additions & 1 deletion src/common_symbols1.inc
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,3 @@ jl_symbol("UInt"),
jl_symbol("haskey"),
jl_symbol("setproperty!"),
jl_symbol("promote"),
jl_symbol("undef"),
2 changes: 1 addition & 1 deletion src/common_symbols2.inc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
jl_symbol("undef"),
jl_symbol("Vector"),
jl_symbol("parent"),
jl_symbol("_promote"),
Expand Down Expand Up @@ -251,4 +252,3 @@ jl_symbol("Complex"),
jl_symbol("checked_add"),
jl_symbol("mod"),
jl_symbol("unsafe_write"),
jl_symbol("libuv.jl"),
3 changes: 2 additions & 1 deletion src/dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -3305,7 +3305,8 @@ void jl_init_serializer(void)
jl_voidpointer_type, jl_newvarnode_type, jl_abstractstring_type,
jl_array_symbol_type, jl_anytuple_type, jl_tparam0(jl_anytuple_type),
jl_emptytuple_type, jl_array_uint8_type, jl_code_info_type,
jl_typeofbottom_type, jl_namedtuple_type, jl_array_int32_type,
jl_typeofbottom_type, jl_typeofbottom_type->super,
jl_namedtuple_type, jl_array_int32_type,
jl_typedslot_type, jl_uint32_type, jl_uint64_type,
jl_type_type_mt, jl_nonfunction_mt,

Expand Down
232 changes: 206 additions & 26 deletions src/subtype.c
Original file line number Diff line number Diff line change
Expand Up @@ -188,15 +188,20 @@ static void restore_env(jl_stenv_t *e, jl_value_t *root, jl_savedenv_t *se) JL_N
// quickly test that two types are identical
static int obviously_egal(jl_value_t *a, jl_value_t *b)
{
if (a == (jl_value_t*)jl_typeofbottom_type->super)
a = (jl_value_t*)jl_typeofbottom_type; // supertype(typeof(Union{})) is equal to, although distinct from, itself
if (b == (jl_value_t*)jl_typeofbottom_type->super)
b = (jl_value_t*)jl_typeofbottom_type; // supertype(typeof(Union{})) is equal to, although distinct from, itself
if (a == b) return 1;
if (jl_typeof(a) != jl_typeof(b)) return 0;
if (jl_is_datatype(a)) {
jl_datatype_t *ad = (jl_datatype_t*)a, *bd = (jl_datatype_t*)b;
jl_datatype_t *ad = (jl_datatype_t*)a;
jl_datatype_t *bd = (jl_datatype_t*)b;
if (ad->name != bd->name) return 0;
if (ad->isconcretetype || bd->isconcretetype) return 0;
size_t i, np = jl_nparams(ad);
if (np != jl_nparams(bd)) return 0;
for(i=0; i < np; i++) {
for (i = 0; i < np; i++) {
if (!obviously_egal(jl_tparam(ad,i), jl_tparam(bd,i)))
return 0;
}
Expand All @@ -216,6 +221,10 @@ static int obviously_egal(jl_value_t *a, jl_value_t *b)

static int obviously_unequal(jl_value_t *a, jl_value_t *b)
{
if (a == (jl_value_t*)jl_typeofbottom_type->super)
a = (jl_value_t*)jl_typeofbottom_type; // supertype(typeof(Union{})) is equal to, although distinct from, itself
if (b == (jl_value_t*)jl_typeofbottom_type->super)
b = (jl_value_t*)jl_typeofbottom_type; // supertype(typeof(Union{})) is equal to, although distinct from, itself
if (a == b)
return 0;
if (jl_is_unionall(a))
Expand Down Expand Up @@ -1415,6 +1424,28 @@ JL_DLLEXPORT int jl_subtype_env_size(jl_value_t *t)
return sz;
}

// compute the minimum bound on the number of concrete types that are subtypes of `t`
// returns 0, 1, or many (2+)
static int concrete_min(jl_value_t *t)
{
if (jl_is_unionall(t))
t = jl_unwrap_unionall(t);
if (jl_is_datatype(t)) {
if (jl_is_type_type(t))
return 0; // Type{T} may have the concrete supertype `typeof(T)`, so don't try to handle them here
return jl_is_concrete_type(t) ? 1 : 2;
}
if (jl_is_typevar(t))
return 0; // could be 0 or more, since we didn't track if it was unbound
if (jl_is_uniontype(t)) {
int count = concrete_min(((jl_uniontype_t*)t)->a);
if (count > 1)
return count;
return count + concrete_min(((jl_uniontype_t*)t)->b);
}
return 2; // up to infinite
}

// quickly compute if x seems like a possible subtype of y
// especially optimized for x isa concrete type
// returns true if it could be easily determined, with the result in subtype
Expand All @@ -1430,6 +1461,10 @@ JL_DLLEXPORT int jl_obvious_subtype(jl_value_t *x, jl_value_t *y, int *subtype)
x = jl_unwrap_unionall(x);
if (jl_is_unionall(y))
y = jl_unwrap_unionall(y);
if (x == (jl_value_t*)jl_typeofbottom_type->super)
x = (jl_value_t*)jl_typeofbottom_type; // supertype(typeof(Union{})) is equal to, although distinct from, itself
if (y == (jl_value_t*)jl_typeofbottom_type->super)
y = (jl_value_t*)jl_typeofbottom_type; // supertype(typeof(Union{})) is equal to, although distinct from, itself
if (x == y || y == (jl_value_t*)jl_any_type) {
*subtype = 1;
return 1;
Expand Down Expand Up @@ -1531,9 +1566,10 @@ JL_DLLEXPORT int jl_obvious_subtype(jl_value_t *x, jl_value_t *y, int *subtype)
}
int i, npx = jl_nparams(x), npy = jl_nparams(y);
jl_vararg_kind_t vx = JL_VARARG_NONE;
jl_vararg_kind_t vy = JL_VARARG_NONE;
jl_value_t *vxt = NULL;
int vy = 0;
int nparams_expanded_x = npx;
int nparams_expanded_y = npy;
if (istuple) {
if (npx > 0) {
jl_value_t *xva = jl_tparam(x, npx - 1);
Expand All @@ -1545,22 +1581,39 @@ JL_DLLEXPORT int jl_obvious_subtype(jl_value_t *x, jl_value_t *y, int *subtype)
nparams_expanded_x += jl_vararg_length(xva);
}
}
vy = npy > 0 && jl_is_vararg_type(jl_tparam(y, npy - 1));
}
if (npx != npy || vx != JL_VARARG_NONE || vy) {
if ((vx == JL_VARARG_NONE || vx == JL_VARARG_UNBOUND) && !vy) {
*subtype = 0;
return 1;
if (npy > 0) {
jl_value_t *yva = jl_tparam(y, npy - 1);
vy = jl_vararg_kind(yva);
if (vy != JL_VARARG_NONE) {
nparams_expanded_y -= 1;
if (vy == JL_VARARG_INT)
nparams_expanded_y += jl_vararg_length(yva);
}
}
if ((vx == JL_VARARG_NONE || vx == JL_VARARG_INT) &&
nparams_expanded_x < npy - vy) {
*subtype = 0;
return 1; // number of fixed parameters in x could be fewer than in y
// if the nparams aren't equal, or at least one of them is a typevar (uncertain), they may be obviously disjoint
if (nparams_expanded_x != nparams_expanded_y || (vx != JL_VARARG_NONE && vx != JL_VARARG_INT) || (vy != JL_VARARG_NONE && vy != JL_VARARG_INT)) {
// we have a stronger bound on x if:
if (vy == JL_VARARG_NONE || vy == JL_VARARG_INT) { // the bound on y is certain
if (vx == JL_VARARG_NONE || vx == JL_VARARG_INT || vx == JL_VARARG_UNBOUND || // and the bound on x is also certain
nparams_expanded_x > nparams_expanded_y || npx > nparams_expanded_y) { // or x is unknown, but definitely longer than y
*subtype = 0;
return 1; // number of fixed parameters in x are more than declared in y
}
}
if (nparams_expanded_x < nparams_expanded_y) {
*subtype = 0;
return 1; // number of fixed parameters in x could be fewer than in y
}
uncertain = 1;
}
// TODO: Can do better here for the JL_VARARG_INT case.
uncertain = 1;
}
for (i = 0; i < npy - vy; i++) {
else if (npx != npy) {
*subtype = 0;
return 1;
}

// inspect the fixed parameters in y against x
for (i = 0; i < npy - (vy == JL_VARARG_NONE ? 0 : 1); i++) {
jl_value_t *a = i >= (npx - (vx == JL_VARARG_NONE ? 0 : 1)) ? vxt : jl_tparam(x, i);
jl_value_t *b = jl_tparam(y, i);
if (iscov || jl_is_typevar(b)) {
Expand Down Expand Up @@ -1597,6 +1650,60 @@ JL_DLLEXPORT int jl_obvious_subtype(jl_value_t *x, jl_value_t *y, int *subtype)
}
}
}
if (i < npx) {
// there are elements left in x (possibly just a Vararg), check them against the Vararg tail of y too
assert(vy != JL_VARARG_NONE && istuple && iscov);
jl_value_t *a1 = (vx != JL_VARARG_NONE && i == npx - 1) ? vxt : jl_tparam(x, i);
jl_value_t *b = jl_unwrap_vararg(jl_tparam(y, i));
if (nparams_expanded_x > npy && jl_is_typevar(b) && concrete_min(a1) > 1) {
// diagonal rule for 2 or more elements: they must all be concrete on the LHS
*subtype = 0;
return 1;
}
if (jl_is_type_type(a1) && jl_is_type(jl_tparam0(a1))) {
a1 = jl_typeof(jl_tparam0(a1));
}
for (; i < npx; i++) {
jl_value_t *a = (vx != JL_VARARG_NONE && i == npx - 1) ? vxt : jl_tparam(x, i);
if (i > npy && jl_is_typevar(b)) { // i == npy implies a == a1
// diagonal rule: all the later parameters are also constrained to be type-equal to the first
jl_value_t *a2 = a;
if (jl_is_type_type(a) && jl_is_type(jl_tparam0(a))) {
// if a is exactly Type{T}, then use the concrete typeof(T) instead here
a2 = jl_typeof(jl_tparam0(a));
}
if (!obviously_egal(a1, a2)) {
if (jl_obvious_subtype(a2, a1, subtype)) {
if (!*subtype)
return 1;
if (jl_has_free_typevars(a1)) // a1 is actually more constrained that this
uncertain = 1;
}
else {
uncertain = 1;
}
if (jl_obvious_subtype(a1, a2, subtype)) {
if (!*subtype)
return 1;
if (jl_has_free_typevars(a2)) // a2 is actually more constrained that this
uncertain = 1;
}
else {
uncertain = 1;
}
}
}
if (jl_obvious_subtype(a, b, subtype)) {
if (!*subtype)
return 1;
if (jl_has_free_typevars(b)) // b is actually more constrained that this
uncertain = 1;
}
else {
uncertain = 1;
}
}
}
if (uncertain)
return 0;
*subtype = 1;
Expand Down Expand Up @@ -1626,21 +1733,20 @@ JL_DLLEXPORT int jl_subtype_env(jl_value_t *x, jl_value_t *y, jl_value_t **env,
#ifdef NDEBUG
if (obvious_subtype == 0)
return obvious_subtype;
else if (jl_has_free_typevars(y))
obvious_subtype = 3;
else if (envsz == 0)
return obvious_subtype;
#else
if (jl_has_free_typevars(y))
obvious_subtype = 3;
#endif
}
else {
obvious_subtype = 3;
}
init_stenv(&e, env, envsz);
int subtype = forall_exists_subtype(x, y, &e, 0);
assert(obvious_subtype == 3 || obvious_subtype == subtype);
assert(obvious_subtype == 3 || obvious_subtype == subtype || jl_has_free_typevars(x) || jl_has_free_typevars(y));
#ifndef NDEBUG
if (obvious_subtype == 0 || (obvious_subtype == 1 && envsz == 0))
subtype = obvious_subtype; // this ensures that running in a debugger doesn't change the result
#endif
return subtype;
}

Expand All @@ -1665,16 +1771,90 @@ JL_DLLEXPORT int jl_subtype(jl_value_t *x, jl_value_t *y)

JL_DLLEXPORT int jl_types_equal(jl_value_t *a, jl_value_t *b)
{
if (obviously_egal(a, b)) return 1;
if (obviously_unequal(a, b)) return 0;
if (obviously_egal(a, b))
return 1;
if (obviously_unequal(a, b))
return 0;
// the following is an interleaved version of:
// return jl_subtype(a, b) && jl_subtype(b, a)
// where we try to do the fast checks before the expensive ones
if (jl_is_datatype(a) && !jl_is_concrete_type(b)) {
// if one type looks more likely to be abstract, check it on the left
// if one type looks simpler, check it on the right
// first in order to reject more quickly.
jl_value_t *temp = a;
a = b;
b = temp;
}
return jl_subtype(a, b) && jl_subtype(b, a);
// first check if a <: b has an obvious answer
int subtype_ab = 2;
if (b == (jl_value_t*)jl_any_type || a == jl_bottom_type) {
subtype_ab = 1;
}
else if (jl_typeof(a) == jl_typeof(b) &&
(jl_is_unionall(b) || jl_is_uniontype(b)) &&
jl_egal(a, b)) {
subtype_ab = 1;
}
else if (jl_obvious_subtype(a, b, &subtype_ab)) {
#ifdef NDEBUG
if (subtype_ab == 0)
return 0;
#endif
}
else {
subtype_ab = 3;
}
// next check if b <: a has an obvious answer
int subtype_ba = 2;
if (a == (jl_value_t*)jl_any_type || b == jl_bottom_type) {
subtype_ba = 1;
}
else if (jl_typeof(b) == jl_typeof(a) &&
(jl_is_unionall(a) || jl_is_uniontype(a)) &&
jl_egal(b, a)) {
subtype_ba = 1;
}
else if (jl_obvious_subtype(b, a, &subtype_ba)) {
#ifdef NDEBUG
if (subtype_ba == 0)
return 0;
#endif
}
else {
subtype_ba = 3;
}
// finally, do full subtyping for any inconclusive test
jl_stenv_t e;
#ifdef NDEBUG
if (subtype_ab != 1)
#endif
{
init_stenv(&e, NULL, 0);
int subtype = forall_exists_subtype(a, b, &e, 0);
assert(subtype_ab == 3 || subtype_ab == subtype || jl_has_free_typevars(a) || jl_has_free_typevars(b));
#ifndef NDEBUG
if (subtype_ab != 0 && subtype_ab != 1) // ensures that running in a debugger doesn't change the result
#endif
subtype_ab = subtype;
#ifdef NDEBUG
if (subtype_ab == 0)
return 0;
#endif
}
#ifdef NDEBUG
if (subtype_ba != 1)
#endif
{
init_stenv(&e, NULL, 0);
int subtype = forall_exists_subtype(b, a, &e, 0);
assert(subtype_ba == 3 || subtype_ba == subtype || jl_has_free_typevars(a) || jl_has_free_typevars(b));
#ifndef NDEBUG
if (subtype_ba != 0 && subtype_ba != 1) // ensures that running in a debugger doesn't change the result
#endif
subtype_ba = subtype;
}
// all tests successful
return subtype_ab && subtype_ba;
}

JL_DLLEXPORT int jl_is_not_broken_subtype(jl_value_t *a, jl_value_t *b)
Expand Down
4 changes: 2 additions & 2 deletions test/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ let ft = Base.datatype_fieldtypes
@test ft(elT2.body)[1].parameters[1] === elT2
@test Base.isconcretetype(ft(elT2.body)[1])
end
struct S22624{A,B,C} <: Ref{S22624{Int64,A}}; end
@test @isdefined S22624
#struct S22624{A,B,C} <: Ref{S22624{Int64,A}}; end
@test_broken @isdefined S22624

# issue #3890
mutable struct A3890{T1}
Expand Down
Loading

0 comments on commit a485f15

Please sign in to comment.