Skip to content

Experiment with the ipo effects on LLVM #47844

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
elseif isa(matches, MethodMatches) ? (!matches.fullmatch || any_ambig(matches)) :
(!all(matches.fullmatches) || any_ambig(matches))
# Account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature.
all_effects = Effects(all_effects; nothrow=false)
all_effects = Effects(all_effects; nothrow=ALWAYS_FALSE)
end

rettype = from_interprocedural!(𝕃ₚ, rettype, sv, arginfo, conditionals)
Expand Down Expand Up @@ -1022,7 +1022,7 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter,
rt, nothrow = ir_abstract_constant_propagation(interp, irsv)
@assert !(rt isa Conditional || rt isa MustAlias) "invalid lattice element returned from IR interpretation"
if !isa(rt, Type) || typeintersect(rt, Bool) === Union{}
new_effects = Effects(result.effects; nothrow=nothrow)
new_effects = Effects(result.effects; nothrow= nothrow ? ALWAYS_TRUE : ALWAYS_FALSE)
return ConstCallResults(rt, SemiConcreteResult(mi, ir, new_effects), new_effects, mi)
end
end
Expand Down Expand Up @@ -1863,7 +1863,7 @@ function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{An
body = a3.parameters[1]
canconst = false
else
return CallMeta(Any, Effects(EFFECTS_TOTAL; nothrow), NoCallInfo())
return CallMeta(Any, Effects(EFFECTS_TOTAL; nothrow = nothrow ? ALWAYS_TRUE : ALWAYS_FALSE), NoCallInfo())
end
if !(isa(body, Type) || isa(body, TypeVar))
return CallMeta(Any, EFFECTS_THROWS, NoCallInfo())
Expand All @@ -1881,7 +1881,7 @@ function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{An
body = UnionAll(tv, body)
end
ret = canconst ? Const(body) : Type{body}
return CallMeta(ret, Effects(EFFECTS_TOTAL; nothrow), NoCallInfo())
return CallMeta(ret, Effects(EFFECTS_TOTAL; nothrow = nothrow ? ALWAYS_TRUE : ALWAYS_FALSE), NoCallInfo())
end
return CallMeta(Any, EFFECTS_UNKNOWN, NoCallInfo())
end
Expand Down Expand Up @@ -2085,7 +2085,7 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter,
(aty, rty) = (unwrap_unionall(ftt)::DataType).parameters
rty = rewrap_unionall(rty isa TypeVar ? rty.lb : rty, ftt)
if !(rt ⊑ₚ rty && tuple_tfunc(𝕃ₚ, arginfo.argtypes[2:end]) ⊑ₚ rewrap_unionall(aty, ftt))
effects = Effects(effects; nothrow=false)
effects = Effects(effects; nothrow=ALWAYS_FALSE)
end
end
rt = from_interprocedural!(𝕃ₚ, rt, sv, arginfo, match.spec_types)
Expand Down Expand Up @@ -2219,7 +2219,7 @@ function abstract_eval_special_value(interp::AbstractInterpreter, @nospecialize(
elseif isa(e, SlotNumber)
vtyp = vtypes[slot_id(e)]
if vtyp.undef
merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; nothrow=false))
merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; nothrow=ALWAYS_FALSE))
end
return vtyp.typ
elseif isa(e, Argument)
Expand Down Expand Up @@ -2353,7 +2353,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp
t = refine_partial_type(t)
end
end
effects = Effects(EFFECTS_TOTAL; consistent, nothrow)
effects = Effects(EFFECTS_TOTAL; consistent, nothrow = nothrow ? ALWAYS_TRUE : ALWAYS_FALSE)
elseif ehead === :splatnew
t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv))
nothrow = false # TODO: More precision
Expand All @@ -2373,7 +2373,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp
t = refine_partial_type(t)
end
consistent = !ismutabletype(t) ? ALWAYS_TRUE : CONSISTENT_IF_NOTRETURNED
effects = Effects(EFFECTS_TOTAL; consistent, nothrow)
effects = Effects(EFFECTS_TOTAL; consistent, nothrow = nothrow ? ALWAYS_TRUE : ALWAYS_FALSE)
elseif ehead === :new_opaque_closure
t = Union{}
effects = Effects() # TODO
Expand Down Expand Up @@ -2500,7 +2500,7 @@ function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, vtypes:
effects = Effects(
override.consistent ? ALWAYS_TRUE : effects.consistent,
override.effect_free ? ALWAYS_TRUE : effects.effect_free,
override.nothrow ? true : effects.nothrow,
override.nothrow ? ALWAYS_TRUE : effects.nothrow,
override.terminates_globally ? true : effects.terminates,
override.notaskstate ? true : effects.notaskstate,
override.inaccessiblememonly ? ALWAYS_TRUE : effects.inaccessiblememonly,
Expand Down Expand Up @@ -2589,13 +2589,14 @@ function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, fram
consistent = inaccessiblememonly = ALWAYS_TRUE
rt = Union{}
end
nothrow = nothrow ? ALWAYS_TRUE : ALWAYS_FALSE
merge_effects!(interp, frame, Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly))
return rt
end

