libc++ Runtime

libc.so` is the SaltyOS C runtime. It is built from `toolchain/llvm-project/ (a git submodule containing the upstream LLVM tree) by lib/basalt/cpp/meson.build (309 lines), which compiles 48 libcxx sources, 18 libcxxabi sources, and 6 libunwind sources directly via Meson custom_target rules without using LLVM’s CMake build at all. This page covers the artifact composition, what each LLVM project contributes, the integration with basaltc, and the configuration choices that distinguish SaltyOS’s libc++ from a stock LLVM build.

Artifacts

basalt produces a single shared library: libc++.so. Inside it are static-linked versions of three LLVM components:

Component Source Provides

libcxx

toolchain/llvm-project/libcxx/src/

The C++ standard library: <vector>, <string>, <map>, <unordered_map>, <thread>, <mutex>, <filesystem> (limited), <chrono>, <charconv>, iostreams, etc.

libcxxabi

toolchain/llvm-project/libcxxabi/src/

The Itanium C++ ABI: cxa_atexit, cxa_throw, __cxa_demangle, RTTI / typeinfo, exception personality functions, vtable destructors.

libunwind

toolchain/llvm-project/libunwind/src/

Stack unwinding for C++ exceptions: _Unwind_RaiseException, _Unwind_Resume, DWARF CFI parsing, register save/restore.

The "static linked" part is significant: libc++.so does not have a DT_NEEDED entry for a separate libcxxabi.so or libunwind.so. All three components live inside the one .so file. This is controlled by LIBCXX_STATICALLY_LINK_ABI_IN_SHARED_LIBRARY=ON in the upstream LLVM build, mirrored by basalt’s per-source Meson rules.

libc++.so does have DT_NEEDED entries for libc.so and libtrona.so, plus statically links the compiler-rt builtins for 128-bit float helpers used by <charconv>.

Diagram

Source Counts

The exact source lists from lib/basalt/cpp/meson.build:

libcxx (48 sources)

Algorithm and utility

algorithm.cpp, any.cpp, bind.cpp, expected.cpp, functional.cpp, hash.cpp, optional.cpp, valarray.cpp, variant.cpp, vector.cpp, verbose_abort.cpp

Threading

atomic.cpp, barrier.cpp, call_once.cpp, condition_variable.cpp, condition_variable_destructor.cpp, future.cpp, mutex.cpp, mutex_destructor.cpp, shared_mutex.cpp, thread.cpp

I/O

ios.cpp, ios.instantiations.cpp, iostream.cpp, ostream.cpp, print.cpp, string.cpp

Locale

locale.cpp

Charconv (Ryu float to string)

charconv.cpp, ryu/d2fixed.cpp, ryu/d2s.cpp, ryu/f2s.cpp

Memory

memory.cpp, memory_resource.cpp, new_handler.cpp, new_helpers.cpp

Filesystem (limited)

filesystem/filesystem_clock.cpp, filesystem/filesystem_error.cpp, filesystem/path.cpp — only the path manipulation, clock, and error pieces. Excluded: directory_entry.cpp, directory_iterator.cpp, operations.cpp, int128_builtins.cpp — because the underlying VFS scanning operations are not yet bridged.

Other

chrono.cpp, error_category.cpp, exception.cpp, random.cpp, random_shuffle.cpp, regex.cpp, stdexcept.cpp, system_error.cpp, typeinfo.cpp

libcxxabi (18 sources)

abort_message.cpp, cxa_aux_runtime.cpp, cxa_default_handlers.cpp, cxa_demangle.cpp, cxa_exception.cpp, cxa_exception_storage.cpp, cxa_guard.cpp, cxa_handlers.cpp, cxa_personality.cpp, cxa_thread_atexit.cpp, cxa_vector.cpp, cxa_virtual.cpp, fallback_malloc.cpp, private_typeinfo.cpp, stdlib_exception.cpp, stdlib_new_delete.cpp, stdlib_stdexcept.cpp, stdlib_typeinfo.cpp

stdlib_new_delete.cpp provides operator new / operator delete (replacing libcxx’s new.cpp, which is excluded). The implementations call basaltc’s malloc / free.

libunwind (6 sources)

