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
12 changes: 11 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,20 @@ jobs:
# _Unwind_Backtrace walking zero frames --- and nothing named the cause.
# So the throw is the check, and the destructor beside it is what says the
# unwind was correct rather than merely non-fatal.
- name: A C++ program above it throws across frames and catches
# ⭐ THE UNWINDER, AND EVERY POSITION `__config_site` DECLARES.
#
# The throw and the destructor beside it are what say the runtime is
# really there. The rest of the probe asks whether the CLAIMS this package
# makes about the environment beneath are true: `std::filesystem` over
# openkal.fs, `std::random_device` over openkal.random, and the two
# operations openkal has no atom for --- which must be REFUSED rather than
# ignored, and which a probe checking only the supported ones would pass
# for a port that silently accepted them.
- name: A C++ program above it throws, catches, and uses what is declared
run: |
cd examples/cxx && mcpp run 2>&1 | tee out.log
grep -q -- '-- failures: 0 --' out.log
! grep -q '^FAIL:' out.log

- name: import std above it
run: |
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ mcpp.lock
.spec/
.impl/
.openkal-*/

# What examples/cxx writes while it runs, if it stops before removing it.
cxx-probe.d/
out.log
38 changes: 29 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ C library.

```toml
[dependencies]
openkal-llvm-runtime = "0.1.0"
openkal-llvm-runtime = "0.3.0"
```

A C++ standard library is not portable in the way a program is. It is
Expand All @@ -31,13 +31,14 @@ itself builds separately from the compiler.

## The one thing the configuration decides

`llvm-generated/generic/__config_site` is libc++'s configure product. Two
positions in it carry the whole of what makes this package different from the
one a toolchain ships:
`llvm-generated/generic/__config_site` is libc++'s configure product, and every
position in it is a **claim about the environment beneath**:

```c
#define _LIBCPP_HAS_MUSL_LIBC 1 /* the C library beneath is musl's */
#define _LIBCPP_HAS_RANDOM_DEVICE 0 /* openkal has no source of entropy */
#define _LIBCPP_HAS_MUSL_LIBC 1 /* the C library beneath is musl's */
#define _LIBCPP_HAS_RANDOM_DEVICE 1 /* openkal.random reaches a source */
#define _LIBCPP_HAS_FILESYSTEM 1 /* openkal.fs, and its sources built */
#define _LIBCPP_HAS_TERMINAL 1 /* isatty answers, rather than lying */
```

The first was measured rather than assumed. With it at `0` — the value a
Expand All @@ -49,9 +50,17 @@ __locale:439: error: unknown rune table for this platform
-- do you mean to define _LIBCPP_PROVIDES_DEFAULT_RUNE_TABLE?
```

With it at `1`, none. The second follows from openkal reporting `ENOSYS` for
entropy: `std::random_device` is not built, and a program that names it is told
by the linker.
With it at `1`, none.

⚠️ **A claim that drifts from what the port provides fails neither the build nor
the link.** It produces a program that takes a path the environment cannot
support, and reports nothing. `_LIBCPP_HAS_RANDOM_DEVICE` was `0` until openkal
gained `openkal.random`; `_LIBCPP_HAS_TERMINAL` was `1` while every `isatty`
over the port beneath returned `0` — for a real terminal as readily as for a
pipe — so `std::print` never took its terminal path and nothing failed. The
remedy there was to repair the port rather than withdraw the claim, and the
workflow now reconciles the two rather than checking once and assuming
afterwards.

## Two configurations, and why there are two

Expand Down Expand Up @@ -95,6 +104,17 @@ whether a hosted standard library is *present*.
- values returned through several frames
- **an exception thrown across three frames and caught**
- **a destructor run while the stack is unwound**
- `hidden`, `weak` and `weak_alias` as the program's own identifiers — the C++
half of `mcpplibs/openkal-musl#13`, where `hidden` was given C *linkage*
rather than emptied and so failed differently from the C case
- `std::filesystem` — a directory created, a file written, enumerated, copied,
its size reported, and the whole removed
- **`std::filesystem::permissions` and `create_symlink` refused, not ignored** —
openkal carries a boolean `writable` rather than a mode word, and has no
operation that creates a link. A probe checking only the supported operations
would pass just as well for a port that silently accepted these two
- `std::random_device` — three draws that **differ**, which a source stuck at a
constant would not satisfy and "a number was produced" would

