Skip to content

Add freethreading build and tests #226

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

Merged
merged 7 commits into from
Mar 16, 2025
Merged
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
54 changes: 21 additions & 33 deletions .github/workflows/buildwheel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,36 @@ jobs:
os: [ubuntu-24.04, ubuntu-24.04-arm, windows-2019, macos-13, macos-14]
# This list to be kept in sync with cibuildwheel config
# and python-requires in pyproject.toml.
python-version: ['3.11', '3.12', '3.13'] # , 'pypy3.10']
python-version: ['3.11', '3.12', '3.13', '3.13t'] # , 'pypy3.10']

# XXX: Maybe this exclude can be removed when changing from
# Quansight-Labs/setup-python...
exclude:
- os: ubuntu-24.04-arm
python-version: '3.13t'

steps:
- uses: actions/setup-python@v5
# Quansight-labs/setup-python is needed for 3.13t until a new version of
# actions/setup-python (>5.4.0) is released.
#
# https://github.com/actions/setup-python/pull/973
- uses: Quansight-Labs/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- uses: actions/download-artifact@v4
with:
name: wheels-${{ matrix.os }}
path: wheelhouse
- run: pip install --no-index --find-links wheelhouse python_flint

# Check if the GIL is disabled in the free-threading build after import.
- run: |
python --version --version
which python
python -c "import sysconfig; print(sysconfig.get_config_var('Py_GIL_DISABLED'))"
python -c "import sys; print(getattr(sys, '_is_gil_enabled', lambda: True)())"
python -c "import sys; import flint; print(getattr(sys, '_is_gil_enabled', lambda: True)())"

- run: python -m flint.test --verbose

# On new enough Ubuntu we can build against the system deb.
Expand Down Expand Up @@ -227,37 +246,6 @@ jobs:
- run: pip install -r requirements-dev.txt
- run: bin/coverage.sh

