Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ImmutableArrays #42465

Closed
wants to merge 58 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
fcbafaa
Implement ImmutableArray
Keno Apr 5, 2019
233bb0c
Implement maybecopy in BoundsError, start optimization, refactor memo…
Sep 29, 2021
424232a
Begin EscapeAnalysis.jl port
Nov 9, 2021
e031da8
Update memory_opt! for usage with EscapeState, begin port of EA.jl in…
Dec 21, 2021
c2ca5c5
Merge branch 'master' into HEAD
aviatesk Dec 21, 2021
49f9337
fixup definitions of array primitives
aviatesk Dec 21, 2021
b02e7c0
fixup `@noinline` annotations within `BoundsError`
aviatesk Dec 21, 2021
2a52e3d
disable EA global caching to succeed in sysimg building
aviatesk Dec 21, 2021
bd0bdb7
define EA in a separate module
aviatesk Dec 21, 2021
51120cf
Merge branch 'master' into kf/immutablearray
aviatesk Dec 23, 2021
4b33fc7
add simple `all`/`any` definitions to make `::BitSet ⊆ ::BitSet` work…
aviatesk Dec 23, 2021
cb4b1b8
comment dead code for now
aviatesk Dec 23, 2021
f59d58b
Merge branch 'master' into kf/immutablearray
aviatesk Dec 25, 2021
bca3078
update to latest EA
aviatesk Dec 25, 2021
2c466e1
update to latest EA
aviatesk Dec 26, 2021
235ab84
Restrict arrayfreeze/thaw arg # in base/compiler/tfuncs.jl
ianatol Dec 28, 2021
0e5833b
Restrict nargs of arrayfreeze/thaw to 1 in builtins.c
Dec 28, 2021
4a57838
Merge branch 'master' into kf/immutablearray
aviatesk Jan 4, 2022
de437d4
update EA and get array supports
aviatesk Jan 4, 2022
a34166c
fixup `memory_opt!`
aviatesk Jan 4, 2022
1b1babf
update EA and handle conflicting field information correctly
aviatesk Jan 4, 2022
eee43bd
minor optimization for `memory_opt!`
aviatesk Jan 4, 2022
9b8233a
define tfuncs for `ImmutableArray` primitives
aviatesk Jan 4, 2022
6fad97d
Correct maybecopy and begin implementing its optimization. Also some …
Jan 8, 2022
e1a1dc5
Merge branch 'master' into kf/immutablearray
aviatesk Jan 8, 2022
dd77189
update to latest EA
aviatesk Jan 8, 2022
39c925b
fixup `memory_opt!`
aviatesk Jan 8, 2022
fbc2c20
update new array tfuncs to accout for `ImmutableArray`
aviatesk Jan 9, 2022
5d93701
simplify `memory_opt!`
aviatesk Jan 9, 2022
19d5d20
Merge branch 'master' into kf/immutablearray
aviatesk Jan 9, 2022
b16190e
Merge branch 'master' into kf/immutablearray
aviatesk Jan 10, 2022
697b6f1
simplify
aviatesk Jan 10, 2022
0403a04
Merge branch 'master' into kf/immutablearray
aviatesk Jan 10, 2022
8507643
improve test implementations
aviatesk Jan 10, 2022
77eec88
Fixup unescaped1_1 test
Jan 10, 2022
c958ac9
Cleanup memory_opt, add some tests
Jan 11, 2022
c718fb9
avoid unintended name leak, better naming
aviatesk Jan 11, 2022
c4c7acf
Add some basic tests and move non-compiler tests
Jan 12, 2022
2438fd4
Merge branch 'master' into kf/immutablearray
aviatesk Jan 12, 2022
0d7d81a
add test cases https://github.com/aviatesk/EscapeAnalysis.jl/pull/72 …
aviatesk Jan 12, 2022
765f59c
Merge branch 'master' into kf/immutablearray
aviatesk Jan 13, 2022
150c490
update EA
aviatesk Jan 13, 2022
eaf5005
no broken tests now
aviatesk Jan 13, 2022
1dcfa1b
Merge branch 'master' into kf/immutablearray
aviatesk Jan 14, 2022
eb519c8
Merge branch 'master' into kf/immutablearray
aviatesk Jan 15, 2022
8110fad
update EA
aviatesk Jan 15, 2022
7b3c099
export `ImmutableArray`, define `ImmutableVector` alias
aviatesk Jan 15, 2022
d494cf2
use `IndexLinear` for immutable array
aviatesk Jan 15, 2022
de410f1
enable `test/immutablearray` testset
aviatesk Jan 15, 2022
d3c21bd
Merge branch 'master' into kf/immutablearray
aviatesk Jan 17, 2022
283c36b
fix tests
aviatesk Jan 18, 2022
eb221f6
Merge branch 'master' into kf/immutablearray
aviatesk Jan 18, 2022
10c161f
Merge branch 'master' into kf/immutablearray
aviatesk Jan 19, 2022
2560b73
update EA
aviatesk Jan 20, 2022
fe3717a
Merge branch 'master' into kf/immutablearray
aviatesk Jan 21, 2022
bbe8b3d
Merge branch 'master' into kf/immutablearray
aviatesk Jan 25, 2022
bca548f
Remove maybecopy code
Jan 27, 2022
df8bccc
Add tests
Jan 27, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,10 @@ function copy(a::AbstractArray)
copymutable(a)
end

