Skip to content

Commit 7abbeaf

Browse files
committed
feat(port): C++ 运行时不再关心后端是谁 —— macOS 交叉构建打通
⭐⭐ 在 Linux 上,`mcpp build --target aarch64-macos` 现在产出: Mach-O 64-bit arm64 executable /usr/lib/libSystem.B.dylib ← 唯一依赖 _clock_gettime_nsec_np ← 借的名字,仍然是那两个 _pthread_create_from_mach_thread dyld_stub_binder ← 第三个属于格式,不属于本包 带着 import std、容器、算法、异常与展开期析构 —— 全部来自 openkal 生态, 没有一个字节来自 Apple 的 SDK。 ## 一个谓词,而不是每个平台一遍 `_LIBCPP_HAS_MUSL_LIBC` 现在是 locale 后端的第一问。⚠️ 第一版写成 「musl **且在 Apple 上**」,第二个目标 Windows 立刻以同样形状失败(`_locale_t`) —— 收窄到一个平台就意味着每个平台再写一遍。 ## 三条搬出了「按目标」的块,因为它们是关于 openkal 的事实 - `_LIBUNWIND_USE_DLADDR=0` —— openkal 在**任何**目标上都没有动态加载器。 ⚠️ 它原来在 cfg(os="none") 里,macOS 目标于是以 `unknown type name 'Dl_info'` 重新发现了同一件事。 - `_GNU_SOURCE` —— musl 的非标准名字住在它后面(vasprintf/strtof_l/syscall)。 同样的搬迁,同样的理由。 - compiler-rt builtins —— ⚠️ 这条**不能**搬成包级:宿主链接里驱动仍然链载荷自己的 归档,实测重复定义 __muloti4。改为按目标格式各一份,并记下清单表达不了 「驱动的运行时选择被替换过」这件事。 ## 覆盖目录里的新增 `port/include/__thread/support.h` —— libc++abi 的 guard 问 `__APPLE__` 要 Mach 的 线程标识。⚠️ 三个分支里只有第一个不适合 openkal,而它正是命中的那个;第二个 (SYS_gettid)会经 musl 的系统调用垫片走到 kal_task_*,第三个是合法答案。 ⚠️ 而它关不掉:_LIBCPP_HAS_THREAD_API_PTHREAD 是我们的,清掉它是为了绕开一个 标识函数而拿掉整个线程层。 `mach-o/dyld.h` 加上卸载钩子 —— 这里没有东西会被卸载。 ## Mach-O 的 thread_local 走模拟 TLS ⚠️ `undefined symbol: _tlv_bootstrap`:这个格式经加载器 bootstrap 的描述符访问 thread_local,而这里没有加载器。`-femulated-tls` 让每次访问改走 `__emutls_get_address`。⭐ 机制不是新的 —— 生态设计的实验 A 验证过这条路 (32 项断言 0 failures),而那次也记下了这个 flag 是 LLVM 专有的, 这正是它能写在这里的原因:这个目标的编译器按构造就是 clang。 ⚠️ 而 `emutls.c` 要从排除表里拿掉,并且**顺序不决定这件事** —— 列表里任何位置的 排除都压过任何位置的包含。实测:加了包含而排除还在,产物没有那个对象。
1 parent 43b132e commit 7abbeaf

3 files changed

Lines changed: 195 additions & 11 deletions

File tree

mcpp.toml

