nomen-lang 0.3.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.
Files changed (50) hide show
  1. package/NOMEN_AGENTS.md +7 -1
  2. package/core/System/Arena.nm +7 -7
  3. package/core/System/Awaitable.nm +21 -0
  4. package/core/System/BigInt.nm +5 -15
  5. package/core/System/Buffer.nm +140 -33
  6. package/core/System/Channel.nm +80 -29
  7. package/core/System/ClassBuffer.nm +89 -32
  8. package/core/System/Console.nm +4 -2
  9. package/core/System/Controls/Button.nm +4 -4
  10. package/core/System/Controls/CheckBox.nm +1 -1
  11. package/core/System/Controls/Text.nm +2 -2
  12. package/core/System/Controls/TextBox.nm +6 -6
  13. package/core/System/Controls/Window.nm +2 -2
  14. package/core/System/Fiber.nm +121 -0
  15. package/core/System/Graph.nm +4 -4
  16. package/core/System/LinkedList.nm +4 -4
  17. package/core/System/List.nm +6 -6
  18. package/core/System/Map.nm +30 -30
  19. package/core/System/Mutex.nm +57 -61
  20. package/core/System/Nursery.nm +10 -8
  21. package/core/System/Set.nm +24 -24
  22. package/core/System/Stream/Directory.nm +4 -4
  23. package/core/System/Stream/File.nm +19 -12
  24. package/core/System/Stream/Http.nm +83 -434
  25. package/core/System/Stream/Tcp.nm +441 -0
  26. package/core/System/String.nm +47 -18
  27. package/core/System/Task.nm +103 -4
  28. package/core/System/Text/JsonTree.nm +15 -15
  29. package/core/System/Thread.nm +83 -0
  30. package/core/System/bool.nm +2 -1
  31. package/core/System/char.nm +2 -1
  32. package/core/System/float.nm +2 -1
  33. package/core/System/float32.nm +2 -1
  34. package/core/System/float64.nm +2 -1
  35. package/core/System/int.nm +4 -2
  36. package/core/System/int16.nm +2 -1
  37. package/core/System/int32.nm +2 -1
  38. package/core/System/int64.nm +2 -1
  39. package/core/System/int8.nm +2 -1
  40. package/core/System/ufloat.nm +2 -1
  41. package/core/System/ufloat32.nm +2 -1
  42. package/core/System/ufloat64.nm +2 -1
  43. package/core/System/uint.nm +2 -1
  44. package/core/System/uint16.nm +2 -1
  45. package/core/System/uint32.nm +2 -1
  46. package/core/System/uint64.nm +2 -1
  47. package/core/System/uint8.nm +2 -1
  48. package/core/docs/System/BigInt.md +0 -1
  49. package/dist/index.mjs +20401 -13796
  50. package/package.json +1 -1
