Debugging & Profiling

SafeC provides three debug modules in std/debug/ for performance measurement, code coverage tracking, and hardware debug integration.

c
#include <std/debug/debug.h>  // master header: perf + coverage + jtag

The SafeC compiler also emits DWARF debug information when invoked with --g:

Flag Output
--g lines DICompileUnit, DISubprogram per function, DILocation per statement
--g full All of lines + DILocalVariable + dbg.declare per local variable
(none) No debug metadata

perf — Hardware Performance Counters

c
#include <std/debug/perf.h>

PerfCounter reads architecture-specific cycle counters for sub-microsecond timing.

Cycle sources

Architecture Instruction
x86-64 rdtsc (EDX:EAX)
AArch64 mrs %0, cntvct_el0
RISC-V csrr %0, cycle
Other 0 (no hardware counter)

Struct

c
struct PerfCounter {
    unsigned long long start_val;
    unsigned long long end_val;
    unsigned long long freq_hz;  // CPU cycles per second

    static struct PerfCounter new();    // zeroed, freq_hz detected (see below)

    void               start();
    void               stop();
    unsigned long long ticks() const;   // end_val - start_val
    unsigned long long ns() const;      // ticks / freq_hz * 1e9
}

Constructor

c
struct PerfCounter pc = std::PerfCounter::new();   // a static method

Hosted: calibrates freq_hz by spinning ~10 ms on POSIX clock() and counting CPU cycles over that interval.

Freestanding: sets freq_hz = 0; ns() returns 0.

One-shot reads

c
unsigned long long perf_read_cycle();   // raw cycle counter for this arch
unsigned long long perf_read_ns();      // hosted: time_mono_ns(); 0 on freestanding

PERF_BEGIN(ctr) / PERF_END(ctr) wrap ctr.start() / ctr.stop() in compat preprocessor mode.

Example

c
#include <std/debug/perf.h>
#include <std/io.h>

int main() {
    struct PerfCounter pc = std::PerfCounter::new();

    pc.start();
    // Work to measure
    for (int i = 0; i < 1000000; i++) {}
    pc.stop();

    std::print("ticks: "); std::println_int((long long)pc.ticks());
    std::print("ns:    "); std::println_int((long long)pc.ns());
    return 0;
}

coverage — Source-Level Code Coverage

c
#include <std/debug/coverage.h>

Instruments call sites with a single macro. Tracks how many times each site was reached. 1024 sites maximum.

Global instance

c
extern struct Coverage coverage;
void coverage_init();  // zero all sites (call at program start)

Struct

c
struct CovSite {
    const &char   file;
    int           line;
    unsigned long count;
}

struct Coverage {
    CovSite       sites[COV_MAX_SITES];  // 1024
    int           count;

    int           register_site(const &char file, int line);
    void          hit(int idx);
    unsigned long get(int idx) const;
    int           covered_count() const;
    int           coverage_pct() const;  // integer 0–100
    void          reset();
    void          report() const;        // print HIT/MISS per site (hosted only)
}

COV_SITE() macro

Place COV_SITE(); at any point you want to instrument. It registers once (via a static int guard) and increments the counter on every call. It is a function-like macro, so it needs compat preprocessor mode ([build] compat_preprocessor = true in a safeguard package; safeguard test turns it on for tests):

c
int clamp(int v, int lo, int hi) {
    COV_SITE();          // instruments this entry point
    if (v < lo) { COV_SITE(); return lo; }
    if (v > hi) { COV_SITE(); return hi; }
    return v;
}

Example

c
#include <std/debug/coverage.h>
#include <std/io.h>

int clamp(int v, int lo, int hi) {
    COV_SITE();
    if (v < lo) { COV_SITE(); return lo; }
    if (v > hi) { COV_SITE(); return hi; }
    return v;
}

int main() {
    coverage_init();

    clamp(5,  0, 10);   // middle branch
    clamp(-1, 0, 10);   // lo branch
    clamp(15, 0, 10);   // hi branch

    coverage.report();
    print("coverage: ");
    println_int(coverage.coverage_pct());
    return 0;
}

Sample output:

text
[cov] /path/clamp.sc:3 HIT (3)
[cov] /path/clamp.sc:4 HIT (1)
[cov] /path/clamp.sc:5 HIT (1)
coverage: 100

jtag — Hardware Debug Helpers

c
#include <std/debug/jtag.h>

Arch-conditional breakpoints, semihosting, and Cortex-M ITM stimulus port output. Useful during early bring-up when a UART is not yet available.

Breakpoints

c
void debug_break();

Emits the native breakpoint instruction:

Architecture Instruction
AArch64 brk #0
ARM / Thumb bkpt #0
x86-64 int3
RISC-V ebreak

Semihosting (ARM / AArch64)

Semihosting lets a Cortex-M or Cortex-A target send output to the host debugger (J-Link, OpenOCD) via the debug connection — no UART needed.

c
void debug_semihost_puts(const &char s);  // SYS_WRITE0 — NUL-terminated
void debug_semihost_putc(char c);         // SYS_WRITEC — single character

These are no-ops on x86-64 and RISC-V.

ITM Stimulus Ports (Cortex-M only)

Registers at ITM_STIMULUS_BASE (0xE0000000) and ITM_TCR_BASE (0xE0000E80).

Instrumentation Trace Macrocell — sends data over the SWO pin for capture by a J-Link or similar probe.

c
void itm_enable_port(unsigned char port);                 // set bit in ITM TER (0xE0000E00)
void itm_putc(unsigned char port, unsigned char c);       // write byte to stimulus register
void itm_put32(unsigned char port, unsigned int word);    // write 32-bit word

itm_putc and itm_put32 poll until the stimulus register is ready (bit 0 == 1) before writing. They are no-ops if the ITM is not enabled or the port is not enabled.

DBG_ASSERT

c
DBG_ASSERT(cond);
// Calls debug_break() if cond is false.

A function-like macro, so it exists only in compat preprocessor mode. In safe mode write if (!cond) { std::debug_break(); }.

Example

c
#include <std/debug/jtag.h>

void board_bringup(int clocks_ok) {
    if (!clocks_ok) { std::debug_break(); }   // stop in the debugger

    // Early startup output via semihost before the UART is ready
    std::debug_semihost_puts("bring-up started\n");

    // Once the probe is attached, switch to ITM for lightweight tracing
    std::itm_enable_port(0);
    std::itm_putc(0, 'A');
}

INFO

ITM requires a Cortex-M3/M4/M7 or later and a debug probe that supports SWO capture (J-Link, ULINK, ST-LINK v3). Configure your probe's SWO clock to match your target CPU's speed.