From 595971f51489dedf129d9e54b30d95993bbec59b Mon Sep 17 00:00:00 2001 From: speak-agent Date: Fri, 11 Sep 2026 20:08:42 +0800 Subject: [PATCH] The suite can be given flags of its own, and the portable example builds again THE PORTABLE EXAMPLE BUILT NOWHERE, AND THE PINS ARE WHY. `src/main.cpp` was updated for 0.11's `kal_spawn` -- one struct where there had been three declarations -- and `examples/portable/mcpp.toml` still pinned openkal 0.10.0: src/main.cpp:256:19: error: 'kal_spawn' does not name a type identically for the host and for every cross target. A version pin in an example is a claim about what that example builds against, and nothing was checking it: the program is not in any package's build, so no repository's continuous integration compiled it. The four pins now name the published versions, and the example runs -- 0 observations that did not hold on the host. AND IT GAINS THE THREE PLATFORMS, WHICH IS TWO LINES AND NOT THREE. Android NO LINE. `aarch64-linux-android` has `os = "linux"` because the kernel IS Linux; bionic is a C library above it, and an implementation written on the kernel's own interface does not know which C library sits above it. `cfg(os = "linux")` already selects `openkal-linux` for both ABIs, and an Android line would be a second name for one answer. iOS ONE LINE, no package. Darwin is Darwin -- the same traps, the same call numbers, the same calling convention -- and what differs between macOS and iOS is the SDK and the deployment-target flag, which belong to the build tool. `cfg(os = "ios")` matches all three iOS rows. Web ONE LINE and new software. `openkal-emscripten` is the first implementation in this ecosystem written ABOVE a C library, which clause 2 permits in as many words. FLAGS THE SUITE ITSELF HAS TO CARRY (`OPENKAL_CONFORMANCE_SUITE_FLAGS`). `OPENKAL_CONFORMANCE_IMPL_FEATURES` names features of the IMPLEMENTATION, and for every platform so far that was the whole of what an unusual arrangement needed. Emscripten is the first where it is not: its thread support is selected by `-pthread`, which chooses a different C library build, a different memory model and a different loader contract, so it is a property of the WHOLE LINK. Measured with the implementation's feature active and the suite built without the flag: DID NOT HOLD an execution context starts DID NOT HOLD four execution contexts start DID NOT HOLD contexts that ran at the same time have identities distinct which is the suite being right: if `openkal.task` is provided, a context starts. The variable adds the flags to the suite's compile AND link, because a flag that selects a memory model must reach every translation unit and the link, and one that reached half would produce a suite whose halves disagree. It does not yet reach far enough, and that is recorded rather than papered over: the specification package's own modules are compiled without it, so a run with the variable set now fails with error: POSIX thread support was disabled in precompiled file '.../pcm.cache/openkal.types.pcm' but is currently enabled A whole-graph flag channel is an engine change; `openkal-emscripten` gates the interface behind a feature so that the absence is a link error rather than a present-and-failing operation, and its README carries the rest. AND THE FEATURE STAMP HAS TWO AXES NOW. The paragraph in this script about a feature-set change not invalidating the build applies to the new variable identically, and the first run that used it reported the previous run's build: a module cache compiled without the flag. The record is now `features|flags`. `check-surface.sh` TAKES `$NM`. A wasm object is not an ELF, and the host's binutils `nm` reports "file format not recognized" for one; the reader that does read it ships with the toolchain that produced it. Named in the environment like the runner and the features, with the default unchanged. Same class of defect as the leading-underscore note beside it, and found the same way: by writing another implementation. --- examples/portable/README.md | 58 +++++++++++++++++++++++++++++++++-- examples/portable/mcpp.toml | 61 ++++++++++++++++++++++++++++++++++--- tools/check-surface.sh | 15 ++++++++- tools/run-conformance.sh | 59 +++++++++++++++++++++++++++++++++-- 4 files changed, 183 insertions(+), 10 deletions(-) diff --git a/examples/portable/README.md b/examples/portable/README.md index ed57de2..1b4ecef 100644 --- a/examples/portable/README.md +++ b/examples/portable/README.md @@ -18,6 +18,58 @@ It prints one line per interface, each beginning `openkal: `, and a final line reporting the number of observations that did not hold. An implementation passes when that number is zero. -The program requires an implementation that provides every interface. An -implementation that omits one fails to link, and the linker names the operations -it did not define — which is the diagnostic clause 4.2 describes. +The program requires an implementation that provides every interface it uses. +An implementation that omits one fails to link, and the linker names the +operations it did not define — which is the diagnostic clause 4.2 describes. + +## Five platforms, and the three ways one is reached + +The manifest names an implementation per platform and the program names none. +What is worth reading in it is that the five platforms are reached in three +different ways: + +| platform | line in the manifest | what supplies it | +|---|---|---| +| Linux (glibc, musl) | `cfg(os = "linux")` | `openkal-linux` | +| Android, both ABIs | the SAME line | `openkal-linux`, unchanged | +| macOS | `cfg(os = "macos")` | `openkal-macos` | +| iOS, both simulator arches | `cfg(os = "ios")` | `openkal-macos`, unchanged | +| Windows | `cfg(windows)` | `openkal-windows` | +| Web (Emscripten) | `cfg(os = "emscripten")` | `openkal-emscripten` | + +**Android needs no line of its own**, because `aarch64-linux-android` has +`os = "linux"`: the kernel IS Linux, bionic is a C library above it, and an +implementation written on the kernel's own system-call interface does not know +which C library sits above it. Adding an Android line would be adding a second +name for one answer. + +**iOS needs a line and not a package.** Darwin is Darwin — the same traps, the +same call numbers, the same calling convention — and what differs between macOS +and iOS is the SDK and the deployment-target flag, which belong to the build +tool. So one `cfg` line selects the macOS implementation for all three iOS rows. + +**The Web needed new software.** Emscripten has no kernel to issue a call to, +so an implementation there cannot be written beneath a C library and has to sit +above one. `openkal-emscripten` provides twelve of the fifteen interfaces; this +program uses eight, all of which are among them. A program that used +`openkal.process` would fail at link naming the symbol, which is how a partial +surface reports itself. + +## The version pins here are a claim, and they were wrong + +`src/main.cpp` was updated for 0.11's `kal_spawn` — one struct where there had +been three declarations — and these pins were not, so the example built +NOWHERE: + +``` +src/main.cpp:256:19: error: 'kal_spawn' does not name a type +``` + +It failed identically for the host and for every cross target, because a +version pin in an example is a claim about what that example builds against and +nothing was checking it. The program is not in any package's build, so no +repository's continuous integration compiled it. + +That is what the platform lines above are for as much as demonstration: each +one is a target something can be asked to build, and the specification's own +conformance job builds this source over each implementation it tests. diff --git a/examples/portable/mcpp.toml b/examples/portable/mcpp.toml index 3968e93..4c98ed7 100644 --- a/examples/portable/mcpp.toml +++ b/examples/portable/mcpp.toml @@ -9,13 +9,66 @@ name = "portable" version = "0.1.0" [dependencies] -openkal = "0.10.0" +openkal = "0.12.0" +# AND THE VERSIONS HAVE TO FOLLOW THE SOURCE, which is the reason this example +# is a criterion rather than an assumption. +# +# `src/main.cpp` was updated for 0.11's `kal_spawn` --- one struct where there +# had been three declarations --- and these four pins were not. So the example +# built nothing on any target: +# +# src/main.cpp:256:19: error: 'kal_spawn' does not name a type +# +# A version pin in an example is a claim about what that example builds +# against, and nothing checked it: the example is not in any package's build, +# so no repository's continuous integration compiled it. The platform legs +# below exist so that it cannot go stale silently again. [target.'cfg(os = "linux")'.dependencies] -openkal-linux = "0.9.0" +openkal-linux = "0.12.0" [target.'cfg(os = "macos")'.dependencies] -openkal-macos = "0.7.0" +openkal-macos = "0.9.0" [target.'cfg(windows)'.dependencies] -openkal-windows = "0.5.0" +openkal-windows = "0.7.0" + +# ANDROID IS NOT A LINE HERE, AND THAT IS THE POINT. +# +# `aarch64-linux-android` and `x86_64-linux-android` have `os = "linux"`, +# because the kernel IS Linux: bionic is a C library above it, and an +# implementation written on the kernel's own system-call interface does not +# know which C library sits above it. So `cfg(os = "linux")` above already +# selects `openkal-linux` for both Android rows, and adding an Android line +# would be adding a second name for one answer. +# +# Verified against the released engine rather than assumed: both ABIs build +# over `openkal-linux` and their objects name no C library symbol, and a +# program over openkal alone ran on an emulator. + +# iOS REUSES THE macOS IMPLEMENTATION, AND THAT NEEDED A LINE BECAUSE THE OS +# SEGMENT DIFFERS. +# +# Darwin is Darwin: the same Mach traps, the same BSD call numbers, the same +# calling convention. What differs between macOS and iOS is the SDK and the +# deployment-target flag, which are the build tool's business and not the +# implementation's -- so this is one `cfg` line and no new package. +# +# All three iOS rows match: the device and both simulator arches carry +# `os = "ios"`. +[target.'cfg(os = "ios")'.dependencies] +openkal-macos = "0.9.0" + +# THE WEB IS THE ONE PLATFORM THAT NEEDED NEW SOFTWARE. +# +# Emscripten has no kernel to issue a call to, so an implementation cannot be +# written the way the other four are -- beneath a C library -- and must sit +# ABOVE one, which clause 2 permits. `openkal-emscripten` is that +# implementation. +# +# It provides twelve of the fifteen interfaces. This program uses eight, all of +# which are among them; a program that used `openkal.process` would fail at +# LINK naming the symbol, which is clause 6.2's second time and is how a +# partial surface reports itself. +[target.'cfg(os = "emscripten")'.dependencies] +openkal-emscripten = "0.1.0" diff --git a/tools/check-surface.sh b/tools/check-surface.sh index e90cb74..06d5fd1 100755 --- a/tools/check-surface.sh +++ b/tools/check-surface.sh @@ -32,7 +32,20 @@ spec="$(grep -vE '^[[:space:]]*(#|$)' "$list" | sort -u)" # would find no names at all on one of them — and would report that as success, # because an empty surface contains nothing unspecified. The defect was found by # writing a second implementation, which is what a second implementation is for. -found="$(nm --defined-only "$@" \ +# THE SYMBOL READER IS NAMED, BECAUSE `nm` DOES NOT READ EVERY OBJECT THIS +# ECOSYSTEM PRODUCES. +# +# A wasm object is not an ELF, and the host's binutils `nm` reports +# "file format not recognized" for one. The reader that does read it ships with +# the toolchain that produced it -- `llvm-nm` inside the emsdk payload -- so it +# is named in the environment rather than assumed, in the same way the runner +# and the features are. The default is unchanged, so every existing caller is. +# +# This is the same class of defect as the leading-underscore note above, found +# the same way: by writing another implementation. +NM="${NM:-nm}" + +found="$($NM --defined-only "$@" \ | awk '$2=="T"||$2=="W"||$2=="R"||$2=="D"||$2=="B"||$2=="S"{print $3}' \ | sed 's/^_//' \ | grep '^kal_' | sort -u || true)" diff --git a/tools/run-conformance.sh b/tools/run-conformance.sh index 862a1a7..ac45eaa 100755 --- a/tools/run-conformance.sh +++ b/tools/run-conformance.sh @@ -108,6 +108,47 @@ if [ -n "$impl_features" ]; then impl_line="$package = { path = \"$implementation_native\", features = [\"$impl_features\"] }" fi +# FLAGS THE SUITE ITSELF HAS TO CARRY, WHICH IS NOT THE SAME AS FLAGS THE +# IMPLEMENTATION CARRIES. +# +# `OPENKAL_CONFORMANCE_IMPL_FEATURES` above names features of the +# IMPLEMENTATION, and for every platform so far that has been the whole of what +# an unusual arrangement needed. Emscripten is the first where it is not: its +# thread support is selected by `-pthread`, which chooses a different C library +# build, a different memory model and a different loader contract --- so it is a +# property of the WHOLE LINK, and a suite compiled without it cannot start a +# context however the implementation was compiled. +# +# Measured 2026-09-11, with the implementation's `threads` feature active and +# the suite built without the flag: +# +# DID NOT HOLD an execution context starts +# DID NOT HOLD four execution contexts start +# DID NOT HOLD contexts that ran at the same time have identities distinct +# +# which is the suite being right: if `openkal.task` is provided, a context +# starts. The flags therefore belong to the suite as well, and they are named in +# the environment for the same reason the runner and the features are --- +# whoever runs the suite knows the arrangement and the suite cannot. +# +# Added to BOTH sides deliberately: a flag that selects a memory model must +# reach every translation unit and the link, and a variable that reached one of +# them would produce a suite whose halves disagree. +suite_flags="${OPENKAL_CONFORMANCE_SUITE_FLAGS:-}" +if [ -n "$suite_flags" ]; then + quoted="" + for f in $suite_flags; do + [ -n "$quoted" ] && quoted="$quoted, " + quoted="$quoted\"$f\"" + done + { + printf '\n[build]\n' + printf 'cxxflags = [%s]\n' "$quoted" + printf 'ldflags = [%s]\n' "$quoted" + } >> "$suite/mcpp.toml" + echo "--- the suite carries: $suite_flags ---" +fi + if ! grep -q "^$package = " "$suite/mcpp.toml"; then # Appended immediately after openkal, which is inside [dependencies]. A # plain append would land under [features]. @@ -143,8 +184,22 @@ cd "$suite" # The remedy is to remember which set the build in `target' was made for, and to # discard the build when the answer changes. Re-running the same set still # builds incrementally, which is what the record is for. +# AND THE SUITE'S FLAGS ARE PART OF WHAT THE RECORD HAS TO REMEMBER. +# +# The paragraph above is about the feature set; `OPENKAL_CONFORMANCE_SUITE_FLAGS` +# is a second axis with the same property, and the first run that used it +# reported the previous run's build: +# +# error: POSIX thread support was disabled in precompiled file +# '.../pcm.cache/openkal.types.pcm' but is currently enabled +# +# which is the module cache from a build made without the flag. The remedy is +# the one already written here, applied to both axes rather than to one: a +# record of what the build in `target' was made for, and a discard when the +# answer changes. stamp="target/.features" -if [ ! -f "$stamp" ] || [ "$(cat "$stamp")" != "$features" ]; then +want_stamp="$features|$suite_flags" +if [ ! -f "$stamp" ] || [ "$(cat "$stamp")" != "$want_stamp" ]; then rm -rf target fi # ⚠️⚠️ AND THE SAME DEFECT AGAIN, IN THE LINE THAT FINDS WHAT WAS BUILT. @@ -170,7 +225,7 @@ find target -type f \( -name 'openkal-conformance' -o -name 'openkal-conformance -delete 2> /dev/null || true mcpp build --features "$features" "$@" -mkdir -p target && printf '%s' "$features" > "$stamp" +mkdir -p target && printf '%s' "$want_stamp" > "$stamp" produced="$(find target -type f \( -name 'openkal-conformance' -o -name 'openkal-conformance.exe' \))" count="$(printf '%s\n' "$produced" | grep -c . || true)"