Skip to content

Commit 3ab4d8f

Browse files
Assert the positions __config_site declares, and repin the C library (#8)
`__config_site` is this package's claim about the environment beneath, and 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_TERMINAL' is the position where that already happened once. The hosted configuration declares FILESYSTEM, RANDOM_DEVICE and TERMINAL and builds the sources behind each, and the bare-metal one declares none of the three and excludes them --- so the exclusions and the switches agree. Nothing in examples/cxx exercised any of them. It does now, and every observation is written so that it can fail: std::filesystem a directory created, a file written, enumerated, copied, its size reported, the whole removed permissions and ⭐⭐ REFUSED, NOT IGNORED. `kal_node_info' carries a create_symlink boolean `writable' rather than a mode word, and SURFACE.txt 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 --- which is what openkal-linux#13 described as "expected 0600, got 0777". std::random_device three draws that DIFFER. "A number was produced" is satisfied by a source stuck at a constant. hidden, weak, the C++ half of openkal-musl#13. In C++ `hidden' was weak_alias given C LINKAGE rather than emptied, so it failed differently from the C case and would not have been caught by it. The report came from a C++23 workspace. Measured here: 17 observations held, none failed. openkal-musl moves to 0.5.0, which is where the routes those refusals are the other side of now live. Also translates the outline-atomics and function-multi-versioning notes in the manifest into English, which is what every other comment in this ecosystem is written in. Co-authored-by: speak-agent <x.d2learn.org@gmail.com>
1 parent da51fdb commit 3ab4d8f

5 files changed

Lines changed: 174 additions & 40 deletions

File tree

.github/workflows/ci.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,20 @@ jobs:
100100
# _Unwind_Backtrace walking zero frames --- and nothing named the cause.
101101
# So the throw is the check, and the destructor beside it is what says the
102102
# unwind was correct rather than merely non-fatal.
103-
- name: A C++ program above it throws across frames and catches
103+
# ⭐ THE UNWINDER, AND EVERY POSITION `__config_site` DECLARES.
104+
#
105+
# The throw and the destructor beside it are what say the runtime is
106+
# really there. The rest of the probe asks whether the CLAIMS this package
107+
# makes about the environment beneath are true: `std::filesystem` over
108+
# openkal.fs, `std::random_device` over openkal.random, and the two
109+
# operations openkal has no atom for --- which must be REFUSED rather than
110+
# ignored, and which a probe checking only the supported ones would pass
111+
# for a port that silently accepted them.
112+
- name: A C++ program above it throws, catches, and uses what is declared
104113
run: |
105114
cd examples/cxx && mcpp run 2>&1 | tee out.log
106115
grep -q -- '-- failures: 0 --' out.log
116+
! grep -q '^FAIL:' out.log
107117
108118
- name: import std above it
109119
run: |

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ mcpp.lock
1818
.spec/
1919
.impl/
2020
.openkal-*/
21+
22+
# What examples/cxx writes while it runs, if it stops before removing it.
23+
cxx-probe.d/
24+
out.log

README.md

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ C library.
66

77
```toml
88
[dependencies]
9-
openkal-llvm-runtime = "0.1.0"
9+
openkal-llvm-runtime = "0.3.0"
1010
```
1111

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

3232
## The one thing the configuration decides
3333

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

3837
```c
39-
#define _LIBCPP_HAS_MUSL_LIBC 1 /* the C library beneath is musl's */
40-
#define _LIBCPP_HAS_RANDOM_DEVICE 0 /* openkal has no source of entropy */
38+
#define _LIBCPP_HAS_MUSL_LIBC 1 /* the C library beneath is musl's */
39+
#define _LIBCPP_HAS_RANDOM_DEVICE 1 /* openkal.random reaches a source */
40+
#define _LIBCPP_HAS_FILESYSTEM 1 /* openkal.fs, and its sources built */
41+
#define _LIBCPP_HAS_TERMINAL 1 /* isatty answers, rather than lying */
4142
```
4243
4344
The first was measured rather than assumed. With it at `0` — the value a
@@ -49,9 +50,17 @@ __locale:439: error: unknown rune table for this platform
4950
-- do you mean to define _LIBCPP_PROVIDES_DEFAULT_RUNE_TABLE?
5051
```
5152

52-
With it at `1`, none. The second follows from openkal reporting `ENOSYS` for
53-
entropy: `std::random_device` is not built, and a program that names it is told
54-
by the linker.
53+
With it at `1`, none.
54+
55+
⚠️ **A claim that drifts from what the port provides fails neither the build nor
56+
the link.** It produces a program that takes a path the environment cannot
57+
support, and reports nothing. `_LIBCPP_HAS_RANDOM_DEVICE` was `0` until openkal
58+
gained `openkal.random`; `_LIBCPP_HAS_TERMINAL` was `1` while every `isatty`
59+
over the port beneath returned `0` — for a real terminal as readily as for a
60+
pipe — so `std::print` never took its terminal path and nothing failed. The
61+
remedy there was to repair the port rather than withdraw the claim, and the
62+
workflow now reconciles the two rather than checking once and assuming
63+
afterwards.
5564

5665
## Two configurations, and why there are two
5766

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

99119
`examples/import-std` asserts the other half: `import std;` — the module, not the
100120
headers — with `std::ranges::sort` and `std::println`.

examples/cxx/src/main.cpp

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,36 @@
11
// What a C++ program above openkal reaches for, and the one observation that
22
// settles whether the runtime is really there: an exception thrown across a
33
// frame and caught.
4+
//
5+
// ⭐ AND THE PARTS OF THE STANDARD LIBRARY WHOSE AVAILABILITY IS A STATEMENT
6+
// ABOUT THE ENVIRONMENT BENEATH. `__config_site` declares
7+
// `_LIBCPP_HAS_FILESYSTEM 1` and `_LIBCPP_HAS_RANDOM_DEVICE 1` for a hosted
8+
// row; those declarations are this package's claim about openkal-musl, and a
9+
// claim that drifts from what the port provides does not fail to build and does
10+
// not fail to link --- it produces a program that takes a path the environment
11+
// cannot support and reports nothing. The same shape as `_LIBCPP_HAS_TERMINAL`,
12+
// which is what the workflow's reconciliation step exists because of.
413
#include <cstdio>
514
#include <vector>
615
#include <string>
716
#include <algorithm>
817
#include <stdexcept>
18+
#include <filesystem>
19+
#include <random>
20+
#include <system_error>
21+
22+
// ⭐ THREE NAMES A PROGRAM ABOVE THIS STACK MAY USE, ASSERTED BY COMPILING.
23+
//
24+
// musl's INTERNAL header overlay defines `hidden`, `weak` and `weak_alias` as
25+
// macros that mean something only to musl's own sources, and openkal-musl used
26+
// to publish the path it is built from --- so a program above it could not
27+
// declare any of the three (mcpplibs/openkal-musl#13). This is the C++ half of
28+
// that criterion, and it belongs here rather than there: the report came from a
29+
// C++23 workspace, and `hidden` in C++ was given C LINKAGE rather than emptied,
30+
// which is a different failure from the C one and would not be caught by it.
31+
static int hidden = 7;
32+
static int weak = 11;
33+
struct weak_alias { int value; };
934

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

67+
check(hidden + weak == 18 && weak_alias{3}.value == 3,
68+
"hidden, weak and weak_alias are the program's own identifiers");
69+
70+
// --- std::filesystem, which is the C++ face of openkal.fs ---------------
71+
72+
namespace fs = std::filesystem;
73+
const fs::path dir = "cxx-probe.d";
74+
std::error_code ec;
75+
76+
fs::remove_all(dir, ec);
77+
check(fs::create_directory(dir, ec) && !ec, "a directory is created");
78+
79+
{
80+
std::FILE* f = std::fopen((dir / "a.txt").c_str(), "w");
81+
check(f != nullptr, "a file is created inside it");
82+
if (f) { std::fputs("0123456789", f); std::fclose(f); }
83+
}
84+
85+
check(fs::exists(dir / "a.txt", ec) && !ec, "the file is found by name");
86+
check(fs::file_size(dir / "a.txt", ec) == 10 && !ec, "its size is reported");
87+
88+
int entries = 0;
89+
for (const auto& e : fs::directory_iterator(dir, ec)) { (void)e; ++entries; }
90+
check(entries == 1 && !ec, "the directory enumerates exactly what is in it");
91+
92+
check(fs::copy_file(dir / "a.txt", dir / "b.txt", ec) && !ec,
93+
"a file is copied");
94+
check(fs::file_size(dir / "b.txt", ec) == 10 && !ec,
95+
"and the copy has the same size");
96+
97+
// ⭐⭐ AND THE TWO OPERATIONS openkal HAS NO ATOM FOR, CHECKED AS REFUSALS.
98+
//
99+
// `kal_node_info` carries a boolean `writable` and not a mode word, and
100+
// SURFACE.txt has no operation that creates a symbolic link. openkal-musl
101+
// therefore refuses `chmod` and `symlink` rather than succeeding and
102+
// reporting something else afterwards --- and a refusal that arrives as a
103+
// `std::error_code` is what a C++ caller can act upon.
104+
//
105+
// ⚠️ THIS IS THE HALF THAT WOULD BE OMITTED. A probe checking only that the
106+
// supported operations work would pass just as well for a port that
107+
// silently accepted these two, which is the outcome the report
108+
// (openkal-linux#13) described as "expected 0600, got 0777".
109+
ec.clear();
110+
fs::permissions(dir / "a.txt", fs::perms::owner_read, ec);
111+
check(static_cast<bool>(ec), "changing permission bits is refused, not ignored");
112+
113+
ec.clear();
114+
fs::create_symlink(dir / "a.txt", dir / "link", ec);
115+
check(static_cast<bool>(ec), "creating a symbolic link is refused, not ignored");
116+
117+
fs::remove_all(dir, ec);
118+
check(!fs::exists(dir, ec), "the directory and its contents are removed");
119+
120+
// --- std::random_device, which is the C++ face of openkal.random --------
121+
122+
{
123+
std::random_device rd;
124+
const unsigned a = rd(), b = rd(), c = rd();
125+
// ⚠️ THE CRITERION IS THAT THEY DIFFER, NOT THAT ANY ONE OF THEM IS
126+
// ANYTHING. A source stuck at a constant satisfies "a number was
127+
// produced" and is exactly what a port that forgot to fill the buffer
128+
// would produce.
129+
check(!(a == b && b == c), "three draws from the entropy source differ");
130+
}
131+
42132
std::printf("-- failures: %d --\n", failures);
43133
return failures != 0;
44134
}

mcpp.toml

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

200210
[dependencies]
201-
openkal-musl = "0.4.0"
211+
openkal-musl = "0.5.0"
202212

203213
[build]
204214
cxx_standard = "c++23"

0 commit comments

Comments
 (0)