Skip to content

Commit

Permalink
effects: NFC refactor on effect-related code (JuliaLang#46034)
Browse files Browse the repository at this point in the history
This refactoring would make it clearer how to add new effect in
the future. These changes also improve the robustness of
`Base.infer_effects` and now it doesn't throw on empty input types.
  • Loading branch information
aviatesk committed Jul 15, 2022
1 parent d7f01ff commit 13f6537
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 91 deletions.
31 changes: 14 additions & 17 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1678,7 +1678,8 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f),
return abstract_finalizer(interp, argtypes, sv)
end
rt = abstract_call_builtin(interp, f, arginfo, sv, max_methods)
return CallMeta(rt, builtin_effects(f, argtypes, rt), false)
effects = builtin_effects(f, argtypes[2:end], rt)
return CallMeta(rt, effects, false)
elseif isa(f, Core.OpaqueClosure)
# calling an OpaqueClosure about which we have no information returns no information
return CallMeta(Any, Effects(), false)
Expand Down Expand Up @@ -2059,21 +2060,19 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
@goto t_computed
end
end
effects = EFFECTS_UNKNOWN
cconv = e.args[5]
if isa(cconv, QuoteNode) && (v = cconv.value; isa(v, Tuple{Symbol, UInt8}))
effects = v[2]
effects = decode_effects_override(effects)
tristate_merge!(sv, Effects(
effects.consistent ? ALWAYS_TRUE : ALWAYS_FALSE,
effects.effect_free ? ALWAYS_TRUE : ALWAYS_FALSE,
effects.nothrow ? ALWAYS_TRUE : ALWAYS_FALSE,
effects.terminates_globally ? ALWAYS_TRUE : ALWAYS_FALSE,
#=nonoverlayed=#true,
effects.notaskstate ? ALWAYS_TRUE : ALWAYS_FALSE
))
else
tristate_merge!(sv, EFFECTS_UNKNOWN)
end
override = decode_effects_override(v[2])
effects = Effects(
override.consistent ? ALWAYS_TRUE : effects.consistent,
override.effect_free ? ALWAYS_TRUE : effects.effect_free,
override.nothrow ? ALWAYS_TRUE : effects.nothrow,
override.terminates_globally ? ALWAYS_TRUE : effects.terminates_globally,
effects.nonoverlayed ? true : false,
override.notaskstate ? ALWAYS_TRUE : effects.notaskstate)
end
tristate_merge!(sv, effects)
elseif ehead === :cfunction
tristate_merge!(sv, EFFECTS_UNKNOWN)
t = e.args[1]
Expand Down Expand Up @@ -2337,9 +2336,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState)
@assert !frame.inferred
frame.dont_work_on_me = true # mark that this function is currently on the stack
W = frame.ip
def = frame.linfo.def
isva = isa(def, Method) && def.isva
nargs = length(frame.result.argtypes) - isva
nargs = narguments(frame)
slottypes = frame.slottypes
ssavaluetypes = frame.ssavaluetypes
bbs = frame.cfg.blocks
Expand Down
75 changes: 38 additions & 37 deletions base/compiler/effects.jl
Original file line number Diff line number Diff line change
Expand Up @@ -57,27 +57,28 @@ struct Effects
# 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
end
function Effects(
consistent::TriState,
effect_free::TriState,
nothrow::TriState,
terminates::TriState,
nonoverlayed::Bool,
notaskstate::TriState)
return Effects(
consistent,
effect_free,
nothrow,
terminates,
nonoverlayed,
notaskstate,
false)
function Effects(
consistent::TriState,
effect_free::TriState,
nothrow::TriState,
terminates::TriState,
nonoverlayed::Bool,
notaskstate::TriState,
inbounds_taints_consistency::Bool = false)
return new(
consistent,
effect_free,
nothrow,
terminates,
nonoverlayed,
notaskstate,
inbounds_taints_consistency)
end
end

const EFFECTS_TOTAL = Effects(ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, true, ALWAYS_TRUE)
const EFFECTS_THROWS = Effects(ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_FALSE, ALWAYS_TRUE, true, ALWAYS_TRUE)
const EFFECTS_UNKNOWN = Effects(ALWAYS_FALSE, ALWAYS_FALSE, ALWAYS_FALSE, ALWAYS_FALSE, true, ALWAYS_FALSE) # mostly unknown, but it's not overlayed at least (e.g. it's not a call)
const EFFECTS_UNKNOWN = Effects(ALWAYS_FALSE, ALWAYS_FALSE, ALWAYS_FALSE, ALWAYS_FALSE, true, ALWAYS_FALSE) # mostly unknown, but it's not overlayed at least (e.g. it's not a call)
const EFFECTS_UNKNOWN′ = Effects(ALWAYS_FALSE, ALWAYS_FALSE, ALWAYS_FALSE, ALWAYS_FALSE, false, ALWAYS_FALSE) # unknown, really

