criterion-polyglot 0.1.0

An extension trait for criterion providing benchmark methods for various non-Rust programming languages
Documentation
#ifdef __MACH__
#include <mach/mach.h>
#include <mach/clock.h>
#else
#include <sys/time.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <sys/errno.h>
#include <time.h>

@"imports"@

int reset();

@"declarations"@

int get_elapsed(unsigned long* elapsed) {
    /* Store a static timespec as the basis time */
    static struct timespec* since = NULL;
    struct timespec now;
    unsigned long elapsed_value;

    /*
    * macOS does implement clock_gettime() since 10.12, but, despite
    * returning timespec.tv_nsec, it only has usec precision. The following
    * returns nanosecond-precise timestamps for all versions of macOS.
    *
    * Everywhere else, just use clock_gettime().
    */
    #ifdef __MACH__
    clock_serv_t clock_service;
    mach_timespec_t mach_time;

    if (host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &clock_service) != 0) {
        return 1;
    }

    if (clock_get_time(clock_service, &mach_time) != 0) {
        return 1;
    }

    if (mach_port_deallocate(mach_task_self(), clock_service) != 0) {
        return 1;
    }

    now.tv_sec = mach_time.tv_sec;
    now.tv_nsec = mach_time.tv_nsec;
    #else
    if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
        return 1;
    }
    #endif

    /*
    * Uninitialized since means this function has never been called and no basis has been
    * established. This malloc will only be called once and never freed; that's okay because
    * static since should live for the lifetime of the execution. If since has already been
    * initialized, then use it to calculate elapsed time.
    */
    if (NULL == since) {
        since = malloc(sizeof(struct timespec));
        *since = now;
        elapsed_value = 0;
    } else {
        elapsed_value = (now.tv_sec - since->tv_sec)*1e9 + (now.tv_nsec - since->tv_nsec);
    }

    /*
    * If the caller provided a pointer to receive the elapsed value, then set it,
    * otherwise reset the basis.
    */
    if (NULL != elapsed)
    *elapsed = elapsed_value;
    else
    *since = now;

    return 0;
}

int reset() {
    return get_elapsed(NULL);
}

int main()
{
    static void (*volatile bench_func)(void);
    unsigned long i, n_repeat, elapsed;
    // A base-10, unsigned, 64-bit integer can be 20 ASCII digits long
    char line[21];
    int line_len;

    // Every benchmark timing must be flushed immediately
    setlinebuf(stdout);

    @"global"@

    while (fgets(line, sizeof line, stdin) != NULL) {
        line_len = strlen(line);
        if (line_len == 20 && line[19] != '\n') {
            fprintf(stderr, "error - input line was too long\n");
            return 1;
        }
        // Strip the newline
        line[line_len-1] = '\0';

        n_repeat = strtoul(line, NULL, 10);
        if (errno) {
            fprintf(stderr, "error - '%s' is not an integer\n", line);
            return 1;
        }

        if (n_repeat == 0) {
            printf("0 nsec\n");
            return 0;
        }

        @"sample"@

        /*
        * Use a volatile asm statement inside the loop so
        * the compiler will never optimize the number
        * of repetitions away. Optimization can still
        * happen inside the timed code, but it **WILL**
        * get called n_repeat times.
        */
        reset();
        for (i = 0; i < n_repeat; i++) {
            asm volatile("" : "+g" (i));
            @"timed"@
        }
        get_elapsed(&elapsed);
        printf("%lu nsec\n", elapsed);
    }

    if (ferror(stdin)) {
        fprintf(stderr, "error reading stdin: %d", ferror(stdin));
        return 1;
    }

    return 0;
}