// Hand-C reference for bench_mono_dispatch — INDIRECT (fnptr) variant.
//
// Same algorithm as bench_mono_direct_noinline.c, but `foo` is reached
// through a function pointer assigned at runtime. This represents the
// "virtual dispatch" / "dictionary-passing" lower bound: the call
// target is opaque to the optimiser at compile time, so clang cannot
// inline and emits an indirect call (`callq *<reg>`).
//
// The fnptr is assigned from main via a volatile-pointer indirection
// to defeat any speculative devirtualisation clang -O2 might attempt
// (without PGO it does not devirt monomorphic-target indirect calls,
// but the volatile guard makes that explicit and stable across clang
// versions).
//
// `foo` keeps `noinline` so we measure dispatch-shape, not body size.
// The pair (direct_noinline vs indirect) isolates exactly the
// indirect-call cost on this hardware.
//
// Build: clang -O2 -o bench_mono_indirect bench_mono_indirect.c

#include <stdio.h>
#include <stdint.h>

static int64_t foo(int64_t x) __attribute__((noinline));
static int64_t foo(int64_t x) {
    return x * 1103515245 + 12345;
}

typedef int64_t (*foo_fn_t)(int64_t);

static int64_t loop_call(foo_fn_t fp, int64_t i, int64_t acc) {
    while (i != 0) {
        acc = acc + fp(acc + i);
        i = i - 1;
    }
    return acc;
}

int main(void) {
    // Volatile barrier so the optimiser sees the target as
    // dynamically-determined — it cannot prove the fnptr is
    // monomorphic at the call site without PGO.
    foo_fn_t volatile fp_v = &foo;
    foo_fn_t fp = fp_v;
    printf("%lld\n", (long long)loop_call(fp, 100000000, 0));
    return 0;
}