Lines changed: 126 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,97 @@ include_dirs = [
149149
# So the freestanding configuration states it, and states the two facilities
150150
# that environment does not have. Nothing else differs: the C library beneath is
151151
# the same one.
152+
# ⚠️ THE COMPILER'S OWN RUNTIME, ON EVERY TARGET WHOSE DRIVER SELECTION WE
153+
# REPLACED — WHICH IS EVERY CROSS, AND NOT THE HOST.
154+
#
155+
# `__udivti3` and its relatives are what the compiler decided to call; no source
156+
# here mentions them. A native link gets them from the toolchain's own
157+
# `libclang_rt.builtins`, which the driver adds. A cross does not — the target
158+
# side comes from this graph — so this package has to supply them.
159+
#
160+
# ⚠️ AND IT MUST NOT SUPPLY THEM NATIVELY. Measured 2026-08-23: adding them to
161+
# the package-wide source list broke the HOST build with duplicate definitions
162+
# of `__muloti4` and its neighbours, against the archive the driver had already
163+
# linked. The fact is "the driver's runtime selection was replaced", and the
164+
# nearest thing a manifest can say today is "this target is not the host of this
165+
# repository's builds" — stated per format rather than once, which is the cost
166+
# of the manifest not being able to say it directly.
167+
[target.'cfg(os = "macos")'.build]
168+
# ⚠️ AND compiler-rt's emutls, WHICH THE EXCLUSION LIST OTHERWISE DROPS.
169+
#
170+
# The package-wide exclusions leave `emutls.c` out because it needs an
171+
# environment — pthread keys and an allocator. This target has both, from
172+
# openkal-musl, and `-femulated-tls` below makes every thread-local access call
173+
# into it. Measured: without it, `undefined symbol: __emutls_get_address`.
174+
#
175+
# ⭐ EMULATED THREAD-LOCAL STORAGE, WHICH THIS OBJECT FORMAT NEEDS AND openkal
176+
# ALREADY VALIDATED.
177+
#
178+
# Mach-O reaches a `thread_local` through a descriptor the dynamic loader
179+
# bootstraps — `_tlv_bootstrap`. There is no dynamic loader here, so the symbol
180+
# is undefined and the program does not link. `-femulated-tls` makes the
181+
# compiler route every access through `__emutls_get_address` instead, which is
182+
# an ordinary function call resolved by compiler-rt against a key the C library
183+
# owns.
184+
#
185+
# ⚠️ Measured 2026-08-23: `undefined symbol: _tlv_bootstrap`, after everything
186+
# else on this target linked. And the mechanism is not new — the ecosystem
187+
# design's experiment A validated exactly this route (32 assertions, 0
188+
# failures); ⚠️ that experiment also recorded that the flag is LLVM-only, which
189+
# is why it can be stated here at all: this target's compiler is clang by
190+
# construction.
191+
cxxflags = ["-femulated-tls"]
192+
cflags = ["-femulated-tls"]
193+
# ⚠️ libc++ carries its OWN copy of the 128-bit multiply-overflow helper, for
194+
# platforms where compiler-rt is not linked. Supplying compiler-rt's makes them
195+
# a pair: `duplicate symbol: __muloti4`. The freestanding block never hit it
196+
# because it excludes the whole filesystem directory for a different reason.
197+
sources = [
198+
"!llvm/libcxx/src/filesystem/int128_builtins.cpp",
199+
"llvm/compiler-rt/lib/builtins/*.c",
200+
"llvm/compiler-rt/lib/builtins/emutls.c",
201+
"!llvm/compiler-rt/lib/builtins/atomic*.c",
202+
"!llvm/compiler-rt/lib/builtins/clear_cache.c",
203+
# ⚠️ `emutls.c` IS NOT EXCLUDED HERE, AND THE ORDER DOES NOT DECIDE THAT —
204+
# AN EXCLUSION ANYWHERE IN A LIST BEATS AN INCLUSION ANYWHERE ELSE IN IT.
205+
# Measured: adding the include above while leaving the exclusion below it
206+
# produced no object and `undefined symbol: __emutls_get_address`. So the
207+
# line is simply absent from this target rather than re-added.
208+
"!llvm/compiler-rt/lib/builtins/enable_execute_stack.c",
209+
"!llvm/compiler-rt/lib/builtins/eprintf.c",
210+
"!llvm/compiler-rt/lib/builtins/gcc_personality_v0.c",
211+
"!llvm/compiler-rt/lib/builtins/crtbegin.c",
212+
"!llvm/compiler-rt/lib/builtins/crtend.c",
213+
# ⚠️ Darwin's own: it asks Apple's SDK which OS version is running, so that a
214+
# weakly-linked symbol can be probed. openkal is not that OS, and nothing here
215+
# weak-links against it. (`apple_versioning.c` is already outside the
216+
# architecture's list; this one is inside it and still Darwin's.)
217+
"!llvm/compiler-rt/lib/builtins/os_version_check.c",
218+
"!llvm/compiler-rt/lib/builtins/*xf*.c",
219+
"!llvm/compiler-rt/lib/builtins/*xc3.c",
220+
]
221+
222+
[target.'cfg(windows)'.build]
223+
sources = [
224+
"!llvm/libcxx/src/filesystem/int128_builtins.cpp",
225+
"llvm/compiler-rt/lib/builtins/*.c",
226+
"!llvm/compiler-rt/lib/builtins/atomic*.c",
227+
"!llvm/compiler-rt/lib/builtins/clear_cache.c",
228+
"!llvm/compiler-rt/lib/builtins/emutls.c",
229+
"!llvm/compiler-rt/lib/builtins/enable_execute_stack.c",
230+
"!llvm/compiler-rt/lib/builtins/eprintf.c",
231+
"!llvm/compiler-rt/lib/builtins/gcc_personality_v0.c",
232+
"!llvm/compiler-rt/lib/builtins/crtbegin.c",
233+
"!llvm/compiler-rt/lib/builtins/crtend.c",
234+
# ⚠️ Darwin's own: it asks Apple's SDK which OS version is running, so that a
235+
# weakly-linked symbol can be probed. openkal is not that OS, and nothing here
236+
# weak-links against it. (`apple_versioning.c` is already outside the
237+
# architecture's list; this one is inside it and still Darwin's.)
238+
"!llvm/compiler-rt/lib/builtins/os_version_check.c",
239+
"!llvm/compiler-rt/lib/builtins/*xf*.c",
240+
"!llvm/compiler-rt/lib/builtins/*xc3.c",
241+
]
242+
152243
[target.'cfg(not(os = "none"))'.build]
153244
include_dirs = ["llvm-generated/generic"]
154245

@@ -157,12 +248,9 @@ include_dirs = ["llvm-generated/freestanding"]
157248
# Two switches that a hosted target answers by recognising the system and this
158249
# one cannot.
159250
#
160-
# the unwinder's use of dladdr --- there is no dynamic loader here, and the
161-
# type its interface names does not exist;
162-
# _GNU_SOURCE --- libc++abi reaches `syscall' for a thread identity, and musl
163-
# declares that name only under this macro. LLVM's own runtimes build defines
164-
# it for the same reason; a hosted build here gets it from elsewhere and the
165-
# difference was invisible until this target asked.
251+
# (dladdr and _GNU_SOURCE moved to the package-wide list above: both are
252+
# facts about openkal rather than about this target, and leaving them here
253+
# meant every new object format rediscovered them.)
166254
# _LIBUNWIND_IS_BAREMETAL — ⭐ the unwinder finds its tables through symbols
167255
# the linker script defines rather than through the program headers. Both
168256
# routes exist upstream and this target must take the second: the headers are
@@ -176,8 +264,7 @@ include_dirs = ["llvm-generated/freestanding"]
176264
# The symbols are `__eh_frame_start` / `__eh_frame_end` (and the `_hdr_`
177265
# pair), and AddressSpace.hpp carries the linker script fragment that defines
178266
# them. examples/same-source/link.ld is that fragment.
179-
cxxflags = ["-D_LIBUNWIND_USE_DLADDR=0", "-D_GNU_SOURCE",
180-
"-D_LIBUNWIND_IS_BAREMETAL=1"]
267+
cxxflags = ["-D_LIBUNWIND_IS_BAREMETAL=1"]
181268
# ⚠️ THE FLAG BELONGS TO THIS PACKAGE AND LANDS ON THE CONSUMER'S LINK.
182269
#
183270
# `_LIBUNWIND_IS_BAREMETAL` above makes the unwinder read `__eh_frame_hdr_start`
@@ -225,9 +312,7 @@ ldflags = ["-Wl,--eh-frame-hdr"]
225312
# two halves of an initialisation array that a linker script places here.
226313
sources = [
227314
"!llvm/libcxx/src/filesystem/*.cpp",
228-
229315
"llvm/compiler-rt/lib/builtins/*.c",
230-
231316
"!llvm/compiler-rt/lib/builtins/atomic*.c",
232317
"!llvm/compiler-rt/lib/builtins/clear_cache.c",
233318
"!llvm/compiler-rt/lib/builtins/emutls.c",
@@ -236,9 +321,14 @@ sources = [
236321
"!llvm/compiler-rt/lib/builtins/gcc_personality_v0.c",
237322
"!llvm/compiler-rt/lib/builtins/crtbegin.c",
238323
"!llvm/compiler-rt/lib/builtins/crtend.c",
239-
324+
# ⚠️ Darwin's own: it asks Apple's SDK which OS version is running, so that a
325+
# weakly-linked symbol can be probed. openkal is not that OS, and nothing here
326+
# weak-links against it. (`apple_versioning.c` is already outside the
327+
# architecture's list; this one is inside it and still Darwin's.)
328+
"!llvm/compiler-rt/lib/builtins/os_version_check.c",
240329
"!llvm/compiler-rt/lib/builtins/*xf*.c",
241330
"!llvm/compiler-rt/lib/builtins/*xc3.c",
331+
242332
"!llvm/compiler-rt/lib/builtins/*bf*.c",
243333
]
244334

@@ -251,6 +341,31 @@ cxxflags = [
251341
# it takes. The C sources had it and the C++ ones did not, which is the kind of
252342
# split a single flags list does not have.
253343
"-D_LIBUNWIND_IS_NATIVE_ONLY",
344+
# ⭐ NO `dladdr`, ON ANY TARGET — WHICH IS A FACT ABOUT openkal, NOT ABOUT ONE
345+
# PLATFORM.
346+
#
347+
# libunwind uses it to put a FUNCTION NAME on a frame, which is a convenience
348+
# for `_Unwind_FindEnclosingFunction` and for printing a backtrace; nothing in
349+
# unwinding needs it. It reaches for it whenever the platform "has a dynamic
350+
# loader", and decides that by the OS.
351+
#
352+
# openkal has no dynamic loader on any target: a program built this way is one
353+
# image. So the answer is the same everywhere, and it is stated once here
354+
# rather than per target — which is what stops the next object format from
355+
# rediscovering it. ⚠️ It was in the `cfg(os = "none")` block before, and the
356+
# macOS target then failed on `unknown type name 'Dl_info'` — the same fact,
357+
# asked again, at a place that had not been told.
358+
"-D_LIBUNWIND_USE_DLADDR=0",
359+
# ⭐ musl's non-standard names live behind this, and they are not optional for
360+
# libc++: the locale backend calls `vasprintf`, `strtof_l`, `strtod_l`,
361+
# `strtold_l`, and libc++abi's guard reaches `syscall`. All of them are musl's
362+
# to declare, and musl declares them here.
363+
#
364+
# ⚠️ It was in the `cfg(os = "none")` block, where it happened to be needed
365+
# first. Measured on the macOS cross: `no member named 'strtof_l' in the
366+
# global namespace`, from a header the freestanding block never applied to.
367+
# The fact is about the C LIBRARY, so it is stated once for the package.
368+
"-D_GNU_SOURCE",
254369
"-D_LIBCXXABI_BUILDING_LIBRARY",
255370
"-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
256371
"-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",

port/include/__thread/support.h

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// The threading support header, plus the two PLATFORM names libc++abi looks for
2+
// at this point and that openkal answers differently.
3+
//
4+
// ⭐ WHY HERE. `cxa_guard_impl.h` includes this unconditionally and then asks
5+
//
6+
// #if defined(__APPLE__) && _LIBCPP_HAS_THREAD_API_PTHREAD
7+
// ... pthread_mach_thread_np(...) → mach_port_t
8+
// #elif defined(SYS_gettid) && _LIBCPP_HAS_THREAD_API_PTHREAD
9+
// ... syscall(SYS_gettid)
10+
// #else
11+
// constexpr uint32_t (*PlatformThreadID)() = nullptr;
12+
//
13+
// ⚠️ ALL THREE BRANCHES ARE FINE FOR openkal EXCEPT THE FIRST, WHICH IS THE ONE
14+
// THAT MATCHES. The second routes through musl's system-call shim, which this
15+
// ecosystem answers with `kal_task_*`; the third is a legal answer that the
16+
// guard implementations already handle. The first names Apple's Mach kernel,
17+
// and there is no Mach here — the platform is `openkal-macos`, and `__APPLE__`
18+
// is a statement about the OBJECT FORMAT.
19+
//
20+
// ⚠️ AND IT CANNOT BE SWITCHED OFF. `_LIBCPP_HAS_THREAD_API_PTHREAD` is ours to
21+
// set, and clearing it would remove the whole threading layer to route around
22+
// one identity function. `_LIBCXXABI_USE_FUTEX` selects a different
23+
// IMPLEMENTATION but does not stop this function from being DEFINED, so it
24+
// still has to compile.
25+
//
26+
// ⇒ So the two names are supplied, as the platform, exactly as
27+
// `AvailabilityMacros.h` and `mach-o/dyld.h` beside this file are. The rule the
28+
// overlay follows throughout: replace the PLATFORM, never the library.
29+
#ifndef OPENKAL_LIBCXX_THREAD_SUPPORT_OVERLAY
30+
#define OPENKAL_LIBCXX_THREAD_SUPPORT_OVERLAY
31+
32+
#include_next <__thread/support.h>
33+
34+
#if defined(__APPLE__) && !defined(OPENKAL_NO_MACH_SHIM)
35+
# include <cstdint>
36+
# include <pthread.h>
37+
38+
// Apple's own width, which `cxa_guard_impl.h` static_asserts against.
39+
typedef std::uint32_t mach_port_t;
40+
41+
// A value that is the same for one execution context and different between two.
42+
//
43+
// ⚠️ THAT IS THE WHOLE CONTRACT AT THIS CALL SITE. The identity is used to
44+
// notice that a guarded initialisation has re-entered itself — it is compared
45+
// for equality and never for anything else, never published, and never handed
46+
// to the system. Apple's answer is a Mach port name because Mach is what
47+
// identifies a thread there; here the descriptor's own address serves, and it
48+
// is unique for the same reason a descriptor is.
49+
inline mach_port_t pthread_mach_thread_np(pthread_t __p) {
50+
return static_cast<mach_port_t>(reinterpret_cast<std::uintptr_t>(__p));
51+
}
52+
#endif
53+
54+
#endif // OPENKAL_LIBCXX_THREAD_SUPPORT_OVERLAY

port/include/mach-o/dyld.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,21 @@ static inline const struct mach_header* _dyld_get_image_header(uint32_t) { retur
3535
static inline intptr_t _dyld_get_image_vmaddr_slide(uint32_t) { return 0; }
3636
static inline const char* _dyld_get_image_name(uint32_t) { return 0; }
3737

38+
// ⚠️ AND THE UNLOAD HOOK, WHICH IS WHY THIS FILE IS NOT ONLY ENQUIRIES.
39+
//
40+
// libunwind's frame-description cache registers a callback so that entries can
41+
// be dropped when an image is unloaded. Nothing is ever unloaded here — there
42+
// is one image and no loader to unload it — so registering is complete when it
43+
// does nothing, and the callback would never be called even if it were kept.
44+
//
45+
// ⭐ Stubbing it here rather than shadowing UnwindCursor.hpp keeps the whole of
46+
// this package's difference from upstream in headers upstream does not own.
47+
// The rule the overlay follows: replace the PLATFORM, never the library.
48+
static inline void _dyld_register_func_for_remove_image(
49+
void (*)(const struct mach_header*, intptr_t)) {}
50+
static inline void _dyld_register_func_for_add_image(
51+
void (*)(const struct mach_header*, intptr_t)) {}
52+
3853
#ifdef __cplusplus
3954
}
4055
#endif

0 commit comments

Comments
 (0)