Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
status: open
kind: defect
opened: 2026-10-01
---

# A shared object does not link against the C sysroot with `-z defs`

ToyOS loads shared objects, a program's `DT_NEEDED` and `dlopen`'s, and its
clang links one with `-shared`. None links against the C sysroot with
`-z defs`. A C one linked without it leaves `__tls_get_addr` undefined, and
lld refuses the executable that links it for that name. LLVM built for
`x86_64-unknown-toyos`
(`issues/build/bootstrap-cannot-build-llvm-clang-and-lld-for-a-toyos-host.md`)
builds `libLTO`, `libRemarks`, `libclang` and `libclang-cpp`, linked
`-shared -z defs`, and each stops on what the sysroot's archives were built
for, an executable:

- `main`, undefined: `libtoyos_c.a`'s entry point, `start_c`, sits in an
object the link takes for other names.
- `__tls_get_addr`, undefined: a shared object reaches a thread-local, its own
or libc's, through it, and only std defines it.
- `R_X86_64_TPOFF32` against libc++abi's `eh_globals`, and `R_X86_64_PC32`
against libc++'s vtables: the C++ runtime is compiled for an executable
(`issues/build/a-rust-std-binary-cannot-link-the-cxx-runtime.md`).

**Exit**: a C and a C++ shared object link against the C sysroot with
`-z defs`, LLVM's four build for ToyOS with their options on, and a guest case
`dlopen`s the C one and calls into it.
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,32 @@ opened: 2026-09-30

# Bootstrap cannot build LLVM, clang and lld for a ToyOS host

M2's clang and lld (`issues/build/toyos-builds-itself.md`) are LLVM built for
`x86_64-unknown-toyos`. Read from the fork at `rust/` and from
`src/llvm-project` at `849da7d62`, not run, four things stop bootstrap's LLVM
step for that target:
M2's clang and lld (`issues/build/toyos-builds-itself.md`) are bootstrap's
`Llvm` step, with `clang = true`, and its `Lld` step for
`x86_64-unknown-toyos`, in a bootstrap build that names no `llvm-config` for
the build triple and so builds that triple's LLVM, and its `clang-tblgen`,
itself. CMake takes its toolchain file from
`CMAKE_TOOLCHAIN_FILE_x86_64_unknown_toyos`: one that includes the C sysroot's
`toolchain.cmake` and adds the ToyOS LLVM's install to `CMAKE_FIND_ROOT_PATH`,
because the `Lld` step names that LLVM to `find_package` by a hint, and the
sysroot's file has CMake find a package under a root alone. What stops the
build, in the order it stops it:

- **`clang-tblgen`.** With `clang = true`, the step for a target that is not
the host panics unless `clang-tblgen` is in the CMake build directory of a
host LLVM bootstrap built itself
(`src/bootstrap/src/core/build_steps/llvm.rs`, `CLANG_TABLEGEN`). Every
compiler build here names the store's LLVM (`src/llvm.rs`) as the host's
`llvm-config`, so bootstrap builds no host LLVM and the file is never there.
- **Its CMake system.** `configure_cmake` names no system for a ToyOS target and
falls back to `Generic`, under which LLVM sets `LLVM_ON_UNIX` to 0
(`llvm/cmake/modules/HandleLLVMOptions.cmake`) and compiles no `Unix/`
implementation of `Support`.
- **`bit.h`.** `llvm/include/llvm/ADT/bit.h` includes `<endian.h>` on the
systems it lists and `<machine/endian.h>` on any other, ToyOS among them.
- **`is_local_impl`.** `llvm/lib/Support/Unix/Path.inc` reads the BSDs'
`MNT_LOCAL` on a system it does not list.
- **Compile.** The first errors are `Support`'s: `Unix/Watchdog.inc` and
`Unix/Program.inc` call `alarm` (`issues/build/libc-has-no-alarm.md`), and
`Program.inc` stage 3's `wait` and `wait4`
(`issues/kernel/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md`).
Then ORC's `shm_open` and `shm_unlink`, the interpreter's `scanf` and
`llvm-objdump`'s `ctime`
(`issues/build/libc-lacks-names-llvm-for-a-toyos-host-calls.md`), and
clang's `std::ifstream`, which libc++ has only with `std::filesystem`
(`issues/build/libcxx-is-built-without-std-filesystem.md`).
- **Link.** clang needs `lround`, which libc does not define
(`issues/build/libc-lacks-names-llvm-for-a-toyos-host-calls.md`), beside
those above. LLVM's shared libraries, `libLTO`, `libRemarks`, `libclang` and
`libclang-cpp`, do not link against the C sysroot
(`issues/build/a-shared-object-does-not-link-against-the-c-sysroot-with-z-defs.md`).
clang and lld use none of them, and ToyOS's build turns them off.

