nomen-lang 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,441 @@
1
+ // Tcp — non-blocking sockets for Nomen tasks.
2
+ //
3
+ // Sockets are put in non-blocking mode and every wait goes through the
4
+ // runtime's netpoller hook (`__nomen_io_wait`): inside a Fiber the task parks
5
+ // (freeing its worker) until the socket is readable/writable; on a plain
6
+ // thread the hook blocks in poll(). So the same code scales from a handful of
7
+ // connections to thousands, and no call blocks a shared worker while it
8
+ // waits. See ASYNC.md Phase 3.
9
+ //
10
+ // Raw helpers return file descriptors / error codes (int-shaped), so the
11
+ // class wrappers construct the Tcp instances in Nomen. `error` mirrors
12
+ // Http's convention: 0 = ok, 1 = DNS, 2 = socket, 3 = connect, 4 = listen,
13
+ // 5 = closed.
14
+ //
15
+ // var Tcp server = Tcp.listen(8080, 128)
16
+ // async {
17
+ // var Tcp conn = server.accept()
18
+ // var bool ok = conn.send("hello")
19
+ // conn.close()
20
+ // }
21
+ //
22
+ // The method bodies are C (compiled directly on the C backend, and via the
23
+ // linked companion on aarch64) — OS socket/DNS interop, where hand-written
24
+ // per-target assembly is not practical.
25
+
26
+ /**
27
+ * A non-blocking TCP socket whose waits park the calling fiber
28
+ **/
29
+ pub class Tcp : Sendable {
30
+ // The underlying file descriptor, -1 when closed/failed.
31
+ pub var int fd = -1
32
+ // 0 = ok; 1 = DNS, 2 = socket, 3 = connect, 4 = listen, 5 = closed.
33
+ pub var int error = 0
34
+
35
+ pub func #init = (self, int handle) {
36
+ self.fd = handle
37
+ self.error = 0
38
+ }
39
+
40
+ pub func #destroy = () {
41
+ ```
42
+ #arch: c
43
+ if (self->fd >= 0) {
44
+ close(self->fd);
45
+ self->fd = -1;
46
+ }
47
+ ```
48
+ ```
49
+ #arch: aarch64_use_c
50
+ if (self->fd >= 0) {
51
+ close(self->fd);
52
+ self->fd = -1;
53
+ }
54
+ ```
55
+ }
56
+
57
+ pub func close = (ref self) {
58
+ Tcp.close_fd(self.fd)
59
+ self.fd = -1
60
+ }
61
+
62
+ pub func is_open = (self, out bool) => self.fd >= 0
63
+
64
+ // ---- listening ----
65
+
66
+ // Create a listening socket bound to `port` on all interfaces.
67
+ pub func listen = (int port, int backlog, move out Tcp) {
68
+ var int handle = Tcp.listen_fd(port, backlog)
69
+ var Tcp t = Tcp(handle)
70
+ if handle < 0 {
71
+ t.error = 4
72
+ }
73
+ return t
74
+ }
75
+
76
+ // Accept the next connection, parking the fiber while the backlog is
77
+ // empty. The returned socket is -1 if the listener was cancelled or
78
+ // failed.
79
+ pub func accept = (ref self, move out Tcp) {
80
+ var int handle = Tcp.accept_fd(self.fd)
81
+ var Tcp t = Tcp(handle)
82
+ if handle < 0 {
83
+ t.error = 5
84
+ }
85
+ return t
86
+ }
87
+
88
+ // ---- connecting ----
89
+
90
+ // Connect to `host`:`port`. Check `fd >= 0` (or `error == 0`) for success.
91
+ pub func connect = (string host, int port, move out Tcp) {
92
+ var int handle = Tcp.socket_fd()
93
+ if handle < 0 {
94
+ var Tcp bad = Tcp(-1)
95
+ bad.error = 2
96
+ return bad
97
+ }
98
+ var int err = Tcp.connect_to(handle, host, port)
99
+ if err != 0 {
100
+ Tcp.close_fd(handle)
101
+ var Tcp bad = Tcp(-1)
102
+ bad.error = err
103
+ return bad
104
+ }
105
+ return Tcp(handle)
106
+ }
107
+
108
+ // ---- transferring ----
109
+
110
+ // Send every byte of `data`. Parks the fiber while the send buffer is
111
+ // full. Returns false on error or cancellation.
112
+ pub func send = (ref self, string data, out bool) {
113
+ return Tcp.send_all(self.fd, data)
114
+ }
115
+
116
+ // Receive up to `max` bytes. Parks the fiber until data arrives; returns
117
+ // the empty string at end-of-stream, on error, or when cancelled.
118
+ pub func recv = (ref self, int max, move out string) {
119
+ return Tcp.recv_some(self.fd, max)
120
+ }
121
+
122
+ // Receive until the peer closes (how Connection: close HTTP works).
123
+ pub func recv_all = (ref self, move out string) {
124
+ return Tcp.recv_until_close(self.fd)
125
+ }
126
+
127
+ // ---- raw helpers (int-shaped, so the wrappers above build the Tcp) ----
128
+
129
+ func socket_fd = (out int) {
130
+ ```
131
+ #arch: c
132
+ #scope: file
133
+ #include <sys/socket.h>
134
+ #include <netdb.h>
135
+ #include <netinet/in.h>
136
+ #include <arpa/inet.h>
137
+ #include <fcntl.h>
138
+ #include <unistd.h>
139
+ #include <string.h>
140
+ #include <stdlib.h>
141
+ #include <errno.h>
142
+ #include <stdbool.h>
143
+ ```
144
+ ```
145
+ #arch: c
146
+ int handle = socket(AF_INET, SOCK_STREAM, 0);
147
+ if (handle < 0) return -1;
148
+ fcntl(handle, F_SETFL, fcntl(handle, F_GETFL, 0) | O_NONBLOCK);
149
+ return handle;
150
+ ```
151
+ ```
152
+ #arch: aarch64_use_c
153
+ #scope: file
154
+ #include <sys/socket.h>
155
+ #include <netdb.h>
156
+ #include <netinet/in.h>
157
+ #include <arpa/inet.h>
158
+ #include <fcntl.h>
159
+ #include <unistd.h>
160
+ #include <string.h>
161
+ #include <stdlib.h>
162
+ #include <errno.h>
163
+ #include <stdbool.h>
164
+ ```
165
+ ```
166
+ #arch: aarch64_use_c
167
+ int handle = socket(AF_INET, SOCK_STREAM, 0);
168
+ if (handle < 0) return -1;
169
+ fcntl(handle, F_SETFL, fcntl(handle, F_GETFL, 0) | O_NONBLOCK);
170
+ return handle;
171
+ ```
172
+ }
173
+
174
+ func listen_fd = (int port, int backlog, out int) {
175
+ ```
176
+ #arch: c
177
+ int handle = socket(AF_INET, SOCK_STREAM, 0);
178
+ if (handle < 0) return -1;
179
+ int one = 1;
180
+ setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
181
+ struct sockaddr_in addr;
182
+ memset(&addr, 0, sizeof(addr));
183
+ addr.sin_family = AF_INET;
184
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
185
+ addr.sin_port = htons((unsigned short)port);
186
+ if (bind(handle, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
187
+ close(handle);
188
+ return -1;
189
+ }
190
+ if (listen(handle, backlog) < 0) {
191
+ close(handle);
192
+ return -1;
193
+ }
194
+ fcntl(handle, F_SETFL, fcntl(handle, F_GETFL, 0) | O_NONBLOCK);
195
+ return handle;
196
+ ```
197
+ ```
198
+ #arch: aarch64_use_c
199
+ int handle = socket(AF_INET, SOCK_STREAM, 0);
200
+ if (handle < 0) return -1;
201
+ int one = 1;
202
+ setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
203
+ struct sockaddr_in addr;
204
+ memset(&addr, 0, sizeof(addr));
205
+ addr.sin_family = AF_INET;
206
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
207
+ addr.sin_port = htons((unsigned short)port);
208
+ if (bind(handle, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
209
+ close(handle);
210
+ return -1;
211
+ }
212
+ if (listen(handle, backlog) < 0) {
213
+ close(handle);
214
+ return -1;
215
+ }
216
+ fcntl(handle, F_SETFL, fcntl(handle, F_GETFL, 0) | O_NONBLOCK);
217
+ return handle;
218
+ ```
219
+ }
220
+
221
+ func accept_fd = (int listener, out int) {
222
+ ```
223
+ #arch: c
224
+ for (;;) {
225
+ int handle = accept(listener, NULL, NULL);
226
+ if (handle >= 0) {
227
+ fcntl(handle, F_SETFL, fcntl(handle, F_GETFL, 0) | O_NONBLOCK);
228
+ return handle;
229
+ }
230
+ if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) return -1;
231
+ if (!__nomen_io_wait(listener, 0)) return -1;
232
+ }
233
+ ```
234
+ ```
235
+ #arch: aarch64_use_c
236
+ for (;;) {
237
+ int handle = accept(listener, NULL, NULL);
238
+ if (handle >= 0) {
239
+ fcntl(handle, F_SETFL, fcntl(handle, F_GETFL, 0) | O_NONBLOCK);
240
+ return handle;
241
+ }
242
+ if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) return -1;
243
+ if (!__nomen_io_wait(listener, 0)) return -1;
244
+ }
245
+ ```
246
+ }
247
+
248
+ // Returns 0 on success, or an error code (1 = DNS, 3 = connect).
249
+ func connect_to = (int handle, string host, int port, out int) {
250
+ ```
251
+ #arch: c
252
+ char portstr[16];
253
+ snprintf(portstr, sizeof(portstr), "%d", port);
254
+ struct addrinfo hints, *res = NULL;
255
+ memset(&hints, 0, sizeof(hints));
256
+ hints.ai_family = AF_INET;
257
+ hints.ai_socktype = SOCK_STREAM;
258
+ if (getaddrinfo(host.ptr, portstr, &hints, &res) != 0) return 1;
259
+ int rc = connect(handle, res->ai_addr, res->ai_addrlen);
260
+ freeaddrinfo(res);
261
+ if (rc == 0) return 0;
262
+ if (errno != EINPROGRESS) return 3;
263
+ if (!__nomen_io_wait(handle, 1)) return 3;
264
+ int so_err = 0;
265
+ socklen_t so_len = sizeof(so_err);
266
+ if (getsockopt(handle, SOL_SOCKET, SO_ERROR, &so_err, &so_len) < 0) return 3;
267
+ return so_err == 0 ? 0 : 3;
268
+ ```
269
+ ```
270
+ #arch: aarch64_use_c
271
+ char portstr[16];
272
+ snprintf(portstr, sizeof(portstr), "%d", port);
273
+ struct addrinfo hints, *res = NULL;
274
+ memset(&hints, 0, sizeof(hints));
275
+ hints.ai_family = AF_INET;
276
+ hints.ai_socktype = SOCK_STREAM;
277
+ if (getaddrinfo(host.ptr, portstr, &hints, &res) != 0) return 1;
278
+ int rc = connect(handle, res->ai_addr, res->ai_addrlen);
279
+ freeaddrinfo(res);
280
+ if (rc == 0) return 0;
281
+ if (errno != EINPROGRESS) return 3;
282
+ if (!__nomen_io_wait(handle, 1)) return 3;
283
+ int so_err = 0;
284
+ socklen_t so_len = sizeof(so_err);
285
+ if (getsockopt(handle, SOL_SOCKET, SO_ERROR, &so_err, &so_len) < 0) return 3;
286
+ return so_err == 0 ? 0 : 3;
287
+ ```
288
+ }
289
+
290
+ func send_all = (int handle, string data, out bool) {
291
+ ```
292
+ #arch: c
293
+ long off = 0;
294
+ while (off < data.len) {
295
+ ssize_t n = send(handle, data.ptr + off, (size_t)(data.len - off), 0);
296
+ if (n > 0) {
297
+ off += (long)n;
298
+ continue;
299
+ }
300
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
301
+ if (!__nomen_io_wait(handle, 1)) return false;
302
+ continue;
303
+ }
304
+ if (errno == EINTR) continue;
305
+ return false;
306
+ }
307
+ return true;
308
+ ```
309
+ ```
310
+ #arch: aarch64_use_c
311
+ long off = 0;
312
+ while (off < data.len) {
313
+ ssize_t n = send(handle, data.ptr + off, (size_t)(data.len - off), 0);
314
+ if (n > 0) {
315
+ off += (long)n;
316
+ continue;
317
+ }
318
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
319
+ if (!__nomen_io_wait(handle, 1)) return false;
320
+ continue;
321
+ }
322
+ if (errno == EINTR) continue;
323
+ return false;
324
+ }
325
+ return true;
326
+ ```
327
+ }
328
+
329
+ func recv_some = (int handle, int max, move out string) {
330
+ ```
331
+ #arch: c
332
+ long cap = max > 0 ? max : 1;
333
+ char *buf = (char *)malloc((size_t)cap);
334
+ for (;;) {
335
+ ssize_t n = recv(handle, buf, (size_t)cap, 0);
336
+ if (n > 0) return (nomen_string){ buf, (long)n };
337
+ if (n == 0) {
338
+ free(buf);
339
+ return (nomen_string){ strdup(""), 0 };
340
+ }
341
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
342
+ if (!__nomen_io_wait(handle, 0)) {
343
+ free(buf);
344
+ return (nomen_string){ strdup(""), 0 };
345
+ }
346
+ continue;
347
+ }
348
+ if (errno == EINTR) continue;
349
+ free(buf);
350
+ return (nomen_string){ strdup(""), 0 };
351
+ }
352
+ ```
353
+ ```
354
+ #arch: aarch64_use_c
355
+ long cap = max > 0 ? max : 1;
356
+ char *buf = (char *)malloc((size_t)cap);
357
+ for (;;) {
358
+ ssize_t n = recv(handle, buf, (size_t)cap, 0);
359
+ if (n > 0) return (nomen_string){ buf, (long)n };
360
+ if (n == 0) {
361
+ free(buf);
362
+ return (nomen_string){ strdup(""), 0 };
363
+ }
364
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
365
+ if (!__nomen_io_wait(handle, 0)) {
366
+ free(buf);
367
+ return (nomen_string){ strdup(""), 0 };
368
+ }
369
+ continue;
370
+ }
371
+ if (errno == EINTR) continue;
372
+ free(buf);
373
+ return (nomen_string){ strdup(""), 0 };
374
+ }
375
+ ```
376
+ }
377
+
378
+ func recv_until_close = (int handle, move out string) {
379
+ ```
380
+ #arch: c
381
+ long cap = 4096, len = 0;
382
+ char *buf = (char *)malloc((size_t)cap);
383
+ for (;;) {
384
+ if (len + 1 >= cap) {
385
+ cap *= 2;
386
+ buf = (char *)realloc(buf, (size_t)cap);
387
+ }
388
+ ssize_t n = recv(handle, buf + len, (size_t)(cap - len - 1), 0);
389
+ if (n > 0) {
390
+ len += (long)n;
391
+ continue;
392
+ }
393
+ if (n == 0) break;
394
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
395
+ if (!__nomen_io_wait(handle, 0)) break;
396
+ continue;
397
+ }
398
+ if (errno == EINTR) continue;
399
+ break;
400
+ }
401
+ buf[len] = 0;
402
+ return (nomen_string){ buf, len };
403
+ ```
404
+ ```
405
+ #arch: aarch64_use_c
406
+ long cap = 4096, len = 0;
407
+ char *buf = (char *)malloc((size_t)cap);
408
+ for (;;) {
409
+ if (len + 1 >= cap) {
410
+ cap *= 2;
411
+ buf = (char *)realloc(buf, (size_t)cap);
412
+ }
413
+ ssize_t n = recv(handle, buf + len, (size_t)(cap - len - 1), 0);
414
+ if (n > 0) {
415
+ len += (long)n;
416
+ continue;
417
+ }
418
+ if (n == 0) break;
419
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
420
+ if (!__nomen_io_wait(handle, 0)) break;
421
+ continue;
422
+ }
423
+ if (errno == EINTR) continue;
424
+ break;
425
+ }
426
+ buf[len] = 0;
427
+ return (nomen_string){ buf, len };
428
+ ```
429
+ }
430
+
431
+ func close_fd = (int handle) {
432
+ ```
433
+ #arch: c
434
+ if (handle >= 0) close(handle);
435
+ ```
436
+ ```
437
+ #arch: aarch64_use_c
438
+ if (handle >= 0) close(handle);
439
+ ```
440
+ }
441
+ }
@@ -14,9 +14,10 @@
14
14
  // See ASYNC.md for the design.
