From 248a874d7425749d29cf900a1c3783c624ea8d8c Mon Sep 17 00:00:00 2001
From: David Robillard <d@drobilla.net>
Date: Sun, 10 Sep 2023 15:06:42 -0400
Subject: Add support for custom allocators

This makes it explicit in the API where memory is allocated, and allows the
user to provide a custom allocator to avoid the use of the default system
allocator for whatever reason.
---
 src/block_dumper.c |   9 ++--
 src/block_dumper.h |   7 ++-
 src/buffer.c       |  14 +++---
 src/byte_source.c  |  51 +++++++++++++------
 src/byte_source.h  |   6 ++-
 src/caret.c        |  24 ++++++---
 src/env.c          | 134 +++++++++++++++++++++++++++++++++----------------
 src/memory.c       |  17 +++++++
 src/memory.h       | 136 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/node.c         | 145 +++++++++++++++++++++++++++++++++--------------------
 src/node.h         |  12 +++--
 src/reader.c       |  37 +++++++++-----
 src/serd_config.h  |  13 -----
 src/sink.c         |  21 +++++---
 src/sink.h         |   8 +--
 src/stack.h        |  10 ++--
 src/statement.c    |  56 +++++++++++++++------
 src/string.c       |   8 +--
 src/system.c       |  48 ------------------
 src/system.h       |  20 +-------
 src/uri.c          |  19 ++++---
 src/world.c        |  28 ++++++++---
 src/world.h        |  13 +++--
 src/writer.c       |  95 ++++++++++++++++++++---------------
 24 files changed, 611 insertions(+), 320 deletions(-)
 create mode 100644 src/memory.c
 create mode 100644 src/memory.h

(limited to 'src')

diff --git a/src/block_dumper.c b/src/block_dumper.c
index 174f0215..21a10fcc 100644
--- a/src/block_dumper.c
+++ b/src/block_dumper.c
@@ -2,12 +2,15 @@
 // SPDX-License-Identifier: ISC
 
 #include "block_dumper.h"
+
+#include "memory.h"
 #include "system.h"
 
 #include <stddef.h>
 
 SerdStatus
