Skip to content

Commit 43aa11a

Browse files
Sunrisepeakmcpplibs
andauthored
openkal-macos 0.3.0: implement on the kernel, and correct the direction of the suspension primitive (#1)
* probe: measure what this system offers before writing against it * probe: report every answer rather than stopping at the first refusal * probe: declare usleep * probe: one runner, so that the answers arrive * openkal-macos 0.3.0: implement on the kernel, so that the program above may supply its own runtime Version 0.2 was written upon the system's C library. That is a correct implementation of openkal for a program that borrows nothing, and it is wrong for the program clause 1 names first among the consumers the specification expects: openkal says nothing about what else a program contains, and a program above it may define every name the system's library defines. If it does, this implementation's calls resolve to the program's and the program's resolve back here. The recursion is unbounded and it appears in neither side's source. This version issues the kernel's own calls. Two names remain and both were chosen for one property --- no C library defines them, so a program that defines every ordinary name still leaves them reachable: clock_gettime_nsec_np this system counts elapsed time in a unit of the processor's and the conversion lives in a library rather than in the kernel pthread_create_from_mach_thread creating an execution context here means arranging state the kernel does not arrange Which names were reachable was measured on the system rather than remembered. .github/workflows/probe.yml asked it and the sources are written against its answers. Three findings are worth recording because they are the return on writing a third implementation. The suspension primitive exists on this kernel too, under a different name and with no shared ancestry with the one Linux offers. openkal.task declares its boundary as a wait upon a word; version 0.2 built that primitive out of a mutex and a condition variable, which had the relation backwards. That two unrelated kernels provide the operation is the evidence that it is the shape of the thing rather than the shape of one kernel. This kernel has no call that suspends for a duration --- what its own library uses is a wait upon an object a program carrying no other runtime does not have --- so kal_time_sleep is the wait openkal.task already requires, upon an address nothing ever wakes. That is not a substitute for a sleep; it is a sleep, expressed with the operation this system has. This kernel has no call that starts a program relative to a directory, so the directory is entered by the duplicate before it replaces itself. That duplicate exists for the length of two calls and is not a resource the caller receives, which is exactly what openkal declines to offer as an operation of its own. The `standalone' feature states whether this implementation is the whole of the program's environment, in the same terms the Linux implementation states it: it is a statement about the program rather than a smaller variant of the implementation, and the consumer that knows which arrangement holds declares it. Continuous integration runs the specification's own suite on both architectures, compares the exported names against SURFACE.txt, and asserts that the objects reference nothing of a C library but the two names that are named --- with a probe that the assertion fails when it should. * ci: the shared suite, both architectures, and the toolchain named once * probe: read the kernel's numbers from the system's own table * probe: three more names the file-system interface needs * openkal-macos: set a file's modification time, with the resolution this kernel's call has * ci: the Intel runner under the label it now has * openkal-macos: report the working directory the way this system reports it, and measure which numbers the kernel serves This kernel has no call that reports the working directory. The measurement is in the system's own table: SYS___getcwd is absent. What it has instead is an enquiry upon an open file that reports the name it was reached by, and that is what a program on this system uses --- so the directory is opened and asked. A number that is in the system's table and not in the kernel's is not a failed call: the kernel raises a signal whose name is "bad system call" and the program stops, so the diagnostic names the program rather than the number. Reading the table cannot distinguish the two. numbers.yml now also issues every call this implementation makes, with arguments that cannot do harm, and reports which the kernel refuses to recognise. The Intel runner is named for the release it now belongs to. Under the label it had, every job that asked for it stayed queued and never started, which reads as a slow build rather than as an absent machine. * openkal-macos: this kernel tells the duplicate apart by the second register Duplicating the calling image is the one call whose result does not fit the convention the rest of this file meets. This kernel reports which image is which in the second register: both receive the same first value, and the second is zero in the original and one in the duplicate. The other kernel tells the duplicate by giving it a first value of zero, and this implementation was written against that. The consequence was not a failed call. Both images took the original's branch, so the duplicate carried on running the program instead of replacing itself, and the original waited for a program that never started --- which the conformance suite reported as "a copy of this program could not be started", four words away from where the fault was. This system's own C library hides the difference by forcing the duplicate's first value to zero in its wrapper. There is no wrapper here, so the difference is met rather than hidden. * openkal-macos: this kernel refuses to commit a stream that is not a file, and says so with its own value * ci: name the object that references a symbol it must not * ci: examine this package's own objects, and not the ones the build tool emits beside them * ci: tell this package's objects from the build tool's by name, since both are in one directory * ci: the build tool has no release for the other architecture, so reach it another way Every job that asked for an Intel runner failed at the first step: the installer answers "Unsupported release target: macosx-x86_64", so the suite cannot be built there at all. That is a property of the tooling and not of this implementation, and a row that always fails says nothing about either. What can be done there is done instead. The `numbers' workflow reads that kernel's own table on both architectures and issues every call this implementation makes on both, and the conformance job now compiles every source for the other architecture with the system's own compiler. The three things that differ between the two --- the calling convention, the register the current context is reached through, and one field of a signal context --- are all in src/sys.h, and all three are compiled. * openkal-macos: ask the kernel which context this is, rather than a register that does not always say Version 0.3 read the identity from the register each architecture reserves for the current thread's own record. That is one instruction and it is wrong for a context created by the one route a program carrying no other runtime can use: such a context observes zero there. openkal requires the identity to be distinct per context and stable within one, and zero for every started context satisfies neither. The way it failed is worth recording: a C library above openkal keys its per-context state on this value, zero is that table's word for "no entry", and what a reader saw was the library reading its own state through a null pointer --- four layers from the register. The kernel is asked instead. It answers with the identity it gave the context, which is never zero and never shared. The cost is a call where a load would have done, which is the right trade for a value whose wrongness the caller cannot detect. --------- Co-authored-by: mcpplibs <noreply@mcpplibs.org>
1 parent 4cfcba6 commit 43aa11a

16 files changed

Lines changed: 1862 additions & 589 deletions

File tree

.github/workflows/ci.yml

Lines changed: 141 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,65 @@
11
name: CI
22

3+
# What this workflow asserts.
4+
#
5+
# conformance the suite in the specification package runs against this
6+
# implementation, on both architectures, and every observation
7+
# holds
8+
# surface the exported names are exactly the fifty-one the
9+
# specification lists
10+
# independence the objects reference nothing of a C library but the two names
11+
# no C library defines
12+
#
13+
# The third is the property version 0.3 exists for. A program above openkal may
14+
# supply every name the system's own library supplies; an implementation that
15+
# called one of them would have its calls resolve to the program's, and the
16+
# program's would resolve back here.
17+
318
on:
419
push:
520
branches: [main]
621
pull_request:
722
workflow_dispatch:
823

24+
env:
25+
MCPP_VERSION: 2026.8.19.4
26+
XLINGS_VERSION: v2026.8.17.2
27+
XLINGS_NON_INTERACTIVE: '1'
28+
929
jobs:
10-
build:
11-
name: conformance (macOS)
12-
runs-on: macos-14
13-
timeout-minutes: 30
14-
env:
15-
# A version verified to build this package, not a measured minimum. The
16-
# package uses modules, exported extern "C" declarations and ordinary
17-
# dependencies, none of which is recent; the pin exists for reproducibility
18-
# rather than because an older mcpp is known to fail.
19-
MCPP_VERSION: 2026.8.19.4
20-
XLINGS_VERSION: v2026.8.17.2
21-
XLINGS_NON_INTERACTIVE: '1'
30+
conformance:
31+
name: conformance (${{ matrix.os }}, ${{ matrix.toolchain }})
32+
runs-on: ${{ matrix.os }}
33+
timeout-minutes: 60
34+
strategy:
35+
fail-fast: false
36+
matrix:
37+
include:
38+
# arm64 only, and not because the other architecture is unsupported.
39+
# The build tool has no release for x86_64 on this system --- its
40+
# installer answers "Unsupported release target: macosx-x86_64" --- so
41+
# the suite cannot be built there at all. What can be done there is
42+
# done: the `numbers' workflow measures that kernel's own table on
43+
# both architectures, and the step below compiles every source for the
44+
# other one with the system's compiler.
45+
- { os: macos-14, toolchain: 'llvm@20.1.7' }
2246
steps:
2347
- uses: actions/checkout@v4
2448

49+
# The specification is checked out at the branch under test where it has
50+
# one, so that this run asserts what it is for: that the specification as
51+
# written there and this implementation as written here agree today.
52+
- name: The specification
53+
run: |
54+
git clone --quiet https://github.com/mcpplibs/openkal.git .spec
55+
branch='${{ github.head_ref || github.ref_name }}'
56+
if git -C .spec rev-parse --verify --quiet "origin/$branch" > /dev/null; then
57+
git -C .spec checkout --quiet "origin/$branch"
58+
echo "the specification is at $branch"
59+
else
60+
echo "the specification has no $branch; its default branch is used"
61+
fi
62+
2563
- name: Install xlings
2664
run: |
2765
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh \
@@ -35,80 +73,107 @@ jobs:
3573
mcpp --version
3674
mcpp self config --mirror GLOBAL
3775
38-
# The version of the specification this implementation is written against
39-
# is stated once, in the manifest, and read from there. The companion
40-
# implementation stated it a second time in this file and the two drifted.
41-
- name: Read the specification version from the manifest
76+
# The compiler family and version for this row. mcpp keeps its toolchains
77+
# in a sandbox of its own, so this selects rather than installs into the
78+
# system, and `mcpp test' and `mcpp run' have no flag for it --- which is
79+
# why it is set once here rather than passed to each command.
80+
- name: Select the toolchain
4281
run: |
43-
v="$(sed -n 's/^openkal[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' mcpp.toml | head -1)"
44-
test -n "$v" || { echo "the manifest does not name openkal" >&2; exit 1; }
45-
echo "OPENKAL_VERSION=$v" >> "$GITHUB_ENV"
46-
echo "written against openkal $v"
82+
spec='${{ matrix.toolchain }}'
83+
case "$spec" in
84+
msvc*) mcpp toolchain default msvc ;;
85+
*) mcpp toolchain install "${spec%@*}" "${spec#*@}"
86+
mcpp toolchain default "$spec" ;;
87+
esac
88+
mcpp toolchain list
4789
48-
- name: Conformance
90+
- name: Every interface, every kind of examination
4991
run: |
50-
mcpp test 2>&1 | tee conformance.log
92+
bash .spec/tools/run-conformance.sh openkal-macos . full
5193
52-
# A suite that discovered nothing reports success, so every suite is
53-
# asserted to have run. The list is derived from the files present
54-
# rather than written out here: a hand-written list names the suites
55-
# that existed when it was written, and a suite added afterwards
56-
# escapes the assertion silently. This assertion named two of five.
57-
missing=0
58-
for f in tests/*.cpp; do
59-
name="$(basename "$f" .cpp)"
60-
if ! grep -q "^$name \.\.\. ok" conformance.log; then
61-
echo "suite did not run, or did not pass: $name" >&2
62-
missing=1
63-
fi
94+
# The other architecture, as far as this system allows it to be reached.
95+
#
96+
# The system-call numbers agree between the two --- measured, in the
97+
# `numbers' workflow --- and three things do not: the calling convention,
98+
# the register the current context is reached through, and one field of a
99+
# signal context. All three are in src/sys.h and all three are compiled
100+
# here, with the system's own compiler rather than the build tool's,
101+
# because the build tool has no release for that architecture.
102+
- name: Every source compiles for the other architecture
103+
run: |
104+
for f in src/*.cpp; do
105+
clang++ -std=c++2b -c -o /dev/null --target=x86_64-apple-macos13 \
106+
-fno-exceptions -fno-rtti -DOKM_STANDALONE -I .spec/include "$f"
64107
done
65-
test "$missing" -eq 0
108+
echo "every source compiles for x86_64"
66109
67-
- name: The specification, at the version the manifest names
68-
uses: actions/checkout@v4
69-
with:
70-
repository: mcpplibs/openkal
71-
ref: ${{ env.OPENKAL_VERSION }}
72-
path: .spec
73-
74-
- name: The exported surface matches the specification
110+
- name: The exported surface is complete and contains nothing else
75111
run: |
76112
# Clause 9.3. The list and the checker come from the specification
77-
# itself rather than from a copy kept here, so that the comparison has
78-
# one source. --complete because this implementation claims every
113+
# rather than from a copy kept here, so that the comparison has one
114+
# source. --complete because this implementation claims every
79115
# interface: a name it fails to export is a failure, not an interface
80116
# it declines to provide.
81-
bash .spec/tools/check-surface.sh --complete .spec/SURFACE.txt \
82-
$(find target -name '*.o' | tr '\n' ' ')
117+
rm -rf target && mcpp build
118+
# This package's own objects, and not the ones the build tool emits
119+
# beside them: one of those exists to initialise a standard library
120+
# this package does not use, and it is not this package's to answer
121+
# for. They are told apart by name, because the tool places both in
122+
# one directory.
123+
objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o' ! -name 'mcpp_*' | tr '\n' ' ')"
124+
test -n "$objs" || { echo "no objects were found" >&2; exit 1; }
125+
bash .spec/tools/check-surface.sh --complete .spec/SURFACE.txt $objs
83126
84-
# The program is taken from the specification rather than copied here. A
85-
# copy in each implementation is a copy that can diverge, and the value of
86-
# the program is precisely that every implementation runs the same one.
87-
#
88-
# The manifest is written here because naming the implementation is the
89-
# manifest's job and not the program's, which is the arrangement the
90-
# program exists to demonstrate.
91-
- name: The portable program runs
127+
# The property version 0.3 exists for. The assertion is made against the
128+
# objects rather than against the source, because a source can reach a C
129+
# library through a macro.
130+
- name: The objects reference nothing of a C library but the two named
92131
run: |
93-
# Not `sed -i`: BSD sed requires a backup suffix after -i and reads
94-
# the next word as one, so the GNU form fails on macOS with
95-
# `invalid command code m`. This form is the same on both.
96-
sed 's|^openkal = ".*"$|openkal = { path = ".spec" }|' mcpp.toml > mcpp.toml.next
97-
mv mcpp.toml.next mcpp.toml
98-
cat > .spec/examples/portable/mcpp.toml <<'TOML'
99-
[package]
100-
name = "portable"
101-
version = "0.1.0"
132+
rm -rf target && mcpp build --features standalone
133+
objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o' ! -name 'mcpp_*')"
134+
test -n "$objs" || { echo "no objects were found; the check would pass vacuously" >&2; exit 1; }
102135
103-
[dependencies]
104-
openkal = { path = "../.." }
105-
openkal-macos = { path = "../../.." }
106-
TOML
107-
cd .spec/examples/portable
108-
mcpp run 2>&1 | tee run.log
109-
# Both directions: that the program reported, and that nothing it
110-
# observed failed to hold. Asserting only the first would pass for a
111-
# program that printed its failures.
112-
grep -q 'openkal: the portable program, above eight interfaces' run.log
113-
grep -q 'openkal: observations that did not hold: 0' run.log
114-
! grep -q 'NOT HELD' run.log
136+
# The permitted set, and why each entry is in it.
137+
#
138+
# memcpy, memmove, memset, memcmp a compiler emits calls to these
139+
# from ordinary loops. They compute rather than call, so none of
140+
# them can re-enter this implementation.
141+
# clock_gettime_nsec_np,
142+
# pthread_create_from_mach_thread the two names no C library
143+
# defines, which is why they are reachable from here at all.
144+
# __libc_start_main, main, _main the hand-over, undefined here by
145+
# construction.
146+
# kal_* the interface itself.
147+
# __stack_chk_* emitted by the toolchain around
148+
# a frame it protects; supplied by the program, not called by this
149+
# implementation.
150+
permitted='^_?(memcpy|memmove|memset|memcmp|bzero|clock_gettime_nsec_np|pthread_create_from_mach_thread|pthread_create|pthread_join|__libc_start_main|main|kal_[a-z_]+|__stack_chk_guard|__stack_chk_fail|GCC_except_table.*|_ZN3okm.*|__Unwind_Resume)$'
151+
# Reported with the object that references it. A symbol without the
152+
# object it came from names a fault and not a place, and the first
153+
# time this check fired the answer was in the object rather than in
154+
# the symbol.
155+
bad=0
156+
for o in $objs; do
157+
for s in $(nm -u "$o" | sed 's/^ *//' | grep -v ':$' | sort -u); do
158+
[ -n "$s" ] || continue
159+
printf '%s\n' "$s" | grep -qE "$permitted" || {
160+
echo "$o references a symbol it must not: $s" >&2
161+
bad=1
162+
}
163+
done
164+
done
165+
test "$bad" -eq 0
166+
echo "the implementation reaches nothing of a C library but the two names that are named"
167+
168+
# A checker is only useful if it fails when it should.
169+
- name: The independence check detects a dependence
170+
run: |
171+
printf 'extern "C" int puts(const char*);\nextern "C" void okm_probe(void) { puts("x"); }\n' > src/probe.cpp
172+
rm -rf target && mcpp build --features standalone
173+
objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o' ! -name 'mcpp_*')"
174+
if ! nm -u $objs | sed 's/^ *//' | grep -qx '_puts'; then
175+
echo "the probe did not produce the reference it was written to produce" >&2
176+
rm -f src/probe.cpp; exit 1
177+
fi
178+
rm -f src/probe.cpp
179+
echo "a dependence upon a C library is visible to the check"

0 commit comments

Comments
 (0)