# Types

> StringZilla for C

The C API is built around length-bounded raw pointers, so most functions take a `sz_cptr_t`, an alias of `char const *`, or a `sz_ptr_t`, an alias of `char *`, together with a `sz_size_t` length, and return either a `sz_cptr_t` into the input or one of the small enums below.

Scalar and result types from `types.h`:

- `sz_cptr_t`, `sz_ptr_t` — immutable and mutable byte pointers.
- `sz_size_t`, `sz_ssize_t` — unsigned and signed size types.
- `sz_u8_t`, `sz_u32_t`, `sz_u64_t` — fixed-width integers; `sz_rune_t` is a 32-bit Unicode scalar value.
- `sz_bool_t` — `{ sz_false_k = 0, sz_true_k = 1 }`.
- `sz_ordering_t` — `{ sz_less_k = -1, sz_equal_k = 0, sz_greater_k = 1 }`, the C analog of `std::strong_ordering`.
- `sz_status_t` — `sz_success_k = 0`, `sz_bad_alloc_k`, `sz_invalid_utf8_k`, `sz_contains_duplicates_k`, `sz_overflow_risk_k`, and friends.
- `sz_pgram_t` — a pointer-sized N-gram, an alias of `sz_size_t`, which also indexes every permutation.

`sz_byteset_t` is a 256-bit set of byte values used by the byte-set search functions:

```c
sz_byteset_t set;
sz_byteset_init(&set);                 // all bits cleared
sz_byteset_add(&set, '\n');            // include a byte
int has = sz_byteset_contains(&set, '\n');
sz_byteset_invert(&set);               // complement the set
```

`sz_allocator_t` is the explicit allocator, a `{ allocate, free, handle }` triple, handed to any function that may allocate:

```c
sz_allocator_t allocator;
sz_status_t status = sz_allocator_init_heap(&allocator); // libc malloc/free
if (status != sz_success_k) return status;
// or a fixed arena with no dynamic allocation:
char arena[4096];
status = sz_allocator_init_arena(&allocator, arena, sizeof(arena), 64);
if (status != sz_success_k) return status;
```

`sz_strings_view_t` is the read-only collection of strings every collection family takes, in one of three layouts: an Apache Arrow tape of 32-bit or 64-bit offsets, which GPUs read directly, or an accessor writing up to 64 views per call over any other shape.
Helpers wire it to plain arrays of C strings, of views, of records, and of NUL-padded rows:

```c
char const *words[] = {"banana", "apple", "cherry"};
sz_strings_view_t strings;
sz_strings_from_null_terminated_strings(words, 3, &strings);
```

