// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module coroutines import v.util import time #flag -I @VEXEROOT/thirdparty/photon #flag @VEXEROOT/thirdparty/photon/photonwrapper.so #include "photonwrapper.h" $if windows { #include "processthreadsapi.h" } $else { #include } #flag -I @VEXEROOT/vlib/coroutines #include "sp_corrector.c" fn C.set_photon_thread_stack_allocator(fn (voidptr, int) voidptr, fn (voidptr, voidptr, int)) // fn C.default_photon_thread_stack_alloc(voidptr, int) voidptr // fn C.default_photon_thread_stack_dealloc(voidptr, voidptr, int) fn C.new_photon_work_pool(int) voidptr fn C.delete_photon_work_pool() fn C.init_photon_work_pool(int) fn C.photon_thread_create_and_migrate_to_work_pool(f voidptr, arg voidptr) fn C.photon_thread_create(f voidptr, arg voidptr) fn C.photon_thread_migrate() // fn C.photon_thread_migrate(work_pool voidptr) fn C.photon_init_default() int fn C.photon_sleep_s(n int) fn C.photon_sleep_ms(n int) fn C.sp_corrector(voidptr, voidptr) // sleep is coroutine-safe version of time.sleep() pub fn sleep(duration time.Duration) { C.photon_sleep_ms(duration.milliseconds()) } fn alloc(_ voidptr, stack_size int) voidptr { unsafe { $if gcboehm ? { // TODO: do this only once when the worker thread is created. // I'm currently just doing it here for convenience. // The second time `C.GC_register_my_thread` gets called from the // same thread it will just fail (returning non 0), so it't not // really a problem, it's just not ideal. mut sb := C.GC_stack_base{} C.GC_get_stack_base(&sb) C.GC_register_my_thread(&sb) } stack_ptr := malloc(stack_size) $if gcboehm ? { // TODO: this wont work if a corouroutine gets moved to a different // thread, so we are using `C.GC_set_sp_corrector` with our own // corrector function which seems to be the best solution for now. // It would probably be more performant if we could hook into photon's context // switching code (currently not possible) or we write our own implementation. // C.GC_set_stackbottom(0, stack_ptr) C.GC_add_roots(stack_ptr, charptr(stack_ptr) + stack_size) } return stack_ptr } } fn dealloc(_ voidptr, stack_ptr voidptr, stack_size int) { unsafe { $if gcboehm ? { // TODO: only do this once when the worker thread is killed (not in each coroutine) // come up with a solution for this and `C.GC_register_my_thread` (see alloc above) // C.GC_unregister_my_thread() C.GC_remove_roots(stack_ptr, charptr(stack_ptr) + stack_size) } free(stack_ptr) } } fn init() { // NOTE `sp_corrector` only works for platforms with the stack growing down // MacOs, Win32 and Linux always have stack growing down. // A proper solution is planned (hopefully) for boehm v8.4.0. C.GC_set_sp_corrector(C.sp_corrector) if C.GC_get_sp_corrector() == unsafe { nil } { panic('stack pointer correction unsupported') } C.set_photon_thread_stack_allocator(alloc, dealloc) ret := C.photon_init_default() // NOTE: instead of using photon's WorkPool, we could start our own worker threads, // then initialize photon in each of them. If we did that we would need to control // the migration of coroutines across threads etc. if util.nr_jobs >= 1 { C.init_photon_work_pool(util.nr_jobs) } if ret < 0 { panic('failed to initialize coroutines via photon (ret=${ret})') } }