Skip to content

Cons-Cat/libCat

Repository files navigation

libCat 🐈‍⬛

libCat is a non-POSIX compliant C++26 runtime. It has no pthreads nor global malloc(), and currently does not support exceptions. It has type-safe arithmetic, SIMD, fast syscalls, CRTP interfaces,

Building

libCat requires a recent development version of Clang 23 from the trunk branch. mise can be used to fetch an appropriate version. This also installs just, recent versions of build tools, and sets CMAKE_GENERATOR=Ninja Multi-Config by default, but setting CMAKE_GENERATOR is supported (e.g. CMAKE_GENERATOR=Ninja) for all tools and in all release modes. mise is not required, but is highly recommended.

mise install
just build
just test

The .clang-format and .clang-tidy configurations are only compatible with correspondingly recent builds of clang-tools.

Currently, running libCat requires AVX2. On x86 microarchitectures lacking that, the Intel Software Development Emulator is known to work for libCat binaries. mise does not install this emulator.

just can configure release modes with one or more optional release mode arguments. If no mode argument is passed, just reuses the last explicit mode from this worktree, defaulting to release. Pass -v to show verbose CMake configure messages and build commands.

just build debug
just build release
just build relwithdebinfo

just build all -v
just build debug relwithdebinfo -v

Developers of libCat are encouraged to enable sanitizers with san. Use nosan to disable them again. If neither argument is passed, CMake keeps the cached CAT_USE_SANITIZERS setting. The order of this argument is arbitrary to the build modes.

just build san
just build nosan

just build san debug
just build debug san release

This will compile every translation unit with AddressSanitizer + UndefinedBehaviorSanitizer. Note that enabling these disables link-time optimization in release and relwithdebinfo.

In an initial build, sanitizers are not enabled. This state may be restored by:

just clean
just clean release
just clean all

Additional optiization or warning flags can be passed in anywhere in the command. -w is special, and disables all compiler warnings in the REPL. These flags are set in the CMake cache, except for -w, which must be used explicitly whenever it is desired.

just build -fomit-frame-pointer -fstrict-bool san -Wno-unused-variable
just build -w release

After --, remaining arguments are forwarded to the native build tool (ninja or make), the same way

just build release -- -n
just build -- -k 0

just ninja runs Ninja directly on the build.ninja file corresponding to the release mode. Ninja tools, such as -t graph or -t targets, can be passed in directly. Flags like -k 0 are passed through uninterrupted.

just ninja graph | dot -Tpng -ograph.png
just ninja release deps
just ninja targets
just ninja -k 0

Tools

libCat provides several developer utilities. They are organized in the cmake/ subdirectory.

The just recipes below use the same build modes as the build recipe. For many of them, build mode is not very relevant. Most are implemented as CMake targets, which have the same names prepended by a cat- namespace (e.g. cat-format, cat-syntax).

test

just test

Builds the selected mode and runs CTest. With no arguments, this runs the main UnitTests executable only.

test accepts the same build modes and sanitizer flags as build:

just test debug
just test release nosan
just test all san

Arguments choose CTest entries:

  • unit runs UnitTests.
  • gdb runs GdbPrettyPrinters.
  • arithmetic runs ArithmeticTypeCheck.

Use full to run every registered CTest entry in the selected mode. Use list to print the matching CTest entries without running them. full and test arguments cannot be combined.

just test full
just test list
just test arithmetic
just test unit gdb release

Pass -v to stream verbose CTest output, including ArithmeticTypeCheck progress. Arguments after -- are passed through to CTest.

just test -v
just test full -- --rerun-failed

format

just format

Runs clang-format in place on every libCat implementation source and public header. The files which changed are reported afterwards.

When not using mise, CMake automatically attempts to find an appropriate version of clang-format on PATH. Override its choice with -DCAT_CLANG_FORMAT_PATH=/path/to/clang-format.

format-check

just format-check

The dry-run variant of format primarily intended for CI. Performs the same comparison as cat-format, but instead of rewriting, it reports every file that would be reformatted. If one or more files are reported, then the script exits with an error code.

restyle-comments

just restyle-comments

clang-format does not aggressively merge erroneous line breaks in comments, which are common after mass refactoring. cat-restyle-comments performs more aggressive formatting of comments alone, including project comment spelling rules, but like clang-format it respects blank lines between paragraphs and URL coherence. The ordered style rules are documented at the top of scripts/restyle_prefix_comment_paragraphs.py.

restyle-comments-check

