Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add partially evaluated methods ===(x), !==(x), and isa(T) #36759

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,7 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, fargs::U
end
elseif (rt === Bool || (isa(rt, Const) && isa(rt.val, Bool))) && isa(fargs, Vector{Any})
# perform very limited back-propagation of type information for `is` and `isa`
if f === isa
if f === isa && la == 3
a = ssa_def_slot(fargs[2], sv)
if isa(a, Slot)
aty = widenconst(argtypes[2])
Expand All @@ -824,7 +824,7 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, fargs::U
end
end
end
elseif f === (===)
elseif f === (===) && la == 3
a = ssa_def_slot(fargs[2], sv)
b = ssa_def_slot(fargs[3], sv)
aty = argtypes[2]
Expand Down
1 change: 1 addition & 0 deletions base/compiler/ssair/passes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,7 @@ function getfield_elim_pass!(ir::IRCode, domtree::DomTree)
compact.ssa_rename[compact.idx-1] = pi
continue
elseif is_known_call(stmt, (===), compact)
length(stmt.args) == 3 || continue
c1 = compact_exprtype(compact, stmt.args[2])
c2 = compact_exprtype(compact, stmt.args[3])
if !(isa(c1, Const) || isa(c2, Const))
Expand Down
23 changes: 21 additions & 2 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,23 @@ function ifelse_tfunc(@nospecialize(cnd), @nospecialize(x), @nospecialize(y))
end
add_tfunc(ifelse, 3, 3, ifelse_tfunc, 1)

# Note: `x` is manually specified at the moment so that it's always a `DataType`
function fix2_tfunc(x::DataType, y)
if !(isdefined(Main, :Base) && isdefined(Main.Base, :Fix2) && isconst(Main.Base, :Fix2))
return Any
end
y = widenconst(y)
Fix2 = Main.Base.Fix2
if isconcretetype(y) || isType(y)
return Fix2{x,y}
elseif y isa UnionAll
return rewrap_unionall(Fix2{x,unwrap_unionall(y)}, y)
elseif y isa Union
return Union{fix2_tfunc(x, y.a),fix2_tfunc(x, y.b)}
end
return Fix2{x}
end

function egal_tfunc(@nospecialize(x), @nospecialize(y))
xx = widenconditional(x)
yy = widenconditional(y)
Expand All @@ -239,7 +256,8 @@ function egal_tfunc(@nospecialize(x), @nospecialize(y))
end
return Bool
end
add_tfunc(===, 2, 2, egal_tfunc, 1)
egal_tfunc(y) = fix2_tfunc(typeof(===), y)
add_tfunc(===, 1, 2, egal_tfunc, 1)

function isdefined_nothrow(argtypes::Array{Any, 1})
length(argtypes) == 2 || return false
Expand Down Expand Up @@ -588,7 +606,8 @@ function isa_tfunc(@nospecialize(v), @nospecialize(tt))
# TODO: handle non-leaftype(t) by testing against lower and upper bounds
return Bool
end
add_tfunc(isa, 2, 2, isa_tfunc, 0)
isa_tfunc(y) = fix2_tfunc(typeof(isa), y)
add_tfunc(isa, 1, 2, isa_tfunc, 0)

function subtype_tfunc(@nospecialize(a), @nospecialize(b))
a, isexact_a = instanceof_tfunc(a)
Expand Down
30 changes: 29 additions & 1 deletion base/docs/basedocs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1576,6 +1576,17 @@ Integer
"""
invoke

"""
===(x)

Create a function that compares its argument to `x` using [`===`](@ref), i.e.
a function equivalent to `y -> y == x`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be y -> y === x


!!! compat "Julia 1.6"
The partially applied form `===(x)` requires Julia 1.6 or later.
"""
===(x)

"""
isa(x, type) -> Bool

