diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index abb497a39b295..0b2d641c8eab8 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1975,7 +1975,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), return abstract_finalizer(interp, argtypes, sv) end rt = abstract_call_builtin(interp, f, arginfo, sv, max_methods) - effects = builtin_effects(๐•ƒแตข, f, argtypes[2:end], rt) + effects = builtin_effects(๐•ƒแตข, f, arginfo, rt) return CallMeta(rt, effects, NoCallInfo()) elseif isa(f, Core.OpaqueClosure) # calling an OpaqueClosure about which we have no information returns no information @@ -1994,7 +1994,8 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), ub_var = argtypes[3] end pT = typevar_tfunc(๐•ƒแตข, n, lb_var, ub_var) - effects = builtin_effects(๐•ƒแตข, Core._typevar, Any[n, lb_var, ub_var], pT) + effects = builtin_effects(๐•ƒแตข, Core._typevar, ArgInfo(nothing, + Any[Const(Core._typevar), n, lb_var, ub_var]), pT) return CallMeta(pT, effects, NoCallInfo()) elseif f === UnionAll return abstract_call_unionall(interp, argtypes) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index e5202e299d070..2c26848ac1ca1 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -289,8 +289,8 @@ function stmt_effect_flags(๐•ƒโ‚’::AbstractLattice, @nospecialize(stmt), @nospe isa(f, Builtin) || return (false, false, false) # Needs to be handled in inlining to look at the callee effects f === Core._apply_iterate && return (false, false, false) - argtypes = Any[argextype(args[arg], src) for arg in 2:length(args)] - effects = builtin_effects(๐•ƒโ‚’, f, argtypes, rt) + argtypes = Any[argextype(args[arg], src) for arg in 1:length(args)] + effects = builtin_effects(๐•ƒโ‚’, f, ArgInfo(args, argtypes), rt) consistent = is_consistent(effects) effect_free = is_effect_free(effects) nothrow = is_nothrow(effects) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 72e02d262ea94..1ff427a480a7d 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -911,41 +911,47 @@ function try_compute_fieldidx(typ::DataType, @nospecialize(field)) return field end -function getfield_boundscheck(argtypes::Vector{Any}) # ::Union{Bool, Nothing} - if length(argtypes) == 2 - return true - elseif length(argtypes) == 3 - boundscheck = argtypes[3] - isvarargtype(boundscheck) && return nothing - if widenconst(boundscheck) === Symbol - return true - end +function getfield_boundscheck((; fargs, argtypes)::ArgInfo) # Symbol + farg = nothing + if length(argtypes) == 3 + return :on elseif length(argtypes) == 4 + fargs !== nothing && (farg = fargs[4]) boundscheck = argtypes[4] + isvarargtype(boundscheck) && return :unknown + if widenconst(boundscheck) === Symbol + return :on + end + elseif length(argtypes) == 5 + fargs !== nothing && (farg = fargs[5]) + boundscheck = argtypes[5] else - return nothing + return :unknown end - isvarargtype(boundscheck) && return nothing - widenconst(boundscheck) === Bool || return nothing + isvarargtype(boundscheck) && return :unknown boundscheck = widenconditional(boundscheck) - if isa(boundscheck, Const) - return boundscheck.val::Bool - else - return nothing + if widenconst(boundscheck) === Bool + if isa(boundscheck, Const) + return boundscheck.val::Bool ? :on : :off + elseif farg !== nothing && isexpr(farg, :boundscheck) + return :boundscheck + end end + return :unknown end -function getfield_nothrow(argtypes::Vector{Any}, boundscheck::Union{Bool,Nothing}=getfield_boundscheck(argtypes)) - boundscheck === nothing && return false +function getfield_nothrow(arginfo::ArgInfo, boundscheck::Symbol=getfield_boundscheck(arginfo)) + (;argtypes) = arginfo + boundscheck === :unknown && return false ordering = Const(:not_atomic) - if length(argtypes) == 3 - isvarargtype(argtypes[3]) && return false - if widenconst(argtypes[3]) !== Bool - ordering = argtypes[3] - end - elseif length(argtypes) == 4 - ordering = argtypes[4] - elseif length(argtypes) != 2 + if length(argtypes) == 4 + isvarargtype(argtypes[4]) && return false + if widenconst(argtypes[4]) !== Bool + ordering = argtypes[4] + end + elseif length(argtypes) == 5 + ordering = argtypes[5] + elseif length(argtypes) != 3 return false end isvarargtype(ordering) && return false @@ -955,7 +961,7 @@ function getfield_nothrow(argtypes::Vector{Any}, boundscheck::Union{Bool,Nothing if ordering !== :not_atomic # TODO: this is assuming not atomic return false end - return getfield_nothrow(argtypes[1], argtypes[2], !(boundscheck === false)) + return getfield_nothrow(argtypes[2], argtypes[3], !(boundscheck === :off)) else return false end @@ -1037,7 +1043,9 @@ end end return getfield_tfunc(๐•ƒ, s00, name) end -@nospecs getfield_tfunc(๐•ƒ::AbstractLattice, s00, name) = _getfield_tfunc(๐•ƒ, s00, name, false) +@nospecs function getfield_tfunc(๐•ƒ::AbstractLattice, s00, name) + _getfield_tfunc(๐•ƒ, s00, name, false) +end function _getfield_fieldindex(s::DataType, name::Const) nv = name.val @@ -2021,7 +2029,7 @@ end elseif f === invoke return false elseif f === getfield - return getfield_nothrow(argtypes) + return getfield_nothrow(ArgInfo(nothing, Any[Const(f), argtypes...])) elseif f === setfield! if na == 3 return setfield!_nothrow(๐•ƒ, argtypes[1], argtypes[2], argtypes[3]) @@ -2179,10 +2187,11 @@ function isdefined_effects(๐•ƒ::AbstractLattice, argtypes::Vector{Any}) return Effects(EFFECTS_TOTAL; consistent, nothrow) end -function getfield_effects(argtypes::Vector{Any}, @nospecialize(rt)) +function getfield_effects(arginfo::ArgInfo, @nospecialize(rt)) + (;argtypes) = arginfo # consistent if the argtype is immutable - isempty(argtypes) && return EFFECTS_THROWS - obj = argtypes[1] + length(argtypes) < 3 && return EFFECTS_THROWS + obj = argtypes[2] isvarargtype(obj) && return Effects(EFFECTS_THROWS; consistent=ALWAYS_FALSE) consistent = (is_immutable_argtype(obj) || is_mutation_free_argtype(obj)) ? ALWAYS_TRUE : CONSISTENT_IF_INACCESSIBLEMEMONLY @@ -2191,20 +2200,26 @@ function getfield_effects(argtypes::Vector{Any}, @nospecialize(rt)) # throws `UndefRefError` so doesn't need to taint it # NOTE `getfield_notundefined` conservatively checks if this field is never initialized # with undefined value so that we don't taint `:consistent`-cy too aggressively here - if !(length(argtypes) โ‰ฅ 2 && getfield_notundefined(obj, argtypes[2])) + if !(length(argtypes) โ‰ฅ 3 && getfield_notundefined(obj, argtypes[3])) consistent = ALWAYS_FALSE end - nothrow = getfield_nothrow(argtypes, true) - if !nothrow && getfield_boundscheck(argtypes) !== true - # If we cannot independently prove inboundsness, taint consistency. - # The inbounds-ness assertion requires dynamic reachability, while - # :consistent needs to be true for all input values. - # N.B. We do not taint for `--check-bounds=no` here that happens in - # InferenceState. - consistent = ALWAYS_FALSE + nothrow = getfield_nothrow(arginfo, :on) + if !nothrow + bcheck = getfield_boundscheck(arginfo) + if !(bcheck === :on || bcheck === :boundscheck) + # If we cannot independently prove inboundsness, taint consistency. + # The inbounds-ness assertion requires dynamic reachability, while + # :consistent needs to be true for all input values. + # However, as a special exception, we do allow literal `:boundscheck`. + # `:consistent`-cy will be tainted in any caller using `@inbounds` based + # on the `:noinbounds` effect. + # N.B. We do not taint for `--check-bounds=no` here. That is handled + # in concrete evaluation. + consistent = ALWAYS_FALSE + end end if hasintersect(widenconst(obj), Module) - inaccessiblememonly = getglobal_effects(argtypes, rt).inaccessiblememonly + inaccessiblememonly = getglobal_effects(argtypes[2:end], rt).inaccessiblememonly elseif is_mutation_free_argtype(obj) inaccessiblememonly = ALWAYS_TRUE else @@ -2233,17 +2248,20 @@ function getglobal_effects(argtypes::Vector{Any}, @nospecialize(rt)) return Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly) end -function builtin_effects(๐•ƒ::AbstractLattice, @nospecialize(f::Builtin), argtypes::Vector{Any}, @nospecialize(rt)) +function builtin_effects(๐•ƒ::AbstractLattice, @nospecialize(f::Builtin), arginfo::ArgInfo, @nospecialize(rt)) if isa(f, IntrinsicFunction) - return intrinsic_effects(f, argtypes) + return intrinsic_effects(f, arginfo.argtypes[2:end]) end @assert !contains_is(_SPECIAL_BUILTINS, f) + if f === getfield + return getfield_effects(arginfo, rt) + end + argtypes = arginfo.argtypes[2:end] + if f === isdefined return isdefined_effects(๐•ƒ, argtypes) - elseif f === getfield - return getfield_effects(argtypes, rt) elseif f === getglobal return getglobal_effects(argtypes, rt) elseif f === Core.get_binding_type diff --git a/base/reflection.jl b/base/reflection.jl index 9bb224c53b436..78a46c8693770 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1493,9 +1493,10 @@ function infer_effects(@nospecialize(f), @nospecialize(types=default_tt(f)); ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions") if isa(f, Core.Builtin) types = to_tuple_type(types) - argtypes = Any[types.parameters...] - rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) - return Core.Compiler.builtin_effects(Core.Compiler.typeinf_lattice(interp), f, argtypes, rt) + argtypes = Any[Core.Compiler.Const(f), types.parameters...] + rt = Core.Compiler.builtin_tfunction(interp, f, argtypes[2:end], nothing) + return Core.Compiler.builtin_effects(Core.Compiler.typeinf_lattice(interp), f, + Core.Compiler.ArgInfo(nothing, argtypes), rt) end tt = signature_type(f, types) result = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) diff --git a/base/tuple.jl b/base/tuple.jl index e0adbfe6d20cc..f5e85137bc6f0 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -67,7 +67,7 @@ end function iterate(@nospecialize(t::Tuple), i::Int=1) @inline - return (1 <= i <= length(t)) ? (@inbounds t[i], i + 1) : nothing + return (1 <= i <= length(t)) ? (t[i], i + 1) : nothing end keys(@nospecialize t::Tuple) = OneTo(length(t)) diff --git a/test/compiler/codegen.jl b/test/compiler/codegen.jl index 0d87f8cf8b56b..6f8c31d6c4015 100644 --- a/test/compiler/codegen.jl +++ b/test/compiler/codegen.jl @@ -794,3 +794,6 @@ end f48085(@nospecialize x...) = length(x) @test Core.Compiler.get_compileable_sig(which(f48085, (Vararg{Any},)), Tuple{typeof(f48085), Vararg{Int}}, Core.svec()) === nothing @test Core.Compiler.get_compileable_sig(which(f48085, (Vararg{Any},)), Tuple{typeof(f48085), Int, Vararg{Int}}, Core.svec()) === Tuple{typeof(f48085), Any, Vararg{Any}} + +# Make sure that the bounds check is elided in tuple iteration +@test !occursin("call void @", get_llvm(iterate, Tuple{NTuple{4, Float64}, Int})) diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index 25b6db0f34310..42e370c922ef6 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -462,9 +462,9 @@ end |> Core.Compiler.is_consistent end |> Core.Compiler.is_effect_free # `getfield_effects` handles access to union object nicely -@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Any[Some{String}, Core.Const(:value)], String)) -@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Any[Some{Symbol}, Core.Const(:value)], Symbol)) -@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Any[Union{Some{Symbol},Some{String}}, Core.Const(:value)], Union{Symbol,String})) +@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Core.Compiler.ArgInfo(nothing, Any[Core.Const(getfield), Some{String}, Core.Const(:value)]), String)) +@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Core.Compiler.ArgInfo(nothing, Any[Core.Const(getfield), Some{Symbol}, Core.Const(:value)]), Symbol)) +@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Core.Compiler.ArgInfo(nothing, Any[Core.Const(getfield), Union{Some{Symbol},Some{String}}, Core.Const(:value)]), Union{Symbol,String})) @test Base.infer_effects((Bool,)) do c obj = c ? Some{String}("foo") : Some{Symbol}(:bar) return getfield(obj, :value) @@ -688,7 +688,8 @@ end # @testset "effects analysis on array construction" begin end # @testset "effects analysis on array ops" begin # Test that builtin_effects handles vararg correctly -@test !Core.Compiler.is_nothrow(Core.Compiler.builtin_effects(Core.Compiler.fallback_lattice, Core.isdefined, Any[String, Vararg{Any}], Bool)) +@test !Core.Compiler.is_nothrow(Core.Compiler.builtin_effects(Core.Compiler.fallback_lattice, Core.isdefined, + Core.Compiler.ArgInfo(nothing, Any[Core.Compiler.Const(Core.isdefined), String, Vararg{Any}]), Bool)) # Test that :new can be eliminated even if an sparam is unknown struct SparamUnused{T} diff --git a/test/tuple.jl b/test/tuple.jl index 945590c2dbf4b..86f41b2fbc660 100644 --- a/test/tuple.jl +++ b/test/tuple.jl @@ -779,3 +779,6 @@ namedtup = (;a=1, b=2, c=3) NamedTuple{(:a, :b), Tuple{Int, Int}}, NamedTuple{(:c,), Tuple{Int}}, } + +# Make sure that tuple iteration is foldable +@test Core.Compiler.is_foldable(Base.infer_effects(iterate, Tuple{NTuple{4, Float64}, Int}))