From 236c69b1a53acf4057de830979138765c79aa596 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Mon, 4 Feb 2019 18:33:39 -0500 Subject: [PATCH] Allow removing unused pointerref MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows the julia-level optimizer to drop unused pointerref. This matches LLVM in that unused non-volatile loads are allowed to be dropped, despite them having possibly observable side effects (segfaults), since those are modeled as undefined. When heavily optimizing code we frequently saw a lot of left over pointerref calls resulting from inlining `length(::SimpleVector)`. We have a special tfunc for that call (that can give us the result as a const), but inline it anyway and thus end up with a pointerref whose result is unused that we didn't used to be able to eliminate. E.g. ``` julia> f(T::Type{S}) where {S} = Val(length(T.parameters)) f (generic function with 1 method) ``` Before: ``` julia> @code_typed f(Int) CodeInfo( 1 ─ %1 = (Base.getfield)(T, :parameters)::SimpleVector │ %2 = $(Expr(:gc_preserve_begin, :(%1))) │ %3 = $(Expr(:foreigncall, :(:jl_value_ptr), Ptr{Nothing}, svec(Any), :(:ccall), 1, :(%1)))::Ptr{Nothing} │ %4 = (Base.bitcast)(Ptr{Int64}, %3)::Ptr{Int64} │ (Base.pointerref)(%4, 1, 1)::Int64 │ $(Expr(:gc_preserve_end, :(%2))) └── return $(QuoteNode(Val{0}())) ) => Val{0} ``` After: ``` julia> @code_typed f(Int) CodeInfo( 1 ─ %1 = (Base.getfield)(T, :parameters)::SimpleVector │ %2 = $(Expr(:gc_preserve_begin, :(%1))) │ $(Expr(:foreigncall, :(:jl_value_ptr), Ptr{Nothing}, svec(Any), :(:ccall), 1, :(%1)))::Ptr{Nothing} │ $(Expr(:gc_preserve_end, :(%2))) └── return $(QuoteNode(Val{0}())) ) => Val{0} ``` Of course we still have the useless foreigncall. That is outside the scope of this PR, but I'm hoping to have a general solution in the future to mark foreigncalls as effect free if unused. --- base/compiler/optimize.jl | 3 +++ base/compiler/ssair/queries.jl | 2 +- base/compiler/tfuncs.jl | 7 +++++++ test/compiler/inline.jl | 16 ++++++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 83466a0ad24a2..8db4b2ea130e4 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -260,6 +260,9 @@ function is_pure_intrinsic_infer(f::IntrinsicFunction) f === Intrinsics.cglobal) # cglobal lookup answer changes at runtime end +# whether `f` is effect free if nothrow +intrinsic_effect_free_if_nothrow(f) = f === Intrinsics.pointerref || is_pure_intrinsic_infer(f) + ## Computing the cost of a function body # saturating sum (inputs are nonnegative), prevents overflow with typemax(Int) below diff --git a/base/compiler/ssair/queries.jl b/base/compiler/ssair/queries.jl index 552184f65e939..31c847b64b280 100644 --- a/base/compiler/ssair/queries.jl +++ b/base/compiler/ssair/queries.jl @@ -23,7 +23,7 @@ function stmt_effect_free(@nospecialize(stmt), @nospecialize(rt), src, sptypes:: f === nothing && return false is_return_type(f) && return true if isa(f, IntrinsicFunction) - is_pure_intrinsic_infer(f) || return false + intrinsic_effect_free_if_nothrow(f) || return false return intrinsic_nothrow(f) || intrinsic_nothrow(f, Any[argextype(ea[i], src, sptypes) for i = 2:length(ea)]) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 6f228427f116f..dd2a2de41606c 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1315,6 +1315,13 @@ function intrinsic_nothrow(f::IntrinsicFunction, argtypes::Array{Any, 1}) return den_val !== -1 || (isa(argtypes[1], Const) && argtypes[1].val !== typemin(typeof(den_val))) end + if f === Intrinsics.pointerref + # Nothrow as long as the types are ok. N.B.: dereferencability is not + # modeled here, but can cause errors (e.g. ReadOnlyMemoryError). We follow LLVM here + # in that it is legal to remove unused non-volatile loads. + length(argtypes) == 3 || return false + return argtypes[1] ⊑ Ptr && argtypes[2] ⊑ Int && argtypes[3] ⊑ Int + end return true end diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index 1a5f10e957937..31d07d954ff7e 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -231,3 +231,19 @@ let code = code_typed(f_subtype, Tuple{})[1][1].code @test length(code) == 1 @test code[1] == Expr(:return, false) end + +# check that pointerref gets deleted if unused +f_pointerref(T::Type{S}) where S = Val(length(T.parameters)) +let code = code_typed(f_pointerref, Tuple{Type{Int}})[1][1].code + any_ptrref = false + for i = 1:length(code) + stmt = code[i] + isa(stmt, Expr) || continue + stmt.head === :call || continue + arg1 = stmt.args[1] + if arg1 === Base.pointerref || (isa(arg1, GlobalRef) && arg1.name === :pointerref) + any_ptrref = true + end + end + @test !any_ptrref +end