@shd101wyy/yo 0.1.29 → 0.1.31

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 (186) hide show
  1. package/.github/skills/yo-async-effects/SKILL.md +3 -3
  2. package/.github/skills/yo-async-effects/async-effects-recipes.md +19 -11
  3. package/.github/skills/yo-core-patterns/core-patterns-cheatsheet.md +33 -13
  4. package/.github/skills/yo-project-workflow/workflow-cheatsheet.md +1 -1
  5. package/.github/skills/yo-syntax/syntax-cheatsheet.md +59 -21
  6. package/README.md +4 -3
  7. package/out/cjs/index.cjs +964 -710
  8. package/out/cjs/yo-cli.cjs +1119 -855
  9. package/out/cjs/yo-lsp.cjs +1003 -749
  10. package/out/esm/index.mjs +964 -710
  11. package/out/types/src/codegen/exprs/async.d.ts +2 -0
  12. package/out/types/src/codegen/exprs/await.d.ts +1 -0
  13. package/out/types/src/codegen/exprs/closures.d.ts +4 -0
  14. package/out/types/src/codegen/functions/context.d.ts +6 -0
  15. package/out/types/src/codegen/index.d.ts +1 -1
  16. package/out/types/src/compiler-utils.d.ts +1 -1
  17. package/out/types/src/env.d.ts +2 -0
  18. package/out/types/src/evaluator/builtins/pragma.d.ts +9 -0
  19. package/out/types/src/evaluator/builtins/unsafe.d.ts +8 -0
  20. package/out/types/src/evaluator/context.d.ts +2 -0
  21. package/out/types/src/evaluator/index.d.ts +1 -1
  22. package/out/types/src/evaluator/memory-safety.d.ts +14 -0
  23. package/out/types/src/evaluator/types/flowability.d.ts +6 -0
  24. package/out/types/src/evaluator/utils/closure.d.ts +2 -1
  25. package/out/types/src/evaluator/utils.d.ts +3 -0
  26. package/out/types/src/evaluator/values/impl.d.ts +14 -0
  27. package/out/types/src/expr-traversal.d.ts +1 -0
  28. package/out/types/src/expr.d.ts +4 -1
  29. package/out/types/src/public-safe-report.d.ts +19 -0
  30. package/out/types/src/tests/comptime-ref-gate.test.d.ts +1 -0
  31. package/out/types/src/tests/pragma-validation.test.d.ts +1 -0
  32. package/out/types/src/tests/public-safe-report.test.d.ts +1 -0
  33. package/out/types/src/tests/thread-safety-codegen.test.d.ts +1 -0
  34. package/out/types/src/tests/type-representation-pointer.test.d.ts +1 -0
  35. package/out/types/src/tests/unsafe-gate.test.d.ts +1 -0
  36. package/out/types/src/tests/unsafe-report-classify.test.d.ts +1 -0
  37. package/out/types/src/types/definitions.d.ts +2 -0
  38. package/out/types/src/types/utils.d.ts +4 -0
  39. package/out/types/src/unsafe-report.d.ts +29 -0
  40. package/out/types/src/value.d.ts +1 -0
  41. package/out/types/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +1 -1
  43. package/scripts/add-pragma-for-pointer-decls.ts +134 -0
  44. package/scripts/add-pragma.ts +58 -0
  45. package/scripts/migrate-amp-method-calls.ts +186 -0
  46. package/scripts/migrate-clone-calls.ts +93 -0
  47. package/scripts/migrate-get-unwrap.ts +166 -0
  48. package/scripts/migrate-index-patterns.ts +210 -0
  49. package/scripts/migrate-index-trait.ts +142 -0
  50. package/scripts/migrate-iterator.ts +150 -0
  51. package/scripts/migrate-self-ptr.ts +220 -0
  52. package/scripts/migrate-skip-pragmas.ts +109 -0
  53. package/scripts/migrate-tostring.ts +134 -0
  54. package/scripts/trim-pragma.ts +130 -0
  55. package/scripts/wrap-extern-calls.ts +161 -0
  56. package/std/alg/hash.yo +3 -2
  57. package/std/allocator.yo +6 -5
  58. package/std/async.yo +2 -2
  59. package/std/build.yo +5 -2
  60. package/std/collections/array_list.yo +59 -40
  61. package/std/collections/btree_map.yo +19 -18
  62. package/std/collections/deque.yo +9 -8
  63. package/std/collections/hash_map.yo +101 -13
  64. package/std/collections/hash_set.yo +5 -4
  65. package/std/collections/linked_list.yo +39 -4
  66. package/std/collections/ordered_map.yo +3 -3
  67. package/std/collections/priority_queue.yo +14 -13
  68. package/std/crypto/md5.yo +2 -1
  69. package/std/crypto/random.yo +16 -15
  70. package/std/crypto/sha256.yo +2 -1
  71. package/std/encoding/base64.yo +14 -14
  72. package/std/encoding/hex.yo +3 -3
  73. package/std/encoding/json.yo +59 -10
  74. package/std/encoding/punycode.yo +24 -23
  75. package/std/encoding/toml.yo +4 -3
  76. package/std/encoding/utf16.yo +2 -2
  77. package/std/env.yo +43 -28
  78. package/std/error.yo +6 -6
  79. package/std/fmt/display.yo +2 -2
  80. package/std/fmt/index.yo +6 -5
  81. package/std/fmt/to_string.yo +39 -38
  82. package/std/fmt/writer.yo +9 -8
  83. package/std/fs/dir.yo +34 -33
  84. package/std/fs/file.yo +52 -51
  85. package/std/fs/metadata.yo +10 -9
  86. package/std/fs/temp.yo +24 -13
  87. package/std/fs/walker.yo +10 -9
  88. package/std/gc.yo +1 -0
  89. package/std/glob.yo +7 -7
  90. package/std/http/client.yo +15 -14
  91. package/std/http/http.yo +6 -6
  92. package/std/http/index.yo +1 -1
  93. package/std/imm/list.yo +34 -1
  94. package/std/imm/map.yo +2 -1
  95. package/std/imm/set.yo +1 -0
  96. package/std/imm/sorted_map.yo +2 -1
  97. package/std/imm/sorted_set.yo +1 -0
  98. package/std/imm/string.yo +27 -23
  99. package/std/imm/vec.yo +18 -2
  100. package/std/io/reader.yo +2 -1
  101. package/std/io/writer.yo +3 -2
  102. package/std/libc/assert.yo +1 -0
  103. package/std/libc/ctype.yo +1 -0
  104. package/std/libc/dirent.yo +1 -0
  105. package/std/libc/errno.yo +1 -0
  106. package/std/libc/fcntl.yo +1 -0
  107. package/std/libc/float.yo +1 -0
  108. package/std/libc/limits.yo +1 -0
  109. package/std/libc/math.yo +1 -0
  110. package/std/libc/signal.yo +1 -0
  111. package/std/libc/stdatomic.yo +251 -1
  112. package/std/libc/stdint.yo +1 -0
  113. package/std/libc/stdio.yo +1 -0
  114. package/std/libc/stdlib.yo +1 -0
  115. package/std/libc/string.yo +1 -0
  116. package/std/libc/sys/stat.yo +1 -0
  117. package/std/libc/time.yo +1 -0
  118. package/std/libc/unistd.yo +1 -0
  119. package/std/libc/wctype.yo +1 -0
  120. package/std/libc/windows.yo +2 -0
  121. package/std/log.yo +7 -6
  122. package/std/net/addr.yo +5 -4
  123. package/std/net/dns.yo +7 -6
  124. package/std/net/errors.yo +8 -8
  125. package/std/net/tcp.yo +19 -18
  126. package/std/net/udp.yo +13 -12
  127. package/std/os/signal.yo +3 -3
  128. package/std/path.yo +1 -0
  129. package/std/prelude.yo +398 -184
  130. package/std/process/command.yo +40 -23
  131. package/std/process/index.yo +2 -1
  132. package/std/regex/compiler.yo +10 -9
  133. package/std/regex/index.yo +41 -41
  134. package/std/regex/match.yo +2 -2
  135. package/std/regex/parser.yo +21 -21
  136. package/std/regex/vm.yo +42 -41
  137. package/std/string/rune.yo +4 -0
  138. package/std/string/string.yo +95 -40
  139. package/std/string/string_builder.yo +9 -9
  140. package/std/string/unicode.yo +50 -49
  141. package/std/sync/atomic.yo +557 -0
  142. package/std/sync/channel.yo +59 -43
  143. package/std/sync/cond.yo +12 -7
  144. package/std/sync/mutex.yo +79 -18
  145. package/std/sync/once.yo +25 -19
  146. package/std/sync/rwlock.yo +18 -15
  147. package/std/sync/waitgroup.yo +25 -16
  148. package/std/sys/advise.yo +1 -0
  149. package/std/sys/bufio/buf_reader.yo +17 -16
  150. package/std/sys/bufio/buf_writer.yo +10 -9
  151. package/std/sys/clock.yo +1 -0
  152. package/std/sys/copy.yo +1 -0
  153. package/std/sys/dir.yo +10 -9
  154. package/std/sys/dns.yo +6 -5
  155. package/std/sys/errors.yo +11 -11
  156. package/std/sys/events.yo +1 -0
  157. package/std/sys/externs.yo +38 -37
  158. package/std/sys/file.yo +17 -16
  159. package/std/sys/future.yo +4 -3
  160. package/std/sys/iov.yo +1 -0
  161. package/std/sys/mmap.yo +1 -0
  162. package/std/sys/path.yo +1 -0
  163. package/std/sys/perm.yo +2 -1
  164. package/std/sys/pipe.yo +1 -0
  165. package/std/sys/process.yo +5 -4
  166. package/std/sys/signal.yo +1 -0
  167. package/std/sys/socketpair.yo +1 -0
  168. package/std/sys/sockinfo.yo +1 -0
  169. package/std/sys/statfs.yo +2 -1
  170. package/std/sys/statx.yo +1 -0
  171. package/std/sys/sysinfo.yo +1 -0
  172. package/std/sys/tcp.yo +15 -14
  173. package/std/sys/temp.yo +1 -0
  174. package/std/sys/time.yo +2 -1
  175. package/std/sys/timer.yo +6 -6
  176. package/std/sys/tty.yo +2 -1
  177. package/std/sys/udp.yo +13 -12
  178. package/std/sys/unix.yo +12 -11
  179. package/std/testing/bench.yo +4 -3
  180. package/std/thread.yo +7 -6
  181. package/std/time/datetime.yo +18 -15
  182. package/std/time/duration.yo +11 -10
  183. package/std/time/instant.yo +4 -4
  184. package/std/time/sleep.yo +1 -0
  185. package/std/url/index.yo +3 -3
  186. package/std/worker.yo +4 -3
