Comptime function call counting

Is it possible to ‘count’ how many times a function is called and use that to build a stack allocated buffer, pseudo code below, comptime var is made up:

const LabeledTimer = struct {
    comptime var n = 0;
    const Result = struct {
        time: i64,
        label: []const u8,
    };
    timer: std.time.Timer,
    results: []Result,

    fn init() @This() {
        return .{
            .timer = try std.time.Timer.start(),
            .results = [n]Result,
        };
    }

    fn add(self: *@This(), comptime label: []const u8) void {
        comptime {
            n += 1;
            self.results[n] = .{
                .label = label,
                .time = 0,
            };
        }
        self.results[n].time = self.timer.lap();
    }

    fn print(self: *@This()) void {
        for (self.results) |r| {
            std.debug.print("{d} : {s}\n", .{ r.time, r.label });
        }
    }
};

My motivation is to create a reusable timer which can add labled intervals:

var timer = LabeledTimer.init();
// do foo
timer.add("foo");
// do bar
timer.add("bar");
timer.print();
// 45 : bar
// 23 : foo

And only have the call to timer.lap() happen at runtime.

You can’t have a global comptime var. This is because comptime is designed such that the comptime evaluation of one function can’t affect that of another. There have been some hacks to allow mutable global state at comptime (like exploiting error codes), but they aren’t intended and can’t be relied upon.

You could keep track of the number of calls to add at comptime by passing a pointer to a comptime var as a parameter, but you would only have the final call count after the calls to add, and you can’t travel back in time to pass this value into the init function.

This is the closest you could get to your example:

const std = @import("std");

const LabeledTimer = struct {
    timer: std.time.Timer,
    results: [128]u64,

    fn init() !@This() {
        return .{
            .timer = try std.time.Timer.start(),
            .results = undefined,
        };
    }

    inline fn add(self: *@This(), comptime label: []const u8, comptime comptime_info: *LabeledTimerComptime) void {
        self.addRuntime(comptime_info.result_labels.len);

        comptime_info.result_labels = comptime_info.result_labels ++ &[_][]const u8{label};
    }

    fn addRuntime(self: *@This(), idx: usize) void {
        self.results[idx] = self.timer.lap();
    }

    inline fn print(self: *@This(), comptime comptime_info: *LabeledTimerComptime) void {
        self.printRuntime(comptime_info.result_labels);
    }

    fn printRuntime(self: *@This(), result_labels: []const []const u8) void {
        for (self.results[0..result_labels.len], result_labels) |time, label| {
            std.debug.print("{d} : {s}\n", .{ time, label });
        }
    }
};

const LabeledTimerComptime = struct {
    result_labels: []const []const u8,

    const init: LabeledTimerComptime = .{
        .result_labels = &.{},
    };
};

pub fn main() !void {
    comptime var timer_comptime: LabeledTimerComptime = .init;
    var timer = try LabeledTimer.init();

    timer.add("foo", &timer_comptime);
    timer.add("bar", &timer_comptime);

    timer.print(&timer_comptime);
}

1 Like