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.
- package/core/System/Awaitable.nm +21 -0
- package/core/System/Buffer.nm +16 -9
- package/core/System/Channel.nm +80 -29
- package/core/System/ClassBuffer.nm +9 -4
- package/core/System/Fiber.nm +121 -0
- package/core/System/Mutex.nm +57 -61
- package/core/System/Nursery.nm +10 -8
- package/core/System/Stream/Http.nm +83 -434
- package/core/System/Stream/Tcp.nm +441 -0
- package/core/System/Task.nm +103 -4
- package/core/System/Thread.nm +83 -0
- package/dist/index.mjs +19224 -13481
- package/package.json +1 -1
|
@@ -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
|
+
}
|
package/core/System/Buffer.nm
CHANGED
|
@@ -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
|
|
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
|
|
416
|
+
ldr x1, [x20]
|
|
412
417
|
b .Lbuffer_modify_T_call
|
|
413
418
|
.Lbuffer_modify_T_ld1:
|
|
414
|
-
ldrb
|
|
419
|
+
ldrb w1, [x20]
|
|
415
420
|
b .Lbuffer_modify_T_call
|
|
416
421
|
.Lbuffer_modify_T_ld2:
|
|
417
|
-
ldrh
|
|
422
|
+
ldrh w1, [x20]
|
|
418
423
|
b .Lbuffer_modify_T_call
|
|
419
424
|
.Lbuffer_modify_T_ld4:
|
|
420
|
-
ldr
|
|
425
|
+
ldr w1, [x20]
|
|
421
426
|
.Lbuffer_modify_T_call:
|
|
422
|
-
blr
|
|
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
|
-
|
|
453
|
+
ldr x0, [x22, #8]
|
|
454
|
+
mov x1, sp
|
|
449
455
|
mov x8, x20
|
|
450
|
-
blr
|
|
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
|
```
|
package/core/System/Channel.nm
CHANGED
|
@@ -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
|
|
5
|
-
// (value, len)
|
|
6
|
-
// `send_string`/`receive_string` move fat strings
|
|
7
|
-
//
|
|
8
|
-
//
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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, #
|
|
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->
|
|
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, #
|
|
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, #
|
|
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
|
-
|
|
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, #
|
|
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
|
-
|
|
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
|
-
|
|
218
|
+
add x0, x19, #40 // &self->waiters
|
|
197
219
|
ldr x1, [x19, #8] // mu
|
|
198
|
-
|
|
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, #
|
|
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
|
-
|
|
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
|
-
|
|
281
|
+
add x0, x19, #40 // &self->waiters
|
|
247
282
|
ldr x1, [x19, #8] // mu
|
|
248
|
-
|
|
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, #
|
|
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
|
|
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
|
|
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, #
|
|
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
|
|
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
|
|
175
|
-
|
|
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
|
+
}
|
package/core/System/Mutex.nm
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
}
|