function copy(a::Core.ImmutableArray)
a
end

function copyto!(B::AbstractVecOrMat{R}, ir_dest::AbstractRange{Int}, jr_dest::AbstractRange{Int},
A::AbstractVecOrMat{S}, ir_src::AbstractRange{Int}, jr_src::AbstractRange{Int}) where {R,S}
if length(ir_dest) != length(ir_src)
Expand Down
71 changes: 65 additions & 6 deletions base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,35 @@ Union type of [`DenseVector{T}`](@ref) and [`DenseMatrix{T}`](@ref).
"""
const DenseVecOrMat{T} = Union{DenseVector{T}, DenseMatrix{T}}

"""
ImmutableArray

Dynamically allocated, immutable array.

"""
const ImmutableArray = Core.ImmutableArray

"""
IMArray{T,N}

Union type of [`Array{T,N}`](@ref) and [`ImmutableArray{T,N}`](@ref)
"""
const IMArray{T,N} = Union{Array{T, N}, ImmutableArray{T,N}}

"""
IMVector{T}

One-dimensional [`ImmutableArray`](@ref) or [`Array`](@ref) with elements of type `T`. Alias for `IMArray{T, 1}`.
"""
const IMVector{T} = IMArray{T, 1}

"""
IMMatrix{T}

Two-dimensional [`ImmutableArray`](@ref) or [`Array`](@ref) with elements of type `T`. Alias for `IMArray{T,2}`.
"""
const IMMatrix{T} = IMArray{T, 2}

## Basic functions ##

import Core: arraysize, arrayset, arrayref, const_arrayref
Expand Down Expand Up @@ -147,12 +176,19 @@ function vect(X...)
return copyto!(Vector{T}(undef, length(X)), X)
end

size(a::Array, d::Integer) = arraysize(a, convert(Int, d))
size(a::Vector) = (arraysize(a,1),)
size(a::Matrix) = (arraysize(a,1), arraysize(a,2))
size(a::Array{<:Any,N}) where {N} = (@_inline_meta; ntuple(M -> size(a, M), Val(N))::Dims)
# Freeze and thaw constructors
ImmutableArray(a::Array) = Core.arrayfreeze(a)
Array(a::ImmutableArray) = Core.arraythaw(a)

ImmutableArray(a::AbstractArray{T,N}) where {T,N} = ImmutableArray{T,N}(a)

asize_from(a::Array, n) = n > ndims(a) ? () : (arraysize(a,n), asize_from(a, n+1)...)
# Size functions for arrays, both mutable and immutable
size(a::IMArray, d::Integer) = arraysize(a, convert(Int, d))
size(a::IMVector) = (arraysize(a,1),)
size(a::IMMatrix) = (arraysize(a,1), arraysize(a,2))
size(a::IMArray{<:Any,N}) where {N} = (@_inline_meta; ntuple(M -> size(a, M), Val(N))::Dims)

asize_from(a::IMArray, n) = n > ndims(a) ? () : (arraysize(a,n), asize_from(a, n+1)...)

allocatedinline(T::Type) = (@_pure_meta; ccall(:jl_stored_inline, Cint, (Any,), T) != Cint(0))

Expand Down Expand Up @@ -223,6 +259,13 @@ function isassigned(a::Array, i::Int...)
ccall(:jl_array_isassigned, Cint, (Any, UInt), a, ii) == 1
end

function isassigned(a::ImmutableArray, i::Int...)
@_inline_meta
ii = (_sub2ind(size(a), i...) % UInt) - 1
@boundscheck ii < length(a) % UInt || return false
ccall(:jl_array_isassigned, Cint, (Any, UInt), a, ii) == 1
end
ianatol marked this conversation as resolved.
Show resolved Hide resolved

## copy ##

"""
Expand Down Expand Up @@ -378,6 +421,18 @@ similar(a::Array{T}, m::Int) where {T} = Vector{T}(undef, m)
similar(a::Array, T::Type, dims::Dims{N}) where {N} = Array{T,N}(undef, dims)
similar(a::Array{T}, dims::Dims{N}) where {T,N} = Array{T,N}(undef, dims)

