Skip to content

Commit f10176c

Browse files
committed
Implement new effect system
* TLDR Before: ``` julia> let b = Expr(:block, (:(y += sin($x)) for x in randn(1000))...) @eval function f_sin_perf() y = 0.0 $b y end end f_sin_perf (generic function with 1 method) julia> @time @code_typed f_sin_perf() 15.707267 seconds (25.95 M allocations: 1.491 GiB, 3.30% gc time) [lots of junk] ``` After: ``` julia> @time @code_typed f_sin_perf() 0.016818 seconds (187.35 k allocations: 7.901 MiB, 99.73% compilation time) CodeInfo( 1 ─ return 27.639138714768546 ) => Float64 ``` so roughly a 1000x improvement in compile time performance for const-prop heavy functions. There are also run time improvements for functions that have patterns like: ``` function some_function_to_big_to_be_inlined_but_pure(x) .... end function foo(x) some_function_to_big_to_be_inlined_but_pure(x) return x end ``` The inliner will now be able to see that some_function_to_big_to_be_inlined_but_pure is effect free, even without inlining it and just delete it, improving runtime performance (if some_function_to_big_to_be_inlined_but_pure is small enough to be inlined, there is a small compile time throughput win, by being able to delete it without inlining, but that's a smaller gain than the compile time gain above). * Motivation / Overview There are two motivations for this work. The first is the above mentioned improvement in compiler performance for const-prop heavy functions. This comes up a fair bit in various Modeling & Simulation codes we have where Julia code is often auto-generated from some combination of parameterized model codes and data. This ends up creating enormous functions with significant need for constant propagation (~50k statements with ~20k constant calls are not uncommon). Our current compiler was designed for people occasionally throwing a `sqrt(2)` or something in a function, not 20k of them, so performance is quite bad. The second motivation is to have finer grained control over our purity modeling. We have `@Base.pure`, but that has somewhat nebulous semantics and is quite a big hammer that is not appropriate in most situations. These may seem like orthogonal concerns at first, but they are not. The compile time issues fundamentally stem from us running constant propagation in inference's abstract interpreter. However, for simple, pure functions, that is entirely unnecessary, because we have a super-fast, JIT compiler version of that function just laying around in general. The issue is that we currently, we generally do not know when it is legal to run the JIT-compiled version of the function and when we need to abstractly interpret it. However, if the compiler were able to figure out an appropriate notion of purity, it could start doing that (which is what it does now for `@Base.pure` functions). This PR adds that kind of notion of purity, converges it along with type information during inference and then makes use of it to speed up evaluation of constant propagation (where it is legal to do so), as well as improving the inliner. * The new purity notions The new purity model consists of four different kinds flags per code instance. For builtins and intrinsics the existing effect free and nothrow models are re-used. There is also a new macro `@Base.assume_effects` available, which can set the purity base case for methods or `:foreigncall`s. Here is the docstring for that macro, which also explains the semantics of the new purity flags: ``` @assume_effects setting... ex @assume_effects(setting..., ex) `@assume_effects` overrides the compiler's effect modeling for the given method. `ex` must be a method definition. WARNING: Improper use of this macro causes undefined behavior (including crashes, incorrect answers, or other hard to track bugs). Use with care an only if absolutely required. In general, each `setting` value makes an assertion about the behavior of the function, without requiring the compiler to prove that this behavior is indeed true. These assertions are made for all world ages. It is thus advisable to limit the use of generic functions that may later be extended to invalidate the assumption (which would cause undefined behavior). The following `settings` are supported. ** `:idempotent` The `:idempotent` setting asserts that for egal inputs: - The manner of termination (return value, exception, non-termination) will always be the same. - If the method returns, the results will always be egal. Note: This in particular implies that the return value of the method must be immutable. Multiple allocations of mutable objects (even with identical contents) are not egal. Note: The idempotency assertion is made world-arge wise. More formally, write fₐ for the evaluation of `f` in world-age `a`, then we require: ∀ a, x, y: x === y → fₐ(x) === fₐ(y) However, for two world ages `a, b` s.t. `a != b`, we may have `fₐ(x) !== fₐ(y)`` Note: A further implication is that idempontent functions may not make their return value dependent on the state of the heap or any other global state that is not constant for a given world age. Note: The idempontency includes all legal rewrites performed by the optimizizer. For example, floating-point fastmath operations are not considered idempotent, because the optimizer may rewrite them causing the output to not be idempotent, even for the same world age (e.g. because one ran in the interpreter, while the other was optimized). ** `:effect_free` The `:effect_free` setting asserts that the method is free of externally semantically visible side effects. The following is an incomplete list of externally semantically visible side effects: - Changing the value of a global variable. - Mutating the heap (e.g. an array or mutable value), except as noted below - Changing the method table (e.g. through calls to eval) - File/Network/etc. I/O - Task switching However, the following are explicitly not semantically visible, even if they may be observable: - Memory allocations (both mutable and immutable) - Elapsed time - Garbage collection - Heap mutations of objects whose lifetime does not exceed the method (i.e. were allocated in the method and do not escape). - The returned value (which is externally visible, but not a side effect) The rule of thumb here is that an externally visible side effect is anything that would affect the execution of the remainder of the program if the function were not executed. Note: The effect free assertion is made both for the method itself and any code that is executed by the method. Keep in mind that the assertion must be valid for all world ages and limit use of this assertion accordingly. ** `:nothrow` The `:nothrow` settings asserts that this method does not terminate abnormally (i.e. will either always return a value or never return). Note: It is permissible for :nothrow annotated methods to make use of exception handling internally as long as the exception is not rethrown out of the method itself. Note: MethodErrors and similar exceptions count as abnormal termination. ** `:terminates_globally` The `:terminates_globally` settings asserts that this method will eventually terminate (either normally or abnormally), i.e. does not infinite loop. Note: The compiler will consider this a strong indication that the method will terminate relatively *quickly* and may (if otherwise legal), call this method at compile time. I.e. it is a bad idea to annotate this setting on a method that *technically*, but not *practically*, terminates. Note: The `terminates_globally` assertion, covers any other methods called by the annotated method. ** `:terminates_locally` The `:terminates_locally` setting is like `:terminates_globally`, except that it only applies to syntactic control flow *within* the annotated method. It is this a much weaker (and thus safer) assertion that allows for the possibility of non-termination if the method calls some other method that does not terminate. Note: `terminates_globally` implies `terminates_locally`. * `:total` The `setting` combines the following other assertions: - `:idempotent` - `:effect_free` - `:nothrow` - `:terminates_globally` and is a convenient shortcut. Note: `@assume_effects :total` is similar to `@Base.pure` with the primary distinction that the idempotency requirement applies world-age wise rather than globally as described above. However, in particular, a method annotated `@Base.pure` is always total. ``` * Changes to data structures - Each CodeInstance gains two sets of four flags corresponding to the notions above (except terminates_locally, which is just a type inference flag). One set of flags tracks IPO-valid information (as determined by inference), the other set of flags tracks optimizer-valid information (as determined after optimization). Otherwise they have identical semantics. - Method and CodeInfo each gain 5 bit flags corresponding 1:1 to the purity notions defined above. No separate distinction is made between IPO valid and optimizer valid flags here. We might in the future want such a distinction, but I'm hoping to get away without it for now, since the IPO-vs-optimizer distinction is a bit subtle and I don't really want to expose that to the user. - `:foreigncall` gains an extra argument (after `cconv`) to describe the effects of the call. * Algorithm Relatively straightforward. - Every call or builtin accumulates its effect information into the current frame. - Finding an effect (throw/global side effect/non-idempotenct, etc.) taints the entire frame. Idempotency is technically a dataflow property, but that is not modeled here and any non-idempotent intrinsic will taint the idempotency flag, even if it does not contribute to the return value. I don't think that's a huge problem in practice, because currently we only use idempotency if effect-free is also set and in effect-free functions you'd generally expect every statement to contribute to the return value. - Any backedge taints the termination effect, as does any recursion - Unknown statements (generic calls, things I haven't gotten around to) taint all effects
1 parent 816c6a2 commit f10176c

36 files changed

+813
-149
lines changed

base/boot.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -418,9 +418,9 @@ eval(Core, :(LineInfoNode(mod::Module, @nospecialize(method), file::Symbol, line
418418
$(Expr(:new, :LineInfoNode, :mod, :method, :file, :line, :inlined_at))))
419419
eval(Core, :(CodeInstance(mi::MethodInstance, @nospecialize(rettype), @nospecialize(inferred_const),
420420
@nospecialize(inferred), const_flags::Int32,
421-
min_world::UInt, max_world::UInt) =
422-
ccall(:jl_new_codeinst, Ref{CodeInstance}, (Any, Any, Any, Any, Int32, UInt, UInt),
423-
mi, rettype, inferred_const, inferred, const_flags, min_world, max_world)))
421+
min_world::UInt, max_world::UInt, ipo_effects::UInt8, effects::UInt8) =
422+
ccall(:jl_new_codeinst, Ref{CodeInstance}, (Any, Any, Any, Any, Int32, UInt, UInt, UInt8, UInt8),
423+
mi, rettype, inferred_const, inferred, const_flags, min_world, max_world, ipo_effects, effects)))
424424
eval(Core, :(Const(@nospecialize(v)) = $(Expr(:new, :Const, :v))))
425425
eval(Core, :(PartialStruct(@nospecialize(typ), fields::Array{Any, 1}) = $(Expr(:new, :PartialStruct, :typ, :fields))))
426426
eval(Core, :(PartialOpaque(@nospecialize(typ), @nospecialize(env), isva::Bool, parent::MethodInstance, source::Method) = $(Expr(:new, :PartialOpaque, :typ, :env, :isva, :parent, :source))))

base/c.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,7 @@ function ccall_macro_lower(convention, func, rettype, types, args, nreq)
676676
esc(etypes),
677677
nreq,
678678
QuoteNode(convention),
679+
nothing,
679680
realargs..., gcroots...)
680681
push!(lowering, exp)
681682

base/compiler/abstractinterpretation.jl

Lines changed: 160 additions & 23 deletions
Large diffs are not rendered by default.

base/compiler/inferencestate.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ mutable struct InferenceState
5959
inferred::Bool
6060
dont_work_on_me::Bool
6161

62+
# Inferred purity flags
63+
ipo_effects::Effects
64+
6265
# The place to look up methods while working on this function.
6366
# In particular, we cache method lookup results for the same function to
6467
# fast path repeated queries.
@@ -126,13 +129,15 @@ mutable struct InferenceState
126129
Vector{InferenceState}(), # callers_in_cycle
127130
#=parent=#nothing,
128131
cache === :global, false, false,
132+
Effects(ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE),
129133
CachedMethodTable(method_table(interp)),
130134
interp)
131135
result.result = frame
132136
cache !== :no && push!(get_inference_cache(interp), result)
133137
return frame
134138
end
135139
end
140+
Effects(state::InferenceState) = state.ipo_effects
136141

137142
function compute_trycatch(code::Vector{Any}, ip::BitSet)
138143
# The goal initially is to record the frame like this for the state at exit:

base/compiler/optimize.jl

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -149,16 +149,6 @@ const IR_FLAG_THROW_BLOCK = 0x01 << 3
149149
# thus be both pure and effect free.
150150
const IR_FLAG_EFFECT_FREE = 0x01 << 4
151151

152-
# known to be always effect-free (in particular nothrow)
153-
const _PURE_BUILTINS = Any[tuple, svec, ===, typeof, nfields]
154-
155-
# known to be effect-free if the are nothrow
156-
const _PURE_OR_ERROR_BUILTINS = [
157-
fieldtype, apply_type, isa, UnionAll,
158-
getfield, arrayref, const_arrayref, arraysize, isdefined, Core.sizeof,
159-
Core.kwfunc, Core.ifelse, Core._typevar, (<:)
160-
]
161-
162152
const TOP_TUPLE = GlobalRef(Core, :tuple)
163153

164154
#########
@@ -292,11 +282,11 @@ function alloc_array_ndims(name::Symbol)
292282
end
293283

294284
function alloc_array_no_throw(args::Vector{Any}, ndims::Int, src::Union{IRCode,IncrementalCompact})
295-
length(args) ndims+6 || return false
296-
atype = instanceof_tfunc(argextype(args[6], src))[1]
285+
length(args) ndims+7 || return false
286+
atype = instanceof_tfunc(argextype(args[7], src))[1]
297287
dims = Csize_t[]
298288
for i in 1:ndims
299-
dim = argextype(args[i+6], src)
289+
dim = argextype(args[i+7], src)
300290
isa(dim, Const) || return false
301291
dimval = dim.val
302292
isa(dimval, Int) || return false
@@ -306,9 +296,9 @@ function alloc_array_no_throw(args::Vector{Any}, ndims::Int, src::Union{IRCode,I
306296
end
307297

308298
function new_array_no_throw(args::Vector{Any}, src::Union{IRCode,IncrementalCompact})
309-
length(args) 7 || return false
310-
atype = instanceof_tfunc(argextype(args[6], src))[1]
311-
dims = argextype(args[7], src)
299+
length(args) 8 || return false
300+
atype = instanceof_tfunc(argextype(args[7], src))[1]
301+
dims = argextype(args[8], src)
312302
isa(dims, Const) || return dims === Tuple{}
313303
dimsval = dims.val
314304
isa(dimsval, Tuple{Vararg{Int}}) || return false
@@ -612,21 +602,6 @@ function slot2reg(ir::IRCode, ci::CodeInfo, sv::OptimizationState)
612602
return ir
613603
end
614604

615-
# whether `f` is pure for inference
616-
function is_pure_intrinsic_infer(f::IntrinsicFunction)
617-
return !(f === Intrinsics.pointerref || # this one is volatile
618-
f === Intrinsics.pointerset || # this one is never effect-free
619-
f === Intrinsics.llvmcall || # this one is never effect-free
620-
f === Intrinsics.arraylen || # this one is volatile
621-
f === Intrinsics.sqrt_llvm_fast || # this one may differ at runtime (by a few ulps)
622-
f === Intrinsics.have_fma || # this one depends on the runtime environment
623-
f === Intrinsics.cglobal) # cglobal lookup answer changes at runtime
624-
end
625-
626-
# whether `f` is effect free if nothrow
627-
intrinsic_effect_free_if_nothrow(f) = f === Intrinsics.pointerref ||
628-
f === Intrinsics.have_fma || is_pure_intrinsic_infer(f)
629-
630605
## Computing the cost of a function body
631606

632607
# saturating sum (inputs are nonnegative), prevents overflow with typemax(Int) below

base/compiler/ssair/inlining.jl

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ struct ResolvedInliningSpec
1717
# If the function being inlined is a single basic block we can use a
1818
# simpler inlining algorithm. This flag determines whether that's allowed
1919
linear_inline_eligible::Bool
20+
# Effects of the call statement
21+
effects::Effects
2022
end
2123

2224
"""
@@ -51,11 +53,16 @@ struct SomeCase
5153
SomeCase(val) = new(val)
5254
end
5355

56+
struct InvokeCase
57+
invoke::Union{MethodInstance, Nothing}
58+
effects::Effects
59+
end
60+
5461
struct InliningCase
5562
sig # ::Type
5663
item # Union{InliningTodo, MethodInstance, ConstantCase}
5764
function InliningCase(@nospecialize(sig), @nospecialize(item))
58-
@assert isa(item, Union{InliningTodo, MethodInstance, ConstantCase}) "invalid inlining item"
65+
@assert isa(item, Union{InliningTodo, InvokeCase, ConstantCase}) "invalid inlining item"
5966
return new(sig, item)
6067
end
6168
end
@@ -506,9 +513,11 @@ function ir_inline_unionsplit!(compact::IncrementalCompact, idx::Int,
506513
end
507514
if isa(case, InliningTodo)
508515
val = ir_inline_item!(compact, idx, argexprs′, linetable, case, boundscheck, todo_bbs)
509-
elseif isa(case, MethodInstance)
516+
elseif isa(case, InvokeCase)
517+
effect_free = is_removable_if_unused(case.effects)
510518
val = insert_node_here!(compact,
511-
NewInstruction(Expr(:invoke, case, argexprs′...), typ, line))
519+
NewInstruction(Expr(:invoke, case.invoke, argexprs′...), typ, nothing,
520+
line, effect_free ? IR_FLAG_EFFECT_FREE : IR_FLAG_NULL, effect_free))
512521
else
513522
case = case::ConstantCase
514523
val = case.val
@@ -715,16 +724,22 @@ function rewrite_apply_exprargs!(
715724
return new_argtypes
716725
end
717726

718-
function compileable_specialization(et::Union{EdgeTracker, Nothing}, match::MethodMatch)
727+
function compileable_specialization(et::Union{EdgeTracker, Nothing}, match::MethodMatch, effects::Effects)
719728
mi = specialize_method(match; compilesig=true)
720729
mi !== nothing && et !== nothing && push!(et, mi::MethodInstance)
721-
return mi
730+
mi === nothing && return nothing
731+
return InvokeCase(mi, effects)
722732
end
723733

724-
function compileable_specialization(et::Union{EdgeTracker, Nothing}, (; linfo)::InferenceResult)
734+
function compileable_specialization(et::Union{EdgeTracker, Nothing}, linfo::MethodInstance, effects::Effects)
725735
mi = specialize_method(linfo.def::Method, linfo.specTypes, linfo.sparam_vals; compilesig=true)
726736
mi !== nothing && et !== nothing && push!(et, mi::MethodInstance)
727-
return mi
737+
mi === nothing && return nothing
738+
return InvokeCase(mi, effects)
739+
end
740+
741+
function compileable_specialization(et::Union{EdgeTracker, Nothing}, (; linfo)::InferenceResult, effects::Effects)
742+
return compileable_specialization(et, linfo, effects)
728743
end
729744

730745
function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8)
@@ -742,6 +757,7 @@ function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8)
742757
else
743758
src = inferred_src
744759
end
760+
effects = match.ipo_effects
745761
else
746762
code = get(state.mi_cache, mi, nothing)
747763
if code isa CodeInstance
@@ -752,29 +768,31 @@ function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8)
752768
else
753769
src = code.inferred
754770
end
771+
effects = decode_effects(code.ipo_purity_bits)
755772
else
773+
effects = Effects()
756774
src = code
757775
end
758776
end
759777

760778
# the duplicated check might have been done already within `analyze_method!`, but still
761779
# we need it here too since we may come here directly using a constant-prop' result
762780
if !state.params.inlining || is_stmt_noinline(flag)
763-
return compileable_specialization(et, match)
781+
return compileable_specialization(et, match, effects)
764782
end
765783

766784
src = inlining_policy(state.interp, src, flag, mi, argtypes)
767785

768786
if src === nothing
769-
return compileable_specialization(et, match)
787+
return compileable_specialization(et, match, effects)
770788
end
771789

772790
if isa(src, IRCode)
773791
src = copy(src)
774792
end
775793

776794
et !== nothing && push!(et, mi)
777-
return InliningTodo(mi, src)
795+
return InliningTodo(mi, src, effects)
778796
end
779797

780798
function resolve_todo((; fully_covered, atype, cases, #=bbs=#)::UnionSplit, state::InliningState, flag::UInt8)
@@ -816,13 +834,9 @@ function analyze_method!(match::MethodMatch, argtypes::Vector{Any},
816834

817835
et = state.et
818836

819-
if !state.params.inlining || is_stmt_noinline(flag)
820-
return compileable_specialization(et, match)
821-
end
822-
823837
# See if there exists a specialization for this method signature
824838
mi = specialize_method(match; preexisting=true) # Union{Nothing, MethodInstance}
825-
isa(mi, MethodInstance) || return compileable_specialization(et, match)
839+
isa(mi, MethodInstance) || return compileable_specialization(et, match, Effects())
826840

827841
todo = InliningTodo(mi, match, argtypes)
828842
# If we don't have caches here, delay resolving this MethodInstance
@@ -831,17 +845,17 @@ function analyze_method!(match::MethodMatch, argtypes::Vector{Any},
831845
return resolve_todo(todo, state, flag)
832846
end
833847

834-
function InliningTodo(mi::MethodInstance, ir::IRCode)
835-
return InliningTodo(mi, ResolvedInliningSpec(ir, linear_inline_eligible(ir)))
848+
function InliningTodo(mi::MethodInstance, ir::IRCode, effects::Effects)
849+
return InliningTodo(mi, ResolvedInliningSpec(ir, linear_inline_eligible(ir), effects))
836850
end
837851

838-
function InliningTodo(mi::MethodInstance, src::Union{CodeInfo, Array{UInt8, 1}})
852+
function InliningTodo(mi::MethodInstance, src::Union{CodeInfo, Array{UInt8, 1}}, effects::Effects)
839853
if !isa(src, CodeInfo)
840854
src = ccall(:jl_uncompress_ir, Any, (Any, Ptr{Cvoid}, Any), mi.def, C_NULL, src::Vector{UInt8})::CodeInfo
841855
end
842856

843-
@timeit "inline IR inflation" begin
844-
return InliningTodo(mi, inflate_ir(src, mi)::IRCode)
857+
@timeit "inline IR inflation" begin;
858+
return InliningTodo(mi, inflate_ir(src, mi)::IRCode, effects)
845859
end
846860
end
847861

@@ -850,10 +864,13 @@ function handle_single_case!(
850864
@nospecialize(case), todo::Vector{Pair{Int, Any}}, isinvoke::Bool = false)
851865
if isa(case, ConstantCase)
852866
ir[SSAValue(idx)][:inst] = case.val
853-
elseif isa(case, MethodInstance)
867+
elseif isa(case, InvokeCase)
868+
if is_total(case.effects)
869+
inline_const_if_inlineable!(ir[SSAValue(idx)]) && return nothing
870+
end
854871
isinvoke && rewrite_invoke_exprargs!(stmt)
855872
stmt.head = :invoke
856-
pushfirst!(stmt.args, case)
873+
pushfirst!(stmt.args, case.invoke)
857874
elseif case === nothing
858875
# Do, well, nothing
859876
else
@@ -1100,10 +1117,10 @@ function process_simple!(ir::IRCode, idx::Int, state::InliningState, todo::Vecto
11001117
length(info.results) == 1 || return nothing
11011118
match = info.results[1]::MethodMatch
11021119
match.fully_covers || return nothing
1103-
case = compileable_specialization(state.et, match)
1120+
case = compileable_specialization(state.et, match, Effects())
11041121
case === nothing && return nothing
11051122
stmt.head = :invoke_modify
1106-
pushfirst!(stmt.args, case)
1123+
pushfirst!(stmt.args, case.invoke)
11071124
ir.stmts[idx][:inst] = stmt
11081125
end
11091126
return nothing
@@ -1220,6 +1237,21 @@ function handle_const_call!(
12201237
for match in meth
12211238
j += 1
12221239
result = results[j]
1240+
if result === false
1241+
# Inference determined that this call is guaranteed to throw.
1242+
# Do not inline.
1243+
fully_covered = false
1244+
continue
1245+
end
1246+
if isa(result, ConstResult)
1247+
case = ConstantCase(quoted(result.result))
1248+
if !is_inlineable_constant(result.result)
1249+
case = compileable_specialization(state.et, result.mi, Effects())
1250+
end
1251+
signature_union = Union{signature_union, result.mi.specTypes}
1252+
push!(cases, InliningCase(result.mi.specTypes, case))
1253+
continue
1254+
end
12231255
if result === nothing
12241256
signature_union = Union{signature_union, match.spec_types}
12251257
fully_covered &= handle_match!(match, argtypes, flag, state, cases)
@@ -1232,7 +1264,7 @@ function handle_const_call!(
12321264

12331265
# if the signature is fully covered and there is only one applicable method,
12341266
# we can try to inline it even if the signature is not a dispatch tuple
1235-
if length(cases) == 0 && length(results) == 1
1267+
if length(cases) == 0 && length(results) == 1 && isa(results[1], InferenceResult)
12361268
(; mi) = item = InliningTodo(results[1]::InferenceResult, argtypes)
12371269
state.mi_cache !== nothing && (item = resolve_todo(item, state, flag))
12381270
validate_sparams(mi.sparam_vals) || return nothing
@@ -1309,6 +1341,7 @@ function assemble_inline_todo!(ir::IRCode, state::InliningState)
13091341
# todo = (inline_idx, (isva, isinvoke, na), method, spvals, inline_linetable, inline_ir, lie)
13101342
todo = Pair{Int, Any}[]
13111343
et = state.et
1344+
13121345
for idx in 1:length(ir.stmts)
13131346
simpleres = process_simple!(ir, idx, state, todo)
13141347
simpleres === nothing && continue
@@ -1447,7 +1480,7 @@ function late_inline_special_case!(
14471480
return SomeCase(typevar_call)
14481481
elseif isinlining && f === UnionAll && length(argtypes) == 3 && (argtypes[2] TypeVar)
14491482
unionall_call = Expr(:foreigncall, QuoteNode(:jl_type_unionall), Any, svec(Any, Any),
1450-
0, QuoteNode(:ccall), stmt.args[2], stmt.args[3])
1483+
0, QuoteNode(:ccall), nothing, stmt.args[2], stmt.args[3])
14511484
return SomeCase(unionall_call)
14521485
elseif is_return_type(f)
14531486
if isconstType(type)

base/compiler/ssair/passes.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,7 @@ function sroa_pass!(ir::IRCode)
668668
nccallargs = length(stmt.args[3]::SimpleVector)
669669
preserved = Int[]
670670
new_preserves = Any[]
671-
for pidx in (6+nccallargs):length(stmt.args)
671+
for pidx in (7+nccallargs):length(stmt.args)
672672
preserved_arg = stmt.args[pidx]
673673
isa(preserved_arg, SSAValue) || continue
674674
let intermediaries = SPCSet()
@@ -960,10 +960,10 @@ end
960960
function form_new_preserves(origex::Expr, intermediates::Vector{Int}, new_preserves::Vector{Any})
961961
newex = Expr(:foreigncall)
962962
nccallargs = length(origex.args[3]::SimpleVector)
963-
for i in 1:(6+nccallargs-1)
963+
for i in 1:(7+nccallargs-1)
964964
push!(newex.args, origex.args[i])
965965
end
966-
for i in (6+nccallargs):length(origex.args)
966+
for i in (7+nccallargs):length(origex.args)
967967
x = origex.args[i]
968968
# don't need to preserve intermediaries
969969
if isa(x, SSAValue) && x.id in intermediates

base/compiler/ssair/show.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,4 +788,19 @@ function show_ir(io::IO, code::Union{IRCode, CodeInfo}, config::IRShowConfig=def
788788
nothing
789789
end
790790

791+
tristate_letter(t::TriState) = t === ALWAYS_TRUE ? '+' : t === ALWAYS_FALSE ? '!' : '?'
792+
tristate_color(t::TriState) = t === ALWAYS_TRUE ? :green : t === ALWAYS_FALSE ? :red : :orange
793+
794+
function Base.show(io::IO, e::Core.Compiler.Effects)
795+
print(io, "(")
796+
printstyled(io, string(tristate_letter(e.consistent), 'c'); color=tristate_color(e.consistent))
797+
print(io, ',')
798+
printstyled(io, string(tristate_letter(e.effect_free), 'e'); color=tristate_color(e.effect_free))
799+
print(io, ',')
800+
printstyled(io, string(tristate_letter(e.nothrow), 'n'); color=tristate_color(e.nothrow))
801+
print(io, ',')
802+
printstyled(io, string(tristate_letter(e.terminates), 't'); color=tristate_color(e.terminates))
803+
print(io, ')')
804+
end
805+
791806
@specialize

0 commit comments

Comments
 (0)