Skip to content

Commit

Permalink
allow @nospecialize-d push! to take arbitrary items
Browse files Browse the repository at this point in the history
Currently the `@nospecialize`-d `push!(::Vector{Any}, ...)` can only
take a single item and we will end up with runtime dispatch when we try
to call it with multiple items:
```julia
julia> code_typed(push!, (Vector{Any}, Any))
1-element Vector{Any}:
 CodeInfo(
1 ─      $(Expr(:foreigncall, :(:jl_array_grow_end), Nothing, svec(Any, UInt64), 0, :(:ccall), Core.Argument(2), 0x0000000000000001, 0x0000000000000001))::Nothing
│   %2 = Base.arraylen(a)::Int64
│        Base.arrayset(true, a, item, %2)::Vector{Any}
└──      return a
) => Vector{Any}

julia> code_typed(push!, (Vector{Any}, Any, Any))
1-element Vector{Any}:
 CodeInfo(
1 ─ %1 = Base.append!(a, iter)::Vector{Any}
└──      return %1
) => Vector{Any}
```

This commit extends it so that it can take arbitrary-length items.
Our compiler should still be able to optimize the single-input case as
before by unrolling using the constant item length information:
```julia
julia> code_typed(push!, (Vector{Any}, Any))
1-element Vector{Any}:
 CodeInfo(
1 ─      $(Expr(:foreigncall, :(:jl_array_grow_end), Nothing, svec(Any, UInt64), 0, :(:ccall), Core.Argument(2), 0x0000000000000001, 0x0000000000000001))::Nothing
│   %2 = Base.arraylen(a)::Int64
│        Base.arrayset(true, a, item, %2)::Vector{Any}
└──      return a
) => Vector{Any}

julia> code_typed(push!, (Vector{Any}, Any, Any))
1-element Vector{Any}:
 CodeInfo(
1 ─ %1  = Base.arraylen(a)::Int64
│         $(Expr(:foreigncall, :(:jl_array_grow_end), Nothing, svec(Any, UInt64), 0, :(:ccall), Core.Argument(2), 0x0000000000000002, 0x0000000000000002))::Nothing
└──       goto #7 if not true
2 ┄ %4  = φ (#1 => 1, #6 => %14)::Int64
│   %5  = φ (#1 => 1, #6 => %15)::Int64
│   %6  = Base.getfield(x, %4, true)::Any
│   %7  = Base.add_int(%1, %4)::Int64
│         Base.arrayset(true, a, %6, %7)::Vector{Any}
│   %9  = (%5 === 2)::Bool
└──       goto #4 if not %9
3 ─       goto #5
4 ─ %12 = Base.add_int(%5, 1)::Int64
└──       goto #5
5 ┄ %14 = φ (#4 => %12)::Int64
│   %15 = φ (#4 => %12)::Int64
│   %16 = φ (#3 => true, #4 => false)::Bool
│   %17 = Base.not_int(%16)::Bool
└──       goto #7 if not %17
6 ─       goto #2
7 ┄       return a
) => Vector{Any}
```
  • Loading branch information
aviatesk committed Jun 24, 2022
1 parent 51c8812 commit 65a82e8
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 3 deletions.
10 changes: 7 additions & 3 deletions base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1053,9 +1053,13 @@ function push!(a::Array{T,1}, item) where T
return a
end

function push!(a::Array{Any,1}, @nospecialize item)
_growend!(a, 1)
arrayset(true, a, item, length(a))
function push!(a::Vector{Any}, @nospecialize x...)
na = length(a)
nx = length(x)
_growend!(a, nx)
for i = 1:nx
arrayset(true, a, x[i], na+i)
end
return a
end

Expand Down
18 changes: 18 additions & 0 deletions test/compiler/inline.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1375,3 +1375,21 @@ let src = code_typed1() do
@test count(isnew, src.code) == 1
@test count(isinvoke(:noinline_finalizer), src.code) == 1
end

# optimize `push!(::Vector{Any}, x...)`
let src = code_typed1((Vector{Any}, Any)) do xs, x
push!(xs, x)
end
@test count(iscall((src, push!)), src.code) == 0
end
let src = code_typed1((Vector{Any}, Any, Any)) do xs, x, y
push!(xs, x, y)
end
@test count(iscall((src, push!)), src.code) == 0
end
let xs = Any[]
push!(xs, :x, "y", 'z')
@test xs[1] === :x
@test xs[2] == "y"
@test xs[3] === 'z'
end

0 comments on commit 65a82e8

Please sign in to comment.