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)"