# Platform Notes

> StringZilla for C

### 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](https://www.unum.cloud/docs/stringzilla/c/unicode-segmentation.md).

[stl-replace]: https://en.cppreference.com/w/cpp/string/string/replace

### 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.

`#include <stringzilla/types.h>`

## STRINGZILLA_WITH_LIBC

When set to 1, the library will include the following LibC headers: <stddef.h> and <stdint.h>.

```c
#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: Compile Health: [https://artificial-mind.net/projects/compile-health/](https://artificial-mind.net/projects/compile-health/)

## STRINGZILLA_ALLOW_MISALIGNED_LOADS

A misaligned load can be - trying to fetch eight consecutive bytes from an address that is not divisible by eight.

```c
#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_THRESHOLD

Threshold for switching to SWAR (8-bytes at a time) backend over serial byte-level for-loops.

```c
#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_CONSTEXPR

A portable scalar helper, inline in whichever translation unit uses it, and `constexpr` from C++20 onwards.

```c
#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`](https://www.unum.cloud/docs/stringzilla/c/types.md#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_API

Every public function: header-inline in header-only builds, otherwise defined once in the library.

```c
#define STRINGZILLA_API
```

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

## STRINGZILLA_CONSTANT

Translation-unit-local lookup table storage; read-only types carry `const` separately.

```c
#define STRINGZILLA_CONSTANT static
```