@@ -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
+ }
@@ -16,8 +16,8 @@ pub struct Graph<T>: Enumerable {
16
16
 
17
17
  pub func add_node = (ref self, move T value) {
18
18
  var int idx = self.node_count
19
- self.values.grow_T(idx + 1)
20
- self.values.store_T(idx, value)
19
+ self.values.grow(idx + 1)
20
+ self.values.store(idx, value)
21
21
  self.head_edge.grow_int(idx + 1)
22
22
  self.head_edge.store_int(idx, -1)
23
23
  self.node_count = self.node_count + 1
@@ -50,11 +50,11 @@ pub struct Graph<T>: Enumerable {
50
50
  }
51
51
 
52
52
  pub func first = (self: self.node_count > 0, out T) {
53
- return self.values.load_T(0)
53
+ return self.values.load(0)
54
54
  }
55
55
 
56
56
  pub func at = (self, int idx: idx >= 0 && idx < self.node_count, out T) {
57
- return self.values.load_T(idx)
57
+ return self.values.load(idx)
58
58
  }
59
59
 
60
60
  pub func node_length = (self, out int) {
@@ -15,8 +15,8 @@ pub struct LinkedList<T>: Enumerable {
15
15
 
16
16
  pub func add = (ref self, move T value) {
17
17
  var int idx = self.count
18
- self.values.grow_T(idx + 1)
19
- self.values.store_T(idx, value)
18
+ self.values.grow(idx + 1)
19
+ self.values.store(idx, value)
20
20
  self.nexts.grow_int(idx + 1)
21
21
  self.nexts.store_int(idx, -1)
22
22
  self.prevs.grow_int(idx + 1)
@@ -50,11 +50,11 @@ pub struct LinkedList<T>: Enumerable {
50
50
  }
51
51
 
52
52
  pub func first = (self: self.count > 0, out T) {
53
- return self.values.load_T(0)
53
+ return self.values.load(0)
54
54
  }
55
55
 
56
56
  pub func at = (self, int idx: idx >= 0 && idx < self.count, out T) {
57
- return self.values.load_T(idx)
57
+ return self.values.load(idx)
58
58
  }
59
59
 
60
60
  // Bounds-checked access: `fallback` when idx is outside [0, self.count).
@@ -12,23 +12,23 @@ pub struct List<T>: Viewable {
12
12
  if old_cap >= 4 {
13
13
  new_cap = old_cap * 2
14
14
  }
15
- self.items.grow_T(new_cap)
15
+ self.items.grow(new_cap)
16
16
  }
17
- self.items.store_T(self.length, value)
17
+ self.items.store(self.length, value)
18
18
  self.length += 1
19
19
  }
20
20
 
21
21
  pub func pop = (ref self, move out T) {
22
22
  if self.length <= 0 {
23
- return self.items.move_T(0)
23
+ return self.items.move(0)
24
24
  }
25
25
  var int idx = self.length - 1
26
26
  self.length = idx
27
- return self.items.move_T(idx)
27
+ return self.items.move(idx)
28
28
  }
29
29
 
30
30
  pub inline func at = (self, int i: i >= 0 && i < self.length, out T) {
31
- return self.items.load_T(i)
31
+ return self.items.load(i)
32
32
  }
33
33
 
34
34
  // Bounds-checked access: `fallback` when i is outside [0, self.length) —
@@ -58,7 +58,7 @@ pub struct List<T>: Viewable {
58
58
  }
59
59
 
60
60
  pub func set = (ref self, int i: i >= 0 && i < self.length, move T value) {
61
- self.items.replace_T(i, value)
61
+ self.items.replace(i, value)
62
62
  }
63
63
 
64
64
  // A deep copy: a fresh List<T> with an independent copy of every element.
@@ -30,22 +30,22 @@ pub struct Map<TK: Hashable + Equatable, TV> {
30
30
  // alloc_T: for Buffer<TK> it is T_SIZE-aware (string keys are
31
31
  // 16-byte slots); for ClassBuffer<TV> it delegates to alloc_int
32
32
  // (one pointer per slot). Both have it for generic parity.
33
- cap = self.keys.alloc_T(8)
34
- self.values.alloc_T(8)
35
- self.used.alloc(8)
33
+ cap = self.keys.alloc(8)
34
+ self.values.alloc(8)
35
+ self.used.alloc_u32(8)
36
36
  }
37
37
  if self.length * 4 >= cap * 3 {
38
38
  self.rehash(cap * 2)
39
39
  cap = self.keys.cap
40
40
  }
41
41
  var int idx = self.find_slot(key, cap)
42
- if self.used.load(idx) == 0 {
43
- self.used.store(idx, 1)
44
- self.keys.store_T(idx, key)
45
- self.values.store_T(idx, value)
42
+ if self.used.load_u32(idx) == 0 {
43
+ self.used.store_u32(idx, 1)
44
+ self.keys.store(idx, key)
45
+ self.values.store(idx, value)
46
46
  self.length += 1
47
47
  } else {
48
- self.values.replace_T(idx, value)
48
+ self.values.replace(idx, value)
49
49
  }
50
50
  }
51
51
 
@@ -55,10 +55,10 @@ pub struct Map<TK: Hashable + Equatable, TV> {
55
55
  return 0
56
56
  }
57
57
  var int idx = self.find_slot(key, cap)
58
- if self.used.load(idx) == 0 {
58
+ if self.used.load_u32(idx) == 0 {
59
59
  return 0
60
60
  }
61
- return self.values.load_T(idx)
61
+ return self.values.load(idx)
62
62
  }
63
63
 
64
64
  // Fused lookup-with-fallback: one probe instead of the `has` + `get`
@@ -69,10 +69,10 @@ pub struct Map<TK: Hashable + Equatable, TV> {
69
69
  return fallback
70
70
  }
71
71
  var int idx = self.find_slot(key, cap)
72
- if self.used.load(idx) == 0 {
72
+ if self.used.load_u32(idx) == 0 {
73
73
  return fallback
74
74
  }
75
- return self.values.load_T(idx)
75
+ return self.values.load(idx)
76
76
  }
77
77
 
78
78
  // Lookup that traps when the key is absent — for when a missing key is
@@ -83,10 +83,10 @@ pub struct Map<TK: Hashable + Equatable, TV> {
83
83
  panic("key not found")
84
84
  }
85
85
  var int idx = self.find_slot(key, cap)
86
- if self.used.load(idx) == 0 {
86
+ if self.used.load_u32(idx) == 0 {
87
87
  panic("key not found")
88
88
  }
89
- return self.values.load_T(idx)
89
+ return self.values.load(idx)
90
90
  }
91
91
 
92
92
  pub func has = (self, TK key, out bool) {
@@ -95,7 +95,7 @@ pub struct Map<TK: Hashable + Equatable, TV> {
95
95
  return false
96
96
  }
97
97
  var int idx = self.find_slot(key, cap)
98
- return self.used.load(idx) != 0
98
+ return self.used.load_u32(idx) != 0
99
99
  }
100
100
 
101
101
  pub func remove = (ref self, TK key) {
@@ -104,7 +104,7 @@ pub struct Map<TK: Hashable + Equatable, TV> {
104
104
  return
105
105
  }
106
106
  var int idx = self.find_slot(key, cap)
107
- if self.used.load(idx) == 0 {
107
+ if self.used.load_u32(idx) == 0 {
108
108
  return
109
109
  }
110
110
  // Backward-shift deletion for linear probing. `gap` is the empty slot
@@ -124,10 +124,10 @@ pub struct Map<TK: Hashable + Equatable, TV> {
124
124
  if k >= cap {
125
125
  k = 0
126
126
  }
127
- if self.used.load(k) == 0 {
127
+ if self.used.load_u32(k) == 0 {
128
128
  break
129
129
  }
130
- var int home = (self.keys.load_T(k).hash() as int) & (cap - 1)
130
+ var int home = (self.keys.load(k).hash() as int) & (cap - 1)
131
131
  var int dgap = gap - home
132
132
  if dgap < 0 {
133
133
  dgap = dgap + cap
@@ -142,13 +142,13 @@ pub struct Map<TK: Hashable + Equatable, TV> {
142
142
  // would strdup a fresh copy into gap while leaving the source
143
143
  // slot's pointer stranded at the vacated slot — leaked once
144
144
  // used.store marks it empty (the buffer destroy skips it).
145
- self.keys.shift_T(gap, k)
146
- self.values.shift_T(gap, k)
147
- self.used.store(gap, 1)
145
+ self.keys.shift(gap, k)
146
+ self.values.shift(gap, k)
147
+ self.used.store_u32(gap, 1)
148
148
  gap = k
149
149
  }
150
150
  }
151
- self.used.store(gap, 0)
151
+ self.used.store_u32(gap, 0)
152
152
  self.length = self.length - 1
153
153
  }
154
154
 
@@ -156,8 +156,8 @@ pub struct Map<TK: Hashable + Equatable, TV> {
156
156
  // cap is a power of two, so this is `hash mod cap` via a mask —
157
157
  // non-negative for any hash (two's complement), no adjustment.
158
158
  var int idx = (key.hash() as int) & (cap - 1)
159
- while self.used.load(idx) != 0 {
160
- if self.keys.load_T(idx) == key {
159
+ while self.used.load_u32(idx) != 0 {
160
+ if self.keys.load(idx) == key {
161
161
  return idx
162
162
  }
163
163
  idx += 1
@@ -174,14 +174,14 @@ pub struct Map<TK: Hashable + Equatable, TV> {
174
174
  var Buffer<int> old_used = move self.used swap Buffer<int>()
175
175
  var int old_cap = old_keys.cap
176
176
 
177
- self.keys.alloc_T(new_cap)
178
- self.values.alloc_T(new_cap)
179
- self.used.alloc(new_cap)
177
+ self.keys.alloc(new_cap)
178
+ self.values.alloc(new_cap)
179
+ self.used.alloc_u32(new_cap)
180
180
  self.length = 0
181
181
 
182
182
  var i = 0
183
183
  while i < old_cap; i += 1 {
184
- if old_used.load(i) != 0 {
184
+ if old_used.load_u32(i) != 0 {
185
185
  // move_T (not load_T) relinquishes the old slot, so each
186
186
  // key/value is owned by exactly one buffer (the new self)
187
187
  // afterwards. load_T would leave the old slot referencing
@@ -191,8 +191,8 @@ pub struct Map<TK: Hashable + Equatable, TV> {
191
191
  // store_T strdup's) is freed here once the new buffer has
192
192
  // made its own copy — passing them inline would orphan the
193
193
  // move_T results.
194
- var TK k = old_keys.move_T(i)
195
- var TV v = old_values.move_T(i)
194
+ var TK k = old_keys.move(i)
195
+ var TV v = old_values.move(i)
196
196
  self.set(move k, move v)
197
197
  }
198
198
  }
@@ -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
  }
@@ -5,18 +5,19 @@
5
5
  // An `async` block names its nursery (`async pool { }`), binding a Nursery
6
6
  // variable in scope. That variable is passed to helper functions with `ref`
7
7
  // and is the receiver of `pool.spawn(fn(args))`. The Nursery wraps the
8
- // per-invocation tracking state of the `async { }` block — a pointer to the
9
- // futures array and a pointer to the count slot, both on the async block's
10
- // stack frame — so it is only valid for the lifetime of its enclosing block
11
- // (which is exactly the structured-concurrency invariant: the block cannot
12
- // exit until every spawned task, including those spawned through a passed
13
- // Nursery, has finished).
8
+ // per-invocation tracking state of the `async { }` block — pointers to the
9
+ // futures list's storage slot, the registered count, and the capacity (the
10
+ // list starts empty and grows by realloc; the compiler's registration helper
11
+ // writes through these pointers) — so it is only valid for the lifetime of
12
+ // its enclosing block (which is exactly the structured-concurrency
13
+ // invariant: the block cannot exit until every spawned task, including those
14
+ // spawned through a passed Nursery, has finished).
14
15
  //
15
16
  // Config rides on the declaration: `async pool = Nursery(timeout: 2000) { }`.
16
17
  //
17
18
  // `name.spawn(fn(args))` is special-cased by the compiler (it needs the spawn
18
- // trampoline machinery), so it is NOT a regular method declared here. The two
19
- // fields are the full runtime contract.
19
+ // trampoline machinery), so it is NOT a regular method declared here. The
20
+ // three fields are the full runtime contract.
20
21
 
21
22
  /**
22
23
  * The capability for spawning into a caller's `async` block; owns and tracks its tasks
@@ -24,4 +25,5 @@
24
25
  pub struct Nursery: Sendable {
25
26
  pub var uint64 futures_ptr = 0
26
27
  pub var uint64 count_ptr = 0
28
+ pub var uint64 cap_ptr = 0
27
29
  }
@@ -15,17 +15,17 @@ pub struct Set<T: Hashable + Equatable> {
15
15
  pub func add = (ref self, move T value) {
16
16
  var int cap = self.slots.cap
17
17
  if cap == 0 {
18
- cap = self.slots.alloc_T(8)
19
- self.used.alloc(8)
18
+ cap = self.slots.alloc(8)
19
+ self.used.alloc_u32(8)
20
20
  }
21
21
  if self.length * 4 >= cap * 3 {
22
22
  self.rehash(cap * 2)
23
23
  cap = self.slots.cap
24
24
  }
25
25
  var int idx = self.find_slot(value, cap)
26
- if self.used.load(idx) == 0 {
27
- self.used.store(idx, 1)
28
- self.slots.store_T(idx, value)
26
+ if self.used.load_u32(idx) == 0 {
27
+ self.used.store_u32(idx, 1)
28
+ self.slots.store(idx, value)
29
29
  self.length += 1
30
30
  }
31
31
  }
@@ -36,7 +36,7 @@ pub struct Set<T: Hashable + Equatable> {
36
36
  return false
37
37
  }
38
38
  var int idx = self.find_slot(value, cap)
39
- return self.used.load(idx) != 0
39
+ return self.used.load_u32(idx) != 0
40
40
  }
41
41
 
42
42
  pub func get = (self, T key, out T) {
@@ -45,10 +45,10 @@ pub struct Set<T: Hashable + Equatable> {
45
45
  return 0
46
46
  }
47
47
  var int idx = self.find_slot(key, cap)
48
- if self.used.load(idx) == 0 {
48
+ if self.used.load_u32(idx) == 0 {
49
49
  return 0
50
50
  }
51
- return self.slots.load_T(idx)
51
+ return self.slots.load(idx)
52
52
  }
53
53
 
54
54
  // Membership lookup with a fallback value: `fallback` when the value is
@@ -59,10 +59,10 @@ pub struct Set<T: Hashable + Equatable> {
59
59
  return fallback
60
60
  }
61
61
  var int idx = self.find_slot(key, cap)
62
- if self.used.load(idx) == 0 {
62
+ if self.used.load_u32(idx) == 0 {
63
63
  return fallback
64
64
  }
65
- return self.slots.load_T(idx)
65
+ return self.slots.load(idx)
66
66
  }
67
67
 
68
68
  // Membership lookup that traps when the value is not in the set.
@@ -72,10 +72,10 @@ pub struct Set<T: Hashable + Equatable> {
72
72
  panic("value not found in set")
73
73
  }
74
74
  var int idx = self.find_slot(key, cap)
75
- if self.used.load(idx) == 0 {
75
+ if self.used.load_u32(idx) == 0 {
76
76
  panic("value not found in set")
77
77
  }
78
- return self.slots.load_T(idx)
78
+ return self.slots.load(idx)
79
79
  }
80
80
 
81
81
  pub func remove = (ref self, T value) {
@@ -84,7 +84,7 @@ pub struct Set<T: Hashable + Equatable> {
84
84
  return
85
85
  }
86
86
  var int idx = self.find_slot(value, cap)
87
- if self.used.load(idx) == 0 {
87
+ if self.used.load_u32(idx) == 0 {
88
88
  return
89
89
  }
90
90
  // Backward-shift deletion for linear probing. `gap` is the empty slot
@@ -104,10 +104,10 @@ pub struct Set<T: Hashable + Equatable> {
104
104
  if k >= cap {
105
105
  k = 0
106
106
  }
107
- if self.used.load(k) == 0 {
107
+ if self.used.load_u32(k) == 0 {
108
108
  break
109
109
  }
110
- var int home = (self.slots.load_T(k).hash() as int) & (cap - 1)
110
+ var int home = (self.slots.load(k).hash() as int) & (cap - 1)
111
111
  var int dgap = gap - home
112
112
  if dgap < 0 {
113
113
  dgap = dgap + cap
@@ -122,12 +122,12 @@ pub struct Set<T: Hashable + Equatable> {
122
122
  // fresh copy into gap while stranding the source slot's
123
123
  // pointer at the vacated slot (leaked once used.store marks it
124
124
  // empty; the buffer destroy skips unused slots).
125
- self.slots.shift_T(gap, k)
126
- self.used.store(gap, 1)
125
+ self.slots.shift(gap, k)
126
+ self.used.store_u32(gap, 1)
127
127
  gap = k
128
128
  }
129
129
  }
130
- self.used.store(gap, 0)
130
+ self.used.store_u32(gap, 0)
131
131
  self.length = self.length - 1
132
132
  }
133
133
 
@@ -135,8 +135,8 @@ pub struct Set<T: Hashable + Equatable> {
135
135
  // cap is a power of two: `hash mod cap` via a mask, non-negative
136
136
  // for any hash — no adjustment needed.
137
137
  var int idx = (value.hash() as int) & (cap - 1)
138
- while self.used.load(idx) != 0 {
139
- if self.slots.load_T(idx) == value {
138
+ while self.used.load_u32(idx) != 0 {
139
+ if self.slots.load(idx) == value {
140
140
  return idx
141
141
  }
142
142
  idx += 1
@@ -152,13 +152,13 @@ pub struct Set<T: Hashable + Equatable> {
152
152
  var Buffer<int> old_used = move self.used swap Buffer<int>()
153
153
  var int old_cap = old_slots.cap
154
154
 
155
- self.slots.alloc_T(new_cap)
156
- self.used.alloc(new_cap)
155
+ self.slots.alloc(new_cap)
156
+ self.used.alloc_u32(new_cap)
157
157
  self.length = 0
158
158
 
159
159
  var i = 0
160
160
  while i < old_cap; i += 1 {
161
- if old_used.load(i) != 0 {
161
+ if old_used.load_u32(i) != 0 {
162
162
  // move_T (not load_T) relinquishes the old slot, so each value
163
163
  // is owned by exactly one buffer (the new self) afterwards.
164
164
  // load_T would leave the old slot referencing the same class
@@ -167,7 +167,7 @@ pub struct Set<T: Hashable + Equatable> {
167
167
  // `Buffer<string>` slot, which store_T strdup's) is freed here
168
168
  // once the new buffer has made its own copy — passing it inline
169
169
  // would orphan the move_T result.
170
- var T v = old_slots.move_T(i)
170
+ var T v = old_slots.move(i)
171
171
  self.add(move v)
172
172
  }
173
173
  }