summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/array.c105
-rw-r--r--src/lib.c184
-rw-r--r--src/log.c90
-rw-r--r--src/ring_buffer.c169
-rw-r--r--src/str.c113
5 files changed, 661 insertions, 0 deletions
diff --git a/src/array.c b/src/array.c
new file mode 100644
index 0000000..ffdc36b
--- /dev/null
+++ b/src/array.c
@@ -0,0 +1,105 @@
+#include "../include/al/lib.h"
+#include "../include/al/array_sort.h"
+
+// based on: https://github.com/lifthrasiir/angolmois/blob/master/angolmois.c#L79
+
+#define array(type) \
+ struct { \
+ u32 size; \
+ u32 alloc; \
+ type *data; \
+ }
+
+#define al_array_init(arr) ((arr).size = 0, (arr).alloc = 0, (arr).data = NULL)
+#define al_array_item_size(arr) (sizeof(*(arr).data)) // NOLINT(bugprone-sizeof-expression)
+
+#define al_array_at(arr, i) ((arr).data[i])
+#define al_array_last(arr) al_array_at(arr, (arr).size - 1)
+
+#define al_array_reserve(arr, size) \
+ ((arr).alloc = _al_array_reserve((void *)&(arr).data, al_array_item_size(arr), (arr).alloc, size))
+
+#define al_array_clone(dest, src) \
+ do { \
+ al_array_init(dest); \
+ al_array_reserve(dest, (src).size); \
+ al_memcpy(&al_array_at(dest, 0), &al_array_at(src, 0), \
+ al_array_item_size(dest) * (dest).size); \
+ (dest).size = (src).size; \
+ } while (0)
+
+#define al_array_push(arr, item) \
+ do { \
+ al_array_reserve(arr, (arr).size + 1); \
+ al_array_at(arr, (arr).size++) = item; \
+ } while (0)
+
+#define al_array_insert(arr, item, i) \
+ do { \
+ if ((arr).size > i) { \
+ al_array_reserve(arr, (arr).size + 1); \
+ al_memmove(&al_array_at(arr, i + 1), &al_array_at(arr, i), \
+ al_array_item_size(arr) * ((arr).size - i)); \
+ (arr).size++; \
+ } else { \
+ al_array_reserve(arr, i + 1); \
+ (arr).size = i + 1; \
+ } \
+ al_array_at(arr, i) = item; \
+ } while (0)
+
+#define al_array_pop_at(arr, i, r) \
+ do { \
+ r = al_array_at((arr), i); \
+ al_array_remove_at((arr), i); \
+ } while (0)
+
+#define al_array_pop(arr) (al_array_at(arr, --(arr).size))
+#define al_array_pop_front(arr) al_array_pop_at(arr, 0)
+
+#define al_array_remove_at(arr, i) \
+ do { \
+ if (i == (arr).size - 1) { \
+ (arr).size--; \
+ } else { \
+ al_memmove(&al_array_at(arr, i), &al_array_at(arr, i + 1), \
+ al_array_item_size(arr) * (--(arr).size - i)); \
+ } \
+ } while (0)
+
+#define al_array_sort(arr, type, cmp) \
+ AL_INSERSION_SORT((arr).data, type, (arr).size, cmp)
+
+#define al_array_free(arr) \
+ if ((arr).alloc) AL_ARRAY_FREE((arr).data)
+
+#define al_array_foreach(arr, i, item) \
+ for (u32 i = 0; (i < (arr).size && (item = al_array_at(arr, i), 1)); i++)
+
+#define al_array_foreach_ptr(arr, i, item) \
+ for (u32 i = 0; (i < (arr).size && (item = &al_array_at(arr, i), 1)); i++)
+
+#define al_array_remove_at_iter(arr, i) \
+ do { \
+ al_array_remove_at(arr, i); \
+ i--; \
+ } while (0)
+
+AL_UNUSED_FUNCTION_PUSH
+
+static inline u32 _al_array_reserve(void **ptr, u32 item_size, u32 prev_size, u32 size)
+{
+ if (size < al_grow_limit) {
+ size = (u32)al_next_power_of_two(size + 1);
+ } else {
+ size = (u32)((size + al_grow_limit) & ~al_grow_limit);
+ }
+
+ if (size <= prev_size) return prev_size;
+
+ *ptr = (!*ptr) ? AL_ARRAY_MALLOC(item_size * size) : AL_ARRAY_REALLOC(*ptr, item_size * size);
+
+ return size;
+}
+
+AL_UNUSED_FUNCTION_POP
diff --git a/src/lib.c b/src/lib.c
new file mode 100644
index 0000000..2df40d3
--- /dev/null
+++ b/src/lib.c
@@ -0,0 +1,184 @@
+// Testing allocation tracking stuff. (Very scuffed)
+
+#include "../include/al/lib.h"
+
+#if AL_USE_STDLIB
+static void *(*_al_malloc)(size_t) = malloc;
+static void *(*_al_calloc)(size_t, size_t) = calloc;
+static void *(*_al_realloc)(void *, size_t) = realloc;
+static void (*_al_free)(void *) = free;
+#ifdef HAVE_POSIX_MEMALIGN
+s32 (*_al_posix_memalign)(void **, size_t, size_t) = posix_memalign;
+#endif
+
+#define MEMORY_TRACKING 0
+
+#if MEMORY_TRACKING
+struct alloc_t {
+ void *ptr;
+ size_t size;
+};
+
+// Use stdlib allocation for the allocation tracking array.
+// This avoids a circular loop and also keeps allocations
+// from the memory tracking code out of the stats.
+#include "../include/al/types.h"
+#define AL_ARRAY_MALLOC malloc
+#define AL_ARRAY_REALLOC realloc
+#define AL_ARRAY_FREE free
+#include "array.c"
+
+static array(struct alloc_t) allocations;
+static u32 total_allocated = 0;
+static u32 currently_allocated = 0;
+static u32 peak_allocated = 0;
+#endif
+
+void al_malloc_init(void)
+{
+#if MEMORY_TRACKING
+ al_array_init(allocations);
+#endif
+}
+
+void *al_malloc(size_t n)
+{
+ void *ptr = _al_malloc(n);
+#if MEMORY_TRACKING
+ printf("**al_malloc(%zu)\n", n);
+ struct alloc_t a = {
+ .ptr = ptr,
+ .size = n
+ };
+ total_allocated += a.size;
+ currently_allocated += a.size;
+ if (currently_allocated > peak_allocated) {
+ peak_allocated = currently_allocated;
+ }
+ al_array_push(allocations, a);
+#endif
+ return ptr;
+}
+
+void *al_calloc(size_t n, size_t size)
+{
+ void *ptr = _al_calloc(n, size);
+#if MEMORY_TRACKING
+ printf("**al_calloc(%zu, %zu)\n", n, size);
+ struct alloc_t a = {
+ .ptr = ptr,
+ .size = n * size
+ };
+ total_allocated += a.size;
+ currently_allocated += a.size;
+ if (currently_allocated > peak_allocated) {
+ peak_allocated = currently_allocated;
+ }
+ al_array_push(allocations, a);
+#endif
+ return ptr;
+}
+
+void *al_realloc(void *ptr, size_t n)
+{
+ void *nptr = _al_realloc(ptr, n);
+#if MEMORY_TRACKING
+ printf("**al_realloc(%p, %zu)\n", ptr, n);
+ for (u32 i = 0; i < allocations.size; i++) {
+ struct alloc_t *a = &al_array_at(allocations, i);
+ if (a->ptr == ptr) {
+ currently_allocated += (ssize_t)n - (ssize_t)a->size;
+ if (currently_allocated > peak_allocated) {
+ peak_allocated = currently_allocated;
+ }
+ a->ptr = nptr;
+ a->size = n;
+ }
+ }
+ if (!ptr) {
+ struct alloc_t a = {
+ .ptr = nptr,
+ .size = n
+ };
+ currently_allocated += a.size;
+ if (currently_allocated > peak_allocated) {
+ peak_allocated = currently_allocated;
+ }
+ al_array_push(allocations, a);
+ }
+ total_allocated += n;
+#endif
+ return nptr;
+}
+
+void al_free(void *ptr)
+{
+#if MEMORY_TRACKING
+ printf("**al_free(%p)\n", ptr);
+ for (u32 i = 0; i < allocations.size; i++) {
+ struct alloc_t *a = &al_array_at(allocations, i);
+ if (a->ptr == ptr) {
+ currently_allocated -= a->size;
+ al_array_remove_at(allocations, i);
+ break;
+ }
+ }
+#endif
+ _al_free(ptr);
+}
+
+#ifdef HAVE_POSIX_MEMALIGN
+s32 al_posix_memalign(void **ptr, size_t alignment, size_t n)
+{
+ s32 res = _al_posix_memalign(ptr, alignment, n);
+#if MEMORY_TRACKING
+ size_t actual_size = (n + alignment) - ((n + alignment) % alignment);
+ printf("**al_posix_memalign(%zu, %zu(%zu))\n", alignment, n, actual_size);
+ struct alloc_t a = {
+ .ptr = *ptr,
+ .size = actual_size
+ };
+ total_allocated += actual_size;
+ currently_allocated += actual_size;
+ if (currently_allocated > peak_allocated) {
+ peak_allocated = currently_allocated;
+ }
+ al_array_push(allocations, a);
+#endif
+ return res;
+}
+#endif
+
+void al_malloc_stats(u32 *current, u32 *peak, u32 *total)
+{
+#if MEMORY_TRACKING
+ *current = currently_allocated;
+ *peak = peak_allocated;
+ *total = total_allocated;
+#else
+ *current = 0;
+ *peak = 0;
+ *total = 0;
+#endif
+}
+
+void al_set_alloc(void *(*malloc_func)(size_t), void *(*calloc_func)(size_t, size_t),
+ void *(*realloc_func)(void *, size_t), void (*free_func)(void *))
+{
+ _al_malloc = malloc_func;
+ _al_calloc = calloc_func;
+ _al_realloc = realloc_func;
+ _al_free = free_func;
+}
+#endif
+
+// Default to 4KB.
+size_t al_page_size = 0x1000;
+size_t al_grow_limit = 0xfff;
+
+void al_set_page_size(size_t size)
+{
+ al_assert(size > 0);
+ al_page_size = size;
+ al_grow_limit = al_page_size - 1;
+}
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000..0444ed6
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,90 @@
+#include "../include/al/log.h"
+
+#define AL_LOG_SKIP 1
+#define AL_LOG_MESSAGE_SIZE 511
+#define AL_LOG_TEMPLATE "%s:%d %s(): %s -> (%s) "
+
+static void *al_log_userdata = NULL;
+
+static s32 al_print_default(void *userdata, char *s)
+{
+ (void)userdata;
+ s32 ret = al_printf("%*.*s\n", 0, AL_LOG_MESSAGE_SIZE, s);
+ al_free(s);
+ return ret;
+}
+
+static s32 (*_al_print)(void *, char *) = al_print_default;
+
+void al_set_print(void *userdata, s32 (*print_func)(void *, char *))
+{
+ al_log_userdata = userdata;
+ _al_print = print_func;
+}
+
+#if !AL_LOG_SKIP
+static char *get_buffer(const char *level, const char *section, const char *fmt,
+ const char *name, const s32 line, const char *func, va_list args)
+{
+ char *buffer = al_calloc(1, AL_LOG_MESSAGE_SIZE + 1);
+ al_memset(buffer, 0, AL_LOG_MESSAGE_SIZE);
+ s32 prefix = snprintf(buffer, AL_LOG_MESSAGE_SIZE, AL_LOG_TEMPLATE, name, line, func, level, section);
+ prefix += vsnprintf(buffer + prefix, AL_LOG_MESSAGE_SIZE - prefix, fmt, args);
+ buffer[strcspn(buffer, "\r\n")] = '\0';
+ return buffer;
+}
+#endif
+
+s32 _al_log_nop(const char *section, const char *fmt, ...)
+{
+ (void)section;
+ (void)fmt;
+ return 0;
+}
+
+s32 _al_log(const char *level, const char *section, const char *name, const s32 line,
+ const char *func, const char *fmt, ...)
+{
+#if AL_FORCE_DISABLE_OUTPUT
+ return 0;
+#else
+ va_list args;
+ va_start(args, fmt);
+#if AL_LOG_SKIP
+ s32 ret = printf(AL_LOG_TEMPLATE, name, line, func, level, section);
+ ret += vprintf(fmt, args);
+ size_t cspn = strcspn(fmt, "\r\n");
+ if (cspn == strlen(fmt)) ret += printf("\n");
+#else
+ char *buffer = get_buffer(level, section, fmt, name, line, func, args);
+#endif
+ va_end(args);
+#if AL_LOG_SKIP
+ return ret;
+#else
+ return _al_print(al_log_userdata, buffer);
+#endif
+#endif
+}
+
+s32 _al_logv(const char *level, const char *section, const char *name, const s32 line,
+ const char *func, const char *fmt, va_list args)
+{
+#if AL_FORCE_DISABLE_OUTPUT
+ return 0;
+#else
+#if AL_LOG_SKIP
+ s32 ret = printf(AL_LOG_TEMPLATE, name, line, func, level, section);
+ ret += vprintf(fmt, args);
+ size_t cspn = strcspn(fmt, "\r\n");
+ if (cspn == strlen(fmt)) ret += printf("\n");
+#else
+ char *buffer = get_buffer(level, section, fmt, name, line, func, args);
+#endif
+#if AL_LOG_SKIP
+ return ret;
+#else
+ return _al_print(al_log_userdata, buffer);
+#endif
+#endif
+}
diff --git a/src/ring_buffer.c b/src/ring_buffer.c
new file mode 100644
index 0000000..eaff139
--- /dev/null
+++ b/src/ring_buffer.c
@@ -0,0 +1,169 @@
+#include "../include/al/lib.h"
+#include "../include/al/ring_buffer.h"
+
+// https://github.com/MusicPlayerDaemon/MPD/blob/master/src/util/RingBuffer.hxx
+// https://andrea.lattuada.me/blog/2019/the-design-and-implementation-of-a-lock-free-ring-buffer-with-contiguous-reservations.html
+
+void al_ring_buffer_init(struct al_ring_buffer *buf, u8 *data, size_t length)
+{
+ buf->start = data;
+ buf->end = data + length;
+ al_ring_buffer_reset(buf);
+}
+
+static inline u8 *previous(struct al_ring_buffer *buf, u8 *ptr)
+{
+ if (ptr == buf->start) ptr = buf->end;
+ return --ptr;
+}
+
+static inline void add(struct al_ring_buffer *buf, volatile u8 **v, u8 *ptr, size_t n)
+{
+ ptr += n;
+ al_assert(ptr <= buf->end);
+ if (ptr == buf->end) ptr = buf->start;
+ al_atomic_ptr_store(v, ptr, AL_ATOMIC_RELEASE);
+}
+
+size_t al_ring_buffer_space(struct al_ring_buffer *buf)
+{
+ u8 *wp = al_atomic_ptr_load(&buf->write, AL_ATOMIC_RELAXED);
+ u8 *rp = previous(buf, (u8 *)al_atomic_ptr_load(&buf->read, AL_ATOMIC_RELAXED));
+ return (wp <= rp) ? rp - wp : (buf->end - wp) + (rp - buf->start);
+}
+
+u8 *al_ring_buffer_write_chunk(struct al_ring_buffer *buf, size_t *size)
+{
+ u8 *wp = al_atomic_ptr_load(&buf->write, AL_ATOMIC_ACQUIRE);
+ u8 *rp = previous(buf, al_atomic_ptr_load(&buf->read, AL_ATOMIC_RELAXED));
+ *size = (wp <= rp ? rp : buf->end) - wp;
+ return wp;
+}
+
+void al_ring_buffer_append(struct al_ring_buffer *buf, u8 *ptr, size_t n)
+{
+ add(buf, &buf->write, ptr, n);
+}
+
+size_t al_ring_buffer_occupied(struct al_ring_buffer *buf)
+{
+ u8 *rp = al_atomic_ptr_load(&buf->read, AL_ATOMIC_RELAXED);
+ u8 *wp = al_atomic_ptr_load(&buf->write, AL_ATOMIC_RELAXED);
+ return (rp <= wp) ? wp - rp : (buf->end - rp) + (wp - buf->start);
+}
+
+u8 *al_ring_buffer_read_chunk(struct al_ring_buffer *buf, size_t *size)
+{
+ u8 *rp = al_atomic_ptr_load(&buf->read, AL_ATOMIC_ACQUIRE);
+ u8 *wp = al_atomic_ptr_load(&buf->write, AL_ATOMIC_RELAXED);
+ *size = (rp <= wp ? wp : buf->end) - rp;
+ return rp;
+}
+
+void al_ring_buffer_consume(struct al_ring_buffer *buf, u8 *ptr, size_t n)
+{
+ add(buf, &buf->read, ptr, n);
+}
+
+size_t al_ring_buffer_write(struct al_ring_buffer *buf, u8 *data, size_t n)
+{
+ u8 *wp = al_atomic_ptr_load(&buf->write, AL_ATOMIC_ACQUIRE);
+ u8 *rp = previous(buf, al_atomic_ptr_load(&buf->read, AL_ATOMIC_RELAXED));
+
+ size_t size = AL_MIN((wp <= rp ? rp : buf->end) - wp, (ptrdiff_t)n);
+
+ al_memcpy(wp, data, size);
+ wp += size;
+
+ if (wp >= buf->end) {
+ size_t wrap = AL_MIN(rp - buf->start, (ptrdiff_t)(n - size));
+ al_memcpy(buf->start, data + size, wrap);
+ wp = buf->start + wrap;
+ size += wrap;
+ }
+
+ al_atomic_ptr_store(&buf->write, wp, AL_ATOMIC_RELEASE);
+
+ return size;
+}
+
+size_t al_ring_buffer_read(struct al_ring_buffer *buf, u8 *ptr, size_t n)
+{
+ u8 *rp = al_atomic_ptr_load(&buf->read, AL_ATOMIC_ACQUIRE);
+ u8 *wp = al_atomic_ptr_load(&buf->write, AL_ATOMIC_RELAXED);
+
+ size_t size = AL_MIN((rp <= wp ? wp : buf->end) - rp, (ptrdiff_t)n);
+
+ al_memcpy(ptr, rp, size);
+ rp += size;
+
+ if (rp >= buf->end) {
+ size_t wrap = AL_MIN(wp - buf->start, (ptrdiff_t)(n - size));
+ al_memcpy(ptr + size, buf->start, wrap);
+ rp = buf->start + wrap;
+ size += wrap;
+ }
+
+ al_atomic_ptr_store(&buf->read, rp, AL_ATOMIC_RELEASE);
+
+ return size;
+}
+
+size_t al_ring_buffer_peek(struct al_ring_buffer *buf, u8 *ptr, size_t offset, size_t n)
+{
+ u8 *rp = al_atomic_ptr_load(&buf->read, AL_ATOMIC_RELAXED);
+ u8 *wp = al_atomic_ptr_load(&buf->write, AL_ATOMIC_RELAXED);
+
+ ptrdiff_t size;
+
+ if (rp <= wp) {
+ rp += offset;
+ size = AL_MIN(wp - rp, (ptrdiff_t)n);
+ if (size < 0) return 0;
+ al_memcpy(ptr, rp, size);
+ } else {
+ rp += offset;
+ size = AL_MIN(buf->end - rp, (ptrdiff_t)n);
+ if (size > 0) {
+ al_memcpy(ptr, rp, size);
+ n -= size;
+ rp = buf->start;
+ } else {
+ rp = buf->start - size;
+ size = 0;
+ }
+ ptrdiff_t wrap = AL_MIN(wp - rp, (ptrdiff_t)n);
+ if (wrap > 0) {
+ al_memcpy(ptr + size, rp, wrap);
+ size += wrap;
+ }
+ }
+
+ return size;
+}
+
+size_t al_ring_buffer_discard(struct al_ring_buffer *buf, size_t n)
+{
+ u8 *rp = al_atomic_ptr_load(&buf->read, AL_ATOMIC_ACQUIRE);
+ u8 *wp = al_atomic_ptr_load(&buf->write, AL_ATOMIC_RELAXED);
+
+ size_t discard = AL_MIN((rp <= wp ? wp : buf->end) - rp, (ptrdiff_t)n);
+ rp += discard;
+
+ if (rp >= buf->end) {
+ size_t wrap = AL_MIN(wp - buf->start, (ptrdiff_t)(n - discard));
+ rp = buf->start + wrap;
+ discard += wrap;
+ }
+
+ al_atomic_ptr_store(&buf->read, rp, AL_ATOMIC_RELEASE);
+
+ return discard;
+}
+
+// Not thread-safe.
+void al_ring_buffer_reset(struct al_ring_buffer *buf)
+{
+ al_atomic_ptr_store(&buf->read, buf->start, AL_ATOMIC_RELAXED);
+ al_atomic_ptr_store(&buf->write, buf->start, AL_ATOMIC_RELAXED);
+}
diff --git a/src/str.c b/src/str.c
new file mode 100644
index 0000000..a3c0946
--- /dev/null
+++ b/src/str.c
@@ -0,0 +1,113 @@
+#include "../include/al/str.h"
+
+// https://sourceware.org/git/?p=newlib-cygwin.git;a=blob;f=newlib/libc/stdlib/strtol.c;h=09d4333ed05f497ec00b7192204644349441e58d;hb=HEAD#l130
+s64 al_str_to_long(str *s, s32 base)
+{
+ u64 ret = 0;
+ u32 i = 0;
+ s32 neg = 0;
+
+ while (al_isspace(al_str_at(s, i)) && i < s->len) i++;
+
+ if (al_str_at(s, i) == '-') {
+ neg = 1;
+ i++;
+ } else if (al_str_at(s, i) == '+') {
+ i++;
+ }
+
+ if ((base == 16 && al_str_at(s, i) == '0' && // 0xbecd
+ (al_str_at(s, i + 1) == 'x' || al_str_at(s, i + 1) == 'X')) ||
+ (base == 2 && al_str_at(s, i) == '0' && // 0b01011101
+ (al_str_at(s, i + 1) == 'b' || al_str_at(s, i + 1) == 'B'))) {
+ i += 2;
+ }
+
+ u64 cutoff = neg ? -(u64)INT64_MIN : INT64_MAX;
+ s32 add_limit = cutoff % (u64)base;
+ cutoff /= (u64)base;
+
+ bool error = false;
+
+ for (; i < s->len; i++) {
+ char c = al_str_at(s, i);
+
+ if (c >= '0' && c <= '9') {
+ c -= '0';
+ } else if (c >= 'A' && c <= 'Z') {
+ c -= 'A' - 10;
+ } else if (c >= 'a' && c <= 'z') {
+ c -= 'a' - 10;
+ } else {
+ // Character not valid when expressing a number.
+ error = true;
+ break;
+ }
+
+ // Character invalid for given base or trying to add the next digit
+ // would cause an overflow/underflow.
+ if (c >= base || ret > cutoff || (ret == cutoff && c > add_limit)) {
+ error = true;
+ break;
+ }
+
+ ret = (ret * base) + c;
+ }
+
+ if (error) ret = neg ? INT64_MIN : INT64_MAX;
+ else if (neg) ret = -ret;
+
+ return ret;
+}
+
+bool al_str_tok(str *s, char sep, str *tok)
+{
+ if (tok->alloc == s->len + 1) {
+ return false;
+ }
+ u32 r = s->len - tok->alloc;
+#ifndef __cplusplus
+ s32 i = al_str_find(al_str_w(s->data, tok->alloc, r), sep);
+#else
+ str w = al_str_ww(s->data, tok->alloc, r);
+ s32 i = al_str_find(&w, sep);
+#endif
+ if (i == -1) i = r;
+ tok->data = s->data + tok->alloc;
+ tok->len = (u32)i;
+ tok->alloc += tok->len + 1;
+ return true;
+}
+
+bool al_str_get_line(str *buffer, str *line)
+{
+ s32 bytes_processed = 0;
+
+ if (line->data == NULL) {
+ *line = *buffer;
+ } else {
+ // Move to the end of the current line.
+ line->data += line->len;
+
+ bytes_processed = (s32)((line->data - buffer->data) + 1);
+
+ // If line->ptr is at the last character, we are done.
+ // Also, if bytes_processed <= 0 buffer is empty or invalid, so return.
+ if (bytes_processed >= (s32)buffer->len || bytes_processed <= 0) {
+ return false;
+ }
+
+ // Else, move line->ptr to the first character on the next line.
+ line->data++;
+ }
+
+ // Find the next newline character.
+ char *nl = (char *)memchr(line->data, '\n', buffer->len - bytes_processed);
+
+ // If there is no newline character move to the end of the buffer.
+ if (!nl) nl = buffer->data + buffer->len;
+
+ line->len = (u32)(nl - line->data);
+
+ return true;
+}