function Effects(e::Effects = EFFECTS_UNKNOWN′;
Expand Down Expand Up @@ -121,22 +122,22 @@ is_removable_if_unused(effects::Effects) =
is_nothrow(effects)

function encode_effects(e::Effects)
return (e.consistent.state << 0) |
(e.effect_free.state << 2) |
(e.nothrow.state << 4) |
(e.terminates.state << 6) |
(UInt32(e.nonoverlayed) << 8) |
(UInt32(e.notaskstate.state) << 9)
return ((e.consistent.state) << 0) |
((e.effect_free.state) << 2) |
((e.nothrow.state) << 4) |
((e.terminates.state) << 6) |
((e.nonoverlayed % UInt32) << 8) |
((e.notaskstate.state % UInt32) << 9)
end

function decode_effects(e::UInt32)
return Effects(
TriState((e >> 0) & 0x03),
TriState((e >> 2) & 0x03),
TriState((e >> 4) & 0x03),
TriState((e >> 6) & 0x03),
_Bool( (e >> 8) & 0x01),
TriState((e >> 9) & 0x03),
false)
TriState((e >> 9) & 0x03))
end

function tristate_merge(old::Effects, new::Effects)
Expand Down Expand Up @@ -166,21 +167,21 @@ end

function encode_effects_override(eo::EffectsOverride)
e = 0x00
eo.consistent && (e |= 0x01)
eo.effect_free && (e |= 0x02)
eo.nothrow && (e |= 0x04)
eo.terminates_globally && (e |= 0x08)
eo.terminates_locally && (e |= 0x10)
eo.notaskstate && (e |= 0x20)
eo.consistent && (e |= (0x01 << 0))
eo.effect_free && (e |= (0x01 << 1))
eo.nothrow && (e |= (0x01 << 2))
eo.terminates_globally && (e |= (0x01 << 3))
eo.terminates_locally && (e |= (0x01 << 4))
eo.notaskstate && (e |= (0x01 << 5))
return e
end

function decode_effects_override(e::UInt8)
return EffectsOverride(
(e & 0x01) != 0x00,
(e & 0x02) != 0x00,
(e & 0x04) != 0x00,
(e & 0x08) != 0x00,
(e & 0x10) != 0x00,
(e & 0x20) != 0x00)
(e & (0x01 << 0)) != 0x00,
(e & (0x01 << 1)) != 0x00,
(e & (0x01 << 2)) != 0x00,
(e & (0x01 << 3)) != 0x00,
(e & (0x01 << 4)) != 0x00,
(e & (0x01 << 5)) != 0x00)
end
7 changes: 7 additions & 0 deletions base/compiler/inferencestate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -521,3 +521,10 @@ function print_callstack(sv::InferenceState)
end

get_curr_ssaflag(sv::InferenceState) = sv.src.ssaflags[sv.currpc]

function narguments(sv::InferenceState)
def = sv.linfo.def
isva = isa(def, Method) && def.isva
nargs = length(sv.result.argtypes) - isva
return nargs
end
29 changes: 14 additions & 15 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1798,45 +1798,44 @@ const _SPECIAL_BUILTINS = Any[
Core._apply_iterate
]

function builtin_effects(f::Builtin, argtypes::Vector{Any}, @nospecialize rt)
function builtin_effects(f::Builtin, argtypes::Vector{Any}, @nospecialize(rt))
if isa(f, IntrinsicFunction)
return intrinsic_effects(f, argtypes)
end

@assert !contains_is(_SPECIAL_BUILTINS, f)

