buf.c (3362B)
1 /* Chunked byte buffer. Append is O(1) (spills to a fresh chunk when the 2 * tail fills). Random-access patch is O(N_chunks) — sections rarely 3 * cross more than a handful of chunks, so a linear walk is fine. */ 4 5 #include "core/buf.h" 6 7 #include <string.h> 8 9 static BufChunk* chunk_new(Heap* h, size_t cap) { 10 BufChunk* c = 11 (BufChunk*)h->alloc(h, sizeof(BufChunk) + cap, _Alignof(BufChunk)); 12 if (!c) return NULL; 13 c->next = NULL; 14 c->used = 0; 15 c->cap = (u32)cap; 16 return c; 17 } 18 19 void buf_init(Buf* b, Heap* h) { 20 b->heap = h; 21 b->head = NULL; 22 b->tail = NULL; 23 b->total = 0; 24 } 25 26 void buf_fini(Buf* b) { 27 BufChunk* c = b->head; 28 while (c) { 29 BufChunk* next = c->next; 30 b->heap->free(b->heap, c, sizeof(BufChunk) + c->cap); 31 c = next; 32 } 33 b->head = b->tail = NULL; 34 b->total = 0; 35 } 36 37 static int buf_ensure_tail(Buf* b, size_t need) { 38 BufChunk* c; 39 size_t cap; 40 if (b->tail && (b->tail->cap - b->tail->used) >= need) return 0; 41 cap = need > BUF_CHUNK ? need : BUF_CHUNK; 42 c = chunk_new(b->heap, cap); 43 if (!c) return 1; 44 if (!b->head) b->head = c; 45 if (b->tail) b->tail->next = c; 46 b->tail = c; 47 return 0; 48 } 49 50 void buf_write(Buf* b, const void* data, size_t n) { 51 const u8* p = (const u8*)data; 52 while (n) { 53 size_t avail; 54 if (buf_ensure_tail(b, 1)) return; /* allocation failure swallowed */ 55 avail = b->tail->cap - b->tail->used; 56 if (avail > n) avail = n; 57 memcpy(b->tail->data + b->tail->used, p, avail); 58 b->tail->used += (u32)avail; 59 b->total += (u32)avail; 60 p += avail; 61 n -= avail; 62 } 63 } 64 65 u8* buf_reserve(Buf* b, size_t n) { 66 u8* p; 67 if (buf_ensure_tail(b, n)) return NULL; 68 p = b->tail->data + b->tail->used; 69 b->tail->used += (u32)n; 70 b->total += (u32)n; 71 return p; 72 } 73 74 u32 buf_pos(const Buf* b) { return b->total; } 75 76 /* Walk the chunk list intersecting [ofs, ofs+n), invoking copy(...) for 77 * each contiguous slice. Patch and read share this; the only difference 78 * is the direction of the memcpy. 79 * 80 * `direction`: 0 = copy from `external` into chunk; 1 = copy from chunk 81 * into `external`. Inlined at both call sites by clang. */ 82 static inline void buf_walk(BufChunk* head, u32 ofs, void* external, size_t n, 83 int from_chunk) { 84 BufChunk* c = head; 85 u32 chunk_start = 0; 86 u8* ext = (u8*)external; 87 while (c && n) { 88 u32 chunk_end = chunk_start + c->used; 89 if (ofs < chunk_end) { 90 u32 within = ofs - chunk_start; 91 u32 avail = c->used - within; 92 u32 take = (u32)(n < avail ? n : avail); 93 if (from_chunk) 94 memcpy(ext, c->data + within, take); 95 else 96 memcpy(c->data + within, ext, take); 97 ext += take; 98 n -= take; 99 ofs += take; 100 } 101 chunk_start = chunk_end; 102 c = c->next; 103 } 104 /* Caller's range must lie inside the written range; if n != 0 here, 105 * the caller exceeded buf_pos and this is a contract violation. 106 * Silent drop matches buf_write's allocation-failure policy. */ 107 } 108 109 void buf_patch(Buf* b, u32 ofs, const void* data, size_t n) { 110 buf_walk(b->head, ofs, (void*)data, n, /*from_chunk=*/0); 111 } 112 113 void buf_read(const Buf* b, u32 ofs, void* dst, size_t n) { 114 buf_walk(b->head, ofs, dst, n, /*from_chunk=*/1); 115 } 116 117 void buf_flatten(const Buf* b, u8* dst) { 118 BufChunk* c = b->head; 119 while (c) { 120 memcpy(dst, c->data, c->used); 121 dst += c->used; 122 c = c->next; 123 } 124 }