// ptr_trace.h - Pointer link read/write tracing (Debug-only, macro-replaceable) // // Purpose: // - Centrally instrument single-linked list next pointer reads/writes // - Zero overhead in release builds (macros devolve to raw ops) // - Compact TLS ring for triage; optional dump at exit via env // // Usage: // - Replace direct next ops with PTR_NEXT_WRITE / PTR_NEXT_READ // - Tag with a short literal (e.g., "tls_push", "tls_pop", "splice") // - Offsets should be header-aware (C0–C6: +1, C7: +0) // // Control: // - Compile-time: HAKMEM_PTR_TRACE (default: 1 for debug, 0 for release) // - Runtime dump: HAKMEM_PTR_TRACE_DUMP=1 (prints ring at exit) #pragma once #include #include #ifndef HAKMEM_PTR_TRACE # if !HAKMEM_BUILD_RELEASE # define HAKMEM_PTR_TRACE 1 # else # define HAKMEM_PTR_TRACE 0 # endif #endif #if HAKMEM_PTR_TRACE #include typedef struct { const char* tag; // literal tag int class_idx; // tiny class (0..7) void* node; // node address (base) void* val; // written/read value size_t off; // offset used for access } ptr_trace_ev_t; #ifndef PTR_TRACE_CAP # define PTR_TRACE_CAP 256 #endif static __thread ptr_trace_ev_t g_ptr_trace_ring[PTR_TRACE_CAP]; static __thread uint32_t g_ptr_trace_idx = 0; static __thread int g_ptr_trace_dump_registered = 0; static inline void ptr_trace_record(const char* tag, int cls, void* node, void* val, size_t off) { uint32_t i = g_ptr_trace_idx++; g_ptr_trace_ring[i & (PTR_TRACE_CAP - 1)] = (ptr_trace_ev_t){ tag, cls, node, val, off }; } static inline void ptr_trace_dump(void) { fprintf(stderr, "\n[PTR_TRACE_DUMP] last=%u (cap=%u)\n", g_ptr_trace_idx, (unsigned)PTR_TRACE_CAP); uint32_t n = (g_ptr_trace_idx < PTR_TRACE_CAP) ? g_ptr_trace_idx : PTR_TRACE_CAP; for (uint32_t k = 0; k < n; k++) { const ptr_trace_ev_t* e = &g_ptr_trace_ring[(g_ptr_trace_idx - n + k) & (PTR_TRACE_CAP - 1)]; fprintf(stderr, "[%3u] tag=%s cls=%d node=%p val=%p off=%zu\n", k, e->tag ? e->tag : "?", e->class_idx, e->node, e->val, e->off); } } static inline void ptr_trace_try_register_dump(void) { if (g_ptr_trace_dump_registered) return; g_ptr_trace_dump_registered = 1; const char* env = getenv("HAKMEM_PTR_TRACE_DUMP"); if (!(env && *env && *env != '0')) return; atexit(ptr_trace_dump); } // Immediate dump (Debug only) — static inline to avoid ODR/link conflicts under LTO #if HAKMEM_PTR_TRACE static inline void ptr_trace_dump_now(const char* reason) { fprintf(stderr, "\n[PTR_TRACE_NOW] reason=%s last=%u (cap=%u)\n", reason ? reason : "(null)", g_ptr_trace_idx, (unsigned)PTR_TRACE_CAP); uint32_t n = (g_ptr_trace_idx < PTR_TRACE_CAP) ? g_ptr_trace_idx : PTR_TRACE_CAP; for (uint32_t k = 0; k < n; k++) { const ptr_trace_ev_t* e = &g_ptr_trace_ring[(g_ptr_trace_idx - n + k) & (PTR_TRACE_CAP - 1)]; fprintf(stderr, "[%3u] tag=%s cls=%d node=%p val=%p off=%zu\n", k, e->tag ? e->tag : "?", e->class_idx, e->node, e->val, e->off); } } #else static inline void ptr_trace_dump_now(const char* reason) { (void)reason; } #endif #define PTR_NEXT_WRITE(tag, cls, node, off, value) do { \ void** __p = (void**)((uint8_t*)(node) + (off)); \ *__p = (value); \ ptr_trace_record((tag), (cls), (node), (value), (off)); \ ptr_trace_try_register_dump(); \ } while(0) #define PTR_NEXT_READ(tag, cls, node, off, out_var) do { \ void** __p = (void**)((uint8_t*)(node) + (off)); \ (out_var) = *__p; \ ptr_trace_record((tag), (cls), (node), (out_var), (off)); \ ptr_trace_try_register_dump(); \ } while(0) #else // HAKMEM_PTR_TRACE == 0 #define PTR_NEXT_WRITE(tag, cls, node, off, value) \ (*(void**)((uint8_t*)(node) + (off)) = (value)) #define PTR_NEXT_READ(tag, cls, node, off, out_var) \ ((out_var) = *(void**)((uint8_t*)(node) + (off))) // Always provide a stub for release builds so callers can link static inline void ptr_trace_dump_now(const char* reason) { (void)reason; } #endif // HAKMEM_PTR_TRACE