|
pool
This page shows C-based memory allocation in two flavors: 1) reference counted and 2) iovec pools. Class names used are short and somewhat arbitrary (verging on weird). Refcount support here is a C rewrite of C++ in the node thorn demo, using hand-rolled vtables for virtual methods.
purpose
The purpose of cy_pool is to abstract heap allocation so C++ classes (like Vt in the vector template) can use C-based memory pools, even when storage is statically allocated. The purpose of cy_vat and vat subclasses is to provide a standard factory source of refcounted objects, using api that can be defined and used in C. Vats allow C clients to define refcounted objects that can be passed in and of subsystems written in C++. The purpose of cy_fix is to represent a refcounted node that can be fixed (pinned) in place with a ref. The purpose of cy_gum is to securely hold such a reference, so refcounting can be done by managing a population of gum instances. Ultimately, a specific short term purpose of cy_vat, cy_fix, and cy_gum is to support cy::Row on the row page, which clones the deck implementation, replacing std::vector with cy::Vt on the vector page. Since the cy_pool api supports memory allocation by cy::Vt, all the clases on this page support row directly or indirectly in C-based scatter/gather utilities in C++.
terms
Terms vat and pool mean nothing more than heap; both are just sources of allocated memory. A subclass of cy_pool is little more than a heap with alloc and free methods. But a subclass of cy_vat allocates refcounted subclasses of cy_fix (the same thing as a yn node in the node demo) which are anchored in place by cy_gum handles (the same as yh thorn handles). Terms fix and gum emphasize a desire to pin a refcounted object in place as long as you hold a reference. The cy_fix subclass allows you to fix (pin) an instance in place while you hold a reference. By design, cy_gum instances fix a reference in place for you, so gum is what makes a fixed object stay in place. (Fix and gum were simply the shortest words in a thesaurus that meant close to the right idea.)
subclassing
Base classes cy_fix and cy_gum are defined in C because actual storage management may be defined in a C runtime. But subclasses can be written in C++. Examples of this appear below. In particular cy::GumT is a preferred subclass of cy_gum to use in C++. It's a template you can specialize to a subclass of cy_fix for typesafety. Basically cy::GumT is a smart reference like auto pointers in other libraries; it's a clone of template yht handles in the node thorn demo.
iovecs
Clones of struct iovec abound in cy source code. Here's one more. All cy iovecs are considered physically equivalent to the extent you can safely cast, blindly, from one to another. /* ----- cy_iovec ----- */
typedef struct cy_iovec_ { /* C clone of struct iovec */
void* iov_base;
unsigned iov_len;
} cy_iovec;
The cy_viov object below represents a vector of iovecs as a single value. /* ----- cy_viov ----- */
typedef struct cy_viov_ { /* iovec vector as single value */
cy_iovec* v_iov; /* first of v_len iovecs */
unsigned v_len;
} cy_viov;
refcounting
The following C api is a clone of yn in the node thorn demo, but with a few methods removed. The pin and unpin methods mean add a reference and remove a reference. To support copy-on-write, the refs method returns the current refcount. (Note: this is only safe in single threaded systems.) The cite and dump methods provide single and multi-line print formats, allowing all refcounted objects to be printe, and by extention all gum handles pointing to a fix refcounted subclass. The term fix emphasizes your ability to pin an instance in place by adding a new counted reference. Under thorn, the term node emphasizes a role as one node in a graph. Here the term fix treats ability to fix in place as more important than being a node in a graph. The name announces: you can fix this in place with a gum handle, describing an aspect of how it's used. fix vtable « /* ----- cy_fix ----- */
typedef struct cy_fix_ cy_fix; /* has vtable */
typedef uint16_t /* addref */
(*cy_fix_pin_fn)(cy_fix* f);
typedef void /* delref */
(*cy_fix_unpin_fn)(cy_fix* f, uint16_t gen);
typedef int /* refcount getter */
(*cy_fix_refs_fn)(const cy_fix* f);
typedef void /* 1-line print */
(*cy_fix_cite_fn)(const cy_fix* f, cy_sink* s);
typedef void /* N-line print */
(*cy_fix_dump_fn)(const cy_fix* f, cy_sink* s);
Each cy_fix subclass defines methods with signatures exactly matching those above. Then a vtable with the following format is created—only one global instance is needed because all instances of the same fix subclass can share the same vtable pointer. typedef struct cy_fix_vfns_ { /* cy_fix vtable */
cy_fix_pin_fn fix_pin; /* add ref */
cy_fix_unpin_fn fix_unpin; /* del ref */
cy_fix_refs_fn fix_refs; /* get ref count */
cy_fix_cite_fn fix_cite; /* 1-line print */
cy_fix_dump_fn fix_dump; /* n-line print */
} cy_fix_vfns;
Each cy_fix subclass starts with the following fields, beginning with the vtable pointer. The tag field must contain octet 'F' as a magic signature for dynamic typing. But the type field can vary on a per subclass basis. Neither fix_gen nor fix_type can change during the lifetime of one fix instance. Presumably refs to fix subclasses are stored in cy_gum instances shown below. Each gum instance copies both the fix_gen and fix_type fields, then later asserts those values have not changed. This catches refcount errors by asserting object lifetime and format does not change as long as refs are nonzero. fix base « #define cy_fix_TAG 'F'
struct cy_fix_ { /* refcounted objects 'fix' */
cy_fix_vfns* fix_vfns; /* vtable: virtual functions */
uint16_t fix_gen;
uint8_t fix_tag; /* must equal cy_fix_TAG */
uint8_t fix_type; /* subclass specific value*/
};
The following two methods are preferred ways to get pseudo random starting generation numbers to put into objects like cy_fix uint16_t cy_rand_gen16(); /* std pseudo rand generation */
uint32_t cy_rand_gen32(); /* std pseudo rand generation */
Inline methods below merely call the virtual methods by dispatching through the vtable. (When the vtable is wrong, one should hope you crash.) fix dispatch « static inline uint16_t /*virtual dispatch*/
fix_pin_do(cy_fix* f) {
return (*f->fix_vfns->fix_pin)(f);
}
static inline void /*virtual dispatch*/
fix_unpin_do(cy_fix* f, uint16_t gen) {
return (*f->fix_vfns->fix_unpin)(f, gen);
}
static inline int /*virtual dispatch*/
fix_refs_do(const cy_fix* f) {
return (*f->fix_vfns->fix_refs)(f);
}
static inline void /*virtual dispatch*/
fix_cite_do(const cy_fix* f, cy_sink* s) {
(*f->fix_vfns->fix_cite)(f, s);
}
static inline void /*virtual dispatch*/
fix_dump_do(const cy_fix* f, cy_sink* s) {
(*f->fix_vfns->fix_dump)(f, s);
}
fix cow « The following nice methods query whether a fix instance should copy itself (copy-on-write) to be nice to other users sharing state with this instance. As a mnemonic, thing of nice as node+ice: a frozen node because it has too many refs. (See the node thorn demo for more comments on copy-on-write). static inline int /*virtual dispatch refs()*/
cy_fix_nice(const cy_fix* f) {
return (*f->fix_vfns->fix_refs)(f) > 1;
}
static inline int /*virtual dispatch refs()*/
cy_fix_nice_max(const cy_fix* f, int32_t max) {
return (*f->fix_vfns->fix_refs)(f) > max;
}
generation numbers « The best way to initialize fix_gen in each fix instance is by calling cy_rand_gen16() below. /* ----- gen ----- */
uint32_t g_cy_rand_seed = 777; /* std pseudo rand gen seed */
uint16_t cy_rand_gen16() { /* std pseudo rand generation */
uint32_t n = g_cy_rand_seed;
if (0 == n) {
n = 1; // avoid zero seed
}
n = cy_u32rand((unsigned long long) n);
g_cy_rand_seed = n;
return (uint16_t) n;
}
uint32_t cy_rand_gen32() { /* std pseudo rand generation */
uint32_t n = g_cy_rand_seed;
if (0 == n) {
n = 1; // avoid zero seed
}
n = cy_u32rand((unsigned long long) n);
g_cy_rand_seed = n;
return n;
}
gum handles « A gum instance is a small handle holding a ref to a refcounted fix instance. This clones yh0 handles in the node thorn demo. /* ----- gum ----- */
#define cy_gum_DEAD 0xde
#define cy_gum_TAG 'g'
typedef struct cy_gum_ { /* auto fix refcount support */
cy_fix* gum_fix; /* pinned cy_fix instance */
uint16_t gum_gen; /* return from fix_pin_do() */
uint8_t gum_tag; /* must equal TAG */
uint8_t gum_type; /* "type" of fix (YOUR choice) */
} cy_gum;
The central method is cy_gum_set(), which sets the gum_fix while managing all the details of releasing any old reference, and copying values from each fix instance into gum_gen and gum_type to detect errors. /* set() assigns fix in gum: addref new, delref old*/
void cy_gum_set(cy_gum* g, cy_fix* f);
/* fix() returns gum_fix: log & assert AGGRRESSIVELY if nil*/
cy_fix* /* must not be nil: logs & asserts if nil pointer*/
cy_gum_fix(const cy_gum* g); /* may assert(0!=gum_fix)*/
int /* true (nonzero) if gum_fix is nil, or matches this gum*/
cy_gum_good(const cy_gum* g);
void cy_gum_dump(const cy_gum* g, cy_sink* o, const char* arc);
void cy_gum_cite(const cy_gum* g, cy_sink* o, const char* arc);
gum-constructor « You construct an instance using constructor cy_gum_ctor() below (or by using subclass cy::GumT in C++). static inline void
cy_gum_ctor(cy_gum* g, cy_fix* f) { /* constructor */
g->gum_fix = 0;
g->gum_tag = cy_gum_TAG;
g->gum_type = 0; /* zero default*/
cy_gum_set(g, f); /* note f can be zero (nil is okay) */
}
static inline void
cy_gum_dtor(cy_gum* g) { /* destructor */
cy_gum_set(g, 0); /* release any ref inside */
g->gum_tag = cy_gum_DEAD; /* dead */
}
gum references « This set() method is a clone of yh0::_hsetn() in the node thorn demo (cf «). This is the primary way in which fix instances should be refcounted in code using cy api. The intended invariant is easy to say: a fix instance remains alive if and only if a reference from a gum instance still exists. The refcount of a fix should exactly equal the number of gum instances pointing to that refcounted fix object. (If you decide to refcount another way—say by manually calling the pin and unpin methods—you have my sympathies: I've never seen it done correctly without refcount leaks or double releases. Avoiding that expected normative failure is exactly the reason I designed this sort of fix and gum api.) /* ----- gum ----- */
/* set() assigns fix in gum: addref new, delref old*/
void cy_gum_set(cy_gum* g, cy_fix* f) {
if (cy_gum_TAG == g->gum_tag) { // valid gum?
// do nothing if fix f is alreay in this gum:
if (f != g->gum_fix) { // not a NO-OP?
cy_fix* old = g->gum_fix; // release AFTER new pin
uint16_t gen = g->gum_gen; // for unpin() later
g->gum_fix = f; // install new value, nil or not
// ALWAYS acquire first before releasing:
if (f) { // need to acquire new ref?
g->gum_gen = fix_pin_do(f);
g->gum_type = f->fix_type;
assert(cy_fix_TAG == f->fix_tag);
}
if (old) { // need to release old ref?
fix_unpin_do(old, gen);
}
}
}
else { // this gum's tag is wrong (maybe destroyed?)
printf("gum_set() gum_tag=0x%02x not 'g'",
(int) g->gum_tag);
assert(cy_gum_TAG == g->gum_tag); // die
}
}
gum dereference « This fix() method is just a getter returning the gum_fix pointer, but for use in any context where a nil pointer is considered a disaster. For example, when a cy::GumT smart reference uses the pointer in operator->() or operator*(), which both dereference the pointer, it helps to log debug info to a log. /* fix() returns gum_fix: log and assert AGGRRESSIVELY if nil*/
cy_fix* /* must not be nil: logs and asserts if nil pointer*/
cy_gum_fix(const cy_gum* g) /* may assert(0!=gum_fix)*/
{
cy_fix* val = g->gum_fix;
if (cy_gum_TAG == g->gum_tag) { // valid gum?
if (val) {
return val;
}
else {
cy_logf(0, "gum_fix() nil gum_fix");
assert(0 != g->gum_fix); // die
}
}
else {
printf("gum_fix() gum_tag=0x%02x not 'g'",
(int) g->gum_tag);
assert(cy_gum_TAG == g->gum_tag); // die
}
return 0;
}
gum wellness « A gum instance is considered good (well-formed) when it contains a nil pointer, or when the fix pointer points to a fix instance whose current generation number and type match those held inside the gum instance. You can use calls to cy_gum_good() for assertions that references are still good, since otherwise you might not see a problem until the reference is released later (possibly a long time after a problem began). int /* true (nonzero) when gum_fix is nil, or matches gum*/
cy_gum_good(const cy_gum* g) {
cy_fix* f = g->gum_fix;
if (cy_gum_TAG == g->gum_tag) { // valid gum?
if (f) { // need to check the generation number?
if (g->gum_gen == f->fix_gen) {
assert(cy_fix_TAG == f->fix_tag);
return 1; // true: gum and fix gens match
}
else {
cy_logf(0, "gum_good() gum_gen=0x%02x fix_gen=0x%02x",
(int) g->gum_gen, (int) f->fix_gen);
assert(g->gum_gen == f->fix_gen); // die
}
}
else {
return 1; // true: nil pointer is perfectly valid gum
}
}
return 0; // false: the tag is wrong
}
gum printing « A dump method allows multi-line format when necessary (unlike cite format aiming for one line only). When you print a gum handle, it also prints the fix instance inside when one is present. Basically, each gum is considered a proxy for the fix value inside. By convention, the arc parameter is the name of the field whose value is this gum handle. If data structures are object graphs, then each fix is a node and each gum is an arc pointing at that node (which explains node and handle terms in thorn). void cy_gum_dump(const cy_gum* g, cy_sink* o, const char* arc)
{
if (!arc)
arc = "0";
if (g->gum_fix) { // ought to print h_n as well?
cy_sink_ftn(o, "<gum arc=%s fix=%lx gen=%02x>", arc,
(long) g->gum_fix, (int) g->gum_gen);
fix_dump_do(g->gum_fix, o);
cy_sink_us(o, "</gum>");
}
else {
cy_sink_f(o, "<gum arc=%s gum=nil gen=%02x/>",
arc, (int) g->gum_gen);
}
}
void cy_gum_cite(const cy_gum* g, cy_sink* o, const char* arc)
{
if (cy_unlikely(!arc))
arc = "0";
if (g->gum_fix) { // ought to print h_n as well?
cy_sink_f(o, "<gum arc=%s fix=%lx gen=%02x>", arc,
(long) g->gum_fix, (int) g->gum_gen);
fix_cite_do(g->gum_fix, o);
cy_sink_s(o, "</gum>");
}
else {
cy_sink_f(o, "<gum arc=%s fix=nil gen=%02x/>",
arc, (int) g->gum_gen);
}
}
auto pointers
Template class GumT below is a subclass of C-based cy_gum which adds support for smart reference and smart pointer semantics, as well as RAII (resource acquisition is initialization) support. The first GumQ struct shown acts as the wrapper GumT uses for printing control. namespace cy {
struct GumQ {
cy_gum const& q_g; const char* q_arc; int q_cite;
GumQ(cy_gum const& h) : q_g(h), q_arc(""), q_cite(0) { }
GumQ(cy_gum const& h, const char* arc)
: q_g(h), q_arc(arc), q_cite(0) { }
GumQ(cy_gum const& h, Gloss1 const&)
: q_g(h), q_arc(""), q_cite(1) { }
GumQ(cy_gum const& h, const char* arc, Gloss1 const&)
: q_g(h), q_arc(arc), q_cite(1) { }
};
Sometimes it is useful to declare typedefs like FixGum below instead of writing GumT<cy_fix> (which looks horrific as a value in other template classes like Vt<E> on the vector page). template <class F> class GumT; // forward for FixGum
// GumT<cy_fix> synonym is a ref to any counted cy_fix:
// typedef GumT<cy_fix> FixGum; // ref *any* counted subclass
The following GumT<F> template class should be specialized with a cy_fix subtype. For example, cy_fix itself is fine to use as F; or you might use IovFixEtc when only that subclass will be held by a gum instance. This template code clones the yht handle subclass shown in the node thorn demo (cf «). template <class F> // typesafe for cy_fix subclass F
class GumT : public cy_gum { // cy_gum handles basic work
public:
explicit GumT() {
cy_gum_ctor(this, 0); // nil value in gum_fix
}
explicit GumT(F* p) {
cy_gum_ctor(this, p); // add one ref to fix p
}
GumT(const GumT& g) { // ref of same type
cy_gum_ctor(this, g.gum_fix); // add one ref to fix p
}
template <class X> explicit GumT(const GumT<X>& g) {
cy_gum_ctor(this, g.gum_fix); // add one ref to fix p
}
explicit GumT(const cy_gum& g) { // ref of same type
cy_gum_ctor(this, g.gum_fix); // add one ref to fix p
}
~GumT() {
cy_gum_dtor(this); // release ref to cy_fix if present
}
void gclear() {
cy_gum_set(this, 0); // release gum_fix if non-nil
}
GumT<F>& operator=(const GumT& g) { // assign any type
if (gum_fix != g.gum_fix) { // changing the fix?
cy_gum_set(this, g.gum_fix); // replace old ref
}
return *this;
}
template <class X> GumT<F>& operator=(const GumT<X>& g) {
if (gum_fix != g.gum_fix) { // changing the fix?
cy_gum_set(this, g.gum_fix); // replace old ref
}
return *this;
}
GumT<F>& operator=(const cy_gum& g) { // assign any type
if (gum_fix != g.gum_fix) { // changing the fix?
cy_gum_set(this, g.gum_fix); // replace old ref
}
return *this;
}
GumT<F>& operator=(F* p) { // same as gset() below
if (p != gum_fix) { // changing the fix?
cy_gum_set(this, p);
}
return *this;
}
void gset(F* p) {
if (p != gum_fix) { // changing the fix?
cy_gum_set(this, p);
}
}
void gswap(GumT<F>& g) { // swap node w/ other gum
assert(cy_gum_TAG == gum_tag);
assert(cy_gum_TAG == g.gum_tag);
assert(gum_type == g.gum_type); // same type
cy_gum* one = &g;
cy_gum* two = this;
cy_gum tmp = *one;
*one = *two;
*two = tmp;
}
// automatically get pointer value (without checking for nil):
operator F*() const { return (F*)gum_fix; } // no nil check
// deref pointer (log and maybe throw if h_n == nil)
F* operator->() const {
return (F*)(gum_fix? gum_fix: cy_gum_pin(this));
}
F& operator*() const {
return *(F*)(gum_fix? gum_fix: cy_gum_pin(this));
}
bool gdown() { return gum_fix && !this->cy_gum_good(this); }
bool gup() { return !gum_fix || this->cy_gum_good(this); }
bool gnice(int32_t maxRefs) const { // need copy-on-write?
cy_fix* f = gum_fix;
return !f || cy_fix_nice_max(f, maxRefs);
}
GumQ quote() const { return GumQ(*this); } // dump
GumQ quote(const char* arc) const {
return GumQ(*this, arc);
}
GumQ cite() const { return GumQ(*this, cy_gloss); }
GumQ cite(const char* arc) const {
return GumQ(*this, arc, cy_gloss);
}
}; // GumT
inline cy_sink& operator<<(cy_sink& o, GumQ const& x) {
if (x.q_cite)
cy_gum_cite(&x.q_g, &o, x.q_arc);
else
cy_gum_dump(&x.q_g, &o, x.q_arc);
return o;
}
}; // namespace cy
vats
The purpose of this abstract vat api is to provide a standard factory for refcounted fix subclass allocation. Each vat is a container of fix instances. The api implies nothing about whether any given vat can be used for all fix subclasses, or just one. This issue is your problem to manage. This api is C-based because memory heaps might be implemented in C, and yet code in C++ might want to use this api anyway. The result is ugly for C++ code, but allows any C client to provide correct memory service. Returned values come in two flavors: iovec and gum handle. When you are most interested in the iovec itself, you can treat the gum as secondary. But when raw space in iovec form is not useful, the gum is primary (and you will need to cast the cy_fix* pointer to MySubclass*.) To free the fix instance later when refs hit zero (and only when refs hit zero), you only need the cy_fix* pointer. Presumably the unpin() method of the fix instance sees refs hit zero and then passes this pointer back to the vat in a virtual call to vat_free_do(). vat vtable « Each vat must support three allocation methods and one free method. The alloc method resembles malloc(), except the object pointer returned in the gum parameter must be a subclass of cy_fix. The iovec returned specifies the address of usable space in iov_base and the length of that space in iov_len. The cy_fix itself, as a whole, is not described by the iovec unless there's no separate space inside the cy_fix subclass to use as raw space. By definition, the refcount of the cy_fix returned is expected to be one, because the first reference should have gone into the gum provided. However, you might be able to make a vat that re-shares existing fix instances when this makes sense, somehow. (If so, you must only use that vat when callers know the behavior is a bit weird.) The copy method is logically the same as alloc followed by a copy from the source to the new object. This basically aims to reduce tedium in callers who would otherwise perform copies at calling sites. The free method is exactly what it seems: the moral equivalent of free() to match the malloc() of the alloc method. The refcount in the fix instance must be zero, and the free method should assert this. Whether space is actually returned to the heap or to some free list is up to the vat. The bind method has a long explanation if I go into every detail, so I won't. Instead I expect you to fill in blanks after a few clues. But basically it deals with scatter/gather allocation. It's an N-way version of the alloc method. This is necessary when a vat's free pool of space has a maximum size granularity, and you ask for more space than this in one request. Consider two extemes any vat subclass might pursue. First, if arbitrary size allocations are supported, bind can do exactly the same thing as alloc, returning output using only the first slot in both the iovec and gum arrays. But there's another possibility: Second, if a vat has a pool of single-sized buffer—two or four kilobytes apiece for example—then a single large allocation might require a sequence of smaller discontiguous pieces. For example, a 32K request migth be filled with sixteen 2K buffers, if the arrays of iovecs and gums have length of at least sixteen. (The purpose of vat_max, when the value is not zero, is to reveal the maximum size of an allocation that bind will do in scatter/gather style, so a caller can correctly know length of arrays necessary.) You might ask: why not just use alloc N times instead? Because that can cause deadlock: two flows of control, both trying to get N buffers, might exhaust available space without either getting enough to finish a task so space can be released. So the real purpose of bind is to allocate all the space at once, or not at all, to avoid deadlock. Are you having fun yet? Note the heap-based subclass shown below on this page does the simple case: a single contiguous allocation. And the current row implementation uses alloc instead of bind like it should. There's one of the loose ends I said I would not tell you about. I must have had a surge of sympathy for your viewpoint. Code here does not use the ability of bind yet, though it's important in this api. /* ----- cy_vat ----- */
typedef struct cy_vat_ cy_vat; /* has vtable */
typedef cy_iovec /* alloc at least sz bytes (can be more) */
(*cy_vat_alloc_fn)(cy_vat* v, unsigned sz, cy_gum* g);
typedef int /* alloc sz bytes; return count of used iovs */
(*cy_vat_bind_fn)(cy_vat* v, unsigned sz,
cy_iovec* viov, cy_gum* vgum, int cnt);
typedef cy_iovec /* alloc at least src.iov_len & copy src */
(*cy_vat_copy_fn)(cy_vat* v, cy_iovec src, cy_gum* g);
/* note: only fix::on_zero_refs() handler calls free(): */
typedef void /* free one fix allocated by this vat */
(*cy_vat_free_fn)(cy_vat* v, cy_fix* fix);
#define cy_vat_TAG 0x56617476 /* 'Vatv' */
struct cy_vat_ { /* iov heap: 'vat' */
cy_vat_alloc_fn vat_alloc; /* one-iov malloc */
cy_vat_bind_fn vat_bind; /* multi-iov alloc */
cy_vat_copy_fn vat_copy; /* one-iov alloc & copy */
cy_vat_free_fn vat_free; /* free one iov */
uint32_t vat_max; /* max bytes per iov in vat */
uint32_t vat_tag; /* must equal cy_vat_TAG */
};
vat constructor « The constructor initializes ever field. As you look at this abstract object, then compare to others on this page, you might notice some objects put methods in a separate vtable (cy_fix for example) while others like cy_vat put the virtual methods in the object itself. Why is that? Why use separate vtables? Why not here? It's all a matter of how many instances are expected. I expect a huge number of fix subclass instances, but very few vat subclass instances. Thus space efficiency matters in cy_fix but not in cy_vat, so only the former factors out virtual methods in a separate object. That's the whole reason. If I ever needed a zillion vat instances, I'll change cy_vat to look like cy_fix. static inline void /* constructor */
cy_vat_ctor(cy_vat* v, cy_vat_alloc_fn a, cy_vat_bind_fn b,
cy_vat_copy_fn c, cy_vat_free_fn f) {
v->vat_alloc = a;
v->vat_bind = b;
v->vat_copy = c;
v->vat_free = f;
v->vat_max = 0; /* unspecified so far */
v->vat_tag = cy_vat_TAG;
}
vat dispatch « Innline methods are a standard way to call virtual methods: static inline cy_iovec /*virtual dispatch*/
vat_alloc_do(cy_vat* v, unsigned sz, cy_gum* g) {
return (*v->vat_alloc)(v, sz, g);
}
static inline int /*virtual dispatch*/
vat_bind_do(cy_vat* v, unsigned sz,
cy_iovec* viov, cy_gum* vgum, int cnt) {
return (*v->vat_bind)(v, sz, viov, vgum, cnt);
}
static inline cy_iovec /*virtual dispatch*/
vat_copy_do(cy_vat* v, cy_iovec src, cy_gum* g) {
return (*v->vat_copy)(v, src, g);
}
static inline void /*virtual dispatch*/
vat_free_do(cy_vat* v, cy_fix* fix) {
return (*v->vat_free)(v, fix);
}
heap vat « The following vat subclass uses malloc() and free() for allocation. Since the heap is used for space, this subclass is named cy_heapvat. These four methods go into the vat vtable: /* ----- cy_heapvat ----- */
cy_iovec /* alloc at least sz bytes (can be more) */
cy_heapvat_alloc_fn(cy_vat* v, unsigned sz, cy_gum* g);
int /* alloc sz bytes; return count of used iovs */
cy_heapvat_bind_fn(cy_vat* v, unsigned sz,
cy_iovec* viov, cy_gum* vgum, int cnt);
cy_iovec /* alloc at least src.iov_len & copy src */
cy_heapvat_copy_fn(cy_vat* v, cy_iovec src, cy_gum* g);
/* note: only fix::on_zero_refs() handler calls free(): */
void /* free one fix allocated by this vat */
cy_heapvat_free_fn(cy_vat* v, cy_fix* fix);
An instance of cy_vat is declared first to make cy_heapvat a subclass in the sense a pointer to one is a pointer to the other. (A subclass written in C++ can simply use normal inheritance notation, but cy_heapvat is a subclass written in C.) #define cy_heapvat_TAG 0x68705654 /* 'hpVT' */
typedef struct cy_heapvat_ { /* iov heap: 'vat' */
cy_vat hv_vat; /* base class: malloc() and free() */
uint32_t hv_tag; /* cy_heapvat_TAG */
uint32_t hv_iovs; /* count of iovs now allocated*/
uint32_t hv_allocs; /* count of allocations*/
uint32_t hv_frees; /* count of frees*/
uint32_t hv_total; /* sum of all current iov lengths*/
} cy_heapvat;
All the other fields are just counters for usage statistics. They aren't necessary, but in practice you are going to wonder whether actual usage matches what you expected. Note absence of any mutex support—this implies cy_heapvat is single threaded in expected use. (But it need not be since malloc() and free() are thread safe.) heap vat constructor « The constructor simply construsts the base vat class first, then zeroes all counters. static inline void /* constructor */
cy_heapvat_ctor(cy_heapvat* hv) {
cy_vat_ctor(&hv->hv_vat, cy_heapvat_alloc_fn,
cy_heapvat_bind_fn, cy_heapvat_copy_fn,
cy_heapvat_free_fn);
hv->hv_tag = cy_heapvat_TAG; /* just a dynammic tag*/
hv->hv_iovs = 0;
hv->hv_allocs = 0;
hv->hv_frees = 0;
hv->hv_total = 0;
}
cy_vat* cy_global_heapvat(); // non-threadsafe global vat
void cy_heapvat_cite(const cy_heapvat* hv, cy_sink* o);
heap vat global « Since this heap vat uses the global heap to allocate and free, there's little reason to have more than one instance, so code below returns a single well known global instance. /* ----- heapvat ----- */
cy_heapvat g_cy_global_heapvat; // NOT for threaded use
cy_vat* cy_global_heapvat() { // non-threadsafe global vat
if (0 == g_cy_global_heapvat.hv_tag) { // 1st time?
cy_heapvat_ctor(&g_cy_global_heapvat);
}
return &g_cy_global_heapvat.hv_vat;
}
heap vat printing « Printing a heap vat just prints the stats: void cy_heapvat_cite(const cy_heapvat* hv, cy_sink* o) {
assert(cy_heapvat_TAG == hv->hv_tag);
cy_sink_f(o,
"<heapvat iovs=%u allocs=%u frees=%u total=%u/>",
(unsigned) hv->hv_iovs, (unsigned) hv->hv_allocs,
(unsigned) hv->hv_frees, (unsigned) hv->hv_total);
}
To use operator<<() when writing a heap vat to a sink, all you need is inlines as follows, declared in a C++ header file: inline cy_sink& operator<<(cy_sink& o, cy_heapvat const& x) {
cy_heapvat_cite(&x, &o); return o;
}
inline cy_sink& operator<<(cy_sink& o, Cite<cy_heapvat> const& x) {
cy_heapvat_cite(&x.c_t, &o); return o;
}
heap vat alloc « The heap vat allocation methods use malloc() to get the necessary space, but a cy_fix subclass named IovFixEtc, written in C++ shown below, is used to represent the space returned. Note the implementation of IovFixEtc clones the y8vn node subclass shown in the node thorn demo (cf «). int /* alloc sz bytes; return count of used iovs */
cy_heapvat_bind_fn(cy_vat* v, unsigned sz,
cy_iovec* viov, cy_gum* vgum, int cnt) {
// this heap vat always allocates just one contig iov:
*viov = cy_heapvat_alloc_fn(v, sz, vgum);
return (0 != viov->iov_base); // the one alloc succeeded
}
cy_iovec /* alloc at least sz bytes (can be more) */
cy_heapvat_alloc_fn(cy_vat* v, unsigned sz, cy_gum* g) {
cy_heapvat* hv = (cy_heapvat*) v;
assert(cy_heapvat_TAG == hv->hv_tag);
if (sz) {
unsigned vol = sizeof(cy::IovFixEtc) + sz;
cy::IovFixEtc* eraw = (cy::IovFixEtc*) ::malloc(vol);
if (eraw) {
cy::IovFixEtc* e = new(eraw) cy::IovFixEtc(v, sz);
++hv->hv_allocs;
hv->hv_total += vol; // total bytes allocated
bool goodEtc = e->egood(); // good magic numbers
if (!goodEtc) { // configured wrong??
e->ebad(); // describe the problem
assert(goodEtc); // die first thing
}
cy_gum_set(g, e); // create first ref to e in g
return e->fix_iov; // the iovec that g pins
}
}
cy_iovec none; none.iov_base = 0; none.iov_len = 0;
return none;
}
cy_iovec /* alloc at least src.iov_len & copy src */
cy_heapvat_copy_fn(cy_vat* v, cy_iovec src, cy_gum* g) {
cy_heapvat* hv = (cy_heapvat*) v;
assert(cy_heapvat_TAG == hv->hv_tag);
if (src.iov_base && src.iov_len) {
unsigned vol = sizeof(cy::IovFixEtc) + src.iov_len;
cy::IovFixEtc* eraw = (cy::IovFixEtc*) ::malloc(vol);
if (eraw) {
cy::IovFixEtc* e =
new(eraw) cy::IovFixEtc(v, src.iov_len);
++hv->hv_allocs;
hv->hv_total += vol; // total bytes allocated
memcpy(e->etc_v, src.iov_base, src.iov_len);
bool goodEtc = e->egood(); // good magic numbers
if (!goodEtc) { // configured wrong??
e->ebad(); // describe the problem
assert(goodEtc); // die first thing
}
cy_gum_set(g, e); // create first ref to e in g
return e->fix_iov; // the iovec that g pins
}
}
cy_iovec none; none.iov_base = 0; none.iov_len = 0;
return none;
}
heap vat free « When the iov allocated by a heap vat hits zero references, it gets freed by calling the free method below, which checks for IovFixEtc instance corruption (due to underwrites or overwrites, for example) by checking for expected guard byte values before and after. /* note: only fix::on_zero_refs() handler calls free(): */
void /* free one fix allocated by this vat */
cy_heapvat_free_fn(cy_vat* v, cy_fix* fix) {
cy::IovFixEtc* e = (cy::IovFixEtc*) fix;
cy_heapvat* hv = (cy_heapvat*) v;
bool goodEtc = e->egood(); // good magic numbers
assert(cy_heapvat_TAG == hv->hv_tag);
if (!goodEtc) { // configured wrong??
e->ebad(); // describe the problem
assert(goodEtc); // die first thing
}
assert(0 == e->fix_refs);
unsigned vol = sizeof(cy::IovFixEtc) + e->fix_iov.iov_len;
e->etc_tag = 0xdeadbeef;
::free(e);
++hv->hv_frees;
if (hv->hv_total >= vol)
hv->hv_total -= vol;
else {
hv->hv_total = 0;
printf("cy_heapvat_free: hv_total=%d < vol=%d",
(int) hv->hv_total, (int) vol);
}
}
pinnable iovs
Actually, two subclasses of cy_fix are shown below. The second, IovFixEtc, inherits from a base IovFix which only knows an iovec is involved. This allows base IovFix to be re-used later when free pools of single size iovs are allocated by another vat. Virtual methods are defined by IovFix and are re-used unchanged by subclass IovFixEtc. (This works because only the vat involved knows the iov is actually a IovFixEtc instance, and not another IovFix subtype.) iov fix « Here are cy_fix virtual methods defined for IovFix: namespace cy { // ----- cy -----
/* ----- IovFix ----- */
struct IovFix; // forward for cy_fix subclass
uint16_t /* addref */
IovFix_pin_fn(cy_fix* f); /* cy_fix_pin_fn */
void /* delref */
IovFix_unpin_fn(cy_fix* f, uint16_t gen); /* cy_fix_unpin_fn */
int /* refcount getter */
IovFix_refs_fn(const cy_fix* f); /* cy_fix_refs_fn */
void /* 1-line print */
IovFix_cite_fn(const cy_fix* f, cy_sink* s);
void /* N-line print */
IovFix_dump_fn(const cy_fix* f, cy_sink* s);
The IovFix struct adds members for refcount, iovec, and vat pointer. struct IovFix : public cy_fix { // fix to pin iov
// struct cy_fix_ { /* refcounted objects 'fix' */
// cy_fix_vfns* fix_vfns; /* virtual method table */
// uint16_t fix_gen;
// uint8_t fix_tag; /* must equal cy_fix_TAG */
// uint8_t fix_type; /* subclass specific value*/
// };
enum { e_iov_type = 'v' }; // goes in fix_type
// PRIVATE members and methods BY CONVENTION
int32_t fix_refs; // refcount
cy_vat* fix_vat;
cy_iovec fix_iov; // contiguous bytes
static cy_fix_vfns* vfns(); // static global vtable
void on_zero_refs() {
cy_vat* v = fix_vat;
assert(0 == fix_refs);
if (v) {
vat_free_do(v, this);
}
}
// ~IovFix(); // NOTE: must NOT explicitly destroy or free
IovFix(cy_vat* vat, void* base, unsigned len);
};
When refs hit zero, on_zero_refs is called to return this IovFix instance to the vat. Note this works even when the specific subclass of IovFix is not known. iov fix etc « Now here's the actual concrete subclass of both cy_fix and IovFix used by cy_heapvat: // IovFixEtc clones y8vn to append iovec state after IovFix:
struct IovFixEtc : public IovFix { // iov follows inline
u16 etc_tag; enum { e_tag = 0x4574 /*'vEtc'*/ };
u8 etc_v[2]; // space for trailing null and 0xAA
IovFixEtc(cy_vat* vat, unsigned len);
// note: all "v8" names are same as original codenames
bool egood() const { // good 1-byte magic signatures
u32 n = fix_iov.iov_len;
return e_tag == etc_tag && 0==etc_v[n]
&& fix_iov.iov_base == etc_v && 0xAA==etc_v[n+1];
}
void ebad() const; // egood failed
void ememset(u8 val) { // set all m_len bytes to val
u32 n = fix_iov.iov_len;
if (n && e_tag == etc_tag)
::memset(etc_v, val, n);
}
// size() and c_str() mimic std::string method names:
u32 size() const { return fix_iov.iov_len; }
const char* c_str() const { return (const char*) etc_v; }
};
The two bytes of member etc_v[2] provide space for a trailing null byte and a trailing 0xAA after-guard byte. Null termination is convenient when the iovec might be used as a C string, especially when viewed by debug tools. Note the length of the iovec is stored in the iov_len field in the iovec member of the IovFix base class. iov fix constructor « The constructor assumes you have passed the right values for base and len. (When the subclass is IovFixEtc, those two values should be the address etc_v and the length of bytes allocated in excess of sizeof(IovFixEtc).) cy_fix_vfns IovFix_vfns = { /* vtable */
IovFix_pin_fn, IovFix_unpin_fn, IovFix_refs_fn,
IovFix_cite_fn, IovFix_dump_fn
};
cy_fix_vfns* IovFix::vfns() { return &IovFix_vfns; }
IovFix::IovFix(cy_vat* vat, void* base, unsigned len) {
fix_vfns = &IovFix_vfns; /* vtable */
fix_gen = cy_rand_gen16();
fix_tag = cy_fix_TAG; /* same for ALL fix subclasses */
fix_type = e_iov_type;
fix_refs = 0;
fix_vat = vat;
fix_iov.iov_base = base;
fix_iov.iov_len = len;
}
iov fix addref « namespace cy { // non-C-scope globals and utilities
///// ----- IovFix ----- /////
uint16_t /* addref */
IovFix_pin_fn(cy_fix* f) { /* cy_fix_pin_fn */
IovFix* self = (IovFix*) f;
assert(IovFix::e_iov_type == f->fix_type);
assert(self->fix_refs >= 0);
++self->fix_refs;
return f->fix_gen;
}
iov fix delref « void /* delref */
IovFix_unpin_fn(cy_fix* f, uint16_t gen) { /* cy_fix_unpin_fn */
IovFix* self = (IovFix*) f;
assert(IovFix::e_iov_type == f->fix_type);
assert(self->fix_refs > 0);
if (0 == --self->fix_refs) {
self->on_zero_refs(); // free to vat
}
}
iov fix current refs « int /* refcount getter */
IovFix_refs_fn(const cy_fix* f) { /* cy_fix_refs_fn */
IovFix* self = (IovFix*) f;
assert(IovFix::e_iov_type == f->fix_type);
assert(self->fix_refs >= 0);
return self->fix_refs;
}
iov fix printing « void /* 1-line print */
IovFix_cite_fn(const cy_fix* f, cy_sink* o) {
IovFix* self = (IovFix*) f;
assert(IovFix::e_iov_type == f->fix_type);
assert(self->fix_refs >= 0);
cy_iovec* v = &self->fix_iov;
cy_sink_f(o, "<IovFix refs=%d gen=%02x v=%#lx len=%d/>",
(int) self->fix_refs, (int) self->fix_gen,
(long) v->iov_base, (int) v->iov_len);
}
void /* N-line print */
IovFix_dump_fn(const cy_fix* f, cy_sink* o) {
IovFix* self = (IovFix*) f;
assert(IovFix::e_iov_type == f->fix_type);
assert(self->fix_refs >= 0);
if (IovFix::e_iov_type == f->fix_type) {
cy_iovec* v = &self->fix_iov;
cy_sink_ftn(o, "<IovFix refs=%d gen=%02x v=%#lx len=%d>",
(int) self->fix_refs, (int) self->fix_gen,
(long) v->iov_base, (int) v->iov_len);
Iov vdat(*v);
vdat.vhexmax(*o, /*maxbytes*/ 8192);
*o << cy_subi << "</IovFix>";
}
else
fix_cite_do(f, o);
}
IovFixEtc constructor « Here's the constructor for leaf class IovFixEtc. Note the assumption that length passed is count bytes allocated in excess of sizeof(IovFixEtc). ///// ----- IovFixEtc ----- /////
IovFixEtc::IovFixEtc(cy_vat* vat, unsigned len)
: IovFix(vat, etc_v, len), etc_tag(e_tag)
{
etc_v[len] = 0;
etc_v[len+1] = 0xAA; // the "after" byte value
}
IovFixEtc errors « When one of the magic numbers checked in actual concrete subclass of both egood() fails, here is where the problem is triaged, logged, and asserted: void IovFixEtc::ebad() const { // egood failed
u32 n = fix_iov.iov_len;
u32 a = n + 1;
if (e_tag != etc_tag) {
cy_logf(0, "etc_tag=%#lx != e_tag=%#lx",
(long) etc_tag, (long) e_tag);
assert(e_tag == etc_tag); // die
}
if (e_iov_type != fix_type) {
cy_logf(0, "e_iov_type=%#x != fix_type=%#x",
(int) e_iov_type, (int) fix_type);
assert(e_iov_type == fix_type); // die
}
if (fix_iov.iov_base != etc_v) {
cy_logf(0, "fix_iov.iov_base=%#lx != etc_v=%#lx",
(long) fix_iov.iov_base, (long) etc_v);
assert(fix_iov.iov_base == etc_v); // die
}
if (0 != etc_v[n]) {
cy_logf(0, "n=%u etc_v[n]=%#x != 0x00",
(int) etc_v[n], (int) fix_type);
assert(0 == etc_v[n]); // die
}
if (0xAA != etc_v[a]) {
cy_logf(0, "n=%u etc_v[n+1]=%#x != 0xAA", (int) n,
(int) etc_v[a], (int) fix_type);
assert(0xAA == etc_v[a]); // die
}
}
pools
A pool is similar to a vat but assumes no refcounting, and further assumes no maximum size granularity is necessary. (Thus no complex bind method like one in cy_vat is needed.) pool vtable « Pool methods cover only alloc, free, and print in cite format (one line only please). /* ----- cy_pool ----- */
typedef struct cy_pool_ cy_pool; /* has vtable */
typedef cy_iovec /* iov_base: space, iov_len: actual size*/
(*cy_pool_alloc_fn)(cy_pool* p, unsigned minSize);
typedef void (*cy_pool_free_fn)(cy_pool* p, cy_iovec old);
typedef void /* 1-line print (optional: nil is okay) */
(*cy_pool_cite_fn)(const cy_pool* p, cy_sink* o);
struct cy_pool_ { /* iov pool: 'pool' */
cy_pool_alloc_fn pool_alloc; /* one-iov malloc */
cy_pool_free_fn pool_free; /* free one iov */
cy_pool_cite_fn pool_cite; /* 1-line stat print */
};
pool constructor « The constructor just assigns function pointers: static inline void /* constructor */
cy_pool_ctor(cy_pool* p, cy_pool_alloc_fn a,
cy_pool_free_fn f, cy_pool_cite_fn c) {
p->pool_alloc = a;
p->pool_free = f;
p->pool_cite = c;
}
pool dispatch « Inline methods dispatch virtual calls: static inline cy_iovec /*virtual dispatch*/
pool_alloc_do(cy_pool* p, unsigned minSize) {
cy_pool_alloc_fn fn = p->pool_alloc;
if (fn) {
return (*fn)(p, minSize);
}
cy_iovec iov;
iov.iov_base = malloc(minSize);
iov.iov_len = minSize;
return iov;
}
static inline void /*virtual dispatch*/
pool_free_do(cy_pool* p, cy_iovec old) {
cy_pool_free_fn fn = p->pool_free;
if (fn)
(*fn)(p, old);
else
free(old.iov_base);
}
static inline void /*virtual dispatch*/
pool_cite_do(const cy_pool* p, cy_sink* o) {
cy_pool_cite_fn fn = p->pool_cite;
if (fn) {
(*fn)(p, o);
}
}
heap pool « The following concrete subclass of cy_vat is written in C and uses malloc() and free() for allocation. (So just like the heap vat class shown earlier, there's little need to have more than one global instance.) /* ----- cy_heappool ----- */
cy_iovec /* iov_base: space, iov_len: actual size*/
cy_heappool_alloc(cy_pool* p, unsigned minSize);
void cy_heappool_free(cy_pool* p, cy_iovec old);
void /* 1-line print (optional: nil is okay) */
cy_heappool_cite(const cy_pool* p, cy_sink* o);
To be a subclass of cy_vat, the first member must be an instance of cy_vat. (A subclass in C++ can simply inherit cy_vat instead.) #define cy_heappool_TAG 0x6870504c /* 'hpPL' */
typedef struct cy_heappool_ { /* malloc based 'pool' */
cy_pool hp_pool; /* base class: malloc() and free() */
uint32_t hp_tag; /* cy_heappool_TAG */
uint32_t hp_iovs; /* count of iovs now allocated*/
uint32_t hp_allocs; /* count of allocations*/
uint32_t hp_frees; /* count of frees*/
uint32_t hp_total; /* sum of all current iov lengths*/
} cy_heappool;
heap pool constructor « The constructor constructs the base cy_vat class, then clears all counter statistics. static inline void /* constructor */
cy_heappool_ctor(cy_heappool* hp) {
cy_pool_ctor(&hp->hp_pool, cy_heappool_alloc,
cy_heappool_free, cy_heappool_cite);
hp->hp_tag = cy_heappool_TAG; /* just a dynammic tag*/
hp->hp_iovs = 0;
hp->hp_allocs = 0;
hp->hp_frees = 0;
hp->hp_total = 0;
}
cy_pool* cy_global_heappool(); // non-threadsafe global pool
heap pool global « Here's a single global heap pool instance, which is not thread-safe in the statisics. Single threaded use is assumed here. /* ----- heappool ----- */
cy_heappool g_cy_global_heappool; // NOT for threaded use
cy_pool* cy_global_heappool() { // non-threadsafe global pool
if (0 == g_cy_global_heappool.hp_tag) { // 1st time?
cy_heappool_ctor(&g_cy_global_heappool);
}
return &g_cy_global_heappool.hp_pool;
}
heap pool printing « Printing just exposes stats: void cy_heappool_cite(const cy_pool* v, cy_sink* o) {
cy_heappool* hv = (cy_heappool*) v;
assert(cy_heappool_TAG == hv->hp_tag);
cy_sink_f(o,
"<heappool iovs=%u allocs=%u frees=%u total=%u/>",
(unsigned) hv->hp_iovs, (unsigned) hv->hp_allocs,
(unsigned) hv->hp_frees, (unsigned) hv->hp_total);
}
heap pool alloc « cy_iovec /* alloc at least sz bytes (can be more) */
cy_heappool_alloc(cy_pool* v, unsigned sz) {
cy_heappool* hv = (cy_heappool*) v;
assert(cy_heappool_TAG == hv->hp_tag);
if (sz) {
void* ptr = ::malloc(sz);
if (sz) {
++hv->hp_allocs;
hv->hp_total += sz; // total bytes allocated
cy_iovec piov;
piov.iov_base = ptr;
piov.iov_len = sz;
return piov; // the iovec for this pool
}
}
cy_iovec none; none.iov_base = 0; none.iov_len = 0;
return none;
}
/* note: only fix::on_zero_refs() handler calls free(): */
void /* free one fix allocated by this pool */
cy_heappool_free(cy_pool* v, cy_iovec old) {
cy_heappool* hv = (cy_heappool*) v;
assert(cy_heappool_TAG == hv->hp_tag);
if (old.iov_base) { // anything to free?
::free(old.iov_base);
++hv->hp_frees;
if (hv->hp_total >= old.iov_len)
hv->hp_total -= old.iov_len;
else {
hv->hp_total = 0;
printf("cy_heappool_free: hp_total=%d < len=%d",
(int) hv->hp_total, (int) old.iov_len);
}
}
}
menu
Here's a menu of pages on cy code.
license
See license and copyright for code here. For more context, see the cy page.
comments
Compared to a thorn demo, I explain cy code less: I care little whether folks use or grasp cy source. But since I aim to get ideas across, I reveal a point to code constructs so you see intentions. If you ask: What was this for? That's the only question I address: why a thing was done. If you what to know how code works or what loose ends remain, figure it out.
color coding
Library source code appears appears in amber (orange/brown): amber is_source(code* c);
Source .cpp code appears in red: void cy_logf(int, const char* f, ...) {
char temp[ 2048 + 4 ];
va_list args;
va_start(args,f);
vsnprintf(temp, 2048, f, args);
va_end(args);
temp[2048] = 0;
printf("%s\n", temp);
}
Sample test code is purple: o << "# purple=test green=stdout" << cy_newl;
Printed output on stdout is green: # purple=test green=stdout
I know these aren't the best color cues. (Amber and green might appear the same hue to color blind folks. I have excellent color discrimination myself.)
testing
This page has no sample test code because the row page provides a basic integrated test of all these objects. |