Invoke pybind from cfunc

Hi, I’m looking for examples how to call to pybind defined c++ methods from cfunc or if not available perhaps someone can look at my specific example. I have defined method with the signature int func_int(); in module with:

  m.def("int_func_address", []() { return (uint64_t)(func_int); }); # method address
  m.def("int_func", &func_int); # method
}

Invocation from python main works:

  bind_int_func = ctypes.CFUNCTYPE(int)(ops.int_func_address())
  returned_int_val = bind_int_func()
  print("returned_int_val: " + str(returned_int_val))

However, when I try to move the call into cfunc, I see error.

@nb.cfunc(
    nb.types.void(),
    nopython=True,
)
def _call():
  bind_int_func = ctypes.CFUNCTYPE(int)(ops.int_func_address())
  returned_int_val = bind_int_func()
  print("returned_int_val: " + str(returned_int_val))

And the error is
numba.core.errors.TypingError: Failed in nopython mode pipeline (step: nopython frontend)

Numba is sometimes grumpy about types being figured out in jitted code… Can you try with bind_int_func at global scope?

1 Like

Thank you for the reply. I have tried global variable, removing c++ namespaces but all without success. It is as if inside the cfunc the method is no longer visible.

I also tried simpler scenario of calling method directly instead of casting the method address.

@nb.cfunc(
    nb.types.void(nb.types.intc),
    nopython=True,
)
def _call(a):
  print(a)
  custom_ops.int_func()
numba.core.errors.TypingError: Failed in nopython mode pipeline (step: nopython frontend)

Unknown attribute 'int_func' of type Module(<xxx.ops' from[...]ops.abi3.so'>)

Update: I got it working. Thank you.

The combination that worked for me: global variable, no namespace in c++ and correct type for method address:

bind_int_func = ctypes.CFUNCTYPE(ctypes.c_int)(custom_ops.int_func_address())

@nelson2005

A follow up question regarding this, how do we convert np.array’s raw pointer inside of the jitted function?

bind_int_func = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.c_int)(custom_ops.int_func_address())

@njit
def f(arr: np.array):
    # How to cast arr.ctypes.data to a raw pointer inside of the jitted function?
    # arr.ctypes.data works but I can't do
    # `ctypes.cast(x.ctypes.data, ctypes.POINTER(ctypes.c_int32))` here
   

If I just directly pass arr.ctypes.data to bind_int_func, I am getting this error:

No implementation of function ExternalFunctionPointer((int32*, int32) -> int32) found for signature:

 >>> ExternalFunctionPointer(uint64, Literal[int](3))

There are 2 candidate implementations:
  - Of which 2 did not match due to:
  Type Restricted Function in function 'unknown': File: unknown: Line unknown.
    With argument(s): '(uint64, int64)':
   No match for registered cases:
    * (int32*, int32) -> int32

During: resolving callee type: ExternalFunctionPointer((int32*, int32) -> int32)
During: typing of call at /usr/local/google/_blaze_jimlintw/2610b537ee3958b4624a3da396c47630/execroot/google3/blaze-out/k8-fastbuild/bin/learning/processing/experimental/tf2jax/symbolic_trace/run_custom_op_with_tracing.runfiles/google3/learning/processing/experimental/tf2jax/symbolic_trace/run_custom_op_with_tracing.py (51)


File "../../../../../../../../../../../../../../../usr/local/google/_blaze_jimlintw/2610b537ee3958b4624a3da396c47630/execroot/google3/blaze-out/k8-fastbuild/bin/learning/processing/experimental/tf2jax/symbolic_trace/run_custom_op_with_tracing.runfiles/google3/learning/processing/experimental/tf2jax/symbolic_trace/run_custom_op_with_tracing.py", line 51:
def f(arr):
    <source elided>
  # ptr = ptr_type(arr.ctypes.data)
  return bind_func(arr.ctypes.data, 3)
  ^

During: Pass nopython_type_inference

Never mind, I think

@njit
def f(arr):
  return bind_func(arr.ctypes)

works

Note that with out @njit, need to cast the pointer manually

def f(arr):
  return bind_func(arr.ctypes.data_as(ctypes.POINTER(ctypes.c_int)))

Would love to see if this difference is minimized.

If you want to inject inttoptr instruction consider this kind of intrinsic.

1 Like

@milton I see thanks!

Just for my own record, the lowering of array.ctypes happens at np/arrayobj.py