SilverSight/experiments/epyc_oisc_cacheline_bench.c
allaun 3b6baec64e wip: durability snapshot of local working tree (pre-existing, uncommitted)
Snapshot of previously-uncommitted local work so nothing is lost after the
power outage. NOT reviewed for correctness — a WIP checkpoint, not a feature:
- multi-language hachimoji encoders (c/cpp/fortran/julia/octave/r/scala/go/rust/coq)
- formal Lean WIP (BraidTree, Eisenstein, HachimojiCapture, MathlibConnect,
  ModularFormBridge, ClusterManifold) + lakefile + E8Sidon edit
- docs/, experiments/ (epyc oisc benches), deploy/, scripts, test scaffolding
- .gitignore: exclude **/target/ and Coq build artifacts

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:49:53 -05:00

319 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* epyc_oisc_cacheline_bench.c
*
* Cache-line-granular OISC benchmark for EPYC 9645 Turin.
*
* Three models, each building on the last:
*
* 1. WORD-SUBLEQ — traditional subleq on int16 words (baseline)
* 2. CL-SUBLEQ — subleq on 64-byte cache lines via AVX-512 aligned load/store
* 3. PCIE-TLP — each OISC instr = 64-byte PCIe TLP from a virtio-style
* ring buffer; measures raw TLP throughput
*
* The model: a cache-native OISC pipeline hooks PCIe signal hooks (TLP
* transactions) as its instruction fetch / memory access path. Every
* instruction is one or more 64-byte cache-line reads/writes — exactly
* what PCIe Gen5 x16 delivers as a single transaction.
*
* Build:
* gcc -march=znver5 -O3 -flto -mavx512f -mavx512bw \
* epyc_oisc_cacheline_bench.c -o oisc_cl_bench
*
* Run:
* perf stat -e cycles,instructions,cache-references,cache-misses,\
* L1-dcache-load-misses,LLC-load-misses,branch-misses \
* ./oisc_cl_bench
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <stdalign.h>
#include <immintrin.h>
/* ================================================================
* Common constants
* ================================================================ */
#define CL_SIZE 64 /* x86 cache line */
#define CL_WORDS (CL_SIZE / sizeof(int16_t)) /* 32 int16 per cache line */
#define N_LINES 4096 /* 4096 cache lines = 256 KB workspace */
#define MEM_SIZE (N_LINES * CL_SIZE)
#define HALT 0x8000
/* ================================================================
* 1. WORD-SUBLEQ (baseline)
* mem[dst] -= mem[src]; if mem[dst] <= 0 → pc = next; else pc += 3
* Word-aligned, no padding.
* ================================================================ */
static int64_t bench_word_subleq(int16_t *mem, int pc, int N) {
int16_t src, dst, next;
int64_t iters = 0;
int cnt = 0;
while (cnt < N) {
src = mem[pc];
dst = mem[pc + 1];
next = mem[pc + 2];
if (next == HALT) break;
mem[dst] -= mem[src];
pc = (mem[dst] <= 0) ? next : pc + 3;
iters++;
cnt++;
}
return iters;
}
/* Build a word-SUBLEQ program that executes ~N iterations.
* Returns pc of first instruction. */
static int build_word_prog(int16_t *mem, int N, int *out_pc) {
memset(mem, 0, MEM_SIZE);
/* Layout:
* line 0: constants (zero=0, neg1=-1, one=1, limit=N/64)
* line 1: program (src,dst,next triples)
* line 2+: workspace
*/
mem[0] = 0; /* zero addr */
mem[1] = -1; /* neg1 */
mem[2] = 1; /* one */
mem[3] = N; /* limit */
int cnt_addr = 64; /* counter at word 64 */
int tmp_addr = 65; /* temp at word 65 */
mem[cnt_addr] = 0;
mem[tmp_addr] = 0;
/*
* Loop:
* I0: cnt -= neg1 (cnt++)
* I1: tmp -= cnt (tmp = N - cnt)
* I2: branch if tmp <= 0
*/
int pc = 100; /* program at word 100 */
*out_pc = pc;
/* I0: cnt += 1 (cnt -= neg1, where neg1 = -1) */
mem[pc] = 1; /* src = addr 1 = neg1 */
mem[pc + 1] = cnt_addr;
mem[pc + 2] = -1; /* fall through */
pc += 3;
/* I1: tmp = N - cnt (tmp -= cnt, tmp starts = N at line 0) */
mem[pc] = cnt_addr;
mem[pc + 1] = tmp_addr;
mem[pc + 2] = pc + 3; /* fall through to I2 */
pc += 3;
/* I2: if tmp <= 0 → halt; else → loop */
/* tmp <= 0 means we ran N iterations */
mem[pc] = 0; /* src = zero (no-op for tmp) */
mem[pc + 1] = tmp_addr;
mem[pc + 2] = *out_pc; /* loop back */
pc += 3;
/* HALT */
mem[pc] = 0;
mem[pc + 1] = 0;
mem[pc + 2] = HALT;
return pc + 3; /* total words used */
}
/* ================================================================
* 2. CL-SUBLEQ (cache-line granular)
*
* Each "word" in this variant is a 64-byte cache line. The SUBLEQ
* instruction becomes:
*
* line[dst] := line[dst] line[src] (elementwise, via AVX-512)
* if all(line[dst]) <= 0 → pc = next; else pc += 3
*
* "All zeros" is checked as: the first int16 of a cache line ≤ 0.
* For a real pipeline, this would be a SIMD compare + mask test.
* ================================================================ */
typedef int64_t cacheline_t[CL_WORDS] __attribute__((aligned(64)));
static inline void cl_sub(cacheline_t *a, cacheline_t *b) {
/* b[] -= a[] using AVX-512 */
__m512i va = _mm512_load_si512(a);
__m512i vb = _mm512_load_si512(b);
__m512i vr = _mm512_sub_epi16(vb, va);
_mm512_store_si512(b, vr);
}
static inline int cl_is_nonpositive(cacheline_t *b) {
/* Return 1 if first element <= 0 (proxy for "all zero") */
return (*b)[0] <= 0;
}
static int64_t bench_cl_subleq(cacheline_t *mem, int pc, int N) {
int16_t src, dst, next;
int64_t iters = 0;
int cnt = 0;
while (cnt < N) {
/* The "program" is still stored as int16 triples in line 0 */
src = ((int16_t *)mem)[pc];
dst = ((int16_t *)mem)[pc + 1];
next = ((int16_t *)mem)[pc + 2];
if (next == HALT) break;
cl_sub(&mem[src], &mem[dst]);
pc = cl_is_nonpositive(&mem[dst]) ? next : pc + 3;
iters++;
cnt++;
}
return iters;
}
/* ================================================================
* 3. PCIE-TLP model
*
* Simulates PCIe Transaction Layer Packets as the instruction transport.
* Each TLP = 64 bytes (one cache line):
* [src_line:16] [dst_line:16] [next_line:16] [flags:16] [payload: 56 B pad]
*
* The OISC engine reads TLPs from a "RX ring" (pre-allocated buffer),
* executes the SUBLEQ on cache lines, and writes result TLPs to a
* "TX ring."
*
* Metric: TLPs processed per second = cache lines / sec.
* At PCIe Gen5 x16 (64 GT/s, 128B/130B encoding) = ~63 GB/s raw.
* Each TLP read+write = 2 × 64B = 128B per instruction.
* Theoretical max: ~492 M TLPs/sec per PCIe Gen5 x16 lane pair.
*
* This benchmark measures the software-side bottleneck.
* ================================================================ */
typedef struct __attribute__((packed, aligned(64))) {
uint16_t src_line;
uint16_t dst_line;
uint16_t next_line;
uint16_t flags;
uint8_t pad[56]; /* fill to 64 B */
} PcieTlp;
/* Generate a batch of TLPs in a ring buffer */
static int generate_tlps(PcieTlp *ring, int count, int src_line,
int dst_line, int next_line) {
for (int i = 0; i < count; i++) {
ring[i].src_line = src_line;
ring[i].dst_line = dst_line;
ring[i].next_line = next_line;
ring[i].flags = 0;
memset(ring[i].pad, 0, 56);
}
return count;
}
/* Execute a TLP stream against a cache-line memory, as PCIe RX→process→TX */
static int64_t bench_pcie_tlp(PcieTlp *rx_ring, int n_tlps,
cacheline_t *mem) {
int64_t processed = 0;
for (int i = 0; i < n_tlps; i++) {
PcieTlp *tlp = &rx_ring[i];
/* SUBLEQ on cache lines */
cl_sub(&mem[tlp->src_line], &mem[tlp->dst_line]);
/* "Write-back" — mark TLP as processed (simulates TX completion) */
tlp->flags = 1;
processed++;
}
return processed;
}
/* ================================================================
* Timing helper
* ================================================================ */
static double now_sec(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec + ts.tv_nsec * 1e-9;
}
/* ================================================================
* Main
* ================================================================ */
int main(void) {
printf("=== EPYC 9645 Turin — Cache-Line OISC Benchmark ===\n\n");
/* ---------- 1. WORD SUBLEQ ---------- */
printf("--- 1. WORD-SUBLEQ (baseline) ---\n");
int16_t *word_mem = aligned_alloc(64, MEM_SIZE);
int word_pc;
build_word_prog(word_mem, 10000000, &word_pc);
double t0 = now_sec();
int64_t wi = bench_word_subleq(word_mem, word_pc, 10000000);
double t1 = now_sec();
double ws = wi / (t1 - t0) / 1e6;
printf(" %ld iter in %.4f sec = %.2f M instr/sec\n",
(long)wi, t1 - t0, ws);
printf(" Words touched/sec: %.2f M\n", ws * 6); /* 6 word accesses per instr */
free(word_mem);
/* ---------- 2. CL SUBLEQ ---------- */
printf("\n--- 2. CL-SUBLEQ (cache-line granular, AVX-512) ---\n");
cacheline_t *cl_mem = aligned_alloc(64, MEM_SIZE);
/* Build program in cl_mem[0] as int16 triples */
int cl_pc;
build_word_prog((int16_t *)cl_mem, 10000000, &cl_pc);
/* Warmup */
bench_cl_subleq(cl_mem, cl_pc, 1000);
build_word_prog((int16_t *)cl_mem, 10000000, &cl_pc);
t0 = now_sec();
int64_t ci = bench_cl_subleq(cl_mem, cl_pc, 1000000);
t1 = now_sec();
double cs = ci / (t1 - t0) / 1e6;
printf(" %ld iter in %.4f sec = %.2f M instr/sec\n",
(long)ci, t1 - t0, cs);
printf(" Cache lines touched/sec: %.2f M\n", cs * 2);
free(cl_mem);
/* ---------- 3. PCIE TLP ---------- */
printf("\n--- 3. PCIE-TLP MODEL (cache-line TLP stream) ---\n");
int n_tlps = 100000;
PcieTlp *rx_ring = aligned_alloc(64, n_tlps * sizeof(PcieTlp));
cacheline_t *tlp_mem = aligned_alloc(64, MEM_SIZE);
/* Set up: zero memory, generate TLP stream */
memset(tlp_mem, 0, MEM_SIZE);
generate_tlps(rx_ring, n_tlps, 2, 3, HALT);
/* line 2 = zero, line 3 = zero. cl_sub(zero, zero) = no-op. */
/* Warmup */
bench_pcie_tlp(rx_ring, 1000, tlp_mem);
t0 = now_sec();
int64_t pi = bench_pcie_tlp(rx_ring, n_tlps, tlp_mem);
t1 = now_sec();
double ps = pi / (t1 - t0) / 1e6;
printf(" %ld TLPs in %.4f sec = %.2f M TLPs/sec\n",
(long)pi, t1 - t0, ps);
printf(" PCIe BW equivalent: %.2f GB/s (2 × 64B per TLP)\n",
ps * 128.0 / 1000.0);
/* Each TLP = 64B read + 64B write = 128B */
free(rx_ring);
free(tlp_mem);
/* ---------- SUMMARY ---------- */
printf("\n=== SUMMARY ===\n");
printf(" %-30s %12.2f M ops/sec\n", "WORD-SUBLEQ", ws);
printf(" %-30s %12.2f M ops/sec\n", "CL-SUBLEQ (AVX-512)", cs);
printf(" %-30s %12.2f M ops/sec\n", "PCIE-TLP (cache line)", ps);
printf("\n TLP → PCIe Gen5 x16 raw: ~63 GB/s\n");
printf(" TLP → PCIe Gen5 x8 raw: ~31 GB/s\n");
printf(" TLP → PCIe Gen4 x16 raw: ~31 GB/s\n");
return 0;
}