From 8bb973ad1bff9f8c0c78db3029e9e6b6d86e8dca Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Tue, 24 May 2022 15:26:26 -0400 Subject: [PATCH] Add notaskstate effect (#45422) Split out from #45272. This effect models the legality of moving code between tasks. It is somewhat related to effect-free/consistent, but only with respect to task-local state. As an example consider something like: ``` global glob function bar() @async (global glob = 1; some_other_code()) end ``` The newly created task is not effect-free, but it would be legal to inline the assignment of `glob` into `bar` (as long it is inlined before the creation of the task of `some_other_code` does not access `glob`). For comparison, the following is neither `notls`, nor `effect_free`: ``` function bar() @async (task_local_storage()[:var] = 1; some_other_code()) end ``` The same implies to implicit task-local state such as the RNG state. Implementation wise, there isn't a lot here, because the implicit tainting by ccall is the correct conservative default. In the future, we may want to annotate various ccalls as being permissible for notls, but let's worry about that when we have a case that needs it. --- base/compiler/abstractinterpretation.jl | 3 ++- base/compiler/ssair/show.jl | 2 ++ base/compiler/types.jl | 27 +++++++++++++++++++------ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 93c51a7f9b962..080a0e218cb1d 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1998,7 +1998,8 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), effects.effect_free ? ALWAYS_TRUE : TRISTATE_UNKNOWN, effects.nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN, effects.terminates_globally ? ALWAYS_TRUE : TRISTATE_UNKNOWN, - #=nonoverlayed=#true + #=nonoverlayed=#true, + #=notaskstate=#TRISTATE_UNKNOWN )) else tristate_merge!(sv, EFFECTS_UNKNOWN) diff --git a/base/compiler/ssair/show.jl b/base/compiler/ssair/show.jl index f4c826a45156f..9d105fc303e50 100644 --- a/base/compiler/ssair/show.jl +++ b/base/compiler/ssair/show.jl @@ -802,6 +802,8 @@ function Base.show(io::IO, e::Core.Compiler.Effects) printstyled(io, string(tristate_letter(e.nothrow), 'n'); color=tristate_color(e.nothrow)) print(io, ',') printstyled(io, string(tristate_letter(e.terminates), 't'); color=tristate_color(e.terminates)) + print(io, ',') + printstyled(io, string(tristate_letter(e.notaskstate), 's'); color=tristate_color(e.notaskstate)) print(io, ')') e.nonoverlayed || printstyled(io, '′'; color=:red) end diff --git a/base/compiler/types.jl b/base/compiler/types.jl index e594c233353d9..6a30d987d0f1f 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -45,6 +45,11 @@ The effects are composed of the following set of different properties: - `terminates::TriState`: this method is guaranteed to terminate - `nonoverlayed::Bool`: indicates that any methods that may be called within this method are not defined in an [overlayed method table](@ref OverlayMethodTable) +- `notaskstate::TriState`: this method does not access any state bound to the current + task and may thus be moved to a different task without changing observable + behavior. Note that this currently implies that `noyield` as well, since + yielding modifies the state of the current task, though this may be split + in the future. See [`Base.@assume_effects`](@ref) for more detailed explanation on the definitions of these properties. Along the abstract interpretation, `Effects` at each statement are analyzed locally and @@ -67,6 +72,7 @@ struct Effects nothrow::TriState terminates::TriState nonoverlayed::Bool + notaskstate::TriState # This effect is currently only tracked in inference and modified # :consistent before caching. We may want to track it in the future. inbounds_taints_consistency::Bool @@ -76,20 +82,22 @@ function Effects( effect_free::TriState, nothrow::TriState, terminates::TriState, - nonoverlayed::Bool) + nonoverlayed::Bool, + notaskstate::TriState) return Effects( consistent, effect_free, nothrow, terminates, nonoverlayed, + notaskstate, false) end -const EFFECTS_TOTAL = Effects(ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, true) -const EFFECTS_THROWS = Effects(ALWAYS_TRUE, ALWAYS_TRUE, TRISTATE_UNKNOWN, ALWAYS_TRUE, true) -const EFFECTS_UNKNOWN = Effects(TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, true) # mostly unknown, but it's not overlayed at least (e.g. it's not a call) -const EFFECTS_UNKNOWN′ = Effects(TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, false) # unknown, really +const EFFECTS_TOTAL = Effects(ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, true, ALWAYS_TRUE) +const EFFECTS_THROWS = Effects(ALWAYS_TRUE, ALWAYS_TRUE, TRISTATE_UNKNOWN, ALWAYS_TRUE, true, ALWAYS_TRUE) +const EFFECTS_UNKNOWN = Effects(TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, true, TRISTATE_UNKNOWN) # mostly unknown, but it's not overlayed at least (e.g. it's not a call) +const EFFECTS_UNKNOWN′ = Effects(TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, false, TRISTATE_UNKNOWN) # unknown, really function Effects(e::Effects = EFFECTS_UNKNOWN′; consistent::TriState = e.consistent, @@ -97,6 +105,7 @@ function Effects(e::Effects = EFFECTS_UNKNOWN′; nothrow::TriState = e.nothrow, terminates::TriState = e.terminates, nonoverlayed::Bool = e.nonoverlayed, + notaskstate::TriState = e.notaskstate, inbounds_taints_consistency::Bool = e.inbounds_taints_consistency) return Effects( consistent, @@ -104,6 +113,7 @@ function Effects(e::Effects = EFFECTS_UNKNOWN′; nothrow, terminates, nonoverlayed, + notaskstate, inbounds_taints_consistency) end @@ -111,6 +121,7 @@ is_consistent(effects::Effects) = effects.consistent === ALWAYS_TRUE is_effect_free(effects::Effects) = effects.effect_free === ALWAYS_TRUE is_nothrow(effects::Effects) = effects.nothrow === ALWAYS_TRUE is_terminates(effects::Effects) = effects.terminates === ALWAYS_TRUE +is_notaskstate(effects::Effects) = effects.notaskstate === ALWAYS_TRUE is_nonoverlayed(effects::Effects) = effects.nonoverlayed is_concrete_eval_eligible(effects::Effects) = @@ -132,7 +143,8 @@ function encode_effects(e::Effects) (e.effect_free.state << 2) | (e.nothrow.state << 4) | (e.terminates.state << 6) | - (UInt32(e.nonoverlayed) << 8) + (UInt32(e.nonoverlayed) << 8) | + (UInt32(e.notaskstate.state) << 9) end function decode_effects(e::UInt32) return Effects( @@ -141,6 +153,7 @@ function decode_effects(e::UInt32) TriState((e >> 4) & 0x03), TriState((e >> 6) & 0x03), _Bool( (e >> 8) & 0x01), + TriState((e >> 9) & 0x03), false) end @@ -155,6 +168,8 @@ function tristate_merge(old::Effects, new::Effects) tristate_merge( old.terminates, new.terminates), old.nonoverlayed & new.nonoverlayed, + tristate_merge( + old.notaskstate, new.notaskstate), old.inbounds_taints_consistency | new.inbounds_taints_consistency) end