Skip to content

Commit

Permalink
define specialized [in|all|any](x, ::Tuple) methods with better eff…
Browse files Browse the repository at this point in the history
…ects (#51002)

So that it can be concrete-evaluated.
There are certain code patterns like:
```julia
if flag::Const(::Char) in ('S', 'T', 'U')
    # do something
else
    # do something else
end
```
and it would be beneficial if we can cut off a dead branch by folding
the call to `in`.
This commit adds similar improvements for `all` and `any` as well.
  • Loading branch information
aviatesk authored Aug 24, 2023
1 parent f21c635 commit 231ca24
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 30 deletions.
23 changes: 14 additions & 9 deletions base/operators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1287,17 +1287,22 @@ used to implement specialized methods.
"""
in(x) = Fix2(in, x)

function in(x, itr)
anymissing = false
for y in itr
v = (y == x)
if ismissing(v)
anymissing = true
elseif v
return true
for ItrT = (Tuple,Any)
# define a generic method and a specialized version for `Tuple`,
# whose method bodies are identical, while giving better effects to the later
@eval function in(x, itr::$ItrT)
$(ItrT === Tuple ? :(@_terminates_locally_meta) : :nothing)
anymissing = false
for y in itr
v = (y == x)
if ismissing(v)
anymissing = true
elseif v
return true
end
end
return anymissing ? missing : false
end
return anymissing ? missing : false
end

const = in
Expand Down
49 changes: 28 additions & 21 deletions base/reduce.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1214,17 +1214,22 @@ false
"""
any(f, itr) = _any(f, itr, :)

function _any(f, itr, ::Colon)
anymissing = false
for x in itr
v = f(x)
if ismissing(v)
anymissing = true
elseif v
return true
for ItrT = (Tuple,Any)
# define a generic method and a specialized version for `Tuple`,
# whose method bodies are identical, while giving better effects to the later
@eval function _any(f, itr::$ItrT, ::Colon)
$(ItrT === Tuple ? :(@_terminates_locally_meta) : :nothing)
anymissing = false
for x in itr
v = f(x)
if ismissing(v)
anymissing = true
else
v && return true
end
end
return anymissing ? missing : false
end
return anymissing ? missing : false
end

# Specialized versions of any(f, ::Tuple)
Expand Down Expand Up @@ -1282,20 +1287,22 @@ true
"""
all(f, itr) = _all(f, itr, :)

function _all(f, itr, ::Colon)
anymissing = false
for x in itr
v = f(x)
if ismissing(v)
anymissing = true
# this syntax allows throwing a TypeError for non-Bool, for consistency with any
elseif v
continue
else
return false
for ItrT = (Tuple,Any)
# define a generic method and a specialized version for `Tuple`,
# whose method bodies are identical, while giving better effects to the later
@eval function _all(f, itr::$ItrT, ::Colon)
$(ItrT === Tuple ? :(@_terminates_locally_meta) : :nothing)
anymissing = false
for x in itr
v = f(x)
if ismissing(v)
anymissing = true
else
v || return false
end
end
return anymissing ? missing : true
end
return anymissing ? missing : true
end

# Specialized versions of all(f, ::Tuple),
Expand Down
18 changes: 18 additions & 0 deletions test/operators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,21 @@ end
Base.:(<)(::B46327, ::B46327) = false
@test B46327() <= B46327()
end

@testset "concrete eval `x in itr::Tuple`" begin
@test Core.Compiler.is_foldable(Base.infer_effects(in, (Int,Tuple{Int,Int,Int})))
@test Core.Compiler.is_foldable(Base.infer_effects(in, (Char,Tuple{Char,Char,Char})))
for i = (1,2,3)
@testset let i = i
@test @eval Base.return_types() do
Val($i in (1,2,3))
end |> only == Val{true}
end
end
@test Base.return_types() do
Val(4 in (1,2,3))
end |> only == Val{false}
@test Base.return_types() do
Val('1' in ('1','2','3'))
end |> only == Val{true}
end
29 changes: 29 additions & 0 deletions test/reduce.jl
Original file line number Diff line number Diff line change
Expand Up @@ -705,3 +705,32 @@ let a = NamedTuple(Symbol(:x,i) => i for i in 1:33),
b = (a...,)
@test fold_alloc(a) == fold_alloc(b) == 0
end

@testset "concrete eval `[any|all](f, itr::Tuple)`" begin
intf = in((1,2,3)); Intf = typeof(intf)
symf = in((:one,:two,:three)); Symf = typeof(symf)
@test Core.Compiler.is_foldable(Base.infer_effects(intf, (Int,)))
@test Core.Compiler.is_foldable(Base.infer_effects(symf, (Symbol,)))
@test Core.Compiler.is_foldable(Base.infer_effects(all, (Intf,Tuple{Int,Int,Int})))
@test Core.Compiler.is_foldable(Base.infer_effects(all, (Symf,Tuple{Symbol,Symbol,Symbol})))
@test Core.Compiler.is_foldable(Base.infer_effects(any, (Intf,Tuple{Int,Int,Int})))
@test Core.Compiler.is_foldable(Base.infer_effects(any, (Symf,Tuple{Symbol,Symbol,Symbol})))
@test Base.return_types() do
Val(all(in((1,2,3)), (1,2,3)))
end |> only == Val{true}
@test Base.return_types() do
Val(all(in((1,2,3)), (1,2,3,4)))
end |> only == Val{false}
@test Base.return_types() do
Val(any(in((1,2,3)), (4,5,3)))
end |> only == Val{true}
@test Base.return_types() do
Val(any(in((1,2,3)), (4,5,6)))
end |> only == Val{false}
@test Base.return_types() do
Val(all(in((:one,:two,:three)),(:three,:four)))
end |> only == Val{false}
@test Base.return_types() do
Val(any(in((:one,:two,:three)),(:four,:three)))
end |> only == Val{true}
end

0 comments on commit 231ca24

Please sign in to comment.