kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

buf.c (5796B)


      1 /* Chunked byte buffer. Append is O(1) (spills to a fresh chunk when the tail
      2  * fills). Random-access patch/read is O(log N_chunks) via a sorted directory of
      3  * chunk start offsets — large sections (e.g. a multi-megabyte .text emitted for
      4  * tens of thousands of functions) span hundreds of chunks, and per-function
      5  * patch-ups would otherwise be O(N_chunks) each, i.e. O(n^2) overall. */
      6 
      7 #include "core/buf.h"
      8 
      9 #include <string.h>
     10 
     11 static BufChunk* chunk_new(Heap* h, size_t cap) {
     12   BufChunk* c =
     13       (BufChunk*)h->alloc(h, sizeof(BufChunk) + cap, _Alignof(BufChunk));
     14   if (!c) return NULL;
     15   c->next = NULL;
     16   c->used = 0;
     17   c->cap = (u32)cap;
     18   return c;
     19 }
     20 
     21 void buf_init(Buf* b, Heap* h) {
     22   b->heap = h;
     23   b->head = NULL;
     24   b->tail = NULL;
     25   b->total = 0;
     26   b->dir = NULL;
     27   b->ndir = 0;
     28   b->dir_cap = 0;
     29 }
     30 
     31 void buf_fini(Buf* b) {
     32   BufChunk* c = b->head;
     33   while (c) {
     34     BufChunk* next = c->next;
     35     b->heap->free(b->heap, c, sizeof(BufChunk) + c->cap);
     36     c = next;
     37   }
     38   if (b->dir)
     39     b->heap->free(b->heap, b->dir, (size_t)b->dir_cap * sizeof(BufChunkRef));
     40   b->head = b->tail = NULL;
     41   b->total = 0;
     42   b->dir = NULL;
     43   b->ndir = b->dir_cap = 0;
     44 }
     45 
     46 /* Record a newly created chunk in the directory. Best-effort: on allocation
     47  * failure the chunk is still reachable via the `next` chain, so buf_walk stays
     48  * correct (just slower for offsets past the last indexed chunk). */
     49 static void buf_dir_push(Buf* b, BufChunk* c, u32 start) {
     50   if (b->ndir == b->dir_cap) {
     51     u32 ncap = b->dir_cap ? b->dir_cap * 2u : 16u;
     52     BufChunkRef* nd = (BufChunkRef*)b->heap->alloc(
     53         b->heap, (size_t)ncap * sizeof(BufChunkRef), _Alignof(BufChunkRef));
     54     if (!nd) return;
     55     if (b->dir) {
     56       memcpy(nd, b->dir, (size_t)b->ndir * sizeof(BufChunkRef));
     57       b->heap->free(b->heap, b->dir, (size_t)b->dir_cap * sizeof(BufChunkRef));
     58     }
     59     b->dir = nd;
     60     b->dir_cap = ncap;
     61   }
     62   b->dir[b->ndir].start = start;
     63   b->dir[b->ndir].chunk = c;
     64   b->ndir++;
     65 }
     66 
     67 static int buf_ensure_tail(Buf* b, size_t need) {
     68   BufChunk* c;
     69   size_t cap;
     70   if (b->tail && (b->tail->cap - b->tail->used) >= need) return 0;
     71   cap = need > BUF_CHUNK ? need : BUF_CHUNK;
     72   c = chunk_new(b->heap, cap);
     73   if (!c) return 1;
     74   /* The new chunk starts at the current total (all prior chunks are frozen). */
     75   buf_dir_push(b, c, b->total);
     76   if (!b->head) b->head = c;
     77   if (b->tail) b->tail->next = c;
     78   b->tail = c;
     79   return 0;
     80 }
     81 
     82 /* Out-of-line tail of buf_write: handles the empty-tail and chunk-spanning
     83  * cases. The common single-chunk append is inlined in buf.h; only writes that
     84  * don't fit the current tail (or hit an empty/NULL tail) land here. */
     85 void buf_write_slow(Buf* b, const void* data, size_t n) {
     86   const u8* p = (const u8*)data;
     87   while (n) {
     88     size_t avail;
     89     if (buf_ensure_tail(b, 1)) return; /* allocation failure swallowed */
     90     avail = b->tail->cap - b->tail->used;
     91     if (avail > n) avail = n;
     92     memcpy(b->tail->data + b->tail->used, p, avail);
     93     b->tail->used += (u32)avail;
     94     b->total += (u32)avail;
     95     p += avail;
     96     n -= avail;
     97   }
     98 }
     99 
    100 u8* buf_reserve(Buf* b, size_t n) {
    101   u8* p;
    102   if (buf_ensure_tail(b, n)) return NULL;
    103   p = b->tail->data + b->tail->used;
    104   b->tail->used += (u32)n;
    105   b->total += (u32)n;
    106   return p;
    107 }
    108 
    109 u32 buf_pos(const Buf* b) { return b->total; }
    110 
    111 /* Walk the chunk list intersecting [ofs, ofs+n), invoking copy(...) for
    112  * each contiguous slice. Patch and read share this; the only difference
    113  * is the direction of the memcpy.
    114  *
    115  * `direction`: 0 = copy from `external` into chunk; 1 = copy from chunk
    116  * into `external`. Inlined at both call sites by clang. */
    117 static inline void buf_walk(const Buf* b, u32 ofs, void* external, size_t n,
    118                             int from_chunk) {
    119   BufChunk* c;
    120   u32 chunk_start;
    121   u8* ext = (u8*)external;
    122   /* Fast path: emit-time patch-ups and reads overwhelmingly target the
    123    * most-recently-written bytes (a function's own just-emitted prologue and
    124    * branches), which live in the last indexed chunk. Check it before the binary
    125    * search so the common case is O(1) instead of O(log n_chunks). */
    126   if (b->ndir > 0 && ofs >= b->dir[b->ndir - 1].start) {
    127     c = b->dir[b->ndir - 1].chunk;
    128     chunk_start = b->dir[b->ndir - 1].start;
    129   } else {
    130     /* Binary-search the directory for the last chunk whose start is <= ofs,
    131      * then walk forward (the range may span into following chunks). */
    132     u32 lo = 0, hi = b->ndir;
    133     while (lo < hi) {
    134       u32 mid = lo + ((hi - lo) >> 1);
    135       if (b->dir[mid].start <= ofs)
    136         lo = mid + 1;
    137       else
    138         hi = mid;
    139     }
    140     if (lo == 0) {
    141       c = b->head;
    142       chunk_start = 0;
    143     } else {
    144       c = b->dir[lo - 1].chunk;
    145       chunk_start = b->dir[lo - 1].start;
    146     }
    147   }
    148   while (c && n) {
    149     u32 chunk_end = chunk_start + c->used;
    150     if (ofs < chunk_end) {
    151       u32 within = ofs - chunk_start;
    152       u32 avail = c->used - within;
    153       u32 take = (u32)(n < avail ? n : avail);
    154       if (from_chunk)
    155         memcpy(ext, c->data + within, take);
    156       else
    157         memcpy(c->data + within, ext, take);
    158       ext += take;
    159       n -= take;
    160       ofs += take;
    161     }
    162     chunk_start = chunk_end;
    163     c = c->next;
    164   }
    165   /* Caller's range must lie inside the written range; if n != 0 here,
    166    * the caller exceeded buf_pos and this is a contract violation.
    167    * Silent drop matches buf_write's allocation-failure policy. */
    168 }
    169 
    170 void buf_patch(Buf* b, u32 ofs, const void* data, size_t n) {
    171   buf_walk(b, ofs, (void*)data, n, /*from_chunk=*/0);
    172 }
    173 
    174 void buf_read(const Buf* b, u32 ofs, void* dst, size_t n) {
    175   buf_walk(b, ofs, dst, n, /*from_chunk=*/1);
    176 }
    177 
    178 void buf_flatten(const Buf* b, u8* dst) {
    179   BufChunk* c = b->head;
    180   while (c) {
    181     memcpy(dst, c->data, c->used);
    182     dst += c->used;
    183     c = c->next;
    184   }
    185 }