Skip to content

Commit 236c69b

Browse files
committed
Allow removing unused pointerref
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.
1 parent 19a0f71 commit 236c69b

File tree

4 files changed

+27
-1
lines changed

4 files changed

+27
-1
lines changed

base/compiler/optimize.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,9 @@ function is_pure_intrinsic_infer(f::IntrinsicFunction)
260260
f === Intrinsics.cglobal) # cglobal lookup answer changes at runtime
261261
end
262262

263+
# whether `f` is effect free if nothrow
264+
intrinsic_effect_free_if_nothrow(f) = f === Intrinsics.pointerref || is_pure_intrinsic_infer(f)
265+
263266
## Computing the cost of a function body
264267

265268
# saturating sum (inputs are nonnegative), prevents overflow with typemax(Int) below

base/compiler/ssair/queries.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ function stmt_effect_free(@nospecialize(stmt), @nospecialize(rt), src, sptypes::
2323
f === nothing && return false
2424
is_return_type(f) && return true
2525
if isa(f, IntrinsicFunction)
26-
is_pure_intrinsic_infer(f) || return false
26+
intrinsic_effect_free_if_nothrow(f) || return false
2727
return intrinsic_nothrow(f) ||
2828
intrinsic_nothrow(f,
2929
Any[argextype(ea[i], src, sptypes) for i = 2:length(ea)])

base/compiler/tfuncs.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1315,6 +1315,13 @@ function intrinsic_nothrow(f::IntrinsicFunction, argtypes::Array{Any, 1})
13151315
return den_val !== -1 || (isa(argtypes[1], Const) &&
13161316
argtypes[1].val !== typemin(typeof(den_val)))
13171317
end
1318+
if f === Intrinsics.pointerref
1319+
# Nothrow as long as the types are ok. N.B.: dereferencability is not
1320+
# modeled here, but can cause errors (e.g. ReadOnlyMemoryError). We follow LLVM here
1321+
# in that it is legal to remove unused non-volatile loads.
1322+
length(argtypes) == 3 || return false
1323+
return argtypes[1] Ptr && argtypes[2] Int && argtypes[3] Int
1324+
end
13181325
return true
13191326
end
13201327

test/compiler/inline.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,3 +231,19 @@ let code = code_typed(f_subtype, Tuple{})[1][1].code
231231
@test length(code) == 1
232232
@test code[1] == Expr(:return, false)
233233
end
234+
235+
# check that pointerref gets deleted if unused
236+
f_pointerref(T::Type{S}) where S = Val(length(T.parameters))
237+
let code = code_typed(f_pointerref, Tuple{Type{Int}})[1][1].code
238+
any_ptrref = false
239+
for i = 1:length(code)
240+
stmt = code[i]
241+
isa(stmt, Expr) || continue
242+
stmt.head === :call || continue
243+
arg1 = stmt.args[1]
244+
if arg1 === Base.pointerref || (isa(arg1, GlobalRef) && arg1.name === :pointerref)
245+
any_ptrref = true
246+
end
247+
end
248+
@test !any_ptrref
249+
end

0 commit comments

Comments
 (0)