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 }