mirror of https://github.com/bsnes-emu/bsnes.git
Merge commit 'fba00e5d3404e1bcfe1007ee2e3bfc3b3bb888af' into update-subtrees
This commit is contained in:
commit
139a44b142
|
@ -0,0 +1,4 @@
|
||||||
|
test_args
|
||||||
|
test_serialization
|
||||||
|
test_timing
|
||||||
|
*.o
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include <libco.h>
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
#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 {
|
||||||
|
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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ C function call.
|
||||||
## libco.x86
|
## libco.x86
|
||||||
* **Overhead:** ~5x
|
* **Overhead:** ~5x
|
||||||
* **Supported processor(s):** 32-bit x86
|
* **Supported processor(s):** 32-bit x86
|
||||||
*** Supported compiler(s**): any
|
* **Supported compiler(s):** any
|
||||||
* **Supported operating system(s):**
|
* **Supported operating system(s):**
|
||||||
* Windows
|
* Windows
|
||||||
* Mac OS X
|
* Mac OS X
|
||||||
|
@ -19,7 +19,7 @@ C function call.
|
||||||
## libco.amd64
|
## libco.amd64
|
||||||
* **Overhead:** ~10x (Windows), ~6x (all other platforms)
|
* **Overhead:** ~10x (Windows), ~6x (all other platforms)
|
||||||
* **Supported processor(s):** 64-bit amd64
|
* **Supported processor(s):** 64-bit amd64
|
||||||
*** Supported compiler(s**): any
|
* **Supported compiler(s):** any
|
||||||
* **Supported operating system(s):**
|
* **Supported operating system(s):**
|
||||||
* Windows
|
* Windows
|
||||||
* Mac OS X
|
* Mac OS X
|
||||||
|
|
|
@ -54,7 +54,9 @@ Handle to cothread.
|
||||||
|
|
||||||
Handle must be of type `void*`.
|
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
|
## co_active
|
||||||
```c
|
```c
|
||||||
|
@ -62,7 +64,12 @@ cothread_t co_active();
|
||||||
```
|
```
|
||||||
Return handle to current cothread.
|
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
|
## co_derive
|
||||||
```c
|
```c
|
||||||
|
@ -119,6 +126,19 @@ Passing handle of active cothread to this function is not allowed.
|
||||||
|
|
||||||
Passing handle of primary cothread 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
|
## co_switch
|
||||||
```c
|
```c
|
||||||
void co_switch(cothread_t cothread);
|
void co_switch(cothread_t cothread);
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
#if !defined(LIBCO_MP) /* Running in single-threaded environment */
|
#if !defined(LIBCO_MP) /* Running in single-threaded environment */
|
||||||
#define thread_local
|
#define thread_local
|
||||||
#else /* Running in multi-threaded environment */
|
#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 */
|
#if defined(_MSC_VER) /* Don't rely on MSVC's C11 support */
|
||||||
#define thread_local __declspec(thread)
|
#define thread_local __declspec(thread)
|
||||||
#elif __STDC_VERSION__ < 201112L /* If we are on C90/99 */
|
#elif __STDC_VERSION__ < 201112L /* If we are on C90/99 */
|
||||||
|
@ -55,7 +55,7 @@
|
||||||
- alignas (TYPE) is equivalent to alignas (alignof (TYPE)).
|
- alignas (TYPE) is equivalent to alignas (alignof (TYPE)).
|
||||||
*/
|
*/
|
||||||
#if !defined(alignas)
|
#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 */
|
#if defined(_MSC_VER) /* Don't rely on MSVC's C11 support */
|
||||||
#define alignas(bytes) __declspec(align(bytes))
|
#define alignas(bytes) __declspec(align(bytes))
|
||||||
#elif __STDC_VERSION__ >= 201112L /* C11 and above */
|
#elif __STDC_VERSION__ >= 201112L /* C11 and above */
|
||||||
|
@ -85,6 +85,30 @@
|
||||||
#define LIBCO_ASSERT assert
|
#define LIBCO_ASSERT assert
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined (__OpenBSD__)
|
||||||
|
#if !defined(LIBCO_MALLOC) || !defined(LIBCO_FREE)
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
|
||||||
|
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)
|
#if !defined(LIBCO_MALLOC) || !defined(LIBCO_FREE)
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#define LIBCO_MALLOC malloc
|
#define LIBCO_MALLOC malloc
|
||||||
|
|
Loading…
Reference in New Issue