Skip to content

Commit ce292c1

Browse files
staticfloatN5N3oscardssmith
authored
[compiler] Remove ConstAPI struct (#48598)
* Avoid split `y::Union` when comparing with a forall var. If `y` has no free typevar. Then `x <: y` wont cause any env change. Thus there's no need to split `y`. * [compiler] Remove `ConstAPI` struct `ConstAPI` predates the effect analysis framework, and was relying on an ad-hoc implementation of effect analysis that only worked for functions of less than 15 statements. As a result, it wasn't used much anymore, but it could cause trouble with external `AbstractInterpreters` that may want to cache additional code metadata within `CodeInstance`s. This commit simply removes this, expecting that the existing effect analysis will pick up the slack. * Add short-circuit `has_offset_axes(::Array)` This ensures that `Base.require_one_based_indexing()` is always fully eliminated, since previously `has_offset_axes(::Array)` is effect-tainted as non-consistent due to calculating `size(::Array)`. This dodges the effect taint by adding this short-circuit, fixing broken tests due to using effects analysis more extensively. * Move coverage effects analysis into inference Code coverage is implemented as an optimization pass that inserts an SSA IR node during optimization, however effects analysis is perfomed during inference, and so the desired effects tainting that should occur from insertion of the code coverage IR node is ignored. This teaches `InferenceState` to taint itself with the effects flag `effects_free = ALWAYS_FALSE` when coverage is applicable to this inference state's method. We just assume that you don't want code coverage if you're using this constructor. This may not always be true. --------- Co-authored-by: N5N3 <[email protected]> Co-authored-by: Oscar Smith <[email protected]>
1 parent 8e19fbb commit ce292c1

File tree

8 files changed

+61
-134
lines changed

8 files changed

+61
-134
lines changed

base/abstractarray.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ has_offset_axes(A::AbstractVector) = Int(firstindex(A))::Int != 1 # improve perf
113113
# note: this could call `any` directly if the compiler can infer it
114114
has_offset_axes(As...) = _any_tuple(has_offset_axes, false, As...)
115115
has_offset_axes(::Colon) = false
116+
has_offset_axes(::Array) = false
116117

117118
"""
118119
require_one_based_indexing(A::AbstractArray)

base/compiler/inferencestate.jl

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,15 @@ mutable struct InferenceState
129129
# Set by default for toplevel frame.
130130
restrict_abstract_call_sites::Bool
131131
cached::Bool # TODO move this to InferenceResult?
132+
insert_coverage::Bool
132133

133134
# The interpreter that created this inference state. Not looked at by
134135
# NativeInterpreter. But other interpreters may use this to detect cycles
135136
interp::AbstractInterpreter
136137

137138
# src is assumed to be a newly-allocated CodeInfo, that can be modified in-place to contain intermediate results
138139
function InferenceState(result::InferenceResult, src::CodeInfo, cache::Symbol,
139-
interp::AbstractInterpreter)
140+
interp::AbstractInterpreter)
140141
linfo = result.linfo
141142
world = get_world_counter(interp)
142143
def = linfo.def
@@ -179,6 +180,32 @@ mutable struct InferenceState
179180
bestguess = Bottom
180181
ipo_effects = EFFECTS_TOTAL
181182

183+
# check if coverage mode is enabled
184+
insert_coverage = coverage_enabled(mod)
185+
if !insert_coverage && JLOptions().code_coverage == 3 # path-specific coverage mode
186+
linetable = src.linetable
187+
if isa(linetable, Vector{Any})
188+
for line in linetable
189+
line = line::LineInfoNode
190+
if is_file_tracked(line.file)
191+
# if any line falls in a tracked file enable coverage for all
192+
insert_coverage = true
193+
break
194+
end
195+
end
196+
elseif isa(linetable, Vector{LineInfo})
197+
for line in linetable
198+
if is_file_tracked(line.file)
199+
insert_coverage = true
200+
break
201+
end
202+
end
203+
end
204+
end
205+
if insert_coverage
206+
ipo_effects = Effects(ipo_effects; effect_free = ALWAYS_FALSE)
207+
end
208+
182209
params = InferenceParams(interp)
183210
restrict_abstract_call_sites = isa(linfo.def, Module)
184211
@assert cache === :no || cache === :local || cache === :global
@@ -189,7 +216,7 @@ mutable struct InferenceState
189216
currbb, currpc, ip, handler_at, ssavalue_uses, bb_vartables, ssavaluetypes, stmt_edges, stmt_info,
190217
pclimitations, limitations, cycle_backedges, callers_in_cycle, dont_work_on_me, parent, inferred,
191218
result, valid_worlds, bestguess, ipo_effects,
192-
params, restrict_abstract_call_sites, cached,
219+
params, restrict_abstract_call_sites, cached, insert_coverage,
193220
interp)
194221

195222
# some more setups

base/compiler/optimize.jl

Lines changed: 9 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,14 @@ mutable struct OptimizationState{Interp<:AbstractInterpreter}
149149
slottypes::Vector{Any}
150150
inlining::InliningState{Interp}
151151
cfg::Union{Nothing,CFG}
152+
insert_coverage::Bool
152153
end
153154
function OptimizationState(frame::InferenceState, params::OptimizationParams,
154155
interp::AbstractInterpreter, recompute_cfg::Bool=true)
155156
inlining = InliningState(frame, params, interp)
156157
cfg = recompute_cfg ? nothing : frame.cfg
157158
return OptimizationState(frame.linfo, frame.src, nothing, frame.stmt_info, frame.mod,
158-
frame.sptypes, frame.slottypes, inlining, cfg)
159+
frame.sptypes, frame.slottypes, inlining, cfg, frame.insert_coverage)
159160
end
160161
function OptimizationState(linfo::MethodInstance, src::CodeInfo, params::OptimizationParams,
161162
interp::AbstractInterpreter)
@@ -179,7 +180,7 @@ function OptimizationState(linfo::MethodInstance, src::CodeInfo, params::Optimiz
179180
# Allow using the global MI cache, but don't track edges.
180181
# This method is mostly used for unit testing the optimizer
181182
inlining = InliningState(params, interp)
182-
return OptimizationState(linfo, src, nothing, stmt_info, mod, sptypes, slottypes, inlining, nothing)
183+
return OptimizationState(linfo, src, nothing, stmt_info, mod, sptypes, slottypes, inlining, nothing, false)
183184
end
184185
function OptimizationState(linfo::MethodInstance, params::OptimizationParams, interp::AbstractInterpreter)
185186
src = retrieve_code_info(linfo)
@@ -228,23 +229,6 @@ is_stmt_inline(stmt_flag::UInt8) = stmt_flag & IR_FLAG_INLINE ≠ 0
228229
is_stmt_noinline(stmt_flag::UInt8) = stmt_flag & IR_FLAG_NOINLINE 0
229230
is_stmt_throw_block(stmt_flag::UInt8) = stmt_flag & IR_FLAG_THROW_BLOCK 0
230231

231-
# These affect control flow within the function (so may not be removed
232-
# if there is no usage within the function), but don't affect the purity
233-
# of the function as a whole.
234-
function stmt_affects_purity(@nospecialize(stmt), ir)
235-
if isa(stmt, GotoNode) || isa(stmt, ReturnNode)
236-
return false
237-
end
238-
if isa(stmt, GotoIfNot)
239-
t = argextype(stmt.cond, ir)
240-
return !(t Bool)
241-
end
242-
if isa(stmt, Expr)
243-
return stmt.head !== :loopinfo && stmt.head !== :enter
244-
end
245-
return true
246-
end
247-
248232
"""
249233
stmt_effect_flags(stmt, rt, src::Union{IRCode,IncrementalCompact}) ->
250234
(consistent::Bool, effect_free_and_nothrow::Bool, nothrow::Bool)
@@ -406,80 +390,26 @@ function argextype(
406390
end
407391
abstract_eval_ssavalue(s::SSAValue, src::Union{IRCode,IncrementalCompact}) = types(src)[s]
408392

409-
struct ConstAPI
410-
val
411-
ConstAPI(@nospecialize val) = new(val)
412-
end
413393

414394
"""
415395
finish(interp::AbstractInterpreter, opt::OptimizationState,
416-
params::OptimizationParams, ir::IRCode, caller::InferenceResult) -> analyzed::Union{Nothing,ConstAPI}
396+
params::OptimizationParams, ir::IRCode, caller::InferenceResult) -> analyzed::Nothing
417397
418398
Post process information derived by Julia-level optimizations for later uses:
419399
- computes "purity", i.e. side-effect-freeness
420400
- computes inlining cost
421-
422-
In a case when the purity is proven, `finish` can return `ConstAPI` object wrapping the constant
423-
value so that the runtime system will use the constant calling convention for the method calls.
424401
"""
425402
function finish(interp::AbstractInterpreter, opt::OptimizationState,
426403
params::OptimizationParams, ir::IRCode, caller::InferenceResult)
427404
(; src, linfo) = opt
428405
(; def, specTypes) = linfo
429406

430-
analyzed = nothing # `ConstAPI` if this call can use constant calling convention
431407
force_noinline = is_declared_noinline(src)
432408

433409
# compute inlining and other related optimizations
434410
result = caller.result
435411
@assert !(result isa LimitedAccuracy)
436412
result = widenslotwrapper(result)
437-
if (isa(result, Const) || isconstType(result))
438-
proven_pure = false
439-
# must be proven pure to use constant calling convention;
440-
# otherwise we might skip throwing errors (issue #20704)
441-
# TODO: Improve this analysis; if a function is marked @pure we should really
442-
# only care about certain errors (e.g. method errors and type errors).
443-
if length(ir.stmts) < 15
444-
proven_pure = true
445-
for i in 1:length(ir.stmts)
446-
node = ir.stmts[i]
447-
stmt = node[:inst]
448-
if stmt_affects_purity(stmt, ir) && !stmt_effect_flags(optimizer_lattice(interp), stmt, node[:type], ir)[2]
449-
proven_pure = false
450-
break
451-
end
452-
end
453-
if proven_pure
454-
for fl in src.slotflags
455-
if (fl & SLOT_USEDUNDEF) != 0
456-
proven_pure = false
457-
break
458-
end
459-
end
460-
end
461-
end
462-
463-
if proven_pure
464-
# use constant calling convention
465-
# Do not emit `jl_fptr_const_return` if coverage is enabled
466-
# so that we don't need to add coverage support
467-
# to the `jl_call_method_internal` fast path
468-
# Still set pure flag to make sure `inference` tests pass
469-
# and to possibly enable more optimization in the future
470-
src.pure = true
471-
if isa(result, Const)
472-
val = result.val
473-
if is_inlineable_constant(val)
474-
analyzed = ConstAPI(val)
475-
end
476-
else
477-
@assert isconstType(result)
478-
analyzed = ConstAPI(result.parameters[1])
479-
end
480-
force_noinline || set_inlineable!(src, true)
481-
end
482-
end
483413

484414
opt.ir = ir
485415

@@ -528,8 +458,7 @@ function finish(interp::AbstractInterpreter, opt::OptimizationState,
528458
src.inlining_cost = inline_cost(ir, params, union_penalties, cost_threshold)
529459
end
530460
end
531-
532-
return analyzed
461+
return nothing
533462
end
534463

535464
# run the optimization work
@@ -612,18 +541,6 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState)
612541
linetable = collect(LineInfoNode, linetable::Vector{Any})::Vector{LineInfoNode}
613542
end
614543

615-
# check if coverage mode is enabled
616-
coverage = coverage_enabled(sv.mod)
617-
if !coverage && JLOptions().code_coverage == 3 # path-specific coverage mode
618-
for line in linetable
619-
if is_file_tracked(line.file)
620-
# if any line falls in a tracked file enable coverage for all
621-
coverage = true
622-
break
623-
end
624-
end
625-
end
626-
627544
# Go through and add an unreachable node after every
628545
# Union{} call. Then reindex labels.
629546
code = copy_exprargs(ci.code)
@@ -639,7 +556,7 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState)
639556
prevloc = zero(eltype(ci.codelocs))
640557
while idx <= length(code)
641558
codeloc = codelocs[idx]
642-
if coverage && codeloc != prevloc && codeloc != 0
559+
if sv.insert_coverage && codeloc != prevloc && codeloc != 0
643560
# insert a side-effect instruction before the current instruction in the same basic block
644561
insert!(code, idx, Expr(:code_coverage_effect))
645562
insert!(codelocs, idx, codeloc)
@@ -650,7 +567,7 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState)
650567
ssachangemap = fill(0, nstmts)
651568
end
652569
if labelchangemap === nothing
653-
labelchangemap = coverage ? fill(0, nstmts) : ssachangemap
570+
labelchangemap = fill(0, nstmts)
654571
end
655572
ssachangemap[oldidx] += 1
656573
if oldidx < length(labelchangemap)
@@ -671,11 +588,11 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState)
671588
ssachangemap = fill(0, nstmts)
672589
end
673590
if labelchangemap === nothing
674-
labelchangemap = coverage ? fill(0, nstmts) : ssachangemap
591+
labelchangemap = sv.insert_coverage ? fill(0, nstmts) : ssachangemap
675592
end
676593
if oldidx < length(ssachangemap)
677594
ssachangemap[oldidx + 1] += 1
678-
coverage && (labelchangemap[oldidx + 1] += 1)
595+
sv.insert_coverage && (labelchangemap[oldidx + 1] += 1)
679596
end
680597
idx += 1
681598
end

