Skip to content

Commit

Permalink
improve @nospecialize-d [push!|pushfirst!] implementations (Julia…
Browse files Browse the repository at this point in the history
…Lang#45790)

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 adds a new specialization that it can take arbitrary-length
items. Our compiler should still be able to optimize the single-input 
case as before via the dispatch mechanism.
```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 JuliaLang#7 if not true
2 ┄ %4  = φ (JuliaLang#1 => 1, JuliaLang#6 => %14)::Int64
│   %5  = φ (JuliaLang#1 => 1, JuliaLang#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 JuliaLang#4 if not %9
3 ─       goto JuliaLang#5
4 ─ %12 = Base.add_int(%5, 1)::Int64
└──       goto JuliaLang#5
5 ┄ %14 = φ (JuliaLang#4 => %12)::Int64
│   %15 = φ (JuliaLang#4 => %12)::Int64
│   %16 = φ (JuliaLang#3 => true, JuliaLang#4 => false)::Bool
│   %17 = Base.not_int(%16)::Bool
└──       goto JuliaLang#7 if not %17
6 ─       goto JuliaLang#2
7 ┄       return a
) => Vector{Any}
```

This commit also adds the equivalent implementations for `pushfirst!`.
  • Loading branch information
aviatesk authored and pcjentsch committed Aug 18, 2022
1 parent a8d9aa0 commit bd23ab2
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 2 deletions.
30 changes: 28 additions & 2 deletions base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -1385,6 +1395,22 @@ function pushfirst!(a::Array{T,1}, item) where T
return a
end

# specialize and optimize the single argument case
function pushfirst!(a::Vector{Any}, @nospecialize x)
_growbeg!(a, 1)
a[1] = x
return a
end
function pushfirst!(a::Vector{Any}, @nospecialize x...)
na = length(a)
nx = length(x)
_growbeg!(a, nx)
for i = 1:nx
a[i] = x[i]
end
return a
end

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

# optimize `[push!|pushfirst!](::Vector{Any}, x...)`
@testset "optimize `$f(::Vector{Any}, x...)`" for f = Any[push!, pushfirst!]
@eval begin
let src = code_typed1((Vector{Any}, Any)) do xs, x
$f(xs, x)
end
@test count(iscall((src, $f)), 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
$f(xs, x, y)
end
@test count(iscall((src, $f)), src.code) == 0
end
let xs = Any[]
$f(xs, :x, "y", 'z')
@test xs[1] === :x
@test xs[2] == "y"
@test xs[3] === 'z'
end
end
end

0 comments on commit bd23ab2

Please sign in to comment.