Skip to content

Commit 1182003

Browse files
authored
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.
1 parent 6fd82d7 commit 1182003

File tree

2 files changed

+71
-25
lines changed

2 files changed

+71
-25
lines changed

src/builtins.c

+35-23
Original file line numberDiff line numberDiff line change
@@ -1694,36 +1694,48 @@ static int equiv_field_types(jl_value_t *old, jl_value_t *ft)
16941694
// inline it. The only way fields can reference this type (due to
16951695
// syntax-enforced restrictions) is via being passed as a type parameter. Thus
16961696
// we can conservatively check this by examining only the parameters of the
1697-
// dependent types.
1698-
// affects_layout is a hack introduced by #35275 to workaround a problem
1699-
// introduced by #34223: it checks whether we will potentially need to
1700-
// compute the layout of the object before we have fully computed the types of
1701-
// the fields during recursion over the allocation of the parameters for the
1702-
// field types (of the concrete subtypes)
1703-
static int references_name(jl_value_t *p, jl_typename_t *name, int affects_layout) JL_NOTSAFEPOINT
1704-
{
1705-
if (jl_is_uniontype(p))
1706-
return references_name(((jl_uniontype_t*)p)->a, name, affects_layout) ||
1707-
references_name(((jl_uniontype_t*)p)->b, name, affects_layout);
1708-
if (jl_is_unionall(p))
1709-
return references_name((jl_value_t*)((jl_unionall_t*)p)->var->lb, name, 0) ||
1710-
references_name((jl_value_t*)((jl_unionall_t*)p)->var->ub, name, 0) ||
1711-
references_name(((jl_unionall_t*)p)->body, name, affects_layout);
1697+
// dependent types. Additionally, a field might have already observed this
1698+
// object for layout purposes before we got around to deciding if inlining
1699+
// would be possible, so we cannot change the layout now if so.
1700+
// affects_layout is a (conservative) analysis of layout_uses_free_typevars
1701+
// freevars is a (conservative) analysis of what calling jl_has_bound_typevars from name->wrapper gives (TODO: just call this instead?)
1702+
static int references_name(jl_value_t *p, jl_typename_t *name, int affects_layout, int freevars) JL_NOTSAFEPOINT
1703+
{
1704+
if (freevars && !jl_has_free_typevars(p))
1705+
freevars = 0;
1706+
while (jl_is_unionall(p)) {
1707+
if (references_name((jl_value_t*)((jl_unionall_t*)p)->var->lb, name, 0, freevars) ||
1708+
references_name((jl_value_t*)((jl_unionall_t*)p)->var->ub, name, 0, freevars))
1709+
return 1;
1710+
p = ((jl_unionall_t*)p)->body;
1711+
}
1712+
if (jl_is_uniontype(p)) {
1713+
return references_name(((jl_uniontype_t*)p)->a, name, affects_layout, freevars) ||
1714+
references_name(((jl_uniontype_t*)p)->b, name, affects_layout, freevars);
1715+
}
17121716
if (jl_is_typevar(p))
17131717
return 0; // already checked by unionall, if applicable
17141718
if (jl_is_datatype(p)) {
17151719
jl_datatype_t *dp = (jl_datatype_t*)p;
17161720
if (affects_layout && dp->name == name)
17171721
return 1;
1718-
// affects_layout checks whether we will need to attempt to layout this
1719-
// type (based on whether all copies of it have the same layout) in
1720-
// that case, we still need to check the recursive parameters for
1721-
// layout recursion happening also, but we know it won't itself cause
1722-
// problems for the layout computation
17231722
affects_layout = ((jl_datatype_t*)jl_unwrap_unionall(dp->name->wrapper))->layout == NULL;
1723+
// and even if it has a layout, the fields themselves might trigger layouts if they use tparam i
1724+
// rather than checking this for each field, we just assume it applies
1725+
if (!affects_layout && freevars && jl_field_names(dp) != jl_emptysvec) {
1726+
jl_svec_t *types = ((jl_datatype_t*)jl_unwrap_unionall(dp->name->wrapper))->types;
1727+
size_t i, l = jl_svec_len(types);
1728+
for (i = 0; i < l; i++) {
1729+
jl_value_t *ft = jl_svecref(types, i);
1730+
if (!jl_is_typevar(ft) && jl_has_free_typevars(ft)) {
1731+
affects_layout = 1;
1732+
break;
1733+
}
1734+
}
1735+
}
17241736
size_t i, l = jl_nparams(p);
17251737
for (i = 0; i < l; i++) {
1726-
if (references_name(jl_tparam(p, i), name, affects_layout))
1738+
if (references_name(jl_tparam(p, i), name, affects_layout, freevars))
17271739
return 1;
17281740
}
17291741
}
@@ -1759,12 +1771,12 @@ JL_CALLABLE(jl_f__typebody)
17591771
// able to compute the layout of the object before needing to
17601772
// publish it, so we must assume it cannot be inlined, if that
17611773
// check passes, then we also still need to check the fields too.
1762-
if (!dt->name->mutabl && (nf == 0 || !references_name((jl_value_t*)dt->super, dt->name, 1))) {
1774+
if (!dt->name->mutabl && (nf == 0 || !references_name((jl_value_t*)dt->super, dt->name, 0, 1))) {
17631775
int mayinlinealloc = 1;
17641776
size_t i;
17651777
for (i = 0; i < nf; i++) {
17661778
jl_value_t *fld = jl_svecref(ft, i);
1767-
if (references_name(fld, dt->name, 1)) {
1779+
if (references_name(fld, dt->name, 1, 1)) {
17681780
mayinlinealloc = 0;
17691781
break;
17701782
}

test/core.jl

+36-2
Original file line numberDiff line numberDiff line change
@@ -374,8 +374,8 @@ let ft = Base.datatype_fieldtypes
374374
@test ft(elT2.body)[1].parameters[1] === elT2
375375
@test Base.isconcretetype(ft(elT2.body)[1])
376376
end
377-
#struct S22624{A,B,C} <: Ref{S22624{Int64,A}}; end
378-
@test_broken @isdefined S22624
377+
struct S22624{A,B,C} <: Ref{S22624{Int,A}}; end
378+
@test sizeof(S22624) == sizeof(S22624{Int,Int,Int}) == 0
379379

380380
# issue #42297
381381
mutable struct Node42297{T, V}
@@ -414,6 +414,18 @@ mutable struct FooFoo{A,B} y::FooFoo{A} end
414414

415415
@test FooFoo{Int} <: FooFoo{Int,AbstractString}.types[1]
416416

417+
# make sure this self-referential struct doesn't crash type layout
418+
struct SelfTyA{V}
419+
a::Base.RefValue{V}
420+
end
421+
struct SelfTyB{T}
422+
a::T
423+
b::SelfTyA{SelfTyB{T}}
424+
end
425+
let T = Base.RefValue{SelfTyB{Int}}
426+
@test sizeof(T) === sizeof(Int)
427+
@test sizeof(T.types[1]) === 2 * sizeof(Int)
428+
end
417429

418430
let x = (2,3)
419431
@test +(x...) == 5
@@ -4110,7 +4122,29 @@ end
41104122
let z1 = Z14477()
41114123
@test isa(z1, Z14477)
41124124
@test isa(z1.fld, Z14477)
4125+
@test isdefined(z1, :fld)
4126+
@test !isdefined(z1.fld, :fld)
4127+
end
4128+
struct Z14477B
4129+
fld::Union{Nothing,Z14477B}
4130+
Z14477B() = new(new(nothing))
41134131
end
4132+
let z1 = Z14477B()
4133+
@test isa(z1, Z14477B)
4134+
@test isa(z1.fld, Z14477B)
4135+
@test isa(z1.fld.fld, Nothing)
4136+
end
4137+
struct Z14477C{T}
4138+
fld::Z14477C{Int8}
4139+
Z14477C() = new{Int16}(new{Int8}())
4140+
end
4141+
let z1 = Z14477C()
4142+
@test isa(z1, Z14477C)
4143+
@test isa(z1.fld, Z14477C)
4144+
@test isdefined(z1, :fld)
4145+
@test !isdefined(z1.fld, :fld)
4146+
end
4147+
41144148

41154149
# issue #8846, generic macros
41164150
macro m8846(a, b=0)

0 commit comments

Comments
 (0)