Expand All @@ -1600,7 +1611,24 @@ julia> 1 isa Number
true
```
"""
isa
isa(x, T)

"""
isa(type)

Create a function that compares its argument to `x` using [`isa`](@ref), i.e.
a function equivalent to `type -> x isa type`.

!!! compat "Julia 1.6"
The partially applied form `isa(type)` requires Julia 1.6 or later.

# Examples
```jldoctest
julia> isa(Int)(1)
true
```
"""
isa(T)

"""
DivideError()
Expand Down
15 changes: 15 additions & 0 deletions base/operators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,9 @@ struct Fix1{F,T} <: Function
x::T

Fix1(f::F, x::T) where {F,T} = new{F,T}(f, x)
Fix1(f::F, x::Type{T}) where {F,T} = new{F,Type{T}}(f, x)
Fix1(f::Type{F}, x::T) where {F,T} = new{Type{F},T}(f, x)
Fix1(f::Type{F}, x::Type{T}) where {F,T} = new{Type{F},Type{T}}(f, x)
end

(f::Fix1)(y) = f.f(f.x, y)
Expand All @@ -942,7 +944,9 @@ struct Fix2{F,T} <: Function
x::T

Fix2(f::F, x::T) where {F,T} = new{F,T}(f, x)
Fix2(f::F, x::Type{T}) where {F,T} = new{F,Type{T}}(f, x)
Fix2(f::Type{F}, x::T) where {F,T} = new{Type{F},T}(f, x)
Fix2(f::Type{F}, x::Type{T}) where {F,T} = new{Type{F},Type{T}}(f, x)
end

(f::Fix2)(y) = f.f(y, f.x)
Expand Down Expand Up @@ -982,6 +986,17 @@ used to implement specialized methods.
"""
!=(x) = Fix2(!=, x)

"""
!==(x)

Create a function that compares its argument to `x` using [`!==`](@ref), i.e.
a function equivalent to `y -> y !== x`.

!!! compat "Julia 1.6"
This functionality requires at least Julia 1.6.
"""
!==(x) = Fix2(!==, x)

"""
>=(x)

Expand Down
4 changes: 2 additions & 2 deletions src/builtin_proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ extern "C" {
#ifdef DEFINE_BUILTIN_GLOBALS
#define DECLARE_BUILTIN(name) \
JL_CALLABLE(jl_f_##name); \
jl_value_t *jl_builtin_##name
jl_value_t *jl_builtin_##name JL_GLOBALLY_ROOTED
#else
#define DECLARE_BUILTIN(name) \
JL_CALLABLE(jl_f_##name); \
extern jl_value_t *jl_builtin_##name
extern jl_value_t *jl_builtin_##name JL_GLOBALLY_ROOTED
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of a long shot for working around analyzegc https://build.julialang.org/#/builders/65/builds/1574/steps/4/logs/stdio

But I wonder if there is a reason why this is not used? Maybe I'm doing something stupid here?

If this is not the right way to do it, maybe I need to do something like this?

diff --git a/src/builtins.c b/src/builtins.c
index 440b69629e..c2576d553d 100644
--- a/src/builtins.c
+++ b/src/builtins.c
@@ -369,6 +369,7 @@ JL_CALLABLE(jl_f_is)
         jl_function_t *fix2 = jl_get_function(jl_base_module, "Fix2");
         if (fix2 == NULL)
             jl_undefined_var_error(jl_symbol("Fix2"));
+        JL_GC_PROMISE_ROOTED(jl_builtin_is);
         return jl_call2(fix2, jl_builtin_is, args[0]);
     }
     if (args[0] == args[1])
@@ -440,6 +441,7 @@ JL_CALLABLE(jl_f_isa)
         jl_function_t *fix2 = jl_get_function(jl_base_module, "Fix2");
         if (fix2 == NULL)
             jl_undefined_var_error(jl_symbol("Fix2"));
+        JL_GC_PROMISE_ROOTED(jl_builtin_isa);
         return jl_call2(fix2, jl_builtin_isa, args[0]);
     }
     JL_TYPECHK(isa, type, args[1]);

#endif

DECLARE_BUILTIN(throw); DECLARE_BUILTIN(is);
Expand Down
16 changes: 14 additions & 2 deletions src/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,13 @@ JL_DLLEXPORT uintptr_t jl_object_id(jl_value_t *v) JL_NOTSAFEPOINT

JL_CALLABLE(jl_f_is)
{
JL_NARGS(===, 2, 2);
JL_NARGS(===, 1, 2);
if (nargs == 1) {
jl_function_t *fix2 = jl_get_function(jl_base_module, "Fix2");
if (fix2 == NULL)
jl_undefined_var_error(jl_symbol("Fix2"));
return jl_call2(fix2, jl_builtin_is, args[0]);
}
if (args[0] == args[1])
return jl_true;
return jl_egal(args[0], args[1]) ? jl_true : jl_false;
Expand Down Expand Up @@ -429,7 +435,13 @@ JL_CALLABLE(jl_f_issubtype)

JL_CALLABLE(jl_f_isa)
{
JL_NARGS(isa, 2, 2);
JL_NARGS(isa, 1, 2);
if (nargs == 1) {
jl_function_t *fix2 = jl_get_function(jl_base_module, "Fix2");
if (fix2 == NULL)
jl_undefined_var_error(jl_symbol("Fix2"));
return jl_call2(fix2, jl_builtin_isa, args[0]);
}
JL_TYPECHK(isa, type, args[1]);
return (jl_isa(args[0],args[1]) ? jl_true : jl_false);
}
Expand Down
14 changes: 14 additions & 0 deletions test/compiler/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2740,6 +2740,20 @@ end
f_generator_splat(t::Tuple) = tuple((identity(l) for l in t)...)
@test Base.return_types(f_generator_splat, (Tuple{Symbol, Int64, Float64},)) == Any[Tuple{Symbol, Int64, Float64}]

f_is2(x) = ===(x)
@test @inferred(f_is2(1)) === Base.Fix2(===, 1)

f_is2_ref(x) = ===(x[])
@test Base.return_types(f_is2_ref, Tuple{Base.RefValue{Vector}}) ==
Any[Base.Fix2{typeof(===),Vector{T}} where {T}]

f_isa2(T) = isa(T)
@test @inferred(f_isa2(Int)) === Base.Fix2(isa, Int)

f_isa2(b, T, S) = isa(b ? T : S)
@test Base.return_types(f_isa2, Tuple{Bool,Type{Int64},Type{Float64}}) ==
Any[Union{Base.Fix2{typeof(isa),Type{Float64}},Base.Fix2{typeof(isa),Type{Int64}}}]

# Issue #36710 - sizeof(::UnionAll) tfunc correctness
@test (sizeof(Ptr),) == sizeof.((Ptr,)) == sizeof.((Ptr{Cvoid},))
@test Core.Compiler.sizeof_tfunc(UnionAll) === Int
Expand Down
24 changes: 24 additions & 0 deletions test/operators.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

using Base: Fix1, Fix2
using Random: randstring

@testset "ifelse" begin
Expand Down Expand Up @@ -229,17 +230,40 @@ end
end

@testset "curried comparisons" begin
isnot5 = (!==)(5)
eql5 = (==)(5)
neq5 = (!=)(5)
gte5 = (>=)(5)
lte5 = (<=)(5)
gt5 = (>)(5)
lt5 = (<)(5)

@test isnot5(5.0) && !isnot5(5)
@test eql5(5) && !eql5(0)
@test neq5(6) && !neq5(5)
@test gte5(5) && gte5(6)
@test lte5(5) && lte5(4)
@test gt5(6) && !gt5(5)
@test lt5(4) && !lt5(5)
end

@testset "curried comparisons (builtins)" begin
isaSymbol = isa(Symbol)
is5 = (===)(5)

@test isaSymbol(:yes) && !isaSymbol("no")
@test is5(5) && !is5(5.0)

# Make sure they are dispatchable:
@test isaSymbol isa Fix2{typeof(isa),Type{Symbol}}
@test is5 isa Fix2{typeof(===),Int}
end

@testset "Fix1 and Fix2 on types" begin
@test Fix1(tuple, Fix1) isa Fix1{typeof(tuple),Type{Fix1}}
@test Fix1(Fix1, tuple) isa Fix1{Type{Fix1},typeof(tuple)}
@test Fix1(Fix1, Fix1) isa Fix1{Type{Fix1},Type{Fix1}}
@test Fix2(tuple, Fix2) isa Fix2{typeof(tuple),Type{Fix2}}
@test Fix2(Fix2, tuple) isa Fix2{Type{Fix2},typeof(tuple)}
@test Fix2(Fix2, Fix2) isa Fix2{Type{Fix2},Type{Fix2}}
end