base/compiler/ssair/inlining.jl

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -843,10 +843,10 @@ function resolve_todo(mi::MethodInstance, result::Union{MethodMatch,InferenceRes
843843
#XXX: update_valid_age!(min_valid[1], max_valid[1], sv)
844844
if isa(result, InferenceResult)
845845
src = result.src
846-
if isa(src, ConstAPI)
846+
if is_total(result.ipo_effects) && isa(result.result, Const)
847847
# use constant calling convention
848848
add_inlining_backedge!(et, mi)
849-
return ConstantCase(quoted(src.val))
849+
return ConstantCase(quoted(result.result.val))
850850
end
851851
effects = result.ipo_effects
852852
else
@@ -866,16 +866,6 @@ function resolve_todo(mi::MethodInstance, result::Union{MethodMatch,InferenceRes
866866
end
867867

868868
src = inlining_policy(state.interp, src, info, flag, mi, argtypes)
869-
870-
if isa(src, ConstAPI)
871-
# duplicates the check above in case inlining_policy has a better idea.
872-
# We still keep the check above to make sure we can inline to ConstAPI
873-
# even if is_stmt_noinline. This doesn't currently happen in Base, but
874-
# can happen with external AbstractInterpreter.
875-
add_inlining_backedge!(et, mi)
876-
return ConstantCase(quoted(src.val))
877-
end
878-
879869
src === nothing && return compileable_specialization(result, effects, et, info;
880870
compilesig_invokes=state.params.compilesig_invokes)
881871

base/compiler/ssair/irinterp.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,9 @@ function concrete_eval_invoke(interp::AbstractInterpreter,
144144
inst::Expr, mi::MethodInstance, irsv::IRInterpretationState)
145145
mi_cache = WorldView(code_cache(interp), irsv.world)
146146
code = get(mi_cache, mi, nothing)
147-
code === nothing && return Pair{Any, Bool}(nothing, false)
147+
if code === nothing
148+
return Pair{Any, Bool}(nothing, false)
149+
end
148150
argtypes = collect_argtypes(interp, inst.args[2:end], nothing, irsv.ir)
149151
argtypes === nothing && return Pair{Any, Bool}(Union{}, false)
150152
effects = decode_effects(code.ipo_purity_bits)

base/compiler/typeinfer.jl

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -269,14 +269,6 @@ function _typeinf(interp::AbstractInterpreter, frame::InferenceState)
269269
opt = caller.src
270270
if opt isa OptimizationState{typeof(interp)} # implies `may_optimize(interp) === true`
271271
analyzed = optimize(interp, opt, OptimizationParams(interp), caller)
272-
if isa(analyzed, ConstAPI)
273-
# XXX: The work in ir_to_codeinf! is essentially wasted. The only reason
274-
# we're doing it is so that code_llvm can return the code
275-
# for the `return ...::Const` (which never runs anyway). We should do this
276-
# as a post processing step instead.
277-
ir_to_codeinf!(opt)
278-
caller.src = analyzed
279-
end
280272
caller.valid_worlds = (opt.inlining.et::EdgeTracker).valid_worlds[]
281273
end
282274
end
@@ -300,9 +292,12 @@ function CodeInstance(
300292
local const_flags::Int32
301293
result_type = result.result
302294
@assert !(result_type isa LimitedAccuracy)
303-
if inferred_result isa ConstAPI
295+
296+
if isa(result_type, Const) && is_foldable(result.ipo_effects) &&
297+
is_nothrow(result.ipo_effects) &&
298+
is_inlineable_constant(result_type.val)
304299
# use constant calling convention
305-
rettype_const = inferred_result.val
300+
rettype_const = result_type.val
306301
const_flags = 0x3
307302
inferred_result = nothing
308303
else
@@ -379,7 +374,7 @@ function transform_result_for_cache(interp::AbstractInterpreter,
379374
inferred_result = maybe_compress_codeinfo(interp, linfo, inferred_result)
380375
end
381376
# The global cache can only handle objects that codegen understands
382-
if !isa(inferred_result, Union{CodeInfo, Vector{UInt8}, ConstAPI})
377+
if !isa(inferred_result, MaybeCompressed)
383378
inferred_result = nothing
384379
end
385380
return inferred_result

test/compiler/inference.jl

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -569,16 +569,9 @@ Base.@pure function fpure(a=rand(); b=rand())
569569
# but would be too big to inline
570570
return a + b + rand()
571571
end
572-
gpure() = fpure()
573-
gpure(x::Irrational) = fpure(x)
572+
574573
@test which(fpure, ()).pure
575574
@test which(fpure, (typeof(pi),)).pure
576-
@test !which(gpure, ()).pure
577-
@test !which(gpure, (typeof(pi),)).pure
578-
@test code_typed(gpure, ())[1][1].pure
579-
@test code_typed(gpure, (typeof(π),))[1][1].pure
580-
@test gpure() == gpure() == gpure()
581-
@test gpure(π) == gpure(π) == gpure(π)
582575

583576
# Make sure @pure works for functions using the new syntax
584577
Base.@pure (fpure2(x::T) where T) = T
@@ -941,13 +934,6 @@ Base.@pure c20704() = (f20704(1.0); 1)
941934
d20704() = c20704()
942935
@test_throws MethodError d20704()
943936

944-
Base.@pure function a20704(x)
945-
rand()
946-
42
947-
end
948-
aa20704(x) = x(nothing)
949-
@test code_typed(aa20704, (typeof(a20704),))[1][1].pure
950-
951937
#issue #21065, elision of _apply_iterate when splatted expression is not effect_free
952938
function f21065(x,y)
953939
println("x=$x, y=$y")

test/reducedim.jl

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,16 @@ using Random
66

77
# issue #35800
88
# tested very early since it can be state-dependent
9-
@test @inferred(mapreduce(x->count(!iszero,x), +, [rand(1)]; init = 0.)) == 1.0
9+
10+
function my_simple_count(pred, g::Vector{T}) where {T}
11+
n::T = zero(T)
12+
for x in g
13+
n += pred(x)
14+
end
15+
return n
16+
end
17+
18+
@test @inferred(mapreduce(x->my_simple_count(!iszero,x), +, [rand(1)]; init = 0.)) == 1.0
1019

1120
function safe_mapslices(op, A, region)
1221
newregion = intersect(region, 1:ndims(A))

0 commit comments

Comments
 (0)