|
|
|
|
|
// Library Libgolang provides Go-like features for C and C++. |
|
|
// |
|
|
// Library Libgolang provides goroutines and other |
|
|
// Library Libgolang provides goroutines, channels with Go semantic and other |
|
|
// accompanying features. The library consists of high-level type-safe C++ API, |
|
|
// and low-level unsafe C API. |
|
|
// and low-level unsafe C API. The low-level C API was inspired by Libtask[1] |
|
|
// and Plan9/Libthread[2]. |
|
|
// |
|
|
// The primary motivation for Libgolang is to serve as runtime for golang.pyx - |
|
|
// - Cython part of Pygolang project. However Libgolang is independent of |
... |
... |
@@ -35,12 +36,29 @@ |
|
|
// C++-level API |
|
|
// |
|
|
// - `go` spawns new task. |
|
|
// - `chan`, and `select` provide channels with Go semantic and automatic |
|
|
// lifetime management. |
|
|
// - `sleep` pauses current task. |
|
|
// - `panic` throws exception that represent C-level panic. |
|
|
// |
|
|
// For example: |
|
|
// |
|
|
// go(worker, 1); // spawn worker(int) |
|
|
// chan ch = makechan(); // create new channel |
|
|
// go(worker, ch, 1); // spawn worker(chan, int) |
|
|
// ch.send(1) |
|
|
// j = ch.recv() |
|
|
// |
|
|
// _ = select({ |
|
|
// _default, // 0 |
|
|
// ch.sends(&i), // 1 |
|
|
// ch.recvs(&j), // 2 |
|
|
// }); |
|
|
// if (_ == 0) |
|
|
// // default case selected |
|
|
// if (_ == 1) |
|
|
// // case 1 selected: i sent to ch |
|
|
// if (_ == 2) |
|
|
// // case 2 selected: j received from ch |
|
|
// |
|
|
// if () |
|
|
// panic("bug"); |
... |
... |
@@ -49,6 +67,10 @@ |
|
|
// C-level API |
|
|
// |
|
|
// - `_taskgo` spawns new task. |
|
|
// - `_makechan` creates raw channel with Go semantic. |
|
|
// - `_chanxincref` and `_chanxdecref` manage channel lifetime. |
|
|
// - `_chansend` and `_chanrecv` send/receive over raw channel. |
|
|
// - `_chanselect`, `_selsend`, `_selrecv`, ... provide raw select functionality. |
|
|
// - `tasknanosleep` pauses current task. |
|
|
// |
|
|
// |
... |
... |
@@ -65,6 +87,10 @@ |
|
|
// |
|
|
// Once again, Libgolang itself is independent from Python, and other runtimes |
|
|
// are possible. |
|
|
// |
|
|
// |
|
|
// [1] Libtask: a Coroutine Library for C and Unix. https://swtch.com/libtask. |
|
|
// [2] http://9p.io/magic/man2html/2/thread. |
|
|
|
|
|
#include <stdbool.h> |
|
|
#include <stddef.h> |
... |
... |
@@ -104,10 +130,88 @@ LIBGOLANG_API void _taskgo(void (*f)(void *arg), void *arg); |
|
|
LIBGOLANG_API void _tasknanosleep(uint64_t dt); |
|
|
LIBGOLANG_API uint64_t _nanotime(void); |
|
|
|
|
|
typedef struct _chan _chan; |
|
|
LIBGOLANG_API _chan *_makechan(unsigned elemsize, unsigned size); |
|
|
LIBGOLANG_API void _chanxincref(_chan *ch); |
|
|
LIBGOLANG_API void _chanxdecref(_chan *ch); |
|
|
LIBGOLANG_API int _chanrefcnt(_chan *ch); |
|
|
LIBGOLANG_API void _chansend(_chan *ch, const void *ptx); |
|
|
LIBGOLANG_API void _chanrecv(_chan *ch, void *prx); |
|
|
LIBGOLANG_API bool _chanrecv_(_chan *ch, void *prx); |
|
|
LIBGOLANG_API void _chanclose(_chan *ch); |
|
|
LIBGOLANG_API unsigned _chanlen(_chan *ch); |
|
|
LIBGOLANG_API unsigned _chancap(_chan *ch); |
|
|
|
|
|
enum _chanop { |
|
|
_CHANSEND = 0, |
|
|
_CHANRECV = 1, |
|
|
_DEFAULT = 2, |
|
|
}; |
|
|
|
|
|
// _selcase represents one select case. |
|
|
typedef struct _selcase { |
|
|
_chan *ch; // channel |
|
|
enum _chanop op; // chansend/chanrecv/default |
|
|
void *data; // chansend: ptx; chanrecv: prx |
|
|
bool *rxok; // chanrecv: where to save ok if !NULL; otherwise not used |
|
|
} _selcase; |
|
|
|
|
|
LIBGOLANG_API int _chanselect(const _selcase *casev, int casec); |
|
|
|
|
|
// _selsend creates `_chansend(ch, ptx)` case for _chanselect. |
|
|
static inline |
|
|
_selcase _selsend(_chan *ch, const void *ptx) { |
|
|
_selcase _ = { |
|
|
.ch = ch, |
|
|
.op = _CHANSEND, |
|
|
.data = (void *)ptx, |
|
|
.rxok = NULL, |
|
|
}; |
|
|
return _; |
|
|
} |
|
|
|
|
|
// _selrecv creates `_chanrecv(ch, prx)` case for _chanselect. |
|
|
static inline |
|
|
_selcase _selrecv(_chan *ch, void *prx) { |
|
|
_selcase _ = { |
|
|
.ch = ch, |
|
|
.op = _CHANRECV, |
|
|
.data = prx, |
|
|
.rxok = NULL, |
|
|
}; |
|
|
return _; |
|
|
} |
|
|
|
|
|
// _selrecv_ creates `*pok = _chanrecv_(ch, prx)` case for _chanselect. |
|
|
static inline |
|
|
_selcase _selrecv_(_chan *ch, void *prx, bool *pok) { |
|
|
_selcase _ = { |
|
|
.ch = ch, |
|
|
.op = _CHANRECV, |
|
|
.data = prx, |
|
|
.rxok = pok, |
|
|
}; |
|
|
return _; |
|
|
} |
|
|
|
|
|
// _default represents default case for _chanselect. |
|
|
extern LIBGOLANG_API const _selcase _default; |
|
|
|
|
|
|
|
|
// libgolang runtime - the runtime must be initialized before any other libgolang use. |
|
|
typedef struct _libgolang_sema _libgolang_sema; |
|
|
typedef enum _libgolang_runtime_flags { |
|
|
// STACK_DEAD_WHILE_PARKED indicates that it is not safe to access |
|
|
// goroutine's stack memory while the goroutine is parked. |
|
|
// |
|
|
// for example gevent/greenlet/stackless use it because they copy g's stack |
|
|
// to heap on park and back on unpark. This way if objects on g's stack |
|
|
// were accessed while g was parked it would be memory of another g's stack. |
|
|
STACK_DEAD_WHILE_PARKED = 1, |
|
|
} _libgolang_runtime_flags; |
|
|
typedef struct _libgolang_runtime_ops { |
|
|
_libgolang_runtime_flags flags; |
|
|
|
|
|
// go should spawn a task (coroutine/thread/...). |
|
|
void (*go)(void (*f)(void *), void *arg); |
|
|
|
... |
... |
@@ -136,6 +240,11 @@ typedef struct _libgolang_runtime_ops { |
|
|
LIBGOLANG_API void _libgolang_init(const _libgolang_runtime_ops *runtime_ops); |
|
|
|
|
|
|
|
|
// for testing |
|
|
LIBGOLANG_API int _tchanrecvqlen(_chan *ch); |
|
|
LIBGOLANG_API int _tchansendqlen(_chan *ch); |
|
|
LIBGOLANG_API extern void (*_tblockforever)(void); |
|
|
|
|
|
#ifdef __cplusplus |
|
|
}} |
|
|
#endif |
... |
... |
@@ -145,8 +254,12 @@ LIBGOLANG_API void _libgolang_init(const _libgolang_runtime_ops *runtime_ops); |
|
|
|
|
|
#ifdef __cplusplus |
|
|
|
|
|
#include |
|
|
#include |
|
|
#include <initializer_list> |
|
|
#include |
|
|
#include <type_traits> |
|
|
#include |
|
|
|
|
|
namespace golang { |
|
|
|
... |
... |
@@ -162,6 +275,122 @@ static inline void go(F /*std::function<void(Argv...)>*/ f, Argv... argv) { |
|
|
}, frun); |
|
|
} |
|
|
|
|
|
template<typename T> class chan; |
|
|
template<typename T> static chan<T> makechan(unsigned size=0); |
|
|
|
|
|
// chan provides type-safe wrapper over _chan. |
|
|
// |
|
|
// chan is automatically reference-counted and is safe to use from multiple |
|
|
// goroutines simultaneously. |
|
|
template<typename T> |
|
|
class chan { |
|
|
_chan *_ch; |
|
|
|
|
|
public: |
|
|
inline chan() { _ch = NULL; } // nil channel if not explicitly initialized |
|
|
friend chan<T> makechan<T>(unsigned size); |
|
|
inline ~chan() { _chanxdecref(_ch); _ch = NULL; } |
|
|
|
|
|
// = nil |
|
|
inline chan(nullptr_t) { _ch = NULL; } |
|
|
inline chan& operator=(nullptr_t) { _chanxdecref(_ch); _ch = NULL; return *this; } |
|
|
// copy |
|
|
inline chan(const chan& from) { _ch = from._ch; _chanxincref(_ch); } |
|
|
inline chan& operator=(const chan& from) { |
|
|
if (this != &from) { |
|
|
_chanxdecref(_ch); _ch = from._ch; _chanxincref(_ch); |
|
|
} |
|
|
return *this; |
|
|
} |
|
|
// move |
|
|
inline chan(chan&& from) { _ch = from._ch; from._ch = NULL; } |
|
|
inline chan& operator=(chan&& from) { |
|
|
if (this != &from) { |
|
|
_chanxdecref(_ch); _ch = from._ch; from._ch = NULL; |
|
|
} |
|
|
return *this; |
|
|
} |
|
|
|
|
|
// _chan does plain memcpy to copy elements. |
|
|
// TODO allow all types (e.g. element=chan ) |
|
|
static_assert(std::is_trivially_copyable<T>::value, "TODO chan: T copy is not trivial"); |
|
|
|
|
|
// send/recv/close |
|
|
inline void send(const T &ptx) { _chansend(_ch, &ptx); } |
|
|
inline T recv() { T rx; _chanrecv(_ch, &rx); return rx; } |
|
|
inline std::pair<T,bool> recv_() { T rx; bool ok = _chanrecv_(_ch, &rx); |
|
|
return std::make_pair(rx, ok); } |
|
|
inline void close() { _chanclose(_ch); } |
|
|
|
|
|
// send/recv in select |
|
|
|
|
|
// ch.sends creates `ch.send(*ptx)` case for select. |
|
|
[[nodiscard]] inline _selcase sends(const T *ptx) { return _selsend(_ch, ptx); } |
|
|
|
|
|
// ch.recvs creates `*prx = ch.recv()` case for select. |
|
|
// |
|
|
// if pok is provided the case is extended to `[*prx, *pok] = ch.recv_()` |
|
|
// if both prx and pok are omitted the case is reduced to `ch.recv()`. |
|
|
[[nodiscard]] inline _selcase recvs(T *prx=NULL, bool *pok=NULL) { |
|
|
return _selrecv_(_ch, prx, pok); |
|
|
} |
|
|
|
|
|
// length/capacity |
|
|
inline unsigned len() { return _chanlen(_ch); } |
|
|
inline unsigned cap() { return _chancap(_ch); } |
|
|
|
|
|
// compare wrt nil |
|
|
inline bool operator==(nullptr_t) { return (_ch == NULL); } |
|
|
inline bool operator!=(nullptr_t) { return (_ch != NULL); } |
|
|
|
|
|
// compare wrt chan |
|
|
inline bool operator==(const chan<T>& ch2) { return (_ch == ch2._ch); } |
|
|
inline bool operator!=(const chan<T>& ch2) { return (_ch != ch2._ch); } |
|
|
|
|
|
// for testing |
|
|
inline _chan *_rawchan() { return _ch; } |
|
|
}; |
|
|
|
|
|
// makechan makes new chan with capacity=size. |
|
|
template<typename T> static inline |
|
|
chan<T> makechan(unsigned size) { |
|
|
chan<T> ch; |
|
|
unsigned elemsize = std::is_empty<T>::value |
|
|
? 0 // eg struct{} for which sizeof() gives 1 - *not* 0 |
|
|
: sizeof(T); |
|
|
ch._ch = _makechan(elemsize, size); |
|
|
if (ch._ch == NULL) |
|
|
throw std::bad_alloc(); |
|
|
return ch; |
|
|
} |
|
|
|
|
|
// structZ is struct{}. |
|
|
// |
|
|
// it's a workaround for e.g. makechan<struct{}> giving |
|
|
// "error: types may not be defined in template arguments". |
|
|
struct structZ{}; |
|
|
|
|
|
// select, together with chan.sends and chan.recvs, provide type-safe |
|
|
// wrappers over _chanselect and _selsend/_selrecv/_selrecv_. |
|
|
// |
|
|
// Usage example: |
|
|
// |
|
|
// _ = select({ |
|
|
// ch1.recvs(&v), // 0 |
|
|
// ch2.recvs(&v, &ok), // 1 |
|
|
// ch2.sends(&v), // 2 |
|
|
// _default, // 3 |
|
|
// }) |
|
|
static inline // select({case1, case2, case3}) |
|
|
int select(const std::initializer_list<const _selcase> &casev) { |
|
|
return _chanselect(casev.begin(), casev.size()); |
|
|
} |
|
|
|
|
|
template<size_t N> static inline // select(casev_array) |
|
|
int select(const _selcase (&casev)[N]) { |
|
|
return _chanselect(&casev[0], N); |
|
|
} |
|
|
|
|
|
|
|
|
namespace time { |
|
|
|
... |
... |
|