diff --git a/changes/linux-ci-link.md b/changes/linux-ci-link.md new file mode 100644 index 00000000..95255158 --- /dev/null +++ b/changes/linux-ci-link.md @@ -0,0 +1,9 @@ +bump: patch +type: fix +**The toolchain links on Linux again** — CI builds, tests and publishes releases. + +The asset-pack boot finds the directory beside the executable with `_NSGetExecutablePath`, +which Darwin's libc has and glibc does not, so every Linux link of the compiler seed failed +from the release that added packs onward: CI's build, suite and C-free bootstrap jobs, and +every `publish` run, so no release after v0.7.0 carried a Linux toolchain or was created by +CI at all. `tools/ci/linux_stdio_shim.ll` now supplies it, over `/proc/self/exe`. diff --git a/tools/ci/linux_stdio_shim.ll b/tools/ci/linux_stdio_shim.ll index 44806336..53f3cbee 100644 --- a/tools/ci/linux_stdio_shim.ll +++ b/tools/ci/linux_stdio_shim.ll @@ -6,7 +6,10 @@ ; any Ludic binary fails with "undefined reference to __stderrp". ; ; This shim defines the two Darwin-named globals and, in a startup constructor, -; points them at glibc's real FILE* streams. It is pure LLVM IR — no C compiler +; points them at glibc's real FILE* streams. It also supplies _NSGetExecutablePath, +; which the asset-pack boot (emit_pak.ludic) calls to find the directory beside the +; executable: Darwin has it, glibc does not, and without it every Linux link of the +; seed failed - which is why CI stopped publishing releases after v0.7.0. It is pure LLVM IR — no C compiler ; and no .c source — so it keeps the toolchain's C-free guarantee intact. It is ; injected on Linux only, via LUDIC_CC (see .forgejo/workflows/ci.yml); macOS ; never links it (libc already provides __stdoutp/__stderrp). @@ -31,3 +34,27 @@ entry: store ptr %e, ptr @__stderrp ret void } + +; _NSGetExecutablePath(buf, &size) -> 0 on success: the running executable's path, +; NUL-terminated, from /proc/self/exe. Same contract as Darwin's (the size is the +; buffer's capacity in; -1 back when the path does not fit or cannot be read). +declare i64 @readlink(ptr, ptr, i64) +@.ludic_self_exe = private unnamed_addr constant [15 x i8] c"/proc/self/exe\00" + +define i32 @_NSGetExecutablePath(ptr %buf, ptr %szp) { +entry: + %sz = load i32, ptr %szp + %cap = sub i32 %sz, 1 + %cap64 = sext i32 %cap to i64 + %n = call i64 @readlink(ptr @.ludic_self_exe, ptr %buf, i64 %cap64) + %bad = icmp slt i64 %n, 0 + %full = icmp sge i64 %n, %cap64 + %no = or i1 %bad, %full + br i1 %no, label %fail, label %ok +ok: + %end = getelementptr i8, ptr %buf, i64 %n + store i8 0, ptr %end + ret i32 0 +fail: + ret i32 -1 +}