I agree with @Sukera, you are just trying to implement a interpreter (or a virtual machine) over an extremely simple arithmetic language. We can do this easily in Haskell with < 100 line codes. If you believe this counts as a computation graph, you might just waste your time.
It’s indeed possible to twist function pointers in Julia, just like FunctionWrappers.jl
, to achieve a more general interpreter (for arbitrary types and functions). And if you know how to do this in C/C++, then you can do this kind of low level thing in Julia (you just need to use Julia’s internal). But this is not easy and beneficial. We already have a task scheduler. You can directly spawn the computation and let Julia do it. Task indeed has some overheads and may not be appropriate if we have a lots of small computation. But if the computation is small, then we shouldn’t spawn it at all (besides, a dynamic interpreter also has some small overheads, because we need to pack and unpack parameters to execute an opcode) .
Instead, you should look at into Debugger.jl or Julia’s abstract interpreter framework. These works are much closer to what you are doing here.
PS: If you really really want to know how to get function pointers, use which(m,tt)
to get Method
object. The specializations
field of Method
stores MethodInstance
for this call. Then the cache
field of MethodInstance
stores CodeInstance
, which is just like a handle. The specptr
field of CodeInstance
stores the function pointer of the function. The invoke
field also stores a pointer. The difference is that the pointer in invoke
is a wrapper of the pointer in specptr
. It has a signature of void* f(void* tparams,void** args, int32 nargs)
. This signature is the same for all different functions. So you can ccall
this function in an uniform way (there is no dispatch since the type of inputs is fixed.)