tinystr: store short string data directly inside the handle itself
authorcassowarii <cassowary@cassowary.me>
Wed, 24 Jun 2026 02:13:04 +0000 (19:13 -0700)
committercassowarii <cassowary@cassowary.me>
Wed, 24 Jun 2026 02:13:04 +0000 (19:13 -0700)
src/data/string.c
src/data/string.h

index c34736a..18f5c4d 100644 (file)
@@ -6,6 +6,15 @@
 #define INLINE_BUFFER_SIZE 256
 #define STRINGS_PER_BLOCK 256
 
+/* highest bit set on handle value means it is a tinystr */
+#define FLAG_TINY (1L << 63)
+
+/* if string length < REQ_ALLOC_LENGTH, we can just put the
+ * string in the handle itself. this should be 64 / 8 = 8,
+ * we need to allocate for an 8 char string because tinystr
+ * uses one byte to store the string length as well. */
+#define REQ_ALLOC_LENGTH (sizeof(hString)/sizeof(char))
+
 /* 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
 sbBuffer g_string_blocks;
 usize g_free_block_index;
 
+typedef u64 hString_mutable;
+
 typedef struct strentry {
   usize length;
-  u64 handle;
+  hString_mutable handle;
   flag is_external;
   flag allocated;
   union {
@@ -41,6 +52,7 @@ 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);
+static flag is_tinystr(hString handle);
 
 void sbString_sys_init() {
   g_free_block_index = 0;
@@ -51,21 +63,66 @@ 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;
+hString sbString_new(const char *value, usize length) {
+  if (length >= REQ_ALLOC_LENGTH) {
+    strentry *e = new_entry(length);
+    place_str_at_offset(e, 0, value, length);
+    return e->handle;
+  } else {
+    /* tinystr */
+    hString_mutable new_handle = 0;
+    for (int i = 0; i < length; i++) {
+      new_handle <<= 8;
+      new_handle |= value[i];
+    }
+    /* set length in high bit */
+    new_handle |= ((length << (8 * 7)) | FLAG_TINY);
+    return new_handle;
+  }
+}
+
+const char *sbString_get_value(hString handle, char *buffer_i_might_use, usize *length_out) {
+  if (is_tinystr(handle)) {
+    /* string is stored backwards because it's easiest to just get the low byte */
+    usize tinylength = ((handle >> (8 * 7)) & 0x8F);
+    if (length_out) *length_out = tinylength;
+    if (buffer_i_might_use) {
+      usize index = 0;
+      hString_mutable handle_to_eat = handle;
+      while (index < tinylength) {
+        buffer_i_might_use[index] = (handle_to_eat & 0xFF); /* get lowest byte and move down */
+        handle_to_eat >>= 8;
+        index ++;
+      }
+      buffer_i_might_use[index] = '\0'; /* don't forget NUL! */
+    }
+    return buffer_i_might_use;
+  } else {
+    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);
+  }
 }
 
-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);
+usize sbString_get_length(hString handle) {
+  if (handle & FLAG_TINY) {
+    /* length is top byte except high bit */
+    return (handle >> (8 * 7)) & 0x8F;
+  } else {
+    /* length is just in the entry */
+    strblk *blk = get_strblk(handle / STRINGS_PER_BLOCK);
+    strentry *entry = get_entry(blk, handle % STRINGS_PER_BLOCK);
+    return entry->length;
+  }
 }
 
 /* --- */
 
+static flag is_tinystr(hString handle) {
+  return handle & FLAG_TINY;
+}
+
 static const char *get_ptr_of_entry(strentry *e) {
   if (e->is_external) {
     return e->somewhere_else_value.data;
index 8ae25fc..a933198 100644 (file)
@@ -2,9 +2,13 @@
 
 typedef const u64 hString;
 
-hString sbString_alloc(const char *value, usize length);
+hString sbString_new(const char *value, usize length);
 
-const char *sbString_get_ptr(hString string, usize *length_out);
+/* buffer_i_might_use should be a pointer to a 8-character (at least) stack allocated buffer.
+ * if tinystr, it will use this to store the string data returned, and then return a pointer
+ * to the buffer. if not tinystr, it will not use the buffer and will return a pointer to
+ * somewhere else. */
+const char *sbString_get_value(hString handle, char *buffer_i_might_use, usize *length_out);
 
 void sbString_sys_init();