fix: toolchain - update docs

This commit is contained in:
2026-08-07 16:27:09 +02:00
parent 53d3e6c568
commit b77449faeb
2 changed files with 89 additions and 9 deletions
+78 -4
View File
@@ -38,9 +38,80 @@ What the target does by default:
wanted. wanted.
- Defines `__montauk__` / `__MONTAUK__`. - Defines `__montauk__` / `__MONTAUK__`.
C++ works with `-fno-exceptions -fno-rtti` (the project convention). The in-tree programs are built `-fno-exceptions -fno-rtti` (the project
There is no libstdc++ yet — only libgcc is built. A hosted libstdc++ convention), but that is a project style choice, not a toolchain limit —
needs a more complete libc and is the next step toward self-hosting. see the STL section below.
## C++ standard library (hosted libstdc++)
`build-montauk-toolchain.sh` builds and installs a **hosted** libstdc++
(`all-target-libstdc++-v3`), so the full STL is available to
`x86_64-montauk-g++` with no special flags:
```bash
toolchain/local/bin/x86_64-montauk-g++ app.cpp -o app.elf
```
Headers live in `toolchain/local/x86_64-montauk/include/c++/14.2.0/`,
the library in `toolchain/local/x86_64-montauk/lib/libstdc++.a`.
`_GLIBCXX_HOSTED` follows `__STDC_HOSTED__`, i.e. 1 for ordinary
compiles.
Note that `toolchain/sysroot/usr/include/` *also* holds a freestanding
C++ subset (copied from `kernel/freestnd-cxx-hdrs`, which is what the
kernel and the in-tree programs build against). That directory is last
on the include search path, so for a plain `g++` invocation the hosted
headers win and the freestanding copies are shadowed. Check
`g++ -x c++ -E -v -` if you ever need to confirm the order.
### What works
Link-verified with the cross compiler, no flags beyond the source file:
| Feature | Status |
|---------|--------|
| `<string>`, `<vector>`, `<map>`, `<algorithm>` | works |
| `<memory>` (`unique_ptr`, `shared_ptr`, `make_*`) | works |
| Exceptions (`throw` / `catch`, `std::exception`) | works |
| RTTI (`typeid`, `dynamic_cast`) | works |
| `<iostream>`, `<sstream>` | works (links a large binary — see below) |
| Static constructors | works (`crt1.c` runs `.init_array`) |
`toolchain/files/cxx-test.cpp` is the on-OS regression check for this;
`make devkit` compiles it to `0:/sdk/bin/cxx-test.elf`. Run it on the
target to confirm the above at **runtime** rather than just at link
time — that is the authoritative check, since a clean link does not by
itself prove exception unwinding works on hardware.
### What does not work
- **Threads.** libstdc++ is built `gthr-single`
(`_GLIBCXX_HAS_GTHREADS` is undefined), because the libc has no
pthreads. `std::thread`, `std::mutex`, `std::condition_variable`,
`std::async` and `std::call_once` do not exist — code using them
fails to *compile*, not to link.
Consequence worth knowing: in single-threaded mode libstdc++ selects
the **non-atomic** `shared_ptr` refcount path. The kernel does have
real threads (`SYS_THREAD_SPAWN/EXIT/JOIN/SELF`,
`programs/include/montauk/thread.h`), so it is possible to spawn
threads and share a `shared_ptr` across them — that will corrupt the
refcount and is a silent use-after-free. Do not do it until a
pthreads/gthreads shim exists and libstdc++ is rebuilt against it.
- **`<iostream>` is expensive.** It drags in the static locale and
iostream init machinery; a hello-world using it links to roughly
5.7 MB versus ~1.8 MB for `<string>` + `<vector>`. Prefer `<cstdio>`
where binary size matters, which on a ramdisk image is most places.
- **`libm.a` is an empty stand-in.** The math functions live inside
`libc.a` (the g++ driver links `-lm` unconditionally, hence the empty
archive). Roughly 50 functions are present — `sqrt`, `sin`, `cos`,
`pow`, `log`, `exp`, `atan2`, `hypot`, `floor`, `ceil`, `fmod`,
`round` and some `float` variants. Absent: `fma`, `cbrt`, `expm1`,
`log1p`, `erf`, `tgamma`, `nearbyint`, `remquo`, most remaining
`float` variants, and all `long double` variants. Ports that need
these have to add them to `programs/lib/libc/libc.c`.
## Refreshing the sysroot ## Refreshing the sysroot
@@ -146,7 +217,10 @@ Notes:
- The `devkit` target in programs/GNUmakefile ships the staged tools - The `devkit` target in programs/GNUmakefile ships the staged tools
into the OS image at 0:/sdk/bin (as.elf, ld.elf, ar.elf, ...) with a into the OS image at 0:/sdk/bin (as.elf, ld.elf, ar.elf, ...) with a
target-side sysroot at 0:/sdk/include + 0:/sdk/lib (libc.a, crt target-side sysroot at 0:/sdk/include + 0:/sdk/lib (libc.a, crt
objects, empty libm/libstdc++) - the Montauk SDK. The kernel objects, the real hosted libstdc++.a plus its c++/ headers, and an
empty libm stand-in) - the Montauk SDK. On-OS g++ therefore gets the
same STL as the cross compiler; see the C++ standard library section
above for what is and is not supported. The kernel
resolves driveless absolute paths ("/sdk/lib") against the cwd resolves driveless absolute paths ("/sdk/lib") against the cwd
drive, so the --prefix=/sdk layout works natively. The shell drive, so the --prefix=/sdk layout works natively. The shell
searches 0:/sdk/bin and tab-completes it. Future ports configure searches 0:/sdk/bin and tab-completes it. Future ports configure
+11 -5
View File
@@ -121,8 +121,12 @@ cp -r "${REPO_DIR}/programs/include/libc/." "${SYSROOT}/usr/include/"
cp -r "${REPO_DIR}/programs/include/montauk" "${SYSROOT}/usr/include/montauk" cp -r "${REPO_DIR}/programs/include/montauk" "${SYSROOT}/usr/include/montauk"
cp -r "${REPO_DIR}/programs/include/Api" "${SYSROOT}/usr/include/Api" cp -r "${REPO_DIR}/programs/include/Api" "${SYSROOT}/usr/include/Api"
# Freestanding C++ headers (<cstdint>, <type_traits>, <atomic>, ...) — # Freestanding C++ headers (<cstdint>, <type_traits>, <atomic>, ...) —
# there is no hosted libstdc++ yet, so C++ programs get the same # the subset the kernel and the in-tree programs build against. This is
# freestanding subset the rest of the tree builds against. # NOT the whole C++ story: a hosted libstdc++ is built further down and
# installs its own headers under ${PREFIX}/x86_64-montauk/include/c++.
# That directory comes first on the include path and this one comes
# last, so a plain g++ invocation gets the hosted headers and these are
# shadowed. See "C++ standard library" in README.md.
cp -r "${REPO_DIR}/kernel/freestnd-cxx-hdrs/x86_64/include/." "${SYSROOT}/usr/include/" cp -r "${REPO_DIR}/kernel/freestnd-cxx-hdrs/x86_64/include/." "${SYSROOT}/usr/include/"
cp "${REPO_DIR}/programs/lib/libc/crt1.o" \ cp "${REPO_DIR}/programs/lib/libc/crt1.o" \
"${REPO_DIR}/programs/lib/libc/crti.o" \ "${REPO_DIR}/programs/lib/libc/crti.o" \
@@ -161,9 +165,11 @@ else
fi fi
# ── Build GCC ───────────────────────────────────────────────────────────────── # ── Build GCC ─────────────────────────────────────────────────────────────────
# Hosted against the Montauk libc headers in the sysroot. Only the # Hosted against the Montauk libc headers in the sysroot. Builds the
# compiler and libgcc are built; libstdc++ needs a more complete libc # compiler, libgcc, and a hosted libstdc++ (full STL: string/vector/map,
# (that is the self-hosting step, not the cross-toolchain step). # exceptions, RTTI, iostream). libstdc++ is built gthr-single because
# the libc has no pthreads, so <thread>/<mutex> are absent and
# shared_ptr uses non-atomic refcounts. See README.md.
if [[ -x "${PREFIX}/bin/${TARGET}-gcc" ]]; then if [[ -x "${PREFIX}/bin/${TARGET}-gcc" ]]; then
bold "GCC already installed — skipping." bold "GCC already installed — skipping."
else else