Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 55 additions & 3 deletions examples/portable/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
61 changes: 57 additions & 4 deletions examples/portable/mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
15 changes: 14 additions & 1 deletion tools/check-surface.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down
59 changes: 57 additions & 2 deletions tools/run-conformance.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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].
Expand Down Expand Up @@ -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.
Expand All @@ -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)"
Expand Down
Loading