Skip to content

Commit c686e4a

Browse files
authored
improve @nospecialize-d [push!|pushfirst!] implementations (JuliaLang#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 #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} ``` This commit also adds the equivalent implementations for `pushfirst!`.
1 parent f849517 commit c686e4a

File tree

2 files changed

+55
-2
lines changed

2 files changed

+55
-2
lines changed

base/array.jl

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,9 +1053,19 @@ function push!(a::Array{T,1}, item) where T
10531053
return a
10541054
end
10551055

1056-
function push!(a::Array{Any,1}, @nospecialize item)
1056+
# specialize and optimize the single argument case
1057+
function push!(a::Vector{Any}, @nospecialize x)
10571058
_growend!(a, 1)
1058-
arrayset(true, a, item, length(a))
1059+
arrayset(true, a, x, length(a))
1060+
return a
1061+
end
1062+
function push!(a::Vector{Any}, @nospecialize x...)
1063+
na = length(a)
1064+
nx = length(x)
1065+
_growend!(a, nx)
1066+
for i = 1:nx
1067+
arrayset(true, a, x[i], na+i)
1068+
end
10591069
return a
10601070
end
10611071

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

1398+
# specialize and optimize the single argument case
1399+
function pushfirst!(a::Vector{Any}, @nospecialize x)
1400+
_growbeg!(a, 1)
1401+
a[1] = x
1402+
return a
1403+
end
1404+
function pushfirst!(a::Vector{Any}, @nospecialize x...)
1405+
na = length(a)
1406+
nx = length(x)
1407+
_growbeg!(a, nx)
1408+
for i = 1:nx
1409+
a[i] = x[i]
1410+
end
1411+
return a
1412+
end
1413+
13881414
"""
13891415
popfirst!(collection) -> item
13901416

test/compiler/inline.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,3 +1375,30 @@ let src = code_typed1() do
13751375
@test count(isnew, src.code) == 1
13761376
@test count(isinvoke(:noinline_finalizer), src.code) == 1
13771377
end
1378+
1379+
# optimize `[push!|pushfirst!](::Vector{Any}, x...)`
1380+
@testset "optimize `$f(::Vector{Any}, x...)`" for f = Any[push!, pushfirst!]
1381+
@eval begin
1382+
let src = code_typed1((Vector{Any}, Any)) do xs, x
1383+
$f(xs, x)
1384+
end
1385+
@test count(iscall((src, $f)), src.code) == 0
1386+
@test count(src.code) do @nospecialize x
1387+
isa(x, Core.GotoNode) ||
1388+
isa(x, Core.GotoIfNot) ||
1389+
iscall((src, getfield))(x)
1390+
end == 0 # no loop should be involved for the common single arg case
1391+
end
1392+
let src = code_typed1((Vector{Any}, Any, Any)) do xs, x, y
1393+
$f(xs, x, y)
1394+
end
1395+
@test count(iscall((src, $f)), src.code) == 0
1396+
end
1397+
let xs = Any[]
1398+
$f(xs, :x, "y", 'z')
1399+
@test xs[1] === :x
1400+
@test xs[2] == "y"
1401+
@test xs[3] === 'z'
1402+
end
1403+
end
1404+
end

0 commit comments

Comments
 (0)