argtypes′ = argtypes[2:end]
if (f === Core.getfield || f === Core.isdefined) && length(argtypes) >= 3
if (f === Core.getfield || f === Core.isdefined) && length(argtypes) >= 2
# consistent if the argtype is immutable
if isvarargtype(argtypes[2])
if isvarargtype(argtypes[1])
return Effects(; effect_free=ALWAYS_TRUE, terminates=ALWAYS_TRUE, nonoverlayed=true)
end
s = widenconst(argtypes[2])
s = widenconst(argtypes[1])
if isType(s) || !isa(s, DataType) || isabstracttype(s)
return Effects(; effect_free=ALWAYS_TRUE, terminates=ALWAYS_TRUE, nonoverlayed=true)
end
s = s::DataType
consistent = !ismutabletype(s) ? ALWAYS_TRUE : ALWAYS_FALSE
if f === Core.getfield && !isvarargtype(argtypes[end]) && getfield_boundscheck(argtypes) !== true
if f === Core.getfield && !isvarargtype(argtypes[end]) && getfield_boundscheck(argtypes) !== true
# If we cannot independently prove inboundsness, taint consistency.
# The inbounds-ness assertion requires dynamic reachability, while
# :consistent needs to be true for all input values.
# N.B. We do not taint for `--check-bounds=no` here -that happens in
# InferenceState.
if getfield_nothrow(argtypes[2], argtypes[3], true)
if getfield_nothrow(argtypes[1], argtypes[2], true)
nothrow = ALWAYS_TRUE
else
consistent = nothrow = ALWAYS_FALSE
end
else
nothrow = (!isvarargtype(argtypes[end]) && builtin_nothrow(f, argtypes, rt)) ?
nothrow = (!isvarargtype(argtypes[end]) && builtin_nothrow(f, argtypes, rt)) ?
ALWAYS_TRUE : ALWAYS_FALSE
end
effect_free = ALWAYS_TRUE
elseif f === getglobal && length(argtypes) >= 3
if getglobal_nothrow(argtypes)
elseif f === getglobal
if getglobal_nothrow(argtypes)
consistent = isconst( # types are already checked in `getglobal_nothrow`
(argtypes[2]::Const).val::Module, (argtypes[3]::Const).val::Symbol) ?
(argtypes[1]::Const).val::Module, (argtypes[2]::Const).val::Symbol) ?
ALWAYS_TRUE : ALWAYS_FALSE
nothrow = ALWAYS_TRUE
else
Expand All @@ -1847,20 +1846,20 @@ function builtin_effects(f::Builtin, argtypes::Vector{Any}, @nospecialize rt)
consistent = contains_is(_CONSISTENT_BUILTINS, f) ? ALWAYS_TRUE : ALWAYS_FALSE
effect_free = (contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f)) ?
ALWAYS_TRUE : ALWAYS_FALSE
nothrow = (!isvarargtype(argtypes[end]) && builtin_nothrow(f, argtypes, rt)) ?
nothrow = (!(!isempty(argtypes) && isvarargtype(argtypes[end])) && builtin_nothrow(f, argtypes, rt)) ?
ALWAYS_TRUE : ALWAYS_FALSE
end

return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow)
end

function builtin_nothrow(@nospecialize(f), argtypes::Array{Any, 1}, @nospecialize(rt))
function builtin_nothrow(@nospecialize(f), argtypes::Vector{Any}, @nospecialize(rt))
rt === Bottom && return false
contains_is(_PURE_BUILTINS, f) && return true
return _builtin_nothrow(f, argtypes, rt)
end

function builtin_tfunction(interp::AbstractInterpreter, @nospecialize(f), argtypes::Array{Any,1},
function builtin_tfunction(interp::AbstractInterpreter, @nospecialize(f), argtypes::Vector{Any},
sv::Union{InferenceState,Nothing})
if f === tuple
return tuple_tfunc(argtypes)
Expand Down Expand Up @@ -2025,7 +2024,7 @@ function intrinsic_effects(f::IntrinsicFunction, argtypes::Vector{Any})
f === Intrinsics.cglobal # cglobal lookup answer changes at runtime
) ? ALWAYS_TRUE : ALWAYS_FALSE
effect_free = !(f === Intrinsics.pointerset) ? ALWAYS_TRUE : ALWAYS_FALSE
nothrow = (!isvarargtype(argtypes[end]) && intrinsic_nothrow(f, argtypes[2:end])) ?
nothrow = (!(!isempty(argtypes) && isvarargtype(argtypes[end])) && intrinsic_nothrow(f, argtypes)) ?
ALWAYS_TRUE : ALWAYS_FALSE

