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

WIP: ImmutableArrays (using EA in Base) #44381

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,8 @@ function copy(a::AbstractArray)
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}
if length(ir_dest) != length(ir_src)
Expand Down
57 changes: 47 additions & 10 deletions base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,36 @@ Union type of [`DenseVector{T}`](@ref) and [`DenseMatrix{T}`](@ref).
"""
const DenseVecOrMat{T} = Union{DenseVector{T}, DenseMatrix{T}}

"""
ImmutableArray{T,N} <: AbstractArray{T,N}
Dynamically allocated, immutable array.
"""
const ImmutableArray = Core.ImmutableArray

"""
ImmutableVector{T} <: AbstractVector{T}
Dynamically allocated, immutable vector.
"""
const ImmutableVector{T} = ImmutableArray{T,1}

"""
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 +177,15 @@ 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)
ImmutableArray(a::Array) = Core.arrayfreeze(a)
Array(a::ImmutableArray) = Core.arraythaw(a)

asize_from(a::Array, n) = n > ndims(a) ? () : (arraysize(a,n), asize_from(a, n+1)...)
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::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 @@ -212,11 +245,11 @@ function bitsunionsize(u::Union)
return sz
end

length(a::Array) = arraylen(a)
length(a::IMArray) = arraylen(a)
elsize(@nospecialize _::Type{A}) where {T,A<:Array{T}} = aligned_sizeof(T)
sizeof(a::Array) = Core.sizeof(a)
sizeof(a::IMArray) = 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 @@ -611,7 +644,7 @@ 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(T::Type{<:IMArray}, a::AbstractArray) = a isa T ? a : T(a)
convert(::Type{Union{}}, a::AbstractArray) = throw(MethodError(convert, (Union{}, 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 +655,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,7 +955,10 @@ 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...))

# Faster contiguous indexing using copyto! for AbstractUnitRange and Colon
@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 UnitRange and Colon
function getindex(A::Array, I::AbstractUnitRange{<:Integer})
@inline
@boundscheck checkbounds(A, I)
Expand Down
60 changes: 33 additions & 27 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -401,33 +401,39 @@ _new(:QuoteNode, :Any)
_new(:SSAValue, :Int)
_new(:Argument, :Int)
_new(:ReturnNode, :Any)
eval(Core, :(ReturnNode() = $(Expr(:new, :ReturnNode)))) # unassigned val indicates unreachable
eval(Core, :(GotoIfNot(@nospecialize(cond), dest::Int) = $(Expr(:new, :GotoIfNot, :cond, :dest))))
eval(Core, :(LineNumberNode(l::Int) = $(Expr(:new, :LineNumberNode, :l, nothing))))
eval(Core, :(LineNumberNode(l::Int, @nospecialize(f)) = $(Expr(:new, :LineNumberNode, :l, :f))))
LineNumberNode(l::Int, f::String) = LineNumberNode(l, Symbol(f))
eval(Core, :(GlobalRef(m::Module, s::Symbol) = $(Expr(:new, :GlobalRef, :m, :s))))
eval(Core, :(SlotNumber(n::Int) = $(Expr(:new, :SlotNumber, :n))))
eval(Core, :(TypedSlot(n::Int, @nospecialize(t)) = $(Expr(:new, :TypedSlot, :n, :t))))
eval(Core, :(PhiNode(edges::Array{Int32, 1}, values::Array{Any, 1}) = $(Expr(:new, :PhiNode, :edges, :values))))
eval(Core, :(PiNode(val, typ) = $(Expr(:new, :PiNode, :val, :typ))))
eval(Core, :(PhiCNode(values::Array{Any, 1}) = $(Expr(:new, :PhiCNode, :values))))
eval(Core, :(UpsilonNode(val) = $(Expr(:new, :UpsilonNode, :val))))
eval(Core, :(UpsilonNode() = $(Expr(:new, :UpsilonNode))))
eval(Core, :(LineInfoNode(mod::Module, @nospecialize(method), file::Symbol, line::Int, inlined_at::Int) =
$(Expr(:new, :LineInfoNode, :mod, :method, :file, :line, :inlined_at))))
eval(Core, :(CodeInstance(mi::MethodInstance, @nospecialize(rettype), @nospecialize(inferred_const),
@nospecialize(inferred), const_flags::Int32,
min_world::UInt, max_world::UInt, ipo_effects::UInt8, effects::UInt8,
relocatability::UInt8) =
ccall(:jl_new_codeinst, Ref{CodeInstance}, (Any, Any, Any, Any, Int32, UInt, UInt, UInt8, UInt8, UInt8),
mi, rettype, inferred_const, inferred, const_flags, min_world, max_world, ipo_effects, effects, relocatability)))
eval(Core, :(Const(@nospecialize(v)) = $(Expr(:new, :Const, :v))))
eval(Core, :(PartialStruct(@nospecialize(typ), fields::Array{Any, 1}) = $(Expr(:new, :PartialStruct, :typ, :fields))))
eval(Core, :(PartialOpaque(@nospecialize(typ), @nospecialize(env), isva::Bool, parent::MethodInstance, source::Method) = $(Expr(:new, :PartialOpaque, :typ, :env, :isva, :parent, :source))))
eval(Core, :(InterConditional(slot::Int, @nospecialize(vtype), @nospecialize(elsetype)) = $(Expr(:new, :InterConditional, :slot, :vtype, :elsetype))))
eval(Core, :(MethodMatch(@nospecialize(spec_types), sparams::SimpleVector, method::Method, fully_covers::Bool) =
$(Expr(:new, :MethodMatch, :spec_types, :sparams, :method, :fully_covers))))
eval(Core, quote
ReturnNode() = $(Expr(:new, :ReturnNode)) # unassigned val indicates unreachable
GotoIfNot(@nospecialize(cond), dest::Int) = $(Expr(:new, :GotoIfNot, :cond, :dest))
LineNumberNode(l::Int) = $(Expr(:new, :LineNumberNode, :l, nothing))
function LineNumberNode(l::Int, @nospecialize(f))
isa(f, String) && (f = Symbol(f))
return $(Expr(:new, :LineNumberNode, :l, :f))
end
LineInfoNode(mod::Module, @nospecialize(method), file::Symbol, line::Int, inlined_at::Int) =
$(Expr(:new, :LineInfoNode, :mod, :method, :file, :line, :inlined_at))
GlobalRef(m::Module, s::Symbol) = $(Expr(:new, :GlobalRef, :m, :s))
SlotNumber(n::Int) = $(Expr(:new, :SlotNumber, :n))
TypedSlot(n::Int, @nospecialize(t)) = $(Expr(:new, :TypedSlot, :n, :t))
PhiNode(edges::Array{Int32, 1}, values::Array{Any, 1}) = $(Expr(:new, :PhiNode, :edges, :values))
PiNode(@nospecialize(val), @nospecialize(typ)) = $(Expr(:new, :PiNode, :val, :typ))
PhiCNode(values::Array{Any, 1}) = $(Expr(:new, :PhiCNode, :values))
UpsilonNode(@nospecialize(val)) = $(Expr(:new, :UpsilonNode, :val))
UpsilonNode() = $(Expr(:new, :UpsilonNode))
function CodeInstance(
mi::MethodInstance, @nospecialize(rettype), @nospecialize(inferred_const),
@nospecialize(inferred), const_flags::Int32, min_world::UInt, max_world::UInt,
ipo_effects::UInt8, effects::UInt8, @nospecialize(argescapes#=::Union{Nothing,Vector{ArgEscapeInfo}}=#),
relocatability::UInt8)
return ccall(:jl_new_codeinst, Ref{CodeInstance},
(Any, Any, Any, Any, Int32, UInt, UInt, UInt8, UInt8, Any, UInt8),
mi, rettype, inferred_const, inferred, const_flags, min_world, max_world, ipo_effects, effects, argescapes, relocatability)
end
Const(@nospecialize(v)) = $(Expr(:new, :Const, :v))
PartialStruct(@nospecialize(typ), fields::Array{Any, 1}) = $(Expr(:new, :PartialStruct, :typ, :fields))
PartialOpaque(@nospecialize(typ), @nospecialize(env), isva::Bool, parent::MethodInstance, source::Method) = $(Expr(:new, :PartialOpaque, :typ, :env, :isva, :parent, :source))
InterConditional(slot::Int, @nospecialize(vtype), @nospecialize(elsetype)) = $(Expr(:new, :InterConditional, :slot, :vtype, :elsetype))
MethodMatch(@nospecialize(spec_types), sparams::SimpleVector, method::Method, fully_covers::Bool) = $(Expr(:new, :MethodMatch, :spec_types, :sparams, :method, :fully_covers))
end)

Module(name::Symbol=:anonymous, std_imports::Bool=true, default_names::Bool=true) = ccall(:jl_f_new_module, Ref{Module}, (Any, Bool, Bool), name, std_imports, default_names)

Expand Down
13 changes: 13 additions & 0 deletions base/broadcast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1345,4 +1345,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
14 changes: 12 additions & 2 deletions base/compiler/bootstrap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@ let
world = get_world_counter()
interp = NativeInterpreter(world)

analyze_escapes_tt = Any[typeof(analyze_escapes), IRCode, Int, Bool,
# typeof(get_escape_cache(code_cache(interp))) # once we enable IPO EA
typeof(null_escape_cache)
]
analyze_escapes_tt = Tuple{analyze_escapes_tt...}
fs = Any[
# we first create caches for the optimizer, because they contain many loop constructions
# and they're better to not run in interpreter even during bootstrapping
run_passes,
analyze_escapes_tt, run_passes,
# then we create caches for inference entries
typeinf_ext, typeinf, typeinf_edge,
]
Expand All @@ -32,7 +37,12 @@ let
end
starttime = time()
for f in fs
for m in _methods_by_ftype(Tuple{typeof(f), Vararg{Any}}, 10, typemax(UInt))
if isa(f, DataType) && f.name === typename(Tuple)
tt = f
else
tt = Tuple{typeof(f), Vararg{Any}}
end
for m in _methods_by_ftype(tt, 10, typemax(UInt))
# remove any TypeVars from the intersection
typ = Any[m.spec_types.parameters...]
for i = 1:length(typ)
Expand Down
2 changes: 2 additions & 0 deletions base/compiler/compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ ntuple(f, n) = (Any[f(i) for i = 1:n]...,)

# core docsystem
include("docs/core.jl")
import Core.Compiler.CoreDocs
Core.atdoc!(CoreDocs.docm)

# sorting
function sort end
Expand Down
115 changes: 80 additions & 35 deletions base/compiler/optimize.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

#############
# constants #
#############

# The slot has uses that are not statically dominated by any assignment
# This is implied by `SLOT_USEDUNDEF`.
# If this is not set, all the uses are (statically) dominated by the defs.
# In particular, if a slot has `AssignedOnce && !StaticUndef`, it is an SSA.
const SLOT_STATICUNDEF = 1 # slot might be used before it is defined (structurally)
const SLOT_ASSIGNEDONCE = 16 # slot is assigned to only once
const SLOT_USEDUNDEF = 32 # slot has uses that might raise UndefVarError
# const SLOT_CALLED = 64

# NOTE make sure to sync the flag definitions below with julia.h and `jl_code_info_set_ir` in method.c

const IR_FLAG_NULL = 0x00
# This statement is marked as @inbounds by user.
# Ff replaced by inlining, any contained boundschecks may be removed.
const IR_FLAG_INBOUNDS = 0x01 << 0
# This statement is marked as @inline by user
const IR_FLAG_INLINE = 0x01 << 1
# This statement is marked as @noinline by user
const IR_FLAG_NOINLINE = 0x01 << 2
const IR_FLAG_THROW_BLOCK = 0x01 << 3
# This statement may be removed if its result is unused. In particular it must
# thus be both pure and effect free.
const IR_FLAG_EFFECT_FREE = 0x01 << 4

const TOP_TUPLE = GlobalRef(Core, :tuple)

#####################
# OptimizationState #
#####################
Expand All @@ -21,10 +51,10 @@ function push!(et::EdgeTracker, ci::CodeInstance)
push!(et, ci.def)
end

struct InliningState{S <: Union{EdgeTracker, Nothing}, T, I<:AbstractInterpreter}
struct InliningState{S <: Union{EdgeTracker, Nothing}, MICache, I<:AbstractInterpreter}
params::OptimizationParams
et::S
mi_cache::T
mi_cache::MICache # TODO move this to `OptimizationState` (as used by EscapeAnalysis as well)
interp::I
end

Expand Down Expand Up @@ -52,7 +82,35 @@ function inlining_policy(interp::AbstractInterpreter, @nospecialize(src), stmt_f
return nothing
end

function argextype end # imported by EscapeAnalysis
function stmt_effect_free end # imported by EscapeAnalysis
function alloc_array_ndims end # imported by EscapeAnalysis
include("compiler/ssair/driver.jl")
using .EscapeAnalysis
import .EscapeAnalysis: EscapeState, ArgEscapeCache, is_ipo_profitable

"""
cache_escapes!(caller::InferenceResult, estate::EscapeState)

Transforms escape information of call arguments of `caller`,
and then caches it into a global cache for later interprocedural propagation.
"""
cache_escapes!(caller::InferenceResult, estate::EscapeState) =
caller.argescapes = ArgEscapeCache(estate)

function ipo_escape_cache(mi_cache::MICache) where MICache
return function (linfo::Union{InferenceResult,MethodInstance})
if isa(linfo, InferenceResult)
argescapes = linfo.argescapes
else
codeinst = get(mi_cache, linfo, nothing)
isa(codeinst, CodeInstance) || return nothing
argescapes = codeinst.argescapes
end
return argescapes !== nothing ? argescapes::ArgEscapeCache : nothing
end
end
null_escape_cache(linfo::Union{InferenceResult,MethodInstance}) = nothing

mutable struct OptimizationState
linfo::MethodInstance
Expand Down Expand Up @@ -121,36 +179,6 @@ function ir_to_codeinf!(opt::OptimizationState)
return src
end

#############
# constants #
#############

# The slot has uses that are not statically dominated by any assignment
# This is implied by `SLOT_USEDUNDEF`.
# If this is not set, all the uses are (statically) dominated by the defs.
# In particular, if a slot has `AssignedOnce && !StaticUndef`, it is an SSA.
const SLOT_STATICUNDEF = 1 # slot might be used before it is defined (structurally)
const SLOT_ASSIGNEDONCE = 16 # slot is assigned to only once
const SLOT_USEDUNDEF = 32 # slot has uses that might raise UndefVarError
# const SLOT_CALLED = 64

# NOTE make sure to sync the flag definitions below with julia.h and `jl_code_info_set_ir` in method.c

const IR_FLAG_NULL = 0x00
# This statement is marked as @inbounds by user.
# Ff replaced by inlining, any contained boundschecks may be removed.
const IR_FLAG_INBOUNDS = 0x01 << 0
# This statement is marked as @inline by user
const IR_FLAG_INLINE = 0x01 << 1
# This statement is marked as @noinline by user
const IR_FLAG_NOINLINE = 0x01 << 2
const IR_FLAG_THROW_BLOCK = 0x01 << 3
# This statement may be removed if its result is unused. In particular it must
# thus be both pure and effect free.
const IR_FLAG_EFFECT_FREE = 0x01 << 4

const TOP_TUPLE = GlobalRef(Core, :tuple)

#########
# logic #
#########
Expand Down Expand Up @@ -503,19 +531,36 @@ end
# run the optimization work
function optimize(interp::AbstractInterpreter, opt::OptimizationState,
params::OptimizationParams, caller::InferenceResult)
@timeit "optimizer" ir = run_passes(opt.src, opt)
@timeit "optimizer" ir = run_passes(opt.src, opt, caller)
return finish(interp, opt, params, ir, caller)
end

function run_passes(ci::CodeInfo, sv::OptimizationState)
function run_passes(ci::CodeInfo, sv::OptimizationState, caller::InferenceResult)
@timeit "convert" ir = convert_to_ircode(ci, sv)
@timeit "slot2reg" ir = slot2reg(ir, ci, sv)
# TODO: Domsorting can produce an updated domtree - no need to recompute here
@timeit "compact 1" ir = compact!(ir)
nargs = let def = sv.linfo.def; isa(def, Method) ? Int(def.nargs) : 0; end
# if is_ipo_profitable(ir, nargs)
# @timeit "IPO EA" begin
# state = analyze_escapes(ir,
# nargs, #=call_resolved=#false, ipo_escape_cache(sv.inlining.mi_cache))
# cache_escapes!(caller, state)
# end
# end
@timeit "Inlining" ir = ssa_inlining_pass!(ir, ir.linetable, sv.inlining, ci.propagate_inbounds)
# @timeit "verify 2" verify_ir(ir)
@timeit "compact 2" ir = compact!(ir)
@timeit "SROA" ir = sroa_pass!(ir)
@timeit "SROA" ir, memory_opt, imarray_memory_opt = linear_pass!(ir)
if memory_opt || imarray_memory_opt
@timeit "Local EA" estate = analyze_escapes(ir, nargs, #=call_resolved=#true, null_escape_cache)
if memory_opt
@timeit "Memory opt" ir = memory_opt_pass!(ir, estate)
end
if imarray_memory_opt
@timeit "imarray opt" ir = imarray_memoryopt_pass!(ir, estate)
end
end
@timeit "ADCE" ir = adce_pass!(ir)
@timeit "type lift" ir = type_lift_pass!(ir)
@timeit "compact 3" ir = compact!(ir)
Expand Down
Loading