C · Platform Notes

Platform Notes

#include <stringzilla/types.h> 6.0.0-rc.1 · 91a27bdd

§ What’s Wrong with the C Standard Library?

StringZilla is not a drop-in replacement for the C Standard Library. It’s designed to be a safer and more modern alternative. Conceptually:

  1. LibC strings are expected to be null-terminated, so to use the efficient LibC implementations on slices of larger strings, you’d have to copy them, which is more expensive than the original string operation.
  2. LibC functionality is asymmetric — you can find the first and the last occurrence of a character within a string, but you can’t find the last occurrence of a substring.
  3. LibC function names are typically very short and cryptic.
  4. LibC lacks crucial functionality like hashing and doesn’t provide primitives for less critical but relevant operations like fuzzy matching.

Something has to be said about its support for UTF-8. Aside from a single-byte char type, LibC provides wchar_t:

  • The size of wchar_t is not consistent across platforms. On Windows, it’s typically 16 bits (suitable for UTF-16), while on Unix-like systems, it’s usually 32 bits (suitable for UTF-32). This inconsistency can lead to portability issues when writing cross-platform code.
  • wchar_t is designed to represent wide characters in a fixed-width format (UTF-16 or UTF-32). In contrast, UTF-8 is a variable-length encoding, where each character can take from 1 to 4 bytes. This fundamental difference means that wchar_t and UTF-8 are incompatible.

StringZilla partially addresses those issues.

§ Compilation Settings and Debugging

STRINGZILLA_DEBUG:

For maximal performance, the C library does not perform any bounds checking in Release builds. In C++, bounds checking happens only in places where the STL std::string would do it. If you want to enable more aggressive bounds-checking, define STRINGZILLA_DEBUG before including the header. If not explicitly set, it will be inferred from the build type.

STRINGZILLA_TARGET_GOLDMONT, STRINGZILLA_TARGET_WESTMERE, STRINGZILLA_TARGET_HASWELL, STRINGZILLA_TARGET_SKYLAKE, STRINGZILLA_TARGET_ICELAKE, STRINGZILLA_TARGET_NEON, STRINGZILLA_TARGET_NEONAES, STRINGZILLA_TARGET_NEONSHA, STRINGZILLA_TARGET_SVE, STRINGZILLA_TARGET_SVE2, STRINGZILLA_TARGET_SVE2AES, STRINGZILLA_TARGET_V128, STRINGZILLA_TARGET_V128RELAXED, STRINGZILLA_TARGET_RVV, STRINGZILLA_TARGET_LOONGSONASX, STRINGZILLA_TARGET_POWERVSX:

One can explicitly enable or disable individual SIMD families for compatibility or benchmarking purposes. In header-only use the defaults are inferred from the compiler’s predefined macros under your own -march flags; LASX and POWER9 need your -mlasx and -mcpu=power9, as their intrinsics headers hide without them. The CMake build, which every binding goes through, resolves them from the probes/ sources instead — compiling each capability’s probe, which calls one of its kernels, to learn whether the toolchain builds them — so the libraries carry every compilable capability. The same names work as CMake cache options (-D STRINGZILLA_TARGET_SVE2=0) and as Cargo environment variables (STRINGZILLA_TARGET_SVE2=0 cargo build); an explicit 1 keeps a capability only where its probe compiles.

STRINGZILLA_TARGET_CUDA, STRINGZILLA_TARGET_ROCM, STRINGZILLA_TARGET_METAL:

Compile the GPU kernels of one vendor: CUDA wherever the unit is compiled as CUDA, by nvcc or by Clang, ROCm wherever it is compiled as HIP, and Metal wherever the build links Metal and Foundation, as STRINGZILLA_METAL_ARCH does. The Metal kernels travel as embedded source and compile on the device at first use. Each defaults to 1 wherever its vendor is present, and setting it to 0 keeps the host API while dropping that vendor’s kernels.

STRINGZILLA_HEADER_ONLY:

By default, the headers declare the StringZilla library, which compiles every capability once and dispatches among them at runtime, as the shared library and every binding do. Set it to 1 to inline the kernels your compiler flags enable instead, calling them by name; the dispatch points and finders then report sz_missing_library_k.

STRINGZILLA_ALLOW_MISALIGNED_LOADS:

Default is platform-dependent: enabled on x86 (where unaligned accesses are fast), disabled on others by default. When enabled, many byte-level operations use word-sized loads, which can significantly accelerate the serial (SWAR) backend. Consider enabling it explicitly if you are targeting platforms that support fast unaligned loads.

STRINGZILLA_WITH_LIBC:

Both the library and the C headers can be built without LibC. This may affect the type resolution system on obscure hardware platforms.

STRINGZILLA_WITH_STL:

When using the C++ interface one can disable implicit conversions from std::string to sz::string_t and back. If not needed, the <string> and <string_view> headers will be excluded, reducing compilation time.

STRINGZILLA_BUILD_SHARED, STRINGZILLA_BUILD_TEST, STRINGZILLA_BUILD_BENCH, STRINGZILLA_MARCH for CMake users:

When compiling the tests and benchmarks, you can explicitly set the target hardware architecture. It’s synonymous to GCC’s -march flag and is used to enable/disable the appropriate instruction sets. You can also disable the shared library build, if you don’t need it.

API

Macros

STRINGZILLA_WITH_LIBCWhen set to 1, the library will include the following LibC headers: <stddef.h> and <stdint.h>.
types.h:55 ↗
#define STRINGZILLA_WITH_LIBC (1)

In debug builds, with STRINGZILLA_DEBUG=1, it will also include <stdio.h> and <stdlib.h>.

You may want to disable this compiling for use in the kernel, or in embedded systems. You may also avoid them, if you are very sensitive to compilation time and avoid pre-compiled headers.

See also

STRINGZILLA_ALLOW_MISALIGNED_LOADSA misaligned load can be - trying to fetch eight consecutive bytes from an address that is not divisible by eight.
types.h:80 ↗
#define STRINGZILLA_ALLOW_MISALIGNED_LOADS (0)

On x86 enabled by default. On Arm it's not.

Most platforms support it, but there is no industry standard way to check for those. This value will mostly affect the performance of the serial (SWAR) backend.

STRINGZILLA_SWAR_THRESHOLDThreshold for switching to SWAR (8-bytes at a time) backend over serial byte-level for-loops.
types.h:172 ↗
#define STRINGZILLA_SWAR_THRESHOLD (24u)

On very short strings, under 16 bytes long, at most a single word will be processed with SWAR. Assuming potentially misaligned loads, SWAR makes sense only after ~24 bytes.

STRINGZILLA_CONSTEXPRA portable scalar helper, inline in whichever translation unit uses it, and constexpr from C++20 onwards.
types.h:233 ↗
#define STRINGZILLA_CONSTEXPR STRINGZILLA_MAYBE_UNUSED_ STRINGZILLA_C_INLINE_

That qualifier is what lets a caller fold the helper at compile time, and what lets a CUDA kernel call it at all - --expt-relaxed-constexpr reaches a host constexpr function from device code, so this layer never has to name an execution space of its own.

A helper reaching an intrinsic can never be constant-evaluated, and a constexpr function with no constant-evaluated path is ill-formed: Clang and MSVC reject the definition, GCC 12 too, and only GCC 13+ softens it to -Winvalid-constexpr. Every such helper - the whole of each ISA backend, plus the few portable ones wrapping a builtin or a type-punned load - carries STRINGZILLA_INLINE instead, which is why no translation unit needs a -Wno- flag to compile this header.

The qualifier waits for C++20 because these helpers declare their locals before filling them, which only C++20 permits in a constexpr function. C, and an older C++ dialect reaching the C API, get the plain helper.

MSVC is the one front end that gets the plain helper: its bit-scan and byte-swap intrinsics are not constant-evaluable, so sz_u64_ctz and every helper that reaches one - sz_size_bit_ceil, the folded rune iterators, the uncased search - is rejected with C3615, an error no /wd can silence. The qualifier buys compile-time folding and nvcc reach, and MSVC hosts neither, so nothing is lost by dropping it.

STRINGZILLA_APIEvery public function: header-inline in header-only builds, otherwise defined once in the library.
types.h:243 ↗
#define STRINGZILLA_API

Windows DLLs export every symbol through CMake, so no import or export spelling.

STRINGZILLA_CONSTANTTranslation-unit-local lookup table storage; read-only types carry const separately.
types.h:247 ↗
#define STRINGZILLA_CONSTANT static