Skip to content

Commit f07dea9

Browse files
committed
effects: define Core.Compiler.infer_effects
This new reflection utility is similar to `Core.Compiler.return_type` but for the effect analysis. It comes with its own tfunc and similar compiler optimizations so that we can fold inferred effects at compiler time when possible (albeit it is as unreliable as `Core.Compiler.return_type`).
1 parent c2da085 commit f07dea9

File tree

11 files changed

+230
-87
lines changed

11 files changed

+230
-87
lines changed

base/compiler/abstractinterpretation.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1616,6 +1616,9 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f),
16161616
elseif is_return_type(f)
16171617
tristate_merge!(sv, EFFECTS_UNKNOWN) # TODO
16181618
return return_type_tfunc(interp, argtypes, sv)
1619+
elseif is_infer_effects(f)
1620+
tristate_merge!(sv, EFFECTS_UNKNOWN) # TODO
1621+
return infer_effects_tfunc(interp, argtypes, sv)
16191622
elseif la == 2 && istopfunction(f, :!)
16201623
# handle Conditional propagation through !Bool
16211624
aty = argtypes[2]

base/compiler/compiler.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ include("options.jl")
4040

4141
# core operations & types
4242
function return_type end # promotion.jl expects this to exist
43-
is_return_type(@Core.nospecialize(f)) = f === return_type
43+
function infer_effects end
44+
is_return_type(@nospecialize f) = f === return_type
45+
is_infer_effects(@nospecialize f) = f === infer_effects
4446
include("promotion.jl")
4547
include("tuple.jl")
4648
include("pair.jl")

