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 25, 2022
1 parent 51c8812 commit d79e61f
Showing 2 changed files with 35 additions and 2 deletions.
14 changes: 12 additions & 2 deletions base/array.jl
Original file line number Diff line number Diff line change
@@ -1053,9 +1053,19 @@ function push!(a::Array{T,1}, item) where T
return a
end

function push!(a::Array{Any,1}, @nospecialize item)
# specialize and optimize the single argument case
function push!(a::Vector{Any}, @nospecialize x)
_growend!(a, 1)
arrayset(true, a, item, length(a))
arrayset(true, a, x, length(a))
return a
end
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

23 changes: 23 additions & 0 deletions test/compiler/inline.jl
Original file line number Diff line number Diff line change
@@ -1375,3 +1375,26 @@ 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
@test count(src.code) do @nospecialize x
isa(x, Core.GotoNode) ||
isa(x, Core.GotoIfNot) ||
iscall((src, getfield))(x)
end == 0 # no loop should be involved for the common single arg case
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 d79e61f

Please sign in to comment.