@shd101wyy/yo 0.1.30 → 0.1.32
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/.github/skills/yo-syntax/syntax-cheatsheet.md +38 -0
- package/out/cjs/index.cjs +800 -633
- package/out/cjs/yo-cli.cjs +1007 -840
- package/out/cjs/yo-lsp.cjs +890 -723
- package/out/esm/index.mjs +804 -637
- package/out/types/src/codegen/index.d.ts +1 -1
- package/out/types/src/compiler-utils.d.ts +1 -1
- package/out/types/src/evaluator/builtins/contracts.d.ts +35 -0
- package/out/types/src/evaluator/memory-safety.d.ts +1 -1
- package/out/types/src/evaluator/types/function.d.ts +2 -0
- package/out/types/src/evaluator/utils/closure.d.ts +2 -1
- package/out/types/src/evaluator/utils.d.ts +3 -0
- package/out/types/src/evaluator/values/impl.d.ts +14 -0
- package/out/types/src/expr.d.ts +6 -0
- package/out/types/src/tests/contracts-comptime-violation.test.d.ts +1 -0
- package/out/types/src/tests/contracts-runtime-violation.test.d.ts +1 -0
- package/out/types/src/tests/thread-safety-codegen.test.d.ts +1 -0
- package/out/types/src/types/creators.d.ts +3 -1
- package/out/types/src/types/definitions.d.ts +2 -0
- package/out/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/std/build.yo +5 -2
- package/std/imm/list.yo +1 -1
- package/std/imm/sorted_map.yo +1 -1
- package/std/libc/stdatomic.yo +285 -1
- package/std/prelude.yo +56 -3
- package/std/spec/numeric.yo +30 -0
- package/std/spec/refine.yo +43 -0
- package/std/string/rune.yo +4 -0
- package/std/sync/atomic.yo +557 -0
- package/std/sync/channel.yo +57 -42
- package/std/sync/cond.yo +7 -3
- package/std/sync/mutex.yo +75 -15
- package/std/sync/once.yo +25 -19
- package/std/sync/rwlock.yo +18 -15
- package/std/sync/waitgroup.yo +25 -16
package/std/sync/channel.yo
CHANGED
|
@@ -23,6 +23,7 @@ pragma(Pragma.AllowUnsafe);
|
|
|
23
23
|
{ malloc, free } :: GlobalAllocator;
|
|
24
24
|
{ Mutex } :: import("./mutex");
|
|
25
25
|
{ Cond } :: import("./cond");
|
|
26
|
+
{ AtomicBool, AtomicUsize, MemoryOrder } :: import("./atomic");
|
|
26
27
|
/// Thread-safe bounded channel for passing values between threads.
|
|
27
28
|
/// Uses atomic reference counting — implements `Send` and can be
|
|
28
29
|
/// safely shared across threads without `Arc` wrapping.
|
|
@@ -32,12 +33,12 @@ Channel :: (fn(comptime(T) : Type, where(T <: Send)) -> comptime(Type))(
|
|
|
32
33
|
object(
|
|
33
34
|
_data : *(T),
|
|
34
35
|
_head : usize,
|
|
35
|
-
_len :
|
|
36
|
+
_len : AtomicUsize,
|
|
36
37
|
_capacity : usize,
|
|
37
|
-
_mutex : Mutex,
|
|
38
|
+
_mutex : Mutex(bool),
|
|
38
39
|
_not_empty : Cond,
|
|
39
40
|
_not_full : Cond,
|
|
40
|
-
_closed :
|
|
41
|
+
_closed : AtomicBool
|
|
41
42
|
)
|
|
42
43
|
)
|
|
43
44
|
);
|
|
@@ -54,12 +55,12 @@ impl(
|
|
|
54
55
|
Self(
|
|
55
56
|
_data : ptr,
|
|
56
57
|
_head : usize(0),
|
|
57
|
-
_len : usize(0),
|
|
58
|
+
_len : AtomicUsize(usize(0)),
|
|
58
59
|
_capacity : capacity,
|
|
59
|
-
_mutex : Mutex.new(),
|
|
60
|
+
_mutex : Mutex(bool).new(false),
|
|
60
61
|
_not_empty : Cond.new(),
|
|
61
62
|
_not_full : Cond.new(),
|
|
62
|
-
_closed : false
|
|
63
|
+
_closed : AtomicBool(false)
|
|
63
64
|
)
|
|
64
65
|
);
|
|
65
66
|
}),
|
|
@@ -67,38 +68,45 @@ impl(
|
|
|
67
68
|
/// Blocks if the channel is full until space becomes available.
|
|
68
69
|
/// Returns .Ok(()) on success, .Err(()) if the channel is closed.
|
|
69
70
|
send : (fn(self : Self, value : T) -> Result(unit, unit))({
|
|
70
|
-
self._mutex.
|
|
71
|
+
self._mutex._raw_lock();
|
|
71
72
|
// Wait while buffer is full and channel is not closed
|
|
72
|
-
while(runtime(
|
|
73
|
-
self.
|
|
73
|
+
while(runtime(
|
|
74
|
+
(self._len.load(MemoryOrder.Relaxed) >= self._capacity)
|
|
75
|
+
&& (!(self._closed.load(MemoryOrder.Relaxed)))
|
|
76
|
+
), {
|
|
77
|
+
self._not_full.wait(self._mutex._raw_handle_ptr());
|
|
74
78
|
});
|
|
75
79
|
cond(
|
|
76
|
-
self._closed => {
|
|
77
|
-
self._mutex.
|
|
80
|
+
self._closed.load(MemoryOrder.Relaxed) => {
|
|
81
|
+
self._mutex._raw_unlock();
|
|
78
82
|
return(.Err(()));
|
|
79
83
|
},
|
|
80
84
|
true => ()
|
|
81
85
|
);
|
|
82
86
|
// Write value at tail position
|
|
83
|
-
(
|
|
87
|
+
(current_len : usize) = self._len.load(MemoryOrder.Relaxed);
|
|
88
|
+
(tail : usize) = ((self._head + current_len) % self._capacity);
|
|
84
89
|
consume((self._data &+ tail).* = value);
|
|
85
|
-
self._len
|
|
90
|
+
self._len.store(current_len + usize(1), MemoryOrder.Release);
|
|
86
91
|
self._not_empty.signal();
|
|
87
|
-
self._mutex.
|
|
92
|
+
self._mutex._raw_unlock();
|
|
88
93
|
return(.Ok(()));
|
|
89
94
|
}),
|
|
90
95
|
/// Receive a value from the channel.
|
|
91
96
|
/// Blocks if the channel is empty until a value is available.
|
|
92
97
|
/// Returns .Some(value) on success, .None if the channel is closed and empty.
|
|
93
98
|
recv : (fn(self : Self) -> ?(T))({
|
|
94
|
-
self._mutex.
|
|
99
|
+
self._mutex._raw_lock();
|
|
95
100
|
// Wait while buffer is empty and channel is not closed
|
|
96
|
-
while(runtime(
|
|
97
|
-
self.
|
|
101
|
+
while(runtime(
|
|
102
|
+
(self._len.load(MemoryOrder.Relaxed) == usize(0))
|
|
103
|
+
&& (!(self._closed.load(MemoryOrder.Relaxed)))
|
|
104
|
+
), {
|
|
105
|
+
self._not_empty.wait(self._mutex._raw_handle_ptr());
|
|
98
106
|
});
|
|
99
107
|
cond(
|
|
100
|
-
(self._len == usize(0)) => {
|
|
101
|
-
self._mutex.
|
|
108
|
+
(self._len.load(MemoryOrder.Relaxed) == usize(0)) => {
|
|
109
|
+
self._mutex._raw_unlock();
|
|
102
110
|
return(.None);
|
|
103
111
|
},
|
|
104
112
|
true => ()
|
|
@@ -108,36 +116,40 @@ impl(
|
|
|
108
116
|
(val : T) = element_ptr.*;
|
|
109
117
|
unsafe.drop(element_ptr.*);
|
|
110
118
|
self._head = ((self._head + usize(1)) % self._capacity);
|
|
111
|
-
self._len
|
|
119
|
+
self._len.store(self._len.load(MemoryOrder.Relaxed) - usize(1), MemoryOrder.Release);
|
|
112
120
|
self._not_full.signal();
|
|
113
|
-
self._mutex.
|
|
121
|
+
self._mutex._raw_unlock();
|
|
114
122
|
return(.Some(val));
|
|
115
123
|
}),
|
|
116
124
|
/// Try to send a value without blocking.
|
|
117
125
|
/// Returns .Ok(()) if sent, .Err(()) if full or closed.
|
|
118
126
|
try_send : (fn(self : Self, value : T) -> Result(unit, unit))({
|
|
119
|
-
self._mutex.
|
|
127
|
+
self._mutex._raw_lock();
|
|
120
128
|
cond(
|
|
121
|
-
(
|
|
122
|
-
self.
|
|
129
|
+
(
|
|
130
|
+
self._closed.load(MemoryOrder.Relaxed)
|
|
131
|
+
|| (self._len.load(MemoryOrder.Relaxed) >= self._capacity)
|
|
132
|
+
) => {
|
|
133
|
+
self._mutex._raw_unlock();
|
|
123
134
|
return(.Err(()));
|
|
124
135
|
},
|
|
125
136
|
true => ()
|
|
126
137
|
);
|
|
127
|
-
(
|
|
138
|
+
(current_len : usize) = self._len.load(MemoryOrder.Relaxed);
|
|
139
|
+
(tail : usize) = ((self._head + current_len) % self._capacity);
|
|
128
140
|
consume((self._data &+ tail).* = value);
|
|
129
|
-
self._len
|
|
141
|
+
self._len.store(current_len + usize(1), MemoryOrder.Release);
|
|
130
142
|
self._not_empty.signal();
|
|
131
|
-
self._mutex.
|
|
143
|
+
self._mutex._raw_unlock();
|
|
132
144
|
return(.Ok(()));
|
|
133
145
|
}),
|
|
134
146
|
/// Try to receive a value without blocking.
|
|
135
147
|
/// Returns .Some(value) if available, .None if empty.
|
|
136
148
|
try_recv : (fn(self : Self) -> ?(T))({
|
|
137
|
-
self._mutex.
|
|
149
|
+
self._mutex._raw_lock();
|
|
138
150
|
cond(
|
|
139
|
-
(self._len == usize(0)) => {
|
|
140
|
-
self._mutex.
|
|
151
|
+
(self._len.load(MemoryOrder.Relaxed) == usize(0)) => {
|
|
152
|
+
self._mutex._raw_unlock();
|
|
141
153
|
return(.None);
|
|
142
154
|
},
|
|
143
155
|
true => ()
|
|
@@ -146,32 +158,34 @@ impl(
|
|
|
146
158
|
(val : T) = element_ptr.*;
|
|
147
159
|
unsafe.drop(element_ptr.*);
|
|
148
160
|
self._head = ((self._head + usize(1)) % self._capacity);
|
|
149
|
-
self._len
|
|
161
|
+
self._len.store(self._len.load(MemoryOrder.Relaxed) - usize(1), MemoryOrder.Release);
|
|
150
162
|
self._not_full.signal();
|
|
151
|
-
self._mutex.
|
|
163
|
+
self._mutex._raw_unlock();
|
|
152
164
|
return(.Some(val));
|
|
153
165
|
}),
|
|
154
166
|
/// Close the channel. No more values can be sent.
|
|
155
167
|
/// Pending recv() calls will return .None once the buffer is drained.
|
|
156
168
|
/// Pending send() calls will return .Err(()).
|
|
157
169
|
close : (fn(self : Self) -> unit)({
|
|
158
|
-
self._mutex.
|
|
159
|
-
self._closed
|
|
170
|
+
self._mutex._raw_lock();
|
|
171
|
+
self._closed.store(true, MemoryOrder.Release);
|
|
160
172
|
self._not_empty.broadcast();
|
|
161
173
|
self._not_full.broadcast();
|
|
162
|
-
self._mutex.
|
|
174
|
+
self._mutex._raw_unlock();
|
|
163
175
|
}),
|
|
164
|
-
/// Check if the channel is closed.
|
|
176
|
+
/// Check if the channel is closed (lock-free atomic load).
|
|
165
177
|
is_closed : (fn(self : Self) -> bool)(
|
|
166
|
-
self._closed
|
|
178
|
+
self._closed.load(MemoryOrder.Acquire)
|
|
167
179
|
),
|
|
168
|
-
/// Return the number of values currently buffered.
|
|
180
|
+
/// Return the number of values currently buffered (lock-free atomic load).
|
|
181
|
+
/// Snapshot semantics: the value may be stale by the time the caller acts on it,
|
|
182
|
+
/// matching Rust's `mpsc::channel` idiom.
|
|
169
183
|
len : (fn(self : Self) -> usize)(
|
|
170
|
-
self._len
|
|
184
|
+
self._len.load(MemoryOrder.Acquire)
|
|
171
185
|
),
|
|
172
|
-
/// Check if the channel buffer is empty.
|
|
186
|
+
/// Check if the channel buffer is empty (lock-free atomic load).
|
|
173
187
|
is_empty : (fn(self : Self) -> bool)(
|
|
174
|
-
self._len == usize(0)
|
|
188
|
+
self._len.load(MemoryOrder.Acquire) == usize(0)
|
|
175
189
|
)
|
|
176
190
|
);
|
|
177
191
|
// Dispose: drop remaining elements and free the buffer
|
|
@@ -181,8 +195,9 @@ impl(
|
|
|
181
195
|
where(T <: Send),
|
|
182
196
|
Dispose(
|
|
183
197
|
dispose : (fn(self : Self) -> unit)({
|
|
198
|
+
(current_len : usize) = self._len.load(MemoryOrder.Relaxed);
|
|
184
199
|
(i : usize) = usize(0);
|
|
185
|
-
while(runtime(i <
|
|
200
|
+
while(runtime(i < current_len), {
|
|
186
201
|
(idx : usize) = ((self._head + i) % self._capacity);
|
|
187
202
|
unsafe.drop((self._data &+ idx).*);
|
|
188
203
|
i = (i + usize(1));
|
package/std/sync/cond.yo
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//! Condition variable for thread synchronization.
|
|
2
2
|
pragma(Pragma.AllowUnsafe);
|
|
3
|
-
{ __YO_THREAD_SYNC_TYPE, mutex_t
|
|
3
|
+
{ __YO_THREAD_SYNC_TYPE, mutex_t } :: import("./mutex");
|
|
4
4
|
extern(
|
|
5
5
|
"Yo",
|
|
6
6
|
__YO_COND_TYPE : Type,
|
|
@@ -10,6 +10,10 @@ extern(
|
|
|
10
10
|
__yo_cond_broadcast : (fn(cv : *(__YO_COND_TYPE)) -> unit),
|
|
11
11
|
__yo_cond_destroy : (fn(cv : *(__YO_COND_TYPE)) -> unit)
|
|
12
12
|
);
|
|
13
|
+
// SAFETY: __YO_COND_TYPE is an opaque C-level condition variable
|
|
14
|
+
// (pthread_cond_t or equivalent). It carries its own internal
|
|
15
|
+
// synchronization and is designed to be shared across threads. It is
|
|
16
|
+
// trivially Acyclic — opaque C types don't participate in ARC.
|
|
13
17
|
impl(__YO_COND_TYPE, Send());
|
|
14
18
|
impl(__YO_COND_TYPE, Acyclic());
|
|
15
19
|
/// Low-level condition variable (manual lifetime via `destroy`).
|
|
@@ -46,8 +50,8 @@ impl(
|
|
|
46
50
|
new : (fn() -> Self)({
|
|
47
51
|
return(Self(__yo_cond_create()));
|
|
48
52
|
}),
|
|
49
|
-
wait : (fn(self : Self,
|
|
50
|
-
return(__yo_cond_wait(&(self.cv),
|
|
53
|
+
wait : (fn(self : Self, handle : *(__YO_THREAD_SYNC_TYPE)) -> unit)({
|
|
54
|
+
return(__yo_cond_wait(&(self.cv), handle));
|
|
51
55
|
}),
|
|
52
56
|
signal : (fn(self : Self) -> unit)({
|
|
53
57
|
return(__yo_cond_signal(&(self.cv)));
|
package/std/sync/mutex.yo
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
//! Mutual exclusion lock primitives.
|
|
2
|
+
//!
|
|
3
|
+
//! Phase D (THREAD_SAFETY): Mutex parameterized over the protected data.
|
|
4
|
+
//! Access is granted via `with_lock(closure)` — the closure receives a
|
|
5
|
+
//! `ref(v): T` that is second-class (cannot escape the closure scope).
|
|
6
|
+
//! The private `__MutexUnlocker` handles unlock on both normal return
|
|
7
|
+
//! and unwind via its `Drop`.
|
|
2
8
|
pragma(Pragma.AllowUnsafe);
|
|
9
|
+
|
|
3
10
|
extern(
|
|
4
11
|
"Yo",
|
|
5
12
|
__YO_THREAD_SYNC_TYPE : Type,
|
|
@@ -8,8 +15,13 @@ extern(
|
|
|
8
15
|
__yo_mutex_unlock : (fn(mutex : *(__YO_THREAD_SYNC_TYPE)) -> unit),
|
|
9
16
|
__yo_mutex_destroy : (fn(mutex : *(__YO_THREAD_SYNC_TYPE)) -> unit)
|
|
10
17
|
);
|
|
18
|
+
// SAFETY: __YO_THREAD_SYNC_TYPE is an opaque C-level synchronization
|
|
19
|
+
// primitive (pthread_mutex_t or equivalent). It carries its own internal
|
|
20
|
+
// synchronization and is designed to be shared across threads. It is
|
|
21
|
+
// trivially Acyclic — opaque C types don't participate in ARC.
|
|
11
22
|
impl(__YO_THREAD_SYNC_TYPE, Send());
|
|
12
23
|
impl(__YO_THREAD_SYNC_TYPE, Acyclic());
|
|
24
|
+
|
|
13
25
|
/// Low-level mutex (manual lifetime via `destroy`).
|
|
14
26
|
mutex_t :: newtype(
|
|
15
27
|
mutex : __YO_THREAD_SYNC_TYPE
|
|
@@ -29,35 +41,83 @@ impl(
|
|
|
29
41
|
return(__yo_mutex_destroy(&(self.mutex)));
|
|
30
42
|
})
|
|
31
43
|
);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Mutex
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Mutex(T) — atomic-object mutex wrapper with closure-scoped lock API
|
|
47
|
+
// ============================================================================
|
|
48
|
+
Mutex :: (fn(comptime(T) : Type, where(T <: Send)) -> comptime(Type))(
|
|
49
|
+
atomic(
|
|
50
|
+
object(
|
|
51
|
+
_handle : __YO_THREAD_SYNC_TYPE,
|
|
52
|
+
_value : T
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// __MutexUnlocker — private object that calls _raw_unlock on Drop.
|
|
58
|
+
// Allocated locally inside with_lock; Yo's drop-on-scope-exit and
|
|
59
|
+
// drop-on-unwind machinery guarantees unlock under both normal return
|
|
60
|
+
// and unwind(...).
|
|
61
|
+
__MutexUnlocker :: (fn(comptime(T) : Type, where(T <: Send)) -> comptime(Type))(
|
|
35
62
|
object(
|
|
36
|
-
|
|
63
|
+
_mutex : Mutex(T)
|
|
37
64
|
)
|
|
38
65
|
);
|
|
39
66
|
impl(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
67
|
+
forall(T : Type),
|
|
68
|
+
where(T <: Send),
|
|
69
|
+
__MutexUnlocker(T),
|
|
70
|
+
Dispose(
|
|
71
|
+
dispose : (fn(self : Self) -> unit)({
|
|
72
|
+
self._mutex._raw_unlock();
|
|
73
|
+
})
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
impl(
|
|
78
|
+
forall(T : Type),
|
|
79
|
+
where(T <: Send),
|
|
80
|
+
Mutex(T),
|
|
81
|
+
new : (fn(value : T) -> Self)({
|
|
82
|
+
return(Self(__yo_mutex_create(), value));
|
|
43
83
|
}),
|
|
44
|
-
|
|
45
|
-
|
|
84
|
+
with_lock : (
|
|
85
|
+
fn(
|
|
86
|
+
forall(R : Type),
|
|
87
|
+
self : Self,
|
|
88
|
+
body : Impl(Fn(ref(v) : T) -> R)
|
|
89
|
+
) -> R
|
|
90
|
+
)({
|
|
91
|
+
self._raw_lock();
|
|
92
|
+
_guard := __MutexUnlocker(T)(self);
|
|
93
|
+
body(self._value)
|
|
46
94
|
}),
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
95
|
+
// Internal methods — pragma-only. These are public to the module system
|
|
96
|
+
// but the _-prefixed naming convention signals "internal API".
|
|
97
|
+
_raw_lock : (fn(self : Self) -> unit)(
|
|
98
|
+
__yo_mutex_lock(&(self._handle))
|
|
99
|
+
),
|
|
100
|
+
_raw_unlock : (fn(self : Self) -> unit)(
|
|
101
|
+
__yo_mutex_unlock(&(self._handle))
|
|
102
|
+
),
|
|
103
|
+
_raw_handle_ptr : (fn(ref(self) : Self) -> *(__YO_THREAD_SYNC_TYPE))(
|
|
104
|
+
&(self._handle)
|
|
105
|
+
)
|
|
50
106
|
);
|
|
51
107
|
impl(
|
|
52
|
-
|
|
108
|
+
forall(T : Type),
|
|
109
|
+
where(T <: Send),
|
|
110
|
+
Mutex(T),
|
|
53
111
|
Dispose(
|
|
54
112
|
dispose : (fn(self : Self) -> unit)({
|
|
55
|
-
return(__yo_mutex_destroy(&(self.
|
|
113
|
+
return(__yo_mutex_destroy(&(self._handle)));
|
|
56
114
|
})
|
|
57
115
|
)
|
|
58
116
|
);
|
|
117
|
+
|
|
59
118
|
export(
|
|
60
119
|
__YO_THREAD_SYNC_TYPE,
|
|
61
120
|
mutex_t,
|
|
62
|
-
Mutex
|
|
121
|
+
Mutex,
|
|
122
|
+
__MutexUnlocker
|
|
63
123
|
);
|
package/std/sync/once.yo
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
//! One-time initialization primitive.
|
|
2
|
+
pragma(Pragma.AllowUnsafe);
|
|
2
3
|
//!
|
|
3
4
|
//! # Example
|
|
4
5
|
//!
|
|
@@ -15,43 +16,48 @@
|
|
|
15
16
|
//! });
|
|
16
17
|
//! ```
|
|
17
18
|
{ Mutex } :: import("./mutex");
|
|
19
|
+
{ AtomicBool, MemoryOrder } :: import("./atomic");
|
|
20
|
+
{
|
|
21
|
+
atomic_bool,
|
|
22
|
+
atomic_load_explicit,
|
|
23
|
+
atomic_store_explicit,
|
|
24
|
+
memory_order_relaxed,
|
|
25
|
+
memory_order_acquire,
|
|
26
|
+
memory_order_release
|
|
27
|
+
} :: import("std/libc/stdatomic");
|
|
18
28
|
/// Execute a function exactly once, thread-safely.
|
|
19
29
|
/// Uses atomic reference counting for safe cross-thread sharing.
|
|
20
30
|
Once :: atomic(
|
|
21
31
|
object(
|
|
22
|
-
_done :
|
|
23
|
-
_mutex : Mutex
|
|
32
|
+
_done : atomic_bool,
|
|
33
|
+
_mutex : Mutex(bool)
|
|
24
34
|
)
|
|
25
35
|
);
|
|
26
36
|
impl(
|
|
27
37
|
Once,
|
|
28
38
|
// Create a new Once.
|
|
29
|
-
new : (fn() -> Self)(
|
|
30
|
-
Self(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
),
|
|
39
|
+
new : (fn() -> Self)({
|
|
40
|
+
v := Self(false, Mutex(bool).new(false));
|
|
41
|
+
unsafe(atomic_store_explicit(&(v._done), false, memory_order_release));
|
|
42
|
+
v
|
|
43
|
+
}),
|
|
35
44
|
// Execute the function exactly once.
|
|
36
45
|
// Subsequent calls are no-ops.
|
|
37
|
-
// Note: The fast-path read of _done is not strictly atomic, but since _done
|
|
38
|
-
// is only ever set from false to true and the double-check inside the lock
|
|
39
|
-
// prevents the function from running twice, this is safe in practice.
|
|
40
46
|
call : (fn(self : Self, f : Impl(Fn() -> unit)) -> unit)({
|
|
41
|
-
// Fast path: already done
|
|
47
|
+
// Fast path: already done (atomic_bool with Acquire load)
|
|
42
48
|
cond(
|
|
43
|
-
self._done => (),
|
|
49
|
+
(unsafe(atomic_load_explicit(&(self._done), memory_order_acquire)) == true) => (),
|
|
44
50
|
true => {
|
|
45
|
-
self._mutex.
|
|
51
|
+
self._mutex._raw_lock();
|
|
46
52
|
// Double-check after acquiring lock
|
|
47
53
|
cond(
|
|
48
|
-
self._done => {
|
|
49
|
-
self._mutex.
|
|
54
|
+
(unsafe(atomic_load_explicit(&(self._done), memory_order_relaxed)) == true) => {
|
|
55
|
+
self._mutex._raw_unlock();
|
|
50
56
|
},
|
|
51
57
|
true => {
|
|
52
58
|
f();
|
|
53
|
-
self._done
|
|
54
|
-
self._mutex.
|
|
59
|
+
unsafe(atomic_store_explicit(&(self._done), true, memory_order_release));
|
|
60
|
+
self._mutex._raw_unlock();
|
|
55
61
|
}
|
|
56
62
|
);
|
|
57
63
|
}
|
|
@@ -59,7 +65,7 @@ impl(
|
|
|
59
65
|
}),
|
|
60
66
|
// Check if the function has been called.
|
|
61
67
|
is_done : (fn(self : Self) -> bool)(
|
|
62
|
-
self._done
|
|
68
|
+
unsafe(atomic_load_explicit(&(self._done), memory_order_acquire)) == true
|
|
63
69
|
)
|
|
64
70
|
);
|
|
65
71
|
export(Once);
|
package/std/sync/rwlock.yo
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
//! Reader-writer lock allowing multiple concurrent readers or one exclusive writer.
|
|
2
|
+
pragma(Pragma.AllowUnsafe);
|
|
2
3
|
//!
|
|
3
4
|
//! # Example
|
|
4
5
|
//!
|
|
@@ -24,7 +25,7 @@ RwLock :: atomic(
|
|
|
24
25
|
object(
|
|
25
26
|
_readers : i32,
|
|
26
27
|
_writer : bool,
|
|
27
|
-
_mutex : Mutex,
|
|
28
|
+
_mutex : Mutex(bool),
|
|
28
29
|
_read_cv : Cond,
|
|
29
30
|
_write_cv : Cond
|
|
30
31
|
)
|
|
@@ -36,7 +37,7 @@ impl(
|
|
|
36
37
|
Self(
|
|
37
38
|
_readers : i32(0),
|
|
38
39
|
_writer : false,
|
|
39
|
-
_mutex : Mutex.new(),
|
|
40
|
+
_mutex : Mutex(bool).new(false),
|
|
40
41
|
_read_cv : Cond.new(),
|
|
41
42
|
_write_cv : Cond.new()
|
|
42
43
|
)
|
|
@@ -44,39 +45,41 @@ impl(
|
|
|
44
45
|
// Acquire a read lock. Blocks if a writer holds the lock.
|
|
45
46
|
// Multiple readers can hold the lock simultaneously.
|
|
46
47
|
read_lock : (fn(self : Self) -> unit)({
|
|
47
|
-
self._mutex.
|
|
48
|
+
self._mutex._raw_lock();
|
|
48
49
|
while(runtime(self._writer), {
|
|
49
|
-
self._read_cv.wait(self._mutex);
|
|
50
|
+
self._read_cv.wait(self._mutex._raw_handle_ptr());
|
|
50
51
|
});
|
|
51
52
|
self._readers = (self._readers + i32(1));
|
|
52
|
-
self._mutex.
|
|
53
|
+
self._mutex._raw_unlock();
|
|
53
54
|
}),
|
|
54
55
|
// Release a read lock.
|
|
55
56
|
read_unlock : (fn(self : Self) -> unit)({
|
|
56
|
-
self._mutex.
|
|
57
|
+
self._mutex._raw_lock();
|
|
57
58
|
self._readers = (self._readers - i32(1));
|
|
58
59
|
cond(
|
|
59
60
|
(self._readers == i32(0)) => self._write_cv.signal(),
|
|
60
61
|
true => ()
|
|
61
62
|
);
|
|
62
|
-
self._mutex.
|
|
63
|
+
self._mutex._raw_unlock();
|
|
63
64
|
}),
|
|
64
|
-
// Acquire a write lock. Blocks
|
|
65
|
+
// Acquire a write lock. Blocks if readers or a writer hold the lock.
|
|
65
66
|
write_lock : (fn(self : Self) -> unit)({
|
|
66
|
-
self._mutex.
|
|
67
|
+
self._mutex._raw_lock();
|
|
67
68
|
while(runtime(self._writer || (self._readers > i32(0))), {
|
|
68
|
-
self._write_cv.wait(self._mutex);
|
|
69
|
+
self._write_cv.wait(self._mutex._raw_handle_ptr());
|
|
69
70
|
});
|
|
70
71
|
self._writer = true;
|
|
71
|
-
self._mutex.
|
|
72
|
+
self._mutex._raw_unlock();
|
|
72
73
|
}),
|
|
73
74
|
// Release a write lock.
|
|
74
75
|
write_unlock : (fn(self : Self) -> unit)({
|
|
75
|
-
self._mutex.
|
|
76
|
+
self._mutex._raw_lock();
|
|
76
77
|
self._writer = false;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
cond(
|
|
79
|
+
(self._readers > i32(0)) => self._read_cv.broadcast(),
|
|
80
|
+
true => self._write_cv.signal()
|
|
81
|
+
);
|
|
82
|
+
self._mutex._raw_unlock();
|
|
80
83
|
})
|
|
81
84
|
);
|
|
82
85
|
export(RwLock);
|
package/std/sync/waitgroup.yo
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
//! Wait for a group of tasks to complete, similar to Go's `sync.WaitGroup`.
|
|
2
|
+
pragma(Pragma.AllowUnsafe);
|
|
2
3
|
//!
|
|
3
4
|
//! # Example
|
|
4
5
|
//!
|
|
@@ -20,12 +21,18 @@
|
|
|
20
21
|
//! ```
|
|
21
22
|
{ Mutex } :: import("./mutex");
|
|
22
23
|
{ Cond } :: import("./cond");
|
|
24
|
+
{ AtomicI32, MemoryOrder } :: import("./atomic");
|
|
23
25
|
/// Synchronization primitive that blocks until a counter reaches zero.
|
|
24
26
|
/// Uses atomic reference counting for safe cross-thread sharing.
|
|
27
|
+
///
|
|
28
|
+
/// The internal counter is an atomic so `count()` can read without
|
|
29
|
+
/// acquiring the mutex (matches Rust's mpsc query-method idiom). Writes
|
|
30
|
+
/// to the counter still happen under the mutex so the `_count == 0`
|
|
31
|
+
/// observation that triggers `broadcast()` is serialized with `wait()`.
|
|
25
32
|
WaitGroup :: atomic(
|
|
26
33
|
object(
|
|
27
|
-
_count :
|
|
28
|
-
_mutex : Mutex,
|
|
34
|
+
_count : AtomicI32,
|
|
35
|
+
_mutex : Mutex(bool),
|
|
29
36
|
_cv : Cond
|
|
30
37
|
)
|
|
31
38
|
);
|
|
@@ -34,24 +41,26 @@ impl(
|
|
|
34
41
|
// Create a new WaitGroup with count 0.
|
|
35
42
|
new : (fn() -> Self)(
|
|
36
43
|
Self(
|
|
37
|
-
_count : i32(0),
|
|
38
|
-
_mutex : Mutex.new(),
|
|
44
|
+
_count : AtomicI32(i32(0)),
|
|
45
|
+
_mutex : Mutex(bool).new(false),
|
|
39
46
|
_cv : Cond.new()
|
|
40
47
|
)
|
|
41
48
|
),
|
|
42
49
|
// Add delta to the counter (can be negative).
|
|
43
50
|
// If the counter becomes zero, all waiters are woken.
|
|
44
51
|
add : (fn(self : Self, delta : i32) -> unit)({
|
|
45
|
-
self._mutex.
|
|
46
|
-
|
|
52
|
+
self._mutex._raw_lock();
|
|
53
|
+
new_count := (self._count.load(MemoryOrder.Relaxed) + delta);
|
|
47
54
|
cond(
|
|
48
|
-
(
|
|
49
|
-
self._count
|
|
55
|
+
(new_count <= i32(0)) => {
|
|
56
|
+
self._count.store(i32(0), MemoryOrder.Release);
|
|
50
57
|
self._cv.broadcast();
|
|
51
58
|
},
|
|
52
|
-
true =>
|
|
59
|
+
true => {
|
|
60
|
+
self._count.store(new_count, MemoryOrder.Release);
|
|
61
|
+
}
|
|
53
62
|
);
|
|
54
|
-
self._mutex.
|
|
63
|
+
self._mutex._raw_unlock();
|
|
55
64
|
}),
|
|
56
65
|
// Decrement the counter by one (equivalent to add(-1)).
|
|
57
66
|
done : (fn(self : Self) -> unit)(
|
|
@@ -59,15 +68,15 @@ impl(
|
|
|
59
68
|
),
|
|
60
69
|
// Block until the counter reaches zero.
|
|
61
70
|
wait : (fn(self : Self) -> unit)({
|
|
62
|
-
self._mutex.
|
|
63
|
-
while(runtime(self._count > i32(0)), {
|
|
64
|
-
self._cv.wait(self._mutex);
|
|
71
|
+
self._mutex._raw_lock();
|
|
72
|
+
while(runtime(self._count.load(MemoryOrder.Acquire) > i32(0)), {
|
|
73
|
+
self._cv.wait(self._mutex._raw_handle_ptr());
|
|
65
74
|
});
|
|
66
|
-
self._mutex.
|
|
75
|
+
self._mutex._raw_unlock();
|
|
67
76
|
}),
|
|
68
|
-
// Get the current count (
|
|
77
|
+
// Get the current count (lock-free atomic load).
|
|
69
78
|
count : (fn(self : Self) -> i32)(
|
|
70
|
-
self._count
|
|
79
|
+
self._count.load(MemoryOrder.Acquire)
|
|
71
80
|
)
|
|
72
81
|
);
|
|
73
82
|
export(WaitGroup);
|