diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index e817e0bd927fe1..daf87525e0228e 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 @@ -2659,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) @@ -2682,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 @@ -2719,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 @@ -2737,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 @@ -2756,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 @@ -2785,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/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/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 bae8ef5bae2421..57d42f109851b3 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 @@ -322,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 @@ -334,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) 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