From 625efa59587a6cd78e40d6a22382cdcdd47fa8a1 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 11 Sep 2026 16:15:35 +0800 Subject: [PATCH 1/2] ci: Android shares this implementation, and CI says so mcpp 2026.9.11.3 makes `aarch64-linux-android` and `x86_64-linux-android` real target rows, and the question that raises for this package has a short answer: nothing here changes. This file is written on the Linux kernel's own system-call interface and borrows nothing from any C library. Android's kernel IS Linux, the per-architecture system-call ABI is the same, and `src/sys.h` branches on `__x86_64__` / `__aarch64__` -- the architecture, not the operating system. A claim about what does not change is exactly the kind that rots unmeasured, so one step now asserts it. THE SECOND HALF IS THE INTERESTING ONE. Compiling is the weaker statement; the property this package exists for is that its objects name no C library symbol, and on Android the C library is a DIFFERENT one. A reference that resolved to glibc by habit would appear here as a bionic name, so the same permitted set is applied to the same kind of output for another libc. The architecture of the objects is asserted too rather than assumed from `--target`, because one of the two is not the runner's. TWO NAMES THE NATIVE SET DOES NOT HAVE, and measurement decides them: `__emutls_get_address` and `__aarch64_swp4_acq` are both defined in the NDK's `libclang_rt.builtins--android.a` and in NEITHER bionic `libc.so`. They belong to the category the set already admits for memcpy and its three neighbours -- compiler-emitted, computing rather than calling, and incapable of re-entering this implementation. The emulated-TLS one is not mcpp's choice: `build.ninja` for this target carries no `-femulated-tls`, and the NDK's clang emits the reference by itself from a bare `thread_local int x;` at API 21. They are permitted in this step only; widening the native step's set for a target it was not measured on is how a check stops being one. One matrix leg, because the axis does not reach this target: the Android rows carry a capability pin, so mcpp refuses any other toolchain for them and `matrix.toolchain` selects nothing here. NOT EXECUTION, and the step says so. Running an Android artifact needs a device or an emulator. Measured by hand on an API 24 x86_64 image: a program written against openkal alone -- no C library, no `import std` -- printed `openkal: 1-2-3` with exit 0. The device ABI has no execution path from an x86_64 runner at all, since Google's emulator refuses a foreign guest. Verified in both directions by extracting the step and running it locally: it passes on both ABIs, and with a `puts` call added it reports "references a symbol it must not: puts" for each and exits 1. --- .github/workflows/ci.yml | 110 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a743899..cdc9a0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -303,6 +303,116 @@ jobs: test "$bad" -eq 0 echo "the implementation references no C library symbol" + # ANDROID SHARES THIS IMPLEMENTATION, AND THIS IS WHAT SAYS SO. + # + # This file is written on the Linux kernel's own system-call interface and + # borrows nothing from any C library. Android's kernel IS Linux, the + # per-architecture system-call ABI is the same, and `src/sys.h` branches + # on `__x86_64__` / `__aarch64__` -- the architecture, not the operating + # system. So the claim is that nothing here needs to change, and a claim + # about what does not change is exactly the kind that rots unmeasured. + # + # THE SECOND HALF IS THE INTERESTING ONE. Compiling is the weaker + # statement; the property this package exists for is that its objects + # name no C library symbol, and on Android the C library is a DIFFERENT + # one. A reference that resolved to glibc by habit would show up here as + # a bionic name, so the same permitted set is applied to the same kind of + # output for a different libc. + # + # ONE MATRIX LEG, AND THE AXIS DOES NOT REACH THIS TARGET. The Android + # rows carry a CAPABILITY pin (`android-ndk@...`), which means mcpp + # refuses any other toolchain for them -- so `matrix.toolchain` selects + # nothing here and running both legs would download the NDK twice for one + # signal. + # + # NOT EXECUTION. Running an Android artifact needs a device or an + # emulator: measured by hand on an API 24 x86_64 image, where a program + # written against openkal alone printed `openkal: 1-2-3` with exit 0, and + # the device ABI has no execution path from an x86_64 runner at all + # (Google's emulator refuses a foreign guest). This job asserts the two + # things a runner can. + - name: Android shares this implementation, and its objects still name no C library + if: matrix.toolchain == 'llvm@22.1.8' + run: | + permitted='^(memcpy|memmove|memset|memcmp|__libc_start_main|main|_GLOBAL_OFFSET_TABLE_|kal_[a-z_]+|__init_array_start|__init_array_end|__preinit_array_start|__preinit_array_end|_ZN3okl.*)$' + weak_permitted='^environ$' + + # TWO NAMES THE NATIVE SET DOES NOT HAVE, AND THEY ARE NOT A C + # LIBRARY'S. + # + # Measured: both are defined in the NDK's own + # `libclang_rt.builtins--android.a` and in NEITHER bionic + # `libc.so` -- so they belong to the same category the set above + # already admits for memcpy and its three neighbours: emitted by the + # compiler, computing rather than calling, and incapable of + # re-entering this implementation. + # + # __emutls_get_address emulated thread-local storage. NOT mcpp's + # choice: `build.ninja` for this target + # carries no `-femulated-tls`, and the NDK's + # clang emits this reference by itself from + # a bare `thread_local int x;` at API 21, + # which is its documented default below the + # level where bionic gained ELF TLS. + # __aarch64_swp4_acq an outline-atomics helper, aarch64 only, + # which is why it appears on one ABI and not + # the other. + # + # Kept in this step rather than widened into the native one: nothing + # about the native build has been measured to need them, and a + # permitted set that grows for a target it was not measured on is how + # a check stops being one. + permitted_compiler_rt='^(__emutls_get_address|__aarch64_[a-z0-9_]+)$' + rm -f "$RUNNER_TEMP/android.bad" + + for target in aarch64-linux-android x86_64-linux-android; do + echo "== $target ==" + rm -rf target + mcpp build --target "$target" --features standalone + + objs=$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o' ! -name 'conformance*') + test -n "$objs" || { + echo "::error::no objects were found for $target; the check would pass vacuously" >&2 + exit 1 + } + + # The objects are for the guest architecture, and one of the two is + # not this runner's -- so assert the architecture rather than trust + # that `--target` was honoured. `file` names it for both. + case "$target" in + aarch64-*) want='ARM aarch64' ;; + x86_64-*) want='x86-64' ;; + esac + one=$(printf '%s\n' $objs | head -1) + file "$one" | grep -q "$want" || { + echo "::error::$one is not $want: $(file "$one")" >&2 + exit 1 + } + + nm --undefined-only $objs | awk '{ print $1, $2 }' | sort -u | + while read -r kind name; do + [ -n "$name" ] || continue + printf '%s\n' "$name" | grep -qE "$permitted" && continue + printf '%s\n' "$name" | grep -qE "$permitted_compiler_rt" && continue + if printf '%s\n' "$name" | grep -qE "$weak_permitted"; then + case "$kind" in + w|v) continue ;; + *) echo "::error::$name is permitted only as a weak reference, and this one is '$kind'" >&2 ;; + esac + else + echo "::error::$target references a symbol it must not: $name" >&2 + fi + echo bad >> "$RUNNER_TEMP/android.bad" + done + done + + if [ -s "$RUNNER_TEMP/android.bad" ]; then + rm -f "$RUNNER_TEMP/android.bad" + exit 1 + fi + rm -f "$RUNNER_TEMP/android.bad" + echo "both Android ABIs build from this implementation unchanged, and name no C library symbol" + # A checker is only useful if it fails when it should. - name: The independence check detects a dependence run: | From c445cbaf94285e4806443918e3544ce10f664b01 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 11 Sep 2026 16:55:47 +0800 Subject: [PATCH 2/2] ci: bump the engine pin with the step that needs it The Android step ran against mcpp 2026.8.27.1 -- this repository's pinned `MCPP_VERSION` -- where `aarch64-linux-android` and `x86_64-linux-android` do not exist as target rows at all, so it failed with exit 2. The rows become real in 2026.9.11.3, which is now tagged. This is the CI pin, which is what this repository is TESTED against, and not a floor: nothing here records a minimum engine for consumers, and moving a floor would make a client stopped at the old one unable to use this package at all. The job stays red until the index carries 2026.9.11.3 -- `xlings install mcpp@$MCPP_VERSION` can only resolve what the published index has, and the release's own ecosystem job is what puts it there. That ordering is the dependency this PR already records, now expressed in the file rather than only in prose. --- .github/workflows/ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cdc9a0a..bd9a26a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,13 @@ on: default: "" env: MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF }} - MCPP_VERSION: 2026.8.27.1 + # BUMPED WITH THE ANDROID STEP, BECAUSE THAT STEP NEEDS THIS ENGINE. + # `aarch64-linux-android` and `x86_64-linux-android` become real target + # rows in 2026.9.11.3; on 2026.8.27.1 they do not exist and the step + # fails with exit 2. This is the CI pin -- what this repository is + # tested against -- and not a floor: nothing here records a minimum + # engine for consumers. + MCPP_VERSION: 2026.9.11.3 XLINGS_VERSION: v2026.8.17.2 XLINGS_NON_INTERACTIVE: '1'