C++

libunwind.cpp, Unwind-EHABI.cpp

C

UnwindLevel1.c, UnwindLevel1-gcc-ext.c

Assembly

UnwindRegistersRestore.S, UnwindRegistersSave.S

Compiler-rt Builtins

<charconv> uses 128-bit floating-point arithmetic helpers (lttf2, trunctfdf2, etc.) that are implemented by compiler-rt’s libclang_rt.builtins-<arch>.a. The Meson build locates this archive at build time:

_clang_rt_builtins = run_command(
  cc_cmd[0], '--target=' + c_target, '-print-libgcc-file-name',
  capture: true, check: true,
).stdout().strip()

and statically links it into libc++.so:

libcxx_so = custom_target('libcxx_so',
  input: _all_objs,
  ...
  command: [cc_cmd, ..., '@INPUT@', _clang_rt_builtins, ...],
)

This follows the Fuchsia approach of bundling compiler-rt builtins per-shared-library rather than relying on a system-wide compiler-rt installation.

Build Without LLVM CMake

Upstream LLVM uses CMake to build libcxx, libcxxabi, and libunwind through the runtimes-build mechanism. basalt avoids CMake entirely. The reason: the LLVM CMake build pulls in the entire LLVM project, has its own option system, and produces artifacts in a layout that does not match the SaltyOS sysroot. basalt’s per-source Meson custom_target approach is more verbose but gives complete control over flags, include paths, and output locations.

The trade-off is that any new libcxx source (added in upstream LLVM) requires editing _cxx_srcs in lib/basalt/cpp/meson.build. This happens rarely — libcxx adds new sources every few minor versions of LLVM — and is straightforward to update by reading the upstream LLVM CMake list.

Submodule Detection

lib/basalt/meson.build checks whether the LLVM submodule is present before building libc++:

_libcxx_opt = get_option('build_libcxx')
_libcxx_src_dir = meson.project_source_root() / 'toolchain' / 'llvm-project' / 'libcxx' / 'src'
_build_libcxx = (_libcxx_opt == 'true') or
                (_libcxx_opt == 'auto' and fs.is_dir(_libcxx_src_dir))
if _libcxx_opt == 'true' and not fs.is_dir(_libcxx_src_dir)
  error('build_libcxx=true but toolchain/llvm-project/libcxx/src/ not found.')
endif
if _build_libcxx
  subdir('cpp')
endif

build_libcxx=auto (the default) means: build libc if the submodule is present, skip otherwise. This lets users check out SaltyOS without the heavy LLVM submodule (a fresh checkout is several gigabytes lighter without it) and still get a working `libc.so`, just without `libc.so`.

build_libcxx=true requires the submodule and errors out if missing. build_libcxx=false always skips, even if the submodule is present.

Integration with basaltc

libc++.so depends on libc.so via DT_NEEDED. The dependencies are:

  • malloc / free — provided by basaltc, called by operator new / operator delete in stdlib_new_delete.cpp.

  • memcpy / memmove / memset / memcmp / strlen / strcmp — provided by basaltc, used throughout libcxx.

  • pthread_* — provided by basaltc, used by <mutex>, <thread>, <condition_variable>.

  • __cxa_atexit — provided by basaltc in crt.rs, called by libcxxabi and by C++ static initializers. See atexit and Process Exit.

  • abort — provided by basaltc, called by libcxxabi’s verbose abort path.

  • getentropy — provided by basaltc, used by <random>’s `random_device (_LIBCPP_USING_GETENTROPY).

  • dl_iterate_phdr — provided by basaltc, used by libunwind to find DSO program headers.

__assertion_handler

lib/basalt/cpp/__assertion_handler is a small header that defines the function libcxx calls when an assertion fails. It is included via -include in the build flags so every libcxx source sees the same handler. basalt’s handler logs the assertion message via libtrona’s debug output and calls abort.

This is also where you would add additional debug behavior (stack traces, log file writes) if needed.

Output Layout

After the build, libc.so` is in `build-<arch>/lib/basalt/cpp/libc.so. The rootfs build (tools/mkrootfs) copies it into the system’s /usr/lib/ so the runtime dynamic linker can find it.