diff --git a/.github/workflows/downstream-tests.yml b/.github/workflows/downstream-tests.yml index 1553ece7dd..eccfcb7ced 100644 --- a/.github/workflows/downstream-tests.yml +++ b/.github/workflows/downstream-tests.yml @@ -62,7 +62,7 @@ jobs: echo "${HOME}/.cargo/bin" >> $GITHUB_PATH - name: Install uv - if: ${{ matrix.name == 'pydantic-core' }} + if: ${{ matrix.name == 'pydantic-core' || matrix.name == 'jiter' }} run: | curl -LsSf https://astral.sh/uv/install.sh | sh echo "$HOME/.local/bin" >> $GITHUB_PATH diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_ast.py b/graalpython/com.oracle.graal.python.test/src/tests/test_ast.py index c4f30e6914..71c7fc2dbb 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_ast.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_ast.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # The Universal Permissive License (UPL), Version 1.0 @@ -193,5 +193,20 @@ def test_parse_empty_module(self): assert eval(code) is None assert isinstance(code, types.CodeType) + def test_compile_match_singleton_none(self): + # This occurs in pytest-generated ASTs + m = ast.Module( + body=[ + ast.Match( + subject=ast.Name("v", ast.Load()), + cases=[ast.match_case(pattern=ast.MatchSingleton(None), body=[ast.Pass()])], + ) + ], + type_ignores=[], + ) + m = ast.fix_missing_locations(m) + code = compile(m, "", "exec") + assert isinstance(code, types.CodeType) + if __name__ == '__main__': unittest.main() diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_unicodedata.py b/graalpython/com.oracle.graal.python.test/src/tests/test_unicodedata.py index 0a0f3c9e7e..410c25f0f2 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_unicodedata.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_unicodedata.py @@ -75,6 +75,10 @@ def test_lookup(self): with self.assertRaisesRegex(KeyError, "name too long"): unicodedata.lookup("a" * 257) + def test_lookup_unknown_name_via_fallback(self): + with self.assertRaisesRegex(KeyError, "undefined character name 'GIBBET NICH'"): + unicodedata.lookup("GIBBET NICH") + def test_lookup_named_sequence(self): if unicodedata.ucd_3_2_0.bidirectional == unicodedata.bidirectional: raise unittest.SkipTest("Only supported with CPython's unicodedata.ucd_3_2_0") diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/UnicodeDataModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/UnicodeDataModuleBuiltins.java index 7bf5646e6f..d06c920a55 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/UnicodeDataModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/UnicodeDataModuleBuiltins.java @@ -81,6 +81,8 @@ import com.oracle.graal.python.nodes.function.builtins.clinic.ArgumentClinicProvider; import com.oracle.graal.python.nodes.object.GetOrCreateDictNode; import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinObjectProfile; +import com.oracle.graal.python.runtime.ExecutionContext.BoundaryCallContext; +import com.oracle.graal.python.runtime.IndirectCallData.BoundaryCallData; import com.oracle.graal.python.runtime.PythonOptions; import com.oracle.graal.python.runtime.exception.PException; import com.oracle.graal.python.nodes.statement.AbstractImportNode; @@ -93,6 +95,7 @@ import com.oracle.truffle.api.dsl.ImportStatic; import com.oracle.truffle.api.dsl.NodeFactory; import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.strings.TruffleString; import com.oracle.truffle.api.strings.TruffleString.FromJavaStringNode; @@ -249,26 +252,23 @@ abstract static class LookupNode extends PythonUnaryClinicBuiltinNode { private static final int NAME_MAX_LENGTH = 256; @Specialization - @TruffleBoundary - static Object lookup(TruffleString name, + static Object lookup(VirtualFrame frame, TruffleString name, @Bind PythonLanguage lang, - @Bind Node inliningTarget) { + @Bind Node inliningTarget, + @Cached("createFor($node)") BoundaryCallData boundaryCallData) { String nameString = ToJavaStringNode.getUncached().execute(name); if (nameString.length() > NAME_MAX_LENGTH) { throw PRaiseNode.raiseStatic(inliningTarget, KeyError, ErrorMessages.NAME_TOO_LONG); } - String character = getCharacterByUnicodeName(nameString); - if (character == null) { - character = getCharacterByUnicodeNameAlias(nameString); - } - if (character != null) { - return FromJavaStringNode.getUncached().execute(character, TS_ENCODING); - } - - Object namedSequence = lookupNamedSequenceFromFallback(lang, name); - if (namedSequence != null) { - return namedSequence; + Object saved = BoundaryCallContext.enter(frame, boundaryCallData); + try { + Object result = lookupBoundary(lang, nameString, name); + if (result != null) { + return result; + } + } finally { + BoundaryCallContext.exit(frame, boundaryCallData, saved); } throw PRaiseNode.raiseStatic(inliningTarget, KeyError, ErrorMessages.UNDEFINED_CHARACTER_NAME, name); } @@ -301,6 +301,17 @@ private static String getCharacterByUnicodeNameAlias(String unicodeName) { } @TruffleBoundary + private static Object lookupBoundary(PythonLanguage lang, String nameString, TruffleString name) { + String character = getCharacterByUnicodeName(nameString); + if (character == null) { + character = getCharacterByUnicodeNameAlias(nameString); + } + if (character != null) { + return FromJavaStringNode.getUncached().execute(character, TS_ENCODING); + } + return lookupNamedSequenceFromFallback(lang, name); + } + private static Object lookupNamedSequenceFromFallback(PythonLanguage lang, TruffleString name) { if (lang.getEngineOption(PythonOptions.UnicodeCharacterDatabaseNativeFallback)) { try { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ast/Obj2SstBase.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ast/Obj2SstBase.java index 056a01293b..51d35cdbb9 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ast/Obj2SstBase.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ast/Obj2SstBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -41,6 +41,7 @@ package com.oracle.graal.python.builtins.modules.ast; import static com.oracle.graal.python.builtins.modules.ast.AstState.T_C_CONSTANT; +import static com.oracle.graal.python.builtins.modules.ast.AstState.T_C_MATCHSINGLETON; import static com.oracle.graal.python.builtins.modules.ast.AstState.T_F_VALUE; import static com.oracle.graal.python.nodes.ErrorMessages.AST_IDENTIFIER_MUST_BE_OF_TYPE_STR; import static com.oracle.graal.python.nodes.ErrorMessages.EXPECTED_SOME_SORT_OF_S_BUT_GOT_S; @@ -124,7 +125,7 @@ T lookupAndConvert(Object obj, TruffleString attrName, TruffleString nodeNam // since our SST nodes are in the pegparser project which does not have access to Python // exceptions. So we handle PNone.NONE here, but there is one exception - None is a // valid value for the required field ExprTy.Constant.value. - if (!(nodeName == T_C_CONSTANT && attrName == T_F_VALUE)) { + if (!((nodeName == T_C_CONSTANT || nodeName == T_C_MATCHSINGLETON) && attrName == T_F_VALUE)) { throw raiseValueError(FIELD_S_IS_REQUIRED_FOR_S, attrName, nodeName); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java index f7aa6368cd..e1fb2dd535 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java @@ -2683,7 +2683,7 @@ public void initializeMultiThreading() { handler.activateGIL(); } - public synchronized void attachThread(Thread thread, ContextThreadLocal threadState) { + public void attachThread(Thread thread, ContextThreadLocal threadState) { CompilerAsserts.neverPartOfCompilation(); PythonThreadState pythonThreadState = threadState.get(thread); threadStateMapping.put(thread, pythonThreadState); @@ -2723,7 +2723,7 @@ public void initializeNativeThreadState(PythonThreadState pythonThreadState) { } } - public synchronized void disposeThread(Thread thread, boolean canRunGuestCode) { + public void disposeThread(Thread thread, boolean canRunGuestCode) { CompilerAsserts.neverPartOfCompilation(); // check if there is a live sentinel lock PythonThreadState ts = threadStateMapping.get(thread); diff --git a/graalpython/lib-graalpython/patches/librt-0.9.0.patch b/graalpython/lib-graalpython/patches/librt-0.9.0.patch new file mode 100644 index 0000000000..53c99e6bc3 --- /dev/null +++ b/graalpython/lib-graalpython/patches/librt-0.9.0.patch @@ -0,0 +1,51 @@ +diff --git a/mypyc_util.h b/mypyc_util.h +index 115c309..d0c825f 100644 +--- a/mypyc_util.h ++++ b/mypyc_util.h +@@ -82,7 +82,7 @@ + + static inline void CPy_INCREF_NO_IMM(PyObject *op) + { +- Py_REFCNT(op)++; ++ Py_INCREF(op); + } + + static inline void CPy_DECREF_NO_IMM(PyObject *op) +diff --git a/pythonsupport.h b/pythonsupport.h +index 6f38a9b..59d5e67 100644 +--- a/pythonsupport.h ++++ b/pythonsupport.h +@@ -117,6 +117,7 @@ CPyLong_AsSsize_tAndOverflow(PyObject *vv, int *overflow) + #endif + + // Adapted from listobject.c in Python 3.7.0 ++#if 0 // GraalPy change + static int + list_resize(PyListObject *self, Py_ssize_t newsize) + { +@@ -162,6 +163,7 @@ list_resize(PyListObject *self, Py_ssize_t newsize) + self->allocated = new_allocated; + return 0; + } ++#endif + + // Changed to use PyList_SetSlice instead of the internal list_ass_slice + static PyObject * +@@ -182,6 +184,8 @@ list_pop_impl(PyListObject *self, Py_ssize_t index) + return NULL; + } + v = PySequence_Fast_ITEMS((PyObject*)self)[index]; ++ Py_INCREF(v); ++#if 0 // GraalPy change + if (index == Py_SIZE(self) - 1) { + status = list_resize(self, Py_SIZE(self) - 1); + if (status >= 0) +@@ -189,7 +193,7 @@ list_pop_impl(PyListObject *self, Py_ssize_t index) + else + return NULL; + } +- Py_INCREF(v); ++#endif + status = PyList_SetSlice((PyObject *)self, index, index+1, (PyObject *)NULL); + if (status < 0) { + Py_DECREF(v); diff --git a/graalpython/lib-graalpython/patches/metadata.toml b/graalpython/lib-graalpython/patches/metadata.toml index 86313d0ebe..b06d68ad2f 100644 --- a/graalpython/lib-graalpython/patches/metadata.toml +++ b/graalpython/lib-graalpython/patches/metadata.toml @@ -1070,6 +1070,11 @@ version = '== 0.7.8' patch = 'librt-0.7.8.patch' license = 'MIT' +[[librt.rules]] +version = '== 0.9.0' +patch = 'librt-0.9.0.patch' +license = 'MIT' + [[opencv-python.rules]] version = '== 4.12.*' patch = 'opencv-python-4.12.patch' diff --git a/mx.graalpython/downstream_tests.py b/mx.graalpython/downstream_tests.py index d255068010..77315b3d88 100644 --- a/mx.graalpython/downstream_tests.py +++ b/mx.graalpython/downstream_tests.py @@ -156,37 +156,60 @@ def downstream_test_pyo3(graalpy, testdir): src = testdir / 'pyo3' venv = src / 'venv' run([graalpy, '-m', 'venv', str(venv)]) - run_in_venv(venv, ['pip', 'install', 'nox']) - run_in_venv(venv, ['nox', '-s', 'test-py'], cwd=src) + run_in_venv(venv, ['python', '-m', 'pip', 'install', '--upgrade', 'pip', 'nox[uv]']) + env = os.environ.copy() + env['NOX_DEFAULT_VENV_BACKEND'] = 'uv' + run_in_venv(venv, ['nox', '-s', 'test-py'], cwd=src, env=env) @downstream_test('pydantic-core') def downstream_test_pydantic_core(graalpy, testdir): run(['git', 'clone', 'https://github.com/pydantic/pydantic.git', '-b', 'main', '--depth', '1'], cwd=testdir) - src = testdir / 'pydantic' / 'pydantic-core' - run(['uv', 'sync', '--python', graalpy, '--group', 'testing-extra', '--no-install-project'], cwd=src) - run(['uv', 'build', '--python', graalpy, '--wheel', '.'], cwd=src) - [wheel] = (src.parent / 'dist').glob('pydantic_core-*graalpy*.whl') - # uv doesn't accept wheels with devtags - if match := re.search('dev[0-9a-f]{6,}', wheel.name): - wheel = wheel.rename(wheel.parent / wheel.name.replace(match.group(), '')) - run(['uv', 'pip', 'install', wheel], cwd=src) + repo = testdir / 'pydantic' env = os.environ.copy() - env['HYPOTHESIS_PROFILE'] = 'slow' - run(['uv', 'run', '--no-sync', 'pytest', '-v', '--tb=short'], cwd=src, env=env) + env['UV_PYTHON_DOWNLOADS'] = 'never' + env['UV_PYTHON'] = str(graalpy) + # Needed for rpds-py dependency + env['UNSAFE_PYO3_SKIP_VERSION_CHECK'] = '1' + # Commands taken from upstream. The upstream has some TODOs so we'll likely need to sync this again soon + run( + ['uv', 'sync', '--directory', 'pydantic-core', '--group', 'testing-extra', '--no-install-package', 'pydantic-core'], + cwd=repo, + env=env, + ) + run( + [ + 'uv', 'sync', '--group', 'testing-extra', '--no-install-package', 'pydantic-core', + '--no-install-package', 'pytest-memray', '--no-install-package', 'memray', + '--no-install-package', 'pytest-codspeed', '--no-install-package', 'cffi', '--inexact', + # GraalPy change: greenlet crashes on import + '--no-install-package', 'greenlet', + ], + cwd=repo, + env=env, + ) + del env['UV_PYTHON'] + run( + ['uv', 'pip', 'install', './pydantic-core', '--no-deps', '--force-reinstall'], + cwd=repo, + env=env, + ) + run( + ['uv', 'run', '--no-sync', 'pytest', 'tests/pydantic_core', '--ignore=tests/pydantic_core/test_docstrings.py'], + cwd=repo, + env=env, + ) @downstream_test('jiter') def downstream_test_jiter(graalpy, testdir): run(['git', 'clone', 'https://github.com/pydantic/jiter.git', '-b', 'main', '--depth', '1'], cwd=testdir) src = testdir / 'jiter' - venv = src / 'venv' - run([graalpy, '-m', 'venv', str(venv)]) - run_in_venv(venv, ['pip', 'install', '-r', 'crates/jiter-python/tests/requirements.txt'], cwd=src) - run_in_venv(venv, ['pip', 'install', '-e', 'crates/jiter-python', - '--config-settings=build-args=--profile dev'], cwd=src) - run_in_venv(venv, ['pytest', '-v', '--tb=short', 'crates/jiter-python/tests'], cwd=src) - run_in_venv(venv, ['python', 'crates/jiter-python/bench.py', 'jiter', 'jiter-cache', '--fast'], cwd=src) + env = os.environ.copy() + env['UV_PYTHON_DOWNLOADS'] = 'never' + run(['uv', 'sync', '--python', graalpy, '--group', 'dev', '--all-packages'], cwd=src, env=env) + run(['uv', 'run', '--python', graalpy, 'pytest', '-v', '--tb=short', 'crates/jiter-python/tests'], cwd=src, env=env) + run(['uv', 'run', '--python', graalpy, 'crates/jiter-python/bench.py', 'jiter', 'jiter-cache', '--fast'], cwd=src, env=env) @downstream_test('cython') diff --git a/mx.graalpython/mx_graalpython.py b/mx.graalpython/mx_graalpython.py index 08eb20f22f..b4668ea43f 100644 --- a/mx.graalpython/mx_graalpython.py +++ b/mx.graalpython/mx_graalpython.py @@ -1947,7 +1947,7 @@ def graalpy_ext(*_): def dev_tag(_=None): - if not get_boolean_env('GRAALPYTHONDEVMODE', True) or 'dev' not in SUITE.release_version(): + if not get_boolean_env('GRAALPYTHONDEVMODE', False) or 'dev' not in SUITE.release_version(): mx.logv("GraalPy dev_tag: <0 because not in dev mode>") return ''