From ee5e88bbcad331529915a95ef72bffd521b23f6f Mon Sep 17 00:00:00 2001 From: "Todd A. Anderson" Date: Wed, 11 Nov 2020 17:10:40 -0600 Subject: [PATCH 1/5] applied patch to allow subclass of ndarray to be maintained and to have custom allocators/deallocators for them. --- numba/_typeof.c | 13 ++++- numba/core/extending.py | 2 +- numba/core/ir_utils.py | 12 ++-- numba/core/pythonapi.py | 9 ++- numba/core/runtime/_nrt_python.c | 37 +++++++++++- numba/core/runtime/_nrt_pythonmod.c | 1 + numba/core/runtime/nrt.c | 90 +++++++++++++++++++++++------ numba/core/runtime/nrt.h | 24 +++++++- numba/core/runtime/nrt_external.h | 16 +++++ numba/core/types/npytypes.py | 4 +- numba/core/typing/npydecl.py | 21 +++++-- numba/np/arrayobj.py | 11 +++- 12 files changed, 203 insertions(+), 37 deletions(-) diff --git a/numba/_typeof.c b/numba/_typeof.c index ffe0e3a3c58..9b259164800 100644 --- a/numba/_typeof.c +++ b/numba/_typeof.c @@ -768,6 +768,7 @@ int typeof_typecode(PyObject *dispatcher, PyObject *val) { PyTypeObject *tyobj = Py_TYPE(val); + int no_subtype_attr; /* This needs to be kept in sync with Dispatcher.typeof_pyval(), * otherwise funny things may happen. */ @@ -794,9 +795,19 @@ typeof_typecode(PyObject *dispatcher, PyObject *val) return typecode_arrayscalar(dispatcher, val); } /* Array handling */ - else if (PyType_IsSubtype(tyobj, &PyArray_Type)) { + else if (tyobj == &PyArray_Type) { return typecode_ndarray(dispatcher, (PyArrayObject*)val); } + /* Subtypes of Array handling */ + else if (PyType_IsSubtype(tyobj, &PyArray_Type)) { + /* If the class has an attribute named __numba_no_subtype_ndarray then + don't treat it as a normal variant of a Numpy ndarray but as it's own + separate type. */ + no_subtype_attr = PyObject_HasAttrString(val, "__numba_no_subtype_ndarray__"); + if (!no_subtype_attr) { + return typecode_ndarray(dispatcher, (PyArrayObject*)val); + } + } return typecode_using_fingerprint(dispatcher, val); } diff --git a/numba/core/extending.py b/numba/core/extending.py index 8d8d8525e21..09373708b48 100644 --- a/numba/core/extending.py +++ b/numba/core/extending.py @@ -14,7 +14,7 @@ lower_setattr, lower_setattr_generic, lower_cast) # noqa: F401 from numba.core.datamodel import models # noqa: F401 from numba.core.datamodel import register_default as register_model # noqa: F401, E501 -from numba.core.pythonapi import box, unbox, reflect, NativeValue # noqa: F401 +from numba.core.pythonapi import box, unbox, reflect, NativeValue, allocator # noqa: F401 from numba._helperlib import _import_cython_function # noqa: F401 from numba.core.serialize import ReduceMixin diff --git a/numba/core/ir_utils.py b/numba/core/ir_utils.py index 1d58c5c8b5b..9ffdfb16b07 100644 --- a/numba/core/ir_utils.py +++ b/numba/core/ir_utils.py @@ -64,6 +64,8 @@ def mk_alloc(typemap, calltypes, lhs, size_var, dtype, scope, loc): out = [] ndims = 1 size_typ = types.intp + # Get the type of the array being allocated. + arr_typ = typemap[lhs.name] if isinstance(size_var, tuple): if len(size_var) == 1: size_var = size_var[0] @@ -108,11 +110,13 @@ def mk_alloc(typemap, calltypes, lhs, size_var, dtype, scope, loc): typ_var_assign = ir.Assign(np_typ_getattr, typ_var, loc) alloc_call = ir.Expr.call(attr_var, [size_var, typ_var], (), loc) if calltypes: - calltypes[alloc_call] = typemap[attr_var.name].get_call_type( + cac = typemap[attr_var.name].get_call_type( typing.Context(), [size_typ, types.functions.NumberClass(dtype)], {}) - # signature( - # types.npytypes.Array(dtype, ndims, 'C'), size_typ, - # types.functions.NumberClass(dtype)) + # By default, all calls to "empty" are typed as returning a standard + # Numpy ndarray. If we are allocating a ndarray subclass here then + # just change the return type to be that of the subclass. + cac._return_type = arr_typ + calltypes[alloc_call] = cac alloc_assign = ir.Assign(alloc_call, lhs, loc) out.extend([g_np_assign, attr_assign, typ_var_assign, alloc_assign]) diff --git a/numba/core/pythonapi.py b/numba/core/pythonapi.py index 7901e761d9f..f84ad7b2ce1 100644 --- a/numba/core/pythonapi.py +++ b/numba/core/pythonapi.py @@ -45,10 +45,13 @@ def lookup(self, typeclass, default=None): _boxers = _Registry() _unboxers = _Registry() _reflectors = _Registry() +# Registry of special allocators for types. +_allocators = _Registry() box = _boxers.register unbox = _unboxers.register reflect = _reflectors.register +allocator = _allocators.register class _BoxContext(namedtuple("_BoxContext", ("context", "builder", "pyapi", "env_manager"))): @@ -1186,8 +1189,11 @@ def nrt_adapt_ndarray_to_python(self, aryty, ary, dtypeptr): assert self.context.enable_nrt, "NRT required" intty = ir.IntType(32) + # Embed the Python type of the array (maybe subclass) in the LLVM. + serial_aryty_pytype = self.unserialize(self.serialize_object(aryty.py_type)) + fnty = Type.function(self.pyobj, - [self.voidptr, intty, intty, self.pyobj]) + [self.voidptr, self.pyobj, intty, intty, self.pyobj]) fn = self._get_function(fnty, name="NRT_adapt_ndarray_to_python") fn.args[0].add_attribute(lc.ATTR_NO_CAPTURE) @@ -1197,6 +1203,7 @@ def nrt_adapt_ndarray_to_python(self, aryty, ary, dtypeptr): aryptr = cgutils.alloca_once_value(self.builder, ary) return self.builder.call(fn, [self.builder.bitcast(aryptr, self.voidptr), + serial_aryty_pytype, ndim, writable, dtypeptr]) def nrt_meminfo_new_from_pyobject(self, data, pyobj): diff --git a/numba/core/runtime/_nrt_python.c b/numba/core/runtime/_nrt_python.c index 33620fd4f1a..efe4467df70 100644 --- a/numba/core/runtime/_nrt_python.c +++ b/numba/core/runtime/_nrt_python.c @@ -55,6 +55,8 @@ int MemInfo_init(MemInfoObject *self, PyObject *args, PyObject *kwds) { return -1; } raw_ptr = PyLong_AsVoidPtr(raw_ptr_obj); + NRT_Debug(nrt_debug_print("MemInfo_init self=%p raw_ptr=%p\n", self, raw_ptr)); + if(PyErr_Occurred()) return -1; self->meminfo = (NRT_MemInfo *)raw_ptr; assert (NRT_MemInfo_refcount(self->meminfo) > 0 && "0 refcount"); @@ -109,6 +111,27 @@ MemInfo_get_refcount(MemInfoObject *self, void *closure) { return PyLong_FromSize_t(refct); } +static +PyObject* +MemInfo_get_external_allocator(MemInfoObject *self, void *closure) { + void *p = NRT_MemInfo_external_allocator(self->meminfo); + printf("MemInfo_get_external_allocator %p\n", p); + return PyLong_FromVoidPtr(p); +} + +static +PyObject* +MemInfo_get_parent(MemInfoObject *self, void *closure) { + void *p = NRT_MemInfo_parent(self->meminfo); + if (p) { + Py_INCREF(p); + return (PyObject*)p; + } else { + Py_INCREF(Py_None); + return Py_None; + } +} + static void MemInfo_dealloc(MemInfoObject *self) { @@ -136,6 +159,13 @@ static PyGetSetDef MemInfo_getsets[] = { (getter)MemInfo_get_refcount, NULL, "Get the refcount", NULL}, + {"external_allocator", + (getter)MemInfo_get_external_allocator, NULL, + "Get the external allocator", + NULL}, + {"parent", + (getter)MemInfo_get_parent, NULL, + NULL}, {NULL} /* Sentinel */ }; @@ -286,7 +316,7 @@ PyObject* try_to_return_parent(arystruct_t *arystruct, int ndim, } NUMBA_EXPORT_FUNC(PyObject *) -NRT_adapt_ndarray_to_python(arystruct_t* arystruct, int ndim, +NRT_adapt_ndarray_to_python(arystruct_t* arystruct, PyTypeObject *retty, int ndim, int writeable, PyArray_Descr *descr) { PyArrayObject *array; @@ -324,10 +354,13 @@ NRT_adapt_ndarray_to_python(arystruct_t* arystruct, int ndim, args = PyTuple_New(1); /* SETITEM steals reference */ PyTuple_SET_ITEM(args, 0, PyLong_FromVoidPtr(arystruct->meminfo)); + NRT_Debug(nrt_debug_print("NRT_adapt_ndarray_to_python arystruct->meminfo=%p\n", arystruct->meminfo)); /* Note: MemInfo_init() does not incref. This function steals the * NRT reference. */ + NRT_Debug(nrt_debug_print("NRT_adapt_ndarray_to_python created MemInfo=%p\n", miobj)); if (MemInfo_init(miobj, args, NULL)) { + NRT_Debug(nrt_debug_print("MemInfo_init returned 0.\n")); return NULL; } Py_DECREF(args); @@ -336,7 +369,7 @@ NRT_adapt_ndarray_to_python(arystruct_t* arystruct, int ndim, shape = arystruct->shape_and_strides; strides = shape + ndim; Py_INCREF((PyObject *) descr); - array = (PyArrayObject *) PyArray_NewFromDescr(&PyArray_Type, descr, ndim, + array = (PyArrayObject *) PyArray_NewFromDescr(retty, descr, ndim, shape, strides, arystruct->data, flags, (PyObject *) miobj); diff --git a/numba/core/runtime/_nrt_pythonmod.c b/numba/core/runtime/_nrt_pythonmod.c index 31e1155fd9f..d1300ee8e9a 100644 --- a/numba/core/runtime/_nrt_pythonmod.c +++ b/numba/core/runtime/_nrt_pythonmod.c @@ -163,6 +163,7 @@ declmethod(MemInfo_alloc); declmethod(MemInfo_alloc_safe); declmethod(MemInfo_alloc_aligned); declmethod(MemInfo_alloc_safe_aligned); +declmethod(MemInfo_alloc_safe_aligned_external); declmethod(MemInfo_alloc_dtor_safe); declmethod(MemInfo_call_dtor); declmethod(MemInfo_new_varsize); diff --git a/numba/core/runtime/nrt.c b/numba/core/runtime/nrt.c index 534681d5417..fe63a691537 100644 --- a/numba/core/runtime/nrt.c +++ b/numba/core/runtime/nrt.c @@ -19,6 +19,7 @@ struct MemInfo { void *dtor_info; void *data; size_t size; /* only used for NRT allocated memory */ + NRT_ExternalAllocator *external_allocator; }; @@ -170,13 +171,16 @@ void NRT_MemSys_set_atomic_cas_stub(void) { */ void NRT_MemInfo_init(NRT_MemInfo *mi,void *data, size_t size, - NRT_dtor_function dtor, void *dtor_info) + NRT_dtor_function dtor, void *dtor_info, + NRT_ExternalAllocator *external_allocator) { mi->refct = 1; /* starts with 1 refct */ mi->dtor = dtor; mi->dtor_info = dtor_info; mi->data = data; mi->size = size; + mi->external_allocator = external_allocator; + NRT_Debug(nrt_debug_print("NRT_MemInfo_init mi=%p external_allocator=%p\n", mi, external_allocator)); /* Update stats */ TheMSys.atomic_inc(&TheMSys.stats_mi_alloc); } @@ -185,7 +189,8 @@ NRT_MemInfo *NRT_MemInfo_new(void *data, size_t size, NRT_dtor_function dtor, void *dtor_info) { NRT_MemInfo *mi = NRT_Allocate(sizeof(NRT_MemInfo)); - NRT_MemInfo_init(mi, data, size, dtor, dtor_info); + NRT_Debug(nrt_debug_print("NRT_MemInfo_new mi=%p\n", mi)); + NRT_MemInfo_init(mi, data, size, dtor, dtor_info, NULL); return mi; } @@ -206,9 +211,10 @@ void nrt_internal_dtor_safe(void *ptr, size_t size, void *info) { } static -void *nrt_allocate_meminfo_and_data(size_t size, NRT_MemInfo **mi_out) { +void *nrt_allocate_meminfo_and_data(size_t size, NRT_MemInfo **mi_out, NRT_ExternalAllocator *allocator) { NRT_MemInfo *mi; - char *base = NRT_Allocate(sizeof(NRT_MemInfo) + size); + NRT_Debug(nrt_debug_print("nrt_allocate_meminfo_and_data %p\n", allocator)); + char *base = NRT_Allocate_External(sizeof(NRT_MemInfo) + size, allocator); mi = (NRT_MemInfo *) base; *mi_out = mi; return base + sizeof(NRT_MemInfo); @@ -230,9 +236,17 @@ void nrt_internal_custom_dtor_safe(void *ptr, size_t size, void *info) { NRT_MemInfo *NRT_MemInfo_alloc(size_t size) { NRT_MemInfo *mi; - void *data = nrt_allocate_meminfo_and_data(size, &mi); + void *data = nrt_allocate_meminfo_and_data(size, &mi, NULL); NRT_Debug(nrt_debug_print("NRT_MemInfo_alloc %p\n", data)); - NRT_MemInfo_init(mi, data, size, NULL, NULL); + NRT_MemInfo_init(mi, data, size, NULL, NULL, NULL); + return mi; +} + +NRT_MemInfo *NRT_MemInfo_alloc_external(size_t size, NRT_ExternalAllocator *allocator) { + NRT_MemInfo *mi; + void *data = nrt_allocate_meminfo_and_data(size, &mi, allocator); + NRT_Debug(nrt_debug_print("NRT_MemInfo_alloc %p\n", data)); + NRT_MemInfo_init(mi, data, size, NULL, NULL, allocator); return mi; } @@ -242,22 +256,23 @@ NRT_MemInfo *NRT_MemInfo_alloc_safe(size_t size) { NRT_MemInfo* NRT_MemInfo_alloc_dtor_safe(size_t size, NRT_dtor_function dtor) { NRT_MemInfo *mi; - void *data = nrt_allocate_meminfo_and_data(size, &mi); + void *data = nrt_allocate_meminfo_and_data(size, &mi, NULL); /* Only fill up a couple cachelines with debug markers, to minimize overhead. */ memset(data, 0xCB, MIN(size, 256)); NRT_Debug(nrt_debug_print("NRT_MemInfo_alloc_dtor_safe %p %zu\n", data, size)); - NRT_MemInfo_init(mi, data, size, nrt_internal_custom_dtor_safe, dtor); + NRT_MemInfo_init(mi, data, size, nrt_internal_custom_dtor_safe, dtor, NULL); return mi; } static void *nrt_allocate_meminfo_and_data_align(size_t size, unsigned align, - NRT_MemInfo **mi) + NRT_MemInfo **mi, NRT_ExternalAllocator *allocator) { size_t offset, intptr, remainder; - char *base = nrt_allocate_meminfo_and_data(size + 2 * align, mi); + NRT_Debug(nrt_debug_print("nrt_allocate_meminfo_and_data_align %p\n", allocator)); + char *base = nrt_allocate_meminfo_and_data(size + 2 * align, mi, allocator); intptr = (size_t) base; /* See if we are aligned */ remainder = intptr % align; @@ -271,26 +286,48 @@ void *nrt_allocate_meminfo_and_data_align(size_t size, unsigned align, NRT_MemInfo *NRT_MemInfo_alloc_aligned(size_t size, unsigned align) { NRT_MemInfo *mi; - void *data = nrt_allocate_meminfo_and_data_align(size, align, &mi); + void *data = nrt_allocate_meminfo_and_data_align(size, align, &mi, NULL); NRT_Debug(nrt_debug_print("NRT_MemInfo_alloc_aligned %p\n", data)); - NRT_MemInfo_init(mi, data, size, NULL, NULL); + NRT_MemInfo_init(mi, data, size, NULL, NULL, NULL); return mi; } NRT_MemInfo *NRT_MemInfo_alloc_safe_aligned(size_t size, unsigned align) { NRT_MemInfo *mi; - void *data = nrt_allocate_meminfo_and_data_align(size, align, &mi); + void *data = nrt_allocate_meminfo_and_data_align(size, align, &mi, NULL); /* Only fill up a couple cachelines with debug markers, to minimize overhead. */ memset(data, 0xCB, MIN(size, 256)); NRT_Debug(nrt_debug_print("NRT_MemInfo_alloc_safe_aligned %p %zu\n", data, size)); - NRT_MemInfo_init(mi, data, size, nrt_internal_dtor_safe, (void*)size); + NRT_MemInfo_init(mi, data, size, nrt_internal_dtor_safe, (void*)size, NULL); return mi; } +NRT_MemInfo *NRT_MemInfo_alloc_safe_aligned_external(size_t size, unsigned align, NRT_ExternalAllocator *allocator) { + NRT_MemInfo *mi; + NRT_Debug(nrt_debug_print("NRT_MemInfo_alloc_safe_aligned_external %p\n", allocator)); + void *data = nrt_allocate_meminfo_and_data_align(size, align, &mi, allocator); + /* Only fill up a couple cachelines with debug markers, to minimize + overhead. */ + memset(data, 0xCB, MIN(size, 256)); + NRT_Debug(nrt_debug_print("NRT_MemInfo_alloc_safe_aligned %p %zu\n", + data, size)); + NRT_MemInfo_init(mi, data, size, nrt_internal_dtor_safe, (void*)size, allocator); + return mi; +} + +void NRT_dealloc(NRT_MemInfo *mi) { + NRT_Debug(nrt_debug_print("NRT_dealloc meminfo: %p external_allocator: %p\n", mi, mi->external_allocator)); + if (mi->external_allocator) { + mi->external_allocator->free(mi, mi->external_allocator->opaque_data); + } else { + NRT_Free(mi); + } +} + void NRT_MemInfo_destroy(NRT_MemInfo *mi) { - NRT_Free(mi); + NRT_dealloc(mi); TheMSys.atomic_inc(&TheMSys.stats_mi_free); } @@ -328,6 +365,14 @@ size_t NRT_MemInfo_size(NRT_MemInfo* mi) { return mi->size; } +void * NRT_MemInfo_external_allocator(NRT_MemInfo *mi) { + NRT_Debug(nrt_debug_print("NRT_MemInfo_external_allocator meminfo: %p external_allocator: %p\n", mi, mi->external_allocator)); + return mi->external_allocator; +} + +void *NRT_MemInfo_parent(NRT_MemInfo *mi) { + return mi->dtor_info; +} void NRT_MemInfo_dump(NRT_MemInfo *mi, FILE *out) { fprintf(out, "MemInfo %p refcount %zu\n", mi, mi->refct); @@ -414,8 +459,18 @@ void NRT_MemInfo_varsize_free(NRT_MemInfo *mi, void *ptr) */ void* NRT_Allocate(size_t size) { - void *ptr = TheMSys.allocator.malloc(size); - NRT_Debug(nrt_debug_print("NRT_Allocate bytes=%zu ptr=%p\n", size, ptr)); + return NRT_Allocate_External(size, NULL); +} + +void* NRT_Allocate_External(size_t size, NRT_ExternalAllocator *allocator) { + void *ptr; + if (allocator) { + ptr = allocator->malloc(size, allocator->opaque_data); + NRT_Debug(nrt_debug_print("NRT_Allocate custom bytes=%zu ptr=%p\n", size, ptr)); + } else { + ptr = TheMSys.allocator.malloc(size); + NRT_Debug(nrt_debug_print("NRT_Allocate bytes=%zu ptr=%p\n", size, ptr)); + } TheMSys.atomic_inc(&TheMSys.stats_alloc); return ptr; } @@ -460,6 +515,7 @@ NRT_MemInfo* nrt_manage_memory(void *data, NRT_managed_dtor dtor) { static const NRT_api_functions nrt_functions_table = { NRT_MemInfo_alloc, + NRT_MemInfo_alloc_external, nrt_manage_memory, NRT_MemInfo_acquire, NRT_MemInfo_release, diff --git a/numba/core/runtime/nrt.h b/numba/core/runtime/nrt.h index 3c74dc58f58..9fb23532964 100644 --- a/numba/core/runtime/nrt.h +++ b/numba/core/runtime/nrt.h @@ -15,13 +15,14 @@ All functions described here are threadsafe. /* Debugging facilities - enabled at compile-time */ /* #undef NDEBUG */ #if 0 -# define NRT_Debug(X) X +# define NRT_Debug(X) {X; fflush(stdout); } #else # define NRT_Debug(X) if (0) { X; } #endif /* TypeDefs */ typedef void (*NRT_dtor_function)(void *ptr, size_t size, void *info); +typedef void (*NRT_dealloc_func)(void *ptr, void *dealloc_info); typedef size_t (*NRT_atomic_inc_dec_func)(size_t *ptr); typedef int (*NRT_atomic_cas_func)(void * volatile *ptr, void *cmp, void *repl, void **oldptr); @@ -32,7 +33,6 @@ typedef void *(*NRT_malloc_func)(size_t size); typedef void *(*NRT_realloc_func)(void *ptr, size_t new_size); typedef void (*NRT_free_func)(void *ptr); - /* Memory System API */ /* Initialize the memory system */ @@ -101,7 +101,8 @@ NRT_MemInfo* NRT_MemInfo_new(void *data, size_t size, VISIBILITY_HIDDEN void NRT_MemInfo_init(NRT_MemInfo *mi, void *data, size_t size, - NRT_dtor_function dtor, void *dtor_info); + NRT_dtor_function dtor, void *dtor_info, + NRT_ExternalAllocator *external_allocator); /* * Returns the refcount of a MemInfo or (size_t)-1 if error. @@ -116,6 +117,8 @@ size_t NRT_MemInfo_refcount(NRT_MemInfo *mi); VISIBILITY_HIDDEN NRT_MemInfo *NRT_MemInfo_alloc(size_t size); +NRT_MemInfo *NRT_MemInfo_alloc_external(size_t size, NRT_ExternalAllocator *allocator); + /* * The "safe" NRT_MemInfo_alloc performs additional steps to help debug * memory errors. @@ -141,6 +144,8 @@ NRT_MemInfo *NRT_MemInfo_alloc_aligned(size_t size, unsigned align); VISIBILITY_HIDDEN NRT_MemInfo *NRT_MemInfo_alloc_safe_aligned(size_t size, unsigned align); +NRT_MemInfo *NRT_MemInfo_alloc_safe_aligned_external(size_t size, unsigned align, NRT_ExternalAllocator *allocator); + /* * Internal API. * Release a MemInfo. Calls NRT_MemSys_insert_meminfo. @@ -179,6 +184,18 @@ void* NRT_MemInfo_data(NRT_MemInfo* mi); VISIBILITY_HIDDEN size_t NRT_MemInfo_size(NRT_MemInfo* mi); +/* + * Returns the external allocator + */ +VISIBILITY_HIDDEN +void* NRT_MemInfo_external_allocator(NRT_MemInfo* mi); + +/* + * Returns the parent MemInfo + */ +VISIBILITY_HIDDEN +void* NRT_MemInfo_parent(NRT_MemInfo* mi); + /* * NRT API for resizable buffers. @@ -207,6 +224,7 @@ void NRT_MemInfo_dump(NRT_MemInfo *mi, FILE *out); * Allocate memory of `size` bytes. */ VISIBILITY_HIDDEN void* NRT_Allocate(size_t size); +VISIBILITY_HIDDEN void* NRT_Allocate_External(size_t size, NRT_ExternalAllocator *allocator); /* * Deallocate memory pointed by `ptr`. diff --git a/numba/core/runtime/nrt_external.h b/numba/core/runtime/nrt_external.h index 391b6fa1b0e..a4835c36f67 100644 --- a/numba/core/runtime/nrt_external.h +++ b/numba/core/runtime/nrt_external.h @@ -7,6 +7,18 @@ typedef struct MemInfo NRT_MemInfo; typedef void NRT_managed_dtor(void *data); +typedef void *(*NRT_external_malloc_func)(size_t size, void *opaque_data); +typedef void *(*NRT_external_realloc_func)(void *ptr, size_t new_size, void *opaque_data); +typedef void (*NRT_external_free_func)(void *ptr, void *opaque_data); + +struct ExternalMemAllocator { + NRT_external_malloc_func malloc; + NRT_external_realloc_func realloc; + NRT_external_free_func free; + void *opaque_data; +}; + +typedef struct ExternalMemAllocator NRT_ExternalAllocator; typedef struct { /* Methods to create MemInfos. @@ -21,6 +33,10 @@ typedef struct { Returning a new reference. */ NRT_MemInfo* (*allocate)(size_t nbytes); + /* Allocator memory using an external allocator but still using Numba's MemInfo. + + */ + NRT_MemInfo* (*allocate_external)(size_t nbytes, NRT_ExternalAllocator *allocator); /* Convert externally allocated memory into a MemInfo. diff --git a/numba/core/types/npytypes.py b/numba/core/types/npytypes.py index 6f6307c5526..3c2191ca23e 100644 --- a/numba/core/types/npytypes.py +++ b/numba/core/types/npytypes.py @@ -8,6 +8,7 @@ from numba.core import utils from .misc import UnicodeType from .containers import Bytes +import numpy as np class CharSeq(Type): """ @@ -394,8 +395,9 @@ class Array(Buffer): Type class for Numpy arrays. """ - def __init__(self, dtype, ndim, layout, readonly=False, name=None, + def __init__(self, dtype, ndim, layout, py_type=np.ndarray, readonly=False, name=None, aligned=True, addrspace=None): + self.py_type = py_type if readonly: self.mutable = False if (not aligned or diff --git a/numba/core/typing/npydecl.py b/numba/core/typing/npydecl.py index 2dbbed39be9..e7ecf452fe9 100644 --- a/numba/core/typing/npydecl.py +++ b/numba/core/typing/npydecl.py @@ -126,7 +126,21 @@ def generic(self, args, kws): ret_tys = ufunc_loop.outputs[-implicit_output_count:] if ndims > 0: assert layout is not None - ret_tys = [types.Array(dtype=ret_ty, ndim=ndims, layout=layout) + # If either of the types involved in the ufunc operation have a + # __array_ufunc__ method then invoke the first such one to + # determine the output type of the ufunc. + array_ufunc_type = None + for a in args: + if hasattr(a, "__array_ufunc__"): + array_ufunc_type = a + break + output_type = types.Array + if array_ufunc_type is not None: + output_type = array_ufunc_type.__array_ufunc__(ufunc, "__call__", *args, **kws) + # Eventually better error handling! FIX ME! + assert(output_type is not None) + + ret_tys = [output_type(dtype=ret_ty, ndim=ndims, layout=layout) for ret_ty in ret_tys] ret_tys = [resolve_output_type(self.context, args, ret_ty) for ret_ty in ret_tys] @@ -517,6 +531,7 @@ def typer(shape, dtype=None): @infer_global(np.empty_like) @infer_global(np.zeros_like) +@infer_global(np.ones_like) class NdConstructorLike(CallableTemplate): """ Typing template for np.empty_like(), .zeros_like(), .ones_like(). @@ -544,9 +559,6 @@ def typer(arg, dtype=None): return typer -infer_global(np.ones_like)(NdConstructorLike) - - @infer_global(np.full) class NdFull(CallableTemplate): @@ -563,6 +575,7 @@ def typer(shape, fill_value, dtype=None): return typer + @infer_global(np.full_like) class NdFullLike(CallableTemplate): diff --git a/numba/np/arrayobj.py b/numba/np/arrayobj.py index 933b1c6565e..5749e7d9b5b 100644 --- a/numba/np/arrayobj.py +++ b/numba/np/arrayobj.py @@ -32,7 +32,7 @@ from numba.misc import quicksort, mergesort from numba.cpython import slicing from numba.cpython.unsafe.tuple import tuple_setitem - +from numba.core.pythonapi import _allocators def set_range_metadata(builder, load, lower_bound, upper_bound): """ @@ -3399,8 +3399,13 @@ def _empty_nd_impl(context, builder, arrtype, shapes): ) align = context.get_preferred_array_alignment(arrtype.dtype) - meminfo = context.nrt.meminfo_alloc_aligned(builder, size=allocsize, - align=align) + def alloc_unsupported(context, builder, size, align): + return context.nrt.meminfo_alloc_aligned(builder, size, align) + + # See if the type has a special allocator, if not use the default + # alloc_unsuppported allocator above. + allocator_impl = _allocators.lookup(arrtype.__class__, alloc_unsupported) + meminfo = allocator_impl(context, builder, size=allocsize, align=align) data = context.nrt.meminfo_data(builder, meminfo) From 56d5bc78bf9f466a89b4a323594976c035317436 Mon Sep 17 00:00:00 2001 From: "Todd A. Anderson" Date: Thu, 12 Nov 2020 14:32:27 -0600 Subject: [PATCH 2/5] dparray changes --- numba/core/cpu.py | 2 + numba/dppl/dparray.py | 660 ++++++++++++++++++++++++++ numba/dppl/dppl_rt.c | 89 ++++ numba/dppl/tests/dppl/test_dparray.py | 213 +++++++++ setup.py | 8 +- 5 files changed, 971 insertions(+), 1 deletion(-) create mode 100644 numba/dppl/dparray.py create mode 100644 numba/dppl/dppl_rt.c create mode 100644 numba/dppl/tests/dppl/test_dparray.py diff --git a/numba/core/cpu.py b/numba/core/cpu.py index af9dc8e335b..409a2f08afe 100644 --- a/numba/core/cpu.py +++ b/numba/core/cpu.py @@ -66,6 +66,7 @@ def load_additional_registries(self): from numba.np import npyimpl from numba.cpython import cmathimpl, mathimpl, printimpl, randomimpl from numba.misc import cffiimpl + from numba.dppl.dparray import numba_register as dparray_register self.install_registry(cmathimpl.registry) self.install_registry(cffiimpl.registry) self.install_registry(mathimpl.registry) @@ -75,6 +76,7 @@ def load_additional_registries(self): # load 3rd party extensions numba.core.entrypoints.init_all() + dparray_register() @property def target_data(self): diff --git a/numba/dppl/dparray.py b/numba/dppl/dparray.py new file mode 100644 index 00000000000..a04fe674d4b --- /dev/null +++ b/numba/dppl/dparray.py @@ -0,0 +1,660 @@ +#from ._ndarray_utils import _transmogrify +import numpy as np +from inspect import getmembers, isfunction, isclass, isbuiltin +from numbers import Number +import numba +from types import FunctionType as ftype, BuiltinFunctionType as bftype +from numba import types +from numba.extending import typeof_impl, register_model, type_callable, lower_builtin +from numba.np import numpy_support +from numba.core.pythonapi import box, allocator +from llvmlite import ir +import llvmlite.llvmpy.core as lc +import llvmlite.binding as llb +from numba.core import types, cgutils +import builtins +import sys +from ctypes.util import find_library +from numba.core.typing.templates import builtin_registry as templates_registry +from numba.core.typing.npydecl import registry as typing_registry +from numba.core.imputils import builtin_registry as lower_registry +import importlib +import functools +import inspect +from numba.core.typing.templates import CallableTemplate +from numba.np.arrayobj import _array_copy + +debug = False + +def dprint(*args): + if debug: + print(*args) + sys.stdout.flush() + +flib = find_library('mkl_intel_ilp64') +dprint("flib:", flib) +llb.load_library_permanently(flib) + +sycl_mem_lib = find_library('DPPLSyclInterface') +dprint("sycl_mem_lib:", sycl_mem_lib) +llb.load_library_permanently(sycl_mem_lib) + +import dpctl +from dpctl._memory import MemoryUSMShared +import numba.dppl._dppl_rt + +functions_list = [o[0] for o in getmembers(np) if isfunction(o[1]) or isbuiltin(o[1])] +class_list = [o for o in getmembers(np) if isclass(o[1])] +# Register the helper function in dppl_rt so that we can insert calls to them via llvmlite. +for py_name, c_address in numba.dppl._dppl_rt.c_helpers.items(): + llb.add_symbol(py_name, c_address) + +array_interface_property = "__array_interface__" +def has_array_interface(x): + return hasattr(x, array_interface_property) + +class ndarray(np.ndarray): + """ + numpy.ndarray subclass whose underlying memory buffer is allocated + with a foreign allocator. + """ + def __new__(subtype, shape, + dtype=float, buffer=None, offset=0, + strides=None, order=None): + # Create a new array. + if buffer is None: + dprint("dparray::ndarray __new__ buffer None") + nelems = np.prod(shape) + dt = np.dtype(dtype) + isz = dt.itemsize + buf = MemoryUSMShared(nbytes=isz*max(1,nelems)) + new_obj = np.ndarray.__new__( + subtype, shape, dtype=dt, + buffer=buf, offset=0, + strides=strides, order=order) + if hasattr(new_obj, array_interface_property): + dprint("buffer None new_obj already has sycl_usm") + else: + dprint("buffer None new_obj will add sycl_usm") + new_obj.__sycl_usm_array_interface__ = {} + return new_obj + # zero copy if buffer is a usm backed array-like thing + elif hasattr(buffer, array_interface_property): + dprint("dparray::ndarray __new__ buffer", array_interface_property) + # also check for array interface + new_obj = np.ndarray.__new__( + subtype, shape, dtype=dtype, + buffer=buffer, offset=offset, + strides=strides, order=order) + if hasattr(new_obj, array_interface_property): + dprint("buffer None new_obj already has sycl_usm") + else: + dprint("buffer None new_obj will add sycl_usm") + new_obj.__sycl_usm_array_interface__ = {} + return new_obj + else: + dprint("dparray::ndarray __new__ buffer not None and not sycl_usm") + nelems = np.prod(shape) + # must copy + ar = np.ndarray(shape, + dtype=dtype, buffer=buffer, + offset=offset, strides=strides, + order=order) + buf = MemoryUSMShared(nbytes=ar.nbytes) + new_obj = np.ndarray.__new__( + subtype, shape, dtype=dtype, + buffer=buf, offset=0, + strides=strides, order=order) + np.copyto(new_obj, ar, casting='no') + if hasattr(new_obj, array_interface_property): + dprint("buffer None new_obj already has sycl_usm") + else: + dprint("buffer None new_obj will add sycl_usm") + new_obj.__sycl_usm_array_interface__ = {} + return new_obj + + def __array_finalize__(self, obj): + dprint("__array_finalize__:", obj, hex(id(obj)), type(obj)) +# import pdb +# pdb.set_trace() + # When called from the explicit constructor, obj is None + if obj is None: return + # When called in new-from-template, `obj` is another instance of our own + # subclass, that we might use to update the new `self` instance. + # However, when called from view casting, `obj` can be an instance of any + # subclass of ndarray, including our own. + if hasattr(obj, array_interface_property): + return + if isinstance(obj, numba.core.runtime._nrt_python._MemInfo): + mobj = obj + while isinstance(mobj, numba.core.runtime._nrt_python._MemInfo): + dprint("array_finalize got Numba MemInfo") + ea = mobj.external_allocator + d = mobj.data + dprint("external_allocator:", hex(ea), type(ea)) + dprint("data:", hex(d), type(d)) + dppl_rt_allocator = numba.dppl._dppl_rt.get_external_allocator() + dprint("dppl external_allocator:", hex(dppl_rt_allocator), type(dppl_rt_allocator)) + dprint(dir(mobj)) + if ea == dppl_rt_allocator: + return + mobj = mobj.parent + if isinstance(mobj, ndarray): + mobj = mobj.base + if isinstance(obj, np.ndarray): + ob = self + while isinstance(ob, np.ndarray): + if hasattr(obj, array_interface_property): + return + ob = ob.base + + # Just raise an exception since __array_ufunc__ makes all reasonable cases not + # need the code below. + raise ValueError("Non-MKL allocated ndarray can not viewed as MKL-allocated one without a copy") + + """ + # since dparray must have mkl_memory underlying it, a copy must be made + newbuf = dpctl.Memory(nbytes=self.data.nbytes) + new_arr = np.ndarray.__new__( + type(self), + self.shape, + buffer=newbuf, offset=0, + dtype=self.dtype, + strides=self.strides) + np.copyto(new_arr, self) + # We need to modify self to now be mkl_memory-backed ndarray + # We only need to change data and base, but these are not writeable. + # + # Modification can not be done by simply setting self either, + # as self is just a local copy of the instance. + # + # raise ValueError("Non-MKL allocated ndarray can not viewed as MKL-allocated one without a copy") + # Will probably have to raise an exception soon as Numpy may disallow this. + _transmogrify(self, new_arr) + """ + + # Tell Numba to not treat this type just like a NumPy ndarray but to propagate its type. + # This way it will use the custom dparray allocator. + __numba_no_subtype_ndarray__ = True + + # Convert to a NumPy ndarray. + def as_ndarray(self): + return np.copy(self) + + def __array__(self): + return self + + def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): + if method == '__call__': + N = None + scalars = [] + typing = [] + for inp in inputs: + if isinstance(inp, Number): + scalars.append(inp) + typing.append(inp) + elif isinstance(inp, (self.__class__, np.ndarray)): + if isinstance(inp, self.__class__): + scalars.append(np.ndarray(inp.shape, inp.dtype, inp)) + typing.append(np.ndarray(inp.shape, inp.dtype)) + else: + scalars.append(inp) + typing.append(inp) + if N is not None: + if N != inp.shape: + raise TypeError("inconsistent sizes") + else: + N = inp.shape + else: + return NotImplemented + # Have to avoid recursive calls to array_ufunc here. + # If no out kwarg then we create a dparray out so that we get + # USM memory. However, if kwarg has dparray-typed out then + # array_ufunc is called recursively so we cast out as regular + # NumPy ndarray (having a USM data pointer). + if kwargs.get('out', None) is None: + # maybe copy? + # deal with multiple returned arrays, so kwargs['out'] can be tuple + res_type = np.result_type(*typing) + out = empty(inputs[0].shape, dtype=res_type) + out_as_np = np.ndarray(out.shape, out.dtype, out) + kwargs['out'] = out_as_np + else: + # If they manually gave dparray as out kwarg then we have to also + # cast as regular NumPy ndarray to avoid recursion. + if isinstance(kwargs['out'], ndarray): + out = kwargs['out'] + kwargs['out'] = np.ndarray(out.shape, out.dtype, out) + else: + out = kwargs['out'] + ret = ufunc(*scalars, **kwargs) + return out + else: + return NotImplemented + +def isdef(x): + try: + eval(x) + return True + except NameEror: + return False + +for c in class_list: + cname = c[0] + if isdef(cname): + continue + # For now we do the simple thing and copy the types from NumPy module into dparray module. + new_func = "%s = np.%s" % (cname, cname) +# new_func = "class %s(np.%s):\n" % (cname, cname) + if cname == "ndarray": + # Implemented explicitly above. + continue + else: + # This is temporary. +# new_func += " pass\n" + # The code below should eventually be made to work and used. +# new_func += " @classmethod\n" +# new_func += " def cast(cls, some_np_obj):\n" +# new_func += " some_np_obj.__class__ = cls\n" +# new_func += " return some_np_obj\n" + pass + try: + the_code = compile(new_func, '__init__', 'exec') + exec(the_code) + except: + print("Failed to exec class", cname) + pass + +# Redefine all Numpy functions in this module and if they +# return a Numpy array, transform that to a USM-backed array +# instead. This is a stop-gap. We should eventually find a +# way to do the allocation correct to start with. +for fname in functions_list: + if isdef(fname): + continue +# print("Adding function", fname) + new_func = "def %s(*args, **kwargs):\n" % fname + new_func += " ret = np.%s(*args, **kwargs)\n" % fname + new_func += " if type(ret) == np.ndarray:\n" + new_func += " ret = ndarray(ret.shape, ret.dtype, ret)\n" + new_func += " return ret\n" + the_code = compile(new_func, '__init__', 'exec') + exec(the_code) + +# This class creates a type in Numba. +class DPArrayType(types.Array): + def __init__(self, dtype, ndim, layout, readonly=False, name=None, + aligned=True, addrspace=None): + # This name defines how this type will be shown in Numba's type dumps. + name = "DPArray:ndarray(%s, %sd, %s)" % (dtype, ndim, layout) + super(DPArrayType, self).__init__(dtype, ndim, layout, + py_type=ndarray, + readonly=readonly, + name=name, + addrspace=addrspace) + + # Tell Numba typing how to combine DPArrayType with other ndarray types. + def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): + if method == '__call__': + for inp in inputs: + if not isinstance(inp, (DPArrayType, types.Array, types.Number)): + return None + + return DPArrayType + else: + return None + +# This tells Numba how to create a DPArrayType when a dparray is passed +# into a njit function. +@typeof_impl.register(ndarray) +def typeof_ta_ndarray(val, c): + try: + dtype = numpy_support.from_dtype(val.dtype) + except NotImplementedError: + raise ValueError("Unsupported array dtype: %s" % (val.dtype,)) + layout = numpy_support.map_layout(val) + readonly = not val.flags.writeable + return DPArrayType(dtype, val.ndim, layout, readonly=readonly) + +# This tells Numba to use the default Numpy ndarray data layout for +# object of type DPArray. +register_model(DPArrayType)(numba.core.datamodel.models.ArrayModel) + +""" +# This code should not work because you can't pass arbitrary buffer to dparray constructor. + +# This tells Numba how to type calls to a DPArray constructor. +@type_callable(ndarray) +def type_ndarray(context): + def typer(shape, ndim, buf): + return DPArrayType(buf.dtype, buf.ndim, buf.layout) + return typer + +@overload(ndarray) +def overload_ndarray_constructor(shape, dtype, buf): + print("overload_ndarray_constructor:", shape, dtype, buf) + + def ndarray_impl(shape, dtype, buf): + pass + + return ndarray_impl + +# This tells Numba how to implement calls to a DPArray constructor. +@lower_builtin(ndarray, types.UniTuple, types.DType, types.Array) +def impl_ndarray(context, builder, sig, args): + # Need to allocate and copy here! + shape, ndim, buf = args + return buf + + context.nrt._require_nrt() + + mod = builder.module + u32 = ir.IntType(32) + + # Get the Numba external allocator for USM memory. + ext_allocator_fnty = ir.FunctionType(cgutils.voidptr_t, []) + ext_allocator_fn = mod.get_or_insert_function(ext_allocator_fnty, + name="dparray_get_ext_allocator") + ext_allocator = builder.call(ext_allocator_fn, []) + # Get the Numba function to allocate an aligned array with an external allocator. + fnty = ir.FunctionType(cgutils.voidptr_t, [cgutils.intp_t, u32, cgutils.voidptr_t]) + fn = mod.get_or_insert_function(fnty, + name="NRT_MemInfo_alloc_safe_aligned_external") + fn.return_value.add_attribute("noalias") + if isinstance(align, builtins.int): + align = context.get_constant(types.uint32, align) + else: + assert align.type == u32, "align must be a uint32" + newary = builder.call(fn, [size, align, ext_allocator]) + + return buf +""" + +# This tells Numba how to convert from its native representation +# of a DPArray in a njit function back to a Python DPArray. +@box(DPArrayType) +def box_array(typ, val, c): + nativearycls = c.context.make_array(typ) + nativeary = nativearycls(c.context, c.builder, value=val) + if c.context.enable_nrt: + np_dtype = numpy_support.as_dtype(typ.dtype) + dtypeptr = c.env_manager.read_const(c.env_manager.add_const(np_dtype)) + # Steals NRT ref + newary = c.pyapi.nrt_adapt_ndarray_to_python(typ, val, dtypeptr) + return newary + else: + parent = nativeary.parent + c.pyapi.incref(parent) + return parent + +# This tells Numba to use this function when it needs to allocate a +# DPArray in a njit function. +@allocator(DPArrayType) +def allocator_DPArray(context, builder, size, align): + context.nrt._require_nrt() + + mod = builder.module + u32 = ir.IntType(32) + + # Get the Numba external allocator for USM memory. + ext_allocator_fnty = ir.FunctionType(cgutils.voidptr_t, []) + ext_allocator_fn = mod.get_or_insert_function(ext_allocator_fnty, + name="dparray_get_ext_allocator") + ext_allocator = builder.call(ext_allocator_fn, []) + # Get the Numba function to allocate an aligned array with an external allocator. + fnty = ir.FunctionType(cgutils.voidptr_t, [cgutils.intp_t, u32, cgutils.voidptr_t]) + fn = mod.get_or_insert_function(fnty, + name="NRT_MemInfo_alloc_safe_aligned_external") + fn.return_value.add_attribute("noalias") + if isinstance(align, builtins.int): + align = context.get_constant(types.uint32, align) + else: + assert align.type == u32, "align must be a uint32" + return builder.call(fn, [size, align, ext_allocator]) + +registered = False + +def numba_register(): + global registered + if not registered: + registered = True + numba_register_typing() + numba_register_lower_builtin() + +# Copy a function registered as a lowerer in Numba but change the +# "np" import in Numba to point to dparray instead of NumPy. +def copy_func_for_dparray(f, dparray_mod): + import copy as cc + # Make a copy so our change below doesn't affect anything else. + gglobals = cc.copy(f.__globals__) + # Make the "np"'s in the code use dparray instead of Numba's default NumPy. + gglobals['np'] = dparray_mod + # Create a new function using the original code but the new globals. + g = ftype(f.__code__, gglobals, None, f.__defaults__, f.__closure__) + # Some other tricks to make sure the function copy works. + g = functools.update_wrapper(g, f) + g.__kwdefaults__ = f.__kwdefaults__ + return g + +def types_replace_array(x): + return tuple([z if z != types.Array else DPArrayType for z in x]) + +def numba_register_lower_builtin(): + todo = [] + todo_builtin = [] + todo_getattr = [] + + # For all Numpy identifiers that have been registered for typing in Numba... + # this registry contains functions, getattrs, setattrs, casts and constants...need to do them all? FIX FIX FIX + for ig in lower_registry.functions: +# print("ig:", ig, type(ig), len(ig)) + impl, func, types = ig +# print("register lower_builtin:", impl, type(impl), func, type(func), types, type(types)) + # If it is a Numpy function... + if isinstance(func, ftype): +# print("isfunction:", func.__module__, type(func.__module__)) + if func.__module__ == np.__name__: +# print("name:", func.__name__) + # If we have overloaded that function in the dparray module (always True right now)... + if func.__name__ in functions_list: + todo.append(ig) + if isinstance(func, bftype): +# print("isbuiltinfunction:", func.__module__, type(func.__module__)) + if func.__module__ == np.__name__: +# print("name:", func.__name__) + # If we have overloaded that function in the dparray module (always True right now)... + if func.__name__ in functions_list: + todo.append(ig) +# print("todo_builtin added:", func.__name__) + + for lg in lower_registry.getattrs: + func, attr, types = lg + types_with_dparray = types_replace_array(types) + if DPArrayType in types_with_dparray: + dprint("lower_getattr:", func, type(func), attr, type(attr), types, type(types)) + todo_getattr.append((func, attr, types_with_dparray)) + + for lg in todo_getattr: + lower_registry.getattrs.append(lg) + + cur_mod = importlib.import_module(__name__) + for impl, func, types in (todo+todo_builtin): + dparray_func = eval(func.__name__) + dprint("need to re-register lowerer for dparray", impl, func, types, dparray_func) + new_impl = copy_func_for_dparray(impl, cur_mod) +# lower_registry.functions.append((impl, dparray_func, types)) + lower_registry.functions.append((new_impl, dparray_func, types)) + +def argspec_to_string(argspec): + first_default_arg = len(argspec.args)-len(argspec.defaults) + non_def = argspec.args[:first_default_arg] + arg_zip = list(zip(argspec.args[first_default_arg:], argspec.defaults)) + combined = [a+"="+str(b) for a,b in arg_zip] + return ",".join(non_def + combined) + +def numba_register_typing(): + todo = [] + todo_classes = [] + todo_getattr = [] + + # For all Numpy identifiers that have been registered for typing in Numba... + for ig in typing_registry.globals: + val, typ = ig +# print("global typing:", val, type(val), typ, type(typ)) + # If it is a Numpy function... + if isinstance(val, (ftype, bftype)): +# print("name:", val.__name__, val.__name__ in functions_list) + # If we have overloaded that function in the dparray module (always True right now)... + if val.__name__ in functions_list: + todo.append(ig) + if isinstance(val, type): + todo_classes.append(ig) + + for tgetattr in templates_registry.attributes: + if tgetattr.key == types.Array: + todo_getattr.append(tgetattr) + + # This is actuallya no-op now. +# for val, typ in todo_classes: +# print("todo_classes:", val, type(val), typ, type(typ)) +# assert len(typ.templates) == 1 +# dpval = eval(val.__name__) + + for val, typ in todo: + assert len(typ.templates) == 1 + # template is the typing class to invoke generic() upon. + template = typ.templates[0] + dpval = eval(val.__name__) + dprint("need to re-register for dparray", val, typ, typ.typing_key) + """ + if debug: + print("--------------------------------------------------------------") + print("need to re-register for dparray", val, typ, typ.typing_key) + print("val:", val, type(val), "dir val", dir(val)) + print("typ:", typ, type(typ), "dir typ", dir(typ)) + print("typing key:", typ.typing_key) + print("name:", typ.name) + print("key:", typ.key) + print("templates:", typ.templates) + print("template:", template, type(template)) + print("dpval:", dpval, type(dpval)) + print("--------------------------------------------------------------") + """ + + class_name = "DparrayTemplate_" + val.__name__ + + @classmethod + def set_key_original(cls, key, original): + cls.key = key + cls.original = original + + def generic_impl(self): +# print("generic_impl", self.__class__.key, self.__class__.original) + original_typer = self.__class__.original.generic(self.__class__.original) + #print("original_typer:", original_typer, type(original_typer), self.__class__) + ot_argspec = inspect.getfullargspec(original_typer) + #print("ot_argspec:", ot_argspec) + astr = argspec_to_string(ot_argspec) + #print("astr:", astr) + + typer_func = """def typer({}): + original_res = original_typer({}) + #print("original_res:", original_res) + if isinstance(original_res, types.Array): + return DPArrayType(dtype=original_res.dtype, ndim=original_res.ndim, layout=original_res.layout) + + return original_res""".format(astr, ",".join(ot_argspec.args)) + + #print("typer_func:", typer_func) + + try: + gs = globals() + ls = locals() + gs["original_typer"] = ls["original_typer"] + exec(typer_func, globals(), locals()) + except NameError as ne: + print("NameError in exec:", ne) + sys.exit(0) + except: + print("exec failed!", sys.exc_info()[0]) + sys.exit(0) + + try: + exec_res = eval("typer") + except NameError as ne: + print("NameError in eval:", ne) + sys.exit(0) + except: + print("eval failed!", sys.exc_info()[0]) + sys.exit(0) + + #print("exec_res:", exec_res) + return exec_res + + new_dparray_template = type(class_name, (template,), { + "set_class_vars" : set_key_original, + "generic" : generic_impl}) + + new_dparray_template.set_class_vars(dpval, template) + + assert(callable(dpval)) + type_handler = types.Function(new_dparray_template) + typing_registry.register_global(dpval, type_handler) + + # Handle dparray attribute typing. + for tgetattr in todo_getattr: + class_name = tgetattr.__name__ + "_dparray" + dprint("tgetattr:", tgetattr, type(tgetattr), class_name) + + @classmethod + def set_key(cls, key): + cls.key = key + + def getattr_impl(self, attr): + if attr.startswith('resolve_'): + #print("getattr_impl starts with resolve_:", self, type(self), attr) + def wrapper(*args, **kwargs): + attr_res = tgetattr.__getattribute__(self, attr)(*args, **kwargs) + if isinstance(attr_res, types.Array): + return DPArrayType(dtype=attr_res.dtype, ndim=attr_res.ndim, layout=attr_res.layout) + return wrapper + else: + return tgetattr.__getattribute__(self, attr) + + new_dparray_template = type(class_name, (tgetattr,), { + "set_class_vars" : set_key, + "__getattribute__" : getattr_impl}) + + new_dparray_template.set_class_vars(DPArrayType) + templates_registry.register_attr(new_dparray_template) + + +def from_ndarray(x): + return copy(x) + +def as_ndarray(x): + return np.copy(x) + +@typing_registry.register_global(as_ndarray) +class DparrayAsNdarray(CallableTemplate): + def generic(self): + def typer(arg): + return types.Array(dtype=arg.dtype, ndim=arg.ndim, layout=arg.layout) + + return typer + +@typing_registry.register_global(from_ndarray) +class DparrayFromNdarray(CallableTemplate): + def generic(self): + def typer(arg): + return DPArrayType(dtype=arg.dtype, ndim=arg.ndim, layout=arg.layout) + + return typer + +@lower_registry.lower(as_ndarray, DPArrayType) +def dparray_conversion_as(context, builder, sig, args): + return _array_copy(context, builder, sig, args) + +@lower_registry.lower(from_ndarray, types.Array) +def dparray_conversion_from(context, builder, sig, args): + return _array_copy(context, builder, sig, args) diff --git a/numba/dppl/dppl_rt.c b/numba/dppl/dppl_rt.c new file mode 100644 index 00000000000..75c05ff5856 --- /dev/null +++ b/numba/dppl/dppl_rt.c @@ -0,0 +1,89 @@ +#include "../_pymodule.h" +#include "../core/runtime/nrt_external.h" +#include "assert.h" +#include +#include + +NRT_ExternalAllocator dparray_allocator; + +void dparray_memsys_init(void) { + void *(*get_queue)(void); + char *lib_name = "libDPPLSyclInterface.so"; + char *malloc_name = "DPPLmalloc_shared"; + char *free_name = "DPPLfree_with_queue"; + char *get_queue_name = "DPPLQueueMgr_GetCurrentQueue"; + + void *sycldl = dlopen(lib_name, RTLD_NOW); + assert(sycldl != NULL); + dparray_allocator.malloc = (NRT_external_malloc_func)dlsym(sycldl, malloc_name); + if (dparray_allocator.malloc == NULL) { + printf("Did not find %s in %s\n", malloc_name, lib_name); + exit(-1); + } + dparray_allocator.realloc = NULL; + dparray_allocator.free = (NRT_external_free_func)dlsym(sycldl, free_name); + if (dparray_allocator.free == NULL) { + printf("Did not find %s in %s\n", free_name, lib_name); + exit(-1); + } + get_queue = (void *(*))dlsym(sycldl, get_queue_name); + if (get_queue == NULL) { + printf("Did not find %s in %s\n", get_queue_name, lib_name); + exit(-1); + } + dparray_allocator.opaque_data = get_queue(); +// printf("dparray_memsys_init: %p %p %p\n", dparray_allocator.malloc, dparray_allocator.free, dparray_allocator.opaque_data); +} + +void * dparray_get_ext_allocator(void) { + printf("dparray_get_ext_allocator %p\n", &dparray_allocator); + return (void*)&dparray_allocator; +} + +static PyObject * +get_external_allocator(PyObject *self, PyObject *args) { + return PyLong_FromVoidPtr(dparray_get_ext_allocator()); +} + +static PyMethodDef ext_methods[] = { +#define declmethod_noargs(func) { #func , ( PyCFunction )func , METH_NOARGS, NULL } + declmethod_noargs(get_external_allocator), + {NULL}, +#undef declmethod_noargs +}; + +static PyObject * +build_c_helpers_dict(void) +{ + PyObject *dct = PyDict_New(); + if (dct == NULL) + goto error; + +#define _declpointer(name, value) do { \ + PyObject *o = PyLong_FromVoidPtr(value); \ + if (o == NULL) goto error; \ + if (PyDict_SetItemString(dct, name, o)) { \ + Py_DECREF(o); \ + goto error; \ + } \ + Py_DECREF(o); \ +} while (0) + + _declpointer("dparray_get_ext_allocator", &dparray_get_ext_allocator); + +#undef _declpointer + return dct; +error: + Py_XDECREF(dct); + return NULL; +} + +MOD_INIT(_dppl_rt) { + PyObject *m; + MOD_DEF(m, "numba.dppl._dppl_rt", "No docs", ext_methods) + if (m == NULL) + return MOD_ERROR_VAL; + dparray_memsys_init(); + PyModule_AddObject(m, "c_helpers", build_c_helpers_dict()); + return MOD_SUCCESS_VAL(m); +} diff --git a/numba/dppl/tests/dppl/test_dparray.py b/numba/dppl/tests/dppl/test_dparray.py new file mode 100644 index 00000000000..5ed1ad3d339 --- /dev/null +++ b/numba/dppl/tests/dppl/test_dparray.py @@ -0,0 +1,213 @@ +from __future__ import print_function, division, absolute_import + +import numba +import numba.dppl.dparray as dparray +import numpy +import sys + +def p1(a): + return a * 2.0 + 13 + +f1 = numba.njit(p1) + +@numba.njit() +def f2(a): + return a + +@numba.njit() +def f3(a, b): # a is dparray, b is numpy + return a * dparray.asarray(b) + +@numba.njit() +def f4(): + return dparray.ones(10) + +def p5(a, b): # a is dparray, b is numpy + return a * b + +f5 = numba.njit(p5) + +@numba.njit() +def f6(a): + return a + 13 + +@numba.njit() +def f7(a): # a is dparray + # implicit conversion of a to numpy.ndarray + b = numpy.ones(10) + c = a * b + d = a.argsort() # with no implicit conversion this fails + +@numba.njit +def f8(a): + return dparray.as_ndarray(a) + +@numba.njit +def f9(a): + return dparray.from_ndarray(a) + +@numba.njit +def f10(): + return dparray.empty((10,10)) + +@numba.njit +def f11(x): + return x.shape + +@numba.njit +def f12(x): + return x.T + +#-------------------------------------------------------------------------------- + +print("------------------- Testing Python Numpy") +sys.stdout.flush() +z1 = numpy.ones(10) +z2 = p1(z1) +print("z2:", z2, type(z2)) +assert(type(z2) == numpy.ndarray) + +print("------------------- Testing Numba Numpy") +sys.stdout.flush() +z1 = numpy.ones(10) +z2 = f1(z1) +print("z2:", z2, type(z2)) +assert(type(z2) == numpy.ndarray) + +print("------------------- Testing dparray ones") +sys.stdout.flush() +a = dparray.ones(10) +print("a:", a, type(a)) +assert(isinstance(a, dparray.ndarray)) +assert(dparray.has_array_interface(a)) + +print("------------------- Testing dparray.dparray.as_ndarray") +sys.stdout.flush() +nd1 = a.as_ndarray() +print("nd1:", nd1, type(nd1)) +assert(type(nd1) == numpy.ndarray) + +print("------------------- Testing dparray.as_ndarray") +sys.stdout.flush() +nd2 = dparray.as_ndarray(a) +print("nd2:", nd2, type(nd2)) +assert(type(nd2) == numpy.ndarray) + +print("------------------- Testing dparray.from_ndarray") +sys.stdout.flush() +dp1 = dparray.from_ndarray(nd2) +print("dp1:", dp1, type(dp1)) +assert(isinstance(dp1, dparray.ndarray)) +assert(dparray.has_array_interface(dp1)) + +print("------------------- Testing dparray multiplication") +sys.stdout.flush() +c = a * 5 +print("c", c, type(c)) +assert(isinstance(c, dparray.ndarray)) +assert(dparray.has_array_interface(c)) + +print("------------------- Testing Python dparray") +sys.stdout.flush() +b = p1(c) +print("b:", b, type(b)) +assert(isinstance(b, dparray.ndarray)) +assert(dparray.has_array_interface(b)) +del b + +print("------------------- Testing Python mixing dparray and numpy.ndarray") +sys.stdout.flush() +h = p5(a, z1) +print("h:", h, type(h)) +assert(isinstance(h, dparray.ndarray)) +assert(dparray.has_array_interface(h)) +del h + +print("------------------- Testing Numba dparray 2") +sys.stdout.flush() +d = f2(a) +print("d:", d, type(d)) +assert(isinstance(d, dparray.ndarray)) +assert(dparray.has_array_interface(d)) +del d + +print("------------------- Testing Numba dparray") +sys.stdout.flush() +b = f1(c) +print("b:", b, type(b)) +assert(isinstance(b, dparray.ndarray)) +assert(dparray.has_array_interface(b)) +del b + +""" +print("------------------- Testing Numba dparray constructor from numpy.ndarray") +sys.stdout.flush() +e = f3(a, z1) +print("e:", e, type(e)) +assert(isinstance(e, dparray.ndarray)) +""" + +print("------------------- Testing Numba mixing dparray and constant") +sys.stdout.flush() +g = f6(a) +print("g:", g, type(g)) +assert(isinstance(g, dparray.ndarray)) +assert(dparray.has_array_interface(g)) +del g + +print("------------------- Testing Numba mixing dparray and numpy.ndarray") +sys.stdout.flush() +h = f5(a, z1) +print("h:", h, type(h)) +assert(isinstance(h, dparray.ndarray)) +assert(dparray.has_array_interface(h)) +del h + +print("------------------- Testing Numba dparray functions") +sys.stdout.flush() +f = f4() +print("f:", f, type(f)) +assert(isinstance(f, dparray.ndarray)) +assert(dparray.has_array_interface(f)) +del f + +print("------------------- Testing Numba dparray.as_ndarray") +sys.stdout.flush() +nd3 = f8(a) +print("nd3:", nd3, type(nd3)) +assert(type(nd3) == numpy.ndarray) + +print("------------------- Testing Numba dparray.from_ndarray") +sys.stdout.flush() +dp2 = f9(nd3) +print("dp2:", dp2, type(dp2)) +assert(isinstance(dp2, dparray.ndarray)) +assert(dparray.has_array_interface(dp2)) +del nd3 +del dp2 + +print("------------------- Testing Numba dparray.empty") +sys.stdout.flush() +dp3 = f10() +print("dp3:", dp3, type(dp3)) +assert(isinstance(dp3, dparray.ndarray)) +assert(dparray.has_array_interface(dp3)) + +print("------------------- Testing Numba dparray.shape") +sys.stdout.flush() +s1 = f11(dp3) +print("s1:", s1, type(s1)) + +print("------------------- Testing Numba dparray.T") +sys.stdout.flush() +dp4 = f12(dp3) +print("dp4:", dp4, type(dp4)) +assert(isinstance(dp4, dparray.ndarray)) +assert(dparray.has_array_interface(dp4)) +del dp3 +del dp4 + +#------------------------------- +del a + +print("SUCCESS") diff --git a/setup.py b/setup.py index 88a7d62b0ec..2d0acd12bc0 100644 --- a/setup.py +++ b/setup.py @@ -277,6 +277,12 @@ def check_file_at_path(path2file): 'numba/core/runtime/_nrt_python.c'], **np_compile_args) + ext_dppl = Extension(name='numba.dppl._dppl_rt', + sources=['numba/dppl/dppl_rt.c'], + depends=['numba/core/runtime/nrt_external.h', + 'numba/core/runtime/nrt.h'], + ) + ext_jitclass_box = Extension(name='numba.experimental.jitclass._box', sources=['numba/experimental/jitclass/_box.c'], depends=['numba/experimental/_pymodule.h'], @@ -289,7 +295,7 @@ def check_file_at_path(path2file): ext_modules = [ext_dynfunc, ext_dispatcher, ext_helperlib, ext_typeconv, ext_np_ufunc, ext_npyufunc_num_threads, ext_mviewbuf, - ext_nrt_python, ext_jitclass_box, ext_cuda_extras] + ext_nrt_python, ext_dppl, ext_jitclass_box, ext_cuda_extras] ext_modules += ext_np_ufunc_backends From ca10de8dd1df56e28ac4b600b9c8bc1a59e49ff6 Mon Sep 17 00:00:00 2001 From: "Todd A. Anderson" Date: Wed, 18 Nov 2020 17:16:59 -0600 Subject: [PATCH 3/5] Code review changes. --- numba/dppl/dparray.py | 128 ++++-------------------------------------- 1 file changed, 10 insertions(+), 118 deletions(-) diff --git a/numba/dppl/dparray.py b/numba/dppl/dparray.py index a04fe674d4b..2a5df3ddc3e 100644 --- a/numba/dppl/dparray.py +++ b/numba/dppl/dparray.py @@ -1,4 +1,3 @@ -#from ._ndarray_utils import _transmogrify import numpy as np from inspect import getmembers, isfunction, isclass, isbuiltin from numbers import Number @@ -11,7 +10,7 @@ from llvmlite import ir import llvmlite.llvmpy.core as lc import llvmlite.binding as llb -from numba.core import types, cgutils +from numba.core import types, cgutils, config import builtins import sys from ctypes.util import find_library @@ -24,19 +23,17 @@ from numba.core.typing.templates import CallableTemplate from numba.np.arrayobj import _array_copy -debug = False +debug = config.DEBUG def dprint(*args): if debug: print(*args) sys.stdout.flush() -flib = find_library('mkl_intel_ilp64') -dprint("flib:", flib) -llb.load_library_permanently(flib) - +# This code makes it so that Numba can contain calls into the DPPLSyclInterface library. sycl_mem_lib = find_library('DPPLSyclInterface') dprint("sycl_mem_lib:", sycl_mem_lib) +# Load the symbols from the DPPL Sycl library. llb.load_library_permanently(sycl_mem_lib) import dpctl @@ -76,7 +73,7 @@ def __new__(subtype, shape, dprint("buffer None new_obj already has sycl_usm") else: dprint("buffer None new_obj will add sycl_usm") - new_obj.__sycl_usm_array_interface__ = {} + setattr(new_obj, array_interface_property, {}) return new_obj # zero copy if buffer is a usm backed array-like thing elif hasattr(buffer, array_interface_property): @@ -90,7 +87,7 @@ def __new__(subtype, shape, dprint("buffer None new_obj already has sycl_usm") else: dprint("buffer None new_obj will add sycl_usm") - new_obj.__sycl_usm_array_interface__ = {} + setattr(new_obj, array_interface_property, {}) return new_obj else: dprint("dparray::ndarray __new__ buffer not None and not sycl_usm") @@ -110,13 +107,11 @@ def __new__(subtype, shape, dprint("buffer None new_obj already has sycl_usm") else: dprint("buffer None new_obj will add sycl_usm") - new_obj.__sycl_usm_array_interface__ = {} + setattr(new_obj, array_interface_property, {}) return new_obj def __array_finalize__(self, obj): dprint("__array_finalize__:", obj, hex(id(obj)), type(obj)) -# import pdb -# pdb.set_trace() # When called from the explicit constructor, obj is None if obj is None: return # When called in new-from-template, `obj` is another instance of our own @@ -150,29 +145,8 @@ def __array_finalize__(self, obj): # Just raise an exception since __array_ufunc__ makes all reasonable cases not # need the code below. - raise ValueError("Non-MKL allocated ndarray can not viewed as MKL-allocated one without a copy") + raise ValueError("Non-USM allocated ndarray can not viewed as a USM-allocated one without a copy") - """ - # since dparray must have mkl_memory underlying it, a copy must be made - newbuf = dpctl.Memory(nbytes=self.data.nbytes) - new_arr = np.ndarray.__new__( - type(self), - self.shape, - buffer=newbuf, offset=0, - dtype=self.dtype, - strides=self.strides) - np.copyto(new_arr, self) - # We need to modify self to now be mkl_memory-backed ndarray - # We only need to change data and base, but these are not writeable. - # - # Modification can not be done by simply setting self either, - # as self is just a local copy of the instance. - # - # raise ValueError("Non-MKL allocated ndarray can not viewed as MKL-allocated one without a copy") - # Will probably have to raise an exception soon as Numpy may disallow this. - _transmogrify(self, new_arr) - """ - # Tell Numba to not treat this type just like a NumPy ndarray but to propagate its type. # This way it will use the custom dparray allocator. __numba_no_subtype_ndarray__ = True @@ -236,7 +210,7 @@ def isdef(x): try: eval(x) return True - except NameEror: + except NameError: return False for c in class_list: @@ -245,24 +219,11 @@ def isdef(x): continue # For now we do the simple thing and copy the types from NumPy module into dparray module. new_func = "%s = np.%s" % (cname, cname) -# new_func = "class %s(np.%s):\n" % (cname, cname) - if cname == "ndarray": - # Implemented explicitly above. - continue - else: - # This is temporary. -# new_func += " pass\n" - # The code below should eventually be made to work and used. -# new_func += " @classmethod\n" -# new_func += " def cast(cls, some_np_obj):\n" -# new_func += " some_np_obj.__class__ = cls\n" -# new_func += " return some_np_obj\n" - pass try: the_code = compile(new_func, '__init__', 'exec') exec(the_code) except: - print("Failed to exec class", cname) + print("Failed to exec type propagation", cname) pass # Redefine all Numpy functions in this module and if they @@ -272,7 +233,6 @@ def isdef(x): for fname in functions_list: if isdef(fname): continue -# print("Adding function", fname) new_func = "def %s(*args, **kwargs):\n" % fname new_func += " ret = np.%s(*args, **kwargs)\n" % fname new_func += " if type(ret) == np.ndarray:\n" @@ -320,56 +280,6 @@ def typeof_ta_ndarray(val, c): # object of type DPArray. register_model(DPArrayType)(numba.core.datamodel.models.ArrayModel) -""" -# This code should not work because you can't pass arbitrary buffer to dparray constructor. - -# This tells Numba how to type calls to a DPArray constructor. -@type_callable(ndarray) -def type_ndarray(context): - def typer(shape, ndim, buf): - return DPArrayType(buf.dtype, buf.ndim, buf.layout) - return typer - -@overload(ndarray) -def overload_ndarray_constructor(shape, dtype, buf): - print("overload_ndarray_constructor:", shape, dtype, buf) - - def ndarray_impl(shape, dtype, buf): - pass - - return ndarray_impl - -# This tells Numba how to implement calls to a DPArray constructor. -@lower_builtin(ndarray, types.UniTuple, types.DType, types.Array) -def impl_ndarray(context, builder, sig, args): - # Need to allocate and copy here! - shape, ndim, buf = args - return buf - - context.nrt._require_nrt() - - mod = builder.module - u32 = ir.IntType(32) - - # Get the Numba external allocator for USM memory. - ext_allocator_fnty = ir.FunctionType(cgutils.voidptr_t, []) - ext_allocator_fn = mod.get_or_insert_function(ext_allocator_fnty, - name="dparray_get_ext_allocator") - ext_allocator = builder.call(ext_allocator_fn, []) - # Get the Numba function to allocate an aligned array with an external allocator. - fnty = ir.FunctionType(cgutils.voidptr_t, [cgutils.intp_t, u32, cgutils.voidptr_t]) - fn = mod.get_or_insert_function(fnty, - name="NRT_MemInfo_alloc_safe_aligned_external") - fn.return_value.add_attribute("noalias") - if isinstance(align, builtins.int): - align = context.get_constant(types.uint32, align) - else: - assert align.type == u32, "align must be a uint32" - newary = builder.call(fn, [size, align, ext_allocator]) - - return buf -""" - # This tells Numba how to convert from its native representation # of a DPArray in a njit function back to a Python DPArray. @box(DPArrayType) @@ -447,25 +357,18 @@ def numba_register_lower_builtin(): # For all Numpy identifiers that have been registered for typing in Numba... # this registry contains functions, getattrs, setattrs, casts and constants...need to do them all? FIX FIX FIX for ig in lower_registry.functions: -# print("ig:", ig, type(ig), len(ig)) impl, func, types = ig -# print("register lower_builtin:", impl, type(impl), func, type(func), types, type(types)) # If it is a Numpy function... if isinstance(func, ftype): -# print("isfunction:", func.__module__, type(func.__module__)) if func.__module__ == np.__name__: -# print("name:", func.__name__) # If we have overloaded that function in the dparray module (always True right now)... if func.__name__ in functions_list: todo.append(ig) if isinstance(func, bftype): -# print("isbuiltinfunction:", func.__module__, type(func.__module__)) if func.__module__ == np.__name__: -# print("name:", func.__name__) # If we have overloaded that function in the dparray module (always True right now)... if func.__name__ in functions_list: todo.append(ig) -# print("todo_builtin added:", func.__name__) for lg in lower_registry.getattrs: func, attr, types = lg @@ -482,7 +385,6 @@ def numba_register_lower_builtin(): dparray_func = eval(func.__name__) dprint("need to re-register lowerer for dparray", impl, func, types, dparray_func) new_impl = copy_func_for_dparray(impl, cur_mod) -# lower_registry.functions.append((impl, dparray_func, types)) lower_registry.functions.append((new_impl, dparray_func, types)) def argspec_to_string(argspec): @@ -500,10 +402,8 @@ def numba_register_typing(): # For all Numpy identifiers that have been registered for typing in Numba... for ig in typing_registry.globals: val, typ = ig -# print("global typing:", val, type(val), typ, type(typ)) # If it is a Numpy function... if isinstance(val, (ftype, bftype)): -# print("name:", val.__name__, val.__name__ in functions_list) # If we have overloaded that function in the dparray module (always True right now)... if val.__name__ in functions_list: todo.append(ig) @@ -514,12 +414,6 @@ def numba_register_typing(): if tgetattr.key == types.Array: todo_getattr.append(tgetattr) - # This is actuallya no-op now. -# for val, typ in todo_classes: -# print("todo_classes:", val, type(val), typ, type(typ)) -# assert len(typ.templates) == 1 -# dpval = eval(val.__name__) - for val, typ in todo: assert len(typ.templates) == 1 # template is the typing class to invoke generic() upon. @@ -549,9 +443,7 @@ def set_key_original(cls, key, original): cls.original = original def generic_impl(self): -# print("generic_impl", self.__class__.key, self.__class__.original) original_typer = self.__class__.original.generic(self.__class__.original) - #print("original_typer:", original_typer, type(original_typer), self.__class__) ot_argspec = inspect.getfullargspec(original_typer) #print("ot_argspec:", ot_argspec) astr = argspec_to_string(ot_argspec) From 7ec48a208e99bb256413e408c7119115c548e5ee Mon Sep 17 00:00:00 2001 From: Sergey Pokhodenko Date: Tue, 1 Dec 2020 14:38:19 +0300 Subject: [PATCH 4/5] Move to numba-dppy --- numba/core/cpu.py | 2 - numba/dppl/dppl_rt.c | 89 --------------------------- numba/dppl/tests/dppl/test_dparray.py | 54 ---------------- setup.py | 8 +-- 4 files changed, 1 insertion(+), 152 deletions(-) delete mode 100644 numba/dppl/dppl_rt.c diff --git a/numba/core/cpu.py b/numba/core/cpu.py index 409a2f08afe..af9dc8e335b 100644 --- a/numba/core/cpu.py +++ b/numba/core/cpu.py @@ -66,7 +66,6 @@ def load_additional_registries(self): from numba.np import npyimpl from numba.cpython import cmathimpl, mathimpl, printimpl, randomimpl from numba.misc import cffiimpl - from numba.dppl.dparray import numba_register as dparray_register self.install_registry(cmathimpl.registry) self.install_registry(cffiimpl.registry) self.install_registry(mathimpl.registry) @@ -76,7 +75,6 @@ def load_additional_registries(self): # load 3rd party extensions numba.core.entrypoints.init_all() - dparray_register() @property def target_data(self): diff --git a/numba/dppl/dppl_rt.c b/numba/dppl/dppl_rt.c deleted file mode 100644 index 75c05ff5856..00000000000 --- a/numba/dppl/dppl_rt.c +++ /dev/null @@ -1,89 +0,0 @@ -#include "../_pymodule.h" -#include "../core/runtime/nrt_external.h" -#include "assert.h" -#include -#include - -NRT_ExternalAllocator dparray_allocator; - -void dparray_memsys_init(void) { - void *(*get_queue)(void); - char *lib_name = "libDPPLSyclInterface.so"; - char *malloc_name = "DPPLmalloc_shared"; - char *free_name = "DPPLfree_with_queue"; - char *get_queue_name = "DPPLQueueMgr_GetCurrentQueue"; - - void *sycldl = dlopen(lib_name, RTLD_NOW); - assert(sycldl != NULL); - dparray_allocator.malloc = (NRT_external_malloc_func)dlsym(sycldl, malloc_name); - if (dparray_allocator.malloc == NULL) { - printf("Did not find %s in %s\n", malloc_name, lib_name); - exit(-1); - } - dparray_allocator.realloc = NULL; - dparray_allocator.free = (NRT_external_free_func)dlsym(sycldl, free_name); - if (dparray_allocator.free == NULL) { - printf("Did not find %s in %s\n", free_name, lib_name); - exit(-1); - } - get_queue = (void *(*))dlsym(sycldl, get_queue_name); - if (get_queue == NULL) { - printf("Did not find %s in %s\n", get_queue_name, lib_name); - exit(-1); - } - dparray_allocator.opaque_data = get_queue(); -// printf("dparray_memsys_init: %p %p %p\n", dparray_allocator.malloc, dparray_allocator.free, dparray_allocator.opaque_data); -} - -void * dparray_get_ext_allocator(void) { - printf("dparray_get_ext_allocator %p\n", &dparray_allocator); - return (void*)&dparray_allocator; -} - -static PyObject * -get_external_allocator(PyObject *self, PyObject *args) { - return PyLong_FromVoidPtr(dparray_get_ext_allocator()); -} - -static PyMethodDef ext_methods[] = { -#define declmethod_noargs(func) { #func , ( PyCFunction )func , METH_NOARGS, NULL } - declmethod_noargs(get_external_allocator), - {NULL}, -#undef declmethod_noargs -}; - -static PyObject * -build_c_helpers_dict(void) -{ - PyObject *dct = PyDict_New(); - if (dct == NULL) - goto error; - -#define _declpointer(name, value) do { \ - PyObject *o = PyLong_FromVoidPtr(value); \ - if (o == NULL) goto error; \ - if (PyDict_SetItemString(dct, name, o)) { \ - Py_DECREF(o); \ - goto error; \ - } \ - Py_DECREF(o); \ -} while (0) - - _declpointer("dparray_get_ext_allocator", &dparray_get_ext_allocator); - -#undef _declpointer - return dct; -error: - Py_XDECREF(dct); - return NULL; -} - -MOD_INIT(_dppl_rt) { - PyObject *m; - MOD_DEF(m, "numba.dppl._dppl_rt", "No docs", ext_methods) - if (m == NULL) - return MOD_ERROR_VAL; - dparray_memsys_init(); - PyModule_AddObject(m, "c_helpers", build_c_helpers_dict()); - return MOD_SUCCESS_VAL(m); -} diff --git a/numba/dppl/tests/dppl/test_dparray.py b/numba/dppl/tests/dppl/test_dparray.py index 5ed1ad3d339..cf60829567a 100644 --- a/numba/dppl/tests/dppl/test_dparray.py +++ b/numba/dppl/tests/dppl/test_dparray.py @@ -1,63 +1,9 @@ from __future__ import print_function, division, absolute_import -import numba import numba.dppl.dparray as dparray import numpy import sys -def p1(a): - return a * 2.0 + 13 - -f1 = numba.njit(p1) - -@numba.njit() -def f2(a): - return a - -@numba.njit() -def f3(a, b): # a is dparray, b is numpy - return a * dparray.asarray(b) - -@numba.njit() -def f4(): - return dparray.ones(10) - -def p5(a, b): # a is dparray, b is numpy - return a * b - -f5 = numba.njit(p5) - -@numba.njit() -def f6(a): - return a + 13 - -@numba.njit() -def f7(a): # a is dparray - # implicit conversion of a to numpy.ndarray - b = numpy.ones(10) - c = a * b - d = a.argsort() # with no implicit conversion this fails - -@numba.njit -def f8(a): - return dparray.as_ndarray(a) - -@numba.njit -def f9(a): - return dparray.from_ndarray(a) - -@numba.njit -def f10(): - return dparray.empty((10,10)) - -@numba.njit -def f11(x): - return x.shape - -@numba.njit -def f12(x): - return x.T - #-------------------------------------------------------------------------------- print("------------------- Testing Python Numpy") diff --git a/setup.py b/setup.py index 2d0acd12bc0..88a7d62b0ec 100644 --- a/setup.py +++ b/setup.py @@ -277,12 +277,6 @@ def check_file_at_path(path2file): 'numba/core/runtime/_nrt_python.c'], **np_compile_args) - ext_dppl = Extension(name='numba.dppl._dppl_rt', - sources=['numba/dppl/dppl_rt.c'], - depends=['numba/core/runtime/nrt_external.h', - 'numba/core/runtime/nrt.h'], - ) - ext_jitclass_box = Extension(name='numba.experimental.jitclass._box', sources=['numba/experimental/jitclass/_box.c'], depends=['numba/experimental/_pymodule.h'], @@ -295,7 +289,7 @@ def check_file_at_path(path2file): ext_modules = [ext_dynfunc, ext_dispatcher, ext_helperlib, ext_typeconv, ext_np_ufunc, ext_npyufunc_num_threads, ext_mviewbuf, - ext_nrt_python, ext_dppl, ext_jitclass_box, ext_cuda_extras] + ext_nrt_python, ext_jitclass_box, ext_cuda_extras] ext_modules += ext_np_ufunc_backends From 7fcf540d181704ba4d6c5ca33ca173b7c8ebb524 Mon Sep 17 00:00:00 2001 From: Sergey Pokhodenko Date: Tue, 1 Dec 2020 14:42:18 +0300 Subject: [PATCH 5/5] Move to dpctl --- numba/dppl/dparray.py | 552 -------------------------- numba/dppl/tests/dppl/test_dparray.py | 159 -------- 2 files changed, 711 deletions(-) delete mode 100644 numba/dppl/dparray.py delete mode 100644 numba/dppl/tests/dppl/test_dparray.py diff --git a/numba/dppl/dparray.py b/numba/dppl/dparray.py deleted file mode 100644 index 2a5df3ddc3e..00000000000 --- a/numba/dppl/dparray.py +++ /dev/null @@ -1,552 +0,0 @@ -import numpy as np -from inspect import getmembers, isfunction, isclass, isbuiltin -from numbers import Number -import numba -from types import FunctionType as ftype, BuiltinFunctionType as bftype -from numba import types -from numba.extending import typeof_impl, register_model, type_callable, lower_builtin -from numba.np import numpy_support -from numba.core.pythonapi import box, allocator -from llvmlite import ir -import llvmlite.llvmpy.core as lc -import llvmlite.binding as llb -from numba.core import types, cgutils, config -import builtins -import sys -from ctypes.util import find_library -from numba.core.typing.templates import builtin_registry as templates_registry -from numba.core.typing.npydecl import registry as typing_registry -from numba.core.imputils import builtin_registry as lower_registry -import importlib -import functools -import inspect -from numba.core.typing.templates import CallableTemplate -from numba.np.arrayobj import _array_copy - -debug = config.DEBUG - -def dprint(*args): - if debug: - print(*args) - sys.stdout.flush() - -# This code makes it so that Numba can contain calls into the DPPLSyclInterface library. -sycl_mem_lib = find_library('DPPLSyclInterface') -dprint("sycl_mem_lib:", sycl_mem_lib) -# Load the symbols from the DPPL Sycl library. -llb.load_library_permanently(sycl_mem_lib) - -import dpctl -from dpctl._memory import MemoryUSMShared -import numba.dppl._dppl_rt - -functions_list = [o[0] for o in getmembers(np) if isfunction(o[1]) or isbuiltin(o[1])] -class_list = [o for o in getmembers(np) if isclass(o[1])] -# Register the helper function in dppl_rt so that we can insert calls to them via llvmlite. -for py_name, c_address in numba.dppl._dppl_rt.c_helpers.items(): - llb.add_symbol(py_name, c_address) - -array_interface_property = "__array_interface__" -def has_array_interface(x): - return hasattr(x, array_interface_property) - -class ndarray(np.ndarray): - """ - numpy.ndarray subclass whose underlying memory buffer is allocated - with a foreign allocator. - """ - def __new__(subtype, shape, - dtype=float, buffer=None, offset=0, - strides=None, order=None): - # Create a new array. - if buffer is None: - dprint("dparray::ndarray __new__ buffer None") - nelems = np.prod(shape) - dt = np.dtype(dtype) - isz = dt.itemsize - buf = MemoryUSMShared(nbytes=isz*max(1,nelems)) - new_obj = np.ndarray.__new__( - subtype, shape, dtype=dt, - buffer=buf, offset=0, - strides=strides, order=order) - if hasattr(new_obj, array_interface_property): - dprint("buffer None new_obj already has sycl_usm") - else: - dprint("buffer None new_obj will add sycl_usm") - setattr(new_obj, array_interface_property, {}) - return new_obj - # zero copy if buffer is a usm backed array-like thing - elif hasattr(buffer, array_interface_property): - dprint("dparray::ndarray __new__ buffer", array_interface_property) - # also check for array interface - new_obj = np.ndarray.__new__( - subtype, shape, dtype=dtype, - buffer=buffer, offset=offset, - strides=strides, order=order) - if hasattr(new_obj, array_interface_property): - dprint("buffer None new_obj already has sycl_usm") - else: - dprint("buffer None new_obj will add sycl_usm") - setattr(new_obj, array_interface_property, {}) - return new_obj - else: - dprint("dparray::ndarray __new__ buffer not None and not sycl_usm") - nelems = np.prod(shape) - # must copy - ar = np.ndarray(shape, - dtype=dtype, buffer=buffer, - offset=offset, strides=strides, - order=order) - buf = MemoryUSMShared(nbytes=ar.nbytes) - new_obj = np.ndarray.__new__( - subtype, shape, dtype=dtype, - buffer=buf, offset=0, - strides=strides, order=order) - np.copyto(new_obj, ar, casting='no') - if hasattr(new_obj, array_interface_property): - dprint("buffer None new_obj already has sycl_usm") - else: - dprint("buffer None new_obj will add sycl_usm") - setattr(new_obj, array_interface_property, {}) - return new_obj - - def __array_finalize__(self, obj): - dprint("__array_finalize__:", obj, hex(id(obj)), type(obj)) - # When called from the explicit constructor, obj is None - if obj is None: return - # When called in new-from-template, `obj` is another instance of our own - # subclass, that we might use to update the new `self` instance. - # However, when called from view casting, `obj` can be an instance of any - # subclass of ndarray, including our own. - if hasattr(obj, array_interface_property): - return - if isinstance(obj, numba.core.runtime._nrt_python._MemInfo): - mobj = obj - while isinstance(mobj, numba.core.runtime._nrt_python._MemInfo): - dprint("array_finalize got Numba MemInfo") - ea = mobj.external_allocator - d = mobj.data - dprint("external_allocator:", hex(ea), type(ea)) - dprint("data:", hex(d), type(d)) - dppl_rt_allocator = numba.dppl._dppl_rt.get_external_allocator() - dprint("dppl external_allocator:", hex(dppl_rt_allocator), type(dppl_rt_allocator)) - dprint(dir(mobj)) - if ea == dppl_rt_allocator: - return - mobj = mobj.parent - if isinstance(mobj, ndarray): - mobj = mobj.base - if isinstance(obj, np.ndarray): - ob = self - while isinstance(ob, np.ndarray): - if hasattr(obj, array_interface_property): - return - ob = ob.base - - # Just raise an exception since __array_ufunc__ makes all reasonable cases not - # need the code below. - raise ValueError("Non-USM allocated ndarray can not viewed as a USM-allocated one without a copy") - - # Tell Numba to not treat this type just like a NumPy ndarray but to propagate its type. - # This way it will use the custom dparray allocator. - __numba_no_subtype_ndarray__ = True - - # Convert to a NumPy ndarray. - def as_ndarray(self): - return np.copy(self) - - def __array__(self): - return self - - def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): - if method == '__call__': - N = None - scalars = [] - typing = [] - for inp in inputs: - if isinstance(inp, Number): - scalars.append(inp) - typing.append(inp) - elif isinstance(inp, (self.__class__, np.ndarray)): - if isinstance(inp, self.__class__): - scalars.append(np.ndarray(inp.shape, inp.dtype, inp)) - typing.append(np.ndarray(inp.shape, inp.dtype)) - else: - scalars.append(inp) - typing.append(inp) - if N is not None: - if N != inp.shape: - raise TypeError("inconsistent sizes") - else: - N = inp.shape - else: - return NotImplemented - # Have to avoid recursive calls to array_ufunc here. - # If no out kwarg then we create a dparray out so that we get - # USM memory. However, if kwarg has dparray-typed out then - # array_ufunc is called recursively so we cast out as regular - # NumPy ndarray (having a USM data pointer). - if kwargs.get('out', None) is None: - # maybe copy? - # deal with multiple returned arrays, so kwargs['out'] can be tuple - res_type = np.result_type(*typing) - out = empty(inputs[0].shape, dtype=res_type) - out_as_np = np.ndarray(out.shape, out.dtype, out) - kwargs['out'] = out_as_np - else: - # If they manually gave dparray as out kwarg then we have to also - # cast as regular NumPy ndarray to avoid recursion. - if isinstance(kwargs['out'], ndarray): - out = kwargs['out'] - kwargs['out'] = np.ndarray(out.shape, out.dtype, out) - else: - out = kwargs['out'] - ret = ufunc(*scalars, **kwargs) - return out - else: - return NotImplemented - -def isdef(x): - try: - eval(x) - return True - except NameError: - return False - -for c in class_list: - cname = c[0] - if isdef(cname): - continue - # For now we do the simple thing and copy the types from NumPy module into dparray module. - new_func = "%s = np.%s" % (cname, cname) - try: - the_code = compile(new_func, '__init__', 'exec') - exec(the_code) - except: - print("Failed to exec type propagation", cname) - pass - -# Redefine all Numpy functions in this module and if they -# return a Numpy array, transform that to a USM-backed array -# instead. This is a stop-gap. We should eventually find a -# way to do the allocation correct to start with. -for fname in functions_list: - if isdef(fname): - continue - new_func = "def %s(*args, **kwargs):\n" % fname - new_func += " ret = np.%s(*args, **kwargs)\n" % fname - new_func += " if type(ret) == np.ndarray:\n" - new_func += " ret = ndarray(ret.shape, ret.dtype, ret)\n" - new_func += " return ret\n" - the_code = compile(new_func, '__init__', 'exec') - exec(the_code) - -# This class creates a type in Numba. -class DPArrayType(types.Array): - def __init__(self, dtype, ndim, layout, readonly=False, name=None, - aligned=True, addrspace=None): - # This name defines how this type will be shown in Numba's type dumps. - name = "DPArray:ndarray(%s, %sd, %s)" % (dtype, ndim, layout) - super(DPArrayType, self).__init__(dtype, ndim, layout, - py_type=ndarray, - readonly=readonly, - name=name, - addrspace=addrspace) - - # Tell Numba typing how to combine DPArrayType with other ndarray types. - def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): - if method == '__call__': - for inp in inputs: - if not isinstance(inp, (DPArrayType, types.Array, types.Number)): - return None - - return DPArrayType - else: - return None - -# This tells Numba how to create a DPArrayType when a dparray is passed -# into a njit function. -@typeof_impl.register(ndarray) -def typeof_ta_ndarray(val, c): - try: - dtype = numpy_support.from_dtype(val.dtype) - except NotImplementedError: - raise ValueError("Unsupported array dtype: %s" % (val.dtype,)) - layout = numpy_support.map_layout(val) - readonly = not val.flags.writeable - return DPArrayType(dtype, val.ndim, layout, readonly=readonly) - -# This tells Numba to use the default Numpy ndarray data layout for -# object of type DPArray. -register_model(DPArrayType)(numba.core.datamodel.models.ArrayModel) - -# This tells Numba how to convert from its native representation -# of a DPArray in a njit function back to a Python DPArray. -@box(DPArrayType) -def box_array(typ, val, c): - nativearycls = c.context.make_array(typ) - nativeary = nativearycls(c.context, c.builder, value=val) - if c.context.enable_nrt: - np_dtype = numpy_support.as_dtype(typ.dtype) - dtypeptr = c.env_manager.read_const(c.env_manager.add_const(np_dtype)) - # Steals NRT ref - newary = c.pyapi.nrt_adapt_ndarray_to_python(typ, val, dtypeptr) - return newary - else: - parent = nativeary.parent - c.pyapi.incref(parent) - return parent - -# This tells Numba to use this function when it needs to allocate a -# DPArray in a njit function. -@allocator(DPArrayType) -def allocator_DPArray(context, builder, size, align): - context.nrt._require_nrt() - - mod = builder.module - u32 = ir.IntType(32) - - # Get the Numba external allocator for USM memory. - ext_allocator_fnty = ir.FunctionType(cgutils.voidptr_t, []) - ext_allocator_fn = mod.get_or_insert_function(ext_allocator_fnty, - name="dparray_get_ext_allocator") - ext_allocator = builder.call(ext_allocator_fn, []) - # Get the Numba function to allocate an aligned array with an external allocator. - fnty = ir.FunctionType(cgutils.voidptr_t, [cgutils.intp_t, u32, cgutils.voidptr_t]) - fn = mod.get_or_insert_function(fnty, - name="NRT_MemInfo_alloc_safe_aligned_external") - fn.return_value.add_attribute("noalias") - if isinstance(align, builtins.int): - align = context.get_constant(types.uint32, align) - else: - assert align.type == u32, "align must be a uint32" - return builder.call(fn, [size, align, ext_allocator]) - -registered = False - -def numba_register(): - global registered - if not registered: - registered = True - numba_register_typing() - numba_register_lower_builtin() - -# Copy a function registered as a lowerer in Numba but change the -# "np" import in Numba to point to dparray instead of NumPy. -def copy_func_for_dparray(f, dparray_mod): - import copy as cc - # Make a copy so our change below doesn't affect anything else. - gglobals = cc.copy(f.__globals__) - # Make the "np"'s in the code use dparray instead of Numba's default NumPy. - gglobals['np'] = dparray_mod - # Create a new function using the original code but the new globals. - g = ftype(f.__code__, gglobals, None, f.__defaults__, f.__closure__) - # Some other tricks to make sure the function copy works. - g = functools.update_wrapper(g, f) - g.__kwdefaults__ = f.__kwdefaults__ - return g - -def types_replace_array(x): - return tuple([z if z != types.Array else DPArrayType for z in x]) - -def numba_register_lower_builtin(): - todo = [] - todo_builtin = [] - todo_getattr = [] - - # For all Numpy identifiers that have been registered for typing in Numba... - # this registry contains functions, getattrs, setattrs, casts and constants...need to do them all? FIX FIX FIX - for ig in lower_registry.functions: - impl, func, types = ig - # If it is a Numpy function... - if isinstance(func, ftype): - if func.__module__ == np.__name__: - # If we have overloaded that function in the dparray module (always True right now)... - if func.__name__ in functions_list: - todo.append(ig) - if isinstance(func, bftype): - if func.__module__ == np.__name__: - # If we have overloaded that function in the dparray module (always True right now)... - if func.__name__ in functions_list: - todo.append(ig) - - for lg in lower_registry.getattrs: - func, attr, types = lg - types_with_dparray = types_replace_array(types) - if DPArrayType in types_with_dparray: - dprint("lower_getattr:", func, type(func), attr, type(attr), types, type(types)) - todo_getattr.append((func, attr, types_with_dparray)) - - for lg in todo_getattr: - lower_registry.getattrs.append(lg) - - cur_mod = importlib.import_module(__name__) - for impl, func, types in (todo+todo_builtin): - dparray_func = eval(func.__name__) - dprint("need to re-register lowerer for dparray", impl, func, types, dparray_func) - new_impl = copy_func_for_dparray(impl, cur_mod) - lower_registry.functions.append((new_impl, dparray_func, types)) - -def argspec_to_string(argspec): - first_default_arg = len(argspec.args)-len(argspec.defaults) - non_def = argspec.args[:first_default_arg] - arg_zip = list(zip(argspec.args[first_default_arg:], argspec.defaults)) - combined = [a+"="+str(b) for a,b in arg_zip] - return ",".join(non_def + combined) - -def numba_register_typing(): - todo = [] - todo_classes = [] - todo_getattr = [] - - # For all Numpy identifiers that have been registered for typing in Numba... - for ig in typing_registry.globals: - val, typ = ig - # If it is a Numpy function... - if isinstance(val, (ftype, bftype)): - # If we have overloaded that function in the dparray module (always True right now)... - if val.__name__ in functions_list: - todo.append(ig) - if isinstance(val, type): - todo_classes.append(ig) - - for tgetattr in templates_registry.attributes: - if tgetattr.key == types.Array: - todo_getattr.append(tgetattr) - - for val, typ in todo: - assert len(typ.templates) == 1 - # template is the typing class to invoke generic() upon. - template = typ.templates[0] - dpval = eval(val.__name__) - dprint("need to re-register for dparray", val, typ, typ.typing_key) - """ - if debug: - print("--------------------------------------------------------------") - print("need to re-register for dparray", val, typ, typ.typing_key) - print("val:", val, type(val), "dir val", dir(val)) - print("typ:", typ, type(typ), "dir typ", dir(typ)) - print("typing key:", typ.typing_key) - print("name:", typ.name) - print("key:", typ.key) - print("templates:", typ.templates) - print("template:", template, type(template)) - print("dpval:", dpval, type(dpval)) - print("--------------------------------------------------------------") - """ - - class_name = "DparrayTemplate_" + val.__name__ - - @classmethod - def set_key_original(cls, key, original): - cls.key = key - cls.original = original - - def generic_impl(self): - original_typer = self.__class__.original.generic(self.__class__.original) - ot_argspec = inspect.getfullargspec(original_typer) - #print("ot_argspec:", ot_argspec) - astr = argspec_to_string(ot_argspec) - #print("astr:", astr) - - typer_func = """def typer({}): - original_res = original_typer({}) - #print("original_res:", original_res) - if isinstance(original_res, types.Array): - return DPArrayType(dtype=original_res.dtype, ndim=original_res.ndim, layout=original_res.layout) - - return original_res""".format(astr, ",".join(ot_argspec.args)) - - #print("typer_func:", typer_func) - - try: - gs = globals() - ls = locals() - gs["original_typer"] = ls["original_typer"] - exec(typer_func, globals(), locals()) - except NameError as ne: - print("NameError in exec:", ne) - sys.exit(0) - except: - print("exec failed!", sys.exc_info()[0]) - sys.exit(0) - - try: - exec_res = eval("typer") - except NameError as ne: - print("NameError in eval:", ne) - sys.exit(0) - except: - print("eval failed!", sys.exc_info()[0]) - sys.exit(0) - - #print("exec_res:", exec_res) - return exec_res - - new_dparray_template = type(class_name, (template,), { - "set_class_vars" : set_key_original, - "generic" : generic_impl}) - - new_dparray_template.set_class_vars(dpval, template) - - assert(callable(dpval)) - type_handler = types.Function(new_dparray_template) - typing_registry.register_global(dpval, type_handler) - - # Handle dparray attribute typing. - for tgetattr in todo_getattr: - class_name = tgetattr.__name__ + "_dparray" - dprint("tgetattr:", tgetattr, type(tgetattr), class_name) - - @classmethod - def set_key(cls, key): - cls.key = key - - def getattr_impl(self, attr): - if attr.startswith('resolve_'): - #print("getattr_impl starts with resolve_:", self, type(self), attr) - def wrapper(*args, **kwargs): - attr_res = tgetattr.__getattribute__(self, attr)(*args, **kwargs) - if isinstance(attr_res, types.Array): - return DPArrayType(dtype=attr_res.dtype, ndim=attr_res.ndim, layout=attr_res.layout) - return wrapper - else: - return tgetattr.__getattribute__(self, attr) - - new_dparray_template = type(class_name, (tgetattr,), { - "set_class_vars" : set_key, - "__getattribute__" : getattr_impl}) - - new_dparray_template.set_class_vars(DPArrayType) - templates_registry.register_attr(new_dparray_template) - - -def from_ndarray(x): - return copy(x) - -def as_ndarray(x): - return np.copy(x) - -@typing_registry.register_global(as_ndarray) -class DparrayAsNdarray(CallableTemplate): - def generic(self): - def typer(arg): - return types.Array(dtype=arg.dtype, ndim=arg.ndim, layout=arg.layout) - - return typer - -@typing_registry.register_global(from_ndarray) -class DparrayFromNdarray(CallableTemplate): - def generic(self): - def typer(arg): - return DPArrayType(dtype=arg.dtype, ndim=arg.ndim, layout=arg.layout) - - return typer - -@lower_registry.lower(as_ndarray, DPArrayType) -def dparray_conversion_as(context, builder, sig, args): - return _array_copy(context, builder, sig, args) - -@lower_registry.lower(from_ndarray, types.Array) -def dparray_conversion_from(context, builder, sig, args): - return _array_copy(context, builder, sig, args) diff --git a/numba/dppl/tests/dppl/test_dparray.py b/numba/dppl/tests/dppl/test_dparray.py deleted file mode 100644 index cf60829567a..00000000000 --- a/numba/dppl/tests/dppl/test_dparray.py +++ /dev/null @@ -1,159 +0,0 @@ -from __future__ import print_function, division, absolute_import - -import numba.dppl.dparray as dparray -import numpy -import sys - -#-------------------------------------------------------------------------------- - -print("------------------- Testing Python Numpy") -sys.stdout.flush() -z1 = numpy.ones(10) -z2 = p1(z1) -print("z2:", z2, type(z2)) -assert(type(z2) == numpy.ndarray) - -print("------------------- Testing Numba Numpy") -sys.stdout.flush() -z1 = numpy.ones(10) -z2 = f1(z1) -print("z2:", z2, type(z2)) -assert(type(z2) == numpy.ndarray) - -print("------------------- Testing dparray ones") -sys.stdout.flush() -a = dparray.ones(10) -print("a:", a, type(a)) -assert(isinstance(a, dparray.ndarray)) -assert(dparray.has_array_interface(a)) - -print("------------------- Testing dparray.dparray.as_ndarray") -sys.stdout.flush() -nd1 = a.as_ndarray() -print("nd1:", nd1, type(nd1)) -assert(type(nd1) == numpy.ndarray) - -print("------------------- Testing dparray.as_ndarray") -sys.stdout.flush() -nd2 = dparray.as_ndarray(a) -print("nd2:", nd2, type(nd2)) -assert(type(nd2) == numpy.ndarray) - -print("------------------- Testing dparray.from_ndarray") -sys.stdout.flush() -dp1 = dparray.from_ndarray(nd2) -print("dp1:", dp1, type(dp1)) -assert(isinstance(dp1, dparray.ndarray)) -assert(dparray.has_array_interface(dp1)) - -print("------------------- Testing dparray multiplication") -sys.stdout.flush() -c = a * 5 -print("c", c, type(c)) -assert(isinstance(c, dparray.ndarray)) -assert(dparray.has_array_interface(c)) - -print("------------------- Testing Python dparray") -sys.stdout.flush() -b = p1(c) -print("b:", b, type(b)) -assert(isinstance(b, dparray.ndarray)) -assert(dparray.has_array_interface(b)) -del b - -print("------------------- Testing Python mixing dparray and numpy.ndarray") -sys.stdout.flush() -h = p5(a, z1) -print("h:", h, type(h)) -assert(isinstance(h, dparray.ndarray)) -assert(dparray.has_array_interface(h)) -del h - -print("------------------- Testing Numba dparray 2") -sys.stdout.flush() -d = f2(a) -print("d:", d, type(d)) -assert(isinstance(d, dparray.ndarray)) -assert(dparray.has_array_interface(d)) -del d - -print("------------------- Testing Numba dparray") -sys.stdout.flush() -b = f1(c) -print("b:", b, type(b)) -assert(isinstance(b, dparray.ndarray)) -assert(dparray.has_array_interface(b)) -del b - -""" -print("------------------- Testing Numba dparray constructor from numpy.ndarray") -sys.stdout.flush() -e = f3(a, z1) -print("e:", e, type(e)) -assert(isinstance(e, dparray.ndarray)) -""" - -print("------------------- Testing Numba mixing dparray and constant") -sys.stdout.flush() -g = f6(a) -print("g:", g, type(g)) -assert(isinstance(g, dparray.ndarray)) -assert(dparray.has_array_interface(g)) -del g - -print("------------------- Testing Numba mixing dparray and numpy.ndarray") -sys.stdout.flush() -h = f5(a, z1) -print("h:", h, type(h)) -assert(isinstance(h, dparray.ndarray)) -assert(dparray.has_array_interface(h)) -del h - -print("------------------- Testing Numba dparray functions") -sys.stdout.flush() -f = f4() -print("f:", f, type(f)) -assert(isinstance(f, dparray.ndarray)) -assert(dparray.has_array_interface(f)) -del f - -print("------------------- Testing Numba dparray.as_ndarray") -sys.stdout.flush() -nd3 = f8(a) -print("nd3:", nd3, type(nd3)) -assert(type(nd3) == numpy.ndarray) - -print("------------------- Testing Numba dparray.from_ndarray") -sys.stdout.flush() -dp2 = f9(nd3) -print("dp2:", dp2, type(dp2)) -assert(isinstance(dp2, dparray.ndarray)) -assert(dparray.has_array_interface(dp2)) -del nd3 -del dp2 - -print("------------------- Testing Numba dparray.empty") -sys.stdout.flush() -dp3 = f10() -print("dp3:", dp3, type(dp3)) -assert(isinstance(dp3, dparray.ndarray)) -assert(dparray.has_array_interface(dp3)) - -print("------------------- Testing Numba dparray.shape") -sys.stdout.flush() -s1 = f11(dp3) -print("s1:", s1, type(s1)) - -print("------------------- Testing Numba dparray.T") -sys.stdout.flush() -dp4 = f12(dp3) -print("dp4:", dp4, type(dp4)) -assert(isinstance(dp4, dparray.ndarray)) -assert(dparray.has_array_interface(dp4)) -del dp3 -del dp4 - -#------------------------------- -del a - -print("SUCCESS")