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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