Skip to content

Commit 689f0d8

Browse files
committed
detect Py_TRACE_REFS at runtime and calculate object offsets accordingly
1 parent 23527d1 commit 689f0d8

File tree

7 files changed

+108
-110
lines changed

7 files changed

+108
-110
lines changed

src/runtime/classderived.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,7 @@ internal static IntPtr ToPython(IPythonDerivedType obj)
101101
// collected while Python still has a reference to it.
102102
if (Runtime.Refcount(self.pyHandle) == 1)
103103
{
104-
105-
#if PYTHON_WITH_PYDEBUG
106-
Runtime._Py_NewReference(self.pyHandle);
107-
#endif
104+
Runtime._Py_NewReference(self.ObjectReference);
108105
GCHandle gc = GCHandle.Alloc(self, GCHandleType.Normal);
109106
Marshal.WriteIntPtr(self.pyHandle, ObjectOffset.magic(self.tpHandle), (IntPtr)gc);
110107
self.gcHandle.Free();

src/runtime/converter.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,16 @@ internal static bool ToManagedValue(IntPtr value, Type obType,
525525

526526
internal delegate bool TryConvertFromPythonDelegate(IntPtr pyObj, out object result);
527527

528+
internal static int ToInt32(BorrowedReference value)
529+
{
530+
nint num = Runtime.PyLong_AsSignedSize_t(value);
531+
if (num == -1 && Exceptions.ErrorOccurred())
532+
{
533+
throw new PythonException();
534+
}
535+
return checked((int)num);
536+
}
537+
528538
/// <summary>
529539
/// Convert a Python value to an instance of a primitive managed type.
530540
/// </summary>

src/runtime/interop.cs

Lines changed: 6 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ internal static partial class TypeOffset
7676
internal static class ManagedDataOffsets
7777
{
7878
public static int Magic { get; internal set; }
79-
public static readonly Dictionary<string, int> NameMapping = new Dictionary<string, int>();
8079

8180
static class DataOffsets
8281
{
@@ -95,17 +94,10 @@ static DataOffsets()
9594

9695
static ManagedDataOffsets()
9796
{
98-
NameMapping = TypeOffset.GetOffsets();
99-
10097
FieldInfo[] fields = typeof(DataOffsets).GetFields(BindingFlags.Static | BindingFlags.Public);
10198
size = fields.Length * IntPtr.Size;
10299
}
103100

104-
public static int GetSlotOffset(string name)
105-
{
106-
return NameMapping[name];
107-
}
108-
109101
private static int BaseOffset(IntPtr type)
110102
{
111103
Debug.Assert(type != IntPtr.Zero);
@@ -135,30 +127,15 @@ internal static class OriginalObjectOffsets
135127
{
136128
static OriginalObjectOffsets()
137129
{
138-
int size = IntPtr.Size;
139-
var n = 0; // Py_TRACE_REFS add two pointers to PyObject_HEAD
140-
#if PYTHON_WITH_PYDEBUG
141-
_ob_next = 0;
142-
_ob_prev = 1 * size;
143-
n = 2;
144-
#endif
145-
ob_refcnt = (n + 0) * size;
146-
ob_type = (n + 1) * size;
130+
ob_refcnt = Native.ABI.ObjectHeadOffset;
131+
ob_type = ob_refcnt + IntPtr.Size;
132+
size = ob_type + IntPtr.Size;
147133
}
148134

149135
public static int Size { get { return size; } }
150136

151-
private static readonly int size =
152-
#if PYTHON_WITH_PYDEBUG
153-
4 * IntPtr.Size;
154-
#else
155-
2 * IntPtr.Size;
156-
#endif
157-
158-
#if PYTHON_WITH_PYDEBUG
159-
public static int _ob_next;
160-
public static int _ob_prev;
161-
#endif
137+
private static readonly int size;
138+
162139
public static int ob_refcnt;
163140
public static int ob_type;
164141
}
@@ -168,10 +145,6 @@ internal class ObjectOffset
168145
{
169146
static ObjectOffset()
170147
{
171-
#if PYTHON_WITH_PYDEBUG
172-
_ob_next = OriginalObjectOffsets._ob_next;
173-
_ob_prev = OriginalObjectOffsets._ob_prev;
174-
#endif
175148
ob_refcnt = OriginalObjectOffsets.ob_refcnt;
176149
ob_type = OriginalObjectOffsets.ob_type;
177150

@@ -198,10 +171,6 @@ public static int Size(IntPtr pyType)
198171
return size;
199172
}
200173

201-
#if PYTHON_WITH_PYDEBUG
202-
public static int _ob_next;
203-
public static int _ob_prev;
204-
#endif
205174
public static int ob_refcnt;
206175
public static int ob_type;
207176
private static readonly int size;
@@ -256,17 +225,12 @@ static BytesOffset()
256225
int size = IntPtr.Size;
257226
for (int i = 0; i < fi.Length; i++)
258227
{
259-
fi[i].SetValue(null, i * size);
228+
fi[i].SetValue(null, i * size + Native.ABI.ObjectHeadOffset);
260229
}
261230
}
262231

263232
/* The *real* layout of a type object when allocated on the heap */
264233
//typedef struct _heaptypeobject {
265-
#if PYTHON_WITH_PYDEBUG
266-
/* _PyObject_HEAD_EXTRA defines pointers to support a doubly-linked list of all live heap objects. */
267-
public static int _ob_next = 0;
268-
public static int _ob_prev = 0;
269-
#endif
270234
// PyObject_VAR_HEAD {
271235
// PyObject_HEAD {
272236
public static int ob_refcnt = 0;

src/runtime/native/ABI.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ namespace Python.Runtime.Native
88

99
static class ABI
1010
{
11+
public static int ObjectHeadOffset { get; } = GetRefCountOffset();
12+
1113
internal static void Initialize(Version version, BorrowedReference pyType)
1214
{
1315
string offsetsClassSuffix = string.Format(CultureInfo.InvariantCulture,
@@ -17,20 +19,36 @@ internal static void Initialize(Version version, BorrowedReference pyType)
1719

1820
const string nativeTypeOffsetClassName = "Python.Runtime.NativeTypeOffset";
1921
string className = "Python.Runtime.TypeOffset" + offsetsClassSuffix;
22+
Type nativeOffsetsClass = thisAssembly.GetType(nativeTypeOffsetClassName, throwOnError: false);
2023
Type typeOffsetsClass =
2124
// Try platform native offsets first. It is only present when generated by setup.py
22-
thisAssembly.GetType(nativeTypeOffsetClassName, throwOnError: false)
23-
?? thisAssembly.GetType(className, throwOnError: false);
25+
nativeOffsetsClass ?? thisAssembly.GetType(className, throwOnError: false);
2426
if (typeOffsetsClass is null)
2527
{
2628
var types = thisAssembly.GetTypes().Select(type => type.Name).Where(name => name.StartsWith("TypeOffset"));
2729
string message = $"Searching for {className}, found {string.Join(",", types)}.";
2830
throw new NotSupportedException($"Python ABI v{version} is not supported: {message}");
2931
}
32+
3033
var typeOffsets = (ITypeOffsets)Activator.CreateInstance(typeOffsetsClass);
31-
TypeOffset.Use(typeOffsets);
34+
TypeOffset.Use(typeOffsets, nativeOffsetsClass == null ? ObjectHeadOffset : 0);
3235

3336
ManagedDataOffsets.Magic = Marshal.ReadInt32(pyType.DangerousGetAddress(), TypeOffset.tp_basicsize);
3437
}
38+
39+
static unsafe int GetRefCountOffset()
40+
{
41+
IntPtr tempObject = Runtime.PyList_New(0);
42+
IntPtr* tempPtr = (IntPtr*)tempObject;
43+
int offset = 0;
44+
while(tempPtr[offset] != (IntPtr)1)
45+
{
46+
offset++;
47+
if (offset > 100)
48+
throw new InvalidProgramException("PyObject_HEAD could not be found withing reasonable distance from the start of PyObject");
49+
}
50+
Runtime.XDecref(tempObject);
51+
return offset * IntPtr.Size;
52+
}
3553
}
3654
}

src/runtime/native/TypeOffset.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ static partial class TypeOffset
7474
internal static int tp_str { get; private set; }
7575
internal static int tp_traverse { get; private set; }
7676

77-
internal static void Use(ITypeOffsets offsets)
77+
internal static void Use(ITypeOffsets offsets, int extraHeadOffset)
7878
{
7979
if (offsets is null) throw new ArgumentNullException(nameof(offsets));
8080

@@ -86,9 +86,12 @@ internal static void Use(ITypeOffsets offsets)
8686

8787
var sourceProperty = typeof(ITypeOffsets).GetProperty(offsetProperty.Name);
8888
int value = (int)sourceProperty.GetValue(offsets, null);
89+
value += extraHeadOffset;
8990
offsetProperty.SetValue(obj: null, value: value, index: null);
9091
}
9192

93+
NameMapping = GetOffsets();
94+
9295
ValidateUnusedTypeOffsetProperties(offsetProperties);
9396
ValidateRequiredOffsetsPresent(offsetProperties);
9497
}
@@ -167,5 +170,12 @@ static void ValidateRequiredOffsetsPresent(PropertyInfo[] offsetProperties)
167170
Debug.Assert(missing.Count == 0,
168171
"Missing slots: " + string.Join(", ", missing));
169172
}
173+
174+
static Dictionary<string, int> NameMapping;
175+
176+
public static int GetSlotOffset(string name)
177+
{
178+
return NameMapping[name];
179+
}
170180
}
171181
}

0 commit comments

Comments
 (0)