-serd_block_dumper_open(SerdBlockDumper* const  dumper,
+serd_block_dumper_open(const SerdWorld* const  world,
+                       SerdBlockDumper* const  dumper,
                        SerdOutputStream* const output,
                        const size_t            block_size)
 {
@@ -24,7 +27,7 @@ serd_block_dumper_open(SerdBlockDumper* const  dumper,
     return SERD_SUCCESS;
   }
 
-  dumper->buf = (char*)serd_allocate_buffer(block_size);
+  dumper->buf = (char*)serd_waligned_alloc(world, SERD_PAGE_SIZE, block_size);
   return dumper->buf ? SERD_SUCCESS : SERD_BAD_ALLOC;
 }
 
@@ -50,5 +53,5 @@ serd_block_dumper_flush(SerdBlockDumper* const dumper)
 void
 serd_block_dumper_close(SerdBlockDumper* const dumper)
 {
-  serd_free_aligned(dumper->buf);
+  serd_aaligned_free(dumper->allocator, dumper->buf);
 }
diff --git a/src/block_dumper.h b/src/block_dumper.h
index 24fb977c..e5a2c318 100644
--- a/src/block_dumper.h
+++ b/src/block_dumper.h
@@ -4,14 +4,18 @@
 #ifndef SERD_SRC_BLOCK_DUMPER_H
 #define SERD_SRC_BLOCK_DUMPER_H
 
+#include "serd/memory.h"
 #include "serd/output_stream.h"
 #include "serd/status.h"
+#include "serd/world.h"
 #include "zix/attributes.h"
 
 #include <stddef.h>
 #include <string.h>
 
 typedef struct {
+  SerdAllocator* ZIX_NONNULL allocator; ///< Buffer allocator
+
   SerdOutputStream* ZIX_ALLOCATED out;        ///< Output stream to write to
   char* ZIX_ALLOCATED             buf;        ///< Local buffer if needed
   size_t                          size;       ///< Bytes pending for this block
@@ -25,7 +29,8 @@ typedef struct {
    calling serd_block_dumper_close().
 */
 SerdStatus
-serd_block_dumper_open(SerdBlockDumper* ZIX_NONNULL  dumper,
+serd_block_dumper_open(const SerdWorld* ZIX_NONNULL  world,
+                       SerdBlockDumper* ZIX_NONNULL  dumper,
                        SerdOutputStream* ZIX_NONNULL output,
                        size_t                        block_size);
 
diff --git a/src/buffer.c b/src/buffer.c
index befa3943..c16a0ad6 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -1,11 +1,12 @@
 // Copyright 2011-2021 David Robillard <d@drobilla.net>
 // SPDX-License-Identifier: ISC
 
+#include "memory.h"
+
 #include "serd/buffer.h"
 
 #include <assert.h>
 #include <stddef.h>
-#include <stdlib.h>
 #include <string.h>
 
 size_t
@@ -20,20 +21,21 @@ serd_buffer_write(const void* const buf,
   SerdBuffer* const buffer  = (SerdBuffer*)stream;
   const size_t      n_bytes = size * nmemb;
 
-  char* const new_buf = (char*)realloc(buffer->buf, buffer->len + n_bytes);
+  char* const new_buf =
+    (char*)serd_arealloc(buffer->allocator, buffer->buf, buffer->len + n_bytes);
+
   if (new_buf) {
     memcpy(new_buf + buffer->len, buf, n_bytes);
     buffer->buf = new_buf;
     buffer->len += nmemb;
+    return n_bytes;
   }
 
-  return new_buf ? nmemb : 0;
+  return 0;
 }
 
 int
 serd_buffer_close(void* const stream)
 {
-  serd_buffer_write("", 1, 1, stream); // Write null terminator
-
-  return 0;
+  return serd_buffer_write("", 1, 1, stream) != 1; // Write null terminator
 }
diff --git a/src/byte_source.c b/src/byte_source.c
index e4810b60..cf9a2466 100644
--- a/src/byte_source.c
+++ b/src/byte_source.c
@@ -3,6 +3,7 @@
 
 #include "byte_source.h"
 
+#include "memory.h"
 #include "system.h"
 
 #include "serd/node.h"
@@ -11,7 +12,6 @@
 #include <assert.h>
 #include <stdbool.h>
 #include <stdint.h>
-#include <stdlib.h>
 #include <string.h>
 
 SerdStatus
@@ -40,33 +40,48 @@ serd_byte_source_page(SerdByteSource* const source)
 }
 
 static void
-serd_byte_source_init_buffer(SerdByteSource* const source)
+serd_byte_source_init_buffer(SerdAllocator* const  allocator,
+                             SerdByteSource* const source)
 {
   if (source->block_size > 1) {
-    source->block    = (uint8_t*)serd_allocate_buffer(source->block_size);
-    source->read_buf = source->block;
-    memset(source->block, '\0', source->block_size);
+    source->block = (uint8_t*)serd_aaligned_alloc(
+      allocator, SERD_PAGE_SIZE, source->block_size);
+
+    if ((source->read_buf = source->block)) {
+      memset(source->block, '\0', source->block_size);
+    }
   } else {
     source->read_buf = &source->read_byte;
   }
 }
 
 SerdByteSource*
-serd_byte_source_new_input(SerdInputStream* const input,
+serd_byte_source_new_input(SerdAllocator* const   allocator,
+                           SerdInputStream* const input,
                            const SerdNode* const  name,
                            const size_t           block_size)
 {
   assert(input);
+  assert(block_size);
+  assert(input->stream);
 
-  if (!block_size || !input->stream) {
+  SerdNode* const source_name =
+    name ? serd_node_copy(allocator, name)
+         : serd_new_string(allocator, serd_string("input"));
+
+  if (!source_name) {
     return NULL;
   }
 
-  SerdByteSource* source = (SerdByteSource*)calloc(1, sizeof(SerdByteSource));
+  SerdByteSource* source =
+    (SerdByteSource*)serd_acalloc(allocator, 1, sizeof(SerdByteSource));
 
-  source->name =
-    name ? serd_node_copy(name) : serd_new_string(serd_string("input"));
+  if (!source) {
+    serd_node_free(allocator, source_name);
+    return NULL;
+  }
 
+  source->name           = source_name;
   source->in             = input;
   source->block_size     = block_size;
   source->buf_size       = block_size;
@@ -74,21 +89,27 @@ serd_byte_source_new_input(SerdInputStream* const input,
   source->caret.line     = 1U;
   source->caret.col      = 1U;
 
-  serd_byte_source_init_buffer(source);
+  serd_byte_source_init_buffer(allocator, source);
+  if (block_size > 1 && !source->block) {
+    serd_node_free(allocator, source_name);
+    serd_afree(allocator, source);
+    return NULL;
+  }
 
   return source;
 }
 
 void
-serd_byte_source_free(SerdByteSource* const source)
+serd_byte_source_free(SerdAllocator* const  allocator,
+                      SerdByteSource* const source)
 {
   if (source) {
     if (source->block_size > 1) {
-      serd_free_aligned(source->block);
+      serd_aaligned_free(allocator, source->block);
     }
 
-    serd_node_free(source->name);
-    free(source);
+    serd_node_free(allocator, source->name);
+    serd_afree(allocator, source);
   }
 }
 
diff --git a/src/byte_source.h b/src/byte_source.h
index 3a16a7c6..ba63e794 100644
--- a/src/byte_source.h
+++ b/src/byte_source.h
@@ -8,6 +8,7 @@
 
 #include "serd/caret.h"
 #include "serd/input_stream.h"
+#include "serd/memory.h"
 #include "serd/node.h"
 #include "serd/status.h"
 #include "zix/attributes.h"
@@ -32,12 +33,13 @@ typedef struct {
 } SerdByteSource;
 
 SerdByteSource*
-serd_byte_source_new_input(SerdInputStream* input,
+serd_byte_source_new_input(SerdAllocator*   allocator,
+                           SerdInputStream* input,
                            const SerdNode*  name,
                            size_t           block_size);
 
 void
-serd_byte_source_free(SerdByteSource* source);
+serd_byte_source_free(SerdAllocator* allocator, SerdByteSource* source);
 
 SerdStatus
 serd_byte_source_prepare(SerdByteSource* source);
diff --git a/src/caret.c b/src/caret.c
index 02d5b94b..2d7e9a8e 100644
--- a/src/caret.c
+++ b/src/caret.c
@@ -3,7 +3,10 @@
 
 #include "caret.h"
 
+#include "memory.h"
+
 #include "serd/caret.h"
+#include "serd/memory.h"
 
 #include <assert.h>
 #include <stdbool.h>
@@ -11,13 +14,15 @@
 #include <string.h>
 
 SerdCaret*
-serd_caret_new(const SerdNode* const document,
+serd_caret_new(SerdAllocator* const  allocator,
+               const SerdNode* const document,
                const unsigned        line,
                const unsigned        column)
 {
   assert(document);
 
-  SerdCaret* caret = (SerdCaret*)malloc(sizeof(SerdCaret));
+  SerdCaret* const caret =
+    (SerdCaret*)serd_amalloc(allocator, sizeof(SerdCaret));
 
   if (caret) {
     caret->document = document;
@@ -29,21 +34,26 @@ serd_caret_new(const SerdNode* const document,
 }
 
 SerdCaret*
-serd_caret_copy(const SerdCaret* const caret)
+serd_caret_copy(SerdAllocator* const allocator, const SerdCaret* const caret)
 {
   if (!caret) {
     return NULL;
   }
 
-  SerdCaret* copy = (SerdCaret*)malloc(sizeof(SerdCaret));
-  memcpy(copy, caret, sizeof(SerdCaret));
+  SerdCaret* const copy =
+    (SerdCaret*)serd_amalloc(allocator, sizeof(SerdCaret));
+
+  if (copy) {
+    memcpy(copy, caret, sizeof(SerdCaret));
+  }
+
   return copy;
 }
 
 void
-serd_caret_free(SerdCaret* const caret)
+serd_caret_free(SerdAllocator* const allocator, SerdCaret* const caret)
 {
-  free(caret);
+  serd_afree(allocator, caret);
 }
 
 bool
diff --git a/src/env.c b/src/env.c
index e9af7d77..90924cd2 100644
--- a/src/env.c
+++ b/src/env.c
@@ -4,6 +4,7 @@
 #include "serd/env.h"
 
 #include "env.h"
+#include "memory.h"
 #include "node.h"
 
 #include "serd/node.h"
@@ -11,8 +12,6 @@
 
 #include <assert.h>
 #include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
 
 typedef struct {
@@ -21,42 +20,69 @@ typedef struct {
 } SerdPrefix;
 
 struct SerdEnvImpl {
-  SerdPrefix* prefixes;
-  size_t      n_prefixes;
-  SerdNode*   base_uri_node;
-  SerdURIView base_uri;
+  SerdAllocator* allocator;
+  SerdPrefix*    prefixes;
+  size_t         n_prefixes;
+  SerdNode*      base_uri_node;
+  SerdURIView    base_uri;
 };
 
 SerdEnv*
-serd_env_new(const SerdStringView base_uri)
+serd_env_new(SerdAllocator* const allocator, const SerdStringView base_uri)
 {
-  SerdEnv* env = (SerdEnv*)calloc(1, sizeof(struct SerdEnvImpl));
-  if (env && base_uri.length) {
-    serd_env_set_base_uri(env, base_uri);
+  SerdEnv* env =
+    (SerdEnv*)serd_acalloc(allocator, 1, sizeof(struct SerdEnvImpl));
+
+  if (env) {
+    env->allocator = allocator;
+
+    if (base_uri.length) {
+      if (serd_env_set_base_uri(env, base_uri)) {
+        serd_afree(allocator, env);
+        return NULL;
+      }
+    }
   }
 
   return env;
 }
 
 SerdEnv*
-serd_env_copy(const SerdEnv* const env)
+serd_env_copy(SerdAllocator* const allocator, const SerdEnv* const env)
 {
   if (!env) {
     return NULL;
   }
 
-  SerdEnv* copy = (SerdEnv*)calloc(1, sizeof(struct SerdEnvImpl));
+  SerdEnv* const copy =
+    (SerdEnv*)serd_acalloc(allocator, 1, sizeof(struct SerdEnvImpl));
+
   if (copy) {
+    copy->allocator  = allocator;
     copy->n_prefixes = env->n_prefixes;
-    copy->prefixes = (SerdPrefix*)malloc(copy->n_prefixes * sizeof(SerdPrefix));
+
+    if (!(copy->prefixes = (SerdPrefix*)serd_acalloc(
+            allocator, copy->n_prefixes, sizeof(SerdPrefix)))) {
+      serd_afree(allocator, copy);
+      return NULL;
+    }
+
     for (size_t i = 0; i < copy->n_prefixes; ++i) {
-      copy->prefixes[i].name = serd_node_copy(env->prefixes[i].name);
-      copy->prefixes[i].uri  = serd_node_copy(env->prefixes[i].uri);
+      if (!(copy->prefixes[i].name =
+              serd_node_copy(allocator, env->prefixes[i].name)) ||
+          !(copy->prefixes[i].uri =
+              serd_node_copy(allocator, env->prefixes[i].uri))) {
+        serd_env_free(copy);
+        return NULL;
+      }
     }
 
     const SerdNode* const base = serd_env_base_uri(env);
     if (base) {
-      serd_env_set_base_uri(copy, serd_node_string_view(base));
+      if (serd_env_set_base_uri(copy, serd_node_string_view(base))) {
+        serd_env_free(copy);
+        return NULL;
+      }
     }
   }
 
@@ -71,12 +97,12 @@ serd_env_free(SerdEnv* const env)
   }
 
   for (size_t i = 0; i < env->n_prefixes; ++i) {
-    serd_node_free(env->prefixes[i].name);
-    serd_node_free(env->prefixes[i].uri);
+    serd_node_free(env->allocator, env->prefixes[i].name);
+    serd_node_free(env->allocator, env->prefixes[i].uri);
   }
-  free(env->prefixes);
-  serd_node_free(env->base_uri_node);
-  free(env);
+  serd_afree(env->allocator, env->prefixes);
+  serd_node_free(env->allocator, env->base_uri_node);
+  serd_afree(env->allocator, env);
 }
 
 bool
@@ -119,7 +145,7 @@ serd_env_set_base_uri(SerdEnv* const env, const SerdStringView uri)
   assert(env);
 
   if (!uri.length) {
-    serd_node_free(env->base_uri_node);
+    serd_node_free(env->allocator, env->base_uri_node);
     env->base_uri_node = NULL;
     env->base_uri      = SERD_URI_NULL;
     return SERD_SUCCESS;
@@ -132,10 +158,14 @@ serd_env_set_base_uri(SerdEnv* const env, const SerdStringView uri)
     serd_resolve_uri(serd_parse_uri(uri.data), env->base_uri);
 
   // Replace the current base URI
-  env->base_uri_node = serd_new_parsed_uri(new_base_uri);
-  env->base_uri      = serd_node_uri_view(env->base_uri_node);
+  if ((env->base_uri_node =
+         serd_new_parsed_uri(env->allocator, new_base_uri))) {
+    env->base_uri = serd_node_uri_view(env->base_uri_node);
+  } else {
+    return SERD_BAD_ALLOC;
+  }
 
-  serd_node_free(old_base_uri);
+  serd_node_free(env->allocator, old_base_uri);
   return SERD_SUCCESS;
 }
 
@@ -170,7 +200,7 @@ serd_env_find(const SerdEnv* const env,
   return NULL;
 }
 
-static void
+static SerdStatus
 serd_env_add(SerdEnv* const       env,
              const SerdStringView name,
              const SerdStringView uri)
@@ -178,18 +208,34 @@ serd_env_add(SerdEnv* const       env,
   SerdPrefix* const prefix = serd_env_find(env, name.data, name.length);
   if (prefix) {
     if (!!strcmp(serd_node_string(prefix->uri), uri.data)) {
-      serd_node_free(prefix->uri);
-      prefix->uri = serd_new_uri(uri);
+      serd_node_free(env->allocator, prefix->uri);
+      prefix->uri = serd_new_uri(env->allocator, uri);
     }
   } else {
-    SerdPrefix* const new_prefixes = (SerdPrefix*)realloc(
-      env->prefixes, (++env->n_prefixes) * sizeof(SerdPrefix));
-    if (new_prefixes) {
-      env->prefixes                           = new_prefixes;
-      env->prefixes[env->n_prefixes - 1].name = serd_new_string(name);
-      env->prefixes[env->n_prefixes - 1].uri  = serd_new_uri(uri);
+    SerdPrefix* const new_prefixes =
+      (SerdPrefix*)serd_arealloc(env->allocator,
+                                 env->prefixes,
+                                 (env->n_prefixes + 1) * sizeof(SerdPrefix));
+    if (!new_prefixes) {
+      return SERD_BAD_ALLOC;
     }
+
+    env->prefixes = new_prefixes;
+
+    SerdNode* const name_node = serd_new_string(env->allocator, name);
+    SerdNode* const uri_node  = serd_new_uri(env->allocator, uri);
+    if (!name_node || !uri_node) {
+      serd_node_free(env->allocator, uri_node);
+      serd_node_free(env->allocator, name_node);
+      return SERD_BAD_ALLOC;
+    }
+
+    new_prefixes[env->n_prefixes].name = name_node;
+    new_prefixes[env->n_prefixes].uri  = uri_node;
+    ++env->n_prefixes;
   }
+
+  return SERD_SUCCESS;
 }
 
 SerdStatus
@@ -201,8 +247,7 @@ serd_env_set_prefix(SerdEnv* const       env,
 
   if (serd_uri_string_has_scheme(uri.data)) {
     // Set prefix to absolute URI
-    serd_env_add(env, name, uri);
-    return SERD_SUCCESS;
+    return serd_env_add(env, name, uri);
   }
 
   if (!env->base_uri_node) {
@@ -215,13 +260,17 @@ serd_env_set_prefix(SerdEnv* const       env,
   assert(abs_uri_view.scheme.length);
 
   // Create a new node for the absolute URI
-  SerdNode* const abs_uri = serd_new_parsed_uri(abs_uri_view);
+  SerdNode* const abs_uri = serd_new_parsed_uri(env->allocator, abs_uri_view);
+  if (!abs_uri) {
+    return SERD_BAD_ALLOC;
+  }
+
   assert(serd_uri_string_has_scheme(serd_node_string(abs_uri)));
 
   // Set prefix to resolved (absolute) URI
-  serd_env_add(env, name, serd_node_string_view(abs_uri));
-  serd_node_free(abs_uri);
-  return SERD_SUCCESS;
+  const SerdStatus st = serd_env_add(env, name, serd_node_string_view(abs_uri));
+  serd_node_free(env->allocator, abs_uri);
+  return st;
 }
 
 SerdStatus
@@ -290,7 +339,7 @@ serd_env_expand_curie(const SerdEnv* const env, const SerdStringView curie)
   }
 
   const size_t    len = prefix.length + suffix.length;
-  SerdNode* const ret = serd_node_malloc(len, 0U, SERD_URI);
+  SerdNode* const ret = serd_node_malloc(env->allocator, len, 0U, SERD_URI);
   if (ret) {
     char* const string = serd_node_buffer(ret);
     memcpy(string, prefix.data, prefix.length);
@@ -310,7 +359,8 @@ serd_env_expand_node(const SerdEnv* const env, const SerdNode* const node)
   const SerdURIView uri     = serd_node_uri_view(node);
   const SerdURIView abs_uri = serd_resolve_uri(uri, env->base_uri);
 
-  return abs_uri.scheme.length ? serd_new_parsed_uri(abs_uri) : NULL;
+  return abs_uri.scheme.length ? serd_new_parsed_uri(env->allocator, abs_uri)
+                               : NULL;
 }
 
 SerdStatus
diff --git a/src/memory.c b/src/memory.c
new file mode 100644
index 00000000..74694c14
--- /dev/null
+++ b/src/memory.c
@@ -0,0 +1,17 @@
+// Copyright 2011-2021 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#include "memory.h"
+
+#include "serd/memory.h"
+#include "zix/allocator.h"
+
+SerdAllocator*
+serd_default_allocator(void)
+{
+  /* Note that SerdAllocator is intentionally the same as ZixAllocator.  It
+     only exists to avoid exposing the zix API in the public serd API, which
+     I'm not sure would be appropriate. */
+
+  return (SerdAllocator*)zix_default_allocator();
+}
diff --git a/src/memory.h b/src/memory.h
new file mode 100644
index 00000000..80c60724
--- /dev/null
+++ b/src/memory.h
@@ -0,0 +1,136 @@
+// Copyright 2011-2021 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#ifndef SERD_SRC_MEMORY_H
+#define SERD_SRC_MEMORY_H
+
+#include "serd/memory.h"
+#include "serd/world.h"
+
+#include <stddef.h>
+#include <string.h>
+
+// Allocator convenience wrappers that fall back to the default for NULL
+
+/// Convenience wrapper that defers to malloc() if allocator is null
+static inline void*
+serd_amalloc(SerdAllocator* const allocator, const size_t size)
+{
+  SerdAllocator* const actual =
+    allocator ? allocator : serd_default_allocator();
+
+  return actual->malloc(actual, size);
+}
+
+/// Convenience wrapper that defers to calloc() if allocator is null
+static inline void*
+serd_acalloc(SerdAllocator* const allocator,
+             const size_t         nmemb,
+             const size_t         size)
+{
+  SerdAllocator* const actual =
+    allocator ? allocator : serd_default_allocator();
+
+  return actual->calloc(actual, nmemb, size);
+}
+
+/// Convenience wrapper that defers to realloc() if allocator is null
+static inline void*
+serd_arealloc(SerdAllocator* const allocator,
+              void* const          ptr,
+              const size_t         size)
+{
+  SerdAllocator* const actual =
+    allocator ? allocator : serd_default_allocator();
+
+  return actual->realloc(actual, ptr, size);
+}
+
+/// Convenience wrapper that defers to free() if allocator is null
+static inline void
+serd_afree(SerdAllocator* const allocator, void* const ptr)
+{
+  SerdAllocator* const actual =
+    allocator ? allocator : serd_default_allocator();
+
+  actual->free(actual, ptr);
+}
+
+/// Convenience wrapper that defers to the system allocator if allocator is null
+static inline void*
+serd_aaligned_alloc(SerdAllocator* const allocator,
+                    const size_t         alignment,
+                    const size_t         size)
+{
+  SerdAllocator* const actual =
+    allocator ? allocator : serd_default_allocator();
+
+  return actual->aligned_alloc(actual, alignment, size);
+}
+
+/// Convenience wrapper for serd_aaligned_alloc that zeros memory
+static inline void*
+serd_aaligned_calloc(SerdAllocator* const allocator,
+                     const size_t         alignment,
+                     const size_t         size)
+{
+  void* const ptr = serd_aaligned_alloc(allocator, alignment, size);
+  if (ptr) {
+    memset(ptr, 0, size);
+  }
+  return ptr;
+}
+
+/// Convenience wrapper that defers to the system allocator if allocator is null
+static inline void
+serd_aaligned_free(SerdAllocator* const allocator, void* const ptr)
+{
+  SerdAllocator* const actual =
+    allocator ? allocator : serd_default_allocator();
+
+  actual->aligned_free(actual, ptr);
+}
+
+// World convenience wrappers
+
+static inline void*
+serd_wmalloc(const SerdWorld* const world, const size_t size)
+{
+  return serd_amalloc(serd_world_allocator(world), size);
+}
+
+static inline void*
+serd_wcalloc(const SerdWorld* const world,
+             const size_t           nmemb,
+             const size_t           size)
+{
+  return serd_acalloc(serd_world_allocator(world), nmemb, size);
+}
+
+static inline void*
+serd_wrealloc(const SerdWorld* const world, void* const ptr, const size_t size)
+{
+  return serd_arealloc(serd_world_allocator(world), ptr, size);
+}
+
+static inline void
+serd_wfree(const SerdWorld* const world, void* const ptr)
+{
+  serd_afree(serd_world_allocator(world), ptr);
+}
+
+static inline void*
+serd_waligned_alloc(const SerdWorld* const world,
+                    const size_t           alignment,
+                    const size_t           size)
+{
+  return serd_aaligned_alloc(serd_world_allocator(world), alignment, size);
+}
+
+static inline void
+serd_waligned_free(const SerdWorld* const world, void* const ptr)
+{
+  serd_aaligned_free(serd_world_allocator(world), ptr);
+}
+
+#endif // SERD_SRC_MEMORY_H
diff --git a/src/node.c b/src/node.c
index a80a38b2..135ccc22 100644
--- a/src/node.c
+++ b/src/node.c
@@ -3,9 +3,9 @@
 
 #include "node.h"
 
+#include "memory.h"
 #include "namespaces.h"
 #include "string_utils.h"
-#include "system.h"
 
 #include "exess/exess.h"
 #include "serd/buffer.h"
@@ -110,35 +110,46 @@ serd_node_total_size(const SerdNode* const node)
 }
 
 SerdNode*
-serd_node_malloc(const size_t        length,
-                 const SerdNodeFlags flags,
-                 const SerdNodeType  type)
+serd_node_malloc(SerdAllocator* const allocator,
+                 const size_t         length,
+                 const SerdNodeFlags  flags,
+                 const SerdNodeType   type)
 {
   const size_t size = sizeof(SerdNode) + serd_node_pad_length(length);
-  SerdNode*    node = (SerdNode*)serd_calloc_aligned(serd_node_align, size);
 
-  node->length = 0;
-  node->flags  = flags;
-  node->type   = type;
+  SerdNode* const node =
+    (SerdNode*)serd_aaligned_calloc(allocator, serd_node_align, size);
+
+  if (node) {
+    node->length = 0;
+    node->flags  = flags;
+    node->type   = type;
+  }
 
   assert((uintptr_t)node % serd_node_align == 0U);
   return node;
 }
 
-void
-serd_node_set(SerdNode** const dst, const SerdNode* const src)
+SerdStatus
+serd_node_set(SerdAllocator* const  allocator,
+              SerdNode** const      dst,
+              const SerdNode* const src)
 {
   assert(dst);
   assert(src);
 
   const size_t size = serd_node_total_size(src);
   if (!*dst || serd_node_total_size(*dst) < size) {
-    serd_free_aligned(*dst);
-    *dst = (SerdNode*)serd_calloc_aligned(serd_node_align, size);
+    serd_aaligned_free(allocator, *dst);
+    if (!(*dst = (SerdNode*)serd_aaligned_calloc(
+            allocator, serd_node_align, size))) {
+      return SERD_BAD_ALLOC;
+    }
   }
 
   assert(*dst);
   memcpy(*dst, src, size);
+  return SERD_SUCCESS;
 }
 
 /**
@@ -169,11 +180,13 @@ result(const SerdStatus status, const size_t count)
 }
 
 SerdNode*
-serd_new_token(const SerdNodeType type, const SerdStringView str)
+serd_new_token(SerdAllocator* const allocator,
+               const SerdNodeType   type,
+               const SerdStringView str)
 {
   SerdNodeFlags flags  = 0U;
   const size_t  length = str.data ? str.length : 0U;
-  SerdNode*     node   = serd_node_malloc(length, flags, type);
+  SerdNode*     node   = serd_node_malloc(allocator, length, flags, type);
 
   if (node) {
     if (str.data) {
@@ -189,10 +202,10 @@ serd_new_token(const SerdNodeType type, const SerdStringView str)
 }
 
 SerdNode*
-serd_new_string(const SerdStringView str)
+serd_new_string(SerdAllocator* const allocator, const SerdStringView str)
 {
   SerdNodeFlags flags = 0U;
-  SerdNode*     node  = serd_node_malloc(str.length, flags, SERD_LITERAL);
+  SerdNode* node = serd_node_malloc(allocator, str.length, flags, SERD_LITERAL);
 
   if (node) {
     if (str.data && str.length) {
@@ -236,12 +249,14 @@ is_langtag(const SerdStringView string)
 }
 
 SerdNode*
-serd_new_literal(const SerdStringView string,
+serd_new_literal(SerdAllocator* const allocator,
+                 const SerdStringView string,
                  const SerdNodeFlags  flags,
                  const SerdStringView meta)
 {
   if (!(flags & (SERD_HAS_DATATYPE | SERD_HAS_LANGUAGE))) {
-    SerdNode* node = serd_node_malloc(string.length, flags, SERD_LITERAL);
+    SerdNode* node =
+      serd_node_malloc(allocator, string.length, flags, SERD_LITERAL);
 
     memcpy(serd_node_buffer(node), string.data, string.length);
     node->length = string.length;
@@ -268,7 +283,8 @@ serd_new_literal(const SerdStringView string,
   const size_t meta_len  = serd_node_pad_length(meta.length);
   const size_t meta_size = sizeof(SerdNode) + meta_len;
 
-  SerdNode* node = serd_node_malloc(len + meta_size, flags, SERD_LITERAL);
+  SerdNode* node =
+    serd_node_malloc(allocator, len + meta_size, flags, SERD_LITERAL);
   memcpy(serd_node_buffer(node), string.data, string.length);
   node->length = string.length;
 
@@ -283,9 +299,9 @@ serd_new_literal(const SerdStringView string,
 }
 
 SerdNode*
-serd_new_blank(const SerdStringView str)
+serd_new_blank(SerdAllocator* const allocator, const SerdStringView str)
 {
-  return serd_new_token(SERD_BLANK, str);
+  return serd_new_token(allocator, SERD_BLANK, str);
 }
 
 ExessResult
@@ -396,16 +412,20 @@ serd_get_base64(const SerdNode* const node,
 }
 
 SerdNode*
-serd_node_copy(const SerdNode* node)
+serd_node_copy(SerdAllocator* const allocator, const SerdNode* node)
 {
   if (!node) {
     return NULL;
   }
 
   const size_t size = serd_node_total_size(node);
-  SerdNode*    copy = (SerdNode*)serd_calloc_aligned(serd_node_align, size);
+  SerdNode*    copy =
+    (SerdNode*)serd_aaligned_alloc(allocator, serd_node_align, size);
+
+  if (copy) {
+    memcpy(copy, node, size);
+  }
 
-  memcpy(copy, node, size);
   return copy;
 }
 
@@ -466,39 +486,45 @@ serd_node_compare(const SerdNode* const a, const SerdNode* const b)
 }
 
 SerdNode*
-serd_new_uri(const SerdStringView string)
+serd_new_uri(SerdAllocator* const allocator, const SerdStringView string)
 {
-  return serd_new_token(SERD_URI, string);
+  return serd_new_token(allocator, SERD_URI, string);
 }
 
 SerdNode*
-serd_new_parsed_uri(const SerdURIView uri)
+serd_new_parsed_uri(SerdAllocator* const allocator, const SerdURIView uri)
 {
-  const size_t    len        = serd_uri_string_length(uri);
-  SerdNode* const node       = serd_node_malloc(len, 0, SERD_URI);
-  char*           ptr        = serd_node_buffer(node);
-  const size_t    actual_len = serd_write_uri(uri, string_sink, &ptr);
+  const size_t    len  = serd_uri_string_length(uri);
+  SerdNode* const node = serd_node_malloc(allocator, len, 0, SERD_URI);
 
-  assert(actual_len == len);
+  if (node) {
+    char*        ptr        = serd_node_buffer(node);
+    const size_t actual_len = serd_write_uri(uri, string_sink, &ptr);
+
+    assert(actual_len == len);
 
-  serd_node_buffer(node)[actual_len] = '\0';
-  node->length                       = actual_len;
+    serd_node_buffer(node)[actual_len] = '\0';
+    node->length                       = actual_len;
+  }
 
   serd_node_check_padding(node);
   return node;
 }
 
 SerdNode*
-serd_new_file_uri(const SerdStringView path, const SerdStringView hostname)
+serd_new_file_uri(SerdAllocator* const allocator,
+                  const SerdStringView path,
+                  const SerdStringView hostname)
 {
-  SerdBuffer buffer = {NULL, 0U};
+  SerdBuffer buffer = {NULL, NULL, 0U};
 
   serd_write_file_uri(path, hostname, serd_buffer_write, &buffer);
   serd_buffer_close(&buffer);
 
   const size_t      length = buffer.len;
   const char* const string = (char*)buffer.buf;
-  SerdNode* const   node   = serd_new_string(serd_substring(string, length));
+  SerdNode* const   node =
+    serd_new_string(allocator, serd_substring(string, length));
 
   free(buffer.buf);
   serd_node_check_padding(node);
@@ -510,7 +536,8 @@ typedef size_t (*SerdWriteLiteralFunc)(const void* user_data,
                                        char*       buf);
 
 static SerdNode*
-serd_new_custom_literal(const void* const          user_data,
+serd_new_custom_literal(SerdAllocator* const       allocator,
+                        const void* const          user_data,
                         const size_t               len,
                         const SerdWriteLiteralFunc write,
                         const SerdNode* const      datatype)
@@ -523,7 +550,7 @@ serd_new_custom_literal(const void* const          user_data,
   const size_t total_size    = serd_node_pad_length(len) + datatype_size;
 
   SerdNode* const node = serd_node_malloc(
-    total_size, datatype ? SERD_HAS_DATATYPE : 0U, SERD_LITERAL);
+    allocator, total_size, datatype ? SERD_HAS_DATATYPE : 0U, SERD_LITERAL);
 
   node->length = write(user_data, len + 1, serd_node_buffer(node));
 
@@ -531,46 +558,50 @@ serd_new_custom_literal(const void* const          user_data,
     memcpy(serd_node_meta(node), datatype, datatype_size);
   }
 
-  serd_node_check_padding(node);
   return node;
 }
 
 SerdNode*
-serd_new_double(const double d)
+serd_new_double(SerdAllocator* const allocator, const double d)
 {
   char buf[EXESS_MAX_DOUBLE_LENGTH + 1] = {0};
 
   const ExessResult r = exess_write_double(d, sizeof(buf), buf);
 
   return r.status ? NULL
-                  : serd_new_literal(serd_substring(buf, r.count),
+                  : serd_new_literal(allocator,
+                                     serd_substring(buf, r.count),
                                      SERD_HAS_DATATYPE,
                                      serd_string(EXESS_XSD_URI "double"));
 }
 
 SerdNode*
-serd_new_float(const float f)
+serd_new_float(SerdAllocator* const allocator, const float f)
 {
   char buf[EXESS_MAX_FLOAT_LENGTH + 1] = {0};
 
   const ExessResult r = exess_write_float(f, sizeof(buf), buf);
 
   return r.status ? NULL
-                  : serd_new_literal(serd_substring(buf, r.count),
+                  : serd_new_literal(allocator,
+                                     serd_substring(buf, r.count),
                                      SERD_HAS_DATATYPE,
                                      serd_string(EXESS_XSD_URI "float"));
 }
 
 SerdNode*
-serd_new_boolean(bool b)
+serd_new_boolean(SerdAllocator* const allocator, bool b)
 {
-  return serd_new_literal(b ? serd_string("true") : serd_string("false"),
+  return serd_new_literal(allocator,
+                          b ? serd_string("true") : serd_string("false"),
                           SERD_HAS_DATATYPE,
                           serd_node_string_view(&serd_xsd_boolean.node));
 }
 
 SerdNode*
-serd_new_decimal(const double d, const SerdNode* const datatype)
+serd_new_decimal(SerdAllocator* const  allocator,
+                 const double          d,
+                 const SerdNode* const datatype)
 {
   // Use given datatype, or xsd:decimal as a default if it is null
   const SerdNode* type      = datatype ? datatype : &serd_xsd_decimal.node;
@@ -581,8 +612,11 @@ serd_new_decimal(const double d, const SerdNode* const datatype)
   assert(!r.status);
 
   // Allocate node with enough space for value and datatype URI
-  SerdNode* const node = serd_node_malloc(
-    serd_node_pad_length(r.count) + type_size, SERD_HAS_DATATYPE, SERD_LITERAL);
+  SerdNode* const node =
+    serd_node_malloc(allocator,
+                     serd_node_pad_length(r.count) + type_size,
+                     SERD_HAS_DATATYPE,
+                     SERD_LITERAL);
 
   // Write string directly into node
   r = exess_write_decimal(d, r.count + 1, serd_node_buffer(node));
@@ -595,7 +629,7 @@ serd_new_decimal(const double d, const SerdNode* const datatype)
 }
 
 SerdNode*
-serd_new_integer(const int64_t i)
+serd_new_integer(SerdAllocator* const allocator, const int64_t i)
 {
   // Use given datatype, or xsd:integer as a default if it is null
   const SerdNode* datatype      = &serd_xsd_integer.node;
@@ -607,7 +641,8 @@ serd_new_integer(const int64_t i)
 
   // Allocate node with enough space for value and datatype URI
   SerdNode* const node =
-    serd_node_malloc(serd_node_pad_length(r.count) + datatype_size,
+    serd_node_malloc(allocator,
+                     serd_node_pad_length(r.count) + datatype_size,
                      SERD_HAS_DATATYPE,
                      SERD_LITERAL);
 
@@ -634,13 +669,13 @@ write_base64_literal(const void* const user_data,
 }
 
 SerdNode*
-serd_new_base64(const void* buf, size_t size)
+serd_new_base64(SerdAllocator* const allocator, const void* buf, size_t size)
 {
   const size_t    len  = exess_write_base64(size, buf, 0, NULL).count;
   SerdConstBuffer blob = {buf, size};
 
   return serd_new_custom_literal(
-    &blob, len, write_base64_literal, &serd_xsd_base64Binary.node);
+    allocator, &blob, len, write_base64_literal, &serd_xsd_base64Binary.node);
 }
 
 SerdNodeType
@@ -721,7 +756,7 @@ serd_node_flags(const SerdNode* const node)
 }
 
 void
-serd_node_free(SerdNode* const node)
+serd_node_free(SerdAllocator* const allocator, SerdNode* const node)
 {
-  serd_free_aligned(node);
+  serd_aaligned_free(allocator, node);
 }
diff --git a/src/node.h b/src/node.h
index a31085be..43368367 100644
--- a/src/node.h
+++ b/src/node.h
@@ -5,7 +5,9 @@
 #define SERD_SRC_NODE_H
 
 #include "exess/exess.h"
+#include "serd/memory.h"
 #include "serd/node.h"
+#include "serd/status.h"
 #include "zix/attributes.h"
 
 #include <stdbool.h>
@@ -46,10 +48,14 @@ serd_node_pattern_match(const SerdNode* ZIX_NULLABLE a,
 }
 
 SerdNode* ZIX_ALLOCATED
-serd_node_malloc(size_t length, SerdNodeFlags flags, SerdNodeType type);
+serd_node_malloc(SerdAllocator* ZIX_NULLABLE allocator,
+                 size_t                      length,
+                 SerdNodeFlags               flags,
+                 SerdNodeType                type);
 
-void
-serd_node_set(SerdNode* ZIX_NONNULL* ZIX_NONNULL dst,
+SerdStatus
+serd_node_set(SerdAllocator* ZIX_NULLABLE        allocator,
+              SerdNode* ZIX_NONNULL* ZIX_NONNULL dst,
               const SerdNode* ZIX_NONNULL        src);
 
 ZIX_PURE_FUNC size_t
diff --git a/src/reader.c b/src/reader.c
index 8b74836f..bbb1325b 100644
--- a/src/reader.c
+++ b/src/reader.c
@@ -4,6 +4,7 @@
 #include "reader.h"
 
 #include "byte_source.h"
+#include "memory.h"
 #include "namespaces.h"
 #include "node.h"
 #include "read_nquads.h"
@@ -13,7 +14,6 @@
 #include "stack.h"
 #include "statement.h"
 #include "string_utils.h"
-#include "system.h"
 #include "world.h"
 
 #include "serd/input_stream.h"
@@ -22,7 +22,6 @@
 #include <assert.h>
 #include <stdarg.h>
 #include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
 
 static SerdStatus
@@ -241,17 +240,25 @@ serd_reader_new(SerdWorld* const      world,
     return NULL;
   }
 
-  SerdReader* me = (SerdReader*)calloc(1, sizeof(SerdReader));
+  SerdReader* me = (SerdReader*)serd_wcalloc(world, 1, sizeof(SerdReader));
+  if (!me) {
+    return NULL;
+  }
 
   me->world   = world;
   me->sink    = sink;
   me->env     = env;
-  me->stack   = serd_stack_new(stack_size, serd_node_align);
+  me->stack   = serd_stack_new(world->allocator, stack_size, serd_node_align);
   me->syntax  = syntax;
   me->flags   = flags;
   me->next_id = 1;
   me->strict  = !(flags & SERD_READ_LAX);
 
+  if (!me->stack.buf) {
+    serd_wfree(world, me);
+    return NULL;
+  }
+
   // Reserve a bit of space at the end of the stack to zero pad nodes
   me->stack.buf_size -= serd_node_align;
 
@@ -278,9 +285,9 @@ serd_reader_free(SerdReader* const reader)
     serd_reader_finish(reader);
   }
 
-  serd_free_aligned(reader->stack.buf);
-  free(reader->bprefix);
-  free(reader);
+  serd_aaligned_free(reader->world->allocator, reader->stack.buf);
+  serd_wfree(reader->world, reader->bprefix);
+  serd_wfree(reader->world, reader);
 }
 
 void
@@ -288,14 +295,15 @@ serd_reader_add_blank_prefix(SerdReader* const reader, const char* const prefix)
 {
   assert(reader);
 
-  free(reader->bprefix);
+  serd_wfree(reader->world, reader->bprefix);
   reader->bprefix_len = 0;
   reader->bprefix     = NULL;
 
   const size_t prefix_len = prefix ? strlen(prefix) : 0;
   if (prefix_len) {
     reader->bprefix_len = prefix_len;
-    reader->bprefix     = (char*)malloc(reader->bprefix_len + 1);
+    reader->bprefix =
+      (char*)serd_wmalloc(reader->world, reader->bprefix_len + 1);
     memcpy(reader->bprefix, prefix, reader->bprefix_len + 1);
   }
 }
@@ -326,13 +334,18 @@ serd_reader_start(SerdReader* const      reader,
   assert(reader);
   assert(input);
 
+  if (!block_size || !input->stream) {
+    return SERD_BAD_ARG;
+  }
+
   if (reader->source) {
     return SERD_BAD_CALL;
   }
 
-  reader->source = serd_byte_source_new_input(input, input_name, block_size);
+  reader->source = serd_byte_source_new_input(
+    reader->world->allocator, input, input_name, block_size);
 
-  return reader->source ? SERD_SUCCESS : SERD_BAD_ARG;
+  return reader->source ? SERD_SUCCESS : SERD_BAD_ALLOC;
 }
 
 static SerdStatus
@@ -393,7 +406,7 @@ serd_reader_finish(SerdReader* const reader)
 {
   assert(reader);
 
-  serd_byte_source_free(reader->source);
+  serd_byte_source_free(reader->world->allocator, reader->source);
   reader->source = NULL;
   return SERD_SUCCESS;
 }
diff --git a/src/serd_config.h b/src/serd_config.h
index 0bd9b5ac..0146c181 100644
--- a/src/serd_config.h
+++ b/src/serd_config.h
@@ -74,13 +74,6 @@
 #    endif
 #  endif
 
-// POSIX.1-2001: posix_memalign()
-#  ifndef HAVE_POSIX_MEMALIGN
-#    if SERD_POSIX_VERSION >= 200112L
-#      define HAVE_POSIX_MEMALIGN 1
-#    endif
-#  endif
-
 // POSIX.1-2001: strerror_r()
 #  ifndef HAVE_STRERROR_R
 #    if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L
@@ -116,12 +109,6 @@
 #  define USE_POSIX_FADVISE 0
 #endif
 
-#if defined(HAVE_POSIX_MEMALIGN) && HAVE_POSIX_MEMALIGN
-#  define USE_POSIX_MEMALIGN 1
-#else
-#  define USE_POSIX_MEMALIGN 0
-#endif
-
 #ifdef HAVE_STRERROR_R
 #  define USE_STRERROR_R 1
 #else
diff --git a/src/sink.c b/src/sink.c
index adf85b08..48ada0c5 100644
--- a/src/sink.c
+++ b/src/sink.c
@@ -3,6 +3,7 @@
 
 #include "sink.h"
 
+#include "memory.h"
 #include "statement.h"
 
 #include "serd/node.h"
@@ -14,15 +15,19 @@
 #include <stdlib.h>
 
 SerdSink*
-serd_sink_new(void* const   handle,
-              SerdEventFunc event_func,
-              SerdFreeFunc  free_handle)
+serd_sink_new(SerdAllocator* const allocator,
+              void* const          handle,
+              SerdEventFunc        event_func,
+              SerdFreeFunc         free_handle)
 {
-  SerdSink* sink = (SerdSink*)calloc(1, sizeof(SerdSink));
+  SerdSink* sink = (SerdSink*)serd_acalloc(allocator, 1, sizeof(SerdSink));
 
-  sink->handle      = handle;
-  sink->on_event    = event_func;
-  sink->free_handle = free_handle;
+  if (sink) {
+    sink->allocator   = allocator;
+    sink->handle      = handle;
+    sink->on_event    = event_func;
+    sink->free_handle = free_handle;
+  }
 
   return sink;
 }
@@ -35,7 +40,7 @@ serd_sink_free(SerdSink* sink)
       sink->free_handle(sink->handle);
     }
 
-    free(sink);
+    serd_afree(sink->allocator, sink);
   }
 }
 
diff --git a/src/sink.h b/src/sink.h
index 097e0d6d..baed246e 100644
--- a/src/sink.h
+++ b/src/sink.h
@@ -5,15 +5,17 @@
 #define SERD_SRC_SINK_H
 
 #include "serd/event.h"
+#include "serd/memory.h"
 #include "serd/sink.h"
 
 /**
    An interface that receives a stream of RDF data.
 */
 struct SerdSinkImpl {
-  void*         handle;
-  SerdFreeFunc  free_handle;
-  SerdEventFunc on_event;
+  SerdAllocator* allocator;
+  void*          handle;
+  SerdFreeFunc   free_handle;
+  SerdEventFunc  on_event;
 };
 
 #endif // SERD_SRC_SINK_H
diff --git a/src/stack.h b/src/stack.h
index 20bd561b..40e8065d 100644
--- a/src/stack.h
+++ b/src/stack.h
@@ -4,7 +4,7 @@
 #ifndef SERD_SRC_STACK_H
 #define SERD_SRC_STACK_H
 
-#include "system.h"
+#include "memory.h"
 
 #include <assert.h>
 #include <stdbool.h>
@@ -19,21 +19,21 @@ typedef struct {
 } SerdStack;
 
 static inline SerdStack
-serd_stack_new(size_t size, size_t align)
+serd_stack_new(SerdAllocator* const allocator, size_t size, size_t align)
 {
   const size_t aligned_size = (size + (align - 1)) / align * align;
 
   SerdStack stack;
-  stack.buf      = (char*)serd_calloc_aligned(align, aligned_size);
+  stack.buf      = (char*)serd_aaligned_calloc(allocator, align, aligned_size);
   stack.buf_size = size;
   stack.size     = align; // 0 is reserved for null
   return stack;
 }
 
 static inline void
-serd_stack_free(SerdStack* stack)
+serd_stack_free(SerdAllocator* const allocator, SerdStack* stack)
 {
-  serd_free_aligned(stack->buf);
+  serd_aaligned_free(allocator, stack->buf);
   stack->buf      = NULL;
   stack->buf_size = 0;
   stack->size     = 0;
diff --git a/src/statement.c b/src/statement.c
index 418e999e..5ea14910 100644
--- a/src/statement.c
+++ b/src/statement.c
@@ -4,8 +4,10 @@
 #include "statement.h"
 
 #include "caret.h"
+#include "memory.h"
 #include "node.h"
 
+#include "serd/memory.h"
 #include "serd/statement.h"
 
 #include <assert.h>
@@ -16,7 +18,7 @@
 static bool
 is_resource(const SerdNode* const node)
 {
-  const SerdNodeType type = node ? serd_node_type(node) : (SerdNodeType)0;
+  const SerdNodeType type = serd_node_type(node);
   return type == SERD_URI || type == SERD_BLANK || type == SERD_VARIABLE;
 }
 
@@ -26,13 +28,16 @@ serd_statement_is_valid(const SerdNode* const subject,
                         const SerdNode* const object,
                         const SerdNode* const graph)
 {
-  return is_resource(subject) && is_resource(predicate) && object &&
+  (void)object;
+
+  return is_resource(subject) && is_resource(predicate) &&
          serd_node_type(predicate) != SERD_BLANK &&
          (!graph || is_resource(graph));
 }
 
 SerdStatement*
-serd_statement_new(const SerdNode* const  s,
+serd_statement_new(SerdAllocator* const   allocator,
+                   const SerdNode* const  s,
                    const SerdNode* const  p,
                    const SerdNode* const  o,
                    const SerdNode* const  g,
@@ -46,39 +51,62 @@ serd_statement_new(const SerdNode* const  s,
     return NULL;
   }
 
-  SerdStatement* statement = (SerdStatement*)malloc(sizeof(SerdStatement));
+  SerdStatement* statement =
+    (SerdStatement*)serd_amalloc(allocator, sizeof(SerdStatement));
+
   if (statement) {
     statement->nodes[0] = s;
     statement->nodes[1] = p;
     statement->nodes[2] = o;
     statement->nodes[3] = g;
-    statement->caret    = serd_caret_copy(caret);
+    statement->caret    = NULL;
+
+    if (caret) {
+      if (!(statement->caret = serd_caret_copy(allocator, caret))) {
+        serd_afree(allocator, statement);
+        return NULL;
+      }
+    }
   }
+
   return statement;
 }
 
 SerdStatement*
-serd_statement_copy(const SerdStatement* const statement)
+serd_statement_copy(SerdAllocator* const       allocator,
+                    const SerdStatement* const statement)
 {
   if (!statement) {
     return NULL;
   }
 
-  SerdStatement* copy = (SerdStatement*)malloc(sizeof(SerdStatement));
-  memcpy(copy, statement, sizeof(SerdStatement));
-  if (statement->caret) {
-    copy->caret = (SerdCaret*)malloc(sizeof(SerdCaret));
-    memcpy(copy->caret, statement->caret, sizeof(SerdCaret));
+  SerdStatement* copy =
+    (SerdStatement*)serd_amalloc(allocator, sizeof(SerdStatement));
+
+  if (copy) {
+    memcpy(copy, statement, sizeof(SerdStatement));
+
+    if (statement->caret) {
+      if (!(copy->caret =
+              (SerdCaret*)serd_amalloc(allocator, sizeof(SerdCaret)))) {
+        serd_afree(allocator, copy);
+        return NULL;
+      }
+
+      memcpy(copy->caret, statement->caret, sizeof(SerdCaret));
+    }
   }
+
   return copy;
 }
 
 void
-serd_statement_free(SerdStatement* const statement)
+serd_statement_free(SerdAllocator* const allocator,
+                    SerdStatement* const statement)
 {
   if (statement) {
-    free(statement->caret);
-    free(statement);
+    serd_afree(allocator, statement->caret);
+    serd_afree(allocator, statement);
   }
 }
 
diff --git a/src/string.c b/src/string.c
index ed3149d0..46f8ff9a 100644
--- a/src/string.c
+++ b/src/string.c
@@ -1,15 +1,15 @@
 // Copyright 2011-2020 David Robillard <d@drobilla.net>
 // SPDX-License-Identifier: ISC
 
+#include "memory.h"
+
 #include "serd/memory.h"
 #include "serd/status.h"
 
-#include <stdlib.h>
-
 void
-serd_free(void* const ptr)
+serd_free(SerdAllocator* const allocator, void* const ptr)
 {
-  free(ptr);
+  serd_afree(allocator, ptr);
 }
 
 const char*
diff --git a/src/system.c b/src/system.c
index 16a292f5..4c29f7a1 100644
--- a/src/system.c
+++ b/src/system.c
@@ -9,8 +9,6 @@
 #  include <malloc.h>
 #endif
 
-#include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
 
 int
@@ -26,49 +24,3 @@ serd_system_strerror(const int errnum, char* const buf, const size_t buflen)
   return 0;
 #endif
 }
-
-void*
-serd_malloc_aligned(const size_t alignment, const size_t size)
-{
-#if defined(_WIN32)
-  return _aligned_malloc(size, alignment);
-#elif USE_POSIX_MEMALIGN
-  void*     ptr = NULL;
-  const int ret = posix_memalign(&ptr, alignment, size);
-  return ret ? NULL : ptr;
-#else
-  (void)alignment;
-  return malloc(size);
-#endif
-}
-
-void*
-serd_calloc_aligned(const size_t alignment, const size_t size)
-{
-#if defined(_WIN32) || defined(USE_POSIX_MEMALIGN)
-  void* const ptr = serd_malloc_aligned(alignment, size);
-  if (ptr) {
-    memset(ptr, 0, size);
-  }
-  return ptr;
-#else
-  (void)alignment;
-  return calloc(1, size);
-#endif
-}
-
-void*
-serd_allocate_buffer(const size_t size)
-{
-  return serd_malloc_aligned(SERD_PAGE_SIZE, size);
-}
-
-void
-serd_free_aligned(void* const ptr)
-{
-#ifdef _WIN32
-  _aligned_free(ptr);
-#else
-  free(ptr);
-#endif
-}
diff --git a/src/system.h b/src/system.h
index a0ec05dc..b606744b 100644
--- a/src/system.h
+++ b/src/system.h
@@ -4,9 +4,7 @@
 #ifndef SERD_SRC_SYSTEM_H
 #define SERD_SRC_SYSTEM_H
 
-#include "zix/attributes.h"
-
-#include <stdio.h>
+#include <stddef.h>
 
 #define SERD_PAGE_SIZE 4096
 
@@ -14,20 +12,4 @@
 int
 serd_system_strerror(int errnum, char* buf, size_t buflen);
 
-/// Allocate a buffer aligned to `alignment` bytes
-ZIX_MALLOC_FUNC void*
-serd_malloc_aligned(size_t alignment, size_t size);
-
-/// Allocate a zeroed buffer aligned to `alignment` bytes
-ZIX_MALLOC_FUNC void*
-serd_calloc_aligned(size_t alignment, size_t size);
-
-/// Allocate an aligned buffer for I/O
-ZIX_MALLOC_FUNC void*
-serd_allocate_buffer(size_t size);
-
-/// Free a buffer allocated with an aligned allocation function
-void
-serd_free_aligned(void* ptr);
-
 #endif // SERD_SRC_SYSTEM_H
diff --git a/src/uri.c b/src/uri.c
index 6fc1f17c..234bf834 100644
--- a/src/uri.c
+++ b/src/uri.c
@@ -1,10 +1,12 @@
 // Copyright 2011-2023 David Robillard <d@drobilla.net>
 // SPDX-License-Identifier: ISC
 
+#include "memory.h"
 #include "string_utils.h"
 #include "uri_utils.h"
 
 #include "serd/buffer.h"
+#include "serd/memory.h"
 #include "serd/status.h"
 #include "serd/stream.h"
 #include "serd/string_view.h"
@@ -14,7 +16,6 @@
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
 
 static SerdStatus
@@ -25,7 +26,9 @@ write_file_uri_char(const char c, void* const stream)
 }
 
 static char*
-parse_hostname(const char* const authority, char** const hostname)
+parse_hostname(SerdAllocator* const allocator,
+               const char* const    authority,
+               char** const         hostname)
 {
   char* const path = strchr(authority, '/');
   if (!path) {
@@ -34,7 +37,7 @@ parse_hostname(const char* const authority, char** const hostname)
 
   if (hostname) {
     const size_t len = (size_t)(path - authority);
-    if (!(*hostname = (char*)calloc(len + 1, 1))) {
+    if (!(*hostname = (char*)serd_acalloc(allocator, len + 1, 1))) {
       return NULL;
     }
 
@@ -45,7 +48,9 @@ parse_hostname(const char* const authority, char** const hostname)
 }
 
 char*
-serd_parse_file_uri(const char* const uri, char** const hostname)
+serd_parse_file_uri(SerdAllocator* const allocator,
+                    const char* const    uri,
+                    char** const         hostname)
 {
   assert(uri);
 
@@ -60,7 +65,7 @@ serd_parse_file_uri(const char* const uri, char** const hostname)
     const char* auth = uri + 7;
     if (*auth == '/') { // No hostname
       path = auth;
-    } else if (!(path = parse_hostname(auth, hostname))) {
+    } else if (!(path = parse_hostname(allocator, auth, hostname))) {
       return NULL;
     }
   }
@@ -69,7 +74,7 @@ serd_parse_file_uri(const char* const uri, char** const hostname)
     ++path;
   }
 
-  SerdBuffer buffer = {NULL, 0};
+  SerdBuffer buffer = {allocator, NULL, 0};
   for (const char* s = path; !st && *s; ++s) {
     if (*s != '%') {
       st = write_file_uri_char(*s, &buffer);
@@ -90,7 +95,7 @@ serd_parse_file_uri(const char* const uri, char** const hostname)
   }
 
   if (st || serd_buffer_close(&buffer)) {
-    free(buffer.buf);
+    serd_free(allocator, buffer.buf);
     return NULL;
   }
 
diff --git a/src/world.c b/src/world.c
index f5ab2f19..2c563f15 100644
--- a/src/world.c
+++ b/src/world.c
@@ -4,6 +4,7 @@
 #include "world.h"
 
 #include "log.h"
+#include "memory.h"
 #include "node.h"
 
 #include "serd/node.h"
@@ -13,23 +14,26 @@
 
 #include <assert.h>
 #include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
 
 SerdWorld*
-serd_world_new(void)
+serd_world_new(SerdAllocator* const allocator)
 {
-  SerdWorld* world      = (SerdWorld*)calloc(1, sizeof(SerdWorld));
-  SerdNode*  blank_node = serd_new_blank(serd_string("b00000000000"));
+  SerdAllocator* const actual =
+    allocator ? allocator : serd_default_allocator();
+
+  SerdWorld* world = (SerdWorld*)serd_acalloc(actual, 1, sizeof(SerdWorld));
+  SerdNode*  blank_node = serd_new_blank(actual, serd_string("b00000000000"));
 
   if (!world || !blank_node) {
-    serd_node_free(blank_node);
-    free(world);
+    serd_node_free(actual, blank_node);
+    serd_afree(actual, world);
     return NULL;
   }
 
   world->limits.reader_stack_size = 1048576U;
   world->limits.writer_max_depth  = 128U;
+  world->allocator                = actual;
   world->blank_node               = blank_node;
 
   serd_log_init(&world->log);
@@ -41,8 +45,8 @@ void
 serd_world_free(SerdWorld* const world)
 {
   if (world) {
-    serd_node_free(world->blank_node);
-    free(world);
+    serd_node_free(world->allocator, world->blank_node);
+    serd_afree(world->allocator, world);
   }
 }
 
@@ -78,3 +82,11 @@ serd_world_get_blank(SerdWorld* const world)
 
 #undef BLANK_CHARS
 }
+
+SerdAllocator*
+serd_world_allocator(const SerdWorld* const world)
+{
+  assert(world);
+  assert(world->allocator);
+  return world->allocator;
+}
diff --git a/src/world.h b/src/world.h
index af6281d4..2499b761 100644
--- a/src/world.h
+++ b/src/world.h
@@ -6,16 +6,21 @@
 
 #include "log.h"
 
+#include "serd/memory.h"
 #include "serd/node.h"
 #include "serd/world.h"
 
+#include <stdbool.h>
 #include <stdint.h>
 
 struct SerdWorldImpl {
-  SerdLimits limits;
-  SerdLog    log;
-  uint32_t   next_blank_id;
-  SerdNode*  blank_node;
+  SerdLimits     limits;
+  SerdAllocator* allocator;
+  SerdLog        log;
+  uint32_t       next_blank_id;
+  SerdNode*      blank_node;
+
+  bool stderr_color;
 };
 
 #endif // SERD_SRC_WORLD_H
diff --git a/src/writer.c b/src/writer.c
index fa1abd9f..6d52b4e6 100644
--- a/src/writer.c
+++ b/src/writer.c
@@ -3,6 +3,7 @@
 
 #include "block_dumper.h"
 #include "env.h"
+#include "memory.h"
 #include "namespaces.h"
 #include "node.h"
 #include "sink.h"
@@ -34,7 +35,6 @@
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
 
 typedef enum {
@@ -175,14 +175,11 @@ supports_uriref(const SerdWriter* writer)
 }
 
 static SerdStatus
-free_context(WriteContext* const ctx)
+free_context(SerdWriter* const writer)
 {
-  serd_node_free(ctx->graph);
-  serd_node_free(ctx->subject);
-  serd_node_free(ctx->predicate);
-  ctx->graph     = NULL;
-  ctx->subject   = NULL;
-  ctx->predicate = NULL;
+  serd_node_free(writer->world->allocator, writer->context.graph);
+  serd_node_free(writer->world->allocator, writer->context.subject);
+  serd_node_free(writer->world->allocator, writer->context.predicate);
   return SERD_SUCCESS;
 }
 
@@ -234,13 +231,14 @@ push_context(SerdWriter* const        writer,
 
   // Update the current context
 
-  const WriteContext current = {type,
-                                flags,
-                                serd_node_copy(graph),
-                                serd_node_copy(subject),
-                                serd_node_copy(predicate),
-                                0U,
-                                0U};
+  const WriteContext current = {
+    type,
+    flags,
+    serd_node_copy(writer->world->allocator, graph),
+    serd_node_copy(writer->world->allocator, subject),
+    serd_node_copy(writer->world->allocator, predicate),
+    0U,
+    0U};
 
   writer->context = current;
   return SERD_SUCCESS;
@@ -251,7 +249,7 @@ pop_context(SerdWriter* writer)
 {
   assert(writer->anon_stack_size > 0);
 
-  free_context(&writer->context);
+  free_context(writer);
   writer->context = writer->anon_stack[--writer->anon_stack_size];
 }
 
@@ -971,10 +969,10 @@ write_pred(SerdWriter* writer, SerdStatementFlags flags, const SerdNode* pred)
   TRY(st, write_node(writer, pred, SERD_PREDICATE, flags));
   TRY(st, write_sep(writer, flags, SEP_P_O));
 
-  serd_node_set(&writer->context.predicate, pred);
   writer->context.predicates     = true;
   writer->context.comma_indented = false;
-  return st;
+  return serd_node_set(
+    writer->world->allocator, &writer->context.predicate, pred);
 }
 
 SERD_NODISCARD static SerdStatus
@@ -1068,7 +1066,7 @@ update_abbreviation_context(SerdWriter* const        writer,
 {
   SerdStatus st = SERD_SUCCESS;
 
-  // Push context for anonymous or list subject if necessary
+  // Push context for list or anonymous subject if necessary
   if (flags & SERD_ANON_S) {
     st = push_context(writer, CTX_BLANK, flags, graph, subject, predicate);
   } else if (flags & SERD_LIST_S) {
@@ -1175,9 +1173,13 @@ write_turtle_trig_statement(SerdWriter* const        writer,
       TRY(st, write_sep(writer, flags, SEP_ANON_S_P));
     }
 
-    // Set context to new subject and write predicate
+    // Set context to new subject
     reset_context(writer, 0U);
-    serd_node_set(&writer->context.subject, subject);
+    TRY(st,
+        serd_node_set(
+          writer->world->allocator, &writer->context.subject, subject));
+
+    // Write predicate
     if (!(flags & SERD_LIST_S)) {
       TRY(st, write_pred(writer, flags, predicate));
     }
@@ -1218,7 +1220,7 @@ write_trig_statement(SerdWriter* const        writer,
     if (graph) {
       TRY(st, write_node(writer, graph, SERD_GRAPH, flags));
       TRY(st, write_sep(writer, flags, SEP_GRAPH_BEGIN));
-      serd_node_set(&writer->context.graph, graph);
+      serd_node_set(writer->world->allocator, &writer->context.graph, graph);
     }
   }
 
@@ -1340,14 +1342,18 @@ serd_writer_new(SerdWorld*        world,
   assert(env);
   assert(output);
 
-  SerdBlockDumper dumper = {NULL, NULL, 0U, 0U};
-  if (serd_block_dumper_open(&dumper, output, block_size)) {
+  SerdBlockDumper dumper = {world->allocator, NULL, NULL, 0U, 0U};
+  if (serd_block_dumper_open(world, &dumper, output, block_size)) {
     return NULL;
   }
 
-  const size_t       max_depth = world->limits.writer_max_depth;
-  const WriteContext context   = WRITE_CONTEXT_NULL;
-  SerdWriter*        writer    = (SerdWriter*)calloc(1, sizeof(SerdWriter));
+  const WriteContext context = WRITE_CONTEXT_NULL;
+
+  SerdWriter* writer = (SerdWriter*)serd_wcalloc(world, 1, sizeof(SerdWriter));
+  if (!writer) {
+    serd_block_dumper_close(&dumper);
+    return NULL;
+  }
 
   writer->world     = world;
   writer->syntax    = syntax;
@@ -1358,13 +1364,19 @@ serd_writer_new(SerdWorld*        world,
   writer->output    = dumper;
   writer->context   = context;
 
-  if (max_depth) {
-    writer->max_depth  = max_depth;
-    writer->anon_stack = (WriteContext*)calloc(max_depth, sizeof(WriteContext));
+  if (world->limits.writer_max_depth) {
+    writer->max_depth  = world->limits.writer_max_depth;
+    writer->anon_stack = (WriteContext*)serd_wcalloc(
+      world, world->limits.writer_max_depth, sizeof(WriteContext));
+    if (!writer->anon_stack) {
+      serd_wfree(world, writer);
+      return NULL;
+    }
   }
 
-  writer->iface.handle   = writer;
-  writer->iface.on_event = (SerdEventFunc)serd_writer_on_event;
+  writer->iface.allocator = world->allocator;
+  writer->iface.handle    = writer;
+  writer->iface.on_event  = (SerdEventFunc)serd_writer_on_event;
 
   return writer;
 }
@@ -1374,14 +1386,15 @@ serd_writer_chop_blank_prefix(SerdWriter* writer, const char* prefix)
 {
   assert(writer);
 
-  free(writer->bprefix);
+  serd_wfree(writer->world, writer->bprefix);
   writer->bprefix_len = 0;
   writer->bprefix     = NULL;
 
   const size_t prefix_len = prefix ? strlen(prefix) : 0;
   if (prefix_len) {
     writer->bprefix_len = prefix_len;
-    writer->bprefix     = (char*)malloc(writer->bprefix_len + 1);
+    writer->bprefix =
+      (char*)serd_wmalloc(writer->world, writer->bprefix_len + 1);
     memcpy(writer->bprefix, prefix, writer->bprefix_len + 1);
   }
 }
@@ -1417,12 +1430,12 @@ serd_writer_set_root_uri(SerdWriter* writer, const SerdStringView uri)
 {
   assert(writer);
 
-  serd_node_free(writer->root_node);
+  serd_node_free(writer->world->allocator, writer->root_node);
   writer->root_node = NULL;
   writer->root_uri  = SERD_URI_NULL;
 
   if (uri.length) {
-    writer->root_node = serd_new_uri(uri);
+    writer->root_node = serd_new_uri(writer->world->allocator, uri);
     writer->root_uri  = serd_node_uri_view(writer->root_node);
   }
 
@@ -1462,13 +1475,13 @@ serd_writer_free(SerdWriter* writer)
   }
 
   serd_writer_finish(writer);
-  free_context(&writer->context);
+  free_context(writer);
   free_anon_stack(writer);
   serd_block_dumper_close(&writer->output);
-  free(writer->anon_stack);
-  free(writer->bprefix);
-  serd_node_free(writer->root_node);
-  free(writer);
+  serd_wfree(writer->world, writer->anon_stack);
+  serd_wfree(writer->world, writer->bprefix);
+  serd_node_free(writer->world->allocator, writer->root_node);
+  serd_wfree(writer->world, writer);
 }
 
 const SerdSink*
-- 
cgit v1.2.1