-
Notifications
You must be signed in to change notification settings - Fork 780
Expand file tree
/
Copy pathABI.cs
More file actions
108 lines (89 loc) · 4.17 KB
/
Copy pathABI.cs
File metadata and controls
108 lines (89 loc) · 4.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
using static System.FormattableString;
using System;
using System.Linq;
using System.Reflection;
namespace Python.Runtime.Native
{
static class ABI
{
// GIL builds only. FT splits the refcount; Refcount uses Py_REFCNT.
public static int RefCountOffset { get; private set; }
// Added to generated TypeOffsets. The FT header is 12 bytes larger on
// 32-bit and 16 bytes larger on 64-bit builds.
public static int ObjectHeadOffset { get; private set; }
public static bool IsFreeThreaded { get; private set; }
internal static void Initialize(Version version)
{
IsFreeThreaded = DetectFreeThreaded();
if (IsFreeThreaded && version < new Version(3, 14))
{
// We need Py_REFCNT to be available as a symbol, which is only
// present in Python 3.14 and later.
throw new NotSupportedException($"Python ABI v{version} is not supported: Free-threaded builds are only supported on Python 3.14 and later.");
}
RefCountOffset = IsFreeThreaded ? -1 : ProbeRefCountOffset();
ObjectHeadOffset = IsFreeThreaded
? (IntPtr.Size == 4 ? 12 : 16)
: RefCountOffset;
var thisAssembly = Assembly.GetExecutingAssembly();
const string nativeTypeOffsetClassName = "Python.Runtime.NativeTypeOffset";
string className = Invariant($"Python.Runtime.TypeOffset{version.Major}{version.Minor}");
Type nativeOffsetsClass = thisAssembly.GetType(nativeTypeOffsetClassName, throwOnError: false);
Type typeOffsetsClass =
// Try platform native offsets first. It is only present when generated by setup.py
nativeOffsetsClass ?? thisAssembly.GetType(className, throwOnError: false);
if (typeOffsetsClass is null)
{
var types = thisAssembly.GetTypes().Select(type => type.Name).Where(name => name.StartsWith("TypeOffset"));
string message = $"Searching for {className}, found {string.Join(",", types)}.";
throw new NotSupportedException($"Python ABI v{version} is not supported: {message}");
}
var typeOffsets = (ITypeOffsets)Activator.CreateInstance(typeOffsetsClass);
TypeOffset.Use(typeOffsets, nativeOffsetsClass == null ? ObjectHeadOffset : 0);
}
/// <summary>
/// True when the running interpreter is a Py_GIL_DISABLED (free-threaded) build.
/// </summary>
static bool DetectFreeThreaded()
{
// sys._is_gil_enabled() was added in Python 3.13; absent means GIL build.
using var sys = Runtime.PyImport_ImportModule("sys");
if (sys.IsNull())
{
Runtime.PyErr_Clear();
return false;
}
using var func = Runtime.PyObject_GetAttrString(sys.Borrow(), "_is_gil_enabled");
if (func.IsNull())
{
Runtime.PyErr_Clear();
return false;
}
using var args = Runtime.PyTuple_New(0);
using var result = Runtime.PyObject_Call(func.Borrow(), args.Borrow(), default);
if (result.IsNull())
{
Runtime.PyErr_Clear();
return false;
}
return Runtime.PyObject_IsTrue(result.Borrow()) == 0;
}
/// <summary>
/// Locates the offset of <c>ob_refcnt</c> inside <c>PyObject</c> by allocating a
/// fresh list and scanning for its refcount value of 1. GIL builds only.
/// </summary>
static unsafe int ProbeRefCountOffset()
{
using var tempObject = Runtime.PyList_New(0);
IntPtr* tempPtr = (IntPtr*)tempObject.DangerousGetAddress();
int offset = 0;
while (tempPtr[offset] != (IntPtr)1)
{
offset++;
if (offset > 100)
throw new InvalidProgramException("PyObject_HEAD could not be found withing reasonable distance from the start of PyObject");
}
return offset * IntPtr.Size;
}
}
}