==> tests/driver.c <==1/* driver.rs's tests (job_setup_in_order, reads_by_name), and the driver end to end over a fake CPU
2 * executor whose "nodes" are a thread playing mesh-rt's node loop on the mailbox: autoload, the blink,
3 * OP_INIT, on-demand tasks, stop and start, refusals, unload, --state, shutdown. */
4#define _XOPEN_SOURCE 700 /* sexc compiles tests without the package's cflags (reported) */
5#include "mesh/check.h"
6#include "mesh/driver.h"
7#include "mesh/log.h"
8#include "mesh/manifest.h"
9
10#include <pthread.h>
11#include <stdatomic.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <sys/stat.h>
16#include <unistd.h>
17
18/* ---- a fake executor ---- */
19
20static mesh_mailbox MB;
21
22typedef struct {
23 pthread_t th;
24 bool started;
25 _Atomic bool running;
26 uint32_t slot, nodes;
27} fake_kernel;
28
29static bool k_zero(const mesh_kernel *k, uint32_t *o, char **e) { (void)k, (void)e; *o = 0; return true; }
30static bool k_one(const mesh_kernel *k, uint32_t *o, char **e) { (void)k, (void)e; *o = 1; return true; }
31static uint64_t k_scratch(const mesh_kernel *k) { (void)k; return 0; }
32static bool k_global(const mesh_kernel *k, const char *n, uint32_t *o, char **e) {
33 (void)k;
34 if (strcmp(n, MESH_RT_ABI_SYMBOL)) { *e = strdup("no such global"); return false; }
35 *o = MESH_RT_ABI;
36 return true;
37}
38static void k_drop(mesh_kernel *k) {
39 fake_kernel *f = k->self;
40 if (f->started) pthread_join(f->th, NULL);
41 free(f);
42}
43static const mesh_kernel_ops KOPS = {MESH_BACKEND_WASM, k_zero, k_zero, k_one, k_scratch, k_global, k_drop};
44
45/* The node loop: online, take tickets in order, run them (OP_INIT writes a magic; a task is arg * 3 + 7), halt on P_SHUTDOWN. */
46static void *nodes(void *arg) {
47 fake_kernel *f = arg;
48 size_t c = mesh_prog_ctl(f->slot);
49 mesh_mailbox_fetch_add(MB, c + MESH_P_ONLINE, f->nodes);
50 uint32_t view = mesh_mailbox_get(MB, mesh_prog_entry(f->slot) + MESH_PT_VIEW_OFF);
51 for (;;) {
52 if (mesh_mailbox_get(MB, c + MESH_P_SHUTDOWN)) break;
53 uint32_t t = mesh_mailbox_get(MB, c + MESH_P_RING_HEAD);
54 size_t e = c + MESH_P_RING + MESH_ENTRY_BYTES * (t % MESH_PROG_RING);
55 if (mesh_mailbox_get(MB, e + MESH_E_SEQ) != t + 1) { mesh_sleep_us(50); continue; }
56 uint32_t op = mesh_mailbox_get(MB, e + MESH_E_OP), a = mesh_mailbox_get(MB, e + MESH_E_ARG), k = mesh_mailbox_get(MB, e + MESH_E_SLOT);
57 uint32_t r = 0;
58 if (op == MESH_OP_INIT) mesh_mailbox_set(MB, view, 0x4f4c4548u);
59 else if (op == MESH_OP_TASK) r = a * 3 + 7;
60 else r = MESH_R_BAD_OP;
61 mesh_mailbox_set(MB, c + MESH_P_RING_HEAD, t + 1);
62 mesh_mailbox_set(MB, c + MESH_P_RESULTS + 4 * k, r);
63 mesh_mailbox_publish(MB, c + MESH_P_MARKS + 4 * k, t + 1);
64 mesh_mailbox_fetch_add(MB, c + MESH_P_DONE, 1);
65 }
66 mesh_mailbox_fetch_add(MB, c + MESH_P_HALTED, f->nodes);
67 atomic_store(&f->running, false);
68 return NULL;
69}
70
71static mesh_caps CAPS = {MESH_BACKEND_WASM, "fake", 0, 0, 1, 1, false, 0, 0, 0, 0, "fake"};
72static const mesh_caps *b_caps(const mesh_backend *b) { (void)b; return &CAPS; }
73static bool b_true(const mesh_backend *b) { (void)b; return true; }
74static bool b_false(const mesh_backend *b) { (void)b; return false; }
75static uint32_t b_iso(const mesh_backend *b) { (void)b; return MESH_ISOLATION_LEASE; }
76static bool b_mailbox(const mesh_backend *b, uint8_t **h, uint64_t *dev, char **e) { (void)b, (void)e; *h = MB.base; *dev = 0; return true; }
77static bool b_current(const mesh_backend *b, char **e) { (void)b, (void)e; return true; }
78
79static bool c_needs_ptx(const mesh_compiler *c) { (void)c; return false; }
80static bool c_prepare(const mesh_compiler *c, mesh_payload *src, const uint64_t *expect, const char *root, mesh_prepared *out, char **err) {
81 (void)c, (void)expect, (void)root;
82 memset(out, 0, sizeof *out);
83 if (!mesh_check_manifest(src, MESH_BACKEND_WASM, &out->manifest, err)) { mesh_payload_free(src); return false; }
84 out->hash = 0x1234;
85 out->code_len = strlen(src->manifest);
86 out->code = (uint8_t *)strdup(src->manifest);
87 out->src = *src;
88 memset(src, 0, sizeof *src);
89 out->exec = MESH_BACKEND_WASM;
90 out->kernel = calloc(1, sizeof *out->kernel);
91 out->kernel->ops = &KOPS;
92 out->kernel->self = calloc(1, sizeof(fake_kernel));
93 return true;
94}
95static const mesh_compiler_ops COPS = {c_needs_ptx, c_prepare};
96static const mesh_compiler COMP = {&COPS, NULL};
97static const mesh_compiler *b_compiler(const mesh_backend *b) { (void)b; return &COMP; }
98
99static bool b_load(const mesh_backend *b, mesh_prepared *p, mesh_kernel **out, char **e) {
100 (void)b, (void)e;
101 *out = p->kernel;
102 p->kernel = NULL;
103 return true;
104}
105static bool b_stream(const mesh_backend *b, uint32_t slot, const mesh_kernel *k, mesh_stream **out, char **e) {
106 (void)b, (void)k, (void)e;
107 *out = calloc(1, sizeof **out);
108 (*out)->exec = MESH_BACKEND_WASM;
109 (*out)->slot = slot;
110 return true;
111}
112static bool b_launch(const mesh_backend *b, const mesh_stream *st, const mesh_kernel *k, uint32_t n, uint64_t dev, uint32_t slot, char **e) {
113 (void)b, (void)st, (void)dev, (void)e;
114 fake_kernel *f = k->self;
115 if (f->started) pthread_join(f->th, NULL);
116 f->slot = slot;
117 f->nodes = n;
118 atomic_store(&f->running, true);
119 f->started = true;
120 pthread_create(&f->th, NULL, nodes, f);
121 return true;
122}
123static bool b_query(const mesh_backend *b, const mesh_stream *st, const mesh_kernel *k, bool *done, char **e) {
124 (void)b, (void)st, (void)e;
125 *done = !atomic_load(&((fake_kernel *)k->self)->running);
126 return true;
127}
128static bool b_unload(const mesh_backend *b, mesh_kernel *k, char **e) {
129 (void)b, (void)e;
130 mesh_kernel_drop(k);
131 return true;
132}
133static bool b_stack(const mesh_backend *b, size_t *o, char **e) { (void)b, (void)e; *o = 0; return true; }
134static bool b_set_stack(const mesh_backend *b, size_t n, char **e) { (void)b, (void)n, (void)e; return true; }
135static bool b_scope(const mesh_backend *b, bool has, uint32_t slot, uint32_t *scope) { (void)b; *scope = has ? slot : MESH_NO_SLOT; return true; }
136
137static void b_destroy(mesh_backend *b) { free(b); }
138static const mesh_backend_ops BOPS = {b_caps, b_true, b_false, b_iso, b_mailbox, b_current, b_compiler, b_load, b_stream, b_launch, b_query,
139 b_unload, b_stack, b_set_stack, b_scope, NULL, NULL, NULL, NULL, NULL, b_destroy};
140
141/* ---- helpers ---- */
142
143static mesh_shared *SH;
144
145static bool ask(mesh_cmd_kind k, const char *name, bool on, char **text) {
146 mesh_cmd c = mesh_cmd_new(k);
147 if (name) c.name = strdup(name);
148 c.on = on;
149 return mesh_shared_ask(SH, c, 10000, text);
150}
151
152static bool load(const char *dir, char **text) {
153 mesh_cmd c = mesh_cmd_new(MESH_CMD_LOAD);
154 c.has_src = true;
155 c.src = mesh_src_catalog(dir);
156 return mesh_shared_ask(SH, c, 10000, text);
157}
158
159static int task(const char *name, uint32_t op, uint32_t arg, uint32_t *v, char **text) {
160 mesh_reply *r = mesh_reply_new();
161 mesh_cmd c = mesh_cmd_new(MESH_CMD_TASK);
162 c.name = strdup(name);
163 c.op = op;
164 c.arg = arg;
165 c.reply = mesh_reply_sender(r);
166 mesh_chan_send(SH->cmd, &c);
167 mesh_result x;
168 int w = mesh_reply_wait(r, 10000, &x);
169 mesh_reply_release(r);
170 if (w != MESH_CHAN_OK) return -1;
171 *v = x.value;
172 *text = x.text ? strdup(x.text) : NULL;
173 int ok = x.ok;
174 mesh_result_free(&x);
175 return ok;
176}
177
178static void *run(void *arg) {
179 *(int *)((void **)arg)[0] = mesh_driver_run((mesh_driver *)((void **)arg)[1]);
180 return NULL;
181}
182
183static void write_file(const char *path, const char *text) {
184 FILE *f = fopen(path, "w");
185 fputs(text, f);
186 fclose(f);
187}
188
189int main(void) {
190 /* ---- the Rust tests ---- */
191 mesh_setup_state j;
192 memset(&j, 0, sizeof j);
193 const mj_setup setup[2] = {{5, 0}, {4, 150}};
194 mesh_setup_step st = mesh_setup_state_step(&j, 7, setup, 2);
195 CHECK(st.k == MESH_SETUP_PUBLISH && st.op == 5 && st.arg == 0);
196 mesh_setup_state_published(&j);
197 CHECK(mesh_setup_state_step(&j, 7, setup, 2).k == MESH_SETUP_WAIT);
198 CHECK(mesh_setup_state_step(&j, 7, setup, 2).k == MESH_SETUP_WAIT);
199 mesh_setup_state_completed(&j, 7, NULL);
200 st = mesh_setup_state_step(&j, 7, setup, 2);
201 CHECK(st.k == MESH_SETUP_PUBLISH && st.op == 4 && st.arg == 150);
202 mesh_setup_state_published(&j);
203 CHECK(mesh_setup_state_step(&j, 7, setup, 2).k == MESH_SETUP_WAIT);
204 mesh_setup_state_completed(&j, 7, NULL);
205 CHECK(mesh_setup_state_step(&j, 7, setup, 2).k == MESH_SETUP_READY);
206 CHECK(mesh_setup_state_step(&j, 7, setup, 2).k == MESH_SETUP_READY);
207 mesh_setup_state k;
208 memset(&k, 0, sizeof k);
209 CHECK(mesh_setup_state_step(&k, 1, setup, 2).k == MESH_SETUP_PUBLISH);
210 CHECK(mesh_setup_state_step(&k, 1, setup, 2).k == MESH_SETUP_PUBLISH);
211 const mj_setup s8[1] = {{6, 32}};
212 st = mesh_setup_state_step(&j, 8, s8, 1);
213 CHECK(st.k == MESH_SETUP_PUBLISH && st.op == 6);
214 mesh_setup_state_published(&j);
215 mesh_setup_state_completed(&j, 7, "old");
216 CHECK(mesh_setup_state_step(&j, 8, s8, 1).k == MESH_SETUP_WAIT);
217 mesh_setup_state_completed(&j, 8, NULL);
218 CHECK(mesh_setup_state_step(&j, 8, s8, 1).k == MESH_SETUP_READY);
219 mesh_setup_state f;
220 memset(&f, 0, sizeof f);
221 CHECK(mesh_setup_state_step(&f, 2, setup, 2).k == MESH_SETUP_PUBLISH);
222 mesh_setup_state_published(&f);
223 mesh_setup_state_completed(&f, 2, "bad op");
224 st = mesh_setup_state_step(&f, 2, setup, 2);
225 CHECK(st.k == MESH_SETUP_FAILED && strcmp(st.err, "bad op") == 0);
226 mesh_setup_state_reset(&j);
227 CHECK(mesh_setup_state_step(&j, 8, s8, 1).k == MESH_SETUP_PUBLISH);
228 mesh_setup_state z;
229 memset(&z, 0, sizeof z);
230 CHECK(mesh_setup_state_step(&z, 3, NULL, 0).k == MESH_SETUP_READY);
231 mesh_setup_state_reset(&j);
232 mesh_setup_state_reset(&k);
233 mesh_setup_state_reset(&f);
234 mesh_setup_state_reset(&z);
235
236 mesh_manifest m;
237 char *err = NULL;
238 const char *t1 = "name = \"infer\"\nabi = 6\nreads = [\"train\", \"doom\"]";
239 CHECK(mesh_manifest_parse(t1, strlen(t1), &m, &err));
240 const char *names[4] = {"train", NULL, "hello", "doom"};
241 CHECK_U(mesh_reads_mask(&m, names, 4), 9);
242 mesh_manifest_free(&m);
243 const char *t2 = "name = \"http\"\nabi = 6\nreads = [\"*\"]";
244 CHECK(mesh_manifest_parse(t2, strlen(t2), &m, &err));
245 CHECK_U(mesh_reads_mask(&m, names, 4), MESH_READS_ALL);
246 mesh_manifest_free(&m);
247
248 /* ---- end to end ---- */
249 char root[64];
250 snprintf(root, sizeof root, "/tmp/mesh-driver-%d", (int)getpid());
251 char path[256];
252 mkdir(root, 0700);
253 snprintf(path, sizeof path, "%s/hello", root);
254 mkdir(path, 0700);
255 snprintf(path, sizeof path, "%s/hello/program.toml", root);
256 write_file(path, "name = \"hello\"\nabi = 6\nview_bytes = 0x1000\nsnap_bytes = 0x40\nnodes = 2\npolicy = \"on_demand\"\nautostart = true\nbackends = [\"wasm\"]\n");
257 snprintf(path, sizeof path, "%s/gpu", root);
258 mkdir(path, 0700);
259 snprintf(path, sizeof path, "%s/gpu/program.toml", root);
260 write_file(path, "name = \"gpu\"\nabi = 6\nbackends = [\"cuda\"]\nautostart = true\n");
261 char state[256];
262 snprintf(state, sizeof state, "%s/state", root);
263 mesh_log_set_fd(-1);
264
265 MB = mesh_mailbox_test();
266 static _Atomic bool quit;
267 SH = mesh_shared_new(MB, root, false, 0, 1, &quit);
268 mesh_backend *gpu = calloc(1, sizeof *gpu);
269 gpu->ops = &BOPS;
270 mesh_driver_config cfg = {false, state, true, false};
271 mesh_driver *d = mesh_driver_new(SH, gpu, NULL, 0, cfg);
272 CHECK(mesh_driver_start(d, &err));
273 void *arg[2] = {0, d};
274 int code = -1;
275 arg[0] = &code;
276 pthread_t th;
277 pthread_create(&th, NULL, run, arg);
278 /* The autoload: hello loads and starts (OP_INIT first); gpu is not loaded here (unsupported). */
279 for (int i = 0; i < 2000 && !atomic_load(&SH->ready); i++) mesh_sleep_us(1000);
280 CHECK(atomic_load(&SH->ready));
281 for (int i = 0; i < 2000 && mesh_mailbox_get(MB, mesh_prog_entry(0) + MESH_PT_STATE) != MESH_ST_RUNNING; i++) mesh_sleep_us(1000);
282 CHECK_U(mesh_mailbox_get(MB, mesh_prog_entry(0) + MESH_PT_STATE), MESH_ST_RUNNING);
283 CHECK_U(mesh_mailbox_get(MB, MESH_H_MAGIC), MESH_MAGIC);
284 uint32_t v = 0;
285 char *text = NULL;
286 CHECK_I(task("hello", MESH_OP_TASK, 5, &v, &text), 1);
287 CHECK_U(v, 22);
288 free(text);
289 mesh_sleep_us(5000);
290 CHECK_U(mesh_mailbox_get(MB, mesh_mailbox_get(MB, mesh_prog_entry(0) + MESH_PT_VIEW_OFF)), 0x4f4c4548u);
291 /* Refusals. */
292 CHECK(!load("hello", &text));
293 CHECK_S(text, "name exists: hello is loaded");
294 free(text);
295 CHECK(!load("../etc", &text));
296 CHECK(strncmp(text, "manifest: ", 10) == 0);
297 free(text);
298 CHECK(!load("gpu", &text));
299 CHECK_S(text, "unsupported: gpu runs on cuda (manifest `backends`); this meshd runs wasm");
300 free(text);
301 CHECK(!ask(MESH_CMD_ROLLBACK, "hello", false, &text));
302 CHECK_S(text, "hello: no previous image to roll back to");
303 free(text);
304 CHECK(!ask(MESH_CMD_PROG_RUN, "nope", true, &text));
305 CHECK_S(text, "no program \"nope\" (`programs` lists them)");
306 free(text);
307 CHECK_I(task("nope", 0, 0, &v, &text), 0);
308 CHECK_S(text, "no program \"nope\" (`programs` lists them)");
309 free(text);
310 /* Stop and start: zero gap for the others, answered once halted / online. */
311 CHECK(ask(MESH_CMD_PROG_RUN, "hello", false, &text));
312 CHECK(strstr(text, " us") != NULL);
313 free(text);
314 CHECK_U(mesh_mailbox_get(MB, mesh_prog_entry(0) + MESH_PT_STATE), MESH_ST_LOADED);
315 CHECK_I(task("hello", MESH_OP_TASK, 1, &v, &text), 0);
316 CHECK_S(text, "hello is not running");
317 free(text);
318 CHECK(ask(MESH_CMD_PROG_RUN, "hello", true, &text));
319 free(text);
320 CHECK_I(task("hello", MESH_OP_TASK, 10, &v, &text), 1);
321 CHECK_U(v, 37);
322 free(text);
323 /* A bad op: the program's answer. */
324 CHECK_I(task("hello", 9, 0, &v, &text), 1);
325 CHECK_U(v, MESH_R_BAD_OP);
326 free(text);
327 /* Reset while running: a blink of its slot; the view zeroed, OP_INIT again. */
328 CHECK(ask(MESH_CMD_RESET, "hello", false, &text));
329 CHECK(strncmp(text, "blink ", 6) == 0);
330 free(text);
331 /* --state records it. */
332 mesh_sleep_us(20000);
333 FILE *sf = fopen(state, "r");
334 char line[256] = "";
335 if (sf) {
336 char *got = fgets(line, sizeof line, sf);
337 got = fgets(line, sizeof line, sf);
338 (void)got;
339 fclose(sf);
340 }
341 CHECK_S(line, "hello hello 1\n");
342 /* Unload. */
343 CHECK(ask(MESH_CMD_UNLOAD, "hello", false, &text));
344 CHECK_S(text, "");
345 free(text);
346 CHECK_U(mesh_mailbox_get(MB, mesh_prog_entry(0) + MESH_PT_STATE), MESH_ST_EMPTY);
347 /* Loaded again into the freed slot (the blink frees the old view). */
348 CHECK(load("hello", &text));
349 CHECK(strncmp(text, "slot 0 blink ", 13) == 0);
350 free(text);
351 atomic_store(&quit, true);
352 pthread_join(th, NULL);
353 CHECK_I(code, 0);
354 mesh_driver_free(d);
355 mesh_shared_free(SH);
356 free(MB.base);
357 char cmd[300];
358 snprintf(cmd, sizeof cmd, "%s/hello/program.toml", root);
359 unlink(cmd);
360 snprintf(cmd, sizeof cmd, "%s/gpu/program.toml", root);
361 unlink(cmd);
362 unlink(state);
363 snprintf(cmd, sizeof cmd, "%s/hello", root);
364 rmdir(cmd);
365 snprintf(cmd, sizeof cmd, "%s/gpu", root);
366 rmdir(cmd);
367 rmdir(root);
368 return mesh_check_done();
369}