==> 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; }