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 // TODO: see above, since the call hasn't happened yet, this overload may
345 // already exist and fail again.
346 ((CPPOverload*)pyol)->AdoptMethod(meth); // takes ownership
347 }
348
349 // Case 5: must be a template proxy, meaning that current template name is
350 // not a template overload
351 else if (bIsCppTP) {
352 ((TemplateProxy*)pyol)->AdoptTemplate(meth->Clone());
353 Py_DECREF(pyol);
354 pyol = (PyObject*)CPPOverload_New(fname, meth); // takes ownership
355 }
356 // Case 6: pre-existing object is not a CPPOverload nor TemplateProxy
357 // we do not cache it, as this might be a pythonization (monkey-patched
358 // func/method)
359 else {
360 Py_DECREF(pyol);
361 pyol = (PyObject*)CPPOverload_New(fname, meth);
362 }
363
364 // Special Case if name was aliased (e.g. typedef in template instantiation)
365 if (!exact && !bExactMatch) {
366 PyType_Type.tp_setattro(fTI->fPyClass, pyresname, pyol);
367 }
368
369 // cleanup
370 Py_XDECREF(exact);
371 Py_DECREF(pyresname);
372 Py_DECREF(pycachename);
373
374 // retrieve fresh (for boundedness) and call
375 PyObject* pymeth = CPPOverload_Type.tp_descr_get(
376 pyol, bNeedsRebind ? fSelf : nullptr, (PyObject*)&CPPOverload_Type);
377 Py_DECREF(pyol);
378 return pymeth;
379 }
380
381 PyErr_Format(PyExc_TypeError, "Failed to instantiate \"%s(%s)\"",
382 fname.c_str(), proto.c_str());
383 return nullptr;
384}
385
386//= cpyrt template proxy construction/destruction =========================
387static TemplateProxy* tpp_new(PyTypeObject*, PyObject*, PyObject*) {
388 // Create a new empty template method proxy.
389 TemplateProxy* pytmpl = PyObject_GC_New(TemplateProxy, &TemplateProxy_Type);
390 pytmpl->fSelf = nullptr;
391 pytmpl->fTemplateArgs = nullptr;
392 pytmpl->fWeakrefList = nullptr;
393 new (&pytmpl->fTI) TP_TInfo_t{};
394 pytmpl->fTI = std::make_shared<TemplateInfo>();
395
396 PyObject_GC_Track(pytmpl);
397 return pytmpl;
398}
399
400//----------------------------------------------------------------------------
401static Py_hash_t tpp_hash(TemplateProxy* self) { return (Py_hash_t)self; }
402
403//----------------------------------------------------------------------------
404static PyObject* tpp_richcompare(TemplateProxy* self, PyObject* other, int op) {
405 if (op == Py_EQ || op == Py_NE) {
406 if (!TemplateProxy_CheckExact(other))
408
409 if (self->fTI == ((TemplateProxy*)other)->fTI)
411
413 }
414
415 Py_INCREF(Py_NotImplemented);
416 return Py_NotImplemented;
417}
418
419//----------------------------------------------------------------------------
420static int tpp_clear(TemplateProxy* pytmpl) {
421 // Garbage collector clear of held python member objects.
422 Py_CLEAR(pytmpl->fSelf);
423 Py_CLEAR(pytmpl->fTemplateArgs);
424
425 return 0;
426}
427
428//----------------------------------------------------------------------------
429static void tpp_dealloc(TemplateProxy* pytmpl) {
430 // Destroy the given template method proxy.
431 if (pytmpl->fWeakrefList)
432 PyObject_ClearWeakRefs((PyObject*)pytmpl);
433 PyObject_GC_UnTrack(pytmpl);
434 tpp_clear(pytmpl);
435 pytmpl->fTI.~TP_TInfo_t();
436 PyObject_GC_Del(pytmpl);
437}
438
439//----------------------------------------------------------------------------
440static int tpp_traverse(TemplateProxy* pytmpl, visitproc visit, void* arg) {
441 // Garbage collector traverse of held python member objects.
442 Py_VISIT(pytmpl->fSelf);
443 Py_VISIT(pytmpl->fTemplateArgs);
444
445 return 0;
446}
447
448//----------------------------------------------------------------------------
449static PyObject* tpp_doc(TemplateProxy* pytmpl, void*) {
450 if (pytmpl->fTI->fDoc) {
451 Py_INCREF(pytmpl->fTI->fDoc);
452 return pytmpl->fTI->fDoc;
453 }
454
455 // Forward to method proxies to doc all overloads
456 PyObject* doc = nullptr;
457 if (pytmpl->fTI->fNonTemplated->HasMethods())
458 doc = PyObject_GetAttrString((PyObject*)pytmpl->fTI->fNonTemplated,
459 "__doc__");
460 if (pytmpl->fTI->fTemplated->HasMethods()) {
461 PyObject* doc2 =
462 PyObject_GetAttrString((PyObject*)pytmpl->fTI->fTemplated, "__doc__");
463 if (doc && doc2) {
464 PyUnicode_AppendAndDel(&doc, PyUnicode_FromString("\n"));
465 PyUnicode_AppendAndDel(&doc, doc2);
466 } else if (!doc && doc2) {
467 doc = doc2;
468 }
469 }
470 if (pytmpl->fTI->fLowPriority->HasMethods()) {
471 PyObject* doc2 =
472 PyObject_GetAttrString((PyObject*)pytmpl->fTI->fLowPriority, "__doc__");
473 if (doc && doc2) {
474 PyUnicode_AppendAndDel(&doc, PyUnicode_FromString("\n"));
475 PyUnicode_AppendAndDel(&doc, doc2);
476 } else if (!doc && doc2) {
477 doc = doc2;
478 }
479 }
480
481 if (doc)
482 return doc;
483
484 return PyUnicode_FromString(TemplateProxy_Type.tp_doc);
485}
486
487static int tpp_doc_set(TemplateProxy* pytmpl, PyObject* val, void*) {
488 Py_XDECREF(pytmpl->fTI->fDoc);
489 Py_INCREF(val);
490 pytmpl->fTI->fDoc = val;
491 return 0;
492}
493
494//----------------------------------------------------------------------------
495
496//= cpyrt template proxy callable behavior ================================
497
498#define TPPCALL_RETURN \
499 { \
500 errors.clear(); \
501 return result; \
502 }
503
504static inline void UpdateDispatchMap(TemplateProxy* pytmpl, bool use_targs,
505 uint64_t sighash, CPPOverload* pymeth) {
506 // Memoize a method in the dispatch map after successful call; replace old if
507 // need be (may be with the same CPPOverload, just with more methods).
508 bool bInserted = false;
509 auto& v = pytmpl->fTI->fDispatchMap[use_targs ? targs2str(pytmpl) : ""];
510
511 Py_INCREF(pymeth);
512 for (auto& p : v) {
513 if (p.first == sighash) {
514 Py_DECREF(p.second);
515 p.second = pymeth;
516 bInserted = true;
517 }
518 }
519 if (!bInserted)
520 v.push_back(std::make_pair(sighash, pymeth));
521}
522
523static inline PyObject*
525 cpyrt_PyArgs_t args, size_t nargsf, PyObject* kwds,
526 bool implicitOkay, bool use_targs, uint64_t sighash,
527 std::vector<Utility::PyError_t>& errors) {
528 // Forward a call to known overloads, if any.
529 if (pymeth->HasMethods()) {
530 PyObject* pycall = CPPOverload_Type.tp_descr_get(
531 (PyObject*)pymeth, pytmpl->fSelf, (PyObject*)&CPPOverload_Type);
532
533 if (!implicitOkay)
534 ((CPPOverload*)pycall)->fFlags |= CallContext::kNoImplicit;
535
536 // now call the method with the arguments (loops internally)
537 PyObject* result = cpyrt_tp_call(pycall, args, nargsf, kwds);
538 Py_DECREF(pycall);
539 if (result) {
540 UpdateDispatchMap(pytmpl, use_targs, sighash, pymeth);
542 }
543 Utility::FetchError(errors);
544 }
545
546 return nullptr;
547}
548
549static inline PyObject* CallMethodImp(TemplateProxy* pytmpl, PyObject*& pymeth,
550 cpyrt_PyArgs_t args, size_t nargsf,
551 PyObject* kwds, bool impOK,
552 uint64_t sighash) {
553 // Actual call of a given overload: takes care of handlign of "self" and
554 // dereferences the overloaded method after use.
555
557 if (!impOK && CPPOverload_Check(pymeth))
558 ((CPPOverload*)pymeth)->fFlags |= CallContext::kNoImplicit;
559 bool isNS =
560 (((CPPScope*)pytmpl->fTI->fPyClass)->fFlags & CPPScope::kIsNamespace);
561 if (isNS && pytmpl->fSelf && pytmpl->fSelf != Py_None) {
562 // this is a global method added a posteriori to the class
563 PyCallArgs cargs{(CPPInstance*&)pytmpl->fSelf, args, nargsf, kwds};
564 AdjustSelf(cargs);
565 result = cpyrt_tp_call(pymeth, cargs.fArgs, cargs.fNArgsf, cargs.fKwds);
566 } else {
567 if (!pytmpl->fSelf && CPPOverload_Check(pymeth))
568 ((CPPOverload*)pymeth)->fFlags &= ~CallContext::kFromDescr;
569 result = cpyrt_tp_call(pymeth, args, nargsf, kwds);
570 }
571
572 if (result) {
573 Py_XDECREF(((CPPOverload*)pymeth)->fSelf);
574 ((CPPOverload*)pymeth)->fSelf = nullptr; // unbind
575 UpdateDispatchMap(pytmpl, true, sighash, (CPPOverload*)pymeth);
576 }
577
578 Py_DECREF(pymeth);
579 pymeth = nullptr;
580 return result;
581}
582
583static PyObject* tpp_vectorcall(TemplateProxy* pytmpl, PyObject* const* args,
584 size_t nargsf, PyObject* kwds) {
585 // Dispatcher to the actual member method, several uses possible; in order:
586 //
587 // case 1: explicit template previously selected through subscript
588 //
589 // case 2: select known non-template overload
590 //
591 // obj.method(a0, a1, ...)
592 // => obj->method(a0, a1, ...) // non-template
593 //
594 // case 3: select known template overload
595 //
596 // obj.method(a0, a1, ...)
597 // => obj->method(a0, a1, ...) // all known templates
598 //
599 // case 4: auto-instantiation from types of arguments
600 //
601 // obj.method(a0, a1, ...)
602 // => obj->method<type(a0), type(a1), ...>(a0, a1, ...)
603 //
604 // Note: explicit instantiation needs to use [] syntax:
605 //
606 // obj.method[type<a0>, type<a1>, ...](a0, a1, ...)
607 //
608 // case 5: low priority methods, such as ones that take void* arguments
609 //
610
611 // TODO: should previously instantiated templates be considered first?
612
613 PyObject *pymeth = nullptr, *result = nullptr;
614
615 // short-cut through memoization map
616 Py_ssize_t argc = cpyrt_PyArgs_GET_SIZE(args, nargsf);
617 uint64_t sighash = HashSignature(args, argc);
618
619 CPPOverload* ol = nullptr;
620 if (!pytmpl->fTemplateArgs) {
621 // look for known signatures ...
622 auto& v = pytmpl->fTI->fDispatchMap[""];
623 for (const auto& p : v) {
624 if (p.first == sighash) {
625 ol = p.second;
626 break;
627 }
628 }
629
630 if (ol != nullptr) {
631 if (!pytmpl->fSelf || pytmpl->fSelf == Py_None) {
632 result = cpyrt_tp_call((PyObject*)ol, args, nargsf, kwds);
633 } else {
634 pymeth = CPPOverload_Type.tp_descr_get((PyObject*)ol, pytmpl->fSelf,
636 result = cpyrt_tp_call(pymeth, args, nargsf, kwds);
637 Py_DECREF(pymeth);
638 pymeth = nullptr;
639 }
640 if (result)
641 return result;
642 }
643 }
644
645 // container for collecting errors
646 std::vector<Utility::PyError_t> errors;
647 if (ol)
648 Utility::FetchError(errors);
649
650 // case 1: explicit template previously selected through subscript
651 if (pytmpl->fTemplateArgs) {
652 // instantiate explicitly
653 PyObject* pyfullname = PyUnicode_FromString(pytmpl->fTI->fCppName.c_str());
654 PyUnicode_Append(&pyfullname, pytmpl->fTemplateArgs);
655
656 // first, lookup by full name, if previously stored
657 bool isNS =
658 (((CPPScope*)pytmpl->fTI->fPyClass)->fFlags & CPPScope::kIsNamespace);
659 if (pytmpl->fSelf && pytmpl->fSelf != Py_None && !isNS)
660 pymeth = PyObject_GetAttr(pytmpl->fSelf, pyfullname);
661 else // by-passes custom scope getattr that searches into Cling
662 pymeth = PyType_Type.tp_getattro(pytmpl->fTI->fPyClass, pyfullname);
663
664 // attempt call if found (this may fail if there are specializations)
665 if (CPPOverload_Check(pymeth)) {
666 // since the template args are fully explicit, allow implicit conversion
667 // of arguments
668 result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, /*impOK=*/true,
669 sighash);
670 if (result) {
671 Py_DECREF(pyfullname);
673 }
674 Utility::FetchError(errors);
675 } else if (pymeth && PyCallable_Check(pymeth)) {
676 // something different (user provided?)
677 result = cpyrt_PyObject_Call(pymeth, args, nargsf, kwds);
678 Py_DECREF(pymeth);
679 if (result) {
680 Py_DECREF(pyfullname);
682 }
683 Utility::FetchError(errors);
684 } else if (!pymeth)
685 PyErr_Clear();
686
687 // not cached or failed call; try instantiation
688 pymeth = pytmpl->Instantiate(PyUnicode_AsUTF8(pyfullname), args, nargsf,
690 if (pymeth) {
691 // attempt actual call; same as above, allow implicit conversion of
692 // arguments
693 result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, /*impOK=*/true,
694 sighash);
695 if (result) {
696 Py_DECREF(pyfullname);
698 }
699 }
700
701 // no drop through if failed (if implicit was desired, don't provide
702 // template args)
703 Utility::FetchError(errors);
704 PyObject* topmsg = PyUnicode_FromFormat(
705 "Could not find \"%s\" (set cppjit.set_debug() for C++ errors):",
706 PyUnicode_AsUTF8(pyfullname));
707 Py_DECREF(pyfullname);
708 Utility::SetDetailedException(std::move(errors), topmsg /* steals */,
709 PyExc_TypeError /* default error */);
710
711 return nullptr;
712 }
713
714 // case 2: select known non-template overload
715 result = SelectAndForward(pytmpl, pytmpl->fTI->fNonTemplated, args, nargsf,
716 kwds, true /* implicitOkay */,
717 false /* use_targs */, sighash, errors);
718 if (result)
720
721 // case 3: select known template overload
722 result = SelectAndForward(pytmpl, pytmpl->fTI->fTemplated, args, nargsf, kwds,
723 false /* implicitOkay */, true /* use_targs */,
724 sighash, errors);
725 if (result)
727
728 // case 4: auto-instantiation from types of arguments
730 // TODO: no need to loop if there are no non-instance arguments; also,
731 // should any failed lookup be removed?
732 int pcnt = 0;
733 pymeth =
734 pytmpl->Instantiate(pytmpl->fTI->fCppName, args, nargsf, pref, &pcnt);
735 if (pymeth) {
736 // attempt actual call; even if argument based, allow implicit
737 // conversions, for example for non-template arguments
738 result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, /*impOK=*/true,
739 sighash);
740 if (result)
742 }
743 Utility::FetchError(errors);
744 if (!pcnt)
745 break; // preference never used; no point trying others
746 }
747
748 // case 5: low priority methods, such as ones that take void* arguments
749 result = SelectAndForward(pytmpl, pytmpl->fTI->fLowPriority, args, nargsf,
750 kwds, false /* implicitOkay */,
751 false /* use_targs */, sighash, errors);
752 if (result)
754
755 // error reporting is fraud, given the numerous steps taken, but more details
756 // seems better
757 if (!errors.empty()) {
758 PyObject* topmsg =
759 PyUnicode_FromString("Template method resolution failed:");
760 Utility::SetDetailedException(std::move(errors), topmsg /* steals */,
761 PyExc_TypeError /* default error */);
762 } else {
763 PyErr_Format(PyExc_TypeError,
764 "cannot resolve method template call for \'%s\'",
765 pytmpl->fTI->fCppName.c_str());
766 }
767
768 return nullptr;
769}
770
771//----------------------------------------------------------------------------
773 PyObject*) {
774 // create and use a new template proxy (language requirement)
775 TemplateProxy* newPyTmpl =
777
778 // new method is to be bound to current object (may be nullptr)
779 if (pyobj) {
780 Py_INCREF(pyobj);
781 newPyTmpl->fSelf = pyobj;
782 } else {
783 Py_INCREF(Py_None);
784 newPyTmpl->fSelf = Py_None;
785 }
786
787 Py_XINCREF(pytmpl->fTemplateArgs);
788 newPyTmpl->fTemplateArgs = pytmpl->fTemplateArgs;
789
790 // copy name, class, etc. pointers
791 new (&newPyTmpl->fTI) std::shared_ptr<TemplateInfo>{pytmpl->fTI};
792
793 newPyTmpl->fVectorCall = pytmpl->fVectorCall;
794
795 return newPyTmpl;
796}
797
798//----------------------------------------------------------------------------
800 // Explicit template member lookup/instantiation; works by re-bounding. This
801 // method can not cache overloads as instantiations need not be unique for the
802 // argument types due to template specializations.
803 TemplateProxy* typeBoundMethod =
804 tpp_descr_get(pytmpl, pytmpl->fSelf, nullptr);
805 Py_XDECREF(typeBoundMethod->fTemplateArgs);
806 typeBoundMethod->fTemplateArgs = PyUnicode_FromString(
807 Utility::ConstructTemplateArgs(nullptr, args).c_str());
808 // Propagate the error that occurs if we can't construct the C++ name
809 // from the provided template argument
810 if (PyErr_Occurred()) {
811 return nullptr;
812 }
813 return (PyObject*)typeBoundMethod;
814}
815
816//-----------------------------------------------------------------------------
818 return PyLong_FromLong(0); // dummy (__useffi__ unused)
819}
820
821//-----------------------------------------------------------------------------
822static int tpp_setuseffi(CPPOverload*, PyObject*, void*) {
823 return 0; // dummy (__useffi__ unused)
824}
825
826//-----------------------------------------------------------------------------
828 if (!self->fTemplateArgs) {
830 }
831
832 Py_INCREF(self->fTemplateArgs);
833 return self->fTemplateArgs;
834}
835
836//-----------------------------------------------------------------------------
838 PyErr_SetString(PyExc_AttributeError, "__template_args__ is read-only");
839 return -1;
840}
841
842//----------------------------------------------------------------------------
843static PyMappingMethods tpp_as_mapping = {nullptr, (binaryfunc)tpp_subscript,
844 nullptr};
845
846static PyGetSetDef tpp_getset[] = {
847 {(char*)"__doc__", (getter)tpp_doc, (setter)tpp_doc_set, nullptr, nullptr},
848 {(char*)"__useffi__", (getter)tpp_getuseffi, (setter)tpp_setuseffi,
849 (char*)"unused", nullptr},
850 {(char*)"__template_args__", (getter)tpp_gettemplateargs,
851 (setter)tpp_settemplateargs,
852 (char*)"the template arguments for this method", nullptr},
853 {(char*)nullptr, nullptr, nullptr, nullptr, nullptr},
854};
855
856//----------------------------------------------------------------------------
857void TemplateProxy::Set(const std::string& cppname, const std::string& pyname,
858 PyObject* pyclass) {
859 // Initialize the proxy for the given 'pyclass.'
860 fSelf = nullptr;
861 fTemplateArgs = nullptr;
862
863 fTI->fCppName = cppname;
864 Py_XINCREF(pyclass);
865 fTI->fPyClass = pyclass;
866
867 std::vector<PyCallable*> dummy;
868 fTI->fNonTemplated = CPPOverload_New(pyname, dummy);
869 fTI->fTemplated = CPPOverload_New(pyname, dummy);
870 fTI->fLowPriority = CPPOverload_New(pyname, dummy);
871
872 fVectorCall = (vectorcallfunc)tpp_vectorcall;
873}
874
875//= cpyrt method proxy access to internals ================================
877 // Select and call a specific C++ overload, based on its signature.
878 const char* sigarg = nullptr;
879 const char* tmplarg = nullptr;
880 PyObject* sigarg_tuple = nullptr;
881 int want_const = -1;
882
883 interop::TCppScope_t scope = nullptr;
884 interop::TCppMethod_t cppmeth = nullptr;
885 std::string proto;
886
887 if (PyArg_ParseTuple(args, const_cast<char*>("s|i:__overload__"), &sigarg,
888 &want_const)) {
889 want_const = PyTuple_GET_SIZE(args) == 1 ? -1 : want_const;
890
891 // check existing overloads in order
892 PyObject* ol = pytmpl->fTI->fNonTemplated->FindOverload(sigarg, want_const);
893 if (ol)
894 return ol;
895 PyErr_Clear();
896 ol = pytmpl->fTI->fTemplated->FindOverload(sigarg, want_const);
897 if (ol)
898 return ol;
899 PyErr_Clear();
900 ol = pytmpl->fTI->fLowPriority->FindOverload(sigarg, want_const);
901 if (ol)
902 return ol;
903
904 proto = Utility::ConstructTemplateArgs(nullptr, args);
905 // Propagate the error that occurs if we can't construct the C++ name
906 // from the provided template argument
907 if (PyErr_Occurred()) {
908 return nullptr;
909 }
910
911 scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType;
912 cppmeth = interop::GetMethodTemplate(scope, pytmpl->fTI->fCppName,
913 proto.substr(1, proto.size() - 2));
914 } else if (PyArg_ParseTuple(args, const_cast<char*>("ss:__overload__"),
915 &sigarg, &tmplarg)) {
916 scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType;
917 std::string full_name =
918 std::string(pytmpl->fTI->fCppName) + "<" + tmplarg + ">";
919
920 cppmeth = interop::GetMethodTemplate(scope, full_name, sigarg);
921 } else if (PyArg_ParseTuple(args, const_cast<char*>("O|i:__overload__"),
922 &sigarg_tuple, &want_const)) {
923 PyErr_Clear();
924 want_const = PyTuple_GET_SIZE(args) == 1 ? -1 : want_const;
925
926 // check existing overloads in order
927 PyObject* ol =
928 pytmpl->fTI->fNonTemplated->FindOverload(sigarg_tuple, want_const);
929 if (ol)
930 return ol;
931 PyErr_Clear();
932 ol = pytmpl->fTI->fTemplated->FindOverload(sigarg_tuple, want_const);
933 if (ol)
934 return ol;
935 PyErr_Clear();
936 ol = pytmpl->fTI->fLowPriority->FindOverload(sigarg_tuple, want_const);
937 if (ol)
938 return ol;
939
940 proto.reserve(128);
941 proto.push_back('<');
942 Py_ssize_t n = PyTuple_Size(sigarg_tuple);
943 for (int i = 0; i < n; i++) {
944 PyObject* pItem = PyTuple_GetItem(sigarg_tuple, i);
945 if (!PyUnicode_Check(pItem)) {
946 PyErr_Format(PyExc_LookupError,
947 "argument types should be in string format");
948 return (PyObject*)nullptr;
949 }
950 proto.append(PyUnicode_AsUTF8(pItem));
951 if (i < n - 1)
952 proto.push_back(',');
953 }
954 proto.push_back('>');
955
956 scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType;
957 cppmeth = interop::GetMethodTemplate(scope, pytmpl->fTI->fCppName,
958 proto.substr(1, proto.size() - 2));
959 } else {
960 PyErr_Format(PyExc_TypeError, "Unexpected arguments to __overload__");
961 return nullptr;
962 }
963
964 // else attempt instantiation
965 if (!cppmeth) {
966 return nullptr;
967 }
968
969 PyErr_Clear();
970
971 // TODO: the next step should be consolidated with Instantiate()
972 PyCallable* meth = nullptr;
973 if (interop::IsNamespace(scope)) {
974 meth = new CPPFunction(scope, cppmeth);
975 } else if (interop::IsStaticMethod(cppmeth)) {
976 meth = new CPPClassMethod(scope, cppmeth);
977 } else if (interop::IsConstructor(cppmeth)) {
978 meth = new CPPConstructor(scope, cppmeth);
979 } else
980 meth = new CPPMethod(scope, cppmeth);
981
982 return (PyObject*)CPPOverload_New(pytmpl->fTI->fCppName + proto, meth);
983}
984
985static PyMethodDef tpp_methods[] = {{(char*)"__overload__",
986 (PyCFunction)tpp_overload, METH_VARARGS,
987 (char*)"select overload for dispatch"},
988 {(char*)nullptr, nullptr, 0, nullptr}};
989
990//= cpyrt template proxy type =============================================
991PyTypeObject TemplateProxy_Type = {
992 PyVarObject_HEAD_INIT(&PyType_Type,
993 0)(char*) "cppjit.TemplateProxy", // tp_name
994 sizeof(TemplateProxy), // tp_basicsize
995 0, // tp_itemsize
996 (destructor)tpp_dealloc, // tp_dealloc
997 offsetof(TemplateProxy, fVectorCall),
998 0, // tp_getattr
999 0, // tp_setattr
1000 0, // tp_as_async / tp_compare
1001 0, // tp_repr
1002 0, // tp_as_number
1003 0, // tp_as_sequence
1004 &tpp_as_mapping, // tp_as_mapping
1005 (hashfunc)tpp_hash, // tp_hash
1006 (ternaryfunc)PyVectorcall_Call, // tp_call
1007 0, // tp_str
1008 0, // tp_getattro
1009 0, // tp_setattro
1010 0, // tp_as_buffer
1011 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_VECTORCALL |
1012 Py_TPFLAGS_METHOD_DESCRIPTOR, // tp_flags
1013 (char*)"cppjit template proxy (internal)", // tp_doc
1014 (traverseproc)tpp_traverse, // tp_traverse
1015 (inquiry)tpp_clear, // tp_clear
1016 (richcmpfunc)tpp_richcompare, // tp_richcompare
1017 offsetof(TemplateProxy, fWeakrefList), // tp_weaklistoffset
1018 0, // tp_iter
1019 0, // tp_iternext
1020 tpp_methods, // tp_methods
1021 0, // tp_members
1022 tpp_getset, // tp_getset
1023 0, // tp_base
1024 0, // tp_dict
1025 (descrgetfunc)tpp_descr_get, // tp_descr_get
1026 0, // tp_descr_set
1027 0, // tp_dictoffset
1028 0, // tp_init
1029 0, // tp_alloc
1030 (newfunc)tpp_new, // tp_new
1031 0, // tp_free
1032 0, // tp_is_gc
1033 0, // tp_bases
1034 0, // tp_mro
1035 0, // tp_cache
1036 0, // tp_subclasses
1037 0, // tp_weaklist
1038 0, // tp_del
1039 0, // tp_version_tag
1040 0, // tp_finalize
1041 0 // tp_vectorcall
1043
1044} // 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:1502
void SetDetailedException(std::vector< PyError_t > &&errors, PyObject *topmsg, PyObject *defexc)
Definition Utility.cxx:1512
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