Skip to content

WIP - port to Cython 3, and GAP 4.12 #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
13 changes: 3 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,8 @@ jobs:
fail-fast: false
matrix:
platform: ["ubuntu-latest", "macos-latest"]
python-version: ["3.7", "3.8"]
gap-version: ["4.10.2", "4.11.0"]
exclude:
- python-version: "3.7"
gap-version: "4.11.0"
- python-version: "3.8"
gap-version: "4.10.2"
- platform: "macos-latest"
python-version: "3.7"
python-version: ["3.9", "3.10", "3.11", "3.12"]
gap-version: ["4.12.2", "4.13.0"]
runs-on: "${{ matrix.platform }}"
steps:
- uses: actions/checkout@v4
Expand All @@ -36,7 +29,7 @@ jobs:
with:
auto-update-conda: true
python-version: "${{ matrix.python-version }}"
use-only-tar-bz2: true
use-only-tar-bz2: false
- name: "Conda info"
run: "conda info"
- name: "Conda install dependencies"
Expand Down
11 changes: 6 additions & 5 deletions gappy/core.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ cdef void dereference_obj(Obj obj):
owned_objects_refcount[wrapped] = refcount - 1


cdef void gasman_callback() with gil:
cdef void gasman_callback() noexcept with gil:
"""
Callback before each GAP garbage collection
"""
Expand Down Expand Up @@ -454,7 +454,7 @@ cdef str extract_errout():
return msg_py


cdef void error_handler() with gil:
cdef void error_handler() noexcept with gil:
"""
The gappy error handler.

Expand All @@ -463,7 +463,7 @@ cdef void error_handler() with gil:

TODO: We should probably prevent re-entering this function if we
are already handling an error; if there is an error in our stream
handling code below it could result in a stack overflow.
h/andling code below it could result in a stack overflow.
"""
cdef PyObject* exc_type = NULL
cdef PyObject* exc_val = NULL
Expand Down Expand Up @@ -1387,8 +1387,9 @@ cdef class Gap:
>>> gap.collect()
"""
self.initialize()
rc = GAP_CollectBags(1)
if rc != 1:
try:
GAP_CollectBags(1)
except:
raise RuntimeError('Garbage collection failed.')


Expand Down
98 changes: 98 additions & 0 deletions gappy/cpython/pycore_long.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#include "Python.h"
#include <stdbool.h>

#if PY_VERSION_HEX >= 0x030C00A5
#define ob_digit(o) (((PyLongObject*)o)->long_value.ob_digit)
#else
#define ob_digit(o) (((PyLongObject*)o)->ob_digit)
#endif

#if PY_VERSION_HEX >= 0x030C00A7
// taken from cpython:Include/internal/pycore_long.h @ 3.12

/* Long value tag bits:
* 0-1: Sign bits value = (1-sign), ie. negative=2, positive=0, zero=1.
* 2: Reserved for immortality bit
* 3+ Unsigned digit count
*/
#define SIGN_MASK 3
#define SIGN_ZERO 1
#define SIGN_NEGATIVE 2
#define NON_SIZE_BITS 3

static inline bool
_PyLong_IsZero(const PyLongObject *op)
{
return (op->long_value.lv_tag & SIGN_MASK) == SIGN_ZERO;
}

static inline bool
_PyLong_IsNegative(const PyLongObject *op)
{
return (op->long_value.lv_tag & SIGN_MASK) == SIGN_NEGATIVE;
}

static inline bool
_PyLong_IsPositive(const PyLongObject *op)
{
return (op->long_value.lv_tag & SIGN_MASK) == 0;
}

static inline Py_ssize_t
_PyLong_DigitCount(const PyLongObject *op)
{
assert(PyLong_Check(op));
return op->long_value.lv_tag >> NON_SIZE_BITS;
}

#define TAG_FROM_SIGN_AND_SIZE(sign, size) ((1 - (sign)) | ((size) << NON_SIZE_BITS))

static inline void
_PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size)
{
assert(size >= 0);
assert(-1 <= sign && sign <= 1);
assert(sign != 0 || size == 0);
op->long_value.lv_tag = TAG_FROM_SIGN_AND_SIZE(sign, (size_t)size);
}

#else
// fallback for < 3.12

static inline bool
_PyLong_IsZero(const PyLongObject *op)
{
return Py_SIZE(op) == 0;
}

static inline bool
_PyLong_IsNegative(const PyLongObject *op)
{
return Py_SIZE(op) < 0;
}

static inline bool
_PyLong_IsPositive(const PyLongObject *op)
{
return Py_SIZE(op) > 0;
}

static inline Py_ssize_t
_PyLong_DigitCount(const PyLongObject *op)
{
Py_ssize_t size = Py_SIZE(op);
return size < 0 ? -size : size;
}

static inline void
_PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size)
{
#if (PY_MAJOR_VERSION == 3) && (PY_MINOR_VERSION < 9)
// The function Py_SET_SIZE is defined starting with python 3.9.
Py_SIZE(op) = size;
#else
Py_SET_SIZE(op, sign < 0 ? -size : size);
#endif
}

#endif
9 changes: 9 additions & 0 deletions gappy/cpython/pycore_long.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from cpython.longintrepr cimport py_long, digit

cdef extern from "pycore_long.h":
digit* ob_digit(py_long o)
bint _PyLong_IsZero(py_long o)
bint _PyLong_IsNegative(py_long o)
bint _PyLong_IsPositive(py_long o)
Py_ssize_t _PyLong_DigitCount(py_long o)
void _PyLong_SetSignAndDigitCount(py_long o, int sign, Py_ssize_t size)
27 changes: 3 additions & 24 deletions gappy/gap_includes.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -125,27 +125,6 @@ cdef extern from "gap/libgap-api.h" nogil:
Obj GAP_NewPrecord(Int)


cdef extern from "gap/gasman.h" nogil:
"""
#define GAP_CollectBags(full) CollectBags(0, full)
"""
void GAP_MarkBag "MarkBag" (Obj bag)
UInt GAP_CollectBags(UInt full)


cdef extern from "gap/io.h" nogil:
UInt OpenOutputStream(Obj stream)
UInt CloseOutput()


# TODO: Replace this with a GAP_MakeStringWithLen from the public API;
# see https://github.com/gap-system/gap/issues/4211
cdef extern from "gap/stringobj.h" nogil:
"""
static inline Obj GAP_MakeStringWithLen(char *s, size_t len) {
Obj ret;
C_NEW_STRING(ret, len, s);
return ret;
}
"""
Obj GAP_MakeStringWithLen(char *, size_t)
void GAP_MarkBag (Obj bag)
void GAP_CollectBags(UInt)
Obj GAP_MakeStringWithLen(const char *, UInt)
37 changes: 18 additions & 19 deletions gappy/gapobj.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ from .exceptions import GAPError
from .operations import OperationInspector
from .utils import _SPECIAL_ATTRS, _converter_for_type

from .cpython.pycore_long cimport (ob_digit)

############################################################################
### helper functions to construct lists and records ########################
Expand Down Expand Up @@ -101,18 +102,18 @@ cdef void capture_stdout(Obj func, Obj obj, Obj out):
args[0] = out
args[1] = GAP_True
stream = GAP_CallFuncArray(output_text_string, 2, args)
stream_ok = OpenOutputStream(stream)
# stream_ok = OpenOutputStream(stream)
sig_off()

if not stream_ok:
raise GAPError("failed to open output capture stream for "
"representing GAP object")
#if not stream_ok:
# raise GAPError("failed to open output capture stream for "
# "representing GAP object")

args[0] = obj
sig_on()
GAP_CallFuncArray(func, 1, args)
CloseOutput()
sig_off()
#sig_on()
#GAP_CallFuncArray(func, 1, args)
#CloseOutput()
#sig_off()
finally:
sig_GAP_Leave()

Expand Down Expand Up @@ -205,13 +206,13 @@ cdef Obj make_gap_integer(x) except NULL:

if -1 <= size <= 1:
# Shortcut for smaller ints (up to 30 bits)
s = <UInt>((<py_long>x).ob_digit[0])
s = <UInt>((ob_digit(<py_long>x))[0])
limbs = &s
else:
# See https://github.com/gap-system/gap/issues/4209
mpz_init(z)
mpz_import(z, size * sign, -1, sizeof(digit), 0,
(sizeof(digit) * 8) - PyLong_SHIFT, (<py_long>x).ob_digit)
(sizeof(digit) * 8) - PyLong_SHIFT, ob_digit(<py_long>x))
do_clear = 1
if sign < 0:
mpz_neg(z, z)
Expand Down Expand Up @@ -271,16 +272,14 @@ cdef Obj make_gap_string(s) except NULL:
``Obj``
A GAP C ``Obj`` representing a GAP string.
"""

