Skip to content

Commit c83f379

Browse files
Vendor in _helperlib cext for CUDA-specific changes (#512)
This PR vendors in the necessary functionality of `_helperlib` C extention from Numba for CUDA-specific changes and expansion in the future. This PR also removes the dependency on `_dynfunc` C extension as it is not needed for the CUDA target. Co-authored-by: Graham Markall <[email protected]>
1 parent 1c04a80 commit c83f379

File tree

8 files changed

+174
-32
lines changed

8 files changed

+174
-32
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: BSD-2-Clause
3+
4+
/*
5+
* Helper functions used by Numba CUDA at runtime.
6+
* This C file is meant to be included after defining the
7+
* NUMBA_EXPORT_FUNC() and NUMBA_EXPORT_DATA() macros.
8+
*/
9+
10+
#include "_pymodule.h"
11+
#include <stddef.h>
12+
13+
/*
14+
* Unicode helpers
15+
*/
16+
17+
/* Developer note:
18+
*
19+
* The hash value of unicode objects is obtained via:
20+
* ((PyASCIIObject *)(obj))->hash;
21+
* The use comes from this definition:
22+
* https://github.com/python/cpython/blob/6d43f6f081023b680d9db4542d19b9e382149f0a/Objects/unicodeobject.c#L119-L120
23+
* and it's used extensively throughout the `cpython/Object/unicodeobject.c`
24+
* source, not least in `unicode_hash` itself:
25+
* https://github.com/python/cpython/blob/6d43f6f081023b680d9db4542d19b9e382149f0a/Objects/unicodeobject.c#L11662-L11679
26+
*
27+
* The Unicode string struct layouts are described here:
28+
* https://github.com/python/cpython/blob/6d43f6f081023b680d9db4542d19b9e382149f0a/Include/cpython/unicodeobject.h#L82-L161
29+
* essentially, all the unicode string layouts start with a `PyASCIIObject` at
30+
* offset 0 (as of commit 6d43f6f081023b680d9db4542d19b9e382149f0a, somewhere
31+
* in the 3.8 development cycle).
32+
*
33+
* For safety against future CPython internal changes, the code checks that the
34+
* _base members of the unicode structs are what is expected in 3.7, and that
35+
* their offset is 0. It then walks the struct to the hash location to make sure
36+
* the offset is indeed the same as PyASCIIObject->hash.
37+
* Note: The large condition in the if should evaluate to a compile time
38+
* constant.
39+
*/
40+
41+
#define MEMBER_SIZE(structure, member) sizeof(((structure *)0)->member)
42+
43+
NUMBA_EXPORT_FUNC(void *)
44+
numba_extract_unicode(PyObject *obj, Py_ssize_t *length, int *kind,
45+
unsigned int *ascii, Py_ssize_t *hash) {
46+
if (!PyUnicode_READY(obj)) {
47+
*length = PyUnicode_GET_LENGTH(obj);
48+
*kind = PyUnicode_KIND(obj);
49+
/* could also use PyUnicode_IS_ASCII but it is not publicly advertised in https://docs.python.org/3/c-api/unicode.html */
50+
*ascii = (unsigned int)(PyUnicode_MAX_CHAR_VALUE(obj) == (0x7f));
51+
/* this is here as a crude check for safe casting of all unicode string
52+
* structs to a PyASCIIObject */
53+
if (MEMBER_SIZE(PyCompactUnicodeObject, _base) == sizeof(PyASCIIObject) &&
54+
MEMBER_SIZE(PyUnicodeObject, _base) == sizeof(PyCompactUnicodeObject) &&
55+
offsetof(PyCompactUnicodeObject, _base) == 0 &&
56+
offsetof(PyUnicodeObject, _base) == 0 &&
57+
offsetof(PyCompactUnicodeObject, _base.hash) == offsetof(PyASCIIObject, hash) &&
58+
offsetof(PyUnicodeObject, _base._base.hash) == offsetof(PyASCIIObject, hash)
59+
) {
60+
/* Grab the hash from the type object cache, do not compute it. */
61+
*hash = ((PyASCIIObject *)(obj))->hash;
62+
}
63+
else {
64+
/* cast is not safe, fail */
65+
return NULL;
66+
}
67+
return PyUnicode_DATA(obj);
68+
} else {
69+
return NULL;
70+
}
71+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: BSD-2-Clause
3+
4+
/*
5+
* Expose all functions as pointers in a dedicated C extension.
6+
*/
7+
8+
/* Import _pymodule.h first, for a recent _POSIX_C_SOURCE */
9+
#include "_pymodule.h"
10+
11+
/* Visibility control macros */
12+
#if defined(_WIN32) || defined(_WIN64)
13+
#define VISIBILITY_HIDDEN
14+
#define VISIBILITY_GLOBAL __declspec(dllexport)
15+
#else
16+
#define VISIBILITY_HIDDEN __attribute__((visibility("hidden")))
17+
#define VISIBILITY_GLOBAL __attribute__((visibility("default")))
18+
#endif
19+
20+
/* Define all runtime-required symbols in this C module, but do not
21+
export them outside the shared library if possible. */
22+
#define NUMBA_EXPORT_FUNC(_rettype) VISIBILITY_HIDDEN _rettype
23+
#define NUMBA_EXPORT_DATA(_vartype) VISIBILITY_HIDDEN _vartype
24+
25+
/* Numba CUDA C helpers */
26+
#include "_helperlib.c"
27+
28+
static PyObject *
29+
build_c_helpers_dict(void)
30+
{
31+
PyObject *dct = PyDict_New();
32+
if (dct == NULL)
33+
goto error;
34+
35+
#define _declpointer(name, value) do { \
36+
PyObject *o = PyLong_FromVoidPtr(value); \
37+
if (o == NULL) goto error; \
38+
if (PyDict_SetItemString(dct, name, o)) { \
39+
Py_DECREF(o); \
40+
goto error; \
41+
} \
42+
Py_DECREF(o); \
43+
} while (0)
44+
45+
#define declmethod(func) _declpointer(#func, &numba_##func)
46+
47+
/* Unicode string support */
48+
declmethod(extract_unicode);
49+
50+
#undef declmethod
51+
return dct;
52+
error:
53+
Py_XDECREF(dct);
54+
return NULL;
55+
}
56+
57+
static PyMethodDef ext_methods[] = {
58+
{ NULL },
59+
};
60+
61+
MOD_INIT(_helperlib) {
62+
PyObject *m;
63+
MOD_DEF(m, "_helperlib", "No docs", ext_methods)
64+
if (m == NULL)
65+
return MOD_ERROR_VAL;
66+
67+
PyModule_AddObject(m, "c_helpers", build_c_helpers_dict());
68+
PyModule_AddIntConstant(m, "long_min", LONG_MIN);
69+
PyModule_AddIntConstant(m, "long_max", LONG_MAX);
70+
PyModule_AddIntConstant(m, "py_buffer_size", sizeof(Py_buffer));
71+
PyModule_AddIntConstant(m, "py_gil_state_size", sizeof(PyGILState_STATE));
72+
PyModule_AddIntConstant(m, "py_unicode_1byte_kind", PyUnicode_1BYTE_KIND);
73+
PyModule_AddIntConstant(m, "py_unicode_2byte_kind", PyUnicode_2BYTE_KIND);
74+
PyModule_AddIntConstant(m, "py_unicode_4byte_kind", PyUnicode_4BYTE_KIND);
75+
#if (PY_MAJOR_VERSION == 3)
76+
#if ((PY_MINOR_VERSION == 10) || (PY_MINOR_VERSION == 11))
77+
PyModule_AddIntConstant(m, "py_unicode_wchar_kind", PyUnicode_WCHAR_KIND);
78+
#endif
79+
#endif
80+
81+
return MOD_SUCCESS_VAL(m);
82+
}

numba_cuda/numba/cuda/core/base.py

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
from llvmlite import ir as llvmir
1212
from llvmlite.ir import Constant
13-
import llvmlite.binding as ll
1413

1514
from numba.core import (
1615
types,
@@ -21,7 +20,6 @@
2120
from numba.cuda import cgutils, debuginfo, utils
2221
from numba.core import errors
2322
from numba.cuda.core import targetconfig, funcdesc
24-
from numba import _dynfunc, _helperlib
2523
from numba.core.compiler_lock import global_compiler_lock
2624
from numba.cuda.core.pythonapi import PythonAPI
2725
from numba.core.imputils import (
@@ -158,26 +156,6 @@ def append(self, value, sig):
158156
self._cache.clear()
159157

160158

161-
@utils.runonce
162-
def _load_global_helpers():
163-
"""
164-
Execute once to install special symbols into the LLVM symbol table.
165-
"""
166-
# This is Py_None's real C name
167-
ll.add_symbol("_Py_NoneStruct", id(None))
168-
169-
# Add Numba C helper functions
170-
for c_helpers in (_helperlib.c_helpers, _dynfunc.c_helpers):
171-
for py_name, c_address in c_helpers.items():
172-
c_name = "numba_" + py_name
173-
ll.add_symbol(c_name, c_address)
174-
175-
# Add all built-in exception classes
176-
for obj in utils.builtins.__dict__.values():
177-
if isinstance(obj, type) and issubclass(obj, BaseException):
178-
ll.add_symbol("PyExc_%s" % (obj.__name__), id(obj))
179-
180-
181159
class BaseContext(object):
182160
"""
183161
@@ -238,8 +216,6 @@ def enable_boundscheck(self, value):
238216
fndesc = None
239217

240218
def __init__(self, typing_context, target):
241-
_load_global_helpers()
242-
243219
self.address_size = utils.MACHINE_BITS
244220
self.typing_context = typing_context
245221
from numba.core.target_extension import target_registry

numba_cuda/numba/cuda/core/environment.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44
import weakref
55
import importlib
66

7-
from numba import _dynfunc
87

9-
10-
class Environment(_dynfunc.Environment):
8+
class Environment:
119
"""Stores globals and constant pyobjects for runtime.
1210
1311
It is often needed to convert b/w nopython objects and pyobjects.
@@ -24,7 +22,7 @@ def from_fndesc(cls, fndesc):
2422
# Avoid creating new Env
2523
return cls._memo[fndesc.env_name]
2624
except KeyError:
27-
inst = cls(fndesc.lookup_globals())
25+
inst = cls()
2826
inst.env_name = fndesc.env_name
2927
cls._memo[fndesc.env_name] = inst
3028
return inst

numba_cuda/numba/cuda/core/pythonapi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from llvmlite.ir import Constant
1111

1212
import ctypes
13-
from numba import _helperlib
13+
from numba.cuda.cext import _helperlib
1414
from numba.core import (
1515
errors,
1616
types,

numba_cuda/numba/cuda/cpython/unicode.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
PY_UNICODE_2BYTE_KIND,
3434
PY_UNICODE_4BYTE_KIND,
3535
)
36-
from numba._helperlib import c_helpers
36+
from numba.cuda.cext._helperlib import c_helpers
3737
from numba.cuda.core.unsafe.bytes import memcpy_region
3838
from numba.core.errors import TypingError
3939
from numba.cuda.cpython.unicode_support import (

numba_cuda/numba/cuda/extending.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
lower_cast,
2828
) # noqa: F401
2929
from numba.cuda.core.pythonapi import box, unbox, reflect, NativeValue # noqa: F401
30-
from numba._helperlib import _import_cython_function # noqa: F401
3130
from numba.cuda.serialize import ReduceMixin
3231
from numba.core.datamodel import models as core_models # noqa: F401
3332

setup.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,23 @@ def get_ext_modules():
9191
# Append our cext dir to include_dirs
9292
ext_dispatcher.include_dirs.append("numba_cuda/numba/cuda/cext")
9393

94-
return [ext_dispatcher, ext_typeconv, ext_mviewbuf, ext_devicearray]
94+
ext_helperlib = Extension(
95+
name="numba_cuda.numba.cuda.cext._helperlib",
96+
sources=["numba_cuda/numba/cuda/cext/_helpermod.c"],
97+
depends=[
98+
"numba_cuda/numba/cuda/cext/_pymodule.h",
99+
"numba_cuda/numba/cuda/cext/_helperlib.c",
100+
],
101+
include_dirs=["numba_cuda/numba/cuda/cext"],
102+
)
103+
104+
return [
105+
ext_dispatcher,
106+
ext_typeconv,
107+
ext_helperlib,
108+
ext_mviewbuf,
109+
ext_devicearray,
110+
]
95111

96112

97113
def is_building():

0 commit comments

Comments
 (0)