From 20069cb7c2cb0686f93af046446331ea183215ab Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Wed, 27 Feb 2019 18:05:39 -0500 Subject: [PATCH] Allow constant-folding intrinsics that are non-pure for inference only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Any constants produced during inference must be *exact* (in the === to what would have been produced at runtime sense). As a result, we disallow constant folding the sqrt intrinsic, since we can't guarantee that this will be the case (depending on what LLVM decides to do). However, this does not apply to the optimizer (and in fact we do it all the time by inlining pure functions here). Thus, for code size, inlining heuristics and non-LLVM backends, enable the optimizer to constant fold sqrt. Before: ``` julia> f() = sqrt(2) f (generic function with 1 method) julia> @code_typed f() CodeInfo( 1 ─ %1 = Base.Math.sqrt_llvm(2.0)::Float64 └── return %1 ) => Float64 julia> @code_typed optimize=false f() CodeInfo( 1 ─ %1 = Main.sqrt(2)::Float64 └── return %1 ) => Float64 ``` After ``` julia> @code_typed f() CodeInfo( 1 ─ return 1.4142135623730951 ) => Float64 julia> @code_typed optimize=false f() CodeInfo( 1 ─ %1 = Main.sqrt(2)::Float64 └── return %1 ) => Float64 ``` Note that we are not able to infer `Const`, but still inline the constant in the optimized version of the IR. --- base/compiler/optimize.jl | 12 ++++++++---- base/compiler/ssair/inlining.jl | 21 +++++++++++++++++++++ test/compiler/inline.jl | 11 +++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index dc3541d06112c..bab46d1db30b6 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -252,17 +252,21 @@ function optimize(opt::OptimizationState, @nospecialize(result)) nothing end - -# whether `f` is pure for inference -function is_pure_intrinsic_infer(f::IntrinsicFunction) +# whether `f` is pure for optimization purposes +function is_pure_intrinsic_optimize(f::IntrinsicFunction) return !(f === Intrinsics.pointerref || # this one is volatile f === Intrinsics.pointerset || # this one is never effect-free f === Intrinsics.llvmcall || # this one is never effect-free f === Intrinsics.arraylen || # this one is volatile - f === Intrinsics.sqrt_llvm || # this one may differ at runtime (by a few ulps) f === Intrinsics.cglobal) # cglobal lookup answer changes at runtime end +# whether `f` is pure for inference +is_pure_intrinsic_optimize_only(f) = f === Intrinsics.sqrt_llvm # this one may differ by a few ulps at runtime +function is_pure_intrinsic_infer(f::IntrinsicFunction) + return is_pure_intrinsic_optimize(f) && !is_pure_intrinsic_optimize_only(f) +end + # whether `f` is effect free if nothrow intrinsic_effect_free_if_nothrow(f) = f === Intrinsics.pointerref || is_pure_intrinsic_infer(f) diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 9e8c7ab2672e9..caecc706129a4 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -1077,6 +1077,21 @@ function ispuretopfunction(@nospecialize(f)) istopfunction(f, :promote_type) end +function intrinsic_tfunc_optimize(f::IntrinsicFunction, atypes::Vector{Any}) + is_pure_intrinsic_optimize(f) || return nothing + args = atypes[2:end] + _all(@nospecialize(a) -> isa(a, Const), args) || return nothing + iidx = Int(reinterpret(Int32, f::IntrinsicFunction)) + 1 + if iidx < 0 || iidx > length(T_IFUNC) + # invalid intrinsic + return nothing + end + (min, max, _) = T_IFUNC[iidx] + min <= length(atypes) - 1 <= max || return nothing + argvals = anymap(a::Const -> a.val, atypes[2:end]) + return f(argvals...) +end + function early_inline_special_case(ir::IRCode, @nospecialize(f), @nospecialize(ft), e::Expr, atypes::Vector{Any}, sv::OptimizationState, @nospecialize(etype)) if (f === typeassert || ft ⊑ typeof(typeassert)) && length(atypes) == 3 @@ -1106,6 +1121,12 @@ function early_inline_special_case(ir::IRCode, @nospecialize(f), @nospecialize(f return quoted(val) end end + elseif isa(f, IntrinsicFunction) && is_pure_intrinsic_optimize_only(f) + # If this intrinsic was not eligible for constant propagation during + # inference, but is now, try to do that here. + val = intrinsic_tfunc_optimize(f, atypes) + val === nothing && return nothing + return quoted(val) end end diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index 31d07d954ff7e..05ea14fae7c69 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -247,3 +247,14 @@ let code = code_typed(f_pointerref, Tuple{Type{Int}})[1][1].code end @test !any_ptrref end + +f_sqrt() = sqrt(2) +let code = code_typed(f_sqrt, Tuple{})[1][1].code + @test length(code) == 1 + @test isa(code[1], Expr) && code[1].head == :return && isa(code[1].args[1], Float64) +end +# Make sure that the optimization doesn't apply to invalid calls +f_sqrt_wrong() = Base.Math.sqrt_llvm(1.0, 2.0) +let (ci, rt) = (code_typed(f_sqrt_wrong, Tuple{})[1]) + @test rt === Union{} +end