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,21 @@
1
+ // Awaitable — the consumption side of the async model (ASYNC.md,
2
+ // "Awaitable trait"). Park-flavored by design: `wait` MAY park the current
3
+ // execution context (inside a fiber it parks the fiber and frees its
4
+ // worker; on a raw thread it blocks). A Rust-style poll/Waker trait is
5
+ // deliberately rejected — it would smuggle the state-machine transform back
6
+ // in through the trait definition.
7
+ //
8
+ // `Task<T>` is the conformer: `Thread(...).start()` and `Fiber(...).start()`
9
+ // both yield one, so a generic helper taking `Awaitable` waits on either.
10
+ // The trait is deliberately NOT the place for must-start semantics —
11
+ // start-optional lifecycles are legitimate for user primitives; must-start
12
+ // is a per-struct `#destroy` contract (see Thread.nm / Fiber.nm and
13
+ // ASYNC.md).
14
+
15
+ /**
16
+ * Something whose completion can be waited on (may park the current context)
17
+ **/
18
+ pub trait Awaitable {
19
+ func wait = (ref self) {
20
+ }
21
+ }
@@ -385,21 +385,26 @@ pub struct Buffer<T> {
385
385
  func modify = (ref self, int i: i >= 0 && i < self.cap, func (T, out T) f) {
386
386
  ```
387
387
  #arch: c
388
- ((T*)(unsigned long long)self->data)[i] = f(((T*)(unsigned long long)self->data)[i]);
388
+ ((T*)(unsigned long long)self->data)[i] = ((T (*)(void *, T))f->code)(f->env, ((T*)(unsigned long long)self->data)[i]);
389
389
  ```
390
390
  ```
391
391
  #arch: aarch64
392
- // x19 = self, x1 = i, x2 = f. Scalar elements round-trip through x0
392
+ // x19 = self, x1 = i, x2 = f (a closure descriptor: { code, env,
393
+ // owned } — the call loads both words and passes env first, shifting
394
+ // the value argument to x1). Scalar elements round-trip through x1
393
395
  // (width-matched); larger trivially-copyable structs pass the slot
394
396
  // address and an sret pointer (x8) — the fn writes the replacement
395
397
  // straight into the slot from a stack-stage copy of the old value.
396
398
  stp x20, x21, [sp, #-16]!
397
399
  stp x22, x23, [sp, #-16]!
400
+ stp x24, x25, [sp, #-16]!
398
401
  mov x22, x2
399
402
  ldr x9, [x19, #8]
400
403
  mov x3, #T_SIZE
401
404
  madd x20, x1, x3, xzr
402
405
  add x20, x20, x9
406
+ ldr x24, [x22]
407
+ ldr x0, [x22, #8]
403
408
  cmp x3, #8
404
409
  b.gt .Lbuffer_modify_T_big
405
410
  cmp x3, #1
@@ -408,18 +413,18 @@ pub struct Buffer<T> {
408
413
  b.eq .Lbuffer_modify_T_ld2
409
414
  cmp x3, #4
410
415
  b.eq .Lbuffer_modify_T_ld4
411
- ldr x0, [x20]
416
+ ldr x1, [x20]
412
417
  b .Lbuffer_modify_T_call
413
418
  .Lbuffer_modify_T_ld1:
414
- ldrb w0, [x20]
419
+ ldrb w1, [x20]
415
420
  b .Lbuffer_modify_T_call
416
421
  .Lbuffer_modify_T_ld2:
417
- ldrh w0, [x20]
422
+ ldrh w1, [x20]
418
423
  b .Lbuffer_modify_T_call
419
424
  .Lbuffer_modify_T_ld4:
420
- ldr w0, [x20]
425
+ ldr w1, [x20]
421
426
  .Lbuffer_modify_T_call:
422
- blr x22
427
+ blr x24
423
428
  cmp x3, #1
424
429
  b.eq .Lbuffer_modify_T_st1
425
430
  cmp x3, #2
@@ -445,11 +450,13 @@ pub struct Buffer<T> {
445
450
  mov x1, x20
446
451
  mov x2, x3
447
452
  bl _memcpy
448
- mov x0, sp
453
+ ldr x0, [x22, #8]
454
+ mov x1, sp
449
455
  mov x8, x20
450
- blr x22
456
+ blr x24
451
457
  add sp, sp, x23
452
458
  .Lbuffer_modify_T_end:
459
+ ldp x24, x25, [sp], #16
453
460
  ldp x22, x23, [sp], #16
454
461
  ldp x20, x21, [sp], #16
455
462
  ```
@@ -1,11 +1,13 @@
1
1
  // Channel — thread-safe single-direction queue for passing values between
2
2
  // tasks. Unbounded, FIFO, blocking receive.
3
3
  //
4
- // The queue node's payload is two words wide: every message carries a
5
- // (value, len) pair. `send`/`receive` move uint64 values (len stays 0);
6
- // `send_string`/`receive_string` move fat strings without truncating the len
7
- // half. A typed Channel<T> wrapper can come later — the storage story is
8
- // identical, only the call-site types change.
4
+ // The queue node's payload is three words wide: every message carries a
5
+ // (value, len, kind) triple. `send`/`receive` move uint64 values (len and
6
+ // kind stay 0); `send_string`/`receive_string` move fat strings (kind = 1)
7
+ // without truncating the len half. `#destroy` frees only string payloads —
8
+ // a raw uint64 payload is a value, not a pointer. A typed Channel<T>
9
+ // wrapper can come later — the storage story is identical, only the
10
+ // call-site types change.
9
11
  //
10
12
  // String payloads are copied in and moved out: `send_string` duplicates the
11
13
  // value into the node (the sender keeps its own string), and
@@ -25,17 +27,20 @@ pub class Channel: Sendable {
25
27
  pub var uint64 not_empty_cv = 0
26
28
  pub var uint64 head = 0
27
29
  pub var uint64 tail = 0
30
+ // Head of the parked-fiber wait list (guarded by mu). Send wakes it.
31
+ pub var uint64 waiters = 0
28
32
 
29
33
  pub func #init = (self) {
30
34
  ```
31
35
  #arch: c
32
36
  #scope: file
33
37
  #include <pthread.h>
34
- struct nomen_channel_node {
35
- unsigned long long value;
36
- unsigned long long len;
37
- struct nomen_channel_node *next;
38
- };
38
+ struct nomen_channel_node {
39
+ unsigned long long value;
40
+ unsigned long long len;
41
+ unsigned long long kind; // 0 = uint64 payload, 1 = owned string
42
+ struct nomen_channel_node *next;
43
+ };
39
44
  ```
40
45
  ```
41
46
  #arch: c
@@ -45,6 +50,7 @@ pub class Channel: Sendable {
45
50
  pthread_cond_init((pthread_cond_t *)self->not_empty_cv, NULL);
46
51
  self->head = 0;
47
52
  self->tail = 0;
53
+ self->waiters = 0;
48
54
  ```
49
55
  ```
50
56
  #arch: aarch64
@@ -60,6 +66,7 @@ pub class Channel: Sendable {
60
66
  bl _pthread_cond_init
61
67
  str xzr, [x19, #24]
62
68
  str xzr, [x19, #32]
69
+ str xzr, [x19, #40]
63
70
  ```
64
71
  }
65
72
 
@@ -69,6 +76,7 @@ pub class Channel: Sendable {
69
76
  struct nomen_channel_node *n = (struct nomen_channel_node *)malloc(sizeof(struct nomen_channel_node));
70
77
  n->value = value;
71
78
  n->len = 0;
79
+ n->kind = 0;
72
80
  n->next = NULL;
73
81
  pthread_mutex_lock((pthread_mutex_t *)self->mu);
74
82
  if (self->tail) {
@@ -78,25 +86,27 @@ pub class Channel: Sendable {
78
86
  }
79
87
  self->tail = (unsigned long long)n;
80
88
  pthread_cond_signal((pthread_cond_t *)self->not_empty_cv);
89
+ __nomen_fiber_waitq_wake((struct nomen_fiber **)&self->waiters);
81
90
  pthread_mutex_unlock((pthread_mutex_t *)self->mu);
82
91
  ```
83
92
  ```
84
93
  #arch: aarch64
85
94
  // x19 = self (set by prologue), x1 = value
86
95
  str x1, [sp, #-16]! // save value on stack
87
- mov x0, #24 // node = { value, len, next }
96
+ mov x0, #32 // node = { value, len, kind, next }
88
97
  bl _malloc // x0 = node
89
98
  ldr x1, [sp], #16 // restore value
90
99
  str x1, [x0, #0] // node->value = value
91
100
  str xzr, [x0, #8] // node->len = 0
92
- str xzr, [x0, #16] // node->next = NULL
101
+ str xzr, [x0, #16] // node->kind = 0 (uint64 payload)
102
+ str xzr, [x0, #24] // node->next = NULL
93
103
  str x0, [sp, #-16]! // save node on stack
94
104
  ldr x0, [x19, #8] // self->mu
95
105
  bl _pthread_mutex_lock
96
106
  ldr x2, [sp], #16 // restore node
97
107
  ldr x1, [x19, #32] // tail
98
108
  cbz x1, .Lchannel_send_empty
99
- str x2, [x1, #16] // tail->next = node
109
+ str x2, [x1, #24] // tail->next = node
100
110
  b .Lchannel_send_enqueued
101
111
  .Lchannel_send_empty:
102
112
  str x2, [x19, #24] // head = node
@@ -104,6 +114,8 @@ pub class Channel: Sendable {
104
114
  str x2, [x19, #32] // tail = node
105
115
  ldr x0, [x19, #16] // not_empty_cv
106
116
  bl _pthread_cond_signal
117
+ add x0, x19, #40 // &self->waiters
118
+ bl ___nomen_fiber_waitq_wake
107
119
  ldr x0, [x19, #8] // self->mu
108
120
  bl _pthread_mutex_unlock
109
121
  ```
@@ -120,6 +132,7 @@ pub class Channel: Sendable {
120
132
  nomen_string _v = value.ptr ? nomen_str_dup(value) : (nomen_string){0, 0};
121
133
  n->value = (unsigned long long)_v.ptr;
122
134
  n->len = (unsigned long long)_v.len;
135
+ n->kind = 1;
123
136
  n->next = NULL;
124
137
  pthread_mutex_lock((pthread_mutex_t *)self->mu);
125
138
  if (self->tail) {
@@ -129,13 +142,14 @@ pub class Channel: Sendable {
129
142
  }
130
143
  self->tail = (unsigned long long)n;
131
144
  pthread_cond_signal((pthread_cond_t *)self->not_empty_cv);
145
+ __nomen_fiber_waitq_wake((struct nomen_fiber **)&self->waiters);
132
146
  pthread_mutex_unlock((pthread_mutex_t *)self->mu);
133
147
  ```
134
148
  ```
135
149
  #arch: aarch64
136
150
  // x19 = self (set by prologue), x1 = value.ptr, x2 = value.len
137
151
  stp x1, x2, [sp, #-32]! // [sp]=ptr [sp+8]=len [sp+16]=node scratch
138
- mov x0, #24 // node = { value, len, next }
152
+ mov x0, #32 // node = { value, len, kind, next }
139
153
  bl _malloc // x0 = node
140
154
  str x0, [sp, #16] // save node across the strdup call
141
155
  ldr x1, [sp, #0] // value.ptr
@@ -150,13 +164,15 @@ pub class Channel: Sendable {
150
164
  str x0, [x2, #0] // node->value = copied ptr
151
165
  ldr x1, [sp, #8] // value.len
152
166
  str x1, [x2, #8] // node->len = len
153
- str xzr, [x2, #16] // node->next = NULL
167
+ mov x1, #1
168
+ str x1, [x2, #16] // node->kind = 1 (owned string payload)
169
+ str xzr, [x2, #24] // node->next = NULL
154
170
  ldr x0, [x19, #8] // self->mu
155
171
  bl _pthread_mutex_lock
156
172
  ldr x2, [sp, #16] // restore node
157
173
  ldr x1, [x19, #32] // tail
158
174
  cbz x1, .Lchannel_send_str_empty
159
- str x2, [x1, #16] // tail->next = node
175
+ str x2, [x1, #24] // tail->next = node
160
176
  b .Lchannel_send_str_enqueued
161
177
  .Lchannel_send_str_empty:
162
178
  str x2, [x19, #24] // head = node
@@ -175,7 +191,13 @@ pub class Channel: Sendable {
175
191
  #arch: c
176
192
  pthread_mutex_lock((pthread_mutex_t *)self->mu);
177
193
  while (!self->head) {
178
- pthread_cond_wait((pthread_cond_t *)self->not_empty_cv, (pthread_mutex_t *)self->mu);
194
+ // Fibers park instead of blocking; a cancelled fiber returns the
195
+ // zero value rather than waiting forever (cooperative cancel).
196
+ if (!__nomen_fiber_waitq_park(
197
+ (struct nomen_fiber **)&self->waiters, (void *)self->mu, (void *)self->not_empty_cv)) {
198
+ pthread_mutex_unlock((pthread_mutex_t *)self->mu);
199
+ return 0;
200
+ }
179
201
  }
180
202
  struct nomen_channel_node *n = (struct nomen_channel_node *)self->head;
181
203
  self->head = (unsigned long long)n->next;
@@ -193,13 +215,21 @@ pub class Channel: Sendable {
193
215
  .Lchannel_receive_wait:
194
216
  ldr x1, [x19, #24] // head
195
217
  cbnz x1, .Lchannel_receive_got
196
- ldr x0, [x19, #16] // not_empty_cv
218
+ add x0, x19, #40 // &self->waiters
197
219
  ldr x1, [x19, #8] // mu
198
- bl _pthread_cond_wait
220
+ ldr x2, [x19, #16] // not_empty_cv
221
+ bl ___nomen_fiber_waitq_park
222
+ cbz x0, .Lchannel_receive_cancelled
199
223
  b .Lchannel_receive_wait
224
+ .Lchannel_receive_cancelled:
225
+ // Cancelled while waiting: return the zero value.
226
+ ldr x0, [x19, #8] // self->mu
227
+ bl _pthread_mutex_unlock
228
+ mov x0, #0
229
+ b .Lchannel_receive_end
200
230
  .Lchannel_receive_got:
201
231
  // x1 = node
202
- ldr x2, [x1, #16] // node->next
232
+ ldr x2, [x1, #24] // node->next
203
233
  str x2, [x19, #24] // head = node->next
204
234
  cbnz x2, .Lchannel_receive_not_empty
205
235
  str xzr, [x19, #32] // tail = 0
@@ -211,6 +241,7 @@ pub class Channel: Sendable {
211
241
  ldr x0, [x19, #8] // self->mu
212
242
  bl _pthread_mutex_unlock
213
243
  ldr x0, [sp], #16 // pop value → x0 (return)
244
+ .Lchannel_receive_end:
214
245
  ```
215
246
  }
216
247
 
@@ -223,7 +254,11 @@ pub class Channel: Sendable {
223
254
  #arch: c
224
255
  pthread_mutex_lock((pthread_mutex_t *)self->mu);
225
256
  while (!self->head) {
226
- pthread_cond_wait((pthread_cond_t *)self->not_empty_cv, (pthread_mutex_t *)self->mu);
257
+ if (!__nomen_fiber_waitq_park(
258
+ (struct nomen_fiber **)&self->waiters, (void *)self->mu, (void *)self->not_empty_cv)) {
259
+ pthread_mutex_unlock((pthread_mutex_t *)self->mu);
260
+ return (nomen_string){ 0, 0 };
261
+ }
227
262
  }
228
263
  struct nomen_channel_node *n = (struct nomen_channel_node *)self->head;
229
264
  self->head = (unsigned long long)n->next;
@@ -243,13 +278,23 @@ pub class Channel: Sendable {
243
278
  .Lchannel_receive_str_wait:
244
279
  ldr x1, [x19, #24] // head
245
280
  cbnz x1, .Lchannel_receive_str_got
246
- ldr x0, [x19, #16] // not_empty_cv
281
+ add x0, x19, #40 // &self->waiters
247
282
  ldr x1, [x19, #8] // mu
248
- bl _pthread_cond_wait
283
+ ldr x2, [x19, #16] // not_empty_cv
284
+ bl ___nomen_fiber_waitq_park
285
+ cbz x0, .Lchannel_receive_str_cancelled
249
286
  b .Lchannel_receive_str_wait
287
+ .Lchannel_receive_str_cancelled:
288
+ // Cancelled while waiting: return the empty fat string (x0 = ptr,
289
+ // x1 = len).
290
+ ldr x0, [x19, #8] // self->mu
291
+ bl _pthread_mutex_unlock
292
+ mov x0, #0
293
+ mov x1, #0
294
+ b .Lchannel_receive_str_end
250
295
  .Lchannel_receive_str_got:
251
296
  // x1 = node
252
- ldr x2, [x1, #16] // node->next
297
+ ldr x2, [x1, #24] // node->next
253
298
  str x2, [x19, #24] // head = node->next
254
299
  cbnz x2, .Lchannel_receive_str_not_empty
255
300
  str xzr, [x19, #32] // tail = 0
@@ -264,6 +309,7 @@ pub class Channel: Sendable {
264
309
  ldr x0, [x19, #8] // self->mu
265
310
  bl _pthread_mutex_unlock
266
311
  ldp x0, x1, [sp], #16 // pop pair → x0/x1 (return)
312
+ .Lchannel_receive_str_end:
267
313
  ```
268
314
  }
269
315
 
@@ -271,12 +317,13 @@ pub class Channel: Sendable {
271
317
  ```
272
318
  #arch: c
273
319
  if (self->mu) {
274
- // Drain any pending nodes. A string payload that was never
275
- // received still owns its buffer — free it with the node.
320
+ // Drain any pending nodes. A string payload (kind = 1) that was
321
+ // never received still owns its buffer — free it with the node.
322
+ // A uint64 payload (kind = 0) is a raw value, never a pointer.
276
323
  struct nomen_channel_node *n = (struct nomen_channel_node *)self->head;
277
324
  while (n) {
278
325
  struct nomen_channel_node *next = n->next;
279
- if (n->value) free((void *)n->value);
326
+ if (n->kind == 1 && n->value) free((void *)n->value);
280
327
  free(n);
281
328
  n = next;
282
329
  }
@@ -295,14 +342,18 @@ pub class Channel: Sendable {
295
342
  // x19 = self (set by prologue)
296
343
  ldr x0, [x19, #8] // self->mu
297
344
  cbz x0, .Lchannel_destroy_done
298
- // Drain nodes (freeing any unconsumed string payload)
345
+ // Drain nodes (freeing only owned-string payloads; a uint64
346
+ // payload is a raw value, never a pointer)
299
347
  ldr x1, [x19, #24] // head
300
348
  .Lchannel_destroy_drain:
301
349
  cbz x1, .Lchannel_destroy_drained
302
- ldr x2, [x1, #16] // next (before calls clobber x1)
350
+ ldr x2, [x1, #24] // next (before calls clobber x1)
303
351
  stp x1, x2, [sp, #-16]! // save node + next
352
+ ldr x3, [x1, #16] // payload kind (1 = owned string)
304
353
  ldr x0, [x1, #0] // payload ptr
305
354
  cbz x0, .Lchannel_destroy_no_payload
355
+ cmp x3, #1
356
+ b.ne .Lchannel_destroy_no_payload
306
357
  bl _free
307
358
  .Lchannel_destroy_no_payload:
308
359
  ldr x0, [sp, #0] // node
@@ -155,7 +155,7 @@ pub struct ClassBuffer<T> {
155
155
  #arch: c
156
156
  long *slots = (long*)(unsigned long long)self->data;
157
157
  void *old = (void*)slots[i];
158
- void *new_value = (void*)f(old);
158
+ void *new_value = (void*)((void *(*)(void *, void *))f->code)(f->env, old);
159
159
  slots[i] = (long)new_value;
160
160
  if (old != new_value) {
161
161
  T_destroy(old);
@@ -164,15 +164,19 @@ pub struct ClassBuffer<T> {
164
164
  ```
165
165
  ```
166
166
  #arch: aarch64
167
- // x19 = self, x1 = i, x2 = f. Class slots are one pointer wide.
167
+ // x19 = self, x1 = i, x2 = f (a closure descriptor — env goes in x0,
168
+ // the old slot value shifts to x1). Class slots are one pointer wide.
168
169
  stp x20, x21, [sp, #-16]!
169
170
  stp x22, x23, [sp, #-16]!
171
+ stp x24, x25, [sp, #-16]!
170
172
  mov x22, x2
171
173
  ldr x9, [x19, #8]
172
174
  lsl x3, x1, #3
173
175
  add x20, x9, x3
174
- ldr x0, [x20]
175
- blr x22
176
+ ldr x24, [x22]
177
+ ldr x0, [x22, #8]
178
+ ldr x1, [x20]
179
+ blr x24
176
180
  mov x23, x0
177
181
  ldr x9, [x20]
178
182
  str x23, [x20]
@@ -183,6 +187,7 @@ pub struct ClassBuffer<T> {
183
187
  mov x0, x9
184
188
  bl _free
185
189
  .Lcb_modify_T_done:
190
+ ldp x24, x25, [sp], #16
186
191
  ldp x22, x23, [sp], #16
187
192
  ldp x20, x21, [sp], #16
188
193
  ```
@@ -0,0 +1,121 @@
1
+ // Fiber — a stackful coroutine (ASYNC.md Phase 1). Fibers multiplex
2
+ // over the worker pool's threads: a parked fiber frees its worker instead of
3
+ // blocking it, so thousands of waiting tasks cost stacks (~64 KB each), not
4
+ // threads (~MB each).
5
+ //
6
+ // `Fiber(fn(args)).start()` mirrors `Thread(fn(args)).start()` — same eager
7
+ // arg packing into a task closure, same future, same Task<T> handle — but
8
+ // the wrapped call runs on a fiber stack under the fiber scheduler. While
9
+ // it waits (Task.result/wait, Channel.receive), the fiber parks and the
10
+ // worker moves on; `Fiber.yield()` cooperatively hands the worker to the
11
+ // next runnable fiber.
12
+ //
13
+ // Like Thread, a Fiber construction is a real value: it can be stored and
14
+ // started later (`var f = Fiber(work(n))`, then `f.start()` or
15
+ // `f.start_on(buf)`), and destroying an unstarted Fiber is a programming
16
+ // error that #destroy reports and aborts on (must-start — the library
17
+ // pattern from ASYNC.md).
18
+ //
19
+ // `Fiber(fn(args)).start_on(buf)` runs the fiber on a caller-provided
20
+ // fixed-size array stack (>= 16 KB) — the embedded/no-heap path.
21
+ //
22
+ // `Fiber.set_cooperative(true)` (before the first spawn) runs fibers on the
23
+ // calling thread and never starts worker threads — a cooperative,
24
+ // single-threaded runtime for bare-metal targets.
25
+ //
26
+ // The constructor, `.start`, and `.start_on` are compiler-special-cased
27
+ // (they need the per-site spawn/trampoline machinery), so they are NOT
28
+ // regular methods declared here. The runtime (scheduler, context switch)
29
+ // lives in the build's emitted companion C — see build_fiber_spawn.
30
+
31
+ /**
32
+ * A stackful coroutine: lightweight, cooperatively scheduled, parks instead of blocking
33
+ **/
34
+ pub class Fiber<T> : Sendable {
35
+ // The packed task closure (a `struct nomen_closure *`); owned by this
36
+ // value until start/start_on/nursery.start transfers it to the runtime.
37
+ pub var uint64 task = 0
38
+ // Result-slot / cancel-flag / future handles for the (single) launch.
39
+ pub var uint64 result_slot = 0
40
+ pub var uint64 cancel_flag = 0
41
+ pub var uint64 future = 0
42
+ // Must-start flag: set by .start() / .start_on(buf) / nursery.start.
43
+ pub var started = false
44
+
45
+ // Destroying an unstarted Fiber is a programming error (the wrapped
46
+ // call would silently never run) — report it and abort. A started
47
+ // Fiber owns nothing: the launch transferred the task closure, result
48
+ // slot, cancel flag, and future to the runtime, and cleared the fields.
49
+ pub func #destroy = () {
50
+ ```
51
+ #arch: c
52
+ if (!self->started) {
53
+ __nomen_spawn_must_start_abort();
54
+ }
55
+ if (self->future) {
56
+ __nomen_future_release((struct nomen_future *)self->future);
57
+ self->future = 0;
58
+ self->result_slot = 0;
59
+ self->cancel_flag = 0;
60
+ }
61
+ ```
62
+ ```
63
+ #arch: aarch64
64
+ // if (!self->started) __nomen_spawn_must_start_abort()
65
+ ldrb w0, [x19, #40]
66
+ cbnz w0, .Lfb_started
67
+ bl ___nomen_spawn_must_start_abort
68
+ .Lfb_started:
69
+ // if (self->future) { release it (an unstarted task's future is
70
+ // unshared — refs == 1 — so this tears down slot + flag + future);
71
+ // zero the fields }
72
+ ldr x0, [x19, #32]
73
+ cbz x0, .Lfb_done
74
+ bl ___nomen_future_release
75
+ str xzr, [x19, #32]
76
+ str xzr, [x19, #16]
77
+ str xzr, [x19, #24]
78
+ .Lfb_done:
79
+ ```
80
+ }
81
+
82
+ // Hand the worker to the next runnable fiber. No-op outside a fiber.
83
+ pub func yield = () {
84
+ ```
85
+ #arch: c
86
+ __nomen_fiber_yield();
87
+ ```
88
+ ```
89
+ #arch: aarch64
90
+ bl ___nomen_fiber_yield
91
+ ```
92
+ }
93
+
94
+ // Is the calling code running inside a fiber? True when Fiber.yield or
95
+ // fiber parking would take effect.
96
+ pub func is_fiber = (out bool) {
97
+ ```
98
+ #arch: c
99
+ return __nomen_fiber_is_active();
100
+ ```
101
+ ```
102
+ #arch: aarch64
103
+ bl ___nomen_fiber_is_active
104
+ ```
105
+ }
106
+
107
+ // Run fibers on the calling thread and never start worker threads. Must
108
+ // be called before the first fiber spawn; intended for single-threaded
109
+ // (bare-metal) targets. In cooperative mode a fiber that parks on an
110
+ // event only another thread could fire will never resume.
111
+ pub func set_cooperative = (bool on) {
112
+ ```
113
+ #arch: c
114
+ __nomen_fiber_set_cooperative(on ? 1 : 0);
115
+ ```
116
+ ```
117
+ #arch: aarch64
118
+ bl ___nomen_fiber_set_cooperative
119
+ ```
120
+ }
121
+ }
@@ -3,6 +3,12 @@
3
3
  // Default stance in Nomen is "no shared mutable state" — communicate by moving
4
4
  // Sendable values (or via Channel<T>). Mutex exists for when you genuinely
5
5
  // need shared mutability. See ASYNC.md.
6
+ //
7
+ // A fiber that would block on a contended lock parks instead of blocking its
8
+ // worker (both models), and unlock wakes the parked fibers. Cancellation
9
+ // while waiting never returns without the lock — a waiter keeps waiting and
10
+ // observes the cancel flag at its next checkpoint after acquiring — so a
11
+ // matching unlock is always sound.
6
12
 
7
13
  /**
8
14
  * A pthread-backed mutual-exclusion lock for protecting shared mutable state
@@ -10,69 +16,59 @@
10
16
  pub class Mutex: Sendable {
11
17
  pub var uint64 handle = 0
12
18
 
13
- pub func #init = (self) {
14
- ```
15
- #arch: c
16
- #scope: file
17
- #include <pthread.h>
18
- ```
19
- ```
20
- #arch: c
21
- self->handle = (unsigned long long)malloc(sizeof(pthread_mutex_t));
22
- pthread_mutex_init((pthread_mutex_t *)self->handle, NULL);
23
- ```
24
- ```
25
- #arch: aarch64
26
- mov x0, #64
27
- bl _malloc
28
- str x0, [x19, #8]
29
- mov x1, #0
30
- bl _pthread_mutex_init
31
- ```
32
- }
19
+ pub func #init = (self) {
20
+ ```
21
+ #arch: c
22
+ self->handle = (unsigned long long)__nomen_mutex_create();
23
+ ```
24
+ ```
25
+ #arch: aarch64
26
+ bl ___nomen_mutex_create
27
+ str x0, [x19, #8]
28
+ ```
29
+ }
33
30
 
34
- pub func lock = (self) {
35
- ```
36
- #arch: c
37
- pthread_mutex_lock((pthread_mutex_t *)self->handle);
38
- ```
39
- ```
40
- #arch: aarch64
41
- ldr x0, [x19, #8]
42
- bl _pthread_mutex_lock
43
- ```
44
- }
31
+ pub func lock = (self) {
32
+ ```
33
+ #arch: c
34
+ // Fibers park instead of blocking the worker (both models); other
35
+ // contexts take the pthread lock directly.
36
+ __nomen_mutex_lock((void *)self->handle);
37
+ ```
38
+ ```
39
+ #arch: aarch64
40
+ ldr x0, [x19, #8]
41
+ bl ___nomen_mutex_lock
42
+ ```
43
+ }
45
44
 
46
- pub func unlock = (self) {
47
- ```
48
- #arch: c
49
- pthread_mutex_unlock((pthread_mutex_t *)self->handle);
50
- ```
51
- ```
52
- #arch: aarch64
53
- ldr x0, [x19, #8]
54
- bl _pthread_mutex_unlock
55
- ```
56
- }
45
+ pub func unlock = (self) {
46
+ ```
47
+ #arch: c
48
+ __nomen_mutex_unlock_wake((void *)self->handle);
49
+ ```
50
+ ```
51
+ #arch: aarch64
52
+ ldr x0, [x19, #8]
53
+ bl ___nomen_mutex_unlock_wake
54
+ ```
55
+ }
57
56
 
58
- pub func #destroy = () {
59
- ```
60
- #arch: c
61
- if (self->handle) {
62
- pthread_mutex_destroy((pthread_mutex_t *)self->handle);
63
- free((void *)self->handle);
64
- self->handle = 0;
65
- }
66
- ```
67
- ```
68
- #arch: aarch64
69
- ldr x0, [x19, #8]
70
- cbz x0, .Lmutex_destroy_end
71
- bl _pthread_mutex_destroy
72
- ldr x0, [x19, #8]
73
- bl _free
74
- str xzr, [x19, #8]
75
- .Lmutex_destroy_end:
76
- ```
57
+ pub func #destroy = () {
58
+ ```
59
+ #arch: c
60
+ if (self->handle) {
61
+ __nomen_mutex_dispose((void *)self->handle);
62
+ self->handle = 0;
77
63
  }
64
+ ```
65
+ ```
66
+ #arch: aarch64
67
+ ldr x0, [x19, #8]
68
+ cbz x0, .Lmutex_destroy_end
69
+ bl ___nomen_mutex_dispose
70
+ str xzr, [x19, #8]
71
+ .Lmutex_destroy_end:
72
+ ```
73
+ }
78
74
  }