@@ -1,5 +1,5 @@
1
1
  //! Bounded multi-producer multi-consumer (MPMC) channel.
2
- //! Uses blocking send/recv via condition variables — does not require IO.
2
+ //! Uses blocking send/recv via condition variables — does not require Io.
3
3
  //!
4
4
  //! Channel uses `atomic object` with atomic reference counting, so it can be
5
5
  //! safely shared across threads without `Arc` wrapping.
@@ -18,10 +18,12 @@
18
18
  //! assert((val.unwrap() == i32(42)), "received value");
19
19
  //! t.join();
20
20
  //! ```
21
+ pragma(Pragma.AllowUnsafe);
21
22
  { GlobalAllocator } :: import("../allocator");
22
23
  { malloc, free } :: GlobalAllocator;
23
24
  { Mutex } :: import("./mutex");
24
25
  { Cond } :: import("./cond");
26
+ { AtomicBool, AtomicUsize, MemoryOrder } :: import("./atomic");
25
27
  /// Thread-safe bounded channel for passing values between threads.
26
28
  /// Uses atomic reference counting — implements `Send` and can be
27
29
  /// safely shared across threads without `Arc` wrapping.
@@ -31,12 +33,12 @@ Channel :: (fn(comptime(T) : Type, where(T <: Send)) -> comptime(Type))(
31
33
  object(
32
34
  _data : *(T),
33
35
  _head : usize,
34
- _len : usize,
36
+ _len : AtomicUsize,
35
37
  _capacity : usize,
36
- _mutex : Mutex,
38
+ _mutex : Mutex(bool),
37
39
  _not_empty : Cond,
38
40
  _not_full : Cond,
39
- _closed : bool
41
+ _closed : AtomicBool
40
42
  )
41
43
  )
