Skip to content

Commit

Permalink
Allow constant-folding intrinsics that are non-pure for inference only
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Keno committed Feb 28, 2019
1 parent 69b6b8e commit 20069cb
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 4 deletions.
12 changes: 8 additions & 4 deletions base/compiler/optimize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
21 changes: 21 additions & 0 deletions base/compiler/ssair/inlining.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
11 changes: 11 additions & 0 deletions test/compiler/inline.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 20069cb

Please sign in to comment.