Skip to content

Commit

Permalink
inference: remove throw block deoptimization completely (JuliaLang#…
Browse files Browse the repository at this point in the history
…49260)

Co-authored-by: Cody Tapscott <[email protected]>
Co-authored-by: Oscar Smith <[email protected]>
  • Loading branch information
3 people authored and lazarusA committed Aug 17, 2024
1 parent 9bcf3ef commit cf951f7
Show file tree
Hide file tree
Showing 10 changed files with 33 additions and 96 deletions.
9 changes: 0 additions & 9 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,6 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
sv::AbsIntState, max_methods::Int)
𝕃ₚ, 𝕃ᵢ = ipo_lattice(interp), typeinf_lattice(interp)
ₚ, ₚ, = partialorder(𝕃ₚ), join(𝕃ₚ), join(𝕃ᵢ)
if !should_infer_this_call(interp, sv)
add_remark!(interp, sv, "Skipped call in throw block")
# At this point we are guaranteed to end up throwing on this path,
# which is all that's required for :consistent-cy. Of course, we don't
# know anything else about this statement.
effects = Effects(; consistent=ALWAYS_TRUE)
return CallMeta(Any, Any, effects, NoCallInfo())
end

argtypes = arginfo.argtypes
matches = find_method_matches(interp, argtypes, atype; max_methods)
if isa(matches, FailedMethodMatch)
Expand Down
3 changes: 1 addition & 2 deletions base/compiler/compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,7 @@ baremodule BuildSettings
using Core: ARGS, include
using Core.Compiler: >, getindex, length

MAX_METHODS::Int = 3
UNOPTIMIZE_THROW_BLOCKS::Bool = true
global MAX_METHODS::Int = 3

if length(ARGS) > 2 && ARGS[2] === "--buildsettings"
include(BuildSettings, ARGS[3])
Expand Down
25 changes: 0 additions & 25 deletions base/compiler/inferencestate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,6 @@ mutable struct InferenceState
restrict_abstract_call_sites = isa(def, Module)

# some more setups
InferenceParams(interp).unoptimize_throw_blocks && mark_throw_blocks!(src, handler_info)
!iszero(cache_mode & CACHE_MODE_LOCAL) && push!(get_inference_cache(interp), result)

