std1.0.0-beta-7 SafeC docs

Source debug/perf.sc

View in the repository

// SafeC Standard Library — Performance Counter Implementation
#pragma once
#include <std/debug/perf.h>
#ifndef __SAFEC_FREESTANDING__
#include <std/time.h>
#endif

// ── Hosted-mode extern declarations ──────────────────────────────────────────
// clock() is used only in hosted mode (PerfCounter::new calibration).
namespace std {

extern long  clock();    // POSIX clock() returns long; CLOCKS_PER_SEC = 1000000

// ── perf_read_cycle ───────────────────────────────────────────────────────────
// Returns raw hardware cycle / virtual counter ticks.
// Architecture selected at compile time via predefined macros.

unsigned long long perf_read_cycle() {
#if defined(__x86_64__) || defined(_M_X64)
    // RDTSC: returns TSC in EDX:EAX.
    unsigned int lo = (unsigned int)0;
    unsigned int hi = (unsigned int)0;
    unsafe {
        asm volatile ("rdtsc" : "=a"(lo), "=d"(hi));
    }
    return ((unsigned long long)hi << 32) | (unsigned long long)lo;

#elif defined(__aarch64__) || defined(__arm64__)
    // Virtual counter — available at EL0 when CNTKCTL_EL1.EL0VCTEN is set.
    unsigned long long v = (unsigned long long)0;
    unsafe {
        asm volatile ("mrs %0, cntvct_el0" : "=r"(v));
    }
    return v;

#elif defined(__riscv)
    // Unprivileged cycle CSR.
    unsigned long long v = (unsigned long long)0;
    unsafe {
        asm volatile ("csrr %0, cycle" : "=r"(v));
    }
    return v;

#else
    // Fallback: unknown architecture — return 0.
    return (unsigned long long)0;
#endif
}

// ── perf_read_ns ──────────────────────────────────────────────────────────────
// Hosted: time.h's monotonic clock (the right clock id per OS).
// Freestanding: return 0 (caller should use ticks + known freq).
inline unsigned long long perf_read_ns() {
#ifndef __SAFEC_FREESTANDING__
    long long t = time_mono_ns();
    if (t < 0LL) { return (unsigned long long)0; }
    return (unsigned long long)t;
#else
    return (unsigned long long)0;
#endif
}

// ── PerfCounter methods ───────────────────────────────────────────────────────

inline void PerfCounter::start() {
    self.start_val = perf_read_cycle();
    self.end_val   = self.start_val;
}

inline void PerfCounter::stop() {
    self.end_val = perf_read_cycle();
}

inline unsigned long long PerfCounter::ticks() const {
    return self.end_val - self.start_val;
}

inline unsigned long long PerfCounter::ns() const {
    if (self.freq_hz == (unsigned long long)0) { return (unsigned long long)0; }
    unsigned long long t = self.end_val - self.start_val;
    // ns = ticks * 1e9 / freq_hz.  Avoid overflow: compute (ticks / freq_hz) * 1e9
    // then add the remainder contribution.
    unsigned long long q = t / self.freq_hz;
    unsigned long long r = t % self.freq_hz;
    return q * (unsigned long long)1000000000
         + r * (unsigned long long)1000000000 / self.freq_hz;
}

// ── PerfCounter::new ──────────────────────────────────────────────────────────
// Attempts to calibrate the cycle counter frequency using clock() over a short
// spin.  In freestanding mode, or if calibration fails, sets freq_hz = 0.

struct PerfCounter PerfCounter::new() {
    struct PerfCounter ctr;
    ctr.start_val = (unsigned long long)0;
    ctr.end_val   = (unsigned long long)0;
    ctr.freq_hz   = (unsigned long long)0;

#ifndef __SAFEC_FREESTANDING__
    // Measure cycles elapsed during a known clock() interval.
    // clock() resolution is 1/CLOCKS_PER_SEC (= 1 µs on most POSIX systems).
    // We spin until clock() advances by at least ~10 ms worth of ticks.
    // CLOCKS_PER_SEC is typically 1,000,000 on POSIX.
    unsigned long long target_clocks = (unsigned long long)10000; // 10 ms @ 1e6 clks/s

    unsafe {
        long c0 = clock();
        unsigned long long cy0 = perf_read_cycle();
        long c1 = clock();
        // Spin until at least target_clocks have elapsed.
        while ((unsigned long long)(c1 - c0) < target_clocks) {
            c1 = clock();
        }
        unsigned long long cy1 = perf_read_cycle();

        unsigned long long elapsed_clocks = (unsigned long long)(c1 - c0);
        if (elapsed_clocks > (unsigned long long)0 && cy1 > cy0) {
            // freq = (cy1-cy0) * CLOCKS_PER_SEC / elapsed_clocks
            // Use 1000000 as CLOCKS_PER_SEC (standard POSIX value).
            unsigned long long clks_per_sec = (unsigned long long)1000000;
            ctr.freq_hz = (cy1 - cy0) * clks_per_sec / elapsed_clocks;
        }
    }
#endif

    return ctr;
}

} // namespace std