ImmutableArray{T}(undef::UndefInitializer, m::Int) where T = ImmutableArray(Array{T}(undef, m))
ImmutableArray{T}(undef::UndefInitializer, dims::Dims) where T = ImmutableArray(Array{T}(undef, dims))

"""
maybecopy(x)

`maybecopy` provides access to `x` while ensuring it does not escape.
To do so, the optimizer decides whether to create a copy of `x` or not based on the implementation
That is, `maybecopy` will either be a call to [`copy`](@ref) or just a reference to x.
"""
maybecopy = Core.maybecopy
ianatol marked this conversation as resolved.
Show resolved Hide resolved

# T[x...] constructs Array{T,1}
"""
getindex(type[, elements...])
Expand Down Expand Up @@ -611,8 +666,8 @@ oneunit(x::AbstractMatrix{T}) where {T} = _one(oneunit(T), x)

## Conversions ##

convert(::Type{T}, a::AbstractArray) where {T<:Array} = a isa T ? a : T(a)
convert(::Type{Union{}}, a::AbstractArray) = throw(MethodError(convert, (Union{}, a)))
convert(T::Union{Type{<:Array},Type{<:Core.ImmutableArray}}, a::AbstractArray) = a isa T ? a : T(a)
aviatesk marked this conversation as resolved.
Show resolved Hide resolved

promote_rule(a::Type{Array{T,n}}, b::Type{Array{S,n}}) where {T,n,S} = el_same(promote_type(T,S), a, b)

Expand All @@ -622,6 +677,7 @@ if nameof(@__MODULE__) === :Base # avoid method overwrite
# constructors should make copies
Array{T,N}(x::AbstractArray{S,N}) where {T,N,S} = copyto_axcheck!(Array{T,N}(undef, size(x)), x)
AbstractArray{T,N}(A::AbstractArray{S,N}) where {T,N,S} = copyto_axcheck!(similar(A,T), A)
ImmutableArray{T,N}(Ar::AbstractArray{S,N}) where {T,N,S} = Core.arrayfreeze(copyto_axcheck!(Array{T,N}(undef, size(Ar)), Ar))
end

## copying iterators to containers
Expand Down Expand Up @@ -895,6 +951,9 @@ function getindex end
@eval getindex(A::Array, i1::Int) = arrayref($(Expr(:boundscheck)), A, i1)
@eval getindex(A::Array, i1::Int, i2::Int, I::Int...) = (@_inline_meta; arrayref($(Expr(:boundscheck)), A, i1, i2, I...))

@eval getindex(A::ImmutableArray, i1::Int) = arrayref($(Expr(:boundscheck)), A, i1)
@eval getindex(A::ImmutableArray, i1::Int, i2::Int, I::Int...) = (@_inline_meta; arrayref($(Expr(:boundscheck)), A, i1, i2, I...))

ianatol marked this conversation as resolved.
Show resolved Hide resolved
# Faster contiguous indexing using copyto! for UnitRange and Colon
function getindex(A::Array, I::AbstractUnitRange{<:Integer})
@_inline_meta
Expand Down
13 changes: 11 additions & 2 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,17 @@ struct BoundsError <: Exception
a::Any
i::Any
BoundsError() = new()
BoundsError(@nospecialize(a)) = (@_noinline_meta; new(a))
BoundsError(@nospecialize(a), i) = (@_noinline_meta; new(a,i))
# For now, always copy arrays to avoid escaping them
# Eventually, we want to figure out if the copy is needed to save the performance of copying
# (i.e., if a escapes elsewhere, don't bother to make a copy)

BoundsError(@nospecialize(a)) = a isa Array ?
(@_noinline_meta; new(Core.maybecopy(a))) :
(@_noinline_meta; new(a))

BoundsError(@nospecialize(a), i) = a isa Array ?
(@_noinline_meta; new(Core.maybecopy(a), i)) :
(@_noinline_meta; new(a, i))
end
struct DivideError <: Exception end
struct OutOfMemoryError <: Exception end
Expand Down
13 changes: 13 additions & 0 deletions base/broadcast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1385,4 +1385,17 @@ function Base.show(io::IO, op::BroadcastFunction)
end
Base.show(io::IO, ::MIME"text/plain", op::BroadcastFunction) = show(io, op)

struct IMArrayStyle <: Broadcast.AbstractArrayStyle{Any} end
BroadcastStyle(::Type{<:Core.ImmutableArray}) = IMArrayStyle()

#similar has to return mutable array
function Base.similar(bc::Broadcasted{IMArrayStyle}, ::Type{ElType}) where ElType
similar(Array{ElType}, axes(bc))
end

@inline function copy(bc::Broadcasted{IMArrayStyle})
ElType = combine_eltypes(bc.f, bc.args)
return Core.ImmutableArray(copyto!(similar(bc, ElType), bc))
end

end # module
3 changes: 2 additions & 1 deletion base/compiler/optimize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,8 @@ function run_passes(ci::CodeInfo, sv::OptimizationState)
ir = adce_pass!(ir)
#@Base.show ("after_adce", ir)
@timeit "type lift" ir = type_lift_pass!(ir)
@timeit "compact 3" ir = compact!(ir)
#@timeit "compact 3" ir = compact!(ir)
ir = memory_opt!(ir)
#@Base.show ir
if JLOptions().debug_level == 2
@timeit "verify 3" (verify_ir(ir); verify_linetable(ir.linetable))
Expand Down
7 changes: 7 additions & 0 deletions base/compiler/ssair/ir.jl
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,13 @@ function setindex!(x::IRCode, @nospecialize(repl), s::SSAValue)
return x
end

function ssadominates(ir::IRCode, domtree::DomTree, ssa1::Int, ssa2::Int)
bb1 = block_for_inst(ir.cfg, ssa1)
bb2 = block_for_inst(ir.cfg, ssa2)
bb1 == bb2 && return ssa1 < ssa2
return dominates(domtree, bb1, bb2)
end

# SSA values that need renaming
struct OldSSAValue
id::Int
Expand Down
182 changes: 182 additions & 0 deletions base/compiler/ssair/passes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1255,3 +1255,185 @@ function cfg_simplify!(ir::IRCode)
compact.active_result_bb = length(bb_starts)
return finish(compact)
end

# function is_known_fcall(stmt::Expr, @nospecialize(func))
# isexpr(stmt, :foreigncall) || return false
# s = stmt.args[1]
# isa(s, QuoteNode) && (s = s.value)
# return s === func
# end

function is_known_fcall(stmt::Expr, funcs::Vector{Symbol})
isexpr(stmt, :foreigncall) || return false
s = stmt.args[1]
isa(s, QuoteNode) && (s = s.value)
# return any(e -> s === e, funcs)
return true in map(e -> s === e, funcs)
end
aviatesk marked this conversation as resolved.
Show resolved Hide resolved

function is_allocation(stmt::Expr)
ianatol marked this conversation as resolved.
Show resolved Hide resolved
isexpr(stmt, :foreigncall) || return false
s = stmt.args[1]
isa(s, QuoteNode) && (s = s.value)
return (s === :jl_alloc_array_1d
|| s === :jl_alloc_array_2d
|| s === :jl_alloc_array_3d
|| s === :jl_new_array)
end

function memory_opt!(ir::IRCode)
compact = IncrementalCompact(ir, false)
uses = IdDict{Int, Vector{Int}}()
relevant = IdSet{Int}() # allocations
revisit = Int[] # potential targets for a mutating_arrayfreeze drop-in
aviatesk marked this conversation as resolved.
Show resolved Hide resolved
maybecopies = Int[] # calls to maybecopy

function mark_escape(@nospecialize val)
isa(val, SSAValue) || return
#println(val.id, " escaped.")
val.id in relevant && pop!(relevant, val.id)
end

function mark_use(val, idx)
isa(val, SSAValue) || return
id = val.id
id in relevant || return
(haskey(uses, id)) || (uses[id] = Int[])
push!(uses[id], idx)
end

for ((_, idx), stmt) in compact
ianatol marked this conversation as resolved.
Show resolved Hide resolved

#println("idx: ", idx, " = ", stmt)

if isa(stmt, ReturnNode)
isdefined(stmt, :val) || continue
val = stmt.val
mark_use(val, idx)
continue

# check for phinodes that are possibly allocations
elseif isa(stmt, PhiNode)

# ensure all of the phinode values are defined
defined = true
for i = 1:length(stmt.values)
if !isassigned(stmt.values, i)
defined = false
end
end

defined || continue

for val in stmt.values
if isa(val, SSAValue) && val.id in relevant
push!(relevant, idx)
end
end
end

(isexpr(stmt, :call) || isexpr(stmt, :foreigncall)) || continue

if is_known_call(stmt, Core.maybecopy, compact)
push!(maybecopies, idx)
continue
end

if is_allocation(stmt)
push!(relevant, idx)
# TODO: Mark everything else here
continue
end

if is_known_call(stmt, arrayset, compact) && length(stmt.args) >= 5
# The value being set escapes, everything else doesn't
mark_escape(stmt.args[4])
arr = stmt.args[3]
mark_use(arr, idx)

elseif is_known_call(stmt, arrayref, compact) && length(stmt.args) == 4
arr = stmt.args[3]
mark_use(arr, idx)

elseif is_known_call(stmt, setindex!, compact) && length(stmt.args) == 4
# handle similarly to arrayset
val = stmt.args[3]
mark_escape(val)

arr = stmt.args[2]
mark_use(arr, idx)

ianatol marked this conversation as resolved.
Show resolved Hide resolved
elseif is_known_call(stmt, (===), compact) && length(stmt.args) == 3
arr1 = stmt.args[2]
arr2 = stmt.args[3]

mark_use(arr1, idx)
mark_use(arr2, idx)

# these foreigncalls have similar structure and don't escape our array, so handle them all at once
elseif is_known_fcall(stmt, [:jl_array_ptr, :jl_array_copy]) && length(stmt.args) == 6
arr = stmt.args[6]
mark_use(arr, idx)

elseif is_known_call(stmt, arraysize, compact) && isa(stmt.args[2], SSAValue)
ianatol marked this conversation as resolved.
Show resolved Hide resolved
arr = stmt.args[2]
mark_use(arr, idx)

elseif is_known_call(stmt, Core.arrayfreeze, compact) && isa(stmt.args[2], SSAValue)
ianatol marked this conversation as resolved.
Show resolved Hide resolved
# mark these for potential replacement with mutating_arrayfreeze
push!(revisit, idx)

else
# Assume everything else escapes
for ur in userefs(stmt)
mark_escape(ur[])
end
end
end

ir = finish(compact)
isempty(revisit) && isempty(maybecopies) && return ir

domtree = construct_domtree(ir.cfg.blocks)

for idx in revisit
# Make sure that the value we reference didn't escape
stmt = ir.stmts[idx][:inst]::Expr
id = (stmt.args[2]::SSAValue).id
(id in relevant) || continue

#println("Revisiting ", stmt)

# We're ok to steal the memory if we don't dominate any uses
ok = true
if haskey(uses, id)
for use in uses[id]
if ssadominates(ir, domtree, idx, use)
ok = false
break
end
end
end
ok || continue
stmt.args[1] = Core.mutating_arrayfreeze
end

# TODO: Use escape analysis info to determine if maybecopy should copy

# for idx in maybecopies
# stmt = ir.stmts[idx][:inst]::Expr
# #println(stmt.args)
# arr = stmt.args[2]
# id = isa(arr, SSAValue) ? arr.id : arr.n # SSAValue or Core.Argument

# if (id in relevant) # didn't escape elsewhere, so make a copy to keep it un-escaped
# #println("didn't escape maybecopy")
# stmt.args[1] = Main.Base.copy
# else # already escaped, so save the cost of copying and just pass the actual object
# #println("escaped maybecopy")
# ir.stmts[idx][:inst] = arr
# end
# end

return ir
end
Loading