Skip to content

Commit

Permalink
reland "effects: add effects analysis for array construction" (JuliaL…
Browse files Browse the repository at this point in the history
  • Loading branch information
aviatesk committed Aug 8, 2022
1 parent 6cf58d6 commit 3abe00a
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 82 deletions.
4 changes: 3 additions & 1 deletion base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2075,7 +2075,9 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
@goto always_throw
end
end
effects = EFFECTS_UNKNOWN
effects = foreigncall_effects(e) do @nospecialize x
abstract_eval_value(interp, x, vtypes, sv)
end
cconv = e.args[5]
if isa(cconv, QuoteNode) && (v = cconv.value; isa(v, Tuple{Symbol, UInt8}))
override = decode_effects_override(v[2])
Expand Down
80 changes: 8 additions & 72 deletions base/compiler/optimize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,12 @@ function stmt_effect_flags(@nospecialize(stmt), @nospecialize(rt), src::Union{IR
end
return (true, true)
elseif head === :foreigncall
total = foreigncall_effect_free(stmt, src)
return (total, total)
effects = foreigncall_effects(stmt) do @nospecialize x
argextype(x, src)
end
effect_free = is_effect_free(effects)
nothrow = is_nothrow(effects)
return (effect_free & nothrow, nothrow)
elseif head === :new_opaque_closure
length(args) < 4 && return (false, false)
typ = argextype(args[1], src)
Expand All @@ -272,8 +276,8 @@ function stmt_effect_flags(@nospecialize(stmt), @nospecialize(rt), src::Union{IR
typ Tuple || return (false, false)
rt_lb = argextype(args[2], src)
rt_ub = argextype(args[3], src)
src = argextype(args[4], src)
if !(rt_lb Type && rt_ub Type && src Method)
source = argextype(args[4], src)
if !(rt_lb Type && rt_ub Type && source Method)
return (false, false)
end
return (true, true)
Expand All @@ -287,74 +291,6 @@ function stmt_effect_flags(@nospecialize(stmt), @nospecialize(rt), src::Union{IR
return (true, true)
end

function foreigncall_effect_free(stmt::Expr, src::Union{IRCode,IncrementalCompact})
args = stmt.args
name = args[1]
isa(name, QuoteNode) && (name = name.value)
isa(name, Symbol) || return false
ndims = alloc_array_ndims(name)
if ndims !== nothing
if ndims == 0
return new_array_no_throw(args, src)
else
return alloc_array_no_throw(args, ndims, src)
end
end
return false
end

function alloc_array_ndims(name::Symbol)
if name === :jl_alloc_array_1d
return 1
elseif name === :jl_alloc_array_2d
return 2
elseif name === :jl_alloc_array_3d
return 3
elseif name === :jl_new_array
return 0
end
return nothing
end

const FOREIGNCALL_ARG_START = 6

function alloc_array_no_throw(args::Vector{Any}, ndims::Int, src::Union{IRCode,IncrementalCompact})
length(args) ndims+FOREIGNCALL_ARG_START || return false
atype = instanceof_tfunc(argextype(args[FOREIGNCALL_ARG_START], src))[1]
dims = Csize_t[]
for i in 1:ndims
dim = argextype(args[i+FOREIGNCALL_ARG_START], src)
isa(dim, Const) || return false
dimval = dim.val
isa(dimval, Int) || return false
push!(dims, reinterpret(Csize_t, dimval))
end
return _new_array_no_throw(atype, ndims, dims)
end

function new_array_no_throw(args::Vector{Any}, src::Union{IRCode,IncrementalCompact})
length(args) FOREIGNCALL_ARG_START+1 || return false
atype = instanceof_tfunc(argextype(args[FOREIGNCALL_ARG_START], src))[1]
dims = argextype(args[FOREIGNCALL_ARG_START+1], src)
isa(dims, Const) || return dims === Tuple{}
dimsval = dims.val
isa(dimsval, Tuple{Vararg{Int}}) || return false
ndims = nfields(dimsval)
isa(ndims, Int) || return false
dims = Csize_t[reinterpret(Csize_t, dimval) for dimval in dimsval]
return _new_array_no_throw(atype, ndims, dims)
end

function _new_array_no_throw(@nospecialize(atype), ndims::Int, dims::Vector{Csize_t})
isa(atype, DataType) || return false
eltype = atype.parameters[1]
iskindtype(typeof(eltype)) || return false
elsz = aligned_sizeof(eltype)
return ccall(:jl_array_validate_dims, Cint,
(Ptr{Csize_t}, Ptr{Csize_t}, UInt32, Ptr{Csize_t}, Csize_t),
#=nel=#RefValue{Csize_t}(), #=tot=#RefValue{Csize_t}(), ndims, dims, elsz) == 0
end

"""
argextype(x, src::Union{IRCode,IncrementalCompact}) -> t
argextype(x, src::CodeInfo, sptypes::Vector{Any}) -> t
Expand Down
84 changes: 84 additions & 0 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2318,4 +2318,88 @@ function get_binding_type_tfunc(@nospecialize(M), @nospecialize(s))
end
add_tfunc(Core.get_binding_type, 2, 2, get_binding_type_tfunc, 0)

# foreigncall
# ===========

# N.B. the `abstract_eval` callback below allows us to use these queries
# both during abstract interpret and optimization

const FOREIGNCALL_ARG_START = 6

function foreigncall_effects(@specialize(abstract_eval), e::Expr)
args = e.args
name = args[1]
isa(name, QuoteNode) && (name = name.value)
isa(name, Symbol) || return EFFECTS_UNKNOWN
ndims = alloc_array_ndims(name)
if ndims !== nothing
if ndims 0
return alloc_array_effects(abstract_eval, args, ndims)
else
return new_array_effects(abstract_eval, args)
end
end
return EFFECTS_UNKNOWN
end

function alloc_array_ndims(name::Symbol)
if name === :jl_alloc_array_1d
return 1
elseif name === :jl_alloc_array_2d
return 2
elseif name === :jl_alloc_array_3d
return 3
elseif name === :jl_new_array
return 0
end
return nothing
end

function alloc_array_effects(@specialize(abstract_eval), args::Vector{Any}, ndims::Int)
nothrow = alloc_array_nothrow(abstract_eval, args, ndims)
return Effects(EFFECTS_TOTAL; consistent=CONSISTENT_IF_NOTRETURNED, nothrow)
end

function alloc_array_nothrow(@specialize(abstract_eval), args::Vector{Any}, ndims::Int)
length(args) ndims+FOREIGNCALL_ARG_START || return false
atype = instanceof_tfunc(abstract_eval(args[FOREIGNCALL_ARG_START]))[1]
dims = Csize_t[]
for i in 1:ndims
dim = abstract_eval(args[i+FOREIGNCALL_ARG_START])
isa(dim, Const) || return false
dimval = dim.val
isa(dimval, Int) || return false
push!(dims, reinterpret(Csize_t, dimval))
end
return _new_array_nothrow(atype, ndims, dims)
end

function new_array_effects(@specialize(abstract_eval), args::Vector{Any})
nothrow = new_array_nothrow(abstract_eval, args)
return Effects(EFFECTS_TOTAL; consistent=CONSISTENT_IF_NOTRETURNED, nothrow)
end

function new_array_nothrow(@specialize(abstract_eval), args::Vector{Any})
length(args) FOREIGNCALL_ARG_START+1 || return false
atype = instanceof_tfunc(abstract_eval(args[FOREIGNCALL_ARG_START]))[1]
dims = abstract_eval(args[FOREIGNCALL_ARG_START+1])
isa(dims, Const) || return dims === Tuple{}
dimsval = dims.val
isa(dimsval, Tuple{Vararg{Int}}) || return false
ndims = nfields(dimsval)
isa(ndims, Int) || return false
dims = Csize_t[reinterpret(Csize_t, dimval) for dimval in dimsval]
return _new_array_nothrow(atype, ndims, dims)
end

function _new_array_nothrow(@nospecialize(atype), ndims::Int, dims::Vector{Csize_t})
isa(atype, DataType) || return false
eltype = atype.parameters[1]
iskindtype(typeof(eltype)) || return false
elsz = aligned_sizeof(eltype)
return ccall(:jl_array_validate_dims, Cint,
(Ptr{Csize_t}, Ptr{Csize_t}, UInt32, Ptr{Csize_t}, Csize_t),
#=nel=#RefValue{Csize_t}(), #=tot=#RefValue{Csize_t}(), ndims, dims, elsz) == 0
end

@specialize
40 changes: 40 additions & 0 deletions test/compiler/effects.jl
Original file line number Diff line number Diff line change
Expand Up @@ -494,3 +494,43 @@ end
getfield(@__MODULE__, :global_ref)[] = nothing
end
@test !Core.Compiler.is_removable_if_unused(Base.infer_effects(unremovable_if_unused3!))

@testset "effects analysis on array ops" begin

@testset "effects analysis on array construction" begin

@noinline construct_array(@nospecialize(T), args...) = Array{T}(undef, args...)

# should eliminate safe but dead allocations
let good_dims = @static Int === Int64 ? (1:10) : (1:8)
Ns = @static Int === Int64 ? (1:10) : (1:8)
for dim = good_dims, N = Ns
dims = ntuple(i->dim, N)
@test @eval Base.infer_effects() do
$construct_array(Int, $(dims...))
end |> Core.Compiler.is_removable_if_unused
@test @eval fully_eliminated() do
$construct_array(Int, $(dims...))
nothing
end
end
end

# should analyze throwness correctly
let bad_dims = [-1, typemax(Int)]
for dim in bad_dims, N in 1:10
dims = ntuple(i->dim, N)
@test @eval Base.infer_effects() do
$construct_array(Int, $(dims...))
end |> !Core.Compiler.is_removable_if_unused
@test @eval !fully_eliminated() do
$construct_array(Int, $(dims...))
nothing
end
@test_throws "invalid Array" @eval $construct_array(Int, $(dims...))
end
end

end # @testset "effects analysis on array construction" begin

end # @testset "effects analysis on array ops" begin
22 changes: 13 additions & 9 deletions test/compiler/irpasses.jl
Original file line number Diff line number Diff line change
Expand Up @@ -950,36 +950,40 @@ end
let # effect-freeness computation for array allocation

# should eliminate dead allocations
good_dims = (0, 2)
for dim in good_dims, N in 0:10
good_dims = @static Int === Int64 ? (1:10) : (1:8)
Ns = @static Int === Int64 ? (1:10) : (1:8)
for dim = good_dims, N = Ns
dims = ntuple(i->dim, N)
@eval @test fully_eliminated(()) do
@test @eval fully_eliminated() do
Array{Int,$N}(undef, $(dims...))
nothing
end
end

# shouldn't eliminate errorneous dead allocations
bad_dims = [-1, # should keep "invalid Array dimensions"
typemax(Int)] # should keep "invalid Array size"
bad_dims = [-1, typemax(Int)]
for dim in bad_dims, N in 1:10
dims = ntuple(i->dim, N)
@eval @test !fully_eliminated(()) do
@test @eval !fully_eliminated() do
Array{Int,$N}(undef, $(dims...))
nothing
end
@test_throws "invalid Array" @eval let
Array{Int,$N}(undef, $(dims...))
nothing
end
end

# some high-level examples
@test fully_eliminated(()) do
@test fully_eliminated() do
Int[]
nothing
end
@test fully_eliminated(()) do
@test fully_eliminated() do
Matrix{Tuple{String,String}}(undef, 4, 4)
nothing
end
@test fully_eliminated(()) do
@test fully_eliminated() do
IdDict{Any,Any}()
nothing
end
Expand Down

0 comments on commit 3abe00a

Please sign in to comment.