The last three are ToyOS arms at existing dispatch sites, written as upstream
would take them. ToyOS joins `bit.h`'s `<endian.h>` list, so libc carries
POSIX's `endian.h` (`userland/libc/include/endian.h`) and no BSD name. With
those two arms on `849da7d62` and CMake told the system by hand, the 71
libraries rustc links compile against the C sysroot but for objects of
`LLVMSupport`; without them, every object that includes `bit.h` stops.
Bootstrap's arm names the system `ToyOS`, which LLVM's configure refuses,
`Unable to determine platform`, until CMake knows ToyOS and sets `UNIX`
(`issues/build/the-cxx-runtime-names-toyos-to-cmake-as-unix.md`).

`clang-tblgen` is no arm, and neither bootstrap nor LLVM changes for it: ToyOS's
build builds the LLVM for a ToyOS host in a bootstrap build that built the
host's LLVM itself, as `src/llvm.rs` builds the store's, never in a compiler
build that names the store's.

**Exit**: bootstrap, with `clang = true`, installs a clang and an `ld.lld` for
`x86_64-unknown-toyos`.
**Exit**: ToyOS's build, with nothing supplied by hand, has bootstrap install
a clang and an `ld.lld` for `x86_64-unknown-toyos`.
16 changes: 16 additions & 0 deletions issues/build/cmake-ships-no-platform-module-for-toyos.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
status: open
kind: tooling
opened: 2026-10-01
---

# CMake ships no platform module for ToyOS, and each C sysroot carries one

CMake knows a system by its `Modules/Platform/<name>.cmake` and ships none for
ToyOS. Each C sysroot carries `Platform/ToyOS.cmake` and
`Platform/ToyOS-Initialize.cmake` beside the `toolchain.cmake` that puts them
on CMake's module path (`src/clang.rs`, `CMAKE`). No upstream merge request
carries them, because none is sent for now.

**Exit**: CMake ships ToyOS's platform module, and the C sysroot carries only
its toolchain file.
23 changes: 23 additions & 0 deletions issues/build/libc-lacks-names-llvm-for-a-toyos-host-calls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
status: open
kind: defect
opened: 2026-10-01
---

# libc lacks names LLVM for a ToyOS host calls

LLVM, clang and lld built for `x86_64-unknown-toyos`
(`issues/build/bootstrap-cannot-build-llvm-clang-and-lld-for-a-toyos-host.md`)
call five names libc neither declares nor defines:

- `shm_open` and `shm_unlink`, with which ORC maps memory on every Unix but
Android (`llvm/lib/ExecutionEngine/Orc/MemoryMapper.cpp`,
`TargetProcess/ExecutorSharedMemoryMapperService.cpp`).
- `scanf`, which LLVM's interpreter hands an interpreted program
(`llvm/lib/ExecutionEngine/Interpreter/ExternalFunctions.cpp`).
- `ctime`, with which `llvm-objdump` prints a file's time stamp.
- `lround`, which libc++'s `std::lround` calls, from clang's `Basic` and its
static analyzer: clang does not link without it.

**Exit**: libc declares and defines each, doing what POSIX says or refusing in
POSIX's form, asserted by a guest C case that reads its effect back.
8 changes: 5 additions & 3 deletions issues/build/libcxx-is-built-without-std-filesystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ opened: 2026-09-30
# libc++ for ToyOS is built without `std::filesystem`

`src/libcxx.rs` configures the C++ runtime with `LIBCXX_ENABLE_FILESYSTEM=OFF`,
so a ToyOS C++ program that includes `<filesystem>` does not compile. libc++'s
filesystem is written on the POSIX directory, `stat` and path calls, and libc
has no `dirent.h`.
so a ToyOS C++ program that includes `<filesystem>` does not compile, and
`<fstream>` goes with the option: `std::ifstream` and `std::ofstream` are
undefined templates. Built with it, libc++ asks libc for `setbuf`, `fseeko`,
`ftello`, `utimes`, `truncate`, `pathconf`, `openat`, `unlinkat`, `fdopendir`,
`_PC_PATH_MAX`, `O_DIRECTORY`, `O_NOFOLLOW`, `AT_FDCWD` and `AT_REMOVEDIR`.

