nomen-lang 0.4.0 → 0.6.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.
@@ -36,17 +36,17 @@ pub struct Arena<T> {
36
36
  // Slot payloads. T = value struct/primitive routes to Buffer<T>; T =
37
37
  // class/trait routes to ClassBuffer<T> (monomorphize rewrites the field),
38
38
  // so slot ownership follows the usual element rules.
39
- var Buffer<T> values = Buffer<T>()
39
+ var values = Buffer<T>()
40
40
  // Per-slot generation, bumped on every free. Slots are calloc'd, so a
41
41
  // fresh slot's generation is 0.
42
- var Buffer<int> generations = Buffer<int>()
42
+ var generations = Buffer<int>()
43
43
  // Freed slot indices available for reuse (LIFO free list). A raw Buffer
44
44
  // plus an explicit count: a `List<int>` field would need its own
45
45
  // monomorphization from inside a generic struct body.
46
- var Buffer<int> free_indices = Buffer<int>()
47
- var int free_count = 0
46
+ var free_indices = Buffer<int>()
47
+ var free_count = 0
48
48
  // High-water mark: every index < count has a slot (live or free).
49
- var int count = 0
49
+ var count = 0
50
50
 
51
51
  /**
52
52
  * A fresh arena with no slots.
@@ -78,7 +78,7 @@ pub struct Arena<T> {
78
78
  }
79
79
  self.values.grow(index + 1)
80
80
  self.values.store(index, value)
81
- var ArenaRef<T> handle = ArenaRef<T>()
81
+ var handle = ArenaRef<T>()
82
82
  handle.index = index
83
83
  handle.generation = self.generations.load_int(index)
84
84
  return handle
@@ -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
+ }
@@ -37,7 +37,7 @@ pub struct Buffer<T> {
37
37
  if new_cap == 0 {
38
38
  new_cap = 8
39
39
  }
40
- while new_cap < needed; new_cap *= 2 {}
40
+ while new_cap < needed; new_cap *= 2 { }
41
41
  self.data = realloc(self.data, new_cap * 4)
42
42
  memset(self.data + self.cap * 4, 0, (new_cap - self.cap) * 4)
43
43
  self.cap = new_cap
@@ -107,7 +107,7 @@ pub struct Buffer<T> {
107
107
  if new_cap == 0 {
108
108
  new_cap = 4
109
109
  }
110
- while new_cap < needed; new_cap *= 2 {}
110
+ while new_cap < needed; new_cap *= 2 { }
111
111
  self.data = realloc(self.data, new_cap * 8)
112
112
  memset(self.data + self.cap * 8, 0, (new_cap - self.cap) * 8)
113
113
  self.cap = new_cap
@@ -204,7 +204,7 @@ pub struct Buffer<T> {
204
204
  if new_cap == 0 {
205
205
  new_cap = 4
206
206
  }
207
- while new_cap < needed; new_cap *= 2 {}
207
+ while new_cap < needed; new_cap *= 2 { }
208
208
  self.data = realloc(self.data, new_cap * T_SIZE)
209
209
  memset(self.data + self.cap * T_SIZE, 0, (new_cap - self.cap) * T_SIZE)
210
210
  self.cap = new_cap
@@ -382,24 +382,29 @@ pub struct Buffer<T> {
382
382
  // this raw form is correct for trivially-copyable elements only. NOT
383
383
  // `inline`: the body calls through the fn pointer, so splicing the raw
384
384
  // block into a caller would bypass the per-element specializations.
385
- func modify = (ref self, int i: i >= 0 && i < self.cap, func (T, out T) f) {
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
  ```
@@ -513,7 +520,11 @@ pub struct Buffer<T> {
513
520
  // version that also reclaims dst's previous value first (see
514
521
  // owning_buffer_specialize.ts); this raw form is correct for
515
522
  // trivially-destructible elements only.
516
- inline func shift = (ref self, int dst: dst >= 0 && dst < self.cap, int src: src >= 0 && src < self.cap) {
523
+ inline func shift = (
524
+ ref self,
525
+ int dst: dst >= 0 && dst < self.cap,
526
+ int src: src >= 0 && src < self.cap
527
+ ) {
517
528
  ```
518
529
  #arch: c
519
530
  T* _slots = (T*)(unsigned long long)self->data;
@@ -580,7 +591,12 @@ pub struct Buffer<T> {
580
591
  // self's scope and is invalidated if self is reassigned. This is the single
581
592
  // primitive every other slice (Array<T>, List<T>, user containers) builds
582
593
  // on — higher layers just `return self.items.slice(start, end)`.
583
- func slice = (self, int start: start >= 0, int end: end >= start && end <= self.cap, out view T) {
594
+ func slice = (
595
+ self,
596
+ int start: start >= 0,
597
+ int end: end >= start && end <= self.cap,
598
+ out view T
599
+ ) {
584
600
  ```
585
601
  #arch: c
586
602
  nomen_view _r;
@@ -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
@@ -164,6 +180,8 @@ pub class Channel: Sendable {
164
180
  str x2, [x19, #32] // tail = node
165
181
  ldr x0, [x19, #16] // not_empty_cv
166
182
  bl _pthread_cond_signal
183
+ add x0, x19, #40 // &self->waiters — wake parked fibers
184
+ bl ___nomen_fiber_waitq_wake
167
185
  ldr x0, [x19, #8] // self->mu
168
186
  bl _pthread_mutex_unlock
169
187
  add sp, sp, #32
@@ -175,7 +193,13 @@ pub class Channel: Sendable {
175
193
  #arch: c
176
194
  pthread_mutex_lock((pthread_mutex_t *)self->mu);
177
195
  while (!self->head) {
178
- pthread_cond_wait((pthread_cond_t *)self->not_empty_cv, (pthread_mutex_t *)self->mu);
196
+ // Fibers park instead of blocking; a cancelled fiber returns the
197
+ // zero value rather than waiting forever (cooperative cancel).
198
+ if (!__nomen_fiber_waitq_park(
199
+ (struct nomen_fiber **)&self->waiters, (void *)self->mu, (void *)self->not_empty_cv)) {
200
+ pthread_mutex_unlock((pthread_mutex_t *)self->mu);
201
+ return 0;
202
+ }
179
203
  }
180
204
  struct nomen_channel_node *n = (struct nomen_channel_node *)self->head;
181
205
  self->head = (unsigned long long)n->next;
@@ -193,13 +217,21 @@ pub class Channel: Sendable {
193
217
  .Lchannel_receive_wait:
194
218
  ldr x1, [x19, #24] // head
195
219
  cbnz x1, .Lchannel_receive_got
196
- ldr x0, [x19, #16] // not_empty_cv
220
+ add x0, x19, #40 // &self->waiters
197
221
  ldr x1, [x19, #8] // mu
198
- bl _pthread_cond_wait
222
+ ldr x2, [x19, #16] // not_empty_cv
223
+ bl ___nomen_fiber_waitq_park
224
+ cbz x0, .Lchannel_receive_cancelled
199
225
  b .Lchannel_receive_wait
226
+ .Lchannel_receive_cancelled:
227
+ // Cancelled while waiting: return the zero value.
228
+ ldr x0, [x19, #8] // self->mu
229
+ bl _pthread_mutex_unlock
230
+ mov x0, #0
231
+ b .Lchannel_receive_end
200
232
  .Lchannel_receive_got:
201
233
  // x1 = node
202
- ldr x2, [x1, #16] // node->next
234
+ ldr x2, [x1, #24] // node->next
203
235
  str x2, [x19, #24] // head = node->next
204
236
  cbnz x2, .Lchannel_receive_not_empty
205
237
  str xzr, [x19, #32] // tail = 0
@@ -211,6 +243,7 @@ pub class Channel: Sendable {
211
243
  ldr x0, [x19, #8] // self->mu
212
244
  bl _pthread_mutex_unlock
213
245
  ldr x0, [sp], #16 // pop value → x0 (return)
246
+ .Lchannel_receive_end:
214
247
  ```
215
248
  }
216
249
 
@@ -223,7 +256,11 @@ pub class Channel: Sendable {
223
256
  #arch: c
224
257
  pthread_mutex_lock((pthread_mutex_t *)self->mu);
225
258
  while (!self->head) {
226
- pthread_cond_wait((pthread_cond_t *)self->not_empty_cv, (pthread_mutex_t *)self->mu);
259
+ if (!__nomen_fiber_waitq_park(
260
+ (struct nomen_fiber **)&self->waiters, (void *)self->mu, (void *)self->not_empty_cv)) {
261
+ pthread_mutex_unlock((pthread_mutex_t *)self->mu);
262
+ return (nomen_string){ 0, 0 };
263
+ }
227
264
  }
228
265
  struct nomen_channel_node *n = (struct nomen_channel_node *)self->head;
229
266
  self->head = (unsigned long long)n->next;
@@ -243,13 +280,23 @@ pub class Channel: Sendable {
243
280
  .Lchannel_receive_str_wait:
244
281
  ldr x1, [x19, #24] // head
245
282
  cbnz x1, .Lchannel_receive_str_got
246
- ldr x0, [x19, #16] // not_empty_cv
283
+ add x0, x19, #40 // &self->waiters
247
284
  ldr x1, [x19, #8] // mu
248
- bl _pthread_cond_wait
285
+ ldr x2, [x19, #16] // not_empty_cv
286
+ bl ___nomen_fiber_waitq_park
287
+ cbz x0, .Lchannel_receive_str_cancelled
249
288
  b .Lchannel_receive_str_wait
289
+ .Lchannel_receive_str_cancelled:
290
+ // Cancelled while waiting: return the empty fat string (x0 = ptr,
291
+ // x1 = len).
292
+ ldr x0, [x19, #8] // self->mu
293
+ bl _pthread_mutex_unlock
294
+ mov x0, #0
295
+ mov x1, #0
296
+ b .Lchannel_receive_str_end
250
297
  .Lchannel_receive_str_got:
251
298
  // x1 = node
252
- ldr x2, [x1, #16] // node->next
299
+ ldr x2, [x1, #24] // node->next
253
300
  str x2, [x19, #24] // head = node->next
254
301
  cbnz x2, .Lchannel_receive_str_not_empty
255
302
  str xzr, [x19, #32] // tail = 0
@@ -264,6 +311,7 @@ pub class Channel: Sendable {
264
311
  ldr x0, [x19, #8] // self->mu
265
312
  bl _pthread_mutex_unlock
266
313
  ldp x0, x1, [sp], #16 // pop pair → x0/x1 (return)
314
+ .Lchannel_receive_str_end:
267
315
  ```
268
316
  }
269
317
 
@@ -271,12 +319,13 @@ pub class Channel: Sendable {
271
319
  ```
272
320
  #arch: c
273
321
  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.
322
+ // Drain any pending nodes. A string payload (kind = 1) that was
323
+ // never received still owns its buffer — free it with the node.
324
+ // A uint64 payload (kind = 0) is a raw value, never a pointer.
276
325
  struct nomen_channel_node *n = (struct nomen_channel_node *)self->head;
277
326
  while (n) {
278
327
  struct nomen_channel_node *next = n->next;
279
- if (n->value) free((void *)n->value);
328
+ if (n->kind == 1 && n->value) free((void *)n->value);
280
329
  free(n);
281
330
  n = next;
282
331
  }
@@ -295,14 +344,18 @@ pub class Channel: Sendable {
295
344
  // x19 = self (set by prologue)
296
345
  ldr x0, [x19, #8] // self->mu
297
346
  cbz x0, .Lchannel_destroy_done
298
- // Drain nodes (freeing any unconsumed string payload)
347
+ // Drain nodes (freeing only owned-string payloads; a uint64
348
+ // payload is a raw value, never a pointer)
299
349
  ldr x1, [x19, #24] // head
300
350
  .Lchannel_destroy_drain:
301
351
  cbz x1, .Lchannel_destroy_drained
302
- ldr x2, [x1, #16] // next (before calls clobber x1)
352
+ ldr x2, [x1, #24] // next (before calls clobber x1)
303
353
  stp x1, x2, [sp, #-16]! // save node + next
354
+ ldr x3, [x1, #16] // payload kind (1 = owned string)
304
355
  ldr x0, [x1, #0] // payload ptr
305
356
  cbz x0, .Lchannel_destroy_no_payload
357
+ cmp x3, #1
358
+ b.ne .Lchannel_destroy_no_payload
306
359
  bl _free
307
360
  .Lchannel_destroy_no_payload:
308
361
  ldr x0, [sp, #0] // node
@@ -37,7 +37,7 @@ pub struct ClassBuffer<T> {
37
37
  if new_cap == 0 {
38
38
  new_cap = 4
39
39
  }
40
- while new_cap < needed; new_cap *= 2 {}
40
+ while new_cap < needed; new_cap *= 2 { }
41
41
  self.data = realloc(self.data, new_cap * 8)
42
42
  memset(self.data + self.cap * 8, 0, (new_cap - self.cap) * 8)
43
43
  self.cap = new_cap
@@ -73,7 +73,7 @@ pub struct ClassBuffer<T> {
73
73
  if new_cap == 0 {
74
74
  new_cap = 4
75
75
  }
76
- while new_cap < needed; new_cap *= 2 {}
76
+ while new_cap < needed; new_cap *= 2 { }
77
77
  self.data = realloc(self.data, new_cap * 8)
78
78
  memset(self.data + self.cap * 8, 0, (new_cap - self.cap) * 8)
79
79
  self.cap = new_cap
@@ -150,12 +150,12 @@ pub struct ClassBuffer<T> {
150
150
  // unless `f` returned it. Storing the returned instance raw is sound:
151
151
  // the slot takes sole ownership and `f`'s return is not retained
152
152
  // elsewhere. NOT `inline`: the body calls through the fn pointer.
153
- func modify = (ref self, int i: i >= 0 && i < self.cap, func (T, out T) f) {
153
+ func modify = (ref self, int i: i >= 0 && i < self.cap, func(T, out T) f) {
154
154
  ```
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
  ```
@@ -294,7 +299,11 @@ pub struct ClassBuffer<T> {
294
299
  // instance afterwards. The class-buffer counterpart of Buffer.shift,
295
300
  // used by Map/Set.remove's backward-shift so shifting entries neither
296
301
  // duplicates nor orphans owned instances.
297
- func shift = (ref self, int dst: dst >= 0 && dst < self.cap, int src: src >= 0 && src < self.cap) {
302
+ func shift = (
303
+ ref self,
304
+ int dst: dst >= 0 && dst < self.cap,
305
+ int src: src >= 0 && src < self.cap
306
+ ) {
298
307
  ```
299
308
  #arch: c
300
309
  long *slots = (long*)(unsigned long long)self->data;
@@ -338,7 +347,12 @@ pub struct ClassBuffer<T> {
338
347
  // pointers, returned as a `view T` (T is a class, so each element is an
339
348
  // 8-byte pointer). Borrows from self — invalidated on reassignment. Mirrors
340
349
  // Buffer.slice; ClassBuffer stores 8-byte slots, so the element width is 8.
341
- func slice = (self, int start: start >= 0, int end: end >= start && end <= self.cap, out view T) {
350
+ func slice = (
351
+ self,
352
+ int start: start >= 0,
353
+ int end: end >= start && end <= self.cap,
354
+ out view T
355
+ ) {
342
356
  ```
343
357
  #arch: c
344
358
  nomen_view _r;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * A push-button control that triggers an action when clicked
3
3
  **/
4
- pub class Button : Control {
4
+ pub class Button: Control {
5
5
  var uint64 handle = 0
6
6
 
7
7
  pub func #init = (self, Window window, string title) {
@@ -162,7 +162,7 @@ pub class Button : Control {
162
162
 
163
163
  pub func intrinsic_size = (self, out Size) {
164
164
  var [w, h] = native_intrinsic(self.handle)
165
- var Size s = Size()
165
+ var s = Size()
166
166
  s.width = w
167
167
  s.height = h
168
168
  return s
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * A toggle control with a title and checked/hidden state
3
3
  **/
4
- pub class CheckBox : Control {
4
+ pub class CheckBox: Control {
5
5
  var uint64 handle = 0
6
6
 
7
7
  pub func #init = (self, Window window) {
@@ -199,7 +199,7 @@ pub class CheckBox : Control {
199
199
  // ── Control trait conformance ──
200
200
  pub func intrinsic_size = (self, out Size) {
201
201
  var [w, h] = native_intrinsic(self.handle)
202
- var Size s = Size()
202
+ var s = Size()
203
203
  s.width = w
204
204
  s.height = h
205
205
  return s
@@ -248,7 +248,7 @@ pub func native_intrinsic = (uint64 handle, out [int, int]) {
248
248
  * so a `Container` can also be driven polymorphically as a `Control` (passed to
249
249
  * a `Control`-taking function, stored in a `ClassBuffer<Control>`, etc.).
250
250
  **/
251
- pub class Container : Control {
251
+ pub class Container: Control {
252
252
  var kinds = Buffer<int>()
253
253
  // Per-leaf requested size, stored as a (kind, value) pair per axis. The
254
254
  // kind is one of LEN_* (auto/fixed/percent/fill); the value is the pixel
@@ -1358,7 +1358,7 @@ pub class Container : Control {
1358
1358
  constraints.max_height,
1359
1359
  )
1360
1360
  var int h = self.rh.load_int(self.root)
1361
- var Size s = Size()
1361
+ var s = Size()
1362
1362
  s.width = w
1363
1363
  s.height = h
1364
1364
  return s
@@ -1371,7 +1371,7 @@ pub class Container : Control {
1371
1371
  pub func intrinsic_size = (self, out Size) {
1372
1372
  var int w = self.measure_node(self.root, 0, INF, 0, INF)
1373
1373
  var int h = self.rh.load_int(self.root)
1374
- var Size s = Size()
1374
+ var s = Size()
1375
1375
  s.width = w
1376
1376
  s.height = h
1377
1377
  return s
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * An editable single-line text input control
3
3
  **/
4
- pub class TextBox : Control {
4
+ pub class TextBox: Control {
5
5
  var uint64 handle = 0
6
6
 
7
7
  pub func #init = (self, Window window) {
@@ -203,7 +203,7 @@ pub class TextBox : Control {
203
203
  // ── Control trait conformance ──
204
204
  pub func intrinsic_size = (self, out Size) {
205
205
  var [w, h] = native_intrinsic(self.handle)
206
- var Size s = Size()
206
+ var s = Size()
207
207
  s.width = w
208
208
  s.height = h
209
209
  return s