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
81 changes: 57 additions & 24 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jobs:
- name: Install mcpp
run: |
xlings update
# ⚠️ THE PIN MAY NAME THE RELEASE THIS RUN IS VALIDATING, which does
# THE PIN MAY NAME THE RELEASE THIS RUN IS VALIDATING, which does
# not exist yet — that is the whole point of MCPP_SOURCE_REF. Bootstrap
# from whatever the index has; the step below replaces it with the
# build under review, and the pin is what an ordinary run tests.
Expand All @@ -82,7 +82,7 @@ jobs:
else
xlings install "mcpp@$MCPP_VERSION" -y -g
fi
# ⚠️⚠️ TRANSITION: GIVE THE BOOTSTRAP THE glibc ITS BINDING NAMES.
# TRANSITION: GIVE THE BOOTSTRAP THE glibc ITS BINDING NAMES.
#
# `xim:glibc`'s `latest` moved from `2.44` to `2.44.2`. A payload
# directory is named after the version a request RESOLVED to, while a
Expand All @@ -94,13 +94,13 @@ jobs:
# error: selected RuntimeBinding glibc@2.44 requires payload
# '…/xpkgs/xim-x-glibc/2.44', but it is not installed
#
# ⚠️ On every NEW machine and on none that already existed, which is why
# On every NEW machine and on none that already existed, which is why
# it is invisible from a developer's own. Measured on `main` as readily
# as on any branch — the index records the same failure verbatim in
# `pkgs/g/glibc.lua` and states the rule it broke: "The index is DATA
# and the client is a PROGRAM: the consumer ships first."
#
# REMOVE THIS once a released mcpp resolves it. `mcpp 2026.8.27.1`
# REMOVE THIS once a released mcpp resolves it. `mcpp 2026.8.27.1`
# accepts an installed payload whose version REFINES the requested one
# (`payload_dir_for_version`), so a bootstrap from it needs nothing
# here. Until then the missing payload is simply installed.
Expand All @@ -112,13 +112,13 @@ jobs:
fi
mcpp --version
mcpp self config --mirror GLOBAL
# ⭐⭐ CROSS-VALIDATION: BUILD THE mcpp UNDER REVIEW AND USE THAT ONE.
# CROSS-VALIDATION: BUILD THE mcpp UNDER REVIEW AND USE THAT ONE.
#
# Empty in the ordinary run, so this job keeps testing the RELEASED
# mcpp the pin above names. Set it — `workflow_dispatch` input, or the
# repository variable — and the same job runs against that source.
#
# ⚠️ THIS EXISTS BECAUSE THE ORDER USED TO BE WRONG. Several mcpp
# THIS EXISTS BECAUSE THE ORDER USED TO BE WRONG. Several mcpp
# releases went out green and only then turned this ecosystem red: the
# engine's own CI cannot see a defect that appears only in a real
# dependency graph, and this repository could not see the engine until
Expand All @@ -131,7 +131,7 @@ jobs:
[ -d "$src" ] || git clone --quiet --depth 1 \
--branch "$MCPP_SOURCE_REF" \
https://github.com/mcpp-community/mcpp.git "$src"
# ⚠️ THE CLONE'S OWN WORKSPACE PIN MUST NOT DECIDE WHICH mcpp
# THE CLONE'S OWN WORKSPACE PIN MUST NOT DECIDE WHICH mcpp
# BUILDS IT. `.xlings.json` at mcpp's root pins the mcpp that
# compiles mcpp, and that pin does not move when mcpp is released —
# so a build inside the checkout obeys it and tries to install a
Expand All @@ -144,7 +144,7 @@ jobs:
# above, which is exactly what removing the file leaves.
rm -f "$src/.xlings.json"
( cd "$src" && mcpp build --release )
# ⚠️ BOTH SPELLINGS, AND NO `-perm`. The matrix reaches Windows and
# BOTH SPELLINGS, AND NO `-perm`. The matrix reaches Windows and
# macOS runners too: on Windows the artefact is `mcpp.exe`, and
# `-perm -u+x` is not a question that filesystem answers the way this
# expects. Measured: `Finished release [optimized] in 173.44s`
Expand All @@ -158,7 +158,7 @@ jobs:
\( -name mcpp -o -name mcpp.exe \) | head -1)
[ -n "$built" ] || { echo "::error::mcpp did not build from $MCPP_SOURCE_REF"; exit 1; }
echo "$(cd "$(dirname "$built")" && pwd)" >> "$GITHUB_PATH"
# ⚠️ Reported, because a PATH entry that does not win looks exactly
# Reported, because a PATH entry that does not win looks exactly
# like one that does until something built with the wrong engine.
echo "under review: $("$built" --version) (from $MCPP_SOURCE_REF)"
fi
Expand All @@ -170,7 +170,7 @@ jobs:
# without a matching line in a `.def` compiles, and fails at the link of a
# CONSUMER --- not of this package, which does not link.
#
# ⚠️ MEASURED. Three declarations were added for openkal 0.8 and the names
# MEASURED. Three declarations were added for openkal 0.8 and the names
# were not, and this package's own CI stayed green: the failure appeared in
# openkal-llvm-runtime's cross-build, one repository away, as
#
Expand All @@ -182,14 +182,14 @@ jobs:
run: |
python3 - <<'PY'
import glob, os, re, sys
# ⚠️ THE ENCODING IS NAMED. Python opens a file with the platform's
# THE ENCODING IS NAMED. Python opens a file with the platform's
# default, which on this runner is cp1252, and these sources are UTF-8:
#
# UnicodeDecodeError: 'charmap' codec can't decode byte 0x90
#
# A check that reads source files must say what they are encoded in, or
# it reports on the runner's locale.
# ⚠️⚠️ EVERY HEADER, AND EVERY SPELLING. THIS CHECK MISSED THE SECOND
# EVERY HEADER, AND EVERY SPELLING. THIS CHECK MISSED THE SECOND
# OF EACH AND THE DEFECT IT WAS WRITTEN FOR HAPPENED AGAIN.
#
# It read `src/win32.h' alone and matched `OKW_IMPORT ... OKW_API'
Expand All @@ -202,7 +202,7 @@ jobs:
# ld.lld: error: undefined symbol:
# __declspec(dllimport) NtQueryVolumeInformationFile
#
# THE CHECK WAS RIGHT AND ITS SCOPE WAS WRONG, which is the harder
# THE CHECK WAS RIGHT AND ITS SCOPE WAS WRONG, which is the harder
# kind to notice: it reported a number, the number was of the names it
# knew about, and nothing said the set was partial. So the headers are
# globbed and both forms are matched.
Expand Down Expand Up @@ -248,7 +248,7 @@ jobs:
run: |
extra=''
[ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}'
# ⚠️ THE SET IS ENUMERATED AND `optional` IS NOT NAMED, AND THE
# THE SET IS ENUMERATED AND `optional` IS NOT NAMED, AND THE
# DIFFERENCE IS ONE INTERFACE.
#
# `optional` includes `space`, which this system does not provide:
Expand All @@ -264,6 +264,39 @@ jobs:
bash .spec/tools/run-conformance.sh openkal-windows . \
full,exec,random,terminal,net,datagram,timeout $extra