There is no dedicated `sz_string_view_t` struct in the C ABI — views are passed as the `(pointer, length)` pairs described above.
The one owning C string type is `sz_string_t`, documented under [String Collections and Containers](https://www.unum.cloud/docs/stringzilla/c/sorting-and-sets.md#string-collections-and-containers) below.

`#include <stringzilla/types.h>`

## sz_i8_t

Even when LibC is not available, compiler macros let us infer the size of integer types.

```c
typedef signed char sz_i8_t;
```

The C standard doesn't specify the signedness of `char`. On x86 `char` is signed by default while on Arm it is unsigned by default. That's why we don't define `sz_char_t` and generally use explicit `[sz_i8_t](#sz_i8_t)` and `sz_u8_t`.

See also: Common Predefined Macros: [https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html](https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html)

## sz_size_t

Now we need to redefine the `size_t`.

```c
typedef sz_u32_t sz_size_t;
```

Microsoft Visual C++ (MSVC) typically follows the LLP64 data model on 64-bit platforms, where integers, pointers, and longs differ in size.

GCC and Clang on 64-bit Unix-like systems typically follow the LP64 model instead:

| LLP64 (MSVC) | LP64 (GCC, Clang) |
|:-|:-|
| 32 bits | 32 bits |
| 32 bits | 64 bits |
| 64 bits | 64 bits |
| 64 bits | 64 bits |

See also: Abstract Data Models: [https://learn.microsoft.com/en-us/windows/win32/winprog64/abstract-data-models](https://learn.microsoft.com/en-us/windows/win32/winprog64/abstract-data-models)

## sz_u1x8_t

Eight booleans in one byte, item 0 in the least significant bit, the order of Arrow and cuDF validity masks and the reverse of NumKong's `nk_u1x8_t`.

```c
typedef sz_u8_t sz_u1x8_t;
```

## sz_bool_t

Simple boolean type, until `_Bool` in C 99 and `true` and `false` in C 23.

```c
enum sz_bool_t
```

- `sz_false_k` = `0`
- `sz_true_k` = `1`

See also: Using boolean values in C: [https://stackoverflow.com/questions/1921539/using-boolean-values-in-c](https://stackoverflow.com/questions/1921539/using-boolean-values-in-c)

## sz_status_t

Outcome of every StringZilla call that can fail: zero on success, negative when the call failed and its outputs hold no result.

```c
enum sz_status_t
```

Positive values are reserved for results with a caveat.

Statuses fall into four categories, with no interruptions to report. Resources: [`sz_bad_alloc_k`](#sz_bad_alloc_k), [`sz_missing_gpu_k`](#sz_missing_gpu_k), [`sz_device_code_mismatch_k`](#sz_device_code_mismatch_k), [`sz_missing_kernel_k`](#sz_missing_kernel_k) and [`sz_missing_library_k`](#sz_missing_library_k). Input: [`sz_invalid_utf8_k`](#sz_invalid_utf8_k), [`sz_contains_duplicates_k`](#sz_contains_duplicates_k), [`sz_unexpected_dimensions_k`](#sz_unexpected_dimensions_k), [`sz_device_memory_mismatch_k`](#sz_device_memory_mismatch_k), [`sz_authentication_failed_k`](#sz_authentication_failed_k) and [`sz_invalid_syntax_k`](#sz_invalid_syntax_k). State: [`sz_overflow_risk_k`](#sz_overflow_risk_k). Defects: [`sz_status_unknown_k`](#sz_status_unknown_k). Any verb that allocates, binds a device or touches a file may also return a Resources status. A verb's own block lists only what its contract adds.

- `sz_success_k` = `0`: For algorithms that return a status, indicates that the operation was successful.
- `sz_bad_alloc_k` = `-10`: For algorithms that require memory allocation, indicates that the allocation failed.
- `sz_invalid_utf8_k` = `-12`: For algorithms that require UTF8 input, indicates that the input is invalid.
- `sz_contains_duplicates_k` = `-13`: For algorithms taking collections of unique elements, reports the presence of duplicates.
- `sz_overflow_risk_k` = `-14`: For algorithms on large inputs, reports the need to upcast the logic to larger types.
- `sz_unexpected_dimensions_k` = `-15`: For algorithms with multi-stage pipelines indicates input/output size mismatch.
- `sz_missing_gpu_k` = `-16`: GPU support is missing in the library.
- `sz_device_code_mismatch_k` = `-17`: Backend-device mismatch: e.g., GPU kernel with CPU/default executor or vice versa.
- `sz_device_memory_mismatch_k` = `-18`: Device memory mismatch: e.g., GPU kernel requires unified/device-accessible memory.
- `sz_authentication_failed_k` = `-19`: An authenticated decryption saw a tag that does not match the ciphertext it accompanies.
- `sz_missing_kernel_k` = `-20`: No capability in the capability mask has this kernel.
- `sz_missing_library_k` = `-21`: A dispatch point or finder called from a header-only build, which links no library.
- `sz_invalid_syntax_k` = `-22`: A pattern, path or document breaking its grammar, or using a construct no automaton runs.
- `sz_status_unknown_k` = `-1`: A sink-hole status for unknown errors.

Spellings: ```
char const * sz_status_name(sz_status_t status);
```

## sz_rune_length_t

Describes the length of a UTF-8 __rune__ / character / codepoint in bytes, from 1 to 4.

```c
enum sz_rune_length_t
```

- `sz_rune_invalid_k` = `0`: Invalid UTF8 character.
- `sz_rune_1byte_k` = `1`: 1-byte UTF8 character.
- `sz_rune_2bytes_k` = `2`: 2-byte UTF8 character.
- `sz_rune_3bytes_k` = `3`: 3-byte UTF8 character.
- `sz_rune_4bytes_k` = `4`: 4-byte UTF8 character.

See also: UTF-8: [https://en.wikipedia.org/wiki/UTF-8](https://en.wikipedia.org/wiki/UTF-8)

## sz_rune_t

Stores a single UTF-8 __rune__ / character / codepoint unpacked into __UTF-32__.

```c
typedef sz_u32_t sz_rune_t;
```

The underlying numeric type holds 4 bytes, with over 4 billion possible states, but:

- UTF-8 in its largest 4-byte form has only 3 + 6 + 6 + 6 = 21 bits of usable space, for 2 million states.
- Unicode, in turn, has only __1'114'112__ possible code points from U+0000 to U+10FFFF.
- Of those, in Unicode 16, only __155'063__ are assigned characters, just over 17 bits' worth.That's __0.004%__ of the 32-bit space, so sparse data-structures suit UTF-8 oriented algorithms.

See also: UTF-32: [https://en.wikipedia.org/wiki/UTF-32](https://en.wikipedia.org/wiki/UTF-32)

## sz_string_view_t

Tiny string-view structure.

```c
struct sz_string_view_t
```

It's a Plain-Old Datatype ____(POD) type, unlike the `std::string_view`.

- `start` `sz_cptr_t`
- `length` `sz_size_t`

See also: PODType: [https://en.cppreference.com/w/cpp/named_req/PODType](https://en.cppreference.com/w/cpp/named_req/PODType)

## sz_byteset_t

Semi-opaque bit-set for 256 possible byte values, useful for filtering and search.

```c
union sz_byteset_t
```

It can be filled and probed like this:

```
#include 
int main() {
    char const *alphabet = "abcdefghijklmnopqrstuvwxyz";
    sz_byteset_t byteset;
    sz_byteset_init(&byteset);
    for (sz_size_t i = 0; i < 26; ++i)
        sz_byteset_add(&byteset, alphabet[i]);
    return sz_byteset_contains(&byteset, 'a') && !sz_byteset_contains(&byteset, 'A') ? 0 : 1;
}
```

- `_u64s` `sz_u64_t`
- `_u32s` `sz_u32_t`
- `_u16s` `sz_u16_t`
- `_u8s` `sz_u8_t`

See also: [sz_byteset_init](#sz_byteset_init), [sz_byteset_add](#sz_byteset_add), [sz_byteset_contains](#sz_byteset_contains), [sz_byteset_invert](#sz_byteset_invert)

## sz_u16_vec_t

Helper structure to simplify work with 16-bit words.

```c
union sz_u16_vec_t
```

- `u16` `sz_u16_t`
- `u8s` `sz_u8_t`

See also: [sz_u16_load](https://www.unum.cloud/docs/stringzilla/c/memory-operations.md#sz_u16_load)

## sz_u32_vec_t

Helper structure to simplify work with 32-bit words.

```c
union sz_u32_vec_t
```

- `u32` `sz_u32_t`
- `i32` `sz_i32_t`
- `u16s` `sz_u16_t`
- `i16s` `sz_i16_t`
- `u8s` `sz_u8_t`
- `i8s` `sz_i8_t`

See also: [sz_u32_load](https://www.unum.cloud/docs/stringzilla/c/memory-operations.md#sz_u32_load)

## sz_u64_vec_t

Helper structure to simplify work with 64-bit words.

```c
union sz_u64_vec_t
```

- `u64` `sz_u64_t`
- `i64` `sz_i64_t`
- `u32s` `sz_u32_t`
- `i32s` `sz_i32_t`
- `u16s` `sz_u16_t`
- `i16s` `sz_i16_t`
- `u8s` `sz_u8_t`
- `i8s` `sz_i8_t`

See also: [sz_u64_load](https://www.unum.cloud/docs/stringzilla/c/memory-operations.md#sz_u64_load)

## sz_u128_vec_t

Helper structure to simplify work with __128-bit__ registers.

```c
union sz_u128_vec_t
```

It can help view the contents as 8-bit, 16-bit, 32-bit, or 64-bit integers, as well as 1x XMM register.

- `f64s` `sz_f64_t`
- `f32s` `sz_f32_t`
- `u64s` `sz_u64_t`
- `i64s` `sz_i64_t`
- `u32s` `sz_u32_t`
- `i32s` `sz_i32_t`
- `u16s` `sz_u16_t`
- `i16s` `sz_i16_t`
- `u8s` `sz_u8_t`
- `i8s` `sz_i8_t`

## sz_u256_vec_t

Helper structure to simplify work with __256-bit__ registers.

```c
union sz_u256_vec_t
```

It can help view the contents as 8-bit, 16-bit, 32-bit, or 64-bit integers, as well as 2x XMM registers or 1x YMM register.

- `f64s` `sz_f64_t`
- `f32s` `sz_f32_t`
- `u64s` `sz_u64_t`
- `i64s` `sz_i64_t`
- `u32s` `sz_u32_t`
- `i32s` `sz_i32_t`
- `u16s` `sz_u16_t`
- `i16s` `sz_i16_t`
- `u8s` `sz_u8_t`
- `i8s` `sz_i8_t`

## sz_u512_vec_t

Helper structure to simplify work with __512-bit__ registers.

```c
union sz_u512_vec_t
```

It can help view the contents as 8-bit, 16-bit, 32-bit, or 64-bit integers, as well as 4x XMM registers or 2x YMM registers or 1x ZMM register.

- `f64s` `sz_f64_t`
- `f32s` `sz_f32_t`
- `u64s` `sz_u64_t`
- `i64s` `sz_i64_t`
- `u32s` `sz_u32_t`
- `i32s` `sz_i32_t`
- `u16s` `sz_u16_t`
- `i16s` `sz_i16_t`
- `u8s` `sz_u8_t`
- `i8s` `sz_i8_t`
- `u128s` `sz_u128_vec_t`
- `u256s` `sz_u128_vec_t`

## sz_strings_get_t

Signature of the `[sz_strings_view_t](#sz_strings_view_t)` accessor, writing the view of every string `indices[i]`.

```c
typedef void(*) sz_strings_get_t(void const *handle, sz_size_t const *indices, sz_size_t count, sz_string_view_t *views);
```

Kernels call it once per block of up to 64 indices, and it must accept any `count`.

## sz_strings_layout_t

How a collection holds its strings, naming the member of its `data` union to read.

```c
enum sz_strings_layout_t
```

- `sz_strings_u32_tape_k` = `0`: Apache Arrow `String`, where string i spans `[offsets[i], offsets[i + 1])` of the bytes.
- `sz_strings_u64_tape_k` = `1`: Apache Arrow `LargeString`, the same with 64-bit offsets.
- `sz_strings_accessor_k` = `2`: Strings found through a `[sz_strings_get_t](#sz_strings_get_t)` accessor, which only the host can call.

## sz_strings_view_t

A read-only, ordered collection of borrowed strings, in one of three layouts.

```c
struct sz_strings_view_t
```

Tapes are what Apache Arrow, the Python `Strings` class and GPU kernels read directly. The accessor covers every other shape, from arrays of views to the nodes of a parsed tree, at one indirect call per block of strings. A slice of a tape keeps its parent's offsets, so its first offset may be above zero.

- `layout` `sz_strings_layout_t`: Which member of `data` describes the strings.
- `count` `sz_size_t`: Number of strings.
- `data` `union sz_strings_view_t::@305030033365211305011023036247110352205365160207`

See also: [sz_strings_from_string_views](#sz_strings_from_string_views), [sz_strings_from_records](https://www.unum.cloud/docs/stringzilla/c/sorting-and-sets.md#sz_strings_from_records), [sz_strings_from_span](#sz_strings_from_span)

## sz_strings_span_t

A writable tape, the output of every collection conversion, in place or not.

```c
struct sz_strings_span_t
```

Its payload and offsets are two blocks of exactly the sizes they need, so both sizes follow from the offsets, which the host can always read. A zeroed span is an empty tape holding no blocks.

- `layout` `sz_strings_layout_t`: Either [`sz_strings_u32_tape_k`](#sz_strings_u32_tape_k) or [`sz_strings_u64_tape_k`](#sz_strings_u64_tape_k), never the accessor.
- `count` `sz_size_t`: Number of strings.
- `data` `union sz_strings_span_t::@077141061375231343114166315312256034361144020235`

## sz_byteset_init

Initializes a bit-set to an empty collection, meaning - all characters are banned.

```c
void sz_byteset_init(sz_byteset_t *s);
```

## sz_byteset_init_ascii

Initializes a bit-set to all ASCII characters.

```c
void sz_byteset_init_ascii(sz_byteset_t *s);
```

## sz_byteset_add_u8

Adds a character to the set and accepts __unsigned__ integers.

```c
void sz_byteset_add_u8(sz_byteset_t *s, sz_u8_t c);
```

## sz_byteset_add

Adds a character to the set.

```c
void sz_byteset_add(sz_byteset_t *s, char c);
```

Consider __sz_byteset_add_u8__.

## sz_byteset_contains_u8

Checks if the set contains a given character and accepts __unsigned__ integers.

```c
sz_bool_t sz_byteset_contains_u8(sz_byteset_t const *s, sz_u8_t c);
```

## sz_byteset_contains

Checks if the set contains a given character.

```c
sz_bool_t sz_byteset_contains(sz_byteset_t const *s, char c);
```

Consider __sz_byteset_contains_u8__.

## sz_byteset_invert

Inverts the contents of the set, so allowed characters get disallowed, and vice versa.

```c
void sz_byteset_invert(sz_byteset_t *s);
```

## sz_u64_bits_reverse

Reverse the 64 bits of `value`, so bit i moves to bit 63 - i: swap adjacent bits, then bit-pairs within nibbles, then nibbles within bytes, then the bytes.

```c
sz_u64_t sz_u64_bits_reverse(sz_u64_t value);
```

Lets an ascending-only byte-compress, like `vpcompressb`, pack lanes in descending order.

## sz_u64_nth_set_bit

Bit index of the n-th (0-based) set bit of `bits`; clears the `n` lowest set bits, then `ctz`.

```c
int sz_u64_nth_set_bit(sz_u64_t bits, sz_size_t n);
```

`bits` must hold more than `n` set bits. One tested home for the per-ISA SIMD "n-th lane" locate.

## sz_u64_blend

Select bits from either `a` or `b` depending on the value of `mask` bits.

```c
sz_u64_t sz_u64_blend(sz_u64_t a, sz_u64_t b, sz_u64_t mask);
```

Similar to the `_mm_blend_epi16` intrinsic on x86.

See also: Bit Twiddling Hacks by Sean Eron Anderson: [https://graphics.stanford.edu/~seander/bithacks.html#ConditionalSetOrClearBitsWithoutBranching](https://graphics.stanford.edu/~seander/bithacks.html#ConditionalSetOrClearBitsWithoutBranching)

## sz_i32_min_of_two

Branchless minimum function for two signed 32-bit integers.

```c
sz_i32_t sz_i32_min_of_two(sz_i32_t x, sz_i32_t y);
```

## sz_i32_max_of_two

Branchless maximum function for two signed 32-bit integers.

```c
sz_i32_t sz_i32_max_of_two(sz_i32_t x, sz_i32_t y);
```

## sz_ssize_clamp_interval_checked

Clamps signed offsets to a Python-style `[offset, offset + length)` slice and reports whether the requested window was non-degenerate.

```c
sz_bool_t sz_ssize_clamp_interval_checked(
    sz_size_t length,
    sz_ssize_t start,
    sz_ssize_t end,
    sz_size_t *normalized_offset,
    sz_size_t *normalized_length);
```

Mirrors CPython's `ADJUST_INDICES:` negative indices count from the end, `end` is clamped into the string, and a window with `start` past `end`, out of range or inverted, is "empty" - exactly when `str.find("")` and `str.rfind("")` return -1.

## sz_ssize_clamp_interval

Clamps signed offsets in a string to a valid range.

```c
void sz_ssize_clamp_interval(
    sz_size_t length,
    sz_ssize_t start,
    sz_ssize_t end,
    sz_size_t *normalized_offset,
    sz_size_t *normalized_length);
```

Used for Pythonic-style slicing.

Notes: Thin wrapper over [sz_ssize_clamp_interval_checked](#sz_ssize_clamp_interval_checked) for callers ignoring validity.

## sz_size_log2i_nonzero

Compute the logarithm base 2 of a positive integer, rounding down.

```c
sz_size_t sz_size_log2i_nonzero(sz_size_t x);
```

Preconditions: Input must be a positive number, as the logarithm of zero is undefined.

## sz_size_divide_round_up

Computes the ceiling of `x` divided by `divisor` - the number of chunks of that size needed to cover `x`.

```c
sz_size_t sz_size_divide_round_up(sz_size_t x, sz_size_t divisor);
```

Assumes a non-zero `divisor` and no overflow on `x + divisor`.

## sz_u32_divide_round_up

Divides rounding up in 32 bits, for device code where the `[sz_size_t](#sz_size_t)` form costs registers.

```c
sz_u32_t sz_u32_divide_round_up(sz_u32_t x, sz_u32_t divisor);
```

## sz_size_bit_ceil

Compute the smallest power of two greater than or equal to `x`.

```c
sz_size_t sz_size_bit_ceil(sz_size_t x);
```

Notes: Uses LZCNT/CLZ for efficient computation on modern CPUs. Edge cases: bit_ceil(0) = 0, bit_ceil(1) = 1.

See also: Rounding up to a power of two: [https://stackoverflow.com/a/10143264](https://stackoverflow.com/a/10143264)

## sz_u64_transpose

Transposes an 8×8 bit matrix packed in a `sz_u64_t`.

```c
sz_u64_t sz_u64_transpose(sz_u64_t x);
```

There is a well known SWAR sequence for that known to chess programmers, willing to flip a bit-matrix of pieces along the main A1-H8 diagonal.

See also: Flipping, Mirroring and Rotating: [https://www.chessprogramming.org/Flipping_Mirroring_and_Rotating](https://www.chessprogramming.org/Flipping_Mirroring_and_Rotating)

See also: Transposing a bit matrix: [https://lukas-prokop.at/articles/2021-07-23-transpose](https://lukas-prokop.at/articles/2021-07-23-transpose)

## sz_strings_from_null_terminated_strings

Describes an array of NUL-terminated strings, like `char const *argv[]`.

```c
void sz_strings_from_null_terminated_strings(
    sz_cptr_t const *start,
    sz_size_t count,
    sz_strings_view_t *strings);
```

- `start` (in): The array of strings, which must outlive `strings`.
- `count` (in): Number of strings in the array.
- `strings` (out): The accessor-backed collection to initialize.

## sz_strings_from_string_views

Describes an array of pointer-length pairs, like a list of borrowed string views.

```c
void sz_strings_from_string_views(
    sz_string_view_t const *views,
    sz_size_t count,
    sz_strings_view_t *strings);
```

- `views` (in): The array of views, which must outlive `strings`.
- `count` (in): Number of views in the array.
- `strings` (out): The accessor-backed collection to initialize.

## sz_strings_slice

Selects the `count` strings at `start + i * step`, copying no string bytes.

```c
void sz_strings_slice(
    sz_strings_view_t const *strings,
    sz_size_t start,
    sz_ssize_t step,
    sz_size_t count,
    sz_string_view_t *views,
    sz_strings_view_t *slice);
```

A step of one over a tape yields a tape over the same bytes and offsets, leaving `views` untouched. Every other selection fills `views` and points the accessor of `slice` at them.

- `strings` (in): The collection to slice, which must outlive `slice`.
- `start` (in): Index of the first selected string.
- `step` (in): Distance between selected indices, negative to walk backward.
- `count` (in): Number of strings to select.
- `views` (out): Room for `count` views, or null for a step of one over a tape.
- `slice` (out): The selected strings.

## sz_strings_from_matches

Views the spans a substrings, regex or subdocuments search located, copying no bytes.

```c
void sz_strings_from_matches(
    sz_strings_view_t const *haystacks,
    sz_match_t const *matches,
    sz_size_t count,
    sz_string_view_t *views,
    sz_strings_view_t *strings);
```

- `haystacks` (in): The collection the matches were found in, which must outlive `strings`.
- `matches` (in): The located matches, in any order.
- `count` (in): Number of matches.
- `views` (out): Receives the bytes of `matches[i]` inside its haystack for each match.
- `strings` (out): Points its accessor at `views`.

## sz_strings_from_span

Views the tape of `span`, so the output of one conversion can feed the next.

```c
void sz_strings_from_span(
    sz_strings_span_t const *span,
    sz_strings_view_t *strings);
```

- `span` (in): The writable tape to read, which must outlive `strings`.
- `strings` (out): The read-only collection to initialize.

## sz_strings_span_free

Returns both blocks of `span` to the `allocator` they came from, leaving it zeroed.

```c
void sz_strings_span_free(
    sz_strings_span_t *span,
    sz_allocator_t *allocator,
    sz_stream_t stream);
```

- `span` (inout): The tape to release, whose offsets the host reads to size both blocks.
- `allocator` (in): The allocator both blocks came from.
- `stream` (in): Null on the CPU, or the GPU stream the release waits behind.

## STRINGZILLA_U8_MAX

Largest value that fits into 8 bits.

```c
#define STRINGZILLA_U8_MAX (255u)
```

## STRINGZILLA_U16_MAX

Largest value that fits into 16 bits.

```c
#define STRINGZILLA_U16_MAX (65535u)
```

## STRINGZILLA_U16_MAX_PRIME

Largest prime number that fits into 16 bits.

```c
#define STRINGZILLA_U16_MAX_PRIME (65521u)
```

## STRINGZILLA_U32_MAX_PRIME

Largest prime number that fits into 31 bits.

```c
#define STRINGZILLA_U32_MAX_PRIME (2147483647u)
```

## STRINGZILLA_U64_MAX_PRIME

Largest prime number that fits into 64 bits.

```c
#define STRINGZILLA_U64_MAX_PRIME (18446744073709551557ull)
```

| 2⁶⁴ | = 18,446,744,073,709,551,616 |
|:-|:-|
| this | = 18,446,744,073,709,551,557 |
| diff | = 59 |

See also: Largest 64-bit prime: [https://mersenneforum.org/showthread.php?t=3471](https://mersenneforum.org/showthread.php?t=3471)

## STRINGZILLA_NULL

Defines [`STRINGZILLA_NULL`](#STRINGZILLA_NULL), analogous to `NULL`.

```c
#define STRINGZILLA_NULL ((void *)0)
```

The default often comes from locale.h, stddef.h, stdio.h, stdlib.h, string.h, time.h, or wchar.h.

## sz_min_of_two

Efficiently computing the minimum and maximum of two or three values can be tricky.

```c
#define sz_min_of_two(x, y) ((x) <(y) ? (x) : (y))
```

A branchless approach is well known for signed integers, but it doesn't apply to unsigned ones. The simple branching baseline comes first, then the shift-only form for signed integers, then two forms for any integers, with and without multiplication:

| x > 31) | 4 unique operations |
| (x > y) * y + (x <= y) * x | 5 operations |
| x & ~((x < y) - 1) + y & ((x < y) - 1) | 6 unique operations |

See also: Branchless min and max: [https://stackoverflow.com/questions/514435/templatized-branchless-int-max-min-function](https://stackoverflow.com/questions/514435/templatized-branchless-int-max-min-function)

See also: Bit Twiddling Hacks: [https://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax](https://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax)

