std1.0.0-beta-7 SafeC docs

Source sync/channel.h

View in the repository

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