just restyle-comments-check

The dry-run variant, same idea as format-check, reports would restyle: ... for any file where comment restyling would change. Run restyle-comments to fix drift.

tidy

just tidy

Runs clang-tidy across every libCat translation unit via the run-clang-tidy driver, applying fix-its in place and re-formatting touched lines with the project’s .clang-format rules. Uses the build directory’s compile_commands.json, which the tidy target forces CMake to emit.

When not using mise, CMake automatically attempts to find an appropriate version of clang-tidy and run-clang-tidy on PATH. Override either with -DCAT_CLANG_TIDY_PATH=/path/to/clang-tidy or -DCAT_RUN_CLANG_TIDY_PATH=/path/to/run-clang-tidy.

tidy-check

just tidy-check

The dry-run variant of tidy primarily intended for CI. Runs the same clang-tidy pass with -warnings-as-errors=* so any diagnostic exits the script with an error code.

opt-report

just opt-report

Compiles a Clang optimization report and prints the opt-viewer.py invocation required to render the resulting record into an interactive HTML document. Optview2 is not distributed with LLVM, but it may be preferred with this output. CMake attempts to use the same version of this script assosciated with your Clang installation.

ir

just ir ll pow.cpp
just ir intel hello.cpp
just ir bc test_alloc
just ir ll examples/
just ir ii s tests/
just ir ll full
just ir s release fn=cat::pow src/

Generates intermediate representations on demand. Each translation unit gets its own <basename>-<kind> target (e.g. pow-ll, hello-s). Domain meta-targets src-<kind> / tests-<kind> / examples-<kind> aggregate one IR across a directory. full-<kind> covers every TU in the project. just ir requires both an IR kind and a selector. Neither has a default and just ir alone is rejected.

IR kinds:

  • ii - Preprocessed C++. fmt / no-fmt toggles clang-format post-processing (fmt by default since the Ninja-driven runs are parallel).
  • s - Disassembled object code via llvm-objdump (defaults to Intel syntax). intel and att are aliases that pin the syntax explicitly and imply s.
  • bc - LLVM bitcode (the -save-temps=obj byproduct).
  • ll - Textual LLVM IR (llvm-dis over .bc), unconditionally normalised to strip absolute paths and the toolchain version string so it diffs cleanly across machines.

Selectors (one or more):

  • filename.cpp or filename - resolves to the filename-<kind> per-TU target.
  • src / tests / examples - the per-domain meta-targets; expands to every libCat / tests / examples TU, respectively.
  • full - every TU in the project across all three domains.
  • Any target-name already known to CMake (e.g. unit_tests, hello).

The output layout mirrors the source tree:

build/<lib>/<config>/<basename>[-<func>].<ext>
build/runtime/Debug/_start.ii
build/string/Release/memset.s
build/tests/Debug/test_alloc.ll
build/examples/Release/hello.s

clang can still be used conventionally on the byproducts:

just ir nosan debug ii s tests/
just ir nosan debug examples/ ii
clang -c build/examples/Debug/hello.s -o hello.o
clang++ -std=gnu++26 build/examples/Debug/hello.ii -static -Lbuild/Debug -lcat -o hello

