skip to content
==> 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}