Skip to content

Latest commit

 

History

History
4357 lines (2767 loc) · 129 KB

fio-stl.md

File metadata and controls

4357 lines (2767 loc) · 129 KB
title sidebar
facil.io - 0.8.x C STL - a Simple Template Library for C
0.8.x/_sidebar.md

{{{title}}}

At the core of the facil.io library is a single header Simple Template Library for C.

The Simple Template Library is a "swiss-army-knife" library, that uses MACROS to generate code for different common types, such as Hash Maps, Arrays, Linked Lists, Binary-Safe Strings, etc'.

The Simple Template Library also offers common functional primitives, such as bit operations, atomic operations, CLI parsing, JSON, task queues, and a custom memory allocator.

In other words, all the common building blocks one could need in a C project are placed in this single header file.

The header could be included multiple times with different results, creating different types or exposing different helper functions.

A Lower Level API Notice for facil.io Application Developers

The core library is probably not the API most facil.io web application developers need to focus on.

This API is used to power the higher level API offered by the facil.io web framework. If you're developing a facil.io web application, use the higher level API when possible.

Simple Template Library (STL) Overview

The core Simple Template Library (STL) is a single file header library (fio-stl.h).

The header includes a Simple Template Library for common types, such as:

In addition, the core Simple Template Library (STL) includes helpers for common tasks, such as:

Testing the Library (FIO_TEST_CSTL)

To test the library, define the FIO_TEST_CSTL macro and include the header. A testing function called fio_test_dynamic_types will be defined. Call that function in your code to test the library.


Version and Common Helper Macros

The facil.io C STL (Simple Template Library) offers a number of common helper macros that are also used internally. These are automatically included once the fio-stl.h is included.

Version Macros

The facil.io C STL library follows semantic versioning and supports macros that will help detect and validate it's version.

FIO_VERSION_MAJOR

Translates to the STL's major version number.

MAJOR version upgrades require a code review and possibly significant changes. Even functions with the same name might change their behavior.

FIO_VERSION_MINOR

Translates to the STL's minor version number.

Please review your code before adopting a MINOR version upgrade.

FIO_VERSION_PATCH

Translates to the STL's patch version number.

PATCH versions should be adopted as soon as possible (they contain bug fixes).

FIO_VERSION_BETA

Translates to the STL's beta version number.

FIO_VERSION_STRING

Translates to the STL's version as a String (i.e., 0.8.0.beta1.

FIO_VERSION_GUARD

If the FIO_VERSION_GUARD macro is defined in a single translation unit (C file) before including fio-stl.h for the first time, then the version macros become available using functions as well: fio_version_major, fio_version_minor, etc'.

FIO_VERSION_VALIDATE

By adding the FIO_VERSION_GUARD functions, a version test could be performed during runtime (which can be used for static libraries), using the macro FIO_VERSION_VALIDATE().

Pointer Arithmetics

FIO_PTR_MATH_LMASK

#define FIO_PTR_MATH_LMASK(T_type, ptr, bits)                                  \
  ((T_type *)((uintptr_t)(ptr) & (((uintptr_t)1 << (bits)) - 1)))

Masks a pointer's left-most bits, returning the right bits (i.e., 0x000000FF).

FIO_PTR_MATH_RMASK

#define FIO_PTR_MATH_RMASK(T_type, ptr, bits)                                  \
  ((T_type *)((uintptr_t)(ptr) & ((~(uintptr_t)0) << bits)))

Masks a pointer's right-most bits, returning the left bits (i.e., 0xFFFFFF00).

FIO_PTR_MATH_ADD

#define FIO_PTR_MATH_ADD(T_type, ptr, offset)                                  \
  ((T_type *)((uintptr_t)(ptr) + (uintptr_t)(offset)))

Add offset bytes to pointer, updating the pointer's type.

FIO_PTR_MATH_SUB

#define FIO_PTR_MATH_SUB(T_type, ptr, offset)                                  \
  ((T_type *)((uintptr_t)(ptr) - (uintptr_t)(offset)))

Subtract X bytes from pointer, updating the pointer's type.

FIO_PTR_FROM_FIELD

#define FIO_PTR_FROM_FIELD(T_type, field, ptr)                                 \
  FIO_PTR_MATH_SUB(T_type, ptr, (&((T_type *)0)->field))

Find the root object (of a struct) from it's field.

Default Memory Allocation