this = new(
Expand Down Expand Up @@ -1102,30 +1101,6 @@ bail_out_apply(::AbstractInterpreter, state::InferenceLoopState, ::InferenceStat
bail_out_apply(::AbstractInterpreter, state::InferenceLoopState, ::IRInterpretationState) =
state.rt === Any

function should_infer_this_call(interp::AbstractInterpreter, sv::InferenceState)
if InferenceParams(interp).unoptimize_throw_blocks
# Disable inference of calls in throw blocks, since we're unlikely to
# need their types. There is one exception however: If up until now, the
# function has not seen any side effects, we would like to make sure there
# aren't any in the throw block either to enable other optimizations.
if is_stmt_throw_block(get_curr_ssaflag(sv))
should_infer_for_effects(sv) || return false
end
end
return true
end
function should_infer_for_effects(sv::InferenceState)
def = sv.linfo.def
def isa Method || return false # toplevel frame will not be [semi-]concrete-evaluated
effects = sv.ipo_effects
override = decode_effects_override(def.purity)
effects.consistent === ALWAYS_FALSE && !is_effect_overridden(override, :consistent) && return false
effects.effect_free === ALWAYS_FALSE && !is_effect_overridden(override, :effect_free) && return false
!effects.terminates && !is_effect_overridden(override, :terminates_globally) && return false
return true
end
should_infer_this_call(::AbstractInterpreter, ::IRInterpretationState) = true

add_remark!(::AbstractInterpreter, ::InferenceState, remark) = return
add_remark!(::AbstractInterpreter, ::IRInterpretationState, remark) = return

Expand Down
40 changes: 18 additions & 22 deletions base/compiler/optimize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,29 @@ const IR_FLAG_INBOUNDS = one(UInt32) << 0
const IR_FLAG_INLINE = one(UInt32) << 1
# This statement is marked as @noinline by user
const IR_FLAG_NOINLINE = one(UInt32) << 2
# This statement is on a code path that eventually `throw`s.
const IR_FLAG_THROW_BLOCK = one(UInt32) << 3
# An optimization pass has updated this statement in a way that may
# have exposed information that inference did not see. Re-running
# inference on this statement may be profitable.
const IR_FLAG_REFINED = one(UInt32) << 4
const IR_FLAG_REFINED = one(UInt32) << 3
# This statement is proven :consistent
const IR_FLAG_CONSISTENT = one(UInt32) << 5
const IR_FLAG_CONSISTENT = one(UInt32) << 4
# This statement is proven :effect_free
const IR_FLAG_EFFECT_FREE = one(UInt32) << 6
const IR_FLAG_EFFECT_FREE = one(UInt32) << 5
# This statement is proven :nothrow
const IR_FLAG_NOTHROW = one(UInt32) << 7
const IR_FLAG_NOTHROW = one(UInt32) << 6
# This statement is proven :terminates
const IR_FLAG_TERMINATES = one(UInt32) << 8
const IR_FLAG_TERMINATES = one(UInt32) << 7
# This statement is proven :noub
const IR_FLAG_NOUB = one(UInt32) << 9
const IR_FLAG_NOUB = one(UInt32) << 8
# TODO: Both of these should eventually go away once
# This statement is :effect_free == EFFECT_FREE_IF_INACCESSIBLEMEMONLY
const IR_FLAG_EFIIMO = one(UInt32) << 10
const IR_FLAG_EFIIMO = one(UInt32) << 9
# This statement is :inaccessiblememonly == INACCESSIBLEMEM_OR_ARGMEMONLY
const IR_FLAG_INACCESSIBLEMEM_OR_ARGMEM = one(UInt32) << 11
const IR_FLAG_INACCESSIBLEMEM_OR_ARGMEM = one(UInt32) << 10
# This statement has no users and may be deleted if flags get refined to IR_FLAGS_REMOVABLE
const IR_FLAG_UNUSED = one(UInt32) << 12
const IR_FLAG_UNUSED = one(UInt32) << 11

const NUM_IR_FLAGS = 13 # sync with julia.h
const NUM_IR_FLAGS = 12 # sync with julia.h

const IR_FLAGS_EFFECTS =
IR_FLAG_CONSISTENT | IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW | IR_FLAG_TERMINATES | IR_FLAG_NOUB
Expand Down Expand Up @@ -249,9 +247,8 @@ end

_topmod(sv::OptimizationState) = _topmod(sv.mod)

is_stmt_inline(stmt_flag::UInt32) = has_flag(stmt_flag, IR_FLAG_INLINE)
is_stmt_noinline(stmt_flag::UInt32) = has_flag(stmt_flag, IR_FLAG_NOINLINE)
is_stmt_throw_block(stmt_flag::UInt32) = has_flag(stmt_flag, IR_FLAG_THROW_BLOCK)
is_stmt_inline(stmt_flag::UInt32) = has_flag(stmt_flag, IR_FLAG_INLINE)
is_stmt_noinline(stmt_flag::UInt32) = has_flag(stmt_flag, IR_FLAG_NOINLINE)

function new_expr_effect_flags(𝕃ₒ::AbstractLattice, args::Vector{Any}, src::Union{IRCode,IncrementalCompact}, pattern_match=nothing)
Targ = args[1]
Expand Down Expand Up @@ -1272,7 +1269,7 @@ plus_saturate(x::Int, y::Int) = max(x, y, x+y)
isknowntype(@nospecialize T) = (T === Union{}) || isa(T, Const) || isconcretetype(widenconst(T))

function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptypes::Vector{VarState},
params::OptimizationParams, error_path::Bool = false)
params::OptimizationParams)
#=const=# UNKNOWN_CALL_COST = 20
head = ex.head
if is_meta_expr_head(head)
Expand Down Expand Up @@ -1333,10 +1330,10 @@ function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptyp
return 0
elseif (f === Core.memoryrefget || f === Core.memoryref_isassigned) && length(ex.args) >= 3
atyp = argextype(ex.args[2], src, sptypes)
return isknowntype(atyp) ? 1 : error_path ? params.inline_error_path_cost : params.inline_nonleaf_penalty
return isknowntype(atyp) ? 1 : params.inline_nonleaf_penalty
elseif f === Core.memoryrefset! && length(ex.args) >= 3
atyp = argextype(ex.args[2], src, sptypes)
return isknowntype(atyp) ? 5 : error_path ? params.inline_error_path_cost : params.inline_nonleaf_penalty
return isknowntype(atyp) ? 5 : params.inline_nonleaf_penalty
elseif f === typeassert && isconstType(widenconst(argextype(ex.args[3], src, sptypes)))
return 1
end
Expand All @@ -1352,7 +1349,7 @@ function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptyp
if extyp === Union{}
return 0
end
return error_path ? params.inline_error_path_cost : params.inline_nonleaf_penalty
return params.inline_nonleaf_penalty
elseif head === :foreigncall
foreigncall = ex.args[1]
if foreigncall isa QuoteNode && foreigncall.value === :jl_string_ptr
Expand All @@ -1375,7 +1372,7 @@ function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptyp
end
a = ex.args[2]
if a isa Expr
cost = plus_saturate(cost, statement_cost(a, -1, src, sptypes, params, error_path))
cost = plus_saturate(cost, statement_cost(a, -1, src, sptypes, params))
end
return cost
elseif head === :copyast
Expand All @@ -1389,8 +1386,7 @@ function statement_or_branch_cost(@nospecialize(stmt), line::Int, src::Union{Cod
thiscost = 0
dst(tgt) = isa(src, IRCode) ? first(src.cfg.blocks[tgt].stmts) : tgt
if stmt isa Expr
thiscost = statement_cost(stmt, line, src, sptypes, params,
is_stmt_throw_block(isa(src, IRCode) ? src.stmts.flag[line] : src.ssaflags[line]))::Int
thiscost = statement_cost(stmt, line, src, sptypes, params)::Int
elseif stmt isa GotoNode
# loops are generally always expensive
# but assume that forward jumps are already counted for from
Expand Down
21 changes: 0 additions & 21 deletions base/compiler/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,6 @@ Parameters that control abstract interpretation-based type inference operation.
information available. [`Base.@constprop :aggressive`](@ref Base.@constprop) can have a
more fine-grained control on this configuration with per-method annotation basis.
---
- `inf_params.unoptimize_throw_blocks::Bool = true`\\
If `true`, skips inferring calls that are in a block that is known to `throw`.
It may improve the compiler latency without sacrificing the runtime performance
in common situations.
---
- `inf_params.assume_bindings_static::Bool = false`\\
If `true`, assumes that no new bindings will be added, i.e. a non-existing binding at
inference time can be assumed to always not exist at runtime (and thus e.g. any access to
Expand All @@ -176,7 +171,6 @@ struct InferenceParams
tuple_complexity_limit_depth::Int
ipo_constant_propagation::Bool
aggressive_constant_propagation::Bool
unoptimize_throw_blocks::Bool
assume_bindings_static::Bool
ignore_recursion_hardlimit::Bool

Expand All @@ -188,7 +182,6 @@ struct InferenceParams
tuple_complexity_limit_depth::Int,
ipo_constant_propagation::Bool,
aggressive_constant_propagation::Bool,
unoptimize_throw_blocks::Bool,
assume_bindings_static::Bool,
ignore_recursion_hardlimit::Bool)
return new(
Expand All @@ -199,7 +192,6 @@ struct InferenceParams
tuple_complexity_limit_depth,
ipo_constant_propagation,
aggressive_constant_propagation,
unoptimize_throw_blocks,
assume_bindings_static,
ignore_recursion_hardlimit)
end
Expand All @@ -213,7 +205,6 @@ function InferenceParams(
#=tuple_complexity_limit_depth::Int=# 3,
#=ipo_constant_propagation::Bool=# true,
#=aggressive_constant_propagation::Bool=# false,
#=unoptimize_throw_blocks::Bool=# BuildSettings.UNOPTIMIZE_THROW_BLOCKS,
#=assume_bindings_static::Bool=# false,
#=ignore_recursion_hardlimit::Bool=# false);
max_methods::Int = params.max_methods,
Expand All @@ -223,7 +214,6 @@ function InferenceParams(
tuple_complexity_limit_depth::Int = params.tuple_complexity_limit_depth,
ipo_constant_propagation::Bool = params.ipo_constant_propagation,
aggressive_constant_propagation::Bool = params.aggressive_constant_propagation,
unoptimize_throw_blocks::Bool = params.unoptimize_throw_blocks,
assume_bindings_static::Bool = params.assume_bindings_static,
ignore_recursion_hardlimit::Bool = params.ignore_recursion_hardlimit)
return InferenceParams(
Expand All @@ -234,7 +224,6 @@ function InferenceParams(
tuple_complexity_limit_depth,
ipo_constant_propagation,
aggressive_constant_propagation,
unoptimize_throw_blocks,
assume_bindings_static,
ignore_recursion_hardlimit)
end
Expand All @@ -259,10 +248,6 @@ Parameters that control optimizer operation.
tuple return types (in hopes of splitting it up). `opt_params.inline_tupleret_bonus` will
be added to `opt_params.inline_cost_threshold` when making inlining decision.
---
- `opt_params.inline_error_path_cost::Int = 20`\\
Specifies the penalty cost for an un-optimized dynamic call in a block that is known to
`throw`. See also [`(inf_params::InferenceParams).unoptimize_throw_blocks`](@ref InferenceParams).
---
- `opt_params.max_tuple_splat::Int = 32`\\
When attempting to inline `Core._apply_iterate`, abort the optimization if the tuple
contains more than this many elements.
Expand All @@ -289,7 +274,6 @@ struct OptimizationParams
inline_cost_threshold::Int
inline_nonleaf_penalty::Int
inline_tupleret_bonus::Int
inline_error_path_cost::Int
max_tuple_splat::Int
compilesig_invokes::Bool
assume_fatal_throw::Bool
Expand All @@ -300,7 +284,6 @@ struct OptimizationParams
inline_cost_threshold::Int,
inline_nonleaf_penalty::Int,
inline_tupleret_bonus::Int,
inline_error_path_cost::Int,
max_tuple_splat::Int,
compilesig_invokes::Bool,
assume_fatal_throw::Bool,
Expand All @@ -310,7 +293,6 @@ struct OptimizationParams
inline_cost_threshold,
inline_nonleaf_penalty,
inline_tupleret_bonus,
inline_error_path_cost,
max_tuple_splat,
compilesig_invokes,
assume_fatal_throw,
Expand All @@ -323,7 +305,6 @@ function OptimizationParams(
#=inline_cost_threshold::Int=# 100,
#=inline_nonleaf_penalty::Int=# 1000,
#=inline_tupleret_bonus::Int=# 250,
#=inline_error_path_cost::Int=# 20,
#=max_tuple_splat::Int=# 32,
#=compilesig_invokes::Bool=# true,
#=assume_fatal_throw::Bool=# false,
Expand All @@ -332,7 +313,6 @@ function OptimizationParams(
inline_cost_threshold::Int = params.inline_cost_threshold,
inline_nonleaf_penalty::Int = params.inline_nonleaf_penalty,
inline_tupleret_bonus::Int = params.inline_tupleret_bonus,
inline_error_path_cost::Int = params.inline_error_path_cost,
max_tuple_splat::Int = params.max_tuple_splat,
compilesig_invokes::Bool = params.compilesig_invokes,
assume_fatal_throw::Bool = params.assume_fatal_throw,
Expand All @@ -342,7 +322,6 @@ function OptimizationParams(
inline_cost_threshold,
inline_nonleaf_penalty,
inline_tupleret_bonus,
inline_error_path_cost,
max_tuple_splat,
compilesig_invokes,
assume_fatal_throw,
Expand Down
21 changes: 10 additions & 11 deletions src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ typedef union __jl_purity_overrides_t {
} _jl_purity_overrides_t;

#define NUM_EFFECTS_OVERRIDES 10
#define NUM_IR_FLAGS 13
#define NUM_IR_FLAGS 12

// This type describes a single function body
typedef struct _jl_code_info_t {
Expand All @@ -288,16 +288,15 @@ typedef struct _jl_code_info_t {
// 1 << 0 = inbounds region
// 1 << 1 = callsite inline region
// 1 << 2 = callsite noinline region
// 1 << 3 = throw block
// 1 << 4 = refined statement
// 1 << 5 = :consistent
// 1 << 6 = :effect_free
// 1 << 7 = :nothrow
// 1 << 8 = :terminates
// 1 << 9 = :noub
// 1 << 10 = :effect_free_if_inaccessiblememonly
// 1 << 11 = :inaccessiblemem_or_argmemonly
// 1 << 12-19 = callsite effects overrides
// 1 << 3 = refined statement
// 1 << 4 = :consistent
// 1 << 5 = :effect_free
// 1 << 6 = :nothrow
// 1 << 7 = :terminates
// 1 << 8 = :noub
// 1 << 9 = :effect_free_if_inaccessiblememonly
// 1 << 10 = :inaccessiblemem_or_argmemonly
// 1 << 11-19 = callsite effects overrides
// miscellaneous data:
jl_array_t *slotnames; // names of local variables
jl_array_t *slotflags; // local var bit flags
Expand Down
3 changes: 1 addition & 2 deletions stdlib/REPL/src/REPLCompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -556,8 +556,7 @@ struct REPLInterpreter <: CC.AbstractInterpreter
function REPLInterpreter(limit_aggressive_inference::Bool=false;
world::UInt = Base.get_world_counter(),
inf_params::CC.InferenceParams = CC.InferenceParams(;
aggressive_constant_propagation=true,
unoptimize_throw_blocks=false),
aggressive_constant_propagation=true),
opt_params::CC.OptimizationParams = CC.OptimizationParams(),
inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[])
return new(limit_aggressive_inference, world, inf_params, opt_params, inf_cache)
Expand Down
1 change: 0 additions & 1 deletion test/compiler/AbstractInterpreter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@ end == Val{6}
@newinterp Issue48097Interp
@MethodTable ISSUE_48097_MT
CC.method_table(interp::Issue48097Interp) = CC.OverlayMethodTable(CC.get_inference_world(interp), ISSUE_48097_MT)
CC.InferenceParams(::Issue48097Interp) = CC.InferenceParams(; unoptimize_throw_blocks=false)
function CC.concrete_eval_eligible(interp::Issue48097Interp,
@nospecialize(f), result::CC.MethodCallResult, arginfo::CC.ArgInfo, sv::CC.AbsIntState)
ret = @invoke CC.concrete_eval_eligible(interp::CC.AbstractInterpreter,
Expand Down
2 changes: 1 addition & 1 deletion test/compiler/codegen.jl
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ mktempdir() do pfx
libs_deleted += 1
end
@test libs_deleted > 0
@test readchomp(`$pfx/bin/$(Base.julia_exename()) -e 'print("no codegen!\n")'`) == "no codegen!"
@test readchomp(`$pfx/bin/$(Base.julia_exename()) --startup-file=no -e 'print("no codegen!\n")'`) == "no codegen!"

# PR #47343
libs_emptied = 0
Expand Down
4 changes: 2 additions & 2 deletions test/dict.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1510,9 +1510,9 @@ end
for T in (Int, Float64, String, Symbol)
@testset let T=T
@test !Core.Compiler.is_consistent(Base.infer_effects(getindex, (Dict{T,Any}, T)))
@test_broken Core.Compiler.is_effect_free(Base.infer_effects(getindex, (Dict{T,Any}, T)))
@test Core.Compiler.is_effect_free(Base.infer_effects(getindex, (Dict{T,Any}, T)))
@test !Core.Compiler.is_nothrow(Base.infer_effects(getindex, (Dict{T,Any}, T)))
@test_broken Core.Compiler.is_terminates(Base.infer_effects(getindex, (Dict{T,Any}, T)))
@test Core.Compiler.is_terminates(Base.infer_effects(getindex, (Dict{T,Any}, T)))
end
end

Expand Down

0 comments on commit cf951f7

Please sign in to comment.