diff --git a/src/embed_tests/TestDomainReload.cs b/src/embed_tests/TestDomainReload.cs index 3556df0f6..4c9de1461 100644 --- a/src/embed_tests/TestDomainReload.cs +++ b/src/embed_tests/TestDomainReload.cs @@ -203,13 +203,13 @@ def test_obj_call(): // Create a new module IntPtr module = PyRuntime.PyModule_New(name); Assert.That(module != IntPtr.Zero); - IntPtr globals = PyRuntime.PyObject_GetAttrString(module, "__dict__"); + IntPtr globals = PyRuntime.PyObject_GetAttr(module, PyIdentifier.__dict__); Assert.That(globals != IntPtr.Zero); try { // import builtins // module.__dict__[__builtins__] = builtins - int res = PyRuntime.PyDict_SetItemString(globals, "__builtins__", + int res = PyRuntime.PyDict_SetItem(globals, PyIdentifier.__builtins__, PyRuntime.PyEval_GetBuiltins()); PythonException.ThrowIfIsNotZero(res); diff --git a/src/runtime/Python.Runtime.15.csproj b/src/runtime/Python.Runtime.15.csproj index d1e959196..4ca8140e9 100644 --- a/src/runtime/Python.Runtime.15.csproj +++ b/src/runtime/Python.Runtime.15.csproj @@ -143,6 +143,22 @@ + + + TextTemplatingFileGenerator + intern_.cs + + + + + + + True + True + intern_.tt + + + diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index a448e2bbd..a11a4b852 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -82,6 +82,8 @@ + + Properties\SharedAssemblyInfo.cs @@ -89,7 +91,7 @@ - + @@ -131,7 +133,7 @@ - + @@ -185,4 +187,4 @@ - + diff --git a/src/runtime/classbase.cs b/src/runtime/classbase.cs index 09adf5afe..a62e76050 100644 --- a/src/runtime/classbase.cs +++ b/src/runtime/classbase.cs @@ -279,7 +279,7 @@ public static IntPtr tp_repr(IntPtr ob) IntPtr args = Runtime.PyTuple_New(1); Runtime.XIncref(ob); Runtime.PyTuple_SetItem(args, 0, ob); - IntPtr reprFunc = Runtime.PyObject_GetAttrString(Runtime.PyBaseObjectType, "__repr__"); + IntPtr reprFunc = Runtime.PyObject_GetAttr(Runtime.PyBaseObjectType, PyIdentifier.__repr__); var output = Runtime.PyObject_Call(reprFunc, args, IntPtr.Zero); Runtime.XDecref(args); Runtime.XDecref(reprFunc); diff --git a/src/runtime/classmanager.cs b/src/runtime/classmanager.cs index 15f3d821d..26d0536ab 100644 --- a/src/runtime/classmanager.cs +++ b/src/runtime/classmanager.cs @@ -232,7 +232,7 @@ private static void InitClassBase(Type type, ClassBase impl) var attr = (DocStringAttribute)attrs[0]; string docStr = attr.DocString; doc = Runtime.PyString_FromString(docStr); - Runtime.PyDict_SetItemString(dict, "__doc__", doc); + Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, doc); Runtime.XDecref(doc); } @@ -249,8 +249,8 @@ private static void InitClassBase(Type type, ClassBase impl) var ctors = new ConstructorBinding(type, tp, co.binder); // ExtensionType types are untracked, so don't Incref() them. // TODO: deprecate __overloads__ soon... - Runtime.PyDict_SetItemString(dict, "__overloads__", ctors.pyHandle); - Runtime.PyDict_SetItemString(dict, "Overloads", ctors.pyHandle); + Runtime.PyDict_SetItem(dict, PyIdentifier.__overloads__, ctors.pyHandle); + Runtime.PyDict_SetItem(dict, PyIdentifier.Overloads, ctors.pyHandle); ctors.DecrRefCount(); } @@ -258,7 +258,7 @@ private static void InitClassBase(Type type, ClassBase impl) if (!CLRModule._SuppressDocs && doc == IntPtr.Zero) { doc = co.GetDocString(); - Runtime.PyDict_SetItemString(dict, "__doc__", doc); + Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, doc); Runtime.XDecref(doc); } } diff --git a/src/runtime/exceptions.cs b/src/runtime/exceptions.cs index 58506bfbb..ab28905d2 100644 --- a/src/runtime/exceptions.cs +++ b/src/runtime/exceptions.cs @@ -284,7 +284,7 @@ public static void SetError(Exception e) } IntPtr op = CLRObject.GetInstHandle(e); - IntPtr etype = Runtime.PyObject_GetAttrString(op, "__class__"); + IntPtr etype = Runtime.PyObject_GetAttr(op, PyIdentifier.__class__); Runtime.PyErr_SetObject(new BorrowedReference(etype), new BorrowedReference(op)); Runtime.XDecref(etype); Runtime.XDecref(op); diff --git a/src/runtime/importhook.cs b/src/runtime/importhook.cs index 8cf57c85d..e2795e4e3 100644 --- a/src/runtime/importhook.cs +++ b/src/runtime/importhook.cs @@ -43,11 +43,11 @@ static void InitImport() // look in CLR modules, then if we don't find any call the default // Python __import__. IntPtr builtins = Runtime.GetBuiltins(); - py_import = Runtime.PyObject_GetAttrString(builtins, "__import__"); + py_import = Runtime.PyObject_GetAttr(builtins, PyIdentifier.__import__); PythonException.ThrowIfIsNull(py_import); hook = new MethodWrapper(typeof(ImportHook), "__import__", "TernaryFunc"); - int res = Runtime.PyObject_SetAttrString(builtins, "__import__", hook.ptr); + int res = Runtime.PyObject_SetAttr(builtins, PyIdentifier.__import__, hook.ptr); PythonException.ThrowIfIsNotZero(res); Runtime.XDecref(builtins); @@ -60,7 +60,7 @@ static void RestoreImport() { IntPtr builtins = Runtime.GetBuiltins(); - int res = Runtime.PyObject_SetAttrString(builtins, "__import__", py_import); + int res = Runtime.PyObject_SetAttr(builtins, PyIdentifier.__import__, py_import); PythonException.ThrowIfIsNotZero(res); Runtime.XDecref(py_import); py_import = IntPtr.Zero; diff --git a/src/runtime/intern.cs b/src/runtime/intern.cs new file mode 100644 index 000000000..d8bdf863e --- /dev/null +++ b/src/runtime/intern.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Python.Runtime +{ + static partial class InternString + { + private static Dictionary _string2interns; + private static Dictionary _intern2strings; + + static InternString() + { + var identifierNames = typeof(PyIdentifier).GetFields().Select(fi => fi.Name); + var validNames = new HashSet(identifierNames); + if (validNames.Count != _builtinNames.Length) + { + throw new InvalidOperationException("Identifiers args not matching"); + } + foreach (var name in _builtinNames) + { + if (!validNames.Contains(name)) + { + throw new InvalidOperationException($"{name} is not declared"); + } + } + } + + public static void Initialize() + { + _string2interns = new Dictionary(); + _intern2strings = new Dictionary(); + + Type type = typeof(PyIdentifier); + foreach (string name in _builtinNames) + { + IntPtr op = Runtime.PyUnicode_InternFromString(name); + SetIntern(name, op); + type.GetField(name).SetValue(null, op); + } + } + + public static void Shutdown() + { + foreach (var ptr in _intern2strings.Keys) + { + Runtime.XDecref(ptr); + } + _string2interns = null; + _intern2strings = null; + } + + public static string GetManagedString(IntPtr op) + { + string s; + if (TryGetInterned(op, out s)) + { + return s; + } + return Runtime.GetManagedString(op); + } + + public static bool TryGetInterned(IntPtr op, out string s) + { + return _intern2strings.TryGetValue(op, out s); + } + + private static void SetIntern(string s, IntPtr op) + { + _string2interns.Add(s, op); + _intern2strings.Add(op, s); + } + } +} diff --git a/src/runtime/intern_.cs b/src/runtime/intern_.cs new file mode 100644 index 000000000..f9b3f43ec --- /dev/null +++ b/src/runtime/intern_.cs @@ -0,0 +1,48 @@ +using System; + +namespace Python.Runtime +{ + static class PyIdentifier + { + public static IntPtr __name__; + public static IntPtr __dict__; + public static IntPtr __doc__; + public static IntPtr __class__; + public static IntPtr __module__; + public static IntPtr __file__; + public static IntPtr __slots__; + public static IntPtr __self__; + public static IntPtr __annotations__; + public static IntPtr __init__; + public static IntPtr __repr__; + public static IntPtr __import__; + public static IntPtr __builtins__; + public static IntPtr builtins; + public static IntPtr __overloads__; + public static IntPtr Overloads; + } + + + static partial class InternString + { + private static readonly string[] _builtinNames = new string[] + { + "__name__", + "__dict__", + "__doc__", + "__class__", + "__module__", + "__file__", + "__slots__", + "__self__", + "__annotations__", + "__init__", + "__repr__", + "__import__", + "__builtins__", + "builtins", + "__overloads__", + "Overloads", + }; + } +} diff --git a/src/runtime/intern_.tt b/src/runtime/intern_.tt new file mode 100644 index 000000000..c7142ec9f --- /dev/null +++ b/src/runtime/intern_.tt @@ -0,0 +1,58 @@ +<#@ template debug="true" hostSpecific="true" #> +<#@ output extension=".cs" #> +<# + string[] internNames = new string[] + { + "__name__", + "__dict__", + "__doc__", + "__class__", + "__module__", + "__file__", + "__slots__", + "__self__", + "__annotations__", + + "__init__", + "__repr__", + "__import__", + "__builtins__", + + "builtins", + + "__overloads__", + "Overloads", + }; +#> +using System; + +namespace Python.Runtime +{ + static class PyIdentifier + { +<# + foreach (var name in internNames) + { +#> + public static IntPtr <#= name #>; +<# + } +#> + } + + + static partial class InternString + { + private static readonly string[] _builtinNames = new string[] + { +<# + foreach (var name in internNames) + { +#> + "<#= name #>", +<# + } +#> + }; + } +} diff --git a/src/runtime/metatype.cs b/src/runtime/metatype.cs index f7afd5d6d..84abe28b9 100644 --- a/src/runtime/metatype.cs +++ b/src/runtime/metatype.cs @@ -109,7 +109,7 @@ public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw) } } - IntPtr slots = Runtime.PyDict_GetItemString(dict, "__slots__"); + IntPtr slots = Runtime.PyDict_GetItem(dict, PyIdentifier.__slots__); if (slots != IntPtr.Zero) { return Exceptions.RaiseTypeError("subclasses of managed classes do not support __slots__"); @@ -197,7 +197,7 @@ public static IntPtr tp_call(IntPtr tp, IntPtr args, IntPtr kw) return IntPtr.Zero; } - var init = Runtime.PyObject_GetAttrString(obj, "__init__"); + var init = Runtime.PyObject_GetAttr(obj, PyIdentifier.__init__); Runtime.PyErr_Clear(); if (init != IntPtr.Zero) diff --git a/src/runtime/methodbinding.cs b/src/runtime/methodbinding.cs index 011d8217d..7a10fcdef 100644 --- a/src/runtime/methodbinding.cs +++ b/src/runtime/methodbinding.cs @@ -84,7 +84,7 @@ public static IntPtr tp_getattro(IntPtr ob, IntPtr key) return IntPtr.Zero; } - string name = Runtime.GetManagedString(key); + string name = InternString.GetManagedString(key); switch (name) { case "__doc__": diff --git a/src/runtime/methodobject.cs b/src/runtime/methodobject.cs index eb3ce8a18..dc23e3ce5 100644 --- a/src/runtime/methodobject.cs +++ b/src/runtime/methodobject.cs @@ -133,8 +133,7 @@ public static IntPtr tp_getattro(IntPtr ob, IntPtr key) return Exceptions.RaiseTypeError("string expected"); } - string name = Runtime.GetManagedString(key); - if (name == "__doc__") + if (Runtime.PyUnicode_Compare(key, PyIdentifier.__doc__) == 0) { IntPtr doc = self.GetDocString(); Runtime.XIncref(doc); diff --git a/src/runtime/moduleobject.cs b/src/runtime/moduleobject.cs index 6313975da..c7085d18a 100644 --- a/src/runtime/moduleobject.cs +++ b/src/runtime/moduleobject.cs @@ -48,10 +48,10 @@ public ModuleObject(string name) IntPtr pyfilename = Runtime.PyString_FromString(filename); IntPtr pydocstring = Runtime.PyString_FromString(docstring); IntPtr pycls = TypeManager.GetTypeHandle(GetType()); - Runtime.PyDict_SetItemString(dict, "__name__", pyname); - Runtime.PyDict_SetItemString(dict, "__file__", pyfilename); - Runtime.PyDict_SetItemString(dict, "__doc__", pydocstring); - Runtime.PyDict_SetItemString(dict, "__class__", pycls); + Runtime.PyDict_SetItem(dict, PyIdentifier.__name__, pyname); + Runtime.PyDict_SetItem(dict, PyIdentifier.__file__, pyfilename); + Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, pydocstring); + Runtime.PyDict_SetItem(dict, PyIdentifier.__class__, pycls); Runtime.XDecref(pyname); Runtime.XDecref(pyfilename); Runtime.XDecref(pydocstring); @@ -282,7 +282,7 @@ public static IntPtr tp_getattro(IntPtr ob, IntPtr key) return op; } - string name = Runtime.GetManagedString(key); + string name = InternString.GetManagedString(key); if (name == "__dict__") { Runtime.XIncref(self.dict); diff --git a/src/runtime/pyscope.cs b/src/runtime/pyscope.cs index fee78b40a..20c933ad2 100644 --- a/src/runtime/pyscope.cs +++ b/src/runtime/pyscope.cs @@ -68,8 +68,8 @@ internal PyScope(IntPtr ptr, PyScopeManager manager) variables = Runtime.PyModule_GetDict(obj); PythonException.ThrowIfIsNull(variables); - int res = Runtime.PyDict_SetItemString( - variables, "__builtins__", + int res = Runtime.PyDict_SetItem( + variables, PyIdentifier.__builtins__, Runtime.PyEval_GetBuiltins() ); PythonException.ThrowIfIsNotZero(res); diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index 1d688ef9a..df6cf7101 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -595,8 +595,8 @@ internal static PyObject RunString(string code, IntPtr? globals, IntPtr? locals, if (globals == IntPtr.Zero) { globals = Runtime.PyDict_New(); - Runtime.PyDict_SetItemString( - globals.Value, "__builtins__", + Runtime.PyDict_SetItem( + globals.Value, PyIdentifier.__builtins__, Runtime.PyEval_GetBuiltins() ); borrowedGlobals = false; diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 915e1db00..b9f471339 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -163,7 +163,7 @@ internal static void Initialize(bool initSigs = false, ShutdownMode mode = Shutd MainManagedThreadId = Thread.CurrentThread.ManagedThreadId; IsFinalizing = false; - + InternString.Initialize(); GenericUtil.Reset(); PyScopeManager.Reset(); ClassManager.Reset(); @@ -237,7 +237,7 @@ private static void InitPyMembers() // a wrapper_descriptor, even though dict.__setitem__ is. // // object.__init__ seems safe, though. - op = PyObject_GetAttrString(PyBaseObjectType, "__init__"); + op = PyObject_GetAttr(PyBaseObjectType, PyIdentifier.__init__); SetPyMember(ref PyWrapperDescriptorType, PyObject_Type(op), () => PyWrapperDescriptorType = IntPtr.Zero); XDecref(op); @@ -378,6 +378,7 @@ internal static void Shutdown(ShutdownMode mode) Exceptions.Shutdown(); Finalizer.Shutdown(); + InternString.Shutdown(); if (mode != ShutdownMode.Normal) { @@ -1598,6 +1599,12 @@ internal static IntPtr PyUnicode_FromString(string s) return PyUnicode_FromUnicode(s, s.Length); } + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr PyUnicode_InternFromString(string s); + + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern int PyUnicode_Compare(IntPtr left, IntPtr right); + internal static string GetManagedString(in BorrowedReference borrowedReference) => GetManagedString(borrowedReference.DangerousGetAddress()); /// @@ -2183,7 +2190,7 @@ internal static void SetNoSiteFlag() /// internal static IntPtr GetBuiltins() { - return PyImport_ImportModule("builtins"); + return PyImport_Import(PyIdentifier.builtins); } } diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index 78c34a027..43c160bc3 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -165,7 +165,7 @@ internal static IntPtr CreateType(Type impl) IntPtr dict = Marshal.ReadIntPtr(type, TypeOffset.tp_dict); IntPtr mod = Runtime.PyString_FromString("CLR"); - Runtime.PyDict_SetItemString(dict, "__module__", mod); + Runtime.PyDict_SetItem(dict, PyIdentifier.__module__, mod); Runtime.XDecref(mod); InitMethods(type, impl); @@ -284,7 +284,7 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType) IntPtr dict = Marshal.ReadIntPtr(type, TypeOffset.tp_dict); string mn = clrType.Namespace ?? ""; IntPtr mod = Runtime.PyString_FromString(mn); - Runtime.PyDict_SetItemString(dict, "__module__", mod); + Runtime.PyDict_SetItem(dict, PyIdentifier.__module__, mod); Runtime.XDecref(mod); // Hide the gchandle of the implementation in a magic type slot. @@ -564,7 +564,7 @@ internal static IntPtr BasicSubType(string name, IntPtr base_, Type impl) IntPtr tp_dict = Marshal.ReadIntPtr(type, TypeOffset.tp_dict); IntPtr mod = Runtime.PyString_FromString("CLR"); - Runtime.PyDict_SetItemString(tp_dict, "__module__", mod); + Runtime.PyDict_SetItem(tp_dict, PyIdentifier.__module__, mod); return type; }