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
MethodInstance for this call. Then the
cache field of
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.)