add string object storage (can't free yet)
authorcassowarii <cassowary@cassowary.me>
Wed, 24 Jun 2026 01:22:15 +0000 (18:22 -0700)
committercassowarii <cassowary@cassowary.me>
Wed, 24 Jun 2026 01:22:15 +0000 (18:22 -0700)
src/data/string.c [new file with mode: 0644]
src/data/string.h [new file with mode: 0644]
src/global.h
src/mem/buffer.c
src/mem/buffer.h
src/mem/cstrutil.c [moved from src/mem/sbstring.c with 97% similarity]
src/mem/cstrutil.h [moved from src/mem/sbstring.h with 100% similarity]
src/mem/mem.h

diff --git a/src/data/string.c b/src/data/string.c
new file mode 100644 (file)
index 0000000..c34736a
--- /dev/null
@@ -0,0 +1,199 @@
+#include "data/string.h"
+
+#include "mem/mem.h"
+#include <string.h>
+
+#define INLINE_BUFFER_SIZE 256
+#define STRINGS_PER_BLOCK 256
+
+/* TODO: Move these globals into some kind of "execution context" struct
+ * that we can pass around to be reentrant */
+/* also TODO a better (slightly more annoying to code) way to structure
+ * this would be SoA style: instead of storing free_count and allocated
+ * inside the block and entry respectively, store them in a separate
+ * array next to the pointers that we can scan quickly. less important
+ * for allocated since it's already in the strblk anyway, but for free_count
+ * this could be important because the counts will otherwise be scattered
+ * to the four winds */
+sbBuffer g_string_blocks;
+usize g_free_block_index;
+
+typedef struct strentry {
+  usize length;
+  u64 handle;
+  flag is_external;
+  flag allocated;
+  union {
+    char inline_value[INLINE_BUFFER_SIZE];
+    sbBuffer somewhere_else_value;
+  };
+} strentry;
+
+typedef struct strblk {
+  usize id;
+  usize free_count;
+  usize next_free_index;
+  strentry entries[STRINGS_PER_BLOCK];
+} strblk;
+
+static const char *get_ptr_of_entry(strentry *e);
+static strblk *get_strblk(usize index);
+static strentry *get_entry(strblk *blk, usize index);
+static strentry *new_entry(usize length);
+static void place_str_at_offset(strentry *e, usize offset, const char *str, usize length);
+
+void sbString_sys_init() {
+  g_free_block_index = 0;
+  sbBuffer_initialize(&g_string_blocks, sizeof(strblk*) * 8);
+}
+
+void sbString_sys_deinit() {
+  sbBuffer_deinitialize(&g_string_blocks);
+}
+
+hString sbString_alloc(const char *value, usize length) {
+  strentry *e = new_entry(length);
+  place_str_at_offset(e, 0, value, length);
+  return (hString)e->handle;
+}
+
+const char *sbString_get_ptr(hString handle, usize *length_out) {
+  strblk *blk = get_strblk(handle / STRINGS_PER_BLOCK);
+  strentry *entry = get_entry(blk, handle % STRINGS_PER_BLOCK);
+  if (length_out) *length_out = entry->length;
+  return get_ptr_of_entry(entry);
+}
+
+/* --- */
+
+static const char *get_ptr_of_entry(strentry *e) {
+  if (e->is_external) {
+    return e->somewhere_else_value.data;
+  } else {
+    return e->inline_value;
+  }
+}
+
+static char *get_mutable_ptr_of_entry(strentry *e) {
+  /* TODO: When we have static strings, panic here if we try to get
+   * a mutable ptr to one of them. (Should copy instead first.) */
+  if (e->is_external) {
+    return e->somewhere_else_value.data;
+  } else {
+    return e->inline_value;
+  }
+}
+
+static strblk *get_strblk(usize index) {
+  usize nblocks = g_string_blocks.size / sizeof(strblk*);
+  if (index >= nblocks) PANIC("attempt to get a string from a block (block #%zu) that does not exist!", index);
+
+  return ((strblk**)g_string_blocks.data)[index];
+}
+
+static strentry *get_entry(strblk *blk, usize index) {
+  if (index >= STRINGS_PER_BLOCK) PANIC("attempt to get a string (string #%zu) that does not exist in block at %zu!", index, blk->id);
+
+  return &blk->entries[index];
+}
+
+static strblk *new_strblk() {
+  usize nblocks = g_string_blocks.size / sizeof(strblk*);
+  strblk *new_block = calloc(1, sizeof(strblk));
+  new_block->id = nblocks;
+  strblk **where_to_put = sbBuffer_expand(&g_string_blocks, sizeof(strblk*));
+  *where_to_put = new_block;
+  return new_block;
+}
+
+static strentry *alloc_entry(strblk *blk) {
+  if (blk->free_count == 0) PANIC("attempt to allocate from a full strblk (%zu)!", blk->id);
+
+  blk->free_count --;
+
+  usize free_index = blk->next_free_index;
+
+  if (blk->free_count > 0) {
+    usize next_free_index = blk->next_free_index;
+    do {
+      next_free_index ++;
+      next_free_index %= STRINGS_PER_BLOCK;
+    } while (blk->entries[free_index].allocated && next_free_index != blk->next_free_index);
+
+    if (next_free_index == blk->next_free_index) {
+      PANIC("free count lied! for strblk #%zu", blk->id);
+    }
+
+    blk->next_free_index = next_free_index;
+  }
+
+  blk->entries[free_index].allocated = TRUE;
+
+  return &blk->entries[free_index];
+}
+
+static strblk *find_free_block() {
+  strblk *block_with_free = get_strblk(g_free_block_index);
+
+  if (block_with_free->free_count == 0) {
+    usize nblocks = g_string_blocks.size / sizeof(strblk*);
+    usize start_index = g_free_block_index;
+
+    do {
+      g_free_block_index ++;
+      g_free_block_index %= nblocks;
+      block_with_free = get_strblk(g_free_block_index);
+    } while (g_free_block_index != start_index && block_with_free->free_count == 0);
+
+    if (g_free_block_index == start_index) {
+      /* Where did that bring you? Back to me. */
+      block_with_free = new_strblk();
+      g_free_block_index = nblocks;
+    }
+  }
+
+  return block_with_free;
+}
+
+static void set_entry_size(strentry *e, usize length) {
+  if (e->is_external) {
+    sbBuffer_set_size(&e->somewhere_else_value, length + 1);
+    /* if we shortened string, need to replace NUL at end */
+    e->somewhere_else_value.data[length] = '\0';
+  } else {
+    if (length > INLINE_BUFFER_SIZE - 1) {
+      /* new length is too big for inline, so we need to copy to the heap */
+      sbBuffer new_buf;
+      /* length + 1 for NUL terminator. even though we track the length, it's
+       * easier to just preserve NUL terminator for talking to C lib functions */
+      sbBuffer_initialize(&new_buf, length + 1);
+      if (e->length > 0) {
+        sbBuffer_append(&new_buf, e->inline_value, e->length + 1);
+      }
+      e->is_external = TRUE;
+      e->somewhere_else_value = new_buf;
+      e->somewhere_else_value.data[length] = '\0';
+    } else {
+      /* we can remain inline, but need to set NUL in the right place */
+      e->inline_value[length] = '\0';
+    }
+  }
+}
+
+static void place_str_at_offset(strentry *e, usize offset, const char *str, usize length) {
+  if (offset >= e->length) return; /* no room */
+
+  usize max_bytes_to_write = e->length - offset;
+  if (length < max_bytes_to_write) max_bytes_to_write = length;
+
+  char *where_to_put = get_mutable_ptr_of_entry(e) + offset;
+  memcpy(where_to_put, str, max_bytes_to_write);
+}
+
+static strentry *new_entry(usize length) {
+  strblk *new_block = find_free_block();
+  strentry *new_entry = alloc_entry(new_block);
+  new_entry->handle = new_block->id * STRINGS_PER_BLOCK + (new_entry - new_block->entries) / sizeof(strentry);
+  set_entry_size(new_entry, length);
+  return new_entry;
+}
diff --git a/src/data/string.h b/src/data/string.h
new file mode 100644 (file)
index 0000000..8ae25fc
--- /dev/null
@@ -0,0 +1,12 @@
+#include "common.h"
+
+typedef const u64 hString;
+
+hString sbString_alloc(const char *value, usize length);
+
+const char *sbString_get_ptr(hString string, usize *length_out);
+
+void sbString_sys_init();
+
+void sbString_sys_deinit();
+
index b12ad0d..8bfc379 100644 (file)
@@ -2,9 +2,9 @@
 #define __SB_GLOBAL_H__
 
 #ifdef DEBUG
-#define PANIC(msg) do { fprintf(stderr, "PANIC: " msg " at " __FILE__ ":%d\n", __LINE__); abort(); } while (0)
+#define PANIC(...) do { fprintf(stderr, "PANIC: " __VA_ARGS__); fprintf(stderr, "\n at " __FILE__ ":%d\n", __LINE__); abort(); } while (0)
 #else
-#define PANIC(msg) do { fprintf(stderr, "PANIC: " msg "\n"); abort(); } while (0)
+#define PANIC(...) do { fprintf(stderr, "PANIC: " __VA_ARGS__); fprintf(stderr, "\n"); abort(); } while (0)
 #endif
 
 #include <stdio.h>
@@ -24,4 +24,7 @@ typedef uint8_t flag;
 typedef size_t usize;
 typedef ptrdiff_t isize;
 
+#define TRUE ((flag)1)
+#define FALSE ((flag)0)
+
 #endif
index c474aea..faa7d4a 100644 (file)
@@ -64,6 +64,14 @@ void *sbBuffer_shrink(hBuffer buf, usize shrink_size) {
     return &buf->data[buf->size];
 }
 
+void sbBuffer_set_size(hBuffer buf, usize new_size) {
+  if (new_size > buf->size) {
+    sbBuffer_expand(buf, new_size - buf->size);
+  } else if (new_size < buf->size) {
+    sbBuffer_shrink(buf, buf->size - new_size);
+  }
+}
+
 void sbBuffer_append(hBuffer buf, const char *data, usize data_length) {
     if (data_length == 0) return;
 
index f04347c..949b94e 100644 (file)
@@ -21,6 +21,8 @@ void *sbBuffer_expand(hBuffer buf, usize expand_size);
 
 void *sbBuffer_shrink(hBuffer buf, usize shrink_size);
 
+void sbBuffer_set_size(hBuffer buf, usize new_size);
+
 void sbBuffer_append(hBuffer buf, const char *data, usize data_length);
 
 void sbBuffer_reset(hBuffer buf);
similarity index 97%
rename from src/mem/sbstring.c
rename to src/mem/cstrutil.c
index 82f39dd..88d940d 100644 (file)
@@ -1,4 +1,4 @@
-#include "sbstring.h"
+#include "cstrutil.h"
 
 usize sbstrncpy(char *dst, const char *src, usize limit) {
     usize count = 0;
similarity index 100%
rename from src/mem/sbstring.h
rename to src/mem/cstrutil.h
index 85a6f10..1498f62 100644 (file)
@@ -4,6 +4,6 @@
 #include "global.h"
 #include "arena.h"
 #include "buffer.h"
-#include "sbstring.h"
+#include "cstrutil.h"
 
 #endif