// SafeC Standard Library — Chan<T>: typed channel over the channel built-ins
//
// struct Chan<int> ch = std::Chan::new(16); // T inferred from the target
// ch.send(42); // blocks while full; 0 once closed
// match (ch.recv()) { // blocks while empty; null once closed and drained
// case some(v): ...
// case none: ...
// }
// ch.close();
//
// The language's chan_create/chan_send/chan_recv/chan_close built-ins are
// untyped: the runtime (channel.sc's __safec_chan_* functions) carries
// fixed 8-byte payload slots and copies exactly 8 bytes per send/recv.
// Chan<T> fixes the element type and pads T into a full 8-byte slot, so
// nothing reads or writes past a smaller T. sizeof(T) <= 8 is checked at
// compile time; for a larger T, send a reference to it (a '?&T' fits on
// every supported target) and keep the referent alive until received.
#pragma once
// Channel runtime (defined in channel.sc, deliberately outside namespace std
// — see there): the C ABI the compiler's chan_* built-ins call, declared
// here because Chan<T>'s generic methods are instantiated in the caller's
// translation unit. Not API: use Chan<T>.
// A channel is a SafecChan_ (its fields are channel.sc's); a payload moves
// through one 8-byte slot.
struct SafecChan_ {
unsigned long long* buf; // ring buffer, 'cap' 8-byte slots
int cap;
int head; // next slot to read
int tail; // next slot to write
int count; // pending items
int closed; // 0 = open, 1 = closed
unsigned long long mtx;
unsigned long long not_empty; // signaled when count > 0, or on close
unsigned long long not_full; // signaled when count < cap, or on close
unsafe impl Send + Sync; // every field is reached under mtx
};
extern SafecChan_* __safec_chan_create(int requestedCap);
extern bool __safec_chan_send(SafecChan_* ch, unsigned long long* slot);
extern bool __safec_chan_recv(SafecChan_* ch, unsigned long long* slot);
extern void __safec_chan_close(SafecChan_* ch);
// A T moves into / out of the runtime's 8-byte slot by memcpy: strict
// aliasing forbids reading a T through the slot's integer type.
#include <std/stddef.h>
extern unsigned char* memcpy(unsigned char* dst, const unsigned char* src, size_t n); // size_t: 32-bit on wasm32 / 32-bit targets
namespace std {
generic<T: Send>
struct Chan {
unsafe impl Send + Sync; // the runtime channel is locked inside
?&SafecChan_ raw_; // runtime channel from __safec_chan_create; null if creation failed
static struct Chan<T> new(int cap); // buffered channel of `cap` slots
int send(T val); // 1 on success, 0 if closed (or never created)
?T recv(); // null once closed and drained
// No more sends; wakes every blocked sender/receiver (receivers drain
// what is buffered first). The runtime channel itself is never freed
// (there is no destroy), so a Chan is not a Drop type.
void close();
// send/recv under the Fifo names (std/collections/traits.h), so generic
// producer/consumer code takes a Chan like any queue. Both block.
int enqueue(T val);
?T dequeue();
};
generic<T>
struct Chan<T> Chan::new(int cap) {
static_assert(sizeof(T) <= 8UL, "Chan<T>: T must fit in 8 bytes (channels carry fixed 8-byte payload slots) — send a ?&T to a larger value instead");
struct Chan<T> c;
c.raw_ = null;
unsafe { c.raw_ = (?&SafecChan_)__safec_chan_create(cap); }
return c;
}
generic<T>
int Chan::send(T val) {
if (self.raw_.is_null()) { drop(val); return 0; }
unsigned long long slot = 0ULL;
bool ok;
unsafe {
memcpy((unsigned char*)&slot, (const unsigned char*)&val, (size_t)sizeof(T));
ok = __safec_chan_send((SafecChan_*)self.raw_, &slot);
}
if (ok) { unsafe { forget(val); } } // its bytes moved into the channel
else drop(val); // closed: nobody will receive it
return ok ? 1 : 0;
}
generic<T>
?T Chan::recv() {
if (self.raw_.is_null()) return null;
unsigned long long slot = 0ULL;
bool ok;
unsafe { ok = __safec_chan_recv((SafecChan_*)self.raw_, &slot); }
if (!ok) return null;
T v[1]; // filled by memcpy, which definite-init can't see through
unsafe { memcpy((unsigned char*)&v[0], (const unsigned char*)&slot, (size_t)sizeof(T)); }
return v[0];
}
generic<T>
int Chan::enqueue(T val) { return self.send(val); }
generic<T>
?T Chan::dequeue() { return self.recv(); }
generic<T>
void Chan::close() {
if (self.raw_.is_null()) return;
unsafe { __safec_chan_close((SafecChan_*)self.raw_); }
}
} // namespace std