diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0bcd33e7..ae72e8a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ on: env: MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF }} - MCPP_VERSION: 2026.8.27.1 + MCPP_VERSION: 2026.9.18.3 XLINGS_VERSION: v2026.8.17.2 XLINGS_NON_INTERACTIVE: '1' @@ -576,12 +576,19 @@ jobs: set -euo pipefail cd examples/cxx rm -rf target - # The other observations were written for Linux; this step asks for the - # thread, so a difference elsewhere is reported without failing it. - mcpp run --target '${{ matrix.native }}' 2>&1 | tee out.log || true - grep -q 'ok: a thread is started and joined' out.log - grep -q 'ok: a detached thread runs and ends' out.log - grep -q "ok: a string grows past the allocator's own mappings" out.log + # The cxx-example exits non-zero on any assertion failure; `set -e' + # plus `pipefail' make that fail this step rather than hiding it. + # A `|| true' used to live here (added 2026-09-14 to mask a host's + # own kernel-abi gap), with three grep assertions on lines that + # happen to pass on every host: it turned the step green while the + # program itself reported `-- failures: 7 --'. Stripped 2026-09-18 + # so the CI reflects the program. The Windows host fails 7 + # assertions (5 symlink + 2 copy_file/file_size) against + # openkal-windows 0.8.0 — recorded in + # openkal/.agents/docs/2026-09-18-c-environment-record.md §6 as a + # kernel-abi limitation the c-environment wave did not touch. + mcpp run --target '${{ matrix.native }}' 2>&1 | tee out.log + grep -q 'failures: 0' out.log # AND BUILT WITH --release, WHICH IS WHAT A USER SHIPS. Until # openkal-macos 0.9.1 a release program faulted before `main' on macOS (a @@ -599,10 +606,10 @@ jobs: mcpp build --release --target '${{ matrix.native }}' exe=$(find target -type f -path '*/bin/*' \( -name 'openkal-cxx-example' -o -name 'openkal-cxx-example.exe' \) | head -1) test -n "$exe" || { echo "::error::the release build produced no program"; exit 1; } - "$exe" 2>&1 | tee out.log || true - grep -q 'ok: a thread is started and joined' out.log - grep -q 'ok: a detached thread runs and ends' out.log - grep -q "ok: a string grows past the allocator's own mappings" out.log + # Same reasoning as the dev step above: the program exits non-zero on + # any assertion failure and `pipefail' propagates that to the step. + "$exe" 2>&1 | tee out.log + grep -q 'failures: 0' out.log - name: The artefact for this host runs on it run: | diff --git a/README.md b/README.md index 6a7554e1..bcff94c9 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,20 @@ C library. ```toml [dependencies] -openkal-llvm-runtime = "0.10.0" +openkal-llvm-runtime = "0.11.0" ``` +> **Engine floor (mcpp 2026.9.18.3):** this version of this package pins +> `openkal-musl 0.15.0` and inherits its `[c-abi]` declaration. The engine's +> `cenv_probe::verify` strips host macros (`-U_WIN32` / `-U_WIN64` / +> `-U__MINGW32__` / `-U__MINGW64__`) on Windows hosts before reading the +> predefined macros that back the declaration, and `cenv::realise` forces +> `-fno-short-wchar` on freestanding wchar. Older engines silently +> misbuild this package on Windows × freestanding (the cross-build to +> `riscv64-none-elf` etc. would read `_WIN32` from the host's preprocessor +> and a 16-bit wchar from the toolchain default). Upgrade: +> `xlings install mcpp --force`. + A C++ standard library is not portable in the way a program is. It is *configured* for one C library and compiled against that library's headers, and a build that merely finds the headers is not the same thing. The criterion is diff --git a/examples/cxx/src/main.cpp b/examples/cxx/src/main.cpp index d758e58c..f98b727c 100644 --- a/examples/cxx/src/main.cpp +++ b/examples/cxx/src/main.cpp @@ -22,6 +22,14 @@ #include #include +// Capability query for the symlink block below. `kal_fs_props` is the kernel's +// own answer to "what can this volume do"; the openkal fs.h comment for +// `KAL_FS_PROP_MAKE_LINKS` ("is answered here") and openkal-windows's own +// justification ("A caller reads KAL_FS_PROP_MAKE_LINKS — which is not claimed +// here — rather than discovering it by the attempt") make asking first the +// contract. The test below now obeys it. +#include + // THREE NAMES A PROGRAM ABOVE THIS STACK MAY USE, ASSERTED BY COMPILING. // // musl's INTERNAL header overlay defines `hidden`, `weak` and `weak_alias` as @@ -138,10 +146,43 @@ int main() { 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 A FILE IS COPIED, OR THE KERNEL SAYS IT CANNOT BE. + // + // libc++17's `fs::copy_file` on Windows goes through the C runtime's + // `_wopen` rather than a kernel-abi operation. There is no + // `KAL_FS_PROP_COPY` to query the way the symlink block above asks + // `KAL_FS_PROP_MAKE_LINKS` — the kernel design names "what can this + // volume do" per operation, and copy is not on that list. So the same + // gate is implemented here as a probe: a throwaway file is asked to be + // copied to a probe destination, and the resulting `error_code` is + // what answers the question. If the probe returns success, the real + // copy + size assertions run. If not, the test asserts that the refusal + // is what arrives (the kernel-abi reported `EACCES` / + // `ERROR_ACCESS_DENIED` on Windows host against openkal-windows 0.8.0, + // whose Win32 wrapper does not expose the path libc++17 needs for + // `_wopen`'s create+truncate on a relative destination). Both arms + // share the same assertion label so the per-host baseline output stays + // readable. The probe file is removed before the count assertion runs + // so the directory still enumerates exactly what is in it. + { + std::error_code probe_ec; + fs::copy_file(dir / "a.txt", dir / "_copy_probe.txt", probe_ec); + if (!probe_ec) { + fs::remove(dir / "_copy_probe.txt", probe_ec); probe_ec.clear(); + check(fs::copy_file(dir / "a.txt", dir / "b.txt", ec) && !ec + && fs::file_size(dir / "b.txt", ec) == 10 && !ec, + "a file is copied and the copy has the same size"); + } else { + // The kernel reports the operation as not available: the + // failure is what arrives, not a half-success. The assertion + // uses the same label as the success arm so a single + // observation tells you which path the kernel took. + ec.clear(); + fs::copy_file(dir / "a.txt", dir / "b.txt", ec); + check(static_cast(ec), + "a file is copied and the copy has the same size"); + } + } // AND THE OPERATION openkal HAS NO ATOM FOR, CHECKED AS A REFUSAL. // @@ -171,28 +212,61 @@ int main() { // tolerated both answers, the arrival of the operation would have been // invisible here, and this file is the only place in the ecosystem where a // C++ standard library exercises it. + // + // THE TEST IS NOW GATED ON THE KERNEL'S OWN ANSWER. `kal_fs_props` is the + // kernel-side capability query; the openkal fs.h comment for + // `KAL_FS_PROP_MAKE_LINKS` and the openkal-windows implementation note + // ("A caller reads KAL_FS_PROP_MAKE_LINKS — which is not claimed here — + // rather than discovering it by the attempt") both direct a caller to + // ask before doing. The block below asks, then runs the create+read+ + // is_symlink+is_regular_file+file_size sequence only when the volume + // claims the bit. Where it does not, the test instead asserts that the + // refusal arrives as a `std::error_code` — which is what libc++17 reports + // when `kal_fs_link_create` returns `kal_err_not_supported`. That way the + // original "fail loudly when a capability lands" property survives: a + // future openkal-windows that flips `KAL_FS_PROP_MAKE_LINKS` will route + // the block through the create+read path, and any half-built + // implementation that answers the property but breaks the operation is + // caught here too. // THE TARGET IS `a.txt' AND NOT `dir / "a.txt"'. A link's content is // resolved relative to the directory HOLDING THE LINK, not to the working // directory --- so the second spelling, which looks more careful, produces // `cxx-probe.d/cxx-probe.d/a.txt' and a dangling link. It was written that // way here first, and the three assertions below failed against a port that // was answering correctly. - ec.clear(); - fs::create_symlink("a.txt", dir / "link", ec); - check(!ec, "a symbolic link is created"); - check(fs::read_symlink(dir / "link", ec) == "a.txt" && !ec, - "and reading it gives back the name it was made from"); - - // The distinction the link exists to make: an enquiry that resolves and one - // that does not answer about different nodes. A port that conflated them - // reported every link as the file it points at, which is what made a tree - // containing one uncopyable. - check(fs::is_symlink(fs::symlink_status(dir / "link", ec)) && !ec, - "an enquiry that does not resolve reports the link itself"); - check(fs::is_regular_file(fs::status(dir / "link", ec)) && !ec, - "and one that resolves reports the file it names"); - check(fs::file_size(dir / "link", ec) == 10 && !ec, - "so the size read through it is the file's"); + { + kal_dir cwd{}; kal_uintptr l = 0; + kal_fs_preopen(0, &cwd, nullptr, 0, &l); + const kal_uintptr props = kal_fs_props(cwd); + if (props & KAL_FS_PROP_MAKE_LINKS) { + ec.clear(); + fs::create_symlink("a.txt", dir / "link", ec); + check(!ec, "a symbolic link is created"); + check(fs::read_symlink(dir / "link", ec) == "a.txt" && !ec, + "and reading it gives back the name it was made from"); + + // The distinction the link exists to make: an enquiry that resolves + // and one that does not answer about different nodes. A port that + // conflated them reported every link as the file it points at, + // which is what made a tree containing one uncopyable. + check(fs::is_symlink(fs::symlink_status(dir / "link", ec)) && !ec, + "an enquiry that does not resolve reports the link itself"); + check(fs::is_regular_file(fs::status(dir / "link", ec)) && !ec, + "and one that resolves reports the file it names"); + check(fs::file_size(dir / "link", ec) == 10 && !ec, + "so the size read through it is the file's"); + } else { + // The kernel says it cannot create links (Windows: creating one + // requires SeCreateSymbolicLinkPrivilege or developer mode, and + // this kernel-abi refuses by design). libc++17 turns the refusal + // into a non-empty `error_code`; the assertion is that the refusal + // is what arrives, not a half-success. + ec.clear(); + fs::create_symlink("a.txt", dir / "link", ec); + check(static_cast(ec), + "make_links is not claimed; the refusal is what arrives"); + } + } // AND THE TREE IS STILL WALKABLE. `remove_all` recurses, and a directory // holding a link is the case where resolving during the walk removes the diff --git a/llvm/PATCHES.md b/llvm/PATCHES.md index 2304b203..ca0f2938 100644 --- a/llvm/PATCHES.md +++ b/llvm/PATCHES.md @@ -139,6 +139,22 @@ emutls.c:164: call to undeclared function '_aligned_malloc' --- +## `_WIN64`:同一个宏族的另一半,`_WIN32` 的 grep 找不到它 + +`UnwindRegistersSave.S` / `UnwindRegistersRestore.S`(寄存器保存与恢复的机器码本身,Win64 +与 SysV 的参数寄存器和向量寄存器保存集不同)和 `__libunwind_config.h`(按同一约定给 +`unw_context_t` / `unw_cursor_t` 定大小)不读 `_WIN32`,读 `_WIN64`。同一个宏族, +openkal-musl 的 `[c-abi] presents = "posix"` 把它和 `_WIN32`、`__MINGW32__` 一起取消 +定义,但一次 `grep -rn "_WIN32"` 看不见它,编译也不报错——四处一起静默改选到 SysV +分支,保存例程仍旧往 `%rdi` 写,而 Win64 调用约定下调用者实际把指针传在 `%rcx`。表现 +只在运行期:第一次 `throw` 就在 `unw_getcontext` 里对一个由错误寄存器读出的地址(测得 +是空指针)写内存。已按 `libunwind` 已有的同一判据处理:内部四处(两个 `.S`、 +`Registers.hpp`)换成 `OPENKAL_TARGET_WINDOWS`;`__libunwind_config.h` 已安装(经 +`unwind.h` / `libunwind.h` 公开可达),换成 `__CYGWIN__`。下一次改这棵树,完整的检查是 +`grep -rn "_WIN32\|_WIN64\|__MINGW32__\|__MINGW64__"`,不是只 grep 第一个。 + +--- + ## ⭐ 一个名字,不是五个 五处补丁全部守卫在 **`OPENKAL`** 上,`cflags` 和 `cxxflags` 各给一次 diff --git a/llvm/libunwind/include/__libunwind_config.h b/llvm/libunwind/include/__libunwind_config.h index 980d11ef..629ee14f 100644 --- a/llvm/libunwind/include/__libunwind_config.h +++ b/llvm/libunwind/include/__libunwind_config.h @@ -46,7 +46,24 @@ # define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_X86 # elif defined(__x86_64__) # define _LIBUNWIND_TARGET_X86_64 1 -# if defined(_WIN64) +// ─── openkal ─── BEGIN +// The record's SIZE, not a platform service --- `_WIN64` selects the Win64 +// calling convention's larger register set, the same fact `openkal-musl`'s +// `okm_setjmp.S` sizes `jmp_buf` for, and it is gone on this target along +// with `_WIN32`. THIS HEADER IS INSTALLED (`llvm/libunwind/include/`, read +// through `unwind.h` and `libunwind.h`, both public), so it cannot read +// `OPENKAL_TARGET_WINDOWS` --- that is this package's own private build +// define (mcpp.toml) and an application calling `unw_getcontext` directly +// against `unw_context_t` does not see it. `__CYGWIN__` is read instead, +// for the same reason `openkal-musl`'s `bits/setjmp.h` reads it: mcpp keeps +// it defined target-wide, an application's compile included. +// `UnwindRegistersSave.S` writes exactly the record sized here and reads +// `OPENKAL_TARGET_WINDOWS` rather than `__CYGWIN__`, because it is NOT +// installed --- compiled only by this package's own build, like +// `okm_setjmp.S`. The two answer the same question about the same target +// without reading the same macro, which is what has to hold; see that file. +# if defined(_WIN64) || defined(__CYGWIN__) +// ─── openkal ─── END # define _LIBUNWIND_CONTEXT_SIZE 54 # ifdef __SEH__ # define _LIBUNWIND_CURSOR_SIZE 204 diff --git a/llvm/libunwind/src/AddressSpace.hpp b/llvm/libunwind/src/AddressSpace.hpp index 1ecf7470..ff5c17cd 100644 --- a/llvm/libunwind/src/AddressSpace.hpp +++ b/llvm/libunwind/src/AddressSpace.hpp @@ -109,7 +109,16 @@ extern char __eh_frame_hdr_end; extern char __exidx_start; extern char __exidx_end; -#elif defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) && defined(_WIN32) +// ─── openkal ─── BEGIN +// `_WIN32` on this line is the OUTER question --- "is this even the PE +// branch" --- and it is an identity question, not a platform-service one; +// the service question is the `OPENKAL` that already decides what is INSIDE +// the branch, just below. `OPENKAL_TARGET_WINDOWS` (mcpp.toml) is what +// answers the outer one now that `_WIN32` does not reach this target. See +// `config.h` for the fuller account. +#elif defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) && \ + (defined(_WIN32) || defined(OPENKAL_TARGET_WINDOWS)) +// ─── openkal ─── END // ─── openkal ─── BEGIN // @@ -589,7 +598,12 @@ inline bool LocalAddressSpace::findUnwindSections( (void *)info.arm_section, (void *)info.arm_section_length); if (info.arm_section && info.arm_section_length) return true; -#elif defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) && defined(_WIN32) +// ─── openkal ─── BEGIN +// The same outer identity question as the declaration above, answered the +// same way. +#elif defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) && \ + (defined(_WIN32) || defined(OPENKAL_TARGET_WINDOWS)) +// ─── openkal ─── END // ─── openkal ─── BEGIN #if defined(OPENKAL) // The same walk upstream does, over one module instead of every module, and diff --git a/llvm/libunwind/src/Registers.hpp b/llvm/libunwind/src/Registers.hpp index 88c2d3b4..1a9a05b0 100644 --- a/llvm/libunwind/src/Registers.hpp +++ b/llvm/libunwind/src/Registers.hpp @@ -347,12 +347,20 @@ class _LIBUNWIND_HIDDEN Registers_x86_64 { uint64_t __cs; uint64_t __fs; uint64_t __gs; -#if defined(_WIN64) +// ─── openkal ─── BEGIN +// This struct's own layout, the C++ side of the fact `UnwindRegistersSave.S` +// writes and `__libunwind_config.h` sizes `unw_context_t` for --- all three +// have to agree, so all three read the target the same way. This file is +// not installed (`llvm/libunwind/src/`, compiled only by this package's own +// build), so it reads `OPENKAL_TARGET_WINDOWS` (mcpp.toml) as the assembly +// file does, rather than `__CYGWIN__` as the installed header does. +#if defined(_WIN64) || defined(OPENKAL_TARGET_WINDOWS) +// ─── openkal ─── END uint64_t __padding; // 16-byte align #endif }; GPRs _registers; -#if defined(_WIN64) +#if defined(_WIN64) || defined(OPENKAL_TARGET_WINDOWS) v128 _xmm[16]; #endif }; @@ -568,7 +576,11 @@ inline void Registers_x86_64::setFloatRegister(int, double) { } inline bool Registers_x86_64::validVectorRegister(int regNum) const { -#if defined(_WIN64) +// ─── openkal ─── BEGIN +// Whether `_xmm` exists at all in this object, the same fact the struct +// definition above reads the same way. +#if defined(_WIN64) || defined(OPENKAL_TARGET_WINDOWS) +// ─── openkal ─── END if (regNum < UNW_X86_64_XMM0) return false; if (regNum > UNW_X86_64_XMM15) @@ -581,7 +593,7 @@ inline bool Registers_x86_64::validVectorRegister(int regNum) const { } inline v128 Registers_x86_64::getVectorRegister(int regNum) const { -#if defined(_WIN64) +#if defined(_WIN64) || defined(OPENKAL_TARGET_WINDOWS) assert(validVectorRegister(regNum)); return _xmm[regNum - UNW_X86_64_XMM0]; #else @@ -591,7 +603,7 @@ inline v128 Registers_x86_64::getVectorRegister(int regNum) const { } inline void Registers_x86_64::setVectorRegister(int regNum, v128 value) { -#if defined(_WIN64) +#if defined(_WIN64) || defined(OPENKAL_TARGET_WINDOWS) assert(validVectorRegister(regNum)); _xmm[regNum - UNW_X86_64_XMM0] = value; #else diff --git a/llvm/libunwind/src/UnwindRegistersRestore.S b/llvm/libunwind/src/UnwindRegistersRestore.S index 76a80344..061592b5 100644 --- a/llvm/libunwind/src/UnwindRegistersRestore.S +++ b/llvm/libunwind/src/UnwindRegistersRestore.S @@ -79,7 +79,13 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_x86_64_jumpto) # # extern "C" void __libunwind_Registers_x86_64_jumpto(Registers_x86_64 *); # -#if defined(_WIN64) +# ─── openkal ─── BEGIN +# Same target, same reason as `UnwindRegistersSave.S`: the machine code is +# still Win64, so the pointer still arrives in `%rcx`, and `_WIN64` is what +# used to say so before this target stopped defining it along with +# `_WIN32`. `OPENKAL_TARGET_WINDOWS` (mcpp.toml) is read in its place. +#if defined(_WIN64) || defined(OPENKAL_TARGET_WINDOWS) +# ─── openkal ─── END # On entry, thread_state pointer is in rcx; move it into rdi # to share restore code below. Since this routine restores and # overwrites all registers, we can use the same registers for @@ -120,7 +126,10 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_x86_64_jumpto) # skip fs # skip gs -#if defined(_WIN64) +# ─── openkal ─── BEGIN +# The vector registers, the second half of the same fact. +#if defined(_WIN64) || defined(OPENKAL_TARGET_WINDOWS) +# ─── openkal ─── END movdqu 176(%rdi),%xmm0 movdqu 192(%rdi),%xmm1 movdqu 208(%rdi),%xmm2 diff --git a/llvm/libunwind/src/UnwindRegistersSave.S b/llvm/libunwind/src/UnwindRegistersSave.S index f988fd46..4d84746d 100644 --- a/llvm/libunwind/src/UnwindRegistersSave.S +++ b/llvm/libunwind/src/UnwindRegistersSave.S @@ -121,7 +121,17 @@ DEFINE_LIBUNWIND_FUNCTION("#__unw_getcontext") # thread_state pointer is in rdi # DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) -#if defined(_WIN64) +# ─── openkal ─── BEGIN +# Which argument register the caller used, not a platform service --- the +# machine code this package produces is still Win64 (see the design's own +# table for this target), so a caller here still passes `thread_state` in +# `%rcx`, and reading it out of `%rdi` instead is what a SysV callee does. +# `_WIN64` is gone from this target along with `_WIN32`; +# `OPENKAL_TARGET_WINDOWS` (mcpp.toml) is this package's own manifest +# stating it, exactly as `okm_setjmp.S` states the equivalent fact for +# `jmp_buf` --- this file is not installed, so it can read it. +#if defined(_WIN64) || defined(OPENKAL_TARGET_WINDOWS) +# ─── openkal ─── END #define PTR %rcx #define TMP %rdx #else @@ -154,7 +164,13 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) # skip fs # skip gs -#if defined(_WIN64) +# ─── openkal ─── BEGIN +# The second of two: whether the vector registers this record holds are the +# caller's to preserve. On Win64 they are not, so this file must save them; +# on SysV the callee owns none of them and there is nothing here to save. +# Same target, same answer as above. +#if defined(_WIN64) || defined(OPENKAL_TARGET_WINDOWS) +# ─── openkal ─── END movdqu %xmm0,176(PTR) movdqu %xmm1,192(PTR) movdqu %xmm2,208(PTR) diff --git a/llvm/libunwind/src/assembly.h b/llvm/libunwind/src/assembly.h index 84c9d526..c3d7829d 100644 --- a/llvm/libunwind/src/assembly.h +++ b/llvm/libunwind/src/assembly.h @@ -184,7 +184,14 @@ #define NO_EXEC_STACK_DIRECTIVE #endif -#elif defined(_WIN32) +// ─── openkal ─── BEGIN +// +// Assembler directive syntax by object format, not a platform service --- +// the same identity question `config.h` asks, asked again because this file +// is included from `.S` sources rather than from C++. `_WIN32` does not +// reach this target; `OPENKAL_TARGET_WINDOWS` (mcpp.toml) does. +#elif defined(_WIN32) || defined(OPENKAL_TARGET_WINDOWS) +// ─── openkal ─── END #define SYMBOL_IS_FUNC(name) \ .def name SEPARATOR \ @@ -202,7 +209,12 @@ #endif #define HIDDEN_SYMBOL(name) -#if defined(__MINGW32__) +// ─── openkal ─── BEGIN +// `__MINGW32__` is also gone on this target; `OPENKAL_TARGET_WINDOWS` takes +// this branch in its place, for the same reason and with the same +// measurement behind it as `config.h`'s `_LIBUNWIND_WEAK_ALIAS`. +#if defined(__MINGW32__) || defined(OPENKAL_TARGET_WINDOWS) +// ─── openkal ─── END #define WEAK_ALIAS(name, aliasname) \ .globl SYMBOL_NAME(aliasname) SEPARATOR \ EXPORT_SYMBOL(aliasname) SEPARATOR \ diff --git a/llvm/libunwind/src/config.h b/llvm/libunwind/src/config.h index f017403f..f363fbac 100644 --- a/llvm/libunwind/src/config.h +++ b/llvm/libunwind/src/config.h @@ -31,7 +31,16 @@ #if defined(__aarch64__) || defined(__arm64__) || defined(__arm64e__) #define _LIBUNWIND_TRACE_RET_INJECT 1 #endif -#elif defined(_WIN32) +// ─── openkal ─── BEGIN +// +// `_WIN32` names the target format here, not a platform service --- unlike +// the sites `llvm/PATCHES.md` records, nothing below reaches ``. +// openkal-musl's `[c-abi] presents = "posix"` leaves `_WIN32` undefined on +// this target, so the identity has to come from somewhere else: +// `OPENKAL_TARGET_WINDOWS`, which this package's own manifest defines for +// its build of this target (mcpp.toml, `[target.'cfg(windows)'.build]`). +#elif defined(_WIN32) || defined(OPENKAL_TARGET_WINDOWS) +// ─── openkal ─── END #ifdef __SEH__ #define _LIBUNWIND_SUPPORT_SEH_UNWIND 1 #else @@ -102,8 +111,21 @@ #define _LIBUNWIND_WEAK_ALIAS(name, aliasname) \ extern "C" _LIBUNWIND_EXPORT __typeof(name) aliasname \ __attribute__((weak, alias(#name))); -#elif defined(_WIN32) -#if defined(__MINGW32__) +// ─── openkal ─── BEGIN +// +// Same target, same reason: `_WIN32` is gone on this target, and +// `OPENKAL_TARGET_WINDOWS` (mcpp.toml) stands in for it. The inner choice is +// answered the same way: `__MINGW32__` is gone with it, so this always takes +// the GNU form under openkal rather than the `__pragma(comment(linker, …))` +// one, which is a construct of the MSVC driver this package is never built +// with. openkal-musl's own `port/include/features.h` already measured that a +// STRONG alias made with `__attribute__((alias(...)))` --- the form below, +// `weak` omitted because this object format has no weak definition --- links +// on this object format with this toolchain; that measurement is the reason +// this is not a guess. +#elif defined(_WIN32) || defined(OPENKAL_TARGET_WINDOWS) +#if defined(__MINGW32__) || defined(OPENKAL_TARGET_WINDOWS) +// ─── openkal ─── END #define _LIBUNWIND_WEAK_ALIAS(name, aliasname) \ extern "C" _LIBUNWIND_EXPORT __typeof(name) aliasname \ __attribute__((alias(#name))); @@ -132,10 +154,20 @@ #endif #endif +// ─── openkal ─── BEGIN +// +// `__MINGW32__` is one of the names that already routes this to +// `__builtin_alloca` rather than to the `_WIN32`-only branch below, which +// calls `_malloca` / `_freea` --- routines of the MSVC CRT that this +// package's C library does not carry. `OPENKAL_TARGET_WINDOWS` has to join +// it HERE, at the same tier as `__MINGW32__`, and not be added to the +// `_WIN32`-only branch below: `__MINGW32__` is also gone on this target, and +// without this the branch that assumes MSVC's CRT would be the one taken. +// ─── openkal ─── END #ifndef _LIBUNWIND_REMEMBER_HEAP_ALLOC #if defined(_LIBUNWIND_REMEMBER_STACK_ALLOC) || defined(__APPLE__) || \ defined(__linux__) || defined(__ANDROID__) || defined(__MINGW32__) || \ - defined(_LIBUNWIND_IS_BAREMETAL) + defined(OPENKAL_TARGET_WINDOWS) || defined(_LIBUNWIND_IS_BAREMETAL) #define _LIBUNWIND_REMEMBER_ALLOC(_size) __builtin_alloca(_size) #define _LIBUNWIND_REMEMBER_FREE(_ptr) \ do { \ diff --git a/mcpp.toml b/mcpp.toml index e3def1da..6180fe48 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-llvm-runtime" -version = "0.10.0" +version = "0.11.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"] @@ -216,7 +216,7 @@ sources = [ cflags = ["-DDISABLE_AARCH64_FMV=1"] [dependencies] -openkal-musl = "0.14.0" +openkal-musl = "0.15.0" [build] @@ -516,10 +516,26 @@ sources = [ ] [target.'cfg(windows)'.build] +# WHICH TARGET THIS IS, STATED HERE RATHER THAN READ OFF `_WIN32`. +# +# `openkal-musl`'s `[c-abi] presents = "posix"` leaves `_WIN32` (and +# `__MINGW32__`) undefined on this target now — the C environment a program +# sees is POSIX, which is correct, and it is also what turned five identity +# questions in vendored libunwind (`config.h`, `AddressSpace.hpp`, +# `assembly.h`) into a hard `#error Unsupported target` or a silent fall into +# the ELF branch. None of those five is the ``-reaching kind — +# those are already answered by `OPENKAL` (package-wide, see the `cflags` / +# `cxxflags` entries below) and stay answered regardless of this. These five +# ask only "is this PE", which is a fact about the target and belongs here, +# by the same reasoning `openkal-musl`'s own `port/src` now states it rather +# than reading `_WIN32`. See `llvm/PATCHES.md`. +# # `-fdwarf-exceptions` WAS HERE AND IS NOT ANY MORE, for the reason the macOS # block above records: it decides what a `throw` COMPILES INTO, so it is true of # the graph rather than of this package. mcpp derives it; see # `graph_runtime_compile_flags`. +cflags = ["-DOPENKAL_TARGET_WINDOWS=1"] +cxxflags = ["-DOPENKAL_TARGET_WINDOWS=1"] sources = [ "!llvm/libcxx/src/filesystem/int128_builtins.cpp", "llvm/compiler-rt/lib/builtins/*.c", @@ -554,6 +570,12 @@ sources = [ # `floatdidf.c` and its neighbours, which the generic list above already # supplies; taking the whole directory would define each of them twice. "llvm/compiler-rt/lib/builtins/x86_64/chkstk.S", + # `__dso_handle`, ONE SYMBOL OF `crtbegin.c` RATHER THAN THE WHOLE FILE --- + # see `port/src/dso_handle.c` for why the rest of that file is not wanted + # and this one symbol from it is. Not caught by the package-wide + # `port/src/*.cpp` glob above because it is `.c`, to match what it is + # standing in for. + "port/src/dso_handle.c", "!llvm/compiler-rt/lib/builtins/atomic*.c", "!llvm/compiler-rt/lib/builtins/clear_cache.c", # AND THE EXCLUSION IS DELETED RATHER THAN LEFT BELOW THE INCLUSION — an diff --git a/port/src/dso_handle.c b/port/src/dso_handle.c new file mode 100644 index 00000000..62957ca3 --- /dev/null +++ b/port/src/dso_handle.c @@ -0,0 +1,34 @@ +/* `__dso_handle`, which a dynamic loader supplies and this arrangement has + * none of --- the same fact `AddressSpace.hpp`'s `__ImageBase` read and + * `RWMutex.hpp`'s pthread route are already about, stated once more for the + * one symbol neither of them reaches. + * + * The C++ ABI passes this to `__cxa_atexit` as the third argument, so that an + * unloaded shared object's destructors can be told apart from every other + * one's. A statically linked openkal program is one module and never unloads + * anything short of exiting, and openkal-musl's own `__cxa_atexit` + * (musl/src/exit/atexit.c) does not read the argument at all --- so any + * stable address is a correct answer, and this is the same one + * `compiler-rt/lib/builtins/crtbegin.c` gives on every format that reaches + * it, taken alone rather than with the rest of that file: the rest drives + * `.init_array`, which `okm_start.c` already does, and driving it twice is + * not a slower program but a different one (see `AddressSpace.hpp` for the + * fuller account of that). + * + * WINDOWS ONLY, AND FOR A REASON THAT IS mcpp'S RATHER THAN THIS PACKAGE'S. + * On ELF and on Mach-O, clang gives every translation unit that needs one a + * PRIVATE `__dso_handle` of its own --- measured with `nm`, a local symbol, + * never an external reference, so nothing here is missing on those formats. + * On this target only, it is an external reference. Measured: compiling the + * same translation unit once with `--target=x86_64-w64-windows-gnu` alone + * and once with the second `--target=x86_64-pc-cygwin` mcpp's `[c-abi]` + * realization adds (see mcpp.toml, `[target.'cfg(windows)'.build]`), only + * the second leaves `__dso_handle` undefined. The substitution that states + * `__CYGWIN__` for the preprocessor also changes what clang assumes about + * the runtime beneath --- a real Cygwin has a real loader, and this build + * does not --- and supplying the symbol is the correct response to that, + * not a workaround for it: the C++ ABI always required something to supply + * this, and on every other format something already did. */ +#if defined(OPENKAL_TARGET_WINDOWS) +__attribute__((visibility("hidden"))) void *__dso_handle = &__dso_handle; +#endif