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 24 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
1 change: 1 addition & 0 deletions base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1078,6 +1078,7 @@ function copy(a::AbstractArray)
@_propagate_inbounds_meta
copymutable(a)
end
copy(a::Core.ImmutableArray) = a

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}
Expand Down
57 changes: 50 additions & 7 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,13 @@ 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; ntuple(M -> size(a, M), Val(N))::Dims)
# 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; ntuple(M -> size(a, M), Val(N))::Dims)

asize_from(a::Array, n) = n > ndims(a) ? () : (arraysize(a,n), asize_from(a, n+1)...)
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 @@ -216,7 +246,7 @@ length(a::Array) = arraylen(a)
elsize(::Type{<:Array{T}}) where {T} = aligned_sizeof(T)
sizeof(a::Array) = Core.sizeof(a)

function isassigned(a::Array, i::Int...)
function isassigned(a::IMArray, i::Int...)
@inline
ii = (_sub2ind(size(a), i...) % UInt) - 1
@boundscheck ii < length(a) % UInt || return false
Expand Down Expand Up @@ -378,6 +408,15 @@ 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)

"""
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.
"""
const maybecopy = Core.maybecopy

# T[x...] constructs Array{T,1}
"""
getindex(type[, elements...])
Expand Down Expand Up @@ -611,8 +650,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::Type{<:IMArray}, a::AbstractArray) = a isa T ? a : T(a)

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 +661,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 @@ -921,6 +961,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; 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; arrayref($(Expr(:boundscheck)), A, i1, i2, I...))

# Faster contiguous indexing using copyto! for AbstractUnitRange and Colon
function getindex(A::Array, I::AbstractUnitRange{<:Integer})
@inline
Expand Down
21 changes: 17 additions & 4 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,14 @@ struct BoundsError <: Exception
a::Any
i::Any
BoundsError() = new()
BoundsError(@nospecialize(a)) = (@noinline; new(a))
BoundsError(@nospecialize(a), i) = (@noinline; new(a,i))
# maybecopy --- non-semantic copy
# if escape analysis proves that this throw is the only place where an object would escape local scope,
# creates a copy to avoid that escape and enable memory optimization through memory_opt!
# otherwise if there are other escapes, maybecopy does not copy and just passes the object
BoundsError(@nospecialize(a)) = (@noinline;
a isa Array ? new(Core.maybecopy(a)) : new(a))
Copy link
Member

Choose a reason for hiding this comment

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

Can we extend this beyond array? This is very much a problem for MArray and would greatly improve it's usability on the GPU.

Copy link
Member

@aviatesk aviatesk Jan 8, 2022

Choose a reason for hiding this comment

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

If we can enforce copy implementation for anything implementing getindex.
But note that all the purpose of this cop? is that it allows EA to skip possible escapes through arrayref and thus this optimization atm is very specific to Arrays. This could also be generalized if we implement some specific interface which tells EA that user type object is always copied upon BoundsError though.

Having said that, I'm not really fan of changing the program semantics this way. Why not always making copy of it? BoundsError doesn't need to be high-performing.

Copy link
Member

Choose a reason for hiding this comment

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

also making the default behavior of maybecopy no-op seems bad.. the default should be copy and we optimize it as no-op when the array is already escaped somewhere else.

Copy link
Member Author

Choose a reason for hiding this comment

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

I do think the plan is eventually to have similar functionality as ImmutableArrays for other types, but yeah Shuhei is right that this initial PR is very Array-centric.

As for the non-semantic copy, I agree that the default should be copy - I misremembered that this was the approach we were taking (possibly because of some old incorrect test I had written). I’ll correct it shortly.

Copy link
Member

Choose a reason for hiding this comment

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

No hurry from my side ;)
We can also talk with other core devs again to discuss if we can make copies by default?

Copy link
Member Author

Choose a reason for hiding this comment

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

Update: #43738 is our new solution to this

BoundsError(@nospecialize(a), i) = (@noinline;
a isa Array ? new(Core.maybecopy(a), i) : new(a, i))
end
struct DivideError <: Exception end
struct OutOfMemoryError <: Exception end
Expand Down Expand Up @@ -465,18 +471,25 @@ Array{T,N}(::UndefInitializer, d::NTuple{N,Int}) where {T,N} = ccall(:jl_new_arr
Array{T}(::UndefInitializer, m::Int) where {T} = Array{T,1}(undef, m)
Array{T}(::UndefInitializer, m::Int, n::Int) where {T} = Array{T,2}(undef, m, n)
Array{T}(::UndefInitializer, m::Int, n::Int, o::Int) where {T} = Array{T,3}(undef, m, n, o)
Array{T}(::UndefInitializer, d::NTuple{N,Int}) where {T,N} = Array{T,N}(undef, d)
Array{T}(::UndefInitializer, d::NTuple{N,Int}#=::Dims=#) where {T,N} = Array{T,N}(undef, d)
# empty vector constructor
Array{T,1}() where {T} = Array{T,1}(undef, 0)


(Array{T,N} where T)(x::AbstractArray{S,N}) where {S,N} = Array{S,N}(x)

Array(A::AbstractArray{T,N}) where {T,N} = Array{T,N}(A)
Array{T}(A::AbstractArray{S,N}) where {T,N,S} = Array{T,N}(A)

AbstractArray{T}(A::AbstractArray{S,N}) where {T,S,N} = AbstractArray{T,N}(A)

# freeze and thaw constructors
ImmutableArray(a::Array) = arrayfreeze(a)
ImmutableArray(a::AbstractArray{T,N}) where {T,N} = ImmutableArray{T,N}(a)
Array(a::ImmutableArray) = arraythaw(a)
# undef initializers
ImmutableArray{T,N}(::UndefInitializer, args...) where {T,N} = ImmutableArray(Array{T,N}(undef, args...))
ImmutableArray{T}(::UndefInitializer, args...) where {T} = ImmutableArray(Array{T}(undef, args...))

# primitive Symbol constructors
eval(Core, :(function Symbol(s::String)
$(Expr(:meta, :pure))
Expand Down
13 changes: 13 additions & 0 deletions base/broadcast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1344,4 +1344,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
Loading