# On new enough Ubuntu we can build against the system deb.
test_freethreaded:
name: Free-threaded ${{ matrix.python-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04]
python-version: ['3.13', '3.14-dev']
steps:
- uses: actions/checkout@v4
# Can't use actions/setup-python
# https://github.com/actions/setup-python/issues/771
# deadsnakes only works for Ubuntu...
- uses: deadsnakes/[email protected]
with:
python-version: ${{ matrix.python-version }}
nogil: true
- run: |
python --version --version
which python
python -c "import sysconfig; print(sysconfig.get_config_var('Py_GIL_DISABLED'))"
python -c "import sys; print(sys._is_gil_enabled())"
- run: sudo apt-get update
- run: sudo apt-get install libflint-dev
# Need Cython master until 3.1 is released
- run: pip install git+https://github.com/cython/cython.git@master
- run: pip install -r requirements-dev.txt
- run: pip install --no-build-isolation .
- run: python -m flint.test --verbose

# Run SymPy test suite against python-flint master
test_sympy:
name: Test SymPy ${{ matrix.sympy-version }}
Expand Down
11 changes: 11 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ add_project_arguments(
language : 'cython'
)

# Enable free-threading if Cython is new enough. The check should be
# >= 3.1.0a1 but meson gets confused by the a1 alpha release suffix.
# so we go with >= 3.1 (which will be correct once 3.1 is released).
cy = meson.get_compiler('cython')
if cy.version().version_compare('>=3.1')
message('Enabling freethreading')
add_project_arguments('-Xfreethreading_compatible=true', language : 'cython')
else
message('Disabling freethreading')
endif

if get_option('coverage')
add_project_arguments('-X', 'linetrace=True', language : 'cython')
add_project_arguments('-DCYTHON_TRACE=1', language : 'c')
Expand Down
25 changes: 12 additions & 13 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ version = "0.7.0"
requires-python = ">= 3.11"
authors = [
{name = "Fredrik Johansson", email = "[email protected]"},
{name = "Oscar Benjamin", email = "[email protected]"},
]
license = {text = "MIT"}
classifiers = [
Expand All @@ -27,16 +28,16 @@ content-type = "text/markdown"
# Minimum build requirements tested in CI need to be kept in sync with the
# versions in requires below so that they are tested.
#
# The upper cap on Cython is speculative but we may as well cap it given that
# it is only a build requirement and that it is not uncommon for newer Cython
# versions to break the build. For example Cython 3.0 broke the build and then
# Cython 3.1 broke the build again so python-flint 0.6.0 did not have any upper
# cap but it should have had cython>=3.0,<3.1 i.e. precisely one minor release
# of Cython works. In future we could contemplate not having an upper cap but
# until we have actually witnessed a Cython 3.x release that does not break the
# build we should keep the upper cap.
# To support the freethreaded build (CPython 3.13t) the alpha release 3.1.0a1
# of Cython is needed as a minimum requirement. It is possible that future
# versions of Cython might still work but typically a Cython release breaks
# something in the build of python-flint so we pin the exact version here.
#
requires = ["meson-python>=0.13", "cython>=3.0.11,<=3.1.0a1"]
# Apart from the freethreading build any Cython version from 3.0.11 onwards is
# fine. It is not possible to have a separate version constraint here for the
# freethreading build only though.
#
requires = ["meson-python>=0.13", "cython==3.1.0a1"]
build-backend = "mesonpy"

[tool.cython-lint]
Expand Down Expand Up @@ -81,12 +82,10 @@ package = "flint"
[tool.cibuildwheel]
# requires-python needs to keep in sync with this and also the list of Python
# versions the wheels are tested against in CI.
build = "cp311-* cp312-* cp313-*" # pp311-*"
build = "cp311-* cp312-* cp313-* cp313t-*" # pp311-*"
skip = "*-win32 *-manylinux_i686 *-musllinux_*"

# This is needed for free-threaded wheels:
# build = "cp313t-*"
# free-threaded-support = true
free-threaded-support = true

manylinux-x86_64-image = "manylinux2014"
manylinux-aarch64-image = "manylinux_2_28"
Expand Down
53 changes: 53 additions & 0 deletions src/flint/test/test_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -1612,10 +1612,12 @@ def set_bad2():
M6 = M6_copy
assert M6.nullspace() == (M([[1,15,1],[0,0,0],[0,0,0]],17).transpose(), 1)


def test_nmod_series():
# XXX: currently no code in nmod_series.pyx
pass


def test_arb():
A = flint.arb
assert A(3) > A(2.5)
Expand All @@ -1626,6 +1628,7 @@ def test_arb():
assert A(3) != A(2)
assert not (A("1.1") == A("1.1"))


def test_pickling():
objects = [
flint.fmpz(1),
Expand Down Expand Up @@ -4735,6 +4738,54 @@ def test_fq_default_poly():
assert raises(lambda: f.pow_trunc(-1, 5), ValueError)


def test_python_threads():
#
# https://github.com/flintlib/python-flint/issues/224
#
# XXX: This test crashes under the free-threading mode because of memory
# corruption.
#
# It is not clear if this should be fixed or if mutating
# matrices/polynomials that are shared between multiple threads should just
# be disallowed.
#

# Skip the test on the free-threaded build...
import sys
if sys.version_info[:2] >= (3, 13) and not sys._is_gil_enabled():
return

from threading import Thread

iterations = 10**5
threads = 3 + 1
size = 3
M = flint.fmpz_mat([[0]*size for _ in range(size)])

def set_values():
for i in range(iterations // 5):
i = random.randrange(M.nrows())
j = random.randrange(M.ncols())
if random.uniform(0, 1) > 0.5:
# Bigger than 2**62:
M[i,j] = 10**128
else:
# Smaller than 2**62:
M[i,j] = 0

def get_dets():
for i in range(iterations):
M.det()

threads = [Thread(target=set_values) for _ in range(threads-1)]
threads.append(Thread(target=get_dets))

for t in threads:
t.start()
for t in threads:
t.join()


def test_all_tests():
test_funcs = {f for name, f in globals().items() if name.startswith("test_")}
untested = test_funcs - set(all_tests)
Expand Down Expand Up @@ -4813,5 +4864,7 @@ def test_all_tests():

test_pickling,

test_python_threads,

test_all_tests,
]
Loading