// gpu_log_helpers.cuh — header-only device helpers for the GPU log ring.
//
// Include this from any .cu file that needs to call `log_record()`.
// The `gpu_log_tick` kernel itself lives in `gpu_log_ring.cu`.
//
// Per `feedback_single_source_of_truth_no_duplicates`: this is the ONE
// definition of `LogHeader` / `LogRecord` / `LogRing` and `log_record`;
// other kernels must include this header instead of duplicating the
// struct layouts. The IDs (kernel_id / record_type / magic / slot math
// constants) live in `gpu_log_ids.h` and are included here.

#ifndef GPU_LOG_HELPERS_CUH
#define GPU_LOG_HELPERS_CUH

#include "gpu_log_ids.h"
#include <cstdint>

struct LogHeader {
    uint32_t magic;
    uint32_t step;
    uint8_t  kernel_id;
    uint8_t  record_type;
    uint8_t  payload_words;
    uint8_t  reserved;
    uint32_t _padding;
};

struct LogRecord {
    LogHeader header;
    float     payload[GPU_LOG_PAYLOAD_F32];
};

struct LogRing {
    LogRecord records[GPU_LOG_N_SLOTS];
};

// log_record — emit a diagnostic record from a kernel.
//
// Single-writer per (step, kernel_id, record_type) slot. Thread (0,0) of
// block 0 writes; all other threads no-op. Header-last commit ensures
// torn-record reads on the host side are detectable via magic mismatch.
//
// Caller responsibility:
//   - g_log_ring == nullptr → no-op (logging disabled).
//   - g_step_counter is the device step counter (incremented by gpu_log_tick).
//   - payload_words ≤ GPU_LOG_PAYLOAD_F32 (12); kernel must respect this.
__device__ __forceinline__ void log_record(
    LogRing*       g_log_ring,
    const int*     g_step_counter,
    uint8_t        kernel_id,
    uint8_t        record_type,
    const float*   payload,
    int            payload_words
) {
    if (g_log_ring == nullptr) return;
    if (blockIdx.x != 0 || threadIdx.x != 0) return;

    const int step = g_step_counter[0];
    const int slot = (step * GPU_LOG_N_RECORDS_PER_STEP
                    + (int)kernel_id * GPU_LOG_N_RT_PER_KERNEL
                    + (int)record_type) & (GPU_LOG_N_SLOTS - 1);
    LogRecord* rec = &g_log_ring->records[slot];

    const int n = payload_words < GPU_LOG_PAYLOAD_F32 ? payload_words : GPU_LOG_PAYLOAD_F32;
    #pragma unroll
    for (int i = 0; i < GPU_LOG_PAYLOAD_F32; ++i) {
        rec->payload[i] = (i < n) ? payload[i] : 0.0f;
    }
    rec->header.step          = (uint32_t)step;
    rec->header.kernel_id     = kernel_id;
    rec->header.record_type   = record_type;
    rec->header.payload_words = (uint8_t)n;
    rec->header.reserved      = 0;
    rec->header._padding      = 0;

    __threadfence();                       // make payload + header fields visible …
    rec->header.magic = GPU_LOG_MAGIC;     // … BEFORE the commit sentinel.
}

#endif // GPU_LOG_HELPERS_CUH
