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 |
|
The C++ standard library: |
libcxxabi |
|
The Itanium C++ ABI: |
libunwind |
|
Stack unwinding for C++ exceptions: |
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>.
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.
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 byoperator new/operator deleteinstdlib_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 incrt.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.
Related Pages
-
libc++ Configuration — the
__config_siteflags that pin SaltyOS-specific behavior -
libcxxabi and libunwind — the C++ ABI and unwinder details
-
Build System — the Meson custom_target machinery
-
atexit and Process Exit —
__cxa_atexitintegration