By setting these macros, the memory allocator used by facil.io could be changed from the default allocator (either the custom allocator or, if missing, the system's allocator).

When facil.io's memory allocator is defined (using FIO_MALLOC), these macros will be automatically overwritten to use the custom memory allocator. To use a different allocator, you may redefine the macros.

FIO_MEM_CALLOC

#define FIO_MEM_CALLOC(size, units) calloc((size), (units))

Allocates size X units of bytes, where all bytes equal zero.

FIO_MEM_REALLOC

#define FIO_MEM_REALLOC(ptr, old_size, new_size, copy_len) realloc((ptr), (new_size))

Reallocates memory, copying (at least) copy_len if necessary.

FIO_MEM_FREE

#define FIO_MEM_FREE(ptr, size) free((ptr))

Frees allocated memory.

FIO_MALLOC_TMP_USE_SYSTEM

When defined, temporarily bypasses the FIO_MEM_CALLOC macros and uses the system's calloc, realloc and free functions.

Naming and Misc. Macros

FIO_IFUNC

#define FIO_IFUNC static inline __attribute__((unused))

Marks a function as static, inline and possibly unused.

FIO_SFUNC

#define FIO_SFUNC static __attribute__((unused))

Marks a function as static and possibly unused.

FIO_NAME

#define FIO_NAME(prefix, postfix)

Used for naming functions and variables resulting in: prefix_postfix

i.e.:

// typedef struct { long l; } number_s
typedef struct { long l; } FIO_NAME(number, s)

// number_s number_add(number_s a, number_s b)
FIO_NAME(number, s) FIO_NAME(number, add)(FIO_NAME(number, s) a, FIO_NAME(number, s) b) {
  a.l += b.l;
  return a;
}

FIO_NAME2

#define FIO_NAME2(prefix, postfix)

Sets naming convention for conversion functions, i.e.: foo2bar

i.e.:

// int64_t a2l(const char * buf)
int64_t FIO_NAME2(a, l)(const char * buf) {
  return fio_atol(&buf);
}

FIO_NAME_BL

#define FIO_NAME_BL(prefix, postfix) 

Sets naming convention for boolean testing functions, i.e.: foo_is_true

i.e.:

// typedef struct { long l; } number_s
typedef struct { long l; } FIO_NAME(number, s)

// int number_is_zero(number_s n)
int FIO_NAME2(number, zero)(FIO_NAME(number, s) n) {
  return (!n.l);
}

Linked Lists

Doubly Linked Lists are an incredibly common and useful data structure.

Linked Lists Performance

Memory overhead (on 64bit machines) is 16 bytes per node (or 8 bytes on 32 bit machines) for the next and prev pointers.

Linked Lists use pointers in order to provide fast add/remove operations with O(1) speeds. This O(1) operation ignores the object allocation time and suffers from poor memory locality, but it's still very fast.

However, Linked Lists suffer from slow seek/find and iteration operations.

Seek/find has a worst case scenario O(n) cost and iteration suffers from a high likelihood of CPU cache misses, resulting in degraded performance.

Linked Lists Overview

Before creating linked lists, the library header should be included at least once.

To create a linked list type, create a struct that includes a FIO_LIST_NODE typed element somewhere within the structure. For example:

// initial `include` defines the `FIO_LIST_NODE` macro and type
#include "fio-stl.h"
// list element
typedef struct {
  long l;
  FIO_LIST_NODE node;
  int i;
  FIO_LIST_NODE node2;
  double d;
} my_list_s;

Next define the FIO_LIST_NAME macro. The linked list helpers and types will all be prefixed by this name. i.e.:

#define FIO_LIST_NAME my_list /* defines list functions (example): my_list_push(...) */

Optionally, define the FIO_LIST_TYPE macro to point at the correct linked-list structure type. By default, the type for linked lists will be <FIO_LIST_NAME>_s.

An example were we need to define the FIO_LIST_TYPE macro will follow later on.

Optionally, define the FIO_LIST_NODE_NAME macro to point the linked list's node. By default, the node for linked lists will be node.

Finally, include the fio-stl.h header to create the linked list helpers.

// initial `include` defines the `FIO_LIST_NODE` macro and type
#include "fio-stl.h"
// list element 
typedef struct {
  long l;
  FIO_LIST_NODE node;
  int i;
  FIO_LIST_NODE node2;
  double d;
} my_list_s;
// create linked list helper functions
#define FIO_LIST_NAME my_list
#include "fio-stl.h"

void example(void) {
  FIO_LIST_HEAD list = FIO_LIST_INIT(list);
  for (int i = 0; i < 10; ++i) {
    my_list_s *n = malloc(sizeof(*n));
    n->i = i;
    my_list_push(&list, n);
  }
  int i = 0;
  while (my_list_any(&list)) {
    my_list_s *n = my_list_shift(&list);
    if (i != n->i) {
      fprintf(stderr, "list error - value mismatch\n"), exit(-1);
    }
    free(n);
    ++i;
  }
  if (i != 10) {
    fprintf(stderr, "list error - count error\n"), exit(-1);
  }
}

Note:

Each node is limited to a single list (an item can't belong to more then one list, unless it's a list of pointers to that item).

Items with more then a single node can belong to more then one list. i.e.:

// list element 
typedef struct {
  long l;
  FIO_LIST_NODE node;
  int i;
  FIO_LIST_NODE node2;
  double d;
} my_list_s;
// list 1 
#define FIO_LIST_NAME my_list
#include "fio-stl.h"
// list 2 
#define FIO_LIST_NAME my_list2
#define FIO_LIST_TYPE my_list_s
#define FIO_LIST_NODE_NAME node2
#include "fio-stl.h"

Linked Lists (embeded) - API

FIO_LIST_INIT(head)

#define FIO_LIST_INIT(obj)                                                     \
  { .next = &(obj), .prev = &(obj) }

This macro initializes an uninitialized node (assumes the data in the node is junk).

LIST_any

int LIST_any(FIO_LIST_HEAD *head)

Returns a non-zero value if there are any linked nodes in the list.

LIST_is_empty

int LIST_is_empty(FIO_LIST_HEAD *head)

Returns a non-zero value if the list is empty.

LIST_remove

FIO_LIST_TYPE *LIST_remove(FIO_LIST_TYPE *node)

Removes a node from the list, Returns NULL if node isn't linked.

LIST_push

FIO_LIST_TYPE *LIST_push(FIO_LIST_HEAD *head, FIO_LIST_TYPE *node)

Pushes an existing node to the end of the list. Returns node.

LIST_pop

FIO_LIST_TYPE *LIST_pop(FIO_LIST_HEAD *head)

Pops a node from the end of the list. Returns NULL if list is empty.

LIST_unshift

FIO_LIST_TYPE *LIST_unshift(FIO_LIST_HEAD *head, FIO_LIST_TYPE *node)

Adds an existing node to the beginning of the list. Returns node.

LIST_shift

FIO_LIST_TYPE *LIST_shift(FIO_LIST_HEAD *head)

Removed a node from the start of the list. Returns NULL if list is empty.

LIST_root

FIO_LIST_TYPE *LIST_root(FIO_LIST_HEAD *ptr)

Returns a pointer to a list's element, from a pointer to a node.

FIO_LIST_EACH

Note: macro, name unchanged, works for all lists

#define FIO_LIST_EACH(type, node_name, head, pos)                              \
  for (type *pos = FIO_PTR_FROM_FIELD(type, node_name, (head)->next),          \
            *next____p_ls =                                                    \
                FIO_PTR_FROM_FIELD(type, node_name, (head)->next->next);       \
       pos != FIO_PTR_FROM_FIELD(type, node_name, (head));                     \
       (pos = next____p_ls),                                                   \
            (next____p_ls = FIO_PTR_FROM_FIELD(type, node_name,                \
                                               next____p_ls->node_name.next)))

Loops through every node in the linked list (except the head).

The type name should reference the list type.

node_name should indicate which node should be used for iteration.

head should point at the head of the list (usually a FIO_LIST_HEAD variable).

pos can be any temporary variable name that will contain the current position in the iteration.

The list can be mutated during the loop, but this is not recommended. Specifically, removing pos is safe, but pushing elements ahead of pos might result in an endless loop.

Note: this macro won't work with pointer tagging


Dynamic Arrays

Dynamic arrays are extremely common and useful data structures.

In essence, Arrays are blocks of memory that contain all their elements "in a row". They grow (or shrink) as more items are added (or removed).

Items are accessed using a numerical index indicating the element's position within the array.

Indexes are zero based (first element == 0).

Dynamic Array Performance

Seeking time is an extremely fast O(1). Arrays are also very fast to iterate since they enjoy high memory locality.

Adding and editing items is also a very fast O(1), especially if enough memory was previously reserved. Otherwise, memory allocation and copying will slow performance.

However, arrays suffer from slow find operations. Find has a worst case scenario O(n) cost.

They also suffer from slow item removal (except, in our case, for pop / unshift operations), since middle-element removal requires memory copying when fixing the "hole" made in the array.

A common solution is to reserve a value for "empty" elements and set the element's value instead of remove the element.

Note: unlike some dynamic array implementations, this STL implementation doesn't grow exponentially. Using the ARY_reserve function is highly encouraged for performance.

Dynamic Array Overview

To create a dynamic array type, define the type name using the FIO_ARRAY_NAME macro. i.e.:

#define FIO_ARRAY_NAME int_ary

Next (usually), define the FIO_ARRAY_TYPE macro with the element type. The default element type is void *. For example:

#define FIO_ARRAY_TYPE int

For complex types, define any (or all) of the following macros:

// set to adjust element copying 
#define FIO_ARRAY_TYPE_COPY(dest, src)  
// set for element cleanup 
#define FIO_ARRAY_TYPE_DESTROY(obj)     
// set to adjust element comparison 
#define FIO_ARRAY_TYPE_CMP(a, b)        
// to be returned when `index` is out of bounds / holes 
#define FIO_ARRAY_TYPE_INVALID 0 
// set ONLY if the invalid element is all zero bytes 
#define FIO_ARRAY_TYPE_INVALID_SIMPLE 1     
// should the object be destroyed when copied to an `old` pointer?
#define FIO_ARRAY_DESTROY_AFTER_COPY 1 
// when array memory grows, how many extra "spaces" should be allocated?
#define FIO_ARRAY_PADDING 4 
// should the array growth be exponential? (ignores FIO_ARRAY_PADDING)
#define FIO_ARRAY_EXPONENTIAL 0 

To create the type and helper functions, include the Simple Template Library header.

For example:

typedef struct {
  int i;
  float f;
} foo_s;

#define FIO_ARRAY_NAME ary
#define FIO_ARRAY_TYPE foo_s
#define FIO_ARRAY_TYPE_CMP(a,b) (a.i == b.i && a.f == b.f)
#include "fio-stl.h"

void example(void) {
  ary_s a = FIO_ARRAY_INIT;
  foo_s *p = ary_push(&a, (foo_s){.i = 42});
  FIO_ARRAY_EACH(&a, pos) { // pos will be a pointer to the element
    fprintf(stderr, "* [%zu]: %p : %d\n", (size_t)(pos - ary_to_a(&a)), pos->i);
  }
  ary_destroy(&a);
}

Dynamic Arrays - API

The Array Type (ARY_s)

typedef struct {
  FIO_ARRAY_TYPE *ary;
  uint32_t capa;
  uint32_t start;
  uint32_t end;
} FIO_NAME(FIO_ARRAY_NAME, s); /* ARY_s in these docs */

The array type should be considered opaque. Use the helper functions to updated the array's state when possible, even though the array's data is easily understood and could be manually adjusted as needed.

FIO_ARRAY_INIT

#define FIO_ARRAY_INIT  {0}

This macro initializes an uninitialized array object.

ARY_destroy

void ARY_destroy(ARY_s * ary);

Destroys any objects stored in the array and frees the internal state.

ARY_new

ARY_s * ARY_new(void);

Allocates a new array object on the heap and initializes it's memory.

ARY_free

void ARY_free(ARY_s * ary);

Frees an array's internal data AND it's container!

ARY_count

uint32_t ARY_count(ARY_s * ary);

Returns the number of elements in the Array.

ARY_capa

uint32_t ARY_capa(ARY_s * ary);

Returns the current, temporary, array capacity (it's dynamic).

ARY_reserve

uint32_t ARY_reserve(ARY_s * ary, int32_t capa);

Reserves a minimal capacity for the array.

If capa is negative, new memory will be allocated at the beginning of the array rather then it's end.

Returns the array's new capacity.

Note: the reserved capacity includes existing data / capacity. If the requested reserved capacity is equal (or less) then the existing capacity, nothing will be done.

ARY_concat

ARY_s * ARY_concat(ARY_s * dest, ARY_s * src);

Adds all the items in the src Array to the end of the dest Array.

The src Array remain untouched.

Always returns the destination array (dest).

ARY_set

FIO_ARRAY_TYPE * ARY_set(ARY_s * ary,
                       int32_t index,
                       FIO_ARRAY_TYPE data,
                       FIO_ARRAY_TYPE *old);

Sets index to the value in data.

If index is negative, it will be counted from the end of the Array (-1 == last element).

If old isn't NULL, the existing data will be copied to the location pointed to by old before the copy in the Array is destroyed.

Returns a pointer to the new object, or NULL on error.

ARY_get

FIO_ARRAY_TYPE ARY_get(ARY_s * ary, int32_t index);

Returns the value located at index (no copying is performed).

If index is negative, it will be counted from the end of the Array (-1 == last element).

Reminder: indexes are zero based (first element == 0).

ARY_find

int32_t ARY_find(ARY_s * ary, FIO_ARRAY_TYPE data, int32_t start_at);

Returns the index of the object or -1 if the object wasn't found.

If start_at is negative (i.e., -1), than seeking will be performed in reverse, where -1 == last index (-2 == second to last, etc').

ARY_remove

int ARY_remove(ARY_s * ary, int32_t index, FIO_ARRAY_TYPE *old);

Removes an object from the array, MOVING all the other objects to prevent "holes" in the data.

If old is set, the data is copied to the location pointed to by old before the data in the array is destroyed.

Returns 0 on success and -1 on error.

This action is O(n) where n in the length of the array. It could get expensive.

ARY_remove2

uint32_t ARY_remove2(ARY_S * ary, FIO_ARRAY_TYPE data);

Removes all occurrences of an object from the array (if any), MOVING all the existing objects to prevent "holes" in the data.

Returns the number of items removed.

This action is O(n) where n in the length of the array. It could get expensive.

ARY_compact

void ARY_compact(ARY_s * ary);

Attempts to lower the array's memory consumption.

ARY_to_a

FIO_ARRAY_TYPE * ARY_to_a(ARY_s * ary);

Returns a pointer to the C array containing the objects.

ARY_push

FIO_ARRAY_TYPE * ARY_push(ARY_s * ary, FIO_ARRAY_TYPE data);

Pushes an object to the end of the Array. Returns a pointer to the new object or NULL on error.

ARY_pop

int ARY_pop(ARY_s * ary, FIO_ARRAY_TYPE *old);

Removes an object from the end of the Array.

If old is set, the data is copied to the location pointed to by old before the data in the array is destroyed.

Returns -1 on error (Array is empty) and 0 on success.

ARY_unshift

FIO_ARRAY_TYPE *ARY_unshift(ARY_s * ary, FIO_ARRAY_TYPE data);

Unshifts an object to the beginning of the Array. Returns a pointer to the new object or NULL on error.

This could be expensive, causing memmove.

ARY_shift

int ARY_shift(ARY_s * ary, FIO_ARRAY_TYPE *old);

Removes an object from the beginning of the Array.

If old is set, the data is copied to the location pointed to by old before the data in the array is destroyed.

Returns -1 on error (Array is empty) and 0 on success.

ARY_each

uint32_t ARY_each(ARY_s * ary, int32_t start_at,
                               int (*task)(FIO_ARRAY_TYPE obj, void *arg),
                               void *arg);

Iteration using a callback for each entry in the array.

The callback task function must accept an the entry data as well as an opaque user pointer.

If the callback returns -1, the loop is broken. Any other value is ignored.

Returns the relative "stop" position (number of items processed + starting point).

FIO_ARRAY_EACH

#define FIO_ARRAY_EACH(array, pos)                                               \
  if ((array)->ary)                                                            \
    for (__typeof__((array)->ary) start__tmp__ = (array)->ary,                 \
                                  pos = ((array)->ary + (array)->start);       \
         pos < (array)->ary + (array)->end;                                    \
         (pos = (array)->ary + (pos - start__tmp__) + 1),                      \
                                  (start__tmp__ = (array)->ary))

Iterates through the list using a for loop.

Access the object with the pointer pos. The pos variable can be named however you please.

It's possible to edit elements within the loop, but avoid editing the array itself (adding / removing elements). Although I hope it's possible, I wouldn't count on it and it could result in items being skipped or unending loops.


Hash Maps / Sets

Hash maps and sets are extremely useful and common mapping / dictionary primitives, also sometimes known as "dictionary".

Hash maps use both a hash and a key to identify a value. The hash value is calculated by feeding the key's data to a hash function (such as Risky Hash or SipHash).

A hash map without a key is known as a Set or a Bag. It uses only a hash (often calculated using the value) to identify a value, sometimes requiring a value equality test as well. This approach often promises a collection of unique values (no duplicate values).

Some map implementations support a FIFO limited storage, which could be used for limited-space caching.

Map Performance

Memory overhead (on 64bit machines) is 24 bytes for the map container + 16 bytes of metadata per object container.

For example, assuming a hash map (not a set) with 8 byte keys and 8 byte values, that would add up to 32 bytes per object (32 * map_capa(map) + 24).

Seeking time is usually a fast O(1), although partial or full hash collisions may increase the cost of the operation.

Adding, editing and removing items is also a very fast O(1), especially if enough memory was previously reserved. However, memory allocation and copying will slow performance, especially when the map need to grow or requires de-fragmentation.

Iteration in this implementation doesn't enjoy memory locality, except for small maps or where the order of insertion randomly produces neighboring hashes. Maps are implemented using an array. The objects are accessed by order of insertion, but they are stored out of order (according to their hash value).

This map implementation has protection features against too many full collisions or non-random hashes. When the map detects a possible "attack", it will start overwriting existing data instead of trying to resolve collisions. This can be adjusted using the FIO_MAP_MAX_FULL_COLLISIONS macro.

Map Overview

To create a map, define FIO_MAP_NAME.

To create a hash map (rather then a set), also define FIO_MAP_KEY (containing the key's type).

Other helpful macros to define might include:

  • FIO_MAP_TYPE, which defaults to void *

  • FIO_MAP_TYPE_INVALID, which defaults to ((FIO_MAP_TYPE){0})

  • FIO_MAP_TYPE_COPY(dest, src), which defaults to (dest) = (src)

  • FIO_MAP_TYPE_DESTROY(obj)

  • FIO_MAP_TYPE_CMP(a, b), which defaults to 1

  • FIO_MAP_KEY

  • FIO_MAP_KEY_INVALID

  • FIO_MAP_KEY_COPY(dest, src)

  • FIO_MAP_KEY_DESTROY(obj)

  • FIO_MAP_KEY_CMP(a, b)

  • FIO_MAP_MAX_FULL_COLLISIONS, which defaults to 96

  • FIO_MAP_DESTROY_AFTER_COPY - uses "smart" defaults to decide if to destroy an object after it was copied (when using set / remove / pop with a pointer to contain old object)

  • FIO_MAP_TYPE_DISCARD(obj) - Handles discarded element data (i.e., insert without overwrite in a Set).

  • FIO_MAP_KEY_DISCARD(obj) - Handles discarded element data (i.e., when overwriting an existing value in a hash map).

  • FIO_MAP_MAX_ELEMENTS - The maximum number of elements allowed before removing old data (FIFO).

  • FIO_MAP_MAX_SEEK - The maximum number of bins to rotate when (partial/full) collisions occur. Limited to a maximum of 255.

To limit the number of elements in a map (FIFO, ignoring last access time), allowing it to behave similarly to a simple caching primitive, define: FIO_MAP_MAX_ELEMENTS.

if FIO_MAP_MAX_ELEMENTS is 0, then the theoretical maximum number of elements should be: (1 << 32) - 1. In practice, the safe limit should be calculated as 1 << 31.

Example:

/* TODO */

Hash Map / Set - API (initialization)

MAP_new

FIO_MAP_PTR MAP_new(void);

Allocates a new map on the heap.

MAP_free

void MAP_free(MAP_PTR m);

Frees a map that was allocated on the heap.

FIO_MAP_INIT

#define FIO_MAP_INIT { .map = NULL }

This macro initializes a map object - often used for maps placed on the stack.

MAP_destroy

void MAP_destroy(MAP_PTR m);

Destroys the map's internal data and re-initializes it.

Hash Map - API (hash map only)

MAP_get (hash map)

FIO_MAP_TYPE MAP_get(FIO_MAP_PTR m,
                     FIO_MAP_HASH hash,
                     FIO_MAP_KEY key);

Returns the object in the hash map (if any) or FIO_MAP_TYPE_INVALID.

MAP_get_ptr (hash map)

FIO_MAP_TYPE *MAP_get_ptr(FIO_MAP_PTR m,
                          FIO_MAP_HASH hash,
                          FIO_MAP_KEY key);

Returns a pointer to the object in the hash map (if any) or NULL.

MAP_set (hash map)

FIO_MAP_TYPE MAP_set(FIO_MAP_PTR m,
                     FIO_MAP_HASH hash,
                     FIO_MAP_KEY key,
                     FIO_MAP_TYPE obj,
                     FIO_MAP_TYPE *old);

Inserts an object to the hash map, returning the new object.

If old is given, existing data will be copied to that location.

MAP_remove (hash map)

int MAP_remove(FIO_MAP_PTR m,
               FIO_MAP_HASH hash,
               FIO_MAP_KEY key,
               FIO_MAP_TYPE *old);

Removes an object from the hash map.

If old is given, existing data will be copied to that location.

Returns 0 on success or -1 if the object couldn't be found.

Set - API (set only)

MAP_get (set)

FIO_MAP_TYPE MAP_get(FIO_MAP_PTR m,
                     FIO_MAP_HASH hash,
                     FIO_MAP_TYPE obj);

Returns the object in the hash map (if any) or FIO_MAP_TYPE_INVALID.

MAP_get_ptr (set)

FIO_MAP_TYPE *MAP_get_ptr(FIO_MAP_PTR m,
                          FIO_MAP_HASH hash,
                          FIO_MAP_TYPE obj);

Returns a pointer to the object in the hash map (if any) or NULL.

set_if_missing (set)

FIO_MAP_TYPE set_if_missing(FIO_MAP_PTR m,
                            FIO_MAP_HASH hash,
                            FIO_MAP_TYPE obj);

Inserts an object to the hash map, returning the existing or new object.

If old is given, existing data will be copied to that location.

MAP_set (set)

void MAP_set(FIO_MAP_PTR m,
             FIO_MAP_HASH hash,
             FIO_MAP_TYPE obj,
             FIO_MAP_TYPE *old);

Inserts an object to the hash map, returning the new object.

If old is given, existing data will be copied to that location.

MAP_remove (set)

int MAP_remove(FIO_MAP_PTR m, FIO_MAP_HASH hash,
               FIO_MAP_TYPE obj, FIO_MAP_TYPE *old);

Removes an object from the hash map.

If old is given, existing data will be copied to that location.

Returns 0 on success or -1 if the object couldn't be found.

Hash Map / Set - API (common)

MAP_count

uintptr_t MAP_count(FIO_MAP_PTR m);

Returns the number of objects in the map.

MAP_capa

uintptr_t MAP_capa(FIO_MAP_PTR m);

Returns the current map's theoretical capacity.

MAP_reserve

uintptr_t MAP_reserve(FIO_MAP_PTR m, uint32_t capa);

Reserves a minimal capacity for the hash map.

MAP_last

FIO_MAP_TYPE MAP_last(FIO_MAP_PTR m);

Allows a peak at the Set's last element.

Remember that objects might be destroyed if the Set is altered (FIO_MAP_TYPE_DESTROY / FIO_MAP_KEY_DESTROY).

MAP_pop

void MAP_pop(FIO_MAP_PTR m, FIO_MAP_TYPE * old);

Allows the Hash to be momentarily used as a stack, destroying the last object added (using FIO_MAP_TYPE_DESTROY / FIO_MAP_KEY_DESTROY).

If old is given, existing data will be copied to that location.

MAP_compact

void MAP_compact(FIO_MAP_PTR m);

Attempts to lower the map's memory consumption.

MAP_rehash

int MAP_rehash(FIO_MAP_PTR m);

Rehashes the Hash Map / Set. Usually this is performed automatically, no need to call the function.

MAP_each

uint32_t FIO_NAME(FIO_MAP_NAME, each)(FIO_MAP_PTR m,
                                      int32_t start_at,
                                      int (*task)(FIO_MAP_TYPE obj, void *arg),
                                      void *arg);

Iteration using a callback for each element in the map.

The callback task function must accept an element variable as well as an opaque user pointer.

If the callback returns -1, the loop is broken. Any other value is ignored.

Returns the relative "stop" position, i.e., the number of items processed + the starting point.

MAP_each_get_key

FIO_MAP_KEY FIO_NAME(FIO_MAP_NAME, each_get_key)(void);

Returns the current key within an each task.

Only available within an each loop.

Note: For sets, returns the hash value, for hash maps, returns the key value.

FIO_MAP_EACH

#define FIO_MAP_EACH(map_, pos_)                                               \
  for (__typeof__((map_)->map) prev__ = NULL,                                  \
                               pos_ = (map_)->map + (map_)->head;              \
       (map_)->head != (uint32_t)-1 &&                                         \
       (prev__ == NULL || pos_ != (map_)->map + (map_)->head);                 \
       (prev__ = pos_), pos_ = (map_)->map + pos_->next)

A macro for a for loop that iterates over all the Map's objects (in order).

Use this macro for small Hash Maps / Sets.

map is a pointer to the Hash Map / Set variable and pos is a temporary variable name to be created for iteration.

pos->hash is the hashing value and pos->obj is the object's data.

For hash maps, use pos->obj.key and pos->obj.value to access the stored data.

Note: this macro doesn't work with pointer tagging.


Dynamic Strings

To create a dynamic string type, define the type name using the FIO_STRING_NAME macro.

The type (FIO_STRING_NAME_s) and the functions will be automatically defined.

String Type information

STR_s

The core type, created by the macro, is the STR_s type - where STR is replaced by FIO_STRING_NAME. i.e.:

#define FIO_STRING_NAME my_str
#include <fio-stl.h>
// results in: my_str_s - i.e.:
void hello(void){
  my_str_s msg = FIO_STRING_INIT;
  my_str_write(&msg, "Hello World", 11);
  printf("%s\n", my_str_data(&msg));
  my_str_destroy(&msg);
}

The type should be considered opaque and must never be accessed directly.

The type's attributes should be accessed ONLY through the accessor functions: STR_info, STR_len, STR2ptr, STR_capa, etc'.

This is because: Small strings that fit into the type directly use the type itself for memory (except the first and last bytes). Larger strings use the type fields for the string's meta-data. Depending on the string's data, the type behaves differently.

fio_str_info_s

Some functions return information about a string's state using the fio_str_info_s type.

This helper type is defined like so:

typedef struct fio_str_info_s {
  char *buf;   /* The string's buffer (pointer to first byte) or NULL on error. */
  size_t len;  /* The string's length, if any. */
  size_t capa; /* The buffer's capacity. Zero (0) indicates the buffer is read-only. */
} fio_str_info_s;

This information type, accessible using the STR_info function, allows direct access and manipulation of the string data.

Changes in string length should be followed by a call to STR_resize.

The data in the string object is always NUL terminated. However, string data might contain binary data, where NUL is a valid character, so using C string functions isn't advised.

String allocation alignment / FIO_STRING_NO_ALIGN

Memory allocators have allocation alignment concerns that require minimum space to be allocated.

The default STR_s type makes use of this extra space for small strings, fitting them into the type.

To prevent this behavior and minimize the space used by the STR_s type, set the FIO_STRING_NO_ALIGN macro to 1.

#define FIO_STRING_NAME big_string
#define FIO_STRING_NO_ALIGN 1
#include <fio-stl.h>
// ...
big_string_s foo = FIO_STRING_INIT;

This could save memory when strings aren't short enough to be contained within the type.

This could also save memory, potentially, if the string type will be wrapped / embedded within other data-types (i.e., using FIO_REF_NAME for reference counting).

String API - Initialization and Destruction

FIO_STRING_INIT

This value should be used for initialization. It should be considered opaque, but is defined as:

#define FIO_STRING_INIT { .special = 1 }

For example:

#define FIO_STRING_NAME fio_str
#include <fio-stl.h>
void example(void) {
  // on the stack
  fio_str_s str = FIO_STRING_INIT;
  // .. 
  fio_str_destroy(&str);
}

FIO_STRING_INIT_EXISTING

This macro allows the container to be initialized with existing data.

#define FIO_STRING_INIT_EXISTING(buffer, length, capacity, dealloc_)              \
  {                                                                            \
    .data = (buffer), .len = (length), .capa = (capacity),                     \
    .dealloc = (dealloc_)                                                      \
  }

The capacity value should exclude the space required for the NUL character (if exists).

dealloc should be a function pointer to a memory deallocation function, such as free, fio_free or NULL (doesn't deallocate the memory).

FIO_STRING_INIT_STATIC

This macro allows the string container to be initialized with existing static data, that shouldn't be freed.

#define FIO_STRING_INIT_STATIC(buffer)                                            \
  { .data = (char *)(buffer), .len = strlen((buffer)), .dealloc = NULL }

FIO_STRING_INIT_STATIC2

This macro allows the string container to be initialized with existing static data, that shouldn't be freed.

#define FIO_STRING_INIT_STATIC2(buffer, length)                                   \
  { .data = (char *)(buffer), .len = (length), .dealloc = NULL }

STR_destroy

void STR_destroy(FIO_STRING_PTR s);

Frees the String's resources and reinitializes the container.

Note: if the container isn't allocated on the stack, it should be freed separately using the appropriate free function, such as STR_free.

STR_new

FIO_STRING_PTR STR_new(void);

Allocates a new String object on the heap.

STR_free

void STR_free(FIO_STRING_PTR s);

Destroys the string and frees the container (if allocated with STR_new).

STR_detach

char * STR_detach(FIO_STRING_PTR s);

Returns a C string with the existing data, re-initializing the String.

Note: the String data is removed from the container, but the container is not freed.

Returns NULL if there's no String data.

String API - String state (data pointers, length, capacity, etc')

STR_info

fio_str_info_s STR_info(const FIO_STRING_PTR s);

Returns the String's complete state (capacity, length and pointer).

STR_len

size_t STR_len(FIO_STRING_PTR s);

Returns the String's length in bytes.

STR2ptr

char *STR2ptr(FIO_STRING_PTR s);

Returns a pointer (char *) to the String's content (first character in the string).

STR_capa

size_t STR_capa(FIO_STRING_PTR s);

Returns the String's existing capacity (total used & available memory).

STR_freeze

void STR_freeze(FIO_STRING_PTR s);

Prevents further manipulations to the String's content.

STR_is_frozen

uint8_t STR_is_frozen(FIO_STRING_PTR s);

Returns true if the string is frozen.

STR_is_eq

int STR_is_eq(const FIO_STRING_PTR str1, const FIO_STRING_PTR str2);

Binary comparison returns 1 if both strings are equal and 0 if not.

STR_hash

uint64_t STR_hash(const FIO_STRING_PTR s);

Returns the string's Risky Hash value.

Note: Hash algorithm might change without notice.

String API - Memory management

STR_resize

fio_str_info_s STR_resize(FIO_STRING_PTR s, size_t size);

Sets the new String size without reallocating any memory (limited by existing capacity).

Returns the updated state of the String.

Note: When shrinking, any existing data beyond the new size may be corrupted or lost.

STR_compact

void STR_compact(FIO_STRING_PTR s);

Performs a best attempt at minimizing memory consumption.

Actual effects depend on the underlying memory allocator and it's implementation. Not all allocators will free any memory.

STR_reserve

fio_str_info_s STR_reserve(FIO_STRING_PTR s, size_t amount);

Reserves at least amount of bytes for the string's data (reserved count includes used data).

Returns the current state of the String.

String API - UTF-8 State

STR_utf8_valid

size_t STR_utf8_valid(FIO_STRING_PTR s);

Returns 1 if the String is UTF-8 valid and 0 if not.

STR_utf8_len

size_t STR_utf8_len(FIO_STRING_PTR s);

Returns the String's length in UTF-8 characters.

STR_utf8_select

int STR_utf8_select(FIO_STRING_PTR s, intptr_t *pos, size_t *len);

Takes a UTF-8 character selection information (UTF-8 position and length) and updates the same variables so they reference the raw byte slice information.

If the String isn't UTF-8 valid up to the requested selection, than pos will be updated to -1 otherwise values are always positive.

The returned len value may be shorter than the original if there wasn't enough data left to accommodate the requested length. When a len value of 0 is returned, this means that pos marks the end of the String.

Returns -1 on error and 0 on success.

String API - Content Manipulation and Review

STR_write

fio_str_info_s STR_write(FIO_STRING_PTR s, const void *src, size_t src_len);

Writes data at the end of the String.

STR_write_i

fio_str_info_s STR_write_i(FIO_STRING_PTR s, int64_t num);

Writes a number at the end of the String using normal base 10 notation.

STR_write_hex

fio_str_info_s STR_write_hex(FIO_STRING_PTR s, int64_t num);

Writes a number at the end of the String using Hex (base 16) notation.

Note: the 0x prefix is automatically written before the hex numerals.

STR_concat / STR_join

fio_str_info_s STR_concat(FIO_STRING_PTR dest, FIO_STRING_PTR const src);

Appends the src String to the end of the dest String. If dest is empty, the resulting Strings will be equal.

STR_join is an alias for STR_concat.

STR_replace

fio_str_info_s STR_replace(FIO_STRING_PTR s,
                           intptr_t start_pos,
                           size_t old_len,
                           const void *src,
                           size_t src_len);

Replaces the data in the String - replacing old_len bytes starting at start_pos, with the data at src (src_len bytes long).

Negative start_pos values are calculated backwards, -1 == end of String.

When old_len is zero, the function will insert the data at start_pos.

If src_len == 0 than src will be ignored and the data marked for replacement will be erased.

STR_vprintf

fio_str_info_s STR_vprintf(FIO_STRING_PTR s, const char *format, va_list argv);

Writes to the String using a vprintf like interface.

Data is written to the end of the String.

STR_printf

fio_str_info_s STR_printf(FIO_STRING_PTR s, const char *format, ...);

Writes to the String using a printf like interface.

Data is written to the end of the String.

STR_readfd

fio_str_info_s STR_readfd(FIO_STRING_PTR s,
                            int fd,
                            intptr_t start_at,
                            intptr_t limit);

Reads data from a file descriptor fd at offset start_at and pastes it's contents (or a slice of it) at the end of the String. If limit == 0, than the data will be read until EOF.

The file should be a regular file or the operation might fail (can't be used for sockets).

Currently implemented only on POSIX systems.

STR_readfile

fio_str_info_s STR_readfile(FIO_STRING_PTR s,
                            const char *filename,
                            intptr_t start_at,
                            intptr_t limit);

Opens the file filename and pastes it's contents (or a slice ot it) at the end of the String. If limit == 0, than the data will be read until EOF.

If the file can't be located, opened or read, or if start_at is beyond the EOF position, NULL is returned in the state's data field.

Works on POSIX systems only.

String API - Base64 support

STR_write_b64enc

fio_str_info_s STR_write_b64enc(FIO_STRING_PTR s,
                                const void *data,
                                size_t data_len,
                                uint8_t url_encoded);

Writes data at the end of the String, encoding the data as Base64 encoded data.

STR_write_b64dec

fio_str_info_s STR_write_b64dec(FIO_STRING_PTR s,
                                const void *encoded,
                                size_t encoded_len);

Writes decoded Base64 data to the end of the String.

String API - escaping / JSON encoding support

STR_write_escape

fio_str_info_s STR_write_escape(FIO_STRING_PTR s,
                                const void *data,
                                size_t data_len);

Writes data at the end of the String, escaping the data using JSON semantics.

The JSON semantic are common to many programming languages, promising a UTF-8 String while making it easy to read and copy the string during debugging.

STR_write_unescape

fio_str_info_s STR_write_unescape(FIO_STRING_PTR s,
                                  const void *escaped,
                                  size_t len);

Writes an escaped data into the string after unescaping the data.


Small (non-dynamic) Strings

The "Small" String helpers use a small footprint to store a binary safe string that doesn't change over time and can be destroyed.

This type was designed to store string information for Hash Maps where most strings might be very short (less than 16 chars on 64 bit systems or less than 8 chars on 32 bit systems).

This approach minimizes memory allocation and improves locality by copying the string data onto the bytes normally used to store the string pointer and it's length.

To create a small, non-dynamic, string type, define the type name using the FIO_SMALL_STR_NAME macro.

The type (FIO_SMALL_STR_NAME_s) and the functions will be automatically defined.

#define FIO_SMALL_STR_NAME key
#define FIO_ATOL
#include "fio-stl.h"

#define FIO_MAP_NAME map
#define FIO_MAP_TYPE uintptr_t
#define FIO_MAP_KEY key_s
#define FIO_MAP_KEY_INVALID (key_s)FIO_SMALL_STR_INIT
#define FIO_MAP_KEY_INVALID_SIMPLE 1 /* invalid type bytes are all zeros */
#define FIO_MAP_KEY_DESTROY(k) key_destroy(&k)
/* destroy discarded keys when overwriting existing data (duplicate keys aren't copied): */
#define FIO_MAP_KEY_DISCARD(k) key_destroy(&k)
#include "fio-stl.h"

void example(void) {
  map_s m = FIO_MAP_INIT;
  /* write the long keys twice, to prove they self-destruct in the Hash-Map */
  for (int overwrite = 0; overwrite < 2; ++overwrite) {
    for (int i = 0; i < 10; ++i) {
      char buf[128] = "a long key will require memory allocation: ";
      size_t len = fio_ltoa(buf + 43, i, 16) + 43;
      key_s k;
      key_set_copy(&k, buf, len);
      map_set(&m, key_hash(&k, (uint64_t)&m), k, (uintptr_t)i, NULL);
    }
  }
  /* short keys don't allocate external memory (string embedded in the object) */
  for (int i = 0; i < 10; ++i) {
    /* short keys fit in pointer + length type... test assumes 64bit addresses */
    char buf[128] = "embed: ";
    size_t len = fio_ltoa(buf + 7, i, 16) + 7;
    key_s k;
    key_set_copy(&k, buf, len);
    map_set(&m, key_hash(&k, (uint64_t)&m), k, (uintptr_t)i, NULL);
  }
  FIO_MAP_EACH(&m, pos) {
    fprintf(stderr, "[%d] %s - memory allocated: %s\n", (int)pos->obj.value,
            key2ptr(&pos->obj.key),
            (key_is_allocated(&pos->obj.key) ? "yes" : "no"));
  }
  map_destroy(&m);
  /* test for memory leaks using valgrind or similar */
}

Small String Initialization

The small string object fits perfectly in one char * pointer and one size_t value, meaning it takes as much memory as storing a string's location and length.

However, the type information isn't stored as simply as one might imagine, allowing short strings to be stored within the object itself, improving locality.

small strings aren't dynamically allocated and their initialization is performed using the FIO_SMALL_STR_INIT macro (for empty strings) or the SMALL_STR_set_const and SMALL_STR_set_copy functions (see example above).

Small String API

SMALL_STR_set_const

void SMALL_STR_set_const(FIO_SMALL_STR_PTR s, const char *str, size_t len);

Initializes the container with the provided static / constant string.

The string will be copied to the container only if it will fit in the container itself. Otherwise, the supplied pointer will be used as is and it should remain valid until the string is destroyed.

The final string can be safely be destroyed (using the destroy function).

SMALL_STR_set_copy

void SMALL_STR_set_copy(FIO_SMALL_STR_PTR s, const char *str, size_t len);

Initializes the container with the provided dynamic string.

The string is always copied and the final string must be destroyed (using the destroy function).

SMALL_STR_destroy

void SMALL_STR_destroy(FIO_SMALL_STR_PTR s);

Frees the String's resources and reinitializes the container.

Note: if the container isn't allocated on the stack, it should be freed separately using the appropriate free function.

SMALL_STR_info

fio_str_info_s SMALL_STR_info(const FIO_SMALL_STR_PTR s);

Returns information regarding the embedded string.

SMALL_STR2ptr

const char *SMALL_STR2ptr(const FIO_SMALL_STR_PTR s);

Returns a pointer (char *) to the String's content.

SMALL_STR_len

size_t SMALL_STR_len(const FIO_SMALL_STR_PTR s);

Returns the String's length in bytes.

SMALL_STR_allocated

int SMALL_STR_allocated(const FIO_SMALL_STR_PTR s);

Returns 1 if memory was allocated and (the String must be destroyed).

SMALL_STR_hash

uint64_t SMALL_STR_hash(const FIO_SMALL_STR_PTR s, uint64_t seed);

Returns the String's Risky Hash.

SMALL_STR_eq

int SMALL_STR_eq(const FIO_SMALL_STR_PTR str1, const FIO_SMALL_STR_PTR str2);

Binary comparison returns 1 if both strings are equal and 0 if not.


Reference Counting / Type Wrapping

If the FIO_REF_NAME macro is defined, then reference counting helpers can be defined for any named type.

By default, FIO_REF_TYPE will equal FIO_REF_NAME_s, using the naming convention in this library.

In addition, the FIO_REF_METADATA macro can be defined with any type, allowing metadata to be attached and accessed using the helper function FIO_REF_metadata(object).

If the FIO_REF_CONSTRUCTOR_ONLY macro is defined, the reference counter constructor (TYPE_new) will be the only constructor function. When set, the reference counting functions will use X_new and X_free. Otherwise (assuming X_new and X_free are already defined), the reference counter will define X_new2 and X_free2 instead.

Note: requires the atomic operations to be defined (FIO_ATOMIC).

Reference counting adds the following functions:

REF_new / REF_new2

FIO_REF_TYPE * REF_new2(void)
// or, if FIO_REF_CONSTRUCTOR_ONLY is defined
FIO_REF_TYPE * REF_new(void)

Allocates a new reference counted object, initializing it using the FIO_REF_INIT(object) macro.

If FIO_REF_METADATA is defined, than the metadata is initialized using the FIO_REF_METADATA_INIT(metadata) macro.

REF_up_ref

FIO_REF_TYPE * REF_up_ref(FIO_REF_TYPE * object)

Increases an object's reference count (an atomic operation, thread-safe).

REF_free / REF_free2

void REF_free2(FIO_REF_TYPE * object)
// or, if FIO_REF_CONSTRUCTOR_ONLY is defined
void REF_free(FIO_REF_TYPE * object)

Frees an object or decreases it's reference count (an atomic operation, thread-safe).

Before the object is freed, the FIO_REF_DESTROY(object) macro will be called.

If FIO_REF_METADATA is defined, than the metadata is also destroyed using the FIO_REF_METADATA_DESTROY(metadata) macro.

REF_metadata

FIO_REF_METADATA * REF_metadata(FIO_REF_TYPE * object)

If FIO_REF_METADATA is defined, than the metadata is accessible using this inlined function.


Pointer Tagging Support:

Pointer tagging allows types created using this library to have their pointers "tagged".

This is when creating / managing dynamic types, where some type data could be written to the pointer data itself.

Note: pointer tagging can't automatically tag "pointers" to objects placed on the stack.

FIO_PTR_TAG

Supports embedded pointer tagging / untagging for the included types.

Should resolve to a tagged pointer value. i.e.: ((uintptr_t)(p) | 1)

FIO_PTR_UNTAG

Supports embedded pointer tagging / untagging for the included types.

Should resolve to an untagged pointer value. i.e.: ((uintptr_t)(p) | ~1UL)

Note: FIO_PTR_UNTAG might be called more then once or on untagged pointers. For this reason, FIO_PTR_UNTAG should always return the valid pointer, even if called on an untagged pointer.

FIO_PTR_TAG_TYPE

If the FIO_PTR_TAG_TYPE is defined, then functions returning a type's pointer will return a pointer of the specified type instead.


Logging and Assertions:

If the FIO_LOG_LENGTH_LIMIT macro is defined (it's recommended that it be greater than 128), than the FIO_LOG2STDERR (weak) function and the FIO_LOG2STDERR2 macro will be defined.

FIO_LOG_LEVEL

An application wide integer with a value of either:

  • FIO_LOG_LEVEL_NONE (0)
  • FIO_LOG_LEVEL_FATAL (1)
  • FIO_LOG_LEVEL_ERROR (2)
  • FIO_LOG_LEVEL_WARNING (3)
  • FIO_LOG_LEVEL_INFO (4)
  • FIO_LOG_LEVEL_DEBUG (5)

The initial value can be set using the FIO_LOG_LEVEL_DEFAULT macro. By default, the level is 4 (FIO_LOG_LEVEL_INFO) for normal compilation and 5 (FIO_LOG_LEVEL_DEBUG) for DEBUG compilation.

FIO_LOG2STDERR(msg, ...)

This printf style function will log a message to stderr, without allocating any memory on the heap for the string (fprintf might).

The function is defined as weak, allowing it to be overridden during the linking stage, so logging could be diverted... although, it's recommended to divert stderr rather then the logging function.

FIO_LOG2STDERR2(msg, ...)

This macro routs to the FIO_LOG2STDERR function after prefixing the message with the file name and line number in which the error occurred.

FIO_LOG_DEBUG(msg, ...)

Logs msg if log level is equal or above requested log level.

FIO_LOG_INFO(msg, ...)

Logs msg if log level is equal or above requested log level.

FIO_LOG_WARNING(msg, ...)

Logs msg if log level is equal or above requested log level.

FIO_LOG_ERROR(msg, ...)

Logs msg if log level is equal or above requested log level.

FIO_LOG_FATAL(msg, ...)

Logs msg if log level is equal or above requested log level.

FIO_ASSERT(cond, msg, ...)

Reports an error unless condition is met, printing out msg using FIO_LOG_FATAL and exiting (not aborting) the application.

In addition, a SIGINT will be sent to the process and any of it's children before exiting the application, supporting debuggers everywhere :-)

FIO_ASSERT_ALLOC(cond, msg, ...)

Reports an error unless condition is met, printing out msg using FIO_LOG_FATAL and exiting (not aborting) the application.

In addition, a SIGINT will be sent to the process and any of it's children before exiting the application, supporting debuggers everywhere :-)

FIO_ASSERT_DEBUG(cond, msg, ...)

Ignored unless DEBUG is defined.

Reports an error unless condition is met, printing out msg using FIO_LOG_FATAL and aborting (not exiting) the application.

In addition, a SIGINT will be sent to the process and any of it's children before aborting the application, because consistency is important.

Note: msg MUST be a string literal.


Atomic operations:

If the FIO_ATOMIC macro is defined than the following macros will be defined:

fio_atomic_load(p_obj)

Atomically loads and returns the value stored in the object pointed to by p_obj.

fio_atomic_xchange(p_obj, value)

Atomically sets the object pointer to by p_obj to value, returning the previous value.

fio_atomic_add(p_obj, value)

A MACRO / function that performs add atomically.

Returns the new value.

fio_atomic_sub(p_obj, value)

A MACRO / function that performs sub atomically.

Returns the new value.

fio_atomic_and(p_obj, value)

A MACRO / function that performs and atomically.

Returns the new value.

fio_atomic_xor(p_obj, value)

A MACRO / function that performs xor atomically.

Returns the new value.

fio_atomic_or(p_obj, value)

A MACRO / function that performs or atomically.

Returns the new value.

fio_atomic_nand(p_obj, value)

A MACRO / function that performs nand atomically.

Returns the new value.

fio_lock_i

A spinlock type based on a volatile unsigned char.

fio_lock(fio_lock_i *)

Busy waits for a lock to become available.

fio_trylock(fio_lock_i *)

Returns 0 on success and 1 on failure.

fio_unlock(fio_lock_i *)

Unlocks the lock, no matter which thread owns the lock.


Bit-Byte operations:

If the FIO_BITWISE macro is defined than the following macros will be defined:

Byte Swapping

Returns a number of the indicated type with it's byte representation swapped.

  • fio_bswap16(i)
  • fio_bswap32(i)
  • fio_bswap64(i)

Bit rotation (left / right)

Returns a number with it's bits left rotated (lrot) or right rotated (rrot) according to the type width specified (i.e., fio_rrot64 indicates a right rotation for uint64_t).

  • fio_lrot32(i, bits)

  • fio_rrot32(i, bits)

  • fio_lrot64(i, bits)

  • fio_rrot64(i, bits)

  • FIO_LROT(i, bits) (MACRO, can be used with any type size)

  • FIO_RROT(i, bits) (MACRO, can be used with any type size)

Numbers to Numbers (network ordered)

On big-endian systems, these macros a NOOPs, whereas on little-endian systems these macros flip the byte order.

  • fio_lton16(i)
  • fio_ntol16(i)
  • fio_lton32(i)
  • fio_ntol32(i)
  • fio_lton64(i)
  • fio_ntol64(i)

Bytes to Numbers (native / reversed / network ordered)

Reads a number from an unaligned memory buffer. The number or bits read from the buffer is indicated by the name of the function.

Big Endian (default):

  • fio_buf2u16(buffer)
  • fio_buf2u32(buffer)
  • fio_buf2u64(buffer)

Little Endian:

  • fio_buf2u16_little(buffer)
  • fio_buf2u32_little(buffer)
  • fio_buf2u64_little(buffer)

Native Byte Order:

  • fio_buf2u16_local(buffer)
  • fio_buf2u32_local(buffer)
  • fio_buf2u64_local(buffer)

Reversed Byte Order:

  • fio_buf2u16_bswap(buffer)
  • fio_buf2u32_bswap(buffer)
  • fio_buf2u64_bswap(buffer)

Numbers to Bytes (native / reversed / network ordered)

Writes a number to an unaligned memory buffer. The number or bits written to the buffer is indicated by the name of the function.

Big Endian (default):

  • fio_u2buf16(buffer, i)
  • fio_u2buf32(buffer, i)
  • fio_u2buf64(buffer, i)

Little Endian:

  • fio_u2buf16_little(buffer, i)
  • fio_u2buf32_little(buffer, i)
  • fio_u2buf64_little(buffer, i)

Native Byte Order:

  • fio_u2buf16_local(buffer, i)
  • fio_u2buf32_local(buffer, i)
  • fio_u2buf64_local(buffer, i)

Reversed Byte Order:

  • fio_u2buf16_bswap(buffer, i)
  • fio_u2buf32_bswap(buffer, i)
  • fio_u2buf64_bswap(buffer, i)

Constant Time Bit Operations

Performs the operation indicated in constant time.

  • fio_ct_true(condition)

    Tests if condition is non-zero (returns 1 / 0).

  • fio_ct_false(condition)

    Tests if condition is zero (returns 1 / 0).

  • fio_ct_if(bool, a_if_true, b_if_false)

    Tests if bool == 1 (returns a / b).

  • fio_ct_if2(condition, a_if_true, b_if_false)

    Tests if condition is non-zero (returns a / b).


Bitmap helpers

If the FIO_BITMAP macro is defined than the following macros will be defined.

In addition, the FIO_ATOMIC will be assumed to be defined, as setting bits in the bitmap is implemented using atomic operations.

Bitmap helpers

  • fio_bitmap_get(void *map, size_t bit)
  • fio_bitmap_set(void *map, size_t bit) (an atomic operation, thread-safe)
  • fio_bitmap_unset(void *map, size_t bit) (an atomic operation, thread-safe)

Risky Hash (data hashing):

If the FIO_RISKY_HASH macro is defined than the following static function will be defined:

fio_risky_hash

uint64_t fio_risky_hash(const void *data, size_t len, uint64_t seed)

This is a non-streaming implementation of the RiskyHash v.3 algorithm.

This function will produce a 64 bit hash for X bytes of data.

fio_risky_mask

void fio_risky_mask(char *buf, size_t len, uint64_t key, uint64_t nonce);

Masks data using a Risky Hash and a counter mode nonce.

Used for mitigating memory access attacks when storing "secret" information in memory.

Keep the nonce information in a different memory address then the secret. For example, if the secret is on the stack, store the nonce on the heap or using a static variable.

Don't use the same nonce-secret combination for other data.

This is not a cryptographically secure encryption. Even if the algorithm was secure, it would provide no more then a 32 bit level encryption, which isn't strong enough for any cryptographic use-case.

However, this could be used to mitigate memory probing attacks. Secrets stored in the memory might remain accessible after the program exists or through core dump information. By storing "secret" information masked in this way, it mitigates the risk of secret information being recognized or deciphered.


Pseudo Random Generation

If the FIO_RAND macro is defined, the following, non-cryptographic psedo-random generator functions will be defined.

The "random" data is initialized / seeded automatically using a small number of functional cycles that collect data and hash it, hopefully resulting in enough jitter entropy.

The data is collected using getrusage (or the system clock if getrusage is unavailable) and hashed using RiskyHash. The data is then combined with the previous state / cycle.

The CPU "jitter" within the calculation should effect getrusage in a way that makes it impossible for an attacker to determine the resulting random state (assuming jitter exists).

However, this is unlikely to prove cryptographically safe and isn't likely to produce a large number of entropy bits (even though a small number of bits have a large impact on the final state).

The facil.io random generator functions appear both faster and more random then the standard rand on my computer (you can test it for yours).

I designed it in the hopes of achieving a cryptographically safe PRNG, but it wasn't cryptographically analyzed, lacks a good source of entropy and should be considered as a non-cryptographic PRNG.

Note: bitwise operations (FIO_BITWISE) and Risky Hash (FIO_RISKY_HASH) are automatically defined along with FIO_RAND, since they are required by the algorithm.

fio_rand64

uint64_t fio_rand64(void)

Returns 64 random bits. Probably not cryptographically safe.

fio_rand_bytes

void fio_rand_bytes(void *data_, size_t len)

Writes len random Bytes to the buffer pointed to by data. Probably not cryptographically safe.

fio_rand_feed2seed

static void fio_rand_feed2seed(void *buf_, size_t len);

An internal function (accessible from the translation unit) that allows a program to feed random data to the PRNG (fio_rand64).

The random data will effect the random seed on the next reseeding.

Limited to 1023 bytes of data per function call.

fio_rand_reseed

void fio_rand_reseed(void);

Forces the random generator state to rotate.

SHOULD be called after fork to prevent the two processes from outputting the same random numbers (until a reseed is called automatically).


String / Number conversion

If the FIO_ATOL macro is defined, the following functions will be defined:

fio_atol

int64_t fio_atol(char **pstr);

A helper function that converts between String data to a signed int64_t.

Numbers are assumed to be in base 10. Octal (0###), Hex (0x##/x##) and binary (0b##/ b##) are recognized as well. For binary Most Significant Bit must come first.

The most significant difference between this function and strtol (aside of API design), is the added support for binary representations.

fio_atof

double fio_atof(char **pstr);

A helper function that converts between String data to a signed double.

fio_ltoa

size_t fio_ltoa(char *dest, int64_t num, uint8_t base);

A helper function that writes a signed int64_t to a string.

No overflow guard is provided, make sure there's at least 68 bytes available (for base 2).

Offers special support for base 2 (binary), base 8 (octal), base 10 and base 16 (hex). An unsupported base will silently default to base 10. Prefixes are automatically added (i.e., "0x" for hex and "0b" for base 2).

Returns the number of bytes actually written (excluding the NUL terminator).

fio_ftoa

size_t fio_ftoa(char *dest, double num, uint8_t base);

A helper function that converts between a double to a string.

No overflow guard is provided, make sure there's at least 130 bytes available (for base 2).

Supports base 2, base 10 and base 16. An unsupported base will silently default to base 10. Prefixes aren't added (i.e., no "0x" or "0b" at the beginning of the string).

Returns the number of bytes actually written (excluding the NUL terminator).


Basic Socket / IO Helpers

The facil.io standard library provides a few simple IO / Sockets helpers for POSIX systems.

By defining FIO_SOCK on a POSIX system, the following functions will be defined.

fio_sock_open

int fio_sock_open(const char *restrict address,
                 const char *restrict port,
                 uint16_t flags);

Creates a new socket according to the provided flags.

The port string will be ignored when FIO_SOCK_UNIX is set.

The address can be NULL for Server sockets (FIO_SOCK_SERVER) when binding to all available interfaces (this is actually recommended unless network filtering is desired).

The flag integer can be a combination of any of the following flags:

  • FIO_SOCK_TCP - Creates a TCP/IP socket.

  • FIO_SOCK_UDP - Creates a UDP socket.

  • FIO_SOCK_UNIX - Creates a Unix socket. If an existing file / Unix socket exists, they will be deleted and replaced.

  • FIO_SOCK_SERVER - Initializes a Server socket. For TCP/IP and Unix sockets, the new socket will be listening for incoming connections (listen will be automatically called).

  • FIO_SOCK_CLIENT - Initializes a Client socket, calling connect using the address and port arguments.

  • FIO_SOCK_NONBLOCK - Sets the new socket to non-blocking mode.

If neither FIO_SOCK_SERVER nor FIO_SOCK_CLIENT are specified, the function will default to a server socket.

Note:

UDP Server Sockets might need to handle traffic from multiple clients, which could require a significantly larger OS buffer then the default buffer offered.

Consider (from this SO answer, see this blog post, this article and this article):

int n = 32*1024*1024; /* try for 32Mb */
while (n >= (4*1024*1024) && setsockopt(socket, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) == -1) {
  /* failed - repeat attempt at 1Mb interval */
  if (n >= (4 * 1024 * 1024)) // OS may have returned max value
    n -= 1024 * 1024;

}

fio_sock_poll

typedef struct {
  void (*on_ready)(int fd, void *udata);
  void (*on_data)(int fd, void *udata);
  void (*on_error)(int fd, void *udata);
  void *udata;
  int timeout;
  struct pollfd *fds;
} fio_sock_poll_args;

int fio_sock_poll(fio_sock_poll_args args);
#define fio_sock_poll(...) fio_sock_poll((fio_sock_poll_args){__VA_ARGS__})

The fio_sock_poll function is shadowed by the fio_sock_poll MACRO, which allows the function to accept the following "named arguments":

  • on_ready:

    This callback will be called if a socket can be written to and the socket is polled for the Write event.

      // callback example:
      void on_ready(int fd, void *udata);
    
  • on_data:

    This callback will be called if data is available to be read from a socket and the socket is polled for the Read event.

      // callback example:
      void on_data(int fd, void *udata);
    
  • on_error:

    This callback will be called if an error occurred when polling the file descriptor.

      // callback example:
      void on_error(int fd, void *udata);
    
  • timeout:

    Polling timeout in milliseconds.

      // type:
      int timeout;
    
  • udata:

    Opaque user data.

      // type:
      void *udata;
    
  • fds:

    A list of struct pollfd with file descriptors to be polled. The list MUST end with a struct pollfd containing an empty events field (and no empty events field should appear in the middle of the list).

    Use the FIO_SOCK_POLL_LIST(...), FIO_SOCK_POLL_RW(fd), FIO_SOCK_POLL_R(fd) and FIO_SOCK_POLL_W(fd) macros to build the list.

The fio_sock_poll function uses the poll system call to poll a simple IO list.

The list must end with a struct pollfd with it's events set to zero. No other member of the list should have their events data set to zero.

It is recommended to use the FIO_SOCK_POLL_LIST(...) and FIO_SOCK_POLL_[RW](fd) macros. i.e.:

int io_fd = fio_sock_open(NULL, "8888", FIO_SOCK_UDP | FIO_SOCK_NONBLOCK | FIO_SOCK_SERVER);
int count = fio_sock_poll(.on_ready = on_ready,
                    .on_data = on_data,
                    .fds = FIO_SOCK_POLL_LIST(FIO_SOCK_POLL_RW(io_fd)));

Note: The poll system call should perform reasonably well for light loads (short lists). However, for complex IO needs or heavier loads, use the system's native IO API, such as kqueue or epoll.

fio_sock_address_new

struct addrinfo *fio_sock_address_new(const char *restrict address,
                                      const char *restrict port,
                                      int sock_type);

Attempts to resolve an address to a valid IP6 / IP4 address pointer.

The sock_type element should be a socket type, such as SOCK_DGRAM (UDP) or SOCK_STREAM (TCP/IP).

The address should be freed using fio_sock_address_free.

fio_sock_address_free

void fio_sock_address_free(struct addrinfo *a);

Frees the pointer returned by fio_sock_address_new.

fio_sock_set_non_block

int fio_sock_set_non_block(int fd);

Sets a file descriptor / socket to non blocking state.

fio_sock_open_local

int fio_sock_open_local(struct addrinfo *addr);

Creates a new network socket and binds it to a local address.

fio_sock_open_remote

int fio_sock_open_remote(struct addrinfo *addr, int nonblock);

Creates a new network socket and connects it to a remote address.

fio_sock_open_unix

int fio_sock_open_unix(const char *address, int is_client, int nonblock);

Creates a new Unix socket and binds it to a local address.


URL (URI) parsing

URIs (Universal Resource Identifier), commonly referred to as URL (Uniform Resource Locator), are a common way to describe network and file addresses.

A common use case for URIs is within the command line interface (CLI), allowing a client to point at a resource that may be local (i.e., file:///users/etc/my.conf) or remote (i.e. http://example.com/conf).

By defining FIO_URL, the following types and functions will be defined:

fio_url_s

/** the result returned by `fio_url_parse` */
typedef struct {
  fio_str_info_s scheme;
  fio_str_info_s user;
  fio_str_info_s password;
  fio_str_info_s host;
  fio_str_info_s port;
  fio_str_info_s path;
  fio_str_info_s query;
  fio_str_info_s target;
} fio_url_s;

The fio_url_s contains a information about a URL (or, URI).

When the information is returned from fio_url_parse, the strings in the fio_url_s (i.e., url.scheme.buf) are not NUL terminated, since the parser is non-destructive, with zero-copy and zero-allocation.

fio_url_parse

fio_url_s fio_url_parse(const char *url, size_t len);

Parses the URI returning it's components and their lengths (no decoding performed, doesn't accept decoded URIs).

The returned string are not NUL terminated, they are merely locations within the original (unmodified) string.

This function attempts to accept many different formats, including any of the following:

  • /complete_path?query#target

    i.e.: /index.html?page=1#list

  • host:port/complete_path?query#target

    i.e.:

    • example.com
    • example.com:8080
    • example.com/index.html
    • example.com:8080/index.html
    • example.com:8080/index.html?key=val#target
  • user:password@host:port/path?query#target

    i.e.: user:1234@example.com:8080/index.html

  • username[:password]@host[:port][...]

    i.e.: john:1234@example.com

  • schema://user:password@host:port/path?query#target

    i.e.: http://example.com/index.html?page=1#list

Invalid formats might produce unexpected results. No error testing performed.

The file and unix schemas are special in the sense that they produce no host (only path).


CLI (command line interface)

The Simple Template Library includes a CLI parser, since parsing command line arguments is a common task.

By defining FIO_CLI, the following functions will be defined.

In addition, FIO_CLI automatically includes the FIO_ATOL flag, since CLI parsing depends on the fio_atol function.

fio_cli_start

#define fio_cli_start(argc, argv, unnamed_min, unnamed_max, description, ...)  \
  fio_cli_start((argc), (argv), (unnamed_min), (unnamed_max), (description),   \
                (char const *[]){__VA_ARGS__, (char const *)NULL})

/* the shadowed function: */
void fio_cli_start   (int argc, char const *argv[],
                      int unnamed_min, int unnamed_max,
                      char const *description,
                      char const **names);

The fio_cli_start macro shadows the fio_cli_start function and defines the CLI interface to be parsed. i.e.,

The fio_cli_start macro accepts the argc and argv, as received by the main functions, a maximum and minimum number of unspecified CLI arguments (beneath which or after which the parser will fail), an application description string and a variable list of (specified) command line arguments.

If the minimum number of unspecified CLI arguments is -1, there will be no maximum limit on the number of unnamed / unrecognized arguments allowed

The text NAME in the description (all capitals) will be replaced with the executable command invoking the application.

Command line arguments can be either String, Integer or Boolean. Optionally, extra data could be added to the CLI help output. CLI arguments and information is added using any of the following macros:

  • FIO_CLI_STRING("-arg [-alias] desc.")

  • FIO_CLI_INT("-arg [-alias] desc.")

  • FIO_CLI_BOOL("-arg [-alias] desc.")

  • FIO_CLI_PRINT_HEADER("header text (printed as a header)")

  • FIO_CLI_PRINT("raw text line (printed as is)")

int main(int argc, char const *argv[]) {
  fio_cli_start(argc, argv, 0, -1,
                "this is a CLI example for the NAME application.",
                FIO_CLI_PRINT_HEADER("CLI type validation"),
                FIO_CLI_STRING("-str -s any data goes here"),
                FIO_CLI_INT("-int -i numeral data goes here"),
                FIO_CLI_BOOL("-bool -b flag (boolean) only - no data"),
                FIO_CLI_PRINT("This test allows for unlimited arguments "
                              "that will simply pass-through"));
  if (fio_cli_get("-s"))
    fprintf(stderr, "String: %s\n", fio_cli_get("-s"));

  if (fio_cli_get("-i"))
    fprintf(stderr, "Integer: %d\n", fio_cli_get_i("-i"));

  fprintf(stderr, "Boolean: %d\n", fio_cli_get_i("-b"));

  if (fio_cli_unnamed_count()) {
    fprintf(stderr, "Printing unlisted / unrecognized arguments:\n");
    for (size_t i = 0; i < fio_cli_unnamed_count(); ++i) {
      fprintf(stderr, "%s\n", fio_cli_unnamed(i));
    }
  }

  fio_cli_end();
  return 0;
}

fio_cli_end

void fio_cli_end(void);

Clears the CLI data storage.

fio_cli_get

char const *fio_cli_get(char const *name);

Returns the argument's value as a string, or NULL if the argument wasn't provided.

fio_cli_get_i

int fio_cli_get_i(char const *name);

Returns the argument's value as an integer, or 0 if the argument wasn't provided.

fio_cli_get_bool

#define fio_cli_get_bool(name) (fio_cli_get((name)) != NULL)

Evaluates to true (1) if the argument was boolean and provided. Otherwise evaluated to false (0).

fio_cli_unnamed_count

unsigned int fio_cli_unnamed_count(void);

Returns the number of unrecognized arguments (arguments unspecified, in fio_cli_start).

fio_cli_unnamed

char const *fio_cli_unnamed(unsigned int index);

Returns a String containing the unrecognized argument at the stated index (indexes are zero based).

fio_cli_set

void fio_cli_set(char const *name, char const *value);

Sets a value for the named argument (but not it's aliases).

fio_cli_set_default(name, value)

#define fio_cli_set_default(name, value)                                       \
  if (!fio_cli_get((name)))                                                    \
    fio_cli_set(name, value);

Sets a value for the named argument (but not it's aliases) only if the argument wasn't set by the user.


Time Helpers

By defining FIO_TIME or FIO_QUEUE, the following time related helpers functions are defined:

fio_time_real

struct timespec fio_time_real();

Returns human (watch) time... this value isn't as safe for measurements.

timespec

struct timespec fio_time_mono();

Returns monotonic time.

fio_time_nano

uint64_t fio_time_nano();

Returns monotonic time in nano-seconds (now in 1 micro of a second).

fio_time_micro

uint64_t fio_time_micro();

Returns monotonic time in micro-seconds (now in 1 millionth of a second).

fio_time_milli

uint64_t fio_time_milli();

Returns monotonic time in milliseconds.

fio_time2gm

struct tm fio_time2gm(time_t timer);

A faster (yet less localized) alternative to gmtime_r.

See the libc gmtime_r documentation for details.

Returns a struct tm object filled with the date information.

This function is used internally for the formatting functions: , fio_time2rfc7231, fio_time2rfc2109, and fio_time2rfc2822.

fio_gm2time

time_t fio_gm2time(struct tm tm)

Converts a struct tm to time in seconds (assuming UTC).

This function is less localized then the mktime / timegm library functions.

fio_time2rfc7231

size_t fio_time2rfc7231(char *target, time_t time);

Writes an RFC 7231 date representation (HTTP date format) to target.

The format is similar to DDD, dd, MON, YYYY, HH:MM:SS GMT

i.e.: Sun, 06 Nov 1994 08:49:37 GMT

fio_time2rfc2109

size_t fio_time2rfc2109(char *target, time_t time);

Writes an RFC 2109 date representation to target.

fio_time2rfc2822

size_t fio_time2rfc2822(char *target, time_t time);

Writes an RFC 2822 date representation to target.


Task Queue

The Simple Template Library includes a simple, thread-safe, task queue based on a linked list of ring buffers.

Since delayed processing is a common task, this queue is provides an easy way to schedule and perform delayed tasks.

In addition, a Timer type allows timed events to be scheduled and moved (according to their "due date") to an existing Task Queue.

Queue Related Types

fio_queue_task_s

/** Task information */
typedef struct {
  /** The function to call */
  void (*fn)(void *, void *);
  /** User opaque data */
  void *udata1;
  /** User opaque data */
  void *udata2;
} fio_queue_task_s;

The fio_queue_task_s type contains information about a delayed task. The information is important for the fio_queue_push MACRO, where it is used as named arguments for the task information.

fio_queue_s

/** The queue object - should be considered opaque (or, at least, read only). */
typedef struct {
  fio___task_ring_s *r;
  fio___task_ring_s *w;
  /** the number of tasks waiting to be performed (read-only). */
  size_t count;
  fio_lock_i lock;
  fio___task_ring_s mem;
} fio_queue_s;

The fio_queue_s object is the queue object.

This object could be placed on the stack or allocated on the heap (using fio_queue_new).

Once the object is no longer in use call fio_queue_destroy (if placed on the stack) of fio_queue_free (if allocated using fio_queue_new).

Queue API

FIO_QUEUE_INIT(queue)

/** Used to initialize a fio_queue_s object. */
#define FIO_QUEUE_INIT(name)                                                   \
  { .r = &(name).mem, .w = &(name).mem, .lock = FIO_LOCK_INIT }

fio_queue_destroy

void fio_queue_destroy(fio_queue_s *q);

Destroys a queue and reinitializes it, after freeing any used resources.

fio_queue_new

fio_queue_s *fio_queue_new(void);

Creates a new queue object (allocated on the heap).

fio_queue_free

void fio_queue_free(fio_queue_s *q);

Frees a queue object after calling fio_queue_destroy.

fio_queue_push

int fio_queue_push(fio_queue_s *q, fio_queue_task_s task);
#define fio_queue_push(q, ...)                                                 \
  fio_queue_push((q), (fio_queue_task_s){__VA_ARGS__})

Pushes a valid (non-NULL) task to the queue.

This function is shadowed by the fio_queue_push MACRO, allowing named arguments to be used.

For example:

void tsk(void *, void *);
fio_queue_s q = FIO_QUEUE_INIT(q);
fio_queue_push(q, .fn = tsk);
// ...
fio_queue_destroy(q);

Returns 0 if task.fn == NULL or if the task was successfully added to the queue.

Returns -1 on error (no memory).

fio_queue_push_urgent

int fio_queue_push_urgent(fio_queue_s *q, fio_queue_task_s task);
#define fio_queue_push_urgent(q, ...)                                          \
  fio_queue_push_urgent((q), (fio_queue_task_s){__VA_ARGS__})

Pushes a task to the head of the queue (LIFO).

Returns -1 on error (no memory).

See fio_queue_push for details.

fio_queue_pop

fio_queue_task_s fio_queue_pop(fio_queue_s *q);

Pops a task from the queue (FIFO).

Returns a NULL task on error (task.fn == NULL).

Note: The task isn't performed automatically, it's just returned. This is useful for queues that don't necessarily contain callable functions.

fio_queue_perform

int fio_queue_perform(fio_queue_s *q);

Pops and performs a task from the queue (FIFO).

Returns -1 on error (queue empty).

fio_queue_perform_all

void fio_queue_perform_all(fio_queue_s *q);

Performs all tasks in the queue.

fio_queue_count

size_t fio_queue_count(fio_queue_s *q);

Returns the number of tasks in the queue.

Timer Related Types

fio_timer_queue_s

typedef struct {
  fio___timer_event_s *next;
  fio_lock_i lock;
} fio_timer_queue_s;

The fio_timer_queue_s struct should be considered an opaque data type and accessed only using the functions or the initialization MACRO.

To create a fio_timer_queue_s on the stack (or statically):

fio_timer_queue_s foo_timer = FIO_TIMER_QUEUE_INIT;

A timer could be allocated dynamically:

fio_timer_queue_s *foo_timer = malloc(sizeof(*foo_timer));
FIO_ASSERT_ALLOC(foo_timer);
*foo_timer = (fio_timer_queue_s)FIO_TIMER_QUEUE_INIT;

FIO_TIMER_QUEUE_INIT

This is a MACRO used to initialize a fio_timer_queue_s object.

Timer API

fio_timer_schedule

void fio_timer_schedule(fio_timer_queue_s *timer_queue,
                        fio_timer_schedule_args_s args);

Adds a time-bound event to the timer queue.

Accepts named arguments using the following argument type and MACRO:

typedef struct {
  /** The timer function. If it returns a non-zero value, the timer stops. */
  int (*fn)(void *, void *);
  /** Opaque user data. */
  void *udata1;
  /** Opaque user data. */
  void *udata2;
  /** Called when the timer is done (finished). */
  void (*on_finish)(void *, void *);
  /** Timer interval, in milliseconds. */
  uint32_t every;
  /** The number of times the timer should repeat itself. 0 == infinity. */
  int32_t repeat;
  /** Millisecond at which to start. If missing, filled automatically. */
  uint64_t start_at;
} fio_timer_schedule_args_s;

#define fio_timer_schedule(timer_queue, ...)                                   \
  fio_timer_schedule((timer_queue), (fio_timer_schedule_args_s){__VA_ARGS__})

Note, the event will repeat every every milliseconds.

It the scheduler is busy or the event is otherwise delayed, its next scheduling may compensate for the delay by being scheduled sooner.

fio_timer_push2queue

/**  */
size_t fio_timer_push2queue(fio_queue_s *queue,
                            fio_timer_queue_s *timer_queue,
                            uint64_t now_in_milliseconds);

Pushes due events from the timer queue to an event queue.

fio_timer_next_at

uint64_t fio_timer_next_at(fio_timer_queue_s *timer_queue);

Returns the millisecond at which the next event should occur.

If no timer is due (list is empty), returns (uint64_t)-1.

Note: Unless manually specified, millisecond timers are relative to fio_time_milli().

fio_timer_clear

void fio_timer_clear(fio_timer_queue_s *timer_queue);

Clears any waiting timer bound tasks.

Note:

The timer queue must NEVER be freed when there's a chance that timer tasks are waiting to be performed in a fio_queue_s.

This is due to the fact that the tasks may try to reschedule themselves (if they repeat).


Memory Allocation

The facil.io Simple Template Library includes a fast, concurrent, memory allocator designed for shot-medium object life-spans.

It's ideal if all long-term allocations are performed during the start-up phase or using a different memory allocator.

The allocator is very fast and protects against fragmentation issues when used correctly (when abused, fragmentation may increase).

Allocated memory is always zeroed out and aligned on a 16 byte boundary.

Reallocated memory is always aligned on a 16 byte boundary but it might be filled with junk data after the valid data. This can be minimized to (up to) 16 bytes of junk data by using fio_realloc2.

Memory allocation overhead is ~ 0.05% (1/2048 bytes per byte, or 16 bytes per 32Kb). In addition there's a small per-process overhead for the allocator's state-machine (usually just 1 page / 4Kb per process, unless you have more then 250 CPU cores).

The memory allocator assumes multiple concurrent allocation/deallocation, short to medium life spans (memory is freed shortly, but not immediately, after it was allocated) and relatively small allocations - anything over FIO_MEMORY_BLOCK_ALLOC_LIMIT (16Kb) is forwarded to mmap.

The memory allocator can be used in conjuncture with the system's malloc to minimize heap fragmentation (long-life objects use malloc, short life objects use fio_malloc) or as a memory pool for specific objects (when used as static functions in a specific C file).

Long term allocation can use fio_mmap to directly allocate memory from the system. The overhead for fio_mmap is 16 bytes per allocation (freed with fio_free).

Note: this custom allocator could increase memory fragmentation if long-life allocations are performed periodically (rather than performed during startup). Use fio_mmap or the system's malloc for long-term allocations.

Memory Allocator Overview

The memory allocator uses mmap to collect memory from the system.

Each allocation collects ~8Mb from the system, aligned on a 32Kb alignment boundary (except direct mmap allocation for large fio_malloc or fio_mmap calls). This memory is divided into 32Kb blocks which are added to a doubly linked "free" list.

The allocator utilizes per-CPU arenas / bins to allow for concurrent memory allocations across threads and to minimize lock contention.

Each arena / bin collects a 32Kb block and allocates "slices" as required by fio_malloc/fio_realloc.

The fio_free function will free the whole 32Kb block as a single unit once the whole of the allocations for that block were freed (no small-allocation "free list" and no per-slice meta-data).

The memory collected from the system (the 8Mb) will be returned to the system once all the memory was both allocated and freed (or during cleanup).

To replace the system's malloc function family compile with the FIO_OVERRIDE_MALLOC defined (-DFIO_OVERRIDE_MALLOC).

It should be possible to use tcmalloc or jemalloc alongside facil.io's allocator. It's also possible to prevent facil.io's custom allocator from compiling by defining FIO_MALLOC_FORCE_SYSTEM (-DFIO_MALLOC_FORCE_SYSTEM).

The Memory Allocator's API

The functions were designed to be a drop in replacement to the system's memory allocation functions (malloc, free and friends).

Where some improvement could be made, it was made using an added function name to add improved functionality (such as fio_realloc2).

By defining FIO_MALLOC, the following functions will be defined:

fio_malloc

void * fio_malloc(size_t size);

Allocates memory using a per-CPU core block memory pool. Memory is zeroed out.

Allocations above FIO_MEMORY_BLOCK_ALLOC_LIMIT (16Kb when using 32Kb blocks) will be redirected to mmap, as if fio_mmap was called.

fio_calloc

void * fio_calloc(size_t size_per_unit, size_t unit_count);

Same as calling fio_malloc(size_per_unit * unit_count);

Allocations above FIO_MEMORY_BLOCK_ALLOC_LIMIT (16Kb when using 32Kb blocks) will be redirected to mmap, as if fio_mmap was called.

fio_free

void fio_free(void *ptr);

Frees memory that was allocated using this library.

fio_realloc

void * fio_realloc(void *ptr, size_t new_size);

Re-allocates memory. An attempt to avoid copying the data is made only for big memory allocations (larger than FIO_MEMORY_BLOCK_ALLOC_LIMIT).

fio_realloc2

void * fio_realloc2(void *ptr, size_t new_size, size_t copy_length);

Re-allocates memory. An attempt to avoid copying the data is made only for big memory allocations (larger than FIO_MEMORY_BLOCK_ALLOC_LIMIT).

This variation is slightly faster as it might copy less data.

fio_mmap

void * fio_mmap(size_t size);

Allocates memory directly using mmap, this is preferred for objects that both require almost a page of memory (or more) and expect a long lifetime.

However, since this allocation will invoke the system call (mmap), it will be inherently slower.

fio_free can be used for deallocating the memory.

fio_malloc_after_fork

void fio_malloc_after_fork(void);

Never fork a multi-threaded process. Doing so might corrupt the memory allocation system. The risk is more relevant for child processes.

However, if a multi-threaded process, calling this function from the child process would perform a best attempt at mitigating any arising issues (at the expense of possible leaks).

FIO_MALLOC_FORCE_SYSTEM

If FIO_MALLOC_FORCE_SYSTEM is defined, the facil.io memory allocator functions will simply pass requests through to the system's memory allocator (calloc / free) rather then use the facil.io custom allocator.

FIO_MALLOC_OVERRIDE_SYSTEM

If FIO_MALLOC_OVERRIDE_SYSTEM is defined, the facil.io memory allocator will replace the system's memory allocator.

FIO_MEMORY_ARENA_COUNT_MAX

Sets the maximum number of memory arenas to initialize. Defaults to 64.

When set to 0 the number of arenas will always match the maximum number of detected CPU cores.

FIO_MEMORY_ARENA_COUNT_DEFAULT

The default number of memory arenas to initialize when CPU core detection fails or isn't available. Defaults to 5.

Normally, facil.io tries to initialize as many memory allocation arenas as the number of CPU cores. This value will only be used if core detection isn't available or fails.


Custom JSON Parser

The facil.io JSON parser is a non-strict parser, with support for trailing commas in collections, new-lines in strings, extended escape characters, comments, and octal, hex and binary numbers.

The parser allows for streaming data and decouples the parsing process from the resulting data-structure by calling static callbacks for JSON related events.

To use the JSON parser, define FIO_JSON before including the fio-slt.h file and later define the static callbacks required by the parser (see list of callbacks).

Note: the JSON parser and the FIOBJ soft types can't be implemented in the same translation unit, since the FIOBJ soft types already define JSON callbacks and use the JSON parser to provide JSON support.

JSON_MAX_DEPTH

#ifndef JSON_MAX_DEPTH
/** Maximum allowed JSON nesting level. Values above 64K might fail. */
#define JSON_MAX_DEPTH 512
#endif

The JSON parser isn't recursive, but it allocates a nesting bitmap on the stack, which consumes stack memory.

To ensure the stack isn't abused, the parser will limit JSON nesting levels to a customizable JSON_MAX_DEPTH number of nesting levels.

fio_json_parser_s

typedef struct {
  /** level of nesting. */
  uint32_t depth;
  /** expectation bit flag: 0=key, 1=colon, 2=value, 4=comma/closure . */
  uint8_t expect;
  /** nesting bit flags - dictionary bit = 0, array bit = 1. */
  uint8_t nesting[(JSON_MAX_DEPTH + 7) >> 3];
} fio_json_parser_s;

The JSON parser type. Memory must be initialized to 0 before first uses (see FIO_JSON_INIT).

The type should be considered opaque. To add user data to the parser, use C-style inheritance and pointer arithmetics or simple type casting.

i.e.:

typedef struct {
  fio_json_parser_s private;
  int my_data;
} my_json_parser_s;
// void use_in_callback (fio_json_parser_s * p) {
//    my_json_parser_s *my = (my_json_parser_s *)p;
// }

FIO_JSON_INIT

#define FIO_JSON_INIT                                                          \
  { .depth = 0 }

A convenient macro that could be used to initialize the parser's memory to 0.

JSON parser API

fio_json_parse

size_t fio_json_parse(fio_json_parser_s *parser,
                      const char *buffer,
                      const size_t len);

Returns the number of bytes consumed before parsing stopped (due to either error or end of data). Stops as close as possible to the end of the buffer or once an object parsing was completed.

Zero (0) is a valid number and may indicate that the buffer's memory contains a partial object that can't be fully parsed just yet.

Note!: partial Numeral objects may be result in errors, as the number 1234 may be fragmented as 12 and 34 when streaming data. facil.io doesn't protect against this possible error.

JSON Required Callbacks

The JSON parser requires the following callbacks to be defined as static functions.

fio_json_on_null

static void fio_json_on_null(fio_json_parser_s *p);

A NULL object was detected

fio_json_on_true

static void fio_json_on_true(fio_json_parser_s *p);

A TRUE object was detected

fio_json_on_false

static void fio_json_on_false(fio_json_parser_s *p);

A FALSE object was detected

fio_json_on_number

static void fio_json_on_number(fio_json_parser_s *p, long long i);

A Number was detected (long long).

fio_json_on_float

static void fio_json_on_float(fio_json_parser_s *p, double f);

A Float was detected (double).

fio_json_on_string

static void fio_json_on_string(fio_json_parser_s *p, const void *start, size_t len);

A String was detected (int / float). update pos to point at ending

fio_json_on_start_object

static int fio_json_on_start_object(fio_json_parser_s *p);

A dictionary object was detected, should return 0 unless error occurred.

fio_json_on_end_object

static void fio_json_on_end_object(fio_json_parser_s *p);

A dictionary object closure detected

fio_json_on_start_array

static int fio_json_on_start_array(fio_json_parser_s *p);

An array object was detected, should return 0 unless error occurred.

fio_json_on_end_array

static void fio_json_on_end_array(fio_json_parser_s *p);

An array closure was detected

fio_json_on_json

static void fio_json_on_json(fio_json_parser_s *p);

The JSON parsing is complete (JSON data parsed so far contains a valid JSON object).

fio_json_on_error

static void fio_json_on_error(fio_json_parser_s *p);

The JSON parsing should stop with an error.


FIOBJ Soft Dynamic Types

The facil.io library includes a dynamic type system that makes it a easy to handle mixed-type tasks, such as JSON object construction.

This soft type system included in the facil.io STL, it is based on the Core types mentioned above and it shares their API (Dynamic Strings, Dynamic Arrays, and Hash Maps).

The FIOBJ API offers type generic functions in addition to the type specific API. An objects underlying type is easily identified using FIOBJ_TYPE(obj) or FIOBJ_TYPE_IS(obj, type).

The documentation regarding the FIOBJ soft-type system is divided as follows:

In the facil.io web application framework, there are extensions to the core FIOBJ primitives, including:

FIOBJ General Considerations

  1. To use the FIOBJ soft types, define the FIO_FIOBJ macro and then include the facil.io STL header.

  2. To include declarations as globally available symbols (allowing the functions to be called from multiple C files), define FIOBJ_EXTERN before including the STL header.

    This also requires that a single C file (translation unit) define FIOBJ_EXTERN_COMPLETE before including the header with the FIOBJ_EXTERN directive.

  3. The FIOBJ types use pointer tagging and require that the memory allocator provide allocations on 8 byte memory alignment boundaries (they also assume each byte is 8 bits).

    If the system allocator doesn't provide (at least) 8 byte memory alignment, use the facil.io memory allocator provided (fio_malloc).

  4. The FIOBJ soft type system uses an "ownership" memory model.

    This means that Arrays "own" their members and Hash Maps "own" their values (but not the keys).

    Freeing an Array will free all the objects within the Array. Freeing a Hash Map will free all the values within the Hash Map (but none of the keys).

    Ownership is only transferred if the object is removed from it's container.

    i.e., fiobj_array_get does not transfer ownership (it just allows temporary "access"). Whereas, fiobj_array_remove does revoke ownership - either freeing the object or moving the ownership to the pointer provided to hold the old value.

FIOBJ Types and Identification

FIOBJ objects can contain any number of possible types, including user defined types.

These are the built-in types / classes that the Core FIOBJ system includes (before any extensions):

  • FIOBJ_T_INVALID: indicates an invalid type class / type (a FIOBJ_INVALID value).

  • FIOBJ_T_PRIMITIVE: indicates a Primitive class / type.

  • FIOBJ_T_NUMBER: indicates a Number class / type.

  • FIOBJ_T_FLOAT: indicates a Float class / type.

  • FIOBJ_T_STRING: indicates a String class / type.

  • FIOBJ_T_ARRAY: indicates an Array class / type.

  • FIOBJ_T_HASH: indicates a Hash Map class / type.

  • FIOBJ_T_OTHER: (internal) indicates an Other class / type. This is designed to indicate an extension / user defined type.

The FIOBJ_T_PRIMITIVE class / type resolves to one of the following types:

  • FIOBJ_T_NULL: indicates a fiobj_null() object.

  • FIOBJ_T_TRUE: indicates a fiobj_true() object.

  • FIOBJ_T_FALSE: indicates a fiobj_false() object.

The following functions / MACROs help identify a FIOBJ object's underlying type.

FIOBJ_TYPE(o)

#define FIOBJ_TYPE(o) fiobj_type(o)

FIOBJ_TYPE_IS(o)

#define FIOBJ_TYPE_IS(o, type) (fiobj_type(o) == type)

FIOBJ_TYPE_CLASS(o)

#define FIOBJ_TYPE_CLASS(o) ((fiobj_class_en)(((uintptr_t)o) & 7UL))

Returns the object's type class. This is limited to one of the core types. FIOBJ_T_PRIMITIVE and FIOBJ_T_OTHER may be returned (they aren't expended to their underlying type).

FIOBJ_IS_INVALID(o)

#define FIOBJ_IS_INVALID(o) (((uintptr_t)(o)&7UL) == 0)

Tests if the object is (probably) a valid FIOBJ

FIOBJ_IS_INVALID(o)

#define FIOBJ_IS_INVALID(o) (((uintptr_t)(o)&7UL) == 0)

FIOBJ_PTR_UNTAG(o)

#define FIOBJ_PTR_UNTAG(o) ((uintptr_t)o & (~7ULL))

Removes the FIOBJ type tag from a FIOBJ objects, allowing access to the underlying pointer and possible type.

This is made available for authoring FIOBJ extensions and shouldn't be normally used.

fiobj_type

size_t fiobj_type(FIOBJ o);

Returns an objects type. This isn't limited to known types.

Avoid calling this function directly. Use the MACRO instead.

FIOBJ Core Memory Management

FIOBJ objects are copied by reference (not by value). Once their reference count is reduced to zero, their memory is freed.

This is extremely important to note, especially in multi-threaded environments. This implied that: access to a dynamic FIOBJ object is NOT thread-safe and FIOBJ objects that may be written to (such as Arrays, Strings and Hash Maps) should not be shared across threads (unless properly protected).

fiobj_dup

FIOBJ fiobj_dup(FIOBJ o);

Increases an object's reference count and returns it.

fiobj_free

void fiobj_free(FIOBJ o);

Decreases an object's reference count or frees it.

Note:

This function is recursive and could cause a stack explosion error.

In addition, recursive object structures may produce unexpected results (for example, objects are always freed).

The FIOBJ_MAX_NESTING nesting limit doesn't apply to fiobj_free since implementing the limit will always result in a memory leak.

This places the responsibility on the user / developer, not to exceed the maximum nesting limit (or errors may occur).

FIOBJ Common Functions

fiobj_is_eq

unsigned char fiobj_is_eq(FIOBJ a, FIOBJ b);

Compares two objects.

Note: objects that contain other objects (i.e., Hash Maps) don't support this equality check just yet (feel free to contribute a PR for this).

fiobj2cstr

fio_str_info_s fiobj2cstr(FIOBJ o);

Returns a temporary String representation for any FIOBJ object.

fiobj2i

intptr_t fiobj2i(FIOBJ o);

Returns an integer representation for any FIOBJ object.

fiobj2f

double fiobj2f(FIOBJ o);

Returns a float (double) representation for any FIOBJ object.

fiobj_each1

uint32_t fiobj_each1(FIOBJ o, int32_t start_at,
                     int (*task)(FIOBJ child, void *arg),
                     void *arg);

Performs a task for each element held by the FIOBJ object directly (but not itself).

If task returns -1, the each loop will break (stop).

Returns the "stop" position - the number of elements processed + start_at.

fiobj_each2

uint32_t fiobj_each2(FIOBJ o,
                     int (*task)(FIOBJ obj, void *arg),
                     void *arg);

Performs a task for each element held by the FIOBJ object (directly or indirectly), including itself and any nested elements (a deep task).

The order of performance is by order of appearance, as if all nesting levels were flattened.

If task returns -1, the each loop will break (stop).

Returns the number of elements processed.

Note:

This function is recursive and could cause a stack explosion error.

The facil.io library attempts to protect against this error by limiting recursive access to FIOBJ_MAX_NESTING... however, this also assumes that a user / developer doesn't exceed the maximum nesting limit (or errors may occur).

FIOBJ Primitive Types

The true, false and null primitive type functions (in addition to the common functions) are only their simple static constructor / accessor functions.

The primitive types are immutable.

fiobj_true

FIOBJ fiobj_true(void);

Returns the true primitive.

fiobj_false

FIOBJ fiobj_false(void);

Returns the false primitive.

fiobj_null

FIOBJ fiobj_null(void);

Returns the nil / null primitive.

FIOBJ Integers

fiobj_num_new

FIOBJ fiobj_num_new(intptr_t i);

Creates a new Number object.

fiobj_num2i

intptr_t fiobj_num2i(FIOBJ i);

Reads the number from a FIOBJ Number.

fiobj_num2f

double fiobj_num2f(FIOBJ i);

Reads the number from a FIOBJ Number, fitting it in a double.

fiobj_num2cstr

fio_str_info_s fiobj_num2cstr(FIOBJ i);

Returns a String representation of the number (in base 10).

fiobj_num_free

void fiobj_num_free(FIOBJ i);

Frees a FIOBJ number (a type specific fiobj_free alternative - use only when the type was validated).

FIOBJ Floats

fiobj_float_new

FIOBJ fiobj_float_new(double i);

Creates a new Float (double) object.

fiobj_float2i

intptr_t fiobj_float2i(FIOBJ i);

Reads the number from a FIOBJ Float rounding it to an integer.

fiobj_float2f

double fiobj_float2f(FIOBJ i);

Reads the value from a FIOBJ Float, as a double.

fiobj_float2cstr

fio_str_info_s fiobj_float2cstr(FIOBJ i);

Returns a String representation of the float.

fiobj_float_free

void fiobj_float_free(FIOBJ i);

Frees a FIOBJ Float (a type specific fiobj_free alternative - use only when the type was validated).

FIOBJ Strings

FIOBJ Strings are based on the core STR_x functions. This means that all these core type functions are available also for this type, using the fiobj_str prefix (i.e., STR_new becomes fiobj_str_new, STR_write becomes fiobj_str_write, etc').

In addition, the following fiobj_str functions and MACROs are defined:

fiobj_str_new_cstr

FIOBJ fiobj_str_new_cstr(const char *ptr, size_t len);

Creates a new FIOBJ string object, copying the data to the new string.

fiobj_str_new_buf

FIOBJ fiobj_str_new_buf(size_t capa);

Creates a new FIOBJ string object with (at least) the requested capacity.

fiobj_str_new_copy

FIOBJ fiobj_str_new_copy(FIOBJ original);

Creates a new FIOBJ string object, copying the origin (fiobj2cstr).

fiobj_str2cstr

fio_str_info_s fiobj_str2cstr(FIOBJ s);

Returns information about the string. Same as fiobj_str_info().

FIOBJ_STR_TEMP_VAR(name)

#define FIOBJ_STR_TEMP_VAR(str_name)                                           \
  FIO_NAME(fiobj_str, s)                            \
  FIO_NAME(str_name, __tmp)[2] = {FIO_STRING_INIT, FIO_STRING_INIT};           \
  memset(FIO_NAME(str_name, __tmp), 0x7f,                                      \
         sizeof(FIO_NAME(str_name, __tmp)[0]));                                \
  FIOBJ str_name = (FIOBJ)(((uintptr_t) & (FIO_NAME(str_name, __tmp)[1])) |    \
                           FIOBJ_T_STRING);

Creates a temporary FIOBJ String object on the stack.

String data might be allocated dynamically, requiring the use of FIOBJ_STR_TEMP_DESTROY.

FIOBJ_STR_TEMP_DESTROY(name)

#define FIOBJ_STR_TEMP_DESTROY(str_name)                                       \
  FIO_NAME(fiobj_str, destroy)(str_name);

Resets a temporary FIOBJ String, freeing and any resources allocated.

FIOBJ Strings - Core Type Functions

In addition, all the functions documented above as STR_x, are defined as fiobj_str_x:

FIOBJ Arrays

FIOBJ Arrays are based on the core ARY_x functions. This means that all these core type functions are available also for this type, using the fiobj_array prefix (i.e., ARY_new becomes fiobj_array_new, ARY_push becomes fiobj_array_push, etc').

These functions include:

FIOBJ Hash Maps

FIOBJ Hash Maps are based on the core MAP_x functions. This means that all these core type functions are available also for this type, using the fiobj_hash prefix (i.e., MAP_new becomes fiobj_hash_new, MAP_set becomes fiobj_hash_set, etc').

In addition, the following fiobj_hash functions and MACROs are defined:

fiobj2hash

uint64_t fiobj2hash(FIOBJ target_hash, FIOBJ value);

Calculates an object's hash value for a specific hash map object.

fiobj_hash_set2

FIOBJ fiobj_hash_set2(FIOBJ hash, FIOBJ key, FIOBJ value);

Inserts a value to a hash map, with a default hash value calculation.

fiobj_hash_get2

FIOBJ fiobj_hash_get2(FIOBJ hash, FIOBJ key);

Finds a value in a hash map, with a default hash value calculation.

fiobj_hash_remove2

int fiobj_hash_remove2(FIOBJ hash, FIOBJ key, FIOBJ *old);

Removes a value from a hash map, with a default hash value calculation.

fiobj_hash_set3

FIOBJ fiobj_hash_set3(FIOBJ hash, const char *key, size_t len, FIOBJ value);

Sets a value in a hash map, allocating the key String and automatically calculating the hash value.

fiobj_hash_get3

FIOBJ fiobj_hash_get3(FIOBJ hash, const char *buf, size_t len);

Finds a String value in a hash map, using a temporary String as the key and automatically calculating the hash value.

fiobj_hash_remove3

int fiobj_hash_remove3(FIOBJ hash, const char *buf, size_t len, FIOBJ *old);

Removes a String value in a hash map, using a temporary String as the key and automatically calculating the hash value.

FIOBJ Hash Map - Core Type Functions

In addition, all the functions documented above as MAP_x, are defined as fiobj_hash_x:

FIOBJ JSON Helpers

fiobj2json

FIOBJ fiobj2json(FIOBJ dest, FIOBJ o, uint8_t beautify);

Returns a JSON valid FIOBJ String, representing the object.

If dest is an existing String, the formatted JSON data will be appended to the existing string.

fiobj_hash_update_json

size_t fiobj_hash_update_json(FIOBJ hash, fio_str_info_s str);

size_t fiobj_hash_update_json2(FIOBJ hash, char *ptr, size_t len);

Updates a Hash using JSON data.

Parsing errors and non-dictionary object JSON data are silently ignored, attempting to update the Hash as much as possible before any errors encountered.

Conflicting Hash data is overwritten (preferring the new over the old).

Returns the number of bytes consumed. On Error, 0 is returned and no data is consumed.

The fiobj_hash_update_json2 function is a helper function, it calls fiobj_hash_update_json with the provided string information.

fiobj_json_parse

FIOBJ fiobj_json_parse(fio_str_info_s str, size_t *consumed);

#define fiobj_json_parse2(data_, len_, consumed)                               \
  fiobj_json_parse((fio_str_info_s){.buf = data_, .len = len_}, consumed)

Parses a C string for JSON data. If consumed is not NULL, the size_t variable will contain the number of bytes consumed before the parser stopped (due to either error or end of a valid JSON data segment).

Returns a FIOBJ object matching the JSON valid C string str.

If the parsing failed (no complete valid JSON data) FIOBJ_INVALID is returned.

fiobj_json_parse2 is a helper macro, it calls fiobj_json_parse with the provided string information.

How to Extend the FIOBJ Type System

The FIOBJ source code includes two extensions for the Float and Number types.

In many cases, numbers and floats can be used without memory allocations. However, when memory allocation is required to store the data, the FIOBJ_T_NUMBER and FIOBJ_T_FLOAT types are extended using the same techniques described here.

FIOBJ Extension Requirements

To extend the FIOBJ soft type system, there are a number of requirements:

  1. A unique type ID must be computed.

    Type IDs are size_t bits in length. Values under 100 are reserved. Values under 40 are illegal (might break implementation).

  2. A static virtual function table object (FIOBJ_class_vtable_s) must be fully populated (NULL values may break cause a segmentation fault).

  3. The unique type construct / destructor must be wrapped using the facil.io reference counting wrapper (using FIO_REF_NAME).

    The FIO_REF_METADATA should be set to a FIOBJ_class_vtable_s pointer and initialized for every object.

  4. The unique type wrapper must use pointer tagging as described bellow (FIO_PTR_TAG).

  5. A public API should be presented.

FIOBJ Pointer Tagging

The FIOBJ types is often identified by th a bit "tag" added to the pointer.

The facil.io memory allocator (fio_malloc), as well as most system allocators, promise a 64 bit allocation alignment.

The FIOBJ types leverage this behavior by utilizing the least significant 3 bits that are always zero.

The following macros should be defined for tagging an extension FIOBJ type, allowing the FIO_REF_NAME constructor / destructor to manage pointer tagging.

#define FIO_PTR_TAG(p) ((uintptr_t)p | FIOBJ_T_OTHER)
#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p)
#define FIO_PTR_TAG_TYPE FIOBJ

FIOBJ Virtual Function Tables

FIOBJ extensions use a virtual function table that is shared by all the objects of that type/class.

Basically, the virtual function table is a struct with the Type ID and function pointers.

All function pointers must be populated (where each1 is only called if count returns a non-zero value).

This is the structure of the virtual table:

/** FIOBJ types can be extended using virtual function tables. */
typedef struct {
  /** A unique number to identify object type. */
  size_t type_id;
  /** Test for equality between two objects with the same `type_id` */
  unsigned char (*is_eq)(FIOBJ a, FIOBJ b);
  /** Converts an object to a String */
  fio_str_info_s (*to_s)(FIOBJ o);
  /** Converts an object to an integer */
  intptr_t (*to_i)(FIOBJ o);
  /** Converts an object to a double */
  double (*to_f)(FIOBJ o);
  /** Returns the number of exposed elements held by the object, if any. */
  uint32_t (*count)(FIOBJ o);
  /** Iterates the exposed elements held by the object. See `fiobj_each1`. */
  uint32_t (*each1)(FIOBJ o, int32_t start_at,
                    int (*task)(FIOBJ child, void *arg), void *arg);
  /**
   * Decreases the reference count and/or frees the object, calling `free2` for
   * any nested objects.
   *
   * Returns 0 if the object is still alive or 1 if the object was freed. The
   * return value is currently ignored, but this might change in the future.
   */
  int (*free2)(FIOBJ o);
} FIOBJ_class_vtable_s;

FIOBJ Extension Example

For our example, let us implement a static string extension type.

The API for this type and the header might look something like this:

/* *****************************************************************************
FIOBJ Static String Extension Header Example

Copyright: Boaz Segev, 2019
License: ISC / MIT (choose your license)

Feel free to copy, use and enjoy according to the license provided.
***************************************************************************** */
#ifndef FIO_STAT_STRING_HEADER_H
#define FIO_STAT_STRING_HEADER_H
/* *****************************************************************************
Perliminaries - include the FIOBJ extension, but not it's implementation
***************************************************************************** */
#define FIO_EXTERN 1
#define FIOBJ_EXTERN 1
#define FIO_FIOBJ
#include "fio-stl.h"

/* *****************************************************************************
Defining the Type ID and the API
***************************************************************************** */

/** The Static String Type ID */
#define FIOBJ_T_STATIC_STRING 101UL

/** Returns a new static string object. The string is considered immutable. */
FIOBJ fiobj_static_new(const char *str, size_t len);

/** Returns a pointer to the static string. */
const char *fiobj_static2ptr(FIOBJ s);

/** Returns the static strings length. */
size_t fiobj_static_len(FIOBJ s);

#endif

Note: The header assumes that somewhere there's a C implementation file that includes the FIOBJ implementation. That C file defines the FIOBJ_EXTERN_COMPLETE macro.

The implementation may look like this.

/* *****************************************************************************
FIOBJ Static String Extension Implementation Example

Copyright: Boaz Segev, 2019
License: ISC / MIT (choose your license)

Feel free to copy, use and enjoy according to the license provided.
***************************************************************************** */
#include <fiobj_static.h> // include the header file here, whatever it's called

/* *****************************************************************************
The Virtual Function Table (definitions and table)
***************************************************************************** */

/** Test for equality between two objects with the same `type_id` */
static unsigned char static_string_is_eq(FIOBJ a, FIOBJ b);
/** Converts an object to a String */
static fio_str_info_s static_string_to_s(FIOBJ o);
/** Converts an object to an integer */
static intptr_t static_string_to_i(FIOBJ o);
/** Converts an object to a double */
static double static_string_to_f(FIOBJ o);
/** Returns the number of exposed elements held by the object, if any. */
static uint32_t static_string_count(FIOBJ o);
/** Iterates the exposed elements held by the object. See `fiobj_each1`. */
static uint32_t static_string_each1(FIOBJ o, int32_t start_at,
                                    int (*task)(FIOBJ, void *), void *arg);
/**
 * Decreases the reference count and/or frees the object, calling `free2` for
 * any nested objects (which we don't have for this type).
 *
 * Returns 0 if the object is still alive or 1 if the object was freed. The
 * return value is currently ignored, but this might change in the future.
 */
static int static_string_free2(FIOBJ o);
/** The virtual function table object. */
static const FIOBJ_class_vtable_s FIOBJ___STATIC_STRING_VTABLE = {
    .type_id = FIOBJ_T_STATIC_STRING,
    .is_eq = static_string_is_eq,
    .to_s = static_string_to_s,
    .to_i = static_string_to_i,
    .to_f = static_string_to_f,
    .count = static_string_count,
    .each1 = static_string_each1,
    .free2 = static_string_free2,
};

/* *****************************************************************************
The Static String Type (internal implementation)
***************************************************************************** */

/* in this example, all functions defined by fio-stl.h will be static (local) */
#undef FIO_EXTERN
#undef FIOBJ_EXTERN
/* leverage the small-string type to hold static string data */
#define FIO_SMALL_STR_NAME fiobj_static_string
/* add required pointer tagging */
#define FIO_PTR_TAG(p) ((uintptr_t)p | FIOBJ_T_OTHER)
#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p)
#define FIO_PTR_TAG_TYPE FIOBJ
/* add required reference counter / wrapper type */
#define FIO_REF_CONSTRUCTOR_ONLY 1
#define FIO_REF_NAME fiobj_static_string
/* initialization - for demonstration purposes, we don't use it here. */
#define FIO_REF_INIT(o)                                                        \
  do {                                                                         \
    o = (fiobj_static_string_s){.aligned = NULL};                              \
    FIOBJ_MARK_MEMORY_ALLOC(); /* mark memory allocation for debugging */      \
  } while (0)
/* cleanup - destroy the object data when the reference count reaches zero. */
#define FIO_REF_DESTROY(o)                                                     \
  do {                                                                         \
    fiobj_static_string_destroy((FIOBJ)&o);                                    \
    FIOBJ_MARK_MEMORY_FREE(); /* mark memory deallocation for debugging */     \
  } while (0)
/* metadata (vtable) definition and initialization. */
#define FIO_REF_METADATA const FIOBJ_class_vtable_s *
/* metadata initialization - required to initialize the vtable. */
#define FIO_REF_METADATA_INIT(m)                                               \
  do {                                                                         \
    m = &FIOBJ___STATIC_STRING_VTABLE;                                         \
  } while (0)
#include <fio-stl.h>

/* *****************************************************************************
The Public API
***************************************************************************** */

/** Returns a new static string object. The string is considered immutable. */
FIOBJ fiobj_static_new(const char *str, size_t len) {
  FIOBJ o = fiobj_static_string_new();
  FIO_ASSERT_ALLOC(FIOBJ_PTR_UNTAG(o));
  fiobj_static_string_set_const(o, str, len);
  return o;
}

/** Returns a pointer to the static string. */
const char *fiobj_static2ptr(FIOBJ o) { return fiobj_static_string2ptr(o); }

/** Returns the static strings length. */
size_t fiobj_static_len(FIOBJ o) { return fiobj_static_string_len(o); }

/* *****************************************************************************
Virtual Function Table Implementation
***************************************************************************** */

/** Test for equality between two objects with the same `type_id` */
static unsigned char static_string_is_eq(FIOBJ a, FIOBJ b) {
  fio_str_info_s ai, bi;
  ai = fiobj_static_string_info(a);
  bi = fiobj_static_string_info(b);
  return (ai.len == bi.len && !memcmp(ai.buf, bi.buf, ai.len));
}
/** Converts an object to a String */
static fio_str_info_s static_string_to_s(FIOBJ o) {
  return fiobj_static_string_info(o);
}
/** Converts an object to an integer */
static intptr_t static_string_to_i(FIOBJ o) {
  fio_str_info_s s = fiobj_static_string_info(o);
  if (s.len)
    return fio_atol(&s.buf);
  return 0;
}
/** Converts an object to a double */
static double static_string_to_f(FIOBJ o) {
  fio_str_info_s s = fiobj_static_string_info(o);
  if (s.len)
    return fio_atof(&s.buf);
  return 0;
}
/** Returns the number of exposed elements held by the object, if any. */
static uint32_t static_string_count(FIOBJ o) {
  return 0;
  (void)o;
}
/** Iterates the exposed elements held by the object. See `fiobj_each1`. */
static uint32_t static_string_each1(FIOBJ o, int32_t start_at,
                                    int (*task)(FIOBJ, void *), void *arg) {
  return 0;
  (void)o; (void)start_at; (void)task; (void)arg;
}
/** Decreases the reference count and/or frees the object. */
static int static_string_free2(FIOBJ o) { return fiobj_static_string_free(o); }

Example usage:

#include "fiobj_static.h" // include FIOBJ extension type
int main(void) {
  FIOBJ o = fiobj_static_new("my static string", 16);
  /* example test of virtual table redirection */
  FIO_ASSERT(fiobj2cstr(o).buf == fiobj_static2ptr(o) &&
                 fiobj2cstr(o).len == fiobj_static_len(o),
             "vtable redirection error.");
  fprintf(stderr, "allocated: %s\n", fiobj_static2ptr(o));
  fprintf(stderr, "it's %zu byte long\n", fiobj_static_len(o));
  fprintf(stderr, "object type: %zu\n", FIOBJ_TYPE(o));
  fiobj_free(o);
  FIOBJ_MARK_MEMORY_PRINT(); /* only in DEBUG mode */
}