vxx2 / vlib / builtin / closure / closure.c.v
872 lines · 810 sloc · 25.6 KB · 1a2d0e5b70d6dd1baaa60290f2446e9ece4324bf
Raw
1@[has_globals]
2module closure
3
4// Inspired from Chris Wellons's work
5// https://nullprogram.com/blog/2017/01/08/
6
7const assumed_page_size = int(0x4000)
8const ppc64_architecture = int(11)
9
10type ClosureGetDataFn = fn () voidptr
11
12type ClosureInitFn = fn ()
13
14struct ClosurePage {
15mut:
16 next &ClosurePage = unsafe { nil }
17 exec_page_start voidptr
18}
19
20struct ClosureLiveInfo {
21mut:
22 ctx voidptr
23 owns_data bool
24 generation u64
25}
26
27struct ClosureLifetimeRecord {
28mut:
29 exec_ptr voidptr
30 generation u64
31}
32
33struct ClosureLifetimeFrame {
34mut:
35 start int
36 end int
37}
38
39@[heap]
40struct ClosureLifetimeState {
41mut:
42 owner_thread u64
43 active bool
44 disposed bool
45 suspended int
46 frame_start int
47 frame_gen u64
48 generation u64
49 frame_generation u64
50 records []ClosureLifetimeRecord
51 frames []ClosureLifetimeFrame
52 next_free &ClosureLifetimeState = unsafe { nil }
53}
54
55// Lifetime owns a set of tracked closure callbacks that can be reclaimed together.
56pub struct Lifetime {
57mut:
58 state &ClosureLifetimeState = unsafe { nil }
59 generation u64
60 disposed bool
61}
62
63struct FrameToken {
64mut:
65 state &ClosureLifetimeState = unsafe { nil }
66 thread_id u64
67 state_generation u64
68 generation u64
69}
70
71@[heap]
72struct Closure {
73 ClosureMutex
74mut:
75 closure_ptr voidptr
76 closure_get_data ClosureGetDataFn = unsafe { nil }
77 closure_cap int
78 free_closure_ptr voidptr
79 pages &ClosurePage = unsafe { nil }
80 v_page_size int = int(0x4000)
81 live map[voidptr]ClosureLiveInfo
82 active_lifetimes map[u64]&ClosureLifetimeState
83 next_generation u64
84 free_lifetime_states &ClosureLifetimeState = unsafe { nil }
85 next_lifetime_generation u64
86 lifetime_state_allocs u64
87}
88
89__global g_closure = Closure{}
90
91enum MemoryProtectAtrr {
92 read_exec
93 read_write
94}
95
96// Keep this runtime check bootstrap-compatible. Older compilers can not parse `$if ppc64` yet.
97@[inline]
98fn is_ppc64() bool {
99 $if big_endian {
100 return C.__V_architecture == ppc64_architecture
101 } $else {
102 return false
103 }
104}
105
106// refer to https://godbolt.org/z/r7P3EYv6c for a complete assembly
107//
108// NOTE: Keep the first branch as the longest byte sequence. In translated/bootstrap C mode
109// (`vc/v.c`), V emits a fixed C array whose size is inferred from the first branch.
110// The final `big_endian` branch maps to ppc64 here, since the supported big-endian
111// closure targets handled above are s390x and sparc64.
112// vfmt off
113pub const closure_thunk = $if ppc64le {
114 [
115 u8(0xa6), 0x02, 0x08, 0x7c, // mflr %r0
116 0x05, 0x00, 0x00, 0x48, // bl here
117 0xa6, 0x02, 0xc8, 0x7d, // here: mflr %r14
118 0xf8, 0xbf, 0xce, 0x39, // addi %r14, %r14, -16392
119 0x00, 0x00, 0xce, 0xc9, // lfd %f14, 0(%r14)
120 0x08, 0x00, 0xce, 0xe9, // ld %r14, 8(%r14)
121 0x78, 0x73, 0xcc, 0x7d, // mr %r12, %r14
122 0xa6, 0x03, 0x08, 0x7c, // mtlr %r0
123 0xa6, 0x03, 0xc9, 0x7d, // mtctr %r14
124 0x20, 0x04, 0x80, 0x4e, // bctr
125 ]!
126} $else $if !ppc64le && !amd64 && !i386 && !arm64 && !arm32 && !rv64 && !rv32 && !s390x && !loongarch64 {
127 // ppc (32-bit PowerPC) - expressed as negation of all other arches for bootstrap compat
128 [
129 u8(0x7c), 0x08, 0x02, 0xa6, // mflr %r0
130 0x48, 0x00, 0x00, 0x05, // bl here
131 0x7d, 0x88, 0x02, 0xa6, // here: mflr %r12
132 0x39, 0x8c, 0xbf, 0xf8, // addi %r12, %r12, -16392
133 0xc9, 0xcc, 0x00, 0x00, // lfd %f14, 0(%r12)
134 0x81, 0x8c, 0x00, 0x04, // lwz %r12, 4(%r12)
135 0x7c, 0x08, 0x03, 0xa6, // mtlr %r0
136 0x7d, 0x89, 0x03, 0xa6, // mtctr %r12
137 0x4e, 0x80, 0x04, 0x20, // bctr
138 ]!
139} $else $if amd64 {
140 [
141 u8(0xF3), 0x44, 0x0F, 0x7E, 0x3D, 0xF7, 0xBF, 0xFF, 0xFF, // movq xmm15, QWORD PTR [rip - userdata]
142 0xFF, 0x25, 0xF9, 0xBF, 0xFF, 0xFF // jmp QWORD PTR [rip - fn]
143 ]!
144} $else $if i386 {
145 [
146 u8(0xe8), 0x00, 0x00, 0x00, 0x00, // call here
147 // here:
148 0x59, // pop ecx
149 0x66, 0x0F, 0x6E, 0xF9, // movd xmm7, ecx
150 0xff, 0xA1, 0xff, 0xbf, 0xff, 0xff, // jmp DWORD PTR [ecx - 0x4001] # <fn>
151 ]!
152} $else $if arm64 {
153 [
154 u8(0x11), 0x00, 0xFE, 0x5C, // ldr d17, userdata
155 0x30, 0x00, 0xFE, 0x58, // ldr x16, fn
156 0x00, 0x02, 0x1F, 0xD6 // br x16
157 ]!
158} $else $if arm32 {
159 [
160 u8(0x04), 0xC0, 0x4F, 0xE2, // adr ip, here
161 // here:
162 0x01, 0xC9, 0x4C, 0xE2, // sub ip, ip, #0x4000
163 0x90, 0xCA, 0x07, 0xEE, // vmov s15, ip
164 0x00, 0xC0, 0x9C, 0xE5, // ldr ip, [ip, 0]
165 0x1C, 0xFF, 0x2F, 0xE1 // bx ip
166 ]!
167} $else $if rv64 {
168 [
169 u8(0x97), 0xCF, 0xFF, 0xFF, // auipc t6, 0xffffc
170 0x03, 0xBF, 0x8F, 0x00, // ld t5, 8(t6)
171 0x07, 0xB3, 0x0F, 0x00, // fld ft6, 0(t6)
172 0x67, 0x00, 0x0F, 0x00, // jr t5
173 ]!
174} $else $if rv32 {
175 [
176 u8(0x97), 0xCF, 0xFF, 0xFF, // auipc t6, 0xffffc
177 0x03, 0xAF, 0x4F, 0x00, // lw t5, 4(t6)
178 0x07, 0xAB, 0x0F, 0x00, // flw fs6, 0(t6)
179 0x67, 0x00, 0x0F, 0x00 // jr t5
180 ]!
181} $else $if s390x {
182 [
183 u8(0xC0), 0x10, 0xFF, 0xFF, 0xE0, 0x00, // larl %r1, -16384
184 0x68, 0xF0, 0x10, 0x00, // ld %f15, 0(%r1)
185 0xE3, 0x10, 0x10, 0x08, 0x00, 0x04, // lg %r1, 8(%r1)
186 0x07, 0xF1, // br %r1
187 ]!
188} $else $if loongarch64 {
189 [
190 u8(0x92), 0xFF, 0xFF, 0x1D, // pcaddu12i t6, -4
191 0x48, 0x02, 0x80, 0x2B, // fld.d f8, t6, 0
192 0x51, 0x22, 0xC0, 0x28, // ld.d t5, t6, 8
193 0x20, 0x02, 0x00, 0x4C, // jr t5
194 ]!
195} $else $if sparc64 {
196 [
197 u8(0x83), 0x41, 0x40, 0x00, // rd %pc, %g1
198 0x05, 0x00, 0x00, 0x10, // sethi %hi(0x4000), %g2
199 0x84, 0x10, 0xa0, 0x00, // mov %g2, %g2 ! 4000 <main>
200 0x82, 0x20, 0x40, 0x02, // sub %g1, %g2, %g1
201 0xff, 0x18, 0x60, 0x00, // ldd [ %l1 ], %d62
202 0xc2, 0x58, 0x60, 0x08, // ldx [ %g1 + 8 ], %g1
203 0x81, 0xc0, 0x40, 0x00, // jmp %g1
204 0x01, 0x00, 0x00, 0x00 // nop
205 ]!
206} $else $if big_endian {
207 [
208 u8(0x7C), 0x08, 0x02, 0xA6, // mflr %r0
209 0x48, 0x00, 0x00, 0x05, // bl here
210 0x7D, 0xC8, 0x02, 0xA6, // here: mflr %r14
211 0x39, 0xCE, 0xC0, 0x08, // addi %r14, %r14, -16376
212 0xC9, 0xCE, 0x00, 0x00, // lfd %f14, 0(%r14) // userdata
213 0xE9, 0xCE, 0x00, 0x08, // ld %r14, 8(%r14) // func descriptor ptr
214 0xE9, 0x8E, 0x00, 0x00, // ld %r12, 0(%r14) // code addr from descriptor
215 0xE8, 0x4E, 0x00, 0x08, // ld %r2, 8(%r14) // TOC from descriptor
216 0x7C, 0x08, 0x03, 0xA6, // mtlr %r0
217 0x7D, 0x89, 0x03, 0xA6, // mtctr %r12
218 0x4E, 0x80, 0x04, 0x20, // bctr
219 ]!
220} $else {
221 [u8(0)]!
222}
223
224// NOTE: Keep the first branch as the longest byte sequence. In translated/bootstrap C mode
225// (`vc/v.c`), V emits a fixed C array whose size is inferred from the first branch.
226const closure_get_data_bytes = $if !ppc64le && !amd64 && !i386 && !arm64 && !arm32 && !rv64 && !rv32 && !s390x && !loongarch64 {
227 // ppc (32-bit PowerPC) - expressed as negation of all other arches for bootstrap compat
228 [
229 u8(0x94), 0x21, 0xff, 0xf0, // stwu %r1, -16(%r1)
230 0xd9, 0xc1, 0x00, 0x08, // stfd %f14, 8(%r1)
231 0x80, 0x61, 0x00, 0x08, // lwz %r3, 8(%r1)
232 0x38, 0x21, 0x00, 0x10, // addi %r1, %r1, 16
233 0x4e, 0x80, 0x00, 0x20, // blr
234 ]!
235} $else $if arm32 {
236 [
237 u8(0x90), 0x0A, 0x17, 0xEE, // vmov r0, s15
238 0x04, 0x00, 0x10, 0xE5, // ldr r0, [r0, #-4]
239 0x1E, 0xFF, 0x2F, 0xE1 // bx lr
240 ]!
241} $else $if amd64 {
242 [
243 u8(0x66), 0x4C, 0x0F, 0x7E, 0xF8, // movq rax, xmm15
244 0xC3 // ret
245 ]!
246} $else $if i386 {
247 [
248 u8(0x66), 0x0F, 0x7E, 0xF8, // movd eax, xmm7
249 0x8B, 0x80, 0xFB, 0xBF, 0xFF, 0xFF, // mov eax, DWORD PTR [eax - 0x4005]
250 0xc3 // ret
251 ]!
252} $else $if arm64 {
253 [
254 u8(0x20), 0x02, 0x66, 0x9E, // fmov x0, d17
255 0xC0, 0x03, 0x5F, 0xD6 // ret
256 ]!
257} $else $if rv64 {
258 [
259 u8(0x53), 0x05, 0x03, 0xE2, // fmv.x.d a0, ft6
260 0x67, 0x80, 0x00, 0x00, // ret
261 ]!
262} $else $if rv32 {
263 [
264 u8(0x53), 0x05, 0x0B, 0xE0, // fmv.x.w a0, fs6
265 0x67, 0x80, 0x00, 0x00 // ret
266 ]!
267} $else $if s390x {
268 [
269 u8(0xB3), 0xCD, 0x00, 0x2F, // lgdr %r2, %f15
270 0x07, 0xFE, // br %r14
271 ]!
272} $else $if ppc64le {
273 [
274 u8(0x66), 0x00, 0xc3, 0x7d, // mfvsrd %r3, %f14
275 0x20, 0x00, 0x80, 0x4e, // blr
276 ]!
277} $else $if loongarch64 {
278 [
279 u8(0x04), 0xB9, 0x14, 0x01, // movfr2gr.d a0, f8
280 0x20, 0x00, 0x00, 0x4C, // ret
281 ]!
282} $else $if sparc64 {
283 [
284 u8(0x91), 0xb0, 0x22, 0x1f, // movdtox %f62, %o0
285 0x81, 0xc3, 0xe0, 0x08, // retl
286 0x01, 0x00, 0x00, 0x00 // nop
287 ]!
288} $else $if big_endian {
289 [
290 u8(0x7d), 0xc3, 0x00, 0x66, // mfvsrd %r3, %f14
291 0x4e, 0x80, 0x00, 0x20 // blr
292 ]!
293} $else {
294 [u8(0)]!
295}
296
297// vfmt on
298
299// equal to `max(2*sizeof(void*), sizeof(__closure_thunk))`, rounded up to the next multiple of `sizeof(void*)`
300// NOTE: This is a workaround for `-usecache` bug, as it can't include `fn get_closure_size()` needed by `const closure_size` in `build-module` mode.
301const closure_size_1 = if 2 * u32(sizeof(voidptr)) > u32(closure_thunk.len) {
302 2 * u32(sizeof(voidptr))
303} else {
304 u32(closure_thunk.len) + u32(sizeof(voidptr)) - 1
305}
306const closure_size = int(closure_size_1 & ~(u32(sizeof(voidptr)) - 1))
307
308@[inline]
309fn closure_exec_ptr(closure voidptr) voidptr {
310 if is_ppc64() {
311 return unsafe { &u8(closure) + assumed_page_size }
312 }
313 return closure
314}
315
316@[inline]
317fn closure_return_ptr(exec_ptr voidptr) voidptr {
318 if is_ppc64() {
319 return unsafe { &u8(exec_ptr) - assumed_page_size }
320 }
321 return exec_ptr
322}
323
324@[inline]
325fn closure_slot_meta(exec_ptr voidptr) &voidptr {
326 return unsafe { &voidptr(&u8(exec_ptr) - assumed_page_size) }
327}
328
329fn closure_register_page(exec_page_start voidptr) {
330 unsafe {
331 node := &ClosurePage(malloc(sizeof(ClosurePage)))
332 *node = ClosurePage{
333 next: g_closure.pages
334 exec_page_start: exec_page_start
335 }
336 g_closure.pages = node
337 }
338}
339
340fn closure_is_managed(exec_ptr voidptr) bool {
341 if isnil(exec_ptr) {
342 return false
343 }
344 exec_addr := unsafe { usize(exec_ptr) }
345 mut page := g_closure.pages
346 for page != unsafe { nil } {
347 page_addr := unsafe { usize(page.exec_page_start) }
348 if exec_addr >= page_addr && exec_addr < page_addr + usize(g_closure.v_page_size) {
349 slot_offset := exec_addr - page_addr
350 return slot_offset >= usize(closure_size) && slot_offset % usize(closure_size) == 0
351 }
352 page = page.next
353 }
354 return false
355}
356
357fn closure_live_set(exec_ptr voidptr, data voidptr, owns_data bool) {
358 g_closure.next_generation++
359 g_closure.live[exec_ptr] = ClosureLiveInfo{
360 ctx: data
361 owns_data: owns_data
362 generation: g_closure.next_generation
363 }
364}
365
366fn closure_live_delete(exec_ptr voidptr) ClosureLiveInfo {
367 if info := g_closure.live[exec_ptr] {
368 g_closure.live[exec_ptr] = ClosureLiveInfo{}
369 g_closure.live.delete(exec_ptr)
370 return info
371 }
372 return ClosureLiveInfo{}
373}
374
375fn new_closure_lifetime_state_no_lock() &ClosureLifetimeState {
376 mut state := g_closure.free_lifetime_states
377 if !isnil(state) {
378 g_closure.free_lifetime_states = state.next_free
379 } else {
380 unsafe {
381 state = &ClosureLifetimeState(malloc(sizeof(ClosureLifetimeState)))
382 }
383 g_closure.lifetime_state_allocs++
384 }
385 g_closure.next_lifetime_generation++
386 unsafe {
387 *state = ClosureLifetimeState{
388 owner_thread: closure_current_thread_id_platform()
389 generation: g_closure.next_lifetime_generation
390 }
391 }
392 return state
393}
394
395fn new_closure_lifetime_state() &ClosureLifetimeState {
396 closure_mtx_lock_platform()
397 state := new_closure_lifetime_state_no_lock()
398 closure_mtx_unlock_platform()
399 return state
400}
401
402fn closure_lifetime_recycle_state_no_lock(mut state &ClosureLifetimeState) {
403 state.disposed = true
404 state.active = false
405 state.suspended = 0
406 state.frame_start = 0
407 state.frame_gen = 0
408 state.frame_generation = 0
409 unsafe {
410 state.records.free()
411 state.frames.free()
412 }
413 state.records = []ClosureLifetimeRecord{}
414 state.frames = []ClosureLifetimeFrame{}
415 state.next_free = g_closure.free_lifetime_states
416 g_closure.free_lifetime_states = state
417}
418
419fn closure_lifetime_error(state &ClosureLifetimeState, generation u64, thread_id u64) string {
420 if state.disposed || state.generation != generation {
421 return 'closure lifetime used after dispose'
422 }
423 if state.owner_thread != thread_id {
424 return 'closure lifetime used from a different thread'
425 }
426 return ''
427}
428
429fn (mut lifetime Lifetime) ensure_state() !&ClosureLifetimeState {
430 closure_ensure_initialized()
431 if isnil(lifetime.state) {
432 if lifetime.disposed {
433 return error('closure lifetime used after dispose')
434 }
435 lifetime.state = new_closure_lifetime_state()
436 lifetime.generation = lifetime.state.generation
437 return lifetime.state
438 }
439 closure_mtx_lock_platform()
440 state := lifetime.state
441 if lifetime.disposed || state.disposed || state.generation != lifetime.generation {
442 closure_mtx_unlock_platform()
443 return error('closure lifetime used after dispose')
444 }
445 closure_mtx_unlock_platform()
446 return state
447}
448
449fn closure_lifetime_track_no_lock(exec_ptr voidptr) {
450 thread_id := closure_current_thread_id_platform()
451 mut state := g_closure.active_lifetimes[thread_id] or { return }
452 if state.suspended > 0 {
453 return
454 }
455 info := g_closure.live[exec_ptr] or { return }
456 state.records << ClosureLifetimeRecord{
457 exec_ptr: exec_ptr
458 generation: info.generation
459 }
460}
461
462@[direct_array_access]
463fn closure_slot_data(exec_ptr voidptr) voidptr {
464 unsafe {
465 mut p := closure_slot_meta(exec_ptr)
466 if is_ppc64() {
467 return p[2]
468 }
469 return p[0]
470 }
471}
472
473@[direct_array_access]
474fn closure_release_no_lock(exec_ptr voidptr, generation u64) bool {
475 if !closure_is_managed(exec_ptr) {
476 return false
477 }
478 info := g_closure.live[exec_ptr] or { return false }
479 if generation != 0 && info.generation != generation {
480 return false
481 }
482 data := closure_slot_data(exec_ptr)
483 _ := closure_live_delete(exec_ptr)
484 if info.owns_data && !isnil(data) {
485 unsafe { free(data) }
486 }
487 unsafe {
488 mut p := closure_slot_meta(exec_ptr)
489 p[0] = g_closure.free_closure_ptr
490 if is_ppc64() {
491 p[1] = nil
492 p[2] = nil
493 p[3] = nil
494 } else {
495 p[1] = nil
496 }
497 g_closure.free_closure_ptr = exec_ptr
498 }
499 return true
500}
501
502fn closure_lifetime_release_records_no_lock(records []ClosureLifetimeRecord, start int, end int) {
503 for i in start .. end {
504 record := records[i]
505 closure_release_no_lock(record.exec_ptr, record.generation)
506 }
507}
508
509fn closure_lifetime_reclaim_no_lock(mut state ClosureLifetimeState, retain int) {
510 keep := if retain < 0 { 0 } else { retain }
511 if state.frames.len <= keep {
512 return
513 }
514 reclaim_count := state.frames.len - keep
515 mut cutoff := 0
516 for i in 0 .. reclaim_count {
517 frame := state.frames[i]
518 closure_lifetime_release_records_no_lock(state.records, frame.start, frame.end)
519 cutoff = frame.end
520 }
521 state.frames.delete_many(0, reclaim_count)
522 if cutoff > 0 {
523 state.records.delete_many(0, cutoff)
524 for mut frame in state.frames {
525 frame.start -= cutoff
526 frame.end -= cutoff
527 }
528 }
529}
530
531fn closure_ensure_initialized() {
532 closure_init_once_platform()
533}
534
535// new_lifetime creates a lifetime object for tracking closure callbacks created inside frames.
536pub fn new_lifetime() Lifetime {
537 closure_ensure_initialized()
538 state := new_closure_lifetime_state()
539 return Lifetime{
540 state: state
541 generation: state.generation
542 }
543}
544
545// lifetime_state_allocs writes the number of allocated closure lifetime bookkeeping states to out.
546@[if track_heap ?]
547pub fn lifetime_state_allocs(out &u64) {
548 closure_ensure_initialized()
549 closure_mtx_lock_platform()
550 unsafe {
551 *out = g_closure.lifetime_state_allocs
552 }
553 closure_mtx_unlock_platform()
554}
555
556fn (mut lifetime Lifetime) begin_frame() !FrameToken {
557 mut state := lifetime.ensure_state()!
558 thread_id := closure_current_thread_id_platform()
559 closure_mtx_lock_platform()
560 err := closure_lifetime_error(state, lifetime.generation, thread_id)
561 if err != '' {
562 closure_mtx_unlock_platform()
563 return error(err)
564 }
565 if state.active {
566 closure_mtx_unlock_platform()
567 return error('closure lifetime frames can not be nested')
568 }
569 if state.suspended > 0 {
570 closure_mtx_unlock_platform()
571 return error('closure lifetime frame while suspended')
572 }
573 if _ := g_closure.active_lifetimes[thread_id] {
574 closure_mtx_unlock_platform()
575 return error('another closure lifetime is already active on this thread')
576 }
577 state.frame_generation++
578 state.active = true
579 state.frame_start = state.records.len
580 state.frame_gen = state.frame_generation
581 g_closure.active_lifetimes[thread_id] = state
582 closure_mtx_unlock_platform()
583 return FrameToken{
584 state: state
585 thread_id: thread_id
586 state_generation: lifetime.generation
587 generation: state.frame_generation
588 }
589}
590
591fn (mut lifetime Lifetime) end_frame(token FrameToken) ! {
592 if isnil(token.state) {
593 return error('invalid closure lifetime frame token')
594 }
595 mut state := token.state
596 thread_id := closure_current_thread_id_platform()
597 closure_mtx_lock_platform()
598 err := closure_lifetime_error(state, token.state_generation, thread_id)
599 if err != '' {
600 closure_mtx_unlock_platform()
601 return error(err)
602 }
603 if token.thread_id != thread_id || token.generation != state.frame_gen || !state.active {
604 closure_mtx_unlock_platform()
605 return error('invalid closure lifetime frame token')
606 }
607 state.frames << ClosureLifetimeFrame{
608 start: state.frame_start
609 end: state.records.len
610 }
611 state.active = false
612 state.frame_start = 0
613 state.frame_gen = 0
614 g_closure.active_lifetimes[thread_id] = unsafe { nil }
615 g_closure.active_lifetimes.delete(thread_id)
616 closure_mtx_unlock_platform()
617}
618
619// frame runs work while tracking closure callbacks created by that work in this lifetime.
620// The work callback itself is borrowed; only closures allocated while the frame is active
621// are owned by the lifetime and later released by reclaim or dispose.
622pub fn (mut lifetime Lifetime) frame(work fn ()) ! {
623 token := lifetime.begin_frame()!
624 mut ended := false
625 defer {
626 if !ended {
627 lifetime.end_frame(token) or {}
628 }
629 }
630 // Terminal panic can abort before scoped cleanup runs.
631 work()
632 lifetime.end_frame(token)!
633 ended = true
634}
635
636// reclaim releases tracked closure callbacks from old frames while retaining the newest retain frames.
637pub fn (mut lifetime Lifetime) reclaim(retain int) ! {
638 mut state := lifetime.ensure_state()!
639 thread_id := closure_current_thread_id_platform()
640 closure_mtx_lock_platform()
641 err := closure_lifetime_error(state, lifetime.generation, thread_id)
642 if err != '' {
643 closure_mtx_unlock_platform()
644 return error(err)
645 }
646 if state.active {
647 closure_mtx_unlock_platform()
648 return error('closure lifetime reclaim while a frame is active')
649 }
650 closure_lifetime_reclaim_no_lock(mut state, retain)
651 closure_mtx_unlock_platform()
652}
653
654// reclaim_all releases all tracked closure callbacks owned by this lifetime.
655pub fn (mut lifetime Lifetime) reclaim_all() ! {
656 lifetime.reclaim(0)!
657}
658
659// dispose releases all tracked closure callbacks and invalidates this lifetime.
660pub fn (mut lifetime Lifetime) dispose() ! {
661 mut state := lifetime.ensure_state()!
662 thread_id := closure_current_thread_id_platform()
663 closure_mtx_lock_platform()
664 err := closure_lifetime_error(state, lifetime.generation, thread_id)
665 if err != '' {
666 closure_mtx_unlock_platform()
667 return error(err)
668 }
669 if state.active {
670 closure_mtx_unlock_platform()
671 return error('closure lifetime dispose while a frame is active')
672 }
673 if state.suspended > 0 {
674 closure_mtx_unlock_platform()
675 return error('closure lifetime dispose while suspended')
676 }
677 closure_lifetime_reclaim_no_lock(mut state, 0)
678 lifetime.state = unsafe { nil }
679 lifetime.disposed = true
680 closure_lifetime_recycle_state_no_lock(mut state)
681 closure_mtx_unlock_platform()
682}
683
684// suspend runs work without tracking closure callbacks in the active frame of this lifetime.
685// The work callback is borrowed and is not owned or released by the lifetime.
686pub fn (mut lifetime Lifetime) suspend(work fn ()) ! {
687 mut state := lifetime.ensure_state()!
688 thread_id := closure_current_thread_id_platform()
689 closure_mtx_lock_platform()
690 err := closure_lifetime_error(state, lifetime.generation, thread_id)
691 if err != '' {
692 closure_mtx_unlock_platform()
693 return error(err)
694 }
695 if active := g_closure.active_lifetimes[thread_id] {
696 if active != state {
697 closure_mtx_unlock_platform()
698 return error('another closure lifetime is already active on this thread')
699 }
700 }
701 state.suspended++
702 closure_mtx_unlock_platform()
703 defer {
704 closure_mtx_lock_platform()
705 state.suspended--
706 closure_mtx_unlock_platform()
707 }
708 work()
709}
710
711// untracked runs work without tracking closure callbacks in this lifetime.
712// The work callback is borrowed and is not owned or released by the lifetime.
713pub fn (mut lifetime Lifetime) untracked(work fn ()) ! {
714 lifetime.suspend(work)!
715}
716
717// closure_alloc allocates executable memory pages for closures(INTERNAL COMPILER USE ONLY).
718fn closure_alloc() {
719 p := closure_alloc_platform()
720 if isnil(p) {
721 return
722 }
723 // Setup executable and guard pages
724 x := unsafe { p + g_closure.v_page_size } // End of guard page
725 mut remaining := g_closure.v_page_size / closure_size // Calculate slot count
726 closure_register_page(x)
727 g_closure.closure_ptr = x // Current allocation pointer
728 g_closure.closure_cap = remaining // Remaining slot count
729
730 // Fill page with closure templates
731 for remaining > 0 {
732 unsafe { vmemcpy(x, &closure_thunk[0], closure_thunk.len) } // Copy template
733 remaining--
734 unsafe {
735 x += closure_size // Move to next slot
736 }
737 }
738 closure_memory_protect_platform(g_closure.closure_ptr, g_closure.v_page_size, .read_exec)
739}
740
741// closure_init initializes global closure subsystem(INTERNAL COMPILER USE ONLY).
742fn closure_init() {
743 closure_ensure_initialized()
744}
745
746fn closure_init_body() {
747 // Determine system page size
748 mut page_size := get_page_size_platform()
749 g_closure.v_page_size = page_size // Store calculated size
750 g_closure.live = map[voidptr]ClosureLiveInfo{}
751 g_closure.active_lifetimes = map[u64]&ClosureLifetimeState{}
752 g_closure.next_generation = 0
753 g_closure.free_lifetime_states = unsafe { nil }
754 g_closure.next_lifetime_generation = 0
755 g_closure.lifetime_state_allocs = 0
756
757 // Initialize thread-safety lock
758 closure_mtx_lock_init_platform()
759
760 // Initial memory allocation
761 closure_alloc()
762
763 // Install closure handler template
764 unsafe {
765 // Temporarily enable write access to executable memory
766 closure_memory_protect_platform(g_closure.closure_ptr, page_size, .read_write)
767 // Copy closure entry stub code
768 vmemcpy(g_closure.closure_ptr, &closure_get_data_bytes[0], closure_get_data_bytes.len)
769 // Re-normalize execution protection
770 closure_memory_protect_platform(g_closure.closure_ptr, page_size, .read_exec)
771 }
772 // Setup global closure handler pointer
773 if is_ppc64() {
774 mut desc := unsafe { &voidptr(&u8(g_closure.closure_ptr) - assumed_page_size) }
775 unsafe {
776 desc[0] = g_closure.closure_ptr
777 desc[1] = nil
778 }
779 g_closure.closure_get_data = unsafe { ClosureGetDataFn(desc) }
780 } else {
781 g_closure.closure_get_data = g_closure.closure_ptr
782 }
783
784 // Advance allocation pointer past header
785 unsafe {
786 g_closure.closure_ptr = &u8(g_closure.closure_ptr) + closure_size
787 }
788 g_closure.closure_cap-- // Account for header slot
789}
790
791// closure_create creates closure objects at compile-time(INTERNAL COMPILER USE ONLY).
792fn closure_create(func voidptr, data voidptr) voidptr {
793 return closure_create_with_data(func, data, true)
794}
795
796// closure_create_with_data creates closure objects with explicit context ownership(INTERNAL COMPILER USE ONLY).
797@[direct_array_access]
798fn closure_create_with_data(func voidptr, data voidptr, owns_data bool) voidptr {
799 closure_ensure_initialized()
800 closure_mtx_lock_platform()
801
802 mut curr_closure := g_closure.free_closure_ptr
803 if !isnil(curr_closure) {
804 unsafe {
805 mut p := closure_slot_meta(curr_closure)
806 g_closure.free_closure_ptr = p[0]
807 }
808 } else {
809 // Handle memory exhaustion
810 if g_closure.closure_cap == 0 {
811 closure_alloc() // Allocate new memory page
812 }
813 g_closure.closure_cap-- // Decrement slot counter
814
815 // Claim current closure slot
816 curr_closure = g_closure.closure_ptr
817 unsafe {
818 // Move to next available slot
819 g_closure.closure_ptr = &u8(g_closure.closure_ptr) + closure_size
820 }
821 }
822 unsafe {
823 // Write closure metadata (data + function pointer)
824 mut p := closure_slot_meta(curr_closure)
825 if is_ppc64() {
826 // ELFv1: guard page layout per slot:
827 // [0] desc[0] = thunk code address <- returned as ELFv1 function pointer
828 // [1] desc[1] = nil (TOC unused; thunk loads real TOC from func descriptor)
829 // [2] userdata
830 // [3] func (V function descriptor pointer into .opd)
831 p[0] = curr_closure
832 p[1] = nil
833 p[2] = data
834 p[3] = func
835 } else {
836 p[0] = data // Stored closure context
837 p[1] = func // Target function to execute
838 }
839 }
840 closure_live_set(curr_closure, data, owns_data)
841 closure_lifetime_track_no_lock(curr_closure)
842 closure_mtx_unlock_platform()
843
844 // Return executable closure object
845 return closure_return_ptr(curr_closure)
846}
847
848// closure_data returns the userdata pointer associated with a closure object.
849@[direct_array_access]
850fn closure_data(closure voidptr) voidptr {
851 unsafe {
852 mut p := closure_slot_meta(closure_exec_ptr(closure))
853 $if ppc64 {
854 return p[2]
855 } $else {
856 return p[0]
857 }
858 }
859}
860
861// Legacy compiler hook for one-shot local cleanup. Scoped lifetime reclaim uses generation checks.
862@[direct_array_access]
863fn closure_try_destroy(closure voidptr) {
864 if isnil(closure) {
865 return
866 }
867 closure_ensure_initialized()
868 exec_ptr := closure_exec_ptr(closure)
869 closure_mtx_lock_platform()
870 closure_release_no_lock(exec_ptr, 0)
871 closure_mtx_unlock_platform()
872}
873