**Exit**: libc carries what libc++'s `src/filesystem` calls, the option goes,
and a guest test lists a directory through `std::filesystem`.
20 changes: 0 additions & 20 deletions issues/build/the-cxx-runtime-names-toyos-to-cmake-as-unix.md

This file was deleted.

10 changes: 3 additions & 7 deletions issues/build/toyos-builds-itself.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,9 @@ goes when lld runs in the guest: the row, the crate and its host tests go togeth
the published crates.io crate is yanked.

**What stops M2: LLVM, clang and lld built for a ToyOS host**, in the order
each blocks the next.
- Configure: the `clang-tblgen` and the CMake system of
`issues/build/bootstrap-cannot-build-llvm-clang-and-lld-for-a-toyos-host.md`.
- Compile: the bootstrap issue's `bit.h` and `is_local_impl`, and `wait` and
`wait4` of the child-process track's stage 3
with `issues/build/libc-has-no-alarm.md`.
- Link: the same stage 3 names and `alarm`.
each blocks the next, as
`issues/build/bootstrap-cannot-build-llvm-clang-and-lld-for-a-toyos-host.md`
measures it.
- Run: what `issues/build/libc-refuses-what-toyos-cannot-yet-answer.md` lists,
`issues/build/libc-has-no-pread-or-pwrite.md`, and
`issues/build/libc-stat-answers-one-serial-number-for-every-file.md`.
Expand Down
2 changes: 1 addition & 1 deletion rust
Submodule rust updated 1 files
+1 −1 src/llvm-project
60 changes: 60 additions & 0 deletions src/clang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,47 @@ pub(crate) fn tools() -> impl Iterator<Item = &'static str> {
TOOLS.into_iter().chain(host_triple().ends_with("apple-darwin").then_some(APPLE_STRIP))
}

/// CMake's description of ToyOS, which CMake does not ship, as every C sysroot
/// carries it: a toolchain file that names the system, the target and the
/// sysroot it sits in, has CMake find a library, a header and a package there
/// alone and a program on the host alone, and puts beside it on CMake's module
/// path the platform module, laid out as CMake's `Modules/Platform` lays out a
/// Unix-like system. The compilers are the caller's, and so is the root of any
/// package it built outside the sysroot (`CMAKE_FIND_ROOT_PATH`). `@PROCESSOR@`
/// and `@TARGET@` are the sysroot's.
pub(crate) const CMAKE: [(&str, &str); 3] = [
(
"toolchain.cmake",
"set(CMAKE_SYSTEM_NAME ToyOS)\n\
set(CMAKE_SYSTEM_PROCESSOR @PROCESSOR@)\n\
set(CMAKE_SYSROOT \"${CMAKE_CURRENT_LIST_DIR}\")\n\
set(CMAKE_C_COMPILER_TARGET @TARGET@)\n\
set(CMAKE_CXX_COMPILER_TARGET @TARGET@)\n\
set(CMAKE_ASM_COMPILER_TARGET @TARGET@)\n\
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)\n\
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\n\
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\n\
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)\n\
list(APPEND CMAKE_MODULE_PATH \"${CMAKE_CURRENT_LIST_DIR}\")\n",
),
("Platform/ToyOS-Initialize.cmake", "set(UNIX 1)\n"),
// `dlopen` and its kin are the C library's own. The loader finds a library
// by the name `DT_NEEDED` holds, beside its executable or in `/system/lib`,
// so a library carries its soname and one without is linked by name; and it
// reads no runtime path, so no flag asks the linker for one. The flags that
// switch a link between static and shared libraries and the `RESCAN` link
// group are left out too: no build of ToyOS's asks CMake for either.
(
"Platform/ToyOS.cmake",
"set(CMAKE_DL_LIBS \"\")\n\
set(CMAKE_SHARED_LIBRARY_SONAME_C_FLAG \"-Wl,-soname,\")\n\
set(CMAKE_EXE_EXPORTS_C_FLAG \"-Wl,--export-dynamic\")\n\
set(CMAKE_PLATFORM_USES_PATH_WHEN_NO_SONAME 1)\n\
\n\
include(Platform/UnixPaths)\n",
),
];

