diff --git a/quaddtype/meson.build b/quaddtype/meson.build index e50f6937..735db3fc 100644 --- a/quaddtype/meson.build +++ b/quaddtype/meson.build @@ -30,6 +30,44 @@ if openmp_dep.found() dependencies += openmp_dep endif +# Thread-local storage detection (borrowed from NumPy) +optional_variable_attributes = [ + ['thread_local', 'HAVE_THREAD_LOCAL'], # C23 + ['_Thread_local', 'HAVE__THREAD_LOCAL'], # C11/C17 + ['__thread', 'HAVE___THREAD'], # GCC/Clang + ['__declspec(thread)', 'HAVE___DECLSPEC_THREAD_'] # MSVC +] + +if not is_variable('cdata') + cdata = configuration_data() +endif + +foreach optional_attr: optional_variable_attributes + attr = optional_attr[0] + code = ''' + #pragma GCC diagnostic error "-Wattributes" + #pragma clang diagnostic error "-Wattributes" + + int @0@ foo; + + int main() { + return 0; + } + '''.format(attr) + + if c.compiles(code, name: optional_attr[0]) + cdata.set10(optional_attr[1], true) + message('Thread-local storage support found: @0@'.format(attr)) + endif +endforeach + +configure_file( + output: 'quaddtype_config.h', + configuration: cdata +) + +build_includes = include_directories('.') + includes = include_directories( [ incdir_numpy, @@ -84,5 +122,5 @@ py.extension_module('_quaddtype_main', dependencies: dependencies, install: true, subdir: 'numpy_quaddtype', - include_directories: includes + include_directories: [includes, build_includes] ) \ No newline at end of file diff --git a/quaddtype/numpy_quaddtype/src/dragon4.c b/quaddtype/numpy_quaddtype/src/dragon4.c index 34ad4cbb..8e96c0bc 100644 --- a/quaddtype/numpy_quaddtype/src/dragon4.c +++ b/quaddtype/numpy_quaddtype/src/dragon4.c @@ -11,6 +11,9 @@ Modifications are specific to support the SLEEF_QUAD #include #include +#include "quaddtype_config.h" + + #define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API #define PY_UFUNC_UNIQUE_SYMBOL QuadPrecType_UFUNC_API #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION @@ -22,6 +25,21 @@ Modifications are specific to support the SLEEF_QUAD #include "dtype.h" #include "scalar.h" + +#ifdef __cplusplus + #define NPY_TLS thread_local +#elif defined(HAVE_THREAD_LOCAL) + #define NPY_TLS thread_local +#elif defined(HAVE__THREAD_LOCAL) + #define NPY_TLS _Thread_local +#elif defined(HAVE___THREAD) + #define NPY_TLS __thread +#elif defined(HAVE___DECLSPEC_THREAD_) + #define NPY_TLS __declspec(thread) +#else + #define NPY_TLS +#endif + #if 0 #define DEBUG_ASSERT(stmnt) assert(stmnt) #else diff --git a/quaddtype/numpy_quaddtype/src/quaddtype_main.c b/quaddtype/numpy_quaddtype/src/quaddtype_main.c index 929927a4..734de21b 100644 --- a/quaddtype/numpy_quaddtype/src/quaddtype_main.c +++ b/quaddtype/numpy_quaddtype/src/quaddtype_main.c @@ -30,6 +30,29 @@ py_is_longdouble_128(PyObject *self, PyObject *args) } } +#ifdef SLEEF_QUAD_C +static const Sleef_quad SMALLEST_SUBNORMAL_VALUE = SLEEF_QUAD_DENORM_MIN; +#else +// Use the exact same struct layout as the original buggy code +static const union { + struct { +#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + uint64_t h, l; +#else + uint64_t l, h; +#endif + } parts; + Sleef_quad value; +} smallest_subnormal_const = {.parts = { +#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + .h = 0x0000000000000000ULL, .l = 0x0000000000000001ULL +#else + .l = 0x0000000000000001ULL, .h = 0x0000000000000000ULL +#endif + }}; +#define SMALLEST_SUBNORMAL_VALUE (smallest_subnormal_const.value) +#endif + static PyObject * get_sleef_constant(PyObject *self, PyObject *args) { @@ -71,7 +94,7 @@ get_sleef_constant(PyObject *self, PyObject *args) result->value.sleef_value = SLEEF_QUAD_MIN; } else if (strcmp(constant_name, "smallest_subnormal") == 0) { - result->value.sleef_value = SLEEF_QUAD_DENORM_MIN; + result->value.sleef_value = SMALLEST_SUBNORMAL_VALUE; } else if (strcmp(constant_name, "bits") == 0) { Py_DECREF(result); diff --git a/quaddtype/numpy_quaddtype/src/scalar.c b/quaddtype/numpy_quaddtype/src/scalar.c index 6c2e4b51..6d82d198 100644 --- a/quaddtype/numpy_quaddtype/src/scalar.c +++ b/quaddtype/numpy_quaddtype/src/scalar.c @@ -15,10 +15,13 @@ #include "scalar_ops.h" #include "dragon4.h" + QuadPrecisionObject * QuadPrecision_raw_new(QuadBackendType backend) { - QuadPrecisionObject *new = PyObject_New(QuadPrecisionObject, &QuadPrecision_Type); + QuadPrecisionObject *new; + new = PyObject_New(QuadPrecisionObject, &QuadPrecision_Type); + if (!new) return NULL; new->backend = backend; diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index 0a2cf3ff..1833c4e6 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -24,6 +24,16 @@ def test_finfo_constant(name): assert isinstance(getattr(numpy_quaddtype, name), QuadPrecision) +def test_smallest_subnormal_value(): + """Test that smallest_subnormal has the correct value across all platforms.""" + smallest_sub = numpy_quaddtype.smallest_subnormal + repr_str = repr(smallest_sub) + + # The repr should show QuadPrecision('6.0e-4966', backend='sleef') + assert "6.0e-4966" in repr_str, f"Expected '6.0e-4966' in repr, got {repr_str}" + + assert smallest_sub > 0, "smallest_subnormal should be positive" + @pytest.mark.parametrize("name,value", [("bits", 128), ("precision", 33)]) def test_finfo_int_constant(name, value): assert getattr(numpy_quaddtype, name) == value