LLVM optimisation passes can be applied via pass…= – the argument implies ll and is forwarded to opt. pass=list prints every available pass. See [[https://llvm.org/docs/NewPassManager.html#invoking-opt][invoking opt]] in the LLVM docs.

just ir examples/ pass=instcombine,loop-unroll
just ir examples/ 'pass=function(instcombine),always-inline'

The fn argument narrows every requested IR to the matching symbols and is appended to the output filename as <basename>-<func>.<ext>. fn is a literal source-level identifier. The driver collects every defined symbol whose demangled name is shaped like <fn> followed by an overload ((...)), template parameter list (<...>), or LTO clone suffix (.cold, .0, …) and feeds them to the underlying tool: ll / bc via llvm-extract --func for each match. ii via a best-effort whole-word textual slice. s via per-symbol address-range disassembly (so weak / function-section symbols don’t get silently dropped by llvm-objdump --disassemble-symbols). The fn filter ANDs with the selector above.

just ir ll fn=cat::memset src/
just ir bc tests/ fn=cat::pow
just ir examples/ ll fn=cat::pow pass=instcombine

Anything after -- is forwarded verbatim to llvm-objdump and is only accepted when .s is the sole requested IR (any other kind alongside is rejected). Handy for adjusting how the disassembly renders – dropping leading addresses, re-adding raw bytes, ANSI colourisation, etc.

just ir intel examples/ fn=main -- --no-leading-addr --disassembler-color=on
just ir att examples/ -- --print-imm-hex

This is useful for manually inspecting macro expansion and optimization, among other purposes.

LLVM optimizations can be examined via the pass argument. pass=list prints every available pass. For more info, read https://llvm.org/docs/NewPassManager.html#invoking-opt in the LLVM documentation.

Clang flags can be forwarded to the underlying compile by writing them verbatim with a leading dash (the same way just build accepts them):

just ir ll pow.cpp -fno-inline -mllvm -inline-threshold=0

elf

just elf hello
just elf unit_tests release
just elf hello debug -v
just elf path/to/binary

Inspect a libCat-produced executable. Without -v, runs elfls for a compact program / section header summary. With -v, runs llvm-readelf -a --demangle --wide for the full ELF structure (symbol table, dynamic section, relocations, notes). The LLVM demangler is preferred over binutils c++filt because it recognises the newer Itanium ABI extensions Clang emits for concepts and requires-clauses, which readelf -C still leaves mangled.

The selector is either an executable name – resolved against the mode’s examples/ and tests/ subdirectories – or any path. If the named binary doesn’t yet exist in the build directory, the matching CMake target is built automatically before inspection, so just elf hello works on a fresh checkout. Passing an explicit path skips the build attempt and inspects whatever’s already there.

A mode token (debug / release / relwithdebinfo) selects the build directory. Defaults to the cached last mode.

syntax

just syntax

Runs build with -fsyntax-only. This is useful for quickly checking if code can compile, without waiting on LLVM codegen.

repl

just repl

Launches clang-repl with libCat preloaded so its headers and external linkage symbols resolve at JIT time. The wrapper reads the matching compile flags from the build’s compile_commands.json so the REPL parses the same C++26 / freestanding / SIMD-intrinsic dialect libCat was built against, and pre-loads the ASan runtime when sanitizers are on. This produces a clang-repl-libcat executable that can be called independantly, if desired.

For use in scripts, pass a one-shot snippet or readable path as the final argument, after any conventional clang-repl arguments.

just repl \
    '#include <cat/string> \
     auto _ = cat::println("Meow world!");'

# Meow world!

The input code or file must be in quotes. clang-repl flags are forwarded through automatically.

just repl "1 + 1"
just repl -Wno-unused-variable "1 + 1"
just repl -w
just repl san -fstrict-bool "bool b = 2;"
just repl build/Debug/examples/hello.cpp
just repl "1 + 1"

skip-main prevents evaluating main(). Use drop to evaluate the input, then stay in the REPL.

just repl drop skip-main build/Debug/examples/hello.cpp
# ...
clang-repl> main();

# Hello, world!

Piping code on stdin works the same way.

echo "1 + 1" | just repl release

# 2

The just repl recipe configures the build with CAT_BUILD_SHARED=ON before building the wrapper. Pass extra clang-repl arguments at the end of the command, after a --.

status

just status
just status release -v

Retrieve the current compile configuration of a release mode. This extracts state from CMake and prints it in a digestible summary. -v can be used to print an elaborate description of the build flags, retrieved from the clang++ CLI. Non-verbose output skips empty sections. This compile configuration can be changed by just build flags.

For an example configuration:

just status release
# CAT_USE_SANITIZERS: OFF (cached)
# CMAKE_CXX_COMPILER: ./.cache/cat-llvm/bin/clang++
# CMAKE_LINKER_TYPE: LLD
# ISA: default (x86_64-pc-linux-gnu)
# Language: gnu++26 nostdlib nostdlib++
# #define: CMAKE_INTDIR="Release" NDEBUG
# Inclusion: include=global_includes.hpp
# Exceptions: no-exceptions no-unwind-tables no-asynchronous-unwind-tables
# RTTI: no-rtti
# Warnings: all extra invalid-pch
# Disabled warnings: unused-function unknown-pragmas missing-braces unqualified-std-cast-call main redundant-consteval-if gnu
# Lifetime/Analysis: dangling dangling-gsl experimental-bounds-safety
# Features: no-plt no-pch-timestamp
# Architecture: sse4.2 avx2 fma lzcnt bmi bmi2 fsgsbase
# Optimization: O3
# LTO: function-sections data-sections lto=auto
# Visibility: visibility=hidden visibility-inlines-hidden
# Link options: nostdlib z=noseparate-code -gc-sections T=src/libcat.ld
# Link libraries: build/Release/libcat.a

# Run just status -v to see all fields or flag descriptions.
  
just status debug -v
# CAT_USE_SANITIZERS: OFF (cached)
# CMAKE_CXX_COMPILER: /home/lgooch/libCat/.cache/cat-llvm/bin/clang++
# CMAKE_LINKER_TYPE: LLD
# ISA: default (x86_64-pc-linux-gnu)
# Language:
#   -std=gnu++26: Language standard to compile for
#   -nostdlib: n/a
#   -nostdlib++: n/a
# C++ modules:
# #define:
#   -DCMAKE_INTDIR="Debug"
# Inclusion:
#   -include=global_includes.hpp: Include file before parsing
# Exceptions:
#   -fno-exceptions: Disable support for exception handling
#   -fno-unwind-tables: n/a
#   -fno-asynchronous-unwind-tables: n/a
# RTTI:
#   -fno-rtti: Disable generation of rtti information
# Warnings:
#   -Wall: Enable the specified warning
#   -Wextra: Enable the specified warning
#   -Winvalid-pch: Enable the specified warning
# ...

cat-gdb-tests

This target does not require manual invocation. CMake symlinks a .gdbinit configuration that loads GDB pretty printers from gdb_pretty_printers/cat_printers.py to every CMake build directory automatically, granted that the user’s GDB is configured with set auto-load local-gdbinit on. It is registered with ctest.

Other build options

All build options are CMake cache variables; pass them with -D at configure time.

PCH and caching

  • CAT_PCH (default ON) - Enable precompiled headers for libCat’s internal build. Private to libCat; downstream consumers of cat keep their own PCH strategy intact whether this is on or off.
  • CAT_USE_SCCACHE (default: ON if sccache is on PATH, else OFF) Use sccache as the C++ compiler launcher. Combined with -Xclang -fno-pch-timestamp (added automatically for Clang + PCH builds), this gives near-100% hit rates across build directories and fresh checkouts. Override the binary with -DCAT_SCCACHE_EXECUTABLE=/path/to/sccache.

Library output

  • CAT_BUILD_SHARED (default OFF) - Also build libCat as a shared library alongside the default static archive. Required for the cat-repl target. Off by default so the build stays cheap when nobody needs the shared form.
  • CAT_USE_SHARED (default OFF) - Link cat against the shared library instead of the static archive, so every in-tree consumer and downstream find_package(cat) user that links cat transparently picks it up. Implies CAT_BUILD_SHARED.

Tests and examples

  • CAT_BUILD_UNIT_TESTS (default ON) - Compile the unit_tests binary registered with ctest as UnitTests.
  • CAT_BUILD_ALL_EXAMPLES (default ON) - Shortcut that compiles every individual example below.
  • CAT_BUILD_EXAMPLE_HELLO / _ECHO / _CLIENT_SERVER / _WINDOW / _CAT (default OFF) - Per-example opt-ins for fine-grained selection when CAT_BUILD_ALL_EXAMPLES=OFF.
  • CAT_BUILD_LIBC_EXAMPLES (default OFF) - Build the libc-linked comparison binaries (hello_libc, memcpy_libc, …) used to A/B performance against the freestanding libCat versions.

Tooling overrides

  • CAT_LIBCAT_JOBS - Worker count for cat-format, cat-restyle-comments, and cat-restyle-comments-check (capped at 4 in the scripts to limit RAM use, e.g. on WSL2). Use 1 or 2 for tighter machines, or the script -j flag, which is also capped at 4.
  • CAT_CLANG_FORMAT_PATH - Path to a specific clang-format binary (defaults to whatever cmake auto-discovers on PATH). Only consulted by the cat-format / cat-format-check targets. cat-restyle-* is separate, uses find_program for python3, and has no -D override.
  • CAT_CLANG_TIDY_PATH / CAT_RUN_CLANG_TIDY_PATH - Paths to specific clang-tidy and run-clang-tidy binaries (default to auto-discovery on PATH). Only consulted by the cat-tidy / cat-tidy-check targets.
  • CAT_SCCACHE_EXECUTABLE - Path to a specific sccache binary (defaults to find_program on PATH). Only consulted when CAT_USE_SCCACHE=ON.

About

🐈‍⬛ A runtime for C++26 w/out libC or POSIX. Smaller binaries, only arena allocators, SIMD, stronger type safety than STL, and value-based errors!

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Contributors