Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
TemplateProxy.cxx
Go to the documentation of this file.
1// Bindings
2#include "cpyrt.h"
3
4using namespace cppjit;
5#include "CPPClassMethod.h"
6#include "CPPConstructor.h"
7#include "CPPFunction.h"
8#include "CPPMethod.h"
9#include "CPPOverload.h"
10#include "PyCallable.h"
11#include "PyStrings.h"
12#include "TemplateProxy.h"
13#include "Utility.h"
14#include "cppjit_interop.h"
15
16// Standard
17#include <algorithm>
18
19namespace cppjit::cpyrt {
20
21static inline std::string targs2str(TemplateProxy* pytmpl) {
22 if (!pytmpl || !pytmpl->fTemplateArgs)
23 return "";
24 return PyUnicode_AsUTF8(pytmpl->fTemplateArgs);
25}
26
27//----------------------------------------------------------------------------
29 : fPyClass(nullptr), fNonTemplated(nullptr), fTemplated(nullptr),
30 fLowPriority(nullptr), fDoc(nullptr) {
31 /* empty */
32}
33
34//----------------------------------------------------------------------------
36 Py_XDECREF(fPyClass);
37
38 Py_XDECREF(fDoc);
39 Py_DECREF(fNonTemplated);
40 Py_DECREF(fTemplated);
41 Py_DECREF(fLowPriority);
42
43 for (const auto& p : fDispatchMap) {
44 for (const auto& c : p.second) {
45 Py_DECREF(c.second);
46 }
47 }
48}
49
50//----------------------------------------------------------------------------
52 // Store overloads of this templated method.
53 bool isGreedy = false;
54 for (auto pc : mp->fMethodInfo->fMethods) {
55 if (pc->IsGreedy()) {
56 isGreedy = true;
57 break;
58 }
59 }
60
61 CPPOverload* cppol = isGreedy ? fTI->fLowPriority : fTI->fNonTemplated;
62 cppol->MergeOverload(mp);
63}
64
66 // Store overload of this templated method.
67 CPPOverload* cppol = pc->IsGreedy() ? fTI->fLowPriority : fTI->fNonTemplated;
68 cppol->AdoptMethod(pc);
69}
70
72 // Store known template methods.
73 fTI->fTemplated->AdoptMethod(pc);
74}
75
76//----------------------------------------------------------------------------
77PyObject* TemplateProxy::Instantiate(const std::string& fname,
78 cpyrt_PyArgs_t args, size_t nargsf,
79 Utility::ArgPreference pref, int* pcnt) {
80 // Instantiate (and cache) templated methods, return method if any
81 std::string proto = "";
82 std::string protoPair = "";
83
84 // adjust arguments for self if this is a rebound global function
85 bool isNS = (((CPPScope*)fTI->fPyClass)->fFlags & CPPScope::kIsNamespace);
86 if (!isNS && cpyrt_PyArgs_GET_SIZE(args, nargsf) &&
87 (!fSelf || (fSelf == Py_None &&
89 ((CPPScope*)fTI->fPyClass)->fCppType, fname)))) {
90 args += 1;
91 nargsf -= 1;
92 }
93
94 Py_ssize_t argc = cpyrt_PyArgs_GET_SIZE(args, nargsf);
95 if (argc != 0) {
96 PyObject* tpArgs = PyTuple_New(argc);
97 for (Py_ssize_t i = 0; i < argc; ++i) {
98 PyObject* itemi = cpyrt_PyArgs_GET_ITEM(args, i);
99
100 bool bArgSet = false;
101
102 // special case for arrays
103 if (TemplateProxy_CheckExact(itemi)) {
104 TemplateProxy* tn = (TemplateProxy*)itemi;
105 PyObject* f = PyUnicode_FromFormat("%s%s", tn->fTI->fCppName.c_str(),
106 targs2str(tn).c_str());
107 PyTuple_SET_ITEM(tpArgs, i, f);
108 bArgSet = true;
109 }
110 PyObject* pytc = nullptr;
111 if (!bArgSet && (pytc = PyObject_GetAttr(itemi, PyStrings::gTypeCode))) {
112 Py_buffer bufinfo;
113 memset(&bufinfo, 0, sizeof(Py_buffer));
114 std::string ptrdef;
115 if (PyObject_GetBuffer(itemi, &bufinfo, PyBUF_FORMAT) == 0) {
116 for (int j = 0; j < bufinfo.ndim; ++j)
117 ptrdef += "*";
118 PyBuffer_Release(&bufinfo);
119 } else {
120 ptrdef += "*";
121 PyErr_Clear();
122 }
123
124 PyObject* pyptrname = Utility::CT2CppName(pytc, ptrdef.c_str(), true);
125 if (pyptrname) {
126 PyTuple_SET_ITEM(tpArgs, i, pyptrname);
127 bArgSet = true;
128 // string added, but not counted towards nStrings
129 }
130 Py_DECREF(pytc);
131 pytc = nullptr;
132 } else
133 PyErr_Clear();
134
135 // if not arg set, try special case for ctypes
136 if (!bArgSet)
137 pytc = PyObject_GetAttr(itemi, PyStrings::gCTypesType);
138
139 if (!bArgSet && pytc) {
140 PyObject* pyactname = Utility::CT2CppName(pytc, "&", false);
141 if (!pyactname) {
142 // _type_ of a pointer to c_type is that type, which will have a type
143 PyObject* newpytc = PyObject_GetAttr(pytc, PyStrings::gCTypesType);
144 Py_DECREF(pytc);
145 pytc = newpytc;
146 if (pytc) {
147 pyactname = Utility::CT2CppName(pytc, "*", false);
148 } else
149 PyErr_Clear();
150 }
151 Py_XDECREF(pytc);
152 pytc = nullptr;
153 if (pyactname) {
154 PyTuple_SET_ITEM(tpArgs, i, pyactname);
155 bArgSet = true;
156 // string added, but not counted towards nStrings
157 }
158 } else
159 PyErr_Clear();
160
161 if (!bArgSet) {
162 // normal case (may well fail)
163 PyErr_Clear();
164 PyObject* tp = (PyObject*)Py_TYPE(itemi);
165 Py_INCREF(tp);
166 PyTuple_SET_ITEM(tpArgs, i, tp);
167 }
168 }
169
170 PyObject* pyargs = PyTuple_New(argc);
171 for (Py_ssize_t i = 0; i < argc; ++i) {
172 PyObject* item = cpyrt_PyArgs_GET_ITEM(args, i);
173 Py_INCREF(item);
174 PyTuple_SET_ITEM(pyargs, i, item);
175 }
176 const std::string& name_v1 =
177 Utility::ConstructTemplateArgs(nullptr, tpArgs, pyargs, pref, 0, pcnt);
178
179 // A Python 2-tuple is typed above as std::initializer_list, but it may
180 // equally represent a std::pair, to which an initializer_list can not
181 // convert; construct an alternative prototype to retry the lookup with if
182 // the first one fails
183 if (!PyErr_Occurred()) {
184 bool has2Tuple = false;
185 for (Py_ssize_t i = 0; i < argc; ++i) {
186 PyObject* itemi = cpyrt_PyArgs_GET_ITEM(args, i);
187 if (PyTuple_CheckExact(itemi) && PyTuple_GET_SIZE(itemi) == 2) {
188 has2Tuple = true;
189 break;
190 }
191 }
192
193 if (has2Tuple) {
194 PyObject* tpArgsPair = PyTuple_New(argc);
195 for (Py_ssize_t i = 0; i < argc; ++i) {
196 PyObject* itemi = cpyrt_PyArgs_GET_ITEM(args, i);
197 PyObject* entry = nullptr;
198 if (PyTuple_CheckExact(itemi) && PyTuple_GET_SIZE(itemi) == 2) {
199 PyObject* subtypes = PyTuple_New(2);
200 for (Py_ssize_t j = 0; j < 2; ++j) {
201 PyObject* tp = (PyObject*)Py_TYPE(PyTuple_GET_ITEM(itemi, j));
202 Py_INCREF(tp);
203 PyTuple_SET_ITEM(subtypes, j, tp);
204 }
205 const std::string& sub = Utility::ConstructTemplateArgs(
206 nullptr, subtypes, itemi, pref, 0, nullptr);
207 Py_DECREF(subtypes);
208 if (!PyErr_Occurred() && 2 < sub.size())
209 entry = PyUnicode_FromString(("std::pair" + sub).c_str());
210 else
211 PyErr_Clear();
212 }
213 if (!entry) {
214 entry = PyTuple_GET_ITEM(tpArgs, i);
215 Py_INCREF(entry);
216 }
217 PyTuple_SET_ITEM(tpArgsPair, i, entry);
218 }
219
220 const std::string& name_v2 = Utility::ConstructTemplateArgs(
221 nullptr, tpArgsPair, pyargs, pref, 0, nullptr);
222 Py_DECREF(tpArgsPair);
223 if (!PyErr_Occurred() && name_v2.size())
224 protoPair = name_v2.substr(1, name_v2.size() - 2);
225 else
226 PyErr_Clear();
227 }
228 }
229
230 Py_DECREF(pyargs);
231 Py_DECREF(tpArgs);
232
233 // Propagate the error that occurs if we can't construct the C++ name
234 // from the provided template argument
235 if (PyErr_Occurred()) {
236 return nullptr;
237 }
238
239 if (name_v1.size())
240 proto = name_v1.substr(1, name_v1.size() - 2);
241 }
242
243 // the following causes instantiation as necessary
244 interop::TCppScope_t scope = ((CPPClass*)fTI->fPyClass)->fCppType;
245 interop::TCppMethod_t cppmeth =
246 interop::GetMethodTemplate(scope, fname, proto);
247 if (!cppmeth && !protoPair.empty() && protoPair != proto) {
248 // retry with 2-tuples typed as std::pair instead of std::initializer_list
249 cppmeth = interop::GetMethodTemplate(scope, fname, protoPair);
250 if (cppmeth)
251 proto = protoPair;
252 }
253 if (cppmeth) { // overload stops here
254 // A successful instantiation needs to be cached to pre-empt future
255 // instantiations. There are two names involved, the original asked (which
256 // may be partial) and the received.
257 //
258 // Caching scheme: if the match is exact, simply add the overload to the
259 // pre-existing one, or create a new overload for later lookups. If the
260 // match is not exact, do the same, but also create an alias. Only add exact
261 // matches to the set of known template instantiations, to prevent piling on
262 // from different partial instantiations.
263 //
264 // TODO: this caches the lookup method before the call, meaning that failing
265 // overloads can add already existing overloads to the set of methods.
266
267 std::string resname =
269
270 // An initializer_list is preferred for the argument types, but should not
271 // leak into the argument types. If it did, replace with vector and lookup
272 // anew.
273 if (resname.find("initializer_list") != std::string::npos) {
274 auto pos = proto.find("initializer_list");
275 while (pos != std::string::npos) {
276 proto.replace(pos, 16, "vector");
277 pos = proto.find("initializer_list", pos + 6);
278 }
279
281 interop::GetMethodTemplate(scope, fname, proto);
282 if (m2 && m2 != cppmeth) {
283 // replace if the new method with vector was found; otherwise just
284 // continue with the previously found method with initializer_list.
285 cppmeth = m2;
286 resname = interop::GetFullName(interop::TCppScope_t(cppmeth.data));
287 }
288 }
289
290 bool bExactMatch = fname == resname;
291
292 // lookup on existing name in case this was an overload, not a caching,
293 // failure
294 PyObject* dct = PyObject_GetAttr(fTI->fPyClass, PyStrings::gDict);
295 PyObject* pycachename = PyUnicode_InternFromString(fname.c_str());
296 PyObject* pyol = PyObject_GetItem(dct, pycachename);
297 if (!pyol)
298 PyErr_Clear();
299 bool bIsCppOL = CPPOverload_Check(pyol);
300 bool bIsCppTP = TemplateProxy_Check(pyol);
301
302 // find the full name if the requested one was partial
303 PyObject* exact = nullptr;
304 PyObject* pyresname = PyUnicode_FromString(resname.c_str());
305 if (!bExactMatch) {
306 exact = PyObject_GetItem(dct, pyresname);
307 if (!exact)
308 PyErr_Clear();
309 }
310 Py_DECREF(dct);
311
312 bool bIsConstructor = false, bNeedsRebind = true;
313
314 PyCallable* meth = nullptr;
315 if (interop::IsNamespace(scope)) {
316 meth = new CPPFunction(scope, cppmeth);
317 bNeedsRebind = false;
318 } else if (interop::IsStaticMethod(cppmeth)) {
319 meth = new CPPClassMethod(scope, cppmeth);
320 bNeedsRebind = false;
321 } else if (interop::IsConstructor(cppmeth)) {
322 bIsConstructor = true;
323 meth = new CPPConstructor(scope, cppmeth);
324 } else
325 meth = new CPPMethod(scope, cppmeth);
326
327 // Case 1/2: method simply did not exist before
328 if (!pyol) {
329 // actual overload to use (now owns meth)
330 pyol = (PyObject*)CPPOverload_New(fname, meth);
331 if (bIsConstructor) {
332 // TODO: this is an ugly hack :(
333 ((CPPOverload*)pyol)->fMethodInfo->fFlags |=
335 }
336
337 // add to class dictionary
338 PyType_Type.tp_setattro(fTI->fPyClass, pycachename, pyol);
339 }
340
341 // Case 3/4: pre-existing method that was either not found b/c the full
342 // templated name was constructed in this call or it failed as overload
343 else if (bIsCppOL) {
344 // The lookup is cached before the call, so when the call then fails
345 // (e.g. an implicit conversion attempt that is not allowed to nest), the
346 // next call instantiates the same method again. Adopting it again would
347 // grow the overload set with duplicates and force the whole set to be
348 // ranked anew on every call.
349 bool known = false;
350 for (PyCallable* pc : ((CPPOverload*)pyol)->fMethodInfo->fMethods) {
351 auto* existing = dynamic_cast<CPPMethod*>(pc);
352 if (existing && existing->GetMethod() == cppmeth) {
353 known = true;
354 break;
355 }
356 }
357 if (known)
358 delete meth;
359 else
360 ((CPPOverload*)pyol)->AdoptMethod(meth); // takes ownership
361 }
362
363 // Case 5: must be a template proxy, meaning that current template name is
364 // not a template overload
365 else if (bIsCppTP) {
366 ((TemplateProxy*)pyol)->AdoptTemplate(meth->Clone());
367 Py_DECREF(pyol);
368 pyol = (PyObject*)CPPOverload_New(fname, meth); // takes ownership
369 }
370 // Case 6: pre-existing object is not a CPPOverload nor TemplateProxy
371 // we do not cache it, as this might be a pythonization (monkey-patched
372 // func/method)
373 else {
374 Py_DECREF(pyol);
375 pyol = (PyObject*)CPPOverload_New(fname, meth);
376 }
377
378 // Special Case if name was aliased (e.g. typedef in template instantiation)
379 if (!exact && !bExactMatch) {
380 PyType_Type.tp_setattro(fTI->fPyClass, pyresname, pyol);
381 }
382
383 // cleanup
384 Py_XDECREF(exact);
385 Py_DECREF(pyresname);
386 Py_DECREF(pycachename);
387
388 // retrieve fresh (for boundedness) and call
389 PyObject* pymeth = CPPOverload_Type.tp_descr_get(
390 pyol, bNeedsRebind ? fSelf : nullptr, (PyObject*)&CPPOverload_Type);
391 Py_DECREF(pyol);
392 return pymeth;
393 }
394
395 PyErr_Format(PyExc_TypeError, "Failed to instantiate \"%s(%s)\"",
396 fname.c_str(), proto.c_str());
397 return nullptr;
398}
399
400//= cpyrt template proxy construction/destruction =========================
401static TemplateProxy* tpp_new(PyTypeObject*, PyObject*, PyObject*) {
402 // Create a new empty template method proxy.
403 TemplateProxy* pytmpl = PyObject_GC_New(TemplateProxy, &TemplateProxy_Type);
404 pytmpl->fSelf = nullptr;
405 pytmpl->fTemplateArgs = nullptr;
406 pytmpl->fWeakrefList = nullptr;
407 new (&pytmpl->fTI) TP_TInfo_t{};
408 pytmpl->fTI = std::make_shared<TemplateInfo>();
409
410 PyObject_GC_Track(pytmpl);
411 return pytmpl;
412}
413
414//----------------------------------------------------------------------------
415static Py_hash_t tpp_hash(TemplateProxy* self) { return (Py_hash_t)self; }
416
417//----------------------------------------------------------------------------
418static PyObject* tpp_richcompare(TemplateProxy* self, PyObject* other, int op) {
419 if (op == Py_EQ || op == Py_NE) {
420 if (!TemplateProxy_CheckExact(other))
422
423 if (self->fTI == ((TemplateProxy*)other)->fTI)
425
427 }
428
429 Py_INCREF(Py_NotImplemented);
430 return Py_NotImplemented;
431}
432
433//----------------------------------------------------------------------------
434static int tpp_clear(TemplateProxy* pytmpl) {
435 // Garbage collector clear of held python member objects.
436 Py_CLEAR(pytmpl->fSelf);
437 Py_CLEAR(pytmpl->fTemplateArgs);
438
439 return 0;
440}
441
442//----------------------------------------------------------------------------
443static void tpp_dealloc(TemplateProxy* pytmpl) {
444 // Destroy the given template method proxy.
445 if (pytmpl->fWeakrefList)
446 PyObject_ClearWeakRefs((PyObject*)pytmpl);
447 PyObject_GC_UnTrack(pytmpl);
448 tpp_clear(pytmpl);
449 pytmpl->fTI.~TP_TInfo_t();
450 PyObject_GC_Del(pytmpl);
451}
452
453//----------------------------------------------------------------------------
454static int tpp_traverse(TemplateProxy* pytmpl, visitproc visit, void* arg) {
455 // Garbage collector traverse of held python member objects.
456 Py_VISIT(pytmpl->fSelf);
457 Py_VISIT(pytmpl->fTemplateArgs);
458
459 return 0;
460}
461
462//----------------------------------------------------------------------------
463static PyObject* tpp_doc(TemplateProxy* pytmpl, void*) {
464 if (pytmpl->fTI->fDoc) {
465 Py_INCREF(pytmpl->fTI->fDoc);
466 return pytmpl->fTI->fDoc;
467 }
468
469 // Forward to method proxies to doc all overloads
470 PyObject* doc = nullptr;
471 if (pytmpl->fTI->fNonTemplated->HasMethods())
472 doc = PyObject_GetAttrString((PyObject*)pytmpl->fTI->fNonTemplated,
473 "__doc__");
474 if (pytmpl->fTI->fTemplated->HasMethods()) {
475 PyObject* doc2 =
476 PyObject_GetAttrString((PyObject*)pytmpl->fTI->fTemplated, "__doc__");
477 if (doc && doc2) {
478 PyUnicode_AppendAndDel(&doc, PyUnicode_FromString("\n"));
479 PyUnicode_AppendAndDel(&doc, doc2);
480 } else if (!doc && doc2) {
481 doc = doc2;
482 }
483 }
484 if (pytmpl->fTI->fLowPriority->HasMethods()) {
485 PyObject* doc2 =
486 PyObject_GetAttrString((PyObject*)pytmpl->fTI->fLowPriority, "__doc__");
487 if (doc && doc2) {
488 PyUnicode_AppendAndDel(&doc, PyUnicode_FromString("\n"));
489 PyUnicode_AppendAndDel(&doc, doc2);
490 } else if (!doc && doc2) {
491 doc = doc2;
492 }
493 }
494
495 if (doc)
496 return doc;
497
498 return PyUnicode_FromString(TemplateProxy_Type.tp_doc);
499}
500
501static int tpp_doc_set(TemplateProxy* pytmpl, PyObject* val, void*) {
502 Py_XDECREF(pytmpl->fTI->fDoc);
503 Py_INCREF(val);
504 pytmpl->fTI->fDoc = val;
505 return 0;
506}
507
508//----------------------------------------------------------------------------
509
510//= cpyrt template proxy callable behavior ================================
511
512#define TPPCALL_RETURN \
513 { \
514 errors.clear(); \
515 return result; \
516 }
517
518static inline void UpdateDispatchMap(TemplateProxy* pytmpl, bool use_targs,
519 uint64_t sighash, CPPOverload* pymeth) {
520 // Memoize a method in the dispatch map after successful call; replace old if
521 // need be (may be with the same CPPOverload, just with more methods).
522 bool bInserted = false;
523 auto& v = pytmpl->fTI->fDispatchMap[use_targs ? targs2str(pytmpl) : ""];
524
525 Py_INCREF(pymeth);
526 for (auto& p : v) {
527 if (p.first == sighash) {
528 Py_DECREF(p.second);
529 p.second = pymeth;
530 bInserted = true;
531 }
532 }
533 if (!bInserted)
534 v.push_back(std::make_pair(sighash, pymeth));
535}
536
537static inline PyObject*
539 cpyrt_PyArgs_t args, size_t nargsf, PyObject* kwds,
540 bool implicitOkay, bool use_targs, uint64_t sighash,
541 std::vector<Utility::PyError_t>& errors) {
542 // Forward a call to known overloads, if any.
543 if (pymeth->HasMethods()) {
544 PyObject* pycall = CPPOverload_Type.tp_descr_get(
545 (PyObject*)pymeth, pytmpl->fSelf, (PyObject*)&CPPOverload_Type);
546
547 if (!implicitOkay)
548 ((CPPOverload*)pycall)->fFlags |= CallContext::kNoImplicit;
549
550 // now call the method with the arguments (loops internally)
551 PyObject* result = cpyrt_tp_call(pycall, args, nargsf, kwds);
552 Py_DECREF(pycall);
553 if (result) {
554 UpdateDispatchMap(pytmpl, use_targs, sighash, pymeth);
556 }
557 Utility::FetchError(errors);
558 }
559
560 return nullptr;
561}
562
563static inline PyObject* CallMethodImp(TemplateProxy* pytmpl, PyObject*& pymeth,
564 cpyrt_PyArgs_t args, size_t nargsf,
565 PyObject* kwds, bool impOK,
566 uint64_t sighash) {
567 // Actual call of a given overload: takes care of handlign of "self" and
568 // dereferences the overloaded method after use.
569
571 if (!impOK && CPPOverload_Check(pymeth))
572 ((CPPOverload*)pymeth)->fFlags |= CallContext::kNoImplicit;
573 bool isNS =
574 (((CPPScope*)pytmpl->fTI->fPyClass)->fFlags & CPPScope::kIsNamespace);
575 if (isNS && pytmpl->fSelf && pytmpl->fSelf != Py_None) {
576 // this is a global method added a posteriori to the class
577 PyCallArgs cargs{(CPPInstance*&)pytmpl->fSelf, args, nargsf, kwds};
578 AdjustSelf(cargs);
579 result = cpyrt_tp_call(pymeth, cargs.fArgs, cargs.fNArgsf, cargs.fKwds);
580 } else {
581 if (!pytmpl->fSelf && CPPOverload_Check(pymeth))
582 ((CPPOverload*)pymeth)->fFlags &= ~CallContext::kFromDescr;
583 result = cpyrt_tp_call(pymeth, args, nargsf, kwds);
584 }
585
586 if (result) {
587 Py_XDECREF(((CPPOverload*)pymeth)->fSelf);
588 ((CPPOverload*)pymeth)->fSelf = nullptr; // unbind
589 UpdateDispatchMap(pytmpl, true, sighash, (CPPOverload*)pymeth);
590 }
591
592 Py_DECREF(pymeth);
593 pymeth = nullptr;
594 return result;
595}
596
597static PyObject* tpp_vectorcall(TemplateProxy* pytmpl, PyObject* const* args,
598 size_t nargsf, PyObject* kwds) {
599 // Dispatcher to the actual member method, several uses possible; in order:
600 //
601 // case 1: explicit template previously selected through subscript
602 //
603 // case 2: select known non-template overload
604 //
605 // obj.method(a0, a1, ...)
606 // => obj->method(a0, a1, ...) // non-template
607 //
608 // case 3: select known template overload
609 //
610 // obj.method(a0, a1, ...)
611 // => obj->method(a0, a1, ...) // all known templates
612 //
613 // case 4: auto-instantiation from types of arguments
614 //
615 // obj.method(a0, a1, ...)
616 // => obj->method<type(a0), type(a1), ...>(a0, a1, ...)
617 //
618 // Note: explicit instantiation needs to use [] syntax:
619 //
620 // obj.method[type<a0>, type<a1>, ...](a0, a1, ...)
621 //
622 // case 5: low priority methods, such as ones that take void* arguments
623 //
624
625 // TODO: should previously instantiated templates be considered first?
626
627 PyObject *pymeth = nullptr, *result = nullptr;
628
629 // short-cut through memoization map
630 Py_ssize_t argc = cpyrt_PyArgs_GET_SIZE(args, nargsf);
631 uint64_t sighash = HashSignature(args, argc);
632
633 CPPOverload* ol = nullptr;
634 if (!pytmpl->fTemplateArgs) {
635 // look for known signatures ...
636 auto& v = pytmpl->fTI->fDispatchMap[""];
637 for (const auto& p : v) {
638 if (p.first == sighash) {
639 ol = p.second;
640 break;
641 }
642 }
643
644 if (ol != nullptr) {
645 if (!pytmpl->fSelf || pytmpl->fSelf == Py_None) {
646 result = cpyrt_tp_call((PyObject*)ol, args, nargsf, kwds);
647 } else {
648 pymeth = CPPOverload_Type.tp_descr_get((PyObject*)ol, pytmpl->fSelf,
650 result = cpyrt_tp_call(pymeth, args, nargsf, kwds);
651 Py_DECREF(pymeth);
652 pymeth = nullptr;
653 }
654 if (result)
655 return result;
656 }
657 }
658
659 // container for collecting errors
660 std::vector<Utility::PyError_t> errors;
661 if (ol)
662 Utility::FetchError(errors);
663
664 // case 1: explicit template previously selected through subscript
665 if (pytmpl->fTemplateArgs) {
666 // instantiate explicitly
667 PyObject* pyfullname = PyUnicode_FromString(pytmpl->fTI->fCppName.c_str());
668 PyUnicode_Append(&pyfullname, pytmpl->fTemplateArgs);
669
670 // first, lookup by full name, if previously stored
671 bool isNS =
672 (((CPPScope*)pytmpl->fTI->fPyClass)->fFlags & CPPScope::kIsNamespace);
673 if (pytmpl->fSelf && pytmpl->fSelf != Py_None && !isNS)
674 pymeth = PyObject_GetAttr(pytmpl->fSelf, pyfullname);
675 else // by-passes custom scope getattr that searches into Cling
676 pymeth = PyType_Type.tp_getattro(pytmpl->fTI->fPyClass, pyfullname);
677
678 // attempt call if found (this may fail if there are specializations)
679 if (CPPOverload_Check(pymeth)) {
680 // since the template args are fully explicit, allow implicit conversion
681 // of arguments
682 result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, /*impOK=*/true,
683 sighash);
684 if (result) {
685 Py_DECREF(pyfullname);
687 }
688 Utility::FetchError(errors);
689 } else if (pymeth && PyCallable_Check(pymeth)) {
690 // something different (user provided?)
691 result = cpyrt_PyObject_Call(pymeth, args, nargsf, kwds);
692 Py_DECREF(pymeth);
693 if (result) {
694 Py_DECREF(pyfullname);
696 }
697 Utility::FetchError(errors);
698 } else if (!pymeth)
699 PyErr_Clear();
700
701 // not cached or failed call; try instantiation
702 pymeth = pytmpl->Instantiate(PyUnicode_AsUTF8(pyfullname), args, nargsf,
704 if (pymeth) {
705 // attempt actual call; same as above, allow implicit conversion of
706 // arguments
707 result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, /*impOK=*/true,
708 sighash);
709 if (result) {
710 Py_DECREF(pyfullname);
712 }
713 }
714
715 // no drop through if failed (if implicit was desired, don't provide
716 // template args)
717 Utility::FetchError(errors);
718 PyObject* topmsg = PyUnicode_FromFormat(
719 "Could not find \"%s\" (set cppjit.set_debug() for C++ errors):",
720 PyUnicode_AsUTF8(pyfullname));
721 Py_DECREF(pyfullname);
722 Utility::SetDetailedException(std::move(errors), topmsg /* steals */,
723 PyExc_TypeError /* default error */);
724
725 return nullptr;
726 }
727
728 // case 2: select known non-template overload
729 result = SelectAndForward(pytmpl, pytmpl->fTI->fNonTemplated, args, nargsf,
730 kwds, true /* implicitOkay */,
731 false /* use_targs */, sighash, errors);
732 if (result)
734
735 // case 3: select known template overload
736 result = SelectAndForward(pytmpl, pytmpl->fTI->fTemplated, args, nargsf, kwds,
737 false /* implicitOkay */, true /* use_targs */,
738 sighash, errors);
739 if (result)
741
742 // case 4: auto-instantiation from types of arguments
744 // TODO: no need to loop if there are no non-instance arguments; also,
745 // should any failed lookup be removed?
746 int pcnt = 0;
747 pymeth =
748 pytmpl->Instantiate(pytmpl->fTI->fCppName, args, nargsf, pref, &pcnt);
749 if (pymeth) {
750 // attempt actual call; even if argument based, allow implicit
751 // conversions, for example for non-template arguments
752 result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, /*impOK=*/true,
753 sighash);
754 if (result)
756 }
757 Utility::FetchError(errors);
758 if (!pcnt)
759 break; // preference never used; no point trying others
760 }
761
762 // case 5: low priority methods, such as ones that take void* arguments
763 result = SelectAndForward(pytmpl, pytmpl->fTI->fLowPriority, args, nargsf,
764 kwds, false /* implicitOkay */,
765 false /* use_targs */, sighash, errors);
766 if (result)
768
769 // error reporting is fraud, given the numerous steps taken, but more details
770 // seems better
771 if (!errors.empty()) {
772 PyObject* topmsg =
773 PyUnicode_FromString("Template method resolution failed:");
774 Utility::SetDetailedException(std::move(errors), topmsg /* steals */,
775 PyExc_TypeError /* default error */);
776 } else {
777 PyErr_Format(PyExc_TypeError,
778 "cannot resolve method template call for \'%s\'",
779 pytmpl->fTI->fCppName.c_str());
780 }
781
782 return nullptr;
783}
784
785//----------------------------------------------------------------------------
787 PyObject*) {
788 // create and use a new template proxy (language requirement)
789 TemplateProxy* newPyTmpl =
791
792 // new method is to be bound to current object (may be nullptr)
793 if (pyobj) {
794 Py_INCREF(pyobj);
795 newPyTmpl->fSelf = pyobj;
796 } else {
797 Py_INCREF(Py_None);
798 newPyTmpl->fSelf = Py_None;
799 }
800
801 Py_XINCREF(pytmpl->fTemplateArgs);
802 newPyTmpl->fTemplateArgs = pytmpl->fTemplateArgs;
803
804 // copy name, class, etc. pointers
805 new (&newPyTmpl->fTI) std::shared_ptr<TemplateInfo>{pytmpl->fTI};
806
807 newPyTmpl->fVectorCall = pytmpl->fVectorCall;
808
809 return newPyTmpl;
810}
811
812//----------------------------------------------------------------------------
814 // Explicit template member lookup/instantiation; works by re-bounding. This
815 // method can not cache overloads as instantiations need not be unique for the
816 // argument types due to template specializations.
817 TemplateProxy* typeBoundMethod =
818 tpp_descr_get(pytmpl, pytmpl->fSelf, nullptr);
819 Py_XDECREF(typeBoundMethod->fTemplateArgs);
820 typeBoundMethod->fTemplateArgs = PyUnicode_FromString(
821 Utility::ConstructTemplateArgs(nullptr, args).c_str());
822 // Propagate the error that occurs if we can't construct the C++ name
823 // from the provided template argument
824 if (PyErr_Occurred()) {
825 return nullptr;
826 }
827 return (PyObject*)typeBoundMethod;
828}
829
830//-----------------------------------------------------------------------------
832 return PyLong_FromLong(0); // dummy (__useffi__ unused)
833}
834
835//-----------------------------------------------------------------------------
836static int tpp_setuseffi(CPPOverload*, PyObject*, void*) {
837 return 0; // dummy (__useffi__ unused)
838}
839
840//-----------------------------------------------------------------------------
842 if (!self->fTemplateArgs) {
844 }
845
846 Py_INCREF(self->fTemplateArgs);
847 return self->fTemplateArgs;
848}
849
850//-----------------------------------------------------------------------------
852 PyErr_SetString(PyExc_AttributeError, "__template_args__ is read-only");
853 return -1;
854}
855
856//----------------------------------------------------------------------------
857static PyMappingMethods tpp_as_mapping = {nullptr, (binaryfunc)tpp_subscript,
858 nullptr};
859
860static PyGetSetDef tpp_getset[] = {
861 {(char*)"__doc__", (getter)tpp_doc, (setter)tpp_doc_set, nullptr, nullptr},
862 {(char*)"__useffi__", (getter)tpp_getuseffi, (setter)tpp_setuseffi,
863 (char*)"unused", nullptr},
864 {(char*)"__template_args__", (getter)tpp_gettemplateargs,
865 (setter)tpp_settemplateargs,
866 (char*)"the template arguments for this method", nullptr},
867 {(char*)nullptr, nullptr, nullptr, nullptr, nullptr},
868};
869
870//----------------------------------------------------------------------------
871void TemplateProxy::Set(const std::string& cppname, const std::string& pyname,
872 PyObject* pyclass) {
873 // Initialize the proxy for the given 'pyclass.'
874 fSelf = nullptr;
875 fTemplateArgs = nullptr;
876
877 fTI->fCppName = cppname;
878 Py_XINCREF(pyclass);
879 fTI->fPyClass = pyclass;
880
881 std::vector<PyCallable*> dummy;
882 fTI->fNonTemplated = CPPOverload_New(pyname, dummy);
883 fTI->fTemplated = CPPOverload_New(pyname, dummy);
884 fTI->fLowPriority = CPPOverload_New(pyname, dummy);
885
886 fVectorCall = (vectorcallfunc)tpp_vectorcall;
887}
888
889//= cpyrt method proxy access to internals ================================
891 // Select and call a specific C++ overload, based on its signature.
892 const char* sigarg = nullptr;
893 const char* tmplarg = nullptr;
894 PyObject* sigarg_tuple = nullptr;
895 int want_const = -1;
896
897 interop::TCppScope_t scope = nullptr;
898 interop::TCppMethod_t cppmeth = nullptr;
899 std::string proto;
900
901 if (PyArg_ParseTuple(args, const_cast<char*>("s|i:__overload__"), &sigarg,
902 &want_const)) {
903 want_const = PyTuple_GET_SIZE(args) == 1 ? -1 : want_const;
904
905 // check existing overloads in order
906 PyObject* ol = pytmpl->fTI->fNonTemplated->FindOverload(sigarg, want_const);
907 if (ol)
908 return ol;
909 PyErr_Clear();
910 ol = pytmpl->fTI->fTemplated->FindOverload(sigarg, want_const);
911 if (ol)
912 return ol;
913 PyErr_Clear();
914 ol = pytmpl->fTI->fLowPriority->FindOverload(sigarg, want_const);
915 if (ol)
916 return ol;
917
918 proto = Utility::ConstructTemplateArgs(nullptr, args);
919 // Propagate the error that occurs if we can't construct the C++ name
920 // from the provided template argument
921 if (PyErr_Occurred()) {
922 return nullptr;
923 }
924
925 scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType;
926 cppmeth = interop::GetMethodTemplate(scope, pytmpl->fTI->fCppName,
927 proto.substr(1, proto.size() - 2));
928 } else if (PyArg_ParseTuple(args, const_cast<char*>("ss:__overload__"),
929 &sigarg, &tmplarg)) {
930 scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType;
931 std::string full_name =
932 std::string(pytmpl->fTI->fCppName) + "<" + tmplarg + ">";
933
934 cppmeth = interop::GetMethodTemplate(scope, full_name, sigarg);
935 } else if (PyArg_ParseTuple(args, const_cast<char*>("O|i:__overload__"),
936 &sigarg_tuple, &want_const)) {
937 PyErr_Clear();
938 want_const = PyTuple_GET_SIZE(args) == 1 ? -1 : want_const;
939
940 // check existing overloads in order
941 PyObject* ol =
942 pytmpl->fTI->fNonTemplated->FindOverload(sigarg_tuple, want_const);
943 if (ol)
944 return ol;
945 PyErr_Clear();
946 ol = pytmpl->fTI->fTemplated->FindOverload(sigarg_tuple, want_const);
947 if (ol)
948 return ol;
949 PyErr_Clear();
950 ol = pytmpl->fTI->fLowPriority->FindOverload(sigarg_tuple, want_const);
951 if (ol)
952 return ol;
953
954 proto.reserve(128);
955 proto.push_back('<');
956 Py_ssize_t n = PyTuple_Size(sigarg_tuple);
957 for (int i = 0; i < n; i++) {
958 PyObject* pItem = PyTuple_GetItem(sigarg_tuple, i);
959 if (!PyUnicode_Check(pItem)) {
960 PyErr_Format(PyExc_LookupError,
961 "argument types should be in string format");
962 return (PyObject*)nullptr;
963 }
964 proto.append(PyUnicode_AsUTF8(pItem));
965 if (i < n - 1)
966 proto.push_back(',');
967 }
968 proto.push_back('>');
969
970 scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType;
971 cppmeth = interop::GetMethodTemplate(scope, pytmpl->fTI->fCppName,
972 proto.substr(1, proto.size() - 2));
973 } else {
974 PyErr_Format(PyExc_TypeError, "Unexpected arguments to __overload__");
975 return nullptr;
976 }
977
978 // else attempt instantiation
979 if (!cppmeth) {
980 return nullptr;
981 }
982
983 PyErr_Clear();
984
985 // TODO: the next step should be consolidated with Instantiate()
986 PyCallable* meth = nullptr;
987 if (interop::IsNamespace(scope)) {
988 meth = new CPPFunction(scope, cppmeth);
989 } else if (interop::IsStaticMethod(cppmeth)) {
990 meth = new CPPClassMethod(scope, cppmeth);
991 } else if (interop::IsConstructor(cppmeth)) {
992 meth = new CPPConstructor(scope, cppmeth);
993 } else
994 meth = new CPPMethod(scope, cppmeth);
995
996 return (PyObject*)CPPOverload_New(pytmpl->fTI->fCppName + proto, meth);
997}
998
999static PyMethodDef tpp_methods[] = {{(char*)"__overload__",
1000 (PyCFunction)tpp_overload, METH_VARARGS,
1001 (char*)"select overload for dispatch"},
1002 {(char*)nullptr, nullptr, 0, nullptr}};
1003
1004//= cpyrt template proxy type =============================================
1005PyTypeObject TemplateProxy_Type = {
1006 PyVarObject_HEAD_INIT(&PyType_Type,
1007 0)(char*) "cppjit.TemplateProxy", // tp_name
1008 sizeof(TemplateProxy), // tp_basicsize
1009 0, // tp_itemsize
1010 (destructor)tpp_dealloc, // tp_dealloc
1011 offsetof(TemplateProxy, fVectorCall),
1012 0, // tp_getattr
1013 0, // tp_setattr
1014 0, // tp_as_async / tp_compare
1015 0, // tp_repr
1016 0, // tp_as_number
1017 0, // tp_as_sequence
1018 &tpp_as_mapping, // tp_as_mapping
1019 (hashfunc)tpp_hash, // tp_hash
1020 (ternaryfunc)PyVectorcall_Call, // tp_call
1021 0, // tp_str
1022 0, // tp_getattro
1023 0, // tp_setattro
1024 0, // tp_as_buffer
1025 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_VECTORCALL |
1026 Py_TPFLAGS_METHOD_DESCRIPTOR, // tp_flags
1027 (char*)"cppjit template proxy (internal)", // tp_doc
1028 (traverseproc)tpp_traverse, // tp_traverse
1029 (inquiry)tpp_clear, // tp_clear
1030 (richcmpfunc)tpp_richcompare, // tp_richcompare
1031 offsetof(TemplateProxy, fWeakrefList), // tp_weaklistoffset
1032 0, // tp_iter
1033 0, // tp_iternext
1034 tpp_methods, // tp_methods
1035 0, // tp_members
1036 tpp_getset, // tp_getset
1037 0, // tp_base
1038 0, // tp_dict
1039 (descrgetfunc)tpp_descr_get, // tp_descr_get
1040 0, // tp_descr_set
1041 0, // tp_dictoffset
1042 0, // tp_init
1043 0, // tp_alloc
1044 (newfunc)tpp_new, // tp_new
1045 0, // tp_free
1046 0, // tp_is_gc
1047 0, // tp_bases
1048 0, // tp_mro
1049 0, // tp_cache
1050 0, // tp_subclasses
1051 0, // tp_weaklist
1052 0, // tp_del
1053 0, // tp_version_tag
1054 0, // tp_finalize
1055 0 // tp_vectorcall
1057
1058} // namespace cppjit::cpyrt
_object PyObject
#define f(i)
Definition RSha256.hxx:104
#define c(i)
Definition RSha256.hxx:101
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t result
#define TPPCALL_RETURN
void AdoptMethod(PyCallable *pc)
void MergeOverload(CPPOverload *meth)
MethodInfo_t * fMethodInfo
Definition CPPOverload.h:83
virtual bool IsGreedy()=0
virtual PyCallable * Clone()=0
TP_DispatchMap_t fDispatchMap
void AdoptTemplate(PyCallable *pc)
void Set(const std::string &cppname, const std::string &pyname, PyObject *pyclass)
PyObject * Instantiate(const std::string &fname, cpyrt_PyArgs_t tmplArgs, size_t nargsf, Utility::ArgPreference, int *pcnt=nullptr)
void AdoptMethod(PyCallable *pc)
PyObject_HEAD PyObject * fSelf
void MergeOverload(CPPOverload *mp)
#define Py_RETURN_TRUE
Definition cpyrt.h:73
#define Py_RETURN_FALSE
Definition cpyrt.h:77
PyObject *const * cpyrt_PyArgs_t
Definition cpyrt.h:81
static Py_ssize_t cpyrt_PyArgs_GET_SIZE(cpyrt_PyArgs_t, size_t nargsf)
Definition cpyrt.h:90
PyObject * cpyrt_tp_call(PyObject *cb, cpyrt_PyArgs_t args, size_t nargsf, PyObject *kwds)
Definition cpyrt.h:100
#define Py_TPFLAGS_HAVE_VECTORCALL
Definition cpyrt.h:108
#define CPYRT_PYTYPE_TAIL
Definition cpyrt.h:159
#define Py_RETURN_NONE
Definition cpyrt.h:69
#define cpyrt_PyObject_Call
Definition cpyrt.h:99
static PyObject * cpyrt_PyArgs_GET_ITEM(cpyrt_PyArgs_t args, Py_ssize_t i)
Definition cpyrt.h:82
const Int_t n
Definition legend1.C:16
std::string ConstructTemplateArgs(PyObject *pyname, PyObject *tpArgs, PyObject *args=nullptr, ArgPreference=kNone, int argoff=0, int *pcnt=nullptr)
Definition Utility.cxx:640
size_t FetchError(std::vector< PyError_t > &, bool is_cpp=false)
Definition Utility.cxx:1627
void SetDetailedException(std::vector< PyError_t > &&errors, PyObject *topmsg, PyObject *defexc)
Definition Utility.cxx:1637
PyObject * CT2CppName(PyObject *pytc, const char *cpd, bool allow_voidp)
Definition Utility.h:50
bool CPPOverload_Check(T *object)
Definition CPPOverload.h:94
bool TemplateProxy_Check(T *object)
static void tpp_dealloc(TemplateProxy *pytmpl)
static int tpp_settemplateargs(TemplateProxy *, PyObject *, void *)
static PyMethodDef tpp_methods[]
static PyGetSetDef tpp_getset[]
static TemplateProxy * tpp_new(PyTypeObject *, PyObject *, PyObject *)
static void UpdateDispatchMap(TemplateProxy *pytmpl, bool use_targs, uint64_t sighash, CPPOverload *pymeth)
bool AdjustSelf(PyCallArgs &cargs)
static PyObject * tpp_vectorcall(TemplateProxy *pytmpl, PyObject *const *args, size_t nargsf, PyObject *kwds)
uint64_t HashSignature(cpyrt_PyArgs_t args, size_t nargsf)
Definition CPPOverload.h:16
static int tpp_setuseffi(CPPOverload *, PyObject *, void *)
PyTypeObject CPPOverload_Type
static PyObject * tpp_gettemplateargs(TemplateProxy *self, void *)
static int tpp_clear(TemplateProxy *pytmpl)
static PyObject * tpp_getuseffi(CPPOverload *, void *)
static std::string targs2str(TemplateProxy *pytmpl)
static PyObject * SelectAndForward(TemplateProxy *pytmpl, CPPOverload *pymeth, cpyrt_PyArgs_t args, size_t nargsf, PyObject *kwds, bool implicitOkay, bool use_targs, uint64_t sighash, std::vector< Utility::PyError_t > &errors)
PyTypeObject TemplateProxy_Type
static int tpp_doc_set(TemplateProxy *pytmpl, PyObject *val, void *)
static PyObject * tpp_subscript(TemplateProxy *pytmpl, PyObject *args)
static TemplateProxy * tpp_descr_get(TemplateProxy *pytmpl, PyObject *pyobj, PyObject *)
static Py_hash_t tpp_hash(TemplateProxy *self)
bool TemplateProxy_CheckExact(T *object)
static PyMappingMethods tpp_as_mapping
std::shared_ptr< TemplateInfo > TP_TInfo_t
static int tpp_traverse(TemplateProxy *pytmpl, visitproc visit, void *arg)
static PyObject * tpp_richcompare(TemplateProxy *self, PyObject *other, int op)
CPPOverload * CPPOverload_New(const std::string &name, std::vector< PyCallable * > &methods)
static PyObject * CallMethodImp(TemplateProxy *pytmpl, PyObject *&pymeth, cpyrt_PyArgs_t args, size_t nargsf, PyObject *kwds, bool impOK, uint64_t sighash)
static PyObject * tpp_doc(TemplateProxy *pytmpl, void *)
static PyObject * tpp_overload(TemplateProxy *pytmpl, PyObject *args)
TCppMethod_t GetMethodTemplate(TCppScope_t scope, const std::string &name, const std::string &proto)
bool IsStaticTemplate(TCppScope_t scope, const std::string &name)
bool IsConstructor(TCppMethod_t method)
Cpp::FuncRef TCppMethod_t
Definition API.h:44
std::string GetFullName(TCppScope_t)
bool IsNamespace(TCppScope_t scope)
bool IsStaticMethod(TCppMethod_t method)
Cpp::DeclRef TCppScope_t
Definition API.h:41
Definition API.cxx:34