From dfcebae678d83ff45c171b2b5f7a2840cc3cd754 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Thu, 20 Aug 2026 11:57:48 +0200 Subject: [PATCH 01/24] Add option EnableAbi3t --- .../src/com/oracle/graal/python/runtime/PythonOptions.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java index f65751169a..962f9298e7 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java @@ -385,6 +385,9 @@ private PythonOptions() { @EngineOption @Option(category = OptionCategory.USER, usageSyntax = "true|false", help = "Emulate some Jython features that can cause performance degradation", stability = OptionStability.STABLE) // public static final OptionKey EmulateJython = new OptionKey<>(false); + @Option(category = OptionCategory.EXPERT, usageSyntax = "true|false", help = "Enable Python abi3t compatibility") // + public static final OptionKey EnableAbi3t = new OptionKey<>(false); + @EngineOption @Option(category = OptionCategory.EXPERT, usageSyntax = "true|false", help = "Enable tracing of native memory (ATTENTION: this will have significant impact on CExt execution performance).") // public static final OptionKey TraceNativeMemory = new OptionKey<>(false); From 09b80180940b7f59c62d53eeeca09356d7c1fad1 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Thu, 20 Aug 2026 11:58:25 +0200 Subject: [PATCH 02/24] Add ext suffixes for abi3t --- .../builtins/modules/ImpModuleBuiltins.java | 15 ++++++++++++--- .../oracle/graal/python/nodes/StringLiterals.java | 4 ++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ImpModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ImpModuleBuiltins.java index 01d1fbed92..cf7cbe2496 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ImpModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ImpModuleBuiltins.java @@ -50,6 +50,8 @@ import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___ORIGNAME__; import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___PATH__; import static com.oracle.graal.python.nodes.StringLiterals.J_PY_EXTENSION; +import static com.oracle.graal.python.nodes.StringLiterals.T_ABI3T_EXT_SO; +import static com.oracle.graal.python.nodes.StringLiterals.T_ABI3T_MULTIARCH; import static com.oracle.graal.python.nodes.StringLiterals.T_EXT_PYD; import static com.oracle.graal.python.nodes.StringLiterals.T_EXT_SO; import static com.oracle.graal.python.nodes.StringLiterals.T_NAME; @@ -830,9 +832,16 @@ protected ArgumentClinicProvider getArgumentClinic() { @GenerateNodeFactory public abstract static class ExtensionSuffixesNode extends PythonBuiltinNode { @Specialization - Object run( - @Bind PythonLanguage language) { - return PFactory.createList(language, new Object[]{PythonContext.get(this).getExtensionSuffix(), T_EXT_SO, T_EXT_PYD}); + @TruffleBoundary + static Object run( + @Bind PythonLanguage language, + @Bind PythonContext context) { + if (context.getOption(PythonOptions.EnableAbi3t)) { + TruffleString multiarch = PythonLanguage.getPlatformInfo().multiarch(); + TruffleString abi3tMultiarch = T_ABI3T_MULTIARCH.concatUncached(multiarch, TS_ENCODING, false).concatUncached(T_EXT_SO, TS_ENCODING, false); + return PFactory.createList(language, new Object[]{context.getExtensionSuffix(), abi3tMultiarch, T_ABI3T_EXT_SO, T_EXT_SO, T_EXT_PYD}); + } + return PFactory.createList(language, new Object[]{context.getExtensionSuffix(), T_EXT_SO, T_EXT_PYD}); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/StringLiterals.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/StringLiterals.java index a13d0082df..56f54dd10a 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/StringLiterals.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/StringLiterals.java @@ -114,6 +114,10 @@ public abstract class StringLiterals { public static final String J_TYPE_ID = "_typeid"; public static final TruffleString T_TYPE_ID = tsLiteral(J_TYPE_ID); public static final String J_LIB_PREFIX = "lib"; + public static final String J_ABI3T_MULTIARCH = ".abi3t-"; + public static final TruffleString T_ABI3T_MULTIARCH = tsLiteral(J_ABI3T_MULTIARCH); + public static final String J_ABI3T_EXT_SO = ".abi3t.so"; + public static final TruffleString T_ABI3T_EXT_SO= tsLiteral(J_ABI3T_EXT_SO); public static final String J_EXT_SO = ".so"; public static final TruffleString T_EXT_SO = tsLiteral(J_EXT_SO); public static final String J_EXT_PYD = ".pyd"; From e188d6f94a6e42024ebf250a06b75656203b771d Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Thu, 20 Aug 2026 15:55:42 +0200 Subject: [PATCH 03/24] Add ABI param to CApiBuiltin --- .../python/builtins/modules/cext/PythonCextBuiltins.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextBuiltins.java index 49db8bed93..ac08c8faf6 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextBuiltins.java @@ -763,6 +763,11 @@ public enum CApiCallPath { Ignored, } + public enum ABI { + GRAALPY, + ABI3T + } + @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.SOURCE) public @interface CApiBuiltins { @@ -824,6 +829,9 @@ public enum CApiCallPath { * Comment to explain, e.g., why a builtin is ignored. */ String comment() default ""; + + /** Specifies to which ABIs this builtin belongs to. A builtin can belong to several ABIs */ + ABI[] abi() default {ABI.GRAALPY}; } @CApiBuiltin(ret = PyObjectRawPointer, call = Ignored) From f0ba05ffa24723dd2274129c8db76a37e167ad79 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Thu, 20 Aug 2026 17:20:16 +0200 Subject: [PATCH 04/24] Add ABI3T native export audit --- abi/abi3t-linux.txt | 958 +++++++++++++++++++++++++++++ abi/abi3t-missing-linux.txt | 71 +++ mx.graalpython/mx_graalpython.py | 99 +++ scripts/generate_abi3t_manifest.py | 104 ++++ 4 files changed, 1232 insertions(+) create mode 100644 abi/abi3t-linux.txt create mode 100644 abi/abi3t-missing-linux.txt create mode 100644 scripts/generate_abi3t_manifest.py diff --git a/abi/abi3t-linux.txt b/abi/abi3t-linux.txt new file mode 100644 index 0000000000..9d38f0f3f8 --- /dev/null +++ b/abi/abi3t-linux.txt @@ -0,0 +1,958 @@ +# Generated by scripts/generate_abi3t_manifest.py. Do not edit manually. +# Source: CPython v3.15.0rc1 (37e98da7c19a9e5892ee756d6dee08225422cd49) +# Source file: Misc/stable_abi.toml +# Target: Linux ABI3T (MS_WINDOWS, Py_REF_DEBUG, and USE_STACKCHECK disabled) +# Format: + +FUNC PyABIInfo_Check +FUNC PyAIter_Check +FUNC PyArg_Parse +FUNC PyArg_ParseTuple +FUNC PyArg_ParseTupleAndKeywords +FUNC PyArg_UnpackTuple +FUNC PyArg_VaParse +FUNC PyArg_VaParseTupleAndKeywords +FUNC PyArg_ValidateKeywordArguments +OBJECT PyBaseObject_Type +FUNC PyBool_FromLong +OBJECT PyBool_Type +FUNC PyBuffer_FillContiguousStrides +FUNC PyBuffer_FillInfo +FUNC PyBuffer_FromContiguous +FUNC PyBuffer_GetPointer +FUNC PyBuffer_IsContiguous +FUNC PyBuffer_Release +FUNC PyBuffer_SizeFromFormat +FUNC PyBuffer_ToContiguous +OBJECT PyByteArrayIter_Type +FUNC PyByteArray_AsString +FUNC PyByteArray_Concat +FUNC PyByteArray_FromObject +FUNC PyByteArray_FromStringAndSize +FUNC PyByteArray_Resize +FUNC PyByteArray_Size +OBJECT PyByteArray_Type +OBJECT PyBytesIter_Type +FUNC PyBytes_AsString +FUNC PyBytes_AsStringAndSize +FUNC PyBytes_Concat +FUNC PyBytes_ConcatAndDel +FUNC PyBytes_DecodeEscape +FUNC PyBytes_FromFormat +FUNC PyBytes_FromFormatV +FUNC PyBytes_FromObject +FUNC PyBytes_FromString +FUNC PyBytes_FromStringAndSize +FUNC PyBytes_Repr +FUNC PyBytes_Size +OBJECT PyBytes_Type +FUNC PyCFunction_Call +FUNC PyCFunction_GetFlags +FUNC PyCFunction_GetFunction +FUNC PyCFunction_GetSelf +FUNC PyCFunction_New +FUNC PyCFunction_NewEx +OBJECT PyCFunction_Type +FUNC PyCMethod_New +FUNC PyCallIter_New +OBJECT PyCallIter_Type +FUNC PyCallable_Check +FUNC PyCapsule_GetContext +FUNC PyCapsule_GetDestructor +FUNC PyCapsule_GetName +FUNC PyCapsule_GetPointer +FUNC PyCapsule_Import +FUNC PyCapsule_IsValid +FUNC PyCapsule_New +FUNC PyCapsule_SetContext +FUNC PyCapsule_SetDestructor +FUNC PyCapsule_SetName +FUNC PyCapsule_SetPointer +OBJECT PyCapsule_Type +OBJECT PyClassMethodDescr_Type +FUNC PyCodec_BackslashReplaceErrors +FUNC PyCodec_Decode +FUNC PyCodec_Decoder +FUNC PyCodec_Encode +FUNC PyCodec_Encoder +FUNC PyCodec_IgnoreErrors +FUNC PyCodec_IncrementalDecoder +FUNC PyCodec_IncrementalEncoder +FUNC PyCodec_KnownEncoding +FUNC PyCodec_LookupError +FUNC PyCodec_NameReplaceErrors +FUNC PyCodec_Register +FUNC PyCodec_RegisterError +FUNC PyCodec_ReplaceErrors +FUNC PyCodec_StreamReader +FUNC PyCodec_StreamWriter +FUNC PyCodec_StrictErrors +FUNC PyCodec_Unregister +FUNC PyCodec_XMLCharRefReplaceErrors +FUNC PyComplex_FromDoubles +FUNC PyComplex_ImagAsDouble +FUNC PyComplex_RealAsDouble +OBJECT PyComplex_Type +FUNC PyCriticalSection2_Begin +FUNC PyCriticalSection2_End +FUNC PyCriticalSection_Begin +FUNC PyCriticalSection_End +FUNC PyDescr_NewClassMethod +FUNC PyDescr_NewGetSet +FUNC PyDescr_NewMember +FUNC PyDescr_NewMethod +OBJECT PyDictItems_Type +OBJECT PyDictIterItem_Type +OBJECT PyDictIterKey_Type +OBJECT PyDictIterValue_Type +OBJECT PyDictKeys_Type +FUNC PyDictProxy_New +OBJECT PyDictProxy_Type +OBJECT PyDictRevIterItem_Type +OBJECT PyDictRevIterKey_Type +OBJECT PyDictRevIterValue_Type +OBJECT PyDictValues_Type +FUNC PyDict_Clear +FUNC PyDict_Contains +FUNC PyDict_Copy +FUNC PyDict_DelItem +FUNC PyDict_DelItemString +FUNC PyDict_GetItem +FUNC PyDict_GetItemRef +FUNC PyDict_GetItemString +FUNC PyDict_GetItemStringRef +FUNC PyDict_GetItemWithError +FUNC PyDict_Items +FUNC PyDict_Keys +FUNC PyDict_Merge +FUNC PyDict_MergeFromSeq2 +FUNC PyDict_New +FUNC PyDict_Next +FUNC PyDict_SetDefaultRef +FUNC PyDict_SetItem +FUNC PyDict_SetItemString +FUNC PyDict_Size +OBJECT PyDict_Type +FUNC PyDict_Update +FUNC PyDict_Values +OBJECT PyEllipsis_Type +OBJECT PyEnum_Type +FUNC PyErr_BadArgument +FUNC PyErr_BadInternalCall +FUNC PyErr_CheckSignals +FUNC PyErr_Clear +FUNC PyErr_Display +FUNC PyErr_DisplayException +FUNC PyErr_ExceptionMatches +FUNC PyErr_Fetch +FUNC PyErr_Format +FUNC PyErr_FormatV +FUNC PyErr_GetExcInfo +FUNC PyErr_GetHandledException +FUNC PyErr_GetRaisedException +FUNC PyErr_GivenExceptionMatches +FUNC PyErr_NewException +FUNC PyErr_NewExceptionWithDoc +FUNC PyErr_NoMemory +FUNC PyErr_NormalizeException +FUNC PyErr_Occurred +FUNC PyErr_Print +FUNC PyErr_PrintEx +FUNC PyErr_ProgramText +FUNC PyErr_ResourceWarning +FUNC PyErr_Restore +FUNC PyErr_SetExcInfo +FUNC PyErr_SetFromErrno +FUNC PyErr_SetFromErrnoWithFilename +FUNC PyErr_SetFromErrnoWithFilenameObject +FUNC PyErr_SetFromErrnoWithFilenameObjects +FUNC PyErr_SetHandledException +FUNC PyErr_SetImportError +FUNC PyErr_SetImportErrorSubclass +FUNC PyErr_SetInterrupt +FUNC PyErr_SetInterruptEx +FUNC PyErr_SetNone +FUNC PyErr_SetObject +FUNC PyErr_SetRaisedException +FUNC PyErr_SetString +FUNC PyErr_SyntaxLocation +FUNC PyErr_SyntaxLocationEx +FUNC PyErr_WarnEx +FUNC PyErr_WarnExplicit +FUNC PyErr_WarnFormat +FUNC PyErr_WriteUnraisable +FUNC PyEval_AcquireLock +FUNC PyEval_AcquireThread +FUNC PyEval_CallFunction +FUNC PyEval_CallMethod +FUNC PyEval_CallObjectWithKeywords +FUNC PyEval_EvalCode +FUNC PyEval_EvalCodeEx +FUNC PyEval_EvalFrame +FUNC PyEval_EvalFrameEx +FUNC PyEval_GetBuiltins +FUNC PyEval_GetFrame +FUNC PyEval_GetFrameBuiltins +FUNC PyEval_GetFrameGlobals +FUNC PyEval_GetFrameLocals +FUNC PyEval_GetFuncDesc +FUNC PyEval_GetFuncName +FUNC PyEval_GetGlobals +FUNC PyEval_GetLocals +FUNC PyEval_InitThreads +FUNC PyEval_ReleaseLock +FUNC PyEval_ReleaseThread +FUNC PyEval_RestoreThread +FUNC PyEval_SaveThread +FUNC PyEval_ThreadsInitialized +OBJECT PyExc_ArithmeticError +OBJECT PyExc_AssertionError +OBJECT PyExc_AttributeError +OBJECT PyExc_BaseException +OBJECT PyExc_BaseExceptionGroup +OBJECT PyExc_BlockingIOError +OBJECT PyExc_BrokenPipeError +OBJECT PyExc_BufferError +OBJECT PyExc_BytesWarning +OBJECT PyExc_ChildProcessError +OBJECT PyExc_ConnectionAbortedError +OBJECT PyExc_ConnectionError +OBJECT PyExc_ConnectionRefusedError +OBJECT PyExc_ConnectionResetError +OBJECT PyExc_DeprecationWarning +OBJECT PyExc_EOFError +OBJECT PyExc_EncodingWarning +OBJECT PyExc_EnvironmentError +OBJECT PyExc_Exception +OBJECT PyExc_FileExistsError +OBJECT PyExc_FileNotFoundError +OBJECT PyExc_FloatingPointError +OBJECT PyExc_FutureWarning +OBJECT PyExc_GeneratorExit +OBJECT PyExc_IOError +OBJECT PyExc_ImportError +OBJECT PyExc_ImportWarning +OBJECT PyExc_IndentationError +OBJECT PyExc_IndexError +OBJECT PyExc_InterruptedError +OBJECT PyExc_IsADirectoryError +OBJECT PyExc_KeyError +OBJECT PyExc_KeyboardInterrupt +OBJECT PyExc_LookupError +OBJECT PyExc_MemoryError +OBJECT PyExc_ModuleNotFoundError +OBJECT PyExc_NameError +OBJECT PyExc_NotADirectoryError +OBJECT PyExc_NotImplementedError +OBJECT PyExc_OSError +OBJECT PyExc_OverflowError +OBJECT PyExc_PendingDeprecationWarning +OBJECT PyExc_PermissionError +OBJECT PyExc_ProcessLookupError +OBJECT PyExc_RecursionError +OBJECT PyExc_ReferenceError +OBJECT PyExc_ResourceWarning +OBJECT PyExc_RuntimeError +OBJECT PyExc_RuntimeWarning +OBJECT PyExc_StopAsyncIteration +OBJECT PyExc_StopIteration +OBJECT PyExc_SyntaxError +OBJECT PyExc_SyntaxWarning +OBJECT PyExc_SystemError +OBJECT PyExc_SystemExit +OBJECT PyExc_TabError +OBJECT PyExc_TimeoutError +OBJECT PyExc_TypeError +OBJECT PyExc_UnboundLocalError +OBJECT PyExc_UnicodeDecodeError +OBJECT PyExc_UnicodeEncodeError +OBJECT PyExc_UnicodeError +OBJECT PyExc_UnicodeTranslateError +OBJECT PyExc_UnicodeWarning +OBJECT PyExc_UserWarning +OBJECT PyExc_ValueError +OBJECT PyExc_Warning +OBJECT PyExc_ZeroDivisionError +FUNC PyExceptionClass_Name +FUNC PyException_GetArgs +FUNC PyException_GetCause +FUNC PyException_GetContext +FUNC PyException_GetTraceback +FUNC PyException_SetArgs +FUNC PyException_SetCause +FUNC PyException_SetContext +FUNC PyException_SetTraceback +FUNC PyFile_FromFd +FUNC PyFile_GetLine +FUNC PyFile_WriteObject +FUNC PyFile_WriteString +OBJECT PyFilter_Type +FUNC PyFloat_AsDouble +FUNC PyFloat_FromDouble +FUNC PyFloat_FromString +FUNC PyFloat_GetInfo +FUNC PyFloat_GetMax +FUNC PyFloat_GetMin +OBJECT PyFloat_Type +FUNC PyFrame_GetCode +FUNC PyFrame_GetLineNumber +FUNC PyFrozenSet_New +OBJECT PyFrozenSet_Type +FUNC PyGC_Collect +FUNC PyGC_Disable +FUNC PyGC_Enable +FUNC PyGC_IsEnabled +FUNC PyGILState_Ensure +FUNC PyGILState_GetThisThreadState +FUNC PyGILState_Release +OBJECT PyGetSetDescr_Type +FUNC PyImport_AddModule +FUNC PyImport_AddModuleObject +FUNC PyImport_AddModuleRef +FUNC PyImport_AppendInittab +FUNC PyImport_ExecCodeModule +FUNC PyImport_ExecCodeModuleEx +FUNC PyImport_ExecCodeModuleObject +FUNC PyImport_ExecCodeModuleWithPathnames +FUNC PyImport_GetImporter +FUNC PyImport_GetMagicNumber +FUNC PyImport_GetMagicTag +FUNC PyImport_GetModule +FUNC PyImport_GetModuleDict +FUNC PyImport_Import +FUNC PyImport_ImportFrozenModule +FUNC PyImport_ImportFrozenModuleObject +FUNC PyImport_ImportModule +FUNC PyImport_ImportModuleLevel +FUNC PyImport_ImportModuleLevelObject +FUNC PyImport_ImportModuleNoBlock +FUNC PyImport_ReloadModule +FUNC PyIndex_Check +FUNC PyInterpreterGuard_Close +FUNC PyInterpreterGuard_FromCurrent +FUNC PyInterpreterGuard_FromView +FUNC PyInterpreterState_Clear +FUNC PyInterpreterState_Delete +FUNC PyInterpreterState_Get +FUNC PyInterpreterState_GetDict +FUNC PyInterpreterState_GetID +FUNC PyInterpreterState_New +FUNC PyInterpreterView_Close +FUNC PyInterpreterView_FromCurrent +FUNC PyInterpreterView_FromMain +FUNC PyIter_Check +FUNC PyIter_Next +FUNC PyIter_NextItem +FUNC PyIter_Send +OBJECT PyListIter_Type +OBJECT PyListRevIter_Type +FUNC PyList_Append +FUNC PyList_AsTuple +FUNC PyList_GetItem +FUNC PyList_GetItemRef +FUNC PyList_GetSlice +FUNC PyList_Insert +FUNC PyList_New +FUNC PyList_Reverse +FUNC PyList_SetItem +FUNC PyList_SetSlice +FUNC PyList_Size +FUNC PyList_Sort +OBJECT PyList_Type +OBJECT PyLongRangeIter_Type +FUNC PyLongWriter_Create +FUNC PyLongWriter_Discard +FUNC PyLongWriter_Finish +FUNC PyLong_AsDouble +FUNC PyLong_AsInt +FUNC PyLong_AsInt32 +FUNC PyLong_AsInt64 +FUNC PyLong_AsLong +FUNC PyLong_AsLongAndOverflow +FUNC PyLong_AsLongLong +FUNC PyLong_AsLongLongAndOverflow +FUNC PyLong_AsNativeBytes +FUNC PyLong_AsSize_t +FUNC PyLong_AsSsize_t +FUNC PyLong_AsUInt32 +FUNC PyLong_AsUInt64 +FUNC PyLong_AsUnsignedLong +FUNC PyLong_AsUnsignedLongLong +FUNC PyLong_AsUnsignedLongLongMask +FUNC PyLong_AsUnsignedLongMask +FUNC PyLong_AsVoidPtr +FUNC PyLong_Export +FUNC PyLong_FreeExport +FUNC PyLong_FromDouble +FUNC PyLong_FromInt32 +FUNC PyLong_FromInt64 +FUNC PyLong_FromLong +FUNC PyLong_FromLongLong +FUNC PyLong_FromNativeBytes +FUNC PyLong_FromSize_t +FUNC PyLong_FromSsize_t +FUNC PyLong_FromString +FUNC PyLong_FromUInt32 +FUNC PyLong_FromUInt64 +FUNC PyLong_FromUnsignedLong +FUNC PyLong_FromUnsignedLongLong +FUNC PyLong_FromUnsignedNativeBytes +FUNC PyLong_FromVoidPtr +FUNC PyLong_GetInfo +FUNC PyLong_GetNativeLayout +OBJECT PyLong_Type +OBJECT PyMap_Type +FUNC PyMapping_Check +FUNC PyMapping_GetItemString +FUNC PyMapping_GetOptionalItem +FUNC PyMapping_GetOptionalItemString +FUNC PyMapping_HasKey +FUNC PyMapping_HasKeyString +FUNC PyMapping_HasKeyStringWithError +FUNC PyMapping_HasKeyWithError +FUNC PyMapping_Items +FUNC PyMapping_Keys +FUNC PyMapping_Length +FUNC PyMapping_SetItemString +FUNC PyMapping_Size +FUNC PyMapping_Values +FUNC PyMarshal_ReadObjectFromString +FUNC PyMarshal_WriteObjectToString +FUNC PyMem_Calloc +FUNC PyMem_Free +FUNC PyMem_Malloc +FUNC PyMem_RawCalloc +FUNC PyMem_RawFree +FUNC PyMem_RawMalloc +FUNC PyMem_RawRealloc +FUNC PyMem_Realloc +OBJECT PyMemberDescr_Type +FUNC PyMember_GetOne +FUNC PyMember_SetOne +FUNC PyMemoryView_FromBuffer +FUNC PyMemoryView_FromMemory +FUNC PyMemoryView_FromObject +FUNC PyMemoryView_GetContiguous +OBJECT PyMemoryView_Type +OBJECT PyMethodDescr_Type +FUNC PyModuleDef_Init +OBJECT PyModuleDef_Type +FUNC PyModule_Add +FUNC PyModule_AddFunctions +FUNC PyModule_AddIntConstant +FUNC PyModule_AddObject +FUNC PyModule_AddObjectRef +FUNC PyModule_AddStringConstant +FUNC PyModule_AddType +FUNC PyModule_Create2 +FUNC PyModule_Exec +FUNC PyModule_ExecDef +FUNC PyModule_FromDefAndSpec2 +FUNC PyModule_FromSlotsAndSpec +FUNC PyModule_GetDef +FUNC PyModule_GetDict +FUNC PyModule_GetFilename +FUNC PyModule_GetFilenameObject +FUNC PyModule_GetName +FUNC PyModule_GetNameObject +FUNC PyModule_GetState +FUNC PyModule_GetStateSize +FUNC PyModule_GetState_DuringGC +FUNC PyModule_GetToken +FUNC PyModule_GetToken_DuringGC +FUNC PyModule_New +FUNC PyModule_NewObject +FUNC PyModule_SetDocString +OBJECT PyModule_Type +FUNC PyNumber_Absolute +FUNC PyNumber_Add +FUNC PyNumber_And +FUNC PyNumber_AsSsize_t +FUNC PyNumber_Check +FUNC PyNumber_Divmod +FUNC PyNumber_Float +FUNC PyNumber_FloorDivide +FUNC PyNumber_InPlaceAdd +FUNC PyNumber_InPlaceAnd +FUNC PyNumber_InPlaceFloorDivide +FUNC PyNumber_InPlaceLshift +FUNC PyNumber_InPlaceMatrixMultiply +FUNC PyNumber_InPlaceMultiply +FUNC PyNumber_InPlaceOr +FUNC PyNumber_InPlacePower +FUNC PyNumber_InPlaceRemainder +FUNC PyNumber_InPlaceRshift +FUNC PyNumber_InPlaceSubtract +FUNC PyNumber_InPlaceTrueDivide +FUNC PyNumber_InPlaceXor +FUNC PyNumber_Index +FUNC PyNumber_Invert +FUNC PyNumber_Long +FUNC PyNumber_Lshift +FUNC PyNumber_MatrixMultiply +FUNC PyNumber_Multiply +FUNC PyNumber_Negative +FUNC PyNumber_Or +FUNC PyNumber_Positive +FUNC PyNumber_Power +FUNC PyNumber_Remainder +FUNC PyNumber_Rshift +FUNC PyNumber_Subtract +FUNC PyNumber_ToBase +FUNC PyNumber_TrueDivide +FUNC PyNumber_Xor +FUNC PyOS_AfterFork +FUNC PyOS_AfterFork_Child +FUNC PyOS_AfterFork_Parent +FUNC PyOS_BeforeFork +FUNC PyOS_FSPath +OBJECT PyOS_InputHook +FUNC PyOS_InterruptOccurred +FUNC PyOS_double_to_string +FUNC PyOS_getsig +FUNC PyOS_mystricmp +FUNC PyOS_mystrnicmp +FUNC PyOS_setsig +FUNC PyOS_snprintf +FUNC PyOS_string_to_double +FUNC PyOS_strtol +FUNC PyOS_strtoul +FUNC PyOS_vsnprintf +FUNC PyObject_ASCII +FUNC PyObject_AsCharBuffer +FUNC PyObject_AsFileDescriptor +FUNC PyObject_AsReadBuffer +FUNC PyObject_AsWriteBuffer +FUNC PyObject_Bytes +FUNC PyObject_Call +FUNC PyObject_CallFinalizerFromDealloc +FUNC PyObject_CallFunction +FUNC PyObject_CallFunctionObjArgs +FUNC PyObject_CallMethod +FUNC PyObject_CallMethodObjArgs +FUNC PyObject_CallNoArgs +FUNC PyObject_CallObject +FUNC PyObject_Calloc +FUNC PyObject_CheckBuffer +FUNC PyObject_CheckReadBuffer +FUNC PyObject_ClearWeakRefs +FUNC PyObject_CopyData +FUNC PyObject_DelAttr +FUNC PyObject_DelAttrString +FUNC PyObject_DelItem +FUNC PyObject_DelItemString +FUNC PyObject_Dir +FUNC PyObject_Format +FUNC PyObject_Free +FUNC PyObject_GC_Del +FUNC PyObject_GC_IsFinalized +FUNC PyObject_GC_IsTracked +FUNC PyObject_GC_Track +FUNC PyObject_GC_UnTrack +FUNC PyObject_GenericGetAttr +FUNC PyObject_GenericGetDict +FUNC PyObject_GenericSetAttr +FUNC PyObject_GenericSetDict +FUNC PyObject_GetAIter +FUNC PyObject_GetAttr +FUNC PyObject_GetAttrString +FUNC PyObject_GetBuffer +FUNC PyObject_GetItem +FUNC PyObject_GetIter +FUNC PyObject_GetOptionalAttr +FUNC PyObject_GetOptionalAttrString +FUNC PyObject_GetTypeData +FUNC PyObject_GetTypeData_DuringGC +FUNC PyObject_HasAttr +FUNC PyObject_HasAttrString +FUNC PyObject_HasAttrStringWithError +FUNC PyObject_HasAttrWithError +FUNC PyObject_Hash +FUNC PyObject_HashNotImplemented +FUNC PyObject_Init +FUNC PyObject_InitVar +FUNC PyObject_IsInstance +FUNC PyObject_IsSubclass +FUNC PyObject_IsTrue +FUNC PyObject_Length +FUNC PyObject_Malloc +FUNC PyObject_Not +FUNC PyObject_Realloc +FUNC PyObject_Repr +FUNC PyObject_RichCompare +FUNC PyObject_RichCompareBool +FUNC PyObject_SelfIter +FUNC PyObject_SetAttr +FUNC PyObject_SetAttrString +FUNC PyObject_SetItem +FUNC PyObject_Size +FUNC PyObject_Str +FUNC PyObject_Type +FUNC PyObject_Vectorcall +FUNC PyObject_VectorcallMethod +OBJECT PyProperty_Type +OBJECT PyRangeIter_Type +OBJECT PyRange_Type +OBJECT PyReversed_Type +FUNC PySeqIter_New +OBJECT PySeqIter_Type +FUNC PySequence_Check +FUNC PySequence_Concat +FUNC PySequence_Contains +FUNC PySequence_Count +FUNC PySequence_DelItem +FUNC PySequence_DelSlice +FUNC PySequence_Fast +FUNC PySequence_GetItem +FUNC PySequence_GetSlice +FUNC PySequence_In +FUNC PySequence_InPlaceConcat +FUNC PySequence_InPlaceRepeat +FUNC PySequence_Index +FUNC PySequence_Length +FUNC PySequence_List +FUNC PySequence_Repeat +FUNC PySequence_SetItem +FUNC PySequence_SetSlice +FUNC PySequence_Size +FUNC PySequence_Tuple +OBJECT PySetIter_Type +FUNC PySet_Add +FUNC PySet_Clear +FUNC PySet_Contains +FUNC PySet_Discard +FUNC PySet_New +FUNC PySet_Pop +FUNC PySet_Size +OBJECT PySet_Type +FUNC PySlice_AdjustIndices +FUNC PySlice_GetIndices +FUNC PySlice_GetIndicesEx +FUNC PySlice_New +OBJECT PySlice_Type +FUNC PySlice_Unpack +FUNC PyState_AddModule +FUNC PyState_FindModule +FUNC PyState_RemoveModule +FUNC PyStructSequence_GetItem +FUNC PyStructSequence_New +FUNC PyStructSequence_NewType +FUNC PyStructSequence_SetItem +OBJECT PyStructSequence_UnnamedField +OBJECT PySuper_Type +FUNC PySys_AddWarnOption +FUNC PySys_AddWarnOptionUnicode +FUNC PySys_AddXOption +FUNC PySys_Audit +FUNC PySys_AuditTuple +FUNC PySys_FormatStderr +FUNC PySys_FormatStdout +FUNC PySys_GetAttr +FUNC PySys_GetAttrString +FUNC PySys_GetObject +FUNC PySys_GetOptionalAttr +FUNC PySys_GetOptionalAttrString +FUNC PySys_GetXOptions +FUNC PySys_HasWarnOptions +FUNC PySys_ResetWarnOptions +FUNC PySys_SetArgv +FUNC PySys_SetArgvEx +FUNC PySys_SetObject +FUNC PySys_SetPath +FUNC PySys_WriteStderr +FUNC PySys_WriteStdout +FUNC PyThreadState_Clear +FUNC PyThreadState_Delete +FUNC PyThreadState_DeleteCurrent +FUNC PyThreadState_Ensure +FUNC PyThreadState_EnsureFromView +FUNC PyThreadState_Get +FUNC PyThreadState_GetDict +FUNC PyThreadState_GetFrame +FUNC PyThreadState_GetID +FUNC PyThreadState_GetInterpreter +FUNC PyThreadState_New +FUNC PyThreadState_Release +FUNC PyThreadState_SetAsyncExc +FUNC PyThreadState_Swap +FUNC PyThread_GetInfo +FUNC PyThread_ReInitTLS +FUNC PyThread_acquire_lock +FUNC PyThread_acquire_lock_timed +FUNC PyThread_allocate_lock +FUNC PyThread_create_key +FUNC PyThread_delete_key +FUNC PyThread_delete_key_value +FUNC PyThread_exit_thread +FUNC PyThread_free_lock +FUNC PyThread_get_key_value +FUNC PyThread_get_stacksize +FUNC PyThread_get_thread_ident +FUNC PyThread_get_thread_native_id +FUNC PyThread_init_thread +FUNC PyThread_release_lock +FUNC PyThread_set_key_value +FUNC PyThread_set_stacksize +FUNC PyThread_start_new_thread +FUNC PyThread_tss_alloc +FUNC PyThread_tss_create +FUNC PyThread_tss_delete +FUNC PyThread_tss_free +FUNC PyThread_tss_get +FUNC PyThread_tss_is_created +FUNC PyThread_tss_set +FUNC PyTraceBack_Here +FUNC PyTraceBack_Print +OBJECT PyTraceBack_Type +OBJECT PyTupleIter_Type +FUNC PyTuple_GetItem +FUNC PyTuple_GetSlice +FUNC PyTuple_New +FUNC PyTuple_Pack +FUNC PyTuple_SetItem +FUNC PyTuple_Size +OBJECT PyTuple_Type +FUNC PyType_ClearCache +FUNC PyType_Freeze +FUNC PyType_FromMetaclass +FUNC PyType_FromModuleAndSpec +FUNC PyType_FromSlots +FUNC PyType_FromSpec +FUNC PyType_FromSpecWithBases +FUNC PyType_GenericAlloc +FUNC PyType_GenericNew +FUNC PyType_GetBaseByToken +FUNC PyType_GetBaseByToken_DuringGC +FUNC PyType_GetFlags +FUNC PyType_GetFullyQualifiedName +FUNC PyType_GetModule +FUNC PyType_GetModuleByDef +FUNC PyType_GetModuleByToken +FUNC PyType_GetModuleByToken_DuringGC +FUNC PyType_GetModuleName +FUNC PyType_GetModuleState +FUNC PyType_GetModuleState_DuringGC +FUNC PyType_GetModule_DuringGC +FUNC PyType_GetName +FUNC PyType_GetQualName +FUNC PyType_GetSlot +FUNC PyType_GetTypeDataSize +FUNC PyType_IsSubtype +FUNC PyType_Modified +FUNC PyType_Ready +OBJECT PyType_Type +FUNC PyUnicodeDecodeError_Create +FUNC PyUnicodeDecodeError_GetEncoding +FUNC PyUnicodeDecodeError_GetEnd +FUNC PyUnicodeDecodeError_GetObject +FUNC PyUnicodeDecodeError_GetReason +FUNC PyUnicodeDecodeError_GetStart +FUNC PyUnicodeDecodeError_SetEnd +FUNC PyUnicodeDecodeError_SetReason +FUNC PyUnicodeDecodeError_SetStart +FUNC PyUnicodeEncodeError_GetEncoding +FUNC PyUnicodeEncodeError_GetEnd +FUNC PyUnicodeEncodeError_GetObject +FUNC PyUnicodeEncodeError_GetReason +FUNC PyUnicodeEncodeError_GetStart +FUNC PyUnicodeEncodeError_SetEnd +FUNC PyUnicodeEncodeError_SetReason +FUNC PyUnicodeEncodeError_SetStart +OBJECT PyUnicodeIter_Type +FUNC PyUnicodeTranslateError_GetEnd +FUNC PyUnicodeTranslateError_GetObject +FUNC PyUnicodeTranslateError_GetReason +FUNC PyUnicodeTranslateError_GetStart +FUNC PyUnicodeTranslateError_SetEnd +FUNC PyUnicodeTranslateError_SetReason +FUNC PyUnicodeTranslateError_SetStart +FUNC PyUnicode_Append +FUNC PyUnicode_AppendAndDel +FUNC PyUnicode_AsASCIIString +FUNC PyUnicode_AsCharmapString +FUNC PyUnicode_AsDecodedObject +FUNC PyUnicode_AsDecodedUnicode +FUNC PyUnicode_AsEncodedObject +FUNC PyUnicode_AsEncodedString +FUNC PyUnicode_AsEncodedUnicode +FUNC PyUnicode_AsLatin1String +FUNC PyUnicode_AsRawUnicodeEscapeString +FUNC PyUnicode_AsUCS4 +FUNC PyUnicode_AsUCS4Copy +FUNC PyUnicode_AsUTF16String +FUNC PyUnicode_AsUTF32String +FUNC PyUnicode_AsUTF8AndSize +FUNC PyUnicode_AsUTF8String +FUNC PyUnicode_AsUnicodeEscapeString +FUNC PyUnicode_AsWideChar +FUNC PyUnicode_AsWideCharString +FUNC PyUnicode_BuildEncodingMap +FUNC PyUnicode_Compare +FUNC PyUnicode_CompareWithASCIIString +FUNC PyUnicode_Concat +FUNC PyUnicode_Contains +FUNC PyUnicode_Count +FUNC PyUnicode_Decode +FUNC PyUnicode_DecodeASCII +FUNC PyUnicode_DecodeCharmap +FUNC PyUnicode_DecodeFSDefault +FUNC PyUnicode_DecodeFSDefaultAndSize +FUNC PyUnicode_DecodeLatin1 +FUNC PyUnicode_DecodeLocale +FUNC PyUnicode_DecodeLocaleAndSize +FUNC PyUnicode_DecodeRawUnicodeEscape +FUNC PyUnicode_DecodeUTF16 +FUNC PyUnicode_DecodeUTF16Stateful +FUNC PyUnicode_DecodeUTF32 +FUNC PyUnicode_DecodeUTF32Stateful +FUNC PyUnicode_DecodeUTF7 +FUNC PyUnicode_DecodeUTF7Stateful +FUNC PyUnicode_DecodeUTF8 +FUNC PyUnicode_DecodeUTF8Stateful +FUNC PyUnicode_DecodeUnicodeEscape +FUNC PyUnicode_EncodeFSDefault +FUNC PyUnicode_EncodeLocale +FUNC PyUnicode_Equal +FUNC PyUnicode_EqualToUTF8 +FUNC PyUnicode_EqualToUTF8AndSize +FUNC PyUnicode_FSConverter +FUNC PyUnicode_FSDecoder +FUNC PyUnicode_Find +FUNC PyUnicode_FindChar +FUNC PyUnicode_Format +FUNC PyUnicode_FromEncodedObject +FUNC PyUnicode_FromFormat +FUNC PyUnicode_FromFormatV +FUNC PyUnicode_FromObject +FUNC PyUnicode_FromOrdinal +FUNC PyUnicode_FromString +FUNC PyUnicode_FromStringAndSize +FUNC PyUnicode_FromWideChar +FUNC PyUnicode_GetDefaultEncoding +FUNC PyUnicode_GetLength +FUNC PyUnicode_GetSize +FUNC PyUnicode_InternFromString +FUNC PyUnicode_InternImmortal +FUNC PyUnicode_InternInPlace +FUNC PyUnicode_IsIdentifier +FUNC PyUnicode_Join +FUNC PyUnicode_Partition +FUNC PyUnicode_RPartition +FUNC PyUnicode_RSplit +FUNC PyUnicode_ReadChar +FUNC PyUnicode_Replace +FUNC PyUnicode_Resize +FUNC PyUnicode_RichCompare +FUNC PyUnicode_Split +FUNC PyUnicode_Splitlines +FUNC PyUnicode_Substring +FUNC PyUnicode_Tailmatch +FUNC PyUnicode_Translate +OBJECT PyUnicode_Type +FUNC PyUnicode_WriteChar +FUNC PyVectorcall_Call +FUNC PyVectorcall_NARGS +FUNC PyWeakref_GetObject +FUNC PyWeakref_GetRef +FUNC PyWeakref_NewProxy +FUNC PyWeakref_NewRef +OBJECT PyWrapperDescr_Type +FUNC PyWrapper_New +OBJECT PyZip_Type +FUNC Py_AddPendingCall +FUNC Py_AtExit +FUNC Py_BuildValue +FUNC Py_BytesMain +FUNC Py_CompileString +FUNC Py_DecRef +FUNC Py_DecodeLocale +FUNC Py_EncodeLocale +FUNC Py_EndInterpreter +FUNC Py_EnterRecursiveCall +FUNC Py_Exit +FUNC Py_FatalError +OBJECT Py_FileSystemDefaultEncodeErrors +OBJECT Py_FileSystemDefaultEncoding +FUNC Py_Finalize +FUNC Py_FinalizeEx +FUNC Py_GenericAlias +OBJECT Py_GenericAliasType +FUNC Py_GetArgcArgv +FUNC Py_GetBuildInfo +FUNC Py_GetCompiler +FUNC Py_GetConstant +FUNC Py_GetConstantBorrowed +FUNC Py_GetCopyright +FUNC Py_GetExecPrefix +FUNC Py_GetPath +FUNC Py_GetPlatform +FUNC Py_GetPrefix +FUNC Py_GetProgramFullPath +FUNC Py_GetProgramName +FUNC Py_GetPythonHome +FUNC Py_GetRecursionLimit +FUNC Py_GetVersion +OBJECT Py_HasFileSystemDefaultEncoding +FUNC Py_IS_TYPE +FUNC Py_IncRef +FUNC Py_Initialize +FUNC Py_InitializeEx +FUNC Py_Is +FUNC Py_IsFalse +FUNC Py_IsFinalizing +FUNC Py_IsInitialized +FUNC Py_IsNone +FUNC Py_IsTrue +FUNC Py_LeaveRecursiveCall +FUNC Py_Main +FUNC Py_MakePendingCalls +FUNC Py_NewInterpreter +FUNC Py_NewRef +FUNC Py_PACK_FULL_VERSION +FUNC Py_PACK_VERSION +FUNC Py_REFCNT +FUNC Py_ReprEnter +FUNC Py_ReprLeave +FUNC Py_SET_SIZE +FUNC Py_SIZE +FUNC Py_SetPath +FUNC Py_SetProgramName +FUNC Py_SetPythonHome +FUNC Py_SetRecursionLimit +FUNC Py_TYPE +OBJECT Py_UTF8Mode +FUNC Py_VaBuildValue +OBJECT Py_Version +FUNC Py_XNewRef +FUNC _PyArg_ParseTupleAndKeywords_SizeT +FUNC _PyArg_ParseTuple_SizeT +FUNC _PyArg_Parse_SizeT +FUNC _PyArg_VaParseTupleAndKeywords_SizeT +FUNC _PyArg_VaParse_SizeT +FUNC _PyErr_BadInternalCall +FUNC _PyObject_CallFunction_SizeT +FUNC _PyObject_CallMethod_SizeT +FUNC _PyObject_GC_New +FUNC _PyObject_GC_NewVar +FUNC _PyObject_GC_Resize +FUNC _PyObject_New +FUNC _PyObject_NewVar +FUNC _PyState_AddModule +FUNC _PyThreadState_Init +FUNC _PyThreadState_Prealloc +OBJECT _PyWeakref_CallableProxyType +OBJECT _PyWeakref_ProxyType +OBJECT _PyWeakref_RefType +FUNC _Py_BuildValue_SizeT +FUNC _Py_CheckRecursiveCall +FUNC _Py_Dealloc +FUNC _Py_DecRef +OBJECT _Py_EllipsisObject +OBJECT _Py_FalseStruct +FUNC _Py_IncRef +OBJECT _Py_NoneStruct +OBJECT _Py_NotImplementedStruct +FUNC _Py_SetRefcnt +OBJECT _Py_SwappedOp +OBJECT _Py_TrueStruct +FUNC _Py_VaBuildValue_SizeT diff --git a/abi/abi3t-missing-linux.txt b/abi/abi3t-missing-linux.txt new file mode 100644 index 0000000000..512ee834e9 --- /dev/null +++ b/abi/abi3t-missing-linux.txt @@ -0,0 +1,71 @@ +FUNC PyABIInfo_Check +FUNC PyEval_AcquireLock +FUNC PyEval_CallFunction +FUNC PyEval_CallMethod +FUNC PyEval_ReleaseLock +FUNC PyInterpreterGuard_Close +FUNC PyInterpreterGuard_FromCurrent +FUNC PyInterpreterGuard_FromView +FUNC PyInterpreterView_Close +FUNC PyInterpreterView_FromCurrent +FUNC PyInterpreterView_FromMain +FUNC PyIter_NextItem +FUNC PyLongWriter_Create +FUNC PyLongWriter_Discard +FUNC PyLongWriter_Finish +FUNC PyLong_AsInt32 +FUNC PyLong_AsInt64 +FUNC PyLong_AsUInt32 +FUNC PyLong_AsUInt64 +FUNC PyLong_Export +FUNC PyLong_FreeExport +FUNC PyLong_FromInt32 +FUNC PyLong_FromInt64 +FUNC PyLong_FromUInt32 +FUNC PyLong_FromUInt64 +FUNC PyLong_GetNativeLayout +FUNC PyModule_Exec +FUNC PyModule_FromSlotsAndSpec +FUNC PyModule_GetStateSize +FUNC PyModule_GetState_DuringGC +FUNC PyModule_GetToken +FUNC PyModule_GetToken_DuringGC +FUNC PyObject_GetTypeData_DuringGC +FUNC PySys_AddWarnOption +FUNC PySys_AddWarnOptionUnicode +FUNC PySys_AddXOption +FUNC PySys_GetAttr +FUNC PySys_GetAttrString +FUNC PySys_GetOptionalAttr +FUNC PySys_GetOptionalAttrString +FUNC PySys_HasWarnOptions +FUNC PySys_SetPath +FUNC PyThreadState_Ensure +FUNC PyThreadState_EnsureFromView +FUNC PyThreadState_Release +FUNC PyType_Freeze +FUNC PyType_FromSlots +FUNC PyType_GetBaseByToken +FUNC PyType_GetBaseByToken_DuringGC +FUNC PyType_GetModuleByToken +FUNC PyType_GetModuleByToken_DuringGC +FUNC PyType_GetModuleState_DuringGC +FUNC PyType_GetModule_DuringGC +FUNC PyUnicode_Equal +OBJECT Py_FileSystemDefaultEncodeErrors +OBJECT Py_HasFileSystemDefaultEncoding +FUNC Py_IS_TYPE +FUNC Py_PACK_FULL_VERSION +FUNC Py_PACK_VERSION +FUNC Py_SET_SIZE +FUNC Py_SIZE +FUNC Py_SetPath +FUNC Py_TYPE +FUNC _PyState_AddModule +FUNC _PyThreadState_Init +FUNC _PyThreadState_Prealloc +OBJECT _Py_EllipsisObject +OBJECT _Py_FalseStruct +OBJECT _Py_NoneStruct +OBJECT _Py_NotImplementedStruct +OBJECT _Py_TrueStruct diff --git a/mx.graalpython/mx_graalpython.py b/mx.graalpython/mx_graalpython.py index f7f45052ee..e5983c1336 100644 --- a/mx.graalpython/mx_graalpython.py +++ b/mx.graalpython/mx_graalpython.py @@ -3372,6 +3372,104 @@ def abi_check(_args): ], nonZeroIsFatal=True) +def _read_abi3t_symbols(path): + symbols = {} + with open(path, encoding='utf-8') as file: + for line_number, line in enumerate(file, 1): + line = line.strip() + if not line or line.startswith('#'): + continue + fields = line.split() + if len(fields) != 2 or fields[0] not in ('FUNC', 'OBJECT'): + mx.abort(f"Invalid ABI3T manifest entry at {path}:{line_number}: {line}") + kind, name = fields + if name in symbols: + mx.abort(f"Duplicate ABI3T symbol at {path}:{line_number}: {name}") + symbols[name] = kind + if list(symbols) != sorted(symbols): + mx.abort(f"ABI3T symbols are not sorted by name: {path}") + return symbols + + +def _read_elf_dynamic_exports(shared_library): + output = mx.OutputCapture() + run(['readelf', '--dyn-syms', '--wide', shared_library], out=output) + exports = {} + for line in output.data.splitlines(): + fields = line.split() + if len(fields) < 8 or not fields[0].rstrip(':').isdigit(): + continue + kind, binding, visibility, section, name = fields[3:8] + if section == 'UND' or binding not in ('GLOBAL', 'WEAK') or visibility not in ('DEFAULT', 'PROTECTED'): + continue + name = name.split('@', 1)[0] + # GNU indirect functions have function semantics for ABI purposes. + if kind == 'IFUNC': + kind = 'FUNC' + exports.setdefault(name, set()).add(kind) + return exports + + +def abi3t_check(raw_args): + parser = ArgumentParser(prog='mx abi3t-check') + parser.add_argument('--library', help='libpython-native.so to inspect; builds a JVM standalone by default') + parser.add_argument('--update', action='store_true', help='update the checked-in missing-symbol report') + args = parser.parse_args(raw_args) + + if not shutil.which('readelf'): + mx.abort("Required tool 'readelf' was not found on PATH") + + if args.library: + shared_library = os.path.abspath(args.library) + else: + standalone_home = graalpy_standalone_home('jvm', dev=True, build=True) + shared_library = os.path.join( + standalone_home, + 'lib', + f'graalpy{graal_version_short("major_minor")}', + 'libpython-native.so', + ) + if not os.path.exists(shared_library): + mx.abort(f"Could not find shared library to check: {shared_library}") + + manifest = os.path.join(SUITE.dir, 'abi', 'abi3t-linux.txt') + report = os.path.join(SUITE.dir, 'abi', 'abi3t-missing-linux.txt') + if not os.path.exists(manifest): + mx.abort(f"Could not find ABI3T export manifest: {manifest}") + + expected = _read_abi3t_symbols(manifest) + exports = _read_elf_dynamic_exports(shared_library) + gaps = {} + wrong_kinds = [] + for name, expected_kind in expected.items(): + actual_kinds = exports.get(name) + if not actual_kinds or expected_kind not in actual_kinds: + gaps[name] = expected_kind + if actual_kinds: + wrong_kinds.append((name, expected_kind, sorted(actual_kinds))) + + generated_report = ''.join(f'{kind} {name}\n' for name, kind in gaps.items()) + if args.update: + with open(report, 'w', encoding='utf-8') as file: + file.write(generated_report) + mx.log(f"Updated ABI3T missing-symbol report: {report}") + else: + if not os.path.exists(report): + mx.abort(f"Could not find ABI3T missing-symbol report: {report}; run mx abi3t-check --update") + with open(report, encoding='utf-8') as file: + checked_in_report = file.read() + if checked_in_report != generated_report: + mx.abort( + f"ABI3T missing-symbol report is out of date: {report}\n" + "Run mx abi3t-check --update and commit the result." + ) + + for index, (name, expected_kind, actual_kinds) in enumerate(wrong_kinds, 1): + mx.warn(f"{index}. {name}: expected {expected_kind}, found {', '.join(actual_kinds)}") + satisfied = len(expected) - len(gaps) + mx.log(f"ABI3T exports satisfied: {satisfied}/{len(expected)}; remaining gaps: {len(gaps)}") + + class GraalpythonBuildTask(mx.ProjectBuildTask): class PrefixingOutput(): def __init__(self, prefix, printfunc): @@ -3860,6 +3958,7 @@ def update_github_unittest_tags(args): 'python-src-import': [mx_graalpython_import.import_python_sources, ''], 'python-coverage': [python_coverage, ''], 'abi-check': [abi_check, ''], + 'abi3t-check': [abi3t_check, '[--library LIBPYTHON_NATIVE_SO] [--update]'], 'punittest': [punittest, ''], 'graalpytest': [graalpytest, '[-h] [--python PYTHON] [TESTS]'], 'clean': [python_clean, '[--just-pyc]'], diff --git a/scripts/generate_abi3t_manifest.py b/scripts/generate_abi3t_manifest.py new file mode 100644 index 0000000000..76b5d09518 --- /dev/null +++ b/scripts/generate_abi3t_manifest.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 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 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and in any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import annotations + +import argparse +import subprocess +from pathlib import Path + +try: + import tomllib +except ModuleNotFoundError: + from pip._vendor import tomli as tomllib # pylint: disable=no-name-in-module + + +DEFAULT_OUTPUT = Path(__file__).resolve().parents[1] / "abi" / "abi3t-linux.txt" +LINUX_DISABLED_FEATURES = {"MS_WINDOWS", "Py_REF_DEBUG", "USE_STACKCHECK"} + + +def git_output(repository: Path, *arguments: str) -> str: + return subprocess.check_output( + ["git", "-C", str(repository), *arguments], + text=True, + ).strip() + + +def generate_manifest(repository: Path, source_ref: str) -> str: + commit = git_output(repository, "rev-parse", f"{source_ref}^{{commit}}") + source = git_output(repository, "show", f"{source_ref}:Misc/stable_abi.toml") + stable_abi = tomllib.loads(source) + + symbols: list[tuple[str, str]] = [] + for item_kind, elf_kind in (("function", "FUNC"), ("data", "OBJECT")): + for name, properties in stable_abi[item_kind].items(): + if properties.get("ifdef") not in LINUX_DISABLED_FEATURES: + symbols.append((name, elf_kind)) + symbols.sort() + + lines = [ + "# Generated by scripts/generate_abi3t_manifest.py. Do not edit manually.", + f"# Source: CPython {source_ref} ({commit})", + "# Source file: Misc/stable_abi.toml", + "# Target: Linux ABI3T (MS_WINDOWS, Py_REF_DEBUG, and USE_STACKCHECK disabled)", + "# Format: ", + "", + ] + lines.extend(f"{kind} {name}" for name, kind in symbols) + return "\n".join(lines) + "\n" + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Generate the Linux ABI3T export manifest from CPython's stable_abi.toml", + ) + parser.add_argument("cpython", type=Path, help="path to a CPython Git repository") + parser.add_argument("--source-ref", default="v3.15.0rc1", help="pinned CPython Git ref") + parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT) + args = parser.parse_args() + + manifest = generate_manifest(args.cpython.resolve(), args.source_ref) + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(manifest, encoding="utf-8") + + +if __name__ == "__main__": + main() From 75d401e7e3416b2af11f1b5e27c728c262483dfb Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Thu, 20 Aug 2026 17:37:38 +0200 Subject: [PATCH 05/24] Extract common error message --- .../builtins/objects/cext/capi/CApiContext.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java index ff972999d4..3aa0305fad 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java @@ -174,6 +174,11 @@ public final class CApiContext extends CExtContext { private static final TruffleLogger LOGGER = PythonLanguage.getLogger(LOGGER_CAPI_NAME); public static final TruffleLogger GC_LOGGER = PythonLanguage.getLogger(CApiContext.LOGGER_CAPI_NAME + ".gc"); + private static final String COULD_NOT_LOAD_MODULE_FORMAT = """ + could not load module %s (real path: %s) from virtual file system. + + !!! Please try to run with java system property org.graalvm.python.vfs.extractOnStartup=true !!! + See also: https://www.graalvm.org/python/docs/#graalpy-troubleshooting"""; /** Native pointers for context-insensitive singletons like {@link PNone#NONE}. */ @CompilationFinal(dimensions = 1) private final long[] singletonNativePtrs; @@ -1154,24 +1159,21 @@ public static Object loadCExtModule(Node location, PythonContext context, Module dlopenFlags |= PosixConstants.RTLD_LOCAL.value; } + String format = String.format(COULD_NOT_LOAD_MODULE_FORMAT, spec.path, realPath); try { library = context.ensureNativeContext().loadLibrary(loadPath, dlopenFlags); } catch (PException e) { throw e; } catch (NativeLibraryLoadException e) { if (!realPath.exists() && realPath.toString().contains("org.graalvm.python.vfsx")) { - getLogger(CApiContext.class).severe(String.format("could not load module %s (real path: %s) from virtual file system.\n\n" + - "!!! Please try to run with java system property org.graalvm.python.vfs.extractOnStartup=true !!!\n" + - "See also: https://www.graalvm.org/python/docs/#graalpy-troubleshooting", spec.path, realPath)); + getLogger(CApiContext.class).severe(format); } throw new ImportException(null, spec.name, spec.path, ErrorMessages.CANNOT_LOAD, spec.path, e.getMessage()); } catch (AbstractTruffleException e) { if (!realPath.exists() && realPath.toString().contains("org.graalvm.python.vfsx")) { // file does not exist and it is from VirtualFileSystem // => we probably failed to extract it due to unconventional libs location - getLogger(CApiContext.class).severe(String.format("could not load module %s (real path: %s) from virtual file system.\n\n" + - "!!! Please try to run with java system property org.graalvm.python.vfs.extractOnStartup=true !!!\n" + - "See also: https://www.graalvm.org/python/docs/#graalpy-troubleshooting", spec.path, realPath)); + getLogger(CApiContext.class).severe(format); } From dc53e46b8d30182c82b65c66f5b58f7313b1db0a Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Thu, 20 Aug 2026 19:09:16 +0200 Subject: [PATCH 06/24] Eliminate obsolete CExtContext --- .../builtin/objects/cext/CExtContextTest.java | 31 +++----- .../modules/cext/CFunctionDocUtils.java | 10 +-- .../cext/PythonCextMethodBuiltins.java | 2 +- .../modules/cext/PythonCextTypeBuiltins.java | 8 +- .../objects/cext/capi/CApiContext.java | 65 +++++++++++++--- .../builtins/objects/cext/capi/CExtNodes.java | 3 +- .../cext/capi/MethodDescriptorWrapper.java | 19 +++-- .../objects/cext/capi/PyCFunctionWrapper.java | 11 ++- .../PyMethodFlags.java} | 75 +------------------ .../objects/cext/common/CExtCommonNodes.java | 5 +- .../objects/function/PBuiltinFunction.java | 18 ++--- .../graal/python/runtime/object/PFactory.java | 4 +- 12 files changed, 108 insertions(+), 143 deletions(-) rename graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/{common/CExtContext.java => capi/PyMethodFlags.java} (55%) diff --git a/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/builtin/objects/cext/CExtContextTest.java b/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/builtin/objects/cext/CExtContextTest.java index 5988f5c1bb..74c01a9b80 100644 --- a/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/builtin/objects/cext/CExtContextTest.java +++ b/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/builtin/objects/cext/CExtContextTest.java @@ -46,9 +46,7 @@ import org.junit.Before; import org.junit.Test; -import com.oracle.graal.python.builtins.objects.cext.common.CExtContext; -import com.oracle.graal.python.runtime.nativeaccess.NativeLibrary; -import com.oracle.graal.python.runtime.PythonContext; +import com.oracle.graal.python.builtins.objects.cext.capi.CApiContext; import com.oracle.graal.python.test.PythonTests; import com.oracle.truffle.api.strings.TruffleString; @@ -70,24 +68,13 @@ static TruffleString ts(String s) { @Test public void testGetBaseName() { - assertEquals(ts(""), TestCExtContext.getBN(ts(""))); - assertEquals(ts("a"), TestCExtContext.getBN(ts("a"))); - assertEquals(ts("aa"), TestCExtContext.getBN(ts("aa"))); - assertEquals(ts("aa"), TestCExtContext.getBN(ts("a.aa"))); - assertEquals(ts("bb"), TestCExtContext.getBN(ts("a.aa.bb"))); - assertEquals(ts(""), TestCExtContext.getBN(ts("a.aa.bb."))); - assertEquals(ts("b"), TestCExtContext.getBN(ts("a.b"))); - assertEquals(ts(""), TestCExtContext.getBN(ts("a.b."))); + assertEquals(ts(""), CApiContext.getBaseName(ts(""))); + assertEquals(ts("a"), CApiContext.getBaseName(ts("a"))); + assertEquals(ts("aa"), CApiContext.getBaseName(ts("aa"))); + assertEquals(ts("aa"), CApiContext.getBaseName(ts("a.aa"))); + assertEquals(ts("bb"), CApiContext.getBaseName(ts("a.aa.bb"))); + assertEquals(ts(""), CApiContext.getBaseName(ts("a.aa.bb."))); + assertEquals(ts("b"), CApiContext.getBaseName(ts("a.b"))); + assertEquals(ts(""), CApiContext.getBaseName(ts("a.b."))); } - - private static class TestCExtContext extends CExtContext { - public TestCExtContext(PythonContext context, NativeLibrary library) { - super(context, library, null); - } - - public static TruffleString getBN(TruffleString s) { - return getBaseName(s); - } - } - } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/CFunctionDocUtils.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/CFunctionDocUtils.java index 19fd7423d3..e8b11eddc4 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/CFunctionDocUtils.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/CFunctionDocUtils.java @@ -40,11 +40,11 @@ */ package com.oracle.graal.python.builtins.modules.cext; -import static com.oracle.graal.python.builtins.objects.cext.common.CExtContext.METH_CLASS; -import static com.oracle.graal.python.builtins.objects.cext.common.CExtContext.METH_COEXIST; -import static com.oracle.graal.python.builtins.objects.cext.common.CExtContext.METH_NOARGS; -import static com.oracle.graal.python.builtins.objects.cext.common.CExtContext.METH_O; -import static com.oracle.graal.python.builtins.objects.cext.common.CExtContext.METH_STATIC; +import static com.oracle.graal.python.builtins.objects.cext.capi.PyMethodFlags.METH_CLASS; +import static com.oracle.graal.python.builtins.objects.cext.capi.PyMethodFlags.METH_COEXIST; +import static com.oracle.graal.python.builtins.objects.cext.capi.PyMethodFlags.METH_NOARGS; +import static com.oracle.graal.python.builtins.objects.cext.capi.PyMethodFlags.METH_O; +import static com.oracle.graal.python.builtins.objects.cext.capi.PyMethodFlags.METH_STATIC; import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___DOC__; import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___TEXT_SIGNATURE__; import static com.oracle.graal.python.util.PythonUtils.tsLiteral; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextMethodBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextMethodBuiltins.java index 1b0759e39e..4c774ad389 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextMethodBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextMethodBuiltins.java @@ -48,7 +48,7 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObject; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObjectTransfer; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyTypeObject; -import static com.oracle.graal.python.builtins.objects.cext.common.CExtContext.METH_METHOD; +import static com.oracle.graal.python.builtins.objects.cext.capi.PyMethodFlags.METH_METHOD; import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.NULLPTR; import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___MODULE__; import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___NAME__; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextTypeBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextTypeBuiltins.java index 479a151df7..c5b2d46b31 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextTypeBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextTypeBuiltins.java @@ -82,6 +82,7 @@ import com.oracle.graal.python.builtins.objects.cext.capi.ExternalFunctionNodes.PExternalFunctionWrapper; import com.oracle.graal.python.builtins.objects.cext.capi.ExternalFunctionNodes.SetterRoot; import com.oracle.graal.python.builtins.objects.cext.capi.MethodDescriptorWrapper; +import com.oracle.graal.python.builtins.objects.cext.capi.PyMethodFlags; import com.oracle.graal.python.builtins.objects.cext.capi.WrapperDescriptorRootNodesGen; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions; @@ -90,7 +91,6 @@ import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonInternalNode; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.TransformExceptionToNativeNode; -import com.oracle.graal.python.builtins.objects.cext.common.CExtContext; import com.oracle.graal.python.builtins.objects.cext.structs.CFields; import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess; import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.ReadObjectNode; @@ -311,14 +311,14 @@ private static PythonBuiltinObject typeAddMethod(PythonLanguage language, long m WriteAttributeToPythonObjectNode.executeUncached(func, T___NAME__, name); CFunctionDocUtils.writeDocAndTextSignature(func, name, doc, flags); } - if (CExtContext.isMethClass(flags)) { - if (CExtContext.isMethStatic(flags)) { + if (PyMethodFlags.isMethClass(flags)) { + if (PyMethodFlags.isMethStatic(flags)) { assert func == null; throw PRaiseNode.raiseStatic(EncapsulatingNodeReference.getCurrent().get(), PythonBuiltinClassType.ValueError, ErrorMessages.METHOD_CANNOT_BE_BOTH_CLASS_AND_STATIC); } assert func != null; return PFactory.createBuiltinClassmethodFromCallableObj(language, func); - } else if (CExtContext.isMethStatic(flags)) { + } else if (PyMethodFlags.isMethStatic(flags)) { return PFactory.createStaticmethodFromCallableObj(language, func); } HiddenAttr.WriteLongNode.executeUncached(func, METHOD_DEF_PTR, methodDefPtr); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java index 3aa0305fad..04048530b3 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java @@ -41,6 +41,7 @@ package com.oracle.graal.python.builtins.objects.cext.capi; import static com.oracle.graal.python.PythonLanguage.CONTEXT_INSENSITIVE_SINGLETONS; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.SystemError; import static com.oracle.graal.python.builtins.objects.PythonAbstractObject.UNINITIALIZED; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.drainReferenceQueueNow; import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readIntField; @@ -49,9 +50,11 @@ import static com.oracle.graal.python.nodes.BuiltinNames.T___GRAALPYTHON__; import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___FILE__; import static com.oracle.graal.python.nodes.StringLiterals.T_DASH; +import static com.oracle.graal.python.nodes.StringLiterals.T_DOT; import static com.oracle.graal.python.nodes.StringLiterals.T_EMPTY_STRING; import static com.oracle.graal.python.nodes.StringLiterals.T_UNDERSCORE; import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.NULLPTR; +import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; import static com.oracle.graal.python.util.PythonUtils.toTruffleStringUncached; import static com.oracle.graal.python.util.PythonUtils.tsLiteral; @@ -96,7 +99,6 @@ import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonInternalNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.ReferenceQueueCoordinator; -import com.oracle.graal.python.builtins.objects.cext.common.CExtContext; import com.oracle.graal.python.builtins.objects.cext.common.LoadCExtException.ApiInitException; import com.oracle.graal.python.builtins.objects.cext.common.LoadCExtException.ImportException; import com.oracle.graal.python.builtins.objects.cext.copying.NativeLibraryLocator; @@ -104,6 +106,7 @@ import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess; import com.oracle.graal.python.builtins.objects.cext.structs.CStructs; import com.oracle.graal.python.builtins.objects.dict.PDict; +import com.oracle.graal.python.builtins.objects.exception.PBaseException; import com.oracle.graal.python.builtins.objects.frame.PFrame; import com.oracle.graal.python.builtins.objects.ints.PInt; import com.oracle.graal.python.builtins.objects.module.PythonModule; @@ -122,6 +125,7 @@ import com.oracle.graal.python.runtime.PythonContext.CApiState; import com.oracle.graal.python.runtime.PythonContext.PythonThreadState; import com.oracle.graal.python.runtime.PythonOptions; +import com.oracle.graal.python.runtime.exception.ExceptionUtils; import com.oracle.graal.python.runtime.exception.PException; import com.oracle.graal.python.runtime.nativeaccess.NativeAccessSupport; import com.oracle.graal.python.runtime.nativeaccess.NativeContext; @@ -158,7 +162,7 @@ import sun.misc.Unsafe; -public final class CApiContext extends CExtContext { +public final class CApiContext { private static final TruffleString T_PY_INIT = tsLiteral("PyInit_"); private static final TruffleString T_PY_INIT_U = tsLiteral("PyInitU_"); private static final TruffleString T_GRAALPY_TEST_CAPI = tsLiteral("_testcapi"); @@ -175,13 +179,17 @@ public final class CApiContext extends CExtContext { private static final TruffleLogger LOGGER = PythonLanguage.getLogger(LOGGER_CAPI_NAME); public static final TruffleLogger GC_LOGGER = PythonLanguage.getLogger(CApiContext.LOGGER_CAPI_NAME + ".gc"); private static final String COULD_NOT_LOAD_MODULE_FORMAT = """ - could not load module %s (real path: %s) from virtual file system. - - !!! Please try to run with java system property org.graalvm.python.vfs.extractOnStartup=true !!! - See also: https://www.graalvm.org/python/docs/#graalpy-troubleshooting"""; + could not load module %s (real path: %s) from virtual file system. + + !!! Please try to run with java system property org.graalvm.python.vfs.extractOnStartup=true !!! + See also: https://www.graalvm.org/python/docs/#graalpy-troubleshooting"""; /** Native pointers for context-insensitive singletons like {@link PNone#NONE}. */ @CompilationFinal(dimensions = 1) private final long[] singletonNativePtrs; + private final PythonContext context; + /** The library object representing 'libpython.*.so' or similar. */ + private final NativeLibrary library; + private final String libraryName; /** * Pointer to the native {@code GCState GC state}. This corresponds to CPython's @@ -216,6 +224,42 @@ public static boolean isSpecialSingleton(Object delegate) { return getSingletonNativeWrapperIdx(delegate) != -1; } + @TruffleBoundary + public static TruffleString getBaseName(TruffleString name) { + int len = TruffleString.CodePointLengthNode.getUncached().execute(name, TS_ENCODING); + if (len == 1) { + return name.equalsUncached(T_DOT, TS_ENCODING) ? T_EMPTY_STRING : name; + } + int idx = name.lastIndexOfStringUncached(T_DOT, len, 0, TS_ENCODING); + if (idx < 0) { + return name; + } + if (idx == len - 1) { + return T_EMPTY_STRING; + } + return name.substringUncached(idx + 1, len - idx - 1, TS_ENCODING, true); + } + + @TruffleBoundary + public static PException wrapJavaException(Throwable e, Node raisingNode) { + TruffleString message = toTruffleStringUncached(e.getMessage()); + PBaseException excObject = PFactory.createBaseException(PythonLanguage.get(null), SystemError, message != null ? message : toTruffleStringUncached(e.toString()), + PythonUtils.EMPTY_OBJECT_ARRAY); + return ExceptionUtils.wrapJavaException(e, raisingNode, excObject); + } + + public PythonContext getContext() { + return context; + } + + public NativeLibrary getLibrary() { + return library; + } + + public String getLibraryName() { + return libraryName; + } + private record ClosureInfo(Object delegate, Object executable, long pointer) { } @@ -325,7 +369,10 @@ public static TruffleLogger getLogger(Class clazz) { } public CApiContext(PythonContext context, NativeLibrary library, NativeLibraryLocator locator) { - super(context, library, locator.getCapiLibrary()); + this.context = context; + this.library = library; + this.libraryName = locator.getCapiLibrary(); + this.nativeCAPISymbols = new NativeFunctionPointer[NativeCAPISymbol.values().length]; this.nativeLibraryLocator = locator; @@ -1177,7 +1224,7 @@ public static Object loadCExtModule(Node location, PythonContext context, Module } - throw new ImportException(CExtContext.wrapJavaException(e, location), spec.name, spec.path, ErrorMessages.CANNOT_LOAD_M, spec.path, e); + throw new ImportException(CApiContext.wrapJavaException(e, location), spec.name, spec.path, ErrorMessages.CANNOT_LOAD_M, spec.path, e); } return cApiContext.initCApiModule(location, library, spec.getInitFunctionName(), spec); } @@ -1323,8 +1370,8 @@ public void finalizeCApi(boolean cancelling) { } } - @TruffleBoundary public Object initCApiModule(Node node, NativeLibrary sharedLibrary, TruffleString initFuncName, ModuleSpec spec) throws ImportException { + CompilerAsserts.neverPartOfCompilation(); PythonContext context = getContext(); CApiContext cApiContext = context.getCApiContext(); long pyinitFunc = sharedLibrary.lookupOptionalSymbol(initFuncName.toJavaStringUncached()); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java index e106a8dc70..bcaed1cdfb 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java @@ -107,7 +107,6 @@ import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.TransformExceptionFromNativeNode; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.TransformPExceptionToNativeNode; -import com.oracle.graal.python.builtins.objects.cext.common.CExtContext; import com.oracle.graal.python.builtins.objects.cext.structs.CFields; import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess; import com.oracle.graal.python.builtins.objects.cext.structs.CStructs; @@ -1189,7 +1188,7 @@ static PBuiltinFunction createLegacyMethod(long methodDefPtr, int element, Pytho // CPy-style methods // TODO(fa) support static and class methods MethodDescriptorWrapper sig = MethodDescriptorWrapper.fromMethodFlags(flags); - PRootNode rootNode = MethodDescriptorWrapper.getOrCreateRootNode(language, sig, methodName, CExtContext.isMethStatic(flags)); + PRootNode rootNode = MethodDescriptorWrapper.getOrCreateRootNode(language, sig, methodName, PyMethodFlags.isMethStatic(flags)); NativeFunctionPointer fun = CExtCommonNodes.bindFunctionPointer(mlMethObj, sig); PKeyword[] kwDefaults = ExternalFunctionNodes.createKwDefaults(fun); PBuiltinFunction function = PFactory.createBuiltinFunction(language, methodName, null, PythonUtils.EMPTY_OBJECT_ARRAY, kwDefaults, flags, rootNode); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/MethodDescriptorWrapper.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/MethodDescriptorWrapper.java index 571a88e9c6..0a12551eec 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/MethodDescriptorWrapper.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/MethodDescriptorWrapper.java @@ -57,7 +57,6 @@ import com.oracle.graal.python.builtins.objects.cext.capi.ExternalFunctionNodes.MethVarargsRoot; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes; -import com.oracle.graal.python.builtins.objects.cext.common.CExtContext; import com.oracle.graal.python.builtins.objects.cext.common.NativeCExtSymbol; import com.oracle.graal.python.builtins.objects.function.PBuiltinFunction; import com.oracle.graal.python.builtins.objects.function.PKeyword; @@ -173,7 +172,7 @@ public static PBuiltinFunction createWrapperFunction(PythonLanguage language, Tr return null; } - PRootNode rootNode = getOrCreateRootNode(language, methodDescriptorWrapper, name, CExtContext.isMethStatic(flags)); + PRootNode rootNode = getOrCreateRootNode(language, methodDescriptorWrapper, name, PyMethodFlags.isMethStatic(flags)); PKeyword[] kwDefaults = ExternalFunctionNodes.createKwDefaults(CExtCommonNodes.bindFunctionPointer(callable, methodDescriptorWrapper)); @@ -187,29 +186,29 @@ public static PBuiltinFunction createWrapperFunction(PythonLanguage language, Tr /** * See {@code PyDescr_NewMethod} * - * @param flags The method flags {@link CExtContext#METH_VARARGS} and others. + * @param flags The method flags {@link PyMethodFlags#METH_VARARGS} and others. * @return */ static MethodDescriptorWrapper fromMethodFlags(int flags) { - if (CExtContext.isMethVarargs(flags)) { + if (PyMethodFlags.isMethVarargs(flags)) { return VARARGS; } - if (CExtContext.isMethVarargsWithKeywords(flags)) { + if (PyMethodFlags.isMethVarargsWithKeywords(flags)) { return KEYWORDS; } - if (CExtContext.isMethFastcall(flags)) { + if (PyMethodFlags.isMethFastcall(flags)) { return FASTCALL; } - if (CExtContext.isMethFastcallWithKeywords(flags)) { + if (PyMethodFlags.isMethFastcallWithKeywords(flags)) { return FASTCALL_WITH_KEYWORDS; } - if (CExtContext.isMethNoArgs(flags)) { + if (PyMethodFlags.isMethNoArgs(flags)) { return NOARGS; } - if (CExtContext.isMethO(flags)) { + if (PyMethodFlags.isMethO(flags)) { return O; } - if (CExtContext.isMethMethod(flags)) { + if (PyMethodFlags.isMethMethod(flags)) { return METHOD; } throw CompilerDirectives.shouldNotReachHere("illegal method flags"); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PyCFunctionWrapper.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PyCFunctionWrapper.java index 6b1969a69a..2e8c666901 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PyCFunctionWrapper.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PyCFunctionWrapper.java @@ -56,7 +56,6 @@ import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonInternalNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.TransformExceptionToNativeNode; -import com.oracle.graal.python.builtins.objects.cext.common.CExtContext; import com.oracle.graal.python.builtins.objects.function.PBuiltinFunction; import com.oracle.graal.python.builtins.objects.function.PKeyword; import com.oracle.graal.python.builtins.objects.function.Signature; @@ -118,7 +117,7 @@ public abstract class PyCFunctionWrapper { private final long pointer; /** - * Built-in functions may appear as {@link CExtContext#METH_VARARGS} etc. but we implement them + * Built-in functions may appear as {@link PyMethodFlags#METH_VARARGS} etc. but we implement them * as nodes where the specializations have a fixed arity and then sometimes allow optional * arguments (specified with {@link Builtin#minNumOfPositionalArgs()}, * {@link Builtin#maxNumOfPositionalArgs()}, and similar). This is usually not a problem because @@ -182,13 +181,13 @@ public static PyCFunctionWrapper createFromBuiltinFunction(CApiContext cApiConte RootCallTarget ct = builtinFunction.getCallTarget(); Signature signature = builtinFunction.getSignature(); Object[] defaults = builtinFunction.getDefaults(); - if (CExtContext.isMethNoArgs(flags)) { + if (PyMethodFlags.isMethNoArgs(flags)) { return cApiContext.getOrCreatePyCFunctionWrapper(ct, k -> new PyCFunctionUnaryWrapper(k, signature, defaults)); - } else if (CExtContext.isMethO(flags)) { + } else if (PyMethodFlags.isMethO(flags)) { return cApiContext.getOrCreatePyCFunctionWrapper(ct, k -> new PyCFunctionBinaryWrapper(k, signature, defaults)); - } else if (CExtContext.isMethVarargs(flags)) { + } else if (PyMethodFlags.isMethVarargs(flags)) { return cApiContext.getOrCreatePyCFunctionWrapper(ct, k -> new PyCFunctionVarargsWrapper(k, signature, defaults)); - } else if (CExtContext.isMethVarargsWithKeywords(flags)) { + } else if (PyMethodFlags.isMethVarargsWithKeywords(flags)) { return cApiContext.getOrCreatePyCFunctionWrapper(ct, k -> new PyCFunctionKeywordsWrapper(k, signature, defaults)); } else { throw shouldNotReachHere("other signature " + Integer.toHexString(flags)); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PyMethodFlags.java similarity index 55% rename from graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtContext.java rename to graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PyMethodFlags.java index ff3fff9731..13b7cf9f2e 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PyMethodFlags.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, 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 @@ -38,27 +38,10 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package com.oracle.graal.python.builtins.objects.cext.common; -import static com.oracle.graal.python.builtins.PythonBuiltinClassType.SystemError; -import static com.oracle.graal.python.nodes.StringLiterals.T_DOT; -import static com.oracle.graal.python.nodes.StringLiterals.T_EMPTY_STRING; -import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; -import static com.oracle.graal.python.util.PythonUtils.toTruffleStringUncached; +package com.oracle.graal.python.builtins.objects.cext.capi; -import com.oracle.graal.python.PythonLanguage; -import com.oracle.graal.python.builtins.objects.exception.PBaseException; -import com.oracle.graal.python.runtime.PythonContext; -import com.oracle.graal.python.runtime.exception.ExceptionUtils; -import com.oracle.graal.python.runtime.exception.PException; -import com.oracle.graal.python.runtime.nativeaccess.NativeLibrary; -import com.oracle.graal.python.runtime.object.PFactory; -import com.oracle.graal.python.util.PythonUtils; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.api.strings.TruffleString; - -public abstract class CExtContext { +public final class PyMethodFlags { public static final int METH_VARARGS = 0x0001; public static final int METH_KEYWORDS = 0x0002; public static final int METH_NOARGS = 0x0004; @@ -68,32 +51,10 @@ public abstract class CExtContext { public static final int METH_COEXIST = 0x0040; public static final int METH_FASTCALL = 0x0080; public static final int METH_METHOD = 0x0200; - // Filter out only the base convention, without orthogonal modifiers private static final int CALL_CONVENTION_MASK = METH_VARARGS | METH_KEYWORDS | METH_NOARGS | METH_O | METH_FASTCALL | METH_METHOD; - private final PythonContext context; - - /** The library object representing 'libpython.*.so' or similar. */ - private final NativeLibrary library; - private final String libraryName; - - public CExtContext(PythonContext context, NativeLibrary library, String libraryName) { - this.context = context; - this.library = library; - this.libraryName = libraryName; - } - - public final PythonContext getContext() { - return context; - } - - public final NativeLibrary getLibrary() { - return library; - } - - public final String getLibraryName() { - return libraryName; + private PyMethodFlags() { } public static boolean isMethVarargs(int flags) { @@ -131,32 +92,4 @@ public static boolean isMethStatic(int flags) { public static boolean isMethClass(int flags) { return (flags & METH_CLASS) != 0; } - - public static boolean isClassOrStaticMethod(int flags) { - return isMethClass(flags) || isMethStatic(flags); - } - - @TruffleBoundary - protected static TruffleString getBaseName(TruffleString name) { - int len = TruffleString.CodePointLengthNode.getUncached().execute(name, TS_ENCODING); - if (len == 1) { - return name.equalsUncached(T_DOT, TS_ENCODING) ? T_EMPTY_STRING : name; - } - int idx = name.lastIndexOfStringUncached(T_DOT, len, 0, TS_ENCODING); - if (idx < 0) { - return name; - } - if (idx == len - 1) { - return T_EMPTY_STRING; - } - return name.substringUncached(idx + 1, len - idx - 1, TS_ENCODING, true); - } - - @TruffleBoundary - public static PException wrapJavaException(Throwable e, Node raisingNode) { - TruffleString message = toTruffleStringUncached(e.getMessage()); - PBaseException excObject = PFactory.createBaseException(PythonLanguage.get(null), SystemError, message != null ? message : toTruffleStringUncached(e.toString()), - PythonUtils.EMPTY_OBJECT_ARRAY); - return ExceptionUtils.wrapJavaException(e, raisingNode, excObject); - } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtCommonNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtCommonNodes.java index fcd3cb5820..31a010861a 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtCommonNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtCommonNodes.java @@ -99,6 +99,7 @@ import com.oracle.graal.python.runtime.object.PFactory; import com.oracle.graal.python.util.OverflowException; import com.oracle.graal.python.util.PythonUtils; +import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff; import com.oracle.truffle.api.TruffleLogger; @@ -590,7 +591,7 @@ public static GetIndexNode create() { } } - private static final TruffleLogger LOGGER = CApiContext.getLogger(CExtContext.class); + private static final TruffleLogger LOGGER = CApiContext.getLogger(CExtCommonNodes.class); /** * Binds a native pointer with a signature to a typed native function pointer. @@ -600,8 +601,8 @@ public static GetIndexNode create() { * access} is not allowed *

*/ - @TruffleBoundary public static NativeFunctionPointer bindFunctionPointer(long pointer, NativeCExtSymbol descriptor) { + CompilerAsserts.neverPartOfCompilation(); PythonContext pythonContext = PythonContext.get(null); if (!pythonContext.isNativeAccessAllowed()) { LOGGER.severe(PythonUtils.formatJString("Attempting to bind %s to a native callable but native access is not allowed", pointer)); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/function/PBuiltinFunction.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/function/PBuiltinFunction.java index a41da7f09d..a10346a095 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/function/PBuiltinFunction.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/function/PBuiltinFunction.java @@ -38,7 +38,7 @@ import com.oracle.graal.python.builtins.PythonBuiltins; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.cext.capi.ExternalFunctionNodes.PExternalFunctionWrapper; -import com.oracle.graal.python.builtins.objects.cext.common.CExtContext; +import com.oracle.graal.python.builtins.objects.cext.capi.PyMethodFlags; import com.oracle.graal.python.builtins.objects.object.PythonBuiltinObject; import com.oracle.graal.python.builtins.objects.str.PString; import com.oracle.graal.python.builtins.objects.str.StringUtils; @@ -156,11 +156,11 @@ public int getFlags() { } public boolean isStatic() { - return (flags & CExtContext.METH_STATIC) != 0; + return (flags & PyMethodFlags.METH_STATIC) != 0; } public boolean needsDeclaringType() { - return (flags & CExtContext.METH_METHOD) != 0; + return (flags & PyMethodFlags.METH_METHOD) != 0; } @TruffleBoundary @@ -170,23 +170,23 @@ public static int getFlags(Builtin builtin, Signature signature) { } int flags = 0; if (builtin.isClassmethod()) { - flags |= CExtContext.METH_CLASS; + flags |= PyMethodFlags.METH_CLASS; } if (builtin.isStaticmethod()) { - flags |= CExtContext.METH_STATIC; + flags |= PyMethodFlags.METH_STATIC; } if (!signature.takesKeywordArgs() && !signature.takesVarArgs() && PythonBuiltins.numDefaults(builtin) == 0) { int params = signature.getParameterIds().length; if (params == 1) { // only 'self' - return flags | CExtContext.METH_NOARGS; + return flags | PyMethodFlags.METH_NOARGS; } else if (params == 2) { - return flags | CExtContext.METH_O; + return flags | PyMethodFlags.METH_O; } } - flags |= CExtContext.METH_VARARGS; + flags |= PyMethodFlags.METH_VARARGS; if (signature.takesKeywordArgs()) { - flags |= CExtContext.METH_KEYWORDS; + flags |= PyMethodFlags.METH_KEYWORDS; } return flags; } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/object/PFactory.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/object/PFactory.java index 4565d027cc..7a453e8dbd 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/object/PFactory.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/object/PFactory.java @@ -92,8 +92,8 @@ import com.oracle.graal.python.builtins.objects.capsule.PyCapsule; import com.oracle.graal.python.builtins.objects.cell.PCell; import com.oracle.graal.python.builtins.objects.cext.capi.ExternalFunctionNodes.PExternalFunctionWrapper; +import com.oracle.graal.python.builtins.objects.cext.capi.PyMethodFlags; import com.oracle.graal.python.builtins.objects.cext.capi.TruffleObjectNativeWrapper; -import com.oracle.graal.python.builtins.objects.cext.common.CExtContext; import com.oracle.graal.python.builtins.objects.code.PCode; import com.oracle.graal.python.builtins.objects.common.DynamicObjectStorage; import com.oracle.graal.python.builtins.objects.common.EconomicMapStorage; @@ -541,7 +541,7 @@ public static PBuiltinFunction createWrapperDescriptor(PythonLanguage language, } public static PBuiltinMethod createNewWrapper(PythonLanguage language, Object type, Object[] defaults, PKeyword[] kwdefaults, PRootNode rootNode, TpSlot slot) { - PBuiltinFunction func = createWrapperDescriptor(language, T___NEW__, type, defaults, kwdefaults, CExtContext.METH_VARARGS | CExtContext.METH_KEYWORDS, rootNode, slot, + PBuiltinFunction func = createWrapperDescriptor(language, T___NEW__, type, defaults, kwdefaults, PyMethodFlags.METH_VARARGS | PyMethodFlags.METH_KEYWORDS, rootNode, slot, PExternalFunctionWrapper.NEW); return createBuiltinMethod(language, type, func); } From 61ee68d9d459a8b0a2d4ab7c3daed675e9b9b79e Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Thu, 20 Aug 2026 20:02:48 +0200 Subject: [PATCH 07/24] Remove unused ReferenceStack --- .../objects/cext/common/ReferenceStack.java | 146 ------------------ 1 file changed, 146 deletions(-) delete mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/ReferenceStack.java diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/ReferenceStack.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/ReferenceStack.java deleted file mode 100644 index c36dd4db70..0000000000 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/ReferenceStack.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2020, 2022, 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 - * - * Subject to the condition set forth below, permission is hereby granted to any - * person obtaining a copy of this software, associated documentation and/or - * data (collectively the "Software"), free of charge and under any and all - * copyright rights in the Software, and any and all patent rights owned or - * freely licensable by each licensor hereunder covering either (i) the - * unmodified Software as contributed to or provided by such licensor, or (ii) - * the Larger Works (as defined below), to deal in both - * - * (a) the Software, and - * - * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if - * one is included with the Software each a "Larger Work" to which the Software - * is contributed by such licensors), - * - * without restriction, including without limitation the rights to copy, create - * derivative works of, display, perform, and distribute the Software and make, - * use, sell, offer for sale, import, export, have made, and have sold the - * Software and the Larger Work(s), and to sublicense the foregoing rights on - * either these or other terms. - * - * This license is subject to the following condition: - * - * The above copyright notice and either this complete permission notice or at a - * minimum a reference to the UPL must be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.oracle.graal.python.builtins.objects.cext.common; - -import java.util.Arrays; -import java.util.Iterator; - -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; - -public final class ReferenceStack implements Iterable { - private static final int INITIAL_CAPACITY = 64; - - private final IntegerStack freeList; - private Object[] nativeObjectWrapperList; - - @TruffleBoundary - public ReferenceStack() { - nativeObjectWrapperList = new Object[INITIAL_CAPACITY]; - freeList = new IntegerStack(INITIAL_CAPACITY); - freeList.addToFreeList(0, INITIAL_CAPACITY); - } - - @TruffleBoundary - private void enlargeNativeReferenceList() { - int oldSize = nativeObjectWrapperList.length; - int newSize = oldSize * 2; - nativeObjectWrapperList = Arrays.copyOf(nativeObjectWrapperList, newSize); - freeList.addToFreeList(oldSize, newSize); - } - - @SuppressWarnings("unchecked") - public T get(int idx) { - if (0 <= idx && idx < nativeObjectWrapperList.length) { - return (T) nativeObjectWrapperList[idx]; - } else { - assert false : "incorrect reference ID"; - } - return null; - } - - @SuppressWarnings("unchecked") - public T remove(int idx) { - assert 0 <= idx && idx < nativeObjectWrapperList.length; - T ref = (T) nativeObjectWrapperList[idx]; - nativeObjectWrapperList[idx] = null; - freeList.push(idx); - return ref; - } - - public int reserve() { - int nativeRefID = freeList.pop(); - if (nativeRefID == -1) { - enlargeNativeReferenceList(); - nativeRefID = freeList.pop(); - } - assert nativeRefID != -1; - return nativeRefID; - } - - public void commit(int idx, T nativeObjectReference) { - assert 0 <= idx && idx < nativeObjectWrapperList.length; - assert nativeObjectWrapperList[idx] == null : "cannot overwrite an allocated native object reference slot"; - nativeObjectWrapperList[idx] = nativeObjectReference; - } - - @SuppressWarnings("unchecked") - public T resurrect(int idx, T nativeObjectReference) { - assert 0 <= idx && idx < nativeObjectWrapperList.length; - T old = (T) nativeObjectWrapperList[idx]; - nativeObjectWrapperList[idx] = nativeObjectReference; - return old; - } - - @Override - @SuppressWarnings("unchecked") - public Iterator iterator() { - return (Iterator) Arrays.asList(nativeObjectWrapperList).iterator(); - } - - static final class IntegerStack { - private int[] stack; - private int top = 0; - - public IntegerStack(int initialCapacity) { - stack = new int[initialCapacity]; - } - - void push(int i) { - if (top >= stack.length) { - stack = Arrays.copyOf(stack, stack.length * 2); - } - stack[top++] = i; - } - - int pop() { - if (top <= 0) { - return -1; - } - return stack[--top]; - } - - void addToFreeList(int start, int end) { - for (int i = end - 1; i >= start; i--) { - push(i); - } - } - } -} From a691d7f9706d0f958735473b7679c7bc1afeea0a Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Thu, 20 Aug 2026 20:09:37 +0200 Subject: [PATCH 08/24] Remove unused CExtCommonNodes.Charsets --- .../objects/cext/common/CExtCommonNodes.java | 45 ------------------- 1 file changed, 45 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtCommonNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtCommonNodes.java index 31a010861a..c1f2b61495 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtCommonNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtCommonNodes.java @@ -57,9 +57,7 @@ import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.readShortArrayElement; import static com.oracle.graal.python.util.PythonUtils.SURROGATE_CODE_POINT_SET; import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; -import static com.oracle.graal.python.util.PythonUtils.tsLiteral; -import java.nio.charset.Charset; import java.util.logging.Level; import com.oracle.graal.python.PythonLanguage; @@ -230,49 +228,6 @@ private static boolean isSurrogate(int codePoint) { } } - public abstract static class Charsets { - private static final int NATIVE_ORDER = 0; - private static Charset UTF32; - private static Charset UTF32LE; - private static Charset UTF32BE; - - private static final TruffleString T_UTF_32 = tsLiteral("UTF-32"); - private static final TruffleString T_UTF_32LE = tsLiteral("UTF-32LE"); - private static final TruffleString T_UTF_32BE = tsLiteral("UTF-32BE"); - - @TruffleBoundary - public static Charset getUTF32Charset(int byteorder) { - String utf32Name = getUTF32Name(byteorder).toJavaStringUncached(); - if (byteorder == NATIVE_ORDER) { - if (UTF32 == null) { - UTF32 = Charset.forName(utf32Name); - } - return UTF32; - } else if (byteorder < NATIVE_ORDER) { - if (UTF32LE == null) { - UTF32LE = Charset.forName(utf32Name); - } - return UTF32LE; - } - if (UTF32BE == null) { - UTF32BE = Charset.forName(utf32Name); - } - return UTF32BE; - } - - public static TruffleString getUTF32Name(int byteorder) { - TruffleString csName; - if (byteorder == 0) { - csName = T_UTF_32; - } else if (byteorder < 0) { - csName = T_UTF_32LE; - } else { - csName = T_UTF_32BE; - } - return csName; - } - } - @GenerateInline @GenerateCached(false) @GenerateUncached From 05f89951116e90fd7ba12413d34f534c4dcae792 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Thu, 20 Aug 2026 20:22:18 +0200 Subject: [PATCH 09/24] Remove unused CApiContext.dlopenFlagsToString --- .../builtins/objects/cext/capi/CApiContext.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java index 04048530b3..5afd1b65c7 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java @@ -68,7 +68,6 @@ import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; -import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -1146,20 +1145,6 @@ private static CApiContext loadCApi(Node node, PythonContext context, TruffleStr "termios", "pyexpat"); - private static String dlopenFlagsToString(int flags) { - String str = "RTLD_NOW"; - if ((flags & PosixConstants.RTLD_LAZY.value) != 0) { - str = "RTLD_LAZY"; - } - if ((flags & PosixConstants.RTLD_GLOBAL.value) != 0) { - str += "|RTLD_GLOBAL"; - } - if ((flags & PosixConstants.RTLD_LOCAL.value) != 0) { - str += "|RTLD_LOCAL"; - } - return str; - } - /** * This method loads a C extension module (C API) and will initialize the corresponding native * contexts if necessary. From 83e1884db425f9dfa046316955aba8aaf71b8414 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Mon, 24 Aug 2026 17:14:28 +0200 Subject: [PATCH 10/24] Add _imp multi-interpreter extension check override --- .../src/tests/test_imports.py | 11 ++++++++ .../builtins/modules/ImpModuleBuiltins.java | 25 +++++++++++++++++++ .../graal/python/nodes/ErrorMessages.java | 1 + .../graal/python/runtime/PythonContext.java | 20 +++++++++++++++ 4 files changed, 57 insertions(+) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_imports.py b/graalpython/com.oracle.graal.python.test/src/tests/test_imports.py index 8088257962..e9dcafd0f3 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_imports.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_imports.py @@ -59,6 +59,17 @@ def test_module_docstring(): assert moduleA.__doc__ == "MODULE A DOC" +def test_imp_override_multi_interp_extensions_check_is_rejected_in_main_interpreter(): + import _imp + + try: + _imp._override_multi_interp_extensions_check(1) + except RuntimeError as e: + assert str(e) == "_imp._override_multi_interp_extensions_check() cannot be used in the main interpreter" + else: + raise AssertionError("expected RuntimeError in the main interpreter") + + def test_dotted_import(): # this is to prevent ides from optimising out the unused import try: diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ImpModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ImpModuleBuiltins.java index cf7cbe2496..bc53c29ee0 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ImpModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ImpModuleBuiltins.java @@ -40,6 +40,7 @@ */ package com.oracle.graal.python.builtins.modules; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.RuntimeError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; import static com.oracle.graal.python.builtins.modules.ImpModuleBuiltins.FrozenStatus.FROZEN_DISABLED; import static com.oracle.graal.python.builtins.modules.ImpModuleBuiltins.FrozenStatus.FROZEN_EXCLUDED; @@ -918,4 +919,28 @@ protected ArgumentClinicProvider getArgumentClinic() { return ImpModuleBuiltinsClinicProviders.OverrideFrozenModulesForTestsClinicProviderGen.INSTANCE; } } + + @Builtin(name = "_override_multi_interp_extensions_check", minNumOfPositionalArgs = 1, parameterNames = {"override"}) + @ArgumentClinic(name = "override", conversion = ClinicConversion.Int) + @GenerateNodeFactory + abstract static class OverrideMultiInterpExtensionsCheck extends PythonUnaryClinicBuiltinNode { + + @Specialization + @TruffleBoundary + static Object doInt(int override, + @Bind Node node, + @Bind PythonContext context) { + if (context.isMainInterpreter()) { + throw PRaiseNode.raiseStatic(node, RuntimeError, ErrorMessages.CANNOT_BE_USED_IN_MAIN_INTERP); + } + int old = context.getOverrideMultiInterpExtensionsCheck(); + context.setOverrideMultiInterpExtensionsCheck(override); + return old; + } + + @Override + protected ArgumentClinicProvider getArgumentClinic() { + return ImpModuleBuiltinsClinicProviders.OverrideMultiInterpExtensionsCheckClinicProviderGen.INSTANCE; + } + } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java index 2e73793ff5..85860e580e 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java @@ -1636,4 +1636,5 @@ public abstract class ErrorMessages { public static final TruffleString S_MUST_BE_CALLABLE = tsLiteral("%s must be callable"); public static final TruffleString METHOD_CANNOT_BE_BOTH_CLASS_AND_STATIC = tsLiteral("method cannot be both class and static"); + public static final TruffleString CANNOT_BE_USED_IN_MAIN_INTERP = tsLiteral("_imp._override_multi_interp_extensions_check() cannot be used in the main interpreter"); } 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 df0b4c4c53..5dda431df8 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 @@ -837,6 +837,13 @@ public Thread getOwner() { // Used by CPython tests to selectively enable or disable frozen modules. private TriState overrideFrozenModules = TriState.UNDEFINED; + /** + * Per-interpreter override for checking legacy extension modules. + * Subinterpreters are not supported yet, but keeping this state on the context makes the + * eventual implementation independent for each interpreter. + */ + private int overrideMultiInterpExtensionsCheck; + // the full module name for package imports private TruffleString pyPackageContext; @@ -907,6 +914,19 @@ public void setOverrideFrozenModules(TriState overrideFrozenModules) { this.overrideFrozenModules = overrideFrozenModules; } + public int getOverrideMultiInterpExtensionsCheck() { + return overrideMultiInterpExtensionsCheck; + } + + public void setOverrideMultiInterpExtensionsCheck(int override) { + this.overrideMultiInterpExtensionsCheck = override; + } + + /** GraalPy does not support subinterpreters yet, so every context is currently the main one. */ + public boolean isMainInterpreter() { + return true; + } + @TruffleBoundary private static int computeMinIntBitLengthOverLimit(int limit) { /* From b16ad01345ccf35cc8b88b1cd295a4faedcad09a Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Mon, 24 Aug 2026 17:57:00 +0200 Subject: [PATCH 11/24] Implement support for creating modules from slots --- .gitignore | 1 + abi/abi3t-missing-linux.txt | 7 - .../include/Python.h | 1 + .../include/exports.h | 8 + .../include/modsupport.h | 32 ++ .../include/moduleobject.h | 31 +- .../include/pymacro.h | 21 + .../include/pytypedefs.h | 4 +- .../include/slots.h | 61 +++ .../src/tests/cpyext/test_module.py | 44 +- .../builtins/modules/ImpModuleBuiltins.java | 10 +- .../objects/cext/capi/CApiContext.java | 59 ++- .../objects/cext/capi/CApiFunction.java | 10 + .../builtins/objects/cext/capi/CExtNodes.java | 475 +++++++++++++----- .../cext/capi/ExternalFunctionSignature.java | 3 +- .../objects/cext/capi/PySlotIterator.java | 281 +++++++++++ .../cext/capi/transitions/ArgDescriptor.java | 6 +- .../objects/cext/structs/CFields.java | 13 +- .../objects/cext/structs/CStructAccess.java | 9 + .../objects/cext/structs/CStructs.java | 1 + .../builtins/objects/module/PythonModule.java | 76 ++- .../graal/python/nodes/ErrorMessages.java | 24 + .../graal/python/nodes/StringLiterals.java | 2 +- mx.graalpython/copyrights/overrides | 1 + scripts/generate_abi3t_manifest.py | 3 +- 25 files changed, 1019 insertions(+), 164 deletions(-) create mode 100644 graalpython/com.oracle.graal.python.cext/include/slots.h create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PySlotIterator.java diff --git a/.gitignore b/.gitignore index 585daaafed..99f63bcb55 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,7 @@ graalpython/lib-python/3/test/data /venv *.code-workspace graalpython/com.oracle.graal.python.test/src/tests/cpyext/*.c +!graalpython/com.oracle.graal.python.test/src/tests/cpyext/abi3t_smoke.c graalpython/com.oracle.graal.python.test/src/tests/cpyext/*.sha256 graalpython/com.oracle.graal.python.test/src/tests/cpyext/As_FileDescriptor_Testfile graalpython/lib-python/3/lib2to3/Grammar3.8.5.alpha.0.pickle diff --git a/abi/abi3t-missing-linux.txt b/abi/abi3t-missing-linux.txt index 512ee834e9..f1612ec40d 100644 --- a/abi/abi3t-missing-linux.txt +++ b/abi/abi3t-missing-linux.txt @@ -1,4 +1,3 @@ -FUNC PyABIInfo_Check FUNC PyEval_AcquireLock FUNC PyEval_CallFunction FUNC PyEval_CallMethod @@ -24,12 +23,6 @@ FUNC PyLong_FromInt64 FUNC PyLong_FromUInt32 FUNC PyLong_FromUInt64 FUNC PyLong_GetNativeLayout -FUNC PyModule_Exec -FUNC PyModule_FromSlotsAndSpec -FUNC PyModule_GetStateSize -FUNC PyModule_GetState_DuringGC -FUNC PyModule_GetToken -FUNC PyModule_GetToken_DuringGC FUNC PyObject_GetTypeData_DuringGC FUNC PySys_AddWarnOption FUNC PySys_AddWarnOptionUnicode diff --git a/graalpython/com.oracle.graal.python.cext/include/Python.h b/graalpython/com.oracle.graal.python.cext/include/Python.h index fc8b857125..88f6b17ff1 100644 --- a/graalpython/com.oracle.graal.python.cext/include/Python.h +++ b/graalpython/com.oracle.graal.python.cext/include/Python.h @@ -82,6 +82,7 @@ struct timeval; #include "lock.h" #include "object.h" #include "objimpl.h" +#include "slots.h" #include "typeslots.h" #include "pyhash.h" #include "cpython/pydebug.h" diff --git a/graalpython/com.oracle.graal.python.cext/include/exports.h b/graalpython/com.oracle.graal.python.cext/include/exports.h index dba2258190..82621b74b2 100644 --- a/graalpython/com.oracle.graal.python.cext/include/exports.h +++ b/graalpython/com.oracle.graal.python.cext/include/exports.h @@ -14,6 +14,7 @@ inside the Python core, they are private to the core. If in an extension module, it may be declared with external linkage depending on the platform. + PyMODEXPORT_FUNC: Like PyMODINIT_FUNC, but for a slots array. As a number of platforms support/require "__declspec(dllimport/dllexport)", we support a HAVE_DECLSPEC_DLL macro to save duplication. @@ -108,6 +109,13 @@ # define PyMODINIT_FUNC Py_EXPORTED_SYMBOL PyObject* # endif /* __cplusplus */ #endif +#ifndef PyMODEXPORT_FUNC +# if defined(__cplusplus) +# define PyMODEXPORT_FUNC extern "C" Py_EXPORTED_SYMBOL PySlot* +# else +# define PyMODEXPORT_FUNC Py_EXPORTED_SYMBOL PySlot* +# endif +#endif #endif /* Py_EXPORTS_H */ diff --git a/graalpython/com.oracle.graal.python.cext/include/modsupport.h b/graalpython/com.oracle.graal.python.cext/include/modsupport.h index 3c479ce12a..fd59b51fc2 100644 --- a/graalpython/com.oracle.graal.python.cext/include/modsupport.h +++ b/graalpython/com.oracle.graal.python.cext/include/modsupport.h @@ -139,6 +139,38 @@ PyAPI_FUNC(PyObject *) PyModule_FromDefAndSpec2(PyModuleDef *def, #endif /* New in 3.5 */ +#if !defined(Py_LIMITED_API) || defined(Py_TARGET_ABI3T) || Py_LIMITED_API+0 >= 0x030f0000 +typedef struct PyABIInfo { + uint8_t abiinfo_major_version; + uint8_t abiinfo_minor_version; + uint16_t flags; + uint32_t build_version; + uint32_t abi_version; +} PyABIInfo; + +#define PyABIInfo_STABLE 0x0001 +#define PyABIInfo_GIL 0x0002 +#define PyABIInfo_FREETHREADED 0x0004 +#define PyABIInfo_INTERNAL 0x0008 +#define PyABIInfo_FREETHREADING_AGNOSTIC (PyABIInfo_GIL | PyABIInfo_FREETHREADED) + +PyAPI_FUNC(int) PyABIInfo_Check(PyABIInfo *info, const char *module_name); + +#if defined(Py_TARGET_ABI3T) +# define PyABIInfo_DEFAULT_ABI_VERSION Py_TARGET_ABI3T +# define PyABIInfo_DEFAULT_FLAGS (PyABIInfo_STABLE | PyABIInfo_FREETHREADING_AGNOSTIC) +#elif defined(Py_LIMITED_API) +# define PyABIInfo_DEFAULT_ABI_VERSION Py_LIMITED_API +# define PyABIInfo_DEFAULT_FLAGS (PyABIInfo_STABLE | PyABIInfo_GIL) +#else +# define PyABIInfo_DEFAULT_ABI_VERSION PY_VERSION_HEX +# define PyABIInfo_DEFAULT_FLAGS PyABIInfo_GIL +#endif + +#define PyABIInfo_VAR(NAME) \ + static PyABIInfo NAME = {1, 0, PyABIInfo_DEFAULT_FLAGS, PY_VERSION_HEX, PyABIInfo_DEFAULT_ABI_VERSION}; +#endif + #ifndef Py_LIMITED_API # define Py_CPYTHON_MODSUPPORT_H # include "cpython/modsupport.h" diff --git a/graalpython/com.oracle.graal.python.cext/include/moduleobject.h b/graalpython/com.oracle.graal.python.cext/include/moduleobject.h index d800ba8c38..9497878ae4 100644 --- a/graalpython/com.oracle.graal.python.cext/include/moduleobject.h +++ b/graalpython/com.oracle.graal.python.cext/include/moduleobject.h @@ -76,14 +76,32 @@ struct PyModuleDef_Slot { void *value; }; -#define Py_mod_create 1 -#define Py_mod_exec 2 +#if defined(Py_TARGET_ABI3T) || (defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030f0000) +# define Py_mod_create 84 +# define Py_mod_exec 85 +# define Py_mod_multiple_interpreters 86 +# define Py_mod_gil 87 +# define Py_slot_subslots 92 +# define Py_mod_slots 94 +# define Py_mod_name 100 +# define Py_mod_doc 101 +# define Py_mod_state_size 102 +# define Py_mod_methods 103 +# define Py_mod_state_traverse 104 +# define Py_mod_state_clear 105 +# define Py_mod_state_free 106 +# define Py_mod_abi 109 +# define Py_mod_token 110 +#else +# define Py_mod_create 1 +# define Py_mod_exec 2 #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030c0000 # define Py_mod_multiple_interpreters 3 #endif #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030d0000 # define Py_mod_gil 4 #endif +#endif #ifndef Py_LIMITED_API @@ -109,6 +127,15 @@ struct PyModuleDef_Slot { PyAPI_FUNC(int) PyUnstable_Module_SetGIL(PyObject *module, void *gil); #endif +#if !defined(Py_LIMITED_API) || defined(Py_TARGET_ABI3T) || Py_LIMITED_API+0 >= 0x030f0000 +PyAPI_FUNC(PyObject *) PyModule_FromSlotsAndSpec(const PySlot *slots, PyObject *spec); +PyAPI_FUNC(int) PyModule_Exec(PyObject *module); +PyAPI_FUNC(int) PyModule_GetStateSize(PyObject *module, Py_ssize_t *result); +PyAPI_FUNC(int) PyModule_GetToken(PyObject *module, void **result); +PyAPI_FUNC(void *) PyModule_GetState_DuringGC(PyObject *module); +PyAPI_FUNC(int) PyModule_GetToken_DuringGC(PyObject *module, void **result); +#endif + struct PyModuleDef { PyModuleDef_Base m_base; const char* m_name; diff --git a/graalpython/com.oracle.graal.python.cext/include/pymacro.h b/graalpython/com.oracle.graal.python.cext/include/pymacro.h index 16b9d4e0bb..f3167305bf 100644 --- a/graalpython/com.oracle.graal.python.cext/include/pymacro.h +++ b/graalpython/com.oracle.graal.python.cext/include/pymacro.h @@ -28,6 +28,27 @@ # define static_assert _Static_assert #endif +// GraalPy change: added _Py_ANONYMOUS from 3.15 +// _Py_ANONYMOUS: modifier for declaring an anonymous union. +// Usage: _Py_ANONYMOUS union { ... }; +// Standards/compiler support: +// - C++ allows anonymous unions, but not structs +// - C11 and above allows anonymous unions and structs +// - MSVC has warning(disable: 4201) "nonstandard extension used : nameless +// struct/union". This is specific enough that we disable it for all of +// Python.h. +// - GCC & clang needs __extension__ before C11 +// To allow unsupported platforms which need other spellings, we use a +// predefined value of _Py_ANONYMOUS if it exists. +#ifndef _Py_ANONYMOUS +# if (defined(__GNUC__) || defined(__clang__)) \ + && !(defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) +# define _Py_ANONYMOUS __extension__ +# else +# define _Py_ANONYMOUS +# endif +#endif + /* Minimum value between x and y */ #define Py_MIN(x, y) (((x) > (y)) ? (y) : (x)) diff --git a/graalpython/com.oracle.graal.python.cext/include/pytypedefs.h b/graalpython/com.oracle.graal.python.cext/include/pytypedefs.h index 6141fb0305..e36df33333 100644 --- a/graalpython/com.oracle.graal.python.cext/include/pytypedefs.h +++ b/graalpython/com.oracle.graal.python.cext/include/pytypedefs.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2023, Oracle and/or its affiliates. +/* Copyright (c) 2023, 2026, Oracle and/or its affiliates. * Copyright (C) 1996-2023 Python Software Foundation * * Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 @@ -19,6 +19,8 @@ typedef struct PyModuleDef_Slot PyModuleDef_Slot; typedef struct PyMethodDef PyMethodDef; typedef struct PyGetSetDef PyGetSetDef; typedef struct PyMemberDef PyMemberDef; +// GraalPy change: added _Py_ANONYMOUS from 3.15 +typedef struct PySlot PySlot; typedef struct _object PyObject; typedef struct _longobject PyLongObject; diff --git a/graalpython/com.oracle.graal.python.cext/include/slots.h b/graalpython/com.oracle.graal.python.cext/include/slots.h new file mode 100644 index 0000000000..89533c39c8 --- /dev/null +++ b/graalpython/com.oracle.graal.python.cext/include/slots.h @@ -0,0 +1,61 @@ +/* Copyright (c) 2026, Oracle and/or its affiliates. + * Copyright (C) 1996-2026 Python Software Foundation + * + * Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 + */ +#ifndef _Py_HAVE_SLOTS_H +#define _Py_HAVE_SLOTS_H + +typedef void (*_Py_funcptr_t)(void); + +struct PySlot { + uint16_t sl_id; + uint16_t sl_flags; + _Py_ANONYMOUS union { + uint32_t sl_reserved; // must be 0 + }; + _Py_ANONYMOUS union { + void *sl_ptr; + _Py_funcptr_t sl_func; + Py_ssize_t sl_size; + int64_t sl_int64; + uint64_t sl_uint64; + }; +}; + +#define PySlot_OPTIONAL 0x0001 +#define PySlot_STATIC 0x0002 +#define PySlot_INTPTR 0x0004 + +#define Py_slot_invalid 0xffff + +#define PySlot_DATA(NAME, VALUE) \ + {.sl_id=(NAME), .sl_flags=PySlot_INTPTR, .sl_ptr=(void*)(VALUE)} + +#define PySlot_FUNC(NAME, VALUE) \ + {.sl_id=(NAME), .sl_func=(_Py_funcptr_t)(VALUE)} + +#define PySlot_SIZE(NAME, VALUE) \ + {.sl_id=(NAME), .sl_size=(Py_ssize_t)(VALUE)} + +#define PySlot_INT64(NAME, VALUE) \ + {.sl_id=(NAME), .sl_int64=(int64_t)(VALUE)} + +#define PySlot_UINT64(NAME, VALUE) \ + {.sl_id=(NAME), .sl_uint64=(uint64_t)(VALUE)} + +#define PySlot_STATIC_DATA(NAME, VALUE) \ + {.sl_id=(NAME), .sl_flags=PySlot_STATIC, .sl_ptr=(VALUE)} + +#define PySlot_END {0} + + +// Macros without designated initializers (for C++11 and below): + +#define PySlot_PTR(NAME, VALUE) \ + {(NAME), PySlot_INTPTR, {0}, {(void*)(VALUE)}} + +#define PySlot_PTR_STATIC(NAME, VALUE) \ + {(NAME), PySlot_INTPTR | PySlot_STATIC, {0}, {(void*)(VALUE)}} + +#endif // _Py_HAVE_SLOTS_H diff --git a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_module.py b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_module.py index e0a89a85bf..d79e731c43 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_module.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_module.py @@ -1,4 +1,4 @@ -# Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2021, 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 @@ -39,7 +39,7 @@ import sys -from . import CPyExtTestCase, CPyExtFunction, unhandled_error_compare +from . import CPyExtTestCase, CPyExtFunction, compile_module_from_string, unhandled_error_compare ModuleType = type(sys) @@ -72,6 +72,46 @@ def _reference_get_filename(args): class TestPyModule(CPyExtTestCase): + def test_multiple_exec_slots(self): + module = compile_module_from_string(r''' + #include + + static int first_exec(PyObject *module) { + int *state = PyModule_GetState(module); + *state = 1; + return 0; + } + + static int second_exec(PyObject *module) { + int *state = PyModule_GetState(module); + *state = *state * 10 + 2; + return PyModule_AddIntConstant(module, "exec_order", *state); + } + + static PyModuleDef_Slot slots[] = { + {Py_mod_exec, first_exec}, + {Py_mod_exec, second_exec}, + {0, NULL} + }; + + static PyModuleDef module_def = { + PyModuleDef_HEAD_INIT, + "multiple_exec_slots", + NULL, + sizeof(int), + NULL, + slots, + NULL, + NULL, + NULL + }; + + PyMODINIT_FUNC PyInit_multiple_exec_slots(void) { + return PyModuleDef_Init(&module_def); + } + ''', "multiple_exec_slots") + self.assertEqual(module.exec_order, 12) + test_PyModule_Check = CPyExtFunction( lambda args: isinstance(args[0], ModuleType), lambda: ( diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ImpModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ImpModuleBuiltins.java index bc53c29ee0..061d2bf9e4 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ImpModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ImpModuleBuiltins.java @@ -263,21 +263,17 @@ static int doPythonModule(VirtualFrame frame, PythonModule extensionModule, @Bind PythonContext context, @Bind Node inliningTarget, @Cached("createFor($node)") BoundaryCallData boundaryCallData) { - long nativeModuleDef = extensionModule.getNativeModuleDef(); - if (nativeModuleDef == NULLPTR) { - return 0; - } PythonLanguage language = context.getLanguage(inliningTarget); Object state = BoundaryCallContext.enter(frame, language, context, boundaryCallData); try { - return doExec(inliningTarget, context, extensionModule, nativeModuleDef); + return doExec(inliningTarget, context, extensionModule); } finally { BoundaryCallContext.exit(frame, language, context, state); } } @TruffleBoundary - private static int doExec(Node node, PythonContext context, PythonModule extensionModule, long nativeModuleDef) { + private static int doExec(Node node, PythonContext context, PythonModule extensionModule) { /* * Check if module is already initialized. CPython does that by testing if 'md_state != * NULL'. So, we do the same. @@ -295,7 +291,7 @@ private static int doExec(Node node, PythonContext context, PythonModule extensi * ExecModuleNode will run the module definition's exec function which may run arbitrary * C code. So we need to setup an indirect call. */ - return CExtNodes.execModule(node, context.getCApiContext(), extensionModule, nativeModuleDef); + return CExtNodes.execModule(node, context.getCApiContext(), extensionModule); } @Fallback diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java index 5afd1b65c7..61f948c550 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java @@ -100,6 +100,7 @@ import com.oracle.graal.python.builtins.objects.cext.capi.transitions.ReferenceQueueCoordinator; import com.oracle.graal.python.builtins.objects.cext.common.LoadCExtException.ApiInitException; import com.oracle.graal.python.builtins.objects.cext.common.LoadCExtException.ImportException; +import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.TransformExceptionFromNativeNode; import com.oracle.graal.python.builtins.objects.cext.copying.NativeLibraryLocator; import com.oracle.graal.python.builtins.objects.cext.structs.CFields; import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess; @@ -164,6 +165,8 @@ public final class CApiContext { private static final TruffleString T_PY_INIT = tsLiteral("PyInit_"); private static final TruffleString T_PY_INIT_U = tsLiteral("PyInitU_"); + private static final TruffleString T_PY_MOD_EXPORT = tsLiteral("PyModExport_"); + private static final TruffleString T_PY_MOD_EXPORT_U = tsLiteral("PyModExportU_"); private static final TruffleString T_GRAALPY_TEST_CAPI = tsLiteral("_testcapi"); private static final TruffleString T_GRAALPY_TEST_CAPI_NAME = tsLiteral("__graalpython__._testcapi"); @@ -318,13 +321,18 @@ private static boolean canEncode(TruffleString basename) { @TruffleBoundary public TruffleString getInitFunctionName() { - /* - * n.b.: 'getEncodedName' also sets 'ascii' and must therefore be called before 'ascii' - * is queried - */ + // 'getEncodedName' also sets 'ascii' and must therefore be called before 'ascii' is queried TruffleString s = getEncodedName(); return StringUtils.cat((ascii ? T_PY_INIT : T_PY_INIT_U), s); } + + public TruffleString getModExecFunctionName() { + CompilerAsserts.neverPartOfCompilation(); + assert PythonContext.get(null).getOption(PythonOptions.EnableAbi3t); + // 'getEncodedName' also sets 'ascii' and must therefore be called before 'ascii' is queried + TruffleString s = getEncodedName(); + return StringUtils.cat((ascii ? T_PY_MOD_EXPORT : T_PY_MOD_EXPORT_U), s); + } } /* @@ -1211,7 +1219,7 @@ public static Object loadCExtModule(Node location, PythonContext context, Module throw new ImportException(CApiContext.wrapJavaException(e, location), spec.name, spec.path, ErrorMessages.CANNOT_LOAD_M, spec.path, e); } - return cApiContext.initCApiModule(location, library, spec.getInitFunctionName(), spec); + return cApiContext.initCApiModule(location, library, spec); } /** @@ -1355,12 +1363,22 @@ public void finalizeCApi(boolean cancelling) { } } - public Object initCApiModule(Node node, NativeLibrary sharedLibrary, TruffleString initFuncName, ModuleSpec spec) throws ImportException { + public Object initCApiModule(Node node, NativeLibrary sharedLibrary, ModuleSpec spec) throws ImportException { + CompilerAsserts.neverPartOfCompilation(); + if (context.getOption(PythonOptions.EnableAbi3t)) { + TruffleString modExportFuncName = spec.getModExecFunctionName(); + long modExportFunc = sharedLibrary.lookupOptionalSymbol(modExportFuncName.toJavaStringUncached()); + if (modExportFunc != NULLPTR) { + return initAbi3tCApiModule(node, sharedLibrary, modExportFunc, spec); + } + } + return initLegacyCApiModule(node, sharedLibrary, spec.getInitFunctionName(), spec); + } + + private Object initLegacyCApiModule(Node node, NativeLibrary sharedLibrary, TruffleString initFuncName, ModuleSpec spec) throws ImportException { CompilerAsserts.neverPartOfCompilation(); - PythonContext context = getContext(); - CApiContext cApiContext = context.getCApiContext(); long pyinitFunc = sharedLibrary.lookupOptionalSymbol(initFuncName.toJavaStringUncached()); - if (pyinitFunc == 0L) { + if (pyinitFunc == NULLPTR) { throw new ImportException(null, spec.name, spec.path, ErrorMessages.NO_FUNCTION_FOUND, "", initFuncName, spec.path); } NativeContext nativeContext = context.ensureNativeContext(); @@ -1368,7 +1386,7 @@ public Object initCApiModule(Node node, NativeLibrary sharedLibrary, TruffleStri ExternalFunctionSignature.MODINIT.bind(nativeContext, pyinitFunc)); Object result = PyObjectCheckFunctionResultNodeGen.getUncached().execute(context, initFuncName, NativeToPythonInternalNode.executeUncached(nativeResult, false)); - if (!(result instanceof PythonModule)) { + if (!(result instanceof PythonModule module)) { // Multi-phase extension module initialization /* @@ -1383,10 +1401,9 @@ public Object initCApiModule(Node node, NativeLibrary sharedLibrary, TruffleStri throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.SystemError, ErrorMessages.INIT_FUNC_RETURNED_UNINT_OBJ, initFuncName); } - return CExtNodes.createModule(node, cApiContext, spec, nativeResult, sharedLibrary); + return CExtNodes.createModuleFromDefAndSpec(node, this, spec, nativeResult, sharedLibrary); } else { // see: 'import.c: _PyImport_FixupExtensionObject' - PythonModule module = (PythonModule) result; module.setAttribute(T___FILE__, spec.path); addLoadedExtensionLibrary(sharedLibrary); @@ -1408,6 +1425,24 @@ public Object initCApiModule(Node node, NativeLibrary sharedLibrary, TruffleStri } } + // import.c: import_run_modexport + private Object initAbi3tCApiModule(Node node, NativeLibrary library, long modExportFunc, ModuleSpec spec) { + CompilerAsserts.neverPartOfCompilation(); + NativeContext nativeContext = context.ensureNativeContext(); + PythonThreadState threadState = context.getThreadState(context.getLanguage()); + // return value is of type 'PySlot *' + long slots = ExternalFunctionInvoker.invokeMODINIT(null, TIMING_INVOKE_MODULE_INIT, nativeContext, BoundaryCallData.getUncached(), threadState, + ExternalFunctionSignature.MODINIT.bind(nativeContext, modExportFunc)); + TransformExceptionFromNativeNode.getUncached().execute(node, threadState, spec.name, slots == NULLPTR, true, + ErrorMessages.MODULE_EXPORT_HOOK_FAILED, ErrorMessages.MODULE_EXPORT_HOOK_RAISED_EXCEPTION); + Object module = CExtNodes.createModuleFromSlotsAndSpec(node, context.getCApiContext(), library, slots, spec); + if (module instanceof PythonModule pythonModule && pythonModule.getNativeModuleToken() == NULLPTR) { + // import_run_modexport uses the static top-level slots array as the default token. + pythonModule.setNativeModuleToken(slots); + } + return module; + } + @TruffleBoundary public PythonModule findExtension(TruffleString filename, TruffleString name) { return extensions.get(Pair.create(filename, name)); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiFunction.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiFunction.java index 7263c8724f..c9a41c4779 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiFunction.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiFunction.java @@ -41,6 +41,7 @@ package com.oracle.graal.python.builtins.objects.cext.capi; import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.CImpl; +import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Direct; import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Ignored; import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.NotImplemented; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CHAR; @@ -51,6 +52,7 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_PYPRECONFIG_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_PY_BUFFER; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_PY_BUFFER_PTR; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_PY_SLOT_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_PY_SSIZE_T_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_PY_UCS4; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_UINT8_T_PTR; @@ -80,6 +82,7 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PYSTATUS; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PYWIDESTRINGLIST_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_AUDITHOOKFUNCTION; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_ABI_INFO_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_BUFFER_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_COMPILER_FLAGS; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_COMPLEX; @@ -185,6 +188,7 @@ */ public final class CApiFunction { + @CApiBuiltin(name = "PyABIInfo_Check", ret = Int, args = {PY_ABI_INFO_PTR, ConstCharPtr}, call = Direct) @CApiBuiltin(name = "PyGILState_Check", ret = Int, args = {}, acquireGil = false, call = CImpl) @CApiBuiltin(name = "PyArg_Parse", ret = Int, args = {PyObject, ConstCharPtrAsTruffleString, VARARGS}, call = CImpl) @CApiBuiltin(name = "PyArg_ParseTuple", ret = Int, args = {PyObject, ConstCharPtrAsTruffleString, VARARGS}, call = CImpl) @@ -359,11 +363,17 @@ public final class CApiFunction { @CApiBuiltin(name = "PyModule_Create2", ret = PyObject, args = {PYMODULEDEF_PTR, Int}, call = CImpl) @CApiBuiltin(name = "PyModule_FromDefAndSpec2", ret = PyObject, args = {PyModuleDef, PyObject, Int}, call = CImpl) @CApiBuiltin(name = "PyModule_ExecDef", ret = Int, args = {PyObject, PyModuleDef}, call = CImpl) + @CApiBuiltin(name = "PyModule_Exec", ret = Int, args = {PyObject}, call = Direct) + @CApiBuiltin(name = "PyModule_FromSlotsAndSpec", ret = PyObjectTransfer, args = {CONST_PY_SLOT_PTR, PyObject}, call = Direct) @CApiBuiltin(name = "PyModule_GetDef", ret = PyModuleDef, args = {PyObject}, call = CImpl) @CApiBuiltin(name = "PyModule_GetDict", ret = PyObject, args = {PyObject}, call = CImpl) @CApiBuiltin(name = "PyModule_GetFilename", ret = ConstCharPtrAsTruffleString, args = {PyObject}, call = CImpl) @CApiBuiltin(name = "PyModule_GetName", ret = ConstCharPtrAsTruffleString, args = {PyObject}, call = CImpl) @CApiBuiltin(name = "PyModule_GetState", ret = Pointer, args = {PyObject}, call = CImpl) + @CApiBuiltin(name = "PyModule_GetStateSize", ret = Int, args = {PyObject, PY_SSIZE_T_PTR}, call = Direct) + @CApiBuiltin(name = "PyModule_GetState_DuringGC", ret = Pointer, args = {PyObject}, call = Direct) + @CApiBuiltin(name = "PyModule_GetToken", ret = Int, args = {PyObject, VOID_PTR_LIST}, call = Direct) + @CApiBuiltin(name = "PyModule_GetToken_DuringGC", ret = Int, args = {PyObject, VOID_PTR_LIST}, call = Direct) @CApiBuiltin(name = "PyNumber_Absolute", ret = PyObject, args = {PyObject}, call = CImpl) @CApiBuiltin(name = "PyNumber_Add", ret = PyObject, args = {PyObject, PyObject}, call = CImpl) @CApiBuiltin(name = "PyNumber_And", ret = PyObject, args = {PyObject, PyObject}, call = CImpl) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java index bcaed1cdfb..40500c9296 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java @@ -55,12 +55,14 @@ import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyMethodDef__ml_flags; import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyMethodDef__ml_meth; import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyMethodDef__ml_name; -import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyModuleDef_Slot__slot; -import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyModuleDef_Slot__value; +import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyModuleDef__m_clear; import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyModuleDef__m_doc; +import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyModuleDef__m_free; import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyModuleDef__m_methods; +import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyModuleDef__m_name; import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyModuleDef__m_size; import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyModuleDef__m_slots; +import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyModuleDef__m_traverse; import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyObject__ob_type; import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyTypeObject__tp_as_buffer; import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readDoubleField; @@ -69,14 +71,14 @@ import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readStructArrayIntField; import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readStructArrayPtrField; import static com.oracle.graal.python.builtins.objects.object.PythonObject.MANAGED_REFCNT; +import static com.oracle.graal.python.nodes.HiddenAttr.METHOD_DEF_PTR; +import static com.oracle.graal.python.nodes.SpecialMethodNames.T___COMPLEX__; +import static com.oracle.graal.python.runtime.exception.PythonErrorType.SystemError; import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.NULLPTR; import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.calloc; import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.mallocByteArray; import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.writeByteArrayElement; import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.writeByteArrayElements; -import static com.oracle.graal.python.nodes.HiddenAttr.METHOD_DEF_PTR; -import static com.oracle.graal.python.nodes.SpecialMethodNames.T___COMPLEX__; -import static com.oracle.graal.python.runtime.exception.PythonErrorType.SystemError; import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; import java.lang.ref.Reference; @@ -93,10 +95,10 @@ import com.oracle.graal.python.builtins.objects.cext.PythonNativeClass; import com.oracle.graal.python.builtins.objects.cext.PythonNativeObject; import com.oracle.graal.python.builtins.objects.cext.capi.CApiContext.ModuleSpec; -import com.oracle.graal.python.builtins.objects.cext.capi.ExternalFunctionNodes.PyObjectCheckFunctionResultNode; import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodesFactory.AsCharPointerNodeGen; import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodesFactory.EnsurePythonObjectNodeGen; import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodesFactory.FromCharPointerNodeGen; +import com.oracle.graal.python.builtins.objects.cext.capi.ExternalFunctionNodes.PyObjectCheckFunctionResultNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTiming; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.HandlePointerConverter; @@ -134,7 +136,6 @@ import com.oracle.graal.python.lib.PyFloatAsDoubleNode; import com.oracle.graal.python.lib.PyNumberAsSizeNode; import com.oracle.graal.python.lib.PyObjectSizeNode; -import com.oracle.graal.python.runtime.nativeaccess.NativeFunctionPointer; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.HiddenAttr; import com.oracle.graal.python.nodes.PGuards; @@ -156,6 +157,8 @@ import com.oracle.graal.python.runtime.PythonContext.PythonThreadState; import com.oracle.graal.python.runtime.exception.PException; import com.oracle.graal.python.runtime.exception.PythonErrorType; +import com.oracle.graal.python.runtime.nativeaccess.NativeFunctionPointer; +import com.oracle.graal.python.runtime.nativeaccess.NativeLibrary; import com.oracle.graal.python.runtime.object.PFactory; import com.oracle.graal.python.runtime.sequence.storage.MroSequenceStorage; import com.oracle.graal.python.util.PythonUtils; @@ -956,12 +959,46 @@ static long doOther(Node inliningTarget, Object object, } } - // according to definitions in 'moduleobject.h' + // according to definitions in 'moduleobject.h' (for Python 3.15: defined in 'slots.toml') private static final int SLOT_PY_MOD_CREATE = 1; private static final int SLOT_PY_MOD_EXEC = 2; private static final int SLOT_PY_MOD_MULTIPLE_INTERPRETERS = 3; private static final int SLOT_PY_MOD_GIL = 4; + /* + * The following slot values are the "new" values required for abi3t. + * They are defined in the future Python version 3.15. Eventually, they will become the default and the above values will be legacy. + */ + private static final int SLOT_ABI3T_PY_MOD_CREATE = 84; + private static final int SLOT_ABI3T_PY_MOD_EXEC = 85; + private static final int SLOT_ABI3T_PY_MOD_MULTIPLE_INTERPRETERS = 86; + private static final int SLOT_ABI3T_PY_MOD_GIL = 87; + private static final int SLOT_ABI3T_PY_MOD_NAME = 100; + private static final int SLOT_ABI3T_PY_MOD_DOC = 101; + private static final int SLOT_ABI3T_PY_MOD_STATE_SIZE = 102; + private static final int SLOT_ABI3T_PY_MOD_METHODS = 103; + private static final int SLOT_ABI3T_PY_MOD_STATE_TRAVERSE = 104; + private static final int SLOT_ABI3T_PY_MOD_STATE_CLEAR = 105; + private static final int SLOT_ABI3T_PY_MOD_STATE_FREE = 106; + private static final int SLOT_ABI3T_PY_MOD_ABI = 109; + private static final int SLOT_ABI3T_PY_MOD_TOKEN = 110; + + private static final int PY_ABI_INFO_STABLE = 0x0001; + private static final int PY_ABI_INFO_GIL = 0x0002; + private static final int PY_ABI_INFO_FREETHREADED = 0x0004; + private static final int PY_ABI_INFO_INTERNAL = 0x0008; + private static final int ABI3T_VERSION_HEX = 0x030f0000; + private static final int MINIMUM_STABLE_ABI_VERSION_HEX = 0x03020000; + + // for Py_mod_gil + private static final int Py_MOD_GIL_USED = 0; + private static final int Py_MOD_GIL_NOT_USED = 1; + + // for Py_mod_multiple_interpreters + private static final int Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED = 0; + private static final int Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED = 1; + private static final int Py_MOD_PER_INTERPRETER_GIL_SUPPORTED = 2; + private static final CApiTiming TIMING_MOD_CREATE = CApiTiming.create(true, "Py_mod_create"); private static final CApiTiming TIMING_MOD_EXEC = CApiTiming.create(true, "Py_mod_exec"); @@ -984,178 +1021,364 @@ static long doOther(Node inliningTarget, Object object, * */ @TruffleBoundary - static Object createModule(Node node, CApiContext capiContext, ModuleSpec moduleSpec, long moduleDefPtr, Object library) { + static Object createModuleFromDefAndSpec(Node node, CApiContext capiContext, ModuleSpec moduleSpec, long moduleDefPtr, NativeLibrary library) { /* * The name of the module is taken from the module spec and *NOT* from the module * definition. */ TruffleString mName = moduleSpec.name; - Object mDoc; - long mSize; - // do not eagerly read the doc string; this turned out to be unnecessarily expensive - long docPtr = readPtrField(moduleDefPtr, PyModuleDef__m_doc); - if (docPtr == NULLPTR) { - mDoc = NO_VALUE; - } else { - mDoc = FromCharPointerNode.executeUncached(docPtr); + NativeModuleDefinition definition = new NativeModuleDefinition(moduleDefPtr, true); + definition.name = readPtrField(moduleDefPtr, PyModuleDef__m_name); + definition.doc = readPtrField(moduleDefPtr, PyModuleDef__m_doc); + definition.stateSize = readLongField(moduleDefPtr, PyModuleDef__m_size); + + long slotDefinitions = readPtrField(moduleDefPtr, PyModuleDef__m_slots); + definition.methods = readPtrField(moduleDefPtr, PyModuleDef__m_methods); + definition.traverseFunction = readPtrField(moduleDefPtr, PyModuleDef__m_traverse); + definition.clearFunction = readPtrField(moduleDefPtr, PyModuleDef__m_clear); + definition.freeFunction = readPtrField(moduleDefPtr, PyModuleDef__m_free); + definition.token = moduleDefPtr; + return createModule(node, capiContext, library, moduleSpec, + parseModuleSlots(node, PySlotIterator.initLegacy(node, mName, slotDefinitions, PySlotIterator.SlotKind.MODULE), definition, mName).getDefinition()); + } + + /** + * Equivalent of {@code moduleobject.c: PyModule_FromSlotsAndSpec}. Creates a Python module from slots definition. + * + *
+     * struct PySlot {
+     *     uint16_t sl_id;
+     *     uint16_t sl_flags;
+     *     _Py_ANONYMOUS union {
+     *         uint32_t sl_reserved; // must be 0
+     *     };
+     *     _Py_ANONYMOUS union {
+     *         void *sl_ptr;
+     *         _Py_funcptr_t sl_func;
+     *         Py_ssize_t sl_size;
+     *         int64_t sl_int64;
+     *         uint64_t sl_uint64;
+     *     };
+     * };
+     * 
+ */ + @TruffleBoundary + public static Object createModuleFromSlotsAndSpec(Node node, CApiContext capiContext, NativeLibrary library, long slots, ModuleSpec moduleSpec) { + PythonContext context = capiContext.getContext(); + ParsedModuleSlots parsed = parseModuleSlots(node, PySlotIterator.init(node, moduleSpec.name, slots, PySlotIterator.SlotKind.MODULE), null, moduleSpec.name); + if (!parsed.sawAbi) { + throw PRaiseNode.raiseStatic(node, SystemError, ErrorMessages.MODULE_DOES_NOT_DEFINE_ABI, moduleSpec.name); } - mSize = readLongField(moduleDefPtr, PyModuleDef__m_size); + if (parsed.definition.requiresGil) { + // TODO(fa): enable gil + } - if (mSize < 0) { - throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.SystemError, ErrorMessages.M_SIZE_CANNOT_BE_NEGATIVE, mName); + // By default, multi-phase init modules are expected to work under multiple interpreters. + if (parsed.multipleInterpreters == Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED) { + if (!context.isMainInterpreter()) { + checkSubinterpIncompatibleExtensionAllowed(node, context, moduleSpec.name); + } + } else if (parsed.multipleInterpreters != Py_MOD_PER_INTERPRETER_GIL_SUPPORTED && context.ownsGil() && !context.isMainInterpreter()) { + checkSubinterpIncompatibleExtensionAllowed(node, context, moduleSpec.name); } - // parse slot definitions - long createFunction = NULLPTR; - boolean hasExecutionSlots = false; - long slotDefinitions = readPtrField(moduleDefPtr, PyModuleDef__m_slots); - if (slotDefinitions != NULLPTR) { - loop: for (int i = 0;; i++) { - int slotId = readStructArrayIntField(slotDefinitions, i, PyModuleDef_Slot__slot); - switch (slotId) { - case 0: - break loop; - case SLOT_PY_MOD_CREATE: - if (createFunction != NULLPTR) { - throw PRaiseNode.raiseStatic(node, SystemError, ErrorMessages.MODULE_HAS_MULTIPLE_CREATE_SLOTS, mName); + return createModule(node, capiContext, library, moduleSpec, parsed.getDefinition()); + } + + private static final class ParsedModuleSlots { + private final NativeModuleDefinition definition; + private long multipleInterpreters = Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED; + private boolean sawAbi; + + private ParsedModuleSlots(NativeModuleDefinition definition) { + this.definition = definition; + } + + private NativeModuleDefinition getDefinition() { + return definition; + } + } + + private static final class NativeModuleDefinition { + private final long moduleDef; + private final boolean tokenIsDef; + private long createFunction; + private long execFunction; + private boolean hasExecSlots; + private long name; + private long stateSize; + private long doc; + private long methods; + private long traverseFunction; + private long clearFunction; + private long freeFunction; + private long token; + private boolean requiresGil = true; + + private NativeModuleDefinition(long moduleDef, boolean tokenIsDef) { + this.moduleDef = moduleDef; + this.tokenIsDef = tokenIsDef; + } + } + + private static ParsedModuleSlots parseModuleSlots(Node node, PySlotIterator iterator, NativeModuleDefinition originalDefinition, TruffleString moduleName) { + ParsedModuleSlots parsed = new ParsedModuleSlots(originalDefinition == null ? new NativeModuleDefinition(NULLPTR, false) : originalDefinition); + while (iterator.next()) { + PySlotIterator.Slot slot = iterator.current(); + long value = slot.pointer(); + switch (slot.id()) { + case SLOT_ABI3T_PY_MOD_CREATE: + parsed.definition.createFunction = value; + break; + case SLOT_ABI3T_PY_MOD_EXEC: + if (originalDefinition == null) { + if (parsed.definition.hasExecSlots) { + throw PRaiseNode.raiseStatic(node, SystemError, ErrorMessages.MODULE_HAS_MULTIPLE_EXEC_SLOTS, moduleName); } - createFunction = readStructArrayPtrField(slotDefinitions, i, PyModuleDef_Slot__value); - break; - case SLOT_PY_MOD_EXEC: - hasExecutionSlots = true; - break; - case SLOT_PY_MOD_MULTIPLE_INTERPRETERS: - // ignored - // (mq) TODO: handle multiple interpreter cases - break; - case SLOT_PY_MOD_GIL: - // ignored - break; - default: - throw PRaiseNode.raiseStatic(node, SystemError, ErrorMessages.MODULE_USES_UNKNOW_SLOT_ID, mName, slotId); - } + parsed.definition.execFunction = value; + } + parsed.definition.hasExecSlots = true; + break; + case SLOT_ABI3T_PY_MOD_MULTIPLE_INTERPRETERS: + parsed.multipleInterpreters = value; + break; + case SLOT_ABI3T_PY_MOD_GIL: + parsed.definition.requiresGil = value != Py_MOD_GIL_NOT_USED; + break; + case SLOT_ABI3T_PY_MOD_ABI: + checkAbiInfo(node, value, moduleName); + parsed.sawAbi = true; + break; + case SLOT_ABI3T_PY_MOD_TOKEN: + if (originalDefinition != null && originalDefinition.moduleDef != value) { + throw PRaiseNode.raiseStatic(node, SystemError, ErrorMessages.MODULE_ARBITRARY_TOKEN_WITH_MODULE_DEF, moduleName); + } + parsed.definition.token = value; + break; + case SLOT_ABI3T_PY_MOD_NAME: + checkDefinitionField(node, originalDefinition, originalDefinition == null ? 0 : originalDefinition.name, value, moduleName, "m_name"); + parsed.definition.name = value; + break; + case SLOT_ABI3T_PY_MOD_DOC: + checkDefinitionField(node, originalDefinition, originalDefinition == null ? 0 : originalDefinition.doc, value, moduleName, "m_doc"); + parsed.definition.doc = value; + break; + case SLOT_ABI3T_PY_MOD_STATE_SIZE: + checkDefinitionField(node, originalDefinition, originalDefinition == null ? 0 : originalDefinition.stateSize, slot.size(), moduleName, "m_size"); + parsed.definition.stateSize = slot.size(); + break; + case SLOT_ABI3T_PY_MOD_METHODS: + checkDefinitionField(node, originalDefinition, originalDefinition == null ? 0 : originalDefinition.methods, value, moduleName, "m_methods"); + parsed.definition.methods = value; + break; + case SLOT_ABI3T_PY_MOD_STATE_TRAVERSE: + checkDefinitionField(node, originalDefinition, originalDefinition == null ? 0 : originalDefinition.traverseFunction, value, moduleName, "m_traverse"); + parsed.definition.traverseFunction = value; + break; + case SLOT_ABI3T_PY_MOD_STATE_CLEAR: + checkDefinitionField(node, originalDefinition, originalDefinition == null ? 0 : originalDefinition.clearFunction, value, moduleName, "m_clear"); + parsed.definition.clearFunction = value; + break; + case SLOT_ABI3T_PY_MOD_STATE_FREE: + checkDefinitionField(node, originalDefinition, originalDefinition == null ? 0 : originalDefinition.freeFunction, value, moduleName, "m_free"); + parsed.definition.freeFunction = value; + break; } } + parsed.sawAbi = iterator.sawSlot(SLOT_ABI3T_PY_MOD_ABI); + return parsed; + } + + private static void checkDefinitionField(Node node, NativeModuleDefinition originalDefinition, long originalValue, long newValue, TruffleString moduleName, String field) { + if (originalDefinition != null && originalValue != newValue) { + throw PRaiseNode.raiseStatic(node, SystemError, ErrorMessages.MODULE_SLOT_CONFLICTS_WITH_MODULE_DEF, moduleName, field); + } + } + + private static Object createModule(Node node, CApiContext capiContext, NativeLibrary library, ModuleSpec moduleSpec, NativeModuleDefinition definition) { + + if (definition.stateSize < 0) { + throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.SystemError, ErrorMessages.M_SIZE_CANNOT_BE_NEGATIVE, moduleSpec.name); + } PythonContext context = capiContext.getContext(); Object module; - if (createFunction != NULLPTR) { - PythonThreadState threadState = context.getThreadState(context.getLanguage()); - NativeFunctionPointer modCreate = ExternalFunctionSignature.MODCREATE.bind(context.ensureNativeContext(), createFunction); - long result = ExternalFunctionInvoker.invokeMODCREATE(null, TIMING_MOD_CREATE, context.ensureNativeContext(), - BoundaryCallData.getUncached(), threadState, modCreate, - PythonToNativeInternalNode.executeUncached(moduleSpec.originalModuleSpec, false), moduleDefPtr); - TransformExceptionFromNativeNode.getUncached().execute(null, threadState, mName, result == NULLPTR, true, - ErrorMessages.CREATION_FAILD_WITHOUT_EXCEPTION, ErrorMessages.CREATION_RAISED_EXCEPTION); - module = NativeToPythonInternalNode.executeUncached(result, true); - - /* - * We are more strict than CPython and require this to be a PythonModule object. This - * means, if the custom 'create' function uses a native subtype of the module type, then - * we require it to call our new function. - */ - if (!(module instanceof PythonModule)) { - if (mSize > 0) { - throw PRaiseNode.raiseStatic(node, SystemError, ErrorMessages.NOT_A_MODULE_OBJECT_BUT_REQUESTS_MODULE_STATE, mName); - } - if (hasExecutionSlots) { - throw PRaiseNode.raiseStatic(node, SystemError, ErrorMessages.MODULE_SPECIFIES_EXEC_SLOTS_BUT_DIDNT_CREATE_INSTANCE, mName); - } - // otherwise CPython is just fine - } else { - ((PythonModule) module).setNativeModuleDef(moduleDefPtr); - } + if (definition.createFunction != NULLPTR) { + module = callCreateAndCheckResult(node, moduleSpec, context, definition); } else { - PythonModule pythonModule = PFactory.createPythonModule(mName); - pythonModule.setNativeModuleDef(moduleDefPtr); - module = pythonModule; + module = PFactory.createPythonModule(moduleSpec.name); + } + if (module instanceof PythonModule pythonModule) { + initializeNativeModule(pythonModule, definition); } - long methodDefinitions = readPtrField(moduleDefPtr, PyModuleDef__m_methods); - if (methodDefinitions != NULLPTR) { + if (definition.methods != NULLPTR) { for (int i = 0;; i++) { - PBuiltinFunction fun = createLegacyMethod(methodDefinitions, i, context.getLanguage()); + PBuiltinFunction fun = createLegacyMethod(definition.methods, i, context.getLanguage()); if (fun == null) { break; } PBuiltinMethod method = PFactory.createBuiltinMethod(context.getLanguage(), module, fun); - WriteAttributeToPythonObjectNode.getUncached().execute(method, SpecialAttributeNames.T___MODULE__, mName); + WriteAttributeToPythonObjectNode.executeUncached(method, SpecialAttributeNames.T___MODULE__, moduleSpec.name); WriteAttributeToObjectNode.getUncached().execute(module, fun.getName(), method); } } + Object mDoc; + if (definition.doc == NULLPTR) { + mDoc = NO_VALUE; + } else { + mDoc = FromCharPointerNode.executeUncached(definition.doc); + } WriteAttributeToObjectNode.getUncached().execute(module, SpecialAttributeNames.T___DOC__, mDoc); - capiContext.addLoadedExtensionLibrary(library); + + if (library != null) { + capiContext.addLoadedExtensionLibrary(library); + } + return module; + + } + + private static Object callCreateAndCheckResult(Node node, ModuleSpec moduleSpec, PythonContext context, NativeModuleDefinition definition) { + PythonThreadState threadState = context.getThreadState(context.getLanguage()); + NativeFunctionPointer modCreate = ExternalFunctionSignature.MODCREATE.bind(context.ensureNativeContext(), definition.createFunction); + long result = ExternalFunctionInvoker.invokeMODCREATE(null, TIMING_MOD_CREATE, context.ensureNativeContext(), + BoundaryCallData.getUncached(), threadState, modCreate, + PythonToNativeInternalNode.executeUncached(moduleSpec.originalModuleSpec, false), definition.moduleDef); + TransformExceptionFromNativeNode.getUncached().execute(null, threadState, moduleSpec.name, result == NULLPTR, true, + ErrorMessages.CREATION_FAILD_WITHOUT_EXCEPTION, ErrorMessages.CREATION_RAISED_EXCEPTION); + Object module = NativeToPythonInternalNode.executeUncached(result, true); + + /* + * We are stricter than CPython and require this to be a PythonModule object. This + * means, if the custom 'create' function uses a native subtype of the module type, then + * we require it to call our new function. + */ + if (!(module instanceof PythonModule)) { + if (definition.stateSize > 0 || definition.traverseFunction != NULLPTR || definition.clearFunction != NULLPTR || definition.freeFunction != NULLPTR) { + throw PRaiseNode.raiseStatic(node, SystemError, ErrorMessages.NOT_A_MODULE_OBJECT_BUT_REQUESTS_MODULE_STATE, moduleSpec.name); + } + if (definition.hasExecSlots) { + throw PRaiseNode.raiseStatic(node, SystemError, ErrorMessages.MODULE_SPECIFIES_EXEC_SLOTS_BUT_DIDNT_CREATE_INSTANCE, moduleSpec.name); + } + if (!definition.tokenIsDef && definition.token != NULLPTR) { + throw PRaiseNode.raiseStatic(node, SystemError, ErrorMessages.MODULE_SPECIFIES_TOKEN_BUT_DIDNT_CREATE_INSTANCE, moduleSpec.name); + } + // otherwise CPython is just fine + } return module; } + public static int checkAbiInfo(Node node, long info, TruffleString moduleName) { + if (info == NULLPTR) { + throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.ImportError, ErrorMessages.MODULE_NULL_ABI_INFO, moduleName); + } + int majorVersion = com.oracle.graal.python.runtime.nativeaccess.NativeMemory.readByte(info) & 0xff; + if (majorVersion == 0) { + return 0; + } + if (majorVersion > 1) { + throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.ImportError, ErrorMessages.MODULE_ABI_INFO_VERSION_TOO_HIGH, moduleName); + } + int flags = com.oracle.graal.python.runtime.nativeaccess.NativeMemory.readShort(info + 2) & 0xffff; + long abiVersion = com.oracle.graal.python.runtime.nativeaccess.NativeMemory.readInt(info + 8) & 0xffffffffL; + if ((flags & PY_ABI_INFO_INTERNAL) != 0) { + throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.ImportError, ErrorMessages.MODULE_INTERNAL_ABI_UNSUPPORTED, moduleName); + } + if ((flags & PY_ABI_INFO_STABLE) == 0) { + throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.ImportError, ErrorMessages.MODULE_NON_STABLE_ABI_UNSUPPORTED, moduleName); + } + long majorMinorMask = 0xffff0000L; + if (abiVersion != 0 && (abiVersion & majorMinorMask) > (ABI3T_VERSION_HEX & majorMinorMask)) { + throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.ImportError, ErrorMessages.MODULE_FUTURE_STABLE_ABI, moduleName, + (abiVersion >> 24) & 0xff, (abiVersion >> 16) & 0xff); + } + if (abiVersion != 0 && abiVersion < MINIMUM_STABLE_ABI_VERSION_HEX) { + throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.ImportError, ErrorMessages.MODULE_INVALID_STABLE_ABI, moduleName, + (abiVersion >> 24) & 0xff, (abiVersion >> 16) & 0xff); + } + int gilFlags = flags & (PY_ABI_INFO_GIL | PY_ABI_INFO_FREETHREADED); + if (gilFlags == PY_ABI_INFO_FREETHREADED) { + throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.ImportError, ErrorMessages.MODULE_ONLY_FREETHREADED, moduleName); + } + return 0; + } + + private static void initializeNativeModule(PythonModule module, NativeModuleDefinition definition) { + // Equivalent to CPython's module_copy_members_from_deflike() plus token/exec setup. + module.setNativeModuleDef(definition.moduleDef); + module.setNativeModuleStateSize(definition.stateSize); + module.setNativeModuleTraverse(definition.traverseFunction); + module.setNativeModuleClear(definition.clearFunction); + module.setNativeModuleFree(definition.freeFunction); + module.setNativeModuleToken(definition.token); + module.setNativeModuleTokenIsDef(definition.tokenIsDef); + module.setNativeModuleExec(definition.moduleDef == NULLPTR ? definition.execFunction : NULLPTR); + module.setNativeModuleRequiresGil(definition.requiresGil); + } + + // similar to 'import.c: _PyImport_CheckSubinterpIncompatibleExtensionAllowed' + private static void checkSubinterpIncompatibleExtensionAllowed(Node location, PythonContext context, TruffleString name) { + // similar to 'import.c: check_multi_interp_extensions' + if (context.getOverrideMultiInterpExtensionsCheck() > 0) { + assert !context.isMainInterpreter(); + throw PRaiseNode.raiseStatic(location, PythonBuiltinClassType.ImportError, + ErrorMessages.MODULE_S_DOES_NOT_SUPPORT_LOADING_IN_SUBINTERPRETERS, name); + } + } + /** * Equivalent of {@code PyModule_ExecDef}. */ @TruffleBoundary - public static int execModule(Node node, CApiContext capiContext, PythonModule module, long moduleDef) { + public static int execModule(Node node, CApiContext capiContext, PythonModule module) { TruffleString mName = ModuleGetNameNode.executeUncached(module); - long mSize = readLongField(moduleDef, PyModuleDef__m_size); + long mSize = module.getNativeModuleStateSize(); // allocate md_state if necessary - if (mSize >= 0) { - /* - * TODO(fa): We currently leak 'md_state' and need to use a shared finalizer or similar. - * We ignore that for now since the size will usually be very small and/or we could also - * use a Truffle buffer object. - */ + if (mSize >= 0 && module.getNativeModuleState() == NULLPTR) { + // TODO(fa): We currently leak 'md_state' and need to use something like a NativeStorageReference. long mdState = calloc(mSize == 0 ? 1 : mSize); // ensure non-null value assert mdState != NULLPTR; module.setNativeModuleState(mdState); } - // parse slot definitions + long directExecFunction = module.getNativeModuleExec(); + if (directExecFunction != NULLPTR) { + invokeModuleExec(node, capiContext, module, mName, directExecFunction); + return 0; + } + + // Legacy definitions may contain multiple Py_mod_exec slots. + long moduleDef = module.getNativeModuleDef(); + if (moduleDef == NULLPTR) { + return 0; + } long slotDefinitions = readPtrField(moduleDef, PyModuleDef__m_slots); if (slotDefinitions == NULLPTR) { return 0; } - loop: for (int i = 0;; i++) { - int slotId = readStructArrayIntField(slotDefinitions, i, PyModuleDef_Slot__slot); - switch (slotId) { - case 0: - break loop; - case SLOT_PY_MOD_CREATE: - // handled in CreateModuleNode - break; - case SLOT_PY_MOD_EXEC: - long execFunction = readStructArrayPtrField(slotDefinitions, i, PyModuleDef_Slot__value); - PythonContext context = capiContext.getContext(); - PythonThreadState threadState = context.getThreadState(context.getLanguage()); - NativeFunctionPointer boundFunction = ExternalFunctionSignature.MODEXEC.bind(context.ensureNativeContext(), execFunction); - int iResult = ExternalFunctionInvoker.invokeMODEXEC(null, TIMING_MOD_EXEC, context.ensureNativeContext(), - BoundaryCallData.getUncached(), threadState, boundFunction, - PythonToNativeInternalNode.executeUncached(module, false)); - /* - * It's a bit counterintuitive that we use 'isPrimitiveValue = false' but the - * function's return value is actually not a result but a status code. So, if - * the status code is '!=0' we know that an error occurred and won't ignore this - * if no error is set. This is then the same behaviour if we would have a - * pointer return type and got 'NULL'. - */ - TransformExceptionFromNativeNode.getUncached().execute(node, threadState, mName, iResult != 0, true, - ErrorMessages.EXECUTION_FAILED_WITHOUT_EXCEPTION, ErrorMessages.EXECUTION_RAISED_EXCEPTION); - break; - case SLOT_PY_MOD_MULTIPLE_INTERPRETERS: - // ignored - // (mq) TODO: handle multiple interpreter cases - break; - case SLOT_PY_MOD_GIL: - // ignored - break; - default: - throw PRaiseNode.raiseStatic(node, SystemError, ErrorMessages.MODULE_INITIALIZED_WITH_UNKNOWN_SLOT, mName, slotId); + PySlotIterator iterator = PySlotIterator.initLegacy(node, mName, slotDefinitions, PySlotIterator.SlotKind.MODULE); + while (iterator.next()) { + if (iterator.current().id() == SLOT_ABI3T_PY_MOD_EXEC) { + invokeModuleExec(node, capiContext, module, mName, iterator.current().function()); } } return 0; } + private static void invokeModuleExec(Node node, CApiContext capiContext, PythonModule module, TruffleString moduleName, long execFunction) { + PythonContext context = capiContext.getContext(); + PythonThreadState threadState = context.getThreadState(context.getLanguage()); + NativeFunctionPointer boundFunction = ExternalFunctionSignature.MODEXEC.bind(context.ensureNativeContext(), execFunction); + int result = ExternalFunctionInvoker.invokeMODEXEC(null, TIMING_MOD_EXEC, context.ensureNativeContext(), BoundaryCallData.getUncached(), threadState, boundFunction, + PythonToNativeInternalNode.executeUncached(module, false)); + TransformExceptionFromNativeNode.getUncached().execute(node, threadState, moduleName, result != 0, true, + ErrorMessages.EXECUTION_FAILED_WITHOUT_EXCEPTION, ErrorMessages.EXECUTION_RAISED_EXCEPTION); + } + /** * TODO(fa): overlaps with * {@link com.oracle.graal.python.builtins.modules.cext.PythonCextModuleBuiltins#GraalPyPrivate_Module_AddFunctions(long, long)}. diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/ExternalFunctionSignature.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/ExternalFunctionSignature.java index 4a48f55c8d..53d9624e82 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/ExternalFunctionSignature.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/ExternalFunctionSignature.java @@ -168,7 +168,8 @@ public enum ExternalFunctionSignature implements NativeCExtSymbol { MODCREATE(false, PyObjectReturn, Pointer, Pointer), // typedef int (*Py_mod_exec)(PyObject *); MODEXEC(false, Int, Pointer), - // typedef PyObject *(*PyInit_mod)(void); + // typedef PyObject *(*PyModInitFunction)(void); + // typedef PySlot *(*PyModExportFunction)(void); MODINIT(false, Pointer), // typedef PThreadState** (*initialize_graal_capi)(void *, void *, void *, void *, void *); CAPIINIT(false, Pointer, Pointer, Pointer, Pointer, Pointer, Pointer), diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PySlotIterator.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PySlotIterator.java new file mode 100644 index 0000000000..aac76a8655 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PySlotIterator.java @@ -0,0 +1,281 @@ +/* + * Copyright (c) 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 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.graal.python.builtins.objects.cext.capi; + +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.DeprecationWarning; +import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyModuleDef_Slot__slot; +import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyModuleDef_Slot__value; +import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PySlot__sl_id; +import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PySlot__sl_uint64; +import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readStructArrayIntField; +import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readStructArrayLongField; +import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readStructArrayPtrField; +import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readStructArrayShortField; +import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.NULLPTR; + +import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.graal.python.builtins.modules.WarningsModuleBuiltins; +import com.oracle.graal.python.builtins.objects.cext.structs.CFields; +import com.oracle.graal.python.nodes.ErrorMessages; +import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.strings.TruffleString; + +/** Java counterpart of CPython's {@code _PySlotIterator}; type slots can be added later. */ +final class PySlotIterator { + static final int SLOT_PY_MOD_CREATE = 1; + static final int SLOT_PY_MOD_EXEC = 2; + static final int SLOT_PY_MOD_MULTIPLE_INTERPRETERS = 3; + static final int SLOT_PY_MOD_GIL = 4; + static final int SLOT_PY_MOD_CREATE_NEW = 84; + static final int SLOT_PY_MOD_EXEC_NEW = 85; + static final int SLOT_PY_MOD_MULTIPLE_INTERPRETERS_NEW = 86; + static final int SLOT_PY_MOD_GIL_NEW = 87; + static final int SLOT_SUBSLOTS = 92; + static final int SLOT_PY_MOD_SLOTS = 94; + static final int SLOT_INVALID = 0xffff; + static final int SLOT_PY_MOD_NAME = 100; + static final int SLOT_PY_MOD_DOC = 101; + static final int SLOT_PY_MOD_STATE_SIZE = 102; + static final int SLOT_PY_MOD_METHODS = 103; + static final int SLOT_PY_MOD_STATE_TRAVERSE = 104; + static final int SLOT_PY_MOD_STATE_CLEAR = 105; + static final int SLOT_PY_MOD_STATE_FREE = 106; + static final int SLOT_PY_MOD_ABI = 109; + static final int SLOT_PY_MOD_TOKEN = 110; + + static final int PY_SLOT_OPTIONAL = 0x0001; + static final int PY_SLOT_STATIC = 0x0002; + static final int PY_SLOT_INTPTR = 0x0004; + private static final int MAX_NESTING = 5; + + enum SlotKind { + MODULE, + TYPE + } + + private enum StructKind { + SLOT, + MODULE_DEF_SLOT, + TYPE_SLOT + } + + static final class Slot { + private final int id; + private final int flags; + private final long value; + + private Slot(int id, int flags, long value) { + this.id = id; + this.flags = flags; + this.value = value; + } + + int id() { + return id; + } + + int flags() { + return flags; + } + + long pointer() { + return value; + } + + long function() { + return value; + } + + long size() { + return value; + } + + long unsigned() { + return value; + } + } + + private static final class State { + private long slots; + private int index; + private StructKind structKind; + + private State(long slots, StructKind structKind) { + this.slots = slots; + this.structKind = structKind; + } + } + + private final Node node; + private final TruffleString name; + private final SlotKind kind; + private final State[] states = new State[MAX_NESTING]; + private final boolean[] seen = new boolean[SLOT_PY_MOD_TOKEN + 1]; + private int level; + private Slot current; + + private PySlotIterator(Node node, TruffleString name, long slots, SlotKind kind, StructKind structKind) { + this.node = node; + this.name = name; + this.kind = kind; + states[0] = new State(slots, structKind); + } + + static PySlotIterator init(Node node, TruffleString name, long slots, SlotKind kind) { + return new PySlotIterator(node, name, slots, kind, StructKind.SLOT); + } + + static PySlotIterator initLegacy(Node node, TruffleString name, long slots, SlotKind kind) { + return new PySlotIterator(node, name, slots, kind, StructKind.MODULE_DEF_SLOT); + } + + Slot current() { + return current; + } + + boolean sawSlot(int id) { + return id > 0 && id < seen.length && seen[id]; + } + + boolean next() { + while (true) { + State state = states[level]; + if (state.slots == NULLPTR) { + if (level == 0) { + return false; + } + level--; + states[level].index++; + continue; + } + int originalId; + int flags; + long value; + if (state.structKind == StructKind.SLOT) { + originalId = readStructArrayShortField(state.slots, state.index, PySlot__sl_id); + flags = readStructArrayShortField(state.slots, state.index, CFields.PySlot__sl_flags); + value = readStructArrayLongField(state.slots, state.index, PySlot__sl_uint64); + } else if (state.structKind == StructKind.MODULE_DEF_SLOT) { + originalId = readStructArrayIntField(state.slots, state.index, PyModuleDef_Slot__slot); + flags = PY_SLOT_INTPTR; + value = readStructArrayPtrField(state.slots, state.index, PyModuleDef_Slot__value); + } else { + throw new UnsupportedOperationException("type slots are not supported yet"); + } + int id = resolve(originalId); + if (id == SLOT_INVALID) { + if ((flags & PY_SLOT_OPTIONAL) != 0) { + state.index++; + continue; + } + throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.SystemError, ErrorMessages.MODULE_USES_UNKNOW_SLOT_ID, name, originalId); + } + if (id == 0) { + if ((flags & PY_SLOT_OPTIONAL) != 0) { + throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.SystemError, ErrorMessages.INVALID_FLAGS_FOR_SLOT_END, flags); + } + state.slots = NULLPTR; + continue; + } + if (id == SLOT_SUBSLOTS || id == SLOT_PY_MOD_SLOTS) { + if (value == NULLPTR) { + state.index++; + continue; + } + if (states[0].structKind == StructKind.MODULE_DEF_SLOT && state.structKind == StructKind.SLOT && (flags & PY_SLOT_STATIC) == 0) { + throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.SystemError, ErrorMessages.SLOTS_INCLUDED_FROM_MODULE_DEF_MUST_BE_STATIC); + } + if (++level >= MAX_NESTING) { + throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.SystemError, ErrorMessages.MODULE_SLOTS_TOO_DEEPLY_NESTED, name); + } + states[level] = new State(value, id == SLOT_SUBSLOTS ? StructKind.SLOT : StructKind.MODULE_DEF_SLOT); + continue; + } + current = new Slot(id, flags, value); + state.index++; + validate(state, originalId); + return true; + } + } + + private void validate(State state, int originalId) { + int id = current.id; + if (id == SLOT_PY_MOD_METHODS && state.structKind == StructKind.SLOT && (current.flags & PY_SLOT_STATIC) == 0) { + throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.SystemError, ErrorMessages.MODULE_METHODS_SLOT_REQUIRES_STATIC, name); + } + if (current.value == NULLPTR && rejectsNull(id)) { + throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.SystemError, ErrorMessages.NULL_NOT_ALLOWED_FOR_MODULE_SLOT, name, id); + } + if (current.value == NULLPTR && id == SLOT_PY_MOD_CREATE && states[0].structKind == StructKind.SLOT) { + WarningsModuleBuiltins.WarnNode.getUncached().warnFormat(null, null, DeprecationWarning, 1, + ErrorMessages.NULL_VALUE_IN_SLOT_DEPRECATED, originalId); + } + if (id != SLOT_PY_MOD_EXEC_NEW && id != SLOT_PY_MOD_ABI && sawSlot(id)) { + throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.SystemError, ErrorMessages.MODULE_HAS_MULTIPLE_SLOT, name, id); + } + seen[id] = true; + } + + private int resolve(int id) { + if (kind != SlotKind.MODULE) { + return SLOT_INVALID; + } + return switch (id) { + case 0, SLOT_PY_MOD_CREATE_NEW, SLOT_PY_MOD_EXEC_NEW, SLOT_PY_MOD_MULTIPLE_INTERPRETERS_NEW, SLOT_PY_MOD_GIL_NEW, SLOT_SUBSLOTS, SLOT_PY_MOD_SLOTS, + SLOT_PY_MOD_NAME, SLOT_PY_MOD_DOC, SLOT_PY_MOD_STATE_SIZE, SLOT_PY_MOD_METHODS, SLOT_PY_MOD_STATE_TRAVERSE, SLOT_PY_MOD_STATE_CLEAR, + SLOT_PY_MOD_STATE_FREE, SLOT_PY_MOD_ABI, SLOT_PY_MOD_TOKEN -> id; + case SLOT_PY_MOD_CREATE -> SLOT_PY_MOD_CREATE_NEW; + case SLOT_PY_MOD_EXEC -> SLOT_PY_MOD_EXEC_NEW; + case SLOT_PY_MOD_MULTIPLE_INTERPRETERS -> SLOT_PY_MOD_MULTIPLE_INTERPRETERS_NEW; + case SLOT_PY_MOD_GIL -> SLOT_PY_MOD_GIL_NEW; + default -> SLOT_INVALID; + }; + } + + private static boolean rejectsNull(int id) { + return switch (id) { + case SLOT_PY_MOD_EXEC_NEW, SLOT_PY_MOD_NAME, SLOT_PY_MOD_DOC, SLOT_PY_MOD_METHODS, SLOT_PY_MOD_STATE_TRAVERSE, SLOT_PY_MOD_STATE_CLEAR, + SLOT_PY_MOD_STATE_FREE, SLOT_PY_MOD_ABI, SLOT_PY_MOD_TOKEN -> true; + default -> false; + }; + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/ArgDescriptor.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/ArgDescriptor.java index 7ac639fcea..6f05b5b376 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/ArgDescriptor.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/ArgDescriptor.java @@ -80,6 +80,7 @@ enum ArgBehavior { Char8(NativeSimpleType.SINT8), UChar8(NativeSimpleType.SINT8), Char16(NativeSimpleType.SINT16), + UInt16(NativeSimpleType.SINT16), Int32(NativeSimpleType.SINT32), UInt32(NativeSimpleType.SINT32), Int64(NativeSimpleType.SINT64), @@ -189,6 +190,7 @@ public enum ArgDescriptor { FILE_PTR("FILE*"), FREEFUNC("freefunc"), INITTAB("struct _inittab*"), + UINT16_T(ArgBehavior.UInt16, "uint16_t"), INT32_T(ArgBehavior.Int32, "int32_t"), INT_LIST("int*"), INT8_T_PTR(ArgBehavior.Pointer, "int8_t*"), @@ -203,6 +205,7 @@ public enum ArgDescriptor { Py_buffer("Py_buffer"), PY_BUFFER_PTR(ArgBehavior.Pointer, "Py_buffer*"), CONST_PY_BUFFER_PTR(ArgBehavior.Pointer, "const Py_buffer*"), + CONST_PY_SLOT_PTR(ArgBehavior.Pointer, "const PySlot*"), PY_C_FUNCTION(ArgBehavior.Pointer, "PyCFunction"), PyByteArrayObject(ArgBehavior.PyObject, "PyByteArrayObject*"), PyCFunctionObject(ArgBehavior.PyObject, "PyCFunctionObject*"), @@ -229,6 +232,7 @@ public enum ArgDescriptor { PY_GEN_OBJECT(ArgBehavior.PyObject, "PyGenObject*"), PyGetSetDef(ArgBehavior.Pointer, "PyGetSetDef*"), PY_GIL_STATE_STATE(ArgBehavior.Int32, "PyGILState_STATE"), + PY_ABI_INFO_PTR(ArgBehavior.Pointer, "PyABIInfo*"), PY_HASH_T_PTR(ArgBehavior.Pointer, "Py_hash_t*"), PY_IDENTIFIER("_Py_Identifier*"), PyInterpreterState(ArgBehavior.Pointer, "PyInterpreterState*"), @@ -477,7 +481,7 @@ public boolean isI32() { } public boolean isI16() { - return behavior == ArgBehavior.Char16; + return behavior == ArgBehavior.Char16 || behavior == ArgBehavior.UInt16; } public boolean isFloat() { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CFields.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CFields.java index d076b1423c..a16a9e6760 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CFields.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CFields.java @@ -45,6 +45,7 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CharPtrAsTruffleString; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.ConstCharPtr; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Double; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.INT64_T; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.INT_LIST; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Int; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_C_FUNCTION; @@ -66,8 +67,9 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Py_hash_t; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Py_ssize_t; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.UCHAR; -import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.UINTPTR_T; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.UINT16_T; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.UINT64_T; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.UINTPTR_T; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.UNSIGNED_INT; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.UNSIGNED_LONG; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.allocfunc; @@ -141,6 +143,15 @@ public enum CFields { PyModuleDef_Slot__slot(Int), PyModuleDef_Slot__value(Pointer), + PySlot__sl_id(UINT16_T), + PySlot__sl_flags(UINT16_T), + PySlot__sl_reserved(UNSIGNED_INT), + PySlot__sl_ptr(Pointer), + PySlot__sl_func(Pointer), + PySlot__sl_size(Py_ssize_t), + PySlot__sl_int64(INT64_T), + PySlot__sl_uint64(UINT64_T), + PyMethodDef__ml_name(ConstCharPtr), PyMethodDef__ml_meth(PY_C_FUNCTION), PyMethodDef__ml_flags(Int), diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructAccess.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructAccess.java index 7187290021..85ad261956 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructAccess.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructAccess.java @@ -90,6 +90,11 @@ public static void writeByteField(long structBasePtr, CFields field, byte value) NativeMemory.writeByte(getFieldPtr(structBasePtr, field), value); } + public static int readShortField(long structBasePtr, CFields field) { + assert field.type.isI16(); + return NativeMemory.readShort(getFieldPtr(structBasePtr, field)) & 0xffff; + } + public static int readIntField(long structBasePtr, CFields field) { assert field.type.isI32(); return NativeMemory.readInt(getFieldPtr(structBasePtr, field)); @@ -100,6 +105,10 @@ public static void writeIntField(long structBasePtr, CFields field, int value) { NativeMemory.writeInt(getFieldPtr(structBasePtr, field), value); } + public static int readStructArrayShortField(long arrayPtr, long index, CFields field) { + return readShortField(getArrayElementPtr(arrayPtr, index, field.struct), field); + } + public static int readStructArrayIntField(long arrayPtr, long index, CFields field) { return readIntField(getArrayElementPtr(arrayPtr, index, field.struct), field); } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructs.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructs.java index a5bea19834..d95f401319 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructs.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructs.java @@ -53,6 +53,7 @@ public enum CStructs { PyModuleDef, PyModuleDef_Slot, PyMethodDef, + PySlot, PyObject, GraalPyObject, PyBytesObject, diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/PythonModule.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/PythonModule.java index 962a97f1c8..540bf27f23 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/PythonModule.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/PythonModule.java @@ -51,11 +51,21 @@ public final class PythonModule extends PythonObject { /** * Stores the native {@code PyModuleDef *} structure if this module was created via the - * multiphase extension module initialization mechanism. + * multiphase extension module initialization mechanism. Will be non-null only for legacy definitions. */ private long nativeModuleDef; private long nativeModuleState; + private long nativeModuleStateSize; + private long nativeModuleTraverse; + private long nativeModuleClear; + private long nativeModuleFree; + + private long nativeModuleExec; // direct PySlot[] modules + private long nativeModuleToken; + private boolean nativeModuleTokenIsDef; + private boolean nativeModuleRequiresGil; + /** * Replicates the native references of this module's native state in Java. *

@@ -155,6 +165,70 @@ public void setNativeModuleState(long nativeModuleState) { this.nativeModuleState = nativeModuleState; } + public long getNativeModuleStateSize() { + return nativeModuleStateSize; + } + + public void setNativeModuleStateSize(long nativeModuleStateSize) { + this.nativeModuleStateSize = nativeModuleStateSize; + } + + public long getNativeModuleTraverse() { + return nativeModuleTraverse; + } + + public void setNativeModuleTraverse(long nativeModuleTraverse) { + this.nativeModuleTraverse = nativeModuleTraverse; + } + + public long getNativeModuleClear() { + return nativeModuleClear; + } + + public void setNativeModuleClear(long nativeModuleClear) { + this.nativeModuleClear = nativeModuleClear; + } + + public long getNativeModuleFree() { + return nativeModuleFree; + } + + public void setNativeModuleFree(long nativeModuleFree) { + this.nativeModuleFree = nativeModuleFree; + } + + public long getNativeModuleExec() { + return nativeModuleExec; + } + + public void setNativeModuleExec(long nativeModuleExec) { + this.nativeModuleExec = nativeModuleExec; + } + + public long getNativeModuleToken() { + return nativeModuleToken; + } + + public void setNativeModuleToken(long nativeModuleToken) { + this.nativeModuleToken = nativeModuleToken; + } + + public boolean isNativeModuleTokenIsDef() { + return nativeModuleTokenIsDef; + } + + public void setNativeModuleTokenIsDef(boolean nativeModuleTokenIsDef) { + this.nativeModuleTokenIsDef = nativeModuleTokenIsDef; + } + + public boolean isNativeModuleRequiresGil() { + return nativeModuleRequiresGil; + } + + public void setNativeModuleRequiresGil(boolean nativeModuleRequiresGil) { + this.nativeModuleRequiresGil = nativeModuleRequiresGil; + } + /** * For a description, see {@link #replicatedNativeReferences}. */ diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java index 85860e580e..348084cdef 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java @@ -1031,6 +1031,8 @@ public abstract class ErrorMessages { public static final TruffleString INPUT_LOST_SYS_S = tsLiteral("input(): lost sys.%s"); public static final TruffleString EOF_WHEN_READING_A_LINE = tsLiteral("EOF when reading a line"); public static final TruffleString OBJECT_READLINE_RETURNED_NON_STRING = tsLiteral("object.readline() returned non-string"); + public static final TruffleString MODULE_EXPORT_HOOK_FAILED = tsLiteral("module export hook for module %s failed without setting an exception"); + public static final TruffleString MODULE_EXPORT_HOOK_RAISED_EXCEPTION = tsLiteral("module export hook for module %s raised unreported exception"); // SSL errors public static final TruffleString SSL_SESSION_CLOSED = tsLiteral("SSL/TLS session closed cleanly."); @@ -1317,12 +1319,34 @@ public abstract class ErrorMessages { public static final TruffleString CREATION_RAISED_EXCEPTION = tsLiteral("creation of module %s raised unreported exception"); public static final TruffleString NOT_A_MODULE_OBJECT_BUT_REQUESTS_MODULE_STATE = tsLiteral("module %s is not a module object, but requests module state"); public static final TruffleString MODULE_SPECIFIES_EXEC_SLOTS_BUT_DIDNT_CREATE_INSTANCE = tsLiteral("module %s specifies execution slots, but did not create a ModuleType instance"); + public static final TruffleString MODULE_SPECIFIES_TOKEN_BUT_DIDNT_CREATE_INSTANCE = tsLiteral("module %s specifies token, but did not create a ModuleType instance"); + public static final TruffleString MODULE_S_DOES_NOT_SUPPORT_LOADING_IN_SUBINTERPRETERS = tsLiteral("module %s does not support loading in subinterpreters"); public static final TruffleString EXECUTION_FAILED_WITHOUT_EXCEPTION = tsLiteral("execution of module %s failed without setting an exception"); public static final TruffleString EXECUTION_RAISED_EXCEPTION = tsLiteral("execution of module %s raised unreported exception"); public static final TruffleString MODULE_INITIALIZED_WITH_UNKNOWN_SLOT = tsLiteral("module %s initialized with unknown slot %i"); public static final TruffleString M_SIZE_CANNOT_BE_NEGATIVE = tsLiteral("module %s: m_size may not be negative for multi-phase initialization"); public static final TruffleString MODULE_HAS_MULTIPLE_CREATE_SLOTS = tsLiteral("module %s has multiple create slots"); + public static final TruffleString MODULE_HAS_MULTIPLE_EXEC_SLOTS = tsLiteral("module %s has multiple Py_mod_exec slots"); + public static final TruffleString MODULE_HAS_MULTIPLE_SLOT = tsLiteral("module %s has multiple slot %i entries"); public static final TruffleString MODULE_USES_UNKNOW_SLOT_ID = tsLiteral("module %s uses unknown slot ID %i"); + public static final TruffleString MODULE_DOES_NOT_DEFINE_ABI = tsLiteral("module %s does not define Py_mod_abi, which is mandatory for modules defined from slots only"); + public static final TruffleString MODULE_SLOTS_TOO_DEEPLY_NESTED = tsLiteral("module %s has too many levels of nested slots"); + public static final TruffleString INVALID_FLAGS_FOR_SLOT_END = tsLiteral("invalid flags for Py_slot_end: 0x%x"); + public static final TruffleString MODULE_METHODS_SLOT_REQUIRES_STATIC = tsLiteral("module %s: Py_mod_methods requires PySlot_STATIC"); + public static final TruffleString NULL_NOT_ALLOWED_FOR_MODULE_SLOT = tsLiteral("module %s: NULL not allowed for slot %i"); + public static final TruffleString NULL_VALUE_IN_SLOT_DEPRECATED = tsLiteral("NULL value in slot %i is deprecated"); + public static final TruffleString SLOTS_INCLUDED_FROM_MODULE_DEF_MUST_BE_STATIC = tsLiteral("slots included from PyModuleDef must be static"); + public static final TruffleString MODULE_ARBITRARY_TOKEN_WITH_MODULE_DEF = tsLiteral("module %s: arbitrary Py_mod_token not allowed with PyModuleDef"); + public static final TruffleString MODULE_SLOT_CONFLICTS_WITH_MODULE_DEF = tsLiteral("module %s: slot conflicts with PyModuleDef.%s"); + public static final TruffleString MODULE_NULL_ABI_INFO = tsLiteral("%s: NULL PyABIInfo"); + public static final TruffleString MODULE_ABI_INFO_VERSION_TOO_HIGH = tsLiteral("%s: PyABIInfo version too high"); + public static final TruffleString MODULE_INTERNAL_ABI_UNSUPPORTED = tsLiteral("%s: internal CPython ABI is not supported by GraalPy"); + public static final TruffleString MODULE_NON_STABLE_ABI_UNSUPPORTED = tsLiteral("%s: non-stable CPython ABI is not supported by GraalPy"); + public static final TruffleString MODULE_FUTURE_STABLE_ABI = tsLiteral("%s: incompatible future stable ABI version (%d.%d)"); + public static final TruffleString MODULE_INVALID_STABLE_ABI = tsLiteral("%s: invalid stable ABI version (%d.%d)"); + public static final TruffleString MODULE_ONLY_FREETHREADED = tsLiteral("%s: only compatible with free-threaded CPython"); + public static final TruffleString PYMODULE_FROMSLOTS_NULL_SLOTS = tsLiteral("PyModule_FromSlotsAndSpec called with NULL slots"); + public static final TruffleString EXPECTED_MODULE_GOT_T = tsLiteral("expected module, got %T"); public static final TruffleString CANNOT_DELETE = tsLiteral("cannot delete attribute"); public static final TruffleString MV_UNDERLYING_BUF_ISNT_WRITABLE = tsLiteral("memoryview: underlying buffer is not writable"); public static final TruffleString MV_UNDERLYING_BUF_ISNT_C_CONTIGUOUS = tsLiteral("memoryview: underlying buffer is not C-contiguous"); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/StringLiterals.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/StringLiterals.java index 56f54dd10a..e30f53eafa 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/StringLiterals.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/StringLiterals.java @@ -117,7 +117,7 @@ public abstract class StringLiterals { public static final String J_ABI3T_MULTIARCH = ".abi3t-"; public static final TruffleString T_ABI3T_MULTIARCH = tsLiteral(J_ABI3T_MULTIARCH); public static final String J_ABI3T_EXT_SO = ".abi3t.so"; - public static final TruffleString T_ABI3T_EXT_SO= tsLiteral(J_ABI3T_EXT_SO); + public static final TruffleString T_ABI3T_EXT_SO = tsLiteral(J_ABI3T_EXT_SO); public static final String J_EXT_SO = ".so"; public static final TruffleString T_EXT_SO = tsLiteral(J_EXT_SO); public static final String J_EXT_PYD = ".pyd"; diff --git a/mx.graalpython/copyrights/overrides b/mx.graalpython/copyrights/overrides index 80ac8a214d..556e94fda8 100644 --- a/mx.graalpython/copyrights/overrides +++ b/mx.graalpython/copyrights/overrides @@ -260,6 +260,7 @@ graalpython/com.oracle.graal.python.cext/include/pytypedefs.h,python.copyright graalpython/com.oracle.graal.python.cext/include/rangeobject.h,python.copyright graalpython/com.oracle.graal.python.cext/include/setobject.h,python.copyright graalpython/com.oracle.graal.python.cext/include/sliceobject.h,python.copyright +graalpython/com.oracle.graal.python.cext/include/slots.h,python.copyright graalpython/com.oracle.graal.python.cext/include/structmember.h,python.copyright graalpython/com.oracle.graal.python.cext/include/structseq.h,python.copyright graalpython/com.oracle.graal.python.cext/include/sysmodule.h,python.copyright diff --git a/scripts/generate_abi3t_manifest.py b/scripts/generate_abi3t_manifest.py index 76b5d09518..051b49c250 100644 --- a/scripts/generate_abi3t_manifest.py +++ b/scripts/generate_abi3t_manifest.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# # Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # @@ -8,7 +7,7 @@ # Subject to the condition set forth below, permission is hereby granted to any # person obtaining a copy of this software, associated documentation and/or # data (collectively the "Software"), free of charge and under any and all -# copyright rights in the Software, and in any and all patent rights owned or +# copyright rights in the Software, and any and all patent rights owned or # freely licensable by each licensor hereunder covering either (i) the # unmodified Software as contributed to or provided by such licensor, or (ii) # the Larger Works (as defined below), to deal in both From f16e42d43995c1b6779af0a41d3cc3275534ee9f Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Tue, 25 Aug 2026 09:14:53 +0200 Subject: [PATCH 12/24] Add ABI3T module C API upcalls --- .../cext/PythonCextModuleBuiltins.java | 150 +++++++++++++----- 1 file changed, 106 insertions(+), 44 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextModuleBuiltins.java index 7731e3ab25..c7c22bde27 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextModuleBuiltins.java @@ -44,8 +44,12 @@ import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Direct; import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Ignored; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_PY_SLOT_PTR; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.ConstCharPtr; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.ConstCharPtrAsTruffleString; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Int; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_ABI_INFO_PTR; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_SSIZE_T_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Pointer; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyMethodDef; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyModuleDef; @@ -54,17 +58,19 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObject; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObjectAsTruffleString; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObjectTransfer; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.VOID_PTR_LIST; import static com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.bindFunctionPointer; import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readIntField; -import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readLongField; import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readPtrField; -import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.NULLPTR; import static com.oracle.graal.python.nodes.ErrorMessages.NAMELESS_MODULE; import static com.oracle.graal.python.nodes.ErrorMessages.S_NEEDS_S_AS_FIRST_ARG; import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___DOC__; import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___FILE__; import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___NAME__; import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___PACKAGE__; +import static com.oracle.graal.python.nodes.StringLiterals.T_EMPTY_STRING; +import static com.oracle.graal.python.nodes.StringLiterals.T_NAME; +import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.NULLPTR; import static com.oracle.graal.python.util.PythonUtils.EMPTY_OBJECT_ARRAY; import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; @@ -75,10 +81,12 @@ import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiTernaryBuiltinNode; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiUnaryBuiltinNode; import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.builtins.objects.cext.capi.CApiContext.ModuleSpec; +import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes; import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.EnsurePythonObjectNode; import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodesFactory.PRaiseNativeNodeGen; import com.oracle.graal.python.builtins.objects.cext.capi.ExternalFunctionInvoker; -import com.oracle.graal.python.builtins.objects.cext.capi.ExternalFunctionNodes.CheckPrimitiveFunctionResultNode; +import com.oracle.graal.python.builtins.objects.cext.capi.ExternalFunctionNodesFactory.CheckPrimitiveFunctionResultNodeGen; import com.oracle.graal.python.builtins.objects.cext.capi.ExternalFunctionSignature; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTiming; @@ -91,8 +99,8 @@ import com.oracle.graal.python.builtins.objects.object.ObjectBuiltins; import com.oracle.graal.python.builtins.objects.object.PythonBuiltinObject; import com.oracle.graal.python.builtins.objects.str.StringBuiltins.PrefixSuffixNode; +import com.oracle.graal.python.lib.PyObjectGetAttr; import com.oracle.graal.python.lib.PyUnicodeCheckNode; -import com.oracle.graal.python.runtime.nativeaccess.NativeFunctionPointer; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.StringLiterals; @@ -102,8 +110,12 @@ import com.oracle.graal.python.nodes.call.CallNode; import com.oracle.graal.python.nodes.classes.IsSubtypeNode; import com.oracle.graal.python.nodes.object.GetClassNode; +import com.oracle.graal.python.nodes.util.CastToTruffleStringNode; import com.oracle.graal.python.runtime.IndirectCallData.BoundaryCallData; import com.oracle.graal.python.runtime.PythonContext; +import com.oracle.graal.python.runtime.PythonContext.PythonThreadState; +import com.oracle.graal.python.runtime.nativeaccess.NativeFunctionPointer; +import com.oracle.graal.python.runtime.nativeaccess.NativeMemory; import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.Bind; @@ -303,38 +315,94 @@ private static void addMethodsToObject(long functions, Object module, Object mod } } - @CApiBuiltin(ret = Int, args = {PyObject, Pointer, Pointer}, call = Ignored) - abstract static class GraalPyPrivate_Module_Traverse extends CApiTernaryBuiltinNode { - private static final CApiTiming TIMING_INVOKE_TRAVERSE_PROC = CApiTiming.create(true, "invokeTraverseProc"); + @CApiBuiltin(ret = PyObjectTransfer, args = {CONST_PY_SLOT_PTR, PyObject}, call = Direct) + public static long PyModule_FromSlotsAndSpec(long slots, long specPtr) { + if (slots == NULLPTR) { + throw PRaiseNode.raiseStatic(null, SystemError, ErrorMessages.PYMODULE_FROMSLOTS_NULL_SLOTS); + } + Object spec = NativeToPythonInternalNode.executeUncached(specPtr, false); + TruffleString name = CastToTruffleStringNode.executeUncached(PyObjectGetAttr.executeUncached(spec, T_NAME)); + ModuleSpec moduleSpec = new ModuleSpec(name, T_EMPTY_STRING, spec); + Object module = CExtNodes.createModuleFromSlotsAndSpec(null, PythonContext.get(null).getCApiContext(), null, slots, moduleSpec); + return PythonToNativeInternalNode.executeNewRefUncached(module); + } - @Specialization - static int doGeneric(PythonModule self, long visitFun, long arg, - @Bind Node inliningTarget, - @Cached CheckPrimitiveFunctionResultNode checkPrimitiveFunctionResultNode, - @Cached PythonToNativeInternalNode toNativeNode) { + @CApiBuiltin(ret = Int, args = {PyObject}, call = Direct) + public static int PyModule_Exec(long modulePtr) { + Object object = NativeToPythonInternalNode.executeUncached(modulePtr, false); + if (object instanceof PythonModule module) { + return CExtNodes.execModule(null, PythonContext.get(null).getCApiContext(), module); + } + throw PRaiseNode.raiseStatic(null, TypeError, ErrorMessages.EXPECTED_MODULE_GOT_T, object); + } - /* - * As in 'moduleobject.c: module_traverse': 'if (m->md_def && m->md_def->m_traverse && - * (m->md_def->m_size <= 0 || m->md_state != NULL))' - */ - long mdDef = self.getNativeModuleDef(); - if (mdDef != NULLPTR) { - long mTraverse = readPtrField(mdDef, CFields.PyModuleDef__m_traverse); - if (mTraverse != NULLPTR) { - long mSize = readLongField(mdDef, CFields.PyModuleDef__m_size); - long mdState = self.getNativeModuleState(); - if (mSize <= 0 || mdState != NULLPTR) { - PythonContext ctx = PythonContext.get(inliningTarget); - NativeFunctionPointer traverseExecutable = bindFunctionPointer(mTraverse, ExternalFunctionSignature.TRAVERSEPROC); - int ires = ExternalFunctionInvoker.invokeTRAVERSEPROC(null, TIMING_INVOKE_TRAVERSE_PROC, ctx.ensureNativeContext(), BoundaryCallData.getUncached(), - ctx.getThreadState(PythonLanguage.get(inliningTarget)), traverseExecutable, toNativeNode.execute(inliningTarget, self), visitFun, arg); - checkPrimitiveFunctionResultNode.executeLong(inliningTarget, ctx.getThreadState(PythonLanguage.get(inliningTarget)), StringLiterals.T_VISIT, ires); - return ires; - } - } - } + @CApiBuiltin(ret = Int, args = {PyObject, PY_SSIZE_T_PTR}, call = Direct) + public static int PyModule_GetStateSize(long modulePtr, long result) { + Object object = NativeToPythonInternalNode.executeUncached(modulePtr, false); + if (object instanceof PythonModule module) { + NativeMemory.writeLong(result, module.getNativeModuleStateSize()); + return 0; + } + NativeMemory.writeLong(result, -1); + throw PRaiseNode.raiseStatic(null, TypeError, ErrorMessages.EXPECTED_MODULE_GOT_T, object); + } + + @CApiBuiltin(ret = Int, args = {PyObject, VOID_PTR_LIST}, call = Direct) + public static int PyModule_GetToken(long modulePtr, long result) { + Object object = NativeToPythonInternalNode.executeUncached(modulePtr, false); + if (object instanceof PythonModule module) { + NativeMemory.writeLong(result, module.getNativeModuleToken()); + return 0; + } + NativeMemory.writeLong(result, NULLPTR); + throw PRaiseNode.raiseStatic(null, TypeError, ErrorMessages.EXPECTED_MODULE_GOT_T, object); + } + + @CApiBuiltin(ret = Int, args = {PyObject, VOID_PTR_LIST}, call = Direct) + public static int PyModule_GetToken_DuringGC(long modulePtr, long result) { + Object object = NativeToPythonInternalNode.executeUncached(modulePtr, false); + if (object instanceof PythonModule module) { + NativeMemory.writeLong(result, module.getNativeModuleToken()); return 0; } + NativeMemory.writeLong(result, NULLPTR); + return -1; + } + + @CApiBuiltin(ret = Pointer, args = {PyObject}, call = Direct) + public static long PyModule_GetState_DuringGC(long modulePtr) { + Object object = NativeToPythonInternalNode.executeUncached(modulePtr, false); + if (object instanceof PythonModule module) { + return module.getNativeModuleState(); + } + return NULLPTR; + } + + @CApiBuiltin(ret = Int, args = {PY_ABI_INFO_PTR, ConstCharPtr}, call = Direct) + public static int PyABIInfo_Check(long info, long moduleName) { + TruffleString name = moduleName == NULLPTR ? T_EMPTY_STRING : (TruffleString) CharPtrToPythonNode.executeUncached(moduleName); + return CExtNodes.checkAbiInfo(null, info, name); + } + + private static final CApiTiming TIMING_INVOKE_TRAVERSE_PROC = CApiTiming.create(true, "invokeTraverseProc"); + + @CApiBuiltin(ret = Int, args = {PyObject, Pointer, Pointer}, call = Ignored) + public static int GraalPyPrivate_Module_Traverse(long selfPtr, long visitFun, long arg) { + + PythonModule self = (PythonModule) NativeToPythonInternalNode.executeUncached(selfPtr, false); + long traverse = self.getNativeModuleTraverse(); + long stateSize = self.getNativeModuleStateSize(); + long state = self.getNativeModuleState(); + if (traverse != NULLPTR && (stateSize <= 0 || state != NULLPTR)) { + PythonContext ctx = PythonContext.get(null); + PythonThreadState threadState = ctx.getThreadState(ctx.getLanguage()); + NativeFunctionPointer traverseExecutable = bindFunctionPointer(traverse, ExternalFunctionSignature.TRAVERSEPROC); + int ires = ExternalFunctionInvoker.invokeTRAVERSEPROC(null, TIMING_INVOKE_TRAVERSE_PROC, ctx.ensureNativeContext(), BoundaryCallData.getUncached(), + threadState, traverseExecutable, PythonToNativeInternalNode.executeUncached(self, false), visitFun, arg); + CheckPrimitiveFunctionResultNodeGen.getUncached().executeLong(null, threadState, StringLiterals.T_VISIT, ires); + return ires; + } + return 0; } @CApiBuiltin(ret = PyObjectTransfer, args = {PyObject}, call = Direct) @@ -360,20 +428,14 @@ static Object error(@SuppressWarnings("unused") Object module, } @CApiBuiltin(ret = ArgDescriptor.Void, args = {PyModuleObject, PyModuleDef}, call = Ignored) - abstract static class GraalPyPrivate_Module_SetDef extends CApiBinaryBuiltinNode { - @Specialization - static Object set(PythonModule object, long value) { - object.setNativeModuleDef(value); - return PNone.NO_VALUE; - } + public static void GraalPyPrivate_Module_SetDef(long object, long value) { + PythonModule module = (PythonModule) NativeToPythonInternalNode.executeUncached(object, false); + module.setNativeModuleDef(value); } @CApiBuiltin(ret = ArgDescriptor.Void, args = {PyModuleObject, Pointer}, call = Ignored) - abstract static class GraalPyPrivate_Module_SetState extends CApiBinaryBuiltinNode { - @Specialization - static Object set(PythonModule object, long value) { - object.setNativeModuleState(value); - return PNone.NO_VALUE; - } + public static void GraalPyPrivate_Module_SetState(long object, long value) { + PythonModule module = (PythonModule) NativeToPythonInternalNode.executeUncached(object, false); + module.setNativeModuleState(value); } } From 4fd4930b044e88591e7f6cec199b5415c607518c Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Mon, 24 Aug 2026 23:04:35 +0200 Subject: [PATCH 13/24] Add optional abi3t wheel installation support --- .gitignore | 1 - .../src/tests/__init__.py | 12 +- .../src/tests/cpyext/__init__.py | 4 +- .../src/tests/cpyext/test_abi3t.py | 272 ++++++++++++++++++ .../modules/GraalPythonModuleBuiltins.java | 1 + .../cext/capi/transitions/ArgDescriptor.java | 2 +- .../lib-graalpython/patches/pip-26.1.2.patch | 41 +++ .../_bundled/pip-26.1.2-py3-none-any.whl | Bin 1852257 -> 1852439 bytes 8 files changed, 328 insertions(+), 5 deletions(-) create mode 100644 graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abi3t.py diff --git a/.gitignore b/.gitignore index 99f63bcb55..585daaafed 100644 --- a/.gitignore +++ b/.gitignore @@ -76,7 +76,6 @@ graalpython/lib-python/3/test/data /venv *.code-workspace graalpython/com.oracle.graal.python.test/src/tests/cpyext/*.c -!graalpython/com.oracle.graal.python.test/src/tests/cpyext/abi3t_smoke.c graalpython/com.oracle.graal.python.test/src/tests/cpyext/*.sha256 graalpython/com.oracle.graal.python.test/src/tests/cpyext/As_FileDescriptor_Testfile graalpython/lib-python/3/lib2to3/Grammar3.8.5.alpha.0.pickle diff --git a/graalpython/com.oracle.graal.python.test/src/tests/__init__.py b/graalpython/com.oracle.graal.python.test/src/tests/__init__.py index e984e54a60..fb4e1534dc 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/__init__.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/__init__.py @@ -190,10 +190,20 @@ def install_venv(venv_path: Path, use_current_python=False) -> bool: def compile_module_from_string(c_source: str, name: str): + install_dir = compile_module_from_string_no_import(c_source, name) + sys.path.insert(0, install_dir) + try: + cmodule = __import__(name) + finally: + sys.path.pop(0) + return cmodule + + +def compile_module_from_string_no_import(c_source: str, name: str): source_file = DIR / f'{name}.c' with open(source_file, "wb", buffering=0) as f: f.write(bytes(c_source, 'utf-8')) - return compile_module_from_file(name) + return ccompile(None, name) def compile_module_from_file(module_name: str, sibling_to=None): diff --git a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/__init__.py b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/__init__.py index 7ce28b853e..a34d0ec67b 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/__init__.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 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 @@ -45,7 +45,7 @@ from io import StringIO from string import Formatter -from .. import ccompile, compile_module_from_file, compile_module_from_string, DIR +from .. import ccompile, compile_module_from_file, compile_module_from_string, compile_module_from_string_no_import, DIR GRAALPYTHON = sys.implementation.name == "graalpy" diff --git a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abi3t.py b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abi3t.py new file mode 100644 index 0000000000..45a0b9d83c --- /dev/null +++ b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abi3t.py @@ -0,0 +1,272 @@ +# Copyright (c) 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 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import ensurepip +import os +import platform +import subprocess +import sys +import tempfile +import unittest +import zipfile +from pathlib import Path + +from . import compile_module_from_string_no_import + + +ABI3T_SMOKE_SOURCE = r""" +#define Py_TARGET_ABI3T 0x030f0000 +#include + +static char module_token; +static PyABIInfo abi_info; + +static int +exec_module(PyObject *module) +{ + int *state = PyModule_GetState(module); + if (state == NULL) { + return -1; + } + *state = 40; + return 0; +} + +static PyObject * +increment(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + int *state = PyModule_GetState_DuringGC(module); + Py_ssize_t state_size = -1; + void *token = NULL; + void *gc_token = NULL; + + if (PyModule_GetStateSize(module, &state_size) < 0 || + PyModule_GetToken(module, &token) < 0 || + PyModule_GetToken_DuringGC(module, &gc_token) < 0) { + return NULL; + } + if (state == NULL || state_size != sizeof(*state) || + token != &module_token || gc_token != &module_token) { + PyErr_SetString(PyExc_AssertionError, "invalid abi3t module metadata"); + return NULL; + } + return PyLong_FromLong(++*state); +} + +static PyObject * +from_slots(PyObject *Py_UNUSED(module), PyObject *spec) +{ + static PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_END, + }; + return PyModule_FromSlotsAndSpec(slots, spec); +} + +static PyMethodDef methods[] = { + {"increment", increment, METH_NOARGS, NULL}, + {"from_slots", from_slots, METH_O, NULL}, + {NULL, NULL, 0, NULL}, +}; + +static PyABIInfo abi_info = { + 1, + 0, + PyABIInfo_STABLE | PyABIInfo_FREETHREADING_AGNOSTIC, + PY_VERSION_HEX, + 0x030f0000, +}; + +static PyModuleDef_Slot legacy_slots[] = { + {2, exec_module}, + {0, NULL}, +}; + +PyMODEXPORT_FUNC +PyModExport_abi3t_smoke(PyObject *Py_UNUSED(spec)) +{ + static PySlot slots[] = { + PySlot_DATA(Py_mod_abi, &abi_info), + PySlot_STATIC_DATA(Py_mod_methods, methods), + PySlot_SIZE(Py_mod_state_size, sizeof(int)), + PySlot_DATA(Py_mod_token, &module_token), + PySlot_DATA(Py_mod_slots, legacy_slots), + {.sl_id=0xfbad, .sl_flags=PySlot_OPTIONAL}, + PySlot_END, + }; + return slots; +} +""" + + +@unittest.skipUnless(sys.implementation.name == "graalpy", "requires GraalPy") +class TestAbi3t(unittest.TestCase): + def test_option_exposed(self): + code = "import __graalpython__; print(__graalpython__.abi3t_enabled)" + for enabled, expected in ((False, "False"), (True, "True")): + proc = subprocess.run( + [ + sys.executable, + "--experimental-options=true", + f"--python.EnableAbi3t={str(enabled).lower()}", + "-S", + "-c", + code, + ], + capture_output=True, + text=True, + check=True, + ) + self.assertEqual(proc.stdout.strip(), expected) + + def test_pip_abi3t_tags(self): + pip_wheel = next(Path(ensurepip.__file__).parent.joinpath("_bundled").glob("pip-*.whl")) + code = ( + "import sys; from pip._vendor.packaging import tags; " + "values = set(tags.sys_tags()); " + "current = f'cp{sys.version_info[0]}{sys.version_info[1]}'; " + "print(any(tag.interpreter == current and tag.abi == 'abi3t' for tag in values)); " + "print(any(tag.abi == 'abi3' for tag in values)); " + "print(any(tag.interpreter == current and tag.abi == current + 't' for tag in values))" + ) + env = dict(os.environ, PYTHONPATH=str(pip_wheel)) + expected_tags = { + False: ("False", "False", "False"), + True: ("True", "False", "False"), + } + for enabled, expected in expected_tags.items(): + proc = subprocess.run( + [ + sys.executable, + "--experimental-options=true", + f"--python.EnableAbi3t={str(enabled).lower()}", + "-S", + "-c", + code, + ], + env=env, + capture_output=True, + text=True, + check=True, + ) + self.assertEqual(tuple(proc.stdout.strip().splitlines()), expected) + + @unittest.skipUnless(sys.platform == "linux", "requires Linux extension wheel tags") + def test_pip_abi3t_wheel_install(self): + module_dir = Path(compile_module_from_string_no_import(ABI3T_SMOKE_SOURCE, "abi3t_smoke")) + extension = next(module_dir.glob("abi3t_smoke*.so")) + pip_wheel = next(Path(ensurepip.__file__).parent.joinpath("_bundled").glob("pip-*.whl")) + machine = platform.machine().lower().replace("-", "_") + interpreter_tag = f"cp{sys.version_info[0]}{sys.version_info[1]}" + wheel_name = f"abi3t_smoke-0.0.0-{interpreter_tag}-abi3.abi3t-linux_{machine}.whl" + with tempfile.TemporaryDirectory() as tmp: + tmp_path = Path(tmp) + wheel_path = tmp_path / wheel_name + dist_info = "abi3t_smoke-0.0.0.dist-info" + wheel_metadata = ( + "Wheel-Version: 1.0\n" + "Generator: graalpython-abi3t-test\n" + "Root-Is-Purelib: false\n" + f"Tag: {interpreter_tag}-abi3.abi3t-linux_{machine}\n" + ) + metadata = "Metadata-Version: 2.1\nName: abi3t-smoke\nVersion: 0.0.0\n" + with zipfile.ZipFile(wheel_path, "w", compression=zipfile.ZIP_DEFLATED) as wheel: + wheel.write(extension, "abi3t_smoke.so") + wheel.writestr(f"{dist_info}/WHEEL", wheel_metadata) + wheel.writestr(f"{dist_info}/METADATA", metadata) + wheel.writestr(f"{dist_info}/RECORD", "") + + install_dir = tmp_path / "installed" + install_dir.mkdir() + env = dict(os.environ, PYTHONPATH=str(pip_wheel)) + install = subprocess.run( + [ + sys.executable, + "--experimental-options=true", + "--python.EnableAbi3t=true", + "-S", + "-m", + "pip", + "install", + "--no-index", + "--no-deps", + "-vv", + "--target", + str(install_dir), + str(wheel_path), + ], + env=env, + capture_output=True, + text=True, + ) + self.assertEqual(install.returncode, 0, install.stderr) + import_env = dict(os.environ, PYTHONPATH=str(install_dir)) + proc = subprocess.run( + [ + sys.executable, + "--experimental-options=true", + "--python.EnableAbi3t=true", + "-S", + "-c", + "import abi3t_smoke as m; print(m.increment())", + ], + env=import_env, + capture_output=True, + text=True, + check=True, + ) + self.assertEqual(proc.stdout.strip(), "41") + + def test_modexport_smoke(self): + module_dir = compile_module_from_string_no_import(ABI3T_SMOKE_SOURCE, "abi3t_smoke") + env = dict(os.environ) + env["PYTHONPATH"] = module_dir + code = ( + "import importlib.machinery; import abi3t_smoke as m; " + "n = m.from_slots(importlib.machinery.ModuleSpec('from_slots', None)); " + "print(m.increment(), m.increment(), n.__name__)" + ) + proc = subprocess.run( + [sys.executable, "--experimental-options=true", "--python.EnableAbi3t=true", "-S", "-c", code], + env=env, + capture_output=True, + text=True, + check=True, + ) + self.assertEqual(proc.stdout.strip(), "41 42 from_slots") diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/GraalPythonModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/GraalPythonModuleBuiltins.java index 3a96270123..9c163df123 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/GraalPythonModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/GraalPythonModuleBuiltins.java @@ -286,6 +286,7 @@ public void postInitialize(Python3Core core) { mod.setAttribute(tsLiteral("core_home"), coreHome); mod.setAttribute(tsLiteral("stdlib_home"), stdlibHome); mod.setAttribute(tsLiteral("capi_home"), capiHome); + mod.setAttribute(tsLiteral("abi3t_enabled"), context.getOption(PythonOptions.EnableAbi3t)); Object[] arr = convertToObjectArray(PythonOptions.getExecutableList(context)); PList executableList = PFactory.createList(language, arr); mod.setAttribute(tsLiteral("executable_list"), executableList); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/ArgDescriptor.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/ArgDescriptor.java index 6f05b5b376..497e9764e6 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/ArgDescriptor.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/ArgDescriptor.java @@ -321,7 +321,7 @@ public enum ArgDescriptor { VA_LIST(ArgBehavior.Pointer, "va_list"), VA_LIST_PTR(ArgBehavior.Pointer, "va_list*"), VARARGS("..."), - VOID_PTR_LIST("void**"), + VOID_PTR_LIST(ArgBehavior.Pointer, "void**"), WCHAR_T_PTR(ArgBehavior.Pointer, "wchar_t*"), WCHAR_T_CONST_PTR(ArgBehavior.Pointer, "wchar_t*const*"), WCHAR_T_PTR_LIST(ArgBehavior.Pointer, "wchar_t**"), diff --git a/graalpython/lib-graalpython/patches/pip-26.1.2.patch b/graalpython/lib-graalpython/patches/pip-26.1.2.patch index 379ee0600d..dc352e9e53 100644 --- a/graalpython/lib-graalpython/patches/pip-26.1.2.patch +++ b/graalpython/lib-graalpython/patches/pip-26.1.2.patch @@ -222,3 +222,44 @@ index 251600e..2c1b1a7 100644 if self.version: params.append(self.version) path = os.path.join(base[0], *params) # noqa: PTH118 + +diff --git a/pip/_vendor/packaging/tags.py b/pip/_vendor/packaging/tags.py +--- a/pip/_vendor/packaging/tags.py ++++ b/pip/_vendor/packaging/tags.py +@@ -844,0 +845,21 @@ ++def _graalpy_abi3t_tags( ++ python_version: PythonVersion | None = None, ++ platforms: Iterable[str] | None = None, ++) -> Iterator[Tag]: ++ try: ++ import __graalpython__ ++ except ImportError: ++ return ++ if not getattr(__graalpython__, "abi3t_enabled", False): ++ return ++ if python_version is None: ++ python_version = sys.version_info[:2] ++ if len(python_version) < 2 or tuple(python_version) < (3, 2): ++ return ++ platforms = list(platforms or platform_tags()) ++ for minor_version in range(python_version[1], 1, -1): ++ interpreter = f"cp{_version_nodot((python_version[0], minor_version))}" ++ for platform_ in platforms: ++ yield Tag(interpreter, "abi3t", platform_) ++ ++ +@@ -878,0 +900 @@ ++ yield from _graalpy_abi3t_tags() +diff --git a/pip/_internal/utils/compatibility_tags.py b/pip/_internal/utils/compatibility_tags.py +--- a/pip/_internal/utils/compatibility_tags.py ++++ b/pip/_internal/utils/compatibility_tags.py +@@ -9,0 +10 @@ ++ _graalpy_abi3t_tags, +@@ -192,0 +194,7 @@ ++ if impl is None and (abis is None or "abi3t" in abis): ++ supported.extend( ++ _graalpy_abi3t_tags( ++ python_version=python_version, ++ platforms=platforms, ++ ) ++ ) diff --git a/graalpython/lib-python/3/ensurepip/_bundled/pip-26.1.2-py3-none-any.whl b/graalpython/lib-python/3/ensurepip/_bundled/pip-26.1.2-py3-none-any.whl index 7e4a6c3df181d96968ecf8e6d6a4aa64a3712646..f29bce08fdc35e8a9b3e38a59b2527273d037d98 100644 GIT binary patch delta 38832 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 delta 38253 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 From 9532291d6be68a01007f676d4621c171f64bb65d Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Tue, 25 Aug 2026 11:27:47 +0200 Subject: [PATCH 14/24] Make C API signature validation ABI-aware --- abi/abi3t-linux.txt | 6 +- capi-abis.toml | 19 + .../CAPIFunctions-ABI3T.txt | 762 ++++++++++++++++++ .../processor/CApiBuiltinsProcessor.java | 47 +- .../modules/cext/PythonCextBuiltins.java | 20 +- .../cext/PythonCextModuleBuiltins.java | 15 +- .../objects/cext/capi/CApiFunction.java | 72 +- .../cext/capi/transitions/ArgDescriptor.java | 11 + mx.graalpython/mx_graalpython.py | 50 +- scripts/csignature.py | 298 +++++-- scripts/generate_abi3t_manifest.py | 103 --- 11 files changed, 1197 insertions(+), 206 deletions(-) create mode 100644 capi-abis.toml create mode 100644 graalpython/com.oracle.graal.python.cext/CAPIFunctions-ABI3T.txt delete mode 100644 scripts/generate_abi3t_manifest.py diff --git a/abi/abi3t-linux.txt b/abi/abi3t-linux.txt index 9d38f0f3f8..cf51ec1899 100644 --- a/abi/abi3t-linux.txt +++ b/abi/abi3t-linux.txt @@ -1,7 +1,7 @@ -# Generated by scripts/generate_abi3t_manifest.py. Do not edit manually. -# Source: CPython v3.15.0rc1 (37e98da7c19a9e5892ee756d6dee08225422cd49) +# Generated by scripts/csignature.py. Do not edit manually. +# Profile: ABI3T; source: CPython v3.15.0rc1 (37e98da7c19a9e5892ee756d6dee08225422cd49) # Source file: Misc/stable_abi.toml -# Target: Linux ABI3T (MS_WINDOWS, Py_REF_DEBUG, and USE_STACKCHECK disabled) +# Disabled features: MS_WINDOWS, Py_REF_DEBUG, USE_STACKCHECK # Format: FUNC PyABIInfo_Check diff --git a/capi-abis.toml b/capi-abis.toml new file mode 100644 index 0000000000..ab371a470c --- /dev/null +++ b/capi-abis.toml @@ -0,0 +1,19 @@ +# C API signature and export profiles. Paths are relative to the repository root. + +[profiles.GRAALPY] +python_version = "3.13" +source_ref = "v3.13.14" +mode = "full" +defines = [] +inventory = "graalpython/com.oracle.graal.python.cext/CAPIFunctions.txt" + +[profiles.ABI3T] +parent = "GRAALPY" +python_version = "3.15" +source_ref = "v3.15.0rc1" +mode = "stable" +defines = ["Py_TARGET_ABI3T=0x030f0000"] +inventory = "graalpython/com.oracle.graal.python.cext/CAPIFunctions-ABI3T.txt" +export_manifest = "abi/abi3t-linux.txt" +missing_symbols = "abi/abi3t-missing-linux.txt" +disabled_features = ["MS_WINDOWS", "Py_REF_DEBUG", "USE_STACKCHECK"] diff --git a/graalpython/com.oracle.graal.python.cext/CAPIFunctions-ABI3T.txt b/graalpython/com.oracle.graal.python.cext/CAPIFunctions-ABI3T.txt new file mode 100644 index 0000000000..1c710a7b83 --- /dev/null +++ b/graalpython/com.oracle.graal.python.cext/CAPIFunctions-ABI3T.txt @@ -0,0 +1,762 @@ +PyABIInfo_Check;int;PyABIInfo*|const char* +PyAIter_Check;int;PyObject* +PyArg_Parse;int;PyObject*|const char*|... +PyArg_ParseTuple;int;PyObject*|const char*|... +PyArg_ParseTupleAndKeywords;int;PyObject*|PyObject*|const char*|char*const*|... +PyArg_UnpackTuple;int;PyObject*|const char*|Py_ssize_t|Py_ssize_t|... +PyArg_VaParse;int;PyObject*|const char*|va_list +PyArg_VaParseTupleAndKeywords;int;PyObject*|PyObject*|const char*|char*const*|va_list +PyArg_ValidateKeywordArguments;int;PyObject* +PyBool_FromLong;PyObject*;long +PyBuffer_FillContiguousStrides;void;int|Py_ssize_t*|Py_ssize_t*|int|char +PyBuffer_FillInfo;int;Py_buffer*|PyObject*|void*|Py_ssize_t|int|int +PyBuffer_FromContiguous;int;const Py_buffer*|const void*|Py_ssize_t|char +PyBuffer_GetPointer;void*;const Py_buffer*|const Py_ssize_t* +PyBuffer_IsContiguous;int;const Py_buffer*|char +PyBuffer_Release;void;Py_buffer* +PyBuffer_SizeFromFormat;Py_ssize_t;const char* +PyBuffer_ToContiguous;int;void*|const Py_buffer*|Py_ssize_t|char +PyByteArray_AsString;char*;PyObject* +PyByteArray_Concat;PyObject*;PyObject*|PyObject* +PyByteArray_FromObject;PyObject*;PyObject* +PyByteArray_FromStringAndSize;PyObject*;const char*|Py_ssize_t +PyByteArray_Resize;int;PyObject*|Py_ssize_t +PyByteArray_Size;Py_ssize_t;PyObject* +PyBytes_AsString;char*;PyObject* +PyBytes_AsStringAndSize;int;PyObject*|char**|Py_ssize_t* +PyBytes_Concat;void;PyObject**|PyObject* +PyBytes_ConcatAndDel;void;PyObject**|PyObject* +PyBytes_DecodeEscape;PyObject*;const char*|Py_ssize_t|const char*|Py_ssize_t|const char* +PyBytes_FromFormat;PyObject*;const char*|... +PyBytes_FromFormatV;PyObject*;const char*|va_list +PyBytes_FromObject;PyObject*;PyObject* +PyBytes_FromString;PyObject*;const char* +PyBytes_FromStringAndSize;PyObject*;const char*|Py_ssize_t +PyBytes_Repr;PyObject*;PyObject*|int +PyBytes_Size;Py_ssize_t;PyObject* +PyCFunction_GetFlags;int;PyObject* +PyCFunction_GetFunction;PyCFunction;PyObject* +PyCFunction_GetSelf;PyObject*;PyObject* +PyCFunction_New;PyObject*;PyMethodDef*|PyObject* +PyCFunction_NewEx;PyObject*;PyMethodDef*|PyObject*|PyObject* +PyCMethod_New;PyObject*;PyMethodDef*|PyObject*|PyObject*|PyTypeObject* +PyCallIter_New;PyObject*;PyObject*|PyObject* +PyCallable_Check;int;PyObject* +PyCapsule_GetContext;void*;PyObject* +PyCapsule_GetDestructor;PyCapsule_Destructor;PyObject* +PyCapsule_GetName;const char*;PyObject* +PyCapsule_GetPointer;void*;PyObject*|const char* +PyCapsule_Import;void*;const char*|int +PyCapsule_IsValid;int;PyObject*|const char* +PyCapsule_New;PyObject*;void*|const char*|PyCapsule_Destructor +PyCapsule_SetContext;int;PyObject*|void* +PyCapsule_SetDestructor;int;PyObject*|PyCapsule_Destructor +PyCapsule_SetName;int;PyObject*|const char* +PyCapsule_SetPointer;int;PyObject*|void* +PyCodec_BackslashReplaceErrors;PyObject*;PyObject* +PyCodec_Decode;PyObject*;PyObject*|const char*|const char* +PyCodec_Decoder;PyObject*;const char* +PyCodec_Encode;PyObject*;PyObject*|const char*|const char* +PyCodec_Encoder;PyObject*;const char* +PyCodec_IgnoreErrors;PyObject*;PyObject* +PyCodec_IncrementalDecoder;PyObject*;const char*|const char* +PyCodec_IncrementalEncoder;PyObject*;const char*|const char* +PyCodec_KnownEncoding;int;const char* +PyCodec_LookupError;PyObject*;const char* +PyCodec_NameReplaceErrors;PyObject*;PyObject* +PyCodec_Register;int;PyObject* +PyCodec_RegisterError;int;const char*|PyObject* +PyCodec_ReplaceErrors;PyObject*;PyObject* +PyCodec_StreamReader;PyObject*;const char*|PyObject*|const char* +PyCodec_StreamWriter;PyObject*;const char*|PyObject*|const char* +PyCodec_StrictErrors;PyObject*;PyObject* +PyCodec_Unregister;int;PyObject* +PyCodec_XMLCharRefReplaceErrors;PyObject*;PyObject* +PyComplex_FromDoubles;PyObject*;double|double +PyComplex_ImagAsDouble;double;PyObject* +PyComplex_RealAsDouble;double;PyObject* +PyCriticalSection2_Begin;void;PyCriticalSection2*|PyObject*|PyObject* +PyCriticalSection2_End;void;PyCriticalSection2* +PyCriticalSection_Begin;void;PyCriticalSection*|PyObject* +PyCriticalSection_End;void;PyCriticalSection* +PyDescr_NewClassMethod;PyObject*;PyTypeObject*|PyMethodDef* +PyDescr_NewGetSet;PyObject*;PyTypeObject*|PyGetSetDef* +PyDescr_NewMember;PyObject*;PyTypeObject*|PyMemberDef* +PyDescr_NewMethod;PyObject*;PyTypeObject*|PyMethodDef* +PyDictProxy_New;PyObject*;PyObject* +PyDict_Clear;void;PyObject* +PyDict_Contains;int;PyObject*|PyObject* +PyDict_Copy;PyObject*;PyObject* +PyDict_DelItem;int;PyObject*|PyObject* +PyDict_DelItemString;int;PyObject*|const char* +PyDict_GetItem;PyObject*;PyObject*|PyObject* +PyDict_GetItemRef;int;PyObject*|PyObject*|PyObject** +PyDict_GetItemString;PyObject*;PyObject*|const char* +PyDict_GetItemStringRef;int;PyObject*|const char*|PyObject** +PyDict_GetItemWithError;PyObject*;PyObject*|PyObject* +PyDict_Items;PyObject*;PyObject* +PyDict_Keys;PyObject*;PyObject* +PyDict_Merge;int;PyObject*|PyObject*|int +PyDict_MergeFromSeq2;int;PyObject*|PyObject*|int +PyDict_New;PyObject*;void +PyDict_Next;int;PyObject*|Py_ssize_t*|PyObject**|PyObject** +PyDict_SetDefaultRef;int;PyObject*|PyObject*|PyObject*|PyObject** +PyDict_SetItem;int;PyObject*|PyObject*|PyObject* +PyDict_SetItemString;int;PyObject*|const char*|PyObject* +PyDict_Size;Py_ssize_t;PyObject* +PyDict_Update;int;PyObject*|PyObject* +PyDict_Values;PyObject*;PyObject* +PyErr_BadArgument;int;void +PyErr_BadInternalCall;void;void +PyErr_CheckSignals;int;void +PyErr_Clear;void;void +PyErr_Display;void;PyObject*|PyObject*|PyObject* +PyErr_DisplayException;void;PyObject* +PyErr_ExceptionMatches;int;PyObject* +PyErr_Fetch;void;PyObject**|PyObject**|PyObject** +PyErr_Format;PyObject*;PyObject*|const char*|... +PyErr_FormatV;PyObject*;PyObject*|const char*|va_list +PyErr_GetExcInfo;void;PyObject**|PyObject**|PyObject** +PyErr_GetHandledException;PyObject*;void +PyErr_GetRaisedException;PyObject*;void +PyErr_GivenExceptionMatches;int;PyObject*|PyObject* +PyErr_NewException;PyObject*;const char*|PyObject*|PyObject* +PyErr_NewExceptionWithDoc;PyObject*;const char*|const char*|PyObject*|PyObject* +PyErr_NoMemory;PyObject*;void +PyErr_NormalizeException;void;PyObject**|PyObject**|PyObject** +PyErr_Occurred;PyObject*;void +PyErr_Print;void;void +PyErr_PrintEx;void;int +PyErr_ProgramText;PyObject*;const char*|int +PyErr_ResourceWarning;int;PyObject*|Py_ssize_t|const char*|... +PyErr_Restore;void;PyObject*|PyObject*|PyObject* +PyErr_SetExcInfo;void;PyObject*|PyObject*|PyObject* +PyErr_SetFromErrno;PyObject*;PyObject* +PyErr_SetFromErrnoWithFilename;PyObject*;PyObject*|const char* +PyErr_SetFromErrnoWithFilenameObject;PyObject*;PyObject*|PyObject* +PyErr_SetFromErrnoWithFilenameObjects;PyObject*;PyObject*|PyObject*|PyObject* +PyErr_SetHandledException;void;PyObject* +PyErr_SetImportError;PyObject*;PyObject*|PyObject*|PyObject* +PyErr_SetImportErrorSubclass;PyObject*;PyObject*|PyObject*|PyObject*|PyObject* +PyErr_SetInterrupt;void;void +PyErr_SetInterruptEx;int;int +PyErr_SetNone;void;PyObject* +PyErr_SetObject;void;PyObject*|PyObject* +PyErr_SetRaisedException;void;PyObject* +PyErr_SetString;void;PyObject*|const char* +PyErr_SyntaxLocation;void;const char*|int +PyErr_SyntaxLocationEx;void;const char*|int|int +PyErr_WarnEx;int;PyObject*|const char*|Py_ssize_t +PyErr_WarnExplicit;int;PyObject*|const char*|const char*|int|const char*|PyObject* +PyErr_WarnFormat;int;PyObject*|Py_ssize_t|const char*|... +PyErr_WriteUnraisable;void;PyObject* +PyEval_AcquireThread;void;PyThreadState* +PyEval_EvalCode;PyObject*;PyObject*|PyObject*|PyObject* +PyEval_EvalCodeEx;PyObject*;PyObject*|PyObject*|PyObject*|PyObject*const*|int|PyObject*const*|int|PyObject*const*|int|PyObject*|PyObject* +PyEval_EvalFrame;PyObject*;PyFrameObject* +PyEval_EvalFrameEx;PyObject*;PyFrameObject*|int +PyEval_GetBuiltins;PyObject*;void +PyEval_GetFrame;PyFrameObject*;void +PyEval_GetFrameBuiltins;PyObject*;void +PyEval_GetFrameGlobals;PyObject*;void +PyEval_GetFrameLocals;PyObject*;void +PyEval_GetFuncDesc;const char*;PyObject* +PyEval_GetFuncName;const char*;PyObject* +PyEval_GetGlobals;PyObject*;void +PyEval_GetLocals;PyObject*;void +PyEval_InitThreads;void;void +PyEval_ReleaseThread;void;PyThreadState* +PyEval_RestoreThread;void;PyThreadState* +PyEval_SaveThread;PyThreadState*;void +PyExceptionClass_Name;const char*;PyObject* +PyException_GetArgs;PyObject*;PyObject* +PyException_GetCause;PyObject*;PyObject* +PyException_GetContext;PyObject*;PyObject* +PyException_GetTraceback;PyObject*;PyObject* +PyException_SetArgs;void;PyObject*|PyObject* +PyException_SetCause;void;PyObject*|PyObject* +PyException_SetContext;void;PyObject*|PyObject* +PyException_SetTraceback;int;PyObject*|PyObject* +PyFile_FromFd;PyObject*;int|const char*|const char*|int|const char*|const char*|const char*|int +PyFile_GetLine;PyObject*;PyObject*|int +PyFile_WriteObject;int;PyObject*|PyObject*|int +PyFile_WriteString;int;const char*|PyObject* +PyFloat_AsDouble;double;PyObject* +PyFloat_FromDouble;PyObject*;double +PyFloat_FromString;PyObject*;PyObject* +PyFloat_GetInfo;PyObject*;void +PyFloat_GetMax;double;void +PyFloat_GetMin;double;void +PyFrame_GetCode;PyCodeObject*;PyFrameObject* +PyFrame_GetLineNumber;int;PyFrameObject* +PyFrozenSet_New;PyObject*;PyObject* +PyGC_Collect;Py_ssize_t;void +PyGC_Disable;int;void +PyGC_Enable;int;void +PyGC_IsEnabled;int;void +PyGILState_Ensure;PyGILState_STATE;void +PyGILState_GetThisThreadState;PyThreadState*;void +PyGILState_Release;void;PyGILState_STATE +PyImport_AddModule;PyObject*;const char* +PyImport_AddModuleObject;PyObject*;PyObject* +PyImport_AddModuleRef;PyObject*;const char* +PyImport_AppendInittab;int;const char*|PyObject*(*)(void) +PyImport_ExecCodeModule;PyObject*;const char*|PyObject* +PyImport_ExecCodeModuleEx;PyObject*;const char*|PyObject*|const char* +PyImport_ExecCodeModuleObject;PyObject*;PyObject*|PyObject*|PyObject*|PyObject* +PyImport_ExecCodeModuleWithPathnames;PyObject*;const char*|PyObject*|const char*|const char* +PyImport_GetImporter;PyObject*;PyObject* +PyImport_GetMagicNumber;long;void +PyImport_GetMagicTag;const char*;void +PyImport_GetModule;PyObject*;PyObject* +PyImport_GetModuleDict;PyObject*;void +PyImport_Import;PyObject*;PyObject* +PyImport_ImportFrozenModule;int;const char* +PyImport_ImportFrozenModuleObject;int;PyObject* +PyImport_ImportModule;PyObject*;const char* +PyImport_ImportModuleLevel;PyObject*;const char*|PyObject*|PyObject*|PyObject*|int +PyImport_ImportModuleLevelObject;PyObject*;PyObject*|PyObject*|PyObject*|PyObject*|int +PyImport_ReloadModule;PyObject*;PyObject* +PyIndex_Check;int;PyObject* +PyInterpreterGuard_Close;void;PyInterpreterGuard* +PyInterpreterGuard_FromCurrent;PyInterpreterGuard*;void +PyInterpreterGuard_FromView;PyInterpreterGuard*;PyInterpreterView* +PyInterpreterState_Clear;void;PyInterpreterState* +PyInterpreterState_Delete;void;PyInterpreterState* +PyInterpreterState_Get;PyInterpreterState*;void +PyInterpreterState_GetDict;PyObject*;PyInterpreterState* +PyInterpreterState_GetID;int64_t;PyInterpreterState* +PyInterpreterState_New;PyInterpreterState*;void +PyInterpreterView_Close;void;PyInterpreterView* +PyInterpreterView_FromCurrent;PyInterpreterView*;void +PyInterpreterView_FromMain;PyInterpreterView*;void +PyIter_Check;int;PyObject* +PyIter_Next;PyObject*;PyObject* +PyIter_NextItem;int;PyObject*|PyObject** +PyIter_Send;PySendResult;PyObject*|PyObject*|PyObject** +PyList_Append;int;PyObject*|PyObject* +PyList_AsTuple;PyObject*;PyObject* +PyList_GetItem;PyObject*;PyObject*|Py_ssize_t +PyList_GetItemRef;PyObject*;PyObject*|Py_ssize_t +PyList_GetSlice;PyObject*;PyObject*|Py_ssize_t|Py_ssize_t +PyList_Insert;int;PyObject*|Py_ssize_t|PyObject* +PyList_New;PyObject*;Py_ssize_t +PyList_Reverse;int;PyObject* +PyList_SetItem;int;PyObject*|Py_ssize_t|PyObject* +PyList_SetSlice;int;PyObject*|Py_ssize_t|Py_ssize_t|PyObject* +PyList_Size;Py_ssize_t;PyObject* +PyList_Sort;int;PyObject* +PyLongWriter_Create;PyLongWriter*;int|Py_ssize_t|void** +PyLongWriter_Discard;void;PyLongWriter* +PyLongWriter_Finish;PyObject*;PyLongWriter* +PyLong_AsDouble;double;PyObject* +PyLong_AsInt32;int;PyObject*|int32_t* +PyLong_AsInt64;int;PyObject*|int64_t* +PyLong_AsInt;int;PyObject* +PyLong_AsLong;long;PyObject* +PyLong_AsLongAndOverflow;long;PyObject*|int* +PyLong_AsLongLong;long long;PyObject* +PyLong_AsLongLongAndOverflow;long long;PyObject*|int* +PyLong_AsNativeBytes;Py_ssize_t;PyObject*|void*|Py_ssize_t|int +PyLong_AsSize_t;size_t;PyObject* +PyLong_AsSsize_t;Py_ssize_t;PyObject* +PyLong_AsUInt32;int;PyObject*|uint32_t* +PyLong_AsUInt64;int;PyObject*|uint64_t* +PyLong_AsUnsignedLong;unsigned long;PyObject* +PyLong_AsUnsignedLongLong;unsigned long long;PyObject* +PyLong_AsUnsignedLongLongMask;unsigned long long;PyObject* +PyLong_AsUnsignedLongMask;unsigned long;PyObject* +PyLong_AsVoidPtr;void*;PyObject* +PyLong_Export;int;PyObject*|PyLongExport* +PyLong_FreeExport;void;PyLongExport* +PyLong_FromDouble;PyObject*;double +PyLong_FromInt32;PyObject*;int32_t +PyLong_FromInt64;PyObject*;int64_t +PyLong_FromLong;PyObject*;long +PyLong_FromLongLong;PyObject*;long long +PyLong_FromNativeBytes;PyObject*;const void*|size_t|int +PyLong_FromSize_t;PyObject*;size_t +PyLong_FromSsize_t;PyObject*;Py_ssize_t +PyLong_FromString;PyObject*;const char*|char**|int +PyLong_FromUInt32;PyObject*;uint32_t +PyLong_FromUInt64;PyObject*;uint64_t +PyLong_FromUnsignedLong;PyObject*;unsigned long +PyLong_FromUnsignedLongLong;PyObject*;unsigned long long +PyLong_FromUnsignedNativeBytes;PyObject*;const void*|size_t|int +PyLong_FromVoidPtr;PyObject*;void* +PyLong_GetInfo;PyObject*;void +PyLong_GetNativeLayout;const PyLongLayout*;void +PyMapping_Check;int;PyObject* +PyMapping_GetItemString;PyObject*;PyObject*|const char* +PyMapping_GetOptionalItem;int;PyObject*|PyObject*|PyObject** +PyMapping_GetOptionalItemString;int;PyObject*|const char*|PyObject** +PyMapping_HasKey;int;PyObject*|PyObject* +PyMapping_HasKeyString;int;PyObject*|const char* +PyMapping_HasKeyStringWithError;int;PyObject*|const char* +PyMapping_HasKeyWithError;int;PyObject*|PyObject* +PyMapping_Items;PyObject*;PyObject* +PyMapping_Keys;PyObject*;PyObject* +PyMapping_Length;Py_ssize_t;PyObject* +PyMapping_SetItemString;int;PyObject*|const char*|PyObject* +PyMapping_Size;Py_ssize_t;PyObject* +PyMapping_Values;PyObject*;PyObject* +PyMem_Calloc;void*;size_t|size_t +PyMem_Free;void;void* +PyMem_Malloc;void*;size_t +PyMem_RawCalloc;void*;size_t|size_t +PyMem_RawFree;void;void* +PyMem_RawMalloc;void*;size_t +PyMem_RawRealloc;void*;void*|size_t +PyMem_Realloc;void*;void*|size_t +PyMember_GetOne;PyObject*;const char*|PyMemberDef* +PyMember_SetOne;int;char*|PyMemberDef*|PyObject* +PyMemoryView_FromBuffer;PyObject*;const Py_buffer* +PyMemoryView_FromMemory;PyObject*;char*|Py_ssize_t|int +PyMemoryView_FromObject;PyObject*;PyObject* +PyMemoryView_GetContiguous;PyObject*;PyObject*|int|char +PyModuleDef_Init;PyObject*;PyModuleDef* +PyModule_Add;int;PyObject*|const char*|PyObject* +PyModule_AddFunctions;int;PyObject*|PyMethodDef* +PyModule_AddIntConstant;int;PyObject*|const char*|long +PyModule_AddObject;int;PyObject*|const char*|PyObject* +PyModule_AddObjectRef;int;PyObject*|const char*|PyObject* +PyModule_AddStringConstant;int;PyObject*|const char*|const char* +PyModule_AddType;int;PyObject*|PyTypeObject* +PyModule_Create2;PyObject*;PyModuleDef*|int +PyModule_Exec;int;PyObject* +PyModule_ExecDef;int;PyObject*|PyModuleDef* +PyModule_FromDefAndSpec2;PyObject*;PyModuleDef*|PyObject*|int +PyModule_FromSlotsAndSpec;PyObject*;const PySlot*|PyObject* +PyModule_GetDef;PyModuleDef*;PyObject* +PyModule_GetDict;PyObject*;PyObject* +PyModule_GetFilename;const char*;PyObject* +PyModule_GetFilenameObject;PyObject*;PyObject* +PyModule_GetName;const char*;PyObject* +PyModule_GetNameObject;PyObject*;PyObject* +PyModule_GetState;void*;PyObject* +PyModule_GetStateSize;int;PyObject*|Py_ssize_t* +PyModule_GetState_DuringGC;void*;PyObject* +PyModule_GetToken;int;PyObject*|void** +PyModule_GetToken_DuringGC;int;PyObject*|void** +PyModule_New;PyObject*;const char* +PyModule_NewObject;PyObject*;PyObject* +PyModule_SetDocString;int;PyObject*|const char* +PyNumber_Absolute;PyObject*;PyObject* +PyNumber_Add;PyObject*;PyObject*|PyObject* +PyNumber_And;PyObject*;PyObject*|PyObject* +PyNumber_AsSsize_t;Py_ssize_t;PyObject*|PyObject* +PyNumber_Check;int;PyObject* +PyNumber_Divmod;PyObject*;PyObject*|PyObject* +PyNumber_Float;PyObject*;PyObject* +PyNumber_FloorDivide;PyObject*;PyObject*|PyObject* +PyNumber_InPlaceAdd;PyObject*;PyObject*|PyObject* +PyNumber_InPlaceAnd;PyObject*;PyObject*|PyObject* +PyNumber_InPlaceFloorDivide;PyObject*;PyObject*|PyObject* +PyNumber_InPlaceLshift;PyObject*;PyObject*|PyObject* +PyNumber_InPlaceMatrixMultiply;PyObject*;PyObject*|PyObject* +PyNumber_InPlaceMultiply;PyObject*;PyObject*|PyObject* +PyNumber_InPlaceOr;PyObject*;PyObject*|PyObject* +PyNumber_InPlacePower;PyObject*;PyObject*|PyObject*|PyObject* +PyNumber_InPlaceRemainder;PyObject*;PyObject*|PyObject* +PyNumber_InPlaceRshift;PyObject*;PyObject*|PyObject* +PyNumber_InPlaceSubtract;PyObject*;PyObject*|PyObject* +PyNumber_InPlaceTrueDivide;PyObject*;PyObject*|PyObject* +PyNumber_InPlaceXor;PyObject*;PyObject*|PyObject* +PyNumber_Index;PyObject*;PyObject* +PyNumber_Invert;PyObject*;PyObject* +PyNumber_Long;PyObject*;PyObject* +PyNumber_Lshift;PyObject*;PyObject*|PyObject* +PyNumber_MatrixMultiply;PyObject*;PyObject*|PyObject* +PyNumber_Multiply;PyObject*;PyObject*|PyObject* +PyNumber_Negative;PyObject*;PyObject* +PyNumber_Or;PyObject*;PyObject*|PyObject* +PyNumber_Positive;PyObject*;PyObject* +PyNumber_Power;PyObject*;PyObject*|PyObject*|PyObject* +PyNumber_Remainder;PyObject*;PyObject*|PyObject* +PyNumber_Rshift;PyObject*;PyObject*|PyObject* +PyNumber_Subtract;PyObject*;PyObject*|PyObject* +PyNumber_ToBase;PyObject*;PyObject*|int +PyNumber_TrueDivide;PyObject*;PyObject*|PyObject* +PyNumber_Xor;PyObject*;PyObject*|PyObject* +PyOS_AfterFork;void;void +PyOS_AfterFork_Child;void;void +PyOS_AfterFork_Parent;void;void +PyOS_BeforeFork;void;void +PyOS_FSPath;PyObject*;PyObject* +PyOS_InterruptOccurred;int;void +PyOS_double_to_string;char*;double|char|int|int|int* +PyOS_getsig;PyOS_sighandler_t;int +PyOS_mystricmp;int;const char*|const char* +PyOS_mystrnicmp;int;const char*|const char*|Py_ssize_t +PyOS_setsig;PyOS_sighandler_t;int|PyOS_sighandler_t +PyOS_snprintf;int;char*|size_t|const char*|... +PyOS_string_to_double;double;const char*|char**|PyObject* +PyOS_strtol;long;const char*|char**|int +PyOS_strtoul;unsigned long;const char*|char**|int +PyOS_vsnprintf;int;char*|size_t|const char*|va_list +PyObject_ASCII;PyObject*;PyObject* +PyObject_AsFileDescriptor;int;PyObject* +PyObject_Bytes;PyObject*;PyObject* +PyObject_Call;PyObject*;PyObject*|PyObject*|PyObject* +PyObject_CallFinalizerFromDealloc;int;PyObject* +PyObject_CallFunction;PyObject*;PyObject*|const char*|... +PyObject_CallFunctionObjArgs;PyObject*;PyObject*|... +PyObject_CallMethod;PyObject*;PyObject*|const char*|const char*|... +PyObject_CallMethodObjArgs;PyObject*;PyObject*|PyObject*|... +PyObject_CallNoArgs;PyObject*;PyObject* +PyObject_CallObject;PyObject*;PyObject*|PyObject* +PyObject_Calloc;void*;size_t|size_t +PyObject_CheckBuffer;int;PyObject* +PyObject_ClearWeakRefs;void;PyObject* +PyObject_CopyData;int;PyObject*|PyObject* +PyObject_DelAttr;int;PyObject*|PyObject* +PyObject_DelAttrString;int;PyObject*|const char* +PyObject_DelItem;int;PyObject*|PyObject* +PyObject_DelItemString;int;PyObject*|const char* +PyObject_Dir;PyObject*;PyObject* +PyObject_Format;PyObject*;PyObject*|PyObject* +PyObject_Free;void;void* +PyObject_GC_Del;void;void* +PyObject_GC_IsFinalized;int;PyObject* +PyObject_GC_IsTracked;int;PyObject* +PyObject_GC_Track;void;void* +PyObject_GC_UnTrack;void;void* +PyObject_GenericGetAttr;PyObject*;PyObject*|PyObject* +PyObject_GenericGetDict;PyObject*;PyObject*|void* +PyObject_GenericSetAttr;int;PyObject*|PyObject*|PyObject* +PyObject_GenericSetDict;int;PyObject*|PyObject*|void* +PyObject_GetAIter;PyObject*;PyObject* +PyObject_GetAttr;PyObject*;PyObject*|PyObject* +PyObject_GetAttrString;PyObject*;PyObject*|const char* +PyObject_GetBuffer;int;PyObject*|Py_buffer*|int +PyObject_GetItem;PyObject*;PyObject*|PyObject* +PyObject_GetIter;PyObject*;PyObject* +PyObject_GetOptionalAttr;int;PyObject*|PyObject*|PyObject** +PyObject_GetOptionalAttrString;int;PyObject*|const char*|PyObject** +PyObject_GetTypeData;void*;PyObject*|PyTypeObject* +PyObject_GetTypeData_DuringGC;void*;PyObject*|PyTypeObject* +PyObject_HasAttr;int;PyObject*|PyObject* +PyObject_HasAttrString;int;PyObject*|const char* +PyObject_HasAttrStringWithError;int;PyObject*|const char* +PyObject_HasAttrWithError;int;PyObject*|PyObject* +PyObject_Hash;Py_hash_t;PyObject* +PyObject_HashNotImplemented;Py_hash_t;PyObject* +PyObject_Init;PyObject*;PyObject*|PyTypeObject* +PyObject_InitVar;PyVarObject*;PyVarObject*|PyTypeObject*|Py_ssize_t +PyObject_IsInstance;int;PyObject*|PyObject* +PyObject_IsSubclass;int;PyObject*|PyObject* +PyObject_IsTrue;int;PyObject* +PyObject_Length;Py_ssize_t;PyObject* +PyObject_Malloc;void*;size_t +PyObject_Not;int;PyObject* +PyObject_Realloc;void*;void*|size_t +PyObject_Repr;PyObject*;PyObject* +PyObject_RichCompare;PyObject*;PyObject*|PyObject*|int +PyObject_RichCompareBool;int;PyObject*|PyObject*|int +PyObject_SelfIter;PyObject*;PyObject* +PyObject_SetAttr;int;PyObject*|PyObject*|PyObject* +PyObject_SetAttrString;int;PyObject*|const char*|PyObject* +PyObject_SetItem;int;PyObject*|PyObject*|PyObject* +PyObject_Size;Py_ssize_t;PyObject* +PyObject_Str;PyObject*;PyObject* +PyObject_Type;PyObject*;PyObject* +PyObject_Vectorcall;PyObject*;PyObject*|PyObject*const*|size_t|PyObject* +PyObject_VectorcallMethod;PyObject*;PyObject*|PyObject*const*|size_t|PyObject* +PySeqIter_New;PyObject*;PyObject* +PySequence_Check;int;PyObject* +PySequence_Concat;PyObject*;PyObject*|PyObject* +PySequence_Contains;int;PyObject*|PyObject* +PySequence_Count;Py_ssize_t;PyObject*|PyObject* +PySequence_DelItem;int;PyObject*|Py_ssize_t +PySequence_DelSlice;int;PyObject*|Py_ssize_t|Py_ssize_t +PySequence_Fast;PyObject*;PyObject*|const char* +PySequence_GetItem;PyObject*;PyObject*|Py_ssize_t +PySequence_GetSlice;PyObject*;PyObject*|Py_ssize_t|Py_ssize_t +PySequence_In;int;PyObject*|PyObject* +PySequence_InPlaceConcat;PyObject*;PyObject*|PyObject* +PySequence_InPlaceRepeat;PyObject*;PyObject*|Py_ssize_t +PySequence_Index;Py_ssize_t;PyObject*|PyObject* +PySequence_Length;Py_ssize_t;PyObject* +PySequence_List;PyObject*;PyObject* +PySequence_Repeat;PyObject*;PyObject*|Py_ssize_t +PySequence_SetItem;int;PyObject*|Py_ssize_t|PyObject* +PySequence_SetSlice;int;PyObject*|Py_ssize_t|Py_ssize_t|PyObject* +PySequence_Size;Py_ssize_t;PyObject* +PySequence_Tuple;PyObject*;PyObject* +PySet_Add;int;PyObject*|PyObject* +PySet_Clear;int;PyObject* +PySet_Contains;int;PyObject*|PyObject* +PySet_Discard;int;PyObject*|PyObject* +PySet_New;PyObject*;PyObject* +PySet_Pop;PyObject*;PyObject* +PySet_Size;Py_ssize_t;PyObject* +PySlice_AdjustIndices;Py_ssize_t;Py_ssize_t|Py_ssize_t*|Py_ssize_t*|Py_ssize_t +PySlice_GetIndices;int;PyObject*|Py_ssize_t|Py_ssize_t*|Py_ssize_t*|Py_ssize_t* +PySlice_GetIndicesEx;int;PyObject*|Py_ssize_t|Py_ssize_t*|Py_ssize_t*|Py_ssize_t*|Py_ssize_t* +PySlice_New;PyObject*;PyObject*|PyObject*|PyObject* +PySlice_Unpack;int;PyObject*|Py_ssize_t*|Py_ssize_t*|Py_ssize_t* +PyState_AddModule;int;PyObject*|PyModuleDef* +PyState_FindModule;PyObject*;PyModuleDef* +PyState_RemoveModule;int;PyModuleDef* +PyStructSequence_GetItem;PyObject*;PyObject*|Py_ssize_t +PyStructSequence_New;PyObject*;PyTypeObject* +PyStructSequence_NewType;PyTypeObject*;PyStructSequence_Desc* +PyStructSequence_SetItem;void;PyObject*|Py_ssize_t|PyObject* +PySys_Audit;int;const char*|const char*|... +PySys_AuditTuple;int;const char*|PyObject* +PySys_FormatStderr;void;const char*|... +PySys_FormatStdout;void;const char*|... +PySys_GetAttr;PyObject*;PyObject* +PySys_GetAttrString;PyObject*;const char* +PySys_GetObject;PyObject*;const char* +PySys_GetOptionalAttr;int;PyObject*|PyObject** +PySys_GetOptionalAttrString;int;const char*|PyObject** +PySys_GetXOptions;PyObject*;void +PySys_SetArgv;void;int|wchar_t** +PySys_SetArgvEx;void;int|wchar_t**|int +PySys_SetObject;int;const char*|PyObject* +PySys_WriteStderr;void;const char*|... +PySys_WriteStdout;void;const char*|... +PyThreadState_Clear;void;PyThreadState* +PyThreadState_Delete;void;PyThreadState* +PyThreadState_Ensure;PyThreadStateToken*;PyInterpreterGuard* +PyThreadState_EnsureFromView;PyThreadStateToken*;PyInterpreterView* +PyThreadState_Get;PyThreadState*;void +PyThreadState_GetDict;PyObject*;void +PyThreadState_GetFrame;PyFrameObject*;PyThreadState* +PyThreadState_GetID;uint64_t;PyThreadState* +PyThreadState_GetInterpreter;PyInterpreterState*;PyThreadState* +PyThreadState_New;PyThreadState*;PyInterpreterState* +PyThreadState_Release;void;PyThreadStateToken* +PyThreadState_SetAsyncExc;int;unsigned long|PyObject* +PyThreadState_Swap;PyThreadState*;PyThreadState* +PyThread_GetInfo;PyObject*;void +PyThread_ReInitTLS;void;void +PyThread_acquire_lock;int;PyThread_type_lock|int +PyThread_acquire_lock_timed;PyLockStatus;PyThread_type_lock|long long|int +PyThread_allocate_lock;PyThread_type_lock;void +PyThread_create_key;int;void +PyThread_delete_key;void;int +PyThread_delete_key_value;void;int +PyThread_exit_thread;void;void +PyThread_free_lock;void;PyThread_type_lock +PyThread_get_key_value;void*;int +PyThread_get_stacksize;size_t;void +PyThread_get_thread_ident;unsigned long;void +PyThread_get_thread_native_id;unsigned long;void +PyThread_init_thread;void;void +PyThread_release_lock;void;PyThread_type_lock +PyThread_set_key_value;int;int|void* +PyThread_set_stacksize;int;size_t +PyThread_start_new_thread;unsigned long;void (*)(void*)|void* +PyThread_tss_alloc;Py_tss_t*;void +PyThread_tss_create;int;Py_tss_t* +PyThread_tss_delete;void;Py_tss_t* +PyThread_tss_free;void;Py_tss_t* +PyThread_tss_get;void*;Py_tss_t* +PyThread_tss_is_created;int;Py_tss_t* +PyThread_tss_set;int;Py_tss_t*|void* +PyTraceBack_Here;int;PyFrameObject* +PyTraceBack_Print;int;PyObject*|PyObject* +PyTuple_GetItem;PyObject*;PyObject*|Py_ssize_t +PyTuple_GetSlice;PyObject*;PyObject*|Py_ssize_t|Py_ssize_t +PyTuple_New;PyObject*;Py_ssize_t +PyTuple_Pack;PyObject*;Py_ssize_t|... +PyTuple_SetItem;int;PyObject*|Py_ssize_t|PyObject* +PyTuple_Size;Py_ssize_t;PyObject* +PyType_ClearCache;unsigned int;void +PyType_Freeze;int;PyTypeObject* +PyType_FromMetaclass;PyObject*;PyTypeObject*|PyObject*|PyType_Spec*|PyObject* +PyType_FromModuleAndSpec;PyObject*;PyObject*|PyType_Spec*|PyObject* +PyType_FromSlots;PyObject*;struct PySlot* +PyType_FromSpec;PyObject*;PyType_Spec* +PyType_FromSpecWithBases;PyObject*;PyType_Spec*|PyObject* +PyType_GenericAlloc;PyObject*;PyTypeObject*|Py_ssize_t +PyType_GenericNew;PyObject*;PyTypeObject*|PyObject*|PyObject* +PyType_GetBaseByToken;int;PyTypeObject*|void*|PyTypeObject** +PyType_GetBaseByToken_DuringGC;int;PyTypeObject*|void*|PyTypeObject** +PyType_GetFlags;unsigned long;PyTypeObject* +PyType_GetFullyQualifiedName;PyObject*;PyTypeObject* +PyType_GetModule;PyObject*;PyTypeObject* +PyType_GetModuleByDef;PyObject*;PyTypeObject*|PyModuleDef* +PyType_GetModuleByToken;PyObject*;PyTypeObject*|const void* +PyType_GetModuleByToken_DuringGC;PyObject*;PyTypeObject*|const void* +PyType_GetModuleName;PyObject*;PyTypeObject* +PyType_GetModuleState;void*;PyTypeObject* +PyType_GetModuleState_DuringGC;void*;PyTypeObject* +PyType_GetModule_DuringGC;PyObject*;PyTypeObject* +PyType_GetName;PyObject*;PyTypeObject* +PyType_GetQualName;PyObject*;PyTypeObject* +PyType_GetSlot;void*;PyTypeObject*|int +PyType_GetTypeDataSize;Py_ssize_t;PyTypeObject* +PyType_IsSubtype;int;PyTypeObject*|PyTypeObject* +PyType_Modified;void;PyTypeObject* +PyType_Ready;int;PyTypeObject* +PyUnicodeDecodeError_Create;PyObject*;const char*|const char*|Py_ssize_t|Py_ssize_t|Py_ssize_t|const char* +PyUnicodeDecodeError_GetEncoding;PyObject*;PyObject* +PyUnicodeDecodeError_GetEnd;int;PyObject*|Py_ssize_t* +PyUnicodeDecodeError_GetObject;PyObject*;PyObject* +PyUnicodeDecodeError_GetReason;PyObject*;PyObject* +PyUnicodeDecodeError_GetStart;int;PyObject*|Py_ssize_t* +PyUnicodeDecodeError_SetEnd;int;PyObject*|Py_ssize_t +PyUnicodeDecodeError_SetReason;int;PyObject*|const char* +PyUnicodeDecodeError_SetStart;int;PyObject*|Py_ssize_t +PyUnicodeEncodeError_GetEncoding;PyObject*;PyObject* +PyUnicodeEncodeError_GetEnd;int;PyObject*|Py_ssize_t* +PyUnicodeEncodeError_GetObject;PyObject*;PyObject* +PyUnicodeEncodeError_GetReason;PyObject*;PyObject* +PyUnicodeEncodeError_GetStart;int;PyObject*|Py_ssize_t* +PyUnicodeEncodeError_SetEnd;int;PyObject*|Py_ssize_t +PyUnicodeEncodeError_SetReason;int;PyObject*|const char* +PyUnicodeEncodeError_SetStart;int;PyObject*|Py_ssize_t +PyUnicodeTranslateError_GetEnd;int;PyObject*|Py_ssize_t* +PyUnicodeTranslateError_GetObject;PyObject*;PyObject* +PyUnicodeTranslateError_GetReason;PyObject*;PyObject* +PyUnicodeTranslateError_GetStart;int;PyObject*|Py_ssize_t* +PyUnicodeTranslateError_SetEnd;int;PyObject*|Py_ssize_t +PyUnicodeTranslateError_SetReason;int;PyObject*|const char* +PyUnicodeTranslateError_SetStart;int;PyObject*|Py_ssize_t +PyUnicode_Append;void;PyObject**|PyObject* +PyUnicode_AppendAndDel;void;PyObject**|PyObject* +PyUnicode_AsASCIIString;PyObject*;PyObject* +PyUnicode_AsCharmapString;PyObject*;PyObject*|PyObject* +PyUnicode_AsEncodedString;PyObject*;PyObject*|const char*|const char* +PyUnicode_AsLatin1String;PyObject*;PyObject* +PyUnicode_AsRawUnicodeEscapeString;PyObject*;PyObject* +PyUnicode_AsUCS4;Py_UCS4*;PyObject*|Py_UCS4*|Py_ssize_t|int +PyUnicode_AsUCS4Copy;Py_UCS4*;PyObject* +PyUnicode_AsUTF16String;PyObject*;PyObject* +PyUnicode_AsUTF32String;PyObject*;PyObject* +PyUnicode_AsUTF8AndSize;const char*;PyObject*|Py_ssize_t* +PyUnicode_AsUTF8String;PyObject*;PyObject* +PyUnicode_AsUnicodeEscapeString;PyObject*;PyObject* +PyUnicode_AsWideChar;Py_ssize_t;PyObject*|wchar_t*|Py_ssize_t +PyUnicode_AsWideCharString;wchar_t*;PyObject*|Py_ssize_t* +PyUnicode_BuildEncodingMap;PyObject*;PyObject* +PyUnicode_Compare;int;PyObject*|PyObject* +PyUnicode_CompareWithASCIIString;int;PyObject*|const char* +PyUnicode_Concat;PyObject*;PyObject*|PyObject* +PyUnicode_Contains;int;PyObject*|PyObject* +PyUnicode_Count;Py_ssize_t;PyObject*|PyObject*|Py_ssize_t|Py_ssize_t +PyUnicode_Decode;PyObject*;const char*|Py_ssize_t|const char*|const char* +PyUnicode_DecodeASCII;PyObject*;const char*|Py_ssize_t|const char* +PyUnicode_DecodeCharmap;PyObject*;const char*|Py_ssize_t|PyObject*|const char* +PyUnicode_DecodeFSDefault;PyObject*;const char* +PyUnicode_DecodeFSDefaultAndSize;PyObject*;const char*|Py_ssize_t +PyUnicode_DecodeLatin1;PyObject*;const char*|Py_ssize_t|const char* +PyUnicode_DecodeLocale;PyObject*;const char*|const char* +PyUnicode_DecodeLocaleAndSize;PyObject*;const char*|Py_ssize_t|const char* +PyUnicode_DecodeRawUnicodeEscape;PyObject*;const char*|Py_ssize_t|const char* +PyUnicode_DecodeUTF16;PyObject*;const char*|Py_ssize_t|const char*|int* +PyUnicode_DecodeUTF16Stateful;PyObject*;const char*|Py_ssize_t|const char*|int*|Py_ssize_t* +PyUnicode_DecodeUTF32;PyObject*;const char*|Py_ssize_t|const char*|int* +PyUnicode_DecodeUTF32Stateful;PyObject*;const char*|Py_ssize_t|const char*|int*|Py_ssize_t* +PyUnicode_DecodeUTF7;PyObject*;const char*|Py_ssize_t|const char* +PyUnicode_DecodeUTF7Stateful;PyObject*;const char*|Py_ssize_t|const char*|Py_ssize_t* +PyUnicode_DecodeUTF8;PyObject*;const char*|Py_ssize_t|const char* +PyUnicode_DecodeUTF8Stateful;PyObject*;const char*|Py_ssize_t|const char*|Py_ssize_t* +PyUnicode_DecodeUnicodeEscape;PyObject*;const char*|Py_ssize_t|const char* +PyUnicode_EncodeFSDefault;PyObject*;PyObject* +PyUnicode_EncodeLocale;PyObject*;PyObject*|const char* +PyUnicode_Equal;int;PyObject*|PyObject* +PyUnicode_EqualToUTF8;int;PyObject*|const char* +PyUnicode_EqualToUTF8AndSize;int;PyObject*|const char*|Py_ssize_t +PyUnicode_FSConverter;int;PyObject*|void* +PyUnicode_FSDecoder;int;PyObject*|void* +PyUnicode_Find;Py_ssize_t;PyObject*|PyObject*|Py_ssize_t|Py_ssize_t|int +PyUnicode_FindChar;Py_ssize_t;PyObject*|Py_UCS4|Py_ssize_t|Py_ssize_t|int +PyUnicode_Format;PyObject*;PyObject*|PyObject* +PyUnicode_FromEncodedObject;PyObject*;PyObject*|const char*|const char* +PyUnicode_FromFormat;PyObject*;const char*|... +PyUnicode_FromFormatV;PyObject*;const char*|va_list +PyUnicode_FromObject;PyObject*;PyObject* +PyUnicode_FromOrdinal;PyObject*;int +PyUnicode_FromString;PyObject*;const char* +PyUnicode_FromStringAndSize;PyObject*;const char*|Py_ssize_t +PyUnicode_FromWideChar;PyObject*;const wchar_t*|Py_ssize_t +PyUnicode_GetDefaultEncoding;const char*;void +PyUnicode_GetLength;Py_ssize_t;PyObject* +PyUnicode_InternFromString;PyObject*;const char* +PyUnicode_InternInPlace;void;PyObject** +PyUnicode_IsIdentifier;int;PyObject* +PyUnicode_Join;PyObject*;PyObject*|PyObject* +PyUnicode_Partition;PyObject*;PyObject*|PyObject* +PyUnicode_RPartition;PyObject*;PyObject*|PyObject* +PyUnicode_RSplit;PyObject*;PyObject*|PyObject*|Py_ssize_t +PyUnicode_ReadChar;Py_UCS4;PyObject*|Py_ssize_t +PyUnicode_Replace;PyObject*;PyObject*|PyObject*|PyObject*|Py_ssize_t +PyUnicode_Resize;int;PyObject**|Py_ssize_t +PyUnicode_RichCompare;PyObject*;PyObject*|PyObject*|int +PyUnicode_Split;PyObject*;PyObject*|PyObject*|Py_ssize_t +PyUnicode_Splitlines;PyObject*;PyObject*|int +PyUnicode_Substring;PyObject*;PyObject*|Py_ssize_t|Py_ssize_t +PyUnicode_Tailmatch;Py_ssize_t;PyObject*|PyObject*|Py_ssize_t|Py_ssize_t|int +PyUnicode_Translate;PyObject*;PyObject*|PyObject*|const char* +PyUnicode_WriteChar;int;PyObject*|Py_ssize_t|Py_UCS4 +PyVectorcall_Call;PyObject*;PyObject*|PyObject*|PyObject* +PyVectorcall_NARGS;Py_ssize_t;size_t +PyWeakref_GetRef;int;PyObject*|PyObject** +PyWeakref_NewProxy;PyObject*;PyObject*|PyObject* +PyWeakref_NewRef;PyObject*;PyObject*|PyObject* +PyWrapper_New;PyObject*;PyObject*|PyObject* +Py_AddPendingCall;int;int (*)(void*)|void* +Py_AtExit;int;void (*)(void) +Py_BuildValue;PyObject*;const char*|... +Py_BytesMain;int;int|char** +Py_CompileString;PyObject*;const char*|const char*|int +Py_DecRef;void;PyObject* +Py_DecodeLocale;wchar_t*;const char*|size_t* +Py_EncodeLocale;char*;const wchar_t*|size_t* +Py_EndInterpreter;void;PyThreadState* +Py_EnterRecursiveCall;int;const char* +Py_Exit;void;int +Py_FatalError;void;const char* +Py_Finalize;void;void +Py_FinalizeEx;int;void +Py_GenericAlias;PyObject*;PyObject*|PyObject* +Py_GetBuildInfo;const char*;void +Py_GetCompiler;const char*;void +Py_GetConstant;PyObject*;unsigned int +Py_GetConstantBorrowed;PyObject*;unsigned int +Py_GetCopyright;const char*;void +Py_GetPlatform;const char*;void +Py_GetRecursionLimit;int;void +Py_GetVersion;const char*;void +Py_IS_TYPE;int;PyObject*|PyTypeObject* +Py_IncRef;void;PyObject* +Py_Initialize;void;void +Py_InitializeEx;void;int +Py_Is;int;PyObject*|PyObject* +Py_IsFalse;int;PyObject* +Py_IsFinalizing;int;void +Py_IsInitialized;int;void +Py_IsNone;int;PyObject* +Py_IsTrue;int;PyObject* +Py_LeaveRecursiveCall;void;void +Py_Main;int;int|wchar_t** +Py_MakePendingCalls;int;void +Py_NewInterpreter;PyThreadState*;void +Py_NewRef;PyObject*;PyObject* +Py_PACK_FULL_VERSION;uint32_t;int|int|int|int|int +Py_PACK_VERSION;uint32_t;int|int +Py_REFCNT;Py_ssize_t;PyObject* +Py_ReprEnter;int;PyObject* +Py_ReprLeave;void;PyObject* +Py_SET_SIZE;void;PyVarObject*|Py_ssize_t +Py_SIZE;Py_ssize_t;PyObject* +Py_SetProgramName;void;const wchar_t* +Py_SetPythonHome;void;const wchar_t* +Py_SetRecursionLimit;void;int +Py_TYPE;PyTypeObject*;PyObject* +Py_VaBuildValue;PyObject*;const char*|va_list +Py_XNewRef;PyObject*;PyObject* +_PyErr_BadInternalCall;void;const char*|int +_PyObject_GC_New;PyObject*;PyTypeObject* +_PyObject_GC_NewVar;PyVarObject*;PyTypeObject*|Py_ssize_t +_PyObject_GC_Resize;PyVarObject*;PyVarObject*|Py_ssize_t +_PyObject_New;PyObject*;PyTypeObject* +_PyObject_NewVar;PyVarObject*;PyTypeObject*|Py_ssize_t +_Py_Dealloc;void;PyObject* +_Py_DecRef;void;PyObject* +_Py_IncRef;void;PyObject* +_Py_SetRefcnt;void;PyObject*|Py_ssize_t diff --git a/graalpython/com.oracle.graal.python.processor/src/com/oracle/graal/python/processor/CApiBuiltinsProcessor.java b/graalpython/com.oracle.graal.python.processor/src/com/oracle/graal/python/processor/CApiBuiltinsProcessor.java index 4d6a7a094b..8e459f98dc 100644 --- a/graalpython/com.oracle.graal.python.processor/src/com/oracle/graal/python/processor/CApiBuiltinsProcessor.java +++ b/graalpython/com.oracle.graal.python.processor/src/com/oracle/graal/python/processor/CApiBuiltinsProcessor.java @@ -245,9 +245,12 @@ private static final class CApiBuiltinDesc { public final boolean canRaise; public final String call; public final String factory; + /** ABIs explicitly named by the annotation. Parent availability is computed at validation. */ + public final Set abis; public int id; - public CApiBuiltinDesc(Element origin, String name, VariableElement returnType, VariableElement[] arguments, boolean acquireGil, boolean canRaise, String call, String factory) { + public CApiBuiltinDesc(Element origin, String name, VariableElement returnType, VariableElement[] arguments, boolean acquireGil, boolean canRaise, String call, String factory, + Set abis) { this.origin = origin; this.name = name; this.returnType = returnType; @@ -256,6 +259,7 @@ public CApiBuiltinDesc(Element origin, String name, VariableElement returnType, this.canRaise = canRaise; this.call = call; this.factory = factory; + this.abis = new HashSet<>(abis); } } @@ -466,6 +470,10 @@ private T findValue(AnnotationMirror am, String key, Class type) { } } + private static Set abiNames(List abis) { + return abis.stream().map(CApiBuiltinsProcessor::name).collect(Collectors.toSet()); + } + private void addCApiBuiltins(RoundEnvironment re, List javaBuiltins, List additionalBuiltins) { for (var root : re.getRootElements()) { for (var element : root.getEnclosedElements()) { @@ -495,10 +503,11 @@ private void addCApiBuiltins(RoundEnvironment re, List javaBuil continue; } String call = name(findValue(builtin, "call", VariableElement.class)); + Set abis = abiNames(findValues(builtin, "abi", VariableElement.class)); // boolean inlined = findValue(builtin, "inlined", Boolean.class); VariableElement[] args = findValues(builtin, "args", VariableElement.class).toArray(new VariableElement[0]); if (element instanceof TypeElement te && te.getQualifiedName().toString().equals("com.oracle.graal.python.builtins.objects.cext.capi.CApiFunction.Dummy")) { - additionalBuiltins.add(new CApiBuiltinDesc(element, builtinName, ret, args, acquireGil, canRaise, call, null)); + additionalBuiltins.add(new CApiBuiltinDesc(element, builtinName, ret, args, acquireGil, canRaise, call, null, abis)); } else { if (!isValidReturnType(ret)) { processingEnv.getMessager().printError( @@ -529,7 +538,7 @@ private void addCApiBuiltins(RoundEnvironment re, List javaBuil } else { verifyStaticMethod((ExecutableElement) element, builtin); } - javaBuiltins.add(new CApiBuiltinDesc(element, name, ret, args, acquireGil, canRaise, call, genName)); + javaBuiltins.add(new CApiBuiltinDesc(element, name, ret, args, acquireGil, canRaise, call, genName, abis)); } } } @@ -1232,6 +1241,17 @@ private static Path resolvePath(Path path) { "PyUnicode_DecodeCodePageStateful", "PyUnicode_DecodeMBCSStateful", }; + private static final Map ABI_PARENTS = Map.of("ABI3T", "GRAALPY"); + + private static boolean isAvailableIn(CApiBuiltinDesc builtin, String profile) { + for (String current = profile; current != null; current = ABI_PARENTS.get(current)) { + if (builtin.abis.contains(current)) { + return true; + } + } + return false; + } + public String resolveArgDescriptor(String sig) { switch (sig) { case "struct _typeobject*": @@ -1266,7 +1286,13 @@ public String resolveTypeAlias(String sig) { * suggests the appropriate @CApiBuiltin specification. */ private void checkImports(List builtins) throws IOException { - var path = resolvePath(Path.of("com.oracle.graal.python.cext", "CAPIFunctions.txt")); + checkImports(builtins, "GRAALPY"); + checkImports(builtins, "ABI3T"); + } + + private void checkImports(List builtins, String profile) throws IOException { + String filename = profile.equals("GRAALPY") ? "CAPIFunctions.txt" : "CAPIFunctions-" + profile + ".txt"; + var path = resolvePath(Path.of("com.oracle.graal.python.cext", filename)); if (path == null) { return; } @@ -1274,7 +1300,7 @@ private void checkImports(List builtins) throws IOException { TreeSet newBuiltins = new TreeSet<>(); TreeSet names = new TreeSet<>(); - builtins.forEach(n -> names.add(n.name)); + builtins.stream().filter(n -> n.abis.contains(profile)).forEach(n -> names.add(n.name)); for (String line : lines) { String[] s = line.split(";"); @@ -1285,23 +1311,25 @@ private void checkImports(List builtins) throws IOException { String[] argSplit = s[2].isBlank() || "void".equals(s[2]) ? new String[0] : s[2].trim().split("\\|"); String[] args = Arrays.stream(argSplit).map(this::resolveArgDescriptor).toArray(String[]::new); - Optional existing = findBuiltin(builtins, name); + Optional existing = builtins.stream().filter(n -> n.name.equals(name) && isAvailableIn(n, profile)).findFirst(); if (existing.isPresent()) { compareFunction(name, existing.get().returnType, retSig, existing.get().arguments, argSplit); } else { String argString = Arrays.stream(args).map(t -> String.valueOf(t)).collect(Collectors.joining(", ")); - newBuiltins.add(" @CApiBuiltin(name = \"" + name + "\", ret = " + ret + ", args = {" + argString + "}, call = NotImplemented)"); + String abi = profile.equals("GRAALPY") ? "" : ", abi = ABI." + profile; + newBuiltins.add(" @CApiBuiltin(name = \"" + name + "\", ret = " + ret + ", args = {" + argString + "}, call = NotImplemented" + abi + ")"); } } if (!newBuiltins.isEmpty()) { - processingEnv.getMessager().printError("missing builtins (defined in CPython, but not in GraalPy):"); + processingEnv.getMessager().printError("missing " + profile + " builtins (defined in CPython, but not in GraalPy):"); newBuiltins.stream().forEach(processingEnv.getMessager()::printError); } names.removeIf(n -> n.startsWith("GraalPy")); names.removeAll(Arrays.asList(ADDITIONAL)); if (!names.isEmpty()) { - processingEnv.getMessager().printError("extra builtins (defined in GraalPy, but not in CPython - some of these are necessary for internal modules like 'math'):"); + processingEnv.getMessager().printError("extra explicitly " + profile + + " builtins (defined in GraalPy, but not in CPython - some of these are necessary for internal modules like 'math'):"); processingEnv.getMessager().printError(" " + names.stream().collect(Collectors.joining(", "))); } } @@ -2233,6 +2261,7 @@ public boolean process(Set annotations, RoundEnvironment Optional existing1 = findBuiltin(allBuiltins, entry.name); if (existing1.isPresent()) { compareFunction(entry.name, entry.returnType, existing1.get().returnType, entry.arguments, existing1.get().arguments); + existing1.get().abis.addAll(entry.abis); } else { allBuiltins.add(entry); } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextBuiltins.java index ac08c8faf6..971153959a 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextBuiltins.java @@ -764,8 +764,24 @@ public enum CApiCallPath { } public enum ABI { - GRAALPY, - ABI3T + GRAALPY(null), + ABI3T(GRAALPY); + + private final ABI parent; + + ABI(ABI parent) { + this.parent = parent; + } + + /** Whether this profile includes implementations declared for {@code candidate}. */ + public boolean inherits(ABI candidate) { + for (ABI current = this; current != null; current = current.parent) { + if (current == candidate) { + return true; + } + } + return false; + } } @Target({ElementType.TYPE, ElementType.METHOD}) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextModuleBuiltins.java index c7c22bde27..f9746f6800 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextModuleBuiltins.java @@ -77,6 +77,7 @@ import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBinaryBuiltinNode; +import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.ABI; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBuiltin; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiTernaryBuiltinNode; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiUnaryBuiltinNode; @@ -315,7 +316,7 @@ private static void addMethodsToObject(long functions, Object module, Object mod } } - @CApiBuiltin(ret = PyObjectTransfer, args = {CONST_PY_SLOT_PTR, PyObject}, call = Direct) + @CApiBuiltin(ret = PyObjectTransfer, args = {CONST_PY_SLOT_PTR, PyObject}, call = Direct, abi = ABI.ABI3T) public static long PyModule_FromSlotsAndSpec(long slots, long specPtr) { if (slots == NULLPTR) { throw PRaiseNode.raiseStatic(null, SystemError, ErrorMessages.PYMODULE_FROMSLOTS_NULL_SLOTS); @@ -327,7 +328,7 @@ public static long PyModule_FromSlotsAndSpec(long slots, long specPtr) { return PythonToNativeInternalNode.executeNewRefUncached(module); } - @CApiBuiltin(ret = Int, args = {PyObject}, call = Direct) + @CApiBuiltin(ret = Int, args = {PyObject}, call = Direct, abi = ABI.ABI3T) public static int PyModule_Exec(long modulePtr) { Object object = NativeToPythonInternalNode.executeUncached(modulePtr, false); if (object instanceof PythonModule module) { @@ -336,7 +337,7 @@ public static int PyModule_Exec(long modulePtr) { throw PRaiseNode.raiseStatic(null, TypeError, ErrorMessages.EXPECTED_MODULE_GOT_T, object); } - @CApiBuiltin(ret = Int, args = {PyObject, PY_SSIZE_T_PTR}, call = Direct) + @CApiBuiltin(ret = Int, args = {PyObject, PY_SSIZE_T_PTR}, call = Direct, abi = ABI.ABI3T) public static int PyModule_GetStateSize(long modulePtr, long result) { Object object = NativeToPythonInternalNode.executeUncached(modulePtr, false); if (object instanceof PythonModule module) { @@ -347,7 +348,7 @@ public static int PyModule_GetStateSize(long modulePtr, long result) { throw PRaiseNode.raiseStatic(null, TypeError, ErrorMessages.EXPECTED_MODULE_GOT_T, object); } - @CApiBuiltin(ret = Int, args = {PyObject, VOID_PTR_LIST}, call = Direct) + @CApiBuiltin(ret = Int, args = {PyObject, VOID_PTR_LIST}, call = Direct, abi = ABI.ABI3T) public static int PyModule_GetToken(long modulePtr, long result) { Object object = NativeToPythonInternalNode.executeUncached(modulePtr, false); if (object instanceof PythonModule module) { @@ -358,7 +359,7 @@ public static int PyModule_GetToken(long modulePtr, long result) { throw PRaiseNode.raiseStatic(null, TypeError, ErrorMessages.EXPECTED_MODULE_GOT_T, object); } - @CApiBuiltin(ret = Int, args = {PyObject, VOID_PTR_LIST}, call = Direct) + @CApiBuiltin(ret = Int, args = {PyObject, VOID_PTR_LIST}, call = Direct, abi = ABI.ABI3T) public static int PyModule_GetToken_DuringGC(long modulePtr, long result) { Object object = NativeToPythonInternalNode.executeUncached(modulePtr, false); if (object instanceof PythonModule module) { @@ -369,7 +370,7 @@ public static int PyModule_GetToken_DuringGC(long modulePtr, long result) { return -1; } - @CApiBuiltin(ret = Pointer, args = {PyObject}, call = Direct) + @CApiBuiltin(ret = Pointer, args = {PyObject}, call = Direct, abi = ABI.ABI3T) public static long PyModule_GetState_DuringGC(long modulePtr) { Object object = NativeToPythonInternalNode.executeUncached(modulePtr, false); if (object instanceof PythonModule module) { @@ -378,7 +379,7 @@ public static long PyModule_GetState_DuringGC(long modulePtr) { return NULLPTR; } - @CApiBuiltin(ret = Int, args = {PY_ABI_INFO_PTR, ConstCharPtr}, call = Direct) + @CApiBuiltin(ret = Int, args = {PY_ABI_INFO_PTR, ConstCharPtr}, call = Direct, abi = ABI.ABI3T) public static int PyABIInfo_Check(long info, long moduleName) { TruffleString name = moduleName == NULLPTR ? T_EMPTY_STRING : (TruffleString) CharPtrToPythonNode.executeUncached(moduleName); return CExtNodes.checkAbiInfo(null, info, name); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiFunction.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiFunction.java index c9a41c4779..b476b7c003 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiFunction.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiFunction.java @@ -41,7 +41,6 @@ package com.oracle.graal.python.builtins.objects.cext.capi; import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.CImpl; -import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Direct; import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Ignored; import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.NotImplemented; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CHAR; @@ -52,7 +51,6 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_PYPRECONFIG_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_PY_BUFFER; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_PY_BUFFER_PTR; -import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_PY_SLOT_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_PY_SSIZE_T_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_PY_UCS4; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_UINT8_T_PTR; @@ -67,7 +65,9 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.FILE_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.INITTAB; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.INT32_T; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.INT32_T_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.INT64_T; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.INT64_T_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.INT_LIST; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Int; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.LONG_LONG; @@ -82,7 +82,6 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PYSTATUS; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PYWIDESTRINGLIST_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_AUDITHOOKFUNCTION; -import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_ABI_INFO_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_BUFFER_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_COMPILER_FLAGS; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_COMPLEX; @@ -92,7 +91,11 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_GEN_OBJECT; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_GIL_STATE_STATE; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_IDENTIFIER; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_INTERPRETER_GUARD_PTR; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_INTERPRETER_VIEW_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_LOCK_STATUS; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_LONG_EXPORT_PTR; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_LONG_WRITER_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_MONITORING_STATE_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_MUTEX_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_OPENCODEHOOKFUNCTION; @@ -100,10 +103,13 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_REF_TRACER; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_SSIZE_T_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_STRUCT_SEQUENCE_DESC; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_THREAD_STATE_TOKEN_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_THREAD_TYPE_LOCK; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_TRACEFUNC; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_TSS_T_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_TYPE_SPEC; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_SLOT_PTR; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_TYPE_OBJECT_PTR_LIST; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_UCS4; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PY_UCS4_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Pointer; @@ -143,10 +149,13 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyVarObject; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Py_hash_t; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Py_ssize_t; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_PY_LONG_LAYOUT_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.SIZE_T; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.SIZE_T_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.UINT64_T; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.UINT64_T_PTR; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.UINT32_T; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.UINT32_T_PTR; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.UINTPTR_T; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.UNSIGNED_INT; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.UNSIGNED_LONG; @@ -178,6 +187,7 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.visitproc; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins; +import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.ABI; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBuiltin; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor; @@ -188,7 +198,6 @@ */ public final class CApiFunction { - @CApiBuiltin(name = "PyABIInfo_Check", ret = Int, args = {PY_ABI_INFO_PTR, ConstCharPtr}, call = Direct) @CApiBuiltin(name = "PyGILState_Check", ret = Int, args = {}, acquireGil = false, call = CImpl) @CApiBuiltin(name = "PyArg_Parse", ret = Int, args = {PyObject, ConstCharPtrAsTruffleString, VARARGS}, call = CImpl) @CApiBuiltin(name = "PyArg_ParseTuple", ret = Int, args = {PyObject, ConstCharPtrAsTruffleString, VARARGS}, call = CImpl) @@ -363,17 +372,11 @@ public final class CApiFunction { @CApiBuiltin(name = "PyModule_Create2", ret = PyObject, args = {PYMODULEDEF_PTR, Int}, call = CImpl) @CApiBuiltin(name = "PyModule_FromDefAndSpec2", ret = PyObject, args = {PyModuleDef, PyObject, Int}, call = CImpl) @CApiBuiltin(name = "PyModule_ExecDef", ret = Int, args = {PyObject, PyModuleDef}, call = CImpl) - @CApiBuiltin(name = "PyModule_Exec", ret = Int, args = {PyObject}, call = Direct) - @CApiBuiltin(name = "PyModule_FromSlotsAndSpec", ret = PyObjectTransfer, args = {CONST_PY_SLOT_PTR, PyObject}, call = Direct) @CApiBuiltin(name = "PyModule_GetDef", ret = PyModuleDef, args = {PyObject}, call = CImpl) @CApiBuiltin(name = "PyModule_GetDict", ret = PyObject, args = {PyObject}, call = CImpl) @CApiBuiltin(name = "PyModule_GetFilename", ret = ConstCharPtrAsTruffleString, args = {PyObject}, call = CImpl) @CApiBuiltin(name = "PyModule_GetName", ret = ConstCharPtrAsTruffleString, args = {PyObject}, call = CImpl) @CApiBuiltin(name = "PyModule_GetState", ret = Pointer, args = {PyObject}, call = CImpl) - @CApiBuiltin(name = "PyModule_GetStateSize", ret = Int, args = {PyObject, PY_SSIZE_T_PTR}, call = Direct) - @CApiBuiltin(name = "PyModule_GetState_DuringGC", ret = Pointer, args = {PyObject}, call = Direct) - @CApiBuiltin(name = "PyModule_GetToken", ret = Int, args = {PyObject, VOID_PTR_LIST}, call = Direct) - @CApiBuiltin(name = "PyModule_GetToken_DuringGC", ret = Int, args = {PyObject, VOID_PTR_LIST}, call = Direct) @CApiBuiltin(name = "PyNumber_Absolute", ret = PyObject, args = {PyObject}, call = CImpl) @CApiBuiltin(name = "PyNumber_Add", ret = PyObject, args = {PyObject, PyObject}, call = CImpl) @CApiBuiltin(name = "PyNumber_And", ret = PyObject, args = {PyObject, PyObject}, call = CImpl) @@ -1063,6 +1066,55 @@ public final class CApiFunction { @CApiBuiltin(name = "_PyLong_FromDigits", ret = PyLongObject, args = {PrimitiveResult32, Py_ssize_t, DIGIT_PTR}, call = NotImplemented) @CApiBuiltin(name = "_Py_NewReferenceNoTotal", ret = Void, args = {PyObjectRawPointer}, call = CImpl) + // ABI3T-only functions that are not supported yet. Use Ignored to validate their signatures + // without generating C stubs against the 3.13 headers, which lack ABI3T declarations. + @CApiBuiltin(name = "PyInterpreterGuard_Close", ret = Void, args = {PY_INTERPRETER_GUARD_PTR}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyInterpreterGuard_FromCurrent", ret = PY_INTERPRETER_GUARD_PTR, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyInterpreterGuard_FromView", ret = PY_INTERPRETER_GUARD_PTR, args = {PY_INTERPRETER_VIEW_PTR}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyInterpreterView_Close", ret = Void, args = {PY_INTERPRETER_VIEW_PTR}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyInterpreterView_FromCurrent", ret = PY_INTERPRETER_VIEW_PTR, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyInterpreterView_FromMain", ret = PY_INTERPRETER_VIEW_PTR, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyIter_NextItem", ret = PrimitiveResult32, args = {PyObjectReturn, PyObjectPtr}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyLongWriter_Create", ret = PY_LONG_WRITER_PTR, args = {PrimitiveResult32, Py_ssize_t, VOID_PTR_LIST}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyLongWriter_Discard", ret = Void, args = {PY_LONG_WRITER_PTR}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyLongWriter_Finish", ret = PyObjectReturn, args = {PY_LONG_WRITER_PTR}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyLong_AsInt32", ret = PrimitiveResult32, args = {PyObjectReturn, INT32_T_PTR}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyLong_AsInt64", ret = PrimitiveResult32, args = {PyObjectReturn, INT64_T_PTR}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyLong_AsUInt32", ret = PrimitiveResult32, args = {PyObjectReturn, UINT32_T_PTR}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyLong_AsUInt64", ret = PrimitiveResult32, args = {PyObjectReturn, UINT64_T_PTR}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyLong_Export", ret = PrimitiveResult32, args = {PyObjectReturn, PY_LONG_EXPORT_PTR}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyLong_FreeExport", ret = Void, args = {PY_LONG_EXPORT_PTR}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyLong_FromInt32", ret = PyObjectReturn, args = {INT32_T}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyLong_FromInt64", ret = PyObjectReturn, args = {INT64_T}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyLong_FromUInt32", ret = PyObjectReturn, args = {UINT32_T}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyLong_FromUInt64", ret = PyObjectReturn, args = {UINT64_T}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyLong_GetNativeLayout", ret = CONST_PY_LONG_LAYOUT_PTR, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyObject_GetTypeData_DuringGC", ret = Pointer, args = {PyObjectReturn, PyTypeObjectRawPointer}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PySys_GetAttr", ret = PyObjectReturn, args = {PyObjectReturn}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PySys_GetAttrString", ret = PyObjectReturn, args = {ConstCharPtr}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PySys_GetOptionalAttr", ret = PrimitiveResult32, args = {PyObjectReturn, PyObjectPtr}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PySys_GetOptionalAttrString", ret = PrimitiveResult32, args = {ConstCharPtr, PyObjectPtr}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyThreadState_Ensure", ret = PY_THREAD_STATE_TOKEN_PTR, args = {PY_INTERPRETER_GUARD_PTR}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyThreadState_EnsureFromView", ret = PY_THREAD_STATE_TOKEN_PTR, args = {PY_INTERPRETER_VIEW_PTR}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyThreadState_Release", ret = Void, args = {PY_THREAD_STATE_TOKEN_PTR}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyType_Freeze", ret = PrimitiveResult32, args = {PyTypeObjectRawPointer}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyType_FromSlots", ret = PyObjectReturn, args = {PY_SLOT_PTR}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyType_GetBaseByToken", ret = PrimitiveResult32, args = {PyTypeObjectRawPointer, Pointer, PY_TYPE_OBJECT_PTR_LIST}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyType_GetBaseByToken_DuringGC", ret = PrimitiveResult32, args = {PyTypeObjectRawPointer, Pointer, PY_TYPE_OBJECT_PTR_LIST}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyType_GetModuleByToken", ret = PyObjectReturn, args = {PyTypeObjectRawPointer, CONST_VOID_PTR}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyType_GetModuleByToken_DuringGC", ret = PyObjectReturn, args = {PyTypeObjectRawPointer, CONST_VOID_PTR}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyType_GetModuleState_DuringGC", ret = Pointer, args = {PyTypeObjectRawPointer}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyType_GetModule_DuringGC", ret = PyObjectReturn, args = {PyTypeObjectRawPointer}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "PyUnicode_Equal", ret = PrimitiveResult32, args = {PyObjectReturn, PyObjectReturn}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "Py_IS_TYPE", ret = PrimitiveResult32, args = {PyObjectReturn, PyTypeObjectRawPointer}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "Py_PACK_FULL_VERSION", ret = UINT32_T, args = {PrimitiveResult32, PrimitiveResult32, PrimitiveResult32, PrimitiveResult32, + PrimitiveResult32}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "Py_PACK_VERSION", ret = UINT32_T, args = {PrimitiveResult32, PrimitiveResult32}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "Py_REFCNT", ret = Py_ssize_t, args = {PyObjectReturn}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "Py_SET_SIZE", ret = Void, args = {PyVarObject, Py_ssize_t}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "Py_SIZE", ret = Py_ssize_t, args = {PyObjectReturn}, call = Ignored, abi = ABI.ABI3T) + @CApiBuiltin(name = "Py_TYPE", ret = PyTypeObjectRawPointer, args = {PyObjectReturn}, call = Ignored, abi = ABI.ABI3T) + // Windows only functions @CApiBuiltin(name = "PyUnicode_DecodeMBCS", ret = PyObject, args = {ConstCharPtr, Py_ssize_t, ConstCharPtr}, call = NotImplemented) @CApiBuiltin(name = "PyUnicode_DecodeMBCSStateful", ret = PyObject, args = {ConstCharPtr, Py_ssize_t, ConstCharPtr, PY_SSIZE_T_PTR}, call = NotImplemented) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/ArgDescriptor.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/ArgDescriptor.java index 497e9764e6..ff8f5cb5ab 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/ArgDescriptor.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/ArgDescriptor.java @@ -192,6 +192,7 @@ public enum ArgDescriptor { INITTAB("struct _inittab*"), UINT16_T(ArgBehavior.UInt16, "uint16_t"), INT32_T(ArgBehavior.Int32, "int32_t"), + INT32_T_PTR(ArgBehavior.Pointer, "int32_t*"), INT_LIST("int*"), INT8_T_PTR(ArgBehavior.Pointer, "int8_t*"), INT64_T(ArgBehavior.Int64, "int64_t"), @@ -233,6 +234,8 @@ public enum ArgDescriptor { PyGetSetDef(ArgBehavior.Pointer, "PyGetSetDef*"), PY_GIL_STATE_STATE(ArgBehavior.Int32, "PyGILState_STATE"), PY_ABI_INFO_PTR(ArgBehavior.Pointer, "PyABIInfo*"), + PY_INTERPRETER_GUARD_PTR(ArgBehavior.Pointer, "PyInterpreterGuard*"), + PY_INTERPRETER_VIEW_PTR(ArgBehavior.Pointer, "PyInterpreterView*"), PY_HASH_T_PTR(ArgBehavior.Pointer, "Py_hash_t*"), PY_IDENTIFIER("_Py_Identifier*"), PyInterpreterState(ArgBehavior.Pointer, "PyInterpreterState*"), @@ -241,6 +244,9 @@ public enum ArgDescriptor { PyLongObject(ArgBehavior.PyObject, "PyLongObject*"), ConstPyLongObject(ArgBehavior.PyObject, "const PyLongObject*"), PyLongObjectTransfer(ArgBehavior.PyObject, "PyLongObject*", true, false), + PY_LONG_EXPORT_PTR(ArgBehavior.Pointer, "PyLongExport*"), + PY_LONG_WRITER_PTR(ArgBehavior.Pointer, "PyLongWriter*"), + CONST_PY_LONG_LAYOUT_PTR(ArgBehavior.Pointer, "const PyLongLayout*"), PyMemberDef(ArgBehavior.Pointer, "PyMemberDef*"), PyModuleObject(ArgBehavior.PyObject, "PyModuleObject*"), PyModuleObjectTransfer(ArgBehavior.PyObject, "PyModuleObject*", true, false), @@ -266,6 +272,7 @@ public enum ArgDescriptor { PY_STRUCT_SEQUENCE_DESC("PyStructSequence_Desc*"), PyThreadState(ArgBehavior.Pointer, "PyThreadState*"), PyThreadStatePtr(ArgBehavior.Pointer, "PyThreadState**"), + PY_THREAD_STATE_TOKEN_PTR(ArgBehavior.Pointer, "PyThreadStateToken*"), PY_REF_TRACER(ArgBehavior.Pointer, "PyRefTracer"), PY_THREAD_TYPE_LOCK(ArgBehavior.Int64, "PyThread_type_lock"), PY_THREAD_TYPE_LOCK_PTR(ArgBehavior.Pointer, "PyThread_type_lock*"), @@ -273,6 +280,8 @@ public enum ArgDescriptor { PY_TRACEFUNC("Py_tracefunc"), PY_TSS_T_PTR("Py_tss_t*"), PY_TYPE_SPEC("PyType_Spec*"), + PY_SLOT_PTR(ArgBehavior.Pointer, "struct PySlot*"), + PY_TYPE_OBJECT_PTR_LIST(ArgBehavior.Pointer, "PyTypeObject**"), PY_UCS4(ArgBehavior.Int32, "Py_UCS4"), PY_UCS4_PTR("Py_UCS4*"), PY_UNICODE("Py_UNICODE"), @@ -312,6 +321,8 @@ public enum ArgDescriptor { TIMEVAL_PTR("struct timeval*"), TM_PTR("struct tm*"), UINTPTR_T(ArgBehavior.UInt64, "uintptr_t"), + UINT32_T(ArgBehavior.UInt32, "uint32_t"), + UINT32_T_PTR(ArgBehavior.Pointer, "uint32_t*"), UINT64_T(ArgBehavior.UInt64, "uint64_t"), UINT64_T_PTR(ArgBehavior.Pointer, "uint64_t*"), UNSIGNED_CHAR_PTR(ArgBehavior.Pointer, "unsigned char*"), diff --git a/mx.graalpython/mx_graalpython.py b/mx.graalpython/mx_graalpython.py index e5983c1336..418295b788 100644 --- a/mx.graalpython/mx_graalpython.py +++ b/mx.graalpython/mx_graalpython.py @@ -52,6 +52,10 @@ import mx_urlrewrites import tempfile +try: + import tomllib +except ModuleNotFoundError: + from pip._vendor import tomli as tomllib from argparse import ArgumentParser from dataclasses import dataclass @@ -3372,7 +3376,7 @@ def abi_check(_args): ], nonZeroIsFatal=True) -def _read_abi3t_symbols(path): +def _read_stable_abi_symbols(path): symbols = {} with open(path, encoding='utf-8') as file: for line_number, line in enumerate(file, 1): @@ -3381,13 +3385,13 @@ def _read_abi3t_symbols(path): continue fields = line.split() if len(fields) != 2 or fields[0] not in ('FUNC', 'OBJECT'): - mx.abort(f"Invalid ABI3T manifest entry at {path}:{line_number}: {line}") + mx.abort(f"Invalid stable ABI manifest entry at {path}:{line_number}: {line}") kind, name = fields if name in symbols: - mx.abort(f"Duplicate ABI3T symbol at {path}:{line_number}: {name}") + mx.abort(f"Duplicate stable ABI symbol at {path}:{line_number}: {name}") symbols[name] = kind if list(symbols) != sorted(symbols): - mx.abort(f"ABI3T symbols are not sorted by name: {path}") + mx.abort(f"Stable ABI symbols are not sorted by name: {path}") return symbols @@ -3410,12 +3414,22 @@ def _read_elf_dynamic_exports(shared_library): return exports -def abi3t_check(raw_args): - parser = ArgumentParser(prog='mx abi3t-check') +def stable_abi_check(raw_args, default_profile=None): + parser = ArgumentParser(prog='mx stable-abi-check') + parser.add_argument('--profile', default=default_profile, help='ABI profile from capi-abis.toml') parser.add_argument('--library', help='libpython-native.so to inspect; builds a JVM standalone by default') parser.add_argument('--update', action='store_true', help='update the checked-in missing-symbol report') args = parser.parse_args(raw_args) + if not args.profile: + mx.abort("Specify --profile") + config_path = os.path.join(SUITE.dir, 'capi-abis.toml') + with open(config_path, 'rb') as file: + profiles = tomllib.load(file)['profiles'] + profile = profiles.get(args.profile) + if profile is None or profile.get('mode') != 'stable': + mx.abort(f"{args.profile} is not a stable ABI profile in {config_path}") + if not shutil.which('readelf'): mx.abort("Required tool 'readelf' was not found on PATH") @@ -3432,12 +3446,12 @@ def abi3t_check(raw_args): if not os.path.exists(shared_library): mx.abort(f"Could not find shared library to check: {shared_library}") - manifest = os.path.join(SUITE.dir, 'abi', 'abi3t-linux.txt') - report = os.path.join(SUITE.dir, 'abi', 'abi3t-missing-linux.txt') + manifest = os.path.join(SUITE.dir, profile['export_manifest']) + report = os.path.join(SUITE.dir, profile['missing_symbols']) if not os.path.exists(manifest): - mx.abort(f"Could not find ABI3T export manifest: {manifest}") + mx.abort(f"Could not find {args.profile} export manifest: {manifest}") - expected = _read_abi3t_symbols(manifest) + expected = _read_stable_abi_symbols(manifest) exports = _read_elf_dynamic_exports(shared_library) gaps = {} wrong_kinds = [] @@ -3452,22 +3466,27 @@ def abi3t_check(raw_args): if args.update: with open(report, 'w', encoding='utf-8') as file: file.write(generated_report) - mx.log(f"Updated ABI3T missing-symbol report: {report}") + mx.log(f"Updated {args.profile} missing-symbol report: {report}") else: if not os.path.exists(report): - mx.abort(f"Could not find ABI3T missing-symbol report: {report}; run mx abi3t-check --update") + mx.abort(f"Could not find {args.profile} missing-symbol report: {report}; run mx stable-abi-check --profile {args.profile} --update") with open(report, encoding='utf-8') as file: checked_in_report = file.read() if checked_in_report != generated_report: mx.abort( - f"ABI3T missing-symbol report is out of date: {report}\n" - "Run mx abi3t-check --update and commit the result." + f"{args.profile} missing-symbol report is out of date: {report}\n" + f"Run mx stable-abi-check --profile {args.profile} --update and commit the result." ) for index, (name, expected_kind, actual_kinds) in enumerate(wrong_kinds, 1): mx.warn(f"{index}. {name}: expected {expected_kind}, found {', '.join(actual_kinds)}") satisfied = len(expected) - len(gaps) - mx.log(f"ABI3T exports satisfied: {satisfied}/{len(expected)}; remaining gaps: {len(gaps)}") + mx.log(f"{args.profile} exports satisfied: {satisfied}/{len(expected)}; remaining gaps: {len(gaps)}") + + +def abi3t_check(raw_args): + """Compatibility alias for the profile-driven stable ABI export check.""" + stable_abi_check(raw_args, default_profile='ABI3T') class GraalpythonBuildTask(mx.ProjectBuildTask): @@ -3958,6 +3977,7 @@ def update_github_unittest_tags(args): 'python-src-import': [mx_graalpython_import.import_python_sources, ''], 'python-coverage': [python_coverage, ''], 'abi-check': [abi_check, ''], + 'stable-abi-check': [stable_abi_check, '--profile PROFILE [--library LIBPYTHON_NATIVE_SO] [--update]'], 'abi3t-check': [abi3t_check, '[--library LIBPYTHON_NATIVE_SO] [--update]'], 'punittest': [punittest, ''], 'graalpytest': [graalpytest, '[-h] [--python PYTHON] [TESTS]'], diff --git a/scripts/csignature.py b/scripts/csignature.py index 44aa1a8de0..3c202f038f 100644 --- a/scripts/csignature.py +++ b/scripts/csignature.py @@ -1,4 +1,5 @@ -# Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. +#!/usr/bin/env python3 +# Copyright (c) 2018, 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 @@ -37,33 +38,73 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -# This script parses gcc output on the embedded source and generates a list of -# C API functions that can be stored in CAPIFunctions.txt to automatically -# check the consistency of the C API implementation in GraalPy. -# -# Steps to update CAPI functions list: -# 1. Install GNU Binutils (2.43.1). -# 2. Build the desired version of CPython. -# 3. Create and activate a venv using the CPython binary you built above. -# 4. do `pip install pycparser pycparser-fake-libc` -# 5. Finally: -# Just run it in this folder e.g python csignature.py > ../graalpython/com.oracle.graal.python.cext/CAPIFunctions.txt -# -# Make sure to use the exact same CPython version as we are targeting +"""Generate C API signature inventories and stable-ABI export manifests. + +ABI definitions live in a TOML configuration file. Local CPython builds are +supplied on the command line because they are intentionally not checked in. +""" + +from __future__ import annotations +import argparse +from dataclasses import dataclass +import json import os +from pathlib import Path import re import subprocess -import sysconfig import tempfile -import pycparser_fake_libc -from pycparser import c_ast, parse_file, c_generator +try: + import tomllib +except ModuleNotFoundError: + from pip._vendor import tomli as tomllib # pylint: disable=no-name-in-module + + +HELP = """\ +Pass the ABI profile configuration with --config. Each selected profile needs +a local CPython source-tree build mapped in this form: + + PROFILE=CPYTHON_REPOSITORY + +CPYTHON_REPOSITORY is a configured and built CPython Git checkout. The script +uses its python executable and queries it for the version, include directories, +and library location. The checkout must contain the source_ref configured for +the profile. A full profile also requires a shared libpython; a stable profile +reads symbol membership from Misc/stable_abi.toml at its configured source_ref. + +The script requires pycparser and pycparser-fake-libc. One way to install them +without modifying the current environment is: + + python3 -m venv /tmp/graalpy-csignature + /tmp/graalpy-csignature/bin/pip install pycparser pycparser-fake-libc + +Regenerate the GraalPy 3.13 and ABI3T inventories and configured manifests: -source = """\ + /tmp/graalpy-csignature/bin/python scripts/csignature.py \\ + --config capi-abis.toml \\ + --abi GRAALPY=/path/to/cpython-3.13 \\ + --abi ABI3T=/path/to/cpython-3.15 + +Pass only one --abi to regenerate just that profile. Pass --check to verify +that every selected output is current without modifying files: + + /tmp/graalpy-csignature/bin/python scripts/csignature.py \\ + --config capi-abis.toml \\ + --abi ABI3T=/path/to/cpython-3.15 \\ + --check + +The script adds each CPython build directory to LD_LIBRARY_PATH while querying +its executable, so in-tree shared libpython builds do not need manual loader +configuration. + +Output paths, preprocessing definitions, CPython versions, pinned source refs, +and platform exclusions are all defined by the selected TOML file. Relative +output paths are resolved from the directory containing that file. +""" +HEADER_SOURCE = """\ #define __attribute__(x) #define _POSIX_THREADS - #include #include #include @@ -71,48 +112,191 @@ #include """ -include_path = sysconfig.get_config_var("INCLUDEPY") -with tempfile.NamedTemporaryFile('w') as f: - f.write(source) - f.flush() - cpp_args = ['-I', pycparser_fake_libc.directory, '-I', include_path] - ast = parse_file(f.name, use_cpp=True, cpp_args=cpp_args) -lib_path = os.path.join(sysconfig.get_config_var('LIBDIR'), sysconfig.get_config_var('LDLIBRARY')) -out = subprocess.check_output(['nm', '--defined-only', '--dynamic', '--format=just-symbols', lib_path], text=True) -exported_symbols = out.rstrip().splitlines() +@dataclass(frozen=True) +class Profile: + name: str + parent: str | None + python_version: str + source_ref: str + mode: str + defines: tuple[str, ...] + inventory: Path + manifest: Path | None + disabled_features: frozenset[str] + + +@dataclass(frozen=True) +class LocalCPython: + repository: Path + includes: tuple[Path, ...] + library: Path | None + + +def load_profiles(config: Path) -> dict[str, Profile]: + config = config.resolve() + values = tomllib.loads(config.read_text(encoding="utf-8"))["profiles"] + root = config.parent + profiles = {name: Profile(name, value.get("parent"), value["python_version"], value["source_ref"], value["mode"], + tuple(value.get("defines", [])), root / value["inventory"], + root / value["export_manifest"] if "export_manifest" in value else None, + frozenset(value.get("disabled_features", []))) + for name, value in values.items()} + for profile in profiles.values(): + if profile.mode not in ("full", "stable"): + raise ValueError(f"{profile.name}: unknown mode {profile.mode!r}") + if profile.parent and profile.parent not in profiles: + raise ValueError(f"{profile.name}: unknown parent {profile.parent!r}") + return profiles + +def parse_mapping(value: str) -> tuple[str, Path]: + try: + name, repository = value.split("=", 1) + except ValueError as exc: + raise argparse.ArgumentTypeError("expected PROFILE=CPYTHON_REPOSITORY") from exc + return name, Path(repository).resolve() -def cleanup(str): - for i in range(4): - str = re.sub(" \\*", "*", str) - str = re.sub("\\* ", "*", str) - return str +def local_cpython(repository: Path, profile: Profile) -> LocalCPython: + executable = repository / "python" + if not executable.is_file(): + raise ValueError(f"{profile.name}: CPython checkout is not built (missing {executable})") + code = "import json,sys,sysconfig;print(json.dumps({'version':f'{sys.version_info.major}.{sys.version_info.minor}','include':sysconfig.get_path('include'),'confinclude':sysconfig.get_config_var('CONFINCLUDEPY'),'srcdir':sysconfig.get_config_var('srcdir'),'libdir':sysconfig.get_config_var('LIBDIR'),'library':sysconfig.get_config_var('LDLIBRARY')}))" + env = os.environ.copy() + env["LD_LIBRARY_PATH"] = os.pathsep.join(filter(None, (str(repository), env.get("LD_LIBRARY_PATH")))) + data = json.loads(subprocess.check_output([str(executable), "-c", code], text=True, env=env)) + if data["version"] != profile.python_version: + raise ValueError(f"{profile.name}: expected CPython {profile.python_version}, got {data['version']} from {executable}") + library = Path(data["libdir"] or "") / (data["library"] or "") + # A CPython build run from its source tree can retain its installation + # prefix in sysconfig even though the freshly built library is beside it. + if not library.is_file() and data["library"]: + library = executable.parent / data["library"] + if not data["include"]: + raise ValueError(f"{profile.name}: CPython build lacks headers: {executable}") + if profile.mode == "full" and not library.is_file(): + raise ValueError(f"{profile.name}: CPython build lacks the shared library required for full-export generation: {executable}") + includes = tuple(Path(path) for path in (data["include"], data.get("confinclude"), data.get("srcdir")) if path) + return LocalCPython(repository, includes, library if library.is_file() else None) -class FuncDeclVisitor(c_ast.NodeVisitor): - def __init__(self): - self.gen = c_generator.CGenerator() - self.results = [] - def visit_Decl(self, node): - if isinstance(node.type, c_ast.FuncDecl): - if node.name not in exported_symbols: +def git_show(repository: Path, ref: str, filename: str) -> str: + return subprocess.check_output(["git", "-C", str(repository), "show", f"{ref}:{filename}"], text=True) + + +def stable_abi(local: LocalCPython, profile: Profile) -> dict: + return tomllib.loads(git_show(local.repository, profile.source_ref, "Misc/stable_abi.toml")) + + +def enabled(properties: dict, profile: Profile) -> bool: + return properties.get("ifdef") not in profile.disabled_features + + +def cleanup(value: str) -> str: + for _ in range(4): + value = re.sub(" \\*", "*", value) + value = re.sub("\\* ", "*", value) + return value + + +def full_members(local: LocalCPython) -> set[str]: + assert local.library is not None + output = subprocess.check_output(["nm", "--defined-only", "--dynamic", "--format=just-symbols", str(local.library)], text=True) + return set(output.splitlines()) + + +def members(profile: Profile, local: LocalCPython) -> set[str]: + if profile.mode == "full": + return full_members(local) + return {name for name, properties in stable_abi(local, profile)["function"].items() if enabled(properties, profile)} + + +def inventory(profile: Profile, local: LocalCPython) -> str: + try: + import pycparser_fake_libc + from pycparser import c_ast, c_generator, parse_file + except ModuleNotFoundError as exc: + raise SystemExit("missing parser dependency; install pycparser and pycparser-fake-libc (see --help)") from exc + + class FunctionVisitor(c_ast.NodeVisitor): + def __init__(self, function_members: set[str]): + self.members = function_members + self.generator = c_generator.CGenerator() + self.results: list[str] = [] + + def visit_Decl(self, node): # pylint: disable=invalid-name + if not isinstance(node.type, c_ast.FuncDecl) or node.name not in self.members: return - ret = cleanup(self.gen.visit(node.type.type)) - for p in node.type.args.params: - # erase parameter names - if not isinstance(p, c_ast.EllipsisParam): - t = p.type - while isinstance(t, (c_ast.PtrDecl, c_ast.FuncDecl, c_ast.ArrayDecl)): - t = t.type - t.declname = None - args = [cleanup(self.gen.visit(p)) for p in node.type.args.params] - args = "|".join(args) - self.results.append(f"{node.name};{ret};{args}") - - -v = FuncDeclVisitor() -v.visit(ast) -for line in sorted(v.results): - print(line) + params = node.type.args.params if node.type.args else [] + for param in params: + if not isinstance(param, c_ast.EllipsisParam): + typ = param.type + while isinstance(typ, (c_ast.PtrDecl, c_ast.FuncDecl, c_ast.ArrayDecl)): + typ = typ.type + typ.declname = None + result = f"{node.name};{cleanup(self.generator.visit(node.type.type))};" + self.results.append(result + "|".join(cleanup(self.generator.visit(param)) for param in params)) + + with tempfile.NamedTemporaryFile("w", suffix=".c") as source: + source.write(HEADER_SOURCE) + source.flush() + args = ["-I", pycparser_fake_libc.directory] + for include in local.includes: + args.extend(("-I", str(include))) + args.extend(f"-D{definition}" for definition in profile.defines) + ast = parse_file(source.name, use_cpp=True, cpp_args=args) + visitor = FunctionVisitor(members(profile, local)) + visitor.visit(ast) + return "\n".join(sorted(set(visitor.results))) + "\n" + + +def manifest(profile: Profile, local: LocalCPython) -> str: + if profile.mode != "stable" or profile.manifest is None: + raise ValueError(f"{profile.name} has no stable export manifest") + commit = subprocess.check_output(["git", "-C", str(local.repository), "rev-parse", f"{profile.source_ref}^{{commit}}"], text=True).strip() + symbols = [] + for item_kind, elf_kind in (("function", "FUNC"), ("data", "OBJECT")): + symbols.extend((name, elf_kind) for name, properties in stable_abi(local, profile).get(item_kind, {}).items() if enabled(properties, profile)) + lines = ["# Generated by scripts/csignature.py. Do not edit manually.", + f"# Profile: {profile.name}; source: CPython {profile.source_ref} ({commit})", + "# Source file: Misc/stable_abi.toml", + f"# Disabled features: {', '.join(sorted(profile.disabled_features)) or 'none'}", + "# Format: ", ""] + lines.extend(f"{kind} {name}" for name, kind in sorted(symbols)) + return "\n".join(lines) + "\n" + + +def write_or_check(path: Path, contents: str, check: bool) -> None: + if check: + if not path.is_file() or path.read_text(encoding="utf-8") != contents: + raise SystemExit(f"out of date: {path}") + else: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(contents, encoding="utf-8") + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__, epilog=HELP, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--config", required=True, type=Path, metavar="TOML", help="ABI profile configuration file") + parser.add_argument("--abi", action="append", type=parse_mapping, metavar="PROFILE=CPYTHON_REPOSITORY", + help="select an ABI profile and map its CPython source-tree build; repeat to process multiple profiles") + parser.add_argument("--check", action="store_true", help="verify selected outputs without modifying them") + parser.add_argument("--manifest-only", action="store_true", help="skip inventories and process only configured stable export manifests") + args = parser.parse_args() + profiles = load_profiles(args.config) + if not args.abi: + parser.error("at least one --abi mapping is required") + for name, repository in args.abi: + if name not in profiles: + parser.error(f"unknown profile: {name}") + profile = profiles[name] + local = local_cpython(repository, profile) + if not args.manifest_only: + write_or_check(profile.inventory, inventory(profile, local), args.check) + if profile.manifest: + write_or_check(profile.manifest, manifest(profile, local), args.check) + + +if __name__ == "__main__": + main() diff --git a/scripts/generate_abi3t_manifest.py b/scripts/generate_abi3t_manifest.py deleted file mode 100644 index 051b49c250..0000000000 --- a/scripts/generate_abi3t_manifest.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 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 -# -# Subject to the condition set forth below, permission is hereby granted to any -# person obtaining a copy of this software, associated documentation and/or -# data (collectively the "Software"), free of charge and under any and all -# copyright rights in the Software, and any and all patent rights owned or -# freely licensable by each licensor hereunder covering either (i) the -# unmodified Software as contributed to or provided by such licensor, or (ii) -# the Larger Works (as defined below), to deal in both -# -# (a) the Software, and -# -# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -# one is included with the Software each a "Larger Work" to which the Software -# is contributed by such licensors), -# -# without restriction, including without limitation the rights to copy, create -# derivative works of, display, perform, and distribute the Software and make, -# use, sell, offer for sale, import, export, have made, and have sold the -# Software and the Larger Work(s), and to sublicense the foregoing rights on -# either these or other terms. -# -# This license is subject to the following condition: -# -# The above copyright notice and either this complete permission notice or at a -# minimum a reference to the UPL must be included in all copies or substantial -# portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from __future__ import annotations - -import argparse -import subprocess -from pathlib import Path - -try: - import tomllib -except ModuleNotFoundError: - from pip._vendor import tomli as tomllib # pylint: disable=no-name-in-module - - -DEFAULT_OUTPUT = Path(__file__).resolve().parents[1] / "abi" / "abi3t-linux.txt" -LINUX_DISABLED_FEATURES = {"MS_WINDOWS", "Py_REF_DEBUG", "USE_STACKCHECK"} - - -def git_output(repository: Path, *arguments: str) -> str: - return subprocess.check_output( - ["git", "-C", str(repository), *arguments], - text=True, - ).strip() - - -def generate_manifest(repository: Path, source_ref: str) -> str: - commit = git_output(repository, "rev-parse", f"{source_ref}^{{commit}}") - source = git_output(repository, "show", f"{source_ref}:Misc/stable_abi.toml") - stable_abi = tomllib.loads(source) - - symbols: list[tuple[str, str]] = [] - for item_kind, elf_kind in (("function", "FUNC"), ("data", "OBJECT")): - for name, properties in stable_abi[item_kind].items(): - if properties.get("ifdef") not in LINUX_DISABLED_FEATURES: - symbols.append((name, elf_kind)) - symbols.sort() - - lines = [ - "# Generated by scripts/generate_abi3t_manifest.py. Do not edit manually.", - f"# Source: CPython {source_ref} ({commit})", - "# Source file: Misc/stable_abi.toml", - "# Target: Linux ABI3T (MS_WINDOWS, Py_REF_DEBUG, and USE_STACKCHECK disabled)", - "# Format: ", - "", - ] - lines.extend(f"{kind} {name}" for name, kind in symbols) - return "\n".join(lines) + "\n" - - -def main() -> None: - parser = argparse.ArgumentParser( - description="Generate the Linux ABI3T export manifest from CPython's stable_abi.toml", - ) - parser.add_argument("cpython", type=Path, help="path to a CPython Git repository") - parser.add_argument("--source-ref", default="v3.15.0rc1", help="pinned CPython Git ref") - parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT) - args = parser.parse_args() - - manifest = generate_manifest(args.cpython.resolve(), args.source_ref) - args.output.parent.mkdir(parents=True, exist_ok=True) - args.output.write_text(manifest, encoding="utf-8") - - -if __name__ == "__main__": - main() From a221b21b1efa3bb3e00846acfaed21cb0e5d7cf0 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Wed, 26 Aug 2026 10:53:31 +0200 Subject: [PATCH 15/24] Remove obsolete native library retention list --- .../cext/PythonCextModuleBuiltins.java | 2 +- .../objects/cext/capi/CApiContext.java | 34 +++---------------- .../builtins/objects/cext/capi/CExtNodes.java | 14 +++----- 3 files changed, 11 insertions(+), 39 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextModuleBuiltins.java index f9746f6800..267814f5b2 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextModuleBuiltins.java @@ -324,7 +324,7 @@ public static long PyModule_FromSlotsAndSpec(long slots, long specPtr) { Object spec = NativeToPythonInternalNode.executeUncached(specPtr, false); TruffleString name = CastToTruffleStringNode.executeUncached(PyObjectGetAttr.executeUncached(spec, T_NAME)); ModuleSpec moduleSpec = new ModuleSpec(name, T_EMPTY_STRING, spec); - Object module = CExtNodes.createModuleFromSlotsAndSpec(null, PythonContext.get(null).getCApiContext(), null, slots, moduleSpec); + Object module = CExtNodes.createModuleFromSlotsAndSpec(null, PythonContext.get(null).getCApiContext(), slots, moduleSpec); return PythonToNativeInternalNode.executeNewRefUncached(module); } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java index 61f948c550..9216cb392b 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java @@ -67,7 +67,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.util.LinkedList; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -98,9 +97,9 @@ import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonInternalNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.ReferenceQueueCoordinator; +import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.TransformExceptionFromNativeNode; import com.oracle.graal.python.builtins.objects.cext.common.LoadCExtException.ApiInitException; import com.oracle.graal.python.builtins.objects.cext.common.LoadCExtException.ImportException; -import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.TransformExceptionFromNativeNode; import com.oracle.graal.python.builtins.objects.cext.copying.NativeLibraryLocator; import com.oracle.graal.python.builtins.objects.cext.structs.CFields; import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess; @@ -349,22 +348,6 @@ public TruffleString getModExecFunctionName() { */ private final HashMap methodDefinitions = new HashMap<>(4); - /** - * This list holds a strong reference to all loaded extension libraries to keep the library - * objects alive. This is necessary because native library handles may {@code dlclose} the - * library (and thus {@code munmap} all code) if the library object is no longer reachable. - * However, it can happen - * that we still store raw function pointers (as Java {@code long} values) in a native object - * that is referenced by a - * {@link com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeObjectReference}. - * For example, the {code tp_dealloc} functions may be executed a long time after all managed - * objects of the extension died and the native library has been {@code dlclosed}'d. - * - * Since we have no control over the timing when certain garbage will be collected, we need to - * ensure that the code is still mapped. - */ - private final List loadedExtensions = new LinkedList<>(); - private final NativeLibraryLocator nativeLibraryLocator; public final BackgroundGCTask gcTask; @@ -391,11 +374,6 @@ public CApiContext(PythonContext context, NativeLibrary library, NativeLibraryLo this.referenceQueueWatcherTask = new ReferenceQueueWatcherTask(context); } - @TruffleBoundary - void addLoadedExtensionLibrary(Object nativeLibrary) { - loadedExtensions.add(nativeLibrary); - } - @TruffleBoundary public static Object asHex(Object ptr) { if (ptr instanceof Number) { @@ -1369,7 +1347,7 @@ public Object initCApiModule(Node node, NativeLibrary sharedLibrary, ModuleSpec TruffleString modExportFuncName = spec.getModExecFunctionName(); long modExportFunc = sharedLibrary.lookupOptionalSymbol(modExportFuncName.toJavaStringUncached()); if (modExportFunc != NULLPTR) { - return initAbi3tCApiModule(node, sharedLibrary, modExportFunc, spec); + return initAbi3tCApiModule(node, modExportFunc, spec); } } return initLegacyCApiModule(node, sharedLibrary, spec.getInitFunctionName(), spec); @@ -1401,12 +1379,10 @@ private Object initLegacyCApiModule(Node node, NativeLibrary sharedLibrary, Truf throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.SystemError, ErrorMessages.INIT_FUNC_RETURNED_UNINT_OBJ, initFuncName); } - return CExtNodes.createModuleFromDefAndSpec(node, this, spec, nativeResult, sharedLibrary); + return CExtNodes.createModuleFromDefAndSpec(node, this, spec, nativeResult); } else { // see: 'import.c: _PyImport_FixupExtensionObject' module.setAttribute(T___FILE__, spec.path); - addLoadedExtensionLibrary(sharedLibrary); - // add to 'sys.modules' PDict sysModules = context.getSysModules(); sysModules.setItem(spec.name, result); @@ -1426,7 +1402,7 @@ private Object initLegacyCApiModule(Node node, NativeLibrary sharedLibrary, Truf } // import.c: import_run_modexport - private Object initAbi3tCApiModule(Node node, NativeLibrary library, long modExportFunc, ModuleSpec spec) { + private Object initAbi3tCApiModule(Node node, long modExportFunc, ModuleSpec spec) { CompilerAsserts.neverPartOfCompilation(); NativeContext nativeContext = context.ensureNativeContext(); PythonThreadState threadState = context.getThreadState(context.getLanguage()); @@ -1435,7 +1411,7 @@ private Object initAbi3tCApiModule(Node node, NativeLibrary library, long modExp ExternalFunctionSignature.MODINIT.bind(nativeContext, modExportFunc)); TransformExceptionFromNativeNode.getUncached().execute(node, threadState, spec.name, slots == NULLPTR, true, ErrorMessages.MODULE_EXPORT_HOOK_FAILED, ErrorMessages.MODULE_EXPORT_HOOK_RAISED_EXCEPTION); - Object module = CExtNodes.createModuleFromSlotsAndSpec(node, context.getCApiContext(), library, slots, spec); + Object module = CExtNodes.createModuleFromSlotsAndSpec(node, context.getCApiContext(), slots, spec); if (module instanceof PythonModule pythonModule && pythonModule.getNativeModuleToken() == NULLPTR) { // import_run_modexport uses the static top-level slots array as the default token. pythonModule.setNativeModuleToken(slots); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java index 40500c9296..403352f670 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java @@ -158,7 +158,6 @@ import com.oracle.graal.python.runtime.exception.PException; import com.oracle.graal.python.runtime.exception.PythonErrorType; import com.oracle.graal.python.runtime.nativeaccess.NativeFunctionPointer; -import com.oracle.graal.python.runtime.nativeaccess.NativeLibrary; import com.oracle.graal.python.runtime.object.PFactory; import com.oracle.graal.python.runtime.sequence.storage.MroSequenceStorage; import com.oracle.graal.python.util.PythonUtils; @@ -1021,7 +1020,7 @@ static long doOther(Node inliningTarget, Object object, * */ @TruffleBoundary - static Object createModuleFromDefAndSpec(Node node, CApiContext capiContext, ModuleSpec moduleSpec, long moduleDefPtr, NativeLibrary library) { + static Object createModuleFromDefAndSpec(Node node, CApiContext capiContext, ModuleSpec moduleSpec, long moduleDefPtr) { /* * The name of the module is taken from the module spec and *NOT* from the module * definition. @@ -1038,7 +1037,7 @@ static Object createModuleFromDefAndSpec(Node node, CApiContext capiContext, Mod definition.clearFunction = readPtrField(moduleDefPtr, PyModuleDef__m_clear); definition.freeFunction = readPtrField(moduleDefPtr, PyModuleDef__m_free); definition.token = moduleDefPtr; - return createModule(node, capiContext, library, moduleSpec, + return createModule(node, capiContext, moduleSpec, parseModuleSlots(node, PySlotIterator.initLegacy(node, mName, slotDefinitions, PySlotIterator.SlotKind.MODULE), definition, mName).getDefinition()); } @@ -1063,7 +1062,7 @@ static Object createModuleFromDefAndSpec(Node node, CApiContext capiContext, Mod * */ @TruffleBoundary - public static Object createModuleFromSlotsAndSpec(Node node, CApiContext capiContext, NativeLibrary library, long slots, ModuleSpec moduleSpec) { + public static Object createModuleFromSlotsAndSpec(Node node, CApiContext capiContext, long slots, ModuleSpec moduleSpec) { PythonContext context = capiContext.getContext(); ParsedModuleSlots parsed = parseModuleSlots(node, PySlotIterator.init(node, moduleSpec.name, slots, PySlotIterator.SlotKind.MODULE), null, moduleSpec.name); if (!parsed.sawAbi) { @@ -1083,7 +1082,7 @@ public static Object createModuleFromSlotsAndSpec(Node node, CApiContext capiCon checkSubinterpIncompatibleExtensionAllowed(node, context, moduleSpec.name); } - return createModule(node, capiContext, library, moduleSpec, parsed.getDefinition()); + return createModule(node, capiContext, moduleSpec, parsed.getDefinition()); } private static final class ParsedModuleSlots { @@ -1196,7 +1195,7 @@ private static void checkDefinitionField(Node node, NativeModuleDefinition origi } } - private static Object createModule(Node node, CApiContext capiContext, NativeLibrary library, ModuleSpec moduleSpec, NativeModuleDefinition definition) { + private static Object createModule(Node node, CApiContext capiContext, ModuleSpec moduleSpec, NativeModuleDefinition definition) { if (definition.stateSize < 0) { throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.SystemError, ErrorMessages.M_SIZE_CANNOT_BE_NEGATIVE, moduleSpec.name); @@ -1233,9 +1232,6 @@ private static Object createModule(Node node, CApiContext capiContext, NativeLib } WriteAttributeToObjectNode.getUncached().execute(module, SpecialAttributeNames.T___DOC__, mDoc); - if (library != null) { - capiContext.addLoadedExtensionLibrary(library); - } return module; } From aa2ea2a3f5b2b98034d85c8fe76440ec1e2c1ef9 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Wed, 26 Aug 2026 12:01:30 +0200 Subject: [PATCH 16/24] Fix stable ABI check on Python 3.8 --- mx.graalpython/mx_graalpython.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/mx.graalpython/mx_graalpython.py b/mx.graalpython/mx_graalpython.py index 418295b788..acc8ba615f 100644 --- a/mx.graalpython/mx_graalpython.py +++ b/mx.graalpython/mx_graalpython.py @@ -52,10 +52,6 @@ import mx_urlrewrites import tempfile -try: - import tomllib -except ModuleNotFoundError: - from pip._vendor import tomli as tomllib from argparse import ArgumentParser from dataclasses import dataclass @@ -3423,9 +3419,12 @@ def stable_abi_check(raw_args, default_profile=None): if not args.profile: mx.abort("Specify --profile") + # Use mx's bundled TOML parser through its public proxy so this also works with Python < 3.11 without an external dependency. + import mx_codeowners config_path = os.path.join(SUITE.dir, 'capi-abis.toml') - with open(config_path, 'rb') as file: - profiles = tomllib.load(file)['profiles'] + with open(config_path, encoding='utf-8') as file: + config = mx_codeowners.stoml_parse_rules(file.read()) + profiles = {key.partition('.')[2]: value for key, value in config.items() if key.startswith('profiles.')} profile = profiles.get(args.profile) if profile is None or profile.get('mode') != 'stable': mx.abort(f"{args.profile} is not a stable ABI profile in {config_path}") From 86bab2002db3212726ad9f04478c6f3d5a47b4c0 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Wed, 26 Aug 2026 13:42:19 +0200 Subject: [PATCH 17/24] Unify module method installation --- .../src/tests/cpyext/test_abi3t.py | 14 +++- .../src/tests/cpyext/test_module.py | 76 +++++++++++++++++++ .../cext/PythonCextMethodBuiltins.java | 32 ++++++++ .../cext/PythonCextModuleBuiltins.java | 34 +-------- .../builtins/objects/cext/capi/CExtNodes.java | 69 +---------------- .../objects/cext/structs/CStructAccess.java | 2 +- 6 files changed, 125 insertions(+), 102 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abi3t.py b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abi3t.py index 45a0b9d83c..ee5fb28a57 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abi3t.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abi3t.py @@ -100,8 +100,14 @@ } static PyMethodDef methods[] = { - {"increment", increment, METH_NOARGS, NULL}, - {"from_slots", from_slots, METH_O, NULL}, + {"increment", increment, METH_NOARGS, + "increment($module, /)\n" + "--\n\n" + "Increment the module state."}, + {"from_slots", from_slots, METH_O, + "from_slots($module, spec, /)\n" + "--\n\n" + "Create a module from slots."}, {NULL, NULL, 0, NULL}, }; @@ -260,6 +266,10 @@ def test_modexport_smoke(self): code = ( "import importlib.machinery; import abi3t_smoke as m; " "n = m.from_slots(importlib.machinery.ModuleSpec('from_slots', None)); " + "assert m.increment.__name__ == 'increment'; " + "assert m.increment.__module__ == 'abi3t_smoke'; " + "assert m.increment.__doc__ == 'Increment the module state.'; " + "assert m.increment.__text_signature__ == '($module, /)'; " "print(m.increment(), m.increment(), n.__name__)" ) proc = subprocess.run( diff --git a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_module.py b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_module.py index d79e731c43..c8243839fd 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_module.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_module.py @@ -72,6 +72,82 @@ def _reference_get_filename(args): class TestPyModule(CPyExtTestCase): + def test_add_functions_installs_each_method_definition(self): + module = compile_module_from_string(r''' + #include + + // CPython and other don't have this function; so define it + #ifndef GRAALVM_PYTHON + #define GraalPyCFunction_GetMethodDef(OBJ) (((PyCFunctionObject*) (OBJ))->m_ml) + #endif + + static PyObject *first(PyObject *module, PyObject *Py_UNUSED(ignored)) { + Py_RETURN_TRUE; + } + + static PyObject *second(PyObject *module, PyObject *Py_UNUSED(ignored)) { + Py_RETURN_FALSE; + } + + static PyMethodDef added_methods[] = { + {"first", first, METH_NOARGS, + "first($module, /)\n" + "--\n\n" + "Return true."}, + {"second", second, METH_NOARGS, + "second($module, /)\n" + "--\n\n" + "Return false."}, + {NULL, NULL, 0, NULL}, + }; + + static PyObject *verify_method_def_pointers(PyObject *module, PyObject *Py_UNUSED(ignored)) { + PyObject *first_method = PyObject_GetAttrString(module, "first"); + PyObject *second_method = PyObject_GetAttrString(module, "second"); + if (first_method == NULL || second_method == NULL) { + Py_XDECREF(first_method); + Py_XDECREF(second_method); + return NULL; + } + int matches = GraalPyCFunction_GetMethodDef(first_method) == &added_methods[0] && + GraalPyCFunction_GetMethodDef(second_method) == &added_methods[1]; + Py_DECREF(first_method); + Py_DECREF(second_method); + return PyBool_FromLong(matches); + } + + static PyMethodDef module_methods[] = { + {"verify_method_def_pointers", verify_method_def_pointers, METH_NOARGS, NULL}, + {NULL, NULL, 0, NULL}, + }; + + static PyModuleDef module_def = { + PyModuleDef_HEAD_INIT, + "add_functions", + NULL, + -1, + module_methods, + }; + + PyMODINIT_FUNC PyInit_add_functions(void) { + PyObject *module = PyModule_Create(&module_def); + if (module == NULL || PyModule_AddFunctions(module, added_methods) < 0) { + Py_XDECREF(module); + return NULL; + } + return module; + } + ''', "add_functions") + self.assertTrue(module.first()) + self.assertFalse(module.second()) + self.assertTrue(module.verify_method_def_pointers()) + self.assertEqual(module.first.__name__, "first") + self.assertEqual(module.first.__module__, "add_functions") + self.assertEqual(module.first.__doc__, "Return true.") + self.assertEqual(module.first.__text_signature__, "($module, /)") + self.assertEqual(module.second.__doc__, "Return false.") + self.assertEqual(module.second.__text_signature__, "($module, /)") + def test_multiple_exec_slots(self): module = compile_module_from_string(r''' #include diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextMethodBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextMethodBuiltins.java index 4c774ad389..108856cc77 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextMethodBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextMethodBuiltins.java @@ -48,6 +48,9 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObject; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObjectTransfer; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyTypeObject; +import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.getArrayElementPtr; +import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readIntField; +import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readPtrField; import static com.oracle.graal.python.builtins.objects.cext.capi.PyMethodFlags.METH_METHOD; import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.NULLPTR; import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___MODULE__; @@ -57,16 +60,21 @@ import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBuiltin; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.EnsurePythonObjectNode; +import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.FromCharPointerNode; import com.oracle.graal.python.builtins.objects.cext.capi.MethodDescriptorWrapper; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.CharPtrToPythonNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonInternalNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; +import com.oracle.graal.python.builtins.objects.cext.structs.CFields; +import com.oracle.graal.python.builtins.objects.cext.structs.CStructs; import com.oracle.graal.python.builtins.objects.function.PBuiltinFunction; import com.oracle.graal.python.builtins.objects.method.PBuiltinMethod; import com.oracle.graal.python.builtins.objects.object.PythonBuiltinObject; import com.oracle.graal.python.nodes.HiddenAttr; import com.oracle.graal.python.nodes.attributes.WriteAttributeToPythonObjectNode; +import com.oracle.graal.python.nodes.attributes.WriteAttributeToObjectNode; import com.oracle.graal.python.runtime.object.PFactory; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.strings.TruffleString; public final class PythonCextMethodBuiltins { @@ -94,6 +102,30 @@ static PythonBuiltinObject cFunctionNewExMethodNode(PythonLanguage language, lon return method; } + /** + * Implementation of {@code moduleobject.c: _add_methods_to_object}. + */ + @TruffleBoundary + public static void addMethodsToObject(PythonLanguage language, long methods, Object object, Object moduleName) { + for (int i = 0;; i++) { + long methodDef = getArrayElementPtr(methods, i, CStructs.PyMethodDef); + long nameRaw = readPtrField(methodDef, CFields.PyMethodDef__ml_name); + if (nameRaw == NULLPTR) { + break; + } + long methPtr = readPtrField(methodDef, CFields.PyMethodDef__ml_meth); + int flags = readIntField(methodDef, CFields.PyMethodDef__ml_flags); + long docRaw = readPtrField(methodDef, CFields.PyMethodDef__ml_doc); + + TruffleString name = FromCharPointerNode.executeUncached(nameRaw); + Object doc = CharPtrToPythonNode.executeUncached(docRaw); + assert doc == PNone.NO_VALUE || doc instanceof TruffleString; + + PythonBuiltinObject function = cFunctionNewExMethodNode(language, methodDef, name, methPtr, flags, object, moduleName, PNone.NO_VALUE, doc); + WriteAttributeToObjectNode.getUncached().execute(object, name, function); + } + } + @CApiBuiltin(ret = PyObjectTransfer, args = {PyMethodDef, ConstCharPtrAsTruffleString, Pointer, Int, PyObject, PyObject, PyTypeObject, ConstCharPtrAsTruffleString}, call = Ignored) public static long GraalPyPrivate_CMethod_NewEx(long methodDefPtr, long nameRaw, long methPtr, int flags, long selfRaw, long moduleRaw, long clsRaw, long docRaw) { // errors are expected to be thrown already in native code diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextModuleBuiltins.java index 267814f5b2..fbfba567f4 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextModuleBuiltins.java @@ -60,8 +60,6 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObjectTransfer; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.VOID_PTR_LIST; import static com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.bindFunctionPointer; -import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readIntField; -import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readPtrField; import static com.oracle.graal.python.nodes.ErrorMessages.NAMELESS_MODULE; import static com.oracle.graal.python.nodes.ErrorMessages.S_NEEDS_S_AS_FIRST_ARG; import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___DOC__; @@ -94,11 +92,8 @@ import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.CharPtrToPythonNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonInternalNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; -import com.oracle.graal.python.builtins.objects.cext.structs.CFields; -import com.oracle.graal.python.builtins.objects.cext.structs.CStructs; import com.oracle.graal.python.builtins.objects.module.PythonModule; import com.oracle.graal.python.builtins.objects.object.ObjectBuiltins; -import com.oracle.graal.python.builtins.objects.object.PythonBuiltinObject; import com.oracle.graal.python.builtins.objects.str.StringBuiltins.PrefixSuffixNode; import com.oracle.graal.python.lib.PyObjectGetAttr; import com.oracle.graal.python.lib.PyUnicodeCheckNode; @@ -278,7 +273,7 @@ public static int GraalPyPrivate_Module_AddFunctions(long moduleRaw, long functi return PRaiseNativeNodeGen.getUncached().raiseIntWithoutFrame(-1, SystemError, NAMELESS_MODULE, EMPTY_OBJECT_ARRAY); } - addMethodsToObject(functions, module, modName); + PythonCextMethodBuiltins.addMethodsToObject(PythonLanguage.get(null), functions, module, modName); return 0; } @@ -287,35 +282,10 @@ public static int GraalPyPrivate_AddMethodsToObject(long moduleRaw, long nameRaw CompilerAsserts.neverPartOfCompilation(); Object module = NativeToPythonInternalNode.executeUncached(moduleRaw, false); Object name = NativeToPythonInternalNode.executeUncached(nameRaw, false); - addMethodsToObject(functions, module, name); + PythonCextMethodBuiltins.addMethodsToObject(PythonLanguage.get(null), functions, module, name); return 0; } - /** - * Implementation of {@code moduleobject.c: _add_methods_to_object}. - * - * TODO(fa): overlaps with - * {@link com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes#createLegacyMethod} - */ - private static void addMethodsToObject(long functions, Object module, Object modName) { - PythonLanguage language = PythonLanguage.get(null); - long nameRaw; - - // iterate over a native array of PyModuleDef elements - for (long def = functions; (nameRaw = readPtrField(def, CFields.PyMethodDef__ml_name)) != NULLPTR; def += CStructs.PyMethodDef.size()) { - long cfunc = readPtrField(def, CFields.PyMethodDef__ml_meth); - int flags = readIntField(def, CFields.PyMethodDef__ml_flags); - long docRaw = readPtrField(def, CFields.PyMethodDef__ml_doc); - - TruffleString name = (TruffleString) CharPtrToPythonNode.executeUncached(nameRaw); - Object doc = CharPtrToPythonNode.executeUncached(docRaw); - assert doc == PNone.NO_VALUE || doc instanceof TruffleString; - - PythonBuiltinObject func = PythonCextMethodBuiltins.cFunctionNewExMethodNode(language, def, name, cfunc, flags, module, modName, PNone.NO_VALUE, doc); - WriteAttributeToObjectNode.getUncached().execute(module, name, func); - } - } - @CApiBuiltin(ret = PyObjectTransfer, args = {CONST_PY_SLOT_PTR, PyObject}, call = Direct, abi = ABI.ABI3T) public static long PyModule_FromSlotsAndSpec(long slots, long specPtr) { if (slots == NULLPTR) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java index 403352f670..9103e137ef 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java @@ -51,10 +51,6 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.NativeCAPISymbol.FUN_SUBTYPE_TRAVERSE; import static com.oracle.graal.python.builtins.objects.cext.structs.CConstants.PYLONG_BITS_IN_DIGIT; import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyFloatObject__ob_fval; -import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyMethodDef__ml_doc; -import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyMethodDef__ml_flags; -import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyMethodDef__ml_meth; -import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyMethodDef__ml_name; import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyModuleDef__m_clear; import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyModuleDef__m_doc; import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyModuleDef__m_free; @@ -68,10 +64,7 @@ import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readDoubleField; import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readLongField; import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readPtrField; -import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readStructArrayIntField; -import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readStructArrayPtrField; import static com.oracle.graal.python.builtins.objects.object.PythonObject.MANAGED_REFCNT; -import static com.oracle.graal.python.nodes.HiddenAttr.METHOD_DEF_PTR; import static com.oracle.graal.python.nodes.SpecialMethodNames.T___COMPLEX__; import static com.oracle.graal.python.runtime.exception.PythonErrorType.SystemError; import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.NULLPTR; @@ -86,8 +79,7 @@ import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.PythonBuiltinClassType; -import com.oracle.graal.python.builtins.modules.cext.CFunctionDocUtils; -import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.builtins.modules.cext.PythonCextMethodBuiltins; import com.oracle.graal.python.builtins.objects.PythonAbstractObject; import com.oracle.graal.python.builtins.objects.bytes.PByteArray; import com.oracle.graal.python.builtins.objects.bytes.PBytes; @@ -106,7 +98,6 @@ import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonInternalNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.UpdateStrongRefNode; -import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.TransformExceptionFromNativeNode; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.TransformPExceptionToNativeNode; import com.oracle.graal.python.builtins.objects.cext.structs.CFields; @@ -115,11 +106,8 @@ import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; import com.oracle.graal.python.builtins.objects.complex.PComplex; import com.oracle.graal.python.builtins.objects.floats.PFloat; -import com.oracle.graal.python.builtins.objects.function.PBuiltinFunction; -import com.oracle.graal.python.builtins.objects.function.PKeyword; import com.oracle.graal.python.builtins.objects.ints.PInt; import com.oracle.graal.python.builtins.objects.memoryview.PMemoryView; -import com.oracle.graal.python.builtins.objects.method.PBuiltinMethod; import com.oracle.graal.python.builtins.objects.module.ModuleGetNameNode; import com.oracle.graal.python.builtins.objects.module.PythonModule; import com.oracle.graal.python.builtins.objects.object.PythonObject; @@ -141,12 +129,10 @@ import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PNodeWithContext; import com.oracle.graal.python.nodes.PRaiseNode; -import com.oracle.graal.python.nodes.PRootNode; import com.oracle.graal.python.nodes.SpecialAttributeNames; import com.oracle.graal.python.nodes.SpecialMethodNames; import com.oracle.graal.python.nodes.attributes.ReadAttributeFromObjectNode; import com.oracle.graal.python.nodes.attributes.WriteAttributeToObjectNode; -import com.oracle.graal.python.nodes.attributes.WriteAttributeToPythonObjectNode; import com.oracle.graal.python.nodes.call.special.LookupAndCallUnaryNode.LookupAndCallUnaryDynamicNode; import com.oracle.graal.python.nodes.classes.IsSubtypeNode; import com.oracle.graal.python.nodes.object.GetClassNode; @@ -1213,15 +1199,7 @@ private static Object createModule(Node node, CApiContext capiContext, ModuleSpe } if (definition.methods != NULLPTR) { - for (int i = 0;; i++) { - PBuiltinFunction fun = createLegacyMethod(definition.methods, i, context.getLanguage()); - if (fun == null) { - break; - } - PBuiltinMethod method = PFactory.createBuiltinMethod(context.getLanguage(), module, fun); - WriteAttributeToPythonObjectNode.executeUncached(method, SpecialAttributeNames.T___MODULE__, moduleSpec.name); - WriteAttributeToObjectNode.getUncached().execute(module, fun.getName(), method); - } + PythonCextMethodBuiltins.addMethodsToObject(context.getLanguage(), definition.methods, module, moduleSpec.name); } Object mDoc; @@ -1375,49 +1353,6 @@ private static void invokeModuleExec(Node node, CApiContext capiContext, PythonM ErrorMessages.EXECUTION_FAILED_WITHOUT_EXCEPTION, ErrorMessages.EXECUTION_RAISED_EXCEPTION); } - /** - * TODO(fa): overlaps with - * {@link com.oracle.graal.python.builtins.modules.cext.PythonCextModuleBuiltins#GraalPyPrivate_Module_AddFunctions(long, long)}. - * - *
-     *     struct PyMethodDef {
-     *         const char * ml_name;
-     *         PyCFunction  ml_meth;
-     *         int          ml_flags;
-     *         const char * ml_doc;
-     *     };
-     * 
- */ - @TruffleBoundary - static PBuiltinFunction createLegacyMethod(long methodDefPtr, int element, PythonLanguage language) { - long methodNamePtr = readStructArrayPtrField(methodDefPtr, element, PyMethodDef__ml_name); - if (methodNamePtr == NULLPTR) { - return null; - } - TruffleString methodName = FromCharPointerNode.executeUncached(methodNamePtr); - // note: 'ml_doc' may be NULL; in this case, we would store 'None' - Object methodDoc = PNone.NONE; - long methodDocPtr = readStructArrayPtrField(methodDefPtr, element, PyMethodDef__ml_doc); - if (methodDocPtr != NULLPTR) { - methodDoc = FromCharPointerNode.executeUncached(methodDocPtr); - } - - int flags = readStructArrayIntField(methodDefPtr, element, PyMethodDef__ml_flags); - long mlMethObj = readStructArrayPtrField(methodDefPtr, element, PyMethodDef__ml_meth); - // CPy-style methods - // TODO(fa) support static and class methods - MethodDescriptorWrapper sig = MethodDescriptorWrapper.fromMethodFlags(flags); - PRootNode rootNode = MethodDescriptorWrapper.getOrCreateRootNode(language, sig, methodName, PyMethodFlags.isMethStatic(flags)); - NativeFunctionPointer fun = CExtCommonNodes.bindFunctionPointer(mlMethObj, sig); - PKeyword[] kwDefaults = ExternalFunctionNodes.createKwDefaults(fun); - PBuiltinFunction function = PFactory.createBuiltinFunction(language, methodName, null, PythonUtils.EMPTY_OBJECT_ARRAY, kwDefaults, flags, rootNode); - HiddenAttr.WriteLongNode.executeUncached(function, METHOD_DEF_PTR, methodDefPtr); - - CFunctionDocUtils.writeDocAndTextSignature(function, methodName, methodDoc, flags); - - return function; - } - @GenerateInline @GenerateCached(false) @GenerateUncached diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructAccess.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructAccess.java index 85ad261956..f2fd8314d9 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructAccess.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructAccess.java @@ -163,7 +163,7 @@ public static void writeStructArrayPtrField(long arrayPtr, long index, CFields f writePtrField(getArrayElementPtr(arrayPtr, index, field.struct), field, value); } - private static long getArrayElementPtr(long arrayPtr, long index, CStructs struct) { + public static long getArrayElementPtr(long arrayPtr, long index, CStructs struct) { return arrayPtr + index * struct.size(); } From be0f67f282660535aff3128c561a6f3ce3e0422c Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Thu, 27 Aug 2026 10:30:55 +0200 Subject: [PATCH 18/24] Suppress MSVC anonymous union warning --- .../com.oracle.graal.python.cext/include/Python.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/graalpython/com.oracle.graal.python.cext/include/Python.h b/graalpython/com.oracle.graal.python.cext/include/Python.h index 88f6b17ff1..f716f0d93f 100644 --- a/graalpython/com.oracle.graal.python.cext/include/Python.h +++ b/graalpython/com.oracle.graal.python.cext/include/Python.h @@ -72,6 +72,15 @@ struct timeval; // Include Python header files #include "pyport.h" + +#ifdef _MSC_VER +// Ignore MSC warning C4201: "nonstandard extension used: nameless +// struct/union". (Only generated for C standard versions less than C11, which +// we don't *officially* support.) +__pragma(warning(push)) +__pragma(warning(disable: 4201)) +#endif + #include "pymacro.h" #include "pymath.h" #include "pymem.h" @@ -144,6 +153,10 @@ struct timeval; #include "cpython/pyfpe.h" #include "cpython/tracemalloc.h" +#ifdef _MSC_VER +__pragma(warning(pop)) // warning(disable: 4201) +#endif + // helper macro for quick printf debugging #define __PD {printf("%i \t%s\n", __LINE__, __func__); } From 0630a8aa9e732e1e1f87c197a64b986be1ffc3c3 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Thu, 27 Aug 2026 12:26:28 +0200 Subject: [PATCH 19/24] Fix abi3t smoke test linking on Windows --- .../src/tests/cpyext/test_abi3t.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abi3t.py b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abi3t.py index ee5fb28a57..ff0837f401 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abi3t.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abi3t.py @@ -138,6 +138,16 @@ }; return slots; } + +#ifdef MS_WINDOWS +/* Setuptools still requires PyInit_ when linking extensions on Windows. */ +PyMODINIT_FUNC +PyInit_abi3t_smoke(void) +{ + PyErr_SetString(PyExc_ImportError, "PyInit_abi3t_smoke must not be called"); + return NULL; +} +#endif """ From c673f8cee5d07816ba995b2450ced0905498f9d5 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Tue, 1 Sep 2026 10:55:25 +0200 Subject: [PATCH 20/24] Rename init*CApiModule methods --- .../builtins/objects/cext/capi/CApiContext.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java index 9216cb392b..48994b5a2e 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java @@ -1197,7 +1197,7 @@ public static Object loadCExtModule(Node location, PythonContext context, Module throw new ImportException(CApiContext.wrapJavaException(e, location), spec.name, spec.path, ErrorMessages.CANNOT_LOAD_M, spec.path, e); } - return cApiContext.initCApiModule(location, library, spec); + return cApiContext.loadCApiModule(location, library, spec); } /** @@ -1341,19 +1341,19 @@ public void finalizeCApi(boolean cancelling) { } } - public Object initCApiModule(Node node, NativeLibrary sharedLibrary, ModuleSpec spec) throws ImportException { + public Object loadCApiModule(Node node, NativeLibrary sharedLibrary, ModuleSpec spec) throws ImportException { CompilerAsserts.neverPartOfCompilation(); if (context.getOption(PythonOptions.EnableAbi3t)) { TruffleString modExportFuncName = spec.getModExecFunctionName(); long modExportFunc = sharedLibrary.lookupOptionalSymbol(modExportFuncName.toJavaStringUncached()); if (modExportFunc != NULLPTR) { - return initAbi3tCApiModule(node, modExportFunc, spec); + return loadCApiModuleFromPyModExport(node, modExportFunc, spec); } } - return initLegacyCApiModule(node, sharedLibrary, spec.getInitFunctionName(), spec); + return loadCApiModuleFromPyInit(node, sharedLibrary, spec.getInitFunctionName(), spec); } - private Object initLegacyCApiModule(Node node, NativeLibrary sharedLibrary, TruffleString initFuncName, ModuleSpec spec) throws ImportException { + private Object loadCApiModuleFromPyInit(Node node, NativeLibrary sharedLibrary, TruffleString initFuncName, ModuleSpec spec) throws ImportException { CompilerAsserts.neverPartOfCompilation(); long pyinitFunc = sharedLibrary.lookupOptionalSymbol(initFuncName.toJavaStringUncached()); if (pyinitFunc == NULLPTR) { @@ -1402,7 +1402,7 @@ private Object initLegacyCApiModule(Node node, NativeLibrary sharedLibrary, Truf } // import.c: import_run_modexport - private Object initAbi3tCApiModule(Node node, long modExportFunc, ModuleSpec spec) { + private Object loadCApiModuleFromPyModExport(Node node, long modExportFunc, ModuleSpec spec) { CompilerAsserts.neverPartOfCompilation(); NativeContext nativeContext = context.ensureNativeContext(); PythonThreadState threadState = context.getThreadState(context.getLanguage()); From 2659f011078eeabbbdb80e83d0938bbe326c89e8 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Tue, 1 Sep 2026 12:20:13 +0200 Subject: [PATCH 21/24] Move native module fields to companion --- .../objects/module/NativeModuleData.java | 93 +++++++++++++++++++ .../builtins/objects/module/PythonModule.java | 91 +++++++----------- 2 files changed, 125 insertions(+), 59 deletions(-) create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/NativeModuleData.java diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/NativeModuleData.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/NativeModuleData.java new file mode 100644 index 0000000000..0728e599f7 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/NativeModuleData.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2026, 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 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.graal.python.builtins.objects.module; + +import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.NULLPTR; + +final class NativeModuleData { + /** + * Stores the native {@code PyModuleDef *} structure if this module was created via the + * multiphase extension module initialization mechanism. Will be non-null only for legacy definitions. + */ + long def = NULLPTR; + /** {@code void *md_state} */ + long state = NULLPTR; + + /** {@code Py_ssize_t md_state_size} */ + long stateSize; + + /** {@code traverseproc md_state_traverse} */ + long stateTraverse = NULLPTR; + /** {@code inquiry md_state_clear} */ + long stateClear = NULLPTR; + /** {@code freefunc md_state_free} */ + long stateFree = NULLPTR; + + /** {@code _Py_modexecfunc md_exec} */ + long exec = NULLPTR; // direct PySlot[] modules + /** {@code void *md_token} */ + long token = NULLPTR; + /** {@code bool md_token_is_def} */ + boolean tokenIsDef; + /** {@code bool md_requires_gil} */ + boolean requiresGil; + + /** + * Replicates the native references of this module's native state in Java. + *

+ * Since a module can have a native module state where it is valid to store native references to + * other objects, we need this field to replicate those references in Java if we make the handle + * table reference weak in order to break possible reference cycles. This field will ever only + * be set if the module's native definition provides a traverse function (see + * {@code moduleobject.c: module_traverse}). The condition for this is: + * + *

+     * {@code
+     * if (m -> md_def && m -> md_def -> m_traverse && (m -> md_def -> m_size <= 0 || m -> md_state != NULL)) {
+     *     // ...
+     * }
+     * }
+     * 
+ *

+ */ + Object[] replicatedNativeReferences; +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/PythonModule.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/PythonModule.java index 540bf27f23..3ccf91308f 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/PythonModule.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/PythonModule.java @@ -32,6 +32,7 @@ import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___NAME__; import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___PACKAGE__; import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___SPEC__; +import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.NULLPTR; import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.PythonBuiltinClassType; @@ -49,42 +50,7 @@ public final class PythonModule extends PythonObject { - /** - * Stores the native {@code PyModuleDef *} structure if this module was created via the - * multiphase extension module initialization mechanism. Will be non-null only for legacy definitions. - */ - private long nativeModuleDef; - private long nativeModuleState; - - private long nativeModuleStateSize; - private long nativeModuleTraverse; - private long nativeModuleClear; - private long nativeModuleFree; - - private long nativeModuleExec; // direct PySlot[] modules - private long nativeModuleToken; - private boolean nativeModuleTokenIsDef; - private boolean nativeModuleRequiresGil; - - /** - * Replicates the native references of this module's native state in Java. - *

- * Since a module can have a native module state where it is valid to store native references to - * other objects, we need this field to replicate those references in Java if we make the handle - * table reference weak in order to break possible reference cycles. This field will ever only - * be set if the module's native definition provides a traverse function (see - * {@code moduleobject.c: module_traverse}). The condition for this is: - * - *

-     * {@code
-     * if (m -> md_def && m -> md_def -> m_traverse && (m -> md_def -> m_size <= 0 || m -> md_state != NULL)) {
-     *     // ...
-     * }
-     * }
-     * 
- *

- */ - private Object[] replicatedNativeReferences; + private NativeModuleData nativeModuleData; private PythonBuiltins builtins; private Object moduleState; @@ -149,95 +115,102 @@ public String toString() { return ""; } + private NativeModuleData ensureNativeModuleData() { + if (nativeModuleData == null) { + nativeModuleData = new NativeModuleData(); + } + return nativeModuleData; + } + public long getNativeModuleDef() { - return nativeModuleDef; + return nativeModuleData != null ? nativeModuleData.def : NULLPTR; } public void setNativeModuleDef(long nativeModuleDef) { - this.nativeModuleDef = nativeModuleDef; + ensureNativeModuleData().def = nativeModuleDef; } public long getNativeModuleState() { - return nativeModuleState; + return nativeModuleData != null ? nativeModuleData.state : NULLPTR; } public void setNativeModuleState(long nativeModuleState) { - this.nativeModuleState = nativeModuleState; + ensureNativeModuleData().state = nativeModuleState; } public long getNativeModuleStateSize() { - return nativeModuleStateSize; + return nativeModuleData != null ? nativeModuleData.stateSize : 0; } public void setNativeModuleStateSize(long nativeModuleStateSize) { - this.nativeModuleStateSize = nativeModuleStateSize; + ensureNativeModuleData().stateSize = nativeModuleStateSize; } public long getNativeModuleTraverse() { - return nativeModuleTraverse; + return nativeModuleData != null ? nativeModuleData.stateTraverse : NULLPTR; } public void setNativeModuleTraverse(long nativeModuleTraverse) { - this.nativeModuleTraverse = nativeModuleTraverse; + ensureNativeModuleData().stateTraverse = nativeModuleTraverse; } public long getNativeModuleClear() { - return nativeModuleClear; + return nativeModuleData != null ? nativeModuleData.stateClear : NULLPTR; } public void setNativeModuleClear(long nativeModuleClear) { - this.nativeModuleClear = nativeModuleClear; + ensureNativeModuleData().stateClear = nativeModuleClear; } public long getNativeModuleFree() { - return nativeModuleFree; + return nativeModuleData != null ? nativeModuleData.stateFree : NULLPTR; } public void setNativeModuleFree(long nativeModuleFree) { - this.nativeModuleFree = nativeModuleFree; + ensureNativeModuleData().stateFree = nativeModuleFree; } public long getNativeModuleExec() { - return nativeModuleExec; + return nativeModuleData != null ? nativeModuleData.exec : NULLPTR; } public void setNativeModuleExec(long nativeModuleExec) { - this.nativeModuleExec = nativeModuleExec; + ensureNativeModuleData().exec = nativeModuleExec; } public long getNativeModuleToken() { - return nativeModuleToken; + return nativeModuleData != null ? nativeModuleData.token : NULLPTR; } public void setNativeModuleToken(long nativeModuleToken) { - this.nativeModuleToken = nativeModuleToken; + ensureNativeModuleData().token = nativeModuleToken; } public boolean isNativeModuleTokenIsDef() { - return nativeModuleTokenIsDef; + return nativeModuleData != null && nativeModuleData.tokenIsDef; } public void setNativeModuleTokenIsDef(boolean nativeModuleTokenIsDef) { - this.nativeModuleTokenIsDef = nativeModuleTokenIsDef; + ensureNativeModuleData().tokenIsDef = nativeModuleTokenIsDef; } public boolean isNativeModuleRequiresGil() { - return nativeModuleRequiresGil; + return nativeModuleData != null && nativeModuleData.requiresGil; } public void setNativeModuleRequiresGil(boolean nativeModuleRequiresGil) { - this.nativeModuleRequiresGil = nativeModuleRequiresGil; + ensureNativeModuleData().requiresGil = nativeModuleRequiresGil; } /** - * For a description, see {@link #replicatedNativeReferences}. + * For a description, see {@link NativeModuleData#replicatedNativeReferences}. */ public void setReplicatedNativeReferences(Object[] replicatedNativeReferences) { - this.replicatedNativeReferences = replicatedNativeReferences; + ensureNativeModuleData().replicatedNativeReferences = replicatedNativeReferences; } public Object[] getReplicatedNativeReferences() { - return this.replicatedNativeReferences; + return nativeModuleData != null ? nativeModuleData.replicatedNativeReferences : null; } public PDict getDict() { From e803fb46b1d451599de534748766c41bd5e7d473 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Fri, 11 Sep 2026 09:52:37 +0200 Subject: [PATCH 22/24] Gate experimental ABI3T API behind target macro --- abi/suppressions-graalpy253.ini | 4 ++++ .../com.oracle.graal.python.cext/include/Python.h | 8 +++++--- .../com.oracle.graal.python.cext/include/exports.h | 7 ++++--- .../include/modsupport.h | 4 ++-- .../include/moduleobject.h | 6 +++--- .../com.oracle.graal.python.cext/include/pymacro.h | 2 ++ .../include/pytypedefs.h | 3 ++- .../com.oracle.graal.python.cext/include/slots.h | 4 ++++ .../src/tests/cpyext/test_abi3t.py | 5 ++++- mx.graalpython/mx_graalpython.py | 13 +++++++++++++ 10 files changed, 43 insertions(+), 13 deletions(-) create mode 100644 abi/suppressions-graalpy253.ini diff --git a/abi/suppressions-graalpy253.ini b/abi/suppressions-graalpy253.ini new file mode 100644 index 0000000000..b2827350e6 --- /dev/null +++ b/abi/suppressions-graalpy253.ini @@ -0,0 +1,4 @@ +[suppress_function] + label = Experimental ABI3T functions + symbol_name_regexp = ^(PyABIInfo_Check|PyModule_Exec|PyModule_FromSlotsAndSpec|PyModule_GetStateSize|PyModule_GetState_DuringGC|PyModule_GetToken|PyModule_GetToken_DuringGC)$ + drop = true diff --git a/graalpython/com.oracle.graal.python.cext/include/Python.h b/graalpython/com.oracle.graal.python.cext/include/Python.h index f716f0d93f..7722a01768 100644 --- a/graalpython/com.oracle.graal.python.cext/include/Python.h +++ b/graalpython/com.oracle.graal.python.cext/include/Python.h @@ -73,7 +73,7 @@ struct timeval; // Include Python header files #include "pyport.h" -#ifdef _MSC_VER +#if (defined(Py_BUILD_CORE) || defined(Py_TARGET_ABI3T)) && defined(_MSC_VER) // Ignore MSC warning C4201: "nonstandard extension used: nameless // struct/union". (Only generated for C standard versions less than C11, which // we don't *officially* support.) @@ -91,7 +91,9 @@ __pragma(warning(disable: 4201)) #include "lock.h" #include "object.h" #include "objimpl.h" -#include "slots.h" +#if defined(Py_BUILD_CORE) || defined(Py_TARGET_ABI3T) +# include "slots.h" +#endif #include "typeslots.h" #include "pyhash.h" #include "cpython/pydebug.h" @@ -153,7 +155,7 @@ __pragma(warning(disable: 4201)) #include "cpython/pyfpe.h" #include "cpython/tracemalloc.h" -#ifdef _MSC_VER +#if (defined(Py_BUILD_CORE) || defined(Py_TARGET_ABI3T)) && defined(_MSC_VER) __pragma(warning(pop)) // warning(disable: 4201) #endif diff --git a/graalpython/com.oracle.graal.python.cext/include/exports.h b/graalpython/com.oracle.graal.python.cext/include/exports.h index 82621b74b2..be606ce139 100644 --- a/graalpython/com.oracle.graal.python.cext/include/exports.h +++ b/graalpython/com.oracle.graal.python.cext/include/exports.h @@ -14,7 +14,8 @@ inside the Python core, they are private to the core. If in an extension module, it may be declared with external linkage depending on the platform. - PyMODEXPORT_FUNC: Like PyMODINIT_FUNC, but for a slots array. + PyMODEXPORT_FUNC: Like PyMODINIT_FUNC, but for a slots array. This is only + available when experimental ABI3T support is enabled. As a number of platforms support/require "__declspec(dllimport/dllexport)", we support a HAVE_DECLSPEC_DLL macro to save duplication. @@ -109,13 +110,13 @@ # define PyMODINIT_FUNC Py_EXPORTED_SYMBOL PyObject* # endif /* __cplusplus */ #endif -#ifndef PyMODEXPORT_FUNC +#if defined(Py_TARGET_ABI3T) && !defined(PyMODEXPORT_FUNC) # if defined(__cplusplus) # define PyMODEXPORT_FUNC extern "C" Py_EXPORTED_SYMBOL PySlot* # else # define PyMODEXPORT_FUNC Py_EXPORTED_SYMBOL PySlot* # endif -#endif +#endif /* Py_TARGET_ABI3T && !PyMODEXPORT_FUNC */ #endif /* Py_EXPORTS_H */ diff --git a/graalpython/com.oracle.graal.python.cext/include/modsupport.h b/graalpython/com.oracle.graal.python.cext/include/modsupport.h index fd59b51fc2..acdde387ac 100644 --- a/graalpython/com.oracle.graal.python.cext/include/modsupport.h +++ b/graalpython/com.oracle.graal.python.cext/include/modsupport.h @@ -139,7 +139,7 @@ PyAPI_FUNC(PyObject *) PyModule_FromDefAndSpec2(PyModuleDef *def, #endif /* New in 3.5 */ -#if !defined(Py_LIMITED_API) || defined(Py_TARGET_ABI3T) || Py_LIMITED_API+0 >= 0x030f0000 +#if defined(Py_BUILD_CORE) || defined(Py_TARGET_ABI3T) typedef struct PyABIInfo { uint8_t abiinfo_major_version; uint8_t abiinfo_minor_version; @@ -169,7 +169,7 @@ PyAPI_FUNC(int) PyABIInfo_Check(PyABIInfo *info, const char *module_name); #define PyABIInfo_VAR(NAME) \ static PyABIInfo NAME = {1, 0, PyABIInfo_DEFAULT_FLAGS, PY_VERSION_HEX, PyABIInfo_DEFAULT_ABI_VERSION}; -#endif +#endif /* Py_BUILD_CORE || Py_TARGET_ABI3T */ #ifndef Py_LIMITED_API # define Py_CPYTHON_MODSUPPORT_H diff --git a/graalpython/com.oracle.graal.python.cext/include/moduleobject.h b/graalpython/com.oracle.graal.python.cext/include/moduleobject.h index 9497878ae4..e63556ca38 100644 --- a/graalpython/com.oracle.graal.python.cext/include/moduleobject.h +++ b/graalpython/com.oracle.graal.python.cext/include/moduleobject.h @@ -76,7 +76,7 @@ struct PyModuleDef_Slot { void *value; }; -#if defined(Py_TARGET_ABI3T) || (defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030f0000) +#if defined(Py_TARGET_ABI3T) # define Py_mod_create 84 # define Py_mod_exec 85 # define Py_mod_multiple_interpreters 86 @@ -127,14 +127,14 @@ struct PyModuleDef_Slot { PyAPI_FUNC(int) PyUnstable_Module_SetGIL(PyObject *module, void *gil); #endif -#if !defined(Py_LIMITED_API) || defined(Py_TARGET_ABI3T) || Py_LIMITED_API+0 >= 0x030f0000 +#if defined(Py_BUILD_CORE) || defined(Py_TARGET_ABI3T) PyAPI_FUNC(PyObject *) PyModule_FromSlotsAndSpec(const PySlot *slots, PyObject *spec); PyAPI_FUNC(int) PyModule_Exec(PyObject *module); PyAPI_FUNC(int) PyModule_GetStateSize(PyObject *module, Py_ssize_t *result); PyAPI_FUNC(int) PyModule_GetToken(PyObject *module, void **result); PyAPI_FUNC(void *) PyModule_GetState_DuringGC(PyObject *module); PyAPI_FUNC(int) PyModule_GetToken_DuringGC(PyObject *module, void **result); -#endif +#endif /* Py_BUILD_CORE || Py_TARGET_ABI3T */ struct PyModuleDef { PyModuleDef_Base m_base; diff --git a/graalpython/com.oracle.graal.python.cext/include/pymacro.h b/graalpython/com.oracle.graal.python.cext/include/pymacro.h index f3167305bf..7dcd927547 100644 --- a/graalpython/com.oracle.graal.python.cext/include/pymacro.h +++ b/graalpython/com.oracle.graal.python.cext/include/pymacro.h @@ -28,6 +28,7 @@ # define static_assert _Static_assert #endif +#if defined(Py_BUILD_CORE) || defined(Py_TARGET_ABI3T) // GraalPy change: added _Py_ANONYMOUS from 3.15 // _Py_ANONYMOUS: modifier for declaring an anonymous union. // Usage: _Py_ANONYMOUS union { ... }; @@ -48,6 +49,7 @@ # define _Py_ANONYMOUS # endif #endif +#endif /* Py_BUILD_CORE || Py_TARGET_ABI3T */ /* Minimum value between x and y */ #define Py_MIN(x, y) (((x) > (y)) ? (y) : (x)) diff --git a/graalpython/com.oracle.graal.python.cext/include/pytypedefs.h b/graalpython/com.oracle.graal.python.cext/include/pytypedefs.h index e36df33333..c96dd6498d 100644 --- a/graalpython/com.oracle.graal.python.cext/include/pytypedefs.h +++ b/graalpython/com.oracle.graal.python.cext/include/pytypedefs.h @@ -19,8 +19,9 @@ typedef struct PyModuleDef_Slot PyModuleDef_Slot; typedef struct PyMethodDef PyMethodDef; typedef struct PyGetSetDef PyGetSetDef; typedef struct PyMemberDef PyMemberDef; -// GraalPy change: added _Py_ANONYMOUS from 3.15 +#if defined(Py_BUILD_CORE) || defined(Py_TARGET_ABI3T) typedef struct PySlot PySlot; +#endif typedef struct _object PyObject; typedef struct _longobject PyLongObject; diff --git a/graalpython/com.oracle.graal.python.cext/include/slots.h b/graalpython/com.oracle.graal.python.cext/include/slots.h index 89533c39c8..408966e8eb 100644 --- a/graalpython/com.oracle.graal.python.cext/include/slots.h +++ b/graalpython/com.oracle.graal.python.cext/include/slots.h @@ -6,6 +6,10 @@ #ifndef _Py_HAVE_SLOTS_H #define _Py_HAVE_SLOTS_H +#if !defined(Py_BUILD_CORE) && !defined(Py_TARGET_ABI3T) +# error "slots.h requires Py_TARGET_ABI3T" +#endif + typedef void (*_Py_funcptr_t)(void); struct PySlot { diff --git a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abi3t.py b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abi3t.py index ff0837f401..2c4c768e9f 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abi3t.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abi3t.py @@ -151,7 +151,10 @@ """ -@unittest.skipUnless(sys.implementation.name == "graalpy", "requires GraalPy") +@unittest.skipUnless( + sys.implementation.name == "graalpy" and os.environ.get("GRAALPY_TEST_ABI3T"), + "requires the GraalPy ABI3T opt-in test run", +) class TestAbi3t(unittest.TestCase): def test_option_exposed(self): code = "import __graalpython__; print(__graalpython__.abi3t_enabled)" diff --git a/mx.graalpython/mx_graalpython.py b/mx.graalpython/mx_graalpython.py index acc8ba615f..79fa732f32 100644 --- a/mx.graalpython/mx_graalpython.py +++ b/mx.graalpython/mx_graalpython.py @@ -2000,6 +2000,19 @@ def graalpython_gate_runner(_, tasks): parallel=6, ) + with Task('GraalPython ABI3T unittests', tasks, tags=[GraalPythonTags.unittest]) as task: + if task: + env = os.environ.copy() + env['GRAALPY_TEST_ABI3T'] = 'true' + run_python_unittests( + graalpy_standalone_jvm(), + paths=['graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abi3t.py'], + env=env, + nonZeroIsFatal=nonZeroIsFatal, + report=report(), + parallel=0, + ) + with Task('GraalPython BouncyCastle unittests', tasks, tags=[GraalPythonTags.unittest_bouncycastle]) as task: if task: bc_unit_tests = [ From ca720b56a297240634a0c6547a3ebb53001c45f2 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Fri, 11 Sep 2026 10:04:56 +0200 Subject: [PATCH 23/24] Make CApiContext.gcTask private --- .../graal/python/builtins/objects/cext/capi/CApiContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java index 48994b5a2e..e9389454b3 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java @@ -350,7 +350,7 @@ public TruffleString getModExecFunctionName() { private final NativeLibraryLocator nativeLibraryLocator; - public final BackgroundGCTask gcTask; + private final BackgroundGCTask gcTask; private Thread backgroundGCTaskThread; private final ReferenceQueueWatcherTask referenceQueueWatcherTask; From 2b5582d7fac87bb0c0bbee52bf1147df7512e904 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Thu, 24 Sep 2026 11:38:10 +0200 Subject: [PATCH 24/24] Disable flaky test_multiprocessing_spawn.test_large_pool linux-aarch64 --- .../src/tests/unittest_tags/test_multiprocessing_spawn.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_multiprocessing_spawn.txt b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_multiprocessing_spawn.txt index 6fa3218f57..ac1be2a6b4 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_multiprocessing_spawn.txt +++ b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_multiprocessing_spawn.txt @@ -60,7 +60,7 @@ test.test_multiprocessing_spawn.test_manager.WithManagerTestSemaphore.test_bound test.test_multiprocessing_spawn.test_manager.WithManagerTestSemaphore.test_semaphore @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64-github test.test_multiprocessing_spawn.test_misc.ChallengeResponseTest.test_challengeresponse @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_multiprocessing_spawn.test_misc.MiscTestCase.test__all__ @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github -test.test_multiprocessing_spawn.test_misc.MiscTestCase.test_large_pool @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64-github +test.test_multiprocessing_spawn.test_misc.MiscTestCase.test_large_pool @ darwin-arm64,linux-aarch64-github,linux-x86_64-github test.test_multiprocessing_spawn.test_misc.MiscTestCase.test_spawn_sys_executable_none_allows_import @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_multiprocessing_spawn.test_misc.OtherTest.test_answer_challenge_auth_failure @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_multiprocessing_spawn.test_misc.OtherTest.test_deliver_challenge_auth_failure @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64