1818#include " cinderx/Jit/hir/printer.h"
1919#include " cinderx/Jit/threaded_compile.h"
2020#include " cinderx/UpstreamBorrow/borrowed.h" // @donotremove
21+ #include " cinderx/module_state.h"
2122
2223#include < fmt/format.h>
2324
@@ -824,20 +825,47 @@ struct MethodInvoke {
824825 CallMethod* call_method{nullptr };
825826};
826827
827- // Returns true if LoadMethod/CallMethod/GetSecondOutput were removed.
828- // Returns false if they could not be removed.
829- static bool tryEliminateLoadMethod (Function& irfunc, MethodInvoke& invoke) {
830- // This isn't safe in the multi-threaded compilation on 3.12 because we
831- // don't hold the GIL which is required for PyType_Lookup.
832- RETURN_MULTITHREADED_COMPILE (false );
828+ #if PY_VERSION_HEX >= 0x030C0000
829+ BorrowedRef<> immutableMultithreadedTypeLookup (
830+ BorrowedRef<PyTypeObject> type,
831+ BorrowedRef<> name) {
832+ BorrowedRef<> mro = type->tp_mro ;
833+ for (Py_ssize_t i = 0 ; i < PyTuple_GET_SIZE (mro.get ()); i++) {
834+ PyTypeObject* mro_type =
835+ reinterpret_cast <PyTypeObject*>(PyTuple_GET_ITEM (mro.get (), i));
836+ if (PyType_HasFeature (mro_type, _Py_TPFLAGS_STATIC_BUILTIN)) {
837+ auto & builtins = cinderx::getModuleState ()->builtinMembers ();
838+
839+ auto members = builtins.find (mro_type);
840+ if (members == builtins.end ()) {
841+ // We don't know anything about this builtin type.
842+ return nullptr ;
843+ }
844+ // We load all of the members from the MRO in the builtins
845+ // cache so it's completely authorative.
846+ return PyDict_GetItemWithError (members->second , name);
847+ } else if (
848+ !PyType_HasFeature (mro_type, Py_TPFLAGS_IMMUTABLETYPE) ||
849+ !PyType_CheckExact (mro_type)) {
850+ // We can't trust anything about this base type
851+ return nullptr ;
852+ }
833853
834- ThreadedCompileSerialize guard;
835- PyCodeObject* code = invoke.load_method ->frameState ()->code ;
836- PyObject* names = code->co_names ;
837- PyObject* name = PyTuple_GetItem (names, invoke.load_method ->name_idx ());
838- JIT_DCHECK (name != nullptr , " name must not be null" );
839- Register* receiver = invoke.load_method ->receiver ();
840- Type receiver_type = receiver->type ();
854+ BorrowedRef<> method_obj =
855+ PyDict_GetItemWithError (_PyType_GetDict (mro_type), name);
856+ if (method_obj != nullptr ) {
857+ return method_obj;
858+ }
859+ }
860+ return nullptr ;
861+ }
862+ #endif
863+
864+ // Gets a directly invokable method object from a JIT Type. This only succeeds
865+ // if we know the type can be directly invoked.
866+ static BorrowedRef<> getMethodObjectFromType (
867+ Type receiver_type,
868+ BorrowedRef<> name) {
841869 // This is a list of common builtin types whose methods cannot be overwritten
842870 // from managed code and for which looking up the methods is guaranteed to
843871 // not do anything "weird" that needs to happen at runtime, like make a
@@ -846,26 +874,85 @@ static bool tryEliminateLoadMethod(Function& irfunc, MethodInvoke& invoke) {
846874 // loading and invoking methods off an instance (e.g. {}.fromkeys(...)) is
847875 // resolved and called differently than from the type (e.g.
848876 // dict.fromkeys(...)). The code below handles the instance case only.
877+ #if PY_VERSION_HEX < 0x030C0000
878+
849879 if (!(receiver_type <= TArray || receiver_type <= TBool ||
850880 receiver_type <= TBytesExact || receiver_type <= TCode ||
851881 receiver_type <= TDictExact || receiver_type <= TFloatExact ||
852882 receiver_type <= TListExact || receiver_type <= TLongExact ||
853883 receiver_type <= TNoneType || receiver_type <= TSetExact ||
854884 receiver_type <= TTupleExact || receiver_type <= TUnicodeExact)) {
855- return false ;
885+ return nullptr ;
856886 }
857887 PyTypeObject* type = receiver_type.runtimePyType ();
858888 if (type == nullptr ) {
859889 // This might happen for a variety of reasons, such as encountering a
860890 // method load on a maybe-defined value where the definition occurs in a
861- // block of code that isn't seen by the compiler (e.g. in an except block).
891+ // block of code that isn't seen by the compiler (e.g. in an except
892+ // block).
862893 JIT_DCHECK (
863894 receiver_type == TBottom,
864895 " Type {} expected to have PyTypeObject*" ,
865896 receiver_type);
866- return false ;
897+ return nullptr ;
898+ }
899+ return _PyType_Lookup (type, name);
900+ #else
901+ if (!receiver_type.hasTypeExactSpec ()) {
902+ return nullptr ;
903+ }
904+ PyTypeObject* type = receiver_type.runtimePyType ();
905+ if (type == nullptr ) {
906+ // This might happen for a variety of reasons, such as encountering a
907+ // method load on a maybe-defined value where the definition occurs in a
908+ // block of code that isn't seen by the compiler (e.g. in an except
909+ // block).
910+ JIT_DCHECK (
911+ receiver_type == TBottom,
912+ " Type {} expected to have PyTypeObject*" ,
913+ receiver_type);
914+ return nullptr ;
867915 }
868- auto method_obj = Ref<>::create (_PyType_Lookup (type, name));
916+
917+ BorrowedRef<> method_obj = nullptr ;
918+ // In 3.12 we can't do PyType_Lookup because for built-in types it needs
919+ // access to the current runtime, and in multi-threaded compile we don't
920+ // have it. So we instead have a cache of all of the builtin types that we
921+ // support this for.
922+ auto & builtins = cinderx::getModuleState ()->builtinMembers ();
923+
924+ if (PyType_HasFeature (type, _Py_TPFLAGS_STATIC_BUILTIN)) {
925+ auto it = builtins.find (receiver_type.runtimePyType ());
926+ if (it != builtins.end ()) {
927+ method_obj = PyDict_GetItemWithError (it->second , name);
928+ }
929+ } else if (
930+ PyType_HasFeature (type, Py_TPFLAGS_IMMUTABLETYPE) &&
931+ PyType_CheckExact (type) && type->tp_dictoffset == 0 ) {
932+ method_obj = immutableMultithreadedTypeLookup (type, name);
933+ if (Py_TYPE (method_obj) != &PyClassMethodDescr_Type &&
934+ Py_TYPE (method_obj) != &PyMethodDescr_Type &&
935+ Py_TYPE (method_obj) != &PyWrapperDescr_Type &&
936+ Py_TYPE (method_obj) != &PyFunction_Type) {
937+ method_obj = nullptr ;
938+ }
939+ }
940+ return method_obj;
941+ #endif
942+ }
943+
944+ // Returns true if LoadMethod/CallMethod/GetSecondOutput were removed.
945+ // Returns false if they could not be removed.
946+ static bool tryEliminateLoadMethod (Function& irfunc, MethodInvoke& invoke) {
947+ ThreadedCompileSerialize guard;
948+ PyCodeObject* code = invoke.load_method ->frameState ()->code ;
949+ PyObject* names = code->co_names ;
950+ PyObject* name = PyTuple_GetItem (names, invoke.load_method ->name_idx ());
951+ JIT_DCHECK (name != nullptr , " name must not be null" );
952+
953+ Register* receiver = invoke.load_method ->receiver ();
954+ Type receiver_type = receiver->type ();
955+ BorrowedRef<> method_obj = getMethodObjectFromType (receiver_type, name);
869956 if (method_obj == nullptr ) {
870957 // No such method. Let the LoadMethod fail at runtime. _PyType_Lookup does
871958 // not raise an exception.
@@ -889,14 +976,17 @@ static bool tryEliminateLoadMethod(Function& irfunc, MethodInvoke& invoke) {
889976 // Pass the type as the first argument (e.g. dict.fromkeys).
890977 Register* type_reg = irfunc.env .AllocateRegister ();
891978 auto load_type = LoadConst::create (
892- type_reg, Type::fromObject (reinterpret_cast <PyObject*>(type)));
979+ type_reg,
980+ Type::fromObject (
981+ reinterpret_cast <PyObject*>(receiver_type.runtimePyType ())));
893982 load_type->setBytecodeOffset (invoke.load_method ->bytecodeOffset ());
894983 load_type->InsertBefore (*invoke.call_method );
895984 call_static->SetOperand (1 , type_reg);
896985 } else {
897986 JIT_DCHECK (
898987 Py_TYPE (method_obj) == &PyMethodDescr_Type ||
899- Py_TYPE (method_obj) == &PyWrapperDescr_Type,
988+ Py_TYPE (method_obj) == &PyWrapperDescr_Type ||
989+ Py_TYPE (method_obj) == &PyFunction_Type,
900990 " unexpected type" );
901991 // Pass the instance as the first argument (e.g. str.join, str.__mod__).
902992 call_static->SetOperand (1 , receiver);
0 commit comments