13#ifndef CPPINTEROP_TRACING_H
14#define CPPINTEROP_TRACING_H
18#if defined(_WIN32) || defined(__CYGWIN__)
19#define CPPINTEROP_TRACE_API __declspec(dllexport)
20#elif defined(__GNUC__)
21#define CPPINTEROP_TRACE_API __attribute__((__visibility__("default")))
23#define CPPINTEROP_TRACE_API
28#include "llvm/ADT/SmallString.h"
29#include "llvm/ADT/SmallVector.h"
30#include "llvm/ADT/StringMap.h"
31#include "llvm/Support/FormatVariadic.h"
32#include "llvm/Support/Timer.h"
33#include "llvm/Support/raw_ostream.h"
43#include <unordered_map>
58 llvm::TimerGroup m_TG;
59 llvm::StringMap<std::unique_ptr<llvm::Timer>> m_Timers;
60 std::vector<llvm::Timer*> m_TimerStack;
62 std::unordered_map<const void*, std::string> m_HandleMap;
63 unsigned m_VarCount = 0;
67 unsigned m_RetCount = 0;
71 unsigned m_OutCount = 0;
75 std::unordered_map<const void*, unsigned> m_OutAliases;
77 std::vector<std::string> m_Log;
78 size_t m_RegionStart = 0;
79 bool m_InRegion =
false;
83 bool m_Dumping =
false;
86 TraceInfo() : m_TG(
"CppInterOp",
"CppInterOp Timing Report") {}
96 auto& T = m_Timers[Name];
98 T = std::make_unique<llvm::Timer>(Name, Name, m_TG);
106 if (!m_TimerStack.empty())
107 m_TimerStack.back()->stopTimer();
108 m_TimerStack.push_back(T);
113 if (m_TimerStack.empty())
115 m_TimerStack.back()->stopTimer();
116 m_TimerStack.pop_back();
117 if (!m_TimerStack.empty())
118 m_TimerStack.back()->startTimer();
124 auto it = m_HandleMap.find(p);
125 if (it != m_HandleMap.end())
127 return m_HandleMap[p] =
"v" + std::to_string(++m_VarCount);
140 auto it = m_HandleMap.find(p);
141 return (it != m_HandleMap.end()) ? it->second :
"";
153 assert(Addr &&
"OutParam without a source address");
154 auto it = m_OutAliases.find(Addr);
155 if (it != m_OutAliases.end())
156 return {it->second,
false};
157 unsigned idx = m_OutCount++;
158 m_OutAliases.emplace(Addr, idx);
167 size_t idx = m_Log.size();
168 m_Log.push_back(line);
169 if (m_InRegion && m_WriteOnStdErr)
170 llvm::errs() << line <<
"\n";
179 const std::vector<std::string>&
getLog()
const {
return m_Log; }
181 return m_Log.empty() ?
"" : m_Log.back();
196 std::string m_RegionPath;
197 bool m_WriteOnStdErr =
false;
203 while (!m_TimerStack.empty()) {
204 m_TimerStack.back()->stopTimer();
205 m_TimerStack.pop_back();
215 m_OutAliases.clear();
275 using Value =
typename Container::value_type;
276 if constexpr (std::is_pointer_v<Value>) {
280 for (
const auto& Elem : C) {
281 const void* P =
static_cast<const void*
>(Elem);
287 TI.
appendToLog(llvm::formatv(
" void* {0} = _out{1}.size() > {2} ? "
288 "_out{1}[{2}] : nullptr;",
309 llvm::raw_svector_ostream
OS;
331 OS <<
"nullptr /*unknown*/";
345 bool NeedsRaw =
false;
347 auto u =
static_cast<unsigned char>(c);
350 if (c ==
'"' || c ==
'\\' || u < 0x20 || u == 0x7f) {
356 OS <<
"R\"CPPI(" << s <<
")CPPI\"";
358 OS <<
'"' << s <<
'"';
361 appendRaw(s ? std::string_view(s) : std::string_view());
366 void append(
bool v) {
OS << (v ?
"true" :
"false"); }
373 void append(
double d) {
OS << llvm::formatv(
"{0:f}", d); }
374 void append(
float f) {
OS << llvm::formatv(
"{0:f}", f); }
378 template <
typename T>
void append(
const std::vector<T>& V) {
381 for (
const auto& E : V) {
395 void append(
const Cpp::TemplateArgInfo& tai) {
396 OS <<
"Cpp::TemplateArgInfo{";
397 append(
static_cast<const void*
>(tai.m_Type));
399 if (tai.m_IntegralValue ==
nullptr)
411 llvm::StringRef s(sig);
414 auto pos = s.find(
"append<");
415 if (pos == llvm::StringRef::npos)
417 s = s.drop_front(pos + 7);
418 for (
auto kw : {
"enum ",
"class ",
"struct "})
419 if (s.consume_front(kw))
421 return s.take_until([](
char c) {
return c ==
'>'; }).str();
425 auto pos = s.find(
"T = ");
426 if (pos == llvm::StringRef::npos)
428 return s.drop_front(pos + 4)
429 .take_until([](
char c) {
return c ==
',' || c ==
';' || c ==
']'; })
433 template <
typename T, std::enable_if_t<std::is_enum_v<T>,
int> = 0>
438 static const std::string TN =
parseEnumName(__PRETTY_FUNCTION__);
440 OS <<
"static_cast<" << TN <<
">("
441 << +
static_cast<std::underlying_type_t<T>
>(v) <<
")";
446 std::enable_if_t<!std::is_enum_v<T> && !std::is_pointer_v<T>,
int> = 0>
455 template <
typename... Args>
456 void format(llvm::ArrayRef<unsigned> OutIndices, Args&&... args) {
459 auto appendOne = [&](
auto&& val) {
460 using V = std::decay_t<
decltype(val)>;
461 if constexpr (std::is_same_v<V, OutParam>) {
462 if (val.IsPointerContainer) {
466 OS <<
"_out" << OutIndices[nextOut++];
467 }
else if (val.IsScalarPointer) {
477 append(std::forward<
decltype(val)>(val));
480 (appendOne(std::forward<Args>(args)), ...);
497 std::is_same_v<T, Cpp::DeclRef> || std::is_same_v<T, Cpp::TypeRef> ||
498 std::is_same_v<T, Cpp::FuncRef> || std::is_same_v<T, Cpp::ObjectRef> ||
499 std::is_same_v<T, Cpp::InterpRef> || std::is_same_v<T, Cpp::ConstDeclRef> ||
500 std::is_same_v<T, Cpp::ConstTypeRef> ||
501 std::is_same_v<T, Cpp::ConstFuncRef>;
547 std::unique_ptr<TraceData> m_Data;
555 if (!m_Data->Nested && op.IsPointerContainer) {
557 m_Data->OutIndices.push_back(idx);
558 m_Data->OutFirstUse.push_back(firstUse);
560 if (op.RegisterHandles)
561 m_Data->OutCallbacks.push_back(std::move(op.RegisterHandles));
563 template <
typename T>
void captureArg(T&&) {}
566 template <
typename... Args>
TraceRegion(
const char* Name, Args&&... args) {
569 m_Data = std::make_unique<TraceData>();
573 m_Data->Nested = TI.insideTracedRegion();
576 (captureArg(std::forward<Args>(args)), ...);
577 if (!m_Data->Nested) {
578 if constexpr (
sizeof...(args) > 0) {
580 RB.
format(m_Data->OutIndices, std::forward<Args>(args)...);
581 m_Data->ArgStr = RB.
Buffer;
587 for (
size_t i = 0; i < m_Data->OutIndices.size(); ++i)
588 if (m_Data->OutFirstUse[i])
589 TI.appendToLog(llvm::formatv(
" std::vector<void*> _out{0};",
590 m_Data->OutIndices[i])
592 m_Data->LogIndex = TI.appendToLog(
593 llvm::formatv(
" Cpp::{0}({1}); // [aborted before return]",
594 m_Data->Name, m_Data->ArgStr)
597 TI.pushTimer(&TI.getTimer(Name));
598 m_Data->StartTime = llvm::TimeRecord::getCurrentTime(
false).getWallTime();
605 if (!m_Data->Returned) {
606 llvm::errs() <<
"ERROR: Function '" << m_Data->Name
607 <<
"' exited without calling INTEROP_RETURN!\n";
610 "Unannotated exit branch detected: use `return INTEROP_RETURN(...)`");
613 auto EndTime = llvm::TimeRecord::getCurrentTime(
false).getWallTime();
614 auto Dur =
static_cast<long long>((EndTime - m_Data->StartTime) * 1e9);
620 if (m_Data->Nested) {
630 llvm::SmallVector<std::pair<size_t, std::string>, 8> RetDecls;
631 if (m_Data->HasRetVec) {
632 RetIdx =
static_cast<int>(TI.nextRetIndex());
633 for (
size_t i = 0; i < m_Data->RetVecPtrs.size(); ++i) {
634 const void* p = m_Data->RetVecPtrs[i];
637 bool isNew = TI.lookupHandle(p).empty();
638 std::string Name = TI.getOrRegisterHandle(p);
640 RetDecls.emplace_back(i, std::move(Name));
645 if (m_Data->Result) {
646 bool isNew = TI.lookupHandle(m_Data->Result).empty();
647 std::string HandleName = TI.getOrRegisterHandle(m_Data->Result);
649 llvm::formatv(isNew ?
"auto {0} = " :
"/*{0}*/ ", HandleName).str();
650 }
else if (m_Data->HasPtrResult) {
651 VarPart =
"/*nullptr*/ ";
652 }
else if (RetIdx >= 0) {
653 VarPart = llvm::formatv(
"auto _ret{0} = ", RetIdx).str();
657 std::string Call = llvm::formatv(
" {0}Cpp::{1}({2}); // [{3} ns]", VarPart,
658 m_Data->Name, m_Data->ArgStr, Dur);
659 TI.setLogEntry(m_Data->LogIndex, Call);
664 for (
size_t i = 0; i < m_Data->OutCallbacks.size(); ++i)
665 m_Data->OutCallbacks[i](TI, m_Data->OutIndices[i]);
670 for (
auto& [Idx, Name] : RetDecls)
673 " void* {0} = _ret{1}.size() > {2} ? _ret{1}[{2}] : nullptr;",
685 [[nodiscard]]
bool isActive()
const {
return m_Data !=
nullptr; }
692 m_Data->Returned =
true;
693 if constexpr (is_handle_v<T>) {
694 m_Data->HasPtrResult =
true;
695 m_Data->Result =
const_cast<void*
>(
static_cast<const void*
>(val.data));
696 }
else if constexpr (std::is_pointer_v<T>) {
697 m_Data->HasPtrResult =
true;
699 m_Data->Result =
const_cast<void*
>(
static_cast<const void*
>(val));
700 }
else if constexpr (std::is_null_pointer_v<T>) {
701 m_Data->HasPtrResult =
true;
702 }
else if constexpr (is_handle_vector_v<T>) {
703 m_Data->HasRetVec =
true;
704 for (
const auto& h : val)
705 m_Data->RetVecPtrs.push_back(h.data);
706 }
else if constexpr (is_pointer_vector_v<T>) {
709 m_Data->HasRetVec =
true;
711 m_Data->RetVecPtrs.push_back(
static_cast<const void*
>(p));
718 m_Data->Returned =
true;
727 while (*p && *p !=
'(')
740 if (p[0] ==
'v' && p[1] ==
'o' && p[2] ==
'i' && p[3] ==
'd' &&
741 (p[4] ==
')' || (p[4] ==
' ' && p[5] ==
')')))
775 constexpr bool isOut[] = {std::is_same_v<std::decay_t<Args>,
OutParam>...,
778 for (std::size_t i = 0; i <
sizeof...(Args); ++i)
792 unsigned actual =
sizeof...(Args);
793 if (expected != actual) {
794 llvm::errs() <<
"ERROR: INTEROP_TRACE argument count mismatch in '"
795 <<
Name <<
"': function has " << expected
796 <<
" parameter(s) but INTEROP_TRACE received " << actual
798 <<
" Signature: " <<
Sig <<
"\n";
799 assert(expected == actual &&
800 "INTEROP_TRACE argument count does not match function "
801 "parameters. Update the INTEROP_TRACE call.");
811 if (*Expected != actual_out) {
815 *Expected == actual_out &&
816 "INTEROP_TRACE OUT-arg coverage does not match the .td "
817 "OutArg<...> declarations. Wrap or unwrap with INTEROP_OUT.");
836 return llvm::formatv(
"ERROR: INTEROP_OUT coverage mismatch in '{0}': .td "
837 "OutArg mask {1:X} vs INTEROP_OUT-wrapped args "
839 Name, expected, actual)
848#define INTEROP_FUNC_SIG __FUNCSIG__
850#define INTEROP_FUNC_SIG __PRETTY_FUNCTION__
853#define INTEROP_TRACE(...) \
854 CppInterOp::Tracing::TraceRegion _TR = \
855 CppInterOp::Tracing::TraceRegion::Proxy{__func__, \
856 INTEROP_FUNC_SIG}(__VA_ARGS__)
858#define INTEROP_RETURN(Val) _TR.record(Val)
859#define INTEROP_VOID_RETURN() (_TR.recordVoid())
861#define INTEROP_OUT(Var) CppInterOp::Tracing::MakeOutParam(Var)
#define CPPINTEROP_TRACE_API
TraceInfo(const TraceInfo &)=delete
std::string lookupHandle(const void *p)
Resolve a pointer to a printable form.
size_t appendToLog(const std::string &line)
Append a line; the returned index pairs with setLogEntry to rewrite the same slot later (TraceRegion'...
std::string writeToFile(const std::string &Version="")
Write the accumulated reproducer log to a file.
unsigned nextRetIndex()
Allocate the next _retN index for a vector-return placeholder.
const std::vector< std::string > & getLog() const
std::string getLastLogEntry() const
TraceInfo & operator=(TraceInfo &&)=delete
void StopRegion(const std::string &Version="")
End the traced region and write only the region's entries to the file.
void pushTimer(llvm::Timer *T)
bool insideTracedRegion() const
True when at least one TraceRegion is currently active.
std::pair< unsigned, bool > outIndexFor(const void *Addr)
Resolve an OUT-container source address to its _outN index.
TraceInfo & operator=(const TraceInfo &)=delete
void setLogEntry(size_t idx, const std::string &line)
TraceInfo(TraceInfo &&)=delete
std::string getOrRegisterHandle(const void *p)
std::string StartRegion(bool WriteOnStdErr=true)
Begin a traced region.
llvm::Timer & getTimer(llvm::StringRef Name)
TraceRegion(const TraceRegion &)=delete
TraceRegion(const char *Name, Args &&... args)
static std::optional< uint64_t > lookupOutMask(llvm::StringRef Name)
.td-declared OUT-arg bitmask for Name (matches CppName / func), or std::nullopt for non-public-API tr...
TraceRegion & operator=(const TraceRegion &)=delete
static unsigned countParams(const char *sig)
Count parameters from a PRETTY_FUNCTION / FUNCSIG string.
static std::string formatOutMaskMismatchMessage(llvm::StringRef Name, uint64_t expected, uint64_t actual)
Pure-function diagnostic for the OUT-mask mismatch.
TraceRegion(TraceRegion &&) noexcept=default
T record(T val)
Record a non-void return value.
static constexpr uint64_t computeOutMask()
Bit i set iff Args[i] is OutParam (i.e.
OutParam MakeOutParam(const Container &C)
Create an OutParam for any container.
void InitTracing()
Activate tracing.
constexpr bool is_handle_vector_v
constexpr bool is_handle_v
Detect opaque handle structs (Cpp::DeclRef, Cpp::TypeRef, etc.)
void StopTracing(const std::string &Version="")
End the traced region and write the reproducer file containing only the calls made between StartTraci...
constexpr bool is_pointer_vector_v
std::string StartTracing(bool WriteOnStdErr=true)
Begin recording a traced region.
TraceInfo * TheTraceInfo
Process-global tracer pointer.
Marks a function parameter as an output container (e.g.
std::function< void(TraceInfo &, unsigned OutIdx)> RegisterHandles
Register the container's pointer elements as handles and emit one void* vN = _outN[i] : nullptr; decl...
bool IsPointerContainer
Drives the _outN preamble + alias path.
const void * SourceAddr
Address of the source container object (not its data buffer); multiple calls with the same container ...
bool IsScalarPointer
Scalar pointer OUT (e.g. bool*); rendered as nullptr.
Internal helper to stringify arguments into a C++ call format.
void append(unsigned long long v)
llvm::raw_svector_ostream OS
void append(const Cpp::TemplateArgInfo &tai)
void append(Cpp::TypeRef h)
void append(const char *s)
void append(Cpp::ConstDeclRef h)
static std::string parseEnumName(const char *sig)
void append(Cpp::DeclRef h)
void append(Cpp::ConstTypeRef h)
llvm::SmallString< 128 > Buffer
void appendRaw(std::string_view s)
void append(Cpp::InterpRef h)
void append(const void *p)
void format(llvm::ArrayRef< unsigned > OutIndices, Args &&... args)
Format a comma-separated argument list.
void append(const std::string &s)
void append(Cpp::ObjectRef h)
void append(Cpp::FuncRef h)
void append(unsigned long v)
void append(const std::vector< T > &V)
void append(Cpp::ConstFuncRef h)
Holds all the data that is only needed when tracing is active.
bool Nested
Set when another TraceRegion was already active at construction.
llvm::SmallVector< std::function< void(TraceInfo &, unsigned)>, 2 > OutCallbacks
llvm::SmallVector< unsigned, 2 > OutIndices
_outN indices for this call's OUT pointer-containers, in left-to-right order.
bool HasRetVec
Set when the call returned std::vector<P*>; ~TraceRegion uses this to spell auto _retN = Cpp::Foo(....
llvm::SmallString< 128 > ArgStr
size_t LogIndex
Slot for the ctor-emitted placeholder; the dtor rewrites it.
llvm::SmallVector< const void *, 8 > RetVecPtrs
Snapshot of the returned vector's pointer elements, taken at record() time – the source vector goes o...
llvm::SmallVector< bool, 2 > OutFirstUse
Per-OUT argument flag: true on the first call with this source container (preamble decl needed),...
Helper to allow INTEROP_TRACE() to work with zero or more arguments without relying on non-standard #...
TraceRegion operator()(Args &&... args)
Detect vector-of-handle types.
Matches std::vector<T*> for any pointer element type.