Skip to content

Commit 11fd0d8

Browse files
vtjnashKristofferC
authored andcommitted
recursive types: fix implementation of references_name (#50963)
To do the old version correctly, we would need to carefully track whether we end up using that parameter as a parameter of any apply_type call in the fieldtype computation of any of the fields, recursively. Since any of those might cause recursion in the constructor graph, resulting in trying to layout some concrete type before we have finished computing the rest of the fieldtypes. That seems unnecessarily difficult to do well, so instead we go back to just doing the trivial cases. (cherry picked from commit 1182003)
1 parent e3cfb88 commit 11fd0d8

File tree

2 files changed

+71
-31
lines changed

2 files changed

+71
-31
lines changed

src/builtins.c

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1696,42 +1696,48 @@ static int equiv_field_types(jl_value_t *old, jl_value_t *ft)
16961696
// inline it. The only way fields can reference this type (due to
16971697
// syntax-enforced restrictions) is via being passed as a type parameter. Thus
16981698
// we can conservatively check this by examining only the parameters of the
1699-
// dependent types.
1700-
// affects_layout is a hack introduced by #35275 to workaround a problem
1701-
// introduced by #34223: it checks whether we will potentially need to
1702-
// compute the layout of the object before we have fully computed the types of
1703-
// the fields during recursion over the allocation of the parameters for the
1704-
// field types (of the concrete subtypes)
1705-
static int references_name(jl_value_t *p, jl_typename_t *name, int affects_layout) JL_NOTSAFEPOINT
1706-
{
1707-
if (jl_is_uniontype(p))
1708-
return references_name(((jl_uniontype_t*)p)->a, name, affects_layout) ||
1709-
references_name(((jl_uniontype_t*)p)->b, name, affects_layout);
1710-
if (jl_is_vararg(p)) {
1711-
jl_value_t *T = ((jl_vararg_t*)p)->T;
1712-
jl_value_t *N = ((jl_vararg_t*)p)->N;
1713-
return (T && references_name(T, name, affects_layout)) ||
1714-
(N && references_name(N, name, affects_layout));
1715-
}
1716-
if (jl_is_unionall(p))
1717-
return references_name((jl_value_t*)((jl_unionall_t*)p)->var->lb, name, 0) ||
1718-
references_name((jl_value_t*)((jl_unionall_t*)p)->var->ub, name, 0) ||
1719-
references_name(((jl_unionall_t*)p)->body, name, affects_layout);
1699+
// dependent types. Additionally, a field might have already observed this
1700+
// object for layout purposes before we got around to deciding if inlining
1701+
// would be possible, so we cannot change the layout now if so.
1702+
// affects_layout is a (conservative) analysis of layout_uses_free_typevars
1703+
// freevars is a (conservative) analysis of what calling jl_has_bound_typevars from name->wrapper gives (TODO: just call this instead?)
1704+
static int references_name(jl_value_t *p, jl_typename_t *name, int affects_layout, int freevars) JL_NOTSAFEPOINT
1705+
{
1706+
if (freevars && !jl_has_free_typevars(p))
1707+
freevars = 0;
1708+
while (jl_is_unionall(p)) {
1709+
if (references_name((jl_value_t*)((jl_unionall_t*)p)->var->lb, name, 0, freevars) ||
1710+
references_name((jl_value_t*)((jl_unionall_t*)p)->var->ub, name, 0, freevars))
1711+
return 1;
1712+
p = ((jl_unionall_t*)p)->body;
1713+
}
1714+
if (jl_is_uniontype(p)) {
1715+
return references_name(((jl_uniontype_t*)p)->a, name, affects_layout, freevars) ||
1716+
references_name(((jl_uniontype_t*)p)->b, name, affects_layout, freevars);
1717+
}
17201718
if (jl_is_typevar(p))
17211719
return 0; // already checked by unionall, if applicable
17221720
if (jl_is_datatype(p)) {
17231721
jl_datatype_t *dp = (jl_datatype_t*)p;
17241722
if (affects_layout && dp->name == name)
17251723
return 1;
1726-
// affects_layout checks whether we will need to attempt to layout this
1727-
// type (based on whether all copies of it have the same layout) in
1728-
// that case, we still need to check the recursive parameters for
1729-
// layout recursion happening also, but we know it won't itself cause
1730-
// problems for the layout computation
17311724
affects_layout = ((jl_datatype_t*)jl_unwrap_unionall(dp->name->wrapper))->layout == NULL;
1725+
// and even if it has a layout, the fields themselves might trigger layouts if they use tparam i
1726+
// rather than checking this for each field, we just assume it applies
1727+
if (!affects_layout && freevars && jl_field_names(dp) != jl_emptysvec) {
1728+
jl_svec_t *types = ((jl_datatype_t*)jl_unwrap_unionall(dp->name->wrapper))->types;
1729+
size_t i, l = jl_svec_len(types);
1730+
for (i = 0; i < l; i++) {
1731+
jl_value_t *ft = jl_svecref(types, i);
1732+
if (!jl_is_typevar(ft) && jl_has_free_typevars(ft)) {
1733+
affects_layout = 1;
1734+
break;
1735+
}
1736+
}
1737+
}
17321738
size_t i, l = jl_nparams(p);
17331739
for (i = 0; i < l; i++) {
1734-
if (references_name(jl_tparam(p, i), name, affects_layout))
1740+
if (references_name(jl_tparam(p, i), name, affects_layout, freevars))
17351741
return 1;
17361742
}
17371743
}
@@ -1767,12 +1773,12 @@ JL_CALLABLE(jl_f__typebody)
17671773
// able to compute the layout of the object before needing to
17681774
// publish it, so we must assume it cannot be inlined, if that
17691775
// check passes, then we also still need to check the fields too.
1770-
if (!dt->name->mutabl && (nf == 0 || !references_name((jl_value_t*)dt->super, dt->name, 1))) {
1776+
if (!dt->name->mutabl && (nf == 0 || !references_name((jl_value_t*)dt->super, dt->name, 0, 1))) {
17711777
int mayinlinealloc = 1;
17721778
size_t i;
17731779
for (i = 0; i < nf; i++) {
17741780
jl_value_t *fld = jl_svecref(ft, i);
1775-
if (references_name(fld, dt->name, 1)) {
1781+
if (references_name(fld, dt->name, 1, 1)) {
17761782
mayinlinealloc = 0;
17771783
break;
17781784
}

test/core.jl

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,8 @@ let ft = Base.datatype_fieldtypes
389389
@test ft(elT2.body)[1].parameters[1] === elT2
390390
@test Base.isconcretetype(ft(elT2.body)[1])
391391
end
392-
#struct S22624{A,B,C} <: Ref{S22624{Int64,A}}; end
393-
@test_broken @isdefined S22624
392+
struct S22624{A,B,C} <: Ref{S22624{Int,A}}; end
393+
@test sizeof(S22624) == sizeof(S22624{Int,Int,Int}) == 0
394394

395395
# issue #42297
396396
mutable struct Node42297{T, V}
@@ -429,6 +429,18 @@ mutable struct FooFoo{A,B} y::FooFoo{A} end
429429

430430
@test FooFoo{Int} <: FooFoo{Int,AbstractString}.types[1]
431431

432+
# make sure this self-referential struct doesn't crash type layout
433+
struct SelfTyA{V}
434+
a::Base.RefValue{V}
435+
end
436+
struct SelfTyB{T}
437+
a::T
438+
b::SelfTyA{SelfTyB{T}}
439+
end
440+
let T = Base.RefValue{SelfTyB{Int}}
441+
@test sizeof(T) === sizeof(Int)
442+
@test sizeof(T.types[1]) === 2 * sizeof(Int)
443+
end
432444

433445
let x = (2,3)
434446
@test +(x...) == 5
@@ -4125,7 +4137,29 @@ end
41254137
let z1 = Z14477()
41264138
@test isa(z1, Z14477)
41274139
@test isa(z1.fld, Z14477)
4140+
@test isdefined(z1, :fld)
4141+
@test !isdefined(z1.fld, :fld)
4142+
end
4143+
struct Z14477B
4144+
fld::Union{Nothing,Z14477B}
4145+
Z14477B() = new(new(nothing))
41284146
end
4147+
let z1 = Z14477B()
4148+
@test isa(z1, Z14477B)
4149+
@test isa(z1.fld, Z14477B)
4150+
@test isa(z1.fld.fld, Nothing)
4151+
end
4152+
struct Z14477C{T}
4153+
fld::Z14477C{Int8}
4154+
Z14477C() = new{Int16}(new{Int8}())
4155+
end
4156+
let z1 = Z14477C()
4157+
@test isa(z1, Z14477C)
4158+
@test isa(z1.fld, Z14477C)
4159+
@test isdefined(z1, :fld)
4160+
@test !isdefined(z1.fld, :fld)
4161+
end
4162+
41294163

41304164
# issue #8846, generic macros
41314165
macro m8846(a, b=0)

0 commit comments

Comments
 (0)