cdef bytes b

cdef Obj res
cdef UInt slen
try:
GAP_Enter()
if not isinstance(s, bytes):
b = s.encode('utf-8')
else:
b = s
return GAP_MakeStringWithLen(b, len(b))
b = s.encode()
slen = len(b)
res = GAP_MakeStringWithLen(b, slen)
return res
finally:
GAP_Leave()

Expand Down Expand Up @@ -1807,7 +1806,7 @@ cdef class GapInteger(GapObj):
# e.g. if 2**30 we require 31 bits and with PyLong_SHIFT = 30
# this returns 2
x = _PyLong_New((nbits + PyLong_SHIFT - 1) // PyLong_SHIFT)
mpz_export(x.ob_digit, NULL, -1, sizeof(digit), 0,
mpz_export(ob_digit(x), NULL, -1, sizeof(digit), 0,
(sizeof(digit) * 8) - PyLong_SHIFT, z)
sign = mpz_sgn(z)
x *= sign
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[build-system]
requires = [
"Cython<3.0.0",
"Cython",
"cysignals",
"setuptools>=42",
"setuptools>=61.2",
"setuptools_scm[toml]>=3.4",
"wheel"
]
Expand Down
7 changes: 5 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@ classifiers =
License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Operating System :: POSIX :: Linux
Programming Language :: Cython
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
Topic :: Scientific/Engineering :: Mathematics

[options]
# We set packages to find: to automatically find all sub-packages
packages = find:
python_requires = >=3.7
python_requires = >=3.8
setup_requires = setuptools_scm
install_requires =
cysignals
Expand Down
Loading