15
15
 
16
16
  /**
17
- * A handle to an asynchronously-running computation, backed by a shared thread pool
17
+ * A handle to an asynchronously-running computation, backed by a shared thread pool.
18
+ * Conforms to `Awaitable`: `.wait()` may park the current execution context.
18
19
  **/
19
- pub class Task<T> : Sendable {
20
+ pub class Task<T> : Sendable, Awaitable {
20
21
  pub var uint64 handle = 0
21
22
  pub var done = false
22
23
  pub var uint64 result_slot = 0
@@ -164,13 +165,23 @@ pub class Task<T> : Sendable {
164
165
  pub func cancel = (ref self) {
165
166
  ```
166
167
  #arch: c
167
- if (self->cancel_flag) {
168
+ // Sets the flag and wakes any fiber parked on this task's future, so
169
+ // a parked task comes back to observe the cancellation.
170
+ if (self->future) {
171
+ __nomen_future_cancel((struct nomen_future *)self->future);
172
+ } else if (self->cancel_flag) {
168
173
  *(unsigned long long *)self->cancel_flag = 1;
169
174
  }
170
175
  ```
171
176
  ```
172
177
  #arch: aarch64
173
- // if (self->cancel_flag) *(self->cancel_flag) = 1
178
+ // if (self->future) __nomen_future_cancel(self->future);
179
+ // else if (self->cancel_flag) *(self->cancel_flag) = 1
180
+ ldr x0, [x19, #40]
181
+ cbz x0, .Lcancel_flag_path
182
+ bl ___nomen_future_cancel
183
+ b .Lcancel_done
184
+ .Lcancel_flag_path:
174
185
  ldr x0, [x19, #32]
175
186
  cbz x0, .Lcancel_done
176
187
  mov x1, #1
@@ -244,6 +255,94 @@ pub class Task<T> : Sendable {
244
255
  ```
245
256
  }
246
257
 
258
+ // ---- User-defined async primitive seam (ASYNC.md, "User-defined async
259
+ // primitives") ----
260
+ // The construction sugar stores the packed-task machinery in a user
261
+ // class's plain uint64 fields (task / result_slot / cancel_flag /
262
+ // future); these statics are the library boundary its own launch
263
+ // methods drive the runtime through — the same calls the generated
264
+ // Thread/Fiber launch code makes, so a user primitive parks fibers,
265
+ // deadlocks correctly, and frees exactly like a library spawn. The
266
+ // handles are opaque: the only contract is the field NAMES the sugar
267
+ // writes and these calls read.
268
+
269
+ // Submit a packed task closure (the `task` field's value) to the shared
270
+ // worker pool. The closure must come from a construction of an
271
+ // Awaitable-conforming class — it is not callable.
272
+ pub func pool_submit = (uint64 closure) {
273
+ ```
274
+ #arch: c
275
+ __nomen_pool_submit((struct nomen_closure *)(void *)closure);
276
+ ```
277
+ ```
278
+ #arch: aarch64
279
+ // x0 = closure
280
+ bl ___nomen_pool_submit
281
+ ```
282
+ }
283
+
284
+ // Park until the task completes (in a fiber this parks the fiber and
285
+ // frees its worker; on a raw thread it blocks). Idempotent once done.
286
+ pub func future_wait = (uint64 future) {
287
+ ```
288
+ #arch: c
289
+ __nomen_future_wait((struct nomen_future *)(void *)future);
290
+ ```
291
+ ```
292
+ #arch: aarch64
293
+ // x0 = future
294
+ bl ___nomen_future_wait
295
+ ```
296
+ }
297
+
298
+ // Read the uint64 result the task stored. The launch's ref accounting
299
+ // must still hold a reference (see future_set_refs) — read BEFORE the
300
+ // final release, which frees the slot.
301
+ pub func future_result_uint64 = (uint64 future, out uint64) {
302
+ ```
303
+ #arch: c
304
+ return *(unsigned long long *)(void *)((struct nomen_future *)(void *)future)->result_slot;
305
+ ```
306
+ ```
307
+ #arch: aarch64
308
+ // x0 = future; result_slot at #128
309
+ ldr x0, [x0, #128]
310
+ ldr x0, [x0]
311
+ ```
312
+ }
313
+
314
+ // Set the future's reference count before submitting. The construction
315
+ // leaves one reference (the value's own); a launch that parks on the
316
+ // future holds the wait-side reference: set_refs(f, 2), submit, and the
317
+ // running task's completion drops one; the join drops the last (the
318
+ // future frees its slot, flag, and packed closure at zero).
319
+ pub func future_set_refs = (uint64 future, int refs) {
320
+ ```
321
+ #arch: c
322
+ ((struct nomen_future *)(void *)future)->refs = refs;
323
+ ```
324
+ ```
325
+ #arch: aarch64
326
+ // x0 = future, x1 = refs (refs is at #116)
327
+ str w1, [x0, #116]
328
+ ```
329
+ }
330
+
331
+ // Drop one reference to the future. At the last reference the future is
332
+ // freed along with the result slot, cancel flag, and packed task closure
333
+ // it owns. After a release the handle's fields are gone: zero them.
334
+ pub func future_release = (uint64 future) {
335
+ ```
336
+ #arch: c
337
+ __nomen_future_release((struct nomen_future *)(void *)future);
338
+ ```
339
+ ```
340
+ #arch: aarch64
341
+ // x0 = future
342
+ bl ___nomen_future_release
343
+ ```
344
+ }
345
+
247
346
  // Release this handle's reference to the future. The future owns the
248
347
  // result slot and cancel flag; it (and they) are freed only when the
249
348
  // last reference — this handle, the running trampoline, or a tracking
@@ -0,0 +1,83 @@
1
+ // Thread — a task running on the shared worker pool (CLOSURE.md
2
+ // Phase 3b). `Thread(fn(args))` constructs a Thread value: the arguments are
3
+ // evaluated eagerly and packed into a task closure (a heap closure
4
+ // descriptor over the pooled-args environment — the same ABI every lambda
5
+ // lowers to); `.start()` submits that closure to the worker pool and yields
6
+ // a `Task<T>` handle; `.detach()` runs it as an unjoinable daemon.
7
+ //
8
+ // Because the construction is a real value, it can be STORED and started
9
+ // later:
10
+ //
11
+ // var t = Thread(work(n))
12
+ // ...later...
13
+ // var Task<uint64> handle = t.start()
14
+ //
15
+ // The pool starts at a configurable size (default 4, via Task.set_pool_size
16
+ // before the first spawn) and grows on demand up to 64 workers when every
17
+ // worker is busy, preventing deadlocks from nested spawns. The pool drains
18
+ // and joins all workers at process exit; Task.shutdown_pool does this
19
+ // explicitly.
20
+ //
21
+ // The pool infrastructure (types, worker loop, submit, shutdown, cancel
22
+ // thread-local) lives in build_spawn_node and is emitted as file-scope C
23
+ // on the first spawn — not in Thread's own methods — so that Thread can be
24
+ // generic without duplicating the pool per instantiation.
25
+ //
26
+ // MUST-START: a Thread value that is destroyed without ever being started
27
+ // is a programming error — #destroy reports it and aborts. start/detach
28
+ // (and nursery.start) transfer ownership of the packed task out of the
29
+ // value, so a started Thread destroys as a no-op.
30
+ //
31
+ // See ASYNC.md for the design.
32
+
33
+ /**
34
+ * A deferred function call, startable on the shared worker pool
35
+ **/
36
+ pub class Thread<T> : Sendable {
37
+ // The packed task closure (a `struct nomen_closure *`); owned by this
38
+ // value until start/detach/nursery.start transfers it to the runtime.
39
+ pub var uint64 task = 0
40
+ // Result-slot / cancel-flag / future handles for the (single) launch.
41
+ pub var uint64 result_slot = 0
42
+ pub var uint64 cancel_flag = 0
43
+ pub var uint64 future = 0
44
+ // Must-start flag: set by .start() / .detach() / nursery.start.
45
+ pub var started = false
46
+
47
+ // Destroying an unstarted Thread is a programming error (the wrapped
48
+ // call would silently never run) — report it and abort. A started
49
+ // Thread owns nothing: the launch transferred the task closure, result
50
+ // slot, cancel flag, and future to the runtime, and cleared the fields.
51
+ pub func #destroy = () {
52
+ ```
53
+ #arch: c
54
+ if (!self->started) {
55
+ __nomen_spawn_must_start_abort();
56
+ }
57
+ if (self->future) {
58
+ __nomen_future_release((struct nomen_future *)self->future);
59
+ self->future = 0;
60
+ self->result_slot = 0;
61
+ self->cancel_flag = 0;
62
+ }
63
+ ```
64
+ ```
65
+ #arch: aarch64
66
+ // if (!self->started) __nomen_spawn_must_start_abort()
67
+ ldrb w0, [x19, #40]
68
+ cbnz w0, .Lthr_started
69
+ bl ___nomen_spawn_must_start_abort
70
+ .Lthr_started:
71
+ // if (self->future) { release it (an unstarted task's future is
72
+ // unshared — refs == 1 — so this tears down slot + flag + future);
73
+ // zero the fields }
74
+ ldr x0, [x19, #32]
75
+ cbz x0, .Lthr_done
76
+ bl ___nomen_future_release
77
+ str xzr, [x19, #32]
78
+ str xzr, [x19, #16]
79
+ str xzr, [x19, #24]
80
+ .Lthr_done:
81
+ ```
82
+ }
83
+ }