oxinabox / Tricks.jl

Cunning tricks though the julia compiler internals
MIT License
85 stars 9 forks source link

Tricks

Codecov

⚠️ Notice ⚠️

Tricks.static_hasmethod is not required post-Julia v1.10.0-DEV.609. The features of running hasmethod at compile-time are now built into the language. static_hasmethod can still be used for compatibility with older versions of the language, and the other static functions provided by Tricks.jl continue to be useful.

Tricks.jl is an particularly ~evil~ cunning package that does tricks with the Julia edge system.

Currently it has the following tricks:

static_hasmethod.

This is like hasmethod but it does not trigger any dynamic lookup of the method table. It just returns the constant true or false. If methods are added, recompilation is triggered.

If you can make a reproducible case of static_hasmethod not working please post in #2.
I think it can't actually happen, and can't actually be called dynamically in a way that breaks it.

static_methods

This is just like methods, but again it doesn't trigger any dynamic lookup of the method tables.

If you can make a reproducible case of static_methods not working please open an issue.

static_fieldnames, static_fieldtypes, static_fieldcount

Just like Base.fieldnames Base.fieldtypes, and Base.fieldcount but will participate in constant propagation and will be free of runtime dynamism.

Uses

We can use static_hasmethod to declare traits.

For demonstration we include versions based on static and nonstatic has_method.

julia> using Tricks: static_hasmethod

julia> struct Iterable end; struct NonIterable end;

julia> iterableness_dynamic(::Type{T}) where T = hasmethod(iterate, Tuple{T}) ? Iterable() : NonIterable()
iterableness_dynamic (generic function with 1 method)

julia> iterableness_static(::Type{T}) where T = static_hasmethod(iterate, Tuple{T}) ? Iterable() : NonIterable()
iterableness_static (generic function with 1 method)

Demo:

julia> using BenchmarkTools

julia> const examples =  (:a, "abc", [1,2,3], rand, (2,3), ones(4,10,2), 'a',  1:100);

julia> @btime [iterableness_dynamic(typeof(x)) for x in $examples]
  13.608 μs (5 allocations: 304 bytes)
8-element Array{Any,1}:
 NonIterable()
 Iterable()
 Iterable()
 NonIterable()
 Iterable()
 Iterable()
 Iterable()
 Iterable()

julia> @btime [iterableness_static(typeof(x)) for x in $examples]
  582.249 ns (5 allocations: 304 bytes)
8-element Array{Any,1}:
 NonIterable()
 Iterable()
 Iterable()
 NonIterable()
 Iterable()
 Iterable()
 Iterable()
 Iterable()

So it is over 20x faster.

this is because doesn't generate any code that has to run at runtime: (i.e. it is not dynamic)

julia> @code_typed iterableness_static(String)
CodeInfo(
1 ─     return $(QuoteNode(Iterable()))
) => Iterable

julia> @code_typed iterableness_dynamic(String)
CodeInfo(
1 ─ %1 = $(Expr(:foreigncall, :(:jl_gf_invoke_lookup), Any, svec(Any, UInt64), 0, :(:ccall), Tuple{typeof(iterate),String}, 0xffffffffffffffff, 0xffffffffffffffff))::Any
│   %2 = (%1 === Base.nothing)::Bool
│   %3 = Core.Intrinsics.not_int(%2)::Bool
└──      goto #3 if not %3
2 ─      return $(QuoteNode(Iterable()))
3 ─      return $(QuoteNode(NonIterable()))
) => Union{Iterable, NonIterable}

Demonstration of it updating:

julia> struct Foo end

julia> iterableness_static(Foo)
NonIterable()

Initially, it wasn't iterable, but now we will add the iteration methods to it:

julia> Base.iterate(::Foo) = ("Foo", nothing);

julia> Base.iterate(::Foo, ::Nothing) = nothing;

julia> Base.length(::Foo) = 1;

julia> collect(Foo())
1-element Array{Any,1}:
 "Foo"

julia> iterableness_static(Foo)
Iterable()

Julia version support

The core trick that Tricks.jl relies on was introduced in Julia 1.3. As such most of its methods do not work on earlier julia versions.

For compatability purposes we do provide: