Skip to content

Commit 1bbba21

Browse files
authored
Merge pull request JuliaLang#41795 from JuliaLang/jb/simpler_intersection
simplify and improve type intersection algorithm a bit
2 parents 690eae2 + 71757cd commit 1bbba21

File tree

2 files changed

+132
-120
lines changed

2 files changed

+132
-120
lines changed

src/subtype.c

+72-104
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,14 @@ typedef struct jl_varbinding_t {
6868
int8_t occurs_inv; // occurs in invariant position
6969
int8_t occurs_cov; // # of occurrences in covariant position
7070
int8_t concrete; // 1 if another variable has a constraint forcing this one to be concrete
71-
// in covariant position, we need to try constraining a variable in different ways:
72-
// 0 - unconstrained
73-
// 1 - less than
74-
// 2 - greater than
75-
// 3 - inexpressible - occurs when the var has non-trivial overlap with another type,
76-
// and we would need to return `intersect(var,other)`. in this case
77-
// we choose to over-estimate the intersection by returning the var.
71+
// constraintkind: in covariant position, we try three different ways to compute var ∩ type:
72+
// let ub = var.ub ∩ type
73+
// 0 - var.ub <: type ? var : ub
74+
// 1 - var.ub = ub; return var
75+
// 2 - either (var.ub = ub; return var), or return ub
7876
int8_t constraintkind;
7977
int8_t intvalued; // must be integer-valued; i.e. occurs as N in Vararg{_,N}
78+
int8_t limited;
8079
int16_t depth0; // # of invariant constructors nested around the UnionAll type for this var
8180
// when this variable's integer value is compared to that of another,
8281
// it equals `other + offset`. used by vararg length parameters.
@@ -102,6 +101,7 @@ typedef struct jl_stenv_t {
102101
int ignore_free; // treat free vars as black boxes; used during intersection
103102
int intersection; // true iff subtype is being called from intersection
104103
int emptiness_only; // true iff intersection only needs to test for emptiness
104+
int triangular; // when intersecting Ref{X} with Ref{<:Y}
105105
} jl_stenv_t;
106106

107107
// state manipulation utilities
@@ -759,7 +759,7 @@ static jl_unionall_t *unalias_unionall(jl_unionall_t *u, jl_stenv_t *e)
759759
static int subtype_unionall(jl_value_t *t, jl_unionall_t *u, jl_stenv_t *e, int8_t R, int param)
760760
{
761761
u = unalias_unionall(u, e);
762-
jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, 0, 0, 0, 0, 0,
762+
jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, 0, 0, 0, 0, 0, 0,
763763
R ? e->Rinvdepth : e->invdepth, 0, NULL, e->vars };
764764
JL_GC_PUSH4(&u, &vb.lb, &vb.ub, &vb.innervars);
765765
e->vars = &vb;
@@ -1445,6 +1445,7 @@ static void init_stenv(jl_stenv_t *e, jl_value_t **env, int envsz)
14451445
e->ignore_free = 0;
14461446
e->intersection = 0;
14471447
e->emptiness_only = 0;
1448+
e->triangular = 0;
14481449
e->Lunions.depth = 0; e->Runions.depth = 0;
14491450
e->Lunions.more = 0; e->Runions.more = 0;
14501451
e->Lunions.used = 0; e->Runions.used = 0;
@@ -2204,7 +2205,7 @@ static void set_bound(jl_value_t **bound, jl_value_t *val, jl_tvar_t *v, jl_sten
22042205
return;
22052206
jl_varbinding_t *btemp = e->vars;
22062207
while (btemp != NULL) {
2207-
if (btemp->lb == (jl_value_t*)v && btemp->ub == (jl_value_t*)v &&
2208+
if ((btemp->lb == (jl_value_t*)v || btemp->ub == (jl_value_t*)v) &&
22082209
in_union(val, (jl_value_t*)btemp->var))
22092210
return;
22102211
btemp = btemp->prev;
@@ -2256,6 +2257,21 @@ static int reachable_var(jl_value_t *x, jl_tvar_t *y, jl_stenv_t *e)
22562257
return reachable_var(xv->ub, y, e) || reachable_var(xv->lb, y, e);
22572258
}
22582259

2260+
// check whether setting v == t implies v == SomeType{v}, which is unsatisfiable.
2261+
static int check_unsat_bound(jl_value_t *t, jl_tvar_t *v, jl_stenv_t *e) JL_NOTSAFEPOINT
2262+
{
2263+
if (var_occurs_inside(t, v, 0, 0))
2264+
return 1;
2265+
jl_varbinding_t *btemp = e->vars;
2266+
while (btemp != NULL) {
2267+
if (btemp->lb == (jl_value_t*)v && btemp->ub == (jl_value_t*)v &&
2268+
var_occurs_inside(t, btemp->var, 0, 0))
2269+
return 1;
2270+
btemp = btemp->prev;
2271+
}
2272+
return 0;
2273+
}
2274+
22592275
static jl_value_t *intersect_var(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int8_t R, int param)
22602276
{
22612277
jl_varbinding_t *bb = lookup(e, b);
@@ -2285,7 +2301,9 @@ static jl_value_t *intersect_var(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int
22852301
ub = a;
22862302
}
22872303
else {
2304+
e->triangular++;
22882305
ub = R ? intersect_aside(a, bb->ub, e, 1, d) : intersect_aside(bb->ub, a, e, 0, d);
2306+
e->triangular--;
22892307
save_env(e, &root, &se);
22902308
int issub = subtype_in_env_existential(bb->lb, ub, e, 0, d);
22912309
restore_env(e, root, &se);
@@ -2297,88 +2315,44 @@ static jl_value_t *intersect_var(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int
22972315
}
22982316
if (ub != (jl_value_t*)b) {
22992317
if (jl_has_free_typevars(ub)) {
2300-
// constraint X == Ref{X} is unsatisfiable. also check variables set equal to X.
2301-
if (var_occurs_inside(ub, b, 0, 0)) {
2318+
if (check_unsat_bound(ub, b, e)) {
23022319
JL_GC_POP();
23032320
return jl_bottom_type;
23042321
}
2305-
jl_varbinding_t *btemp = e->vars;
2306-
while (btemp != NULL) {
2307-
if (btemp->lb == (jl_value_t*)b && btemp->ub == (jl_value_t*)b &&
2308-
var_occurs_inside(ub, btemp->var, 0, 0)) {
2309-
JL_GC_POP();
2310-
return jl_bottom_type;
2311-
}
2312-
btemp = btemp->prev;
2313-
}
23142322
}
23152323
bb->ub = ub;
23162324
bb->lb = ub;
23172325
}
23182326
JL_GC_POP();
23192327
return ub;
23202328
}
2321-
else if (bb->constraintkind == 0) {
2322-
if (!jl_is_typevar(bb->ub) && !jl_is_typevar(a)) {
2323-
if (try_subtype_in_env(bb->ub, a, e, 0, d))
2324-
return (jl_value_t*)b;
2325-
}
2326-
return R ? intersect_aside(a, bb->ub, e, 1, d) : intersect_aside(bb->ub, a, e, 0, d);
2327-
}
2328-
else if (bb->concrete || bb->constraintkind == 1) {
2329-
jl_value_t *ub = R ? intersect_aside(a, bb->ub, e, 1, d) : intersect_aside(bb->ub, a, e, 0, d);
2330-
if (ub == jl_bottom_type)
2331-
return jl_bottom_type;
2332-
JL_GC_PUSH1(&ub);
2333-
if (!R && !subtype_bounds_in_env(bb->lb, a, e, 0, d)) {
2334-
// this fixes issue #30122. TODO: better fix for R flag.
2335-
JL_GC_POP();
2336-
return jl_bottom_type;
2337-
}
2338-
JL_GC_POP();
2339-
set_bound(&bb->ub, ub, b, e);
2340-
return (jl_value_t*)b;
2341-
}
2342-
else if (bb->constraintkind == 2) {
2343-
// TODO: removing this case fixes many test_brokens in test/subtype.jl
2344-
// but breaks other tests.
2345-
if (!subtype_bounds_in_env(a, bb->ub, e, 1, d)) {
2346-
// mark var as unsatisfiable by making it circular
2347-
bb->lb = (jl_value_t*)b;
2348-
return jl_bottom_type;
2349-
}
2350-
jl_value_t *lb = simple_join(bb->lb, a);
2351-
set_bound(&bb->lb, lb, b, e);
2352-
return a;
2353-
}
2354-
assert(bb->constraintkind == 3);
23552329
jl_value_t *ub = R ? intersect_aside(a, bb->ub, e, 1, d) : intersect_aside(bb->ub, a, e, 0, d);
23562330
if (ub == jl_bottom_type)
23572331
return jl_bottom_type;
2358-
if (jl_is_typevar(a))
2332+
if (bb->constraintkind == 1 || e->triangular) {
2333+
if (e->triangular && check_unsat_bound(ub, b, e))
2334+
return jl_bottom_type;
2335+
set_bound(&bb->ub, ub, b, e);
23592336
return (jl_value_t*)b;
2360-
if (ub == a) {
2361-
if (bb->lb == jl_bottom_type) {
2362-
set_bound(&bb->ub, a, b, e);
2337+
}
2338+
else if (bb->constraintkind == 0) {
2339+
JL_GC_PUSH1(&ub);
2340+
if (!jl_is_typevar(a) && try_subtype_in_env(bb->ub, a, e, 0, d)) {
2341+
JL_GC_POP();
23632342
return (jl_value_t*)b;
23642343
}
2344+
JL_GC_POP();
23652345
return ub;
23662346
}
2367-
else if (bb->ub == bb->lb) {
2368-
return ub;
2369-
}
2370-
root = NULL;
2371-
JL_GC_PUSH2(&root, &ub);
2372-
save_env(e, &root, &se);
2373-
jl_value_t *ii = R ? intersect_aside(a, bb->lb, e, 1, d) : intersect_aside(bb->lb, a, e, 0, d);
2374-
if (ii == jl_bottom_type) {
2375-
restore_env(e, root, &se);
2376-
ii = (jl_value_t*)b;
2347+
assert(bb->constraintkind == 2);
2348+
if (!jl_is_typevar(a)) {
2349+
if (ub == a && bb->lb != jl_bottom_type)
2350+
return ub;
2351+
else if (jl_egal(bb->ub, bb->lb))
2352+
return ub;
23772353
set_bound(&bb->ub, ub, b, e);
23782354
}
2379-
free_env(&se);
2380-
JL_GC_POP();
2381-
return ii;
2355+
return (jl_value_t*)b;
23822356
}
23832357

23842358
// test whether `var` occurs inside constructors. `want_inv` tests only inside
@@ -2422,7 +2396,7 @@ static int var_occurs_inside(jl_value_t *v, jl_tvar_t *var, int inside, int want
24222396
}
24232397

24242398
// Caller might not have rooted `res`
2425-
static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbinding_t *vb, jl_stenv_t *e)
2399+
static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbinding_t *vb, jl_unionall_t *u, jl_stenv_t *e)
24262400
{
24272401
jl_value_t *varval = NULL;
24282402
jl_tvar_t *newvar = vb->var;
@@ -2435,7 +2409,10 @@ static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbind
24352409
// given x<:T<:x, substitute x for T
24362410
varval = vb->ub;
24372411
}
2438-
else if (!vb->occurs_inv && is_leaf_bound(vb->ub)) {
2412+
// TODO: `vb.occurs_cov == 1` here allows substituting Tuple{<:X} => Tuple{X},
2413+
// which is valid but changes some ambiguity errors so we don't need to do it yet.
2414+
else if ((/*vb->occurs_cov == 1 || */is_leaf_bound(vb->ub)) &&
2415+
!var_occurs_invariant(u->body, u->var, 0)) {
24392416
// replace T<:x with x in covariant position when possible
24402417
varval = vb->ub;
24412418
}
@@ -2453,9 +2430,8 @@ static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbind
24532430
}
24542431
}
24552432

2456-
// prefer generating a fresh typevar, to avoid repeated renaming if the result
2457-
// is compared to one of the intersected types later.
2458-
if (!varval)
2433+
// TODO: this can prevent us from matching typevar identities later
2434+
if (!varval && (vb->lb != vb->var->lb || vb->ub != vb->var->ub))
24592435
newvar = jl_new_typevar(vb->var->name, vb->lb, vb->ub);
24602436

24612437
// remove/replace/rewrap free occurrences of this var in the environment
@@ -2573,8 +2549,10 @@ static jl_value_t *intersect_unionall_(jl_value_t *t, jl_unionall_t *u, jl_stenv
25732549
int envsize = 0;
25742550
while (btemp != NULL) {
25752551
envsize++;
2576-
if (envsize > 150)
2552+
if (envsize > 120) {
2553+
vb->limited = 1;
25772554
return t;
2555+
}
25782556
if (btemp->var == u->var || btemp->lb == (jl_value_t*)u->var ||
25792557
btemp->ub == (jl_value_t*)u->var) {
25802558
u = rename_unionall(u);
@@ -2624,46 +2602,37 @@ static jl_value_t *intersect_unionall_(jl_value_t *t, jl_unionall_t *u, jl_stenv
26242602
}
26252603
if (res != jl_bottom_type)
26262604
// res is rooted by callee
2627-
res = finish_unionall(res, vb, e);
2605+
res = finish_unionall(res, vb, u, e);
26282606
JL_GC_POP();
26292607
return res;
26302608
}
26312609

26322610
static jl_value_t *intersect_unionall(jl_value_t *t, jl_unionall_t *u, jl_stenv_t *e, int8_t R, int param)
26332611
{
2634-
jl_value_t *res=NULL, *res2=NULL, *save=NULL, *save2=NULL;
2635-
jl_savedenv_t se, se2;
2636-
jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, 0, 0, 0, 0, 0,
2612+
jl_value_t *res=NULL, *save=NULL;
2613+
jl_savedenv_t se;
2614+
jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, 0, 0, 0, 0, 0, 0,
26372615
R ? e->Rinvdepth : e->invdepth, 0, NULL, e->vars };
2638-
JL_GC_PUSH6(&res, &save2, &vb.lb, &vb.ub, &save, &vb.innervars);
2616+
JL_GC_PUSH5(&res, &vb.lb, &vb.ub, &save, &vb.innervars);
26392617
save_env(e, &save, &se);
26402618
res = intersect_unionall_(t, u, e, R, param, &vb);
2641-
if (res != jl_bottom_type) {
2619+
if (vb.limited) {
2620+
// if the environment got too big, avoid tree recursion and propagate the flag
2621+
if (e->vars)
2622+
e->vars->limited = 1;
2623+
}
2624+
else if (res != jl_bottom_type) {
26422625
if (vb.concrete || vb.occurs_inv>1 || u->var->lb != jl_bottom_type || (vb.occurs_inv && vb.occurs_cov)) {
26432626
restore_env(e, NULL, &se);
26442627
vb.occurs_cov = vb.occurs_inv = 0;
2645-
vb.constraintkind = 3;
2628+
vb.constraintkind = vb.concrete ? 1 : 2;
26462629
res = intersect_unionall_(t, u, e, R, param, &vb);
26472630
}
2648-
else if (vb.occurs_cov) {
2649-
save_env(e, &save2, &se2);
2631+
else if (vb.occurs_cov && !var_occurs_invariant(u->body, u->var, 0)) {
26502632
restore_env(e, save, &se);
26512633
vb.occurs_cov = vb.occurs_inv = 0;
2652-
vb.lb = u->var->lb; vb.ub = u->var->ub;
26532634
vb.constraintkind = 1;
2654-
res2 = intersect_unionall_(t, u, e, R, param, &vb);
2655-
if (res2 == jl_bottom_type) {
2656-
restore_env(e, save, &se);
2657-
vb.occurs_cov = vb.occurs_inv = 0;
2658-
vb.lb = u->var->lb; vb.ub = u->var->ub;
2659-
vb.constraintkind = 2;
2660-
res2 = intersect_unionall_(t, u, e, R, param, &vb);
2661-
if (res2 == jl_bottom_type)
2662-
restore_env(e, save2, &se2);
2663-
}
2664-
if (res2 != jl_bottom_type)
2665-
res = res2;
2666-
free_env(&se2);
2635+
res = intersect_unionall_(t, u, e, R, param, &vb);
26672636
}
26682637
}
26692638
free_env(&se);
@@ -3049,14 +3018,13 @@ static jl_value_t *intersect(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int pa
30493018
jl_value_t *ub=NULL, *lb=NULL;
30503019
JL_GC_PUSH2(&lb, &ub);
30513020
ub = intersect_aside(xub, yub, e, 0, xx ? xx->depth0 : 0);
3052-
if (xlb == y)
3021+
if (reachable_var(xlb, (jl_tvar_t*)y, e))
30533022
lb = ylb;
30543023
else
30553024
lb = simple_join(xlb, ylb);
30563025
if (yy) {
3057-
if (!subtype_by_bounds(lb, y, e))
3058-
yy->lb = lb;
3059-
if (!subtype_by_bounds(y, ub, e))
3026+
yy->lb = lb;
3027+
if (!reachable_var(ub, (jl_tvar_t*)y, e))
30603028
yy->ub = ub;
30613029
assert(yy->ub != y);
30623030
assert(yy->lb != y);

0 commit comments

Comments
 (0)