# run-conformance.sh above has already pointed the manifest at the
# specification's working tree and restored it through a trap; this
# package's own tests want the same working tree, so the substitution is
# repeated here rather than assumed to still hold.
- name: Point at the specification's working tree
run: |
set -euo pipefail
subst() { # subst <file> <relative-path-to-the-specification>
sed "s|^openkal = .*$|openkal = { path = \"$2\" }|" "$1" > "$1.next"
mv "$1.next" "$1"
grep -q "path = \"$2\"" "$1" \
|| { echo "::error::$1 was not substituted"; exit 1; }
}
subst mcpp.toml .spec

# This package's own tests: today, the observation that a spawn does not
# leak a handle it did not place. A caller-made-inheritable handle never
# named in a spawn's streams must not reach the started program, and the
# test proves it directly rather than by inference from the conformance
# suite, which has no vocabulary for a handle outside openkal's own.
- name: This package's own tests
run: |
extra=''
[ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}'
mcpp test $extra 2>&1 | tee tests.log
missing=0
for f in tests/*.cpp; do
name="$(basename "$f" .cpp)"
grep -q "^$name \.\.\. ok" tests.log || { echo "did not run or did not pass: $name" >&2; missing=1; }
done
test "$missing" -eq 0
git checkout -- mcpp.toml

# ---------------------------------------------------------------------------
# From a system that is not this one.
#
Expand All @@ -288,7 +321,7 @@ jobs:
git -C .spec checkout --quiet "origin/$branch"
fi

# ⚠️⚠️ CLONING THE SPECIFICATION IS NOT THE SAME AS BUILDING AGAINST IT,
# CLONING THE SPECIFICATION IS NOT THE SAME AS BUILDING AGAINST IT,
# AND THIS JOB DID THE FIRST WHILE BELIEVING IT DID THE SECOND.
#
# `.spec` above is consumed by `run-conformance.sh`, which substitutes the
Expand All @@ -302,7 +335,7 @@ jobs:
# index ... the index is current, so this name is either wrong or not
# published yet
#
# ⭐⭐ THE UNIT IS THE JOB, NOT THE REPOSITORY. Measured 2026-08-28 across
# THE UNIT IS THE JOB, NOT THE REPOSITORY. Measured 2026-08-28 across
# the eight repositories of this ecosystem: seven jobs in three of them
# had this shape, and each of those repositories ALSO had a job doing it
# correctly --- which is what made the gap invisible to a check done a
Expand All @@ -311,7 +344,7 @@ jobs:
- name: Point at the specification's working tree
run: |
set -euo pipefail
# ⚠️ NOT `sed -i'. This step runs on macOS and on Windows too, and
# NOT `sed -i'. This step runs on macOS and on Windows too, and
# BSD sed requires an argument to -i that GNU sed refuses. A temporary
# file is the spelling that holds on all three.
subst() { # subst <file> <relative-path-to-the-specification>
Expand Down Expand Up @@ -348,7 +381,7 @@ jobs:
- name: Install mcpp
run: |
xlings update
# ⚠️ THE PIN MAY NAME THE RELEASE THIS RUN IS VALIDATING, which does
# THE PIN MAY NAME THE RELEASE THIS RUN IS VALIDATING, which does
# not exist yet — that is the whole point of MCPP_SOURCE_REF. Bootstrap
# from whatever the index has; the step below replaces it with the
# build under review, and the pin is what an ordinary run tests.
Expand All @@ -358,13 +391,13 @@ jobs:
xlings install "mcpp@$MCPP_VERSION" -y -g
fi
mcpp self config --mirror GLOBAL
# ⭐⭐ CROSS-VALIDATION: BUILD THE mcpp UNDER REVIEW AND USE THAT ONE.
# CROSS-VALIDATION: BUILD THE mcpp UNDER REVIEW AND USE THAT ONE.
#
# Empty in the ordinary run, so this job keeps testing the RELEASED
# mcpp the pin above names. Set it — `workflow_dispatch` input, or the
# repository variable — and the same job runs against that source.
#
# ⚠️ THIS EXISTS BECAUSE THE ORDER USED TO BE WRONG. Several mcpp
# THIS EXISTS BECAUSE THE ORDER USED TO BE WRONG. Several mcpp
# releases went out green and only then turned this ecosystem red: the
# engine's own CI cannot see a defect that appears only in a real
# dependency graph, and this repository could not see the engine until
Expand All @@ -377,7 +410,7 @@ jobs:
[ -d "$src" ] || git clone --quiet --depth 1 \
--branch "$MCPP_SOURCE_REF" \
https://github.com/mcpp-community/mcpp.git "$src"
# ⚠️ THE CLONE'S OWN WORKSPACE PIN MUST NOT DECIDE WHICH mcpp
# THE CLONE'S OWN WORKSPACE PIN MUST NOT DECIDE WHICH mcpp
# BUILDS IT. `.xlings.json` at mcpp's root pins the mcpp that
# compiles mcpp, and that pin does not move when mcpp is released —
# so a build inside the checkout obeys it and tries to install a
Expand All @@ -390,7 +423,7 @@ jobs:
# above, which is exactly what removing the file leaves.
rm -f "$src/.xlings.json"
( cd "$src" && mcpp build --release )
# ⚠️ BOTH SPELLINGS, AND NO `-perm`. The matrix reaches Windows and
# BOTH SPELLINGS, AND NO `-perm`. The matrix reaches Windows and
# macOS runners too: on Windows the artefact is `mcpp.exe`, and
# `-perm -u+x` is not a question that filesystem answers the way this
# expects. Measured: `Finished release [optimized] in 173.44s`
Expand All @@ -404,7 +437,7 @@ jobs:
\( -name mcpp -o -name mcpp.exe \) | head -1)
[ -n "$built" ] || { echo "::error::mcpp did not build from $MCPP_SOURCE_REF"; exit 1; }
echo "$(cd "$(dirname "$built")" && pwd)" >> "$GITHUB_PATH"
# ⚠️ Reported, because a PATH entry that does not win looks exactly
# Reported, because a PATH entry that does not win looks exactly
# like one that does until something built with the wrong engine.
echo "under review: $("$built" --version) (from $MCPP_SOURCE_REF)"
fi
Expand Down Expand Up @@ -434,7 +467,7 @@ jobs:
# this environment's would have its calls resolve to the program's, and
# the program's would resolve back here.
#
# ⚠️ IN BOTH PROFILES. An optimizing compiler turns loops into calls ---
# IN BOTH PROFILES. An optimizing compiler turns loops into calls ---
# a counting loop into `strlen` or `wcslen` --- so objects built without
# optimization can hold the property while the ones a release links do not.
- name: The objects reference no C runtime symbol
Expand Down
37 changes: 31 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ An implementation of [openkal](https://github.com/mcpplibs/openkal) for Windows.

```toml
[dependencies]
openkal = "0.9.0"
openkal = "0.13.0"

[target.'cfg(windows)'.dependencies]
openkal-windows = "0.5.0"
openkal-windows = "0.8.0"
```

Its purpose is as much to test the specification as to be used. openkal was
Expand Down Expand Up @@ -117,11 +117,36 @@ The four that openkal 0.8 added and this implementation now provides:

| | on this system |
| --- | --- |
| `openkal.net` | Winsock, started once at the first socket and never stopped. ⚠️ `WSASocketW` with a flags word of zero rather than `socket`: the latter makes an **overlapped** handle, and `ReadFile` upon one of those returns before the bytes arrive. A non-overlapped socket is what lets a connection be a stream here with no second transfer path |
| `openkal.datagram` | the same calls with `SOCK_DGRAM`. ⚠️ This system reports a truncated message as a **failure** where the other two truncate silently; the bytes that fit are delivered either way, and the interface says the excess is lost |
| `openkal.net` | Winsock, started once at the first socket and never stopped. Every socket is made with `WSA_FLAG_OVERLAPPED`, and version 0.13 requires it: a synchronous socket shares one completion event between its two directions, so a read waiting in one thread held back a write from another on the same connection. The transfer operations issue `ReadFile`/`WriteFile` with an `OVERLAPPED` of their own and wait for it, so the two directions no longer contend for one event. A connection's stream placed in `kal_spawn_streams` reaches the started program as an overlapped handle: a program on this implementation transfers upon it correctly, and a program that issues synchronous `ReadFile`/`WriteFile` upon an inherited handle without an `OVERLAPPED` is outside what the system defines for a socket. A caller that starts such a program relays the connection through a channel (`kal_process_channel`) instead |
| `openkal.datagram` | the same calls with `SOCK_DGRAM`, `WSASendTo`/`WSARecvFrom` with their own `OVERLAPPED` for the same reason. This system reports a truncated message as a **failure** where the other two truncate silently; the bytes that fit are delivered either way, and the interface says the excess is lost |
| `openkal.timeout` | `WSAPoll`, which answers for sockets and for nothing else. A bounded read of a stream that is not a socket reports `kal_err_not_supported` — which the interface's own header anticipates in terms. `kal_timeout_wait_process` is the one operation of the interface this system provides **directly**, because a bounded wait upon an object is the primitive here |
| `openkal.exec` | `VirtualAlloc` writable, `VirtualProtect` executable, `FlushInstructionCache`. The third call is not optional and the other two systems' implementations do not need to make it explicit |

## Version 0.13

**A name that exists and is not a form this environment can start.**
`CreateProcessW` reports `ERROR_BAD_EXE_FORMAT` for an image whose header this
loader does not recognise and `ERROR_EXE_MACHINE_TYPE_MISMATCH` for one built for
a different processor; both arrive here as `kal_err_not_program` rather than
`kal_err_io`, which is what let a caller distinguish a name that is not there
from one that is and cannot be started.

**Whether a node may be started is not recorded on this system's ordinary
volumes.** `kal_fs_set_executable_at` validates its arguments as its siblings do
and reports `kal_err_not_supported`; `KAL_FS_PROP_EXECUTABLE` is not claimed, and
`kal_fs_info`/`kal_fs_file_info` never set `KAL_INFO_EXECUTABLE`. Whether a
name may be started here is decided by its form when it is started, not read
from a property a volume stores.

**A spawn inherits only the handles it placed.** `CreateProcessW` with
inheritance enabled hands the started program every inheritable handle of this
process, not only the ones a caller named — a defect distinct from the
specification, found by a detached child that kept a starter's standard output
open long after the starter had gone. The fix is `STARTUPINFOEXW` with a
`PROC_THREAD_ATTRIBUTE_HANDLE_LIST` naming exactly the deduplicated, non-null
standard handles this operation placed; a handle the caller made inheritable for
some other reason and did not place is no longer inherited.

## Verification

The conformance suite in the specification package, built for this target. The
Expand Down Expand Up @@ -166,13 +191,13 @@ assumed — `WaitOnAddress` and its two neighbours are **not** in `kernel32.dll`
and putting them there produces an import table that links and then fails to
bind.

⚠️ **Supplied only where the system's own are absent.** On this system they are
**Supplied only where the system's own are absent.** On this system they are
present, they are the vendor's, and they list every name rather than the
forty-five this implementation calls; `-L` is searched first, so supplying ours
there would shadow them and a consumer calling a forty-sixth would be told there
is no such name.

⚠️ Measured 2026-08-22, on a clean continuous-integration runner, after every
Measured 2026-08-22, on a clean continuous-integration runner, after every
object had compiled:

```
Expand Down
Loading
Loading