Skip to content

Commit 44eb16d

Browse files
authored
inference: fix bitcast nothrow computation, and some other instanceof_tfunc uses (#37575)
Fixes #37532
1 parent 274b973 commit 44eb16d

File tree

2 files changed

+73
-49
lines changed

2 files changed

+73
-49
lines changed

base/compiler/tfuncs.jl

Lines changed: 66 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -58,40 +58,52 @@ end
5858
add_tfunc(throw, 1, 1, (@nospecialize(x)) -> Bottom, 0)
5959

6060
# the inverse of typeof_tfunc
61-
# returns (type, isexact)
61+
# returns (type, isexact, isconcrete)
6262
# if isexact is false, the actual runtime type may (will) be a subtype of t
63+
# if isconcrete is true, the actual runtime type is definitely concrete (unreachable if not valid as a typeof)
6364
function instanceof_tfunc(@nospecialize(t))
6465
if isa(t, Const)
6566
if isa(t.val, Type)
66-
return t.val, true
67+
return t.val, true, isconcretetype(t.val)
6768
end
68-
return Bottom, true
69+
return Bottom, true, false # runtime throws on non-Type
6970
end
7071
t = widenconst(t)
71-
if t === Bottom || t === typeof(Bottom) || typeintersect(t, Type) === Bottom
72-
return Bottom, true
72+
if t === Bottom
73+
return Bottom, true, true # runtime unreachable
74+
elseif t === typeof(Bottom) || typeintersect(t, Type) === Bottom
75+
return Bottom, true, false # literal Bottom or non-Type
7376
elseif isType(t)
7477
tp = t.parameters[1]
75-
return tp, !has_free_typevars(tp)
78+
return tp, !has_free_typevars(tp), isconcretetype(tp)
7679
elseif isa(t, UnionAll)
7780
t′ = unwrap_unionall(t)
78-
t′′, isexact = instanceof_tfunc(t′)
81+
t′′, isexact, isconcrete = instanceof_tfunc(t′)
7982
tr = rewrap_unionall(t′′, t)
80-
if t′′ isa DataType && !has_free_typevars(tr)
83+
if t′′ isa DataType && t′′.name !== Tuple.name && !has_free_typevars(tr)
8184
# a real instance must be within the declared bounds of the type,
8285
# so we can intersect with the original wrapper.
8386
tr = typeintersect(tr, t′′.name.wrapper)
87+
isconcrete = !t′′.abstract
88+
if tr === Union{}
89+
# runtime unreachable (our inference Type{T} where S is
90+
# uninhabited with any runtime T that exists)
91+
isexact = true
92+
end
8493
end
85-
return tr, isexact
94+
return tr, isexact, isconcrete
8695
elseif isa(t, Union)
87-
ta, isexact_a = instanceof_tfunc(t.a)
88-
tb, isexact_b = instanceof_tfunc(t.b)
89-
ta === Union{} && return tb, isexact_b
90-
tb === Union{} && return ta, isexact_a
91-
ta == tb && return ta, isexact_a && isexact_b
92-
return Union{ta, tb}, false # at runtime, will be exactly one of these
93-
end
94-
return Any, false
96+
ta, isexact_a, isconcrete_a = instanceof_tfunc(t.a)
97+
tb, isexact_b, isconcrete_b = instanceof_tfunc(t.b)
98+
isconcrete = isconcrete_a && isconcrete_b
99+
# most users already handle the Union case, so here we assume that
100+
# `isexact` only cares about the answers where there's actually a Type
101+
# (and assuming other cases causing runtime errors)
102+
ta === Union{} && return tb, isexact_b, isconcrete
103+
tb === Union{} && return ta, isexact_a, isconcrete
104+
return Union{ta, tb}, false, isconcrete # at runtime, will be exactly one of these
105+
end
106+
return Any, false, false
95107
end
96108
bitcast_tfunc(@nospecialize(t), @nospecialize(x)) = instanceof_tfunc(t)[1]
97109
math_tfunc(@nospecialize(x)) = widenconst(x)
@@ -294,38 +306,41 @@ add_tfunc(isdefined, 2, 2, isdefined_tfunc, 1)
294306

295307
function sizeof_nothrow(@nospecialize(x))
296308
if isa(x, Const)
297-
x = x.val
298-
if !isa(x, Type) || x === DataType
309+
if !isa(x.val, Type) || x.val === DataType
299310
return true
300311
end
301312
elseif isa(x, Conditional)
302313
return true
303314
end
304-
if isa(x, Union)
305-
return sizeof_nothrow(x.a) && sizeof_nothrow(x.b)
315+
xu = unwrap_unionall(x)
316+
if isa(xu, Union)
317+
return sizeof_nothrow(rewrap_unionall(xu.a, x)) &&
318+
sizeof_nothrow(rewrap_unionall(xu.b, x))
306319
end
307-
t, exact = instanceof_tfunc(x)
308-
if !exact
309-
# Could always be bottom at runtime, which throws
310-
return false
320+
t, exact, isconcrete = instanceof_tfunc(x)
321+
if t === Bottom
322+
# x must be an instance (not a Type) or is the Bottom type object
323+
x = widenconst(x)
324+
return typeintersect(x, Type) === Union{}
311325
end
312-
if t !== Bottom
313-
t === DataType && return true
314-
x = t
315-
x = unwrap_unionall(x)
316-
if isa(x, Union)
317-
isinline, sz, _ = uniontype_layout(x)
318-
return isinline
326+
x = unwrap_unionall(t)
327+
if isconcrete
328+
if isa(x, DataType) && x.layout != C_NULL
329+
# there's just a few concrete types with an opaque layout
330+
(datatype_nfields(x) == 0 && !datatype_pointerfree(x)) && return false
319331
end
320-
isa(x, DataType) || return false
321-
x.layout == C_NULL && return false
322-
(datatype_nfields(x) == 0 && !datatype_pointerfree(x)) && return false
323-
return true
324-
else
325-
x = widenconst(x)
326-
x === DataType && return false
327-
return isconcretetype(x) || isprimitivetype(x)
332+
return true # these must always have a size of these
328333
end
334+
exact || return false # Could always be the type Bottom at runtime, for example, which throws
335+
t === DataType && return true # DataType itself has a size
336+
if isa(x, Union)
337+
isinline, sz, _ = uniontype_layout(x)
338+
return isinline # even any subset of this union would have a size
339+
end
340+
isa(x, DataType) || return false
341+
x.layout == C_NULL && return false
342+
(datatype_nfields(x) == 0 && !datatype_pointerfree(x)) && return false # is-layout-opaque
343+
return true
329344
end
330345

331346
function _const_sizeof(@nospecialize(x))
@@ -346,8 +361,10 @@ function sizeof_tfunc(@nospecialize(x),)
346361
isa(x, Const) && return _const_sizeof(x.val)
347362
isa(x, Conditional) && return _const_sizeof(Bool)
348363
isconstType(x) && return _const_sizeof(x.parameters[1])
349-
if isa(x, Union)
350-
return tmerge(sizeof_tfunc(x.a), sizeof_tfunc(x.b))
364+
xu = unwrap_unionall(x)
365+
if isa(xu, Union)
366+
return tmerge(sizeof_tfunc(rewrap_unionall(xu.a, x)),
367+
sizeof_tfunc(rewrap_unionall(xu.b, x)))
351368
end
352369
# Core.sizeof operates on either a type or a value. First check which
353370
# case we're in.
@@ -356,9 +373,9 @@ function sizeof_tfunc(@nospecialize(x),)
356373
# The value corresponding to `x` at runtime could be a type.
357374
# Normalize the query to ask about that type.
358375
x = unwrap_unionall(t)
359-
if isa(x, Union)
376+
if exact && isa(x, Union)
360377
isinline, sz, _ = uniontype_layout(x)
361-
return isinline ? Const(Int(sz)) : (exact ? Bottom : Int)
378+
return isinline ? Const(Int(sz)) : Bottom
362379
end
363380
isa(x, DataType) || return Int
364381
(isconcretetype(x) || isprimitivetype(x)) && return _const_sizeof(x)
@@ -1531,18 +1548,18 @@ function intrinsic_nothrow(f::IntrinsicFunction, argtypes::Array{Any, 1})
15311548
return argtypes[1] Array
15321549
end
15331550
if f === Intrinsics.bitcast
1534-
ty = instanceof_tfunc(argtypes[1])[1]
1551+
ty, isexact, isconcrete = instanceof_tfunc(argtypes[1])
15351552
xty = widenconst(argtypes[2])
1536-
return isprimitivetype(ty) && isprimitivetype(xty) && ty.size === xty.size
1553+
return isconcrete && isprimitivetype(ty) && isprimitivetype(xty) && Core.sizeof(ty) === Core.sizeof(xty)
15371554
end
15381555
if f in (Intrinsics.sext_int, Intrinsics.zext_int, Intrinsics.trunc_int,
15391556
Intrinsics.fptoui, Intrinsics.fptosi, Intrinsics.uitofp,
15401557
Intrinsics.sitofp, Intrinsics.fptrunc, Intrinsics.fpext)
1541-
# If !isexact, `ty` may be Union{} at runtime even if we have
1558+
# If !isconcrete, `ty` may be Union{} at runtime even if we have
15421559
# isprimitivetype(ty).
1543-
ty, isexact = instanceof_tfunc(argtypes[1])
1560+
ty, isexact, isconcrete = instanceof_tfunc(argtypes[1])
15441561
xty = widenconst(argtypes[2])
1545-
return isexact && isprimitivetype(ty) && isprimitivetype(xty)
1562+
return isconcrete && isprimitivetype(ty) && isprimitivetype(xty)
15461563
end
15471564
# The remaining intrinsics are math/bits/comparison intrinsics. They work on all
15481565
# primitive types of the same type.

test/compiler/inference.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2777,3 +2777,10 @@ f_apply_cglobal(args...) = cglobal(args...)
27772777
@test Core.Compiler.return_type(f_apply_cglobal, Tuple{Any, Vararg{Type{Int}}}) == Ptr
27782778
@test Core.Compiler.return_type(f_apply_cglobal, Tuple{Any, Type{Int}, Vararg{Type{Int}}}) == Ptr{Int}
27792779
@test Core.Compiler.return_type(f_apply_cglobal, Tuple{Any, Type{Int}, Type{Int}, Vararg{Type{Int}}}) == Union{}
2780+
2781+
# issue #37532
2782+
@test Core.Compiler.intrinsic_nothrow(Core.bitcast, Any[Type{Ptr{Int}}, Int])
2783+
@test Core.Compiler.intrinsic_nothrow(Core.bitcast, Any[Type{Ptr{T}} where T, Ptr])
2784+
@test !Core.Compiler.intrinsic_nothrow(Core.bitcast, Any[Type{Ptr}, Ptr])
2785+
f37532(T, x) = (Core.bitcast(Ptr{T}, x); x)
2786+
@test Base.return_types(f37532, Tuple{Any, Int}) == Any[Int]

0 commit comments

Comments
 (0)