Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/big_endian.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ permissions:

jobs:
big_endian_tests:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
continue-on-error: true
strategy:
fail-fast: false
Expand Down
11 changes: 8 additions & 3 deletions quaddtype/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ if sleef_dep.found() and sleef_dep.type_name() != 'internal'
# SLEEF found system-wide - verify quad-precision support
cpp = meson.get_compiler('cpp')
sleefquad_lib = cpp.find_library('sleefquad', required: false)
tlfloat_lib = cpp.find_library('tlfloat', required: false)

if sleefquad_lib.found()
if sleefquad_lib.found() and tlfloat_lib.found()
sleefquad_test_code = '''
#include <sleefquad.h>

Expand All @@ -40,14 +41,17 @@ if sleef_dep.found() and sleef_dep.type_name() != 'internal'
# this should compile and link
quad_works = cpp.links(
sleefquad_test_code,
dependencies: [sleef_dep, sleefquad_lib],
dependencies: [sleef_dep, sleefquad_lib, tlfloat_lib],
name: 'SLEEF quad-precision support'
)

if quad_works
sleefquad_dep = declare_dependency(
dependencies: [sleef_dep, sleefquad_lib]
)
tlfloat_dep = declare_dependency(
dependencies: [tlfloat_lib]
)
use_system_sleef = true
else
fallback_reason = 'System-wide SLEEF installation found but a test for quad precision support failed.'
Expand All @@ -65,6 +69,7 @@ else
sleef_subproj = subproject('sleef')
sleef_dep = sleef_subproj.get_variable('sleef_dep')
sleefquad_dep = sleef_subproj.get_variable('sleefquad_dep')
tlfloat_dep = sleef_subproj.get_variable('tlfloat_dep')
warning(fallback_reason)
message('Proceeding with vendored SLEEF subproject instead')
endif
Expand All @@ -84,7 +89,7 @@ message('Using NumPy version: @0@'.format(numpy_version))
npymath_path = incdir_numpy / '..' / 'lib'
npymath_lib = c.find_library('npymath', dirs: npymath_path)

dependencies = [py_dep, qblas_dep, sleef_dep, sleefquad_dep, npymath_lib]
dependencies = [py_dep, qblas_dep, sleef_dep, sleefquad_dep, tlfloat_dep, npymath_lib]

# Add OpenMP dependency (optional, for threading)
openmp_dep = dependency('openmp', required: false, static: false)
Expand Down
22 changes: 20 additions & 2 deletions quaddtype/numpy_quaddtype/src/scalar.c
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,21 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend)
else if (PyUnicode_Check(value)) {
const char *s = PyUnicode_AsUTF8(value);
char *endptr = NULL;
int err = cstring_to_quad(s, backend, &self->value, &endptr, true);
int err = NumPyOS_ascii_strtoq(s, backend, &self->value, &endptr);
if (err < 0) {
PyErr_SetString(PyExc_ValueError, "Unable to parse string to QuadPrecision");
Py_DECREF(self);
return NULL;
}
// Skip trailing whitespace (matches Python's float() behavior)
while (ascii_isspace(*endptr)) {
endptr++;
}
if (*endptr != '\0') {
PyErr_SetString(PyExc_ValueError, "Unable to parse string to QuadPrecision");
Py_DECREF(self);
return NULL;
}
}
else if (PyBytes_Check(value)) {
const char *s = PyBytes_AsString(value);
Expand All @@ -205,12 +214,21 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend)
return NULL;
}
char *endptr = NULL;
int err = cstring_to_quad(s, backend, &self->value, &endptr, true);
int err = NumPyOS_ascii_strtoq(s, backend, &self->value, &endptr);
if (err < 0) {
PyErr_SetString(PyExc_ValueError, "Unable to parse bytes to QuadPrecision");
Py_DECREF(self);
return NULL;
}
// Skip trailing whitespace (matches Python's float() behavior)
while (ascii_isspace(*endptr)) {
endptr++;
}
if (*endptr != '\0') {
PyErr_SetString(PyExc_ValueError, "Unable to parse bytes to QuadPrecision");
Py_DECREF(self);
return NULL;
}
}
else if (PyLong_Check(value)) {
return quad_from_py_int(value, backend, self);
Expand Down
105 changes: 99 additions & 6 deletions quaddtype/numpy_quaddtype/src/utilities.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,17 @@ NumPyOS_ascii_strtoq(const char *s, QuadBackendType backend, quad_value *out_val
}
}

// Set NaN value (sign is ignored for NaN)
// Set NaN value with sign preserved
if (backend == BACKEND_SLEEF) {
out_value->sleef_value = QUAD_PRECISION_NAN;
Sleef_quad nan_val = QUAD_PRECISION_NAN;
// Apply sign to NaN (negative NaN has sign bit set)
if (sign < 0) {
nan_val = Sleef_negq1(nan_val);
}
out_value->sleef_value = nan_val;
}
else {
out_value->longdouble_value = nanl("");
out_value->longdouble_value = sign < 0 ? -nanl("") : nanl("");
}

if (endptr) {
Expand All @@ -157,15 +162,103 @@ int cstring_to_quad(const char *str, QuadBackendType backend, quad_value *out_va
char **endptr, bool require_full_parse)
{
if(backend == BACKEND_SLEEF) {
out_value->sleef_value = Sleef_strtoq(str, endptr);
// SLEEF 4.0's Sleef_strtoq doesn't properly set endptr to indicate
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kinda seems like a SLEEF bug you should report (even if they'll ignore it).

// where parsing stopped. It always sets endptr to the end of the string.
// We need to manually validate and track the parse position.

const char *p = str;

// Skip leading whitespace
while (ascii_isspace(*p)) {
p++;
}

// Track start of number (after whitespace)
const char *num_start = p;

// Handle optional sign
if (*p == '+' || *p == '-') {
p++;
}

// Must have at least one digit or decimal point followed by digit
int has_digits = 0;
int has_decimal = 0;

// Parse integer part
while (ascii_isdigit(*p)) {
has_digits = 1;
p++;
}

// Parse decimal point and fractional part
if (*p == '.') {
has_decimal = 1;
p++;
while (ascii_isdigit(*p)) {
has_digits = 1;
p++;
}
}

// Must have at least one digit somewhere
if (!has_digits) {
if (endptr) *endptr = (char *)str;
return -1;
}

// Parse optional exponent
if (*p == 'e' || *p == 'E') {
const char *exp_start = p;
p++;

// Optional sign in exponent
if (*p == '+' || *p == '-') {
p++;
}

// Must have at least one digit in exponent
if (!ascii_isdigit(*p)) {
// Invalid exponent, backtrack
p = exp_start;
} else {
while (ascii_isdigit(*p)) {
p++;
}
}
}

// Now p points to where valid parsing ends
// SLEEF 4.0's Sleef_strtoq has a bug where it doesn't properly stop at whitespace
// or other delimiters. We need to create a null-terminated substring.
size_t len = p - str;
char *temp = (char *)malloc(len + 1);
if (!temp) {
if (endptr) *endptr = (char *)str;
return -1;
}
memcpy(temp, str, len);
temp[len] = '\0';

// Call Sleef_strtoq with the bounded string
char *sleef_endptr;
out_value->sleef_value = Sleef_strtoq(temp, &sleef_endptr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is pretty subuptimal :(

Consider refactoring all the code you added here into a function even if there's only one caller, since it's pretty distracting here inline in this function.

free(temp);

// Set endptr to our calculated position
if (endptr) {
*endptr = (char *)p;
}

} else {
out_value->longdouble_value = strtold(str, endptr);
}
if(*endptr == str)

if(endptr && *endptr == str)
return -1; // parse error - nothing was parsed

// If full parse is required
if(require_full_parse && **endptr != '\0')
if(require_full_parse && endptr && **endptr != '\0')
return -1; // parse error - characters remain to be converted

return 0; // success
Expand Down
11 changes: 5 additions & 6 deletions quaddtype/reinstall.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
#!/bin/bash
set -x

if [ -d "build/" ]; then
rm -r build
rm -rf dist/
rm -rf subprojects/qblas
rm -rf subprojects/sleef
fi
rm -rf build/
rm -rf dist/
rm -rf subprojects/qblas
rm -rf subprojects/sleef
rm -rf .mesonpy-*

python -m pip uninstall -y numpy_quaddtype
python -m pip install . -vv --no-build-isolation 2>&1 | tee build_log.txt
Expand Down
24 changes: 20 additions & 4 deletions quaddtype/subprojects/packagefiles/sleef/meson.build
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
project('sleef', version: '3.8')
project('sleef')

cmake = find_program('cmake')
ninja = find_program('ninja', 'make', required: false)
Expand All @@ -8,11 +8,21 @@ sleef_install_dir = 'sleef_install'

# turning off parallel build in windows
parallel_flag = ['--parallel']
build_config_flag = []
if host_machine.system() == 'windows'
parallel_flag = []
# On Windows with MSVC (multi-config generator), must specify --config Release
# to match the main project's release build and avoid runtime library mismatches
build_config_flag = ['--config', 'Release']
endif

# For building sleef with TSan, delete the sleef subproject and follow the README instructions to build sleef externally.
# Enable SIMD extensions that are OFF by default but required by qblas (will change in future)
sleef_simd_flags = []
if host_machine.cpu_family() == 'x86_64' or host_machine.cpu_family() == 'x86'
sleef_simd_flags = ['-DSLEEF_ENABLE_SSE2=ON']
endif

sleef_configure = run_command([
cmake,
'-S', meson.current_source_dir(),
Expand All @@ -24,14 +34,14 @@ sleef_configure = run_command([
'-DSLEEF_BUILD_INLINE_HEADERS=OFF',
'-DCMAKE_POSITION_INDEPENDENT_CODE=ON',
'-DCMAKE_INSTALL_PREFIX=' + meson.current_build_dir() / sleef_install_dir
], check: false, capture: true)
] + sleef_simd_flags, check: false, capture: true)

if sleef_configure.returncode() != 0
error('SLEEF CMake configuration failed: ' + sleef_configure.stderr())
endif

sleef_build_target = custom_target('sleef_build',
command: [cmake, '--build', meson.current_build_dir() / sleef_build_dir, '--target', 'install'] + parallel_flag,
command: [cmake, '--build', meson.current_build_dir() / sleef_build_dir, '--target', 'install'] + build_config_flag + parallel_flag,
output: 'sleef_built.stamp',
console: true,
build_always_stale: true,
Expand Down Expand Up @@ -59,7 +69,13 @@ sleef_dep = declare_dependency(
)

sleefquad_dep = declare_dependency(
dependencies: [sleef_build_dep],
dependencies: [sleef_build_dep, sleef_dep],
compile_args: compile_args_list,
link_args: ['-L' + meson.current_build_dir() / sleef_install_dir / 'lib', '-L' + meson.current_build_dir() / sleef_install_dir / 'lib64', '-lsleefquad']
)

tlfloat_dep = declare_dependency(
dependencies: [sleef_build_dep],
compile_args: compile_args_list,
link_args: ['-L' + meson.current_build_dir() / sleef_install_dir / 'lib', '-L' + meson.current_build_dir() / sleef_install_dir / 'lib64', '-ltlfloat']
)
5 changes: 3 additions & 2 deletions quaddtype/subprojects/sleef.wrap
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
[wrap-git]
directory=sleef
url=https://github.com/shibatch/sleef.git
revision=3.8
revision=43a0252ba9331adc7fb10755021f802863678c38
patch_directory=sleef

[provide]
sleef = sleef_dep
sleefquad = sleefquad_dep
sleefquad = sleefquad_dep
tlfloat = tlfloat_dep
Loading
Loading