Skip to content

Commit

Permalink
Allow removing unused pointerref
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Keno committed Feb 11, 2019
1 parent 19a0f71 commit 236c69b
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 1 deletion.
3 changes: 3 additions & 0 deletions base/compiler/optimize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion base/compiler/ssair/queries.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)])
Expand Down
7 changes: 7 additions & 0 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

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

2 comments on commit 236c69b

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Executing the daily benchmark build, I will reply here when finished:

@nanosoldier runbenchmarks(ALL, isdaily = true)

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @ararslan

Please sign in to comment.