skip to content
==> src/sim.c <==
1/* The CPU implementation of mesh-rt's device primitives, and the launcher. */
2#if !defined(__NVPTX__)
3
4#include "mesh/rt/sim.h"
5#include "mesh/abi/base.h"
6
7#include <errno.h>
8#include <pthread.h>
9#include <stdatomic.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <time.h>
14
15typedef struct {
16    pthread_barrier_t bar;
17    uint32_t xch[32];
18} sim_warp;
19
20typedef struct {
21    uint32_t ctaid;
22    uint32_t ntid;
23    uint32_t smem_bytes;
24    uint8_t *smem;
25    pthread_barrier_t bar;
26    sim_warp *warps;
27} sim_node;
28
29typedef struct {
30    sim_node *node;
31    uint32_t tid;
32    mesh_sim_kernel kernel;
33    uint8_t *mb;
34    uint32_t slot;
35    pthread_t thread;
36    bool started;
37} sim_thread;
38
39struct mesh_sim_launch {
40    uint32_t nodes;
41    uint32_t threads; /* per node */
42    sim_node *node;
43    sim_thread *thr;
44};
45
46static _Thread_local sim_thread *self;
47
48static sim_thread *me(void) {
49    if (!self) {
50        fprintf(stderr, "mesh-rt-sim: a device primitive was called outside a simulated thread\n");
51        abort();
52    }
53    return self;
54}
55
56static _Atomic uint32_t *a32(const uint8_t *p) { return (_Atomic uint32_t *)(void *)(uintptr_t)p; }
57static _Atomic uint64_t *a64(const uint8_t *p) { return (_Atomic uint64_t *)(void *)(uintptr_t)p; }
58
59uint32_t mesh_rt_smid(void) { return me()->node->ctaid; }
60uint32_t mesh_rt_tid(void) { return me()->tid; }
61uint32_t mesh_rt_lane(void) { return me()->tid & 31u; }
62uint32_t mesh_rt_dyn_smem(void) { return me()->node->smem_bytes; }
63uint32_t mesh_rt_ntid(void) { return me()->node->ntid; }
64uint32_t mesh_rt_ctaid(void) { return me()->node->ctaid; }
65
66uint64_t mesh_rt_gtimer(void) {
67    struct timespec ts;
68    clock_gettime(CLOCK_MONOTONIC, &ts);
69    return (uint64_t)ts.tv_sec * 1000000000u + (uint64_t)ts.tv_nsec;
70}
71
72uint32_t mesh_rt_ld_acquire(const uint8_t *p) { return atomic_load_explicit(a32(p), memory_order_acquire); }
73uint64_t mesh_rt_ld_acquire64(const uint8_t *p) { return atomic_load_explicit(a64(p), memory_order_acquire); }
74uint32_t mesh_rt_ld_relaxed(const uint8_t *p) { return atomic_load_explicit(a32(p), memory_order_relaxed); }
75uint64_t mesh_rt_ld_relaxed64(const uint8_t *p) { return atomic_load_explicit(a64(p), memory_order_relaxed); }
76void mesh_rt_st_release(uint8_t *p, uint32_t v) { atomic_store_explicit(a32(p), v, memory_order_release); }
77void mesh_rt_st_release64(uint8_t *p, uint64_t v) { atomic_store_explicit(a64(p), v, memory_order_release); }
78void mesh_rt_st_relaxed(uint8_t *p, uint32_t v) { atomic_store_explicit(a32(p), v, memory_order_relaxed); }
79uint32_t mesh_rt_atom_add(uint8_t *p, uint32_t v) { return atomic_fetch_add_explicit(a32(p), v, memory_order_relaxed); }
80uint32_t mesh_rt_atom_or(uint8_t *p, uint32_t v) { return atomic_fetch_or_explicit(a32(p), v, memory_order_relaxed); }
81uint32_t mesh_rt_atom_and(uint8_t *p, uint32_t v) { return atomic_fetch_and_explicit(a32(p), v, memory_order_relaxed); }
82uint32_t mesh_rt_atom_cas(uint8_t *p, uint32_t cmp, uint32_t desired) {
83    atomic_compare_exchange_strong_explicit(a32(p), &cmp, desired, memory_order_relaxed, memory_order_relaxed);
84    return cmp;
85}
86void mesh_rt_red_add(uint8_t *p, uint32_t v) { atomic_fetch_add_explicit(a32(p), v, memory_order_relaxed); }
87void mesh_rt_fence_sc(void) { atomic_thread_fence(memory_order_seq_cst); }
88void mesh_rt_fence_acq_rel(void) { atomic_thread_fence(memory_order_acq_rel); }
89
90void mesh_rt_nanosleep(uint32_t ns) {
91    struct timespec ts = {0, (long)ns};
92    while (nanosleep(&ts, &ts) != 0 && errno == EINTR) {
93    }
94}
95
96static void wait_bar(pthread_barrier_t *b) {
97    int r = pthread_barrier_wait(b);
98    if (r != 0 && r != PTHREAD_BARRIER_SERIAL_THREAD) {
99        fprintf(stderr, "mesh-rt-sim: pthread_barrier_wait: %s\n", strerror(r));
100        abort();
101    }
102}
103
104static sim_warp *my_warp(void) {
105    sim_thread *t = me();
106    return &t->node->warps[t->tid >> 5];
107}
108
109void mesh_rt_warp_sync(void) { wait_bar(&my_warp()->bar); }
110void mesh_rt_bar_sync(void) {
111    atomic_thread_fence(memory_order_seq_cst);
112    wait_bar(&me()->node->bar);
113    atomic_thread_fence(memory_order_seq_cst);
114}
115uint8_t *mesh_rt_smem_base(void) { return me()->node->smem; }
116
117/* Every lane deposits v; after the barrier each reads what it wants; the second
118 * barrier keeps the next exchange from overwriting a value not yet read. */
119static uint32_t exchange(uint32_t v, uint32_t (*pick)(const uint32_t *xch, uint32_t lane, uint32_t arg), uint32_t arg) {
120    sim_warp *w = my_warp();
121    uint32_t lane = me()->tid & 31u;
122    w->xch[lane] = v;
123    wait_bar(&w->bar);
124    uint32_t r = pick(w->xch, lane, arg);
125    wait_bar(&w->bar);
126    return r;
127}
128static uint32_t pick_idx(const uint32_t *x, uint32_t lane, uint32_t src) { (void)lane; return x[src & 31u]; }
129static uint32_t pick_bfly(const uint32_t *x, uint32_t lane, uint32_t m) { return x[(lane ^ m) & 31u]; }
130static uint32_t pick_up(const uint32_t *x, uint32_t lane, uint32_t d) { return lane >= d ? x[lane - d] : x[lane]; }
131static uint32_t pick_ballot(const uint32_t *x, uint32_t lane, uint32_t unused) {
132    (void)lane, (void)unused;
133    uint32_t r = 0;
134    for (uint32_t i = 0; i < 32; i++)
135        if (x[i]) r |= 1u << i;
136    return r;
137}
138
139uint32_t mesh_rt_shfl_idx(uint32_t v, uint32_t src) { return exchange(v, pick_idx, src); }
140uint32_t mesh_rt_shfl_bfly(uint32_t v, uint32_t m) { return exchange(v, pick_bfly, m); }
141uint32_t mesh_rt_shfl_up(uint32_t v, uint32_t d) { return exchange(v, pick_up, d); }
142uint32_t mesh_rt_ballot(bool p) { return exchange(p ? 1u : 0u, pick_ballot, 0); }
143
144_Noreturn void mesh_rt_trap(void) {
145    sim_thread *t = self;
146    fprintf(stderr, "mesh-rt-sim: trap (node %u, thread %u)\n", t ? t->node->ctaid : 0u, t ? t->tid : 0u);
147    abort();
148}
149
150/* f32 subnormal inputs and results flush to sign-preserving zero, as the
151 * cluster float mode's .ftz instructions do (README "Cluster float mode"). */
152static void ftz_on(void) {
153#if defined(__x86_64__) || defined(__i386__)
154    uint32_t csr;
155    __asm__ __volatile__("stmxcsr %0" : "=m"(csr));
156    csr |= 0x8040u; /* FTZ (bit 15) | DAZ (bit 6) */
157    __asm__ __volatile__("ldmxcsr %0" ::"m"(csr));
158#elif defined(__aarch64__)
159    uint64_t fpcr;
160    __asm__ __volatile__("mrs %0, fpcr" : "=r"(fpcr));
161    fpcr |= (uint64_t)1 << 24; /* FZ */
162    __asm__ __volatile__("msr fpcr, %0" ::"r"(fpcr));
163#endif
164}
165
166static void *run(void *arg) {
167    sim_thread *t = arg;
168    self = t;
169    ftz_on();
170    t->kernel(t->mb, t->slot);
171    self = NULL;
172    return NULL;
173}
174
175static void free_launch(mesh_sim_launch *l) {
176    for (uint32_t n = 0; n < l->nodes; n++) {
177        sim_node *nd = &l->node[n];
178        if (nd->warps) {
179            for (uint32_t w = 0; w < l->threads / 32; w++) pthread_barrier_destroy(&nd->warps[w].bar);
180            free(nd->warps);
181        }
182        pthread_barrier_destroy(&nd->bar);
183        free(nd->smem);
184    }
185    free(l->node);
186    free(l->thr);
187    free(l);
188}
189
190mesh_sim_launch *mesh_sim_start(mesh_sim_kernel kernel, uint8_t *mb, uint32_t slot, uint32_t nodes, uint32_t warps,
191                                uint32_t shared_bytes) {
192    if (nodes == 0 || warps == 0 || warps > MESH_MAX_WARPS) return NULL;
193    mesh_sim_launch *l = calloc(1, sizeof *l);
194    if (!l) return NULL;
195    l->nodes = nodes;
196    l->threads = 32 * warps;
197    l->node = calloc(nodes, sizeof *l->node);
198    l->thr = calloc((size_t)nodes * l->threads, sizeof *l->thr);
199    if (!l->node || !l->thr) { free(l->node); free(l->thr); free(l); return NULL; }
200    for (uint32_t n = 0; n < nodes; n++) {
201        sim_node *nd = &l->node[n];
202        nd->ctaid = n;
203        nd->ntid = l->threads;
204        nd->smem_bytes = MESH_RT_SHARED_BYTES + shared_bytes;
205        nd->smem = aligned_alloc(16, ((size_t)nd->smem_bytes + 15) & ~(size_t)15);
206        nd->warps = calloc(warps, sizeof *nd->warps);
207        if (!nd->smem || !nd->warps) { fprintf(stderr, "mesh-rt-sim: out of memory\n"); abort(); }
208        memset(nd->smem, 0, nd->smem_bytes);
209        pthread_barrier_init(&nd->bar, NULL, l->threads);
210        for (uint32_t w = 0; w < warps; w++) pthread_barrier_init(&nd->warps[w].bar, NULL, 32);
211    }
212    pthread_attr_t attr;
213    pthread_attr_init(&attr);
214    pthread_attr_setstacksize(&attr, (size_t)1 << 20);
215    for (uint32_t n = 0; n < nodes; n++) {
216        for (uint32_t i = 0; i < l->threads; i++) {
217            sim_thread *t = &l->thr[(size_t)n * l->threads + i];
218            *t = (sim_thread){.node = &l->node[n], .tid = i, .kernel = kernel, .mb = mb, .slot = slot};
219            int r = pthread_create(&t->thread, &attr, run, t);
220            if (r != 0) {
221                /* Threads already started would wait at a barrier forever. */
222                fprintf(stderr, "mesh-rt-sim: pthread_create: %s\n", strerror(r));
223                abort();
224            }
225            t->started = true;
226        }
227    }
228    pthread_attr_destroy(&attr);
229    return l;
230}
231
232void mesh_sim_join(mesh_sim_launch *l) {
233    if (!l) return;
234    for (size_t i = 0; i < (size_t)l->nodes * l->threads; i++)
235        if (l->thr[i].started) pthread_join(l->thr[i].thread, NULL);
236    free_launch(l);
237}
238
239#else
240/* nvptx64: nothing here (an empty translation unit is not ISO C). */
241typedef int mesh_rt_sim_unused;
242#endif