==> src/parse.c <==1/* manifest.rs's `parse`: every key, default and refusal, in the Rust's order
2 * (so the first refusal is the same one), with the Rust's messages. */
3#include "mesh/manifest.h"
4#include "mesh/toml.h"
5
6#include "mesh/abi/backend.h"
7#include "mesh/abi/base.h"
8#include "mesh/abi/ext.h"
9#include "mesh/abi/input_queue.h"
10#include "mesh/abi/name.h"
11#include "mesh/abi/node.h"
12#include "mesh/abi/peer_sizes.h"
13#include "mesh/abi/pipe_ring.h"
14#include "mesh/abi/placement.h"
15#include "mesh/abi/program_table.h"
16#include "mesh/abi/ring.h"
17#include "mesh/abi/view_heap.h"
18
19#include <math.h>
20#include <stdlib.h>
21#include <string.h>
22
23static const char *const KEYS[] = {
24 "name", "abi", "view_bytes", "view_abi", "snap_bytes", "nodes", "warps", "shared_bytes", "scratch_bytes", "sms",
25 "policy", "hz", "fanout", "in_flight", "arg", "reads", "input", "request", "peer", "counters", "fields",
26 "autostart", "place", "place_state", "place_frame", "place_out", "place_show", "backends", "param", "float",
27 "mem_mb", "task_ms", "cpu", "nice", "background", "inbox", "inbox_type", "feeds", "replicas", "spread", "prefer",
28 "avoid", "arch", "min_mem_mb", "colocate_with", "group", "group_size", "group_members", "priority", "migrate",
29 "job_base", "job_out", "job_payload", "device"};
30_Static_assert(sizeof KEYS / sizeof KEYS[0] == 54, "KEYS: [&str; 54]");
31static const char *const NATIVE_KEYS[] = {"nice", "background", "device"};
32
33typedef struct {
34 char *key;
35 mesh_toml_val val;
36} kv;
37
38/* The parse's state: the key/values, the message being built, temporaries. */
39typedef struct {
40 kv *kvs;
41 size_t nkv;
42 mesh_text msg;
43 uint32_t view_bytes;
44} P;
45
46static const mesh_toml_val *get(const P *p, const char *k) {
47 for (size_t i = 0; i < p->nkv; i++)
48 if (strcmp(p->kvs[i].key, k) == 0) return &p->kvs[i].val;
49 return NULL;
50}
51
52/* Message building: printf, {:?} of a string, {:?} of a value. */
53static void m_(P *p, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
54static void m_(P *p, const char *fmt, ...) {
55 va_list ap;
56 va_start(ap, fmt);
57 mesh_text_vprintf(&p->msg, fmt, ap);
58 va_end(ap);
59}
60static void q_(P *p, const char *s) { mesh_text_debug_str(&p->msg, s, strlen(s)); }
61static void qn_(P *p, const char *s, size_t n) { mesh_text_debug_str(&p->msg, s, n); }
62static void v_(P *p, const mesh_toml_val *v) { mesh_toml_val_debug(&p->msg, v); }
63static void list_(P *p, const char *const *v, size_t n) {
64 mesh_text_putc(&p->msg, '[');
65 for (size_t i = 0; i < n; i++) {
66 if (i) mesh_text_puts(&p->msg, ", ");
67 q_(p, v[i]);
68 }
69 mesh_text_putc(&p->msg, ']');
70}
71static bool fail_(P *p, char **err) {
72 if (err) {
73 free(*err);
74 *err = mesh_text_take(&p->msg);
75 } else {
76 mesh_text_free(&p->msg);
77 }
78 return false;
79}
80
81#define FAIL(...) \
82 do { \
83 m_(p, __VA_ARGS__); \
84 return fail_(p, err); \
85 } while (0)
86
87static bool num(P *p, const char *k, uint32_t d, uint32_t *out, char **err) {
88 const mesh_toml_val *v = get(p, k);
89 if (!v) { *out = d; return true; }
90 if (v->kind == MESH_TOML_INT) { *out = v->n; return true; }
91 m_(p, "%s: expected a number, got ", k);
92 v_(p, v);
93 return fail_(p, err);
94}
95
96/* A string value or the default; *out points into the parse (not owned). */
97static bool text(P *p, const char *k, const char *d, const char **out, char **err) {
98 const mesh_toml_val *v = get(p, k);
99 if (!v) { *out = d; return true; }
100 if (v->kind == MESH_TOML_STR) { *out = v->s; return true; }
101 m_(p, "%s: expected a string, got ", k);
102 v_(p, v);
103 return fail_(p, err);
104}
105
106static char *const no_items[1] = {NULL};
107
108static bool list(P *p, const char *k, char *const **items, size_t *n, char **err) {
109 const mesh_toml_val *v = get(p, k);
110 if (!v) { *items = no_items; *n = 0; return true; }
111 if (v->kind == MESH_TOML_LIST) { *items = v->items; *n = v->nitems; return true; }
112 m_(p, "%s: expected a list, got ", k);
113 v_(p, v);
114 return fail_(p, err);
115}
116
117static bool in_view(P *p, const char *what, uint32_t off, uint32_t len, char **err) {
118 if ((uint64_t)off + len > p->view_bytes)
119 FAIL("%s: 0x%x+0x%x is outside view_bytes 0x%x", what, (unsigned)off, (unsigned)len, (unsigned)p->view_bytes);
120 return true;
121}
122
123static bool valid_type(const char *t) {
124 size_t n = strlen(t);
125 if (n == 0 || n > MESH_NAME_MAX) return false;
126 for (size_t i = 0; i < n; i++)
127 if (!((t[i] >= 'a' && t[i] <= 'z') || (t[i] >= '0' && t[i] <= '9') || t[i] == '_')) return false;
128 return true;
129}
130
131static bool vname(const char *s) { return mesh_valid_name((const uint8_t *)s, strlen(s)); }
132static bool pair(const char *s, uint32_t *a, uint32_t *b) { return mesh_toml_pair(s, strlen(s), a, b); }
133static bool ints(const char *s, size_t n, uint32_t *out) { return mesh_toml_int(s, n, out); }
134static bool is_pow2(uint32_t x) { return x != 0 && (x & (x - 1)) == 0; }
135
136static int position(const char *const *v, size_t n, const char *s) {
137 for (size_t i = 0; i < n; i++)
138 if (strcmp(v[i], s) == 0) return (int)i;
139 return -1;
140}
141
142static bool term_list(P *p, const char *k, mesh_term **out, size_t *nout, char **err) {
143 char *const *items;
144 size_t n;
145 if (!list(p, k, &items, &n, err)) return false;
146 *out = n ? mesh_alloc(n * sizeof **out) : NULL;
147 *nout = 0;
148 for (size_t i = 0; i < n; i++) {
149 char *e = NULL;
150 if (!mesh_term_parse(items[i], &(*out)[i], &e)) {
151 m_(p, "%s: %s", k, e);
152 free(e);
153 for (size_t j = 0; j < i; j++) mesh_term_free(&(*out)[j]);
154 free(*out);
155 *out = NULL;
156 return fail_(p, err);
157 }
158 (*nout)++;
159 }
160 return true;
161}
162
163typedef struct {
164 uint32_t off, len;
165 const char *what;
166} span;
167
168static bool overlaps(span a, span b) { return a.off < b.off + b.len && b.off < a.off + a.len; }
169
170static bool parse_kv(P *p, const char *text, size_t n, char **err) {
171 size_t lineno = 0;
172 size_t i = 0;
173 while (i < n) {
174 /* Rust's str::lines: split at \n, drop one \r before it, no empty last line. */
175 const char *line = text + i;
176 const char *nl = memchr(line, '\n', n - i);
177 size_t len = nl ? (size_t)(nl - line) : n - i;
178 i += len + (nl ? 1 : 0);
179 if (len > 0 && line[len - 1] == '\r') len--;
180 lineno++;
181 size_t ln = mesh_toml_strip_comment(line, len);
182 const char *l = line;
183 mesh_trim(&l, &ln);
184 if (ln == 0) continue;
185 const char *eq = memchr(l, '=', ln);
186 if (!eq) {
187 m_(p, "line %zu: expected key = value: ", lineno);
188 qn_(p, l, ln);
189 return fail_(p, err);
190 }
191 const char *k = l;
192 size_t kn = (size_t)(eq - l);
193 mesh_trim(&k, &kn);
194 char *key = mesh_strndup(k, kn);
195 if (position(KEYS, sizeof KEYS / sizeof KEYS[0], key) < 0) {
196 m_(p, "line %zu: unknown key ", lineno);
197 q_(p, key);
198 free(key);
199 return fail_(p, err);
200 }
201 if (get(p, key)) {
202 m_(p, "line %zu: repeated key ", lineno);
203 q_(p, key);
204 free(key);
205 return fail_(p, err);
206 }
207 mesh_toml_val v;
208 char *e = NULL;
209 if (!mesh_toml_value(eq + 1, (size_t)(l + ln - (eq + 1)), &v, &e)) {
210 m_(p, "line %zu: %s", lineno, e);
211 free(e);
212 free(key);
213 return fail_(p, err);
214 }
215 p->kvs = mesh_realloc(p->kvs, (p->nkv + 1) * sizeof *p->kvs);
216 p->kvs[p->nkv].key = key;
217 p->kvs[p->nkv].val = v;
218 p->nkv++;
219 }
220 return true;
221}
222
223static bool parse(P *p, const char *src, size_t srcn, mesh_manifest *m, char **err) {
224 if (!parse_kv(p, src, srcn, err)) return false;
225 const mesh_toml_val *v;
226
227 /* name, abi */
228 v = get(p, "name");
229 if (!v) FAIL("name: missing");
230 if (v->kind != MESH_TOML_STR) {
231 m_(p, "name: expected a string, got ");
232 v_(p, v);
233 return fail_(p, err);
234 }
235 m->name = mesh_strdup(v->s);
236 if (!vname(m->name)) {
237 m_(p, "name ");
238 q_(p, m->name);
239 FAIL(": must be [a-z0-9-]{1,%d}", MESH_NAME_MAX);
240 }
241 v = get(p, "abi");
242 if (!v) FAIL("abi: missing");
243 if (v->kind != MESH_TOML_INT) {
244 m_(p, "abi: expected a number, got ");
245 v_(p, v);
246 return fail_(p, err);
247 }
248 m->abi = v->n;
249
250 /* the view */
251 if (!num(p, "view_bytes", 0, &m->view_bytes, err)) return false;
252 if ((size_t)m->view_bytes > (size_t)MESH_VIEW_PAGES * MESH_VIEW_PAGE)
253 FAIL("view_bytes 0x%x: the view heap has 0x%zx", (unsigned)m->view_bytes, (size_t)MESH_VIEW_PAGES * MESH_VIEW_PAGE);
254 p->view_bytes = m->view_bytes;
255 if (!num(p, "snap_bytes", 0, &m->snap_bytes, err)) return false;
256 if (!in_view(p, "snap_bytes", 0, m->snap_bytes, err)) return false;
257
258 /* the node shape */
259 v = get(p, "nodes");
260 m->nodes_fit = v && v->kind == MESH_TOML_STR && strcmp(v->s, "fit") == 0;
261 if (m->nodes_fit) m->nodes = MESH_PROG_MAX_NODES;
262 else if (!num(p, "nodes", 1, &m->nodes, err)) return false;
263 if (m->nodes == 0 || m->nodes > MESH_PROG_MAX_NODES) FAIL("nodes %u: must be 1..=%d", (unsigned)m->nodes, MESH_PROG_MAX_NODES);
264 if (!num(p, "warps", 1, &m->warps, err)) return false;
265 if (m->warps == 0 || m->warps > MESH_MAX_WARPS)
266 FAIL("warps %u: must be 1..=%u (a node of 32 x warps threads)", (unsigned)m->warps, (unsigned)MESH_MAX_WARPS);
267 if (!num(p, "shared_bytes", 0, &m->shared_bytes, err)) return false;
268 if (m->shared_bytes > MESH_MAX_SHARED_BYTES || m->shared_bytes % MESH_SHARED_ALIGN != 0)
269 FAIL("shared_bytes 0x%x: a multiple of %u, at most 0x%x", (unsigned)m->shared_bytes, (unsigned)MESH_SHARED_ALIGN,
270 (unsigned)MESH_MAX_SHARED_BYTES);
271 if (!num(p, "scratch_bytes", 0, &m->scratch_bytes, err)) return false;
272 if (m->scratch_bytes > MESH_MAX_SCRATCH_BYTES)
273 FAIL("scratch_bytes 0x%x: at most 0x%x", (unsigned)m->scratch_bytes, (unsigned)MESH_MAX_SCRATCH_BYTES);
274 if (!num(p, "sms", 0, &m->sms, err)) return false;
275 if (m->sms % 2 != 0) FAIL("sms %u: green partitions come in 2-SM units", (unsigned)m->sms);
276
277 /* the policy */
278 const char *s;
279 if (!text(p, "policy", "on_demand", &s, err)) return false;
280 int pos = position(MESH_POLICIES, MESH_POLICIES_LEN, s);
281 if (pos < 0) {
282 m_(p, "policy ");
283 q_(p, s);
284 m_(p, ": one of ");
285 list_(p, MESH_POLICIES, MESH_POLICIES_LEN);
286 return fail_(p, err);
287 }
288 m->policy = (uint32_t)pos;
289 if (!num(p, "hz", 0, &m->hz, err)) return false;
290 if (m->policy == MESH_POLICY_PERIODIC && !(m->hz >= 1 && m->hz <= 1000)) FAIL("hz %u: periodic needs 1..=1000", (unsigned)m->hz);
291 if (m->policy != MESH_POLICY_PERIODIC && get(p, "hz")) FAIL("hz: only for policy = \"periodic\"");
292 if (!num(p, "fanout", 1, &m->fanout, err)) return false;
293 if (m->fanout == 0 || m->fanout > MESH_PROG_RING / 2) FAIL("fanout %u: must be 1..=%u", (unsigned)m->fanout, (unsigned)(MESH_PROG_RING / 2));
294 if (!text(p, "in_flight", "batch", &s, err)) return false;
295 if (strcmp(s, "batch") == 0) m->batch = true;
296 else if (strcmp(s, "free") == 0) m->batch = false;
297 else {
298 m_(p, "in_flight ");
299 q_(p, s);
300 FAIL(": \"batch\" or \"free\"");
301 }
302 const char *arg_s;
303 if (!text(p, "arg", "index", &arg_s, err)) return false;
304 const char *colon = strchr(arg_s, ':');
305 if (!colon && strcmp(arg_s, "index") == 0) {
306 m->arg_mode = MESH_ARG_INDEX;
307 m->arg_off = 0;
308 } else if (colon && (size_t)(colon - arg_s) == 8 && memcmp(arg_s, "view_u32", 8) == 0) {
309 uint32_t o;
310 if (!ints(colon + 1, strlen(colon + 1), &o)) {
311 m_(p, "arg ");
312 q_(p, arg_s);
313 FAIL(": bad offset");
314 }
315 if (o % 4 != 0) {
316 m_(p, "arg ");
317 q_(p, arg_s);
318 FAIL(": offset not 4-aligned");
319 }
320 if (!in_view(p, "arg", o, 4, err)) return false;
321 m->arg_mode = MESH_ARG_VIEW_U32;
322 m->arg_off = o;
323 } else {
324 m_(p, "arg ");
325 q_(p, arg_s);
326 FAIL(": \"index\" or \"view_u32:<off>\"");
327 }
328
329 /* reads */
330 char *const *items;
331 size_t nitems;
332 if (!list(p, "reads", &items, &nitems, err)) return false;
333 for (size_t i = 0; i < nitems; i++) {
334 const char *r = items[i];
335 if (strcmp(r, "*") == 0) {
336 if (nitems != 1 || strcmp(m->name, "http") != 0) FAIL("reads [\"*\"]: only for http, and alone");
337 } else if (!vname(r) || strcmp(r, m->name) == 0) {
338 m_(p, "reads ");
339 q_(p, r);
340 FAIL(": another program's name");
341 }
342 }
343 m->reads = mesh_strs_copy(items, nitems);
344 m->nreads = nitems;
345
346 /* input */
347 v = get(p, "input");
348 if (!v || (v->kind == MESH_TOML_INT && v->n == 0)) {
349 m->input_off = m->input_len = 0;
350 } else if (v->kind == MESH_TOML_INT) {
351 uint32_t need = (uint32_t)MESH_IQ_DATA + v->n; /* wraps as the Rust release build */
352 if (m->view_bytes < need) FAIL("input: does not fit the view");
353 m->input_off = (m->view_bytes - need) & ~15u;
354 m->input_len = v->n;
355 } else if (v->kind == MESH_TOML_STR) {
356 if (!pair(v->s, &m->input_off, &m->input_len)) {
357 m_(p, "input ");
358 q_(p, v->s);
359 FAIL(": \"off:len\"");
360 }
361 } else {
362 m_(p, "input: expected a number or \"off:len\", got ");
363 v_(p, v);
364 return fail_(p, err);
365 }
366 if (m->input_len != 0) {
367 if (!is_pow2(m->input_len)) FAIL("input: %u bytes is not a power of two", (unsigned)m->input_len);
368 if (m->input_off % 16 != 0) FAIL("input: offset 0x%x not 16-aligned", (unsigned)m->input_off);
369 if (!in_view(p, "input", m->input_off, (uint32_t)MESH_IQ_DATA + m->input_len, err)) return false;
370 }
371
372 /* request */
373 v = get(p, "request");
374 if (v && v->kind == MESH_TOML_STR) {
375 const char *comma = strchr(v->s, ',');
376 mesh_manifest_window w;
377 bool ok = comma != NULL;
378 if (ok) {
379 char *a = mesh_strndup(v->s, (size_t)(comma - v->s));
380 ok = pair(a, &w.req_off, &w.req_max) && pair(comma + 1, &w.resp_off, &w.resp_max);
381 free(a);
382 }
383 if (!ok) {
384 m_(p, "request ");
385 q_(p, v->s);
386 FAIL(": \"req_off:req_max,resp_off:resp_max\"");
387 }
388 if (!in_view(p, "request", w.req_off, w.req_max, err)) return false;
389 if (!in_view(p, "request response", w.resp_off, w.resp_max, err)) return false;
390 if (w.req_off < w.resp_off + w.resp_max && w.resp_off < w.req_off + w.req_max) {
391 m_(p, "request ");
392 q_(p, v->s);
393 FAIL(": windows overlap");
394 }
395 if (w.req_max == 0 || w.resp_max == 0 || w.req_off % 4 != 0 || w.resp_off % 4 != 0) {
396 m_(p, "request ");
397 q_(p, v->s);
398 FAIL(": empty or unaligned window");
399 }
400 m->has_request = true;
401 m->request = w;
402 } else if (v) {
403 m_(p, "request: expected a string, got ");
404 v_(p, v);
405 return fail_(p, err);
406 }
407
408 /* peer */
409 if (!text(p, "peer", "none", &s, err)) return false;
410 pos = position(MESH_PEER_MODES, MESH_PEER_MODES_LEN, s);
411 if (pos < 0) {
412 m_(p, "peer ");
413 q_(p, s);
414 m_(p, ": one of ");
415 list_(p, MESH_PEER_MODES, MESH_PEER_MODES_LEN);
416 return fail_(p, err);
417 }
418 m->peer = (uint32_t)pos;
419 bool lockstep = m->peer == MESH_PEER_MODE_LOCKSTEP;
420 if (lockstep && (m->nodes != 1 || m->fanout != 1))
421 FAIL("peer \"lockstep\": needs nodes = 1 and fanout = 1 (the exchange page has one writer per system), got %u and %u",
422 (unsigned)m->nodes, (unsigned)m->fanout);
423
424 /* counters, fields, autostart */
425 if (!list(p, "counters", &items, &nitems, err)) return false;
426 if (nitems > MESH_COUNTERS) FAIL("counters: at most %d", MESH_COUNTERS);
427 for (size_t i = 0; i < nitems; i++)
428 if (strlen(items[i]) > MESH_NAME_MAX) {
429 m_(p, "counters ");
430 q_(p, items[i]);
431 FAIL(": at most %d bytes", MESH_NAME_MAX);
432 }
433 for (size_t i = 0; i < MESH_COUNTERS; i++) m->counters[i] = mesh_strdup(i < nitems ? items[i] : "");
434 if (!list(p, "fields", &items, &nitems, err)) return false;
435 for (size_t i = 0; i < nitems; i++) {
436 const char *f = items[i];
437 /* name:type:off, exactly three parts */
438 const char *c1 = strchr(f, ':');
439 const char *c2 = c1 ? strchr(c1 + 1, ':') : NULL;
440 bool three = c1 && c2 && !strchr(c2 + 1, ':');
441 if (!three) {
442 m_(p, "fields ");
443 q_(p, f);
444 m_(p, ": \"name:type:off\", type one of ");
445 list_(p, MESH_FIELD_TYPES, MESH_FIELD_TYPES_LEN);
446 return fail_(p, err);
447 }
448 size_t nn = (size_t)(c1 - f);
449 bool name_ok = nn > 0 && nn <= MESH_FD_NAME_MAX;
450 for (size_t k = 0; name_ok && k < nn; k++)
451 name_ok = (f[k] >= 'a' && f[k] <= 'z') || (f[k] >= '0' && f[k] <= '9') || f[k] == '_';
452 if (!name_ok) {
453 m_(p, "fields ");
454 q_(p, f);
455 FAIL(": name must be [a-z0-9_]{1,%d}", MESH_FD_NAME_MAX);
456 }
457 char *ty = mesh_strndup(c1 + 1, (size_t)(c2 - c1 - 1));
458 int t = position(MESH_FIELD_TYPES, MESH_FIELD_TYPES_LEN, ty);
459 free(ty);
460 uint32_t off;
461 if (t < 0 || !ints(c2 + 1, strlen(c2 + 1), &off)) {
462 m_(p, "fields ");
463 q_(p, f);
464 m_(p, ": \"name:type:off\", type one of ");
465 list_(p, MESH_FIELD_TYPES, MESH_FIELD_TYPES_LEN);
466 return fail_(p, err);
467 }
468 static const uint32_t sizes[] = {4, 4, 4, 8, 1};
469 uint32_t size = sizes[t];
470 if (off % size != 0) {
471 m_(p, "fields ");
472 q_(p, f);
473 FAIL(": offset not %u-aligned", (unsigned)size);
474 }
475 if (!in_view(p, "fields", off, size, err)) return false;
476 m->fields = mesh_realloc(m->fields, (m->nfields + 1) * sizeof *m->fields);
477 m->fields[m->nfields++] = (mesh_manifest_field){mesh_strndup(f, nn), (uint32_t)t, off};
478 }
479 if (m->nfields > MESH_MAX_FIELDS) FAIL("fields: at most %d", MESH_MAX_FIELDS);
480 v = get(p, "autostart");
481 if (v && v->kind != MESH_TOML_BOOL) {
482 m_(p, "autostart: expected true/false, got ");
483 v_(p, v);
484 return fail_(p, err);
485 }
486 m->autostart = v && v->b;
487
488 /* place */
489 if (!text(p, "place", "none", &s, err)) return false;
490 pos = position(MESH_PLACE_CAPS, MESH_PLACE_CAPS_LEN, s);
491 if (pos < 0) {
492 m_(p, "place ");
493 q_(p, s);
494 m_(p, ": one of ");
495 list_(p, MESH_PLACE_CAPS, MESH_PLACE_CAPS_LEN);
496 return fail_(p, err);
497 }
498 uint32_t cap = (uint32_t)pos;
499 if (cap == MESH_PLACE_CAP_NONE) {
500 static const char *const pk[] = {"place_state", "place_frame", "place_out", "place_show"};
501 for (size_t i = 0; i < 4; i++)
502 if (get(p, pk[i])) FAIL("%s: only with place = \"pin\" or \"split\"", pk[i]);
503 } else {
504 mesh_manifest_place *pl = mesh_alloc(sizeof *pl);
505 m->place = pl;
506 pl->cap = cap;
507 static const char *const rk[] = {"place_state", "place_out"};
508 for (int which = 0; which < 2; which++) {
509 const char *k = rk[which];
510 if (!list(p, k, &items, &nitems, err)) return false;
511 if (nitems == 0) FAIL("%s: missing (a list of \"off:len\")", k);
512 mesh_manifest_region *r = mesh_alloc(nitems * sizeof *r);
513 if (which == 0) { pl->state = r; pl->nstate = 0; } else { pl->out = r; pl->nout = 0; }
514 for (size_t i = 0; i < nitems; i++) {
515 uint32_t o, n;
516 if (!pair(items[i], &o, &n)) {
517 m_(p, "%s ", k);
518 q_(p, items[i]);
519 FAIL(": \"off:len\"");
520 }
521 if (o % 4 != 0 || n % 4 != 0 || n == 0) {
522 m_(p, "%s ", k);
523 q_(p, items[i]);
524 FAIL(": offset and length 4-aligned, length > 0");
525 }
526 if (!in_view(p, k, o, n, err)) return false;
527 r[i] = (mesh_manifest_region){o, n};
528 if (which == 0) pl->nstate++; else pl->nout++;
529 }
530 }
531 v = get(p, "place_frame");
532 if (!v) FAIL("place_frame: missing");
533 if (v->kind != MESH_TOML_INT) {
534 m_(p, "place_frame: expected a number, got ");
535 v_(p, v);
536 return fail_(p, err);
537 }
538 pl->frame = v->n;
539 v = get(p, "place_show");
540 if (!v) FAIL("place_show: missing");
541 if (v->kind != MESH_TOML_INT) {
542 m_(p, "place_show: expected a number, got ");
543 v_(p, v);
544 return fail_(p, err);
545 }
546 pl->show = v->n;
547 pl->fanout = m->fanout;
548 if (m->policy == MESH_POLICY_ON_DEMAND || !m->batch)
549 FAIL("place: needs policy \"periodic\" or \"continuous\" with in_flight = \"batch\" (a frame is one batch)");
550 if (m->peer != MESH_PEER_MODE_NONE) FAIL("place: not with peer = \"lockstep\"");
551 if (cap == MESH_PLACE_CAP_SPLIT && !(m->fanout >= 2 && m->fanout <= MESH_PLACE_PARTS_MAX))
552 FAIL("place = \"split\": fanout %u: 2..=%d parts", (unsigned)m->fanout, (int)MESH_PLACE_PARTS_MAX);
553 bool in_state = false;
554 for (size_t i = 0; i < pl->nstate; i++) {
555 uint32_t o = pl->state[i].off, n = pl->state[i].len;
556 if (pl->frame % 4 == 0 && pl->frame >= o && (uint32_t)(pl->frame + 4) <= (uint32_t)(o + n)) in_state = true;
557 }
558 if (!in_state) FAIL("place_frame 0x%x: a 4-aligned u32 inside a place_state region", (unsigned)pl->frame);
559 for (size_t i = 0; i < pl->nout; i++)
560 if (pl->out[i].len % (4 * m->fanout) != 0)
561 FAIL("place_out 0x%x:0x%x: length not a multiple of 4 x fanout", (unsigned)pl->out[i].off, (unsigned)pl->out[i].len);
562 if (pl->show % 16 != 0) FAIL("place_show 0x%x: not 16-aligned", (unsigned)pl->show);
563 uint32_t show_n = mesh_show_bytes(mesh_manifest_place_out_bytes(pl));
564 if (!in_view(p, "place_show", pl->show, show_n, err)) return false;
565 if (mesh_manifest_place_state_bytes(pl) + mesh_manifest_place_out_bytes(pl) > (size_t)MESH_F_PLACE_MAX - 64)
566 FAIL("place: state + output over F_PLACE_MAX (%d bytes)", (int)MESH_F_PLACE_MAX);
567 size_t nall = pl->nstate + pl->nout + 1 + (m->input_len != 0);
568 span *all = mesh_alloc(nall * sizeof *all);
569 size_t k = 0;
570 for (size_t i = 0; i < pl->nstate; i++) all[k++] = (span){pl->state[i].off, pl->state[i].len, "place_state"};
571 for (size_t i = 0; i < pl->nout; i++) all[k++] = (span){pl->out[i].off, pl->out[i].len, "place_out"};
572 all[k++] = (span){pl->show, show_n, "place_show"};
573 if (m->input_len != 0) all[k++] = (span){m->input_off, (uint32_t)MESH_IQ_DATA + m->input_len, "input"};
574 for (size_t i = 0; i < nall; i++)
575 for (size_t j = i + 1; j < nall; j++)
576 if (overlaps(all[i], all[j])) {
577 m_(p, "place: %s 0x%x+0x%x overlaps %s 0x%x+0x%x", all[i].what, (unsigned)all[i].off, (unsigned)all[i].len,
578 all[j].what, (unsigned)all[j].off, (unsigned)all[j].len);
579 free(all);
580 return fail_(p, err);
581 }
582 free(all);
583 }
584
585 /* backends */
586 if (!get(p, "backends")) {
587 m->backends = mesh_alloc(sizeof *m->backends);
588 m->backends[0] = mesh_strdup(MESH_BACKENDS[MESH_BACKEND_CUDA]);
589 m->nbackends = 1;
590 } else {
591 if (!list(p, "backends", &items, &nitems, err)) return false;
592 m->backends = mesh_strs_copy(items, nitems);
593 m->nbackends = nitems;
594 }
595 if (m->nbackends == 0) {
596 m_(p, "backends: at least one of ");
597 list_(p, MESH_BACKENDS, MESH_BACKENDS_LEN);
598 return fail_(p, err);
599 }
600 for (size_t i = 0; i < m->nbackends; i++) {
601 const char *b = m->backends[i];
602 if (position(MESH_BACKENDS, MESH_BACKENDS_LEN, b) < 0) {
603 m_(p, "backends ");
604 q_(p, b);
605 m_(p, ": one of ");
606 list_(p, MESH_BACKENDS, MESH_BACKENDS_LEN);
607 return fail_(p, err);
608 }
609 if (position((const char *const *)m->backends, i, b) >= 0) {
610 m_(p, "backends: ");
611 q_(p, b);
612 FAIL(" twice");
613 }
614 }
615 bool native = mesh_manifest_native(m);
616 if (native && m->nbackends > 1) FAIL("backends: \"native\" stands alone (a native program is an executable, not a GPU image)");
617 if (native && lockstep) FAIL("peer \"lockstep\": not for a native program (platform float, not the cluster float mode)");
618 if (!native)
619 for (size_t i = 0; i < 3; i++)
620 if (get(p, NATIVE_KEYS[i])) FAIL("%s: only for backends = [\"native\"]", NATIVE_KEYS[i]);
621 if (!num(p, "param", 0, &m->param, err)) return false;
622 bool wasm = position((const char *const *)m->backends, m->nbackends, "wasm") >= 0;
623 if (native && get(p, "float")) FAIL("float: not for a native program (it computes in platform float)");
624 if (!text(p, "float", "ftz", &s, err)) return false;
625 if (strcmp(s, "ftz") == 0) m->ftz = true;
626 else if (strcmp(s, "ieee") == 0) m->ftz = false;
627 else {
628 m_(p, "float ");
629 q_(p, s);
630 FAIL(": \"ftz\" or \"ieee\"");
631 }
632 if (!m->ftz && lockstep)
633 FAIL("float = \"ieee\": not with peer = \"lockstep\" (the lockstep group computes in the cluster float mode, ftz)");
634 if (!m->ftz)
635 for (size_t i = 0; i < m->nbackends; i++)
636 if (strcmp(m->backends[i], "wasm") != 0)
637 FAIL("float = \"ieee\": only for a wasm-only program (backends = [\"wasm\"]): the GPUs compute in ftz");
638 if (!wasm && get(p, "task_ms")) FAIL("task_ms: only for a program with a wasm image (backends lists \"wasm\")");
639 if (!wasm && !native && get(p, "mem_mb")) FAIL("mem_mb: only for a wasm image or a native program");
640 if (native) {
641 v = get(p, "mem_mb");
642 if (!v) {
643 m->mem_mb = 0;
644 m->mem = 0;
645 } else if (v->kind == MESH_TOML_INT && v->n >= 1) {
646 m->mem_mb = v->n;
647 m->mem = (uint64_t)v->n * (1u << 20);
648 } else {
649 m_(p, "mem_mb: MiB, at least 1 (e.g. 4096), got ");
650 v_(p, v);
651 return fail_(p, err);
652 }
653 } else {
654 if (!num(p, "mem_mb", 64, &m->mem_mb, err)) return false;
655 if (!(m->mem_mb >= 1 && m->mem_mb <= 4096)) FAIL("mem_mb %u: 1..=4096", (unsigned)m->mem_mb);
656 m->mem = 0;
657 }
658 if (!num(p, "task_ms", 50, &m->task_ms, err)) return false;
659 if (!(m->task_ms >= 1 && m->task_ms <= 60000)) FAIL("task_ms %u: 1..=60000", (unsigned)m->task_ms);
660 if (get(p, "cpu") && !native && !wasm)
661 FAIL("cpu: only for a native program (a cap and a reservation) or a wasm image (a reservation)");
662 uint32_t cpu_pct = 0;
663 v = get(p, "cpu");
664 if (v) {
665 if (v->kind == MESH_TOML_INT && v->n >= 1 && v->n <= 1024) cpu_pct = v->n * 100;
666 else if (v->kind == MESH_TOML_FLOAT && v->f >= 0.01 && v->f <= 1024.0) cpu_pct = (uint32_t)round(v->f * 100.0);
667 else {
668 m_(p, "cpu: cores, 0.01..=1024 (e.g. 2.0), got ");
669 v_(p, v);
670 return fail_(p, err);
671 }
672 }
673 if (!num(p, "nice", 0, &m->nice, err)) return false;
674 if (m->nice > 19) FAIL("nice %u: 0..=19 (a worker never runs above meshd's priority)", (unsigned)m->nice);
675
676 /* pipes */
677 v = get(p, "inbox");
678 if (!v) {
679 if (get(p, "inbox_type")) FAIL("inbox_type: only with inbox");
680 } else if (v->kind == MESH_TOML_STR) {
681 const char *is = v->s;
682 uint32_t parts[3];
683 size_t np = 0;
684 bool ok = true;
685 for (const char *c = is;;) {
686 const char *e = strchr(c, ':');
687 size_t n = e ? (size_t)(e - c) : strlen(c);
688 uint32_t x;
689 if (!ints(c, n, &x)) { ok = false; break; }
690 if (np < 3) parts[np] = x;
691 np++;
692 if (!e) break;
693 c = e + 1;
694 }
695 mesh_pipe_inbox ib = {0, ok && np == 3 ? parts[1] : 0, ok && np == 3 ? parts[2] : 0};
696 if (!ok || np != 3 || !mesh_pipe_inbox_valid(&ib)) {
697 m_(p, "inbox ");
698 q_(p, is);
699 FAIL(": \"off:cap:rec\" (cap records, a power of two 4..=%u: half per producer side; rec bytes, a multiple of 4, 4..=%u)",
700 (unsigned)MESH_PIPE_CAP_MAX, (unsigned)MESH_PIPE_REC_MAX);
701 }
702 uint32_t off = parts[0], icap = parts[1], rec = parts[2];
703 if (off % 0x80 != 0) {
704 m_(p, "inbox ");
705 q_(p, is);
706 FAIL(": offset 0x%x not 0x80-aligned (its head and tail lines)", (unsigned)off);
707 }
708 uint32_t ibytes = (uint32_t)mesh_pipe_inbox_bytes(icap, rec);
709 if (!in_view(p, "inbox", off, ibytes, err)) return false;
710 span ibs = {off, ibytes, "inbox"};
711 span others[3];
712 size_t no = 0;
713 if (m->input_len != 0) others[no++] = (span){m->input_off, (uint32_t)MESH_IQ_DATA + m->input_len, "input"};
714 if (m->has_request) {
715 others[no++] = (span){m->request.req_off, m->request.req_max, "request"};
716 others[no++] = (span){m->request.resp_off, m->request.resp_max, "request response"};
717 }
718 for (size_t i = 0; i < no; i++)
719 if (overlaps(ibs, others[i])) {
720 m_(p, "inbox ");
721 q_(p, is);
722 FAIL(" overlaps %s 0x%x+0x%x", others[i].what, (unsigned)others[i].off, (unsigned)others[i].len);
723 }
724 const char *ty;
725 if (!text(p, "inbox_type", "", &ty, err)) return false;
726 if (!valid_type(ty)) {
727 m_(p, "inbox_type ");
728 q_(p, ty);
729 FAIL(": required with inbox, [a-z0-9_]{1,%d}", MESH_NAME_MAX);
730 }
731 m->inbox = mesh_alloc(sizeof *m->inbox);
732 *m->inbox = (mesh_manifest_inbox){off, icap, rec, mesh_strdup(ty)};
733 } else {
734 m_(p, "inbox: expected \"off:cap:rec\", got ");
735 v_(p, v);
736 return fail_(p, err);
737 }
738 if (!list(p, "feeds", &items, &nitems, err)) return false;
739 for (size_t i = 0; i < nitems; i++) {
740 const char *f = items[i];
741 const char *c = strchr(f, ':');
742 if (!c) {
743 m_(p, "feeds ");
744 q_(p, f);
745 FAIL(": \"consumer:type\"");
746 }
747 char *consumer = mesh_strndup(f, (size_t)(c - f));
748 const char *t = c + 1;
749 if (!vname(consumer) || strcmp(consumer, m->name) == 0) {
750 free(consumer);
751 m_(p, "feeds ");
752 q_(p, f);
753 FAIL(": another program's name");
754 }
755 if (!valid_type(t)) {
756 free(consumer);
757 m_(p, "feeds ");
758 q_(p, f);
759 FAIL(": type [a-z0-9_]{1,%d}", MESH_NAME_MAX);
760 }
761 for (size_t j = 0; j < m->nfeeds; j++)
762 if (strcmp(m->feeds[j].consumer, consumer) == 0) {
763 m_(p, "feeds: ");
764 q_(p, consumer);
765 free(consumer);
766 FAIL(" twice");
767 }
768 m->feeds = mesh_realloc(m->feeds, (m->nfeeds + 1) * sizeof *m->feeds);
769 m->feeds[m->nfeeds++] = (mesh_manifest_feed){consumer, mesh_strdup(t)};
770 }
771 if (lockstep && (m->inbox || m->nfeeds))
772 FAIL("inbox, feeds: not for the lockstep program (its steps must not depend on another program's timing)");
773 v = get(p, "background");
774 if (v && v->kind != MESH_TOML_BOOL) {
775 m_(p, "background: expected true/false, got ");
776 v_(p, v);
777 return fail_(p, err);
778 }
779 m->background = v && v->b;
780 if (!text(p, "device", "cpu", &s, err)) return false;
781 if (strcmp(s, "cpu") == 0) m->device_gpu = false;
782 else if (strcmp(s, "gpu") == 0) m->device_gpu = true;
783 else {
784 m_(p, "device ");
785 q_(p, s);
786 FAIL(": \"cpu\" or \"gpu\"");
787 }
788
789 /* jobs */
790 if (m->policy == MESH_POLICY_JOB) {
791 if (lockstep) FAIL("policy \"job\": not with peer = \"lockstep\" (a job's items go to any member, a group's steps to all)");
792 if (m->place) FAIL("policy \"job\": not with place (a job's items are leased, not placed)");
793 if (m->arg_mode != MESH_ARG_INDEX) FAIL("policy \"job\": arg is the item (OP_JOB), not \"view_u32\"");
794 } else {
795 static const char *const jk[] = {"job_base", "job_out", "job_payload"};
796 for (size_t i = 0; i < 3; i++)
797 if (get(p, jk[i])) FAIL("%s: only for policy = \"job\"", jk[i]);
798 }
799 v = get(p, "job_base");
800 if (v) {
801 if (v->kind == MESH_TOML_INT && v->n % 8 == 0) {
802 if (!in_view(p, "job_base", v->n, 8, err)) return false;
803 m->has_job_base = true;
804 m->job_base = v->n;
805 } else {
806 m_(p, "job_base: an 8-aligned view offset (of the u64 item base), got ");
807 v_(p, v);
808 return fail_(p, err);
809 }
810 }
811 v = get(p, "job_out");
812 if (v) {
813 if (v->kind != MESH_TOML_STR) {
814 m_(p, "job_out: expected \"off:rec\", got ");
815 v_(p, v);
816 return fail_(p, err);
817 }
818 uint32_t off, rec;
819 if (!pair(v->s, &off, &rec)) {
820 m_(p, "job_out ");
821 q_(p, v->s);
822 FAIL(": \"off:rec\"");
823 }
824 if (off % 4 != 0 || rec % 4 != 0 || !(rec >= 4 && rec <= MESH_JOB_OUT_MAX)) {
825 m_(p, "job_out ");
826 q_(p, v->s);
827 FAIL(": offset 4-aligned, rec a multiple of 4 in 4..=%u", (unsigned)MESH_JOB_OUT_MAX);
828 }
829 if (!in_view(p, "job_out", off, rec * MESH_PROG_RESULTS, err)) return false;
830 if (m->has_job_base && m->job_base < off + rec * MESH_PROG_RESULTS && off < m->job_base + 8) {
831 m_(p, "job_out ");
832 q_(p, v->s);
833 FAIL(" overlaps job_base 0x%x", (unsigned)m->job_base);
834 }
835 m->has_job_out = true;
836 m->job_out_off = off;
837 m->job_out_rec = rec;
838 }
839 v = get(p, "job_payload");
840 if (v) {
841 if (v->kind != MESH_TOML_STR) {
842 m_(p, "job_payload: expected \"off:len\", got ");
843 v_(p, v);
844 return fail_(p, err);
845 }
846 uint32_t off, len;
847 if (!pair(v->s, &off, &len)) {
848 m_(p, "job_payload ");
849 q_(p, v->s);
850 FAIL(": \"off:len\"");
851 }
852 if (off % 4 != 0 || len % 4 != 0 || !(len >= 4 && len <= MESH_JOB_PAYLOAD_MAX)) {
853 m_(p, "job_payload ");
854 q_(p, v->s);
855 FAIL(": offset 4-aligned, len a multiple of 4 in 4..=%u", (unsigned)MESH_JOB_PAYLOAD_MAX);
856 }
857 uint32_t total = len * MESH_PROG_RESULTS;
858 if (!in_view(p, "job_payload", off, total, err)) return false;
859 bool over_base = m->has_job_base && m->job_base < off + total && off < m->job_base + 8;
860 bool over_out = m->has_job_out && m->job_out_off < off + total &&
861 off < m->job_out_off + m->job_out_rec * MESH_PROG_RESULTS;
862 if (over_base || over_out) {
863 m_(p, "job_payload ");
864 q_(p, v->s);
865 FAIL(" overlaps job_base or job_out");
866 }
867 m->has_job_payload = true;
868 m->job_payload_off = off;
869 m->job_payload_len = len;
870 }
871
872 /* the fleet scheduler's keys */
873 mesh_needs *nd = &m->needs;
874 v = get(p, "replicas");
875 if (!v || (v->kind == MESH_TOML_STR && strcmp(v->s, "all") == 0)) {
876 nd->replicas.all = true;
877 } else if (v->kind == MESH_TOML_INT && v->n >= 1 && v->n <= MESH_MAX_SYSTEMS) {
878 nd->replicas.all = false;
879 nd->replicas.n = v->n;
880 } else {
881 m_(p, "replicas: \"all\" or 1..=%u, got ", (unsigned)MESH_MAX_SYSTEMS);
882 v_(p, v);
883 return fail_(p, err);
884 }
885 if (lockstep && get(p, "replicas")) FAIL("replicas: not for the lockstep program (its group: group_size, group_members)");
886 if (cap != MESH_PLACE_CAP_NONE && !nd->replicas.all)
887 FAIL("replicas: not with `place` (a placed program is loaded on every member; `place` decides who runs it)");
888 if (!text(p, "spread", "spread", &s, err)) return false;
889 if (strcmp(s, "spread") == 0) nd->pack = false;
890 else if (strcmp(s, "pack") == 0) nd->pack = true;
891 else {
892 m_(p, "spread ");
893 q_(p, s);
894 FAIL(": \"spread\" or \"pack\"");
895 }
896 if (!term_list(p, "prefer", &nd->prefer, &nd->nprefer, err)) return false;
897 if (!term_list(p, "avoid", &nd->avoid, &nd->navoid, err)) return false;
898 v = get(p, "arch");
899 if (v) {
900 if (v->kind == MESH_TOML_STR && v->s[0]) nd->arch = mesh_strdup(v->s);
901 else {
902 m_(p, "arch: a CPU architecture (\"aarch64\", \"x86_64\"), got ");
903 v_(p, v);
904 return fail_(p, err);
905 }
906 }
907 if (!num(p, "min_mem_mb", 0, &nd->min_mem_mb, err)) return false;
908 v = get(p, "colocate_with");
909 if (v) {
910 if (v->kind == MESH_TOML_STR && vname(v->s) && strcmp(v->s, m->name) != 0) nd->colocate_with = mesh_strdup(v->s);
911 else {
912 m_(p, "colocate_with: another program's name, got ");
913 v_(p, v);
914 return fail_(p, err);
915 }
916 }
917 if (nd->colocate_with && (lockstep || get(p, "replicas")))
918 FAIL("colocate_with: it runs where that program runs (no replicas, not lockstep)");
919 v = get(p, "group");
920 if (v) {
921 if (v->kind == MESH_TOML_STR && vname(v->s)) nd->group = mesh_strdup(v->s);
922 else {
923 m_(p, "group: a name, got ");
924 v_(p, v);
925 return fail_(p, err);
926 }
927 }
928 if (!lockstep && (get(p, "group") || get(p, "group_size") || get(p, "group_members")))
929 FAIL("group, group_size, group_members: only with peer = \"lockstep\"");
930 if (!num(p, "group_size", 0, &nd->group_size, err)) return false;
931 if (nd->group_size > MESH_MAX_SYSTEMS)
932 FAIL("group_size %u: 0 (every eligible member) ..=%u", (unsigned)nd->group_size, (unsigned)MESH_MAX_SYSTEMS);
933 if (!list(p, "group_members", &items, &nitems, err)) return false;
934 if (!(nitems == 1 && strcmp(items[0], "all") == 0))
935 if (!term_list(p, "group_members", &nd->group_members, &nd->ngroup_members, err)) return false;
936 v = get(p, "priority");
937 if (!v) nd->priority = 0;
938 else if (v->kind == MESH_TOML_INT && v->n <= 10) nd->priority = (int32_t)v->n;
939 else if (v->kind == MESH_TOML_NEG && v->n <= 10) nd->priority = -(int32_t)v->n;
940 else {
941 m_(p, "priority: -10..=10, got ");
942 v_(p, v);
943 return fail_(p, err);
944 }
945 if (!text(p, "migrate", "state", &s, err)) return false;
946 if (strcmp(s, "state") == 0) nd->migrate = MESH_MIGRATE_STATE;
947 else if (strcmp(s, "restart") == 0) nd->migrate = MESH_MIGRATE_RESTART;
948 else if (strcmp(s, "never") == 0) nd->migrate = MESH_MIGRATE_NEVER;
949 else {
950 m_(p, "migrate ");
951 q_(p, s);
952 FAIL(": \"state\", \"restart\" or \"never\"");
953 }
954 nd->backends = mesh_strs_copy(m->backends, m->nbackends);
955 nd->nbackends = m->nbackends;
956 nd->feeds = m->nfeeds ? mesh_alloc(m->nfeeds * sizeof *nd->feeds) : NULL;
957 for (size_t i = 0; i < m->nfeeds; i++) nd->feeds[i] = mesh_strdup(m->feeds[i].consumer);
958 nd->nfeeds = m->nfeeds;
959 nd->cpu = (float)cpu_pct / 100.0f;
960 nd->mem_mb = native ? m->mem_mb : wasm ? m->mem_mb * m->nodes : 0;
961 nd->lockstep = lockstep;
962 nd->view_bytes = m->view_bytes;
963 nd->warps = m->warps;
964 nd->shared = m->shared_bytes;
965 if (!num(p, "view_abi", 1, &m->view_abi, err)) return false;
966 m->cpu_pct = native ? cpu_pct : 0;
967 return true;
968}
969
970bool mesh_manifest_parse(const char *src, size_t n, mesh_manifest *out, char **err) {
971 P p = {0};
972 *out = (mesh_manifest){0};
973 bool ok = parse(&p, src, n, out, err);
974 for (size_t i = 0; i < p.nkv; i++) {
975 free(p.kvs[i].key);
976 mesh_toml_val_free(&p.kvs[i].val);
977 }
978 free(p.kvs);
979 mesh_text_free(&p.msg);
980 if (!ok) mesh_manifest_free(out);
981 return ok;
982}