`examples/import-std` asserts the other half: `import std;` — the module, not the
headers — with `std::ranges::sort` and `std::println`.
Expand Down
90 changes: 90 additions & 0 deletions examples/cxx/src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
// What a C++ program above openkal reaches for, and the one observation that
// settles whether the runtime is really there: an exception thrown across a
// frame and caught.
//
// ⭐ AND THE PARTS OF THE STANDARD LIBRARY WHOSE AVAILABILITY IS A STATEMENT
// ABOUT THE ENVIRONMENT BENEATH. `__config_site` declares
// `_LIBCPP_HAS_FILESYSTEM 1` and `_LIBCPP_HAS_RANDOM_DEVICE 1` for a hosted
// row; those declarations are this package's claim about openkal-musl, and a
// claim that drifts from what the port provides does not fail to build and does
// not fail to link --- it produces a program that takes a path the environment
// cannot support and reports nothing. The same shape as `_LIBCPP_HAS_TERMINAL`,
// which is what the workflow's reconciliation step exists because of.
#include <cstdio>
#include <vector>
#include <string>
#include <algorithm>
#include <stdexcept>
#include <filesystem>
#include <random>
#include <system_error>

// ⭐ THREE NAMES A PROGRAM ABOVE THIS STACK MAY USE, ASSERTED BY COMPILING.
//
// musl's INTERNAL header overlay defines `hidden`, `weak` and `weak_alias` as
// macros that mean something only to musl's own sources, and openkal-musl used
// to publish the path it is built from --- so a program above it could not
// declare any of the three (mcpplibs/openkal-musl#13). This is the C++ half of
// that criterion, and it belongs here rather than there: the report came from a
// C++23 workspace, and `hidden` in C++ was given C LINKAGE rather than emptied,
// which is a different failure from the C one and would not be caught by it.
static int hidden = 7;
static int weak = 11;
struct weak_alias { int value; };

