==> include/mesh/rt_wasm.h <==1/* @ajax/mesh-rt-wasm: the runtime a CPU mesh program is built on -- mesh-rt's surface for meshd's
2 * wasm executor (mesh-abi BACKEND_WASM), ported from alpha-rust-mesh crates/mesh-rt-wasm. A program
3 * (`programs/<name>-wasm`, a sexc "wasm" package) includes this and writes
4 *
5 * static void init(const mesh_ctx *ctx) { ... } // op 1 (OP_INIT)
6 * static uint32_t task(const mesh_ctx *ctx, uint32_t op, uint32_t arg) { ... } // op 0 and 2..
7 * MESH_PROGRAM(init, task)
8 *
9 * which exports mesh_init, mesh_task and mesh_rt_abi (the contract is mesh-abi v2::wasm,
10 * @ajax/mesh-abi-wasm). Differences from mesh-rt, all from running on one CPU thread instead of a
11 * warp:
12 * - a node is a lane thread of meshd running one instance: mesh_ctx_lane() is 0,
13 * mesh_ctx_leader() true, and the collectives (input_pop, respond) are plain calls;
14 * - the mailbox is not in linear memory: a mesh_view names a window and every access is a
15 * bounds-checked mesh::* import (out of range: 0 / dropped and P_FAULT, as on the GPU). Bulk
16 * read/write move whole ranges in one call -- what a hot loop should use;
17 * - linear memory is scratch, like registers: state that must survive a task, a restart or an
18 * upgrade lives in the view;
19 * - f32 in the cluster float mode is Z (@ajax/mesh-ftz: on wasm32 its operations are the exported
20 * mesh_ftz_* helpers): an image whose manifest declares float = "ftz" (the default) is refused by
21 * meshd if any f32 arithmetic bypasses it. */
22#ifndef MESH_RT_WASM_H
23#define MESH_RT_WASM_H
24
25#include "mesh/abi/base.h"
26#include "mesh/abi/exchange.h"
27#include "mesh/abi/fault.h"
28#include "mesh/abi/ops.h"
29#include "mesh/abi/pipe.h"
30#include "mesh/abi/wasm.h"
31#include "mesh/ftz.h"
32
33#include <stdbool.h>
34#include <stddef.h>
35#include <stdint.h>
36
37/* ---- the mesh::* imports ---- */
38
39#define MESH_IMPORT(nm) __attribute__((import_module("mesh"), import_name(#nm)))
40MESH_IMPORT(info) uint32_t mesh_sys_info(uint32_t code);
41MESH_IMPORT(ld8) uint32_t mesh_sys_ld8(uint32_t h, uint32_t off);
42MESH_IMPORT(ld32) uint32_t mesh_sys_ld32(uint32_t h, uint32_t off);
43MESH_IMPORT(ld64) uint64_t mesh_sys_ld64(uint32_t h, uint32_t off);
44MESH_IMPORT(st8) void mesh_sys_st8(uint32_t h, uint32_t off, uint32_t v);
45MESH_IMPORT(st32) void mesh_sys_st32(uint32_t h, uint32_t off, uint32_t v);
46MESH_IMPORT(st64) void mesh_sys_st64(uint32_t h, uint32_t off, uint64_t v);
47MESH_IMPORT(cas32) uint32_t mesh_sys_cas32(uint32_t h, uint32_t off, uint32_t cmp, uint32_t desired);
48MESH_IMPORT(add32) uint32_t mesh_sys_add32(uint32_t h, uint32_t off, uint32_t v);
49MESH_IMPORT(add64) uint64_t mesh_sys_add64(uint32_t h, uint32_t off, uint64_t v);
50MESH_IMPORT(read) void mesh_sys_read(uint32_t h, uint32_t off, void *ptr, uint32_t len);
51MESH_IMPORT(write) void mesh_sys_write(uint32_t h, uint32_t off, const void *ptr, uint32_t len);
52MESH_IMPORT(peer) int32_t mesh_sys_peer(uint32_t slot);
53MESH_IMPORT(find) int32_t mesh_sys_find(const void *ptr, uint32_t len);
54MESH_IMPORT(prog_state) uint32_t mesh_sys_prog_state(uint32_t slot);
55MESH_IMPORT(count) void mesh_sys_count(uint32_t i, uint32_t n);
56MESH_IMPORT(counter) uint32_t mesh_sys_counter(uint32_t i);
57MESH_IMPORT(input_pending) uint32_t mesh_sys_input_pending(void);
58MESH_IMPORT(input_pop) int32_t mesh_sys_input_pop(void);
59MESH_IMPORT(respond) void mesh_sys_respond(uint32_t len);
60MESH_IMPORT(exchange) uint32_t mesh_sys_exchange(uint64_t n);
61MESH_IMPORT(pipe_push) int32_t mesh_sys_pipe_push(uint32_t slot, const void *ptr, uint32_t len);
62MESH_IMPORT(pipe_pop) int32_t mesh_sys_pipe_pop(void *ptr, uint32_t len);
63MESH_IMPORT(pipe_len) int32_t mesh_sys_pipe_len(uint32_t slot);
64MESH_IMPORT(host_ns) uint64_t mesh_sys_host_ns(void);
65MESH_IMPORT(fault) void mesh_sys_fault(uint32_t code);
66#undef MESH_IMPORT
67
68/* ---- View: a byte window of the mailbox (a view or a part of one). Offsets are bytes from its
69 * start; u32/f32 accessors need 4-aligned offsets, u64 8-aligned. ---- */
70
71typedef struct {
72 uint32_t h, base, len;
73} mesh_view;
74
75static inline bool mesh_view_ok(mesh_view v, uint32_t off, uint32_t n) {
76 bool fine = (uint64_t)off + n <= v.len;
77 if (!fine) mesh_sys_fault(MESH_FAULT_VIEW_BOUNDS | off << 8);
78 return fine;
79}
80static inline uint32_t mesh_view_len(mesh_view v) { return v.len; }
81static inline bool mesh_view_is_empty(mesh_view v) { return v.len == 0; }
82static inline uint32_t mesh_view_u32(mesh_view v, uint32_t off) { return mesh_view_ok(v, off, 4) ? mesh_sys_ld32(v.h, v.base + off) : 0; }
83static inline float mesh_view_f32(mesh_view v, uint32_t off) { return mesh_ftz_from_bits(mesh_view_u32(v, off)); }
84static inline uint64_t mesh_view_u64(mesh_view v, uint32_t off) { return mesh_view_ok(v, off, 8) ? mesh_sys_ld64(v.h, v.base + off) : 0; }
85static inline uint8_t mesh_view_u8(mesh_view v, uint32_t off) { return mesh_view_ok(v, off, 1) ? (uint8_t)mesh_sys_ld8(v.h, v.base + off) : 0; }
86static inline void mesh_view_set_u32(mesh_view v, uint32_t off, uint32_t x) { if (mesh_view_ok(v, off, 4)) mesh_sys_st32(v.h, v.base + off, x); }
87static inline void mesh_view_set_f32(mesh_view v, uint32_t off, float x) { mesh_view_set_u32(v, off, mesh_ftz_bits(x)); }
88static inline void mesh_view_set_u64(mesh_view v, uint32_t off, uint64_t x) { if (mesh_view_ok(v, off, 8)) mesh_sys_st64(v.h, v.base + off, x); }
89static inline void mesh_view_set_u8(mesh_view v, uint32_t off, uint8_t x) { if (mesh_view_ok(v, off, 1)) mesh_sys_st8(v.h, v.base + off, x); }
90/* Atomics (acq_rel): compare-and-swap (the value seen), fetch-adds (the old value). */
91static inline uint32_t mesh_view_cas32(mesh_view v, uint32_t off, uint32_t cmp, uint32_t desired) {
92 return mesh_view_ok(v, off, 4) ? mesh_sys_cas32(v.h, v.base + off, cmp, desired) : 0;
93}
94static inline uint32_t mesh_view_add32(mesh_view v, uint32_t off, uint32_t x) { return mesh_view_ok(v, off, 4) ? mesh_sys_add32(v.h, v.base + off, x) : 0; }
95static inline uint64_t mesh_view_add64(mesh_view v, uint32_t off, uint64_t x) { return mesh_view_ok(v, off, 8) ? mesh_sys_add64(v.h, v.base + off, x) : 0; }
96/* n bytes from off into dst in one call, and back. */
97static inline void mesh_view_read(mesh_view v, uint32_t off, void *dst, uint32_t n) { if (mesh_view_ok(v, off, n)) mesh_sys_read(v.h, v.base + off, dst, n); }
98static inline void mesh_view_write(mesh_view v, uint32_t off, const void *src, uint32_t n) { if (mesh_view_ok(v, off, n)) mesh_sys_write(v.h, v.base + off, src, n); }
99/* u32 / f32 words (little-endian, as the view holds them). */
100static inline void mesh_view_read_u32s(mesh_view v, uint32_t off, uint32_t *dst, uint32_t n) { mesh_view_read(v, off, dst, 4 * n); }
101static inline void mesh_view_write_u32s(mesh_view v, uint32_t off, const uint32_t *src, uint32_t n) { mesh_view_write(v, off, src, 4 * n); }
102static inline void mesh_view_read_f32s(mesh_view v, uint32_t off, float *dst, uint32_t n) { mesh_view_read(v, off, dst, 4 * n); }
103static inline void mesh_view_write_f32s(mesh_view v, uint32_t off, const float *src, uint32_t n) { mesh_view_write(v, off, src, 4 * n); }
104/* A sub-window [off, off + len): true with *out, or false (and a fault) when out of range. */
105static inline bool mesh_view_sub(mesh_view v, uint32_t off, uint32_t len, mesh_view *out) {
106 if (!mesh_view_ok(v, off, len)) return false;
107 out->h = v.h;
108 out->base = v.base + off;
109 out->len = len;
110 return true;
111}
112
113/* ---- Exchange: the exchange page (EX_BASE) for the lockstep program: system k's u64 round
114 * counter and its two EX_SLOT_BYTES slots (parity 0, 1). ---- */
115
116typedef struct {
117 mesh_view w;
118} mesh_exchange;
119
120#define MESH_EX_SLOT_WORDS (MESH_EX_SLOT_BYTES / 4)
121
122static inline uint64_t mesh_exchange_counter(mesh_exchange x, uint32_t k) {
123 return mesh_view_u64(x.w, (uint32_t)(mesh_ex_counter(k) - MESH_EX_BASE));
124}
125/* System k's slot of parity par (0, 1) as u32 words. */
126static inline void mesh_exchange_slot(mesh_exchange x, uint32_t k, uint32_t par, uint32_t out[MESH_EX_SLOT_WORDS]) {
127 mesh_view_read_u32s(x.w, (uint32_t)(mesh_ex_slots(k) - MESH_EX_BASE) + MESH_EX_SLOT_BYTES * par, out, MESH_EX_SLOT_WORDS);
128}
129static inline void mesh_exchange_set_slot(mesh_exchange x, uint32_t k, uint32_t par, const uint32_t w[MESH_EX_SLOT_WORDS]) {
130 mesh_view_write_u32s(x.w, (uint32_t)(mesh_ex_slots(k) - MESH_EX_BASE) + MESH_EX_SLOT_BYTES * par, w, MESH_EX_SLOT_WORDS);
131}
132/* Round n: publish this system's counter = n (once), wait -- bounded, PEER_POLLS x PEER_POLL_NS,
133 * in meshd -- for every member's. true: all are there. */
134static inline bool mesh_exchange_round(mesh_exchange x, uint64_t n) {
135 (void)x;
136 return mesh_sys_exchange(n) == 1;
137}
138
139/* ---- Ctx: what a program's init/task see. ---- */
140
141typedef struct {
142 uint32_t slot, node, nodes;
143 mesh_view view;
144 uint32_t req_off, resp_off, resp_max;
145 bool lockstep;
146} mesh_ctx;
147
148/* The instance's Ctx (built on first use; its facts are fixed for the instance's life). */
149const mesh_ctx *mesh_ctx_get(void);
150
151/* ids */
152static inline uint32_t mesh_ctx_slot(const mesh_ctx *c) { return c->slot; }
153static inline uint32_t mesh_ctx_node(const mesh_ctx *c) { return c->node; }
154static inline uint32_t mesh_ctx_lane(const mesh_ctx *c) { (void)c; return 0; }
155static inline uint32_t mesh_ctx_nodes(const mesh_ctx *c) { return c->nodes; }
156static inline bool mesh_ctx_leader(const mesh_ctx *c) { (void)c; return true; }
157/* The running task's ring ticket. */
158static inline uint32_t mesh_ctx_ticket(const mesh_ctx *c) { (void)c; return mesh_sys_info(MESH_WASM_I_TICKET); }
159/* This system's number in the cluster (0..systems), or PEER_NONE. */
160static inline uint32_t mesh_ctx_system(const mesh_ctx *c) { (void)c; return mesh_sys_info(MESH_WASM_I_SYSTEM); }
161static inline uint32_t mesh_ctx_systems(const mesh_ctx *c) { (void)c; return mesh_sys_info(MESH_WASM_I_SYSTEMS); }
162/* The lockstep group this member is in: true with (rank, size), 2 <= size <= MAX_SYSTEMS, rank < size. */
163static inline bool mesh_ctx_group(const mesh_ctx *c, uint32_t *rank, uint32_t *n) {
164 (void)c;
165 uint32_t r = mesh_sys_info(MESH_WASM_I_GROUP_RANK), k = mesh_sys_info(MESH_WASM_I_GROUP_N);
166 if (k < 2 || k > MESH_MAX_SYSTEMS || r >= k) return false;
167 *rank = r;
168 *n = k;
169 return true;
170}
171/* The manifest's `param` (PX_PARAM, read live). */
172static inline uint32_t mesh_ctx_param(const mesh_ctx *c) { (void)c; return mesh_sys_info(MESH_WASM_I_PARAM); }
173/* H_SMS (on a CPU-only meshd: its hardware threads). */
174static inline uint32_t mesh_ctx_sms(const mesh_ctx *c) { (void)c; return mesh_sys_info(MESH_WASM_I_SMS); }
175/* The host clock in ns (the trace's timebase). */
176static inline uint64_t mesh_ctx_host_ns(const mesh_ctx *c) { (void)c; return mesh_sys_host_ns(); }
177
178/* own view */
179static inline mesh_view mesh_ctx_view(const mesh_ctx *c) { return c->view; }
180static inline uint32_t mesh_ctx_view_len(const mesh_ctx *c) { return c->view.len; }
181static inline uint32_t mesh_ctx_view_u32(const mesh_ctx *c, uint32_t off) { return mesh_view_u32(c->view, off); }
182static inline float mesh_ctx_view_f32(const mesh_ctx *c, uint32_t off) { return mesh_view_f32(c->view, off); }
183static inline uint64_t mesh_ctx_view_u64(const mesh_ctx *c, uint32_t off) { return mesh_view_u64(c->view, off); }
184static inline uint8_t mesh_ctx_view_u8(const mesh_ctx *c, uint32_t off) { return mesh_view_u8(c->view, off); }
185static inline void mesh_ctx_set_u32(const mesh_ctx *c, uint32_t off, uint32_t v) { mesh_view_set_u32(c->view, off, v); }
186static inline void mesh_ctx_set_f32(const mesh_ctx *c, uint32_t off, float v) { mesh_view_set_f32(c->view, off, v); }
187static inline void mesh_ctx_set_u64(const mesh_ctx *c, uint32_t off, uint64_t v) { mesh_view_set_u64(c->view, off, v); }
188static inline void mesh_ctx_set_u8(const mesh_ctx *c, uint32_t off, uint8_t v) { mesh_view_set_u8(c->view, off, v); }
189
190/* other programs: slot s's view for this task, if the manifest's `reads` lists it and the slot
191 * holds a program (an undeclared read also records FAULT_PEER_VIEW). */
192static inline bool mesh_ctx_peer_view(const mesh_ctx *c, uint32_t s, mesh_view *out) {
193 (void)c;
194 int32_t len = mesh_sys_peer(s);
195 if (len < 0) return false;
196 out->h = mesh_wasm_w_peer(s);
197 out->base = 0;
198 out->len = (uint32_t)len;
199 return true;
200}
201/* The slot holding the program `name` (len bytes): true with *slot. */
202static inline bool mesh_ctx_find(const mesh_ctx *c, const char *name, uint32_t len, uint32_t *slot) {
203 (void)c;
204 int32_t s = mesh_sys_find(name, len);
205 if (s < 0) return false;
206 *slot = (uint32_t)s;
207 return true;
208}
209static inline uint32_t mesh_ctx_prog_state(const mesh_ctx *c, uint32_t s) { (void)c; return mesh_sys_prog_state(s); }
210
211/* pipes (manifest `inbox`, `feeds`): push one record (exactly the consumer's record size) into slot
212 * s's inbox: true pushed, false full (backpressure). push_code gives the full PIPE_* code. */
213static inline int32_t mesh_ctx_push_code(const mesh_ctx *c, uint32_t s, const void *rec, uint32_t len) {
214 (void)c;
215 return mesh_sys_pipe_push(s, rec, len);
216}
217static inline bool mesh_ctx_push(const mesh_ctx *c, uint32_t s, const void *rec, uint32_t len) {
218 return mesh_ctx_push_code(c, s, rec, len) == MESH_PIPE_OK;
219}
220static inline bool mesh_ctx_push_u32s(const mesh_ctx *c, uint32_t s, const uint32_t *rec, uint32_t n) {
221 return mesh_ctx_push(c, s, rec, 4 * n);
222}
223/* Pops the own inbox's next record into out (its record size): false when empty. */
224static inline bool mesh_ctx_pop(const mesh_ctx *c, void *out, uint32_t len) {
225 (void)c;
226 return mesh_sys_pipe_pop(out, len) == MESH_PIPE_OK;
227}
228static inline bool mesh_ctx_pop_u32s(const mesh_ctx *c, uint32_t *out, uint32_t n) { return mesh_ctx_pop(c, out, 4 * n); }
229/* Records waiting in slot s's inbox (the own slot, or one this program feeds): true with *n. */
230static inline bool mesh_ctx_pipe_len(const mesh_ctx *c, uint32_t s, uint32_t *n) {
231 (void)c;
232 int32_t k = mesh_sys_pipe_len(s);
233 if (k < 0) return false;
234 *n = (uint32_t)k;
235 return true;
236}
237
238/* input queue (manifest `input`) */
239static inline uint32_t mesh_ctx_input_pending(const mesh_ctx *c) { (void)c; return mesh_sys_input_pending(); }
240/* The next input byte: true with *b; false when empty. */
241static inline bool mesh_ctx_input_pop(const mesh_ctx *c, uint8_t *b) {
242 (void)c;
243 int32_t x = mesh_sys_input_pop();
244 if (x < 0) return false;
245 *b = (uint8_t)x;
246 return true;
247}
248
249/* request window (manifest `request`, op OP_REQ) */
250static inline mesh_view mesh_ctx_request(const mesh_ctx *c) {
251 mesh_view v = {MESH_WASM_W_VIEW, c->req_off, mesh_sys_info(MESH_WASM_I_REQ_LEN)};
252 return v;
253}
254static inline mesh_view mesh_ctx_response(const mesh_ctx *c) {
255 mesh_view v = {MESH_WASM_W_VIEW, c->resp_off, c->resp_max};
256 return v;
257}
258static inline void mesh_ctx_respond(const mesh_ctx *c, uint32_t len) { (void)c; mesh_sys_respond(len); }
259
260/* lockstep: the exchange page, only for the program with peer = "lockstep". */
261static inline bool mesh_ctx_exchange(const mesh_ctx *c, mesh_exchange *out) {
262 if (!c->lockstep) return false;
263 mesh_view w = {MESH_WASM_W_EXCHANGE, 0, MESH_EX_BYTES};
264 out->w = w;
265 return true;
266}
267
268/* counters (manifest `counters`) */
269static inline void mesh_ctx_count(const mesh_ctx *c, uint32_t i, uint32_t n) { (void)c; mesh_sys_count(i, n); }
270static inline uint32_t mesh_ctx_counter(const mesh_ctx *c, uint32_t i) { (void)c; return mesh_sys_counter(i); }
271
272/* Traps the instance (wasm `unreachable`): FAULT_TRAP, the slot FAILED. */
273#define MESH_TRAP() __builtin_trap()
274
275/* Declares the program: init(const mesh_ctx *) runs op 1 (OP_INIT); task(const mesh_ctx *, op, arg)
276 * every other op, its result the task's. Exports mesh_init, mesh_task, mesh_rt_abi. */
277#define MESH_PROGRAM(init_fn, task_fn) \
278 __attribute__((export_name("mesh_init"))) void mesh_init(void); \
279 void mesh_init(void) { init_fn(mesh_ctx_get()); } \
280 __attribute__((export_name("mesh_task"))) uint32_t mesh_task(uint32_t op, uint32_t arg); \
281 uint32_t mesh_task(uint32_t op, uint32_t arg) { return task_fn(mesh_ctx_get(), op, arg); } \
282 __attribute__((export_name("mesh_rt_abi"))) uint32_t mesh_rt_abi(void); \
283 uint32_t mesh_rt_abi(void) { return MESH_RT_ABI; }
284
285#endif