Skip to content

Commit 0b1be4b

Browse files
aviatesktkf
andcommitted
effects: add reflection utility for the new effect analysis (#44785)
This commit adds new reflection utility named `Base.infer_effects` that works in the same way as `Base.return_types` but returns inferred effects instead. It would be helpful to test that certain method call has an expected effects. For example, we can now remove `Base.@pure` annotation from the definition of `BroadcastStyle(a::A, b::B) where {A<:AbstractArrayStyle{M},B<:AbstractArrayStyle{N}} where {M,N}` and checks it's still eligible for concrete evaluation like this (see <#44776 (comment)> for the context): ```julia julia> import Base.Broadcast: AbstractArrayStyle, DefaultArrayStyle, Unknown julia> function BroadcastStyle(a::A, b::B) where {A<:AbstractArrayStyle{M},B<:AbstractArrayStyle{N}} where {M,N} if Base.typename(A) === Base.typename(B) return A(Val(max(M, N))) end return Unknown() end BroadcastStyle (generic function with 1 method) julia> # test that the above definition is eligible for concrete evaluation @test Base.infer_effects(BroadcastStyle, (DefaultArrayStyle{1},DefaultArrayStyle{2},)) |> Core.Compiler.is_total_or_error Test Passed ``` Co-authored-by: Takafumi Arakaki <[email protected]>
1 parent e71e722 commit 0b1be4b

File tree

5 files changed

+88
-12
lines changed

5 files changed

+88
-12
lines changed

base/compiler/abstractinterpretation.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
4040
# aren't any in the throw block either to enable other optimizations.
4141
add_remark!(interp, sv, "Skipped call in throw block")
4242
nonoverlayed = false
43-
if isoverlayed(method_table(interp)) && sv.ipo_effects.nonoverlayed
43+
if isoverlayed(method_table(interp)) && is_nonoverlayed(sv.ipo_effects)
4444
# as we may want to concrete-evaluate this frame in cases when there are
4545
# no overlayed calls, try an additional effort now to check if this call
4646
# isn't overlayed rather than just handling it conservatively
@@ -702,7 +702,7 @@ function concrete_eval_eligible(interp::AbstractInterpreter,
702702
@nospecialize(f), result::MethodCallResult, arginfo::ArgInfo, sv::InferenceState)
703703
# disable concrete-evaluation since this function call is tainted by some overlayed
704704
# method and currently there is no direct way to execute overlayed methods
705-
isoverlayed(method_table(interp)) && !result.edge_effects.nonoverlayed && return false
705+
isoverlayed(method_table(interp)) && !is_nonoverlayed(result.edge_effects) && return false
706706
return f !== nothing &&
707707
result.edge !== nothing &&
708708
is_total_or_error(result.edge_effects) &&

base/compiler/typeinfer.jl

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -891,15 +891,24 @@ end
891891

892892
# compute an inferred AST and return type
893893
function typeinf_code(interp::AbstractInterpreter, method::Method, @nospecialize(atype), sparams::SimpleVector, run_optimizer::Bool)
894+
frame = typeinf_frame(interp, method, atype, sparams, run_optimizer)
895+
frame === nothing && return nothing, Any
896+
frame.inferred || return nothing, Any
897+
code = frame.src
898+
rt = widenconst(ignorelimited(frame.result.result))
899+
return code, rt
900+
end
901+
902+
# compute an inferred frame
903+
function typeinf_frame(interp::AbstractInterpreter, method::Method, @nospecialize(atype), sparams::SimpleVector, run_optimizer::Bool)
894904
mi = specialize_method(method, atype, sparams)::MethodInstance
895905
ccall(:jl_typeinf_begin, Cvoid, ())
896906
result = InferenceResult(mi)
897907
frame = InferenceState(result, run_optimizer ? :global : :no, interp)
898-
frame === nothing && return (nothing, Any)
908+
frame === nothing && return nothing
899909
typeinf(interp, frame)
900910
ccall(:jl_typeinf_end, Cvoid, ())
901-
frame.inferred || return (nothing, Any)
902-
return (frame.src, widenconst(ignorelimited(result.result)))
911+
return frame
903912
end
904913

905914
# compute (and cache) an inferred AST and return type

base/compiler/types.jl

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,19 +79,25 @@ function Effects(e::Effects = EFFECTS_UNKNOWN′;
7979
inbounds_taints_consistency)
8080
end
8181

82+
is_consistent(effects::Effects) = effects.consistent === ALWAYS_TRUE
83+
is_effect_free(effects::Effects) = effects.effect_free === ALWAYS_TRUE
84+
is_nothrow(effects::Effects) = effects.nothrow === ALWAYS_TRUE
85+
is_terminates(effects::Effects) = effects.terminates === ALWAYS_TRUE
86+
is_nonoverlayed(effects::Effects) = effects.nonoverlayed
87+
8288
is_total_or_error(effects::Effects) =
83-
effects.consistent === ALWAYS_TRUE &&
84-
effects.effect_free === ALWAYS_TRUE &&
85-
effects.terminates === ALWAYS_TRUE
89+
is_consistent(effects) &&
90+
is_effect_free(effects) &&
91+
is_terminates(effects)
8692

8793
is_total(effects::Effects) =
8894
is_total_or_error(effects) &&
89-
effects.nothrow === ALWAYS_TRUE
95+
is_nothrow(effects)
9096

9197
is_removable_if_unused(effects::Effects) =
92-
effects.effect_free === ALWAYS_TRUE &&
93-
effects.terminates === ALWAYS_TRUE &&
94-
effects.nothrow === ALWAYS_TRUE
98+
is_effect_free(effects) &&
99+
is_terminates(effects) &&
100+
is_nothrow(effects)
95101

96102
function encode_effects(e::Effects)
97103
return (e.consistent.state << 0) |

base/reflection.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,35 @@ function return_types(@nospecialize(f), @nospecialize(types=default_tt(f));
13061306
return rt
13071307
end
13081308

1309+
function infer_effects(@nospecialize(f), @nospecialize(types=default_tt(f));
1310+
world = get_world_counter(),
1311+
interp = Core.Compiler.NativeInterpreter(world))
1312+
ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions")
1313+
types = to_tuple_type(types)
1314+
if isa(f, Core.Builtin)
1315+
args = Any[types.parameters...]
1316+
rt = Core.Compiler.builtin_tfunction(interp, f, args, nothing)
1317+
return Core.Compiler.builtin_effects(f, args, rt)
1318+
else
1319+
effects = Core.Compiler.EFFECTS_TOTAL
1320+
matches = _methods(f, types, -1, world)::Vector
1321+
if isempty(matches)
1322+
# although this call is known to throw MethodError (thus `nothrow=ALWAYS_FALSE`),
1323+
# still mark it `TRISTATE_UNKNOWN` just in order to be consistent with a result
1324+
# derived by the effect analysis, which can't prove guaranteed throwness at this moment
1325+
return Core.Compiler.Effects(effects; nothrow=Core.Compiler.TRISTATE_UNKNOWN)
1326+
end
1327+
for match in matches
1328+
match = match::Core.MethodMatch
1329+
frame = Core.Compiler.typeinf_frame(interp,
1330+
match.method, match.spec_types, match.sparams, #=run_optimizer=#false)
1331+
frame === nothing && return Core.Compiler.Effects()
1332+
effects = Core.Compiler.tristate_merge(effects, frame.ipo_effects)
1333+
end
1334+
return effects
1335+
end
1336+
end
1337+
13091338
"""
13101339
print_statement_costs(io::IO, f, types)
13111340

test/reflection.jl

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,3 +964,35 @@ end
964964
@eval m f4(a) = return
965965
@test Base.default_tt(m.f4) == Tuple
966966
end
967+
968+
Base.@assume_effects :terminates_locally function issue41694(x::Int)
969+
res = 1
970+
1 < x < 20 || throw("bad")
971+
while x > 1
972+
res *= x
973+
x -= 1
974+
end
975+
return res
976+
end
977+
maybe_effectful(x::Int) = 42
978+
maybe_effectful(x::Any) = unknown_operation()
979+
function f_no_methods end
980+
981+
@testset "infer_effects" begin
982+
@test Base.infer_effects(issue41694, (Int,)) |> Core.Compiler.is_terminates
983+
@test Base.infer_effects((Int,)) do x
984+
issue41694(x)
985+
end |> Core.Compiler.is_terminates
986+
@test Base.infer_effects(issue41694) |> Core.Compiler.is_terminates # use `default_tt`
987+
let effects = Base.infer_effects(maybe_effectful, (Any,)) # union split
988+
@test !Core.Compiler.is_consistent(effects)
989+
@test !Core.Compiler.is_effect_free(effects)
990+
@test !Core.Compiler.is_nothrow(effects)
991+
@test !Core.Compiler.is_terminates(effects)
992+
@test !Core.Compiler.is_nonoverlayed(effects)
993+
end
994+
@test Base.infer_effects(f_no_methods) |> !Core.Compiler.is_nothrow
995+
# builtins
996+
@test Base.infer_effects(typeof, (Any,)) |> Core.Compiler.is_total
997+
@test Base.infer_effects(===, (Any,Any)) |> Core.Compiler.is_total
998+
end

0 commit comments

Comments
 (0)