From f293f897e1a880568a8b2b7f4c28b5564abf1d51 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Thu, 19 Aug 2021 22:09:51 +0900 Subject: [PATCH 1/2] introduce `@noinfer` macro to tell the compiler to avoid excess inference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces new compiler annotation named `@noinfer`, which requests the compiler to avoid excess inference. In order to discuss `@noinfer`, it would help a lot to understand the behavior of `@nospecialize`. Its docstring says simply: > This is only a hint for the compiler to avoid excess code generation. More specifically, it works by _suppressing dispatches_ with complex runtime types of the annotated arguments. This could be understood with the example below: ```julia julia> function invokef(f, itr) local r = 0 r += f(itr[1]) r += f(itr[2]) r += f(itr[3]) r end; julia> _isa = isa; # just for the sake of explanation, global variable to prevent inling julia> f(a) = _isa(a, Function); julia> g(@nospecialize a) = _isa(a, Function); julia> dispatchonly = Any[sin, muladd, nothing]; # untyped container can cause excessive runtime dispatch julia> @code_typed invokef(f, dispatchonly) CodeInfo( 1 ─ %1 = π (0, Int64) │ %2 = Base.arrayref(true, itr, 1)::Any │ %3 = (f)(%2)::Any │ %4 = (%1 + %3)::Any │ %5 = Base.arrayref(true, itr, 2)::Any │ %6 = (f)(%5)::Any │ %7 = (%4 + %6)::Any │ %8 = Base.arrayref(true, itr, 3)::Any │ %9 = (f)(%8)::Any │ %10 = (%7 + %9)::Any └── return %10 ) => Any julia> @code_typed invokef(g, dispatchonly) CodeInfo( 1 ─ %1 = π (0, Int64) │ %2 = Base.arrayref(true, itr, 1)::Any │ %3 = invoke f(%2::Any)::Any │ %4 = (%1 + %3)::Any │ %5 = Base.arrayref(true, itr, 2)::Any │ %6 = invoke f(%5::Any)::Any │ %7 = (%4 + %6)::Any │ %8 = Base.arrayref(true, itr, 3)::Any │ %9 = invoke f(%8::Any)::Any │ %10 = (%7 + %9)::Any └── return %10 ) => Any ``` The calls of `f` remain to be `:call` expression (thus dispatched and compiled at runtime) while the calls of `g` are resolved as `:invoke` expressions. This is because `@nospecialize` requests the compiler to give up compiling `g` with concrete argument types but with precisely declared argument types, and in this way `invokef(g, dispatchonly)` will avoid runtime dispatches and accompanying JIT compilations (i.e. "excess code generation"). The problem here is, it influences dispatch only, does not intervene into inference in anyway. So there is still a possibility of "excess inference" when the compiler sees a considerable complexity of argument types during inference: ```julia julia> withinfernce = tuple(sin, muladd, "foo"); # typed container can cause excessive inference julia> @time @code_typed invokef(f, withinfernce); 0.000812 seconds (3.77 k allocations: 217.938 KiB, 94.34% compilation time) julia> @time @code_typed invokef(g, withinfernce); 0.000753 seconds (3.77 k allocations: 218.047 KiB, 92.42% compilation time) ``` The purpose of this PR is basically to provide a more drastic way to avoid excess compilation. Here are some ideas to implement the functionality: 1. make `@nospecialize` avoid inference also 2. add noinfer effect when `@nospecialize`d method is annotated as `@noinline` also 3. implement as `@pure`-like boolean annotation to request noinfer effect on top of `@nospecialize` 4. implement as annotation that is orthogonal to `@nospecialize` After trying 1 ~ 3., I decided to submit 3. for now, because I think the interface is ready to be experimented. This is almost same as what Jameson has done at . It turned out that this approach performs very badly because some of `@nospecialize`'d arguments still need inference to perform reasonably. For example, it's obvious that the following definition of `getindex(@nospecialize(t::Tuple), i::Int)` would perform very badly if `@nospecialize` blocks inference, because of a lack of useful type information for succeeding optimizations: The important observation is that we often use `@nospecialize` even when we expect inference to forward type and constant information. Adversely, we may be able to exploit the fact that we usually don't expect inference to forward information to a callee when we annotate it as `@noinline`. So the idea is to enable the inference suppression when `@nospecialize`'d method is annotated as `@noinline` also. It's a reasonable choice, and could be implemented efficiently after . But it sounds a bit weird to me to associate no infer effect with `@noinline`, and I also think there may be some cases we want to inline a method while _partially_ avoiding inference, e.g.: ```julia @noinline function twof(@nospecialize(f), n) # we really want not to inline this method body ? if occursin('+', string(typeof(f).name.name::Symbol)) 2 + n elseif occursin('*', string(typeof(f).name.name::Symbol)) 2n else zero(n) end end ``` So this is what this commit implements. It basically replaces the previous `@noinline` flag with newly-introduced annotation named `@noinfer`. It's still associated with `@nospecialize` and it only has effect when used together with `@nospecialize`, but now it's not associated to `@noinline` at least, and it would help us reason about the behavior of `@noinfer` and experiment its effect more reliably: ```julia Base.@noinfer function twof(@nospecialize(f), n) # the compiler may or not inline this method if occursin('+', string(typeof(f).name.name::Symbol)) 2 + n elseif occursin('*', string(typeof(f).name.name::Symbol)) 2n else zero(n) end end ``` Actually, we can have `@nospecialize` and `@noinfer` separately, and it would allow us to configure compilation strategies in a more fine-grained way. ```julia function noinfspec(Base.@noinfer(f), @nospecialize(g)) ... end ``` I'm fine with this approach, if initial experiments show `@noinfer` is useful. Co-authored-by: Mosè Giordano Co-authored-by: Tim Holy --- base/compiler/abstractinterpretation.jl | 10 ++- base/compiler/ssair/inlining.jl | 6 +- base/compiler/utilities.jl | 26 ++++++- base/essentials.jl | 3 +- base/expr.jl | 34 ++++++++- doc/src/base/base.md | 2 + src/ast.c | 2 + src/ircode.c | 10 +-- src/jltypes.c | 14 ++-- src/julia.h | 2 + src/julia_internal.h | 2 + src/method.c | 5 ++ stdlib/Serialization/src/Serialization.jl | 11 ++- test/compiler/inference.jl | 88 ++++++++++++++++++++--- test/compiler/irutils.jl | 18 +++-- 15 files changed, 205 insertions(+), 28 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index e817e0bd927fe1..a6e0463e34b3d9 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -544,6 +544,10 @@ function abstract_call_method(interp::AbstractInterpreter, sigtuple = unwrap_unionall(sig) sigtuple isa DataType || return MethodCallResult(Any, false, false, nothing, Effects()) + if is_noinfer(method) + sig = get_nospecialize_sig(method, sig, sparams) + end + # Limit argument type tuple growth of functions: # look through the parents list to see if there's a call to the same method # and from the same method. @@ -1075,7 +1079,11 @@ function maybe_get_const_prop_profitable(interp::AbstractInterpreter, return nothing end force |= all_overridden - mi = specialize_method(match; preexisting=!force) + if is_noinfer(method) + mi = specialize_method_noinfer(match; preexisting=!force) + else + mi = specialize_method(match; preexisting=!force) + end if mi === nothing add_remark!(interp, sv, "[constprop] Failed to specialize") return nothing diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 3b1cb2c46ce6e1..eed7aa99265308 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -952,7 +952,11 @@ function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, end # See if there exists a specialization for this method signature - mi = specialize_method(match; preexisting=true) # Union{Nothing, MethodInstance} + if is_noinfer(method) + mi = specialize_method_noinfer(match; preexisting=true) + else + mi = specialize_method(match; preexisting=true) + end if mi === nothing et = InliningEdgeTracker(state.et, invokesig) effects = info_effects(nothing, match, state) diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index bae8ef5bae2421..0d81008125ddc3 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -107,6 +107,11 @@ function is_inlineable_constant(@nospecialize(x)) return count_const_size(x) <= MAX_INLINE_CONST_SIZE end +is_nospecialized(method::Method) = method.nospecialize ≠ 0 + +is_noinfer(method::Method) = method.noinfer && is_nospecialized(method) +# is_noinfer(method::Method) = is_nospecialized(method) && is_declared_noinline(method) + ########################### # MethodInstance/CodeInfo # ########################### @@ -158,6 +163,19 @@ function get_compileable_sig(method::Method, @nospecialize(atype), sparams::Simp mt, atype, sparams, method) end +function get_nospecialize_sig(method::Method, @nospecialize(atype), sparams::SimpleVector) + if isa(atype, UnionAll) + atype, sparams = normalize_typevars(method, atype, sparams) + end + isa(atype, DataType) || return method.sig + mt = ccall(:jl_method_table_for, Any, (Any,), atype) + mt === nothing && return method.sig + # TODO allow uncompileable signatures to be returned here + sig = ccall(:jl_normalize_to_compilable_sig, Any, (Any, Any, Any, Any), + mt, atype, sparams, method) + return sig === nothing ? method.sig : sig +end + isa_compileable_sig(@nospecialize(atype), sparams::SimpleVector, method::Method) = !iszero(ccall(:jl_isa_compileable_sig, Int32, (Any, Any, Any), atype, sparams, method)) @@ -199,7 +217,8 @@ function normalize_typevars(method::Method, @nospecialize(atype), sparams::Simpl end # get a handle to the unique specialization object representing a particular instantiation of a call -function specialize_method(method::Method, @nospecialize(atype), sparams::SimpleVector; preexisting::Bool=false, compilesig::Bool=false) +function specialize_method(method::Method, @nospecialize(atype), sparams::SimpleVector; + preexisting::Bool=false, compilesig::Bool=false) if isa(atype, UnionAll) atype, sparams = normalize_typevars(method, atype, sparams) end @@ -225,6 +244,11 @@ function specialize_method(match::MethodMatch; kwargs...) return specialize_method(match.method, match.spec_types, match.sparams; kwargs...) end +function specialize_method_noinfer((; method, spec_types, sparams)::MethodMatch; kwargs...) + atype = get_nospecialize_sig(method, spec_types, sparams) + return specialize_method(method, atype, sparams; kwargs...) +end + """ is_declared_inline(method::Method) -> Bool diff --git a/base/essentials.jl b/base/essentials.jl index 829341c4823833..c059bba21588bd 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -85,7 +85,8 @@ f(y) = [x for x in y] !!! note `@nospecialize` affects code generation but not inference: it limits the diversity of the resulting native code, but it does not impose any limitations (beyond the - standard ones) on type-inference. + standard ones) on type-inference. Use [`Base.@noinfer`](@ref) together with + `@nospecialize` to additionally suppress inference. # Example diff --git a/base/expr.jl b/base/expr.jl index 5649303b41ef4a..c61a6605455ab9 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -339,7 +339,6 @@ macro noinline(x) return annotate_meta_def_or_block(x, :noinline) end - """ @constprop setting [ex] @@ -753,6 +752,39 @@ function compute_assumed_setting(@nospecialize(setting), val::Bool=true) end end +""" + Base.@noinfer function f(args...) + @nospecialize ... + ... + end + Base.@noinfer f(@nospecialize args...) = ... + +Tells the compiler to infer `f` using the declared types of `@nospecialize`d arguments. +This can be used to limit the number of compiler-generated specializations during inference. + +# Example + +```julia +julia> f(A::AbstractArray) = g(A) +f (generic function with 1 method) + +julia> @noinline Base.@noinfer g(@nospecialize(A::AbstractArray)) = A[1] +g (generic function with 1 method) + +julia> @code_typed f([1.0]) +CodeInfo( +1 ─ %1 = invoke Main.g(_2::AbstractArray)::Any +└── return %1 +) => Any +``` + +In this example, `f` will be inferred for each specific type of `A`, +but `g` will only be inferred once. +""" +macro noinfer(ex) + esc(isa(ex, Expr) ? pushmeta!(ex, :noinfer) : ex) +end + """ @propagate_inbounds diff --git a/doc/src/base/base.md b/doc/src/base/base.md index 7e45e2176478d3..72f0a635e6b389 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -285,6 +285,8 @@ Base.@inline Base.@noinline Base.@nospecialize Base.@specialize +Base.@noinfer +Base.@constprop Base.gensym Base.@gensym var"name" diff --git a/src/ast.c b/src/ast.c index 3f3d6176d342e5..91fbae5f80d5df 100644 --- a/src/ast.c +++ b/src/ast.c @@ -83,6 +83,7 @@ JL_DLLEXPORT jl_sym_t *jl_aggressive_constprop_sym; JL_DLLEXPORT jl_sym_t *jl_no_constprop_sym; JL_DLLEXPORT jl_sym_t *jl_purity_sym; JL_DLLEXPORT jl_sym_t *jl_nospecialize_sym; +JL_DLLEXPORT jl_sym_t *jl_noinfer_sym; JL_DLLEXPORT jl_sym_t *jl_macrocall_sym; JL_DLLEXPORT jl_sym_t *jl_colon_sym; JL_DLLEXPORT jl_sym_t *jl_hygienicscope_sym; @@ -342,6 +343,7 @@ void jl_init_common_symbols(void) jl_isdefined_sym = jl_symbol("isdefined"); jl_nospecialize_sym = jl_symbol("nospecialize"); jl_specialize_sym = jl_symbol("specialize"); + jl_noinfer_sym = jl_symbol("noinfer"); jl_optlevel_sym = jl_symbol("optlevel"); jl_compile_sym = jl_symbol("compile"); jl_force_compile_sym = jl_symbol("force_compile"); diff --git a/src/ircode.c b/src/ircode.c index f967dd1a29f510..6aa5199faf46a5 100644 --- a/src/ircode.c +++ b/src/ircode.c @@ -434,13 +434,14 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) } } -static jl_code_info_flags_t code_info_flags(uint8_t inferred, uint8_t propagate_inbounds, - uint8_t has_fcall, uint8_t inlining, uint8_t constprop) +static jl_code_info_flags_t code_info_flags(uint8_t inferred, uint8_t propagate_inbounds, uint8_t has_fcall, + uint8_t noinfer, uint8_t inlining, uint8_t constprop) { jl_code_info_flags_t flags; flags.bits.inferred = inferred; flags.bits.propagate_inbounds = propagate_inbounds; flags.bits.has_fcall = has_fcall; + flags.bits.noinfer = noinfer; flags.bits.inlining = inlining; flags.bits.constprop = constprop; return flags; @@ -780,8 +781,8 @@ JL_DLLEXPORT jl_array_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) 1 }; - jl_code_info_flags_t flags = code_info_flags(code->inferred, code->propagate_inbounds, - code->has_fcall, code->inlining, code->constprop); + jl_code_info_flags_t flags = code_info_flags(code->inferred, code->propagate_inbounds, code->has_fcall, + code->noinfer, code->inlining, code->constprop); write_uint8(s.s, flags.packed); write_uint8(s.s, code->purity.bits); write_uint16(s.s, code->inlining_cost); @@ -880,6 +881,7 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t code->inferred = flags.bits.inferred; code->propagate_inbounds = flags.bits.propagate_inbounds; code->has_fcall = flags.bits.has_fcall; + code->noinfer = flags.bits.noinfer; code->purity.bits = read_uint8(s.s); code->inlining_cost = read_uint16(s.s); diff --git a/src/jltypes.c b/src/jltypes.c index 2aa8385e744a34..4dbf13137fc5a5 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -2681,7 +2681,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_code_info_type = jl_new_datatype(jl_symbol("CodeInfo"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(21, + jl_perm_symsvec(22, "code", "codelocs", "ssavaluetypes", @@ -2699,11 +2699,12 @@ void jl_init_types(void) JL_GC_DISABLED "inferred", "propagate_inbounds", "has_fcall", + "noinfer", "inlining", "constprop", "purity", "inlining_cost"), - jl_svec(21, + jl_svec(22, jl_array_any_type, jl_array_int32_type, jl_any_type, @@ -2721,17 +2722,18 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type, jl_bool_type, jl_bool_type, + jl_bool_type, jl_uint8_type, jl_uint8_type, jl_uint8_type, jl_uint16_type), jl_emptysvec, - 0, 1, 20); + 0, 1, 22); jl_method_type = jl_new_datatype(jl_symbol("Method"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(28, + jl_perm_symsvec(29, "name", "module", "file", @@ -2758,9 +2760,10 @@ void jl_init_types(void) JL_GC_DISABLED "nkw", "isva", "is_for_opaque_closure", + "noinfer", "constprop", "purity"), - jl_svec(28, + jl_svec(29, jl_symbol_type, jl_module_type, jl_symbol_type, @@ -2787,6 +2790,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_int32_type, jl_bool_type, jl_bool_type, + jl_bool_type, jl_uint8_type, jl_uint8_type), jl_emptysvec, diff --git a/src/julia.h b/src/julia.h index 45363c8092bb13..ab0fdd85026123 100644 --- a/src/julia.h +++ b/src/julia.h @@ -286,6 +286,7 @@ typedef struct _jl_code_info_t { uint8_t inferred; uint8_t propagate_inbounds; uint8_t has_fcall; + uint8_t noinfer; // uint8 settings uint8_t inlining; // 0 = default; 1 = @inline; 2 = @noinline uint8_t constprop; // 0 = use heuristic; 1 = aggressive; 2 = none @@ -343,6 +344,7 @@ typedef struct _jl_method_t { // various boolean properties uint8_t isva; uint8_t is_for_opaque_closure; + uint8_t noinfer; // uint8 settings uint8_t constprop; // 0x00 = use heuristic; 0x01 = aggressive; 0x02 = none diff --git a/src/julia_internal.h b/src/julia_internal.h index 4f1a0b4513d8d7..6c0cdb3248777b 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -599,6 +599,7 @@ typedef struct { uint8_t inferred:1; uint8_t propagate_inbounds:1; uint8_t has_fcall:1; + uint8_t noinfer:1; uint8_t inlining:2; // 0 = use heuristic; 1 = aggressive; 2 = none uint8_t constprop:2; // 0 = use heuristic; 1 = aggressive; 2 = none } jl_code_info_flags_bitfield_t; @@ -1566,6 +1567,7 @@ extern JL_DLLEXPORT jl_sym_t *jl_aggressive_constprop_sym; extern JL_DLLEXPORT jl_sym_t *jl_no_constprop_sym; extern JL_DLLEXPORT jl_sym_t *jl_purity_sym; extern JL_DLLEXPORT jl_sym_t *jl_nospecialize_sym; +extern JL_DLLEXPORT jl_sym_t *jl_noinfer_sym; extern JL_DLLEXPORT jl_sym_t *jl_macrocall_sym; extern JL_DLLEXPORT jl_sym_t *jl_colon_sym; extern JL_DLLEXPORT jl_sym_t *jl_hygienicscope_sym; diff --git a/src/method.c b/src/method.c index 8b4c87a46ecd97..7cc50d29cfb248 100644 --- a/src/method.c +++ b/src/method.c @@ -320,6 +320,8 @@ static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) li->inlining = 2; else if (ma == (jl_value_t*)jl_propagate_inbounds_sym) li->propagate_inbounds = 1; + else if (ma == (jl_value_t*)jl_noinfer_sym) + li->noinfer = 1; else if (ma == (jl_value_t*)jl_aggressive_constprop_sym) li->constprop = 1; else if (ma == (jl_value_t*)jl_no_constprop_sym) @@ -476,6 +478,7 @@ JL_DLLEXPORT jl_code_info_t *jl_new_code_info_uninit(void) src->inferred = 0; src->propagate_inbounds = 0; src->has_fcall = 0; + src->noinfer = 0; src->edges = jl_nothing; src->constprop = 0; src->inlining = 0; @@ -679,6 +682,7 @@ static void jl_method_set_source(jl_method_t *m, jl_code_info_t *src) } } m->called = called; + m->noinfer = src->noinfer; m->constprop = src->constprop; m->purity.bits = src->purity.bits; jl_add_function_name_to_lineinfo(src, (jl_value_t*)m->name); @@ -808,6 +812,7 @@ JL_DLLEXPORT jl_method_t *jl_new_method_uninit(jl_module_t *module) m->primary_world = 1; m->deleted_world = ~(size_t)0; m->is_for_opaque_closure = 0; + m->noinfer = 0; m->constprop = 0; JL_MUTEX_INIT(&m->writelock); return m; diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index 630185ebd575a0..411f6c0ce5f05e 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -80,7 +80,7 @@ const TAGS = Any[ const NTAGS = length(TAGS) @assert NTAGS == 255 -const ser_version = 23 # do not make changes without bumping the version #! +const ser_version = 24 # do not make changes without bumping the version #! format_version(::AbstractSerializer) = ser_version format_version(s::Serializer) = s.version @@ -418,6 +418,7 @@ function serialize(s::AbstractSerializer, meth::Method) serialize(s, meth.nargs) serialize(s, meth.isva) serialize(s, meth.is_for_opaque_closure) + serialize(s, meth.noinfer) serialize(s, meth.constprop) serialize(s, meth.purity) if isdefined(meth, :source) @@ -1026,10 +1027,14 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) nargs = deserialize(s)::Int32 isva = deserialize(s)::Bool is_for_opaque_closure = false + noinfer = false constprop = purity = 0x00 template_or_is_opaque = deserialize(s) if isa(template_or_is_opaque, Bool) is_for_opaque_closure = template_or_is_opaque + if format_version(s) >= 24 + noinfer = deserialize(s)::Bool + end if format_version(s) >= 14 constprop = deserialize(s)::UInt8 end @@ -1054,6 +1059,7 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) meth.nargs = nargs meth.isva = isva meth.is_for_opaque_closure = is_for_opaque_closure + meth.noinfer = noinfer meth.constprop = constprop meth.purity = purity if template !== nothing @@ -1195,6 +1201,9 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) if format_version(s) >= 20 ci.has_fcall = deserialize(s) end + if format_version(s) >= 24 + ci.noinfer = deserialize(s)::Bool + end if format_version(s) >= 21 ci.inlining = deserialize(s)::UInt8 end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index d97f5d3a8a0951..bbc60db8a4b7b9 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -1164,25 +1164,18 @@ let typeargs = Tuple{Type{Int},Type{Int},Type{Int},Type{Int},Type{Int},Type{Int} @test only(Base.return_types(promote_type, typeargs)) === Type{Int} end -function count_specializations(method::Method) - specs = method.specializations - specs isa Core.MethodInstance && return 1 - n = count(!isnothing, specs::Core.SimpleVector) - return n -end - # demonstrate that inference can complete without waiting for MAX_TYPE_DEPTH copy_dims_out(out) = () copy_dims_out(out, dim::Int, tail...) = copy_dims_out((out..., dim), tail...) copy_dims_out(out, dim::Colon, tail...) = copy_dims_out((out..., dim), tail...) @test Base.return_types(copy_dims_out, (Tuple{}, Vararg{Union{Int,Colon}})) == Any[Tuple{}, Tuple{}, Tuple{}] -@test all(m -> 4 < count_specializations(m) < 15, methods(copy_dims_out)) # currently about 5 +@test all(m -> 4 < length(Base.specializations(m)) < 15, methods(copy_dims_out)) # currently about 5 copy_dims_pair(out) = () copy_dims_pair(out, dim::Int, tail...) = copy_dims_pair(out => dim, tail...) copy_dims_pair(out, dim::Colon, tail...) = copy_dims_pair(out => dim, tail...) @test Base.return_types(copy_dims_pair, (Tuple{}, Vararg{Union{Int,Colon}})) == Any[Tuple{}, Tuple{}, Tuple{}] -@test all(m -> 3 < count_specializations(m) < 15, methods(copy_dims_pair)) # currently about 5 +@test all(m -> 3 < length(Base.specializations(m)) < 15, methods(copy_dims_pair)) # currently about 5 # splatting an ::Any should still allow inference to use types of parameters preceding it f22364(::Int, ::Any...) = 0 @@ -4157,6 +4150,83 @@ Base.getproperty(x::Interface41024Extended, sym::Symbol) = x.x end |> only === Int +count_specialization(@nospecialize f) = length(Base.specializations(only(methods(f)))) + +function invokef(f, itr) + local r = 0 + r += f(itr[1]) + r += f(itr[2]) + r += f(itr[3]) + r += f(itr[4]) + r += f(itr[5]) + r +end +global inline_checker = c -> c # untyped global prevents inlining +# if `f` is inlined, `GlobalRef(m, :inline_checker)` should appear within the body of `invokef` +function is_inline_checker(@nospecialize stmt) + isa(stmt, GlobalRef) && stmt.name === :inline_checker +end + +function nospecialize(@nospecialize a) + c = isa(a, Function) + inline_checker(c) +end + +@inline function inline_nospecialize(@nospecialize a) + c = isa(a, Function) + inline_checker(c) +end + +Base.@noinfer function noinfer(@nospecialize a) + c = isa(a, Function) + inline_checker(c) +end + +# `@noinfer` should still allow inlinining +Base.@noinfer @inline function inline_noinfer(@nospecialize a) + c = isa(a, Function) + inline_checker(c) +end + +@testset "compilation annotations" begin + dispatchonly = Any[sin, muladd, "foo", nothing, Dict] # untyped container can cause excessive runtime dispatch + withinfernce = tuple(sin, muladd, "foo", nothing, Dict) # typed container can cause excessive inference + + @testset "@nospecialize" begin + # `@nospecialize` should suppress runtime dispatches of `nospecialize` + invokef(nospecialize, dispatchonly) + @test count_specialization(nospecialize) == 1 + # `@nospecialize` should allow inference to happen + invokef(nospecialize, withinfernce) + @test count_specialization(nospecialize) == 6 + @test !any(is_inline_checker, @get_code invokef(nospecialize, dispatchonly)) + + # `@nospecialize` should allow inlinining + invokef(inline_nospecialize, dispatchonly) + @test count_specialization(inline_nospecialize) == 1 + invokef(inline_nospecialize, withinfernce) + @test count_specialization(inline_nospecialize) == 6 + @test any(is_inline_checker, @get_code invokef(inline_nospecialize, dispatchonly)) + end + + @testset "@noinfer" begin + # `@nospecialize` should suppress runtime dispatches of `nospecialize` + invokef(noinfer, dispatchonly) + @test count_specialization(noinfer) == 1 + # `@noinfer` suppresses inference also + invokef(noinfer, withinfernce) + @test count_specialization(noinfer) == 1 + @test !any(is_inline_checker, @get_code invokef(noinfer, dispatchonly)) + + # `@noinfer` should still allow inlinining + invokef(inline_noinfer, dispatchonly) + @test count_specialization(inline_noinfer) == 1 + invokef(inline_noinfer, withinfernce) + @test count_specialization(inline_noinfer) == 1 + @test any(is_inline_checker, @get_code invokef(inline_noinfer, dispatchonly)) + end +end + @testset "fieldtype for unions" begin # e.g. issue #40177 f40177(::Type{T}) where {T} = fieldtype(T, 1) for T in [ diff --git a/test/compiler/irutils.jl b/test/compiler/irutils.jl index 95ac0d555ef889..00de9b2472de4b 100644 --- a/test/compiler/irutils.jl +++ b/test/compiler/irutils.jl @@ -1,10 +1,17 @@ -import Core: CodeInfo, ReturnNode, MethodInstance -import Core.Compiler: IRCode, IncrementalCompact, VarState, argextype, singleton_type -import Base.Meta: isexpr +using Core: CodeInfo, ReturnNode, MethodInstance +using Core.Compiler: IRCode, IncrementalCompact, singleton_type, VarState +using Base.Meta: isexpr +using InteractiveUtils: gen_call_with_extracted_types_and_kwargs -argextype(@nospecialize args...) = argextype(args..., VarState[]) +argextype(@nospecialize args...) = Core.Compiler.argextype(args..., VarState[]) code_typed1(args...; kwargs...) = first(only(code_typed(args...; kwargs...)))::CodeInfo +macro code_typed1(ex0...) + return gen_call_with_extracted_types_and_kwargs(__module__, :code_typed1, ex0) +end get_code(args...; kwargs...) = code_typed1(args...; kwargs...).code +macro get_code(ex0...) + return gen_call_with_extracted_types_and_kwargs(__module__, :get_code, ex0) +end # check if `x` is a statement with a given `head` isnew(@nospecialize x) = isexpr(x, :new) @@ -45,3 +52,6 @@ function fully_eliminated(@nospecialize args...; retval=(@__FILE__), kwargs...) return length(code) == 1 && isreturn(code[1]) end end +macro fully_eliminated(ex0...) + return gen_call_with_extracted_types_and_kwargs(__module__, :fully_eliminated, ex0) +end From 61b6236b40eb2345c29b7f7dcd76ded2ddb6ed6f Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Wed, 12 Apr 2023 19:19:35 +0900 Subject: [PATCH 2/2] experiment `@noinfer` on `Core.Compiler` --- base/compiler/abstractinterpretation.jl | 30 +++++------ base/compiler/abstractlattice.jl | 50 +++++++++---------- base/compiler/typelattice.jl | 66 ++++++++++++------------- base/compiler/typelimits.jl | 23 +++++---- base/compiler/utilities.jl | 4 +- 5 files changed, 86 insertions(+), 87 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index a6e0463e34b3d9..daf87525e0228e 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2667,18 +2667,18 @@ struct BestguessInfo{Interp<:AbstractInterpreter} end end -function widenreturn(@nospecialize(rt), info::BestguessInfo) +@noinfer function widenreturn(@nospecialize(rt), info::BestguessInfo) return widenreturn(typeinf_lattice(info.interp), rt, info) end -function widenreturn(𝕃ᵢ::AbstractLattice, @nospecialize(rt), info::BestguessInfo) +@noinfer function widenreturn(𝕃ᵢ::AbstractLattice, @nospecialize(rt), info::BestguessInfo) return widenreturn(widenlattice(𝕃ᵢ), rt, info) end -function widenreturn_noslotwrapper(𝕃ᵢ::AbstractLattice, @nospecialize(rt), info::BestguessInfo) +@noinfer function widenreturn_noslotwrapper(𝕃ᵢ::AbstractLattice, @nospecialize(rt), info::BestguessInfo) return widenreturn_noslotwrapper(widenlattice(𝕃ᵢ), rt, info) end -function widenreturn(𝕃ᵢ::MustAliasesLattice, @nospecialize(rt), info::BestguessInfo) +@noinfer function widenreturn(𝕃ᵢ::MustAliasesLattice, @nospecialize(rt), info::BestguessInfo) if isa(rt, MustAlias) if 1 ≤ rt.slot ≤ info.nargs rt = InterMustAlias(rt) @@ -2690,7 +2690,7 @@ function widenreturn(𝕃ᵢ::MustAliasesLattice, @nospecialize(rt), info::Bestg return widenreturn(widenlattice(𝕃ᵢ), rt, info) end -function widenreturn(𝕃ᵢ::ConditionalsLattice, @nospecialize(rt), info::BestguessInfo) +@noinfer function widenreturn(𝕃ᵢ::ConditionalsLattice, @nospecialize(rt), info::BestguessInfo) ⊑ᵢ = ⊑(𝕃ᵢ) if !(⊑(ipo_lattice(info.interp), info.bestguess, Bool)) || info.bestguess === Bool # give up inter-procedural constraint back-propagation @@ -2727,7 +2727,7 @@ function widenreturn(𝕃ᵢ::ConditionalsLattice, @nospecialize(rt), info::Best isa(rt, InterConditional) && return rt return widenreturn(widenlattice(𝕃ᵢ), rt, info) end -function bool_rt_to_conditional(@nospecialize(rt), info::BestguessInfo) +@noinfer function bool_rt_to_conditional(@nospecialize(rt), info::BestguessInfo) bestguess = info.bestguess if isa(bestguess, InterConditional) # if the bestguess so far is already `Conditional`, try to convert @@ -2745,7 +2745,7 @@ function bool_rt_to_conditional(@nospecialize(rt), info::BestguessInfo) end return rt end -function bool_rt_to_conditional(@nospecialize(rt), slot_id::Int, info::BestguessInfo) +@noinfer function bool_rt_to_conditional(@nospecialize(rt), slot_id::Int, info::BestguessInfo) ⊑ᵢ = ⊑(typeinf_lattice(info.interp)) old = info.slottypes[slot_id] new = widenslotwrapper(info.changes[slot_id].typ) # avoid nested conditional @@ -2764,13 +2764,13 @@ function bool_rt_to_conditional(@nospecialize(rt), slot_id::Int, info::Bestguess return rt end -function widenreturn(𝕃ᵢ::PartialsLattice, @nospecialize(rt), info::BestguessInfo) +@noinfer function widenreturn(𝕃ᵢ::PartialsLattice, @nospecialize(rt), info::BestguessInfo) return widenreturn_partials(𝕃ᵢ, rt, info) end -function widenreturn_noslotwrapper(𝕃ᵢ::PartialsLattice, @nospecialize(rt), info::BestguessInfo) +@noinfer function widenreturn_noslotwrapper(𝕃ᵢ::PartialsLattice, @nospecialize(rt), info::BestguessInfo) return widenreturn_partials(𝕃ᵢ, rt, info) end -function widenreturn_partials(𝕃ᵢ::PartialsLattice, @nospecialize(rt), info::BestguessInfo) +@noinfer function widenreturn_partials(𝕃ᵢ::PartialsLattice, @nospecialize(rt), info::BestguessInfo) if isa(rt, PartialStruct) fields = copy(rt.fields) local anyrefine = false @@ -2793,21 +2793,21 @@ function widenreturn_partials(𝕃ᵢ::PartialsLattice, @nospecialize(rt), info: return widenreturn(widenlattice(𝕃ᵢ), rt, info) end -function widenreturn(::ConstsLattice, @nospecialize(rt), ::BestguessInfo) +@noinfer function widenreturn(::ConstsLattice, @nospecialize(rt), ::BestguessInfo) return widenreturn_consts(rt) end -function widenreturn_noslotwrapper(::ConstsLattice, @nospecialize(rt), ::BestguessInfo) +@noinfer function widenreturn_noslotwrapper(::ConstsLattice, @nospecialize(rt), ::BestguessInfo) return widenreturn_consts(rt) end -function widenreturn_consts(@nospecialize(rt)) +@noinfer function widenreturn_consts(@nospecialize(rt)) isa(rt, Const) && return rt return widenconst(rt) end -function widenreturn(::JLTypeLattice, @nospecialize(rt), ::BestguessInfo) +@noinfer function widenreturn(::JLTypeLattice, @nospecialize(rt), ::BestguessInfo) return widenconst(rt) end -function widenreturn_noslotwrapper(::JLTypeLattice, @nospecialize(rt), ::BestguessInfo) +@noinfer function widenreturn_noslotwrapper(::JLTypeLattice, @nospecialize(rt), ::BestguessInfo) return widenconst(rt) end diff --git a/base/compiler/abstractlattice.jl b/base/compiler/abstractlattice.jl index a84050816cb210..d9aa802074ede8 100644 --- a/base/compiler/abstractlattice.jl +++ b/base/compiler/abstractlattice.jl @@ -161,7 +161,7 @@ If `𝕃` is `JLTypeLattice`, this is equivalent to subtyping. """ function ⊑ end -⊑(::JLTypeLattice, @nospecialize(a::Type), @nospecialize(b::Type)) = a <: b +@noinfer ⊑(::JLTypeLattice, @nospecialize(a::Type), @nospecialize(b::Type)) = a <: b """ ⊏(𝕃::AbstractLattice, a, b) -> Bool @@ -169,7 +169,7 @@ function ⊑ end The strict partial order over the type inference lattice. This is defined as the irreflexive kernel of `⊑`. """ -⊏(𝕃::AbstractLattice, @nospecialize(a), @nospecialize(b)) = ⊑(𝕃, a, b) && !⊑(𝕃, b, a) +@noinfer ⊏(𝕃::AbstractLattice, @nospecialize(a), @nospecialize(b)) = ⊑(𝕃, a, b) && !⊑(𝕃, b, a) """ ⋤(𝕃::AbstractLattice, a, b) -> Bool @@ -177,7 +177,7 @@ This is defined as the irreflexive kernel of `⊑`. This order could be used as a slightly more efficient version of the strict order `⊏`, where we can safely assume `a ⊑ b` holds. """ -⋤(𝕃::AbstractLattice, @nospecialize(a), @nospecialize(b)) = !⊑(𝕃, b, a) +@noinfer ⋤(𝕃::AbstractLattice, @nospecialize(a), @nospecialize(b)) = !⊑(𝕃, b, a) """ is_lattice_equal(𝕃::AbstractLattice, a, b) -> Bool @@ -186,7 +186,7 @@ Check if two lattice elements are partial order equivalent. This is basically `a ⊑ b && b ⊑ a` in the lattice of `𝕃` but (optionally) with extra performance optimizations. """ -function is_lattice_equal(𝕃::AbstractLattice, @nospecialize(a), @nospecialize(b)) +@noinfer function is_lattice_equal(𝕃::AbstractLattice, @nospecialize(a), @nospecialize(b)) a === b && return true return ⊑(𝕃, a, b) && ⊑(𝕃, b, a) end @@ -197,14 +197,14 @@ end Determines whether the given lattice element `t` of `𝕃` has non-trivial extended lattice information that would not be available from the type itself. """ -has_nontrivial_extended_info(𝕃::AbstractLattice, @nospecialize t) = +@noinfer has_nontrivial_extended_info(𝕃::AbstractLattice, @nospecialize t) = has_nontrivial_extended_info(widenlattice(𝕃), t) -function has_nontrivial_extended_info(𝕃::PartialsLattice, @nospecialize t) +@noinfer function has_nontrivial_extended_info(𝕃::PartialsLattice, @nospecialize t) isa(t, PartialStruct) && return true isa(t, PartialOpaque) && return true return has_nontrivial_extended_info(widenlattice(𝕃), t) end -function has_nontrivial_extended_info(𝕃::ConstsLattice, @nospecialize t) +@noinfer function has_nontrivial_extended_info(𝕃::ConstsLattice, @nospecialize t) isa(t, PartialTypeVar) && return true if isa(t, Const) val = t.val @@ -212,7 +212,7 @@ function has_nontrivial_extended_info(𝕃::ConstsLattice, @nospecialize t) end return has_nontrivial_extended_info(widenlattice(𝕃), t) end -has_nontrivial_extended_info(::JLTypeLattice, @nospecialize(t)) = false +@noinfer has_nontrivial_extended_info(::JLTypeLattice, @nospecialize(t)) = false """ is_const_prop_profitable_arg(𝕃::AbstractLattice, t) -> Bool @@ -220,9 +220,9 @@ has_nontrivial_extended_info(::JLTypeLattice, @nospecialize(t)) = false Determines whether the given lattice element `t` of `𝕃` has new extended lattice information that should be forwarded along with constant propagation. """ -is_const_prop_profitable_arg(𝕃::AbstractLattice, @nospecialize t) = +@noinfer is_const_prop_profitable_arg(𝕃::AbstractLattice, @nospecialize t) = is_const_prop_profitable_arg(widenlattice(𝕃), t) -function is_const_prop_profitable_arg(𝕃::PartialsLattice, @nospecialize t) +@noinfer function is_const_prop_profitable_arg(𝕃::PartialsLattice, @nospecialize t) if isa(t, PartialStruct) return true # might be a bit aggressive, may want to enable some check like follows: # for i = 1:length(t.fields) @@ -236,7 +236,7 @@ function is_const_prop_profitable_arg(𝕃::PartialsLattice, @nospecialize t) isa(t, PartialOpaque) && return true return is_const_prop_profitable_arg(widenlattice(𝕃), t) end -function is_const_prop_profitable_arg(𝕃::ConstsLattice, @nospecialize t) +@noinfer function is_const_prop_profitable_arg(𝕃::ConstsLattice, @nospecialize t) if isa(t, Const) # don't consider mutable values useful constants val = t.val @@ -245,24 +245,24 @@ function is_const_prop_profitable_arg(𝕃::ConstsLattice, @nospecialize t) isa(t, PartialTypeVar) && return false # this isn't forwardable return is_const_prop_profitable_arg(widenlattice(𝕃), t) end -is_const_prop_profitable_arg(::JLTypeLattice, @nospecialize t) = false +@noinfer is_const_prop_profitable_arg(::JLTypeLattice, @nospecialize t) = false -is_forwardable_argtype(𝕃::AbstractLattice, @nospecialize(x)) = +@noinfer is_forwardable_argtype(𝕃::AbstractLattice, @nospecialize(x)) = is_forwardable_argtype(widenlattice(𝕃), x) -function is_forwardable_argtype(𝕃::ConditionalsLattice, @nospecialize x) +@noinfer function is_forwardable_argtype(𝕃::ConditionalsLattice, @nospecialize x) isa(x, Conditional) && return true return is_forwardable_argtype(widenlattice(𝕃), x) end -function is_forwardable_argtype(𝕃::PartialsLattice, @nospecialize x) +@noinfer function is_forwardable_argtype(𝕃::PartialsLattice, @nospecialize x) isa(x, PartialStruct) && return true isa(x, PartialOpaque) && return true return is_forwardable_argtype(widenlattice(𝕃), x) end -function is_forwardable_argtype(𝕃::ConstsLattice, @nospecialize x) +@noinfer function is_forwardable_argtype(𝕃::ConstsLattice, @nospecialize x) isa(x, Const) && return true return is_forwardable_argtype(widenlattice(𝕃), x) end -function is_forwardable_argtype(::JLTypeLattice, @nospecialize x) +@noinfer function is_forwardable_argtype(::JLTypeLattice, @nospecialize x) return false end @@ -281,9 +281,9 @@ External lattice `𝕃ᵢ::ExternalLattice` may overload: """ function widenreturn end, function widenreturn_noslotwrapper end -is_valid_lattice(𝕃::AbstractLattice, @nospecialize(elem)) = +@noinfer is_valid_lattice(𝕃::AbstractLattice, @nospecialize(elem)) = is_valid_lattice_norec(𝕃, elem) && is_valid_lattice(widenlattice(𝕃), elem) -is_valid_lattice(𝕃::JLTypeLattice, @nospecialize(elem)) = is_valid_lattice_norec(𝕃, elem) +@noinfer is_valid_lattice(𝕃::JLTypeLattice, @nospecialize(elem)) = is_valid_lattice_norec(𝕃, elem) has_conditional(𝕃::AbstractLattice) = has_conditional(widenlattice(𝕃)) has_conditional(::AnyConditionalsLattice) = true @@ -306,12 +306,12 @@ has_extended_unionsplit(::JLTypeLattice) = false const fallback_lattice = InferenceLattice(BaseInferenceLattice.instance) const fallback_ipo_lattice = InferenceLattice(IPOResultLattice.instance) -⊑(@nospecialize(a), @nospecialize(b)) = ⊑(fallback_lattice, a, b) -tmeet(@nospecialize(a), @nospecialize(b)) = tmeet(fallback_lattice, a, b) -tmerge(@nospecialize(a), @nospecialize(b)) = tmerge(fallback_lattice, a, b) -⊏(@nospecialize(a), @nospecialize(b)) = ⊏(fallback_lattice, a, b) -⋤(@nospecialize(a), @nospecialize(b)) = ⋤(fallback_lattice, a, b) -is_lattice_equal(@nospecialize(a), @nospecialize(b)) = is_lattice_equal(fallback_lattice, a, b) +@noinfer @nospecialize(a) ⊑ @nospecialize(b) = ⊑(fallback_lattice, a, b) +@noinfer @nospecialize(a) ⊏ @nospecialize(b) = ⊏(fallback_lattice, a, b) +@noinfer @nospecialize(a) ⋤ @nospecialize(b) = ⋤(fallback_lattice, a, b) +@noinfer tmeet(@nospecialize(a), @nospecialize(b)) = tmeet(fallback_lattice, a, b) +@noinfer tmerge(@nospecialize(a), @nospecialize(b)) = tmerge(fallback_lattice, a, b) +@noinfer is_lattice_equal(@nospecialize(a), @nospecialize(b)) = is_lattice_equal(fallback_lattice, a, b) # Widenlattice with argument widenlattice(::JLTypeLattice, @nospecialize(t)) = widenconst(t) diff --git a/base/compiler/typelattice.jl b/base/compiler/typelattice.jl index 700a6d333cbc42..7126ca95025bd3 100644 --- a/base/compiler/typelattice.jl +++ b/base/compiler/typelattice.jl @@ -244,7 +244,7 @@ const CompilerTypes = Union{MaybeUndef, Const, Conditional, MustAlias, NotFound, # slot wrappers # ============= -function assert_nested_slotwrapper(@nospecialize t) +@noinfer function assert_nested_slotwrapper(@nospecialize t) @assert !(t isa Conditional) "found nested Conditional" @assert !(t isa InterConditional) "found nested InterConditional" @assert !(t isa MustAlias) "found nested MustAlias" @@ -252,7 +252,7 @@ function assert_nested_slotwrapper(@nospecialize t) return t end -function widenslotwrapper(@nospecialize typ) +@noinfer function widenslotwrapper(@nospecialize typ) if isa(typ, AnyConditional) return widenconditional(typ) elseif isa(typ, AnyMustAlias) @@ -261,7 +261,7 @@ function widenslotwrapper(@nospecialize typ) return typ end -function widenwrappedslotwrapper(@nospecialize typ) +@noinfer function widenwrappedslotwrapper(@nospecialize typ) if isa(typ, LimitedAccuracy) return LimitedAccuracy(widenslotwrapper(typ.typ), typ.causes) end @@ -271,7 +271,7 @@ end # Conditional # =========== -function widenconditional(@nospecialize typ) +@noinfer function widenconditional(@nospecialize typ) if isa(typ, AnyConditional) if typ.thentype === Union{} return Const(false) @@ -285,7 +285,7 @@ function widenconditional(@nospecialize typ) end return typ end -function widenwrappedconditional(@nospecialize typ) +@noinfer function widenwrappedconditional(@nospecialize typ) if isa(typ, LimitedAccuracy) return LimitedAccuracy(widenconditional(typ.typ), typ.causes) end @@ -294,7 +294,7 @@ end # `Conditional` and `InterConditional` are valid in opposite contexts # (i.e. local inference and inter-procedural call), as such they will never be compared -function issubconditional(lattice::AbstractLattice, a::C, b::C) where {C<:AnyConditional} +@noinfer function issubconditional(lattice::AbstractLattice, a::C, b::C) where {C<:AnyConditional} if is_same_conditionals(a, b) if ⊑(lattice, a.thentype, b.thentype) if ⊑(lattice, a.elsetype, b.elsetype) @@ -307,7 +307,7 @@ end is_same_conditionals(a::C, b::C) where C<:AnyConditional = a.slot == b.slot -is_lattice_bool(lattice::AbstractLattice, @nospecialize(typ)) = typ !== Bottom && ⊑(lattice, typ, Bool) +@noinfer is_lattice_bool(lattice::AbstractLattice, @nospecialize(typ)) = typ !== Bottom && ⊑(lattice, typ, Bool) maybe_extract_const_bool(c::Const) = (val = c.val; isa(val, Bool)) ? val : nothing function maybe_extract_const_bool(c::AnyConditional) @@ -315,12 +315,12 @@ function maybe_extract_const_bool(c::AnyConditional) (c.elsetype === Bottom && !(c.thentype === Bottom)) && return true nothing end -maybe_extract_const_bool(@nospecialize c) = nothing +@noinfer maybe_extract_const_bool(@nospecialize c) = nothing # MustAlias # ========= -function widenmustalias(@nospecialize typ) +@noinfer function widenmustalias(@nospecialize typ) if isa(typ, AnyMustAlias) return typ.fldtyp elseif isa(typ, LimitedAccuracy) @@ -329,13 +329,13 @@ function widenmustalias(@nospecialize typ) return typ end -function isalreadyconst(@nospecialize t) +@noinfer function isalreadyconst(@nospecialize t) isa(t, Const) && return true isa(t, DataType) && isdefined(t, :instance) && return true return isconstType(t) end -function maybe_const_fldidx(@nospecialize(objtyp), @nospecialize(fldval)) +@noinfer function maybe_const_fldidx(@nospecialize(objtyp), @nospecialize(fldval)) t = widenconst(objtyp) if isa(fldval, Int) fldidx = fldval @@ -352,7 +352,7 @@ function maybe_const_fldidx(@nospecialize(objtyp), @nospecialize(fldval)) return fldidx end -function form_mustalias_conditional(alias::MustAlias, @nospecialize(thentype), @nospecialize(elsetype)) +@noinfer function form_mustalias_conditional(alias::MustAlias, @nospecialize(thentype), @nospecialize(elsetype)) (; slot, vartyp, fldidx) = alias if isa(vartyp, PartialStruct) fields = vartyp.fields @@ -401,7 +401,7 @@ ignorelimited(typ::LimitedAccuracy) = typ.typ # lattice order # ============= -function ⊑(lattice::InferenceLattice, @nospecialize(a), @nospecialize(b)) +@noinfer function ⊑(lattice::InferenceLattice, @nospecialize(a), @nospecialize(b)) r = ⊑(widenlattice(lattice), ignorelimited(a), ignorelimited(b)) r || return false isa(b, LimitedAccuracy) || return true @@ -420,7 +420,7 @@ function ⊑(lattice::InferenceLattice, @nospecialize(a), @nospecialize(b)) return b.causes ⊆ a.causes end -function ⊑(lattice::OptimizerLattice, @nospecialize(a), @nospecialize(b)) +@noinfer function ⊑(lattice::OptimizerLattice, @nospecialize(a), @nospecialize(b)) if isa(a, MaybeUndef) isa(b, MaybeUndef) || return false a, b = a.typ, b.typ @@ -430,7 +430,7 @@ function ⊑(lattice::OptimizerLattice, @nospecialize(a), @nospecialize(b)) return ⊑(widenlattice(lattice), a, b) end -function ⊑(lattice::AnyConditionalsLattice, @nospecialize(a), @nospecialize(b)) +@noinfer function ⊑(lattice::AnyConditionalsLattice, @nospecialize(a), @nospecialize(b)) # Fast paths for common cases b === Any && return true a === Any && return false @@ -450,7 +450,7 @@ function ⊑(lattice::AnyConditionalsLattice, @nospecialize(a), @nospecialize(b) return ⊑(widenlattice(lattice), a, b) end -function ⊑(𝕃::AnyMustAliasesLattice, @nospecialize(a), @nospecialize(b)) +@noinfer function ⊑(𝕃::AnyMustAliasesLattice, @nospecialize(a), @nospecialize(b)) MustAliasT = isa(𝕃, MustAliasesLattice) ? MustAlias : InterMustAlias if isa(a, MustAliasT) if isa(b, MustAliasT) @@ -463,7 +463,7 @@ function ⊑(𝕃::AnyMustAliasesLattice, @nospecialize(a), @nospecialize(b)) return ⊑(widenlattice(𝕃), a, b) end -function ⊑(lattice::PartialsLattice, @nospecialize(a), @nospecialize(b)) +@noinfer function ⊑(lattice::PartialsLattice, @nospecialize(a), @nospecialize(b)) if isa(a, PartialStruct) if isa(b, PartialStruct) if !(length(a.fields) == length(b.fields) && a.typ <: b.typ) @@ -526,7 +526,7 @@ function ⊑(lattice::PartialsLattice, @nospecialize(a), @nospecialize(b)) return ⊑(widenlattice(lattice), a, b) end -function ⊑(lattice::ConstsLattice, @nospecialize(a), @nospecialize(b)) +@noinfer function ⊑(lattice::ConstsLattice, @nospecialize(a), @nospecialize(b)) if isa(a, Const) if isa(b, Const) return a.val === b.val @@ -548,7 +548,7 @@ function ⊑(lattice::ConstsLattice, @nospecialize(a), @nospecialize(b)) return ⊑(widenlattice(lattice), a, b) end -function is_lattice_equal(lattice::InferenceLattice, @nospecialize(a), @nospecialize(b)) +@noinfer function is_lattice_equal(lattice::InferenceLattice, @nospecialize(a), @nospecialize(b)) if isa(a, LimitedAccuracy) isa(b, LimitedAccuracy) || return false a.causes == b.causes || return false @@ -560,7 +560,7 @@ function is_lattice_equal(lattice::InferenceLattice, @nospecialize(a), @nospecia return is_lattice_equal(widenlattice(lattice), a, b) end -function is_lattice_equal(lattice::OptimizerLattice, @nospecialize(a), @nospecialize(b)) +@noinfer function is_lattice_equal(lattice::OptimizerLattice, @nospecialize(a), @nospecialize(b)) if isa(a, MaybeUndef) || isa(b, MaybeUndef) # TODO: Unwrap these and recurse to is_lattice_equal return ⊑(lattice, a, b) && ⊑(lattice, b, a) @@ -568,7 +568,7 @@ function is_lattice_equal(lattice::OptimizerLattice, @nospecialize(a), @nospecia return is_lattice_equal(widenlattice(lattice), a, b) end -function is_lattice_equal(lattice::AnyConditionalsLattice, @nospecialize(a), @nospecialize(b)) +@noinfer function is_lattice_equal(lattice::AnyConditionalsLattice, @nospecialize(a), @nospecialize(b)) ConditionalT = isa(lattice, ConditionalsLattice) ? Conditional : InterConditional if isa(a, ConditionalT) || isa(b, ConditionalT) # TODO: Unwrap these and recurse to is_lattice_equal @@ -577,7 +577,7 @@ function is_lattice_equal(lattice::AnyConditionalsLattice, @nospecialize(a), @no return is_lattice_equal(widenlattice(lattice), a, b) end -function is_lattice_equal(lattice::PartialsLattice, @nospecialize(a), @nospecialize(b)) +@noinfer function is_lattice_equal(lattice::PartialsLattice, @nospecialize(a), @nospecialize(b)) if isa(a, PartialStruct) isa(b, PartialStruct) || return false length(a.fields) == length(b.fields) || return false @@ -600,7 +600,7 @@ function is_lattice_equal(lattice::PartialsLattice, @nospecialize(a), @nospecial return is_lattice_equal(widenlattice(lattice), a, b) end -function is_lattice_equal(lattice::ConstsLattice, @nospecialize(a), @nospecialize(b)) +@noinfer function is_lattice_equal(lattice::ConstsLattice, @nospecialize(a), @nospecialize(b)) a === b && return true if a isa Const if issingletontype(b) @@ -625,7 +625,7 @@ end # lattice operations # ================== -function tmeet(lattice::PartialsLattice, @nospecialize(v), @nospecialize(t::Type)) +@noinfer function tmeet(lattice::PartialsLattice, @nospecialize(v), @nospecialize(t::Type)) if isa(v, PartialStruct) has_free_typevars(t) && return v widev = widenconst(v) @@ -663,7 +663,7 @@ function tmeet(lattice::PartialsLattice, @nospecialize(v), @nospecialize(t::Type return tmeet(widenlattice(lattice), v, t) end -function tmeet(lattice::ConstsLattice, @nospecialize(v), @nospecialize(t::Type)) +@noinfer function tmeet(lattice::ConstsLattice, @nospecialize(v), @nospecialize(t::Type)) if isa(v, Const) if !has_free_typevars(t) && !isa(v.val, t) return Bottom @@ -673,7 +673,7 @@ function tmeet(lattice::ConstsLattice, @nospecialize(v), @nospecialize(t::Type)) tmeet(widenlattice(lattice), widenconst(v), t) end -function tmeet(lattice::ConditionalsLattice, @nospecialize(v), @nospecialize(t::Type)) +@noinfer function tmeet(lattice::ConditionalsLattice, @nospecialize(v), @nospecialize(t::Type)) if isa(v, Conditional) if !(Bool <: t) return Bottom @@ -683,33 +683,33 @@ function tmeet(lattice::ConditionalsLattice, @nospecialize(v), @nospecialize(t:: tmeet(widenlattice(lattice), v, t) end -function tmeet(𝕃::MustAliasesLattice, @nospecialize(v), @nospecialize(t::Type)) +@noinfer function tmeet(𝕃::MustAliasesLattice, @nospecialize(v), @nospecialize(t::Type)) if isa(v, MustAlias) v = widenmustalias(v) end return tmeet(widenlattice(𝕃), v, t) end -function tmeet(lattice::InferenceLattice, @nospecialize(v), @nospecialize(t::Type)) +@noinfer function tmeet(lattice::InferenceLattice, @nospecialize(v), @nospecialize(t::Type)) # TODO: This can probably happen and should be handled @assert !isa(v, LimitedAccuracy) tmeet(widenlattice(lattice), v, t) end -function tmeet(lattice::InterConditionalsLattice, @nospecialize(v), @nospecialize(t::Type)) +@noinfer function tmeet(lattice::InterConditionalsLattice, @nospecialize(v), @nospecialize(t::Type)) # TODO: This can probably happen and should be handled @assert !isa(v, AnyConditional) tmeet(widenlattice(lattice), v, t) end -function tmeet(𝕃::InterMustAliasesLattice, @nospecialize(v), @nospecialize(t::Type)) +@noinfer function tmeet(𝕃::InterMustAliasesLattice, @nospecialize(v), @nospecialize(t::Type)) if isa(v, InterMustAlias) v = widenmustalias(v) end return tmeet(widenlattice(𝕃), v, t) end -function tmeet(lattice::OptimizerLattice, @nospecialize(v), @nospecialize(t::Type)) +@noinfer function tmeet(lattice::OptimizerLattice, @nospecialize(v), @nospecialize(t::Type)) # TODO: This can probably happen and should be handled @assert !isa(v, MaybeUndef) tmeet(widenlattice(lattice), v, t) @@ -727,7 +727,7 @@ widenconst(m::MaybeUndef) = widenconst(m.typ) widenconst(::PartialTypeVar) = TypeVar widenconst(t::PartialStruct) = t.typ widenconst(t::PartialOpaque) = t.typ -widenconst(t::Type) = t +@noinfer widenconst(@nospecialize t::Type) = t widenconst(::TypeVar) = error("unhandled TypeVar") widenconst(::TypeofVararg) = error("unhandled Vararg") widenconst(::LimitedAccuracy) = error("unhandled LimitedAccuracy") @@ -743,7 +743,7 @@ function smerge(lattice::AbstractLattice, sa::Union{NotFound,VarState}, sb::Unio return VarState(tmerge(lattice, sa.typ, sb.typ), sa.undef | sb.undef) end -@inline schanged(lattice::AbstractLattice, @nospecialize(n), @nospecialize(o)) = +@noinfer @inline schanged(lattice::AbstractLattice, @nospecialize(n), @nospecialize(o)) = (n !== o) && (o === NOT_FOUND || (n !== NOT_FOUND && !(n.undef <= o.undef && ⊑(lattice, n.typ, o.typ)))) # remove any lattice elements that wrap the reassigned slot object from the vartable diff --git a/base/compiler/typelimits.jl b/base/compiler/typelimits.jl index b5bbcde63e699e..b998816dda9dac 100644 --- a/base/compiler/typelimits.jl +++ b/base/compiler/typelimits.jl @@ -303,7 +303,7 @@ end # A simplified type_more_complex query over the extended lattice # (assumes typeb ⊑ typea) -function issimplertype(𝕃::AbstractLattice, @nospecialize(typea), @nospecialize(typeb)) +@noinfer function issimplertype(𝕃::AbstractLattice, @nospecialize(typea), @nospecialize(typeb)) typea isa MaybeUndef && (typea = typea.typ) # n.b. does not appear in inference typeb isa MaybeUndef && (typeb = typeb.typ) # n.b. does not appear in inference @assert !isa(typea, LimitedAccuracy) && !isa(typeb, LimitedAccuracy) "LimitedAccuracy not supported by simplertype lattice" # n.b. the caller was supposed to handle these @@ -413,7 +413,7 @@ function merge_causes(causesa::IdSet{InferenceState}, causesb::IdSet{InferenceSt end end -@noinline function tmerge_limited(lattice::InferenceLattice, @nospecialize(typea), @nospecialize(typeb)) +@noinfer @noinline function tmerge_limited(lattice::InferenceLattice, @nospecialize(typea), @nospecialize(typeb)) typea === Union{} && return typeb typeb === Union{} && return typea @@ -464,7 +464,7 @@ end return LimitedAccuracy(tmerge(widenlattice(lattice), typea, typeb), causes) end -function tmerge(lattice::InferenceLattice, @nospecialize(typea), @nospecialize(typeb)) +@noinfer function tmerge(lattice::InferenceLattice, @nospecialize(typea), @nospecialize(typeb)) if isa(typea, LimitedAccuracy) || isa(typeb, LimitedAccuracy) return tmerge_limited(lattice, typea, typeb) end @@ -474,7 +474,7 @@ function tmerge(lattice::InferenceLattice, @nospecialize(typea), @nospecialize(t return tmerge(widenlattice(lattice), typea, typeb) end -function tmerge(lattice::ConditionalsLattice, @nospecialize(typea), @nospecialize(typeb)) +@noinfer function tmerge(lattice::ConditionalsLattice, @nospecialize(typea), @nospecialize(typeb)) # type-lattice for Conditional wrapper (NOTE never be merged with InterConditional) if isa(typea, Conditional) && isa(typeb, Const) if typeb.val === true @@ -509,7 +509,7 @@ function tmerge(lattice::ConditionalsLattice, @nospecialize(typea), @nospecializ return tmerge(widenlattice(lattice), typea, typeb) end -function tmerge(lattice::InterConditionalsLattice, @nospecialize(typea), @nospecialize(typeb)) +@noinfer function tmerge(lattice::InterConditionalsLattice, @nospecialize(typea), @nospecialize(typeb)) # type-lattice for InterConditional wrapper (NOTE never be merged with Conditional) if isa(typea, InterConditional) && isa(typeb, Const) if typeb.val === true @@ -544,7 +544,7 @@ function tmerge(lattice::InterConditionalsLattice, @nospecialize(typea), @nospec return tmerge(widenlattice(lattice), typea, typeb) end -function tmerge(𝕃::AnyMustAliasesLattice, @nospecialize(typea), @nospecialize(typeb)) +@noinfer function tmerge(𝕃::AnyMustAliasesLattice, @nospecialize(typea), @nospecialize(typeb)) typea = widenmustalias(typea) typeb = widenmustalias(typeb) return tmerge(widenlattice(𝕃), typea, typeb) @@ -552,7 +552,7 @@ end # N.B. This can also be called with both typea::Const and typeb::Const to # to recover PartialStruct from `Const`s with overlapping fields. -function tmerge_partial_struct(lattice::PartialsLattice, @nospecialize(typea), @nospecialize(typeb)) +@noinfer function tmerge_partial_struct(lattice::PartialsLattice, @nospecialize(typea), @nospecialize(typeb)) aty = widenconst(typea) bty = widenconst(typeb) if aty === bty @@ -610,7 +610,7 @@ function tmerge_partial_struct(lattice::PartialsLattice, @nospecialize(typea), @ return nothing end -function tmerge(lattice::PartialsLattice, @nospecialize(typea), @nospecialize(typeb)) +@noinfer function tmerge(lattice::PartialsLattice, @nospecialize(typea), @nospecialize(typeb)) # type-lattice for Const and PartialStruct wrappers aps = isa(typea, PartialStruct) bps = isa(typeb, PartialStruct) @@ -653,8 +653,7 @@ function tmerge(lattice::PartialsLattice, @nospecialize(typea), @nospecialize(ty return tmerge(wl, typea, typeb) end - -function tmerge(lattice::ConstsLattice, @nospecialize(typea), @nospecialize(typeb)) +@noinfer function tmerge(lattice::ConstsLattice, @nospecialize(typea), @nospecialize(typeb)) acp = isa(typea, Const) || isa(typea, PartialTypeVar) bcp = isa(typeb, Const) || isa(typeb, PartialTypeVar) if acp && bcp @@ -666,7 +665,7 @@ function tmerge(lattice::ConstsLattice, @nospecialize(typea), @nospecialize(type return tmerge(wl, typea, typeb) end -function tmerge(::JLTypeLattice, @nospecialize(typea::Type), @nospecialize(typeb::Type)) +@noinfer function tmerge(::JLTypeLattice, @nospecialize(typea::Type), @nospecialize(typeb::Type)) # it's always ok to form a Union of two concrete types act = isconcretetype(typea) bct = isconcretetype(typeb) @@ -682,7 +681,7 @@ function tmerge(::JLTypeLattice, @nospecialize(typea::Type), @nospecialize(typeb return tmerge_types_slow(typea, typeb) end -@noinline function tmerge_types_slow(@nospecialize(typea::Type), @nospecialize(typeb::Type)) +@noinfer @noinline function tmerge_types_slow(@nospecialize(typea::Type), @nospecialize(typeb::Type)) # collect the list of types from past tmerge calls returning Union # and then reduce over that list types = Any[] diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index 0d81008125ddc3..57d42f109851b3 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -346,7 +346,7 @@ end # types # ######### -function singleton_type(@nospecialize(ft)) +@noinfer function singleton_type(@nospecialize(ft)) ft = widenslotwrapper(ft) if isa(ft, Const) return ft.val @@ -358,7 +358,7 @@ function singleton_type(@nospecialize(ft)) return nothing end -function maybe_singleton_const(@nospecialize(t)) +@noinfer function maybe_singleton_const(@nospecialize(t)) if isa(t, DataType) if issingletontype(t) return Const(t.instance)