Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
interop_wrapper.cxx
Go to the documentation of this file.
1#ifndef _WIN32
2#ifndef _CRT_SECURE_NO_WARNINGS
3// silence warnings about getenv, strncpy, etc.
4#define _CRT_SECURE_NO_WARNINGS
5#endif
6#endif
7
8#include "precommondefs.h" // This defines several system feature macros and should be included before any system header.
9
10// Bindings
11#include "cppjit_interop.h"
12
13using namespace cppjit;
14#include "callcontext.h"
15
16// ROOT
17#include "TClass.h"
18#include "TInterpreter.h"
19#include "TROOT.h"
20#include "TSystem.h"
21
23
24static inline size_t CALL_NARGS(size_t nargs) { return nargs & ~DIRECT_CALL; }
25
26// Standard
27#include <algorithm> // for std::count, std::remove
28#include <array>
29#include <cassert>
30#include <csignal>
31#include <cstdlib> // for getenv
32#include <cstring>
33#include <iostream>
34#include <map>
35#include <mutex>
36#include <new>
37#include <regex>
38#include <set>
39#include <sstream>
40#include <stdexcept>
41#include <typeinfo>
42#include <vector>
43
44// Take ROOT's interpreter lock (when it exists) around every CppInterOp
45// entry point, so Python-side reflection serializes against TCling users on
46// other threads (I/O, RDataFrame workers).
47// The private recursive mutex still serializes concurrent Python-side callers
48// when ROOT thread safety is not enabled. Lock order is global-then-local; no
49// deadlock is possible since threads coming through TCling only ever take the
50// global mutex.
52public:
53 void lock() {
54 // gInterpreterMutex may get created at any point (by
55 // ROOT::EnableThreadSafety), so remember for each acquisition what was
56 // actually locked, to release exactly that in unlock().
58 if (global)
59 global->Lock();
60 fLocal.lock();
61 fGlobalLocked.push_back(global);
62 }
63
64 void unlock() {
65 TVirtualMutex* global = fGlobalLocked.back();
66 fGlobalLocked.pop_back();
67 fLocal.unlock();
68 if (global)
69 global->UnLock();
70 }
71
72private:
73 std::recursive_mutex fLocal;
74 // Lock/unlock pairs are properly nested per thread, so a per-thread stack
75 // suffices to match each unlock() to its lock().
76 static thread_local std::vector<TVirtualMutex*> fGlobalLocked;
77};
78
79thread_local std::vector<TVirtualMutex*> RInterOpMutex::fGlobalLocked;
80
82
83// builtin types
84static std::set<std::string> g_builtins = {"bool",
85 "char",
86 "signed char",
87 "unsigned char",
88 "wchar_t",
89 "short",
90 "unsigned short",
91 "int",
92 "unsigned int",
93 "long",
94 "unsigned long",
95 "long long",
96 "unsigned long long",
97 "float",
98 "double",
99 "long double",
100 "void"};
101
102// configuration
103static bool gEnableFastPath = true;
104
105// global initialization -----------------------------------------------------
106namespace {
107
108static inline bool is_integral(std::string& s) {
109 if (s == "false") {
110 s = "0";
111 return true;
112 } else if (s == "true") {
113 s = "1";
114 return true;
115 }
116 return !s.empty() && std::find_if(s.begin(), s.end(), [](unsigned char c) {
117 return !std::isdigit(c);
118 }) == s.end();
119}
120
121struct InterOpPaths {
122 std::string Library;
123 std::vector<std::string> IncludeDirs;
124 std::string ClangIncludeDir; // empty when no usable resource dir is known
125};
126
127// In ROOT the dispatch API comes from libCling (loadDispatchAPI) and the
128// CppInterOp headers are staged under ROOT's etc directory, etc/cppinterop
129// in the build tree and in the install tree alike, so TROOT::GetEtcDir()
130// locates them in both.
131static InterOpPaths cppinterop_paths() {
132 InterOpPaths Paths;
133 std::string dir = TROOT::GetEtcDir().Data();
134 dir += "/cppinterop";
135 if (gSystem->AccessPathName(dir.c_str()))
136 std::cerr << "[cppjit] CppInterOp headers not found in " << dir
137 << std::endl;
138 Paths.IncludeDirs.push_back(dir);
139 return Paths;
140}
141
142// The one place the dispatch source is dlopen'd. In ROOT, CppInterOp is
143// compiled into libCling, so the dispatch API is loaded from there instead of
144// from a standalone libclangCppInterOp.
145static bool loadDispatchAPI(const InterOpPaths& /*Paths*/) {
146 (void)gROOT;
147 char* libcling = gSystem->DynamicPathName("libCling");
148
149 if (!libcling) {
150 std::cerr << "[cppjit] Failed to find libCling" << std::endl;
151 return false;
152 }
153 if (!Cpp::LoadDispatchAPI(libcling)) {
154 std::cerr << "[cppjit] Failed to load CppInterOp" << std::endl;
155 return false;
156 }
157 return true;
158}
159
160// CppInterOp itself appends CPPINTEROP_EXTRA_INTERPRETER_ARGS inside
161// CreateInterpreter, so nothing needs to be forwarded from here.
163acquireOrCreateInterpreter(const InterOpPaths& Paths) {
164 if (auto existingInterp = Cpp::GetInterpreter())
165 return existingInterp;
166
167 std::vector<const char*> args = {"-std=c++17"};
168#if !(defined(__arm64__) && defined(__APPLE__))
169 // apple silicon clang rejects -march=native
170 args.push_back("-march=native");
171#endif
172 // Without clang's builtin headers the interpreter fails at its first
173 // #include. Prefer the bundled copy: it matches the build clang and
174 // needs no LLVM on the host. DetectResourceDir refuses version
175 // mismatches, and CppInterOp itself probes only bare `clang`.
176 std::string resourceDir = Paths.ClangIncludeDir;
177 if (resourceDir.empty())
178 resourceDir = Cpp::DetectResourceDir("clang-" CPPJIT_CLANG_MAJOR);
179 if (!resourceDir.empty()) {
180 args.push_back("-resource-dir");
181 args.push_back(resourceDir.c_str());
182 }
183 return Cpp::CreateInterpreter(args, /*GpuArgs=*/{});
184}
185
186static void configureInterpreter(const InterOpPaths& Paths) {
187 std::set<std::string> bi{g_builtins};
188 for (const auto& name : bi) {
189 for (const char* a : {"*", "&", "*&", "[]", "*[]"})
190 g_builtins.insert(name + a);
191 }
192
193 if (getenv("CPPJIT_DISABLE_FASTPATH"))
194 gEnableFastPath = false;
195
196 // set opt level (default to 2 if not given; Cling itself defaults to 0)
197 int optLevel = 2;
198
199 if (getenv("CPPJIT_OPT_LEVEL"))
200 optLevel = atoi(getenv("CPPJIT_OPT_LEVEL"));
201
202 if (optLevel != 0) {
203 std::ostringstream s;
204 s << "#pragma cling optimize " << optLevel;
205 Cpp::Process(s.str().c_str());
206 }
207
208 for (const std::string& dir : Paths.IncludeDirs)
209 Cpp::AddIncludePath(dir.c_str());
210 Cpp::LoadLibrary("libstdc++", /* lookup= */ true);
211}
212
213static bool preloadHeaders() {
214 const char* code = "#include <algorithm>\n"
215 "#include <numeric>\n"
216 "#include <complex>\n"
217 "#include <iostream>\n"
218 "#include <string.h>\n" // for strcpy
219 "#include <string>\n"
220 "#include <vector>\n"
221 "#include <utility>\n"
222 "#include <memory>\n"
223 "#include <functional>\n" // for the dispatcher code to
224 // use std::function
225 "#include <map>\n" // FIXME: Replace with modules
226 "#include <sstream>\n" // FIXME: Replace with modules
227 "#include <array>\n" // FIXME: Replace with modules
228 "#include <list>\n" // FIXME: Replace with modules
229 "#include <deque>\n" // FIXME: Replace with modules
230 "#include <tuple>\n" // FIXME: Replace with modules
231 "#include <set>\n" // FIXME: Replace with modules
232 "#include <chrono>\n" // FIXME: Replace with modules
233 "#include <cmath>\n" // FIXME: Replace with modules
234 "#if __has_include(<optional>)\n"
235 "#include <optional>\n"
236 "#endif\n"
237 "#include <CppInterOp/Dispatch.h>\n";
238 return Cpp::Process(code) == 0;
239}
240
241static void defineRuntimeHelpers() {
242 Cpp::Declare("namespace __cppjit_internal { template<class C1, class C2>"
243 " bool is_equal(const C1& c1, const C2& c2) { return "
244 "(bool)(c1 == c2); } }",
245 /*silent=*/false);
246 Cpp::Declare("namespace __cppjit_internal { template<class C1, class C2>"
247 " bool is_not_equal(const C1& c1, const C2& c2) { return "
248 "(bool)(c1 != c2); } }",
249 /*silent=*/false);
250
251 // helper for multiple inheritance
252 Cpp::Declare("namespace __cppjit_internal { struct Sep; }",
253 /*silent=*/false);
254}
255
256} // unnamed namespace
257
258// Load CppInterOp and set up the interpreter. A dlopen during static
259// initialization is unsafe, so _cpython_cppjit.py calls this explicitly
260// before the first libcppjit use. Thread-safe and idempotent; returns 1 on
261// success.
262extern "C" {
264}
265
266extern "C" int LoadCppInterOp() {
267 static std::once_flag Once;
268 static int Loaded = 0;
269 std::call_once(Once, [] {
270 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
271 const InterOpPaths Paths = cppinterop_paths();
272 if (!loadDispatchAPI(Paths))
273 return;
274
275 if (!acquireOrCreateInterpreter(Paths)) {
276 std::cerr << "[cppjit] Failed to create the C++ interpreter" << std::endl;
277 return;
278 }
279 configureInterpreter(Paths);
280 if (!preloadHeaders()) {
281 std::cerr << "[cppjit] The C++ standard headers do not parse, see the "
282 "diagnostic above. Install a C++ toolchain such as g++ or "
283 "the conda package cxx-compiler."
284 << std::endl;
285 return;
286 }
287 defineRuntimeHelpers();
288
289 Loaded = 1;
290 });
291 return Loaded;
292}
293
294// local helpers -------------------------------------------------------------
295static inline char* cppstring_to_cstring(const std::string& cppstr) {
296 char* cstr = (char*)malloc(cppstr.size() + 1);
297 memcpy(cstr, cppstr.c_str(), cppstr.size() + 1);
298 return cstr;
299}
300
301// direct interpreter access -------------------------------------------------
302// Returns false on failure and true on success
303bool interop::Compile(const std::string& code, bool silent) {
304 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
305 // Declare returns an enum which equals 0 on success
306 return !Cpp::Declare(code.c_str(), silent);
307}
308
310
312
314 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
315 if (klass && obj && !Cpp::IsNamespace(klass))
316 return Cpp::ObjToString(Cpp::GetQualifiedCompleteName(klass).c_str(),
317 obj.data);
318 return "";
319}
320
321// // name to opaque C++ scope representation
322// -----------------------------------
323std::string interop::ResolveName(const std::string& name) {
324 if (!name.empty()) {
326 interop::GetType(name, /*enable_slow_lookup=*/true))
328 return name;
329 }
330 return "";
331}
332
334 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
335 if (Cpp::GetValueKind(type) != Cpp::ValueKind::LValue)
336 return type;
337
338 TCppType_t nonReferenceType = Cpp::GetNonReferenceType(type);
339 if (Cpp::IsEnumType(nonReferenceType)) {
340 TCppType_t underlying_type =
341 Cpp::GetIntegerTypeFromEnumType(nonReferenceType);
342 return Cpp::GetReferencedType(underlying_type, /*rvalue=*/false);
343 }
344 return type;
345}
346
348 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
349 if (!Cpp::IsPointerType(type))
350 return type;
351
352 TCppType_t PointeeType = Cpp::GetPointeeType(type);
353 if (Cpp::IsEnumType(PointeeType)) {
354 TCppType_t underlying_type = Cpp::GetIntegerTypeFromEnumType(PointeeType);
355 return Cpp::GetPointerType(underlying_type);
356 }
357 return type;
358}
359
361 interop::TCppType_t check_int_typedefs = type;
362 if (Cpp::IsPointerType(check_int_typedefs))
363 check_int_typedefs = Cpp::GetPointeeType(check_int_typedefs);
364 if (Cpp::IsReferenceType(check_int_typedefs))
365 check_int_typedefs =
366 Cpp::GetReferencedType(check_int_typedefs, /*rvalue=*/false);
367
368 if (Cpp::GetTypeAsString(check_int_typedefs) == "int8_t" ||
369 Cpp::GetTypeAsString(check_int_typedefs) == "uint8_t")
370 return check_int_typedefs;
371 return nullptr;
372}
373
375 if (!type)
376 return type;
377
378 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
379
380 TCppType_t check_int_typedefs = int_like_type(type);
381 if (check_int_typedefs)
382 return type;
383
384 interop::TCppType_t canonType = Cpp::GetCanonicalType(type);
385
386 if (Cpp::IsEnumType(canonType)) {
387 if (Cpp::GetTypeAsString(type) != "std::byte")
388 return Cpp::GetIntegerTypeFromEnumType(canonType);
389 }
390 if (Cpp::HasTypeQualifier(canonType, Cpp::QualKind::Restrict)) {
391 return Cpp::RemoveTypeQualifier(canonType, Cpp::QualKind::Restrict);
392 }
393
394 return canonType;
395}
396
398 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
399 TCppType_t check_int_typedefs = int_like_type(type);
400 if (check_int_typedefs)
401 return check_int_typedefs;
402 return Cpp::GetUnderlyingType(type);
403}
404
406 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
407 return Cpp::GetPointerType(type);
408}
409
411 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
412 return Cpp::GetReferencedType(type, rvalue);
413}
414
416 return Cpp::GetValueKind(type) == Cpp::ValueKind::RValue;
417}
418
420 return Cpp::GetValueKind(type) == Cpp::ValueKind::LValue;
421}
422
423// Whether the callee can rebind a pointer through this reference (T*&);
424// false for T* const& and for references to non-pointers.
426 if (!Cpp::IsReferenceType(type))
427 return false;
428 TCppType_t nonref = Cpp::GetNonReferenceType(type);
429 return Cpp::IsPointerType(nonref) &&
430 !Cpp::HasTypeQualifier(nonref, Cpp::QualKind::Const);
431}
432
433bool interop::IsClassType(TCppType_t type) { return Cpp::IsRecordType(type); }
434
435bool interop::IsIntegerType(TCppType_t type, bool* is_signed /*= nullptr*/) {
436 if (is_signed) {
437 Cpp::Signedness sign;
438 bool res = Cpp::IsIntegerType(type, &sign);
439 *is_signed = (sign == Cpp::Signedness::kSigned);
440 return res;
441 }
442 return Cpp::IsIntegerType(type, nullptr);
443}
444
446 return Cpp::IsPointerType(type);
447}
448
450 return Cpp::IsFunctionPointerType(type);
451}
452
453std::string trim(const std::string& line) {
454 if (line.empty())
455 return "";
456 const char* WhiteSpace = " \t\v\r\n";
457 std::size_t start = line.find_first_not_of(WhiteSpace);
458 std::size_t end = line.find_last_not_of(WhiteSpace);
459 return line.substr(start, end - start + 1);
460}
461
462// returns false of angular brackets dont match, else true
463bool split_comma_saparated_types(const std::string& name,
464 std::vector<std::string>& types) {
465 std::string trimed_name = trim(name);
466 size_t start_pos = 0;
467 size_t end_pos = 0;
468 int matching_angular_brackets = 0;
469 while (end_pos < trimed_name.size()) {
470 switch (trimed_name[end_pos]) {
471 case ',': {
472 if (!matching_angular_brackets) {
473 if (end_pos > start_pos)
474 types.push_back(
475 trim(trimed_name.substr(start_pos, end_pos - start_pos)));
476 start_pos = end_pos + 1;
477 }
478 break;
479 }
480 case '<': {
481 matching_angular_brackets++;
482 break;
483 }
484 case '>': {
485 matching_angular_brackets--;
486 break;
487 }
488 }
489 end_pos++;
490 }
491 if (start_pos < trimed_name.size())
492 types.push_back(trim(trimed_name.substr(start_pos, end_pos - start_pos)));
493 return true;
494}
495
497 std::string delim = "::";
498 size_t start = 0;
499 size_t end = name.find(delim);
500 interop::TCppScope_t curr_scope;
501 while (end != std::string::npos) {
502 curr_scope = Cpp::GetNamed(name.substr(start, end - start), curr_scope);
503 start = end + delim.length();
504 end = name.find(delim, start);
505 }
506 return Cpp::GetNamed(name.substr(start, end), curr_scope);
507}
508static bool is_identifier(std::string_view s) {
509 if (s.empty())
510 return false;
511 auto is_valid_start = [](unsigned char c) {
512 return std::isalpha(c) || c == '_';
513 };
514 auto is_valid_body = [](unsigned char c) {
515 return std::isalnum(c) || c == '_';
516 };
517 return is_valid_start(s[0]) &&
518 std::all_of(s.begin() + 1, s.end(), is_valid_body);
519};
520
521// returns true if no new type was added.
522bool interop::AppendTypesSlow(const std::string& name,
523 std::vector<Cpp::TemplateArgInfo>& types,
524 interop::TCppScope_t parent) {
525
526 // Add no new type if string is empty
527 if (name.empty())
528 return true;
529
530 // The ast printer gave us garbage.
531 if (name == "<unnamed>")
532 return true;
533
534 // A type inside an anonymous namespace cannot be spelled in injected
535 // code; attempting it crashes codegen on the ill-formed recovery (e.g.
536 // auto-downcasting to an anonymous FuncExporter<...> instantiation).
537 if (name.find("(anonymous namespace)") != std::string::npos)
538 return true;
539
540 auto replace_all = [](std::string& str, const std::string& from,
541 const std::string& to) {
542 if (from.empty())
543 return;
544 size_t start_pos = 0;
545 while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
546 str.replace(start_pos, from.length(), to);
547 start_pos += to.length();
548 }
549 };
550
551 std::string resolved_name = name;
552 replace_all(resolved_name, "std::initializer_list<",
553 "std::vector<"); // replace initializer_list with vector
554
555 // If we have a single identifier, we don't need anything complicated.
556 // Try scoped lookup first (catches type aliases / nested types declared
557 // inside `parent`), then fall back to TU (catches typedefs declared
558 // outside the query scope, e.g. `typedef Foo Bar;` at TU consulted
559 // from a method on Foo).
560 if (is_identifier(name)) {
561 TCppType_t type = parent ? Cpp::GetType(name, parent) : nullptr;
562 if (!type)
563 type = Cpp::GetType(name);
564 if (type) {
565 types.emplace_back(type.data);
566 return false;
567 }
568 if (!parent || parent == Cpp::GetGlobalScope())
569 return true;
570 // Fall through: the name may live in a scope enclosing `parent` (e.g.
571 // "RVecF" from within ROOT::RDF::RInterface<...> resolves to
572 // ROOT::RVecF); the trampoline below applies real unqualified lookup.
573 }
574
575 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
576
577 // We might have an entire expression such as int, double.
578 static unsigned long long struct_count = 0;
579 std::string code =
580 "template<typename ...T> struct __cppjit_interop_AppendTypesSlow {};\n";
581 if (!struct_count)
582 Cpp::Declare(code.c_str(), /*silent=*/true); // initialize the trampoline
583
584 // Perform the lookup from within the innermost (reopenable) namespace
585 // enclosing `parent` -- `parent` itself if it is a namespace, else the
586 // namespace its class chain lives in -- by reopening that namespace around
587 // the trampoline declaration: the compiler then applies real C++ name
588 // lookup, walking outward with proper shadowing of outer names, which a
589 // global declaration with a parent-qualified fallback cannot emulate (e.g.
590 // "RVecF" from within ROOT::RDF::RInterface<...> resolves to ROOT::RVecF).
591 std::string lookup_ns;
592 TCppScope_t lookup_scope = nullptr;
593 for (TCppScope_t s = parent; s && s != Cpp::GetGlobalScope();
594 s = Cpp::GetParentScope(s)) {
595 if (!Cpp::IsNamespace(s))
596 continue;
597 std::string qname = Cpp::GetQualifiedName(s);
598 if (!qname.empty() && qname.find('(') == std::string::npos) { // unnamed?
599 lookup_ns = qname;
600 lookup_scope = s;
601 }
602 break;
603 }
604
605 // The reopened namespace doesn't see names nested in a class parent, so a
606 // name written relative to it (e.g. a nested type) won't resolve. Try the
607 // name as given, then qualified by the parent.
608 std::vector<std::string> candidates = {resolved_name};
609 if (parent && parent != Cpp::GetGlobalScope() && parent != lookup_scope &&
610 (interop::IsNamespace(parent) || interop::IsClass(parent)))
611 candidates.push_back(Cpp::GetQualifiedCompleteName(parent) +
612 "::" + resolved_name);
613
614 for (const std::string& candidate : candidates) {
615 std::string var = "__cppjit_interop_s" + std::to_string(struct_count++);
616 // nodebug: with -g the variable's debug info would carry the full DIE
617 // tree of every template argument (all member declarations included) --
618 // a large, uncacheable per-lookup cost on heavyweight types.
619 std::string decl = "__cppjit_interop_AppendTypesSlow<" + candidate +
620 "> __attribute__((nodebug)) " + var + ";\n";
621 if (!lookup_ns.empty())
622 decl = "namespace " + lookup_ns + " { " + decl + "}\n";
623 if (!Cpp::Declare(decl.c_str(), /*silent=*/true)) {
624 TCppType_t varN = Cpp::GetVariableType(Cpp::GetNamed(
625 var.c_str(), lookup_ns.empty() ? nullptr : lookup_scope));
626 TCppScope_t instance_class = Cpp::GetScopeFromType(varN);
627 if (!instance_class)
628 continue; // recovered-but-broken decl; try next candidate or split path
629 size_t oldSize = types.size();
630 Cpp::GetClassTemplateInstantiationArgs(instance_class, types);
631 return oldSize == types.size();
632 }
633 }
634
635 // We split each individual types based on , and resolve it
636 // FIXME: see discussion on should we support template instantiation with
637 // string:
638 // https://github.com/compiler-research/cppyy-backend/pull/137#discussion_r2079357491
639 // We should consider eliminating the `split_comma_saparated_types` and
640 // `is_integral` string parsing.
641 std::vector<std::string> individual_types;
642 if (!split_comma_saparated_types(resolved_name, individual_types))
643 return true;
644
645 for (std::string& i : individual_types) {
646 // Try going via interop::GetType first.
647 const char* integral_value = nullptr;
648 interop::TCppType_t type = nullptr;
649
650 if (!lookup_ns.empty()) {
651 // resolve from within the parent namespace, honouring C++ name lookup
652 // and shadowing (see the trampoline declaration above)
653 std::string id = "__cppjit_interop_s" + std::to_string(struct_count++);
654 if (!Cpp::Declare(("namespace " + lookup_ns + " { using " + id +
655 " = __typeof__(" + i + "); }\n")
656 .c_str(),
657 /*silent=*/true))
658 type = Cpp::GetCanonicalType(
659 Cpp::GetTypeFromScope(Cpp::GetNamed(id, lookup_scope)));
660 } else {
661 type = GetType(i, /*enable_slow_lookup=*/true);
662 if (!type && parent &&
663 (interop::IsNamespace(parent) || interop::IsClass(parent))) {
664 type =
665 interop::GetTypeFromScope(interop::GetNamed(resolved_name, parent));
666 }
667 }
668
669 if (!type) {
670 types.clear();
671 return true;
672 }
673
674 if (is_integral(i))
675 integral_value = strdup(i.c_str());
677 if (Cpp::IsEnumConstant(scope))
678 integral_value =
679 strdup(std::to_string(Cpp::GetEnumConstantValue(scope)).c_str());
680 types.emplace_back(type.data, integral_value);
681 }
682 return false;
683}
684
686 bool enable_slow_lookup /* = false */) {
687 // The ast printer gave us garbage.
688 if (name == "<unnamed>")
689 return nullptr;
690 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
691
692 if (auto type = Cpp::GetType(name))
693 return type;
694
695 // Plain identifiers don't need the heavy __typeof__ trampoline:
696 // Cpp::GetType above already covers builtin types and named
697 // scopes. Exception: the three identifier-shaped C++ value-
698 // literals -- `true`, `false`, `nullptr` -- aren't reachable by
699 // name (no type called "false") but appear as non-type template
700 // args in libstdc++ types like _Node_iterator<..., false, false>;
701 // map them to their underlying type directly so the per-chunk
702 // fallback in AppendTypesSlow gets a real type without paying
703 // the trampoline cost.
704 if (is_identifier(name)) {
705 if (name == "true" || name == "false")
706 return Cpp::GetType("bool");
707 if (name == "nullptr")
708 return Cpp::GetType("nullptr_t", Cpp::GetNamed("std"));
709 return nullptr;
710 }
711
712 if (!enable_slow_lookup) {
713 if (name.find("::") != std::string::npos)
714 throw std::runtime_error(
715 "Calling interop::GetType with qualified name '" + name + "'\n");
716 return nullptr;
717 }
718
719 // Here we might need to deal with integral types such as 3.14.
720
721 // Declaring the trampoline parses and compiles code, and each one adds a
722 // new alias to the AST, so memoize the resolved types by name. A name that
723 // fails to resolve may succeed once more declarations are available, so
724 // failures are retried.
725 static std::map<std::string, TCppType_t> s_slow_type_cache;
726 auto cached = s_slow_type_cache.find(name);
727 if (cached != s_slow_type_cache.end())
728 return cached->second;
729
730 static unsigned long long var_count = 0;
731 std::string id = "__cppjit_interop_GetType_" + std::to_string(var_count++);
732 std::string using_clause = "using " + id + " = __typeof__(" + name + ");\n";
733
734 if (!Cpp::Declare(using_clause.c_str(), /*silent=*/true)) {
735 TCppScope_t lookup = Cpp::GetNamed(id);
736 TCppType_t lookup_ty = Cpp::GetTypeFromScope(lookup);
737 TCppType_t result = Cpp::GetCanonicalType(lookup_ty);
738 if (result)
739 s_slow_type_cache.emplace(name, result);
740 return result;
741 }
742 return nullptr;
743}
744
746 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
747 return Cpp::GetComplexType(Cpp::GetType(name));
748}
749
751 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
752 std::string type =
753 Cpp::GetTypeAsString(Cpp::GetIntegerTypeFromEnumScope(handle));
754 if (type == "signed char")
755 return "char";
756 return type;
757}
758
760 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
761 return Cpp::GetUnderlyingScope(scope);
762}
763
765 TCppScope_t parent_scope) {
766 std::unique_lock<RInterOpMutex> Lock(InterOpMutex);
767 // CppInterOp directly looks at the AST which is not enough.
768 // We require lazy module loading that ROOT relies on, so we do it here
769 // first. Use TClass::GetClass to trigger auto-loading of dictionaries and
770 // modules.
771 if (!parent_scope || parent_scope == Cpp::GetGlobalScope())
772 TClass::GetClass(name.c_str(), true /* load */, true /* silent */);
773
774 if (interop::TCppScope_t scope = Cpp::GetScope(name, parent_scope))
775 return scope;
776 if (!parent_scope || parent_scope == Cpp::GetGlobalScope()) {
777 if (interop::TCppScope_t scope = Cpp::GetScopeFromCompleteName(name))
778 return scope;
779 } else if (name.find('<') == std::string::npos) {
780 // Cpp::GetScope handles single identifiers only, so walk the components
781 // of a qualified name. Templated names take the branch below: "::" inside
782 // a template argument list cannot be split naively.
783 TCppScope_t curr = parent_scope;
784 size_t start = 0, end;
785 while (curr && (end = name.find("::", start)) != std::string::npos) {
786 curr = Cpp::GetScope(name.substr(start, end - start), curr);
787 start = end + 2;
788 }
789 if (curr && curr != parent_scope)
790 if (interop::TCppScope_t scope = Cpp::GetScope(name.substr(start), curr))
791 return scope;
792 }
793
794 // FIXME: avoid string parsing here
795 if (name.find('<') != std::string::npos) {
796 // Templated type; may need instantiation. Resolve the whole type
797 // expression (e.g. "std::array<float, 3>") and read back its scope.
798 // Splitting off the argument list and resolving it directly cannot
799 // represent non-type arguments such as the `3` in std::array<float, 3>.
800 std::vector<Cpp::TemplateArgInfo> types;
801 Lock.unlock(); // unlock to allow AppendTypesSlow
802 bool added_new_type =
803 !interop::AppendTypesSlow(name, types, /*parent=*/parent_scope);
804 Lock.lock();
805 if (added_new_type && types.size() == 1) {
806 // A pointer or reference spelling (e.g. "std::chrono::nanoseconds *",
807 // the return type of std::array<nanoseconds, N>::begin()) does not
808 // name a scope; GetScopeFromType would silently strip the pointer and
809 // return the pointee's scope, misclassifying the name.
810 if (Cpp::IsPointerType(types[0].m_Type) ||
811 Cpp::IsReferenceType(types[0].m_Type))
812 return nullptr;
813 TCppScope_t scope = Cpp::GetScopeFromType(types[0].m_Type);
814 // Naming the type as a template argument above does not instantiate
815 // it, so the specialization may still be declared-but-undefined.
816 // Force its definition: callers expect a complete scope, e.g. to
817 // walk its base classes.
818 if (scope)
819 Cpp::IsComplete(scope);
820 return scope;
821 }
822 }
823 return nullptr;
824}
825
829
831 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
832 return Cpp::GetScopeFromType(Cpp::GetVariableType(var));
833}
834
836 TCppScope_t parent_scope) {
837 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
838 if (TCppScope_t named = Cpp::GetNamed(name, parent_scope))
839 return named;
840
841 // "gROOT" is a macro expanding to ROOT::GetROOT(); ROOT master exposed it
842 // to cppyy as a TGlobalMappedFunction, a mechanism this backend does not
843 // consult. Resolve it to the underlying variable instead (making sure it
844 // has been initialized first).
845 if (name == "gROOT" &&
846 (!parent_scope || parent_scope == Cpp::GetGlobalScope())) {
848 return Cpp::GetNamed(
849 "gROOTLocal",
850 Cpp::GetNamed("Internal", Cpp::GetNamed("ROOT", nullptr)));
851 }
852
853 return nullptr;
854}
855
857 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
858 return Cpp::GetParentScope(scope);
859}
860
862 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
863 return Cpp::GetScopeFromType(type);
864}
865
867 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
868 return Cpp::GetTypeFromScope(klass);
869}
870
872 // The global scope (the first declaration of the interpreter's translation
873 // unit) never changes, but this is called on every method call that
874 // receives 'self' as its first argument, so avoid the lock and the query.
875 static const TCppScope_t s_global = [] {
876 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
877 return Cpp::GetGlobalScope();
878 }();
879 return s_global;
880}
881
882bool interop::IsTemplate(TCppScope_t handle) { return Cpp::IsTemplate(handle); }
883
885 return Cpp::IsTemplateSpecialization(handle);
886}
887
889 return Cpp::IsTypedefed(handle);
890}
891
892namespace {
893class AutoCastRTTI {
894public:
895 virtual ~AutoCastRTTI() {}
896};
897} // namespace
898
900 TCppObject_t obj) {
901 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
902
903 if (!obj || !Cpp::IsClassPolymorphic(klass))
904 return klass;
905
906 // Skip the std stream hierarchy: autocasting it is not useful, and on MSVC
907 // its virtual inheritance puts the vbptr, not a vfptr, at offset 0, so the
908 // RTTI probe below follows a garbage pointer.
909 static const std::array<TCppScope_t, 3> stream_bases = {
910 interop::GetScope("std::ios_base"), interop::GetScope("std::streambuf"),
911 interop::GetScope("std::wstreambuf")};
912 for (TCppScope_t base : stream_bases)
913 if (base && interop::IsSubclass(klass, base))
914 return klass;
915
916 // This is hit for every polymorphic object handed to Python, and the name
917 // lookups below cost microseconds, so their outcomes are memoized. Keyed on
918 // (static class, dynamic type). Only outcomes that cannot change later are
919 // cached; a failed lookup may succeed once more declarations are available,
920 // so failures are retried.
921 static std::map<std::pair<const void*, const std::type_info*>, TCppScope_t>
922 s_actual_class_cache;
923 const std::type_info* typ = &typeid(*(AutoCastRTTI*)obj.data);
924 if (!typ)
925 return klass;
926 const auto cacheKey = std::make_pair((const void*)klass.data, typ);
927 auto cached = s_actual_class_cache.find(cacheKey);
928 if (cached != s_actual_class_cache.end())
929 return cached->second;
930
931#ifdef _WIN32
932 // MSVC's type_info::name() is already human-readable, but prefixed with
933 // the tag kind ("class TWinNTSystem"), which plain name lookup does not
934 // accept. Strip the prefix by hand; respelling through type resolution
935 // would JIT an uncached declaration per downcast. Template arguments
936 // keep their tags, which is harmless as templated names go through type
937 // resolution rather than plain lookup.
938 std::string demangled_name = typ->name();
939 for (const char* prefix : {"class ", "struct ", "union ", "enum "}) {
940 if (demangled_name.compare(0, strlen(prefix), prefix) == 0) {
941 demangled_name = demangled_name.substr(strlen(prefix));
942 break;
943 }
944 }
945#else
946 std::string mangled_name = typ->name();
947 std::string demangled_name = Cpp::Demangle(mangled_name);
948#endif
949
950 // A type in an anonymous namespace cannot be named in injected code and
951 // has no dictionary, so looking it up would fail; worse, its unspellable
952 // name crashes the interpreter ("(anonymous namespace)" on Itanium,
953 // "`anonymous namespace'" on MSVC). Keep the base type.
954 if (demangled_name.find("anonymous namespace") != std::string::npos)
955 return klass;
956
957 if (TCppScope_t scope = interop::GetScope(demangled_name)) {
958 // A type inside an anonymous namespace cannot be spelled in injected
959 // code (e.g. the dispatcher's), so it is unusable as a cast target;
960 // keep the static type. Anonymous namespaces are the ones without a
961 // name of their own.
962 for (TCppScope_t p = Cpp::GetParentScope(scope); p;
963 p = Cpp::GetParentScope(p))
964 if (Cpp::IsNamespace(p) && Cpp::GetName(p).empty())
965 return klass;
966 // Only return the derived type once it has a complete definition. Under
967 // runtime_cxxmodules=OFF, autoloading the dictionary registers only a
968 // forward declaration, so force the definition into the AST here. Internal
969 // classes like TCling have no header and cannot be completed (their
970 // CXXRecordDecl has no DefinitionData); GetOrForceDefinition returns null
971 // for them and we fall back to the base type, avoiding a crash when
972 // querying offsets.
973 if (Cpp::GetOrForceDefinition(scope)) {
974 s_actual_class_cache.emplace(cacheKey, scope);
975 return scope;
976 }
977 }
978
979 return klass;
980}
981
983 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
984 return Cpp::SizeOf(klass);
985}
986
988 if (!klass)
989 return 0;
990 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
991 return Cpp::GetSizeOfType(klass);
992}
993
994bool interop::IsBuiltin(const std::string& type_name) {
995 static std::set<std::string> s_builtins = {"bool",
996 "char",
997 "signed char",
998 "unsigned char",
999 "wchar_t",
1000 "short",
1001 "unsigned short",
1002 "int",
1003 "unsigned int",
1004 "long",
1005 "unsigned long",
1006 "long long",
1007 "unsigned long long",
1008 "float",
1009 "double",
1010 "long double",
1011 "void"};
1012 if (s_builtins.find(trim(type_name)) != s_builtins.end())
1013 return true;
1014
1015 if (strstr(type_name.c_str(), "std::complex"))
1016 return true;
1017
1018 return false;
1019}
1020
1021bool interop::IsBuiltin(TCppType_t type) { return Cpp::IsBuiltin(type); }
1022
1024 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1025 return Cpp::IsComplete(scope);
1026}
1027
1028// // memory management
1029// ---------------------------------------------------------
1031 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1032 return Cpp::Allocate(scope, /*count=*/1);
1033}
1034
1036 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1037 Cpp::Deallocate(scope, instance, /*count=*/1);
1038}
1039
1040// Constructors and destructors are resolved (and JIT-compiled) under the
1041// lock and run without it, as WrapperCall does: their bodies are user code.
1043 void* arena /*=nullptr*/) {
1044 std::unique_lock<RInterOpMutex> Lock(InterOpMutex);
1045 if (!Cpp::HasDefaultConstructor(scope))
1046 return nullptr;
1047 Cpp::JitCall JC =
1048 Cpp::MakeFunctionCallable(Cpp::GetDefaultConstructor(scope));
1049 Lock.unlock();
1050 if (!JC)
1051 return nullptr;
1052 void* result = arena;
1053 JC.InvokeConstructor(&result, /*nary=*/1, /*args=*/{},
1054 /*is_arena=*/arena ? reinterpret_cast<void*>(1)
1055 : nullptr);
1056 return result;
1057}
1058
1059static Cpp::JitCall destructor_callable(interop::TCppScope_t scope) {
1060 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1061 return Cpp::MakeFunctionCallable(Cpp::GetDestructor(scope));
1062}
1063
1065 if (Cpp::JitCall JC = destructor_callable(scope))
1066 JC.InvokeDestructor(instance.data, /*nary=*/0, /*withFree=*/true);
1067}
1068
1069static inline bool copy_args(Parameter* args, size_t nargs, void** vargs) {
1070 bool runRelease = false;
1071 for (size_t i = 0; i < nargs; ++i) {
1072 switch (args[i].fTypeCode) {
1073 case 'X': /* (void*)type& with free */
1074 runRelease = true;
1075 case 'V': /* (void*)type& */
1076 vargs[i] = args[i].fValue.fVoidp;
1077 break;
1078 case 'r': /* const type& */
1079 vargs[i] = args[i].fRef;
1080 break;
1081 default: /* all other types in union */
1082 vargs[i] = (void*)&args[i].fValue.fVoidp;
1083 break;
1084 }
1085 }
1086 return runRelease;
1087}
1088
1089static inline void release_args(Parameter* args, size_t nargs) {
1090 for (size_t i = 0; i < nargs; ++i) {
1091 if (args[i].fTypeCode == 'X')
1092 free(args[i].fValue.fVoidp);
1093 }
1094}
1095
1096static inline bool WrapperCall(interop::TCppMethod_t method, size_t nargs,
1097 void* args_, void* self, void* result) {
1098 Parameter* args = (Parameter*)args_;
1099 // bool is_direct = nargs & DIRECT_CALL;
1100 nargs = CALL_NARGS(nargs);
1101
1102 // if (!is_ready(wrap, is_direct))
1103 // return false; // happens with compilation error
1105 if (Cpp::JitCall JC = Cpp::MakeFunctionCallable(method)) {
1107 bool runRelease = false;
1108 // const auto& fgen = /* is_direct ? faceptr.fDirect : */ faceptr;
1109 if (nargs <= cpyrt::SMALL_ARGS_N) {
1110 void* smallbuf[cpyrt::SMALL_ARGS_N];
1111 if (nargs)
1112 runRelease = copy_args(args, nargs, smallbuf);
1113 // CLING_CATCH_UNCAUGHT_
1114 JC.Invoke(result, {smallbuf, nargs}, self);
1115 // _CLING_CATCH_UNCAUGHT
1116 } else {
1117 std::vector<void*> buf(nargs);
1118 runRelease = copy_args(args, nargs, buf.data());
1119 // CLING_CATCH_UNCAUGHT_
1120 JC.Invoke(result, {buf.data(), nargs}, self);
1121 // _CLING_CATCH_UNCAUGHT
1122 }
1123 if (runRelease)
1124 release_args(args, nargs);
1125 return true;
1126 }
1128 return false;
1129}
1130
1131template <typename T>
1133 size_t nargs, void* args) {
1134 T t{};
1135 if (WrapperCall(method, nargs, args, self.data, &t))
1136 return t;
1137 throw std::runtime_error("failed to resolve function");
1138 return (T)-1;
1139}
1140
1141#ifdef PRINT_DEBUG
1142#define _IMP_CALL_PRINT_STMT(type) printf("IMP CALL with type: %s\n", #type);
1143#else
1144#define _IMP_CALL_PRINT_STMT(type)
1145#endif
1146
1147#define CPPJIT_IMP_CALL(typecode, rtype) \
1148 rtype interop::Call##typecode(TCppMethod_t method, TCppObject_t self, \
1149 size_t nargs, void* args) { \
1150 _IMP_CALL_PRINT_STMT(rtype) \
1151 return CallT<rtype>(method, self, nargs, args); \
1152 }
1153
1154void interop::CallV(TCppMethod_t method, TCppObject_t self, size_t nargs,
1155 void* args) {
1156 if (!WrapperCall(method, nargs, args, self.data, nullptr))
1157 return /* TODO ... report error */;
1158}
1159
1160// clang-format off
1161CPPJIT_IMP_CALL(B, unsigned char)
1166CPPJIT_IMP_CALL(LL, long long )
1169CPPJIT_IMP_CALL(LD, long double )
1170// clang-format on
1171
1172void* interop::CallR(TCppMethod_t method, TCppObject_t self, size_t nargs,
1173 void* args) {
1174 void* r = nullptr;
1175 if (WrapperCall(method, nargs, args, self.data, &r))
1176 return r;
1177 return nullptr;
1178}
1179
1180char* interop::CallS(TCppMethod_t method, TCppObject_t self, size_t nargs,
1181 void* args, size_t* length) {
1182 char* cstr = nullptr;
1183 // TClassRef cr("std::string"); // TODO: Why is this required?
1184 std::string* cppresult = (std::string*)malloc(sizeof(std::string));
1185 if (WrapperCall(method, nargs, args, self.data, (void*)cppresult)) {
1186 cstr = cppstring_to_cstring(*cppresult);
1187 *length = cppresult->size();
1188 cppresult->std::string::~basic_string();
1189 } else
1190 *length = 0;
1191 free((void*)cppresult);
1192 return cstr;
1193}
1194
1196 TCppScope_t /*klass*/,
1197 size_t nargs, void* args) {
1198 void* obj = nullptr;
1199 WrapperCall(method, nargs, args, nullptr, &obj);
1200 return (TCppObject_t)obj;
1201}
1202
1204 if (Cpp::JitCall JC = destructor_callable(scope))
1205 JC.InvokeDestructor(self.data, /*nary=*/0, /*withFree=*/false);
1206}
1207
1209 size_t nargs, void* args,
1210 TCppType_t result_type) {
1211 size_t size = interop::SizeOfType(result_type);
1212 if (size == 0)
1213 return TCppObject_t{}; // unsizable return type; the caller reports
1214 void* obj = ::operator new(size);
1215 if (WrapperCall(method, nargs, args, self.data, obj))
1216 return (TCppObject_t)obj;
1217 ::operator delete(obj);
1218 return TCppObject_t{};
1219}
1220
1222 bool /*check_enabled*/) {
1223 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1224 return Cpp::GetFunctionAddress(method);
1225}
1226
1227// handling of function argument buffer --------------------------------------
1229 return new Parameter[nargs];
1230}
1231
1232void interop::DeallocateFunctionArgs(void* args) { delete[] (Parameter*)args; }
1233
1234size_t interop::GetFunctionArgSizeof() { return sizeof(Parameter); }
1235
1237 return offsetof(Parameter, fTypeCode);
1238}
1239
1240// scope reflection information ----------------------------------------------
1242 if (!scope)
1243 return false;
1244
1245 // Test if this scope represents a namespace.
1246 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1247 return Cpp::IsNamespace(scope) || Cpp::GetGlobalScope() == scope;
1248}
1249
1251 // Test if this scope represents a namespace.
1252 return Cpp::IsClass(scope);
1253}
1254//
1256 // Test if this type may not be instantiated.
1257 return Cpp::IsAbstract(scope);
1258}
1259
1260bool interop::IsEnumScope(TCppScope_t scope) { return Cpp::IsEnumScope(scope); }
1261
1263 return Cpp::IsEnumConstant(interop::GetUnderlyingScope(scope));
1264}
1265
1266bool interop::IsEnumType(TCppType_t type) { return Cpp::IsEnumType(type); }
1267
1269 // Test if this type is a "plain old data" type
1270 return Cpp::IsAggregate(type);
1271}
1272
1274 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1275 // Test if this type has a default constructor or is a "plain old data" type
1276 return Cpp::HasDefaultConstructor(scope);
1277}
1278
1279bool interop::IsVariable(TCppScope_t scope) { return Cpp::IsVariable(scope); }
1280
1282 std::set<std::string>& cppnames) {
1283 // Collect all known names of C++ entities under scope. This is useful for
1284 // IDEs employing tab-completion, for example. Note that functions names need
1285 // not be unique as they can be overloaded.
1286 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1287 Cpp::GetAllCppNames(scope, cppnames);
1288}
1289
1290// class reflection information ----------------------------------------------
1291std::vector<interop::TCppScope_t>
1293 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1294 return Cpp::GetUsingNamespaces(scope);
1295}
1296
1297// Normalize a type or scope name to cppyy's canonical form: no space after
1298// the commas separating template arguments, and pointers/references attached
1299// to the type. This is the form cpyrt itself constructs (e.g. when
1300// looking up cached template instantiations by name, see
1301// Utility::ConstructTemplateArgs) and the convention that user code and the
1302// test suite inherited from upstream cppyy; clang's printer instead emits
1303// "a, b", "T *" and "T &".
1304static std::string cppyy_normalize_name(std::string name) {
1305 std::string::size_type pos = 0;
1306 while ((pos = name.find(", ", pos)) != std::string::npos)
1307 name.erase(pos + 1, 1);
1308 pos = 0;
1309 while ((pos = name.find(" *", pos)) != std::string::npos)
1310 name.erase(pos, 1);
1311 pos = 0;
1312 while ((pos = name.find(" &", pos)) != std::string::npos)
1313 name.erase(pos, 1);
1314 return name;
1315}
1316
1317// class reflection information ----------------------------------------------
1319 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1320 return cppyy_normalize_name(
1321 Cpp::GetCompleteName(Cpp::GetUnderlyingScope(klass)));
1322}
1323
1325 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1326 return cppyy_normalize_name(Cpp::GetQualifiedCompleteName(klass));
1327}
1328
1330 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1331 TCppMethod_t func = Cpp::GetDestructor(scope);
1332 return Cpp::IsVirtualMethod(func);
1333}
1334
1336 // Get the total number of base classes that this class has.
1337 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1338 // Autoloading may have registered only a forward declaration of the class;
1339 // complete it, or the proxy is built without its bases.
1340 Cpp::GetOrForceDefinition(klass);
1341 return Cpp::GetNumBases(klass);
1342}
1343
1344////////////////////////////////////////////////////////////////////////////////
1345/// \fn interop::TCppIndex_t interop::GetNumBasesLongestBranch(TCppScope_t
1346/// klass) \brief Retrieve number of base classes in the longest branch of the
1347/// inheritance tree of the input class.
1348/// \param[in] klass The class to start the retrieval process from.
1349///
1350/// This is a helper function for interop::GetNumBasesLongestBranch.
1351/// Given an inheritance tree, the function assigns weight 1 to each class that
1352/// has at least one base. Starting from the input class, the function is
1353/// called recursively on all the bases. For each base the return value is one
1354/// (the weight of the base itself) plus the maximum value retrieved for their
1355/// bases in turn. For example, given the following inheritance tree:
1356///
1357/// ~~~{.cpp}
1358/// class A {}; class B: public A {};
1359/// class X {}; class Y: public X {}; class Z: public Y {};
1360/// class C: public B, Z {};
1361/// ~~~
1362///
1363/// calling this function on an instance of `C` will return 3, the steps
1364/// required to go from C to X.
1366 std::vector<size_t> num;
1367 for (TCppIndex_t ibase = 0; ibase < GetNumBases(klass); ++ibase)
1368 num.push_back(
1370 if (num.empty())
1371 return 0;
1372 return *std::max_element(num.begin(), num.end()) + 1;
1373}
1374
1376 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1377 return Cpp::GetName(Cpp::GetBaseClass(klass, ibase));
1378}
1379
1381 TCppIndex_t ibase) {
1382 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1383 return Cpp::GetBaseClass(klass, ibase);
1384}
1385
1387 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1388 // Checked on every method call that receives 'self' as its first argument
1389 // (e.g. from pythonizations and protocol slots), so memoize per class
1390 // pair. A class that is still incomplete can gain bases once its
1391 // definition is loaded, so a negative answer is only cached for complete
1392 // classes.
1393 static std::map<std::pair<const void*, const void*>, bool> s_subclass_cache;
1394 const auto cacheKey = std::make_pair(derived.data, base.data);
1395 auto cached = s_subclass_cache.find(cacheKey);
1396 if (cached != s_subclass_cache.end())
1397 return cached->second;
1398 bool result = Cpp::IsSubclass(derived, base);
1399 if (result || (Cpp::IsComplete(derived) && Cpp::IsComplete(base)))
1400 s_subclass_cache.emplace(cacheKey, result);
1401 return result;
1402}
1403
1404static std::set<std::string> gSmartPtrTypes = {
1405 "std::auto_ptr", "std::shared_ptr", "std::unique_ptr", "std::weak_ptr"};
1406
1408 const std::string& rn = interop::GetScopedFinalName(klass);
1409 if (gSmartPtrTypes.find(rn.substr(0, rn.find("<"))) != gSmartPtrTypes.end())
1410 return true;
1411 return false;
1412}
1413
1414bool interop::GetSmartPtrInfo(const std::string& tname, TCppScope_t* raw,
1415 TCppMethod_t* deref) {
1416 // TODO: We can directly accept scope instead of name
1417 const std::string& rn = ResolveName(tname);
1418 if (gSmartPtrTypes.find(rn.substr(0, rn.find("<"))) == gSmartPtrTypes.end())
1419 return false;
1420
1421 if (!raw && !deref)
1422 return true;
1423
1424 TCppScope_t scope = interop::GetScope(rn);
1425 if (!scope)
1426 return false;
1427
1428 std::vector<TCppMethod_t> ops;
1429 {
1430 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1431 Cpp::GetOperator(scope, Cpp::Operator::OP_Arrow, ops,
1432 /*kind=*/Cpp::OperatorArity::kBoth);
1433 if (ops.size() != 1)
1434 return false;
1435
1436 // The dereference operator can be a member template: MSVC's
1437 // std::shared_ptr::operator-> is SFINAE-constrained on the element
1438 // type not being an array. Its return type is dependent, so it has
1439 // to be instantiated (it takes no arguments) before the pointee
1440 // type can be determined - and before it can be called.
1441 if (Cpp::IsTemplatedFunction(ops[0]))
1442 ops[0] = Cpp::BestOverloadFunctionMatch(ops, {}, {});
1443 if (!ops[0])
1444 return false;
1445 }
1446
1447 if (deref)
1448 *deref = ops[0];
1449 if (raw)
1451 return (!deref || *deref) && (!raw || *raw);
1452}
1453
1454// type offsets --------------------------------------------------------------
1456 TCppObject_t /*address*/, int direction,
1457 bool rerror) {
1458 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1459
1460 // Queried for every downcast object handed to Python; computing it walks
1461 // the inheritance paths, so memoize per class pair. The object address is
1462 // not used, so the offset is fixed per pair once both records are complete.
1463 static std::map<std::pair<const void*, const void*>, intptr_t>
1464 s_base_offset_cache;
1465 const auto cacheKey = std::make_pair(derived.data, base.data);
1466 auto cached = s_base_offset_cache.find(cacheKey);
1467 intptr_t offset = 0;
1468 if (cached != s_base_offset_cache.end()) {
1469 offset = cached->second;
1470 } else {
1471 // An incomplete record has no walkable bases (GetBaseClassOffset would
1472 // read an empty base path), and the answer may change once the
1473 // definition is loaded, so treat it like the error case below and retry
1474 // next time instead of caching.
1475 if (!Cpp::IsComplete(derived) || !Cpp::IsComplete(base))
1476 return rerror ? (ptrdiff_t)-1 : 0;
1477
1478 offset = Cpp::GetBaseClassOffset(derived, base);
1479
1480 if (offset == -1) // Cling error, treat silently
1481 return rerror ? (ptrdiff_t)offset : 0;
1482
1483 s_base_offset_cache.emplace(cacheKey, offset);
1484 }
1485
1486 return (ptrdiff_t)(direction < 0 ? -offset : offset);
1487}
1488
1489// method/function reflection information ------------------------------------
1490// A deleted overload is not callable, and leaving it in the set adds a
1491// spurious conversion error to every failed-call report.
1492static void
1493remove_deleted_methods(std::vector<interop::TCppMethod_t>& methods) {
1494 methods.erase(std::remove_if(methods.begin(), methods.end(),
1496 return Cpp::IsFunctionDeleted(m);
1497 }),
1498 methods.end());
1499}
1500
1502 std::vector<interop::TCppMethod_t>& methods) {
1503 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1504 Cpp::GetClassMethods(scope, methods);
1505 remove_deleted_methods(methods);
1506}
1507
1508std::vector<interop::TCppMethod_t>
1510 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1511 std::vector<interop::TCppMethod_t> methods =
1512 Cpp::GetFunctionsUsingName(scope, name);
1513 remove_deleted_methods(methods);
1514 return methods;
1515}
1516
1517std::string interop::GetName(TCppScope_t method) {
1518 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1519 return Cpp::GetName(method);
1520}
1521
1523 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1524 return cppyy_normalize_name(Cpp::GetCompleteName(method));
1525}
1526
1528 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1529 return Cpp::GetFunctionReturnType(method);
1530}
1531
1533 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1534 TCppType_t ret = Cpp::GetCanonicalType(Cpp::GetFunctionReturnType(method));
1535 // C++ deletes top-level cv-qualifiers on non-class return types from the
1536 // function type ([dcl.fct]); mirror that so name matching sees the plain
1537 // type, e.g. "unsigned long" for `static const size_t size()`. Class
1538 // types keep the qualifier: it stays part of the function type there
1539 // (e.g. for override matching in the dispatcher).
1540 if (ret && !Cpp::IsRecordType(ret))
1541 ret = Cpp::RemoveTypeQualifier(ret,
1542 Cpp::QualKind::Const | Cpp::QualKind::Volatile);
1543 return Cpp::GetTypeAsString(ret);
1544}
1545
1547 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1548 return Cpp::GetFunctionNumArgs(method);
1549}
1550
1552 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1553 return Cpp::GetFunctionRequiredArgs(method);
1554}
1555
1557 if (!method)
1558 return "<unknown>";
1559
1560 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1561 return Cpp::GetFunctionArgName(method, iarg);
1562}
1563
1565 TCppIndex_t iarg) {
1566 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1567 return Cpp::GetFunctionArgType(method, iarg);
1568}
1569
1571 TCppIndex_t iarg) {
1572 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1573 return Cpp::GetTypeAsString(Cpp::RemoveTypeQualifier(
1574 Cpp::GetFunctionArgType(method, iarg), Cpp::QualKind::Const));
1575}
1576
1578 TCppIndex_t iarg) {
1579 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1580 return Cpp::GetTypeAsString(
1581 Cpp::GetCanonicalType(Cpp::GetFunctionArgType(method, iarg)));
1582}
1583
1585 TCppIndex_t iarg) {
1586 if (!method)
1587 return "";
1588
1589 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1590 return Cpp::GetFunctionArgDefault(method, iarg);
1591}
1592
1595 const std::string& /*req_type*/) {
1596 // if (method) {
1597 // TFunction* f = m2f(method);
1598 // TMethodArg* arg = (TMethodArg
1599 // *)f->GetListOfMethodArgs()->At((int)iarg); void *argqtp =
1600 // gInterpreter->TypeInfo_QualTypePtr(arg->GetTypeInfo());
1601
1602 // TypeInfo_t *reqti = gInterpreter->TypeInfo_Factory(req_type.c_str());
1603 // void *reqqtp = gInterpreter->TypeInfo_QualTypePtr(reqti);
1604
1605 // if (ArgSimilarityScore(argqtp, reqqtp) < 10) {
1606 // return ArgSimilarityScore(argqtp, reqqtp);
1607 // }
1608 // else { // Match using underlying types
1609 // if(gInterpreter->IsPointerType(argqtp))
1610 // argqtp =
1611 // gInterpreter->TypeInfo_QualTypePtr(gInterpreter->GetPointerType(argqtp));
1612
1613 // // Handles reference types and strips qualifiers
1614 // TypeInfo_t *arg_ul = gInterpreter->GetNonReferenceType(argqtp);
1615 // TypeInfo_t *req_ul = gInterpreter->GetNonReferenceType(reqqtp);
1616 // argqtp =
1617 // gInterpreter->TypeInfo_QualTypePtr(gInterpreter->GetUnqualifiedType(gInterpreter->TypeInfo_QualTypePtr(arg_ul)));
1618 // reqqtp =
1619 // gInterpreter->TypeInfo_QualTypePtr(gInterpreter->GetUnqualifiedType(gInterpreter->TypeInfo_QualTypePtr(req_ul)));
1620
1621 // return ArgSimilarityScore(argqtp, reqqtp);
1622 // }
1623 // }
1624 return 0; // Method is not valid
1625}
1626
1628 bool show_formal_args,
1629 TCppIndex_t max_args) {
1630 std::ostringstream sig;
1631 sig << "(";
1632 int nArgs = GetMethodNumArgs(method);
1633 if (max_args != (TCppIndex_t)-1)
1634 nArgs = std::min(nArgs, (int)max_args);
1635 for (int iarg = 0; iarg < nArgs; ++iarg) {
1636 sig << cppyy_normalize_name(
1637 interop::GetMethodArgTypeAsString(method, iarg));
1638 if (show_formal_args) {
1639 std::string argname = interop::GetMethodArgName(method, iarg);
1640 if (!argname.empty())
1641 sig << " " << argname;
1642 std::string defvalue = interop::GetMethodArgDefault(method, iarg);
1643 if (!defvalue.empty())
1644 sig << " = " << defvalue;
1645 }
1646 if (iarg != nArgs - 1)
1647 sig << ", ";
1648 }
1649 sig << ")";
1650 return sig.str();
1651}
1652
1654 fn_type = Cpp::IsReferenceType(fn_type) ? Cpp::GetNonReferenceType(fn_type)
1655 : fn_type;
1656 fn_type =
1657 Cpp::IsPointerType(fn_type) ? Cpp::GetPointeeType(fn_type) : fn_type;
1658 TCppScope_t scope = Cpp::GetScopeFromType(fn_type);
1659 std::vector<Cpp::TemplateArgInfo> args;
1660 Cpp::GetClassTemplateArgs(scope, args);
1661 assert(args.size() == 1);
1662 if (args.size() == 1)
1663 return args[0].m_Type;
1664 return nullptr;
1665}
1666
1668 std::vector<TCppType_t>& arg_types) {
1669 fn_type = Cpp::IsReferenceType(fn_type) ? Cpp::GetNonReferenceType(fn_type)
1670 : fn_type;
1671 fn_type =
1672 Cpp::IsPointerType(fn_type) ? Cpp::GetPointeeType(fn_type) : fn_type;
1673 Cpp::GetFnTypeSignature(fn_type, arg_types);
1674}
1675
1677 return Cpp::IsSameType(typ1, typ2);
1678}
1679
1681 typ = Cpp::IsReferenceType(typ) ? Cpp::GetNonReferenceType(typ) : typ;
1682 typ = Cpp::IsPointerType(typ) ? Cpp::GetPointeeType(typ) : typ;
1683 return Cpp::IsFunctionProtoType(typ);
1684}
1685
1687 typ1 = Cpp::IsReferenceType(typ1) ? Cpp::GetNonReferenceType(typ1) : typ1;
1688 typ2 = Cpp::IsReferenceType(typ2) ? Cpp::GetNonReferenceType(typ2) : typ2;
1689 typ1 = Cpp::IsPointerType(typ1) ? Cpp::GetPointeeType(typ1) : typ1;
1690 typ2 = Cpp::IsPointerType(typ2) ? Cpp::GetPointeeType(typ2) : typ2;
1691 return Cpp::IsSameType(typ1, typ2);
1692}
1693
1694std::string interop::GetDoxygenComment(TCppScope_t scope, bool strip_markers) {
1695 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1696 return Cpp::GetDoxygenComment(scope, strip_markers);
1697}
1698
1700 if (!method)
1701 return false;
1702 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1703 return Cpp::IsConstMethod(method);
1704}
1705
1707 std::vector<interop::TCppMethod_t>& methods) {
1708 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1709 Cpp::GetFunctionTemplatedDecls(scope, methods);
1710}
1711
1713interop::GetNumTemplatedMethods(TCppScope_t scope, bool /*accept_namespace*/) {
1714 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1715 std::vector<interop::TCppMethod_t> mc;
1716 Cpp::GetFunctionTemplatedDecls(scope, mc);
1717 return mc.size();
1718}
1719
1721 TCppIndex_t imeth) {
1722 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1723 std::vector<interop::TCppMethod_t> mc;
1724 Cpp::GetFunctionTemplatedDecls(scope, mc);
1725
1726 if (imeth < mc.size())
1727 return Cpp::GetName(TCppScope_t(mc[imeth].data));
1728
1729 return "";
1730}
1731
1732bool interop::ExistsMethodTemplate(TCppScope_t scope, const std::string& name) {
1733 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1734 return Cpp::ExistsFunctionTemplate(name, scope);
1735}
1736
1738 return Cpp::IsTemplatedFunction(method);
1739}
1740
1741bool interop::IsStaticTemplate(TCppScope_t scope, const std::string& name) {
1742 std::vector<TCppMethod_t> candidate_methods;
1743 Cpp::GetClassTemplatedMethods(name, scope, candidate_methods);
1744 bool is_static = true;
1745 for (auto i : candidate_methods) {
1746 if (!Cpp::IsStaticMethod(i)) {
1747 is_static = false;
1748 break;
1749 }
1750 }
1751 return is_static;
1752}
1753
1755 const std::string& name,
1756 const std::string& proto) {
1757 std::string pureName;
1758 std::string explicit_params;
1759
1760 if ((name.find("operator<") != 0) && (name.find('<') != std::string::npos)) {
1761 pureName = name.substr(0, name.find('<'));
1762 size_t start = name.find('<');
1763 size_t end = name.rfind('>');
1764 explicit_params = name.substr(start + 1, end - start - 1);
1765 } else {
1766 pureName = name;
1767 }
1768
1769 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1770
1771 std::vector<interop::TCppMethod_t> unresolved_candidate_methods;
1772 Cpp::GetClassTemplatedMethods(pureName, scope, unresolved_candidate_methods);
1773 if (unresolved_candidate_methods.empty() && name.find("operator") == 0) {
1774 // try operators
1775 interop::GetClassOperators(scope, pureName, unresolved_candidate_methods);
1776 }
1777
1778 // cpyrt assumes that we attempt instantiation here
1779 std::vector<Cpp::TemplateArgInfo> arg_types;
1780 std::vector<Cpp::TemplateArgInfo> templ_params;
1781 interop::AppendTypesSlow(proto, arg_types, scope);
1782 interop::AppendTypesSlow(explicit_params, templ_params, scope);
1783 interop::TCppMethod_t cppmeth = nullptr;
1784 cppmeth = Cpp::BestOverloadFunctionMatch(unresolved_candidate_methods,
1785 templ_params, arg_types);
1786
1787 // If overload resolution failed but explicit template arguments were
1788 // supplied, fall back to direct template-argument substitution: ask Sema
1789 // to instantiate each candidate with the explicit args. Sema's SFINAE
1790 // rejects overloads whose substitution fails (e.g. the initializer_list
1791 // form of std::make_any with non-init-list explicit args), so iterating
1792 // gives back exactly the viable specialisation. The wrapper-side argument
1793 // conversion then handles e.g. taking the address of an instance when the
1794 // substituted parameter is a pointer.
1795 if (!cppmeth && !templ_params.empty()) {
1796 for (const auto& cand : unresolved_candidate_methods) {
1797 if (Cpp::DeclRef spec = Cpp::InstantiateTemplate(
1798 TCppScope_t(cand.data), templ_params.data(), templ_params.size(),
1799 /*instantiate_body=*/false)) {
1800 cppmeth = spec.data;
1801 break;
1802 }
1803 }
1804 }
1805
1806 return TCppMethod_t(cppmeth.data);
1807 // if it fails, use Sema to propogate info about why it failed (DeductionInfo)
1808}
1809
1810static inline bool is_basic_string_of(const std::string& n, const char* charT) {
1811 // Match "std::basic_string<charT" whether or not the default template
1812 // arguments (char_traits, allocator) are spelled out; the character type
1813 // must be followed by ',' or '>' so that e.g. "char" does not match
1814 // "char16_t".
1815 std::string prefix = "std::basic_string<";
1816 prefix += charT;
1817 if (n.compare(0, prefix.size(), prefix) != 0)
1818 return false;
1819 return n.size() > prefix.size() &&
1820 (n[prefix.size()] == ',' || n[prefix.size()] == '>');
1821}
1822
1823static inline std::string type_remap(const std::string& n1,
1824 const std::string& n2) {
1825 // Operator lookups of (C++ string, Python str) should succeed for the
1826 // combos of string/str, wstring/str, string/unicode and wstring/unicode;
1827 // since C++ does not have a operator+(std::string, std::wstring), we'll
1828 // have to look up the same type and rely on the converters in
1829 // cpyrt/_cppjit.
1830 if (n1 == "str" || n1 == "unicode" || is_basic_string_of(n1, "char")) {
1831 if (is_basic_string_of(n2, "wchar_t"))
1832 return "std::basic_string<wchar_t>&"; // match like for like
1833 return "std::basic_string<char>&"; // probably best bet
1834 } else if (is_basic_string_of(n1, "wchar_t")) {
1835 return "std::basic_string<wchar_t>&";
1836 } else if (n1 == "complex") {
1837 return "std::complex<double>";
1838 }
1839 return n1;
1840}
1841
1843 const std::string& opname,
1844 std::vector<TCppMethod_t>& operators) {
1845 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1846 std::string op = opname.substr(8);
1847 Cpp::GetOperator(klass, Cpp::GetOperatorFromSpelling(op), operators,
1848 /*kind=*/Cpp::OperatorArity::kBoth);
1849}
1850
1852 const std::string& lc,
1853 const std::string& rc,
1854 const std::string& opname) {
1855 std::string rc_type = type_remap(rc, lc);
1856 std::string lc_type = type_remap(lc, rc);
1857
1858 std::vector<TCppMethod_t> overloads;
1859 Cpp::GetOperator(scope, Cpp::GetOperatorFromSpelling(opname), overloads,
1860 /*kind=*/Cpp::OperatorArity::kBoth);
1861
1862 // Avoid pushing nullptr into arg_types which would crash
1863 // BestOverloadFunctionMatch when it dereferences each entry's QualType.
1864 auto resolve_arg_type = [](const std::string& name) -> interop::TCppType_t {
1865 if (auto s = interop::GetScope(name))
1866 if (auto t = interop::GetTypeFromScope(s))
1868 return interop::GetType(name, /*enable_slow_lookup=*/true);
1869 };
1870
1871 std::vector<Cpp::TemplateArgInfo> arg_types;
1872 if (auto l = resolve_arg_type(lc_type))
1873 arg_types.emplace_back(l.data);
1874 else
1875 return nullptr;
1876
1877 if (!rc_type.empty()) {
1878 if (auto r = resolve_arg_type(rc_type))
1879 arg_types.emplace_back(r.data);
1880 else
1881 return nullptr;
1882 }
1883 interop::TCppMethod_t cppmeth =
1884 Cpp::BestOverloadFunctionMatch(overloads, {}, arg_types);
1885 if (cppmeth)
1886 return cppmeth;
1887 return nullptr;
1888}
1889
1890// method properties ---------------------------------------------------------
1892 return Cpp::IsFunctionDeleted(method);
1893}
1894
1896 return Cpp::IsPublicMethod(method);
1897}
1898
1900 return Cpp::IsProtectedMethod(method);
1901}
1902
1904 return Cpp::IsPrivateMethod(method);
1905}
1906
1908 return Cpp::IsConstructor(method);
1909}
1910
1912 return Cpp::IsDestructor(method);
1913}
1914
1916 return Cpp::IsStaticMethod(method);
1917}
1918
1920 return Cpp::IsExplicit(method);
1921}
1922
1923// data member reflection information ----------------------------------------
1925 std::vector<TCppScope_t>& datamembers) {
1926 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1927 Cpp::GetDatamembers(scope, datamembers);
1928 Cpp::GetStaticDatamembers(scope, datamembers);
1929 Cpp::GetEnumConstantDatamembers(scope, datamembers, false);
1930}
1931
1932bool interop::CheckDatamember(TCppScope_t scope, const std::string& name) {
1933 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1934 return (bool)Cpp::LookupDatamember(name, scope);
1935}
1936
1938 return Cpp::IsLambdaClass(type);
1939}
1940
1942 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1943 std::ostringstream code;
1944 std::string name = interop::GetFinalName(var);
1945 code << "namespace __cppjit_internal_wrap_g {\n"
1946 << " " << "std::function " << name
1947 << " = ::" << Cpp::GetQualifiedName(var) << ";\n"
1948 << "}\n";
1949
1950 if (interop::Compile(code.str().c_str())) {
1951 TCppScope_t res = Cpp::GetNamed(
1952 name, Cpp::GetScope("__cppjit_internal_wrap_g", /*parent=*/nullptr));
1953 if (res)
1954 return res;
1955 }
1956 return var;
1957}
1958
1961 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1962
1963 std::string fn_name = Cpp::GetQualifiedCompleteName(TCppScope_t(fn.data));
1964 std::string signature = interop::GetMethodSignature(fn, true);
1965
1966 std::ostringstream call;
1967 call << "(";
1968 for (size_t i = 0, n = interop::GetMethodNumArgs(fn); i < n; i++) {
1969 call << interop::GetMethodArgName(fn, i);
1970 if (i != n - 1)
1971 call << ", ";
1972 }
1973 call << ")";
1974
1975 std::ostringstream code;
1976 static int i = 0;
1977 std::string name = "lambda_return_convert_" + std::to_string(++i);
1978 code << "namespace __cppjit_internal_wrap_g {\n"
1979 << "auto " << name << signature << "{" << "return std::function("
1980 << fn_name << call.str() << "); }\n"
1981 << "}\n";
1982 if (interop::Compile(code.str().c_str())) {
1983 TCppScope_t res = Cpp::GetNamed(
1984 name, Cpp::GetScope("__cppjit_internal_wrap_g", /*parent=*/nullptr));
1985 if (res)
1986 return TCppMethod_t(res.data);
1987 }
1988 return fn;
1989}
1990
1992 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1993 return Cpp::GetVariableType(Cpp::GetUnderlyingScope(var));
1994}
1995
1997 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1998 return Cpp::GetTypeAsString(
1999 Cpp::GetVariableType(Cpp::GetUnderlyingScope(scope)));
2000}
2001
2003 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
2004 return Cpp::GetTypeAsString(type);
2005}
2006
2008 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
2009 return Cpp::GetVariableOffset(Cpp::GetUnderlyingScope(var), klass);
2010}
2011
2012// data member properties ----------------------------------------------------
2014 return Cpp::IsPublicVariable(datamem);
2015}
2016
2018 return Cpp::IsProtectedVariable(datamem);
2019}
2020
2022 return Cpp::IsPrivateVariable(datamem);
2023}
2024
2026 return Cpp::IsStaticVariable(interop::GetUnderlyingScope(var));
2027}
2028
2029bool interop::IsConstVar(TCppScope_t var) { return Cpp::IsConstVariable(var); }
2030
2032 TCppType_t reduce) {
2033 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
2034
2035 std::string fn_name = Cpp::GetQualifiedCompleteName(TCppScope_t(fn.data));
2036 std::string signature = interop::GetMethodSignature(fn, true);
2037 std::string result_type = interop::GetTypeAsString(reduce);
2038
2039 std::ostringstream call;
2040 call << "(";
2041 for (size_t i = 0, n = interop::GetMethodNumArgs(fn); i < n; i++) {
2042 call << interop::GetMethodArgName(fn, i);
2043 if (i != n - 1)
2044 call << ", ";
2045 }
2046 call << ")";
2047
2048 std::ostringstream code;
2049 static int i = 0;
2050 std::string name = "reduced_function_" + std::to_string(++i);
2051 code << "namespace __cppjit_internal_wrap_g {\n"
2052 << result_type << " " << name << signature << "{" << "return ("
2053 << result_type << ")::" << fn_name << call.str() << "; }\n"
2054 << "}\n";
2055 if (interop::Compile(code.str().c_str())) {
2056 TCppScope_t res = Cpp::GetNamed(
2057 name, Cpp::GetScope("__cppjit_internal_wrap_g", /*parent=*/nullptr));
2058 if (res)
2059 return TCppMethod_t(res.data);
2060 }
2061 return fn;
2062}
2063
2065 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
2066 return Cpp::GetDimensions(type);
2067}
2068
2069// enum properties -----------------------------------------------------------
2070std::vector<interop::TCppScope_t> interop::GetEnumConstants(TCppScope_t scope) {
2071 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
2072 return Cpp::GetEnumConstants(scope);
2073}
2074
2076 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
2077 return Cpp::GetEnumConstantType(Cpp::GetUnderlyingScope(scope));
2078}
2079
2081 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
2082 return Cpp::GetEnumConstantValue(scope);
2083}
2084
2086 Cpp::TemplateArgInfo* args,
2087 size_t args_size) {
2088 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
2089 return Cpp::InstantiateTemplate(tmpl, args, args_size,
2090 /*instantiate_body=*/false);
2091}
2092
2094 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
2095 Cpp::DumpScope(scope);
2096}
free(fBuffer)
#define c(i)
Definition RSha256.hxx:101
#define a(i)
Definition RSha256.hxx:99
static Roo_reg_AGKInteg1D instance
size_t size(const MatrixT &matrix)
retrieve the size of a square matrix
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void data
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 Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h offset
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 r
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
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 Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h length
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 Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t type
char name[80]
Definition TGX11.cxx:142
R__EXTERN TVirtualMutex * gInterpreterMutex
#define gROOT
Definition TROOT.h:417
R__EXTERN TSystem * gSystem
Definition TSystem.h:582
std::recursive_mutex fLocal
static thread_local std::vector< TVirtualMutex * > fGlobalLocked
static TClass * GetClass(const char *name, Bool_t load=kTRUE, Bool_t silent=kFALSE)
Static method returning pointer to TClass of the specified class name.
Definition TClass.cxx:2999
static const TString & GetEtcDir()
Get the sysconfig directory in the installation. Static utility function.
Definition TROOT.cxx:3396
const char * Data() const
Definition TString.h:385
virtual Bool_t AccessPathName(const char *path, EAccessMode mode=kFileExists)
Returns FALSE if one can access a file using the specified access mode.
Definition TSystem.cxx:1318
char * DynamicPathName(const char *lib, Bool_t quiet=kFALSE)
Find a dynamic library called lib using the system search paths.
Definition TSystem.cxx:2042
This class implements a mutex interface.
virtual Int_t UnLock()=0
virtual Int_t Lock()=0
TLine * line
std::string trim(const std::string &line)
static std::string type_remap(const std::string &n1, const std::string &n2)
static bool is_identifier(std::string_view s)
static bool copy_args(Parameter *args, size_t nargs, void **vargs)
interop::TCppType_t int_like_type(interop::TCppType_t type)
static std::set< std::string > gSmartPtrTypes
interop::TCppScope_t GetEnumFromCompleteName(const std::string &name)
static char * cppstring_to_cstring(const std::string &cppstr)
static void release_args(Parameter *args, size_t nargs)
static bool WrapperCall(interop::TCppMethod_t method, size_t nargs, void *args_, void *self, void *result)
static std::set< std::string > g_builtins
cppjit::cpyrt::Parameter Parameter
static bool is_basic_string_of(const std::string &n, const char *charT)
static bool gEnableFastPath
static T CallT(interop::TCppMethod_t method, interop::TCppObject_t self, size_t nargs, void *args)
static void remove_deleted_methods(std::vector< interop::TCppMethod_t > &methods)
#define CPPJIT_IMP_CALL(typecode, rtype)
static Cpp::JitCall destructor_callable(interop::TCppScope_t scope)
bool split_comma_saparated_types(const std::string &name, std::vector< std::string > &types)
static size_t CALL_NARGS(size_t nargs)
int LoadCppInterOp()
static std::string cppyy_normalize_name(std::string name)
RInterOpMutex InterOpMutex
const Int_t n
Definition legend1.C:16
TROOT * GetROOT()
Definition TROOT.cxx:550
const int SMALL_ARGS_N
Definition callcontext.h:14
bool IsConstVar(TCppScope_t var)
Cpp::TypeRef TCppType_t
Definition API.h:42
TCppScope_t GetFullScope(const std::string &scope_name)
void GetDatamembers(TCppScope_t scope, std::vector< TCppScope_t > &datamembers)
Cpp::ObjectRef TCppObject_t
Definition API.h:43
TCppType_t GetTypeFromScope(TCppScope_t klass)
std::string GetTypeAsString(TCppType_t type)
TCppType_t GetDatamemberType(TCppScope_t data)
TCppType_t ResolveEnumPointerType(TCppType_t type)
bool ExistsMethodTemplate(TCppScope_t scope, const std::string &name)
bool IsFunctionType(TCppType_t typ)
TCppScope_t WrapLambdaFromVariable(TCppScope_t var)
void DeallocateFunctionArgs(void *args)
bool IsBuiltin(const std::string &type_name)
void CallV(TCppMethod_t method, TCppObject_t self, size_t nargs, void *args)
std::string GetName(TCppScope_t)
std::string ToString(TCppScope_t klass, TCppObject_t obj)
bool IsClassType(TCppType_t type)
void CallDestructor(TCppScope_t type, TCppObject_t self)
bool CheckDatamember(TCppScope_t scope, const std::string &name)
TCppMethod_t GetMethodTemplate(TCppScope_t scope, const std::string &name, const std::string &proto)
bool IsTemplateInstantiation(TCppScope_t scope)
TCppMethod_t AdaptFunctionForLambdaReturn(TCppMethod_t fn)
bool IsStaticTemplate(TCppScope_t scope, const std::string &name)
bool IsConstructor(TCppMethod_t method)
std::string GetMethodArgCanonTypeAsString(TCppMethod_t method, TCppIndex_t iarg)
bool HasVirtualDestructor(TCppScope_t type)
TCppIndex_t GetNumTemplatedMethods(TCppScope_t scope, bool accept_namespace=false)
TCppScope_t GetActualClass(TCppScope_t klass, TCppObject_t obj)
void GetTemplatedMethods(TCppScope_t scope, std::vector< TCppMethod_t > &methods)
std::string ResolveName(const std::string &cppitem_name)
std::string GetMethodArgTypeAsString(TCppMethod_t method, TCppIndex_t iarg)
TCppIndex_t GetMethodReqArgs(TCppMethod_t)
bool AppendTypesSlow(const std::string &name, std::vector< Cpp::TemplateArgInfo > &types, TCppScope_t parent=nullptr)
bool IsAbstract(TCppScope_t scope)
TCppScope_t GetParentScope(TCppScope_t scope)
std::string GetMethodReturnTypeAsString(TCppMethod_t)
TCppType_t GetReferencedType(TCppType_t type, bool rvalue=false)
TCppIndex_t GetMethodNumArgs(TCppMethod_t)
bool IsStaticDatamember(TCppScope_t var)
bool IsProtectedMethod(TCppMethod_t method)
Cpp::InterpRef TInterp_t
bool IsClass(TCppScope_t scope)
void Destruct(TCppScope_t scope, TCppObject_t instance)
TCppIndex_t GetNumBases(TCppScope_t klass)
TCppIndex_t CompareMethodArgType(TCppMethod_t, TCppIndex_t iarg, const std::string &req_type)
bool IsSubclass(TCppScope_t derived, TCppScope_t base)
TCppScope_t GetNamed(const std::string &scope_name, TCppScope_t parent_scope=TCppScope_t{})
intptr_t GetDatamemberOffset(TCppScope_t var, TCppScope_t klass=nullptr)
size_t SizeOfType(TCppType_t type)
Cpp::FuncRef TCppMethod_t
Definition API.h:44
bool IsPublicData(TCppScope_t var)
TCppType_t GetMethodArgType(TCppMethod_t, TCppIndex_t iarg)
TCppScope_t GetBaseScope(TCppScope_t klass, TCppIndex_t ibase)
bool IsIntegerType(TCppType_t type, bool *is_signed=nullptr)
bool IsFunctionPointerType(TCppType_t type)
bool IsTypedefed(TCppScope_t scope)
bool IsPrivateMethod(TCppMethod_t method)
TCppObject_t Allocate(TCppScope_t scope)
bool IsEnumScope(TCppScope_t scope)
bool IsSimilarFnTypes(TCppType_t typ1, TCppType_t typ2)
void GetClassMethods(TCppScope_t scope, std::vector< TCppMethod_t > &methods)
TCppMethod_t GetGlobalOperator(TCppScope_t scope, const std::string &lc, const std::string &rc, const std::string &op)
TCppMethod_t ReduceReturnType(TCppMethod_t fn, TCppType_t reduce)
void * AllocateFunctionArgs(size_t nargs)
void GetClassOperators(TCppScope_t klass, const std::string &opname, std::vector< TCppMethod_t > &operators)
TCppObject_t CallO(TCppMethod_t method, TCppObject_t self, size_t nargs, void *args, TCppType_t result_type)
bool IsPointerType(TCppType_t type)
std::string GetMethodArgName(TCppMethod_t, TCppIndex_t iarg)
void GetFnTypeSig(TCppType_t fn_type, std::vector< TCppType_t > &arg_types)
TCppFuncAddr_t GetFunctionAddress(TCppMethod_t method, bool check_enabled=true)
long long GetEnumDataValue(TCppScope_t scope)
size_t SizeOf(TCppScope_t klass)
std::string GetDoxygenComment(TCppScope_t scope, bool strip_markers=true)
TCppType_t GetMethodReturnType(TCppMethod_t)
TCppType_t ResolveType(TCppType_t cppitem_name)
TCppScope_t GetUnderlyingScope(TCppScope_t scope)
std::string GetMethodSignature(TCppMethod_t, bool show_formal_args, TCppIndex_t max_args=(TCppIndex_t) -1)
bool IsExplicit(TCppMethod_t method)
TCppScope_t GetGlobalScope()
std::string GetTemplatedMethodName(TCppScope_t scope, TCppIndex_t imeth)
std::string GetFullName(TCppScope_t)
TCppScope_t GetTypeScope(TCppScope_t klass)
bool IsTemplate(TCppScope_t scope)
bool IsDestructor(TCppMethod_t method)
std::vector< TCppScope_t > GetEnumConstants(TCppScope_t scope)
bool Compile(const std::string &code, bool silent=false)
TCppType_t GetPointerType(TCppType_t type)
std::vector< TCppMethod_t > GetMethodsFromName(TCppScope_t scope, const std::string &name)
std::string ResolveEnum(TCppScope_t enum_scope)
size_t TCppIndex_t
Definition API.h:45
bool IsDefaultConstructable(TCppScope_t scope)
TCppObject_t CallConstructor(TCppMethod_t method, TCppScope_t klass, size_t nargs, void *args)
bool IsDeletedMethod(TCppMethod_t method)
bool IsLValueReferenceType(TCppType_t type)
bool IsNamespace(TCppScope_t scope)
bool IsTemplatedMethod(TCppMethod_t method)
void DumpScope(TCppScope_t scope)
TCppScope_t GetScopeFromType(TCppType_t type)
TCppType_t GetFnTypeFromStdFn(TCppType_t fn_type)
std::string GetBaseName(TCppScope_t klass, TCppIndex_t ibase)
TCppType_t GetRealType(TCppType_t type)
TCppIndex_t GetNumBasesLongestBranch(TCppScope_t klass)
TCppScope_t GetScope(const std::string &scope_name, TCppScope_t parent_scope=TCppScope_t{})
TCppType_t GetEnumConstantType(TCppScope_t scope)
bool IsSameType(TCppType_t typ1, TCppType_t typ2)
bool IsEnumConstant(TCppScope_t scope)
std::string GetFinalName(TCppScope_t type)
bool IsProtectedData(TCppScope_t var)
bool IsLambdaClass(TCppType_t type)
bool IsConstMethod(TCppMethod_t)
ptrdiff_t GetBaseOffset(TCppScope_t derived, TCppScope_t base, TCppObject_t address, int direction, bool rerror=false)
TCppObject_t Construct(TCppScope_t scope, void *arena=nullptr)
bool IsPrivateData(TCppScope_t var)
void Deallocate(TCppScope_t scope, TCppObject_t instance)
void * TCppFuncAddr_t
Definition API.h:46
bool IsMutablePtrRefType(TCppType_t type)
bool IsVariable(TCppScope_t scope)
bool IsSmartPtr(TCppScope_t klass)
std::string GetMethodArgDefault(TCppMethod_t, TCppIndex_t iarg)
bool IsStaticMethod(TCppMethod_t method)
char * CallS(TCppMethod_t method, TCppObject_t self, size_t nargs, void *args, size_t *length)
bool IsRValueReferenceType(TCppType_t type)
TCppType_t GetComplexType(const std::string &element_type)
bool GetSmartPtrInfo(const std::string &, TCppScope_t *raw, TCppMethod_t *deref)
std::vector< TCppScope_t > GetUsingNamespaces(TCppScope_t)
bool IsPublicMethod(TCppMethod_t method)
TCppType_t GetType(const std::string &name, bool enable_slow_lookup=false)
bool IsAggregate(TCppScope_t type)
std::string GetScopedFinalName(TCppScope_t type)
void GetAllCppNames(TCppScope_t scope, std::set< std::string > &cppnames)
size_t GetFunctionArgTypeoffset()
std::string GetDatamemberTypeAsString(TCppScope_t var)
TCppType_t ResolveEnumReferenceType(TCppType_t type)
Cpp::DeclRef TCppScope_t
Definition API.h:41
std::vector< long int > GetDimensions(TCppType_t type)
bool IsEnumType(TCppType_t type)
bool IsComplete(TCppScope_t type)
TCppScope_t InstantiateTemplate(TCppScope_t tmpl, Cpp::TemplateArgInfo *args, size_t args_size)
Definition API.cxx:34
#define RPY_EXPORTED
union cppjit::cpyrt::Parameter::Value fValue
TMarker m
Definition textangle.C:8
TLine l
Definition textangle.C:4