-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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
Restructure of the promotion mechanism for broadcast #18642
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ | |
module Broadcast | ||
|
||
using Base.Cartesian | ||
using Base: promote_eltype_op, linearindices, tail, OneTo, to_shape, | ||
using Base: promote_eltype_op, _default_eltype, linearindices, tail, OneTo, to_shape, | ||
_msk_end, unsafe_bitgetindex, bitcache_chunks, bitcache_size, dumpbitcache | ||
import Base: .+, .-, .*, ./, .\, .//, .==, .<, .!=, .<=, .÷, .%, .<<, .>>, .^ | ||
import Base: broadcast | ||
|
@@ -16,7 +16,7 @@ export broadcast_getindex, broadcast_setindex! | |
broadcast(f) = f() | ||
@inline broadcast(f, x::Number...) = f(x...) | ||
@inline broadcast{N}(f, t::NTuple{N}, ts::Vararg{NTuple{N}}) = map(f, t, ts...) | ||
@inline broadcast(f, As::AbstractArray...) = broadcast_t(f, promote_eltype_op(f, As...), As...) | ||
@inline broadcast(f, As::AbstractArray...) = broadcast_c(f, Array, As...) | ||
|
||
# special cases for "X .= ..." (broadcast!) assignments | ||
broadcast!(::typeof(identity), X::AbstractArray, x::Number) = fill!(X, x) | ||
|
@@ -127,14 +127,14 @@ Base.@propagate_inbounds _broadcast_getindex(::Any, A, I) = A[I] | |
## Broadcasting core | ||
# nargs encodes the number of As arguments (which matches the number | ||
# of keeps). The first two type parameters are to ensure specialization. | ||
@generated function _broadcast!{K,ID,AT,nargs}(f, B::AbstractArray, keeps::K, Idefaults::ID, As::AT, ::Type{Val{nargs}}) | ||
@generated function _broadcast!{K,ID,AT,nargs}(f, B::AbstractArray, keeps::K, Idefaults::ID, As::AT, ::Type{Val{nargs}}, iter) | ||
quote | ||
$(Expr(:meta, :noinline)) | ||
# destructure the keeps and As tuples | ||
@nexprs $nargs i->(A_i = As[i]) | ||
@nexprs $nargs i->(keep_i = keeps[i]) | ||
@nexprs $nargs i->(Idefault_i = Idefaults[i]) | ||
@simd for I in CartesianRange(indices(B)) | ||
@simd for I in iter | ||
# reverse-broadcast the indices | ||
@nexprs $nargs i->(I_i = newindex(I, keep_i, Idefault_i)) | ||
# extract array values | ||
|
@@ -148,7 +148,7 @@ end | |
|
||
# For BitArray outputs, we cache the result in a "small" Vector{Bool}, | ||
# and then copy in chunks into the output | ||
@generated function _broadcast!{K,ID,AT,nargs}(f, B::BitArray, keeps::K, Idefaults::ID, As::AT, ::Type{Val{nargs}}) | ||
@generated function _broadcast!{K,ID,AT,nargs}(f, B::BitArray, keeps::K, Idefaults::ID, As::AT, ::Type{Val{nargs}}, iter) | ||
quote | ||
$(Expr(:meta, :noinline)) | ||
# destructure the keeps and As tuples | ||
|
@@ -159,7 +159,7 @@ end | |
Bc = B.chunks | ||
ind = 1 | ||
cind = 1 | ||
@simd for I in CartesianRange(indices(B)) | ||
@simd for I in iter | ||
# reverse-broadcast the indices | ||
@nexprs $nargs i->(I_i = newindex(I, keep_i, Idefault_i)) | ||
# extract array values | ||
|
@@ -193,12 +193,12 @@ as in `broadcast!(f, A, A, B)` to perform `A[:] = broadcast(f, A, B)`. | |
shape = indices(B) | ||
check_broadcast_indices(shape, As...) | ||
keeps, Idefaults = map_newindexer(shape, As) | ||
_broadcast!(f, B, keeps, Idefaults, As, Val{nargs}) | ||
B | ||
iter = CartesianRange(shape) | ||
_broadcast!(f, B, keeps, Idefaults, As, Val{nargs}, iter) | ||
return B | ||
end | ||
|
||
# broadcast with computed element type | ||
|
||
@generated function _broadcast!{K,ID,AT,nargs}(f, B::AbstractArray, keeps::K, Idefaults::ID, As::AT, ::Type{Val{nargs}}, iter, st, count) | ||
quote | ||
$(Expr(:meta, :noinline)) | ||
|
@@ -233,12 +233,8 @@ end | |
end | ||
end | ||
|
||
function broadcast_t(f, ::Type{Any}, As...) | ||
shape = broadcast_indices(As...) | ||
iter = CartesianRange(shape) | ||
if isempty(iter) | ||
return similar(Array{Any}, shape) | ||
end | ||
# broadcast methods that dispatch on the type found by inference | ||
function broadcast_t(f, ::Type{Any}, shape, iter, As...) | ||
nargs = length(As) | ||
keeps, Idefaults = map_newindexer(shape, As) | ||
st = start(iter) | ||
|
@@ -248,17 +244,46 @@ function broadcast_t(f, ::Type{Any}, As...) | |
B[I] = val | ||
return _broadcast!(f, B, keeps, Idefaults, As, Val{nargs}, iter, st, 1) | ||
end | ||
@inline function broadcast_t(f, T, shape, iter, As...) | ||
B = similar(Array{T}, shape) | ||
nargs = length(As) | ||
keeps, Idefaults = map_newindexer(shape, As) | ||
_broadcast!(f, B, keeps, Idefaults, As, Val{nargs}, iter) | ||
return B | ||
end | ||
|
||
@inline broadcast_t(f, T, As...) = broadcast!(f, similar(Array{T}, broadcast_indices(As...)), As...) | ||
|
||
# broadcast method that uses inference to find the type, but preserves abstract | ||
# container types when possible (used by binary elementwise operators) | ||
@inline broadcast_elwise_op(f, As...) = | ||
broadcast!(f, similar(Array{promote_eltype_op(f, As...)}, broadcast_indices(As...)), As...) | ||
|
||
ftype(f, A) = typeof(a -> f(a)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this just be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I think so, I don't know why I put it like this (same for the one below). |
||
ftype(f, A...) = typeof(a -> f(a...)) | ||
ftype(T::DataType, A) = Type{T} | ||
ftype(T::DataType, A...) = Type{T} | ||
ziptype(A) = Tuple{eltype(A)} | ||
ziptype(A, B) = Iterators.Zip2{Tuple{eltype(A)}, Tuple{eltype(B)}} | ||
@inline ziptype(A, B, C, D...) = Iterators.Zip{Tuple{eltype(A)}, ziptype(B, C, D...)} | ||
|
||
# broadcast methods that dispatch on the type of the final container | ||
@inline function broadcast_c(f, ::Type{Array}, As...) | ||
T = _default_eltype(Base.Generator{ziptype(As...), ftype(f, As...)}) | ||
shape = broadcast_indices(As...) | ||
iter = CartesianRange(shape) | ||
if isleaftype(T) | ||
return broadcast_t(f, T, shape, iter, As...) | ||
end | ||
if isempty(iter) | ||
return similar(Array{T}, shape) | ||
end | ||
return broadcast_t(f, Any, shape, iter, As...) | ||
end | ||
function broadcast_c(f, ::Type{Tuple}, As...) | ||
shape = broadcast_indices(As...) | ||
check_broadcast_indices(shape, As...) | ||
n = length(shape[1]) | ||
return ntuple(k->f((_broadcast_getindex(A, k) for A in As)...), n) | ||
end | ||
@inline broadcast_c(f, ::Type{Any}, a...) = f(a...) | ||
@inline broadcast_c(f, ::Type{Array}, As...) = broadcast_t(f, promote_eltype_op(f, As...), As...) | ||
|
||
""" | ||
broadcast(f, As...) | ||
|
@@ -441,10 +466,10 @@ end | |
## elementwise operators ## | ||
|
||
for op in (:÷, :%, :<<, :>>, :-, :/, :\, ://, :^) | ||
@eval $(Symbol(:., op))(A::AbstractArray, B::AbstractArray) = broadcast($op, A, B) | ||
@eval $(Symbol(:., op))(A::AbstractArray, B::AbstractArray) = broadcast_elwise_op($op, A, B) | ||
end | ||
.+(As::AbstractArray...) = broadcast(+, As...) | ||
.*(As::AbstractArray...) = broadcast(*, As...) | ||
.+(As::AbstractArray...) = broadcast_elwise_op(+, As...) | ||
.*(As::AbstractArray...) = broadcast_elwise_op(*, As...) | ||
|
||
# ## element-wise comparison operators returning BitArray ## | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -217,34 +217,23 @@ max(x::Real, y::Real) = max(promote(x,y)...) | |
min(x::Real, y::Real) = min(promote(x,y)...) | ||
minmax(x::Real, y::Real) = minmax(promote(x, y)...) | ||
|
||
# "Promotion" that takes a function into account. These are meant to be | ||
# used mainly by broadcast methods, so it is advised against overriding them | ||
if isdefined(Core, :Inference) | ||
function _promote_op(op, T::ANY) | ||
G = Tuple{Generator{Tuple{T},typeof(op)}} | ||
return Core.Inference.return_type(first, G) | ||
end | ||
function _promote_op(op, R::ANY, S::ANY) | ||
F = typeof(a -> op(a...)) | ||
G = Tuple{Generator{Iterators.Zip2{Tuple{R},Tuple{S}},F}} | ||
return Core.Inference.return_type(first, G) | ||
end | ||
else | ||
_promote_op(::ANY...) = (@_pure_meta; Any) | ||
end | ||
# "Promotion" that takes a function into account and tries to preserve | ||
# non-concrete types. These are meant to be used mainly by elementwise | ||
# operations, so it is advised against overriding them | ||
_default_type(T::Type) = (@_pure_meta; T) | ||
|
||
promote_op(::Any...) = (@_pure_meta; Any) | ||
promote_op(T::Type, ::Any) = (@_pure_meta; T) | ||
promote_op(T::Type, ::Type) = (@_pure_meta; T) # To handle ambiguities | ||
# Promotion that tries to preserve non-concrete types | ||
function promote_op{S}(f, ::Type{S}) | ||
T = _promote_op(f, _default_type(S)) | ||
@_pure_meta | ||
Z = Tuple{_default_type(S)} | ||
T = _default_eltype(Generator{Z, typeof(a -> f(a))}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. |
||
isleaftype(S) && return isleaftype(T) ? T : Any | ||
return typejoin(S, T) | ||
end | ||
function promote_op{R,S}(f, ::Type{R}, ::Type{S}) | ||
T = _promote_op(f, _default_type(R), _default_type(S)) | ||
@_pure_meta | ||
Z = Iterators.Zip2{Tuple{_default_type(R)}, Tuple{_default_type(S)}} | ||
T = _default_eltype(Generator{Z, typeof(a -> f(a...))}) | ||
isleaftype(R) && isleaftype(S) && return isleaftype(T) ? T : Any | ||
return typejoin(R, S, T) | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -300,7 +300,6 @@ import Base.Meta: isexpr | |
# PR 16988 | ||
@test Base.promote_op(+, Bool) === Int | ||
@test isa(broadcast(+, [true]), Array{Int,1}) | ||
@test Base.promote_op(Float64, Bool) === Float64 | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you dropping this test because it would fail now? That would be unfortunate in case something outside Base relies on it... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this now return something different, or is it a method error? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I returns I guess I could leave the definition for uses outside Base addressing @martinholters concern above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should it be deprecated if base won't need it going forward? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I guess so, will do. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW promote_op(op::Type, T) = Core.Inference.return_type(op, Tuple{T})
promote_op(op::Type, T1, T2) = Core.Inference.return_type(op, Tuple{T1,T2}) seems to be inferable and handles cases where the constructor specializes, e.g. Not that I'm too worried about improving the obsolete |
||
# issue #17304 | ||
let foo = [[1,2,3],[4,5,6],[7,8,9]] | ||
|
@@ -312,7 +311,7 @@ end | |
let f17314 = x -> x < 0 ? false : x | ||
@test eltype(broadcast(f17314, 1:3)) === Int | ||
@test eltype(broadcast(f17314, -1:1)) === Integer | ||
@test eltype(broadcast(f17314, Int[])) === Any | ||
@test eltype(broadcast(f17314, Int[])) === Union{Bool,Int} | ||
end | ||
let io = IOBuffer() | ||
broadcast(x->print(io,x), 1:5) # broadcast with side effects | ||
|
@@ -337,3 +336,19 @@ end | |
@test broadcast(+, 1.0, (0, -2.0)) == (1.0,-1.0) | ||
@test broadcast(+, 1.0, (0, -2.0), [1]) == [2.0, 0.0] | ||
@test broadcast(*, ["Hello"], ", ", ["World"], "!") == ["Hello, World!"] | ||
|
||
# Ensure that even strange constructors that break `T(x)::T` work with broadcast | ||
immutable StrangeType18623 end | ||
StrangeType18623(x) = x | ||
StrangeType18623(x,y) = (x,y) | ||
@test @inferred broadcast(StrangeType18623, 1:3) == [1,2,3] | ||
@test @inferred broadcast(StrangeType18623, 1:3, 4:6) == [(1,4),(2,5),(3,6)] | ||
|
||
@test typeof(Int.(Number[1, 2, 3])) === typeof((x->Int(x)).(Number[1, 2, 3])) | ||
|
||
@test @inferred broadcast(CartesianIndex, 1:2) == [CartesianIndex(1), CartesianIndex(2)] | ||
@test @inferred broadcast(CartesianIndex, 1:2, 3:4) == [CartesianIndex(1,3), CartesianIndex(2,4)] | ||
|
||
# Issue 18622 | ||
@test @inferred muladd.([1.0], [2.0], [3.0])::Vector{Float64} == [5.0] | ||
@test @inferred tuple.(1:3, 4:6, 7:9)::Vector{Tuple{Int,Int,Int}} == [(1,4,7), (2,5,8), (3,6,9)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this being removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
broadcast_indices
already fails when the sizes are not compatible. The only places where we really need it is forbroadcast!
where we don't know the size of the supplied container.