From 248a874d7425749d29cf900a1c3783c624ea8d8c Mon Sep 17 00:00:00 2001 From: David Robillard 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. --- include/serd/buffer.h | 12 +++--- include/serd/caret.h | 11 ++++-- include/serd/env.h | 6 ++- include/serd/memory.h | 96 +++++++++++++++++++++++++++++++++++++++++++++++- include/serd/node.h | 60 +++++++++++++++++++++--------- include/serd/sink.h | 9 +++-- include/serd/statement.h | 11 ++++-- include/serd/uri.h | 8 +++- include/serd/world.h | 7 +++- 9 files changed, 182 insertions(+), 38 deletions(-) (limited to 'include/serd') diff --git a/include/serd/buffer.h b/include/serd/buffer.h index 20abfb29..a56188e7 100644 --- a/include/serd/buffer.h +++ b/include/serd/buffer.h @@ -5,6 +5,7 @@ #define SERD_BUFFER_H #include "serd/attributes.h" +#include "serd/memory.h" #include "zix/attributes.h" #include @@ -23,18 +24,19 @@ SERD_BEGIN_DECLS @{ */ -/// A mutable buffer in memory +/// A dynamically resizable mutable buffer in memory typedef struct { - void* ZIX_NULLABLE buf; ///< Buffer - size_t len; ///< Size of buffer in bytes + SerdAllocator* ZIX_NULLABLE allocator; ///< Allocator for buf + void* ZIX_NULLABLE buf; ///< Buffer + size_t len; ///< Size of buffer in bytes } SerdBuffer; /** A function for writing to a buffer, resizing it if necessary. This function can be used as a #SerdWriteFunc to write to a #SerdBuffer - which is resized as necessary with realloc(). The `stream` parameter must - point to an initialized #SerdBuffer. + which is reallocated as necessary. The `stream` parameter must point to an + initialized #SerdBuffer. Note that when writing a string, the string in the buffer will not be null-terminated until serd_buffer_close() is called. diff --git a/include/serd/caret.h b/include/serd/caret.h index 429a6fcd..54df0932 100644 --- a/include/serd/caret.h +++ b/include/serd/caret.h @@ -5,6 +5,7 @@ #define SERD_CARET_H #include "serd/attributes.h" +#include "serd/memory.h" #include "serd/node.h" #include "zix/attributes.h" @@ -29,23 +30,27 @@ typedef struct SerdCaretImpl SerdCaret; valid. That is, serd_caret_document() will return exactly the pointer `document`, not a copy. + @param allocator Allocator to use for caret memory. @param document The document or the caret refers to (usually a file URI) @param line The line number in the document (1-based) @param column The column number in the document (1-based) @return A new caret that must be freed with serd_caret_free() */ SERD_API SerdCaret* ZIX_ALLOCATED -serd_caret_new(const SerdNode* ZIX_NONNULL document, +serd_caret_new(SerdAllocator* ZIX_NULLABLE allocator, + const SerdNode* ZIX_NONNULL document, unsigned line, unsigned column); /// Return a copy of `caret` SERD_API SerdCaret* ZIX_ALLOCATED -serd_caret_copy(const SerdCaret* ZIX_NULLABLE caret); +serd_caret_copy(SerdAllocator* ZIX_NULLABLE allocator, + const SerdCaret* ZIX_NULLABLE caret); /// Free `caret` SERD_API void -serd_caret_free(SerdCaret* ZIX_NULLABLE caret); +serd_caret_free(SerdAllocator* ZIX_NULLABLE allocator, + SerdCaret* ZIX_NULLABLE caret); /// Return true iff `lhs` is equal to `rhs` SERD_PURE_API bool diff --git a/include/serd/env.h b/include/serd/env.h index b4f908a9..a0c47997 100644 --- a/include/serd/env.h +++ b/include/serd/env.h @@ -5,6 +5,7 @@ #define SERD_ENV_H #include "serd/attributes.h" +#include "serd/memory.h" #include "serd/node.h" #include "serd/sink.h" #include "serd/status.h" @@ -26,11 +27,12 @@ typedef struct SerdEnvImpl SerdEnv; /// Create a new environment SERD_API SerdEnv* ZIX_ALLOCATED -serd_env_new(SerdStringView base_uri); +serd_env_new(SerdAllocator* ZIX_NULLABLE allocator, SerdStringView base_uri); /// Copy an environment SERD_API SerdEnv* ZIX_ALLOCATED -serd_env_copy(const SerdEnv* ZIX_NULLABLE env); +serd_env_copy(SerdAllocator* ZIX_NULLABLE allocator, + const SerdEnv* ZIX_NULLABLE env); /// Return true iff `a` is equal to `b` SERD_PURE_API bool diff --git a/include/serd/memory.h b/include/serd/memory.h index 050b7597..4a653349 100644 --- a/include/serd/memory.h +++ b/include/serd/memory.h @@ -7,6 +7,8 @@ #include "serd/attributes.h" #include "zix/attributes.h" +#include + SERD_BEGIN_DECLS /** @@ -15,15 +17,107 @@ SERD_BEGIN_DECLS @{ */ +/** + A memory allocator. + + This object-like structure provides an interface like the standard C + functions malloc(), calloc(), realloc(), free(), and aligned_alloc(). It + contains function pointers that differ from their standard counterparts by + taking a context parameter (a pointer to this struct), which allows the user + to implement custom stateful allocators. +*/ +typedef struct SerdAllocatorImpl SerdAllocator; + +/** + General malloc-like memory allocation function. + + This works like the standard C malloc(), except has an additional handle + parameter for implementing stateful allocators without static data. +*/ +typedef void* ZIX_ALLOCATED (*SerdAllocatorMallocFunc)( // + SerdAllocator* ZIX_NULLABLE allocator, + size_t size); + +/** + General calloc-like memory allocation function. + + This works like the standard C calloc(), except has an additional handle + parameter for implementing stateful allocators without static data. +*/ +typedef void* ZIX_ALLOCATED (*SerdAllocatorCallocFunc)( // + SerdAllocator* ZIX_NULLABLE allocator, + size_t nmemb, + size_t size); + +/** + General realloc-like memory reallocation function. + + This works like the standard C remalloc(), except has an additional handle + parameter for implementing stateful allocators without static data. +*/ +typedef void* ZIX_ALLOCATED (*SerdAllocatorReallocFunc)( // + SerdAllocator* ZIX_NULLABLE allocator, + void* ZIX_NULLABLE ptr, + size_t size); + +/** + General free-like memory deallocation function. + + This works like the standard C remalloc(), except has an additional handle + parameter for implementing stateful allocators without static data. +*/ +typedef void (*SerdAllocatorFreeFunc)( // + SerdAllocator* ZIX_NULLABLE allocator, + void* ZIX_NULLABLE ptr); + +/** + General aligned_alloc-like memory deallocation function. + + This works like the standard C aligned_alloc(), except has an additional + handle parameter for implementing stateful allocators without static data. +*/ +typedef void* ZIX_ALLOCATED (*SerdAllocatorAlignedAllocFunc)( // + SerdAllocator* ZIX_NULLABLE allocator, + size_t alignment, + size_t size); + +/** + General aligned memory deallocation function. + + This works like the standard C free(), but must be used to free memory + allocated with the aligned_alloc() method of the allocator. This allows + portability to systems (like Windows) that can not use the same free function + in these cases. +*/ +typedef void (*SerdAllocatorAlignedFreeFunc)( // + SerdAllocator* ZIX_NULLABLE allocator, + void* ZIX_NULLABLE ptr); + +/// Definition of SerdAllocator +struct SerdAllocatorImpl { + SerdAllocatorMallocFunc ZIX_ALLOCATED malloc; + SerdAllocatorCallocFunc ZIX_ALLOCATED calloc; + SerdAllocatorReallocFunc ZIX_ALLOCATED realloc; + SerdAllocatorFreeFunc ZIX_ALLOCATED free; + SerdAllocatorAlignedAllocFunc ZIX_ALLOCATED aligned_alloc; + SerdAllocatorAlignedFreeFunc ZIX_ALLOCATED aligned_free; +}; + +/// Return the default allocator which simply uses the system allocator +SERD_CONST_API SerdAllocator* ZIX_NONNULL +serd_default_allocator(void); + /** Free memory allocated by Serd. This function exists because some systems require memory allocated by a library to be freed by code in the same library. It is otherwise equivalent to the standard C free() function. + + This may be used to free memory allocated using serd_default_allocator(). */ SERD_API void -serd_free(void* ZIX_NULLABLE ptr); +serd_free(SerdAllocator* ZIX_NULLABLE allocator, void* ZIX_NULLABLE ptr); /** @} diff --git a/include/serd/node.h b/include/serd/node.h index b0b14a24..f42f07f1 100644 --- a/include/serd/node.h +++ b/include/serd/node.h @@ -5,6 +5,7 @@ #define SERD_NODE_H #include "serd/attributes.h" +#include "serd/memory.h" #include "serd/string_view.h" #include "serd/uri.h" #include "serd/write_result.h" @@ -117,13 +118,15 @@ typedef uint32_t SerdNodeFlags; to create URIs, blank nodes, CURIEs, and simple string literals. */ SERD_API SerdNode* ZIX_ALLOCATED -serd_new_token(SerdNodeType type, SerdStringView string); +serd_new_token(SerdAllocator* ZIX_NULLABLE allocator, + SerdNodeType type, + SerdStringView string); /** Create a new string literal node. */ SERD_API SerdNode* ZIX_ALLOCATED -serd_new_string(SerdStringView string); +serd_new_string(SerdAllocator* ZIX_NULLABLE allocator, SerdStringView string); /** Create a new literal node with optional datatype or language. @@ -132,6 +135,8 @@ serd_new_string(SerdStringView string); associated datatype URI or language tag, as well as control whether a literal should be written as a short or long (triple-quoted) string. + @param allocator Allocator for the returned node. + @param string The string value of the literal. @param flags Flags to describe the literal and its metadata. This must be a @@ -147,27 +152,28 @@ serd_new_string(SerdStringView string); serd_node_free(), or null if the arguments are invalid or allocation failed. */ SERD_API SerdNode* ZIX_ALLOCATED -serd_new_literal(SerdStringView string, - SerdNodeFlags flags, - SerdStringView meta); +serd_new_literal(SerdAllocator* ZIX_NULLABLE allocator, + SerdStringView string, + SerdNodeFlags flags, + SerdStringView meta); /** Create a new node from a blank node label. */ SERD_API SerdNode* ZIX_ALLOCATED -serd_new_blank(SerdStringView string); +serd_new_blank(SerdAllocator* ZIX_NULLABLE allocator, SerdStringView string); /** Create a new URI node from a parsed URI. */ SERD_API SerdNode* ZIX_ALLOCATED -serd_new_parsed_uri(SerdURIView uri); +serd_new_parsed_uri(SerdAllocator* ZIX_NULLABLE allocator, SerdURIView uri); /** Create a new URI node from a string. */ SERD_API SerdNode* ZIX_ALLOCATED -serd_new_uri(SerdStringView string); +serd_new_uri(SerdAllocator* ZIX_NULLABLE allocator, SerdStringView string); /** Create a new file URI node from a file system path and optional hostname. @@ -178,13 +184,15 @@ serd_new_uri(SerdStringView string); If `path` is relative, `hostname` is ignored. */ SERD_API SerdNode* ZIX_ALLOCATED -serd_new_file_uri(SerdStringView path, SerdStringView hostname); +serd_new_file_uri(SerdAllocator* ZIX_NULLABLE allocator, + SerdStringView path, + SerdStringView hostname); /** Create a new canonical xsd:boolean node. */ SERD_API SerdNode* ZIX_ALLOCATED -serd_new_boolean(bool b); +serd_new_boolean(SerdAllocator* ZIX_NULLABLE allocator, bool b); /** Create a new canonical xsd:decimal literal. @@ -196,11 +204,14 @@ serd_new_boolean(bool b); (a leading and/or trailing '0' will be added if necessary), for example, "1.0". It will never be in scientific notation. + @param allocator Allocator for the returned node. @param d The value for the new node. @param datatype Datatype of node, or NULL for xsd:decimal. */ SERD_API SerdNode* ZIX_ALLOCATED -serd_new_decimal(double d, const SerdNode* ZIX_NULLABLE datatype); +serd_new_decimal(SerdAllocator* ZIX_NULLABLE allocator, + double d, + const SerdNode* ZIX_NULLABLE datatype); /** Create a new canonical xsd:double literal. @@ -212,11 +223,12 @@ serd_new_decimal(double d, const SerdNode* ZIX_NULLABLE datatype); Uses the shortest possible representation that precisely describes the value, which has at most 17 significant digits (under 24 characters total). + @param allocator Allocator for the returned node. @param d Double value to write. @return A literal node with datatype xsd:double. */ SERD_API SerdNode* ZIX_ALLOCATED -serd_new_double(double d); +serd_new_double(SerdAllocator* ZIX_NULLABLE allocator, double d); /** Create a new canonical xsd:float literal. @@ -224,11 +236,12 @@ serd_new_double(double d); Uses identical formatting to serd_new_double(), except with at most 9 significant digits (under 14 characters total). + @param allocator Allocator for the returned node. @param f Float value of literal. @return A literal node with datatype xsd:float. */ SERD_API SerdNode* ZIX_ALLOCATED -serd_new_float(float f); +serd_new_float(SerdAllocator* ZIX_NULLABLE allocator, float f); /** Create a new canonical xsd:integer literal. @@ -236,10 +249,11 @@ serd_new_float(float f); The node will be an xsd:integer literal like "1234", with datatype xsd:integer. + @param allocator Allocator for the returned node. @param i Integer value of literal. */ SERD_API SerdNode* ZIX_ALLOCATED -serd_new_integer(int64_t i); +serd_new_integer(SerdAllocator* ZIX_NULLABLE allocator, int64_t i); /** Create a new canonical xsd:base64Binary literal. @@ -247,21 +261,31 @@ serd_new_integer(int64_t i); This function can be used to make a node out of arbitrary binary data, which can be decoded using serd_base64_decode(). + @param allocator Allocator for the returned node. @param buf Raw binary data to encode in node. @param size Size of `buf` in bytes. */ SERD_API SerdNode* ZIX_ALLOCATED -serd_new_base64(const void* ZIX_NONNULL buf, size_t size); +serd_new_base64(SerdAllocator* ZIX_NULLABLE allocator, + const void* ZIX_NONNULL buf, + size_t size); + +/** + Return a deep copy of `node`. -/// Return a deep copy of `node` + @param allocator Allocator for the returned node. + @param node The node to copyl +*/ SERD_API SerdNode* ZIX_ALLOCATED -serd_node_copy(const SerdNode* ZIX_NULLABLE node); +serd_node_copy(SerdAllocator* ZIX_NULLABLE allocator, + const SerdNode* ZIX_NULLABLE node); /** Free any data owned by `node`. */ SERD_API void -serd_node_free(SerdNode* ZIX_NULLABLE node); +serd_node_free(SerdAllocator* ZIX_NULLABLE allocator, + SerdNode* ZIX_NULLABLE node); /** @} diff --git a/include/serd/sink.h b/include/serd/sink.h index 291e8d3f..e34ce941 100644 --- a/include/serd/sink.h +++ b/include/serd/sink.h @@ -6,6 +6,7 @@ #include "serd/attributes.h" #include "serd/event.h" +#include "serd/memory.h" #include "serd/node.h" #include "serd/statement.h" #include "serd/status.h" @@ -65,14 +66,16 @@ typedef void (*SerdFreeFunc)(void* ZIX_NULLABLE ptr); /** Create a new sink. + @param allocator Allocator to use for the returned sink. @param handle Opaque handle that will be passed to sink functions. @param event_func Function that will be called for every event. @param free_handle Free function to call on handle in serd_sink_free(). */ SERD_API SerdSink* ZIX_ALLOCATED -serd_sink_new(void* ZIX_NULLABLE handle, - SerdEventFunc ZIX_NULLABLE event_func, - SerdFreeFunc ZIX_NULLABLE free_handle); +serd_sink_new(SerdAllocator* ZIX_NULLABLE allocator, + void* ZIX_NULLABLE handle, + SerdEventFunc ZIX_NULLABLE event_func, + SerdFreeFunc ZIX_NULLABLE free_handle); /// Free `sink` SERD_API void diff --git a/include/serd/statement.h b/include/serd/statement.h index a4109958..03d7ae6d 100644 --- a/include/serd/statement.h +++ b/include/serd/statement.h @@ -6,6 +6,7 @@ #include "serd/attributes.h" #include "serd/caret.h" +#include "serd/memory.h" #include "serd/node.h" #include "zix/attributes.h" @@ -55,6 +56,7 @@ typedef struct SerdStatementImpl SerdStatement; statements in models, this is the lifetime of the model. For user-created statements, the simplest way to handle this is to use `SerdNodes`. + @param allocator Allocator for the returned statement. @param s The subject @param p The predicate ("key") @param o The object ("value") @@ -63,7 +65,8 @@ typedef struct SerdStatementImpl SerdStatement; @return A new statement that must be freed with serd_statement_free() */ SERD_API SerdStatement* ZIX_ALLOCATED -serd_statement_new(const SerdNode* ZIX_NONNULL s, +serd_statement_new(SerdAllocator* ZIX_NULLABLE allocator, + const SerdNode* ZIX_NONNULL s, const SerdNode* ZIX_NONNULL p, const SerdNode* ZIX_NONNULL o, const SerdNode* ZIX_NULLABLE g, @@ -71,11 +74,13 @@ serd_statement_new(const SerdNode* ZIX_NONNULL s, /// Return a copy of `statement` SERD_API SerdStatement* ZIX_ALLOCATED -serd_statement_copy(const SerdStatement* ZIX_NULLABLE statement); +serd_statement_copy(SerdAllocator* ZIX_NULLABLE allocator, + const SerdStatement* ZIX_NULLABLE statement); /// Free `statement` SERD_API void -serd_statement_free(SerdStatement* ZIX_NULLABLE statement); +serd_statement_free(SerdAllocator* ZIX_NULLABLE allocator, + SerdStatement* ZIX_NULLABLE statement); /// Return the given node of the statement SERD_PURE_API const SerdNode* ZIX_NULLABLE diff --git a/include/serd/uri.h b/include/serd/uri.h index 7a63f183..c978782f 100644 --- a/include/serd/uri.h +++ b/include/serd/uri.h @@ -5,6 +5,7 @@ #define SERD_URI_H #include "serd/attributes.h" +#include "serd/memory.h" #include "serd/stream.h" #include "serd/string_view.h" #include "zix/attributes.h" @@ -55,12 +56,15 @@ static const SerdURIView SERD_URI_NULL = The returned path and `*hostname` must be freed with serd_free(). + @param allocator Allocator for the returned string. @param uri A file URI. @param hostname If non-NULL, set to the hostname, if present. - @return A newly-allocated filesystem path. + + @return A newly allocated path string that must be freed with serd_free(). */ SERD_API char* ZIX_ALLOCATED -serd_parse_file_uri(const char* ZIX_NONNULL uri, +serd_parse_file_uri(SerdAllocator* ZIX_NULLABLE allocator, + const char* ZIX_NONNULL uri, char* ZIX_NONNULL* ZIX_NULLABLE hostname); /// Return true iff `string` starts with a valid URI scheme diff --git a/include/serd/world.h b/include/serd/world.h index 9440e6eb..dc2b3b83 100644 --- a/include/serd/world.h +++ b/include/serd/world.h @@ -5,6 +5,7 @@ #define SERD_WORLD_H #include "serd/attributes.h" +#include "serd/memory.h" #include "serd/node.h" #include "serd/status.h" #include "zix/attributes.h" @@ -35,12 +36,16 @@ typedef struct { shared between worlds. */ SERD_MALLOC_API SerdWorld* ZIX_ALLOCATED -serd_world_new(void); +serd_world_new(SerdAllocator* ZIX_NULLABLE allocator); /// Free `world` SERD_API void serd_world_free(SerdWorld* ZIX_NULLABLE world); +/// Return the allocator used by `world` +SERD_PURE_API SerdAllocator* ZIX_NONNULL +serd_world_allocator(const SerdWorld* ZIX_NONNULL world); + /** Return the current resource limits. -- cgit v1.2.1