diff --git a/src/runtime/interop.cs b/src/runtime/interop.cs index 95f3e5b9f..a56fd4a95 100644 --- a/src/runtime/interop.cs +++ b/src/runtime/interop.cs @@ -164,6 +164,11 @@ public static int TypeDictOffset(IntPtr type) return ManagedDataOffsets.DictOffset(type); } + public static int Size() + { + return size; + } + public static int Size(IntPtr pyType) { if (IsException(pyType)) diff --git a/src/runtime/interop34.cs b/src/runtime/interop34.cs index 6857ff2d0..178e62c92 100644 --- a/src/runtime/interop34.cs +++ b/src/runtime/interop34.cs @@ -131,9 +131,9 @@ public static int magic() public static int sq_inplace_repeat = 0; public static int bf_getbuffer = 0; public static int bf_releasebuffer = 0; - public static int name = 0; + public static int ht_name = 0; public static int ht_slots = 0; - public static int qualname = 0; + public static int ht_qualname = 0; public static int ht_cached_keys = 0; /* here are optional user slots, followed by the members. */ diff --git a/src/runtime/interop35.cs b/src/runtime/interop35.cs index a30bfa4fd..a8397a3bd 100644 --- a/src/runtime/interop35.cs +++ b/src/runtime/interop35.cs @@ -136,9 +136,9 @@ public static int magic() public static int sq_inplace_repeat = 0; public static int bf_getbuffer = 0; public static int bf_releasebuffer = 0; - public static int name = 0; + public static int ht_name = 0; public static int ht_slots = 0; - public static int qualname = 0; + public static int ht_qualname = 0; public static int ht_cached_keys = 0; /* here are optional user slots, followed by the members. */ diff --git a/src/runtime/interop36.cs b/src/runtime/interop36.cs index c46bcc2f5..7ba02e202 100644 --- a/src/runtime/interop36.cs +++ b/src/runtime/interop36.cs @@ -136,9 +136,9 @@ public static int magic() public static int sq_inplace_repeat = 0; public static int bf_getbuffer = 0; public static int bf_releasebuffer = 0; - public static int name = 0; + public static int ht_name = 0; public static int ht_slots = 0; - public static int qualname = 0; + public static int ht_qualname = 0; public static int ht_cached_keys = 0; /* here are optional user slots, followed by the members. */ diff --git a/src/runtime/interop37.cs b/src/runtime/interop37.cs index d5fc76ad3..34f92f56d 100644 --- a/src/runtime/interop37.cs +++ b/src/runtime/interop37.cs @@ -136,9 +136,9 @@ public static int magic() public static int sq_inplace_repeat = 0; public static int bf_getbuffer = 0; public static int bf_releasebuffer = 0; - public static int name = 0; + public static int ht_name = 0; public static int ht_slots = 0; - public static int qualname = 0; + public static int ht_qualname = 0; public static int ht_cached_keys = 0; /* here are optional user slots, followed by the members. */ diff --git a/src/runtime/interop38.cs b/src/runtime/interop38.cs index 9126bca6a..44cdcec40 100644 --- a/src/runtime/interop38.cs +++ b/src/runtime/interop38.cs @@ -139,9 +139,9 @@ public static int magic() public static int sq_inplace_repeat = 0; public static int bf_getbuffer = 0; public static int bf_releasebuffer = 0; - public static int name = 0; + public static int ht_name = 0; public static int ht_slots = 0; - public static int qualname = 0; + public static int ht_qualname = 0; public static int ht_cached_keys = 0; /* here are optional user slots, followed by the members. */ diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index f63b1feae..350696ded 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1726,6 +1726,9 @@ internal static IntPtr PyType_GenericAlloc(IntPtr type, long n) return PyType_GenericAlloc(type, new IntPtr(n)); } + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern NewReference PyType_FromSpec(ref TypeManager.PyTypeSpec spec); + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr PyType_GenericAlloc(IntPtr type, IntPtr n); diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index 04d40a2ba..167c6d541 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -81,23 +81,24 @@ internal static IntPtr GetTypeHandle(ManagedType obj, Type type) /// internal static IntPtr CreateType(Type impl) { - IntPtr type = AllocateTypeObject(impl.Name); - int ob_size = ObjectOffset.Size(type); + var slotArray = CreateSlotArray(impl); + int flags = TypeFlags.Default | TypeFlags.Managed | + TypeFlags.HeapType | TypeFlags.HaveGC; - // Set tp_basicsize to the size of our managed instance objects. - Marshal.WriteIntPtr(type, TypeOffset.tp_basicsize, (IntPtr)ob_size); + IntPtr type = CreateTypeObject(impl.Name, ObjectOffset.Size(), flags, slotArray); + + if (ObjectOffset.Size() != ObjectOffset.Size(type)) + { + //should we reset the size and call PyType_Ready again?? + //how do we deal with the fact that size is based on whether + //the type is an exception type. Should CreateSlotArray + //return a tuple with both the slot array and a flag on + //whether the type array describes an exception or not? + } var offset = (IntPtr)ObjectOffset.TypeDictOffset(type); Marshal.WriteIntPtr(type, TypeOffset.tp_dictoffset, offset); - InitializeSlots(type, impl); - - int flags = TypeFlags.Default | TypeFlags.Managed | - TypeFlags.HeapType | TypeFlags.HaveGC; - Util.WriteCLong(type, TypeOffset.tp_flags, flags); - - Runtime.PyType_Ready(type); - IntPtr dict = Marshal.ReadIntPtr(type, TypeOffset.tp_dict); IntPtr mod = Runtime.PyString_FromString("CLR"); Runtime.PyDict_SetItemString(dict, "__module__", mod); @@ -202,6 +203,17 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType) return type; } + static PY_TYPE_SLOT InitializeSlot(TypeSlots slotNumber, MethodInfo method) + { + var thunk = Interop.GetThunk(method); + return new PY_TYPE_SLOT { slot = slotNumber, func = thunk.Address}; + } + + static PY_TYPE_SLOT InitializeSlot(TypeSlots slotNumber, IntPtr thunk) + { + return new PY_TYPE_SLOT { slot = slotNumber, func = thunk }; + } + static void InitializeSlot(IntPtr type, int slotOffset, MethodInfo method) { var thunk = Interop.GetThunk(method); @@ -422,6 +434,163 @@ internal static IntPtr BasicSubType(string name, IntPtr base_, Type impl) return type; } + internal enum TypeSlots : int + { + bf_getbuffer = 1, + bf_releasebuffer = 2, + mp_ass_subscript = 3, + mp_length = 4, + mp_subscript = 5, + nb_absolute = 6, + nb_add = 7, + nb_and = 8, + nb_bool = 9, + nb_divmod = 10, + nb_float = 11, + nb_floor_divide = 12, + nb_index = 13, + nb_inplace_add = 14, + nb_inplace_and = 15, + nb_inplace_floor_divide = 16, + nb_inplace_lshift = 17, + nb_inplace_multiply = 18, + nb_inplace_or = 19, + nb_inplace_power = 20, + nb_inplace_remainder = 21, + nb_inplace_rshift = 22, + nb_inplace_subtract = 23, + nb_inplace_true_divide = 24, + nb_inplace_xor = 25, + nb_int = 26, + nb_invert = 27, + nb_lshift = 28, + nb_multiply = 29, + nb_negative = 30, + nb_or = 31, + nb_positive = 32, + nb_power = 33, + nb_remainder = 34, + nb_rshift = 35, + nb_subtract = 36, + nb_true_divide = 37, + nb_xor = 38, + sq_ass_item = 39, + sq_concat = 40, + sq_contains = 41, + sq_inplace_concat = 42, + sq_inplace_repeat = 43, + sq_item = 44, + sq_length = 45, + sq_repeat = 46, + tp_alloc = 47, + tp_base = 48, + tp_bases = 49, + tp_call = 50, + tp_clear = 51, + tp_dealloc = 52, + tp_del = 53, + tp_descr_get = 54, + tp_descr_set = 55, + tp_doc = 56, + tp_getattr = 57, + tp_getattro = 58, + tp_hash = 59, + tp_init = 60, + tp_is_gc = 61, + tp_iter = 62, + tp_iternext = 63, + tp_methods = 64, + tp_new = 65, + tp_repr = 66, + tp_richcompare = 67, + tp_setattr = 68, + tp_setattro = 69, + tp_str = 70, + tp_traverse = 71, + tp_members = 72, + tp_getset = 73, + tp_free = 74, + nb_matrix_multiply = 75, + nb_inplace_matrix_multiply = 76, + am_await = 77, + am_aiter = 78, + am_anext = 79, + tp_finalize = 80, + } + + private static TypeSlots getSlotNumber(string methodName) + { + return (TypeSlots)Enum.Parse(typeof(TypeSlots), methodName); + } + + [StructLayout(LayoutKind.Sequential)] + internal struct PY_TYPE_SLOT + { + internal TypeSlots slot; //slot id, from typeslots.h + internal IntPtr func; //function pointer of the function implementing the slot + } + + [StructLayout(LayoutKind.Sequential)] + internal struct PyTypeSpec + { + public IntPtr Name; + public int BasicSize; + public int ItemSize; + public int Flags; + public IntPtr Slots; + } + + internal static IntPtr CreateTypeObject(string name, int obSize, int obFlags, PY_TYPE_SLOT[] type_slots) + { + //convert type slot array to PyType_Slot* + int structSize = Marshal.SizeOf(typeof(PY_TYPE_SLOT)); + GCHandle pinnedArray = GCHandle.Alloc(type_slots, GCHandleType.Pinned); + IntPtr slotsPtr = pinnedArray.AddrOfPinnedObject(); + + //convert name to char* + byte[] ascii = System.Text.Encoding.ASCII.GetBytes(name); + GCHandle pinnedName = GCHandle.Alloc(ascii, GCHandleType.Pinned); + IntPtr namePtr = pinnedName.AddrOfPinnedObject(); + + var typeSpec = new PyTypeSpec + { + Name = namePtr, + BasicSize = obSize, + ItemSize = 0, + Flags = obFlags, + Slots = slotsPtr + }; + + var type = Runtime.PyType_FromSpec(ref typeSpec).DangerousGetAddress(); + pinnedArray.Free(); + pinnedName.Free(); + + // Cheat a little: we'll set tp_name to the internal char * of + // the Python version of the type name - otherwise we'd have to + // allocate the tp_name and would have no way to free it. + IntPtr temp = Runtime.PyUnicode_FromString(name); + IntPtr raw = Runtime.PyUnicode_AsUTF8(temp); + Marshal.WriteIntPtr(type, TypeOffset.tp_name, raw); + Marshal.WriteIntPtr(type, TypeOffset.ht_name, temp); + + Marshal.WriteIntPtr(type, TypeOffset.ht_qualname, temp); + + long ptr = type.ToInt64(); // 64-bit safe + + temp = new IntPtr(ptr + TypeOffset.nb_add); + Marshal.WriteIntPtr(type, TypeOffset.tp_as_number, temp); + + temp = new IntPtr(ptr + TypeOffset.sq_length); + Marshal.WriteIntPtr(type, TypeOffset.tp_as_sequence, temp); + + temp = new IntPtr(ptr + TypeOffset.mp_length); + Marshal.WriteIntPtr(type, TypeOffset.tp_as_mapping, temp); + + temp = new IntPtr(ptr + TypeOffset.bf_getbuffer); + Marshal.WriteIntPtr(type, TypeOffset.tp_as_buffer, temp); + + return type; + } /// /// Utility method to allocate a type object & do basic initialization. @@ -436,9 +605,9 @@ internal static IntPtr AllocateTypeObject(string name) IntPtr temp = Runtime.PyUnicode_FromString(name); IntPtr raw = Runtime.PyUnicode_AsUTF8(temp); Marshal.WriteIntPtr(type, TypeOffset.tp_name, raw); - Marshal.WriteIntPtr(type, TypeOffset.name, temp); + Marshal.WriteIntPtr(type, TypeOffset.ht_name, temp); - Marshal.WriteIntPtr(type, TypeOffset.qualname, temp); + Marshal.WriteIntPtr(type, TypeOffset.ht_qualname, temp); long ptr = type.ToInt64(); // 64-bit safe @@ -680,6 +849,79 @@ internal static void InitializeNativeCodePage() } #endregion + /// + /// Given a managed Type that provides the implementation for the type, + /// create a PY_TYPE_SLOT array to be used for PyType_FromSpec. + /// + internal static PY_TYPE_SLOT[] CreateSlotArray(Type impl) + { + // We work from the most-derived class up; make sure to get + // the most-derived slot and not to override it with a base + // class's slot. + var seen = new HashSet(); + var typeslots = new List(); + + while (impl != null) + { + MethodInfo[] methods = impl.GetMethods(tbFlags); + foreach (MethodInfo method in methods) + { + string name = method.Name; + if (!(name.StartsWith("tp_") || + name.StartsWith("nb_") || + name.StartsWith("sq_") || + name.StartsWith("mp_") || + name.StartsWith("bf_") + )) + { + continue; + } + + if (seen.Contains(name)) + { + continue; + } + + typeslots.Add(InitializeSlot(getSlotNumber(name), method)); + seen.Add(name); + } + + impl = impl.BaseType; + } + + var native = NativeCode.Active; + + // The garbage collection related slots always have to return 1 or 0 + // since .NET objects don't take part in Python's gc: + // tp_traverse (returns 0) + // tp_clear (returns 0) + // tp_is_gc (returns 1) + // These have to be defined, though, so by default we fill these with + // static C# functions from this class. + + var ret0 = Interop.GetThunk(((Func)Return0).Method).Address; + var ret1 = Interop.GetThunk(((Func)Return1).Method).Address; + + if (native != null) + { + // If we want to support domain reload, the C# implementation + // cannot be used as the assembly may get released before + // CPython calls these functions. Instead, for amd64 and x86 we + // load them into a separate code page that is leaked + // intentionally. + InitializeNativeCodePage(); + ret1 = NativeCodePage + native.Return1; + ret0 = NativeCodePage + native.Return0; + } + + typeslots.Add(InitializeSlot(getSlotNumber("tp_traverse"), ret0)); + typeslots.Add(InitializeSlot(getSlotNumber("tp_clear"), ret0)); + typeslots.Add(InitializeSlot(getSlotNumber("tp_is_gc"), ret1)); + + typeslots.Add(new PY_TYPE_SLOT { slot = 0, func = IntPtr.Zero }); + return typeslots.ToArray(); + } + /// /// Given a newly allocated Python type object and a managed Type that /// provides the implementation for the type, connect the type slots of