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.
- package/NOMEN_AGENTS.md +7 -1
- package/core/System/Arena.nm +7 -7
- package/core/System/Awaitable.nm +21 -0
- package/core/System/BigInt.nm +5 -15
- package/core/System/Buffer.nm +140 -33
- package/core/System/Channel.nm +80 -29
- package/core/System/ClassBuffer.nm +89 -32
- package/core/System/Console.nm +4 -2
- package/core/System/Controls/Button.nm +4 -4
- package/core/System/Controls/CheckBox.nm +1 -1
- package/core/System/Controls/Text.nm +2 -2
- package/core/System/Controls/TextBox.nm +6 -6
- package/core/System/Controls/Window.nm +2 -2
- package/core/System/Fiber.nm +121 -0
- package/core/System/Graph.nm +4 -4
- package/core/System/LinkedList.nm +4 -4
- package/core/System/List.nm +6 -6
- package/core/System/Map.nm +30 -30
- package/core/System/Mutex.nm +57 -61
- package/core/System/Nursery.nm +10 -8
- package/core/System/Set.nm +24 -24
- package/core/System/Stream/Directory.nm +4 -4
- package/core/System/Stream/File.nm +19 -12
- package/core/System/Stream/Http.nm +83 -434
- package/core/System/Stream/Tcp.nm +441 -0
- package/core/System/String.nm +47 -18
- package/core/System/Task.nm +103 -4
- package/core/System/Text/JsonTree.nm +15 -15
- package/core/System/Thread.nm +83 -0
- package/core/System/bool.nm +2 -1
- package/core/System/char.nm +2 -1
- package/core/System/float.nm +2 -1
- package/core/System/float32.nm +2 -1
- package/core/System/float64.nm +2 -1
- package/core/System/int.nm +4 -2
- package/core/System/int16.nm +2 -1
- package/core/System/int32.nm +2 -1
- package/core/System/int64.nm +2 -1
- package/core/System/int8.nm +2 -1
- package/core/System/ufloat.nm +2 -1
- package/core/System/ufloat32.nm +2 -1
- package/core/System/ufloat64.nm +2 -1
- package/core/System/uint.nm +2 -1
- package/core/System/uint16.nm +2 -1
- package/core/System/uint32.nm +2 -1
- package/core/System/uint64.nm +2 -1
- package/core/System/uint8.nm +2 -1
- package/core/docs/System/BigInt.md +0 -1
- package/dist/index.mjs +20401 -13796
- 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
|
+
}
|
package/core/System/Graph.nm
CHANGED
|
@@ -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.
|
|
20
|
-
self.values.
|
|
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.
|
|
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.
|
|
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.
|
|
19
|
-
self.values.
|
|
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.
|
|
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.
|
|
57
|
+
return self.values.load(idx)
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
// Bounds-checked access: `fallback` when idx is outside [0, self.count).
|
package/core/System/List.nm
CHANGED
|
@@ -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.
|
|
15
|
+
self.items.grow(new_cap)
|
|
16
16
|
}
|
|
17
|
-
self.items.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
package/core/System/Map.nm
CHANGED
|
@@ -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.
|
|
34
|
-
self.values.
|
|
35
|
-
self.used.
|
|
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.
|
|
43
|
-
self.used.
|
|
44
|
-
self.keys.
|
|
45
|
-
self.values.
|
|
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.
|
|
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.
|
|
58
|
+
if self.used.load_u32(idx) == 0 {
|
|
59
59
|
return 0
|
|
60
60
|
}
|
|
61
|
-
return self.values.
|
|
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.
|
|
72
|
+
if self.used.load_u32(idx) == 0 {
|
|
73
73
|
return fallback
|
|
74
74
|
}
|
|
75
|
-
return self.values.
|
|
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.
|
|
86
|
+
if self.used.load_u32(idx) == 0 {
|
|
87
87
|
panic("key not found")
|
|
88
88
|
}
|
|
89
|
-
return self.values.
|
|
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.
|
|
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.
|
|
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.
|
|
127
|
+
if self.used.load_u32(k) == 0 {
|
|
128
128
|
break
|
|
129
129
|
}
|
|
130
|
-
var int home = (self.keys.
|
|
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.
|
|
146
|
-
self.values.
|
|
147
|
-
self.used.
|
|
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.
|
|
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.
|
|
160
|
-
if self.keys.
|
|
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.
|
|
178
|
-
self.values.
|
|
179
|
-
self.used.
|
|
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.
|
|
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.
|
|
195
|
-
var TV v = old_values.
|
|
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
|
}
|
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
|
}
|
package/core/System/Nursery.nm
CHANGED
|
@@ -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 —
|
|
9
|
-
// futures
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
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
|
|
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
|
}
|
package/core/System/Set.nm
CHANGED
|
@@ -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.
|
|
19
|
-
self.used.
|
|
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.
|
|
27
|
-
self.used.
|
|
28
|
-
self.slots.
|
|
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.
|
|
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.
|
|
48
|
+
if self.used.load_u32(idx) == 0 {
|
|
49
49
|
return 0
|
|
50
50
|
}
|
|
51
|
-
return self.slots.
|
|
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.
|
|
62
|
+
if self.used.load_u32(idx) == 0 {
|
|
63
63
|
return fallback
|
|
64
64
|
}
|
|
65
|
-
return self.slots.
|
|
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.
|
|
75
|
+
if self.used.load_u32(idx) == 0 {
|
|
76
76
|
panic("value not found in set")
|
|
77
77
|
}
|
|
78
|
-
return self.slots.
|
|
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.
|
|
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.
|
|
107
|
+
if self.used.load_u32(k) == 0 {
|
|
108
108
|
break
|
|
109
109
|
}
|
|
110
|
-
var int home = (self.slots.
|
|
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.
|
|
126
|
-
self.used.
|
|
125
|
+
self.slots.shift(gap, k)
|
|
126
|
+
self.used.store_u32(gap, 1)
|
|
127
127
|
gap = k
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
|
-
self.used.
|
|
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.
|
|
139
|
-
if self.slots.
|
|
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.
|
|
156
|
-
self.used.
|
|
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.
|
|
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.
|
|
170
|
+
var T v = old_slots.move(i)
|
|
171
171
|
self.add(move v)
|
|
172
172
|
}
|
|
173
173
|
}
|