function handle_global_assignment!(interp::AbstractInterpreter, frame::InferenceState, lhs::GlobalRef, @nospecialize(newty))
effect_free = ALWAYS_FALSE
nothrow = global_assignment_nothrow(lhs.mod, lhs.name, newty)
nothrow = global_assignment_nothrow(lhs.mod, lhs.name, newty) ? ALWAYS_TRUE : ALWAYS_FALSE
inaccessiblememonly = ALWAYS_FALSE
merge_effects!(interp, frame, Effects(EFFECTS_TOTAL; effect_free, nothrow, inaccessiblememonly))
return nothing
Expand Down
49 changes: 30 additions & 19 deletions base/compiler/effects.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,15 @@ following meanings:
will not be refined anyway.
* `EFFECT_FREE_IF_INACCESSIBLEMEMONLY`: the `:effect-free`-ness of this method can later be
refined to `ALWAYS_TRUE` in a case when `:inaccessiblememonly` is proven.
- `nothrow::Bool`: this method is guaranteed to not throw an exception.
- `nothrow::UInt8`:
* `ALWAYS_TRUE`: this method is guaranteed to not throw any exception
* `ALWAYS_FALSE`: this method may throw an exception, and there is
no need for further analysis with respect to this effect property as this conclusion
will not be refined anyway.
* `NOTHROW_NON_STRICT`: this method is guaranteed to not raise an exception, but may still
throw an exception locally that is guaranteed to be caught within the frame of this call.
On the other hand, `ALWAYS_TRUE` is guaranteed to not throw any exeption even locall.
The separation between `ALWAYS_TRUE` and `NOTHROW_NON_STRICT` is mostly for LLVM use case.
- `terminates::Bool`: this method is guaranteed to terminate.
- `notaskstate::Bool`: this method does not access any state bound to the current
task and may thus be moved to a different task without changing observable
Expand Down Expand Up @@ -58,7 +66,7 @@ analysis on each statement usually taint the global conclusion conservatively.
struct Effects
consistent::UInt8
effect_free::UInt8
nothrow::Bool
nothrow::UInt8
terminates::Bool
notaskstate::Bool
inaccessiblememonly::UInt8
Expand All @@ -67,7 +75,7 @@ struct Effects
function Effects(
consistent::UInt8,
effect_free::UInt8,
nothrow::Bool,
nothrow::UInt8,
terminates::Bool,
notaskstate::Bool,
inaccessiblememonly::UInt8,
Expand Down Expand Up @@ -95,18 +103,21 @@ const CONSISTENT_IF_INACCESSIBLEMEMONLY = 0x01 << 2
# :effect_free-ness bits
const EFFECT_FREE_IF_INACCESSIBLEMEMONLY = 0x01 << 1

# :nothrow bits
const NOTHROW_NON_STRICT = 0x01 << 1

# :inaccessiblememonly bits
const INACCESSIBLEMEM_OR_ARGMEMONLY = 0x01 << 1

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

function Effects(e::Effects = EFFECTS_UNKNOWN′;
consistent::UInt8 = e.consistent,
effect_free::UInt8 = e.effect_free,
nothrow::Bool = e.nothrow,
nothrow::UInt8 = e.nothrow,
terminates::Bool = e.terminates,
notaskstate::Bool = e.notaskstate,
inaccessiblememonly::UInt8 = e.inaccessiblememonly,
Expand Down Expand Up @@ -145,7 +156,7 @@ merge_effectbits(old::Bool, new::Bool) = old & new

is_consistent(effects::Effects) = effects.consistent === ALWAYS_TRUE
is_effect_free(effects::Effects) = effects.effect_free === ALWAYS_TRUE
is_nothrow(effects::Effects) = effects.nothrow
is_nothrow(effects::Effects) = effects.nothrow !== ALWAYS_FALSE
is_terminates(effects::Effects) = effects.terminates
is_notaskstate(effects::Effects) = effects.notaskstate
is_inaccessiblememonly(effects::Effects) = effects.inaccessiblememonly === ALWAYS_TRUE
Expand Down Expand Up @@ -181,23 +192,23 @@ function encode_effects(e::Effects)
return ((e.consistent % UInt32) << 0) |
((e.effect_free % UInt32) << 3) |
((e.nothrow % UInt32) << 5) |
((e.terminates % UInt32) << 6) |
((e.notaskstate % UInt32) << 7) |
((e.inaccessiblememonly % UInt32) << 8) |
((e.nonoverlayed % UInt32) << 10)|
((e.noinbounds % UInt32) << 11)
((e.terminates % UInt32) << 7) |
((e.notaskstate % UInt32) << 8) |
((e.inaccessiblememonly % UInt32) << 9) |
((e.nonoverlayed % UInt32) << 11)|
((e.noinbounds % UInt32) << 12)
end

function decode_effects(e::UInt32)
return Effects(
UInt8((e >> 0) & 0x07),
UInt8((e >> 3) & 0x03),
_Bool((e >> 5) & 0x01),
_Bool((e >> 6) & 0x01),
UInt8((e >> 5) & 0x03),
_Bool((e >> 7) & 0x01),
UInt8((e >> 8) & 0x03),
_Bool((e >> 10) & 0x01),
_Bool((e >> 11) & 0x01))
_Bool((e >> 8) & 0x01),
UInt8((e >> 9) & 0x03),
_Bool((e >> 11) & 0x01),
_Bool((e >> 12) & 0x01))
end

struct EffectsOverride
Expand Down
2 changes: 1 addition & 1 deletion base/compiler/ssair/inlining.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1388,7 +1388,7 @@ function compute_inlining_cases(@nospecialize(info::CallInfo), flag::UInt8, sig:
end
end

joint_effects = Effects(joint_effects; nothrow)
joint_effects = Effects(joint_effects; nothrow = nothrow ? ALWAYS_TRUE : ALWAYS_FALSE)

if handled_all_cases && revisit_idx !== nothing
# we handled everything except one match with unmatched sparams,
Expand Down
18 changes: 13 additions & 5 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2183,7 +2183,8 @@ function isdefined_effects(𝕃::AbstractLattice, argtypes::Vector{Any})
na == 0 && return EFFECTS_THROWS
obj = argtypes[1]
consistent = is_immutable_argtype(unwrapva(obj)) ? ALWAYS_TRUE : ALWAYS_FALSE
nothrow = !isvarargtype(argtypes[end]) && na == 2 && isdefined_nothrow(𝕃, obj, argtypes[2])
nothrow = !isvarargtype(argtypes[end]) && na == 2 && isdefined_nothrow(𝕃, obj, argtypes[2]) ?
ALWAYS_TRUE : ALWAYS_FALSE
return Effects(EFFECTS_TOTAL; consistent, nothrow)
end

Expand Down Expand Up @@ -2218,6 +2219,7 @@ function getfield_effects(arginfo::ArgInfo, @nospecialize(rt))
consistent = ALWAYS_FALSE
end
end
nothrow = nothrow ? ALWAYS_TRUE : ALWAYS_FALSE
if hasintersect(widenconst(obj), Module)
inaccessiblememonly = getglobal_effects(argtypes[2:end], rt).inaccessiblememonly
elseif is_mutation_free_argtype(obj)
Expand Down Expand Up @@ -2245,6 +2247,7 @@ function getglobal_effects(argtypes::Vector{Any}, @nospecialize(rt))
end
end
end
nothrow = nothrow ? ALWAYS_TRUE : ALWAYS_FALSE
return Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly)
end

Expand Down Expand Up @@ -2278,7 +2281,11 @@ function builtin_effects(𝕃::AbstractLattice, @nospecialize(f::Builtin), argin
else
effect_free = ALWAYS_FALSE
end
nothrow = (!(!isempty(argtypes) && isvarargtype(argtypes[end])) && builtin_nothrow(𝕃, f, argtypes, rt))
if (!(!isempty(argtypes) && isvarargtype(argtypes[end])) && builtin_nothrow(𝕃, f, argtypes, rt))
nothrow = ALWAYS_TRUE
else
nothrow = ALWAYS_FALSE
end
if contains_is(_INACCESSIBLEMEM_BUILTINS, f)
inaccessiblememonly = ALWAYS_TRUE
elseif contains_is(_ARGMEM_BUILTINS, f)
Expand Down Expand Up @@ -2460,7 +2467,8 @@ function intrinsic_effects(f::IntrinsicFunction, argtypes::Vector{Any})

consistent = contains_is(_INCONSISTENT_INTRINSICS, f) ? ALWAYS_FALSE : ALWAYS_TRUE
effect_free = !(f === Intrinsics.pointerset) ? ALWAYS_TRUE : ALWAYS_FALSE
nothrow = (!(!isempty(argtypes) && isvarargtype(argtypes[end])) && intrinsic_nothrow(f, argtypes))
nothrow = (!(!isempty(argtypes) && isvarargtype(argtypes[end])) && intrinsic_nothrow(f, argtypes)) ?
ALWAYS_TRUE : ALWAYS_FALSE

return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow)
end
Expand Down Expand Up @@ -2657,7 +2665,7 @@ function alloc_array_ndims(name::Symbol)
end

function alloc_array_effects(@specialize(abstract_eval), args::Vector{Any}, ndims::Int)
nothrow = alloc_array_nothrow(abstract_eval, args, ndims)
nothrow = alloc_array_nothrow(abstract_eval, args, ndims) ? ALWAYS_TRUE : ALWAYS_FALSE
return Effects(EFFECTS_TOTAL; consistent=CONSISTENT_IF_NOTRETURNED, nothrow)
end

Expand All @@ -2676,7 +2684,7 @@ function alloc_array_nothrow(@specialize(abstract_eval), args::Vector{Any}, ndim
end

function new_array_effects(@specialize(abstract_eval), args::Vector{Any})
nothrow = new_array_nothrow(abstract_eval, args)
nothrow = new_array_nothrow(abstract_eval, args) ? ALWAYS_TRUE : ALWAYS_FALSE
return Effects(EFFECTS_TOTAL; consistent=CONSISTENT_IF_NOTRETURNED, nothrow)
end

Expand Down
2 changes: 1 addition & 1 deletion base/compiler/typeinfer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ function adjust_effects(sv::InferenceState)
ipo_effects = Effects(ipo_effects; effect_free=ALWAYS_TRUE)
end
if is_effect_overridden(override, :nothrow)
ipo_effects = Effects(ipo_effects; nothrow=true)
ipo_effects = Effects(ipo_effects; nothrow=ALWAYS_TRUE)
end
if is_effect_overridden(override, :terminates_globally)
ipo_effects = Effects(ipo_effects; terminates=true)
Expand Down
2 changes: 1 addition & 1 deletion base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1511,7 +1511,7 @@ function infer_effects(@nospecialize(f), @nospecialize(types=default_tt(f));
effects = Core.Compiler.EFFECTS_TOTAL
if matches.ambig || !any(match::Core.MethodMatch->match.fully_covers, matches.matches)
# account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature.
effects = Core.Compiler.Effects(effects; nothrow=false)
effects = Core.Compiler.Effects(effects; nothrow=Core.Compiler.ALWAYS_FALSE)
end
for match in matches.matches
match = match::Core.MethodMatch
Expand Down
Loading