How do I reference a custom call operator?

Suppose I’ve defined a data type in Julia with its own call operator.

struct Adder
    x::Int
end

@noinline function (a::Adder)(y::Int)
    a.x + y
end

How do I refer to the call operator I’ve just defined as a function? For example, I might want to check what sort of native code it compiles down to. However, code_native(Adder, (Int,)) gives me the code for the Adder constructor, while code_native(Adder, (Adder, Int)) tells me there’s “no unique matching method found for the specified argument types.” What I really want to say is code_native((a, x) -> a(x), (Adder, Int)), but this adds an additional level of indirection:

julia> code_native((a, x) -> a(x), (Adder, Int), syntax=:intel)

	.text
; ┌ @ In[23]:1 within `#15'
	push	rbp
	mov	rbp, rsp
	sub	rsp, 32
	movabs	rax, offset Adder
	call	rax
	add	rsp, 32
	pop	rbp
	ret
	nop	word ptr [rax + rax]
; └

Of course, this could be fixed by marking @inline function (a::Adder)(y::Int), but sometimes I define very long call operators for which this is wasteful.

You can use an instance of the type:

julia> code_native(Adder(1), (Int,))
        .text
; ┌ @ REPL[2]:2 within `Adder'
; │┌ @ REPL[2]:2 within `+'
        addq    (%rdi), %rsi
; │└
        movq    %rsi, %rax
        retq
        nopw    (%rax,%rax)
;
2 Likes

Can’t you just use the macros like @code_native?

@maleadt Thanks, I didn’t think of that! Is there a way to do this without constructing an instance? Say, for example, Adder has a really expensive inner constructor.

1 Like

@dpsanders I can use a macro for code_native in this situation, but one could imagine a scenario where the function (a::Adder, x::Int) -> a(x) itself would be passed as a callback to another function. Does Julia not provide a mechanism for referring to function (a::Adder)(x::Int) directly?

I seem to remember that in very old versions of Julia (around v0.4?) an invocation of Adder(1)(x) would get translated into Base.call(Adder(1), x), and so my function (a::Adder)(x::Int) would instead have been defined as a new method of Base.call. I guess what I’m asking for is a mechanism in v1.1 to directly name what Base.call used to refer to in v0.4 – i.e., what Python calls obj.__call__, and what C++ calls MyClass::operator().

The signature_type that gets constructed by _dump_function does deal with the type of Adder, and not the instance:

julia> f = Adder(1);

julia> t = (Int,);

julia> t = Base.to_tuple_type(t)
Tuple{Int64}

julia> Base.signature_type(f, t)
Tuple{Adder,Int64}

(vs. Tuple{Type{Adder},Int64} for the constructor method)

But you can’t pass that into any of the public reflection functions. You can do it manually though:

julia> world = typemax(UInt)
0xffffffffffffffff

julia> meth = which(f, t)
(a::Adder)(y::Int64) in Main at REPL[2]:2

julia> (ti, env) = ccall(:jl_type_intersection_with_env, Any, (Any, Any), tt, meth.sig)::Core.SimpleVector
svec(Tuple{Adder,Int64}, svec())

julia> meth = Base.func_for_method_checked(meth, ti)
(a::Adder)(y::Int64) in Main at REPL[2]:2

julia> linfo = ccall(:jl_specializations_get_linfo, Ref{Core.MethodInstance}, (Any, Any, Any, UInt), meth, ti, env, world)
MethodInstance for (::Adder)(::Int64)

julia> InteractiveUtils._dump_function_linfo(linfo, world, #=native=# true, #=wrapper=# false, #=strip_ir_metadata=# false, #=dump_module=# false, #=syntax=# :att, #=optimize=# true, #=debuginfo=# :default);

julia> println(ans)
        .text
; ┌ @ REPL[2]:2 within `Adder'
; │┌ @ REPL[2]:2 within `+'
        addq    (%rdi), %rsi
; │└
        movq    %rsi, %rax
        retq
        nopw    (%rax,%rax)
; └
2 Likes

@maleadt Thanks pointing me to these reflection functions! I should have known to take a look at the source of InteractiveUtils to see what was really going on. It’s interesting to see that Julia itself displays the MethodInstance for function (a::Adder)(y::Int) as (::Adder)(::Int64).

Do you think it would be reasonable to propose new language syntax to reflect this internal representation? I don’t think (::Adder) conflicts with anything right now, so this hypothetical language extension would let you write code_native((::Adder), (Int,)) to achieve the same result.

1 Like

(a,y) -> a(y)

@stevengj I clearly explained in the original post that I considered that solution and why it isn’t satisfactory.