base/compiler/optimize.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ function stmt_effect_free(@nospecialize(stmt), @nospecialize(rt), src::Union{IRC
202202
f = argextype(args[1], src)
203203
f = singleton_type(f)
204204
f === nothing && return false
205-
is_return_type(f) && return true
205+
(is_return_type(f) || is_infer_effects(f)) && return true
206206
if isa(f, IntrinsicFunction)
207207
intrinsic_effect_free_if_nothrow(f) || return false
208208
return intrinsic_nothrow(f,

base/compiler/ssair/EscapeAnalysis/interprocedural.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import Core.Compiler:
44
MethodInstance, InferenceResult, Signature, ConstResult,
55
MethodResultPure, MethodMatchInfo, UnionSplitInfo, ConstCallInfo, InvokeCallInfo,
6-
call_sig, argtypes_to_type, is_builtin, is_return_type, istopfunction, validate_sparams,
7-
specialize_method, invoke_rewrite
6+
call_sig, argtypes_to_type, is_builtin, is_return_type, is_infer_effects, istopfunction,
7+
validate_sparams, specialize_method, invoke_rewrite
88

99
const Linfo = Union{MethodInstance,InferenceResult}
1010
struct CallInfo
@@ -31,7 +31,7 @@ function resolve_call(ir::IRCode, stmt::Expr, @nospecialize(info))
3131
return true
3232
elseif f === UnionAll && length(argtypes) == 3 && (argtypes[2] TypeVar)
3333
return true
34-
elseif is_return_type(f)
34+
elseif is_return_type(f) || is_infer_effects(f)
3535
return true
3636
end
3737
if info isa MethodResultPure
@@ -49,7 +49,7 @@ function resolve_call(ir::IRCode, stmt::Expr, @nospecialize(info))
4949
infos = MethodMatchInfo[info]
5050
elseif isa(info, UnionSplitInfo)
5151
infos = info.matches
52-
else # isa(info, ReturnTypeCallInfo), etc.
52+
else # isa(info, VirtualCallInfo), etc.
5353
return missing
5454
end
5555
return analyze_call(sig, infos)

base/compiler/ssair/inlining.jl

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,9 +1217,6 @@ function process_simple!(ir::IRCode, idx::Int, state::InliningState, todo::Vecto
12171217
ir[SSAValue(idx)][:inst] = lateres.val
12181218
check_effect_free!(ir, idx, lateres.val, rt)
12191219
return nothing
1220-
elseif is_return_type(sig.f)
1221-
check_effect_free!(ir, idx, stmt, rt)
1222-
return nothing
12231220
end
12241221

12251222
return stmt, sig
@@ -1445,7 +1442,7 @@ function assemble_inline_todo!(ir::IRCode, state::InliningState)
14451442
elseif isa(info, UnionSplitInfo)
14461443
infos = info.matches
14471444
else
1448-
continue # isa(info, ReturnTypeCallInfo), etc.
1445+
continue # isa(info, VirtualCallInfo), etc.
14491446
end
14501447

14511448
analyze_single_call!(ir, idx, stmt, infos, flag, sig, state, todo)
@@ -1534,7 +1531,7 @@ function late_inline_special_case!(
15341531
unionall_call = Expr(:foreigncall, QuoteNode(:jl_type_unionall), Any, svec(Any, Any),
15351532
0, QuoteNode(:ccall), stmt.args[2], stmt.args[3])
15361533
return SomeCase(unionall_call)
1537-
elseif is_return_type(f)
1534+
elseif is_return_type(f) || is_infer_effects(f)
15381535
if isconstType(type)
15391536
return SomeCase(quoted(type.parameters[1]))
15401537
elseif isa(type, Const)

base/compiler/stmtinfo.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,13 @@ end
166166
# the AbstractInterpreter.
167167

168168
"""
169-
info::ReturnTypeCallInfo
169+
info::VirtualCallInfo
170170
171-
Represents a resolved call of `Core.Compiler.return_type`.
172-
`info.call` wraps the info corresponding to the call that `Core.Compiler.return_type` call
173-
was supposed to analyze.
171+
Represents a resolved call of `Core.Compiler.return_type` or `Core.Compiler.infer_effects`.
172+
`info.call` wraps the info corresponding to the call that the reflection function was
173+
supposed to analyze.
174174
"""
175-
struct ReturnTypeCallInfo
175+
struct VirtualCallInfo
176176
info::Any
177177
end
178178

base/compiler/tfuncs.jl

Lines changed: 80 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2014,59 +2014,88 @@ function intrinsic_effects(f::IntrinsicFunction, argtypes::Vector{Any})
20142014
nothrow = nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN)
20152015
end
20162016

2017-
# TODO: this function is a very buggy and poor model of the return_type function
2018-
# since abstract_call_gf_by_type is a very inaccurate model of _method and of typeinf_type,
2017+
# TODO: this function is a very buggy and poor model of the `return_type` function
2018+
# since `abstract_call_gf_by_type` is a very inaccurate model of `_method` and of `typeinf_type`,
20192019
# while this assumes that it is an absolutely precise and accurate and exact model of both
20202020
function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, sv::InferenceState)
2021-
if length(argtypes) == 3
2022-
tt = argtypes[3]
2023-
if isa(tt, Const) || (isType(tt) && !has_free_typevars(tt))
2024-
aft = argtypes[2]
2025-
if isa(aft, Const) || (isType(aft) && !has_free_typevars(aft)) ||
2026-
(isconcretetype(aft) && !(aft <: Builtin))
2027-
af_argtype = isa(tt, Const) ? tt.val : (tt::DataType).parameters[1]
2028-
if isa(af_argtype, DataType) && af_argtype <: Tuple
2029-
argtypes_vec = Any[aft, af_argtype.parameters...]
2030-
if contains_is(argtypes_vec, Union{})
2031-
return CallMeta(Const(Union{}), false)
2032-
end
2033-
# Run the abstract_call without restricting abstract call
2034-
# sites. Otherwise, our behavior model of abstract_call
2035-
# below will be wrong.
2036-
old_restrict = sv.restrict_abstract_call_sites
2037-
sv.restrict_abstract_call_sites = false
2038-
call = abstract_call(interp, ArgInfo(nothing, argtypes_vec), sv, -1)
2039-
sv.restrict_abstract_call_sites = old_restrict
2040-
info = verbose_stmt_info(interp) ? ReturnTypeCallInfo(call.info) : false
2041-
rt = widenconditional(call.rt)
2042-
if isa(rt, Const)
2043-
# output was computed to be constant
2044-
return CallMeta(Const(typeof(rt.val)), info)
2045-
end
2046-
rt = widenconst(rt)
2047-
if rt === Bottom || (isconcretetype(rt) && !iskindtype(rt))
2048-
# output cannot be improved so it is known for certain
2049-
return CallMeta(Const(rt), info)
2050-
elseif !isempty(sv.pclimitations)
2051-
# conservatively express uncertainty of this result
2052-
# in two ways: both as being a subtype of this, and
2053-
# because of LimitedAccuracy causes
2054-
return CallMeta(Type{<:rt}, info)
2055-
elseif (isa(tt, Const) || isconstType(tt)) &&
2056-
(isa(aft, Const) || isconstType(aft))
2057-
# input arguments were known for certain
2058-
# XXX: this doesn't imply we know anything about rt
2059-
return CallMeta(Const(rt), info)
2060-
elseif isType(rt)
2061-
return CallMeta(Type{rt}, info)
2062-
else
2063-
return CallMeta(Type{<:rt}, info)
2064-
end
2065-
end
2066-
end
2067-
end
2068-
end
2069-
return CallMeta(Type, false)
2021+
length(argtypes) == 3 || return CallMeta(Type, false)
2022+
aft = argtypes[2]
2023+
if !(isa(aft, Const) ||
2024+
(isType(aft) && !has_free_typevars(aft)) ||
2025+
(isconcretetype(aft) && !(aft <: Builtin)))
2026+
return CallMeta(Type, false)
2027+
end
2028+
tt = argtypes[3]
2029+
isa(tt, Const) || (isType(tt) && !has_free_typevars(tt)) || return CallMeta(Type, false)
2030+
af_argtype = isa(tt, Const) ? tt.val : (tt::DataType).parameters[1]
2031+
(isa(af_argtype, DataType) && af_argtype <: Tuple) || return CallMeta(Type, false)
2032+
argtypes_vec = Any[aft, af_argtype.parameters...]
2033+
if contains_is(argtypes_vec, Union{})
2034+
return CallMeta(Const(Union{}), false)
2035+
end
2036+
call, _ = virtual_abstract_call(interp, argtypes_vec, sv)
2037+
rt = widenconditional(call.rt)
2038+
info = verbose_stmt_info(interp) ? VirtualCallInfo(call.info) : false
2039+
if isa(rt, Const)
2040+
# output was computed to be constant
2041+
return CallMeta(Const(typeof(rt.val)), info)
2042+
end
2043+
rt = widenconst(rt)
2044+
if rt === Bottom || (isconcretetype(rt) && !iskindtype(rt))
2045+
# output cannot be improved so it is known for certain
2046+
return CallMeta(Const(rt), info)
2047+
elseif !isempty(sv.pclimitations)
2048+
# conservatively express uncertainty of this result in two ways:
2049+
# both as being a subtype of this, and because of LimitedAccuracy causes
2050+
return CallMeta(Type{<:rt}, info)
2051+
end
2052+
aft = argtypes[2]
2053+
tt = argtypes[3]
2054+
if (isa(tt, Const) || isconstType(tt)) && (isa(aft, Const) || isconstType(aft))
2055+
# input arguments were known for certain
2056+
# XXX: this doesn't imply we know anything about rt
2057+
return CallMeta(Const(rt), info)
2058+
elseif isType(rt)
2059+
return CallMeta(Type{rt}, info)
2060+
end
2061+
return CallMeta(Type{<:rt}, info)
2062+
end
2063+
2064+
# XXX this tfunc has the same unreliability as `return_type_tfunc`, and also some duplications with it
2065+
function infer_effects_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, sv::InferenceState)
2066+
length(argtypes) == 3 || return CallMeta(Effects, false)
2067+
aft = argtypes[2]
2068+
tt = argtypes[3]
2069+
# models the infer_effects function only when the input arguments are fully known
2070+
if !((isa(tt, Const) || isconstType(tt)) && (isa(aft, Const) || isconstType(aft)))
2071+
return CallMeta(Effects, false)
2072+
end
2073+
af_argtype = isa(tt, Const) ? tt.val : (tt::DataType).parameters[1]
2074+
(isa(af_argtype, DataType) && af_argtype <: Tuple) || return CallMeta(Effects, false)
2075+
argtypes_vec = Any[aft, af_argtype.parameters...]
2076+
call, effects = virtual_abstract_call(interp, argtypes_vec, sv)
2077+
info = verbose_stmt_info(interp) ? VirtualCallInfo(call.info) : false
2078+
if !isempty(sv.pclimitations)
2079+
# conservatively express uncertainty of this result
2080+
return CallMeta(Effects, false)
2081+
end
2082+
return CallMeta(Const(effects), info)
2083+
end
2084+
2085+
# first set temporal states, then model inference on this call with the virtual `abstract_call`,
2086+
# and finally reset to the original states
2087+
function virtual_abstract_call(interp::AbstractInterpreter, argtypes::Vector{Any}, sv::InferenceState)
2088+
old_restrict = sv.restrict_abstract_call_sites
2089+
old_effects = sv.ipo_effects
2090+
# Run the `abstract_call` without restricting abstract call sites.
2091+
# Otherwise, our behavior model of `abstract_call` below will be wrong.
2092+
sv.restrict_abstract_call_sites = false
2093+
sv.ipo_effects = EFFECTS_TOTAL # XXX respect inbounds_taints_consistency?
2094+
call = abstract_call(interp, ArgInfo(nothing, argtypes), sv, -1)
2095+
effects = sv.ipo_effects
2096+
sv.restrict_abstract_call_sites = old_restrict
2097+
sv.ipo_effects = old_effects
2098+
return call, effects
20702099
end
20712100

20722101
# N.B.: typename maps type equivalence classes to a single value

base/compiler/typeinfer.jl

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,26 +1030,10 @@ function typeinf_ext_toplevel(interp::AbstractInterpreter, linfo::MethodInstance
10301030
return src
10311031
end
10321032

1033-
function return_type(@nospecialize(f), t::DataType) # this method has a special tfunc
1034-
world = ccall(:jl_get_tls_world_age, UInt, ())
1035-
args = Any[_return_type, NativeInterpreter(world), Tuple{Core.Typeof(f), t.parameters...}]
1036-
return ccall(:jl_call_in_typeinf_world, Any, (Ptr{Ptr{Cvoid}}, Cint), args, length(args))
1037-
end
1038-
1039-
function return_type(@nospecialize(f), t::DataType, world::UInt)
1040-
return return_type(Tuple{Core.Typeof(f), t.parameters...}, world)
1041-
end
1042-
1043-
function return_type(t::DataType)
1044-
world = ccall(:jl_get_tls_world_age, UInt, ())
1045-
return return_type(t, world)
1046-
end
1047-
1048-
function return_type(t::DataType, world::UInt)
1049-
args = Any[_return_type, NativeInterpreter(world), t]
1050-
return ccall(:jl_call_in_typeinf_world, Any, (Ptr{Ptr{Cvoid}}, Cint), args, length(args))
1051-
end
1052-
1033+
return_type(@nospecialize(f), t::DataType) = return_type(f, t, get_tsl_world_counter()) # this method has a special tfunc
1034+
return_type(@nospecialize(f), t::DataType, world::UInt) = return_type(Tuple{Core.Typeof(f), t.parameters...}, world)
1035+
return_type(t::DataType) = return_type(t, get_tsl_world_counter())
1036+
return_type(t::DataType, world::UInt) = call_in_typeinf_world(Any[_return_type, NativeInterpreter(world), t])
10531037
function _return_type(interp::AbstractInterpreter, t::DataType)
10541038
rt = Union{}
10551039
f = singleton_type(t.parameters[1])
@@ -1069,3 +1053,39 @@ function _return_type(interp::AbstractInterpreter, t::DataType)
10691053
end
10701054
return rt
10711055
end
1056+
1057+
infer_effects(@nospecialize(f), t::DataType) = infer_effects(f, t, get_tsl_world_counter())
1058+
infer_effects(@nospecialize(f), t::DataType, world::UInt) = infer_effects(Tuple{Core.Typeof(f), t.parameters...}, world)
1059+
infer_effects(t::DataType) = infer_effects(t, get_tsl_world_counter())
1060+
infer_effects(t::DataType, world::UInt) = call_in_typeinf_world(Any[_infer_effects, NativeInterpreter(world), t])
1061+
function _infer_effects(interp::AbstractInterpreter, t::DataType)
1062+
f = singleton_type(t.parameters[1])
1063+
if isa(f, Builtin)
1064+
args = Any[t.parameters...]
1065+
popfirst!(args)
1066+
rt = builtin_tfunction(interp, f, args, nothing)
1067+
return builtin_effects(f, args, rt)
1068+
else
1069+
effects = EFFECTS_TOTAL
1070+
matches = _methods_by_ftype(t, -1, get_world_counter(interp))::Vector
1071+
if isempty(matches)
1072+
# although this call is known to throw MethodError (thus `nothrow=ALWAYS_FALSE`),
1073+
# still mark it `TRISTATE_UNKNOWN` just in order to be consistent with a result
1074+
# derived by the effect analysis, which can't prove guaranteed throwness at this moment
1075+
return Effects(effects; nothrow=TRISTATE_UNKNOWN)
1076+
end
1077+
for match in matches
1078+
match = match::MethodMatch
1079+
frame = typeinf_frame(interp, match.method, match.spec_types, match.sparams, #=run_optimizer=#false)
1080+
frame === nothing && return Effects()
1081+
effects = tristate_merge(effects, frame.ipo_effects)
1082+
end
1083+
end
1084+
return effects
1085+
end
1086+
1087+
get_tsl_world_counter() = ccall(:jl_get_tls_world_age, UInt, ())
1088+
1089+
function call_in_typeinf_world(args::Vector{Any})
1090+
return ccall(:jl_call_in_typeinf_world, Any, (Ptr{Ptr{Cvoid}}, Cint), args, length(args))
1091+
end

base/reflection.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,6 +1310,7 @@ function return_types(@nospecialize(f), @nospecialize(types=default_tt(f));
13101310
return rt
13111311
end
13121312

1313+
if nameof(@__MODULE__) === :Base
13131314
function infer_effects(@nospecialize(f), @nospecialize(types=default_tt(f));
13141315
world = get_world_counter(),
13151316
interp = Core.Compiler.NativeInterpreter(world))
@@ -1338,6 +1339,7 @@ function infer_effects(@nospecialize(f), @nospecialize(types=default_tt(f));
13381339
return effects
13391340
end
13401341
end
1342+
end # if nameof(@__MODULE__) === :Base
13411343

13421344
"""
13431345
print_statement_costs(io::IO, f, types)

0 commit comments

Comments
 (0)