Skip to content

Commit 3ac28ee

Browse files
authored
Avoid broken kind type handling when compiling isa (#27736)
* small workaround checks against incorrect subtyping for kind types for isa_tfunc * add test for #27078 * fix pretty crazy bug where Type{T}s were inferred as Ts * work around broken subtyping in emit_isa codegen optimization * a test that was checking for exact/optimal inference result is now broken Justification for allowing this test to remain broken for now: - benchmarking the expression (including downstream toy calculations on the output, e.g. broadcast sin) using BenchmarkTools reveals no actual performance difference - inference returning an optimal result before was probably reliant on the broken subtyping behavior; correctness >>> performance - inference is still returning a fairly tightly bounded, correct Union type
1 parent 6014e7d commit 3ac28ee

File tree

8 files changed

+68
-31
lines changed

8 files changed

+68
-31
lines changed

base/compiler/abstractinterpretation.jl

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -638,19 +638,20 @@ function abstract_call(@nospecialize(f), fargs::Union{Tuple{},Vector{Any}}, argt
638638
if !isa(body, Type) && !isa(body, TypeVar)
639639
return Any
640640
end
641-
has_free_typevars(body) || return body
642-
if isa(argtypes[2], Const)
643-
tv = argtypes[2].val
644-
elseif isa(argtypes[2], PartialTypeVar)
645-
ptv = argtypes[2]
646-
tv = ptv.tv
647-
canconst = false
648-
else
649-
return Any
641+
if has_free_typevars(body)
642+
if isa(argtypes[2], Const)
643+
tv = argtypes[2].val
644+
elseif isa(argtypes[2], PartialTypeVar)
645+
ptv = argtypes[2]
646+
tv = ptv.tv
647+
canconst = false
648+
else
649+
return Any
650+
end
651+
!isa(tv, TypeVar) && return Any
652+
body = UnionAll(tv, body)
650653
end
651-
!isa(tv, TypeVar) && return Any
652-
theunion = UnionAll(tv, body)
653-
ret = canconst ? AbstractEvalConstant(theunion) : Type{theunion}
654+
ret = canconst ? AbstractEvalConstant(body) : Type{body}
654655
return ret
655656
end
656657
return Any

base/compiler/tfuncs.jl

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,15 @@ add_tfunc(throw, 1, 1, (@nospecialize(x)) -> Bottom, 0)
5353
# returns (type, isexact)
5454
# if isexact is false, the actual runtime type may (will) be a subtype of t
5555
function instanceof_tfunc(@nospecialize(t))
56-
if t === Bottom || t === typeof(Bottom)
57-
return Bottom, true
58-
elseif isa(t, Const)
56+
if isa(t, Const)
5957
if isa(t.val, Type)
6058
return t.val, true
6159
end
60+
return Bottom, true
61+
end
62+
t = widenconst(t)
63+
if t === Bottom || t === typeof(Bottom) || typeintersect(t, Type) === Bottom
64+
return Bottom, true
6265
elseif isType(t)
6366
tp = t.parameters[1]
6467
return tp, !has_free_typevars(tp)
@@ -385,21 +388,29 @@ add_tfunc(typeassert, 2, 2,
385388
return typeintersect(v, t)
386389
end, 4)
387390
add_tfunc(isa, 2, 2,
388-
function (@nospecialize(v), @nospecialize(t))
389-
t, isexact = instanceof_tfunc(t)
391+
function (@nospecialize(v), @nospecialize(tt))
392+
t, isexact = instanceof_tfunc(tt)
393+
if t === Bottom
394+
# check if t could be equivalent to typeof(Bottom), since that's valid in `isa`, but the set of `v` is empty
395+
# if `t` cannot have instances, it's also invalid on the RHS of isa
396+
if typeintersect(widenconst(tt), Type) === Union{}
397+
return Union{}
398+
end
399+
return Const(false)
400+
end
390401
if !has_free_typevars(t)
391-
if t === Bottom
392-
return Const(false)
393-
elseif v t
394-
if isexact
402+
if v t
403+
if isexact && isnotbrokensubtype(v, t)
395404
return Const(true)
396405
end
397406
elseif isa(v, Const) || isa(v, Conditional) || isdispatchelem(v)
398407
# this tests for knowledge of a leaftype appearing on the LHS
399408
# (ensuring the isa is precise)
400409
return Const(false)
401-
elseif isexact && typeintersect(v, t) === Bottom
402-
if !iskindtype(v) #= subtyping currently intentionally answers this query incorrectly for kinds =#
410+
elseif typeintersect(v, t) === Bottom
411+
# similar to `isnotbrokensubtype` check above, `typeintersect(v, t)`
412+
# can't be trusted for kind types so we do an extra check here
413+
if !iskindtype(v)
403414
return Const(false)
404415
end
405416
end

base/compiler/typeutils.jl

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ function issingletontype(@nospecialize t)
3131
return false
3232
end
3333

34+
# Subtyping currently intentionally answers certain queries incorrectly for kind types. For
35+
# some of these queries, this check can be used to somewhat protect against making incorrect
36+
# decisions based on incorrect subtyping. Note that this check, itself, is broken for
37+
# certain combinations of `a` and `b` where one/both isa/are `Union`/`UnionAll` type(s)s.
38+
isnotbrokensubtype(a, b) = (!iskindtype(b) || !isType(a) || issingletontype(a.parameters[1]))
39+
3440
argtypes_to_type(argtypes::Array{Any,1}) = Tuple{anymap(widenconst, argtypes)...}
3541

3642
function isknownlength(t::DataType)
@@ -52,7 +58,7 @@ end
5258
# return an upper-bound on type `a` with type `b` removed
5359
# such that `return <: a` && `Union{return, b} == Union{a, b}`
5460
function typesubtract(@nospecialize(a), @nospecialize(b))
55-
if a <: b
61+
if a <: b && isnotbrokensubtype(a, b)
5662
return Bottom
5763
end
5864
if isa(a, Union)

src/cgutils.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,13 +1060,18 @@ static void emit_type_error(jl_codectx_t &ctx, const jl_cgval_t &x, Value *type,
10601060

10611061
static std::pair<Value*, bool> emit_isa(jl_codectx_t &ctx, const jl_cgval_t &x, jl_value_t *type, const std::string *msg)
10621062
{
1063+
// TODO: The subtype check below suffers from incorrectness issues due to broken
1064+
// subtyping for kind types (see https://github.com/JuliaLang/julia/issues/27078). For
1065+
// actual `isa` calls, this optimization should already have been performed upstream
1066+
// anyway, but having this optimization in codegen might still be beneficial for
1067+
// `typeassert`s if we can make it correct.
10631068
Optional<bool> known_isa;
10641069
jl_value_t *intersected_type = type;
10651070
if (x.constant)
10661071
known_isa = jl_isa(x.constant, type);
1067-
else if (jl_subtype(x.typ, type))
1072+
else if (jl_is_not_broken_subtype(x.typ, type) && jl_subtype(x.typ, type)) {
10681073
known_isa = true;
1069-
else {
1074+
} else {
10701075
intersected_type = jl_type_intersection(x.typ, type);
10711076
if (intersected_type == (jl_value_t*)jl_bottom_type)
10721077
known_isa = false;

src/julia.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,7 @@ JL_DLLEXPORT int jl_subtype_env_size(jl_value_t *t);
10671067
JL_DLLEXPORT int jl_subtype_env(jl_value_t *x, jl_value_t *y, jl_value_t **env, int envsz);
10681068
JL_DLLEXPORT int jl_isa(jl_value_t *a, jl_value_t *t);
10691069
JL_DLLEXPORT int jl_types_equal(jl_value_t *a, jl_value_t *b) JL_NOTSAFEPOINT;
1070+
JL_DLLEXPORT int jl_is_not_broken_subtype(jl_value_t *a, jl_value_t *b);
10701071
JL_DLLEXPORT jl_value_t *jl_type_union(jl_value_t **ts, size_t n);
10711072
JL_DLLEXPORT jl_value_t *jl_type_intersection(jl_value_t *a, jl_value_t *b);
10721073
JL_DLLEXPORT int jl_has_empty_intersection(jl_value_t *x, jl_value_t *y);

src/subtype.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,6 +1165,13 @@ JL_DLLEXPORT int jl_types_equal(jl_value_t *a, jl_value_t *b)
11651165
return jl_subtype(a, b) && jl_subtype(b, a);
11661166
}
11671167

1168+
JL_DLLEXPORT int jl_is_not_broken_subtype(jl_value_t *a, jl_value_t *b)
1169+
{
1170+
// TODO: the final commented out check here isn't correct; it should be closer to the
1171+
// `issingletype` check used by `isnotbrokensubtype` in `base/compiler/typeutils.jl`
1172+
return !jl_is_kind(b) || !jl_is_type_type(a); // || jl_is_datatype_singleton((jl_datatype_t*)jl_tparam0(a));
1173+
}
1174+
11681175
int jl_tuple_isa(jl_value_t **child, size_t cl, jl_datatype_t *pdt)
11691176
{
11701177
if (jl_is_tuple_type(pdt) && !jl_is_va_tuple(pdt)) {

test/compiler/compiler.jl

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,7 +1179,7 @@ let isa_tfunc = Core.Compiler.T_FFUNC_VAL[
11791179
@test isa_tfunc(Array{Real}, Type{AbstractArray{Int}}) === Const(false)
11801180
@test isa_tfunc(Array{Real, 2}, Const(AbstractArray{Real, 2})) === Const(true)
11811181
@test isa_tfunc(Array{Real, 2}, Const(AbstractArray{Int, 2})) === Const(false)
1182-
@test isa_tfunc(DataType, Int) === Bool # could be improved
1182+
@test isa_tfunc(DataType, Int) === Union{}
11831183
@test isa_tfunc(DataType, Const(Type{Int})) === Bool
11841184
@test isa_tfunc(DataType, Const(Type{Array})) === Bool
11851185
@test isa_tfunc(UnionAll, Const(Type{Int})) === Bool # could be improved
@@ -1189,7 +1189,7 @@ let isa_tfunc = Core.Compiler.T_FFUNC_VAL[
11891189
@test isa_tfunc(typeof(Union{}), Const(Int)) === Const(false) # any result is ok
11901190
@test isa_tfunc(typeof(Union{}), Const(Union{})) === Const(false)
11911191
@test isa_tfunc(typeof(Union{}), typeof(Union{})) === Const(false)
1192-
@test isa_tfunc(typeof(Union{}), Union{}) === Const(false) # any result is ok
1192+
@test isa_tfunc(typeof(Union{}), Union{}) === Union{} # any result is ok
11931193
@test isa_tfunc(typeof(Union{}), Type{typeof(Union{})}) === Const(true)
11941194
@test isa_tfunc(typeof(Union{}), Const(typeof(Union{}))) === Const(true)
11951195
let c = Conditional(Core.SlotNumber(0), Const(Union{}), Const(Union{}))
@@ -1204,7 +1204,7 @@ let isa_tfunc = Core.Compiler.T_FFUNC_VAL[
12041204
@test isa_tfunc(Val{1}, Type{Val{T}} where T) === Bool
12051205
@test isa_tfunc(Val{1}, DataType) === Bool
12061206
@test isa_tfunc(Any, Const(Any)) === Const(true)
1207-
@test isa_tfunc(Any, Union{}) === Const(false) # any result is ok
1207+
@test isa_tfunc(Any, Union{}) === Union{} # any result is ok
12081208
@test isa_tfunc(Any, Type{Union{}}) === Const(false)
12091209
@test isa_tfunc(Union{Int64, Float64}, Type{Real}) === Const(true)
12101210
@test isa_tfunc(Union{Int64, Float64}, Type{Integer}) === Bool
@@ -1245,7 +1245,7 @@ let subtype_tfunc = Core.Compiler.T_FFUNC_VAL[
12451245
@test subtype_tfunc(Type{Union{}}, Union{Type{Int64}, Type{Float64}}) === Const(true)
12461246
@test subtype_tfunc(Type{Union{}}, Union{Type{T}, Type{Float64}} where T) === Const(true)
12471247
let c = Conditional(Core.SlotNumber(0), Const(Union{}), Const(Union{}))
1248-
@test subtype_tfunc(c, Const(Bool)) === Bool # any result is ok
1248+
@test subtype_tfunc(c, Const(Bool)) === Const(true) # any result is ok
12491249
end
12501250
@test subtype_tfunc(Type{Val{1}}, Type{Val{T}} where T) === Bool
12511251
@test subtype_tfunc(Type{Val{1}}, DataType) === Bool
@@ -1717,3 +1717,8 @@ Base.iterate(i::Iterator27434, ::Val{2}) = i.z, Val(3)
17171717
Base.iterate(::Iterator27434, ::Any) = nothing
17181718
@test @inferred splat27434(Iterator27434(1, 2, 3)) == (1, 2, 3)
17191719
@test Core.Compiler.return_type(splat27434, Tuple{typeof(Iterators.repeated(1))}) == Union{}
1720+
1721+
# issue #27078
1722+
f27078(T::Type{S}) where {S} = isa(T, UnionAll) ? f27078(T.body) : T
1723+
T27078 = Vector{Vector{T}} where T
1724+
@test f27078(T27078) === T27078.body

test/sets.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,8 @@ end
557557
x = @inferred replace(x -> x > 1, [1, 2], missing)
558558
@test isequal(x, [1, missing]) && x isa Vector{Union{Int, Missing}}
559559

560-
x = @inferred replace([1, missing], missing=>2)
560+
@test_broken @inferred replace([1, missing], missing=>2)
561+
x = replace([1, missing], missing=>2)
561562
@test x == [1, 2] && x isa Vector{Int}
562563
x = @inferred replace([1, missing], missing=>2, count=1)
563564
@test x == [1, 2] && x isa Vector{Union{Int, Missing}}

0 commit comments

Comments
 (0)