import ctypes import traceback from cpython cimport Py_INCREF, Py_DECREF from numbers import Number, Integral from ..base import string_types from ..object_generic import convert_to_object, ObjectGeneric from ..runtime_ctypes import DGLDataType as CTypesDGLDataType, \ DGLContext as CTypesDGLContext, \ DGLByteArray cdef void dgl_callback_finalize(void* fhandle): local_pyfunc = (fhandle) Py_DECREF(local_pyfunc) cdef int dgl_callback(DGLValue* args, int* type_codes, int num_args, DGLRetValueHandle ret, void* fhandle) with gil: cdef list pyargs cdef DGLValue value cdef int tcode local_pyfunc = (fhandle) pyargs = [] for i in range(num_args): value = args[i] tcode = type_codes[i] if (tcode == kObjectHandle or tcode == kFuncHandle or tcode == kModuleHandle or tcode > kExtBegin): CALL(DGLCbArgToReturn(&value, tcode)) if tcode != kArrayHandle: pyargs.append(make_ret(value, tcode)) else: pyargs.append(c_make_array(value.v_handle, True)) try: rv = local_pyfunc(*pyargs) except Exception: msg = traceback.format_exc() DGLAPISetLastError(c_str(msg)) return -1 if rv is not None: if isinstance(rv, tuple): raise ValueError("PackedFunction can only support one return value") temp_args = [] make_arg(rv, &value, &tcode, temp_args) CALL(DGLCFuncSetReturn(ret, &value, &tcode, 1)) return 0 def convert_to_dgl_func(object pyfunc): """Convert a python function to DGL function Parameters ---------- pyfunc : python function The python function to be converted. Returns ------- dglfunc: dgl.Function The converted dgl function. """ cdef DGLFunctionHandle chandle Py_INCREF(pyfunc) CALL(DGLFuncCreateFromCFunc(dgl_callback, (pyfunc), dgl_callback_finalize, &chandle)) ret = _CLASS_FUNCTION(None, False) (ret).chandle = chandle return ret cdef inline int make_arg(object arg, DGLValue* value, int* tcode, list temp_args) except -1: """Pack arguments into c args dgl call accept""" cdef unsigned long long ptr if isinstance(arg, ObjectBase): value[0].v_handle = (arg).chandle tcode[0] = kObjectHandle elif isinstance(arg, NDArrayBase): value[0].v_handle = (arg).chandle tcode[0] = (kNDArrayContainer if not (arg).c_is_view else kArrayHandle) elif isinstance(arg, _DGL_COMPATS): ptr = arg._dgl_handle value[0].v_handle = (ptr) tcode[0] = arg.__class__._dgl_tcode elif isinstance(arg, (int, long)): value[0].v_int64 = arg tcode[0] = kObjectInt elif isinstance(arg, float): value[0].v_float64 = arg tcode[0] = kObjectFloat elif isinstance(arg, str): tstr = c_str(arg) value[0].v_str = tstr tcode[0] = kStr temp_args.append(tstr) elif arg is None: value[0].v_handle = NULL tcode[0] = kNull elif isinstance(arg, Number): value[0].v_float64 = arg tcode[0] = kObjectFloat elif isinstance(arg, CTypesDGLDataType): tstr = c_str(str(arg)) value[0].v_str = tstr tcode[0] = kStr temp_args.append(tstr) elif isinstance(arg, CTypesDGLContext): value[0].v_ctx = (( ctypes.addressof(arg)))[0] tcode[0] = kDGLContext elif isinstance(arg, bytearray): arr = DGLByteArray() arr.data = ctypes.cast( (ctypes.c_byte * len(arg)).from_buffer(arg), ctypes.POINTER(ctypes.c_byte)) arr.size = len(arg) value[0].v_handle = ( ctypes.addressof(arr)) tcode[0] = kBytes temp_args.append(arr) elif isinstance(arg, string_types): tstr = c_str(arg) value[0].v_str = tstr tcode[0] = kStr temp_args.append(tstr) elif isinstance(arg, (list, tuple, dict, ObjectGeneric)): arg = convert_to_object(arg) value[0].v_handle = (arg).chandle tcode[0] = kObjectHandle temp_args.append(arg) #elif isinstance(arg, _CLASS_MODULE): # value[0].v_handle = c_handle(arg.handle) # tcode[0] = kModuleHandle elif isinstance(arg, FunctionBase): value[0].v_handle = (arg).chandle tcode[0] = kFuncHandle elif isinstance(arg, ctypes.c_void_p): value[0].v_handle = c_handle(arg) tcode[0] = kHandle elif callable(arg): arg = convert_to_dgl_func(arg) value[0].v_handle = (arg).chandle tcode[0] = kFuncHandle temp_args.append(arg) else: raise TypeError("Don't know how to handle type %s" % type(arg)) return 0 cdef inline bytearray make_ret_bytes(void* chandle): handle = ctypes_handle(chandle) arr = ctypes.cast(handle, ctypes.POINTER(DGLByteArray))[0] size = arr.size res = bytearray(size) rptr = (ctypes.c_byte * size).from_buffer(res) if not ctypes.memmove(rptr, arr.data, size): raise RuntimeError('memmove failed') return res cdef inline object make_ret(DGLValue value, int tcode): """convert result to return value.""" if tcode == kObjectHandle: return make_ret_object(value.v_handle) elif tcode == kNull: return None elif tcode == kObjectInt: return value.v_int64 elif tcode == kObjectFloat: return value.v_float64 elif tcode == kNDArrayContainer: return c_make_array(value.v_handle, False) elif tcode == kStr: return py_str(value.v_str) elif tcode == kBytes: return make_ret_bytes(value.v_handle) elif tcode == kHandle: return ctypes_handle(value.v_handle) elif tcode == kDGLContext: return CTypesDGLContext(value.v_ctx.device_type, value.v_ctx.device_id) # (minjie): class module are not used in DGL. #elif tcode == kModuleHandle: # return _CLASS_MODULE(ctypes_handle(value.v_handle)) elif tcode == kFuncHandle: fobj = _CLASS_FUNCTION(None, False) (fobj).chandle = value.v_handle return fobj elif tcode in _DGL_EXT_RET: return _DGL_EXT_RET[tcode](ctypes_handle(value.v_handle)) raise ValueError("Unhandled type code %d" % tcode) cdef inline int FuncCall3(void* chandle, tuple args, int nargs, DGLValue* ret_val, int* ret_tcode) except -1: cdef DGLValue[3] values cdef int[3] tcodes nargs = len(args) temp_args = [] for i in range(nargs): make_arg(args[i], &values[i], &tcodes[i], temp_args) with nogil: ret = DGLFuncCall(chandle, &values[0], &tcodes[0], nargs, ret_val, ret_tcode) if ret != 0: raise DGLError(py_str(DGLGetLastError())) return 0 cdef inline int FuncCall(void* chandle, tuple args, DGLValue* ret_val, int* ret_tcode) except -1: cdef int nargs nargs = len(args) if nargs <= 3: FuncCall3(chandle, args, nargs, ret_val, ret_tcode) return 0 cdef vector[DGLValue] values cdef vector[int] tcodes values.resize(max(nargs, 1)) tcodes.resize(max(nargs, 1)) temp_args = [] for i in range(nargs): make_arg(args[i], &values[i], &tcodes[i], temp_args) with nogil: ret = DGLFuncCall(chandle, &values[0], &tcodes[0], nargs, ret_val, ret_tcode) if ret != 0: raise DGLError(py_str(DGLGetLastError())) return 0 cdef inline int ConstructorCall(void* constructor_handle, int type_code, tuple args, void** handle) except -1: """Call contructor of a handle function""" cdef DGLValue ret_val cdef int ret_tcode FuncCall(constructor_handle, args, &ret_val, &ret_tcode) assert ret_tcode == type_code handle[0] = ret_val.v_handle return 0 cdef class FunctionBase: cdef DGLFunctionHandle chandle cdef int is_global cdef inline _set_handle(self, handle): if handle is None: self.chandle = NULL else: self.chandle = c_handle(handle) property is_global: def __get__(self): return self.c_is_global != 0 def __set__(self, value): self.c_is_global = value property handle: def __get__(self): if self.chandle == NULL: return None else: return ctypes.cast(self.chandle, ctypes.c_void_p) def __set__(self, value): self._set_handle(value) def __init__(self, handle, is_global): self._set_handle(handle) self.c_is_global = is_global def __dealloc__(self): if self.is_global == 0: CALL(DGLFuncFree(self.chandle)) def __call__(self, *args): cdef DGLValue ret_val cdef int ret_tcode FuncCall(self.chandle, args, &ret_val, &ret_tcode) return make_ret(ret_val, ret_tcode) _CLASS_FUNCTION = None _CLASS_MODULE = None def _set_class_module(module_class): """Initialize the module.""" global _CLASS_MODULE _CLASS_MODULE = module_class def _set_class_function(func_class): global _CLASS_FUNCTION _CLASS_FUNCTION = func_class