doc: Add example programs using libco.

These examples were originally included in various standalone libco
releases, not as part of any higan or bsnes release. test_timing in
particular is a benchmark and smoke-test as well as a basic example
of how to use libco.
This commit is contained in:
Tim Allen 2021-01-09 01:25:00 +11:00 committed by Screwtapello
parent a22bd9dba2
commit 4f75515cd3
7 changed files with 266 additions and 0 deletions

4
doc/examples/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
test_args
test_serialization
test_timing
*.o

8
doc/examples/build.bat Executable file
View File

@ -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

8
doc/examples/build.sh Executable file
View File

@ -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

6
doc/examples/test.h Normal file
View File

@ -0,0 +1,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <libco.h>

View File

@ -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;
}

View File

@ -0,0 +1,112 @@
#include "test.h"
#include <stdint.h>
#include <sys/mman.h>
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 {
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;
}

View File

@ -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;
}