From 7983d5f4ebed1758bb97e0d45bddfb38531561b8 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Mon, 20 Apr 2026 10:56:28 +0200 Subject: [PATCH 1/3] Upgrade CppInterOp to master --- interpreter/CppInterOp/.clang-tidy | 2 + interpreter/CppInterOp/cppinterop-version.tag | 2 +- .../include/CppInterOp/CppInterOp.h | 3 +- .../CppInterOp/lib/CppInterOp/CMakeLists.txt | 1 + .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 1168 +++++++++++------ .../lib/CppInterOp/CppInterOpInterpreter.h | 21 +- interpreter/CppInterOp/lib/CppInterOp/Sins.h | 28 + .../CppInterOp/lib/CppInterOp/Tracing.cpp | 125 ++ .../CppInterOp/lib/CppInterOp/Tracing.h | 449 +++++++ .../CppInterOp/lib/CppInterOp/exports.ld | 1 + .../CppInterOp/unittests/CMakeLists.txt | 7 + .../unittests/CppInterOp/CMakeLists.txt | 18 + .../unittests/CppInterOp/InterpreterTest.cpp | 101 +- .../CppInterOp/ScopeReflectionTest.cpp | 115 +- .../unittests/CppInterOp/TracingTests.cpp | 923 +++++++++++++ 15 files changed, 2551 insertions(+), 413 deletions(-) create mode 100644 interpreter/CppInterOp/lib/CppInterOp/Sins.h create mode 100644 interpreter/CppInterOp/lib/CppInterOp/Tracing.cpp create mode 100644 interpreter/CppInterOp/lib/CppInterOp/Tracing.h create mode 100644 interpreter/CppInterOp/unittests/CppInterOp/TracingTests.cpp diff --git a/interpreter/CppInterOp/.clang-tidy b/interpreter/CppInterOp/.clang-tidy index 0491d89f23ec8..824f5b5d2c144 100644 --- a/interpreter/CppInterOp/.clang-tidy +++ b/interpreter/CppInterOp/.clang-tidy @@ -26,6 +26,8 @@ Checks: > -readability-implicit-bool-conversion, -readability-magic-numbers, -readability-named-parameter, + -modernize-concat-nested-namespaces, + -readability-avoid-return-with-void-value, -readability-function-cognitive-complexity, -readability-redundant-access-specifiers, -cppcoreguidelines-avoid-magic-numbers, diff --git a/interpreter/CppInterOp/cppinterop-version.tag b/interpreter/CppInterOp/cppinterop-version.tag index 522df1a6f38f5..d6dd1456372a4 100644 --- a/interpreter/CppInterOp/cppinterop-version.tag +++ b/interpreter/CppInterOp/cppinterop-version.tag @@ -1 +1 @@ -039bf0423284b1139674525d097e656cb0feb783 \ No newline at end of file +b8ae9f666cde92bc0d848f6bf357c162980b7d7e \ No newline at end of file diff --git a/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h b/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h index 0fdcc609a1172..dcb9ff7b3ebd4 100644 --- a/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h +++ b/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h @@ -38,6 +38,7 @@ using TCppIndex_t = size_t; using TCppScope_t = void*; using TCppConstScope_t = const void*; using TCppType_t = void*; +using TCppConstType_t = const void*; using TCppFunction_t = void*; using TCppConstFunction_t = const void*; using TCppFuncAddr_t = void*; @@ -368,7 +369,7 @@ CPPINTEROP_API bool IsComplete(TCppScope_t scope); CPPINTEROP_API size_t SizeOf(TCppScope_t scope); /// Checks if it is a "built-in" or a "complex" type. -CPPINTEROP_API bool IsBuiltin(TCppType_t type); +CPPINTEROP_API bool IsBuiltin(TCppConstType_t type); /// Checks if it is a templated class. CPPINTEROP_API bool IsTemplate(TCppScope_t handle); diff --git a/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt b/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt index 4d7a2c4241c39..0c426d645fedd 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt +++ b/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt @@ -109,6 +109,7 @@ add_llvm_library(clangCppInterOp BUILDTREE_ONLY CppInterOp.cpp Dispatch.cpp CXCppInterOp.cpp + Tracing.cpp ${DLM} LINK_LIBS ${link_libs} diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index 9c6f00432765a..7c8fd83b9ddd8 100755 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -10,6 +10,8 @@ #include "CppInterOp/CppInterOp.h" #include "Compatibility.h" +#include "Sins.h" // for access to private members +#include "Tracing.h" #include "clang/AST/Attrs.inc" #include "clang/AST/CXXInheritance.h" @@ -50,6 +52,7 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/Demangle/Demangle.h" +#include "llvm/ExecutionEngine/JITSymbol.h" #if CLANG_VERSION_MAJOR >= 20 #include "llvm/ExecutionEngine/Orc/AbsoluteSymbols.h" #include "llvm/ExecutionEngine/Orc/CoreContainers.h" @@ -64,6 +67,9 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/TargetSelect.h" #include "llvm/Support/raw_ostream.h" #include "llvm/TargetParser/Host.h" #include "llvm/TargetParser/Triple.h" @@ -73,11 +79,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -128,6 +136,13 @@ using namespace llvm; struct InterpreterInfo { compat::Interpreter* Interpreter = nullptr; bool isOwned = true; + // Store the list of builtin types. + llvm::StringMap BuiltinMap; + // Per-interpreter wrapper caches. Keyed on AST nodes that belong to this + // interpreter, so the caches must be destroyed together with it. + std::map WrapperStore; + std::map DtorWrapperStore; + InterpreterInfo(compat::Interpreter* I, bool Owned) : Interpreter(I), isOwned(Owned) {} @@ -162,15 +177,156 @@ struct InterpreterInfo { InterpreterInfo& operator=(const InterpreterInfo&) = delete; }; -// std::deque avoids relocations and calling the dtor of InterpreterInfo. -static llvm::ManagedStatic> sInterpreters; +static void DefaultProcessCrashHandler(void*); +// Function-static storage for interpreters +static std::deque& GetInterpreters() { + // static int FakeArgc = 1; + // static const std::string VersionStr = GetVersion(); + // static const char* ArgvBuffer[] = {VersionStr.c_str(), nullptr}; + // static const char** FakeArgv = ArgvBuffer; + // static llvm::InitLLVM X(FakeArgc, FakeArgv); + // Cannot be a llvm::ManagedStatic because X will call shutdown which will + // trigger destruction on llvm::ManagedStatics and the destruction of the + // InterpreterInfos require to have llvm around. + // FIXME: Currently we never call llvm::llvm_shutdown and sInterpreters leaks. + static llvm::ManagedStatic> sInterpreters; + static std::once_flag ProcessInitialized; + std::call_once(ProcessInitialized, []() { + llvm::sys::PrintStackTraceOnErrorSignal("CppInterOp"); + + if (getenv("CPPINTEROP_LOG") != nullptr) + CppInterOp::Tracing::InitTracing(); + + // Initialize all targets (required for device offloading) + llvm::InitializeAllTargetInfos(); + llvm::InitializeAllTargets(); + llvm::InitializeAllTargetMCs(); + llvm::InitializeAllAsmParsers(); + llvm::InitializeAllAsmPrinters(); + + llvm::sys::AddSignalHandler(DefaultProcessCrashHandler, /*Cookie=*/nullptr); + + // std::atexit(llvm::llvm_shutdown); + }); + + return *sInterpreters; +} + +// Global crash handler for the entire process +static void DefaultProcessCrashHandler(void*) { + // Access the static deque via the getter + std::deque& Interps = GetInterpreters(); + + llvm::errs() << "\n**************************************************\n"; + llvm::errs() << " CppInterOp CRASH DETECTED\n"; + if (CppInterOp::Tracing::TraceInfo::TheTraceInfo) { + std::string Path = + CppInterOp::Tracing::TraceInfo::TheTraceInfo->writeToFile(); + if (!Path.empty()) + llvm::errs() << " Reproducer saved to: " << Path << "\n"; + else + llvm::errs() << " Failed to write reproducer file.\n"; + } else { + llvm::errs() << " Re-run with CPPINTEROP_LOG=1 for a crash reproducer\n"; + } + + if (!Interps.empty()) { + llvm::errs() << " Active Interpreters:\n"; + for (const auto& Info : Interps) { + if (Info.Interpreter) + llvm::errs() << " - " << Info.Interpreter << "\n"; + } + } + + llvm::errs() << "**************************************************\n"; + llvm::errs().flush(); + + // Print backtrace (includes JIT symbols if registered) + llvm::sys::PrintStackTrace(llvm::errs()); + + llvm::errs() << "**************************************************\n"; + llvm::errs().flush(); + + // The process must actually terminate for EXPECT_DEATH to pass. + // We use _exit to avoid calling atexit() handlers which might be corrupted. + llvm::sys::Process::Exit(/*RetCode=*/1, /*NoCleanup=*/false); +} + +static void RegisterInterpreter(compat::Interpreter* I, bool Owned) { + std::deque& Interps = GetInterpreters(); + Interps.emplace_back(I, Owned); +} + +static InterpreterInfo& getInterpInfo(compat::Interpreter* I = nullptr) { + auto& Interps = GetInterpreters(); + assert(!Interps.empty() && + "Interpreter instance must be set before calling this!"); + if (I) { + for (auto& Info : Interps) + if (Info.Interpreter == I) + return Info; + } + return Interps.back(); +} static compat::Interpreter& getInterp(TInterp_t I = nullptr) { if (I) return *static_cast(I); - assert(!sInterpreters->empty() && - "Interpreter instance must be set before calling this!"); - return *sInterpreters->back().Interpreter; + return *getInterpInfo().Interpreter; +} + +TInterp_t GetInterpreter() { + INTEROP_TRACE(); + std::deque& Interps = GetInterpreters(); + if (Interps.empty()) + return INTEROP_RETURN(nullptr); + return INTEROP_RETURN(Interps.back().Interpreter); +} + +void UseExternalInterpreter(TInterp_t I) { + INTEROP_TRACE(I); + assert(GetInterpreters().empty() && "sInterpreter already in use!"); + RegisterInterpreter(static_cast(I), /*Owned=*/false); + return INTEROP_VOID_RETURN(); +} + +bool ActivateInterpreter(TInterp_t I) { + INTEROP_TRACE(I); + if (!I) + return INTEROP_RETURN(false); + + std::deque& Interps = GetInterpreters(); + auto found = + std::find_if(Interps.begin(), Interps.end(), + [&I](const auto& Info) { return Info.Interpreter == I; }); + if (found == Interps.end()) + return INTEROP_RETURN(false); + + if (std::next(found) != Interps.end()) // if not already last element. + std::rotate(found, found + 1, Interps.end()); + + return INTEROP_RETURN(true); // success +} + +bool DeleteInterpreter(TInterp_t I /*=nullptr*/) { + INTEROP_TRACE(I); + std::deque& Interps = GetInterpreters(); + if (Interps.empty()) + return INTEROP_RETURN(false); + + if (!I) { + Interps.pop_back(); // Triggers ~InterpreterInfo() and potential delete + return INTEROP_RETURN(true); + } + + auto found = + std::find_if(Interps.begin(), Interps.end(), + [&I](const auto& Info) { return Info.Interpreter == I; }); + if (found == Interps.end()) + return INTEROP_RETURN(false); // failure + + Interps.erase(found); + return INTEROP_RETURN(true); } static clang::Sema& getSema() { return getInterp().getCI()->getSema(); } @@ -258,29 +414,47 @@ bool JitCall::AreArgumentsValid(void* result, ArgList args, void* self, void JitCall::ReportInvokeStart(void* result, ArgList args, void* self) const { std::string Name; llvm::raw_string_ostream OS(Name); - auto FD = (const FunctionDecl*)m_FD; + auto* FD = (const FunctionDecl*)m_FD; FD->getNameForDiagnostic(OS, FD->getASTContext().getPrintingPolicy(), /*Qualified=*/true); LLVM_DEBUG(dbgs() << "Run '" << Name << "', compiled at: " << (void*)m_GenericCall << " with result at: " << result << " , args at: " << args.m_Args << " , arg count: " << args.m_ArgSize << " , self at: " << self << "\n";); + + if (auto* TI = CppInterOp::Tracing::TraceInfo::TheTraceInfo) { + std::string SelfPart = self ? TI->lookupHandle(self) : ""; + std::string Entry = + llvm::formatv(" // JitCall::Invoke {0}(nargs={1}, self={2})", Name, + args.m_ArgSize, SelfPart.empty() ? "nullptr" : SelfPart); + TI->appendToLog(Entry); + } } void JitCall::ReportInvokeStart(void* object, unsigned long nary, int withFree) const { std::string Name; llvm::raw_string_ostream OS(Name); - auto FD = (const FunctionDecl*)m_FD; + auto* FD = (const FunctionDecl*)m_FD; FD->getNameForDiagnostic(OS, FD->getASTContext().getPrintingPolicy(), /*Qualified=*/true); LLVM_DEBUG(dbgs() << "Finish '" << Name << "', compiled at: " << (void*)m_DestructorCall); + + if (auto* TI = CppInterOp::Tracing::TraceInfo::TheTraceInfo) { + std::string ObjPart = object ? TI->lookupHandle(object) : "nullptr"; + std::string Entry = llvm::formatv( + " // JitCall::InvokeDestructor {0}(object={1}, nary={2}, " + "withFree={3})", + Name, ObjPart, nary, withFree); + TI->appendToLog(Entry); + } } #undef DEBUG_TYPE std::string GetVersion() { + INTEROP_TRACE(); const char* const VERSION = CPPINTEROP_VERSION; std::string fullVersion = "CppInterOp version"; fullVersion += VERSION; @@ -290,21 +464,30 @@ std::string GetVersion() { #else "clang-repl"; #endif // CPPINTEROP_USE_CLING - return fullVersion + "[" + clang::getClangFullVersion() + "])\n"; + return INTEROP_RETURN(fullVersion + "[" + clang::getClangFullVersion() + + "])\n"); } std::string Demangle(const std::string& mangled_name) { + INTEROP_TRACE(mangled_name); #ifdef _WIN32 std::string demangle = microsoftDemangle(mangled_name, nullptr, nullptr); #else std::string demangle = itaniumDemangle(mangled_name); #endif - return demangle; + return INTEROP_RETURN(demangle); } -void EnableDebugOutput(bool value /* =true*/) { llvm::DebugFlag = value; } +void EnableDebugOutput(bool value /* =true*/) { + INTEROP_TRACE(value); + llvm::DebugFlag = value; + return INTEROP_VOID_RETURN(); +} -bool IsDebugOutputEnabled() { return llvm::DebugFlag; } +bool IsDebugOutputEnabled() { + INTEROP_TRACE(); + return INTEROP_RETURN(llvm::DebugFlag); +} static void InstantiateFunctionDefinition(Decl* D) { compat::SynthesizingCodeRAII RAII(&getInterp()); @@ -323,46 +506,52 @@ static void InstantiateFunctionDefinition(Decl* D) { } bool IsAggregate(TCppScope_t scope) { + INTEROP_TRACE(scope); Decl* D = static_cast(scope); // Aggregates are only arrays or tag decls. if (ValueDecl* ValD = dyn_cast(D)) if (ValD->getType()->isArrayType()) - return true; + return INTEROP_RETURN(true); // struct, class, union if (CXXRecordDecl* CXXRD = dyn_cast(D)) - return CXXRD->isAggregate(); + return INTEROP_RETURN(CXXRD->isAggregate()); - return false; + return INTEROP_RETURN(false); } bool IsNamespace(TCppScope_t scope) { + INTEROP_TRACE(scope); Decl* D = static_cast(scope); - return isa(D); + return INTEROP_RETURN(isa(D)); } bool IsClass(TCppScope_t scope) { + INTEROP_TRACE(scope); Decl* D = static_cast(scope); - return isa(D); + return INTEROP_RETURN(isa(D)); } bool IsFunction(TCppScope_t scope) { + INTEROP_TRACE(scope); Decl* D = static_cast(scope); - return isa(D); + return INTEROP_RETURN(isa(D)); } bool IsFunctionPointerType(TCppType_t type) { + INTEROP_TRACE(type); QualType QT = QualType::getFromOpaquePtr(type); - return QT->isFunctionPointerType(); + return INTEROP_RETURN(QT->isFunctionPointerType()); } bool IsClassPolymorphic(TCppScope_t klass) { + INTEROP_TRACE(klass); Decl* D = static_cast(klass); if (auto* CXXRD = llvm::dyn_cast(D)) if (auto* CXXRDD = CXXRD->getDefinition()) - return CXXRDD->isPolymorphic(); - return false; + return INTEROP_RETURN(CXXRDD->isPolymorphic()); + return INTEROP_RETURN(false); } static SourceLocation GetValidSLoc(Sema& semaRef) { @@ -372,8 +561,9 @@ static SourceLocation GetValidSLoc(Sema& semaRef) { // See TClingClassInfo::IsLoaded bool IsComplete(TCppScope_t scope) { + INTEROP_TRACE(scope); if (!scope) - return false; + return INTEROP_RETURN(false); Decl* D = static_cast(scope); @@ -382,84 +572,94 @@ bool IsComplete(TCppScope_t scope) { clang::Sema& S = getSema(); SourceLocation fakeLoc = GetValidSLoc(S); compat::SynthesizingCodeRAII RAII(&getInterp()); - return S.isCompleteType(fakeLoc, QT); + return INTEROP_RETURN(S.isCompleteType(fakeLoc, QT)); } if (auto* CXXRD = dyn_cast(D)) - return CXXRD->hasDefinition(); + return INTEROP_RETURN(CXXRD->hasDefinition()); else if (auto* TD = dyn_cast(D)) - return TD->getDefinition(); + return INTEROP_RETURN(TD->getDefinition()); // Everything else is considered complete. - return true; + return INTEROP_RETURN(true); } size_t SizeOf(TCppScope_t scope) { + INTEROP_TRACE(scope); assert(scope); if (!IsComplete(scope)) - return 0; + return INTEROP_RETURN(0); if (auto* RD = dyn_cast(static_cast(scope))) { ASTContext& Context = RD->getASTContext(); const ASTRecordLayout& Layout = Context.getASTRecordLayout(RD); - return Layout.getSize().getQuantity(); + return INTEROP_RETURN(Layout.getSize().getQuantity()); } - return 0; + return INTEROP_RETURN(0); } -bool IsBuiltin(TCppType_t type) { +bool IsBuiltin(TCppConstType_t type) { + INTEROP_TRACE(type); QualType Ty = QualType::getFromOpaquePtr(type); if (Ty->isBuiltinType() || Ty->isAnyComplexType()) - return true; + return INTEROP_RETURN(true); // Check for std::complex specializations. if (const auto* RD = Ty->getAsCXXRecordDecl()) { if (const auto* CTSD = dyn_cast(RD)) { IdentifierInfo* II = CTSD->getSpecializedTemplate()->getIdentifier(); if (II && II->isStr("complex") && CTSD->getDeclContext()->isStdNamespace()) - return true; + return INTEROP_RETURN(true); } } - return false; + return INTEROP_RETURN(false); } bool IsTemplate(TCppScope_t handle) { + INTEROP_TRACE(handle); auto* D = (clang::Decl*)handle; - return llvm::isa_and_nonnull(D); + return INTEROP_RETURN(llvm::isa_and_nonnull(D)); } bool IsTemplateSpecialization(TCppScope_t handle) { + INTEROP_TRACE(handle); auto* D = (clang::Decl*)handle; - return llvm::isa_and_nonnull(D); + return INTEROP_RETURN( + llvm::isa_and_nonnull(D)); } bool IsTypedefed(TCppScope_t handle) { + INTEROP_TRACE(handle); auto* D = (clang::Decl*)handle; - return llvm::isa_and_nonnull(D); + return INTEROP_RETURN(llvm::isa_and_nonnull(D)); } bool IsAbstract(TCppType_t klass) { + INTEROP_TRACE(klass); auto* D = (clang::Decl*)klass; if (auto* CXXRD = llvm::dyn_cast_or_null(D)) - return CXXRD->isAbstract(); + return INTEROP_RETURN(CXXRD->isAbstract()); - return false; + return INTEROP_RETURN(false); } bool IsEnumScope(TCppScope_t handle) { + INTEROP_TRACE(handle); auto* D = (clang::Decl*)handle; - return llvm::isa_and_nonnull(D); + return INTEROP_RETURN(llvm::isa_and_nonnull(D)); } bool IsEnumConstant(TCppScope_t handle) { + INTEROP_TRACE(handle); auto* D = (clang::Decl*)handle; - return llvm::isa_and_nonnull(D); + return INTEROP_RETURN(llvm::isa_and_nonnull(D)); } bool IsEnumType(TCppType_t type) { + INTEROP_TRACE(type); QualType QT = QualType::getFromOpaquePtr(type); - return QT->isEnumeralType(); + return INTEROP_RETURN(QT->isEnumeralType()); } static bool isSmartPointer(const RecordType* RT) { @@ -505,6 +705,7 @@ static bool isSmartPointer(const RecordType* RT) { } bool IsSmartPtrType(TCppType_t type) { + INTEROP_TRACE(type); QualType QT = QualType::getFromOpaquePtr(type); if (const RecordType* RT = QT->getAs()) { // Add quick checks for the std smart prts to cover most of the cases. @@ -513,33 +714,36 @@ bool IsSmartPtrType(TCppType_t type) { if (tsRef.starts_with("std::unique_ptr") || tsRef.starts_with("std::shared_ptr") || tsRef.starts_with("std::weak_ptr")) - return true; - return isSmartPointer(RT); + return INTEROP_RETURN(true); + return INTEROP_RETURN(isSmartPointer(RT)); } - return false; + return INTEROP_RETURN(false); } TCppType_t GetIntegerTypeFromEnumScope(TCppScope_t handle) { + INTEROP_TRACE(handle); auto* D = (clang::Decl*)handle; if (auto* ED = llvm::dyn_cast_or_null(D)) { - return ED->getIntegerType().getAsOpaquePtr(); + return INTEROP_RETURN(ED->getIntegerType().getAsOpaquePtr()); } - return 0; + return INTEROP_RETURN(nullptr); } TCppType_t GetIntegerTypeFromEnumType(TCppType_t enum_type) { + INTEROP_TRACE(enum_type); if (!enum_type) - return nullptr; + return INTEROP_RETURN(nullptr); QualType QT = QualType::getFromOpaquePtr(enum_type); if (auto* ET = QT->getAs()) - return ET->getDecl()->getIntegerType().getAsOpaquePtr(); + return INTEROP_RETURN(ET->getDecl()->getIntegerType().getAsOpaquePtr()); - return nullptr; + return INTEROP_RETURN(nullptr); } std::vector GetEnumConstants(TCppScope_t handle) { + INTEROP_TRACE(handle); auto* D = (clang::Decl*)handle; if (auto* ED = llvm::dyn_cast_or_null(D)) { @@ -548,60 +752,65 @@ std::vector GetEnumConstants(TCppScope_t handle) { enum_constants.push_back((TCppScope_t)ECD); } - return enum_constants; + return INTEROP_RETURN(enum_constants); } - return {}; + return INTEROP_RETURN(std::vector{}); } TCppType_t GetEnumConstantType(TCppScope_t handle) { + INTEROP_TRACE(handle); if (!handle) - return nullptr; + return INTEROP_RETURN(nullptr); auto* D = (clang::Decl*)handle; if (auto* ECD = llvm::dyn_cast(D)) - return ECD->getType().getAsOpaquePtr(); + return INTEROP_RETURN(ECD->getType().getAsOpaquePtr()); - return 0; + return INTEROP_RETURN(nullptr); } TCppIndex_t GetEnumConstantValue(TCppScope_t handle) { + INTEROP_TRACE(handle); auto* D = (clang::Decl*)handle; if (auto* ECD = llvm::dyn_cast_or_null(D)) { const llvm::APSInt& Val = ECD->getInitVal(); - return Val.getExtValue(); + return INTEROP_RETURN(Val.getExtValue()); } - return 0; + return INTEROP_RETURN(0); } size_t GetSizeOfType(TCppType_t type) { + INTEROP_TRACE(type); QualType QT = QualType::getFromOpaquePtr(type); if (const TagType* TT = QT->getAs()) - return SizeOf(TT->getDecl()); + return INTEROP_RETURN(SizeOf(TT->getDecl())); // FIXME: Can we get the size of a non-tag type? auto TI = getSema().getASTContext().getTypeInfo(QT); size_t TypeSize = TI.Width; - return TypeSize / 8; + return INTEROP_RETURN(TypeSize / 8); } bool IsVariable(TCppScope_t scope) { + INTEROP_TRACE(scope); auto* D = (clang::Decl*)scope; - return llvm::isa_and_nonnull(D); + return INTEROP_RETURN(llvm::isa_and_nonnull(D)); } std::string GetName(TCppType_t klass) { + INTEROP_TRACE(klass); auto* D = (clang::NamedDecl*)klass; if (llvm::isa_and_nonnull(D)) { - return ""; + return INTEROP_RETURN(""); } if (auto* ND = llvm::dyn_cast_or_null(D)) { - return ND->getNameAsString(); + return INTEROP_RETURN(ND->getNameAsString()); } - return ""; + return INTEROP_RETURN(""); } static std::string GetCompleteNameImpl(TCppType_t klass, bool qualified) { @@ -647,49 +856,54 @@ static std::string GetCompleteNameImpl(TCppType_t klass, bool qualified) { } std::string GetCompleteName(TCppType_t klass) { - return GetCompleteNameImpl(klass, /*qualified=*/false); + INTEROP_TRACE(klass); + return INTEROP_RETURN(GetCompleteNameImpl(klass, /*qualified=*/false)); } std::string GetQualifiedName(TCppType_t klass) { + INTEROP_TRACE(klass); auto* D = (Decl*)klass; if (auto* ND = llvm::dyn_cast_or_null(D)) { - return ND->getQualifiedNameAsString(); + return INTEROP_RETURN(ND->getQualifiedNameAsString()); } if (llvm::isa_and_nonnull(D)) { - return ""; + return INTEROP_RETURN(""); } - return ""; + return INTEROP_RETURN(""); } std::string GetQualifiedCompleteName(TCppType_t klass) { - return GetCompleteNameImpl(klass, /*qualified=*/true); + INTEROP_TRACE(klass); + return INTEROP_RETURN(GetCompleteNameImpl(klass, /*qualified=*/true)); } std::string GetDoxygenComment(TCppScope_t scope, bool strip_comment_markers) { + INTEROP_TRACE(scope, strip_comment_markers); auto* D = static_cast(scope); if (!D) - return ""; + return INTEROP_RETURN(""); D = D->getCanonicalDecl(); ASTContext& C = D->getASTContext(); const RawComment* RC = C.getRawCommentForAnyRedecl(D); if (!RC) - return ""; + return INTEROP_RETURN(""); (void)C.getCommentForDecl(D, /*PP=*/nullptr); const SourceManager& SM = C.getSourceManager(); if (!strip_comment_markers) - return RC->getRawText(SM).str(); + return INTEROP_RETURN(RC->getRawText(SM).str()); - return RC->getFormattedText(SM, C.getDiagnostics()); + return INTEROP_RETURN(RC->getFormattedText(SM, C.getDiagnostics())); } std::vector GetUsingNamespaces(TCppScope_t scope) { + INTEROP_TRACE(scope); auto* D = (clang::Decl*)scope; if (auto* DC = llvm::dyn_cast_or_null(D)) { @@ -697,14 +911,16 @@ std::vector GetUsingNamespaces(TCppScope_t scope) { for (auto UD : DC->using_directives()) { namespaces.push_back((TCppScope_t)UD->getNominatedNamespace()); } - return namespaces; + return INTEROP_RETURN(namespaces); } - return {}; + return INTEROP_RETURN(std::vector{}); } TCppScope_t GetGlobalScope() { - return getSema().getASTContext().getTranslationUnitDecl()->getFirstDecl(); + INTEROP_TRACE(); + return INTEROP_RETURN( + getSema().getASTContext().getTranslationUnitDecl()->getFirstDecl()); } static Decl* GetScopeFromType(QualType QT) { @@ -721,8 +937,9 @@ static Decl* GetScopeFromType(QualType QT) { } TCppScope_t GetScopeFromType(TCppType_t type) { + INTEROP_TRACE(type); QualType QT = QualType::getFromOpaquePtr(type); - return (TCppScope_t)GetScopeFromType(QT); + return INTEROP_RETURN((TCppScope_t)GetScopeFromType(QT)); } static clang::Decl* GetUnderlyingScope(clang::Decl* D) { @@ -738,33 +955,36 @@ static clang::Decl* GetUnderlyingScope(clang::Decl* D) { } TCppScope_t GetUnderlyingScope(TCppScope_t scope) { + INTEROP_TRACE(scope); if (!scope) - return 0; - return GetUnderlyingScope((clang::Decl*)scope); + return INTEROP_RETURN(nullptr); + return INTEROP_RETURN(GetUnderlyingScope((clang::Decl*)scope)); } TCppScope_t GetScope(const std::string& name, TCppScope_t parent) { + INTEROP_TRACE(name, parent); // FIXME: GetScope should be replaced by a general purpose lookup // and filter function. The function should be like GetNamed but // also take in a filter parameter which determines which results // to pass back if (name == "") - return GetGlobalScope(); + return INTEROP_RETURN(GetGlobalScope()); auto* ND = (NamedDecl*)GetNamed(name, parent); if (!ND || ND == (NamedDecl*)-1) - return 0; + return INTEROP_RETURN(nullptr); if (llvm::isa(ND) || llvm::isa(ND) || llvm::isa(ND) || llvm::isa(ND) || llvm::isa(ND) || llvm::isa(ND)) - return (TCppScope_t)(ND->getCanonicalDecl()); + return INTEROP_RETURN((TCppScope_t)(ND->getCanonicalDecl())); - return 0; + return INTEROP_RETURN(nullptr); } TCppScope_t GetScopeFromCompleteName(const std::string& name) { + INTEROP_TRACE(name); std::string delim = "::"; size_t start = 0; size_t end = name.find(delim); @@ -774,11 +994,12 @@ TCppScope_t GetScopeFromCompleteName(const std::string& name) { start = end + delim.length(); end = name.find(delim, start); } - return GetScope(name.substr(start, end), curr_scope); + return INTEROP_RETURN(GetScope(name.substr(start, end), curr_scope)); } TCppScope_t GetNamed(const std::string& name, TCppScope_t parent /*= nullptr*/) { + INTEROP_TRACE(name, parent); clang::DeclContext* Within = 0; if (parent) { auto* D = (clang::Decl*)parent; @@ -792,32 +1013,34 @@ TCppScope_t GetNamed(const std::string& name, compat::SynthesizingCodeRAII RAII(&getInterp()); auto* ND = CppInternal::utils::Lookup::Named(&getSema(), name, Within); if (ND && ND != (clang::NamedDecl*)-1) { - return (TCppScope_t)(ND->getCanonicalDecl()); + return INTEROP_RETURN((TCppScope_t)(ND->getCanonicalDecl())); } - return 0; + return INTEROP_RETURN(nullptr); } TCppScope_t GetParentScope(TCppScope_t scope) { + INTEROP_TRACE(scope); auto* D = (clang::Decl*)scope; if (llvm::isa_and_nonnull(D)) { - return 0; + return INTEROP_RETURN(nullptr); } auto* ParentDC = D->getDeclContext(); if (!ParentDC) - return 0; + return INTEROP_RETURN(nullptr); auto* P = clang::Decl::castFromDeclContext(ParentDC)->getCanonicalDecl(); if (auto* TU = llvm::dyn_cast_or_null(P)) - return (TCppScope_t)TU->getFirstDecl(); + return INTEROP_RETURN((TCppScope_t)TU->getFirstDecl()); - return (TCppScope_t)P; + return INTEROP_RETURN((TCppScope_t)P); } TCppIndex_t GetNumBases(TCppScope_t klass) { + INTEROP_TRACE(klass); auto* D = (Decl*)klass; if (auto* CTSD = llvm::dyn_cast_or_null(D)) @@ -825,43 +1048,46 @@ TCppIndex_t GetNumBases(TCppScope_t klass) { compat::InstantiateClassTemplateSpecialization(getInterp(), CTSD); if (auto* CXXRD = llvm::dyn_cast_or_null(D)) { if (CXXRD->hasDefinition()) - return CXXRD->getNumBases(); + return INTEROP_RETURN(CXXRD->getNumBases()); } - return 0; + return INTEROP_RETURN(0); } TCppScope_t GetBaseClass(TCppScope_t klass, TCppIndex_t ibase) { + INTEROP_TRACE(klass, ibase); auto* D = (Decl*)klass; auto* CXXRD = llvm::dyn_cast_or_null(D); if (!CXXRD || CXXRD->getNumBases() <= ibase) - return 0; + return INTEROP_RETURN(nullptr); auto type = (CXXRD->bases_begin() + ibase)->getType(); if (auto RT = type->getAs()) - return (TCppScope_t)RT->getDecl()->getCanonicalDecl(); + return INTEROP_RETURN((TCppScope_t)RT->getDecl()->getCanonicalDecl()); - return 0; + return INTEROP_RETURN(nullptr); } // FIXME: Consider dropping this interface as it seems the same as // IsTypeDerivedFrom. bool IsSubclass(TCppScope_t derived, TCppScope_t base) { + INTEROP_TRACE(derived, base); if (derived == base) - return true; + return INTEROP_RETURN(true); if (!derived || !base) - return false; + return INTEROP_RETURN(false); auto* derived_D = (clang::Decl*)derived; auto* base_D = (clang::Decl*)base; if (!isa(derived_D) || !isa(base_D)) - return false; + return INTEROP_RETURN(false); auto Derived = cast(derived_D); auto Base = cast(base_D); - return IsTypeDerivedFrom(GetTypeFromScope(Derived), GetTypeFromScope(Base)); + return INTEROP_RETURN( + IsTypeDerivedFrom(GetTypeFromScope(Derived), GetTypeFromScope(Base))); } // Copied from VTableBuilder.cpp @@ -913,15 +1139,16 @@ static unsigned ComputeBaseOffset(const ASTContext& Context, } int64_t GetBaseClassOffset(TCppScope_t derived, TCppScope_t base) { + INTEROP_TRACE(derived, base); if (base == derived) - return 0; + return INTEROP_RETURN(0); assert(derived || base); auto* DD = (Decl*)derived; auto* BD = (Decl*)base; if (!isa(DD) || !isa(BD)) - return -1; + return INTEROP_RETURN(-1); CXXRecordDecl* DCXXRD = cast(DD); CXXRecordDecl* BCXXRD = cast(BD); CXXBasePaths Paths(/*FindAmbiguities=*/false, /*RecordPaths=*/true, @@ -929,7 +1156,8 @@ int64_t GetBaseClassOffset(TCppScope_t derived, TCppScope_t base) { DCXXRD->isDerivedFrom(BCXXRD, Paths); // FIXME: We might want to cache these requests as they seem expensive. - return ComputeBaseOffset(getSema().getASTContext(), DCXXRD, Paths.front()); + return INTEROP_RETURN( + ComputeBaseOffset(getSema().getASTContext(), DCXXRD, Paths.front())); } template @@ -982,21 +1210,26 @@ static void GetClassDecls(TCppScope_t klass, } void GetClassMethods(TCppScope_t klass, std::vector& methods) { + INTEROP_TRACE(klass, INTEROP_OUT(methods)); GetClassDecls(klass, methods); + return INTEROP_VOID_RETURN(); } void GetFunctionTemplatedDecls(TCppScope_t klass, std::vector& methods) { + INTEROP_TRACE(klass, INTEROP_OUT(methods)); GetClassDecls(klass, methods); + return INTEROP_VOID_RETURN(); } bool HasDefaultConstructor(TCppScope_t scope) { + INTEROP_TRACE(scope); auto* D = (clang::Decl*)scope; if (auto* CXXRD = llvm::dyn_cast_or_null(D)) - return CXXRD->hasDefaultConstructor(); + return INTEROP_RETURN(CXXRD->hasDefaultConstructor()); - return false; + return INTEROP_RETURN(false); } TCppFunction_t GetDefaultConstructor(compat::Interpreter& interp, @@ -1010,31 +1243,36 @@ TCppFunction_t GetDefaultConstructor(compat::Interpreter& interp, } TCppFunction_t GetDefaultConstructor(TCppScope_t scope) { - return GetDefaultConstructor(getInterp(), scope); + INTEROP_TRACE(scope); + return INTEROP_RETURN(GetDefaultConstructor(getInterp(), scope)); } TCppFunction_t GetDestructor(TCppScope_t scope) { + INTEROP_TRACE(scope); auto* D = (clang::Decl*)scope; if (auto* CXXRD = llvm::dyn_cast_or_null(D)) { getSema().ForceDeclarationOfImplicitMembers(CXXRD); - return CXXRD->getDestructor(); + return INTEROP_RETURN(CXXRD->getDestructor()); } - return 0; + return INTEROP_RETURN(nullptr); } void DumpScope(TCppScope_t scope) { + INTEROP_TRACE(scope); auto* D = (clang::Decl*)scope; D->dump(); + return INTEROP_VOID_RETURN(); } std::vector GetFunctionsUsingName(TCppScope_t scope, const std::string& name) { + INTEROP_TRACE(scope, name); auto* D = (Decl*)scope; if (!scope || name.empty()) - return {}; + return INTEROP_RETURN(std::vector{}); D = GetUnderlyingScope(D); @@ -1048,7 +1286,7 @@ std::vector GetFunctionsUsingName(TCppScope_t scope, CppInternal::utils::Lookup::Named(&S, R, Decl::castToDeclContext(D)); if (R.empty()) - return funcs; + return INTEROP_RETURN(funcs); R.resolveKind(); @@ -1056,10 +1294,11 @@ std::vector GetFunctionsUsingName(TCppScope_t scope, if (llvm::isa(Found)) funcs.push_back(Found); - return funcs; + return INTEROP_RETURN(funcs); } TCppType_t GetFunctionReturnType(TCppFunction_t func) { + INTEROP_TRACE(func); auto* D = (clang::Decl*)func; if (auto* FD = llvm::dyn_cast_or_null(D)) { QualType Type = FD->getReturnType(); @@ -1077,53 +1316,58 @@ TCppType_t GetFunctionReturnType(TCppFunction_t func) { } Type = FD->getReturnType(); } - return Type.getAsOpaquePtr(); + return INTEROP_RETURN(Type.getAsOpaquePtr()); } if (auto* FD = llvm::dyn_cast_or_null(D)) - return (FD->getTemplatedDecl())->getReturnType().getAsOpaquePtr(); + return INTEROP_RETURN( + (FD->getTemplatedDecl())->getReturnType().getAsOpaquePtr()); - return 0; + return INTEROP_RETURN(nullptr); } TCppIndex_t GetFunctionNumArgs(TCppFunction_t func) { + INTEROP_TRACE(func); auto* D = (clang::Decl*)func; if (auto* FD = llvm::dyn_cast_or_null(D)) - return FD->getNumParams(); + return INTEROP_RETURN(FD->getNumParams()); if (auto* FD = llvm::dyn_cast_or_null(D)) - return (FD->getTemplatedDecl())->getNumParams(); + return INTEROP_RETURN((FD->getTemplatedDecl())->getNumParams()); - return 0; + return INTEROP_RETURN(0); } TCppIndex_t GetFunctionRequiredArgs(TCppConstFunction_t func) { + INTEROP_TRACE(func); const auto* D = static_cast(func); if (auto* FD = llvm::dyn_cast_or_null(D)) - return FD->getMinRequiredArguments(); + return INTEROP_RETURN(FD->getMinRequiredArguments()); if (auto* FD = llvm::dyn_cast_or_null(D)) - return (FD->getTemplatedDecl())->getMinRequiredArguments(); + return INTEROP_RETURN((FD->getTemplatedDecl())->getMinRequiredArguments()); - return 0; + return INTEROP_RETURN(0); } TCppType_t GetFunctionArgType(TCppFunction_t func, TCppIndex_t iarg) { + INTEROP_TRACE(func, iarg); auto* D = (clang::Decl*)func; if (auto* FD = llvm::dyn_cast_or_null(D)) { if (iarg < FD->getNumParams()) { auto* PVD = FD->getParamDecl(iarg); - return PVD->getOriginalType().getAsOpaquePtr(); + return INTEROP_RETURN(PVD->getOriginalType().getAsOpaquePtr()); } } - return 0; + return INTEROP_RETURN(nullptr); } std::string GetFunctionSignature(TCppFunction_t func) { + INTEROP_TRACE(func); if (!func) - return ""; + return INTEROP_RETURN(""); auto* D = (clang::Decl*)func; clang::FunctionDecl* FD; @@ -1133,7 +1377,7 @@ std::string GetFunctionSignature(TCppFunction_t func) { else if (auto* FTD = llvm::dyn_cast(D)) FD = FTD->getTemplatedDecl(); else - return ""; + return INTEROP_RETURN(""); std::string Signature; raw_string_ostream SS(Signature); @@ -1144,7 +1388,7 @@ std::string GetFunctionSignature(TCppFunction_t func) { Policy.SuppressDefaultTemplateArgs = false; FD->print(SS, Policy); SS.flush(); - return Signature; + return INTEROP_RETURN(Signature); } // Internal functions that are not needed outside the library are @@ -1169,20 +1413,24 @@ bool IsTemplateInstantiationOrSpecialization(Decl* D) { } // namespace bool IsFunctionDeleted(TCppConstFunction_t function) { + INTEROP_TRACE(function); const auto* FD = cast(static_cast(function)); - return FD->isDeleted(); + return INTEROP_RETURN(FD->isDeleted()); } bool IsTemplatedFunction(TCppFunction_t func) { + INTEROP_TRACE(func); auto* D = (Decl*)func; - return IsTemplatedFunction(D) || IsTemplateInstantiationOrSpecialization(D); + return INTEROP_RETURN(IsTemplatedFunction(D) || + IsTemplateInstantiationOrSpecialization(D)); } // FIXME: This lookup is broken, and should no longer be used in favour of // `GetClassTemplatedMethods` If the candidate set returned is =1, that means // the template function exists and >1 means overloads bool ExistsFunctionTemplate(const std::string& name, TCppScope_t parent) { + INTEROP_TRACE(name, parent); DeclContext* Within = 0; if (parent) { auto* D = (Decl*)parent; @@ -1192,19 +1440,20 @@ bool ExistsFunctionTemplate(const std::string& name, TCppScope_t parent) { auto* ND = CppInternal::utils::Lookup::Named(&getSema(), name, Within); if ((intptr_t)ND == (intptr_t)0) - return false; + return INTEROP_RETURN(false); if ((intptr_t)ND != (intptr_t)-1) - return IsTemplatedFunction(ND) || - IsTemplateInstantiationOrSpecialization(ND); + return INTEROP_RETURN(IsTemplatedFunction(ND) || + IsTemplateInstantiationOrSpecialization(ND)); // FIXME: Cycle through the Decls and check if there is a templated function - return true; + return INTEROP_RETURN(true); } // Looks up all constructors in the current DeclContext void LookupConstructors(const std::string& name, TCppScope_t parent, std::vector& funcs) { + INTEROP_TRACE(name, parent, INTEROP_OUT(funcs)); auto* D = (Decl*)parent; if (auto* CXXRD = llvm::dyn_cast_or_null(D)) { @@ -1217,13 +1466,15 @@ void LookupConstructors(const std::string& name, TCppScope_t parent, if (GetName(i) == name) funcs.push_back(i); } + return INTEROP_VOID_RETURN(); } bool GetClassTemplatedMethods(const std::string& name, TCppScope_t parent, std::vector& funcs) { + INTEROP_TRACE(name, parent, INTEROP_OUT(funcs)); auto* D = (Decl*)parent; if (!D && name.empty()) - return false; + return INTEROP_RETURN(false); // Accumulate constructors LookupConstructors(name, parent, funcs); @@ -1237,7 +1488,7 @@ bool GetClassTemplatedMethods(const std::string& name, TCppScope_t parent, CppInternal::utils::Lookup::Named(&S, R, DC); if (R.getResultKind() == clang_LookupResult_Not_Found && funcs.empty()) - return false; + return INTEROP_RETURN(false); // Distinct match, single Decl else if (R.getResultKind() == clang_LookupResult_Found) { @@ -1258,7 +1509,7 @@ bool GetClassTemplatedMethods(const std::string& name, TCppScope_t parent, // Produce a diagnostic describing the ambiguity that resulted // from name lookup as done in Sema::DiagnoseAmbiguousLookup // - return !funcs.empty(); + return INTEROP_RETURN(!funcs.empty()); } // Adapted from inner workings of Sema::BuildCallExpr @@ -1266,6 +1517,7 @@ TCppFunction_t BestOverloadFunctionMatch(const std::vector& candidates, const std::vector& explicit_types, const std::vector& arg_types) { + INTEROP_TRACE(candidates, explicit_types, arg_types); auto& S = getSema(); auto& C = S.getASTContext(); @@ -1343,7 +1595,7 @@ BestOverloadFunctionMatch(const std::vector& candidates, FunctionDecl* Result = Best != Overloads.end() ? Best->Function : nullptr; delete[] Exprs; - return Result; + return INTEROP_RETURN(Result); } // Gets the AccessSpecifier of the function and checks if it is equal to @@ -1358,46 +1610,55 @@ bool CheckMethodAccess(TCppFunction_t method, AccessSpecifier AS) { } bool IsMethod(TCppConstFunction_t method) { - return dyn_cast_or_null( - static_cast(method)); + INTEROP_TRACE(method); + return INTEROP_RETURN( + dyn_cast_or_null(static_cast(method))); } bool IsPublicMethod(TCppFunction_t method) { - return CheckMethodAccess(method, AccessSpecifier::AS_public); + INTEROP_TRACE(method); + return INTEROP_RETURN(CheckMethodAccess(method, AccessSpecifier::AS_public)); } bool IsProtectedMethod(TCppFunction_t method) { - return CheckMethodAccess(method, AccessSpecifier::AS_protected); + INTEROP_TRACE(method); + return INTEROP_RETURN( + CheckMethodAccess(method, AccessSpecifier::AS_protected)); } bool IsPrivateMethod(TCppFunction_t method) { - return CheckMethodAccess(method, AccessSpecifier::AS_private); + INTEROP_TRACE(method); + return INTEROP_RETURN(CheckMethodAccess(method, AccessSpecifier::AS_private)); } bool IsConstructor(TCppConstFunction_t method) { + INTEROP_TRACE(method); const auto* D = static_cast(method); if (const auto* FTD = dyn_cast(D)) - return IsConstructor(FTD->getTemplatedDecl()); - return llvm::isa_and_nonnull(D); + return INTEROP_RETURN(IsConstructor(FTD->getTemplatedDecl())); + return INTEROP_RETURN(llvm::isa_and_nonnull(D)); } bool IsDestructor(TCppConstFunction_t method) { + INTEROP_TRACE(method); const auto* D = static_cast(method); - return llvm::isa_and_nonnull(D); + return INTEROP_RETURN(llvm::isa_and_nonnull(D)); } bool IsStaticMethod(TCppConstFunction_t method) { + INTEROP_TRACE(method); const auto* D = static_cast(method); if (auto* CXXMD = llvm::dyn_cast_or_null(D)) { - return CXXMD->isStatic(); + return INTEROP_RETURN(CXXMD->isStatic()); } - return false; + return INTEROP_RETURN(false); } bool IsExplicit(TCppConstFunction_t method) { + INTEROP_TRACE(method); if (!method) - return false; + return INTEROP_RETURN(false); const auto* D = static_cast(method); @@ -1405,26 +1666,27 @@ bool IsExplicit(TCppConstFunction_t method) { D = FTD->getTemplatedDecl(); if (const auto* CD = llvm::dyn_cast_or_null(D)) - return CD->isExplicit(); + return INTEROP_RETURN(CD->isExplicit()); if (const auto* CD = llvm::dyn_cast_or_null(D)) - return CD->isExplicit(); + return INTEROP_RETURN(CD->isExplicit()); if (const auto* DGD = llvm::dyn_cast_or_null(D)) - return DGD->isExplicit(); + return INTEROP_RETURN(DGD->isExplicit()); - return false; + return INTEROP_RETURN(false); } TCppFuncAddr_t GetFunctionAddress(const char* mangled_name) { + INTEROP_TRACE(mangled_name); auto& I = getInterp(); auto FDAorErr = compat::getSymbolAddress(I, mangled_name); if (llvm::Error Err = FDAorErr.takeError()) llvm::consumeError(std::move(Err)); // nullptr if missing else - return llvm::jitTargetAddressToPointer(*FDAorErr); + return INTEROP_RETURN(llvm::jitTargetAddressToPointer(*FDAorErr)); - return nullptr; + return INTEROP_RETURN(nullptr); } static TCppFuncAddr_t GetFunctionAddress(const FunctionDecl* FD) { @@ -1454,6 +1716,7 @@ static TCppFuncAddr_t GetFunctionAddress(const FunctionDecl* FD) { } TCppFuncAddr_t GetFunctionAddress(TCppFunction_t method) { + INTEROP_TRACE(method); auto* D = static_cast(method); if (auto* FD = llvm::dyn_cast_or_null(D)) { if ((IsTemplateInstantiationOrSpecialization(FD) || @@ -1463,21 +1726,23 @@ TCppFuncAddr_t GetFunctionAddress(TCppFunction_t method) { ASTContext& C = getASTContext(); if (isDiscardableGVALinkage(C.GetGVALinkageForFunction(FD))) ForceCodeGen(FD, getInterp()); - return GetFunctionAddress(FD); + return INTEROP_RETURN(GetFunctionAddress(FD)); } - return nullptr; + return INTEROP_RETURN(nullptr); } bool IsVirtualMethod(TCppFunction_t method) { + INTEROP_TRACE(method); auto* D = (Decl*)method; if (auto* CXXMD = llvm::dyn_cast_or_null(D)) { - return CXXMD->isVirtual(); + return INTEROP_RETURN(CXXMD->isVirtual()); } - return false; + return INTEROP_RETURN(false); } void GetDatamembers(TCppScope_t scope, std::vector& datamembers) { + INTEROP_TRACE(scope, INTEROP_OUT(datamembers)); auto* D = (Decl*)scope; if (auto* CXXRD = llvm::dyn_cast_or_null(D)) { @@ -1516,16 +1781,20 @@ void GetDatamembers(TCppScope_t scope, std::vector& datamembers) { stack_begin.back()++; } } + return INTEROP_VOID_RETURN(); } void GetStaticDatamembers(TCppScope_t scope, std::vector& datamembers) { + INTEROP_TRACE(scope, INTEROP_OUT(datamembers)); GetClassDecls(scope, datamembers); + return INTEROP_VOID_RETURN(); } void GetEnumConstantDatamembers(TCppScope_t scope, std::vector& datamembers, bool include_enum_class) { + INTEROP_TRACE(scope, INTEROP_OUT(datamembers), include_enum_class); std::vector EDs; GetClassDecls(scope, EDs); for (TCppScope_t i : EDs) { @@ -1538,9 +1807,11 @@ void GetEnumConstantDatamembers(TCppScope_t scope, std::copy(ED->enumerator_begin(), ED->enumerator_end(), std::back_inserter(datamembers)); } + return INTEROP_VOID_RETURN(); } TCppScope_t LookupDatamember(const std::string& name, TCppScope_t parent) { + INTEROP_TRACE(name, parent); clang::DeclContext* Within = 0; if (parent) { auto* D = (clang::Decl*)parent; @@ -1550,22 +1821,24 @@ TCppScope_t LookupDatamember(const std::string& name, TCppScope_t parent) { auto* ND = CppInternal::utils::Lookup::Named(&getSema(), name, Within); if (ND && ND != (clang::NamedDecl*)-1) { if (llvm::isa_and_nonnull(ND)) { - return (TCppScope_t)ND; + return INTEROP_RETURN((TCppScope_t)ND); } } - return 0; + return INTEROP_RETURN(nullptr); } bool IsLambdaClass(TCppType_t type) { + INTEROP_TRACE(type); QualType QT = QualType::getFromOpaquePtr(type); if (auto* CXXRD = QT->getAsCXXRecordDecl()) { - return CXXRD->isLambda(); + return INTEROP_RETURN(CXXRD->isLambda()); } - return false; + return INTEROP_RETURN(false); } TCppType_t GetVariableType(TCppScope_t var) { + INTEROP_TRACE(var); auto* D = static_cast(var); if (auto DD = llvm::dyn_cast_or_null(D)) { @@ -1573,18 +1846,18 @@ TCppType_t GetVariableType(TCppScope_t var) { // Check if the type is a typedef type if (QT->isTypedefNameType()) { - return QT.getAsOpaquePtr(); + return INTEROP_RETURN(QT.getAsOpaquePtr()); } // Else, return the canonical type QT = QT.getCanonicalType(); - return QT.getAsOpaquePtr(); + return INTEROP_RETURN(QT.getAsOpaquePtr()); } if (auto* ECD = llvm::dyn_cast_or_null(D)) - return ECD->getType().getAsOpaquePtr(); + return INTEROP_RETURN(ECD->getType().getAsOpaquePtr()); - return 0; + return INTEROP_RETURN(nullptr); } intptr_t GetVariableOffset(compat::Interpreter& I, Decl* D, @@ -1697,9 +1970,10 @@ intptr_t GetVariableOffset(compat::Interpreter& I, Decl* D, } intptr_t GetVariableOffset(TCppScope_t var, TCppScope_t parent) { + INTEROP_TRACE(var, parent); auto* D = static_cast(var); auto* RD = llvm::dyn_cast_or_null(static_cast(parent)); - return GetVariableOffset(getInterp(), D, RD); + return INTEROP_RETURN(GetVariableOffset(getInterp(), D, RD)); } // Check if the Access Specifier of the variable matches the provided value. @@ -1709,96 +1983,114 @@ bool CheckVariableAccess(TCppScope_t var, AccessSpecifier AS) { } bool IsPublicVariable(TCppScope_t var) { - return CheckVariableAccess(var, AccessSpecifier::AS_public); + INTEROP_TRACE(var); + return INTEROP_RETURN(CheckVariableAccess(var, AccessSpecifier::AS_public)); } bool IsProtectedVariable(TCppScope_t var) { - return CheckVariableAccess(var, AccessSpecifier::AS_protected); + INTEROP_TRACE(var); + return INTEROP_RETURN( + CheckVariableAccess(var, AccessSpecifier::AS_protected)); } bool IsPrivateVariable(TCppScope_t var) { - return CheckVariableAccess(var, AccessSpecifier::AS_private); + INTEROP_TRACE(var); + return INTEROP_RETURN(CheckVariableAccess(var, AccessSpecifier::AS_private)); } bool IsStaticVariable(TCppScope_t var) { + INTEROP_TRACE(var); auto* D = (Decl*)var; if (llvm::isa_and_nonnull(D)) { - return true; + return INTEROP_RETURN(true); } - return false; + return INTEROP_RETURN(false); } bool IsConstVariable(TCppScope_t var) { + INTEROP_TRACE(var); auto* D = (clang::Decl*)var; if (auto* VD = llvm::dyn_cast_or_null(D)) { - return VD->getType().isConstQualified(); + return INTEROP_RETURN(VD->getType().isConstQualified()); } - return false; + return INTEROP_RETURN(false); } bool IsRecordType(TCppType_t type) { + INTEROP_TRACE(type); QualType QT = QualType::getFromOpaquePtr(type); - return QT->isRecordType(); + return INTEROP_RETURN(QT->isRecordType()); } bool IsPODType(TCppType_t type) { + INTEROP_TRACE(type); QualType QT = QualType::getFromOpaquePtr(type); if (QT.isNull()) - return false; + return INTEROP_RETURN(false); - return QT.isPODType(getASTContext()); + return INTEROP_RETURN(QT.isPODType(getASTContext())); } bool IsPointerType(TCppType_t type) { + INTEROP_TRACE(type); QualType QT = QualType::getFromOpaquePtr(type); - return QT->isPointerType(); + return INTEROP_RETURN(QT->isPointerType()); } TCppType_t GetPointeeType(TCppType_t type) { + INTEROP_TRACE(type); if (!IsPointerType(type)) - return nullptr; + return INTEROP_RETURN(nullptr); QualType QT = QualType::getFromOpaquePtr(type); - return QT->getPointeeType().getAsOpaquePtr(); + return INTEROP_RETURN(QT->getPointeeType().getAsOpaquePtr()); } bool IsReferenceType(TCppType_t type) { + INTEROP_TRACE(type); QualType QT = QualType::getFromOpaquePtr(type); - return QT->isReferenceType(); + return INTEROP_RETURN(QT->isReferenceType()); } ValueKind GetValueKind(TCppType_t type) { + INTEROP_TRACE(type); QualType QT = QualType::getFromOpaquePtr(type); if (QT->isRValueReferenceType()) - return ValueKind::RValue; + return INTEROP_RETURN(ValueKind::RValue); if (QT->isLValueReferenceType()) - return ValueKind::LValue; - return ValueKind::None; + return INTEROP_RETURN(ValueKind::LValue); + return INTEROP_RETURN(ValueKind::None); } TCppType_t GetPointerType(TCppType_t type) { + INTEROP_TRACE(type); QualType QT = QualType::getFromOpaquePtr(type); - return getASTContext().getPointerType(QT).getAsOpaquePtr(); + return INTEROP_RETURN(getASTContext().getPointerType(QT).getAsOpaquePtr()); } TCppType_t GetReferencedType(TCppType_t type, bool rvalue) { + INTEROP_TRACE(type, rvalue); QualType QT = QualType::getFromOpaquePtr(type); if (rvalue) - return getASTContext().getRValueReferenceType(QT).getAsOpaquePtr(); - return getASTContext().getLValueReferenceType(QT).getAsOpaquePtr(); + return INTEROP_RETURN( + getASTContext().getRValueReferenceType(QT).getAsOpaquePtr()); + return INTEROP_RETURN( + getASTContext().getLValueReferenceType(QT).getAsOpaquePtr()); } TCppType_t GetNonReferenceType(TCppType_t type) { + INTEROP_TRACE(type); if (!IsReferenceType(type)) - return nullptr; + return INTEROP_RETURN(nullptr); QualType QT = QualType::getFromOpaquePtr(type); - return QT.getNonReferenceType().getAsOpaquePtr(); + return INTEROP_RETURN(QT.getNonReferenceType().getAsOpaquePtr()); } TCppType_t GetUnderlyingType(TCppType_t type) { + INTEROP_TRACE(type); QualType QT = QualType::getFromOpaquePtr(type); QT = QT->getCanonicalTypeUnqualified(); @@ -1813,49 +2105,53 @@ TCppType_t GetUnderlyingType(TCppType_t type) { QT = PT; } QT = QT->getCanonicalTypeUnqualified(); - return QT.getAsOpaquePtr(); + return INTEROP_RETURN(QT.getAsOpaquePtr()); } std::string GetTypeAsString(TCppType_t var) { + INTEROP_TRACE(var); QualType QT = QualType::getFromOpaquePtr(var); PrintingPolicy Policy(getASTContext().getPrintingPolicy()); Policy.Bool = true; // Print bool instead of _Bool. Policy.SuppressTagKeyword = true; // Do not print `class std::string`. Policy.SuppressElaboration = true; Policy.FullyQualifiedName = true; - return QT.getAsString(Policy); + return INTEROP_RETURN(QT.getAsString(Policy)); } TCppType_t GetCanonicalType(TCppType_t type) { + INTEROP_TRACE(type); if (!type) - return 0; + return INTEROP_RETURN(nullptr); QualType QT = QualType::getFromOpaquePtr(type); - return QT.getCanonicalType().getAsOpaquePtr(); + return INTEROP_RETURN(QT.getCanonicalType().getAsOpaquePtr()); } bool HasTypeQualifier(TCppType_t type, QualKind qual) { + INTEROP_TRACE(type, qual); if (!type) - return false; + return INTEROP_RETURN(false); QualType QT = QualType::getFromOpaquePtr(type); if (qual & QualKind::Const) { if (!QT.isConstQualified()) - return false; + return INTEROP_RETURN(false); } if (qual & QualKind::Volatile) { if (!QT.isVolatileQualified()) - return false; + return INTEROP_RETURN(false); } if (qual & QualKind::Restrict) { if (!QT.isRestrictQualified()) - return false; + return INTEROP_RETURN(false); } - return true; + return INTEROP_RETURN(true); } TCppType_t RemoveTypeQualifier(TCppType_t type, QualKind qual) { + INTEROP_TRACE(type, qual); if (!type) - return type; + return INTEROP_RETURN(type); auto QT = QualType(QualType::getFromOpaquePtr(type)); if (qual & QualKind::Const) @@ -1864,12 +2160,13 @@ TCppType_t RemoveTypeQualifier(TCppType_t type, QualKind qual) { QT.removeLocalVolatile(); if (qual & QualKind::Restrict) QT.removeLocalRestrict(); - return QT.getAsOpaquePtr(); + return INTEROP_RETURN(QT.getAsOpaquePtr()); } TCppType_t AddTypeQualifier(TCppType_t type, QualKind qual) { + INTEROP_TRACE(type, qual); if (!type) - return type; + return INTEROP_RETURN(type); auto QT = QualType(QualType::getFromOpaquePtr(type)); if (qual & QualKind::Const) { @@ -1884,107 +2181,155 @@ TCppType_t AddTypeQualifier(TCppType_t type, QualKind qual) { if (!QT.isRestrictQualified()) QT.addRestrict(); } - return QT.getAsOpaquePtr(); + return INTEROP_RETURN(QT.getAsOpaquePtr()); } -// Internal functions that are not needed outside the library are -// encompassed in an anonymous namespace as follows. This function converts -// from a string to the actual type. It is used in the GetType() function. -namespace { +// Registers all permutations of a word set +static void RegisterPerms(llvm::StringMap& Map, QualType QT, + llvm::SmallVectorImpl& Words) { + std::sort(Words.begin(), Words.end()); + do { + std::string Key; + for (size_t i = 0; i < Words.size(); ++i) { + if (i > 0) + Key += ' '; + Key += Words[i].str(); + } + Map[Key] = QT; + } while (std::next_permutation(Words.begin(), Words.end())); +} +ALLOW_ACCESS(ASTContext, Types, llvm::SmallVector); +static void PopulateBuiltinMap(ASTContext& Context) { + const PrintingPolicy Policy(Context.getLangOpts()); + auto& BuiltinMap = GetInterpreters().back().BuiltinMap; + const auto& Types = ACCESS(Context, Types); + + for (clang::Type* T : Types) { + auto* BT = llvm::dyn_cast(T); + if (!BT || BT->isPlaceholderType()) + continue; + + QualType QT(BT, 0); + std::string Name = QT.getAsString(Policy); + if (Name.empty() || Name[0] == '<') + continue; + + // Initial entry (e.g., "int", "unsigned long") + BuiltinMap[Name] = QT; + + llvm::SmallVector Words; + llvm::StringRef(Name).split(Words, ' ', -1, false); + + bool hasInt = false; + bool hasSigned = false; + bool hasUnsigned = false; + bool hasChar = false; + bool isModifiable = false; + + for (auto W : Words) { + if (W == "int") + hasInt = true; + else if (W == "signed") + hasSigned = true; + else if (W == "unsigned") + hasUnsigned = true; + else if (W == "char") + hasChar = true; + + if (W == "long" || W == "short" || hasInt) + isModifiable = true; + } + + // Skip things like 'float' or 'double' that aren't combined + if (!isModifiable && !hasUnsigned && !hasSigned) + continue; + + // Register base permutations (e.g., "long long" or "unsigned int") + if (Words.size() > 1) + RegisterPerms(BuiltinMap, QT, Words); + + // Expansion: Add "int" suffix where missing (e.g., "short" -> "short int") + if (!hasInt && !hasChar) { + auto WithInt = Words; + WithInt.push_back("int"); + RegisterPerms(BuiltinMap, QT, WithInt); + + // If we are adding 'int', we should also try adding 'signed' + // to cover cases like "short" -> "signed short int" + if (!hasSigned && !hasUnsigned) { + auto WithBoth = WithInt; + WithBoth.push_back("signed"); + RegisterPerms(BuiltinMap, QT, WithBoth); + } + } + + // Expansion: Add "signed" prefix + // (e.g., "int" -> "signed int", "long" -> "signed long") + if (!hasSigned && !hasUnsigned) { + auto WithSigned = Words; + WithSigned.push_back("signed"); + RegisterPerms(BuiltinMap, QT, WithSigned); + } + } + + // Explicit global synonym + BuiltinMap["signed"] = Context.IntTy; + BuiltinMap["unsigned"] = Context.UnsignedIntTy; +} static QualType findBuiltinType(llvm::StringRef typeName, ASTContext& Context) { - bool issigned = false; - bool isunsigned = false; - if (typeName.starts_with("signed ")) { - issigned = true; - typeName = StringRef(typeName.data() + 7, typeName.size() - 7); - } - if (!issigned && typeName.starts_with("unsigned ")) { - isunsigned = true; - typeName = StringRef(typeName.data() + 9, typeName.size() - 9); - } - if (typeName == "char") { - if (isunsigned) - return Context.UnsignedCharTy; - return Context.SignedCharTy; - } - if (typeName == "short") { - if (isunsigned) - return Context.UnsignedShortTy; - return Context.ShortTy; - } - if (typeName == "int") { - if (isunsigned) - return Context.UnsignedIntTy; - return Context.IntTy; - } - if (typeName == "long") { - if (isunsigned) - return Context.UnsignedLongTy; - return Context.LongTy; - } - if (typeName == "long long") { - if (isunsigned) - return Context.UnsignedLongLongTy; - return Context.LongLongTy; - } - if (!issigned && !isunsigned) { - if (typeName == "bool") - return Context.BoolTy; - if (typeName == "float") - return Context.FloatTy; - if (typeName == "double") - return Context.DoubleTy; - if (typeName == "long double") - return Context.LongDoubleTy; - - if (typeName == "wchar_t") - return Context.WCharTy; - if (typeName == "char16_t") - return Context.Char16Ty; - if (typeName == "char32_t") - return Context.Char32Ty; - } - /* Missing - CanQualType WideCharTy; // Same as WCharTy in C++, integer type in C99. - CanQualType WIntTy; // [C99 7.24.1], integer type unchanged by default - promotions. - */ - return QualType(); + llvm::StringMap& BuiltinMap = GetInterpreters().back().BuiltinMap; + if (BuiltinMap.empty()) + PopulateBuiltinMap(Context); + + // Fast Lookup + auto It = BuiltinMap.find(typeName); + if (It != BuiltinMap.end()) + return It->second; + + return QualType(); // Return null if not a builtin +} +static std::optional GetTypeInternal(Decl* D) { + if (!D) + return {}; + // Even though typedefs derive from TypeDecl, their getTypeForDecl() + // returns a nullptr. + if (const auto* TND = llvm::dyn_cast_or_null(D)) + return TND->getUnderlyingType(); + + if (auto* VD = dyn_cast(D)) + return VD->getType(); + + if (const auto* TD = llvm::dyn_cast_or_null(D)) + return QualType(TD->getTypeForDecl(), 0); + + return {}; } -} // namespace TCppType_t GetType(const std::string& name) { + INTEROP_TRACE(name); QualType builtin = findBuiltinType(name, getASTContext()); if (!builtin.isNull()) - return builtin.getAsOpaquePtr(); + return INTEROP_RETURN(builtin.getAsOpaquePtr()); - auto* D = (Decl*)GetNamed(name, /* Within= */ 0); - if (auto* TD = llvm::dyn_cast_or_null(D)) { - return QualType(TD->getTypeForDecl(), 0).getAsOpaquePtr(); - } - - return (TCppType_t)0; + return INTEROP_RETURN(GetTypeFromScope(GetNamed(name, /*parent=*/nullptr))); } TCppType_t GetComplexType(TCppType_t type) { + INTEROP_TRACE(type); QualType QT = QualType::getFromOpaquePtr(type); - return getASTContext().getComplexType(QT).getAsOpaquePtr(); + return INTEROP_RETURN(getASTContext().getComplexType(QT).getAsOpaquePtr()); } TCppType_t GetTypeFromScope(TCppScope_t klass) { + INTEROP_TRACE(klass); if (!klass) - return 0; + return INTEROP_RETURN(nullptr); - auto* D = (Decl*)klass; - - if (auto* VD = dyn_cast(D)) - return VD->getType().getAsOpaquePtr(); + if (auto QT = GetTypeInternal((Decl*)klass)) + return INTEROP_RETURN(QT->getAsOpaquePtr()); - if (auto* TD = dyn_cast(D)) - return getASTContext().getTypeDeclType(TD).getAsOpaquePtr(); - - return (TCppType_t) nullptr; + return INTEROP_RETURN(nullptr); } // Internal functions that are not needed outside the library are @@ -3003,10 +3348,10 @@ int get_wrapper_code(compat::Interpreter& I, const FunctionDecl* FD, JitCall::GenericCall make_wrapper(compat::Interpreter& I, const FunctionDecl* FD) { - static std::map gWrapperStore; + auto& WrapperStore = getInterpInfo(&I).WrapperStore; - auto R = gWrapperStore.find(FD); - if (R != gWrapperStore.end()) + auto R = WrapperStore.find(FD); + if (R != WrapperStore.end()) return (JitCall::GenericCall)R->second; std::string wrapper_name; @@ -3015,6 +3360,24 @@ JitCall::GenericCall make_wrapper(compat::Interpreter& I, if (get_wrapper_code(I, FD, wrapper_name, wrapper_code) == 0) return 0; + // Log the wrapper source for the crash reproducer. + if (auto* TI = CppInterOp::Tracing::TraceInfo::TheTraceInfo) { + std::string FuncName; + llvm::raw_string_ostream FNS(FuncName); + FD->getNameForDiagnostic(FNS, FD->getASTContext().getPrintingPolicy(), + /*Qualified=*/true); + TI->appendToLog(" // === Wrapper for " + FuncName + " ==="); + // Emit each line of the wrapper source as a comment. + llvm::StringRef WC(wrapper_code); + while (!WC.empty()) { + auto [Line, Rest] = WC.split('\n'); + if (!Line.empty()) + TI->appendToLog((" // " + Line).str()); + WC = Rest; + } + TI->appendToLog(" // === End wrapper ==="); + } + // // Compile the wrapper code. // @@ -3025,7 +3388,7 @@ JitCall::GenericCall make_wrapper(compat::Interpreter& I, void* wrapper = compile_wrapper(I, wrapper_name, wrapper_code, withAccessControl); if (wrapper) { - gWrapperStore.insert(std::make_pair(FD, wrapper)); + WrapperStore.insert(std::make_pair(FD, wrapper)); } else { llvm::errs() << "TClingCallFunc::make_wrapper" << ":" @@ -3095,10 +3458,10 @@ static JitCall::DestructorCall make_dtor_wrapper(compat::Interpreter& interp, // //-- - static std::map gDtorWrapperStore; + auto& DtorWrapperStore = getInterpInfo(&interp).DtorWrapperStore; - auto I = gDtorWrapperStore.find(D); - if (I != gDtorWrapperStore.end()) + auto I = DtorWrapperStore.find(D); + if (I != DtorWrapperStore.end()) return (JitCall::DestructorCall)I->second; // @@ -3199,7 +3562,7 @@ static JitCall::DestructorCall make_dtor_wrapper(compat::Interpreter& interp, void* F = compile_wrapper(interp, wrapper_name, wrapper, /*withAccessControl=*/false); if (F) { - gDtorWrapperStore.insert(std::make_pair(D, F)); + DtorWrapperStore.insert(std::make_pair(D, F)); } else { llvm::errs() << "make_dtor_wrapper" << "Failed to compile\n" @@ -3216,36 +3579,39 @@ static JitCall::DestructorCall make_dtor_wrapper(compat::Interpreter& interp, CPPINTEROP_API JitCall MakeFunctionCallable(TInterp_t I, TCppConstFunction_t func) { + INTEROP_TRACE(I, func); const auto* D = static_cast(func); if (!D) - return {}; + return INTEROP_RETURN(JitCall{}); auto* interp = static_cast(I); // FIXME: Unify with make_wrapper. if (const auto* Dtor = dyn_cast(D)) { if (auto Wrapper = make_dtor_wrapper(*interp, Dtor->getParent())) - return {JitCall::kDestructorCall, Wrapper, Dtor}; + return INTEROP_RETURN(JitCall(JitCall::kDestructorCall, Wrapper, Dtor)); // FIXME: else error we failed to compile the wrapper. - return {}; + return INTEROP_RETURN(JitCall{}); } if (const auto* Ctor = dyn_cast(D)) { if (auto Wrapper = make_wrapper(*interp, cast(D))) - return {JitCall::kConstructorCall, Wrapper, Ctor}; + return INTEROP_RETURN(JitCall(JitCall::kConstructorCall, Wrapper, Ctor)); // FIXME: else error we failed to compile the wrapper. - return {}; + return INTEROP_RETURN(JitCall{}); } if (auto Wrapper = make_wrapper(*interp, cast(D))) { - return {JitCall::kGenericCall, Wrapper, cast(D)}; + return INTEROP_RETURN( + JitCall(JitCall::kGenericCall, Wrapper, cast(D))); } // FIXME: else error we failed to compile the wrapper. - return {}; + return INTEROP_RETURN(JitCall{}); } CPPINTEROP_API JitCall MakeFunctionCallable(TCppConstFunction_t func) { - return MakeFunctionCallable(&getInterp(), func); + INTEROP_TRACE(func); + return INTEROP_RETURN(MakeFunctionCallable(&getInterp(), func)); } namespace { @@ -3330,6 +3696,7 @@ std::string ExtractArgument(const std::vector& Args, TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, const std::vector& GpuArgs /*={}*/) { + INTEROP_TRACE(Args, GpuArgs); std::string MainExecutableName = sys::fs::getMainExecutable(nullptr, nullptr); // In some systems, CppInterOp cannot manually detect the correct resource. // Then the -resource-dir passed by the user is assumed to be the correct @@ -3362,7 +3729,7 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, if (Arg0 != "cuda") { llvm::errs() << "[CreateInterpreter]: Make sure --cuda is passed as the" << " first argument of the GpuArgs\n"; - return nullptr; + return INTEROP_RETURN(nullptr); } } ClingArgv.insert(ClingArgv.end(), GpuArgs.begin(), GpuArgs.end()); @@ -3382,6 +3749,9 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, std::back_inserter(ClingArgv), [&](const std::string& str) { return str.c_str(); }); + // Force global process initialization. + (void)GetInterpreters(); + #ifdef CPPINTEROP_USE_CLING auto I = new compat::Interpreter(ClingArgv.size(), &ClingArgv[0]); #else @@ -3389,7 +3759,7 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, compat::Interpreter::create(static_cast(ClingArgv.size()), ClingArgv.data(), nullptr, {}, nullptr, true); if (!Interp) - return nullptr; + return INTEROP_RETURN(nullptr); auto* I = Interp.release(); #endif @@ -3424,7 +3794,7 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, )"); } - sInterpreters->emplace_back(I, /*Owned=*/true); + RegisterInterpreter(I, /*Owned=*/true); // Define runtime symbols in the JIT dylib for clang-repl #if !defined(CPPINTEROP_USE_CLING) && !defined(EMSCRIPTEN) @@ -3453,56 +3823,19 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, *I, "__clang_Interpreter_SetValueNoAlloc", reinterpret_cast(&__clang_Interpreter_SetValueNoAlloc)); #endif - return I; -} - -bool DeleteInterpreter(TInterp_t I /*=nullptr*/) { - if (!I) { - sInterpreters->pop_back(); - return true; - } - - auto found = - std::find_if(sInterpreters->begin(), sInterpreters->end(), - [&I](const auto& Info) { return Info.Interpreter == I; }); - if (found == sInterpreters->end()) - return false; // failure - - sInterpreters->erase(found); - return true; -} - -bool ActivateInterpreter(TInterp_t I) { - if (!I) - return false; - - auto found = - std::find_if(sInterpreters->begin(), sInterpreters->end(), - [&I](const auto& Info) { return Info.Interpreter == I; }); - if (found == sInterpreters->end()) - return false; - - if (std::next(found) != sInterpreters->end()) // if not already last element. - std::rotate(found, found + 1, sInterpreters->end()); - - return true; // success -} - -TInterp_t GetInterpreter() { - if (sInterpreters->empty()) - return nullptr; - return sInterpreters->back().Interpreter; + return INTEROP_RETURN(I); } InterpreterLanguage GetLanguage(TInterp_t I /*=nullptr*/) { + INTEROP_TRACE(I); compat::Interpreter* interp = &getInterp(I); const auto& LO = interp->getCI()->getLangOpts(); // CUDA and HIP reuse C++ language standards, so LangStd alone reports CXX. if (LO.CUDA) - return InterpreterLanguage::CUDA; + return INTEROP_RETURN(InterpreterLanguage::CUDA); if (LO.HIP) - return InterpreterLanguage::HIP; + return INTEROP_RETURN(InterpreterLanguage::HIP); auto standard = clang::LangStandard::getLangStandardForKind(LO.LangStd); auto lang = static_cast(standard.getLanguage()); @@ -3510,10 +3843,11 @@ InterpreterLanguage GetLanguage(TInterp_t I /*=nullptr*/) { assert(static_cast(lang) <= static_cast(InterpreterLanguage::HLSL) && "Unhandled Language"); - return lang; + return INTEROP_RETURN(lang); } InterpreterLanguageStandard GetLanguageStandard(TInterp_t I /*=nullptr*/) { + INTEROP_TRACE(I); compat::Interpreter* interp = &getInterp(I); const auto& LO = interp->getCI()->getLangOpts(); auto langStandard = static_cast(LO.LangStd); @@ -3523,21 +3857,19 @@ InterpreterLanguageStandard GetLanguageStandard(TInterp_t I /*=nullptr*/) { static_cast( InterpreterLanguageStandard::lang_unspecified) && "Unhandled language standard."); - return langStandard; -} - -void UseExternalInterpreter(TInterp_t I) { - assert(sInterpreters->empty() && "sInterpreter already in use!"); - sInterpreters->emplace_back(static_cast(I), - /*isOwned=*/false); + return INTEROP_RETURN(langStandard); } void AddSearchPath(const char* dir, bool isUser, bool prepend) { + INTEROP_TRACE(dir, isUser, prepend); getInterp().getDynamicLibraryManager()->addSearchPath(dir, isUser, prepend); + return INTEROP_VOID_RETURN(); } const char* GetResourceDir() { - return getInterp().getCI()->getHeaderSearchOpts().ResourceDir.c_str(); + INTEROP_TRACE(); + return INTEROP_RETURN( + getInterp().getCI()->getHeaderSearchOpts().ResourceDir.c_str()); } ///\returns 0 on success. @@ -3570,40 +3902,49 @@ static bool exec(const char* cmd, std::vector& outputs) { } std::string DetectResourceDir(const char* ClangBinaryName /* = clang */) { + INTEROP_TRACE(ClangBinaryName); std::string cmd = std::string(ClangBinaryName) + " -print-resource-dir"; std::vector outs; exec(cmd.c_str(), outs); if (outs.empty() || outs.size() > 1) - return ""; + return INTEROP_RETURN(""); std::string detected_resource_dir = outs.back(); std::string version = CLANG_VERSION_MAJOR_STRING; // We need to check if the detected resource directory is compatible. if (llvm::sys::path::filename(detected_resource_dir) != version) - return ""; + return INTEROP_RETURN(""); - return detected_resource_dir; + return INTEROP_RETURN(detected_resource_dir); } void DetectSystemCompilerIncludePaths(std::vector& Paths, const char* CompilerName /*= "c++"*/) { + INTEROP_TRACE(INTEROP_OUT(Paths), CompilerName); std::string cmd = "LC_ALL=C "; cmd += CompilerName; cmd += " -xc++ -E -v /dev/null 2>&1 | sed -n -e '/^.include/,${' -e '/^ " "\\/.*/p' -e '}'"; std::vector outs; exec(cmd.c_str(), Paths); + return INTEROP_VOID_RETURN(); } -void AddIncludePath(const char* dir) { getInterp().AddIncludePath(dir); } +void AddIncludePath(const char* dir) { + INTEROP_TRACE(dir); + getInterp().AddIncludePath(dir); + return INTEROP_VOID_RETURN(); +} void GetIncludePaths(std::vector& IncludePaths, bool withSystem, bool withFlags) { + INTEROP_TRACE(INTEROP_OUT(IncludePaths), withSystem, withFlags); llvm::SmallVector paths(1); getInterp().GetIncludePaths(paths, withSystem, withFlags); for (auto& i : paths) IncludePaths.push_back(i); + return INTEROP_VOID_RETURN(); } namespace { @@ -3633,12 +3974,17 @@ int Declare(compat::Interpreter& I, const char* code, bool silent) { } int Declare(const char* code, bool silent) { - return Declare(getInterp(), code, silent); + INTEROP_TRACE(code, silent); + return INTEROP_RETURN(Declare(getInterp(), code, silent)); } -int Process(const char* code) { return getInterp().process(code); } +int Process(const char* code) { + INTEROP_TRACE(code); + return INTEROP_RETURN(getInterp().process(code)); +} intptr_t Evaluate(const char* code, bool* HadError /*=nullptr*/) { + INTEROP_TRACE(code, HadError); compat::Value V; if (HadError) @@ -3649,31 +3995,38 @@ intptr_t Evaluate(const char* code, bool* HadError /*=nullptr*/) { if (HadError) *HadError = true; // FIXME: Make this return llvm::Expected - return ~0UL; + return INTEROP_RETURN(~0UL); } - return compat::convertTo(V); + return INTEROP_RETURN(compat::convertTo(V)); } std::string LookupLibrary(const char* lib_name) { - return getInterp().getDynamicLibraryManager()->lookupLibrary(lib_name); + INTEROP_TRACE(lib_name); + return INTEROP_RETURN( + getInterp().getDynamicLibraryManager()->lookupLibrary(lib_name)); } bool LoadLibrary(const char* lib_stem, bool lookup) { + INTEROP_TRACE(lib_stem, lookup); compat::Interpreter::CompilationResult res = getInterp().loadLibrary(lib_stem, lookup); - return res == compat::Interpreter::kSuccess; + return INTEROP_RETURN(res == compat::Interpreter::kSuccess); } void UnloadLibrary(const char* lib_stem) { + INTEROP_TRACE(lib_stem); getInterp().getDynamicLibraryManager()->unloadLibrary(lib_stem); + return INTEROP_VOID_RETURN(); } std::string SearchLibrariesForSymbol(const char* mangled_name, bool search_system /*true*/) { + INTEROP_TRACE(mangled_name, search_system); auto* DLM = getInterp().getDynamicLibraryManager(); - return DLM->searchLibrariesForSymbol(mangled_name, search_system); + return INTEROP_RETURN( + DLM->searchLibrariesForSymbol(mangled_name, search_system)); } bool InsertOrReplaceJitSymbol(compat::Interpreter& I, @@ -3757,11 +4110,14 @@ bool InsertOrReplaceJitSymbol(compat::Interpreter& I, bool InsertOrReplaceJitSymbol(const char* linker_mangled_name, uint64_t address) { - return InsertOrReplaceJitSymbol(getInterp(), linker_mangled_name, address); + INTEROP_TRACE(linker_mangled_name, address); + return INTEROP_RETURN( + InsertOrReplaceJitSymbol(getInterp(), linker_mangled_name, address)); } std::string ObjToString(const char* type, void* obj) { - return getInterp().toString(type, obj); + INTEROP_TRACE(type, obj); + return INTEROP_RETURN(getInterp().toString(type, obj)); } static Decl* InstantiateTemplate(TemplateDecl* TemplateD, @@ -3858,12 +4214,14 @@ TCppScope_t InstantiateTemplate(TCppScope_t tmpl, const TemplateArgInfo* template_args, size_t template_args_size, bool instantiate_body) { - return InstantiateTemplate(getInterp(), tmpl, template_args, - template_args_size, instantiate_body); + INTEROP_TRACE(tmpl, template_args, template_args_size, instantiate_body); + return INTEROP_RETURN(InstantiateTemplate( + getInterp(), tmpl, template_args, template_args_size, instantiate_body)); } void GetClassTemplateInstantiationArgs(TCppScope_t templ_instance, std::vector& args) { + INTEROP_TRACE(templ_instance, INTEROP_OUT(args)); auto* CTSD = static_cast(templ_instance); for (const auto& TA : CTSD->getTemplateInstantiationArgs().asArray()) { switch (TA.getKind()) { @@ -3884,10 +4242,12 @@ void GetClassTemplateInstantiationArgs(TCppScope_t templ_instance, args.push_back({TA.getAsType().getAsOpaquePtr()}); } } + return INTEROP_VOID_RETURN(); } TCppFunction_t InstantiateTemplateFunctionFromString(const char* function_template) { + INTEROP_TRACE(function_template); // FIXME: Drop this interface and replace it with the proper overload // resolution handling and template instantiation selection. @@ -3899,12 +4259,13 @@ InstantiateTemplateFunctionFromString(const char* function_template) { if (!Cpp::Declare(instance.c_str(), /*silent=*/false)) { VarDecl* VD = (VarDecl*)Cpp::GetNamed(id, 0); DeclRefExpr* DRE = (DeclRefExpr*)VD->getInit()->IgnoreImpCasts(); - return DRE->getDecl(); + return INTEROP_RETURN(DRE->getDecl()); } - return nullptr; + return INTEROP_RETURN(nullptr); } void GetAllCppNames(TCppScope_t scope, std::set& names) { + INTEROP_TRACE(scope, INTEROP_OUT(names)); auto* D = (clang::Decl*)scope; clang::DeclContext* DC; clang::DeclContext::decl_iterator decl; @@ -3922,7 +4283,7 @@ void GetAllCppNames(TCppScope_t scope, std::set& names) { DC = clang::TranslationUnitDecl::castToDeclContext(TUD); decl = DC->decls_begin(); } else { - return; + return INTEROP_VOID_RETURN(); } for (/* decl set above */; decl != DC->decls_end(); decl++) { @@ -3930,13 +4291,15 @@ void GetAllCppNames(TCppScope_t scope, std::set& names) { names.insert(ND->getNameAsString()); } } + return INTEROP_VOID_RETURN(); } void GetEnums(TCppScope_t scope, std::vector& Result) { + INTEROP_TRACE(scope, INTEROP_OUT(Result)); auto* D = static_cast(scope); if (!llvm::isa_and_nonnull(D)) - return; + return INTEROP_VOID_RETURN(); auto* DC = llvm::dyn_cast(D); @@ -3951,14 +4314,16 @@ void GetEnums(TCppScope_t scope, std::vector& Result) { } } } + return INTEROP_VOID_RETURN(); } // FIXME: On the CPyCppyy side the receiver is of type // vector instead of vector std::vector GetDimensions(TCppType_t type) { + INTEROP_TRACE(type); QualType Qual = QualType::getFromOpaquePtr(type); if (Qual.isNull()) - return {}; + return INTEROP_RETURN(std::vector{}); Qual = Qual.getCanonicalType(); std::vector dims; if (Qual->isArrayType()) { @@ -3976,23 +4341,25 @@ std::vector GetDimensions(TCppType_t type) { } ArrayType = ArrayType->getElementType()->getAsArrayTypeUnsafe(); } - return dims; + return INTEROP_RETURN(dims); } - return dims; + return INTEROP_RETURN(dims); } bool IsTypeDerivedFrom(TCppType_t derived, TCppType_t base) { + INTEROP_TRACE(derived, base); auto& S = getSema(); auto fakeLoc = GetValidSLoc(S); auto derivedType = clang::QualType::getFromOpaquePtr(derived); auto baseType = clang::QualType::getFromOpaquePtr(base); compat::SynthesizingCodeRAII RAII(&getInterp()); - return S.IsDerivedFrom(fakeLoc, derivedType, baseType); + return INTEROP_RETURN(S.IsDerivedFrom(fakeLoc, derivedType, baseType)); } std::string GetFunctionArgDefault(TCppFunction_t func, TCppIndex_t param_index) { + INTEROP_TRACE(func, param_index); auto* D = (clang::Decl*)func; clang::ParmVarDecl* PI = nullptr; @@ -4019,29 +4386,31 @@ std::string GetFunctionArgDefault(TCppFunction_t func, // float PI = 3.14 is printed as 3.1400000000000001 if (PI->getType()->isFloatingType()) { if (!Result.empty() && Result.back() == '.') - return Result; + return INTEROP_RETURN(Result); auto DefaultArgValue = std::stod(Result); std::ostringstream oss; oss << DefaultArgValue; Result = oss.str(); } - return Result; + return INTEROP_RETURN(Result); } - return ""; + return INTEROP_RETURN(""); } bool IsConstMethod(TCppFunction_t method) { + INTEROP_TRACE(method); if (!method) - return false; + return INTEROP_RETURN(false); auto* D = (clang::Decl*)method; if (auto* func = dyn_cast(D)) - return func->getMethodQualifiers().hasConst(); + return INTEROP_RETURN(func->getMethodQualifiers().hasConst()); - return false; + return INTEROP_RETURN(false); } std::string GetFunctionArgName(TCppFunction_t func, TCppIndex_t param_index) { + INTEROP_TRACE(func, param_index); auto* D = (clang::Decl*)func; clang::ParmVarDecl* PI = nullptr; @@ -4050,23 +4419,27 @@ std::string GetFunctionArgName(TCppFunction_t func, TCppIndex_t param_index) { else if (auto* FD = llvm::dyn_cast_or_null(D)) PI = (FD->getTemplatedDecl())->getParamDecl(param_index); - return PI->getNameAsString(); + return INTEROP_RETURN(PI->getNameAsString()); } std::string GetSpellingFromOperator(Operator Operator) { - return clang::getOperatorSpelling((clang::OverloadedOperatorKind)Operator); + INTEROP_TRACE(Operator); + return INTEROP_RETURN( + clang::getOperatorSpelling((clang::OverloadedOperatorKind)Operator)); } Operator GetOperatorFromSpelling(const std::string& op) { + INTEROP_TRACE(op); #define OVERLOADED_OPERATOR(Name, Spelling, Token, Unary, Binary, MemberOnly) \ if ((Spelling) == op) { \ - return (Operator)OO_##Name; \ + return INTEROP_RETURN((Operator)OO_##Name); \ } #include "clang/Basic/OperatorKinds.def" - return Operator::OP_None; + return INTEROP_RETURN(Operator::OP_None); } OperatorArity GetOperatorArity(TCppFunction_t op) { + INTEROP_TRACE(op); Decl* D = static_cast(op); if (auto* FD = llvm::dyn_cast(D)) { if (FD->isOverloadedOperator()) { @@ -4074,11 +4447,11 @@ OperatorArity GetOperatorArity(TCppFunction_t op) { #define OVERLOADED_OPERATOR(Name, Spelling, Token, Unary, Binary, MemberOnly) \ case OO_##Name: \ if ((Unary) && (Binary)) \ - return kBoth; \ + return INTEROP_RETURN(kBoth); \ if (Unary) \ - return kUnary; \ + return INTEROP_RETURN(kUnary); \ if (Binary) \ - return kBinary; \ + return INTEROP_RETURN(kBinary); \ break; #include "clang/Basic/OperatorKinds.def" default: @@ -4086,11 +4459,12 @@ OperatorArity GetOperatorArity(TCppFunction_t op) { } } } - return (OperatorArity)~0U; + return INTEROP_RETURN((OperatorArity)~0U); } void GetOperator(TCppScope_t scope, Operator op, std::vector& operators, OperatorArity kind) { + INTEROP_TRACE(scope, op, INTEROP_OUT(operators), kind); Decl* D = static_cast(scope); compat::SynthesizingCodeRAII RAII(&getInterp()); if (auto* CXXRD = llvm::dyn_cast_or_null(D)) { @@ -4118,15 +4492,20 @@ void GetOperator(TCppScope_t scope, Operator op, operators.push_back(i); } } + return INTEROP_VOID_RETURN(); } TCppObject_t Allocate(TCppScope_t scope, TCppIndex_t count) { - return (TCppObject_t)::operator new(Cpp::SizeOf(scope) * count); + INTEROP_TRACE(scope, count); + return INTEROP_RETURN( + (TCppObject_t)::operator new(Cpp::SizeOf(scope) * count)); } void Deallocate(TCppScope_t scope, TCppObject_t address, TCppIndex_t count) { + INTEROP_TRACE(scope, address, count); size_t bytes = Cpp::SizeOf(scope) * count; ::operator delete(address, bytes); + return INTEROP_VOID_RETURN(); } // FIXME: Add optional arguments to the operator new. @@ -4157,7 +4536,8 @@ TCppObject_t Construct(compat::Interpreter& interp, TCppScope_t scope, TCppObject_t Construct(TCppScope_t scope, void* arena /*=nullptr*/, TCppIndex_t count /*=1UL*/) { - return Construct(getInterp(), scope, arena, count); + INTEROP_TRACE(scope, arena, count); + return INTEROP_RETURN(Construct(getInterp(), scope, arena, count)); } bool Destruct(compat::Interpreter& interp, TCppObject_t This, const Decl* Class, @@ -4172,8 +4552,9 @@ bool Destruct(compat::Interpreter& interp, TCppObject_t This, const Decl* Class, bool Destruct(TCppObject_t This, TCppConstScope_t scope, bool withFree /*=true*/, TCppIndex_t count /*=0UL*/) { + INTEROP_TRACE(This, scope, withFree, count); const auto* Class = static_cast(scope); - return Destruct(getInterp(), This, Class, withFree, count); + return INTEROP_RETURN(Destruct(getInterp(), This, Class, withFree, count)); } class StreamCaptureInfo { @@ -4303,41 +4684,48 @@ static std::stack& GetRedirectionStack() { } void BeginStdStreamCapture(CaptureStreamKind fd_kind) { + INTEROP_TRACE(fd_kind); GetRedirectionStack().emplace((int)fd_kind); + return INTEROP_VOID_RETURN(); } std::string EndStdStreamCapture() { + INTEROP_TRACE(); assert(GetRedirectionStack().size()); StreamCaptureInfo& SCI = GetRedirectionStack().top(); std::string result = SCI.GetCapturedString(); GetRedirectionStack().pop(); - return result; + return INTEROP_RETURN(result); } void CodeComplete(std::vector& Results, const char* code, unsigned complete_line /* = 1U */, unsigned complete_column /* = 1U */) { + INTEROP_TRACE(INTEROP_OUT(Results), code, complete_line, complete_column); compat::codeComplete(Results, getInterp(), code, complete_line, complete_column); + return INTEROP_VOID_RETURN(); } int Undo(unsigned N) { + INTEROP_TRACE(N); compat::SynthesizingCodeRAII RAII(&getInterp()); #ifdef CPPINTEROP_USE_CLING getInterp().unload(N); - return compat::Interpreter::kSuccess; + return INTEROP_RETURN(compat::Interpreter::kSuccess); #else - return getInterp().undo(N); + return INTEROP_RETURN(getInterp().undo(N)); #endif } #ifndef _WIN32 pid_t GetExecutorPID() { + INTEROP_TRACE(); #ifdef LLVM_BUILT_WITH_OOP_JIT auto& I = getInterp(); - return I.getOutOfProcessExecutorPID(); + return INTEROP_RETURN(I.getOutOfProcessExecutorPID()); #endif - return getpid(); + return INTEROP_RETURN(getpid()); } #endif diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h b/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h index d4cd3d501124b..380e0b6dd69e7 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h @@ -171,14 +171,14 @@ class Interpreter { private: static std::tuple initAndGetFileDescriptors(std::vector& vargs, - std::unique_ptr& io_ctx) { + IOContext& io_ctx) { int stdin_fd = 0; int stdout_fd = 1; int stderr_fd = 2; // Only initialize temp files if not already initialized - if (!io_ctx->stdin_file || !io_ctx->stdout_file || !io_ctx->stderr_file) { - bool init = io_ctx->initializeTempFiles(); + if (!io_ctx.stdin_file || !io_ctx.stdout_file || !io_ctx.stderr_file) { + bool init = io_ctx.initializeTempFiles(); if (!init) { llvm::errs() << "Can't start out-of-process JIT execution.\n"; stdin_fd = -1; @@ -186,9 +186,9 @@ class Interpreter { stderr_fd = -1; } } - stdin_fd = fileno(io_ctx->stdin_file.get()); - stdout_fd = fileno(io_ctx->stdout_file.get()); - stderr_fd = fileno(io_ctx->stderr_file.get()); + stdin_fd = fileno(io_ctx.stdin_file.get()); + stdout_fd = fileno(io_ctx.stdout_file.get()); + stderr_fd = fileno(io_ctx.stderr_file.get()); return std::make_tuple(stdin_fd, stdout_fd, stderr_fd); } @@ -208,13 +208,6 @@ class Interpreter { const std::vector>& moduleExtensions = {}, void* extraLibHandle = nullptr, bool noRuntime = true) { - // Initialize all targets (required for device offloading) - llvm::InitializeAllTargetInfos(); - llvm::InitializeAllTargets(); - llvm::InitializeAllTargetMCs(); - llvm::InitializeAllAsmParsers(); - llvm::InitializeAllAsmPrinters(); - std::vector vargs(argv + 1, argv + argc); int stdin_fd = 0; @@ -233,7 +226,7 @@ class Interpreter { if (outOfProcess) { std::tie(stdin_fd, stdout_fd, stderr_fd) = - initAndGetFileDescriptors(vargs, io_ctx); + initAndGetFileDescriptors(vargs, *io_ctx); if (stdin_fd == -1 || stdout_fd == -1 || stderr_fd == -1) { llvm::errs() diff --git a/interpreter/CppInterOp/lib/CppInterOp/Sins.h b/interpreter/CppInterOp/lib/CppInterOp/Sins.h new file mode 100644 index 0000000000000..ecd3b5f0e063b --- /dev/null +++ b/interpreter/CppInterOp/lib/CppInterOp/Sins.h @@ -0,0 +1,28 @@ +#ifndef LIB_CPPINTEROP_SINS_H +#define LIB_CPPINTEROP_SINS_H + +/// Standard-protected facility allowing access into private members in C++. +/// Use with caution! +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +#define CONCATE_(X, Y) X##Y +#define CONCATE(X, Y) CONCATE_(X, Y) +#define ALLOW_ACCESS(CLASS, MEMBER, ...) \ + template \ + struct CONCATE(MEMBER, __LINE__) { \ + friend __VA_ARGS__ CLASS::*Access(Only*) { return Member; } \ + }; \ + template struct Only_##MEMBER; \ + template <> struct Only_##MEMBER { \ + friend __VA_ARGS__ CLASS::*Access(Only_##MEMBER*); \ + }; \ + template struct CONCATE(MEMBER, \ + __LINE__), &CLASS::MEMBER> + +#define ACCESS(OBJECT, MEMBER) \ + (OBJECT).* \ + Access( \ + (Only_##MEMBER>*)nullptr) + +// NOLINTEND(cppcoreguidelines-macro-usage) + +#endif // LIB_CPPINTEROP_SINS_H diff --git a/interpreter/CppInterOp/lib/CppInterOp/Tracing.cpp b/interpreter/CppInterOp/lib/CppInterOp/Tracing.cpp new file mode 100644 index 0000000000000..06caeff3fee77 --- /dev/null +++ b/interpreter/CppInterOp/lib/CppInterOp/Tracing.cpp @@ -0,0 +1,125 @@ +//===--- Tracing.cpp - Tracing implementation -------------------*- C++ -*-===// +// +// Part of the compiler-research project, under the Apache License v2.0 with +// LLVM Exceptions. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Tracing.h" + +#include "CppInterOp/CppInterOp.h" + +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/ManagedStatic.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include + +namespace CppInterOp { +namespace Tracing { + +TraceInfo* TraceInfo::TheTraceInfo = nullptr; + +void InitTracing() { + assert(!TraceInfo::TheTraceInfo); + static llvm::ManagedStatic TI; + TraceInfo::TheTraceInfo = &*TI; +} + +/// Helper: emit version info as comment lines. +static void WriteVersionComment(llvm::raw_ostream& OS, + const std::string& Version) { + llvm::StringRef Ver(Version); + while (!Ver.empty()) { + auto [Line, Rest] = Ver.split('\n'); + if (!Line.empty()) + OS << "// " << Line << "\n"; + Ver = Rest; + } +} + +std::string TraceInfo::writeToFile(const std::string& Version) { + llvm::SmallString<128> TmpDir; + llvm::sys::path::system_temp_directory(/*ErasedOnReboot=*/true, TmpDir); + llvm::SmallString<128> Path; + llvm::sys::path::append(Path, TmpDir, "cppinterop-reproducer-%%%%%%.cpp"); + + int FD; + std::error_code EC = llvm::sys::fs::createUniqueFile(Path, FD, Path); + if (EC) + return ""; + + llvm::raw_fd_ostream OS(FD, /*shouldClose=*/true); + + OS << "// CppInterOp crash reproducer\n"; + std::string Ver = Version.empty() ? CppImpl::GetVersion() : Version; + WriteVersionComment(OS, Ver); + OS << "// Generated automatically — re-run to reproduce the crash.\n"; + OS << "#include \n\n"; + OS << "void reproducer() {\n"; + for (const auto& Line : m_Log) + OS << Line << "\n"; + OS << "}\n\n"; + OS << "int main() { reproducer(); return 0; }\n"; + OS.flush(); + return std::string(Path); +} + +std::string TraceInfo::StartRegion(bool WriteOnStdErr) { + m_RegionStart = m_Log.size(); + m_InRegion = true; + m_WriteOnStdErr = WriteOnStdErr; + + // When streaming to stderr, skip creating a file. + if (WriteOnStdErr) { + m_RegionPath.clear(); + return ""; + } + + llvm::SmallString<128> TmpDir; + llvm::sys::path::system_temp_directory(/*ErasedOnReboot=*/true, TmpDir); + llvm::SmallString<128> Path; + llvm::sys::path::append(Path, TmpDir, "cppinterop-reproducer-%%%%%%.cpp"); + int FD; + std::error_code EC = llvm::sys::fs::createUniqueFile(Path, FD, Path); + if (EC) + return ""; + llvm::sys::Process::SafelyCloseFileDescriptor(FD); + m_RegionPath = std::string(Path); + return m_RegionPath; +} + +void TraceInfo::StopRegion(const std::string& Version) { + if (!m_InRegion) + return; + m_InRegion = false; + + // When streaming to stderr, there is no file to write. + if (m_WriteOnStdErr) + return; + + std::error_code EC; + llvm::raw_fd_ostream OS(m_RegionPath, EC); + if (EC) + return; + + std::string Ver = Version.empty() ? CppImpl::GetVersion() : Version; + + OS << "// CppInterOp trace region\n"; + WriteVersionComment(OS, Ver); + OS << "// Generated automatically.\n"; + OS << "#include \n\n"; + OS << "void reproducer() {\n"; + for (size_t i = m_RegionStart; i < m_Log.size(); ++i) + OS << m_Log[i] << "\n"; + OS << "}\n\n"; + OS << "int main() { reproducer(); return 0; }\n"; + OS.flush(); +} + +} // namespace Tracing +} // namespace CppInterOp diff --git a/interpreter/CppInterOp/lib/CppInterOp/Tracing.h b/interpreter/CppInterOp/lib/CppInterOp/Tracing.h new file mode 100644 index 0000000000000..1bc27b88cb246 --- /dev/null +++ b/interpreter/CppInterOp/lib/CppInterOp/Tracing.h @@ -0,0 +1,449 @@ +//===--- Tracing.h - A layer for tracing and reproducibility ----*- C++ -*-===// +// +// Part of the compiler-research project, under the Apache License v2.0 with +// LLVM Exceptions. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines API for performance analysis and building reproducers. +// +//===----------------------------------------------------------------------===// + +#ifndef CPPINTEROP_TRACING_H +#define CPPINTEROP_TRACING_H + +// Visibility for tracing symbols that must be exported from the shared +// library so that tests (and the crash handler) can access them. +#if defined(_WIN32) || defined(__CYGWIN__) +#define CPPINTEROP_TRACE_API __declspec(dllexport) +#elif defined(__GNUC__) +#define CPPINTEROP_TRACE_API __attribute__((__visibility__("default"))) +#else +#define CPPINTEROP_TRACE_API +#endif + +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/Timer.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace CppInterOp { +namespace Tracing { + +class TraceInfo { + llvm::TimerGroup m_TG; + llvm::StringMap> m_Timers; + std::vector m_TimerStack; + + std::unordered_map m_HandleMap; + unsigned m_VarCount = 0; + + std::vector m_Log; + size_t m_RegionStart = 0; ///< Log index where current region began. + bool m_InRegion = false; ///< True between StartTracing/StopTracing. + +public: + TraceInfo() : m_TG("CppInterOp", "CppInterOp Timing Report") {} + ~TraceInfo() { TheTraceInfo = nullptr; } + TraceInfo(const TraceInfo&) = delete; + TraceInfo& operator=(const TraceInfo&) = delete; + TraceInfo(TraceInfo&&) = delete; + TraceInfo& operator=(TraceInfo&&) = delete; + + static bool isEnabled() { return TheTraceInfo; } + + llvm::Timer& getTimer(llvm::StringRef Name) { + auto& T = m_Timers[Name]; + if (!T) + T = std::make_unique(Name, Name, m_TG); + return *T; + } + + void pushTimer(llvm::Timer* T) { + if (!m_TimerStack.empty()) + m_TimerStack.back()->stopTimer(); + m_TimerStack.push_back(T); + T->startTimer(); + } + + void popTimer() { + if (m_TimerStack.empty()) + return; + m_TimerStack.back()->stopTimer(); + m_TimerStack.pop_back(); + if (!m_TimerStack.empty()) + m_TimerStack.back()->startTimer(); + } + + std::string getOrRegisterHandle(void* p) { + if (!p) + return ""; + auto it = m_HandleMap.find(p); + if (it != m_HandleMap.end()) + return it->second; + return m_HandleMap[p] = "v" + std::to_string(++m_VarCount); + } + + std::string lookupHandle(void* p) { + if (!p) + return "nullptr"; + auto it = m_HandleMap.find(p); + return (it != m_HandleMap.end()) ? it->second : "nullptr"; + } + + void appendToLog(const std::string& line) { + m_Log.push_back(line); + if (m_InRegion && m_WriteOnStdErr) + llvm::errs() << line << "\n"; + } + const std::vector& getLog() const { return m_Log; } + std::string getLastLogEntry() const { + return m_Log.empty() ? "" : m_Log.back(); + } + + /// Write the accumulated reproducer log to a file. + /// \param Version optional version string embedded as comments. + CPPINTEROP_TRACE_API std::string writeToFile(const std::string& Version = ""); + + /// Begin a traced region. Returns the path where StopTracing will write. + /// \param WriteOnStdErr if true, also emit the reproducer to stderr. + CPPINTEROP_TRACE_API std::string StartRegion(bool WriteOnStdErr = true); + + /// End the traced region and write only the region's entries to the file. + CPPINTEROP_TRACE_API void StopRegion(const std::string& Version = ""); + +private: + std::string m_RegionPath; + bool m_WriteOnStdErr = false; + +public: + void clear() { + // Stop any running timers before clearing to avoid triggering + // TimerGroup's destructor report. + while (!m_TimerStack.empty()) { + m_TimerStack.back()->stopTimer(); + m_TimerStack.pop_back(); + } + // Clear timers after clearing the group to suppress the report. + m_TG.clear(); + m_Timers.clear(); + m_HandleMap.clear(); + m_Log.clear(); + m_VarCount = 0; + } + + CPPINTEROP_TRACE_API static TraceInfo* TheTraceInfo; +}; + +/// Activate tracing. Called once during process initialization. +/// After this, TheTraceInfo is non-null and all INTEROP_TRACE calls record. +CPPINTEROP_TRACE_API void InitTracing(); + +/// Begin recording a traced region. If tracing is not yet active, activates +/// it. Returns the path where StopTracing() will write the reproducer. +/// \param WriteOnStdErr if true, also emit the reproducer to stderr on stop. +inline std::string StartTracing(bool WriteOnStdErr = true) { + if (!TraceInfo::TheTraceInfo) + InitTracing(); + return TraceInfo::TheTraceInfo->StartRegion(WriteOnStdErr); +} + +/// End the traced region and write the reproducer file containing only the +/// calls made between StartTracing() and this call. +inline void StopTracing(const std::string& Version = "") { + if (TraceInfo::TheTraceInfo) + TraceInfo::TheTraceInfo->StopRegion(Version); +} + +/// Marks a function parameter as an output container (e.g. std::vector& +/// that the function fills). Constructed via the INTEROP_OUT(var) macro. +/// +/// Purpose: +/// - Excluded from the reproducer's argument list (it's an output, not input). +/// - If the container holds pointers, its elements are registered as handles +/// at trace-region exit so later calls can reference them by name. +/// +/// The container type is erased at construction via MakeOutParam() so that +/// all downstream code (ReproBuffer, TraceRegion) works with a single +/// concrete type — no template specializations or detection traits needed. +struct OutParam { + /// Callback that registers the container's pointer elements as handles. + /// Null when the container doesn't hold pointers (e.g. vector). + std::function RegisterHandles; +}; + +/// Create an OutParam for any container. Only sets up handle registration +/// when the container's value_type is a pointer. +template OutParam MakeOutParam(const Container& C) { + OutParam OP; + using Value = typename Container::value_type; + if constexpr (std::is_pointer_v) { + OP.RegisterHandles = [&C](TraceInfo& TI) { + for (const auto& Elem : C) + TI.getOrRegisterHandle(reinterpret_cast(Elem)); + }; + } + return OP; +} + +/// Internal helper to stringify arguments into a C++ call format. +struct ReproBuffer { + llvm::SmallString<128> Buffer; + llvm::raw_svector_ostream OS; + + ReproBuffer() : OS(Buffer) {} + + // Opaque handle pointers — resolved to their registered name. + void append(void* p) { OS << TraceInfo::TheTraceInfo->lookupHandle(p); } + + // Strings — quoted. + void append(const char* s) { OS << "\"" << s << "\""; } + void append(const std::string& s) { OS << "\"" << s << "\""; } + + // Numeric types — printed directly. + void append(bool v) { OS << (v ? "true" : "false"); } + void append(int v) { OS << v; } + void append(unsigned v) { OS << v; } + void append(long v) { OS << v; } + void append(unsigned long v) { OS << v; } + void append(long long v) { OS << v; } + void append(unsigned long long v) { OS << v; } + void append(double d) { OS << llvm::formatv("{0:f}", d); } + void append(float f) { OS << llvm::formatv("{0:f}", f); } + + // Containers — not meaningfully printable; emit a placeholder. + template void append(const std::vector&) { OS << "{...}"; } + + // Anything else we haven't accounted for. + template void append(const T&) { OS << "?"; } + + /// Format a comma-separated argument list, skipping OutParam entries. + template void format(Args&&... args) { + bool first = true; + auto appendOne = [&](auto&& val) { + if constexpr (!std::is_same_v, OutParam>) { + if (!first) + OS << ", "; + first = false; + append(std::forward(val)); + } + }; + (appendOne(std::forward(args)), ...); + } +}; + +/// Holds all the data that is only needed when tracing is active. +/// Heap-allocated only when TheTraceInfo != nullptr, so disabled tracing +/// pays zero cost beyond a single pointer + bool on the stack. +struct TraceData { + const char* Name; + llvm::SmallString<128> ArgStr; + void* Result = nullptr; + bool HasPtrResult = false; + double StartTime = 0; + bool Returned = false; + llvm::SmallVector, 2> OutCallbacks; +}; + +class TraceRegion { + std::unique_ptr m_Data; + + // Capture an OutParam's handle-registration callback; ignore everything else. + void captureArg(OutParam&& op) { + if (op.RegisterHandles) + m_Data->OutCallbacks.push_back(std::move(op.RegisterHandles)); + } + template void captureArg(T&&) {} + +public: + template TraceRegion(const char* Name, Args&&... args) { + if (!TraceInfo::TheTraceInfo) + return; + m_Data = std::make_unique(); + m_Data->Name = Name; + (captureArg(std::forward(args)), ...); + if constexpr (sizeof...(args) > 0) { + ReproBuffer RB; + RB.format(std::forward(args)...); + m_Data->ArgStr = RB.Buffer; + } + TraceInfo& TI = *TraceInfo::TheTraceInfo; + TI.pushTimer(&TI.getTimer(Name)); + m_Data->StartTime = llvm::TimeRecord::getCurrentTime(false).getWallTime(); + } + + ~TraceRegion() { + if (!m_Data) + return; + + if (!m_Data->Returned) { + llvm::errs() << "ERROR: Function '" << m_Data->Name + << "' exited without calling INTEROP_RETURN!\n"; + assert( + m_Data->Returned && + "Unannotated exit branch detected: use `return INTEROP_RETURN(...)`"); + } + + auto EndTime = llvm::TimeRecord::getCurrentTime(false).getWallTime(); + auto Dur = static_cast((EndTime - m_Data->StartTime) * 1e9); + TraceInfo& TI = *TraceInfo::TheTraceInfo; + TI.popTimer(); + + // Register out-param handles now that the function has filled them. + for (auto& cb : m_Data->OutCallbacks) + cb(TI); + + std::string VarPart; + if (m_Data->Result) { + bool isNew = TI.lookupHandle(m_Data->Result) == "nullptr"; + std::string HandleName = TI.getOrRegisterHandle(m_Data->Result); + VarPart = + llvm::formatv(isNew ? "auto {0} = " : "/*{0}*/ ", HandleName).str(); + } else if (m_Data->HasPtrResult) { + VarPart = "/*nullptr*/ "; + } + + std::string Call = llvm::formatv(" {0}Cpp::{1}({2}); // [{3} ns]", VarPart, + m_Data->Name, m_Data->ArgStr, Dur); + + // Store in log for the reproducer file. + TI.appendToLog(Call); + + m_Data.reset(); + } + + TraceRegion(const TraceRegion&) = delete; + TraceRegion& operator=(const TraceRegion&) = delete; + TraceRegion(TraceRegion&&) noexcept = default; + TraceRegion& operator=(TraceRegion&&) = delete; + + [[nodiscard]] bool isActive() const { return m_Data != nullptr; } + + /// Record a non-void return value. Tracks pointer results for the + /// reproducer's handle chain (e.g. auto v1 = Cpp::GetScope(...)). + template T record(T val) { + if (!m_Data) + return val; + m_Data->Returned = true; + if constexpr (std::is_pointer_v) { + m_Data->HasPtrResult = true; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + m_Data->Result = const_cast(static_cast(val)); + } else if constexpr (std::is_null_pointer_v) { + m_Data->HasPtrResult = true; + } + return val; + } + + void recordVoid() { + if (m_Data) + m_Data->Returned = true; + } + + /// Count parameters from a __PRETTY_FUNCTION__ / __FUNCSIG__ string. + /// Counts top-level commas between the outermost '(' and ')' of the + /// function signature, handling nested <>, (), and [] correctly. + static unsigned countParams(const char* sig) { + // Find the first '(' — that's the start of the parameter list. + const char* p = sig; + while (*p && *p != '(') + ++p; + if (!*p) + return 0; + ++p; // skip '(' + + // Skip whitespace. + while (*p == ' ') + ++p; + if (*p == ')') + return 0; // empty parameter list + + // MSVC's __FUNCSIG__ uses (void) for no-parameter functions. + if (p[0] == 'v' && p[1] == 'o' && p[2] == 'i' && p[3] == 'd' && + (p[4] == ')' || (p[4] == ' ' && p[5] == ')'))) + return 0; + + unsigned count = 1; // at least one parameter + int depth = 0; // nesting depth for <>, (), [] + for (; *p; ++p) { + switch (*p) { + case '<': + case '(': + case '[': + ++depth; + break; + case '>': + case ')': + case ']': + if (depth > 0) + --depth; + else + return count; // closing ')' of the parameter list + break; + case ',': + if (depth == 0) + ++count; + break; + default: + break; + } + } + return count; + } + + /// Helper to allow INTEROP_TRACE() to work with zero or more arguments + /// without relying on non-standard ##__VA_ARGS__ comma elision. + struct Proxy { + const char* Name; + const char* Sig; + template TraceRegion operator()(Args&&... args) { +#ifndef NDEBUG + unsigned expected = countParams(Sig); + unsigned actual = sizeof...(Args); + if (expected != actual) { + llvm::errs() << "ERROR: INTEROP_TRACE argument count mismatch in '" + << Name << "': function has " << expected + << " parameter(s) but INTEROP_TRACE received " << actual + << " argument(s).\n" + << " Signature: " << Sig << "\n"; + assert(expected == actual && + "INTEROP_TRACE argument count does not match function " + "parameters. Update the INTEROP_TRACE call."); + } +#endif + return TraceRegion(Name, std::forward(args)...); + } + }; +}; + +} // namespace Tracing +} // namespace CppInterOp + +#ifdef _MSC_VER +#define INTEROP_FUNC_SIG __FUNCSIG__ +#else +#define INTEROP_FUNC_SIG __PRETTY_FUNCTION__ +#endif + +#define INTEROP_TRACE(...) \ + CppInterOp::Tracing::TraceRegion _TR = \ + CppInterOp::Tracing::TraceRegion::Proxy{__func__, \ + INTEROP_FUNC_SIG}(__VA_ARGS__) + +#define INTEROP_RETURN(Val) _TR.record(Val) +#define INTEROP_VOID_RETURN() (_TR.recordVoid()) + +#define INTEROP_OUT(Var) CppInterOp::Tracing::MakeOutParam(Var) + +#endif // CPPINTEROP_TRACING_H diff --git a/interpreter/CppInterOp/lib/CppInterOp/exports.ld b/interpreter/CppInterOp/lib/CppInterOp/exports.ld index 67b50dae463f9..762a463096e68 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/exports.ld +++ b/interpreter/CppInterOp/lib/CppInterOp/exports.ld @@ -54,3 +54,4 @@ -Wl,--export=_ZN4llvm15SmallVectorBaseIjE8set_sizeEm -Wl,--export=_ZN5clang11Interpreter6createENSt3__210unique_ptrINS_16CompilerInstanceENS1_14default_deleteIS3_EEEENS2_IN4llvm3orc12LLJITBuilderENS4_IS9_EEEE -Wl,--export=_ZNK5clang13CXXRecordDecl19isInjectedClassNameEv +-Wl,--export=_ZNK5clang8QualType11getAsStringERKNS_14PrintingPolicyE diff --git a/interpreter/CppInterOp/unittests/CMakeLists.txt b/interpreter/CppInterOp/unittests/CMakeLists.txt index 295f1b0a9f182..5faf6657ad487 100644 --- a/interpreter/CppInterOp/unittests/CMakeLists.txt +++ b/interpreter/CppInterOp/unittests/CMakeLists.txt @@ -41,6 +41,13 @@ function(add_cppinterop_unittest name) ${ARGN} ) add_executable(${name} EXCLUDE_FROM_ALL ${ARG_UNPARSED_ARGUMENTS}) + if(NOT LLVM_ENABLE_RTTI) + if(MSVC) + target_compile_options(${name} PRIVATE "/GR-") + else() + target_compile_options(${name} PRIVATE "-fno-rtti") + endif() + endif() add_dependencies(CppInterOpUnitTests ${name}) target_include_directories(${name} PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${GTEST_INCLUDE_DIR}) set_property(TARGET ${name} PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt b/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt index 1d6ccd3e86548..6ef5b3ceed60a 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt +++ b/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt @@ -98,6 +98,24 @@ add_cppinterop_unittest(DynamicLibraryManagerTests ${EXTRA_TEST_SOURCE_FILES} ) +add_cppinterop_unittest(TracingTests + TracingTests.cpp + Utils.cpp + ${EXTRA_TEST_SOURCE_FILES} +) +target_compile_definitions(TracingTests PRIVATE + "CPPINTEROP_DIR=\"${CMAKE_SOURCE_DIR}\"" +) + +if(EMSCRIPTEN) + target_compile_options(TracingTests + PUBLIC ${CPPINTEROP_COMMON_WASM_TEST_COMPILE_FLAGS} + ) + target_link_options(TracingTests + PUBLIC ${CPPINTEROP_COMMON_WASM_TEST_LINK_FLAGS} + ) +endif() + if(EMSCRIPTEN) set(TEST_SHARED_LIBRARY_PATH "${CMAKE_CURRENT_BINARY_DIR}/bin/$/") string(REPLACE "@" "@@" ESCAPED_TEST_SHARED_LIBRARY_PATH "${TEST_SHARED_LIBRARY_PATH}") diff --git a/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp index 51652a5c6594b..f513d1fab5e90 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp @@ -25,6 +25,7 @@ #include "gtest/gtest.h" #include +#include using ::testing::StartsWith; @@ -108,7 +109,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_DeleteInterpreter) { EXPECT_EQ(I3, Cpp::GetInterpreter()) << "I3 is not active"; - EXPECT_TRUE(Cpp::DeleteInterpreter(nullptr)); + EXPECT_TRUE(Cpp::DeleteInterpreter(/*I=*/nullptr)); EXPECT_EQ(I2, Cpp::GetInterpreter()); auto* I4 = reinterpret_cast(static_cast(~0U)); @@ -456,3 +457,101 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_ExternalInterpreter) { delete ExtInterp; #endif } + +// Verify the basic crash banner and Active Interpreter reporting +#ifdef GTEST_HAS_DEATH_TEST +TYPED_TEST(CPPINTEROP_TEST_MODE, SignalHandler_BasicBanner) { + // Ensure a clean registry for each JIT configuration + + // FIXME: Uncomment after resolving compiler-research/CppInterOp#887 + + // while (Cpp::GetInterpreter()) + // Cpp::DeleteInterpreter(/*I=*/nullptr); + + // EXPECT_FALSE(Cpp::GetInterpreter()) << "Failed to delete all interpreters"; + + // // Create an interpreter (this calls RegisterInterpreter internally) + // TInterp_t I = TestFixture::CreateInterpreter(); + // ASSERT_NE(I, nullptr); + + // We expect the banner to appear in stderr when the process dies + std::string ExpectedMsg = "CppInterOp CRASH DETECTED"; +#ifdef _WIN32 + // FIXME: Windows says 'Actual msg:' without maybe capturing the message. + ExpectedMsg = ""; +#endif //_WIN32 + + EXPECT_DEATH( + { + // Trigger a synchronous signal + raise(SIGABRT); + }, + ExpectedMsg); +} +#endif // GTEST_HAS_DEATH_TEST + +// Verify that the handler correctly lists multiple interpreters +#ifdef GTEST_HAS_DEATH_TEST +TYPED_TEST(CPPINTEROP_TEST_MODE, SignalHandler_MultipleInterpreters) { + ASSERT_NE(TestFixture::CreateInterpreter(), nullptr); + ASSERT_NE(TestFixture::CreateInterpreter(), nullptr); + + // The handler iterates through the deque and prints the pointers + + // We check for the "Active Interpreters:" header and the list format + std::string ExpectedMsg = "Active Interpreters:.*- 0x"; +#ifdef _WIN32 + // FIXME: Windows says 'Actual msg:' without maybe capturing the message. + ExpectedMsg = ""; +#endif //_WIN32 + + EXPECT_DEATH({ raise(SIGSEGV); }, ExpectedMsg); +} +#endif // GTEST_HAS_DEATH_TEST + +#ifndef EMSCRIPTEN +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_WrapperCacheIsPerInterpreter) { + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; + + // Create first interpreter: add(a,b) returns a + b. + auto* I1 = TestFixture::CreateInterpreter(); + ASSERT_NE(I1, nullptr); + Cpp::ActivateInterpreter(I1); + Cpp::Declare("int add(int a, int b) { return a + b; }" DFLT_FALSE); + auto* AddDecl1 = Cpp::GetNamed("add" DFLT_NULLPTR); + ASSERT_NE(AddDecl1, nullptr); + + Cpp::Declare("#include " DFLT_FALSE); // Needed by JitCall + auto JC1 = Cpp::MakeFunctionCallable(AddDecl1); + ASSERT_TRUE(JC1.isValid()); + + int a1 = 3, b1 = 4, r1 = 0; + void* args1[] = {&a1, &b1}; + JC1.Invoke(&r1, {args1, 2}); + EXPECT_EQ(r1, 7); + + // Create second interpreter: add(a,b) returns a * b. + auto* I2 = TestFixture::CreateInterpreter(); + ASSERT_NE(I2, nullptr); + Cpp::ActivateInterpreter(I2); + Cpp::Declare("int add(int a, int b) { return a * b; }" DFLT_FALSE); + auto* AddDecl2 = Cpp::GetNamed("add" DFLT_NULLPTR); + ASSERT_NE(AddDecl2, nullptr); + + Cpp::Declare("#include " DFLT_FALSE); // Needed by JitCall + auto JC2 = Cpp::MakeFunctionCallable(AddDecl2); + ASSERT_TRUE(JC2.isValid()); + + // Delete the first interpreter. + EXPECT_TRUE(Cpp::DeleteInterpreter(I1)); + + // The second interpreter's wrapper must still work and must use its own + // implementation (multiply), not a stale cache entry from deleted I1. + int a2 = 3, b2 = 4, r2 = 0; + void* args2[] = {&a2, &b2}; + JC2.Invoke(&r2, {args2, 2}); + EXPECT_EQ(r2, 12) << "Wrapper should use I2's implementation (multiply), " + "not a stale cache from deleted I1"; +} +#endif // !EMSCRIPTEN diff --git a/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp index 3aece27b73000..988a15e735d32 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp @@ -4,19 +4,17 @@ #include "clang-c/CXCppInterOp.h" #include "clang/AST/ASTContext.h" -#include "clang/Basic/Version.h" -#include "clang/Frontend/CompilerInstance.h" -#include "clang/Sema/Sema.h" - #include "clang/AST/ASTDumper.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/GlobalDecl.h" +#include "clang/AST/Type.h" +#include "clang/Basic/Version.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Sema/Sema.h" #include "llvm/Support/Valgrind.h" -#include "clang-c/CXCppInterOp.h" - #include "gtest/gtest.h" #include @@ -25,6 +23,111 @@ using namespace TestUtils; using namespace llvm; using namespace clang; +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetTypeOfBuiltins) { + // Structural check: Ensure every valid BuiltinType kind can be resolved + TestFixture::CreateInterpreter(); + ASTContext& C = Interp->getCI()->getASTContext(); // for brevity + +#define BUILTIN_TYPE(Id, SingletonId) \ + { \ + QualType QT = C.SingletonId; \ + if (!QT.isNull()) { \ + std::string name = QT.getAsString(C.getPrintingPolicy()); \ + if (name[0] != '<' && !QT->isPlaceholderType()) { \ + const auto* FoundTy = Cpp::GetType(name); \ + EXPECT_TRUE(FoundTy) << "Failed to find builtin: '" << name << "'"; \ + if (FoundTy) { \ + EXPECT_EQ(FoundTy, QT.getAsOpaquePtr()) \ + << "Type mismatch for '" << name << "'"; \ + EXPECT_TRUE(Cpp::IsBuiltin(FoundTy)); \ + } \ + } \ + } \ + } +#include "clang/AST/BuiltinTypes.def" +#undef BUILTIN_TYPE + + auto Verify = [&](const char* Name, QualType Expected) { + void* Found = Cpp::GetType(Name); + EXPECT_EQ(Found, Expected.getAsOpaquePtr()) << "Failed for: " << Name; + }; + + // --- Integer Variations --- + Verify("int", C.IntTy); + Verify("signed int", C.IntTy); + Verify("signed", C.IntTy); + Verify("int signed", C.IntTy); + + // --- Short Variations --- + Verify("short", C.ShortTy); + Verify("short int", C.ShortTy); + Verify("signed short", C.ShortTy); + Verify("signed short int", C.ShortTy); + Verify("int short signed", C.ShortTy); + + // --- Long Variations --- + Verify("long", C.LongTy); + Verify("long int", C.LongTy); + Verify("signed long", C.LongTy); + Verify("signed long int", C.LongTy); + Verify("int long", C.LongTy); + + // --- Long Long Variations --- + Verify("long long", C.LongLongTy); + Verify("long long int", C.LongLongTy); + Verify("signed long long", C.LongLongTy); + Verify("signed long long int", C.LongLongTy); + Verify("int long long signed", C.LongLongTy); + + // --- Unsigned Variations --- + Verify("unsigned", C.UnsignedIntTy); + Verify("unsigned int", C.UnsignedIntTy); + Verify("int unsigned", C.UnsignedIntTy); + + Verify("unsigned short", C.UnsignedShortTy); + Verify("unsigned short int", C.UnsignedShortTy); + Verify("int short unsigned", C.UnsignedShortTy); + + Verify("unsigned long", C.UnsignedLongTy); + Verify("unsigned long int", C.UnsignedLongTy); + + Verify("unsigned long long", C.UnsignedLongLongTy); + Verify("unsigned long long int", C.UnsignedLongLongTy); + + // --- Character Variations (No 'int' suffix allowed) --- + Verify("char", C.CharTy); + Verify("signed char", C.SignedCharTy); + Verify("unsigned char", C.UnsignedCharTy); + + // Negative check: signed char int is NOT a valid builtin + EXPECT_EQ(Cpp::GetType("signed char int"), nullptr); + + // Maybe these should be considered builtins? + // EXPECT_EQ(Cpp::GetType("size_t"), C.getSizeType().getAsOpaquePtr()); + // EXPECT_EQ(Cpp::GetType("ptrdiff_t"), + // C.getPointerDiffType().getAsOpaquePtr()); + // EXPECT_EQ(Cpp::GetType("intptr_t"), C.getIntPtrType().getAsOpaquePtr()); + // EXPECT_EQ(Cpp::GetType("uintptr_t"), C.getUIntPtrType().getAsOpaquePtr()); + // EXPECT_EQ(Cpp::GetType("size_t"), C.getSizeType().getAsOpaquePtr()); + // EXPECT_EQ(Cpp::GetType("size_t"), C.getSizeType().getAsOpaquePtr()); + + // EXPECT_EQ(Cpp::GetType("wint_t"), C.WIntTy.getAsOpaquePtr()); + // EXPECT_EQ(Cpp::GetType("wchar_t"), C.WCharTy.getAsOpaquePtr()); +} + +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetTypeOfTypedef) { + std::string code = R"( + typedef int Type_t; + )"; + + std::vector Decls; + GetAllTopLevelDecls(code, Decls); + auto Ty = Cpp::GetType("Type_t"); + EXPECT_TRUE(Ty); + EXPECT_TRUE(Ty == Cpp::GetTypeFromScope(Decls[0])); + EXPECT_FALSE(Cpp::GetTypeFromScope(nullptr)); +} + TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_IsEnumScope) { std::vector Decls; std::vector SubDecls; diff --git a/interpreter/CppInterOp/unittests/CppInterOp/TracingTests.cpp b/interpreter/CppInterOp/unittests/CppInterOp/TracingTests.cpp new file mode 100644 index 0000000000000..78f64aa32e997 --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/TracingTests.cpp @@ -0,0 +1,923 @@ +#include "../../lib/CppInterOp/Tracing.h" + +#include "CppInterOp/CppInterOp.h" + +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include +#include +#include +#include + +using namespace CppInterOp::Tracing; +using ::testing::HasSubstr; +using ::testing::Not; + +// Helper: join the full trace log into a single string for matching. +static std::string getFullLog() { + std::string result; + for (const auto& entry : TraceInfo::TheTraceInfo->getLog()) + result += entry + "\n"; + return result; +} + +// --------------------------------------------------------------------------- +// Fixtures +// --------------------------------------------------------------------------- + +class TracingTest : public ::testing::Test { +protected: + void SetUp() override { + if (TraceInfo::TheTraceInfo) + TraceInfo::TheTraceInfo->clear(); + TraceInfo::TheTraceInfo = nullptr; + InitTracing(); + } +}; + +class NoTracingTest : public ::testing::Test { +protected: + void SetUp() override { TraceInfo::TheTraceInfo = nullptr; } +}; + +// --------------------------------------------------------------------------- +// Helpers — small functions that use the macros +// --------------------------------------------------------------------------- + +int UnannotatedFunction(int x) { + INTEROP_TRACE(x); + return x * 2; // Missing INTEROP_RETURN! +} + +int AnnotatedFunction(int x) { + INTEROP_TRACE(x); + return INTEROP_RETURN(x * 2); +} + +void VoidFunc() { + INTEROP_TRACE(); + return INTEROP_VOID_RETURN(); +} + +void FillHandles(std::vector& out) { + INTEROP_TRACE(INTEROP_OUT(out)); + out.push_back((void*)0xA); + out.push_back((void*)0xB); + out.push_back((void*)0xC); + return INTEROP_VOID_RETURN(); +} + +// --------------------------------------------------------------------------- +// Tests: disabled tracing +// --------------------------------------------------------------------------- + +TEST_F(NoTracingTest, ZeroCostWhenDisabled) { + EXPECT_EQ(UnannotatedFunction(5), 10); +} + +TEST_F(NoTracingTest, RespectsEnvVar) { + TraceInfo TI; + EXPECT_FALSE(TI.isEnabled()); +} + +// --------------------------------------------------------------------------- +// Tests: basic tracing +// --------------------------------------------------------------------------- + +TEST_F(TracingTest, DetectUnannotatedBranch) { +#ifndef NDEBUG + EXPECT_DEATH({ UnannotatedFunction(5); }, "Unannotated exit branch detected"); +#endif +} + +// A function where INTEROP_TRACE is missing an argument — simulates a +// developer adding a parameter but forgetting to update the trace call. +void MissingTraceArg(int x, int y) { + INTEROP_TRACE(x); // only passes x, not y + return INTEROP_VOID_RETURN(); +} + +TEST_F(TracingTest, DetectMissingTraceArgument) { +#ifndef NDEBUG + EXPECT_DEATH({ MissingTraceArg(1, 2); }, "argument count mismatch"); +#endif +} + +// A function with an out-param where INTEROP_OUT is forgotten. +void MissingOutParam(int x, std::vector& out) { + INTEROP_TRACE(x); // missing INTEROP_OUT(out) + out.push_back((void*)0x1); + return INTEROP_VOID_RETURN(); +} + +TEST_F(TracingTest, DetectMissingOutParam) { +#ifndef NDEBUG + std::vector v; + EXPECT_DEATH({ MissingOutParam(1, v); }, "argument count mismatch"); +#endif +} + +TEST_F(TracingTest, CountParamsHandlesVoidParamList) { + // MSVC's __FUNCSIG__ uses "(void)" for zero-parameter functions. + // Verify countParams handles both styles correctly. + using TR = CppInterOp::Tracing::TraceRegion; + EXPECT_EQ(TR::countParams("void foo()"), 0U); + EXPECT_EQ(TR::countParams("void foo(void)"), 0U); + EXPECT_EQ(TR::countParams("void *__cdecl CppImpl::GetInterpreter(void)"), 0U); + EXPECT_EQ(TR::countParams("void foo(int)"), 1U); + EXPECT_EQ(TR::countParams("void foo(int, double)"), 2U); + EXPECT_EQ(TR::countParams("void foo(std::vector&)"), 1U); + EXPECT_EQ(TR::countParams("void foo(std::map&)"), 1U); + EXPECT_EQ(TR::countParams("void foo(int, std::vector&)"), 2U); +} + +TEST_F(TracingTest, ValidAnnotatedBranch) { + EXPECT_EQ(AnnotatedFunction(5), 10); +} + +TEST_F(TracingTest, VoidFunctionTracing) { + VoidFunc(); + auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("Cpp::VoidFunc();")); +} + +// --------------------------------------------------------------------------- +// Tests: INTEROP_TRACE with and without arguments (__VA_OPT__ coverage) +// --------------------------------------------------------------------------- + +void NoArgTrace() { + INTEROP_TRACE(); + return INTEROP_VOID_RETURN(); +} + +void WithOutParamTrace(std::vector& out) { + INTEROP_TRACE(INTEROP_OUT(out)); + out.push_back((void*)0xDE); + return INTEROP_VOID_RETURN(); +} + +TEST_F(TracingTest, TraceWithNoArgs) { + NoArgTrace(); + auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("Cpp::NoArgTrace();")); +} + +TEST_F(TracingTest, TraceWithOutParamArg) { + std::vector v; + WithOutParamTrace(v); + auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); + // OutParam should not appear in the arg list. + EXPECT_THAT(output, HasSubstr("Cpp::WithOutParamTrace();")); + EXPECT_EQ(v.size(), 1u); +} + +// --------------------------------------------------------------------------- +// Tests: argument formatting in reproducer output +// --------------------------------------------------------------------------- + +void* FuncReturningHandle() { + INTEROP_TRACE(); + return INTEROP_RETURN((void*)0xBEEF); +} + +void FuncTakingHandle(void* h) { + INTEROP_TRACE(h); + return INTEROP_VOID_RETURN(); +} + +void FuncTakingString(const char* s) { + INTEROP_TRACE(s); + return INTEROP_VOID_RETURN(); +} + +void FuncTakingInt(int x) { + INTEROP_TRACE(x); + return INTEROP_VOID_RETURN(); +} + +void FuncTakingMixed(void* h, const char* s, int x) { + INTEROP_TRACE(h, s, x); + return INTEROP_VOID_RETURN(); +} + +void FuncWithHandleAndOut(void* h, std::vector& out) { + INTEROP_TRACE(h, INTEROP_OUT(out)); + out.push_back((void*)0xF00); + return INTEROP_VOID_RETURN(); +} + +TEST_F(TracingTest, ArgFormattingHandle) { + void* h = FuncReturningHandle(); + FuncTakingHandle(h); + auto output = getFullLog(); + // The handle should be registered as v1, then reused. + EXPECT_THAT(output, HasSubstr("auto v1 = Cpp::FuncReturningHandle()")); + EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingHandle(v1)")); +} + +TEST_F(TracingTest, ArgFormattingString) { + FuncTakingString("hello"); + auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingString(\"hello\")")); +} + +TEST_F(TracingTest, ArgFormattingInt) { + FuncTakingInt(42); + auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingInt(42)")); +} + +TEST_F(TracingTest, ArgFormattingMixed) { + void* h = FuncReturningHandle(); + FuncTakingMixed(h, "world", 99); + auto output = getFullLog(); + EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingMixed(v1, \"world\", 99)")); +} + +TEST_F(TracingTest, ArgFormattingOutParamSkipped) { + // OutParam should not appear in the formatted args, + // but regular args before it should. + void* h = FuncReturningHandle(); + std::vector out; + FuncWithHandleAndOut(h, out); + auto output = getFullLog(); + // Should show only the handle arg, not the OutParam. + EXPECT_THAT(output, HasSubstr("Cpp::FuncWithHandleAndOut(v1)")); +} + +// Test: when two functions return the same pointer, the second should not +// redeclare the handle variable — it should emit a comment instead. +void* ReturnSameHandle() { + INTEROP_TRACE(); + return INTEROP_RETURN((void*)0xDA1E); +} + +TEST_F(TracingTest, DuplicateHandleNotRedeclared) { + void* h1 = ReturnSameHandle(); // first call → "auto v1 = ..." + void* h2 = ReturnSameHandle(); // same pointer → "/*v1*/ ..." + (void)h1; + (void)h2; + auto output = getFullLog(); + + // Count occurrences of "auto v1 =" + size_t firstDecl = output.find("auto v1 ="); + ASSERT_NE(firstDecl, std::string::npos) << "First call should declare v1"; + + // The second occurrence should NOT be "auto v1 =" but "/*v1*/" + size_t secondCall = output.find("Cpp::ReturnSameHandle()", firstDecl + 1); + ASSERT_NE(secondCall, std::string::npos) << "Should have a second call"; + + // There should be exactly one "auto v1 =" in the output. + size_t secondDecl = output.find("auto v1 =", firstDecl + 1); + EXPECT_EQ(secondDecl, std::string::npos) + << "Handle v1 should not be redeclared. Output:\n" + << output; + + // The second call should have the comment form. + EXPECT_THAT(output, HasSubstr("/*v1*/")); +} + +// Test: when a pointer-returning function returns nullptr, the output +// should annotate it as /*nullptr*/ so the reader knows. +void* ReturnNull() { + INTEROP_TRACE(); + return INTEROP_RETURN(nullptr); +} + +TEST_F(TracingTest, NullptrReturnAnnotated) { + ReturnNull(); + auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("/*nullptr*/")); + EXPECT_THAT(output, HasSubstr("Cpp::ReturnNull()")); +} + +// Test: non-void, non-pointer returns (int, bool, size_t) should NOT +// get any handle annotation. +int ReturnInt() { + INTEROP_TRACE(); + return INTEROP_RETURN(42); +} + +TEST_F(TracingTest, NonPointerReturnNoAnnotation) { + ReturnInt(); + auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, Not(HasSubstr("auto"))); + EXPECT_THAT(output, Not(HasSubstr("nullptr"))); + EXPECT_THAT(output, HasSubstr("Cpp::ReturnInt()")); +} + +// Test: non-streamable types (e.g. std::vector) are formatted as placeholders. +void FuncTakingVector(const std::vector& args) { + INTEROP_TRACE(args); + return INTEROP_VOID_RETURN(); +} + +TEST_F(TracingTest, ArgFormattingNonStreamable) { + std::vector v = {"a", "b"}; + FuncTakingVector(v); + auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); + // Non-streamable types should get a {...} placeholder. + EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingVector({...})")); +} + +// --------------------------------------------------------------------------- +// Tests: handle registry +// --------------------------------------------------------------------------- + +TEST_F(TracingTest, HandleRegistry) { + TraceInfo& TI = *TraceInfo::TheTraceInfo; + void* ptr1 = (void*)0x1234; + void* ptr2 = (void*)0x5678; + + std::string v1 = TI.getOrRegisterHandle(ptr1); + EXPECT_FALSE(v1.empty()); + EXPECT_EQ(v1[0], 'v'); + + EXPECT_EQ(TI.getOrRegisterHandle(ptr1), v1); + EXPECT_EQ(TI.lookupHandle(ptr1), v1); + + std::string v2 = TI.getOrRegisterHandle(ptr2); + EXPECT_NE(v1, v2); + EXPECT_EQ(TI.lookupHandle(ptr2), v2); + + EXPECT_EQ(TI.lookupHandle(nullptr), "nullptr"); + + // getOrRegisterHandle(nullptr) should return empty, not register. + EXPECT_EQ(TI.getOrRegisterHandle(nullptr), ""); +} + +// --------------------------------------------------------------------------- +// Tests: timer stacking +// --------------------------------------------------------------------------- + +TEST_F(TracingTest, TimerStackPushPop) { + TraceInfo& TI = *TraceInfo::TheTraceInfo; + + llvm::Timer& T1 = TI.getTimer("FuncA"); + llvm::Timer& T2 = TI.getTimer("FuncB"); + + // Push T1 — it should be running. + TI.pushTimer(&T1); + EXPECT_TRUE(T1.isRunning()); + + // Push T2 — T1 paused, T2 running (covers line 75: nested stop). + TI.pushTimer(&T2); + EXPECT_FALSE(T1.isRunning()); + EXPECT_TRUE(T2.isRunning()); + + // Pop T2 — T1 resumes (covers line 86: restart parent). + TI.popTimer(); + EXPECT_TRUE(T1.isRunning()); + EXPECT_FALSE(T2.isRunning()); + + // Pop T1 — nothing running. + TI.popTimer(); + EXPECT_FALSE(T1.isRunning()); + + // Pop on empty stack — should not crash (covers line 82). + TI.popTimer(); +} + +TEST_F(TracingTest, ClearWithRunningTimers) { + TraceInfo& TI = *TraceInfo::TheTraceInfo; + + llvm::Timer& T1 = TI.getTimer("RunningTimer"); + TI.pushTimer(&T1); + EXPECT_TRUE(T1.isRunning()); + + // clear() should stop running timers (covers lines 129-130). + TI.clear(); + EXPECT_FALSE(T1.isRunning()); +} + +// --------------------------------------------------------------------------- +// Tests: StartTracing activates tracing if not yet active +// --------------------------------------------------------------------------- + +TEST(StartTracingActivation, ActivatesIfNotActive) { + // Save and clear TheTraceInfo to simulate tracing not being active. + auto* saved = TraceInfo::TheTraceInfo; + TraceInfo::TheTraceInfo = nullptr; + + // StartTracing should call InitTracing (covers line 151). + std::string Path = CppInterOp::Tracing::StartTracing(/*WriteOnStdErr=*/false); + ASSERT_NE(TraceInfo::TheTraceInfo, nullptr); + ASSERT_FALSE(Path.empty()); + + CppInterOp::Tracing::StopTracing(); + llvm::sys::fs::remove(Path); + + // Restore so other tests aren't affected. + TraceInfo::TheTraceInfo = saved; +} + +// --------------------------------------------------------------------------- +// Tests: countParams edge cases +// --------------------------------------------------------------------------- + +TEST_F(TracingTest, CountParamsEdgeCases) { + using TR = CppInterOp::Tracing::TraceRegion; + + // No parenthesis at all (covers line 333). + EXPECT_EQ(TR::countParams("nosig"), 0U); + + // Spaces after opening paren (covers line 338). + EXPECT_EQ(TR::countParams("void foo( int x )"), 1U); + EXPECT_EQ(TR::countParams("void foo( )"), 0U); +} + +// --------------------------------------------------------------------------- +// Tests: handle chaining across calls +// --------------------------------------------------------------------------- + +TEST_F(TracingTest, ChainedReproducerLogic) { + { + void* h; + { + INTEROP_TRACE(); + h = (void*)0x111; + INTEROP_RETURN(h); + } + { + INTEROP_TRACE(); + INTEROP_RETURN(0); + } + } + auto output = getFullLog(); + EXPECT_THAT(output, HasSubstr("auto v1 =")); +} + +// --------------------------------------------------------------------------- +// Tests: out-parameters +// --------------------------------------------------------------------------- + +TEST_F(TracingTest, OutParamRegistersHandles) { + TraceInfo& TI = *TraceInfo::TheTraceInfo; + + std::vector results; + FillHandles(results); + + // The three handles pushed inside FillHandles should now be registered. + EXPECT_NE(TI.lookupHandle((void*)0xA), "nullptr"); + EXPECT_NE(TI.lookupHandle((void*)0xB), "nullptr"); + EXPECT_NE(TI.lookupHandle((void*)0xC), "nullptr"); +} + +TEST_F(TracingTest, OutParamHandlesUsableInLaterCalls) { + TraceInfo& TI = *TraceInfo::TheTraceInfo; + + std::vector results; + FillHandles(results); + + // The handle should have been registered by the OutParam mechanism. + std::string handleName = TI.lookupHandle(results[0]); + EXPECT_EQ(handleName[0], 'v'); +} + +TEST_F(NoTracingTest, OutParamNoOpWhenDisabled) { + std::vector results; + // Should not crash when tracing is disabled. + FillHandles(results); + EXPECT_EQ(results.size(), 3u); +} + +// Out-param with non-pointer elements (e.g., std::vector) +// should compile and work — handles are only registered for pointer types. +void FillStrings(std::vector& out) { + INTEROP_TRACE(INTEROP_OUT(out)); + out.push_back("hello"); + out.push_back("world"); + return INTEROP_VOID_RETURN(); +} + +TEST_F(TracingTest, OutParamNonPointerElementType) { + std::vector results; + FillStrings(results); + auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); + + EXPECT_EQ(results.size(), 2u); + EXPECT_EQ(results[0], "hello"); + EXPECT_EQ(results[1], "world"); + EXPECT_THAT(output, HasSubstr("Cpp::FillStrings();")); +} + +TEST_F(NoTracingTest, OutParamNonPointerDisabled) { + std::vector results; + FillStrings(results); + EXPECT_EQ(results.size(), 2u); +} + +// --------------------------------------------------------------------------- +// Tests: writeToFile +// --------------------------------------------------------------------------- + +TEST_F(TracingTest, WriteToFileProducesValidReproducer) { + // Generate some trace entries. + { + INTEROP_TRACE(); + INTEROP_RETURN((void*)0xF00); + } + { + INTEROP_TRACE(); + return INTEROP_VOID_RETURN(); + } + + TraceInfo& TI = *TraceInfo::TheTraceInfo; + std::string Path = TI.writeToFile(); + ASSERT_FALSE(Path.empty()); + + // Read the file back. + std::ifstream ifs(Path); + ASSERT_TRUE(ifs.good()); + std::string content((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + + EXPECT_THAT(content, HasSubstr("#include ")); + EXPECT_THAT(content, HasSubstr("void reproducer()")); + EXPECT_THAT(content, HasSubstr("Cpp::")); + + // Clean up. + llvm::sys::fs::remove(Path); +} + +TEST_F(TracingTest, WriteToFileContainsOutParamCalls) { + std::vector results; + FillHandles(results); + + TraceInfo& TI = *TraceInfo::TheTraceInfo; + std::string Path = TI.writeToFile(); + ASSERT_FALSE(Path.empty()); + + std::ifstream ifs(Path); + std::string content((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + + EXPECT_THAT(content, HasSubstr("Cpp::FillHandles();")); + + llvm::sys::fs::remove(Path); +} + +// --------------------------------------------------------------------------- +// Tests: reproducer compiles via interpreter +// --------------------------------------------------------------------------- + +#ifndef EMSCRIPTEN +TEST_F(TracingTest, ReproducerCompilesViaInterpreter) { + // Create an interpreter so we can exercise real API calls. + Cpp::CreateInterpreter({}); + + // Verify tracing is active after interpreter creation. + ASSERT_NE(TraceInfo::TheTraceInfo, nullptr) + << "TheTraceInfo was cleared during CreateInterpreter"; + + // Clear any prior trace state so the reproducer only contains our calls. + TraceInfo::TheTraceInfo->clear(); + + // Exercise real API calls that will be recorded. + auto GlobalScope = Cpp::GetGlobalScope(); + ASSERT_NE(GlobalScope, nullptr); + + Cpp::Declare("namespace ReproNS { struct Foo { int x; }; }"); + + auto ReproNS = Cpp::GetScope("ReproNS", GlobalScope); + ASSERT_NE(ReproNS, nullptr); + + auto Foo = Cpp::GetScope("Foo", ReproNS); + ASSERT_NE(Foo, nullptr); + + EXPECT_TRUE(Cpp::IsClass(Foo)); + EXPECT_FALSE(Cpp::IsNamespace(Foo)); + + auto FooType = Cpp::GetTypeFromScope(Foo); + ASSERT_NE(FooType, nullptr); + + Cpp::GetName(Foo); + Cpp::SizeOf(Foo); + + // Write the reproducer. + TraceInfo& TI = *TraceInfo::TheTraceInfo; + std::string Path = TI.writeToFile(); + ASSERT_FALSE(Path.empty()); + + // Read it back. + std::ifstream ifs(Path); + ASSERT_TRUE(ifs.good()); + std::string content((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + ifs.close(); + + // Verify the reproducer has proper structure and content. + EXPECT_THAT(content, HasSubstr("#include ")); + EXPECT_THAT(content, HasSubstr("void reproducer()")); + EXPECT_THAT(content, HasSubstr("Cpp::")); + + // Create a fresh interpreter and #include the reproducer file as-is. + Cpp::CreateInterpreter({}); + Cpp::AddIncludePath(CPPINTEROP_DIR "/include"); + + std::string includeDirective = "#include \"" + Path + "\""; + EXPECT_EQ(Cpp::Process(includeDirective.c_str()), 0) + << "Reproducer failed to compile via #include:\n" + << content; + + // Execute the reproducer in clean interpreter state. + EXPECT_EQ(Cpp::Process("reproducer();"), 0) << "Reproducer failed to execute"; + + llvm::sys::fs::remove(Path); +} +#endif // !EMSCRIPTEN + +// --------------------------------------------------------------------------- +// Tests: JitCall wrapper source and Invoke are logged +// --------------------------------------------------------------------------- + +TEST_F(TracingTest, JitCallWrapperSourceLogged) { + Cpp::CreateInterpreter({}); + ASSERT_NE(TraceInfo::TheTraceInfo, nullptr); + + // Placement new requires . + Cpp::Declare("#include "); + Cpp::Declare("namespace WrapNS { int add(int a, int b) { return a + b; } }"); + auto* Func = + static_cast(Cpp::GetNamed("add", Cpp::GetScope("WrapNS"))); + ASSERT_NE(Func, nullptr); + + TraceInfo::TheTraceInfo->clear(); + + // MakeFunctionCallable triggers make_wrapper which logs the wrapper source. + auto JC = Cpp::MakeFunctionCallable(Func); + ASSERT_TRUE(JC.isValid()); + + auto log = getFullLog(); + EXPECT_THAT(log, HasSubstr("=== Wrapper for")); + EXPECT_THAT(log, HasSubstr("=== End wrapper ===")); + // The wrapper source should contain the function name. + EXPECT_THAT(log, HasSubstr("add")); +} + +#ifndef NDEBUG +TEST_F(TracingTest, JitCallInvokeLogged) { + Cpp::CreateInterpreter({}); + ASSERT_NE(TraceInfo::TheTraceInfo, nullptr); + + Cpp::Declare("#include "); + Cpp::Declare("namespace InvNS { int square(int x) { return x * x; } }"); + auto* Func = + static_cast(Cpp::GetNamed("square", Cpp::GetScope("InvNS"))); + ASSERT_NE(Func, nullptr); + + auto JC = Cpp::MakeFunctionCallable(Func); + ASSERT_TRUE(JC.isValid()); + + // Clear so only the Invoke shows up. + TraceInfo::TheTraceInfo->clear(); + + int arg = 7; + int result = 0; + void* args[] = {&arg}; + JC.Invoke(&result, {args, 1}); + + auto log = getFullLog(); + // ReportInvokeStart should have logged the invoke. + EXPECT_THAT(log, HasSubstr("JitCall::Invoke")); + EXPECT_THAT(log, HasSubstr("square")); + EXPECT_THAT(log, HasSubstr("nargs=1")); + + // And the function should have actually executed. + EXPECT_EQ(result, 49); +} +#endif + +// Verify that log entries include timing annotations. +TEST_F(TracingTest, LogEntriesIncludeTimingAnnotation) { + VoidFunc(); + auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("// [")); + EXPECT_THAT(output, HasSubstr("ns]")); +} + +// --------------------------------------------------------------------------- +// Tests: StartTracing / StopTracing region-based tracing +// --------------------------------------------------------------------------- + +#ifndef EMSCRIPTEN +static std::string ReadFileToString(const std::string& path) { + std::ifstream ifs(path); + return {std::istreambuf_iterator(ifs), + std::istreambuf_iterator()}; +} +// This test reads source files from the build tree via CPPINTEROP_DIR. +TEST_F(TracingTest, StartStopTracingWritesToFile) { + // StartTracing begins recording; StopTracing writes the file. + std::string Path = CppInterOp::Tracing::StartTracing(/*WriteOnStdErr=*/false); + ASSERT_FALSE(Path.empty()); + + // Calls within the region are recorded. + VoidFunc(); + AnnotatedFunction(5); + + CppInterOp::Tracing::StopTracing(); + + // The file should exist and contain the traced calls. + std::string content = ReadFileToString(Path); + ASSERT_FALSE(content.empty()); + EXPECT_THAT(content, HasSubstr("Cpp::VoidFunc()")); + EXPECT_THAT(content, HasSubstr("Cpp::AnnotatedFunction(")); + EXPECT_THAT(content, HasSubstr("#include ")); + + llvm::sys::fs::remove(Path); +} + +TEST_F(TracingTest, OnlyRegionCallsAreRecorded) { + // Calls before StartTracing should not appear. + VoidFunc(); + + std::string Path = CppInterOp::Tracing::StartTracing(/*WriteOnStdErr=*/false); + ASSERT_FALSE(Path.empty()); + + AnnotatedFunction(42); + + CppInterOp::Tracing::StopTracing(); + + // Calls after StopTracing should not appear either. + VoidFunc(); + + std::string content = ReadFileToString(Path); + // Should contain only the call within the region. + EXPECT_THAT(content, HasSubstr("Cpp::AnnotatedFunction(42)")); + // VoidFunc was called before and after — should NOT be in the file. + // Count occurrences of VoidFunc in the reproducer body. + auto bodyStart = content.find("void reproducer()"); + ASSERT_NE(bodyStart, std::string::npos); + std::string body = content.substr(bodyStart); + EXPECT_EQ(body.find("VoidFunc"), std::string::npos) + << "VoidFunc should not appear in the reproducer body:\n" + << body; + + llvm::sys::fs::remove(Path); +} + +TEST_F(TracingTest, StartTracingWithEnvVarNarrowsToRegion) { + // When CPPINTEROP_LOG=1 is set (tracing already active from SetUp), + // StartTracing/StopTracing should still narrow to just the region. + ASSERT_NE(TraceInfo::TheTraceInfo, nullptr); + + // This call is before the region. + VoidFunc(); + + std::string Path = CppInterOp::Tracing::StartTracing(/*WriteOnStdErr=*/false); + ASSERT_FALSE(Path.empty()); + + AnnotatedFunction(7); + + CppInterOp::Tracing::StopTracing(); + + std::string content = ReadFileToString(Path); + auto bodyStart = content.find("void reproducer()"); + ASSERT_NE(bodyStart, std::string::npos); + std::string body = content.substr(bodyStart); + EXPECT_THAT(body, HasSubstr("Cpp::AnnotatedFunction(7)")); + EXPECT_EQ(body.find("VoidFunc"), std::string::npos); + + llvm::sys::fs::remove(Path); +} + +TEST_F(TracingTest, MultipleStartStopRegions) { + // Multiple regions should each produce their own file. + std::string Path1 = + CppInterOp::Tracing::StartTracing(/*WriteOnStdErr=*/false); + AnnotatedFunction(1); + CppInterOp::Tracing::StopTracing(); + + std::string Path2 = + CppInterOp::Tracing::StartTracing(/*WriteOnStdErr=*/false); + AnnotatedFunction(2); + CppInterOp::Tracing::StopTracing(); + + ASSERT_NE(Path1, Path2); + + std::string content1 = ReadFileToString(Path1); + std::string content2 = ReadFileToString(Path2); + + EXPECT_THAT(content1, HasSubstr("Cpp::AnnotatedFunction(1)")); + EXPECT_THAT(content2, HasSubstr("Cpp::AnnotatedFunction(2)")); + + // Each file should only have its own call. + EXPECT_EQ(content1.find("AnnotatedFunction(2)"), std::string::npos); + EXPECT_EQ(content2.find("AnnotatedFunction(1)"), std::string::npos); + + llvm::sys::fs::remove(Path1); + llvm::sys::fs::remove(Path2); +} + +// --------------------------------------------------------------------------- +// Tests: StartTracing WriteOnStdErr option +// --------------------------------------------------------------------------- + +TEST_F(TracingTest, WriteOnStdErrStreamsEntriesImmediately) { + testing::internal::CaptureStderr(); + + // Default is WriteOnStdErr=true — no file is created. + std::string Path = CppInterOp::Tracing::StartTracing(); + EXPECT_TRUE(Path.empty()); + + AnnotatedFunction(99); + + // Capture stderr *before* StopTracing — the entry must already be there. + std::string Mid = testing::internal::GetCapturedStderr(); + EXPECT_THAT(Mid, HasSubstr("Cpp::AnnotatedFunction(99)")) + << "Entry should appear on stderr immediately, not after StopTracing"; + + // Restart capture for the rest. + testing::internal::CaptureStderr(); + VoidFunc(); + CppInterOp::Tracing::StopTracing("test-version"); + std::string Rest = testing::internal::GetCapturedStderr(); + EXPECT_THAT(Rest, HasSubstr("Cpp::VoidFunc()")); +} + +TEST_F(TracingTest, WriteToFileWhenStdErrDisabled) { + std::string Path = CppInterOp::Tracing::StartTracing(/*WriteOnStdErr=*/false); + ASSERT_FALSE(Path.empty()); + + AnnotatedFunction(77); + + CppInterOp::Tracing::StopTracing(); + + // The file should contain the full reproducer. + std::string FileContent = ReadFileToString(Path); + EXPECT_THAT(FileContent, HasSubstr("#include ")); + EXPECT_THAT(FileContent, HasSubstr("Cpp::AnnotatedFunction(77)")); + + llvm::sys::fs::remove(Path); +} + +// --------------------------------------------------------------------------- +// Tests: all CPPINTEROP_API functions must have INTEROP_TRACE +// --------------------------------------------------------------------------- + +TEST(TracingCoverageTest, AllPublicAPIsAreTraced) { + // 1. Parse the header to extract all CPPINTEROP_API function names. + std::string Header = + ReadFileToString(CPPINTEROP_DIR "/include/CppInterOp/CppInterOp.h"); + ASSERT_FALSE(Header.empty()) << "Could not read CppInterOp.h"; + + std::regex ApiRe(R"(CPPINTEROP_API\s+[\w:<>\s\*&]+?\s+(\w+)\s*\()"); + std::set ApiNames; + for (std::sregex_iterator it(Header.begin(), Header.end(), ApiRe), end; + it != end; ++it) { + ApiNames.insert((*it)[1].str()); + } + + // Exclude JitCall member functions declared with CPPINTEROP_API — they + // are class members, not free API functions we annotate. + ApiNames.erase("AreArgumentsValid"); + ApiNames.erase("ReportInvokeStart"); + ApiNames.erase("ReportInvokeEnd"); + + ASSERT_GT(ApiNames.size(), 100u) + << "Suspiciously few API functions found — regex may be broken"; + + // 2. For each API name, find its definition in the implementation and + // verify INTEROP_TRACE appears shortly after the opening brace. + std::string Impl = + ReadFileToString(CPPINTEROP_DIR "/lib/CppInterOp/CppInterOp.cpp"); + ASSERT_FALSE(Impl.empty()) << "Could not read CppInterOp.cpp"; + + std::vector Missing; + for (const auto& Name : ApiNames) { + std::string Pattern = Name + "("; + size_t Pos = 0; + bool Found = false; + while ((Pos = Impl.find(Pattern, Pos)) != std::string::npos) { + // Find the opening brace of the function body. + size_t BracePos = Impl.find('{', Pos); + if (BracePos == std::string::npos || BracePos - Pos > 300) { + Pos += Pattern.size(); + continue; + } + + // Check the next ~200 chars after '{' for INTEROP_TRACE. + std::string Body = + Impl.substr(BracePos, std::min(200, Impl.size() - BracePos)); + if (Body.find("INTEROP_TRACE") != std::string::npos) { + Found = true; + break; + } + Pos += Pattern.size(); + } + if (!Found) + Missing.push_back(Name); + } + + if (!Missing.empty()) { + std::string Msg = "Public API functions missing INTEROP_TRACE:\n"; + for (const auto& Name : Missing) + Msg += " - " + Name + "\n"; + Msg += "Add INTEROP_TRACE() to each function listed above.\n"; + FAIL() << Msg; + } +} +#endif // !EMSCRIPTEN From e6e31528881c776eed348524b24662c29e62baa5 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 21 Apr 2026 11:12:07 +0200 Subject: [PATCH 2/3] [roottest] Make TExceptionHandlerTests into a separate gtest executable --- core/base/test/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/base/test/CMakeLists.txt b/core/base/test/CMakeLists.txt index ecc8e6161e57b..3297a08c513ea 100644 --- a/core/base/test/CMakeLists.txt +++ b/core/base/test/CMakeLists.txt @@ -7,7 +7,6 @@ ROOT_ADD_GTEST(CoreBaseTests TNamedTests.cxx TQObjectTests.cxx - TExceptionHandlerTests.cxx TStringTest.cxx TUrlTest.cxx TBitsTests.cxx @@ -22,6 +21,8 @@ endif() target_compile_options(CoreBaseTests PRIVATE -DEXPECTED_SHARED_LIBRARY_DIR="${localruntimedir}") target_compile_options(CoreBaseTests PRIVATE -DEXPECTED_INCLUDE_DIR="${CMAKE_BINARY_DIR}/include") +ROOT_ADD_GTEST(CoreExceptionHandlerTests TExceptionHandlerTests.cxx LIBRARIES RIO Core) + ROOT_ADD_GTEST(CoreErrorTests TErrorTests.cxx LIBRARIES Core) ROOT_ADD_GTEST(CoreSystemTests TSystemTests.cxx LIBRARIES Core) From 82ea7cddb5ae802bffab6fe27ed15a2eab61a03d Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 21 Apr 2026 11:12:47 +0200 Subject: [PATCH 3/3] [cppinterop] Fix TracingTests can't include CppInterOp.h in non-standalone build --- interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt b/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt index 6ef5b3ceed60a..d672c760c4988 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt +++ b/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt @@ -104,7 +104,7 @@ add_cppinterop_unittest(TracingTests ${EXTRA_TEST_SOURCE_FILES} ) target_compile_definitions(TracingTests PRIVATE - "CPPINTEROP_DIR=\"${CMAKE_SOURCE_DIR}\"" + "CPPINTEROP_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/../../\"" ) if(EMSCRIPTEN)