diff --git a/libco/doc/examples/.gitignore b/libco/doc/examples/.gitignore new file mode 100644 index 00000000..d3db2568 --- /dev/null +++ b/libco/doc/examples/.gitignore @@ -0,0 +1,4 @@ +test_args +test_serialization +test_timing +*.o diff --git a/libco/doc/examples/build.bat b/libco/doc/examples/build.bat new file mode 100755 index 00000000..f8fecb26 --- /dev/null +++ b/libco/doc/examples/build.bat @@ -0,0 +1,8 @@ +cc -O3 -fomit-frame-pointer -I../.. -o libco.o -c ../../libco.c +c++ -O3 -fomit-frame-pointer -I../.. -c test_timing.cpp +c++ -O3 -fomit-frame-pointer -o test_timing libco.o test_timing.o +c++ -O3 -fomit-frame-pointer -I../.. -c test_args.cpp +c++ -O3 -fomit-frame-pointer -o test_args libco.o test_args.o +c++ -O3 -fomit-frame-pointer -I../.. -c test_serialization.cpp +c++ -O3 -fomit-frame-pointer -o test_serialization libco.o test_serialization.o +@del *.o diff --git a/libco/doc/examples/build.sh b/libco/doc/examples/build.sh new file mode 100755 index 00000000..dd187a99 --- /dev/null +++ b/libco/doc/examples/build.sh @@ -0,0 +1,8 @@ +cc -O3 -fomit-frame-pointer -I../.. -o libco.o -c ../../libco.c +c++ -O3 -fomit-frame-pointer -I../.. -c test_timing.cpp +c++ -O3 -fomit-frame-pointer -o test_timing libco.o test_timing.o +c++ -O3 -fomit-frame-pointer -I../.. -c test_args.cpp +c++ -O3 -fomit-frame-pointer -o test_args libco.o test_args.o +c++ -O3 -fomit-frame-pointer -I../.. -c test_serialization.cpp +c++ -O3 -fomit-frame-pointer -o test_serialization libco.o test_serialization.o +rm -f *.o diff --git a/libco/doc/examples/test.h b/libco/doc/examples/test.h new file mode 100644 index 00000000..753ea52f --- /dev/null +++ b/libco/doc/examples/test.h @@ -0,0 +1,6 @@ +#include +#include +#include +#include + +#include diff --git a/libco/doc/examples/test_args.cpp b/libco/doc/examples/test_args.cpp new file mode 100644 index 00000000..1e6e2bbb --- /dev/null +++ b/libco/doc/examples/test_args.cpp @@ -0,0 +1,76 @@ +/***** + * cothread parameterized function example + ***** + * entry point to cothreads cannot take arguments. + * this is due to portability issues: each processor, + * operating system, programming language and compiler + * can use different parameter passing methods, so + * arguments to the cothread entry points were omitted. + * + * however, the behavior can easily be simulated by use + * of a specialized co_switch to set global parameters to + * be used as function arguments. + * + * in this way, with a bit of extra red tape, one gains + * even more flexibility than would be possible with a + * fixed argument list entry point, such as void (*)(void*), + * as any number of arguments can be used. + * + * this also eliminates race conditions where a pointer + * passed to co_create may have changed or become invalidated + * before call to co_switch, as said pointer would now be set + * when calling co_switch, instead. + *****/ + +#include "test.h" + +cothread_t thread[3]; + +namespace co_arg { + int param_x; + int param_y; +}; + +//one could also call this co_init or somesuch if they preferred ... +void co_switch(cothread_t thread, int param_x, int param_y) { + co_arg::param_x = param_x; + co_arg::param_y = param_y; + co_switch(thread); +} + +void co_entrypoint() { +int param_x = co_arg::param_x; +int param_y = co_arg::param_y; + printf("co_entrypoint(%d, %d)\n", param_x, param_y); + co_switch(thread[0]); + +//co_arg::param_x will change here (due to co_switch(cothread_t, int, int) call changing values), +//however, param_x and param_y will persist as they are thread local + + printf("co_entrypoint(%d, %d)\n", param_x, param_y); + co_switch(thread[0]); + throw; +} + +int main() { + printf("cothread parameterized function example\n\n"); + + thread[0] = co_active(); + thread[1] = co_create(65536, co_entrypoint); + thread[2] = co_create(65536, co_entrypoint); + +//use specialized co_switch(cothread_t, int, int) for initial co_switch call + co_switch(thread[1], 1, 2); + co_switch(thread[2], 4, 8); + +//after first call, entry point arguments have been initialized, standard +//co_switch(cothread_t) can be used from now on + co_switch(thread[2]); + co_switch(thread[1]); + + printf("\ndone\n"); +#if defined(_MSC_VER) || defined(__DJGPP__) + getch(); +#endif + return 0; +} diff --git a/libco/doc/examples/test_serialization.cpp b/libco/doc/examples/test_serialization.cpp new file mode 100644 index 00000000..15fbdb19 --- /dev/null +++ b/libco/doc/examples/test_serialization.cpp @@ -0,0 +1,117 @@ +#include "test.h" +#include +#include + +namespace Thread { + cothread_t host; + cothread_t cpu; + cothread_t apu; +} + +namespace Buffer { + uint8_t cpu[65536]; + uint8_t apu[65536]; +} + +namespace Memory { + uint8_t* buffer; +} + +struct CPU { + static auto Enter() -> void; + auto main() -> void; + auto sub() -> void; + auto leaf() -> void; +} cpu; + +struct APU { + static auto Enter() -> void; + auto main() -> void; + auto sub() -> void; + auto leaf() -> void; +} apu; + +auto CPU::Enter() -> void { + while(true) cpu.main(); +} + +auto CPU::main() -> void { + printf("2\n"); + sub(); +} + +auto CPU::sub() -> void { + co_switch(Thread::apu); + printf("4\n"); + leaf(); +} + +auto CPU::leaf() -> void { + int x = 42; + co_switch(Thread::host); + printf("6\n"); + co_switch(Thread::apu); + printf("8 (%d)\n", x); + co_switch(Thread::host); +} + +auto APU::Enter() -> void { + while(true) apu.main(); +} + +auto APU::main() -> void { + printf("3\n"); + sub(); +} + +auto APU::sub() -> void { + co_switch(Thread::cpu); + printf("7\n"); + leaf(); +} + +auto APU::leaf() -> void { + co_switch(Thread::cpu); +} + +auto main() -> int { + if(!co_serializable()) { + printf("This implementation does not support serialization\n"); + return 1; + } + + Memory::buffer = (uint8_t*)mmap( + (void*)0x10'0000'0000, 2 * 65536, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0 + ); + Memory::buffer[0] = 42; + printf("%p (%u)\n", Memory::buffer, Memory::buffer[0]); + + Thread::host = co_active(); + Thread::cpu = co_derive((void*)(Memory::buffer + 0 * 65536), 65536, CPU::Enter); + Thread::apu = co_derive((void*)(Memory::buffer + 1 * 65536), 65536, APU::Enter); + + printf("1\n"); + co_switch(Thread::cpu); + + printf("5\n"); + memcpy(Buffer::cpu, Thread::cpu, 65536); + memcpy(Buffer::apu, Thread::apu, 65536); + co_switch(Thread::cpu); + + Thread::cpu = nullptr; + Thread::apu = nullptr; + Thread::cpu = co_derive((void*)(Memory::buffer + 0 * 65536), 65536, CPU::Enter); + Thread::apu = co_derive((void*)(Memory::buffer + 1 * 65536), 65536, APU::Enter); + + printf("9\n"); + memcpy(Thread::cpu, Buffer::cpu, 65536); + memcpy(Thread::apu, Buffer::apu, 65536); + co_switch(Thread::cpu); + + Thread::cpu = nullptr; + Thread::apu = nullptr; + munmap((void*)0x900000000, 2 * 65536); + return 0; +} diff --git a/libco/doc/examples/test_timing.cpp b/libco/doc/examples/test_timing.cpp new file mode 100644 index 00000000..0232e47e --- /dev/null +++ b/libco/doc/examples/test_timing.cpp @@ -0,0 +1,52 @@ +#include "test.h" +enum { Iterations = 500000000 }; + +namespace thread { + cothread_t x; + cothread_t y; + volatile int counter; +} + +void co_timingtest() { + for(;;) { + thread::counter++; + co_switch(thread::x); + } +} + +void sub_timingtest() { + thread::counter++; +} + +int main() { + printf("context-switching timing test\n\n"); + time_t start, end; + int i, t1, t2; + + start = clock(); + for(thread::counter = 0, i = 0; i < Iterations; i++) { + sub_timingtest(); + } + end = clock(); + + t1 = (int)difftime(end, start); + printf("%2.3f seconds per 50 million subroutine calls (%d iterations)\n", (float)t1 / CLOCKS_PER_SEC, thread::counter); + + thread::x = co_active(); + thread::y = co_create(65536, co_timingtest); + + start = clock(); + for(thread::counter = 0, i = 0; i < Iterations; i++) { + co_switch(thread::y); + } + end = clock(); + + co_delete(thread::y); + + t2 = (int)difftime(end, start); + printf("%2.3f seconds per 100 million co_switch calls (%d iterations)\n", (float)t2 / CLOCKS_PER_SEC, thread::counter); + + printf("co_switch skew = %fx\n\n", (double)t2 / (double)t1); + return 0; +} + diff --git a/libco/doc/targets.md b/libco/doc/targets.md index 29400c48..d01dcbea 100644 --- a/libco/doc/targets.md +++ b/libco/doc/targets.md @@ -9,7 +9,7 @@ C function call. ## libco.x86 * **Overhead:** ~5x * **Supported processor(s):** 32-bit x86 -*** Supported compiler(s**): any +* **Supported compiler(s):** any * **Supported operating system(s):** * Windows * Mac OS X @@ -19,7 +19,7 @@ C function call. ## libco.amd64 * **Overhead:** ~10x (Windows), ~6x (all other platforms) * **Supported processor(s):** 64-bit amd64 -*** Supported compiler(s**): any +* **Supported compiler(s):** any * **Supported operating system(s):** * Windows * Mac OS X diff --git a/libco/doc/usage.md b/libco/doc/usage.md index cb0d1929..a3b0f04d 100644 --- a/libco/doc/usage.md +++ b/libco/doc/usage.md @@ -54,7 +54,9 @@ Handle to cothread. Handle must be of type `void*`. -A value of `null` (0) indicates an uninitialized or invalid handle, whereas a non-zero value indicates a valid handle. +A value of null (0) indicates an uninitialized or invalid handle, whereas a +non-zero value indicates a valid handle. A valid handle is backed by execution +state to which the execution can be co_switch()ed to. ## co_active ```c @@ -62,7 +64,12 @@ cothread_t co_active(); ``` Return handle to current cothread. -Always returns a valid handle, even when called from the main program thread. +Note that the handle is valid even if the function is called from a non-cothread +context. To achieve this, we save the execution state in an internal buffer, +instead of using the user-provided memory. Since this handle is valid, it can +be used to co_switch to this context from another cothread. In multi-threaded +applications, make sure to not switch non-cothread context across CPU cores, +to prevent any possible conflicts with the OS scheduler. ## co_derive ```c @@ -119,6 +126,19 @@ Passing handle of active cothread to this function is not allowed. Passing handle of primary cothread is not allowed. +## co_serializable + +```c +int co_serializable(void); +``` + +Returns non-zero if the implementation keeps the entire coroutine state in the +buffer passed to `co_derive()`. That is, if `co_serializable()` returns +non-zero, and if your cothread does not modify the heap or any process-wide +state, then you can "snapshot" the cothread's state by taking a copy of the +buffer originally passed to `co_derive()`, and "restore" a previous state +by copying the snapshot back into the buffer it came from. + ## co_switch ```c void co_switch(cothread_t cothread); diff --git a/libco/settings.h b/libco/settings.h index bd65b151..721c1acf 100644 --- a/libco/settings.h +++ b/libco/settings.h @@ -14,7 +14,7 @@ #if !defined(LIBCO_MP) /* Running in single-threaded environment */ #define thread_local #else /* Running in multi-threaded environment */ - #if defined(__STDC_VERSION__) /* Compiling as C Language */ + #if defined(__STDC__) /* Compiling as C Language */ #if defined(_MSC_VER) /* Don't rely on MSVC's C11 support */ #define thread_local __declspec(thread) #elif __STDC_VERSION__ < 201112L /* If we are on C90/99 */ @@ -55,7 +55,7 @@ - alignas (TYPE) is equivalent to alignas (alignof (TYPE)). */ #if !defined(alignas) - #if defined(__STDC_VERSION__) /* C Language */ + #if defined(__STDC__) /* C Language */ #if defined(_MSC_VER) /* Don't rely on MSVC's C11 support */ #define alignas(bytes) __declspec(align(bytes)) #elif __STDC_VERSION__ >= 201112L /* C11 and above */ @@ -85,6 +85,30 @@ #define LIBCO_ASSERT assert #endif +#if defined (__OpenBSD__) + #if !defined(LIBCO_MALLOC) || !defined(LIBCO_FREE) + #include + #include + + static void* malloc_obsd(size_t size) { + long pagesize = sysconf(_SC_PAGESIZE); + char* memory = (char*)mmap(NULL, size + pagesize, PROT_READ|PROT_WRITE, MAP_STACK|MAP_PRIVATE|MAP_ANON, -1, 0); + if (memory == MAP_FAILED) return NULL; + *(size_t*)memory = size + pagesize; + memory += pagesize; + return (void*)memory; + } + + static void free_obsd(void *ptr) { + char* memory = (char*)ptr - sysconf(_SC_PAGESIZE); + munmap(memory, *(size_t*)memory); + } + + #define LIBCO_MALLOC malloc_obsd + #define LIBCO_FREE free_obsd + #endif +#endif + #if !defined(LIBCO_MALLOC) || !defined(LIBCO_FREE) #include #define LIBCO_MALLOC malloc