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 void 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 Cpp::Process(code);
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 acquireOrCreateInterpreter(Paths);
276 configureInterpreter(Paths);
277 preloadHeaders();
278 defineRuntimeHelpers();
279
280 Loaded = 1;
281 });
282 return Loaded;
283}
284
285// local helpers -------------------------------------------------------------
286static inline char* cppstring_to_cstring(const std::string& cppstr) {
287 char* cstr = (char*)malloc(cppstr.size() + 1);
288 memcpy(cstr, cppstr.c_str(), cppstr.size() + 1);
289 return cstr;
290}
291
292// direct interpreter access -------------------------------------------------
293// Returns false on failure and true on success
294bool interop::Compile(const std::string& code, bool silent) {
295 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
296 // Declare returns an enum which equals 0 on success
297 return !Cpp::Declare(code.c_str(), silent);
298}
299
301
303
305 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
306 if (klass && obj && !Cpp::IsNamespace(klass))
307 return Cpp::ObjToString(Cpp::GetQualifiedCompleteName(klass).c_str(),
308 obj.data);
309 return "";
310}
311
312// // name to opaque C++ scope representation
313// -----------------------------------
314std::string interop::ResolveName(const std::string& name) {
315 if (!name.empty()) {
317 interop::GetType(name, /*enable_slow_lookup=*/true))
319 return name;
320 }
321 return "";
322}
323
325 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
326 if (Cpp::GetValueKind(type) != Cpp::ValueKind::LValue)
327 return type;
328
329 TCppType_t nonReferenceType = Cpp::GetNonReferenceType(type);
330 if (Cpp::IsEnumType(nonReferenceType)) {
331 TCppType_t underlying_type =
332 Cpp::GetIntegerTypeFromEnumType(nonReferenceType);
333 return Cpp::GetReferencedType(underlying_type, /*rvalue=*/false);
334 }
335 return type;
336}
337
339 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
340 if (!Cpp::IsPointerType(type))
341 return type;
342
343 TCppType_t PointeeType = Cpp::GetPointeeType(type);
344 if (Cpp::IsEnumType(PointeeType)) {
345 TCppType_t underlying_type = Cpp::GetIntegerTypeFromEnumType(PointeeType);
346 return Cpp::GetPointerType(underlying_type);
347 }
348 return type;
349}
350
352 interop::TCppType_t check_int_typedefs = type;
353 if (Cpp::IsPointerType(check_int_typedefs))
354 check_int_typedefs = Cpp::GetPointeeType(check_int_typedefs);
355 if (Cpp::IsReferenceType(check_int_typedefs))
356 check_int_typedefs =
357 Cpp::GetReferencedType(check_int_typedefs, /*rvalue=*/false);
358
359 if (Cpp::GetTypeAsString(check_int_typedefs) == "int8_t" ||
360 Cpp::GetTypeAsString(check_int_typedefs) == "uint8_t")
361 return check_int_typedefs;
362 return nullptr;
363}
364
366 if (!type)
367 return type;
368
369 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
370
371 TCppType_t check_int_typedefs = int_like_type(type);
372 if (check_int_typedefs)
373 return type;
374
375 interop::TCppType_t canonType = Cpp::GetCanonicalType(type);
376
377 if (Cpp::IsEnumType(canonType)) {
378 if (Cpp::GetTypeAsString(type) != "std::byte")
379 return Cpp::GetIntegerTypeFromEnumType(canonType);
380 }
381 if (Cpp::HasTypeQualifier(canonType, Cpp::QualKind::Restrict)) {
382 return Cpp::RemoveTypeQualifier(canonType, Cpp::QualKind::Restrict);
383 }
384
385 return canonType;
386}
387
389 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
390 TCppType_t check_int_typedefs = int_like_type(type);
391 if (check_int_typedefs)
392 return check_int_typedefs;
393 return Cpp::GetUnderlyingType(type);
394}
395
397 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
398 return Cpp::GetPointerType(type);
399}
400
402 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
403 return Cpp::GetReferencedType(type, rvalue);
404}
405
407 return Cpp::GetValueKind(type) == Cpp::ValueKind::RValue;
408}
409
411 return Cpp::GetValueKind(type) == Cpp::ValueKind::LValue;
412}
413
414// Whether the callee can rebind a pointer through this reference (T*&);
415// false for T* const& and for references to non-pointers.
417 if (!Cpp::IsReferenceType(type))
418 return false;
419 TCppType_t nonref = Cpp::GetNonReferenceType(type);
420 return Cpp::IsPointerType(nonref) &&
421 !Cpp::HasTypeQualifier(nonref, Cpp::QualKind::Const);
422}
423
424bool interop::IsClassType(TCppType_t type) { return Cpp::IsRecordType(type); }
425
426bool interop::IsIntegerType(TCppType_t type, bool* is_signed /*= nullptr*/) {
427 if (is_signed) {
428 Cpp::Signedness sign;
429 bool res = Cpp::IsIntegerType(type, &sign);
430 *is_signed = (sign == Cpp::Signedness::kSigned);
431 return res;
432 }
433 return Cpp::IsIntegerType(type, nullptr);
434}
435
437 return Cpp::IsPointerType(type);
438}
439
441 return Cpp::IsFunctionPointerType(type);
442}
443
444std::string trim(const std::string& line) {
445 if (line.empty())
446 return "";
447 const char* WhiteSpace = " \t\v\r\n";
448 std::size_t start = line.find_first_not_of(WhiteSpace);
449 std::size_t end = line.find_last_not_of(WhiteSpace);
450 return line.substr(start, end - start + 1);
451}
452
453// returns false of angular brackets dont match, else true
454bool split_comma_saparated_types(const std::string& name,
455 std::vector<std::string>& types) {
456 std::string trimed_name = trim(name);
457 size_t start_pos = 0;
458 size_t end_pos = 0;
459 int matching_angular_brackets = 0;
460 while (end_pos < trimed_name.size()) {
461 switch (trimed_name[end_pos]) {
462 case ',': {
463 if (!matching_angular_brackets) {
464 if (end_pos > start_pos)
465 types.push_back(
466 trim(trimed_name.substr(start_pos, end_pos - start_pos)));
467 start_pos = end_pos + 1;
468 }
469 break;
470 }
471 case '<': {
472 matching_angular_brackets++;
473 break;
474 }
475 case '>': {
476 matching_angular_brackets--;
477 break;
478 }
479 }
480 end_pos++;
481 }
482 if (start_pos < trimed_name.size())
483 types.push_back(trim(trimed_name.substr(start_pos, end_pos - start_pos)));
484 return true;
485}
486
488 std::string delim = "::";
489 size_t start = 0;
490 size_t end = name.find(delim);
491 interop::TCppScope_t curr_scope;
492 while (end != std::string::npos) {
493 curr_scope = Cpp::GetNamed(name.substr(start, end - start), curr_scope);
494 start = end + delim.length();
495 end = name.find(delim, start);
496 }
497 return Cpp::GetNamed(name.substr(start, end), curr_scope);
498}
499static bool is_identifier(std::string_view s) {
500 if (s.empty())
501 return false;
502 auto is_valid_start = [](unsigned char c) {
503 return std::isalpha(c) || c == '_';
504 };
505 auto is_valid_body = [](unsigned char c) {
506 return std::isalnum(c) || c == '_';
507 };
508 return is_valid_start(s[0]) &&
509 std::all_of(s.begin() + 1, s.end(), is_valid_body);
510};
511
512// returns true if no new type was added.
513bool interop::AppendTypesSlow(const std::string& name,
514 std::vector<Cpp::TemplateArgInfo>& types,
515 interop::TCppScope_t parent) {
516
517 // Add no new type if string is empty
518 if (name.empty())
519 return true;
520
521 // The ast printer gave us garbage.
522 if (name == "<unnamed>")
523 return true;
524
525 // A type inside an anonymous namespace cannot be spelled in injected
526 // code; attempting it crashes codegen on the ill-formed recovery (e.g.
527 // auto-downcasting to an anonymous FuncExporter<...> instantiation).
528 if (name.find("(anonymous namespace)") != std::string::npos)
529 return true;
530
531 auto replace_all = [](std::string& str, const std::string& from,
532 const std::string& to) {
533 if (from.empty())
534 return;
535 size_t start_pos = 0;
536 while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
537 str.replace(start_pos, from.length(), to);
538 start_pos += to.length();
539 }
540 };
541
542 std::string resolved_name = name;
543 replace_all(resolved_name, "std::initializer_list<",
544 "std::vector<"); // replace initializer_list with vector
545
546 // If we have a single identifier, we don't need anything complicated.
547 // Try scoped lookup first (catches type aliases / nested types declared
548 // inside `parent`), then fall back to TU (catches typedefs declared
549 // outside the query scope, e.g. `typedef Foo Bar;` at TU consulted
550 // from a method on Foo).
551 if (is_identifier(name)) {
552 TCppType_t type = parent ? Cpp::GetType(name, parent) : nullptr;
553 if (!type)
554 type = Cpp::GetType(name);
555 if (type) {
556 types.emplace_back(type.data);
557 return false;
558 }
559 if (!parent || parent == Cpp::GetGlobalScope())
560 return true;
561 // Fall through: the name may live in a scope enclosing `parent` (e.g.
562 // "RVecF" from within ROOT::RDF::RInterface<...> resolves to
563 // ROOT::RVecF); the trampoline below applies real unqualified lookup.
564 }
565
566 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
567
568 // We might have an entire expression such as int, double.
569 static unsigned long long struct_count = 0;
570 std::string code =
571 "template<typename ...T> struct __cppjit_interop_AppendTypesSlow {};\n";
572 if (!struct_count)
573 Cpp::Declare(code.c_str(), /*silent=*/true); // initialize the trampoline
574
575 // Perform the lookup from within the innermost (reopenable) namespace
576 // enclosing `parent` -- `parent` itself if it is a namespace, else the
577 // namespace its class chain lives in -- by reopening that namespace around
578 // the trampoline declaration: the compiler then applies real C++ name
579 // lookup, walking outward with proper shadowing of outer names, which a
580 // global declaration with a parent-qualified fallback cannot emulate (e.g.
581 // "RVecF" from within ROOT::RDF::RInterface<...> resolves to ROOT::RVecF).
582 std::string lookup_ns;
583 TCppScope_t lookup_scope = nullptr;
584 for (TCppScope_t s = parent; s && s != Cpp::GetGlobalScope();
585 s = Cpp::GetParentScope(s)) {
586 if (!Cpp::IsNamespace(s))
587 continue;
588 std::string qname = Cpp::GetQualifiedName(s);
589 if (!qname.empty() && qname.find('(') == std::string::npos) { // unnamed?
590 lookup_ns = qname;
591 lookup_scope = s;
592 }
593 break;
594 }
595
596 // The reopened namespace doesn't see names nested in a class parent, so a
597 // name written relative to it (e.g. a nested type) won't resolve. Try the
598 // name as given, then qualified by the parent.
599 std::vector<std::string> candidates = {resolved_name};
600 if (parent && parent != Cpp::GetGlobalScope() && parent != lookup_scope &&
601 (interop::IsNamespace(parent) || interop::IsClass(parent)))
602 candidates.push_back(Cpp::GetQualifiedCompleteName(parent) +
603 "::" + resolved_name);
604
605 for (const std::string& candidate : candidates) {
606 std::string var = "__cppjit_interop_s" + std::to_string(struct_count++);
607 // nodebug: with -g the variable's debug info would carry the full DIE
608 // tree of every template argument (all member declarations included) --
609 // a large, uncacheable per-lookup cost on heavyweight types.
610 std::string decl = "__cppjit_interop_AppendTypesSlow<" + candidate +
611 "> __attribute__((nodebug)) " + var + ";\n";
612 if (!lookup_ns.empty())
613 decl = "namespace " + lookup_ns + " { " + decl + "}\n";
614 if (!Cpp::Declare(decl.c_str(), /*silent=*/true)) {
615 TCppType_t varN = Cpp::GetVariableType(Cpp::GetNamed(
616 var.c_str(), lookup_ns.empty() ? nullptr : lookup_scope));
617 TCppScope_t instance_class = Cpp::GetScopeFromType(varN);
618 if (!instance_class)
619 continue; // recovered-but-broken decl; try next candidate or split path
620 size_t oldSize = types.size();
621 Cpp::GetClassTemplateInstantiationArgs(instance_class, types);
622 return oldSize == types.size();
623 }
624 }
625
626 // We split each individual types based on , and resolve it
627 // FIXME: see discussion on should we support template instantiation with
628 // string:
629 // https://github.com/compiler-research/cppyy-backend/pull/137#discussion_r2079357491
630 // We should consider eliminating the `split_comma_saparated_types` and
631 // `is_integral` string parsing.
632 std::vector<std::string> individual_types;
633 if (!split_comma_saparated_types(resolved_name, individual_types))
634 return true;
635
636 for (std::string& i : individual_types) {
637 // Try going via interop::GetType first.
638 const char* integral_value = nullptr;
639 interop::TCppType_t type = nullptr;
640
641 if (!lookup_ns.empty()) {
642 // resolve from within the parent namespace, honouring C++ name lookup
643 // and shadowing (see the trampoline declaration above)
644 std::string id = "__cppjit_interop_s" + std::to_string(struct_count++);
645 if (!Cpp::Declare(("namespace " + lookup_ns + " { using " + id +
646 " = __typeof__(" + i + "); }\n")
647 .c_str(),
648 /*silent=*/true))
649 type = Cpp::GetCanonicalType(
650 Cpp::GetTypeFromScope(Cpp::GetNamed(id, lookup_scope)));
651 } else {
652 type = GetType(i, /*enable_slow_lookup=*/true);
653 if (!type && parent &&
654 (interop::IsNamespace(parent) || interop::IsClass(parent))) {
655 type =
656 interop::GetTypeFromScope(interop::GetNamed(resolved_name, parent));
657 }
658 }
659
660 if (!type) {
661 types.clear();
662 return true;
663 }
664
665 if (is_integral(i))
666 integral_value = strdup(i.c_str());
668 if (Cpp::IsEnumConstant(scope))
669 integral_value =
670 strdup(std::to_string(Cpp::GetEnumConstantValue(scope)).c_str());
671 types.emplace_back(type.data, integral_value);
672 }
673 return false;
674}
675
677 bool enable_slow_lookup /* = false */) {
678 // The ast printer gave us garbage.
679 if (name == "<unnamed>")
680 return nullptr;
681 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
682
683 if (auto type = Cpp::GetType(name))
684 return type;
685
686 // Plain identifiers don't need the heavy __typeof__ trampoline:
687 // Cpp::GetType above already covers builtin types and named
688 // scopes. Exception: the three identifier-shaped C++ value-
689 // literals -- `true`, `false`, `nullptr` -- aren't reachable by
690 // name (no type called "false") but appear as non-type template
691 // args in libstdc++ types like _Node_iterator<..., false, false>;
692 // map them to their underlying type directly so the per-chunk
693 // fallback in AppendTypesSlow gets a real type without paying
694 // the trampoline cost.
695 if (is_identifier(name)) {
696 if (name == "true" || name == "false")
697 return Cpp::GetType("bool");
698 if (name == "nullptr")
699 return Cpp::GetType("nullptr_t", Cpp::GetNamed("std"));
700 return nullptr;
701 }
702
703 if (!enable_slow_lookup) {
704 if (name.find("::") != std::string::npos)
705 throw std::runtime_error(
706 "Calling interop::GetType with qualified name '" + name + "'\n");
707 return nullptr;
708 }
709
710 // Here we might need to deal with integral types such as 3.14.
711
712 // Declaring the trampoline parses and compiles code, and each one adds a
713 // new alias to the AST, so memoize the resolved types by name. A name that
714 // fails to resolve may succeed once more declarations are available, so
715 // failures are retried.
716 static std::map<std::string, TCppType_t> s_slow_type_cache;
717 auto cached = s_slow_type_cache.find(name);
718 if (cached != s_slow_type_cache.end())
719 return cached->second;
720
721 static unsigned long long var_count = 0;
722 std::string id = "__cppjit_interop_GetType_" + std::to_string(var_count++);
723 std::string using_clause = "using " + id + " = __typeof__(" + name + ");\n";
724
725 if (!Cpp::Declare(using_clause.c_str(), /*silent=*/true)) {
726 TCppScope_t lookup = Cpp::GetNamed(id);
727 TCppType_t lookup_ty = Cpp::GetTypeFromScope(lookup);
728 TCppType_t result = Cpp::GetCanonicalType(lookup_ty);
729 if (result)
730 s_slow_type_cache.emplace(name, result);
731 return result;
732 }
733 return nullptr;
734}
735
737 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
738 return Cpp::GetComplexType(Cpp::GetType(name));
739}
740
742 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
743 std::string type =
744 Cpp::GetTypeAsString(Cpp::GetIntegerTypeFromEnumScope(handle));
745 if (type == "signed char")
746 return "char";
747 return type;
748}
749
751 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
752 return Cpp::GetUnderlyingScope(scope);
753}
754
756 TCppScope_t parent_scope) {
757 std::unique_lock<RInterOpMutex> Lock(InterOpMutex);
758 // CppInterOp directly looks at the AST which is not enough.
759 // We require lazy module loading that ROOT relies on, so we do it here
760 // first. Use TClass::GetClass to trigger auto-loading of dictionaries and
761 // modules.
762 if (!parent_scope || parent_scope == Cpp::GetGlobalScope())
763 TClass::GetClass(name.c_str(), true /* load */, true /* silent */);
764
765 if (interop::TCppScope_t scope = Cpp::GetScope(name, parent_scope))
766 return scope;
767 if (!parent_scope || parent_scope == Cpp::GetGlobalScope()) {
768 if (interop::TCppScope_t scope = Cpp::GetScopeFromCompleteName(name))
769 return scope;
770 } else if (name.find('<') == std::string::npos) {
771 // Cpp::GetScope handles single identifiers only, so walk the components
772 // of a qualified name. Templated names take the branch below: "::" inside
773 // a template argument list cannot be split naively.
774 TCppScope_t curr = parent_scope;
775 size_t start = 0, end;
776 while (curr && (end = name.find("::", start)) != std::string::npos) {
777 curr = Cpp::GetScope(name.substr(start, end - start), curr);
778 start = end + 2;
779 }
780 if (curr && curr != parent_scope)
781 if (interop::TCppScope_t scope = Cpp::GetScope(name.substr(start), curr))
782 return scope;
783 }
784
785 // FIXME: avoid string parsing here
786 if (name.find('<') != std::string::npos) {
787 // Templated type; may need instantiation. Resolve the whole type
788 // expression (e.g. "std::array<float, 3>") and read back its scope.
789 // Splitting off the argument list and resolving it directly cannot
790 // represent non-type arguments such as the `3` in std::array<float, 3>.
791 std::vector<Cpp::TemplateArgInfo> types;
792 Lock.unlock(); // unlock to allow AppendTypesSlow
793 bool added_new_type =
794 !interop::AppendTypesSlow(name, types, /*parent=*/parent_scope);
795 Lock.lock();
796 if (added_new_type && types.size() == 1) {
797 // A pointer or reference spelling (e.g. "std::chrono::nanoseconds *",
798 // the return type of std::array<nanoseconds, N>::begin()) does not
799 // name a scope; GetScopeFromType would silently strip the pointer and
800 // return the pointee's scope, misclassifying the name.
801 if (Cpp::IsPointerType(types[0].m_Type) ||
802 Cpp::IsReferenceType(types[0].m_Type))
803 return nullptr;
804 TCppScope_t scope = Cpp::GetScopeFromType(types[0].m_Type);
805 // Naming the type as a template argument above does not instantiate
806 // it, so the specialization may still be declared-but-undefined.
807 // Force its definition: callers expect a complete scope, e.g. to
808 // walk its base classes.
809 if (scope)
810 Cpp::IsComplete(scope);
811 return scope;
812 }
813 }
814 return nullptr;
815}
816
820
822 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
823 return Cpp::GetScopeFromType(Cpp::GetVariableType(var));
824}
825
827 TCppScope_t parent_scope) {
828 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
829 if (TCppScope_t named = Cpp::GetNamed(name, parent_scope))
830 return named;
831
832 // "gROOT" is a macro expanding to ROOT::GetROOT(); ROOT master exposed it
833 // to cppyy as a TGlobalMappedFunction, a mechanism this backend does not
834 // consult. Resolve it to the underlying variable instead (making sure it
835 // has been initialized first).
836 if (name == "gROOT" &&
837 (!parent_scope || parent_scope == Cpp::GetGlobalScope())) {
839 return Cpp::GetNamed(
840 "gROOTLocal",
841 Cpp::GetNamed("Internal", Cpp::GetNamed("ROOT", nullptr)));
842 }
843
844 return nullptr;
845}
846
848 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
849 return Cpp::GetParentScope(scope);
850}
851
853 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
854 return Cpp::GetScopeFromType(type);
855}
856
858 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
859 return Cpp::GetTypeFromScope(klass);
860}
861
863 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
864 return Cpp::GetGlobalScope();
865}
866
867bool interop::IsTemplate(TCppScope_t handle) { return Cpp::IsTemplate(handle); }
868
870 return Cpp::IsTemplateSpecialization(handle);
871}
872
874 return Cpp::IsTypedefed(handle);
875}
876
877namespace {
878class AutoCastRTTI {
879public:
880 virtual ~AutoCastRTTI() {}
881};
882} // namespace
883
885 TCppObject_t obj) {
886 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
887
888 if (!obj || !Cpp::IsClassPolymorphic(klass))
889 return klass;
890
891 // Skip the std stream hierarchy: autocasting it is not useful, and on MSVC
892 // its virtual inheritance puts the vbptr, not a vfptr, at offset 0, so the
893 // RTTI probe below follows a garbage pointer.
894 static const std::array<TCppScope_t, 3> stream_bases = {
895 interop::GetScope("std::ios_base"), interop::GetScope("std::streambuf"),
896 interop::GetScope("std::wstreambuf")};
897 for (TCppScope_t base : stream_bases)
898 if (base && interop::IsSubclass(klass, base))
899 return klass;
900
901 // This is hit for every polymorphic object handed to Python, and the name
902 // lookups below cost microseconds, so their outcomes are memoized. Keyed on
903 // (static class, dynamic type). Only outcomes that cannot change later are
904 // cached; a failed lookup may succeed once more declarations are available,
905 // so failures are retried.
906 static std::map<std::pair<const void*, const std::type_info*>, TCppScope_t>
907 s_actual_class_cache;
908 const std::type_info* typ = &typeid(*(AutoCastRTTI*)obj.data);
909 if (!typ)
910 return klass;
911 const auto cacheKey = std::make_pair((const void*)klass.data, typ);
912 auto cached = s_actual_class_cache.find(cacheKey);
913 if (cached != s_actual_class_cache.end())
914 return cached->second;
915
916#ifdef _WIN32
917 // MSVC's type_info::name() is already human-readable, but prefixed with
918 // the tag kind ("class TWinNTSystem"), which plain name lookup does not
919 // accept. Strip the prefix by hand; respelling through type resolution
920 // would JIT an uncached declaration per downcast. Template arguments
921 // keep their tags, which is harmless as templated names go through type
922 // resolution rather than plain lookup.
923 std::string demangled_name = typ->name();
924 for (const char* prefix : {"class ", "struct ", "union ", "enum "}) {
925 if (demangled_name.compare(0, strlen(prefix), prefix) == 0) {
926 demangled_name = demangled_name.substr(strlen(prefix));
927 break;
928 }
929 }
930#else
931 std::string mangled_name = typ->name();
932 std::string demangled_name = Cpp::Demangle(mangled_name);
933#endif
934
935 // A type in an anonymous namespace cannot be named in injected code and
936 // has no dictionary, so looking it up would fail; worse, its unspellable
937 // name crashes the interpreter ("(anonymous namespace)" on Itanium,
938 // "`anonymous namespace'" on MSVC). Keep the base type.
939 if (demangled_name.find("anonymous namespace") != std::string::npos)
940 return klass;
941
942 if (TCppScope_t scope = interop::GetScope(demangled_name)) {
943 // A type inside an anonymous namespace cannot be spelled in injected
944 // code (e.g. the dispatcher's), so it is unusable as a cast target;
945 // keep the static type. Anonymous namespaces are the ones without a
946 // name of their own.
947 for (TCppScope_t p = Cpp::GetParentScope(scope); p;
948 p = Cpp::GetParentScope(p))
949 if (Cpp::IsNamespace(p) && Cpp::GetName(p).empty())
950 return klass;
951 // Only return the derived type once it has a complete definition. Under
952 // runtime_cxxmodules=OFF, autoloading the dictionary registers only a
953 // forward declaration, so force the definition into the AST here. Internal
954 // classes like TCling have no header and cannot be completed (their
955 // CXXRecordDecl has no DefinitionData); GetOrForceDefinition returns null
956 // for them and we fall back to the base type, avoiding a crash when
957 // querying offsets.
958 if (Cpp::GetOrForceDefinition(scope)) {
959 s_actual_class_cache.emplace(cacheKey, scope);
960 return scope;
961 }
962 }
963
964 return klass;
965}
966
968 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
969 return Cpp::SizeOf(klass);
970}
971
973 if (!klass)
974 return 0;
975 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
976 return Cpp::GetSizeOfType(klass);
977}
978
979bool interop::IsBuiltin(const std::string& type_name) {
980 static std::set<std::string> s_builtins = {"bool",
981 "char",
982 "signed char",
983 "unsigned char",
984 "wchar_t",
985 "short",
986 "unsigned short",
987 "int",
988 "unsigned int",
989 "long",
990 "unsigned long",
991 "long long",
992 "unsigned long long",
993 "float",
994 "double",
995 "long double",
996 "void"};
997 if (s_builtins.find(trim(type_name)) != s_builtins.end())
998 return true;
999
1000 if (strstr(type_name.c_str(), "std::complex"))
1001 return true;
1002
1003 return false;
1004}
1005
1006bool interop::IsBuiltin(TCppType_t type) { return Cpp::IsBuiltin(type); }
1007
1009 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1010 return Cpp::IsComplete(scope);
1011}
1012
1013// // memory management
1014// ---------------------------------------------------------
1016 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1017 return Cpp::Allocate(scope, /*count=*/1);
1018}
1019
1021 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1022 Cpp::Deallocate(scope, instance, /*count=*/1);
1023}
1024
1025// Constructors and destructors are resolved (and JIT-compiled) under the
1026// lock and run without it, as WrapperCall does: their bodies are user code.
1028 void* arena /*=nullptr*/) {
1029 std::unique_lock<RInterOpMutex> Lock(InterOpMutex);
1030 if (!Cpp::HasDefaultConstructor(scope))
1031 return nullptr;
1032 Cpp::JitCall JC =
1033 Cpp::MakeFunctionCallable(Cpp::GetDefaultConstructor(scope));
1034 Lock.unlock();
1035 if (!JC)
1036 return nullptr;
1037 void* result = arena;
1038 JC.InvokeConstructor(&result, /*nary=*/1, /*args=*/{},
1039 /*is_arena=*/arena ? reinterpret_cast<void*>(1)
1040 : nullptr);
1041 return result;
1042}
1043
1044static Cpp::JitCall destructor_callable(interop::TCppScope_t scope) {
1045 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1046 return Cpp::MakeFunctionCallable(Cpp::GetDestructor(scope));
1047}
1048
1050 if (Cpp::JitCall JC = destructor_callable(scope))
1051 JC.InvokeDestructor(instance.data, /*nary=*/0, /*withFree=*/true);
1052}
1053
1054static inline bool copy_args(Parameter* args, size_t nargs, void** vargs) {
1055 bool runRelease = false;
1056 for (size_t i = 0; i < nargs; ++i) {
1057 switch (args[i].fTypeCode) {
1058 case 'X': /* (void*)type& with free */
1059 runRelease = true;
1060 case 'V': /* (void*)type& */
1061 vargs[i] = args[i].fValue.fVoidp;
1062 break;
1063 case 'r': /* const type& */
1064 vargs[i] = args[i].fRef;
1065 break;
1066 default: /* all other types in union */
1067 vargs[i] = (void*)&args[i].fValue.fVoidp;
1068 break;
1069 }
1070 }
1071 return runRelease;
1072}
1073
1074static inline void release_args(Parameter* args, size_t nargs) {
1075 for (size_t i = 0; i < nargs; ++i) {
1076 if (args[i].fTypeCode == 'X')
1077 free(args[i].fValue.fVoidp);
1078 }
1079}
1080
1081static inline bool WrapperCall(interop::TCppMethod_t method, size_t nargs,
1082 void* args_, void* self, void* result) {
1083 Parameter* args = (Parameter*)args_;
1084 // bool is_direct = nargs & DIRECT_CALL;
1085 nargs = CALL_NARGS(nargs);
1086
1087 // if (!is_ready(wrap, is_direct))
1088 // return false; // happens with compilation error
1090 if (Cpp::JitCall JC = Cpp::MakeFunctionCallable(method)) {
1092 bool runRelease = false;
1093 // const auto& fgen = /* is_direct ? faceptr.fDirect : */ faceptr;
1094 if (nargs <= cpyrt::SMALL_ARGS_N) {
1095 void* smallbuf[cpyrt::SMALL_ARGS_N];
1096 if (nargs)
1097 runRelease = copy_args(args, nargs, smallbuf);
1098 // CLING_CATCH_UNCAUGHT_
1099 JC.Invoke(result, {smallbuf, nargs}, self);
1100 // _CLING_CATCH_UNCAUGHT
1101 } else {
1102 std::vector<void*> buf(nargs);
1103 runRelease = copy_args(args, nargs, buf.data());
1104 // CLING_CATCH_UNCAUGHT_
1105 JC.Invoke(result, {buf.data(), nargs}, self);
1106 // _CLING_CATCH_UNCAUGHT
1107 }
1108 if (runRelease)
1109 release_args(args, nargs);
1110 return true;
1111 }
1113 return false;
1114}
1115
1116template <typename T>
1118 size_t nargs, void* args) {
1119 T t{};
1120 if (WrapperCall(method, nargs, args, self.data, &t))
1121 return t;
1122 throw std::runtime_error("failed to resolve function");
1123 return (T)-1;
1124}
1125
1126#ifdef PRINT_DEBUG
1127#define _IMP_CALL_PRINT_STMT(type) printf("IMP CALL with type: %s\n", #type);
1128#else
1129#define _IMP_CALL_PRINT_STMT(type)
1130#endif
1131
1132#define CPPJIT_IMP_CALL(typecode, rtype) \
1133 rtype interop::Call##typecode(TCppMethod_t method, TCppObject_t self, \
1134 size_t nargs, void* args) { \
1135 _IMP_CALL_PRINT_STMT(rtype) \
1136 return CallT<rtype>(method, self, nargs, args); \
1137 }
1138
1139void interop::CallV(TCppMethod_t method, TCppObject_t self, size_t nargs,
1140 void* args) {
1141 if (!WrapperCall(method, nargs, args, self.data, nullptr))
1142 return /* TODO ... report error */;
1143}
1144
1145// clang-format off
1146CPPJIT_IMP_CALL(B, unsigned char)
1151CPPJIT_IMP_CALL(LL, long long )
1154CPPJIT_IMP_CALL(LD, long double )
1155// clang-format on
1156
1157void* interop::CallR(TCppMethod_t method, TCppObject_t self, size_t nargs,
1158 void* args) {
1159 void* r = nullptr;
1160 if (WrapperCall(method, nargs, args, self.data, &r))
1161 return r;
1162 return nullptr;
1163}
1164
1165char* interop::CallS(TCppMethod_t method, TCppObject_t self, size_t nargs,
1166 void* args, size_t* length) {
1167 char* cstr = nullptr;
1168 // TClassRef cr("std::string"); // TODO: Why is this required?
1169 std::string* cppresult = (std::string*)malloc(sizeof(std::string));
1170 if (WrapperCall(method, nargs, args, self.data, (void*)cppresult)) {
1171 cstr = cppstring_to_cstring(*cppresult);
1172 *length = cppresult->size();
1173 cppresult->std::string::~basic_string();
1174 } else
1175 *length = 0;
1176 free((void*)cppresult);
1177 return cstr;
1178}
1179
1181 TCppScope_t /*klass*/,
1182 size_t nargs, void* args) {
1183 void* obj = nullptr;
1184 WrapperCall(method, nargs, args, nullptr, &obj);
1185 return (TCppObject_t)obj;
1186}
1187
1189 if (Cpp::JitCall JC = destructor_callable(scope))
1190 JC.InvokeDestructor(self.data, /*nary=*/0, /*withFree=*/false);
1191}
1192
1194 size_t nargs, void* args,
1195 TCppType_t result_type) {
1196 size_t size = interop::SizeOfType(result_type);
1197 if (size == 0)
1198 return TCppObject_t{}; // unsizable return type; the caller reports
1199 void* obj = ::operator new(size);
1200 if (WrapperCall(method, nargs, args, self.data, obj))
1201 return (TCppObject_t)obj;
1202 ::operator delete(obj);
1203 return TCppObject_t{};
1204}
1205
1207 bool /*check_enabled*/) {
1208 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1209 return Cpp::GetFunctionAddress(method);
1210}
1211
1212// handling of function argument buffer --------------------------------------
1214 return new Parameter[nargs];
1215}
1216
1217void interop::DeallocateFunctionArgs(void* args) { delete[] (Parameter*)args; }
1218
1219size_t interop::GetFunctionArgSizeof() { return sizeof(Parameter); }
1220
1222 return offsetof(Parameter, fTypeCode);
1223}
1224
1225// scope reflection information ----------------------------------------------
1227 if (!scope)
1228 return false;
1229
1230 // Test if this scope represents a namespace.
1231 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1232 return Cpp::IsNamespace(scope) || Cpp::GetGlobalScope() == scope;
1233}
1234
1236 // Test if this scope represents a namespace.
1237 return Cpp::IsClass(scope);
1238}
1239//
1241 // Test if this type may not be instantiated.
1242 return Cpp::IsAbstract(scope);
1243}
1244
1245bool interop::IsEnumScope(TCppScope_t scope) { return Cpp::IsEnumScope(scope); }
1246
1248 return Cpp::IsEnumConstant(interop::GetUnderlyingScope(scope));
1249}
1250
1251bool interop::IsEnumType(TCppType_t type) { return Cpp::IsEnumType(type); }
1252
1254 // Test if this type is a "plain old data" type
1255 return Cpp::IsAggregate(type);
1256}
1257
1259 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1260 // Test if this type has a default constructor or is a "plain old data" type
1261 return Cpp::HasDefaultConstructor(scope);
1262}
1263
1264bool interop::IsVariable(TCppScope_t scope) { return Cpp::IsVariable(scope); }
1265
1267 std::set<std::string>& cppnames) {
1268 // Collect all known names of C++ entities under scope. This is useful for
1269 // IDEs employing tab-completion, for example. Note that functions names need
1270 // not be unique as they can be overloaded.
1271 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1272 Cpp::GetAllCppNames(scope, cppnames);
1273}
1274
1275// class reflection information ----------------------------------------------
1276std::vector<interop::TCppScope_t>
1278 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1279 return Cpp::GetUsingNamespaces(scope);
1280}
1281
1282// Normalize a type or scope name to cppyy's canonical form: no space after
1283// the commas separating template arguments, and pointers/references attached
1284// to the type. This is the form cpyrt itself constructs (e.g. when
1285// looking up cached template instantiations by name, see
1286// Utility::ConstructTemplateArgs) and the convention that user code and the
1287// test suite inherited from upstream cppyy; clang's printer instead emits
1288// "a, b", "T *" and "T &".
1289static std::string cppyy_normalize_name(std::string name) {
1290 std::string::size_type pos = 0;
1291 while ((pos = name.find(", ", pos)) != std::string::npos)
1292 name.erase(pos + 1, 1);
1293 pos = 0;
1294 while ((pos = name.find(" *", pos)) != std::string::npos)
1295 name.erase(pos, 1);
1296 pos = 0;
1297 while ((pos = name.find(" &", pos)) != std::string::npos)
1298 name.erase(pos, 1);
1299 return name;
1300}
1301
1302// class reflection information ----------------------------------------------
1304 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1305 return cppyy_normalize_name(
1306 Cpp::GetCompleteName(Cpp::GetUnderlyingScope(klass)));
1307}
1308
1310 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1311 return cppyy_normalize_name(Cpp::GetQualifiedCompleteName(klass));
1312}
1313
1315 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1316 TCppMethod_t func = Cpp::GetDestructor(scope);
1317 return Cpp::IsVirtualMethod(func);
1318}
1319
1321 // Get the total number of base classes that this class has.
1322 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1323 return Cpp::GetNumBases(klass);
1324}
1325
1326////////////////////////////////////////////////////////////////////////////////
1327/// \fn interop::TCppIndex_t interop::GetNumBasesLongestBranch(TCppScope_t
1328/// klass) \brief Retrieve number of base classes in the longest branch of the
1329/// inheritance tree of the input class.
1330/// \param[in] klass The class to start the retrieval process from.
1331///
1332/// This is a helper function for interop::GetNumBasesLongestBranch.
1333/// Given an inheritance tree, the function assigns weight 1 to each class that
1334/// has at least one base. Starting from the input class, the function is
1335/// called recursively on all the bases. For each base the return value is one
1336/// (the weight of the base itself) plus the maximum value retrieved for their
1337/// bases in turn. For example, given the following inheritance tree:
1338///
1339/// ~~~{.cpp}
1340/// class A {}; class B: public A {};
1341/// class X {}; class Y: public X {}; class Z: public Y {};
1342/// class C: public B, Z {};
1343/// ~~~
1344///
1345/// calling this function on an instance of `C` will return 3, the steps
1346/// required to go from C to X.
1348 std::vector<size_t> num;
1349 for (TCppIndex_t ibase = 0; ibase < GetNumBases(klass); ++ibase)
1350 num.push_back(
1352 if (num.empty())
1353 return 0;
1354 return *std::max_element(num.begin(), num.end()) + 1;
1355}
1356
1358 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1359 return Cpp::GetName(Cpp::GetBaseClass(klass, ibase));
1360}
1361
1363 TCppIndex_t ibase) {
1364 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1365 return Cpp::GetBaseClass(klass, ibase);
1366}
1367
1369 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1370 // Checked on every method call that receives 'self' as its first argument
1371 // (e.g. from pythonizations and protocol slots), so memoize per class
1372 // pair. A class that is still incomplete can gain bases once its
1373 // definition is loaded, so a negative answer is only cached for complete
1374 // classes.
1375 static std::map<std::pair<const void*, const void*>, bool> s_subclass_cache;
1376 const auto cacheKey = std::make_pair(derived.data, base.data);
1377 auto cached = s_subclass_cache.find(cacheKey);
1378 if (cached != s_subclass_cache.end())
1379 return cached->second;
1380 bool result = Cpp::IsSubclass(derived, base);
1381 if (result || (Cpp::IsComplete(derived) && Cpp::IsComplete(base)))
1382 s_subclass_cache.emplace(cacheKey, result);
1383 return result;
1384}
1385
1386static std::set<std::string> gSmartPtrTypes = {
1387 "std::auto_ptr", "std::shared_ptr", "std::unique_ptr", "std::weak_ptr"};
1388
1390 const std::string& rn = interop::GetScopedFinalName(klass);
1391 if (gSmartPtrTypes.find(rn.substr(0, rn.find("<"))) != gSmartPtrTypes.end())
1392 return true;
1393 return false;
1394}
1395
1396bool interop::GetSmartPtrInfo(const std::string& tname, TCppScope_t* raw,
1397 TCppMethod_t* deref) {
1398 // TODO: We can directly accept scope instead of name
1399 const std::string& rn = ResolveName(tname);
1400 if (gSmartPtrTypes.find(rn.substr(0, rn.find("<"))) == gSmartPtrTypes.end())
1401 return false;
1402
1403 if (!raw && !deref)
1404 return true;
1405
1406 TCppScope_t scope = interop::GetScope(rn);
1407 if (!scope)
1408 return false;
1409
1410 std::vector<TCppMethod_t> ops;
1411 {
1412 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1413 Cpp::GetOperator(scope, Cpp::Operator::OP_Arrow, ops,
1414 /*kind=*/Cpp::OperatorArity::kBoth);
1415 if (ops.size() != 1)
1416 return false;
1417
1418 // The dereference operator can be a member template: MSVC's
1419 // std::shared_ptr::operator-> is SFINAE-constrained on the element
1420 // type not being an array. Its return type is dependent, so it has
1421 // to be instantiated (it takes no arguments) before the pointee
1422 // type can be determined - and before it can be called.
1423 if (Cpp::IsTemplatedFunction(ops[0]))
1424 ops[0] = Cpp::BestOverloadFunctionMatch(ops, {}, {});
1425 if (!ops[0])
1426 return false;
1427 }
1428
1429 if (deref)
1430 *deref = ops[0];
1431 if (raw)
1433 return (!deref || *deref) && (!raw || *raw);
1434}
1435
1436// type offsets --------------------------------------------------------------
1438 TCppObject_t /*address*/, int direction,
1439 bool rerror) {
1440 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1441
1442 // Queried for every downcast object handed to Python; computing it walks
1443 // the inheritance paths, so memoize per class pair. The object address is
1444 // not used, so the offset is fixed per pair once both records are complete.
1445 static std::map<std::pair<const void*, const void*>, intptr_t>
1446 s_base_offset_cache;
1447 const auto cacheKey = std::make_pair(derived.data, base.data);
1448 auto cached = s_base_offset_cache.find(cacheKey);
1449 intptr_t offset = 0;
1450 if (cached != s_base_offset_cache.end()) {
1451 offset = cached->second;
1452 } else {
1453 // An incomplete record has no walkable bases (GetBaseClassOffset would
1454 // read an empty base path), and the answer may change once the
1455 // definition is loaded, so treat it like the error case below and retry
1456 // next time instead of caching.
1457 if (!Cpp::IsComplete(derived) || !Cpp::IsComplete(base))
1458 return rerror ? (ptrdiff_t)-1 : 0;
1459
1460 offset = Cpp::GetBaseClassOffset(derived, base);
1461
1462 if (offset == -1) // Cling error, treat silently
1463 return rerror ? (ptrdiff_t)offset : 0;
1464
1465 s_base_offset_cache.emplace(cacheKey, offset);
1466 }
1467
1468 return (ptrdiff_t)(direction < 0 ? -offset : offset);
1469}
1470
1471// method/function reflection information ------------------------------------
1472// A deleted overload is not callable, and leaving it in the set adds a
1473// spurious conversion error to every failed-call report.
1474static void
1475remove_deleted_methods(std::vector<interop::TCppMethod_t>& methods) {
1476 methods.erase(std::remove_if(methods.begin(), methods.end(),
1478 return Cpp::IsFunctionDeleted(m);
1479 }),
1480 methods.end());
1481}
1482
1484 std::vector<interop::TCppMethod_t>& methods) {
1485 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1486 Cpp::GetClassMethods(scope, methods);
1487 remove_deleted_methods(methods);
1488}
1489
1490std::vector<interop::TCppMethod_t>
1492 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1493 std::vector<interop::TCppMethod_t> methods =
1494 Cpp::GetFunctionsUsingName(scope, name);
1495 remove_deleted_methods(methods);
1496 return methods;
1497}
1498
1499std::string interop::GetName(TCppScope_t method) {
1500 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1501 return Cpp::GetName(method);
1502}
1503
1505 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1506 return cppyy_normalize_name(Cpp::GetCompleteName(method));
1507}
1508
1510 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1511 return Cpp::GetFunctionReturnType(method);
1512}
1513
1515 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1516 TCppType_t ret = Cpp::GetCanonicalType(Cpp::GetFunctionReturnType(method));
1517 // C++ deletes top-level cv-qualifiers on non-class return types from the
1518 // function type ([dcl.fct]); mirror that so name matching sees the plain
1519 // type, e.g. "unsigned long" for `static const size_t size()`. Class
1520 // types keep the qualifier: it stays part of the function type there
1521 // (e.g. for override matching in the dispatcher).
1522 if (ret && !Cpp::IsRecordType(ret))
1523 ret = Cpp::RemoveTypeQualifier(ret,
1524 Cpp::QualKind::Const | Cpp::QualKind::Volatile);
1525 return Cpp::GetTypeAsString(ret);
1526}
1527
1529 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1530 return Cpp::GetFunctionNumArgs(method);
1531}
1532
1534 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1535 return Cpp::GetFunctionRequiredArgs(method);
1536}
1537
1539 if (!method)
1540 return "<unknown>";
1541
1542 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1543 return Cpp::GetFunctionArgName(method, iarg);
1544}
1545
1547 TCppIndex_t iarg) {
1548 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1549 return Cpp::GetFunctionArgType(method, iarg);
1550}
1551
1553 TCppIndex_t iarg) {
1554 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1555 return Cpp::GetTypeAsString(Cpp::RemoveTypeQualifier(
1556 Cpp::GetFunctionArgType(method, iarg), Cpp::QualKind::Const));
1557}
1558
1560 TCppIndex_t iarg) {
1561 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1562 return Cpp::GetTypeAsString(
1563 Cpp::GetCanonicalType(Cpp::GetFunctionArgType(method, iarg)));
1564}
1565
1567 TCppIndex_t iarg) {
1568 if (!method)
1569 return "";
1570
1571 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1572 return Cpp::GetFunctionArgDefault(method, iarg);
1573}
1574
1577 const std::string& /*req_type*/) {
1578 // if (method) {
1579 // TFunction* f = m2f(method);
1580 // TMethodArg* arg = (TMethodArg
1581 // *)f->GetListOfMethodArgs()->At((int)iarg); void *argqtp =
1582 // gInterpreter->TypeInfo_QualTypePtr(arg->GetTypeInfo());
1583
1584 // TypeInfo_t *reqti = gInterpreter->TypeInfo_Factory(req_type.c_str());
1585 // void *reqqtp = gInterpreter->TypeInfo_QualTypePtr(reqti);
1586
1587 // if (ArgSimilarityScore(argqtp, reqqtp) < 10) {
1588 // return ArgSimilarityScore(argqtp, reqqtp);
1589 // }
1590 // else { // Match using underlying types
1591 // if(gInterpreter->IsPointerType(argqtp))
1592 // argqtp =
1593 // gInterpreter->TypeInfo_QualTypePtr(gInterpreter->GetPointerType(argqtp));
1594
1595 // // Handles reference types and strips qualifiers
1596 // TypeInfo_t *arg_ul = gInterpreter->GetNonReferenceType(argqtp);
1597 // TypeInfo_t *req_ul = gInterpreter->GetNonReferenceType(reqqtp);
1598 // argqtp =
1599 // gInterpreter->TypeInfo_QualTypePtr(gInterpreter->GetUnqualifiedType(gInterpreter->TypeInfo_QualTypePtr(arg_ul)));
1600 // reqqtp =
1601 // gInterpreter->TypeInfo_QualTypePtr(gInterpreter->GetUnqualifiedType(gInterpreter->TypeInfo_QualTypePtr(req_ul)));
1602
1603 // return ArgSimilarityScore(argqtp, reqqtp);
1604 // }
1605 // }
1606 return 0; // Method is not valid
1607}
1608
1610 bool show_formal_args,
1611 TCppIndex_t max_args) {
1612 std::ostringstream sig;
1613 sig << "(";
1614 int nArgs = GetMethodNumArgs(method);
1615 if (max_args != (TCppIndex_t)-1)
1616 nArgs = std::min(nArgs, (int)max_args);
1617 for (int iarg = 0; iarg < nArgs; ++iarg) {
1618 sig << cppyy_normalize_name(
1619 interop::GetMethodArgTypeAsString(method, iarg));
1620 if (show_formal_args) {
1621 std::string argname = interop::GetMethodArgName(method, iarg);
1622 if (!argname.empty())
1623 sig << " " << argname;
1624 std::string defvalue = interop::GetMethodArgDefault(method, iarg);
1625 if (!defvalue.empty())
1626 sig << " = " << defvalue;
1627 }
1628 if (iarg != nArgs - 1)
1629 sig << ", ";
1630 }
1631 sig << ")";
1632 return sig.str();
1633}
1634
1636 fn_type = Cpp::IsReferenceType(fn_type) ? Cpp::GetNonReferenceType(fn_type)
1637 : fn_type;
1638 fn_type =
1639 Cpp::IsPointerType(fn_type) ? Cpp::GetPointeeType(fn_type) : fn_type;
1640 TCppScope_t scope = Cpp::GetScopeFromType(fn_type);
1641 std::vector<Cpp::TemplateArgInfo> args;
1642 Cpp::GetClassTemplateArgs(scope, args);
1643 assert(args.size() == 1);
1644 if (args.size() == 1)
1645 return args[0].m_Type;
1646 return nullptr;
1647}
1648
1650 std::vector<TCppType_t>& arg_types) {
1651 fn_type = Cpp::IsReferenceType(fn_type) ? Cpp::GetNonReferenceType(fn_type)
1652 : fn_type;
1653 fn_type =
1654 Cpp::IsPointerType(fn_type) ? Cpp::GetPointeeType(fn_type) : fn_type;
1655 Cpp::GetFnTypeSignature(fn_type, arg_types);
1656}
1657
1659 return Cpp::IsSameType(typ1, typ2);
1660}
1661
1663 typ = Cpp::IsReferenceType(typ) ? Cpp::GetNonReferenceType(typ) : typ;
1664 typ = Cpp::IsPointerType(typ) ? Cpp::GetPointeeType(typ) : typ;
1665 return Cpp::IsFunctionProtoType(typ);
1666}
1667
1669 typ1 = Cpp::IsReferenceType(typ1) ? Cpp::GetNonReferenceType(typ1) : typ1;
1670 typ2 = Cpp::IsReferenceType(typ2) ? Cpp::GetNonReferenceType(typ2) : typ2;
1671 typ1 = Cpp::IsPointerType(typ1) ? Cpp::GetPointeeType(typ1) : typ1;
1672 typ2 = Cpp::IsPointerType(typ2) ? Cpp::GetPointeeType(typ2) : typ2;
1673 return Cpp::IsSameType(typ1, typ2);
1674}
1675
1676std::string interop::GetDoxygenComment(TCppScope_t scope, bool strip_markers) {
1677 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1678 return Cpp::GetDoxygenComment(scope, strip_markers);
1679}
1680
1682 if (!method)
1683 return false;
1684 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1685 return Cpp::IsConstMethod(method);
1686}
1687
1689 std::vector<interop::TCppMethod_t>& methods) {
1690 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1691 Cpp::GetFunctionTemplatedDecls(scope, methods);
1692}
1693
1695interop::GetNumTemplatedMethods(TCppScope_t scope, bool /*accept_namespace*/) {
1696 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1697 std::vector<interop::TCppMethod_t> mc;
1698 Cpp::GetFunctionTemplatedDecls(scope, mc);
1699 return mc.size();
1700}
1701
1703 TCppIndex_t imeth) {
1704 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1705 std::vector<interop::TCppMethod_t> mc;
1706 Cpp::GetFunctionTemplatedDecls(scope, mc);
1707
1708 if (imeth < mc.size())
1709 return Cpp::GetName(TCppScope_t(mc[imeth].data));
1710
1711 return "";
1712}
1713
1714bool interop::ExistsMethodTemplate(TCppScope_t scope, const std::string& name) {
1715 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1716 return Cpp::ExistsFunctionTemplate(name, scope);
1717}
1718
1720 return Cpp::IsTemplatedFunction(method);
1721}
1722
1723bool interop::IsStaticTemplate(TCppScope_t scope, const std::string& name) {
1724 std::vector<TCppMethod_t> candidate_methods;
1725 Cpp::GetClassTemplatedMethods(name, scope, candidate_methods);
1726 bool is_static = true;
1727 for (auto i : candidate_methods) {
1728 if (!Cpp::IsStaticMethod(i)) {
1729 is_static = false;
1730 break;
1731 }
1732 }
1733 return is_static;
1734}
1735
1737 const std::string& name,
1738 const std::string& proto) {
1739 std::string pureName;
1740 std::string explicit_params;
1741
1742 if ((name.find("operator<") != 0) && (name.find('<') != std::string::npos)) {
1743 pureName = name.substr(0, name.find('<'));
1744 size_t start = name.find('<');
1745 size_t end = name.rfind('>');
1746 explicit_params = name.substr(start + 1, end - start - 1);
1747 } else {
1748 pureName = name;
1749 }
1750
1751 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1752
1753 std::vector<interop::TCppMethod_t> unresolved_candidate_methods;
1754 Cpp::GetClassTemplatedMethods(pureName, scope, unresolved_candidate_methods);
1755 if (unresolved_candidate_methods.empty() && name.find("operator") == 0) {
1756 // try operators
1757 interop::GetClassOperators(scope, pureName, unresolved_candidate_methods);
1758 }
1759
1760 // cpyrt assumes that we attempt instantiation here
1761 std::vector<Cpp::TemplateArgInfo> arg_types;
1762 std::vector<Cpp::TemplateArgInfo> templ_params;
1763 interop::AppendTypesSlow(proto, arg_types, scope);
1764 interop::AppendTypesSlow(explicit_params, templ_params, scope);
1765 interop::TCppMethod_t cppmeth = nullptr;
1766 cppmeth = Cpp::BestOverloadFunctionMatch(unresolved_candidate_methods,
1767 templ_params, arg_types);
1768
1769 // If overload resolution failed but explicit template arguments were
1770 // supplied, fall back to direct template-argument substitution: ask Sema
1771 // to instantiate each candidate with the explicit args. Sema's SFINAE
1772 // rejects overloads whose substitution fails (e.g. the initializer_list
1773 // form of std::make_any with non-init-list explicit args), so iterating
1774 // gives back exactly the viable specialisation. The wrapper-side argument
1775 // conversion then handles e.g. taking the address of an instance when the
1776 // substituted parameter is a pointer.
1777 if (!cppmeth && !templ_params.empty()) {
1778 for (const auto& cand : unresolved_candidate_methods) {
1779 if (Cpp::DeclRef spec = Cpp::InstantiateTemplate(
1780 TCppScope_t(cand.data), templ_params.data(), templ_params.size(),
1781 /*instantiate_body=*/false)) {
1782 cppmeth = spec.data;
1783 break;
1784 }
1785 }
1786 }
1787
1788 return TCppMethod_t(cppmeth.data);
1789 // if it fails, use Sema to propogate info about why it failed (DeductionInfo)
1790}
1791
1792static inline bool is_basic_string_of(const std::string& n, const char* charT) {
1793 // Match "std::basic_string<charT" whether or not the default template
1794 // arguments (char_traits, allocator) are spelled out; the character type
1795 // must be followed by ',' or '>' so that e.g. "char" does not match
1796 // "char16_t".
1797 std::string prefix = "std::basic_string<";
1798 prefix += charT;
1799 if (n.compare(0, prefix.size(), prefix) != 0)
1800 return false;
1801 return n.size() > prefix.size() &&
1802 (n[prefix.size()] == ',' || n[prefix.size()] == '>');
1803}
1804
1805static inline std::string type_remap(const std::string& n1,
1806 const std::string& n2) {
1807 // Operator lookups of (C++ string, Python str) should succeed for the
1808 // combos of string/str, wstring/str, string/unicode and wstring/unicode;
1809 // since C++ does not have a operator+(std::string, std::wstring), we'll
1810 // have to look up the same type and rely on the converters in
1811 // cpyrt/_cppjit.
1812 if (n1 == "str" || n1 == "unicode" || is_basic_string_of(n1, "char")) {
1813 if (is_basic_string_of(n2, "wchar_t"))
1814 return "std::basic_string<wchar_t>&"; // match like for like
1815 return "std::basic_string<char>&"; // probably best bet
1816 } else if (is_basic_string_of(n1, "wchar_t")) {
1817 return "std::basic_string<wchar_t>&";
1818 } else if (n1 == "complex") {
1819 return "std::complex<double>";
1820 }
1821 return n1;
1822}
1823
1825 const std::string& opname,
1826 std::vector<TCppMethod_t>& operators) {
1827 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1828 std::string op = opname.substr(8);
1829 Cpp::GetOperator(klass, Cpp::GetOperatorFromSpelling(op), operators,
1830 /*kind=*/Cpp::OperatorArity::kBoth);
1831}
1832
1834 const std::string& lc,
1835 const std::string& rc,
1836 const std::string& opname) {
1837 std::string rc_type = type_remap(rc, lc);
1838 std::string lc_type = type_remap(lc, rc);
1839
1840 std::vector<TCppMethod_t> overloads;
1841 Cpp::GetOperator(scope, Cpp::GetOperatorFromSpelling(opname), overloads,
1842 /*kind=*/Cpp::OperatorArity::kBoth);
1843
1844 // Avoid pushing nullptr into arg_types which would crash
1845 // BestOverloadFunctionMatch when it dereferences each entry's QualType.
1846 auto resolve_arg_type = [](const std::string& name) -> interop::TCppType_t {
1847 if (auto s = interop::GetScope(name))
1848 if (auto t = interop::GetTypeFromScope(s))
1850 return interop::GetType(name, /*enable_slow_lookup=*/true);
1851 };
1852
1853 std::vector<Cpp::TemplateArgInfo> arg_types;
1854 if (auto l = resolve_arg_type(lc_type))
1855 arg_types.emplace_back(l.data);
1856 else
1857 return nullptr;
1858
1859 if (!rc_type.empty()) {
1860 if (auto r = resolve_arg_type(rc_type))
1861 arg_types.emplace_back(r.data);
1862 else
1863 return nullptr;
1864 }
1865 interop::TCppMethod_t cppmeth =
1866 Cpp::BestOverloadFunctionMatch(overloads, {}, arg_types);
1867 if (cppmeth)
1868 return cppmeth;
1869 return nullptr;
1870}
1871
1872// method properties ---------------------------------------------------------
1874 return Cpp::IsFunctionDeleted(method);
1875}
1876
1878 return Cpp::IsPublicMethod(method);
1879}
1880
1882 return Cpp::IsProtectedMethod(method);
1883}
1884
1886 return Cpp::IsPrivateMethod(method);
1887}
1888
1890 return Cpp::IsConstructor(method);
1891}
1892
1894 return Cpp::IsDestructor(method);
1895}
1896
1898 return Cpp::IsStaticMethod(method);
1899}
1900
1902 return Cpp::IsExplicit(method);
1903}
1904
1905// data member reflection information ----------------------------------------
1907 std::vector<TCppScope_t>& datamembers) {
1908 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1909 Cpp::GetDatamembers(scope, datamembers);
1910 Cpp::GetStaticDatamembers(scope, datamembers);
1911 Cpp::GetEnumConstantDatamembers(scope, datamembers, false);
1912}
1913
1914bool interop::CheckDatamember(TCppScope_t scope, const std::string& name) {
1915 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1916 return (bool)Cpp::LookupDatamember(name, scope);
1917}
1918
1920 return Cpp::IsLambdaClass(type);
1921}
1922
1924 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1925 std::ostringstream code;
1926 std::string name = interop::GetFinalName(var);
1927 code << "namespace __cppjit_internal_wrap_g {\n"
1928 << " " << "std::function " << name
1929 << " = ::" << Cpp::GetQualifiedName(var) << ";\n"
1930 << "}\n";
1931
1932 if (interop::Compile(code.str().c_str())) {
1933 TCppScope_t res = Cpp::GetNamed(
1934 name, Cpp::GetScope("__cppjit_internal_wrap_g", /*parent=*/nullptr));
1935 if (res)
1936 return res;
1937 }
1938 return var;
1939}
1940
1943 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1944
1945 std::string fn_name = Cpp::GetQualifiedCompleteName(TCppScope_t(fn.data));
1946 std::string signature = interop::GetMethodSignature(fn, true);
1947
1948 std::ostringstream call;
1949 call << "(";
1950 for (size_t i = 0, n = interop::GetMethodNumArgs(fn); i < n; i++) {
1951 call << interop::GetMethodArgName(fn, i);
1952 if (i != n - 1)
1953 call << ", ";
1954 }
1955 call << ")";
1956
1957 std::ostringstream code;
1958 static int i = 0;
1959 std::string name = "lambda_return_convert_" + std::to_string(++i);
1960 code << "namespace __cppjit_internal_wrap_g {\n"
1961 << "auto " << name << signature << "{" << "return std::function("
1962 << fn_name << call.str() << "); }\n"
1963 << "}\n";
1964 if (interop::Compile(code.str().c_str())) {
1965 TCppScope_t res = Cpp::GetNamed(
1966 name, Cpp::GetScope("__cppjit_internal_wrap_g", /*parent=*/nullptr));
1967 if (res)
1968 return TCppMethod_t(res.data);
1969 }
1970 return fn;
1971}
1972
1974 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1975 return Cpp::GetVariableType(Cpp::GetUnderlyingScope(var));
1976}
1977
1979 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1980 return Cpp::GetTypeAsString(
1981 Cpp::GetVariableType(Cpp::GetUnderlyingScope(scope)));
1982}
1983
1985 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1986 return Cpp::GetTypeAsString(type);
1987}
1988
1990 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
1991 return Cpp::GetVariableOffset(Cpp::GetUnderlyingScope(var), klass);
1992}
1993
1994// data member properties ----------------------------------------------------
1996 return Cpp::IsPublicVariable(datamem);
1997}
1998
2000 return Cpp::IsProtectedVariable(datamem);
2001}
2002
2004 return Cpp::IsPrivateVariable(datamem);
2005}
2006
2008 return Cpp::IsStaticVariable(interop::GetUnderlyingScope(var));
2009}
2010
2011bool interop::IsConstVar(TCppScope_t var) { return Cpp::IsConstVariable(var); }
2012
2014 TCppType_t reduce) {
2015 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
2016
2017 std::string fn_name = Cpp::GetQualifiedCompleteName(TCppScope_t(fn.data));
2018 std::string signature = interop::GetMethodSignature(fn, true);
2019 std::string result_type = interop::GetTypeAsString(reduce);
2020
2021 std::ostringstream call;
2022 call << "(";
2023 for (size_t i = 0, n = interop::GetMethodNumArgs(fn); i < n; i++) {
2024 call << interop::GetMethodArgName(fn, i);
2025 if (i != n - 1)
2026 call << ", ";
2027 }
2028 call << ")";
2029
2030 std::ostringstream code;
2031 static int i = 0;
2032 std::string name = "reduced_function_" + std::to_string(++i);
2033 code << "namespace __cppjit_internal_wrap_g {\n"
2034 << result_type << " " << name << signature << "{" << "return ("
2035 << result_type << ")::" << fn_name << call.str() << "; }\n"
2036 << "}\n";
2037 if (interop::Compile(code.str().c_str())) {
2038 TCppScope_t res = Cpp::GetNamed(
2039 name, Cpp::GetScope("__cppjit_internal_wrap_g", /*parent=*/nullptr));
2040 if (res)
2041 return TCppMethod_t(res.data);
2042 }
2043 return fn;
2044}
2045
2047 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
2048 return Cpp::GetDimensions(type);
2049}
2050
2051// enum properties -----------------------------------------------------------
2052std::vector<interop::TCppScope_t> interop::GetEnumConstants(TCppScope_t scope) {
2053 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
2054 return Cpp::GetEnumConstants(scope);
2055}
2056
2058 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
2059 return Cpp::GetEnumConstantType(Cpp::GetUnderlyingScope(scope));
2060}
2061
2063 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
2064 return Cpp::GetEnumConstantValue(scope);
2065}
2066
2068 Cpp::TemplateArgInfo* args,
2069 size_t args_size) {
2070 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
2071 return Cpp::InstantiateTemplate(tmpl, args, args_size,
2072 /*instantiate_body=*/false);
2073}
2074
2076 std::lock_guard<RInterOpMutex> Lock(InterOpMutex);
2077 Cpp::DumpScope(scope);
2078}
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