42
44
  );
@@ -53,12 +55,12 @@ impl(
53
55
  Self(
54
56
  _data : ptr,
55
57
  _head : usize(0),
56
- _len : usize(0),
58
+ _len : AtomicUsize(usize(0)),
57
59
  _capacity : capacity,
58
- _mutex : Mutex.new(),
60
+ _mutex : Mutex(bool).new(false),
59
61
  _not_empty : Cond.new(),
60
62
  _not_full : Cond.new(),
61
- _closed : false
63
+ _closed : AtomicBool(false)
62
64
  )
63
65
  );
64
66
  }),
@@ -66,38 +68,45 @@ impl(
66
68
  /// Blocks if the channel is full until space becomes available.
67
69
  /// Returns .Ok(()) on success, .Err(()) if the channel is closed.
68
70
  send : (fn(self : Self, value : T) -> Result(unit, unit))({
69
- self._mutex.lock();
71
+ self._mutex._raw_lock();
70
72
  // Wait while buffer is full and channel is not closed
71
- while(runtime((self._len >= self._capacity) && (!(self._closed))), {
72
- self._not_full.wait(self._mutex);
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());
73
78
  });
74
79
  cond(
75
- self._closed => {
76
- self._mutex.unlock();
80
+ self._closed.load(MemoryOrder.Relaxed) => {
81
+ self._mutex._raw_unlock();
77
82
  return(.Err(()));
78
83
  },
79
84
  true => ()
80
85
  );
81
86
  // Write value at tail position
82
- (tail : usize) = ((self._head + self._len) % self._capacity);
87
+ (current_len : usize) = self._len.load(MemoryOrder.Relaxed);
88
+ (tail : usize) = ((self._head + current_len) % self._capacity);
83
89
  consume((self._data &+ tail).* = value);
84
- self._len = (self._len + usize(1));
90
+ self._len.store(current_len + usize(1), MemoryOrder.Release);
85
91
  self._not_empty.signal();
86
- self._mutex.unlock();
92
+ self._mutex._raw_unlock();
87
93
  return(.Ok(()));
88
94
  }),
89
95
  /// Receive a value from the channel.
90
96
  /// Blocks if the channel is empty until a value is available.
91
97
  /// Returns .Some(value) on success, .None if the channel is closed and empty.
92
98
  recv : (fn(self : Self) -> ?(T))({
93
- self._mutex.lock();
99
+ self._mutex._raw_lock();
94
100
  // Wait while buffer is empty and channel is not closed
95
- while(runtime((self._len == usize(0)) && (!(self._closed))), {
96
- self._not_empty.wait(self._mutex);
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());
97
106
  });
98
107
  cond(
99
- (self._len == usize(0)) => {
100
- self._mutex.unlock();
108
+ (self._len.load(MemoryOrder.Relaxed) == usize(0)) => {
109
+ self._mutex._raw_unlock();
101
110
  return(.None);
102
111
  },
103
112
  true => ()
@@ -107,36 +116,40 @@ impl(
107
116
  (val : T) = element_ptr.*;
108
117
  unsafe.drop(element_ptr.*);
109
118
  self._head = ((self._head + usize(1)) % self._capacity);
110
- self._len = (self._len - usize(1));
119
+ self._len.store(self._len.load(MemoryOrder.Relaxed) - usize(1), MemoryOrder.Release);
111
120
  self._not_full.signal();
112
- self._mutex.unlock();
121
+ self._mutex._raw_unlock();
113
122
  return(.Some(val));
114
123
  }),
115
124
  /// Try to send a value without blocking.
116
125
  /// Returns .Ok(()) if sent, .Err(()) if full or closed.
117
126
  try_send : (fn(self : Self, value : T) -> Result(unit, unit))({
118
- self._mutex.lock();
127
+ self._mutex._raw_lock();
119
128
  cond(
120
- (self._closed || (self._len >= self._capacity)) => {
121
- self._mutex.unlock();
129
+ (
130
+ self._closed.load(MemoryOrder.Relaxed)
131
+ || (self._len.load(MemoryOrder.Relaxed) >= self._capacity)
132
+ ) => {
133
+ self._mutex._raw_unlock();
122
134
  return(.Err(()));
123
135
  },
124
136
  true => ()
125
137
  );
126
- (tail : usize) = ((self._head + self._len) % self._capacity);
138
+ (current_len : usize) = self._len.load(MemoryOrder.Relaxed);
139
+ (tail : usize) = ((self._head + current_len) % self._capacity);
127
140
  consume((self._data &+ tail).* = value);
128
- self._len = (self._len + usize(1));
141
+ self._len.store(current_len + usize(1), MemoryOrder.Release);
129
142
  self._not_empty.signal();
130
- self._mutex.unlock();
143
+ self._mutex._raw_unlock();
131
144
  return(.Ok(()));
132
145
  }),
133
146
  /// Try to receive a value without blocking.
134
147
  /// Returns .Some(value) if available, .None if empty.
135
148
  try_recv : (fn(self : Self) -> ?(T))({
136
- self._mutex.lock();
149
+ self._mutex._raw_lock();
137
150
  cond(
138
- (self._len == usize(0)) => {
139
- self._mutex.unlock();
151
+ (self._len.load(MemoryOrder.Relaxed) == usize(0)) => {
152
+ self._mutex._raw_unlock();
140
153
  return(.None);
141
154
  },
142
155
  true => ()
@@ -145,32 +158,34 @@ impl(
145
158
  (val : T) = element_ptr.*;
146
159
  unsafe.drop(element_ptr.*);
147
160
  self._head = ((self._head + usize(1)) % self._capacity);
148
- self._len = (self._len - usize(1));
161
+ self._len.store(self._len.load(MemoryOrder.Relaxed) - usize(1), MemoryOrder.Release);
149
162
  self._not_full.signal();
150
- self._mutex.unlock();
163
+ self._mutex._raw_unlock();
151
164
  return(.Some(val));
152
165
  }),
153
166
  /// Close the channel. No more values can be sent.
154
167
  /// Pending recv() calls will return .None once the buffer is drained.
155
168
  /// Pending send() calls will return .Err(()).
156
169
  close : (fn(self : Self) -> unit)({
157
- self._mutex.lock();
158
- self._closed = true;
170
+ self._mutex._raw_lock();
171
+ self._closed.store(true, MemoryOrder.Release);
159
172
  self._not_empty.broadcast();
160
173
  self._not_full.broadcast();
161
- self._mutex.unlock();
174
+ self._mutex._raw_unlock();
162
175
  }),
163
- /// Check if the channel is closed.
176
+ /// Check if the channel is closed (lock-free atomic load).
164
177
  is_closed : (fn(self : Self) -> bool)(
165
- self._closed
178
+ self._closed.load(MemoryOrder.Acquire)
166
179
  ),
167
- /// 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.
168
183
  len : (fn(self : Self) -> usize)(
169
- self._len
184
+ self._len.load(MemoryOrder.Acquire)
170
185
  ),
171
- /// Check if the channel buffer is empty.
186
+ /// Check if the channel buffer is empty (lock-free atomic load).
172
187
  is_empty : (fn(self : Self) -> bool)(
173
- self._len == usize(0)
188
+ self._len.load(MemoryOrder.Acquire) == usize(0)
174
189
  )
175
190
  );
176
191
  // Dispose: drop remaining elements and free the buffer
@@ -180,8 +195,9 @@ impl(
180
195
  where(T <: Send),
181
196
  Dispose(
182
197
  dispose : (fn(self : Self) -> unit)({
198
+ (current_len : usize) = self._len.load(MemoryOrder.Relaxed);
183
199
  (i : usize) = usize(0);
184
- while(runtime(i < self._len), {
200
+ while(runtime(i < current_len), {
185
201
  (idx : usize) = ((self._head + i) % self._capacity);
186
202
  unsafe.drop((self._data &+ idx).*);
187
203
  i = (i + usize(1));
package/std/sync/cond.yo CHANGED
@@ -1,5 +1,6 @@
1
1
  //! Condition variable for thread synchronization.
2
- { __YO_THREAD_SYNC_TYPE, mutex_t, Mutex } :: import("./mutex");
2
+ pragma(Pragma.AllowUnsafe);
3
+ { __YO_THREAD_SYNC_TYPE, mutex_t } :: import("./mutex");
3
4
  extern(
4
5
  "Yo",
5
6
  __YO_COND_TYPE : Type,
@@ -9,6 +10,10 @@ extern(
9
10
  __yo_cond_broadcast : (fn(cv : *(__YO_COND_TYPE)) -> unit),
10
11
  __yo_cond_destroy : (fn(cv : *(__YO_COND_TYPE)) -> unit)
11
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.
12
17
  impl(__YO_COND_TYPE, Send());
13
18
  impl(__YO_COND_TYPE, Acyclic());
14
19
  /// Low-level condition variable (manual lifetime via `destroy`).
@@ -20,16 +25,16 @@ impl(
20
25
  new : (fn() -> Self)({
21
26
  return(Self(__yo_cond_create()));
22
27
  }),
23
- wait : (fn(self : *(Self), mutex : *(mutex_t)) -> unit)({
28
+ wait : (fn(ref(self) : Self, ref(mutex) : mutex_t) -> unit)({
24
29
  return(__yo_cond_wait(&(self.cv), &(mutex.mutex)));
25
30
  }),
26
- signal : (fn(self : *(Self)) -> unit)({
31
+ signal : (fn(ref(self) : Self) -> unit)({
27
32
  return(__yo_cond_signal(&(self.cv)));
28
33
  }),
29
- broadcast : (fn(self : *(Self)) -> unit)({
34
+ broadcast : (fn(ref(self) : Self) -> unit)({
30
35
  return(__yo_cond_broadcast(&(self.cv)));
31
36
  }),
32
- destroy : (fn(self : *(Self)) -> unit)({
37
+ destroy : (fn(ref(self) : Self) -> unit)({
33
38
  return(__yo_cond_destroy(&(self.cv)));
34
39
  })
35
40
  );
@@ -45,8 +50,8 @@ impl(
45
50
  new : (fn() -> Self)({
46
51
  return(Self(__yo_cond_create()));
47
52
  }),
48
- wait : (fn(self : Self, mutex : Mutex) -> unit)({
49
- return(__yo_cond_wait(&(self.cv), &(mutex.mutex)));
53
+ wait : (fn(self : Self, handle : *(__YO_THREAD_SYNC_TYPE)) -> unit)({
54
+ return(__yo_cond_wait(&(self.cv), handle));
50
55
  }),
51
56
  signal : (fn(self : Self) -> unit)({
52
57
  return(__yo_cond_signal(&(self.cv)));
package/std/sync/mutex.yo CHANGED
@@ -1,4 +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`.
8
+ pragma(Pragma.AllowUnsafe);
9
+
2
10
  extern(
3
11
  "Yo",
4
12
  __YO_THREAD_SYNC_TYPE : Type,
@@ -7,8 +15,13 @@ extern(
7
15
  __yo_mutex_unlock : (fn(mutex : *(__YO_THREAD_SYNC_TYPE)) -> unit),
8
16
  __yo_mutex_destroy : (fn(mutex : *(__YO_THREAD_SYNC_TYPE)) -> unit)
9
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.
10
22
  impl(__YO_THREAD_SYNC_TYPE, Send());
11
23
  impl(__YO_THREAD_SYNC_TYPE, Acyclic());
24
+
12
25
  /// Low-level mutex (manual lifetime via `destroy`).
13
26
  mutex_t :: newtype(
14
27
  mutex : __YO_THREAD_SYNC_TYPE
@@ -18,45 +31,93 @@ impl(
18
31
  new : (fn() -> Self)({
19
32
  return(Self(__yo_mutex_create()));
20
33
  }),
21
- lock : (fn(self : *(Self)) -> unit)({
34
+ lock : (fn(ref(self) : Self) -> unit)({
22
35
  return(__yo_mutex_lock(&(self.mutex)));
23
36
  }),
24
- unlock : (fn(self : *(Self)) -> unit)({
37
+ unlock : (fn(ref(self) : Self) -> unit)({
25
38
  return(__yo_mutex_unlock(&(self.mutex)));
26
39
  }),
27
- destroy : (fn(self : *(Self)) -> unit)({
40
+ destroy : (fn(ref(self) : Self) -> unit)({
28
41
  return(__yo_mutex_destroy(&(self.mutex)));
29
42
  })
30
43
  );
31
- /// Reference-counted mutex with automatic cleanup via `Dispose`.
32
- /// Uses atomic reference counting for safe cross-thread sharing.
33
- Mutex :: atomic(
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))(
34
62
  object(
35
- mutex : __YO_THREAD_SYNC_TYPE
63
+ _mutex : Mutex(T)
36
64
  )
37
65
  );
38
66
  impl(
39
- Mutex,
40
- new : (fn() -> Self)({
41
- return(Self(__yo_mutex_create()));
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));
42
83
  }),
43
- lock : (fn(self : Self) -> unit)({
44
- return(__yo_mutex_lock(&(self.mutex)));
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)
45
94
  }),
46
- unlock : (fn(self : Self) -> unit)({
47
- return(__yo_mutex_unlock(&(self.mutex)));
48
- })
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
+ )
49
106
  );
50
107
  impl(
51
- Mutex,
108
+ forall(T : Type),
109
+ where(T <: Send),
110
+ Mutex(T),
52
111
  Dispose(
53
112
  dispose : (fn(self : Self) -> unit)({
54
- return(__yo_mutex_destroy(&(self.mutex)));
113
+ return(__yo_mutex_destroy(&(self._handle)));
55
114
  })
56
115
  )
57
116
  );
117
+
58
118
  export(
59
119
  __YO_THREAD_SYNC_TYPE,
60
120
  mutex_t,
61
- Mutex
121
+ Mutex,
122
+ __MutexUnlocker
62
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 : bool,
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
- _done : false,
32
- _mutex : Mutex.new()
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.lock();
51
+ self._mutex._raw_lock();
46
52
  // Double-check after acquiring lock
47
53
  cond(
48
- self._done => {
49
- self._mutex.unlock();
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 = true;
54
- self._mutex.unlock();
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);
@@ -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.lock();
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.unlock();
53
+ self._mutex._raw_unlock();
53
54
  }),
54
55
  // Release a read lock.
55
56
  read_unlock : (fn(self : Self) -> unit)({
56
- self._mutex.lock();
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.unlock();
63
+ self._mutex._raw_unlock();
63
64
  }),
64
- // Acquire a write lock. Blocks until all readers and writers release.
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.lock();
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.unlock();
72
+ self._mutex._raw_unlock();
72
73
  }),
73
74
  // Release a write lock.
74
75
  write_unlock : (fn(self : Self) -> unit)({
75
- self._mutex.lock();
76
+ self._mutex._raw_lock();
76
77
  self._writer = false;
77
- self._read_cv.broadcast();
78
- self._write_cv.signal();
79
- self._mutex.unlock();
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);
@@ -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 : i32,
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.lock();
46
- self._count = (self._count + delta);
52
+ self._mutex._raw_lock();
53
+ new_count := (self._count.load(MemoryOrder.Relaxed) + delta);
47
54
  cond(
48
- (self._count <= i32(0)) => {
49
- self._count = i32(0);
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.unlock();
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.lock();
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.unlock();
75
+ self._mutex._raw_unlock();
67
76
  }),
68
- // Get the current count (for debugging).
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);
package/std/sys/advise.yo CHANGED
@@ -2,6 +2,7 @@
2
2
  //!
3
3
  //! Advisory wrappers for file and memory access pattern hints.
4
4
  //! Returns 0 on success, -errno / negative platform error on failure.
5
+ pragma(Pragma.AllowUnsafe);
5
6
  { __yo_sync_fadvise, __yo_sync_madvise } :: import("./externs.yo");
6
7
  POSIX_FADV_NORMAL :: i32(0);
7
8
  POSIX_FADV_RANDOM :: i32(1);