static int depth_three(int n) { if (n > 2) throw std::runtime_error("thrown"); return n; }
static int depth_two(int n) { return depth_three(n) + 1; }
Expand Down Expand Up @@ -39,6 +64,71 @@ int main() {
try { sentinel g{&destroyed}; throw 1; } catch (int) {}
check(destroyed, "a destructor runs while the stack is unwound");

check(hidden + weak == 18 && weak_alias{3}.value == 3,
"hidden, weak and weak_alias are the program's own identifiers");

// --- std::filesystem, which is the C++ face of openkal.fs ---------------

namespace fs = std::filesystem;
const fs::path dir = "cxx-probe.d";
std::error_code ec;

fs::remove_all(dir, ec);
check(fs::create_directory(dir, ec) && !ec, "a directory is created");

{
std::FILE* f = std::fopen((dir / "a.txt").c_str(), "w");
check(f != nullptr, "a file is created inside it");
if (f) { std::fputs("0123456789", f); std::fclose(f); }
}

check(fs::exists(dir / "a.txt", ec) && !ec, "the file is found by name");
check(fs::file_size(dir / "a.txt", ec) == 10 && !ec, "its size is reported");

int entries = 0;
for (const auto& e : fs::directory_iterator(dir, ec)) { (void)e; ++entries; }
check(entries == 1 && !ec, "the directory enumerates exactly what is in it");

check(fs::copy_file(dir / "a.txt", dir / "b.txt", ec) && !ec,
"a file is copied");
check(fs::file_size(dir / "b.txt", ec) == 10 && !ec,
"and the copy has the same size");

// ⭐⭐ AND THE TWO OPERATIONS openkal HAS NO ATOM FOR, CHECKED AS REFUSALS.
//
// `kal_node_info` carries a boolean `writable` and not a mode word, and
// SURFACE.txt has no operation that creates a symbolic link. openkal-musl
// therefore refuses `chmod` and `symlink` rather than succeeding and
// reporting something else afterwards --- and a refusal that arrives as a
// `std::error_code` is what a C++ caller can act upon.
//
// ⚠️ THIS IS THE HALF THAT WOULD BE OMITTED. A probe checking only that the
// supported operations work would pass just as well for a port that
// silently accepted these two, which is the outcome the report
// (openkal-linux#13) described as "expected 0600, got 0777".
ec.clear();
fs::permissions(dir / "a.txt", fs::perms::owner_read, ec);
check(static_cast<bool>(ec), "changing permission bits is refused, not ignored");

ec.clear();
fs::create_symlink(dir / "a.txt", dir / "link", ec);
check(static_cast<bool>(ec), "creating a symbolic link is refused, not ignored");

fs::remove_all(dir, ec);
check(!fs::exists(dir, ec), "the directory and its contents are removed");

// --- std::random_device, which is the C++ face of openkal.random --------

{
std::random_device rd;
const unsigned a = rd(), b = rd(), c = rd();
// ⚠️ THE CRITERION IS THAT THEY DIFFER, NOT THAT ANY ONE OF THEM IS
// ANYTHING. A source stuck at a constant satisfies "a number was
// produced" and is exactly what a port that forgot to fill the buffer
// would produce.
check(!(a == b && b == c), "three draws from the entropy source differ");
}

std::printf("-- failures: %d --\n", failures);
return failures != 0;
}
70 changes: 40 additions & 30 deletions mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-llvm-runtime"
version = "0.2.0"
version = "0.3.0"
description = "LLVM's C++ runtime libraries — libc++, libc++abi and libunwind — configured for openkal-musl rather than for a host C library."
license = "Apache-2.0"
authors = ["mcpplibs"]
Expand Down Expand Up @@ -147,58 +147,68 @@ std-module-flags = [
# criterion the whole package exists to satisfy is the one openkal keeps
# returning to --- whether an implementation has been configured FOR this
# target, not whether its headers can be found.
# ⭐⭐ outline-atomics 的 125 个辅助函数,由本包供给 —— 因为本包就是那份
# compiler-rt
# ⭐⭐ THE 125 OUTLINE-ATOMICS HELPERS ARE SUPPLIED BY THIS PACKAGE, BECAUSE
# THIS PACKAGE IS THAT compiler-rt.
#
# `--rtlib=compiler-rt` 让 clang 在 aarch64 上开启 `+outline-atomics`,
# 而它是对的:该特性的 `__aarch64_*` 辅助函数本来就住在 compiler-rt 里。
# 此前本包不产出它们,于是特性被开启而没有人实现:
# `--rtlib=compiler-rt` makes clang enable `+outline-atomics` on aarch64, and it
# is right to: the feature's `__aarch64_*` helpers live in compiler-rt. This
# package did not produce them, so the feature was enabled and nobody
# implemented it:
#
# ld.lld: error: undefined symbol: __aarch64_swp4_acq
# ld.lld: error: undefined symbol: __aarch64_cas8_acq_rel
#
# ⚠️ 引用来自 `openkal-linux/src/memory.o` 与 `openkal-musl/port/src/okm_fd.o`
# —— 两个对这件事一无所知、只是用了原子操作的包。缺的是这份运行时该有的东西。
# ⚠️ The references came from `openkal-linux/src/memory.o` and
# `openkal-musl/port/src/okm_fd.o` --- two packages that know nothing about any
# of this and merely used an atomic operation. What was missing was something
# this runtime is supposed to carry.
#
# upstream 的做法是把 `aarch64/lse.S` 编 125 遍,每遍给不同的
# `-DL_<pat> -DSIZE=<n> -DMODEL=<m>`(6 模式 × 5 尺寸 × 5 模型,
# 非 cas 的 16 字节档不存在)。一个用 glob 表达 sources 的清单传不了
# per-file 定义 —— 于是每个组合成为一个**自己声明宏、再 include 共享正文**
# 的文件。正文是 upstream 的,未经修改,仍在原处。
# Upstream compiles `aarch64/lse.S` 125 times, each with a different
# `-DL_<pat> -DSIZE=<n> -DMODEL=<m>` (6 patterns x 5 sizes x 5 models; the
# 16-byte width exists only for `cas`). A manifest that states its sources as a
# glob cannot carry a per-file definition --- so each combination becomes a file
# that DECLARES THE MACROS ITSELF AND THEN INCLUDES THE SHARED BODY. The body is
# upstream's, unmodified, and stays where it is.
#
# ⭐ 这是把宏从**命令行**移到**文件内部**,而不是复制 125 份实现。
# ⭐ That moves the macros from the COMMAND LINE into the FILE, rather than
# copying the implementation 125 times.
[target.'cfg(arch = "aarch64")'.build]
sources = [
"llvm-generated/outline-atomics/*.S",
# ⚠️ 并且定义那个探测变量的文件。这 125 个辅助函数每一个都读
# `__aarch64_have_lse_atomics` —— 它在运行时决定走 LSE 指令还是
# load/store-exclusive 循环。少了它:
# ⚠️ And the file that defines the probe. Every one of the 125 helpers reads
# `__aarch64_have_lse_atomics`, which decides at run time between the LSE
# instructions and a load/store-exclusive loop. Without it:
#
# ld.lld: error: undefined hidden symbol: __aarch64_have_lse_atomics
#
# ⭐ 它在 `cpu_model/aarch64.c`,而 `[build]` 段的 glob 是
# `llvm/compiler-rt/lib/builtins/*.c` —— **不含子目录**,所以这个文件
# 从未被编过。upstream 把它列在 `aarch64_SOURCES` 里,与本节同一批。
# ⭐ It is in `cpu_model/aarch64.c`, and the `[build]` section's glob is
# `llvm/compiler-rt/lib/builtins/*.c` --- WHICH DOES NOT DESCEND INTO
# SUBDIRECTORIES, so that file had never been compiled. Upstream lists it in
# `aarch64_SOURCES`, in the same set as this section.
"llvm/compiler-rt/lib/builtins/cpu_model/aarch64.c",
]
# ⚠️ FMV 关掉,而它不是可选的清理 —— 那半个文件在 macOS 宿主上根本编不过。
# ⚠️ FUNCTION MULTI-VERSIONING IS TURNED OFF, AND THAT IS NOT OPTIONAL TIDYING:
# that half of the file does not compile on a macOS host at all.
#
# `cpu_model/aarch64.c` 的后半是函数多版本(FMV)的实现,按宿主分支 include
# 一个 `.inc`Apple 那支拉的是系统头:
# The second half of `cpu_model/aarch64.c` implements function multi-versioning,
# and includes one `.inc` per host. Apple's branch reaches for a system header:
#
# aarch64/fmv/apple.inc:1:10: fatal error: 'TargetConditionals.h' file not found
#
# 而本包的 `include_dirs` 刻意不含任何来自机器的路径 —— 见该段的
# ⭐⭐ NOTHING FROM THE MACHINE。⚠️ 本机 Linux 上编得过,CI 的 macOS 那格才炸,
# 是典型的「一台宿主看不见」的形状。
# and this package's `include_dirs` deliberately contains no path that comes
# from the machine --- see ⭐⭐ NOTHING FROM THE MACHINE in that section.
# ⚠️ It compiles on this Linux machine and fails only on the macOS row of
# continuous integration, which is the shape one host cannot see.
#
# ⭐ 而本包**不需要** FMV:要的只是 `__aarch64_have_lse_atomics`,
# 它定义在 `#if !defined(DISABLE_AARCH64_FMV)` 之外。upstream 自己也有这个
# 开关(`COMPILER_RT_DISABLE_AARCH64_FMV`),所以这是走它给的门,不是绕过。
# ⭐ And this package does NOT need multi-versioning: what it wants is
# `__aarch64_have_lse_atomics`, which is defined outside
# `#if !defined(DISABLE_AARCH64_FMV)`. Upstream has the same switch
# (`COMPILER_RT_DISABLE_AARCH64_FMV`), so this goes through the door upstream
# provides rather than around it.
cflags = ["-DDISABLE_AARCH64_FMV=1"]

[dependencies]
openkal-musl = "0.4.0"
openkal-musl = "0.5.0"

[build]
cxx_standard = "c++23"
Expand Down
Loading