skip to content
==> src/worker.c <==
1/* native.rs's Worker (an image: at most one process at a time), the
2 * executor (NativeBackend) and its compiler (NativeCompiler). */
3#include "native_internal.h"
4
5#include "mesh/loader.h"
6
7#include "mesh/abi/backend.h"
8#include "mesh/abi/base.h"
9#include "mesh/abi/control_block.h"
10#include "mesh/abi/fnv.h"
11#include "mesh/abi/host_status.h"
12
13#include <errno.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <sys/stat.h>
18#include <unistd.h>
19
20static char *os_error(int e) { return mesh_fmt("%s (os error %d)", strerror(e), e); }
21
22bool mesh_native_check_format(const char *name, const char *path, const uint8_t *b, size_t n, char **err) {
23#if defined(__aarch64__)
24    const unsigned want = 183;
25#elif defined(__x86_64__)
26    const unsigned want = 62;
27#else
28    const unsigned want = 0;
29#endif
30    if (n < 20 || memcmp(b, "\x7f" "ELF", 4) != 0)
31        return mesh_errf(err, "load: %s: %s is no ELF file, not an executable for %s", name, path, mesh_native_target());
32    unsigned machine = (unsigned)b[18] | (unsigned)b[19] << 8;
33    if (machine != want)
34        return mesh_errf(err, "load: %s: %s is an ELF for machine %u, not an executable for %s", name, path, machine, mesh_native_target());
35    return true;
36}
37
38/* ---- the image -------------------------------------------------------------------------- */
39
40/* A spawn in progress (a start after a stop): its result, for poll to collect. */
41typedef struct {
42    atomic_int refs;
43    pthread_mutex_t mu;
44    bool done, abandoned;
45    proc *p;
46    char *err;
47} pending;
48
49static void pending_unref(pending *x) {
50    if (!x || atomic_fetch_sub(&x->refs, 1) != 1) return;
51    if (x->p) proc_unref(x->p);
52    free(x->err);
53    pthread_mutex_destroy(&x->mu);
54    free(x);
55}
56
57static void spec_unref(spec *s) {
58    if (!s || atomic_fetch_sub(&s->refs, 1) != 1) return;
59    free(s->name);
60    free(s->bin);
61    free(s);
62}
63
64typedef struct {
65    proc *p;
66    pending *pend;
67    uint32_t gen, slot;
68    bool active; /* launched, not yet halted or gone */
69    bool exit_seen;
70    uint64_t exit_seen_at;
71    bool dead_seen;
72    uint64_t dead_seen_at; /* when its process was first seen gone (its status pipe drains after) */
73    char *error;           /* a failure for `crashed` to report once */
74    uint32_t last_exit;
75    uint64_t cpu_gone; /* CPU ns of this image's processes that are gone */
76} run_state;
77
78struct mesh_native_worker {
79    char *name;
80    uint64_t bin_hash, bin_bytes;
81    spec *spec;
82    mesh_native_host *host;
83    pthread_mutex_t mu;
84    run_state run;
85    _Atomic uint32_t crashes;
86};
87
88const char *mesh_native_worker_name(const mesh_native_worker *w) { return w->name; }
89const char *mesh_native_worker_bin(const mesh_native_worker *w) { return w->spec->bin; }
90uint64_t mesh_native_worker_bin_hash(const mesh_native_worker *w) { return w->bin_hash; }
91uint64_t mesh_native_worker_bin_bytes(const mesh_native_worker *w) { return w->bin_bytes; }
92
93bool mesh_native_worker_warm(mesh_native_worker *w, char **err) {
94    pthread_mutex_lock(&w->mu);
95    bool ok = true;
96    if (!(w->run.p && !proc_exited(w->run.p, NULL))) {
97        proc *p;
98        ok = host_spawn(w->host, w->spec, &p, err);
99        if (ok) {
100            if (w->run.p) proc_unref(w->run.p);
101            w->run.p = p;
102        }
103    }
104    pthread_mutex_unlock(&w->mu);
105    return ok;
106}
107
108/* A process of this image is gone: its CPU time kept, the registry updated. */
109static void gone(mesh_native_worker *w, run_state *r, proc *p, bool crashed) {
110    uint64_t cpu, rss;
111    float pct;
112    proc_last(p, &cpu, &rss, &pct);
113    r->cpu_gone += cpu;
114    int st;
115    if (proc_exited(p, &st)) r->last_exit = mesh_exit_word(st);
116    if (crashed) {
117        atomic_fetch_add(&w->crashes, 1);
118        atomic_fetch_add(&w->host->crashes, 1);
119        atomic_fetch_add(&mesh_cpuuse_worker_crashes, 1);
120    }
121    host_forget(w->host, p);
122    if (r->p == p) r->p = NULL;
123    proc_unref(p);
124}
125
126/* Advances the launch's state: a finished spawn, a halt, an exit, a death. */
127static void poll_run(mesh_native_worker *w) {
128    run_state *r = &w->run;
129    if (r->pend) {
130        pending *x = r->pend;
131        pthread_mutex_lock(&x->mu);
132        bool done = x->done;
133        proc *p = x->p;
134        char *e = x->err;
135        x->p = NULL;
136        x->err = NULL;
137        pthread_mutex_unlock(&x->mu);
138        if (!done) return;
139        r->pend = NULL;
140        pending_unref(x);
141        if (p) r->p = p;
142        else {
143            r->active = false;
144            free(r->error);
145            r->error = e;
146            e = NULL;
147        }
148        free(e);
149    }
150    if (!r->active) return;
151    proc *p = r->p;
152    if (!p) {
153        r->active = false;
154        return;
155    }
156    size_t ctl = mesh_prog_ctl(r->slot);
157    bool exit_mode = mesh_mailbox_get(w->host->mb, ctl + MESH_P_SHUTDOWN) == MESH_SHUTDOWN_EXIT;
158    int st;
159    if (proc_exited(p, &st)) {
160        /* Its last status lines and output may still be in the pipes: judge once they
161         * are drained (at most 500 ms: a descendant may hold them open). */
162        bool drained = atomic_load(&p->eof) && atomic_load(&p->outs_done) >= 2;
163        if (!drained) {
164            if (!r->dead_seen) {
165                r->dead_seen = true;
166                r->dead_seen_at = mesh_native_now_ns();
167            }
168            if (mesh_native_now_ns() - r->dead_seen_at < 500000000u) return;
169        }
170        bool halted = atomic_load(&p->halted_gen) == r->gen;
171        r->active = false;
172        /* Leaving after a halt it was told to exit at is the normal stop; anything else is a crash. */
173        bool clean = exit_mode && halted && mesh_status_success(st);
174        if (!clean) {
175            char *d = mesh_describe_status(st), *tail = proc_tail_text(p);
176            free(r->error);
177            r->error = mesh_fmt("worker %d exited (%s)%s; output: %s", (int)p->pid, d, exit_mode ? " while stopping" : " while running", tail);
178            free(d);
179            free(tail);
180        }
181        gone(w, r, p, !clean);
182        return;
183    }
184    bool halted = atomic_load(&p->halted_gen) == r->gen;
185    if (exit_mode) {
186        if (!r->exit_seen) {
187            r->exit_seen = true;
188            r->exit_seen_at = mesh_native_now_ns();
189        }
190        if (halted) proc_tell_exit(p);
191        uint64_t el = mesh_native_now_ns() - r->exit_seen_at;
192        if (el > ((uint64_t)MESH_NATIVE_EXIT_GRACE_MS + 2000u) * 1000000u) {
193            mesh_error("native: %s worker %d did not halt and exit in %.3fs; killing it", w->name, (int)p->pid, (double)el / 1e9);
194            proc_kill(p);
195        }
196    } else if (halted) {
197        /* Halted (a blink): it stays, gated, for the next `run`. */
198        r->active = false;
199    }
200}
201
202typedef struct {
203    mesh_native_host *host;
204    spec *spec;
205    char *line;
206    pending *pend;
207} spawn_job;
208
209static void *spawn_thread(void *a) {
210    spawn_job *j = a;
211    proc *p = NULL;
212    char *err = NULL;
213    if (host_spawn(j->host, j->spec, &p, &err) && !proc_send(p, j->line)) {
214        proc_kill(p);
215        host_forget(j->host, p);
216        proc_unref(p);
217        p = NULL;
218        err = mesh_fmt("%s: the new worker took no `run`", j->spec->name);
219    }
220    pthread_mutex_lock(&j->pend->mu);
221    if (j->pend->abandoned && p) {
222        /* Released while it started: nobody will run it. */
223        proc_tell_exit(p);
224        pthread_mutex_unlock(&j->pend->mu);
225        proc_retire(p, j->host);
226        pthread_mutex_lock(&j->pend->mu);
227        p = NULL;
228    }
229    j->pend->p = p;
230    j->pend->err = err;
231    j->pend->done = true;
232    pthread_mutex_unlock(&j->pend->mu);
233    pending_unref(j->pend);
234    spec_unref(j->spec);
235    free(j->line);
236    free(j);
237    return NULL;
238}
239
240bool mesh_native_worker_launch(mesh_native_worker *w, uint32_t slot, char **err) {
241    pthread_mutex_lock(&w->mu);
242    run_state *r = &w->run;
243    poll_run(w);
244    if (r->active || r->pend) {
245        pthread_mutex_unlock(&w->mu);
246        return mesh_errf(err, "%s: its worker still runs a launch", w->name);
247    }
248    r->gen++;
249    r->slot = slot;
250    r->exit_seen = r->dead_seen = false;
251    free(r->error);
252    r->error = NULL;
253    char *line = mesh_fmt("run %u %u", (unsigned)r->gen, (unsigned)slot);
254    if (r->p) {
255        proc *p = r->p;
256        if (!proc_exited(p, NULL) && proc_send(p, line)) {
257            r->active = true;
258            free(line);
259            pthread_mutex_unlock(&w->mu);
260            return true;
261        }
262        gone(w, r, p, false);
263    }
264    pending *x = mesh_alloc(sizeof *x);
265    atomic_init(&x->refs, 2);
266    pthread_mutex_init(&x->mu, NULL);
267    spawn_job *j = mesh_alloc(sizeof *j);
268    atomic_fetch_add(&w->spec->refs, 1);
269    *j = (spawn_job){w->host, w->spec, line, x};
270    pthread_t t;
271    pthread_attr_t at;
272    pthread_attr_init(&at);
273    pthread_attr_setdetachstate(&at, PTHREAD_CREATE_DETACHED);
274    bool started = pthread_create(&t, &at, spawn_thread, j) == 0;
275    pthread_attr_destroy(&at);
276    if (!started) {
277        pending_unref(x);
278        pending_unref(x);
279        spec_unref(w->spec);
280        free(line);
281        free(j);
282        pthread_mutex_unlock(&w->mu);
283        return mesh_errf(err, "native: thread: cannot start one");
284    }
285    r->pend = x;
286    r->active = true;
287    pthread_mutex_unlock(&w->mu);
288    return true;
289}
290
291bool mesh_native_worker_query(mesh_native_worker *w) {
292    pthread_mutex_lock(&w->mu);
293    poll_run(w);
294    bool over = !w->run.active && !w->run.pend;
295    pthread_mutex_unlock(&w->mu);
296    return over;
297}
298
299char *mesh_native_worker_crashed(mesh_native_worker *w) {
300    pthread_mutex_lock(&w->mu);
301    poll_run(w);
302    char *e = w->run.error;
303    w->run.error = NULL;
304    pthread_mutex_unlock(&w->mu);
305    return e;
306}
307
308mesh_native_stats mesh_native_worker_stats(mesh_native_worker *w) {
309    pthread_mutex_lock(&w->mu);
310    poll_run(w);
311    proc *live = w->run.p && !proc_exited(w->run.p, NULL) ? w->run.p : NULL;
312    uint64_t cpu = 0, rss = 0;
313    float pct = 0;
314    if (live) proc_last(live, &cpu, &rss, &pct);
315    mesh_native_stats s = {live ? live->pid : 0, pct, rss, w->run.cpu_gone + cpu, atomic_load(&w->spec->spawns), atomic_load(&w->crashes),
316                           w->run.last_exit, live ? live->limits : 0};
317    pthread_mutex_unlock(&w->mu);
318    return s;
319}
320
321pid_t mesh_native_worker_pid(mesh_native_worker *w) {
322    pthread_mutex_lock(&w->mu);
323    pid_t pid = w->run.p && !proc_exited(w->run.p, NULL) ? w->run.p->pid : 0;
324    pthread_mutex_unlock(&w->mu);
325    return pid;
326}
327
328void mesh_native_worker_release(mesh_native_worker *w) {
329    pthread_mutex_lock(&w->mu);
330    if (w->run.p) {
331        proc *p = w->run.p;
332        w->run.p = NULL;
333        proc_retire(p, w->host);
334    }
335    if (w->run.pend) {
336        pthread_mutex_lock(&w->run.pend->mu);
337        w->run.pend->abandoned = true;
338        proc *p = w->run.pend->p;
339        w->run.pend->p = NULL;
340        pthread_mutex_unlock(&w->run.pend->mu);
341        if (p) proc_retire(p, w->host);
342        pending_unref(w->run.pend);
343        w->run.pend = NULL;
344    }
345    w->run.active = false;
346    pthread_mutex_unlock(&w->mu);
347}
348
349static void worker_free(mesh_native_worker *w) {
350    mesh_native_worker_release(w);
351    free(w->run.error);
352    spec_unref(w->spec);
353    free(w->name);
354    pthread_mutex_destroy(&w->mu);
355    free(w);
356}
357
358/* ---- the image as a mesh_kernel ------------------------------------------------------------ */
359
360static bool k_zero(const mesh_kernel *k, uint32_t *out, char **err) {
361    (void)k, (void)err;
362    *out = 0;
363    return true;
364}
365static bool k_one(const mesh_kernel *k, uint32_t *out, char **err) {
366    (void)k, (void)err;
367    *out = 1; /* a process: nothing resident */
368    return true;
369}
370static uint64_t k_scratch(const mesh_kernel *k) {
371    (void)k;
372    return 0;
373}
374static bool k_global(const mesh_kernel *k, const char *name, uint32_t *out, char **err) {
375    (void)k;
376    /* The manifest's abi was checked, and the worker's native ABI at its `ready`. */
377    if (strcmp(name, MESH_RT_ABI_SYMBOL) == 0) {
378        *out = MESH_RT_ABI;
379        return true;
380    }
381    return mesh_errf(err, "global %s: a native image reports only %s", name, MESH_RT_ABI_SYMBOL);
382}
383static void k_drop(mesh_kernel *k) { worker_free(k->self); }
384
385static const mesh_kernel_ops kernel_ops = {MESH_BACKEND_NATIVE, k_zero, k_zero, k_one, k_scratch, k_global, k_drop};
386
387/* ---- the compiler --------------------------------------------------------------------------- */
388
389typedef struct {
390    mesh_native_host *host;
391    mesh_compiler comp;
392    mesh_caps caps;
393} native_backend;
394
395static bool c_needs_ptx(const mesh_compiler *c) {
396    (void)c;
397    return false;
398}
399
400static char *slurp(const char *path, size_t *n) {
401    FILE *f = fopen(path, "rb");
402    if (!f) return NULL;
403    mesh_text t = {0};
404    char buf[65536];
405    size_t k;
406    while ((k = fread(buf, 1, sizeof buf, f)) > 0) mesh_text_putn(&t, buf, k);
407    fclose(f);
408    *n = t.len;
409    if (!t.data) {
410        t.data = mesh_alloc(1);
411        return t.data;
412    }
413    return mesh_text_take(&t);
414}
415
416static bool c_prepare(const mesh_compiler *c, mesh_payload *src, const uint64_t *expect, const char *root, mesh_prepared *out, char **err) {
417    native_backend *nb = c->self;
418    uint64_t t0 = mesh_native_now_ns();
419    *out = (mesh_prepared){0};
420    mesh_manifest m;
421    if (!mesh_check_manifest(src, MESH_BACKEND_NATIVE, &m, err)) {
422        mesh_payload_free(src);
423        return false;
424    }
425    char *dir = mesh_loader_resolve(root, src->dir, err), *bin = NULL, *source = NULL;
426    uint8_t *code = NULL;
427    size_t code_len = 0;
428    if (!dir) goto fail;
429    bin = mesh_native_binary(dir, m.name);
430    code = (uint8_t *)slurp(bin, &code_len);
431    if (!code) {
432        char *e = os_error(errno);
433        mesh_errf(err, "unsupported: %s has no binary for %s (%s: %s)", m.name, mesh_native_target(), bin, e);
434        free(e);
435        goto fail;
436    }
437    if (!mesh_native_check_format(m.name, bin, code, code_len, err)) goto fail;
438    struct stat st;
439    if (stat(bin, &st) != 0) {
440        char *e = os_error(errno);
441        mesh_errf(err, "load: %s: %s", bin, e);
442        free(e);
443        goto fail;
444    }
445    if ((st.st_mode & 0111) == 0) {
446        mesh_errf(err, "load: %s: %s is not executable", m.name, bin);
447        goto fail;
448    }
449    char *sp = mesh_fmt("%s/source.hash", dir);
450    size_t sn;
451    source = slurp(sp, &sn);
452    if (!source) {
453        char *e = os_error(errno);
454        mesh_errf(err, "manifest: %s: %s/source.hash: %s (build it with mesh-build-programs)", m.name, dir, e);
455        free(e);
456        free(sp);
457        goto fail;
458    }
459    free(sp);
460    uint64_t hash = mesh_native_identity(src->manifest, source, m.param);
461    if (!mesh_check_hash(m.name, "source", hash, expect, err)) goto fail;
462    mesh_native_limits limits = mesh_native_limits_of(&m);
463    if (!mesh_native_limits_available(&limits, err)) goto fail;
464    uint64_t bin_hash = mesh_fnv1a64(code, code_len);
465    if (m.device_gpu) {
466        if (nb->host->cfg.gpu == MESH_GPU_ACCESS_NONE) {
467            mesh_errf(err, "unsupported: %s: device gpu, and this meshd has no GPU (--backend wasm)", m.name);
468            goto fail;
469        }
470        if (nb->host->cfg.gpu == MESH_GPU_ACCESS_BLINK) {
471            mesh_errf(err, "load: %s: device gpu needs --isolation mps (a worker's own CUDA context is time-sliced against the resident kernels)", m.name);
472            goto fail;
473        }
474    }
475    spec *s = mesh_alloc(sizeof *s);
476    atomic_init(&s->refs, 1);
477    s->name = mesh_strdup(m.name);
478    s->bin = bin;
479    bin = NULL;
480    s->limits = limits;
481    s->gpu = m.device_gpu;
482    mesh_native_worker *w = mesh_alloc(sizeof *w);
483    w->name = mesh_strdup(m.name);
484    w->bin_hash = bin_hash;
485    w->bin_bytes = code_len;
486    w->spec = s;
487    w->host = nb->host;
488    pthread_mutex_init(&w->mu, NULL);
489    const char *trimmed = source;
490    size_t tn = strlen(source);
491    mesh_trim(&trimmed, &tn);
492    mesh_info("native: %s checked: %zu bytes for %s, binary %016llx, image %016llx (source %.*s)", m.name, code_len, mesh_native_target(),
493              (unsigned long long)bin_hash, (unsigned long long)hash, (int)tn, trimmed);
494    mesh_kernel *k = mesh_alloc(sizeof *k);
495    *k = (mesh_kernel){&kernel_ops, w};
496    out->manifest = m;
497    out->hash = hash;
498    out->code = code;
499    out->code_len = code_len;
500    out->compile_ms = (double)(mesh_native_now_ns() - t0) / 1e6;
501    out->src = *src;
502    *src = (mesh_payload){0};
503    out->kernel = k;
504    out->exec = MESH_BACKEND_NATIVE;
505    out->reg_cap = 0;
506    free(dir);
507    free(source);
508    return true;
509fail:
510    free(dir);
511    free(bin);
512    free(code);
513    free(source);
514    mesh_manifest_free(&m);
515    mesh_payload_free(src);
516    return false;
517}
518
519static const mesh_compiler_ops compiler_ops = {c_needs_ptx, c_prepare};
520
521/* ---- the backend ---------------------------------------------------------------------------- */
522
523static native_backend *nbk(const mesh_backend *b) { return b->self; }
524static mesh_native_worker *worker_of(const mesh_kernel *k) { return mesh_kernel_native(k); }
525
526static const mesh_caps *b_caps(const mesh_backend *b) { return &nbk(b)->caps; }
527static bool b_true(const mesh_backend *b) {
528    (void)b;
529    return true;
530}
531static bool b_false(const mesh_backend *b) {
532    (void)b;
533    return false;
534}
535static uint32_t b_isolation(const mesh_backend *b) {
536    (void)b;
537    return MESH_ISOLATION_LEASE;
538}
539static bool b_mailbox(const mesh_backend *b, uint8_t **host, uint64_t *dev, char **err) {
540    (void)err;
541    *host = nbk(b)->host->shm.base;
542    *dev = 0;
543    return true;
544}
545static bool b_make_current(const mesh_backend *b, char **err) {
546    (void)b, (void)err;
547    return true;
548}
549static const mesh_compiler *b_compiler(const mesh_backend *b) { return &nbk(b)->comp; }
550static bool b_load(const mesh_backend *b, mesh_prepared *p, mesh_kernel **out, char **err) {
551    (void)b;
552    if (!p->kernel) return mesh_errf(err, "internal: a native image not checked on the JIT thread");
553    *out = p->kernel;
554    p->kernel = NULL;
555    return true;
556}
557static bool b_new_stream(const mesh_backend *b, uint32_t slot, const mesh_kernel *k, mesh_stream **out, char **err) {
558    (void)b, (void)k, (void)err;
559    mesh_stream *s = mesh_alloc(sizeof *s);
560    *s = (mesh_stream){MESH_BACKEND_NATIVE, slot, NULL, NULL};
561    *out = s;
562    return true;
563}
564static bool b_launch(const mesh_backend *b, const mesh_stream *st, const mesh_kernel *k, uint32_t nodes, uint64_t dev, uint32_t slot, char **err) {
565    (void)b, (void)st, (void)nodes, (void)dev;
566    mesh_native_worker *w = worker_of(k);
567    if (!w) return mesh_errf(err, "internal: a non-native image on the native executor");
568    return mesh_native_worker_launch(w, slot, err);
569}
570static bool b_query(const mesh_backend *b, const mesh_stream *st, const mesh_kernel *k, bool *done, char **err) {
571    (void)b, (void)st;
572    mesh_native_worker *w = worker_of(k);
573    if (!w) return mesh_errf(err, "internal: a non-native image on the native executor");
574    *done = mesh_native_worker_query(w);
575    return true;
576}
577static bool b_unload(const mesh_backend *b, mesh_kernel *k, char **err) {
578    (void)b;
579    if (!worker_of(k)) {
580        mesh_kernel_drop(k);
581        return mesh_errf(err, "internal: a non-native image on the native executor");
582    }
583    mesh_kernel_drop(k); /* releases: its process exits, reaped off this thread */
584    return true;
585}
586static bool b_stack_limit(const mesh_backend *b, size_t *out, char **err) {
587    (void)b, (void)err;
588    *out = SIZE_MAX;
589    return true;
590}
591static bool b_set_stack_limit(const mesh_backend *b, size_t bytes, char **err) {
592    (void)b, (void)bytes, (void)err;
593    return true;
594}
595static bool b_module_scope(const mesh_backend *b, bool has_slot, uint32_t slot, uint32_t *scope) {
596    (void)b;
597    *scope = has_slot ? slot : MESH_NO_SLOT;
598    return true;
599}
600static char *b_crashed(const mesh_backend *b, const mesh_stream *st, const mesh_kernel *k) {
601    (void)b, (void)st;
602    mesh_native_worker *w = worker_of(k);
603    return w ? mesh_native_worker_crashed(w) : NULL;
604}
605static uint32_t b_online(const mesh_backend *b) {
606    (void)b;
607    return MESH_NATIVE_ONLINE_MS;
608}
609static void b_sample(const mesh_backend *b) {
610    mesh_native_totals t = mesh_native_host_totals(nbk(b)->host);
611    atomic_store(&mesh_cpuuse_workers, t.procs);
612}
613static void b_destroy(mesh_backend *b) {
614    native_backend *n = nbk(b);
615    mesh_caps_free(&n->caps);
616    free(n);
617    free(b);
618}
619
620static const mesh_backend_ops backend_ops = {
621    b_caps, b_true, b_false, b_isolation, b_mailbox, b_make_current, b_compiler, b_load, b_new_stream, b_launch, b_query, b_unload,
622    b_stack_limit, b_set_stack_limit, b_module_scope, b_crashed, b_true, b_true, b_sample, b_online, b_destroy,
623};
624
625mesh_backend *mesh_native_backend_new(const mesh_shm *shm, const char *logs, const mesh_worker_cfg *cfg) {
626    native_backend *n = mesh_alloc(sizeof *n);
627    n->host = mesh_native_host_new(shm, logs, cfg);
628    n->comp = (mesh_compiler){&compiler_ops, n};
629    long threads = sysconf(_SC_NPROCESSORS_ONLN);
630    if (threads < 1) threads = 1;
631    n->caps = (mesh_caps){MESH_BACKEND_NATIVE, mesh_fmt("native workers (%s)", mesh_native_target()), 0, 0, (uint32_t)threads,
632                          (uint32_t)threads, false, 0, 0, 0, 0, mesh_strdup(mesh_native_target())};
633    mesh_backend *b = mesh_alloc(sizeof *b);
634    *b = (mesh_backend){&backend_ops, n};
635    mesh_backend_on_cleanup(MESH_CLEANUP_NATIVE, mesh_native_cleanup);
636    return b;
637}
638
639mesh_native_host *mesh_native_backend_host(const mesh_backend *b) { return nbk(b)->host; }