/// `lib/rustlib/<host>/bin` of `toolchain`, where `rust-lld` is.
fn bin(toolchain: &Path) -> PathBuf {
toolchain.join("lib/rustlib").join(host_triple()).join("bin")
Expand All @@ -64,6 +105,8 @@ pub struct CSysroot {
pub ar: PathBuf,
/// The guest target, as clang's `--target` spells it.
pub target: &'static str,
/// Its architecture, as CMake's `CMAKE_SYSTEM_PROCESSOR` spells it.
processor: &'static str,
}

impl CSysroot {
Expand All @@ -75,6 +118,23 @@ impl CSysroot {
clang: bin(toolchain).join("clang"),
ar: bin(toolchain).join("llvm-ar"),
target,
processor: arch.name(),
}
}

/// Its CMake toolchain file, the first of [`CMAKE`].
pub fn cmake_toolchain(&self) -> PathBuf {
self.dir.join(CMAKE[0].0)
}

/// Write [`CMAKE`] into it.
pub fn write_cmake(&self) {
for (name, text) in CMAKE {
let path = self.dir.join(name);
let parent = path.parent().expect("a file in the sysroot");
fs::create_dir_all(parent).unwrap_or_else(|e| panic!("create {}: {e}", parent.display()));
let text = text.replace("@PROCESSOR@", self.processor).replace("@TARGET@", self.target);
fs::write(&path, text).unwrap_or_else(|e| panic!("write {}: {e}", path.display()));
}
}

Expand Down
1 change: 1 addition & 0 deletions src/libc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ pub fn build_c(root: &Path, toolchain: &Path, target_dir: &Path, arch: Arch) {
.unwrap_or_else(|e| panic!("copy {} into {}: {e}", archive.display(), lib.display()));
empty_libraries(&lib);
crate::sysroot::clone_tree(&root.join(CRATE).join("include"), &c.dir.join("include"));
c.write_cmake();
links_naming_every_library(&c, target_dir);
}

Expand Down
19 changes: 6 additions & 13 deletions src/libcxx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,17 @@ use std::fs;
use std::path::Path;
use std::process::Command;

use crate::arch::Arch;
use crate::clang::CSysroot;

/// What of `src/llvm-project` the runtimes' build reads: the runtimes, the CMake
/// modules they share with LLVM, and LLVM's libc, whose number parsing libc++
/// compiles in.
pub(crate) const SOURCES: [&str; 7] = ["runtimes", "cmake", "llvm/cmake", "libunwind", "libcxxabi", "libcxx", "libc"];

/// The runtimes' CMake options beyond the target's and the tools'.
pub(crate) const OPTIONS: [(&str, &str); 20] = [
/// The runtimes' CMake options beyond the target's, which the C sysroot's
/// toolchain file names (`clang::CMAKE`), and the tools'.
pub(crate) const OPTIONS: [(&str, &str); 18] = [
("CMAKE_BUILD_TYPE", "Release"),
// CMake has no platform module for ToyOS, and `UNIX` is how it says what
// the runtimes' build needs to know of one: an ELF system with POSIX threads
// (`issues/build/the-cxx-runtime-names-toyos-to-cmake-as-unix.md`).
("CMAKE_SYSTEM_NAME", "ToyOS"),
("UNIX", "ON"),
("LLVM_ENABLE_RUNTIMES", "libunwind;libcxxabi;libcxx"),
("LLVM_INCLUDE_TESTS", "OFF"),
("LLVM_INCLUDE_DOCS", "OFF"),
Expand All @@ -52,10 +47,10 @@ pub(crate) const OPTIONS: [(&str, &str); 20] = [
("LIBCXX_INCLUDE_TESTS", "OFF"),
];

/// Build `arch`'s C++ runtime from the runtimes' `sources` into `c`, which holds
/// Build `c`'s C++ runtime from the runtimes' `sources` into it, which holds
/// the C library already, under `ninja`, in `scratch`, which it removes when
/// done.
pub fn build(c: &CSysroot, arch: Arch, sources: &Path, ninja: &Path, scratch: &Path) {
pub fn build(c: &CSysroot, sources: &Path, ninja: &Path, scratch: &Path) {
eprintln!("Building the C++ runtime for {} under {}", c.target, ninja.display());
if scratch.exists() {
fs::remove_dir_all(scratch).unwrap_or_else(|e| panic!("remove {}: {e}", scratch.display()));
Expand All @@ -68,14 +63,12 @@ pub fn build(c: &CSysroot, arch: Arch, sources: &Path, ninja: &Path, scratch: &P
.unwrap_or_else(|e| panic!("symlink {} -> {}: {e}", ranlib.display(), c.ar.display()));
let path = |p: &Path| p.display().to_string();
let mut definitions: Vec<(String, String)> = OPTIONS.iter().map(|(n, v)| (n.to_string(), v.to_string())).collect();
definitions.push(("CMAKE_SYSTEM_PROCESSOR".into(), arch.name().into()));
definitions.push(("CMAKE_TOOLCHAIN_FILE".into(), path(&c.cmake_toolchain())));
for lang in ["C", "CXX", "ASM"] {
definitions.push((format!("CMAKE_{lang}_COMPILER"), path(&c.clang)));
definitions.push((format!("CMAKE_{lang}_COMPILER_TARGET"), c.target.into()));
}
definitions.push(("CMAKE_AR".into(), path(&c.ar)));
definitions.push(("CMAKE_RANLIB".into(), path(&ranlib)));
definitions.push(("CMAKE_SYSROOT".into(), path(&c.dir)));
definitions.push(("CMAKE_INSTALL_PREFIX".into(), path(&c.dir)));
definitions.push(("CMAKE_MAKE_PROGRAM".into(), path(ninja)));

Expand Down
20 changes: 13 additions & 7 deletions src/sysroot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,11 @@ const SOURCES: &str = "SOURCES";
/// is none of them, nor std's configuration ([`std_config`]), which the
/// freestanding key reads whole. Moving it moves every key.
const RECIPE: &str = "bootstrap stage-0 local rebuild, libraries from the stamp, libtoyos_c merged, \
a C sysroot of libc's staticlib, the empty libraries beside it, and headers \
per target, refused unless a C program naming each library links against \
it, and its C++ runtime built under n2 from the runtimes' sources of the \
compiler's LLVM, the freestanding libraries cloned from their key's; 12";
a C sysroot of libc's staticlib, the empty libraries beside it, headers and \
CMake's description of ToyOS per target, refused unless a C program naming \
each library links against it, and its C++ runtime built under n2 from the \
runtimes' sources of the compiler's LLVM, the freestanding libraries cloned \
from their key's; 13";

/// Every sysroot on this host.
pub fn sysroots_dir(rust_dir: &Path) -> PathBuf {
Expand Down Expand Up @@ -464,11 +465,16 @@ pub(crate) fn key(root: &Path, freestanding: &Key) -> Key {
}

/// What a sysroot's build is beyond its sources and its freestanding libraries:
/// its targets, the C++ runtime's options and libc's cargo invocations.
/// its targets, the C++ runtime's options, CMake's description of ToyOS and
/// libc's cargo invocations.
fn build_text() -> String {
let targets = Libraries::Worktree.targets().join(" ");
let (libc, staticlib) = (crate::libc::BUILD, crate::libc::BUILD_C);
format!("targets {targets}; C++ runtime {:?}; libc {libc:?} {staticlib:?}", crate::libcxx::OPTIONS)
format!(
"targets {targets}; C++ runtime {:?}; CMake {:?}; libc {libc:?} {staticlib:?}",
crate::libcxx::OPTIONS,
crate::clang::CMAKE,
)
}

/// [`key`], with the build it reads.
Expand Down Expand Up @@ -707,7 +713,7 @@ fn build(root: &Path, rust_dir: &Path, compiler: &Compiler, fork: &Path, keys: &
for arch in Arch::ALL {
let scratch = dir.with_extension(format!("libcxx-{}", arch.name()));
let c = crate::clang::CSysroot::of(partial, arch);
crate::libcxx::build(&c, arch, &llvm.dir.join("src"), &ninja, &scratch);
crate::libcxx::build(&c, &llvm.dir.join("src"), &ninja, &scratch);
}

// The sources the key named are the ones built, or this is not that key's.
Expand Down
Loading