return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow)
Expand Down
6 changes: 3 additions & 3 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1416,9 +1416,9 @@ function infer_effects(@nospecialize(f), @nospecialize(types=default_tt(f));
ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions")
types = to_tuple_type(types)
if isa(f, Core.Builtin)
args = Any[types.parameters...]
rt = Core.Compiler.builtin_tfunction(interp, f, args, nothing)
return Core.Compiler.builtin_effects(f, args, rt)
argtypes = Any[types.parameters...]
rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing)
return Core.Compiler.builtin_effects(f, argtypes, rt)
else
effects = Core.Compiler.EFFECTS_TOTAL
matches = _methods(f, types, -1, world)::Vector
Expand Down
38 changes: 20 additions & 18 deletions src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,19 +234,19 @@ typedef struct _jl_line_info_node_t {
intptr_t inlined_at;
} jl_line_info_node_t;

// the following mirrors `struct EffectsOverride` in `base/compiler/types.jl`
// the following mirrors `struct EffectsOverride` in `base/compiler/effects.jl`
typedef union __jl_purity_overrides_t {
struct {
uint8_t ipo_consistent : 1;
uint8_t ipo_effect_free : 1;
uint8_t ipo_nothrow : 1;
uint8_t ipo_terminates : 1;
uint8_t ipo_consistent : 1;
uint8_t ipo_effect_free : 1;
uint8_t ipo_nothrow : 1;
uint8_t ipo_terminates_globally : 1;
// Weaker form of `terminates` that asserts
// that any control flow syntactically in the method
// is guaranteed to terminate, but does not make
// assertions about any called functions.
uint8_t ipo_terminates_locally : 1;
uint8_t ipo_notaskstate : 1;
uint8_t ipo_terminates_locally : 1;
uint8_t ipo_notaskstate : 1;
} overrides;
uint8_t bits;
} _jl_purity_overrides_t;
Expand Down Expand Up @@ -397,25 +397,27 @@ typedef struct _jl_code_instance_t {

// purity results
#ifdef JL_USE_ANON_UNIONS_FOR_PURITY_FLAGS
// see also encode_effects() and decode_effects() in `base/compiler/types.jl`,
// see also encode_effects() and decode_effects() in `base/compiler/effects.jl`,
union {
uint32_t ipo_purity_bits;
struct {
uint8_t ipo_consistent:2;
uint8_t ipo_effect_free:2;
uint8_t ipo_nothrow:2;
uint8_t ipo_terminates:2;
uint8_t ipo_nonoverlayed:1;
uint8_t ipo_consistent : 2;
uint8_t ipo_effect_free : 2;
uint8_t ipo_nothrow : 2;
uint8_t ipo_terminates : 2;
uint8_t ipo_nonoverlayed : 1;
uint8_t ipo_notaskstate : 2;
} ipo_purity_flags;
};
union {
uint32_t purity_bits;
struct {
uint8_t consistent:2;
uint8_t effect_free:2;
uint8_t nothrow:2;
uint8_t terminates:2;
uint8_t nonoverlayed:1;
uint8_t consistent : 2;
uint8_t effect_free : 2;
uint8_t nothrow : 2;
uint8_t terminates : 2;
uint8_t nonoverlayed : 1;
uint8_t notaskstate : 2;
} purity_flags;
};
#else
Expand Down
2 changes: 1 addition & 1 deletion src/method.c
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir)
li->purity.overrides.ipo_consistent = jl_unbox_bool(jl_exprarg(ma, 0));
li->purity.overrides.ipo_effect_free = jl_unbox_bool(jl_exprarg(ma, 1));
li->purity.overrides.ipo_nothrow = jl_unbox_bool(jl_exprarg(ma, 2));
li->purity.overrides.ipo_terminates = jl_unbox_bool(jl_exprarg(ma, 3));
li->purity.overrides.ipo_terminates_globally = jl_unbox_bool(jl_exprarg(ma, 3));
li->purity.overrides.ipo_terminates_locally = jl_unbox_bool(jl_exprarg(ma, 4));
li->purity.overrides.ipo_notaskstate = jl_unbox_bool(jl_exprarg(ma, 5));
}
Expand Down
2 changes: 2 additions & 0 deletions test/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -995,4 +995,6 @@ function f_no_methods end
# builtins
@test Base.infer_effects(typeof, (Any,)) |> Core.Compiler.is_total
@test Base.infer_effects(===, (Any,Any)) |> Core.Compiler.is_total
@test (Base.infer_effects(setfield!, ()); true) # `builtin_effects` shouldn't throw on empty `argtypes`
@test (Base.infer_effects(Core.Intrinsics.arraylen, ()); true) # `intrinsic_effects` shouldn't throw on empty `argtypes`
end

0 comments on commit 13f6537

Please sign in to comment.