Skip to content

Commit c4eee37

Browse files
committed
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.
1 parent e05e5fb commit c4eee37

14 files changed

Lines changed: 1299 additions & 589 deletions

File tree

.github/workflows/ci.yml

Lines changed: 102 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,62 @@
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 and x86_64. The system-call numbers agree; the calling
39+
# convention, the register the current context is reached through and
40+
# one field of a signal context do not, so both are built.
41+
- { os: macos-14, toolchain: 'llvm@20.1.7' }
42+
- { os: macos-13, toolchain: 'llvm@20.1.7' }
2243
steps:
2344
- uses: actions/checkout@v4
2445

46+
# The specification is checked out at the branch under test where it has
47+
# one, so that this run asserts what it is for: that the specification as
48+
# written there and this implementation as written here agree today.
49+
- name: The specification
50+
run: |
51+
git clone --quiet https://github.com/mcpplibs/openkal.git .spec
52+
branch='${{ github.head_ref || github.ref_name }}'
53+
if git -C .spec rev-parse --verify --quiet "origin/$branch" > /dev/null; then
54+
git -C .spec checkout --quiet "origin/$branch"
55+
echo "the specification is at $branch"
56+
else
57+
echo "the specification has no $branch; its default branch is used"
58+
fi
59+
2560
- name: Install xlings
2661
run: |
2762
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh \
@@ -35,80 +70,67 @@ jobs:
3570
mcpp --version
3671
mcpp self config --mirror GLOBAL
3772
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
42-
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"
47-
48-
- name: Conformance
73+
- name: Every interface, every kind of examination
4974
run: |
50-
mcpp test 2>&1 | tee conformance.log
75+
bash .spec/tools/run-conformance.sh openkal-macos . full \
76+
--toolchain '${{ matrix.toolchain }}'
5177
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
64-
done
65-
test "$missing" -eq 0
66-
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
78+
- name: The exported surface is complete and contains nothing else
7579
run: |
7680
# 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
81+
# rather than from a copy kept here, so that the comparison has one
82+
# source. --complete because this implementation claims every
7983
# interface: a name it fails to export is a failure, not an interface
8084
# it declines to provide.
81-
bash .spec/tools/check-surface.sh --complete .spec/SURFACE.txt \
82-
$(find target -name '*.o' | tr '\n' ' ')
85+
rm -rf target && mcpp build --toolchain '${{ matrix.toolchain }}'
86+
objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o' | tr '\n' ' ')"
87+
test -n "$objs" || { echo "no objects were found" >&2; exit 1; }
88+
bash .spec/tools/check-surface.sh --complete .spec/SURFACE.txt $objs
8389
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
90+
# The property version 0.3 exists for. The assertion is made against the
91+
# objects rather than against the source, because a source can reach a C
92+
# library through a macro.
93+
- name: The objects reference nothing of a C library but the two named
9294
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"
95+
rm -rf target && mcpp build --features standalone --toolchain '${{ matrix.toolchain }}'
96+
objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o')"
97+
test -n "$objs" || { echo "no objects were found; the check would pass vacuously" >&2; exit 1; }
10298
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
99+
# The permitted set, and why each entry is in it.
100+
#
101+
# memcpy, memmove, memset, memcmp a compiler emits calls to these
102+
# from ordinary loops. They compute rather than call, so none of
103+
# them can re-enter this implementation.
104+
# clock_gettime_nsec_np,
105+
# pthread_create_from_mach_thread the two names no C library
106+
# defines, which is why they are reachable from here at all.
107+
# __libc_start_main, main, _main the hand-over, undefined here by
108+
# construction.
109+
# kal_* the interface itself.
110+
# __stack_chk_* emitted by the toolchain around
111+
# a frame it protects; supplied by the program, not called by this
112+
# implementation.
113+
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)$'
114+
bad=0
115+
for s in $(nm -u $objs | sed 's/^ *//' | grep -v ':$' | sort -u); do
116+
[ -n "$s" ] || continue
117+
printf '%s\n' "$s" | grep -qE "$permitted" || {
118+
echo "the implementation references a symbol it must not: $s" >&2
119+
bad=1
120+
}
121+
done
122+
test "$bad" -eq 0
123+
echo "the implementation reaches nothing of a C library but the two names that are named"
124+
125+
# A checker is only useful if it fails when it should.
126+
- name: The independence check detects a dependence
127+
run: |
128+
printf 'extern "C" int puts(const char*);\nextern "C" void okm_probe(void) { puts("x"); }\n' > src/probe.cpp
129+
rm -rf target && mcpp build --features standalone --toolchain '${{ matrix.toolchain }}'
130+
objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o')"
131+
if ! nm -u $objs | sed 's/^ *//' | grep -qx '_puts'; then
132+
echo "the probe did not produce the reference it was written to produce" >&2
133+
rm -f src/probe.cpp; exit 1
134+
fi
135+
rm -f src/probe.cpp
136+
echo "a dependence upon a C library is visible to the check"

README.md

Lines changed: 103 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
# openkal-macos
22

3-
An implementation of [openkal](https://github.com/mcpplibs/openkal) for macOS.
3+
An implementation of [openkal](https://github.com/mcpplibs/openkal) for macOS,
4+
written on the kernel's own calls.
5+
6+
```toml
7+
[dependencies]
8+
openkal = "0.5.0"
9+
10+
[target.'cfg(os = "macos")'.dependencies]
11+
openkal-macos = "0.3.0"
12+
```
413

514
Its purpose is as much to test the specification as to be used. A specification
615
satisfied only by the system it was written against has not been shown to be
@@ -9,51 +18,115 @@ different system is what turns the claim into an observation.
918

1019
## Interfaces provided
1120

12-
All eight, as the reference implementation does. The package exports no module:
13-
the interface belongs to the specification, and this package supplies
14-
definitions.
21+
All eight. `tools/check-surface.sh --complete` in the specification package
22+
compares the exported names against `SURFACE.txt`.
23+
24+
The package exports no module: the interface belongs to the specification, and
25+
this package supplies definitions.
26+
27+
## Why it does not borrow the system's C library
28+
29+
openkal says nothing about what else a program contains, and a program above it
30+
may supply every name the system's own library supplies. If it does, and the
31+
names agree, this implementation's calls resolve to the program's and the
32+
program's resolve back here. The recursion is unbounded and it appears in
33+
neither side's source.
34+
35+
Version 0.2 of this implementation borrowed the system's library. That is a
36+
correct implementation of openkal for a program that borrows nothing, and it is
37+
wrong for the program clause 1 names first among the consumers the specification
38+
expects. Version 0.3 issues the kernel's calls instead.
39+
40+
**Two names remain, and both were chosen for one property: no C library defines
41+
them.** A program that defines every ordinary name still leaves them reachable.
42+
43+
| name | why a kernel call could not replace it |
44+
| --- | --- |
45+
| `clock_gettime_nsec_np` | this system counts elapsed time in a unit of the processor's; the conversion lives in a library rather than in the kernel |
46+
| `pthread_create_from_mach_thread` | creating an execution context here means arranging state the kernel does not arrange, and reproducing that arrangement would be reproducing a part of this system inside an implementation of openkal |
47+
48+
The second is required only under the `standalone` feature. Which names were
49+
reachable and which were not was measured on the system rather than remembered:
50+
`.github/workflows/probe.yml` asked it, and the sources are written against its
51+
answers.
52+
53+
## The `standalone` feature
54+
55+
Whether this implementation is the whole of the program's environment.
1556

16-
## Where this system differs, and what each difference demonstrates
57+
Ordinarily a program already carries a runtime of its own, that runtime has
58+
received control from the loader, and it creates execution contexts. This
59+
implementation borrows both, and that is the default.
60+
61+
Sometimes there is no such runtime — because the program supplies one itself, or
62+
because it has none. Then nothing in the program has received control and
63+
nothing creates contexts, and this implementation supplies the entry point and
64+
creates contexts through the one name above. That is what `standalone` selects.
65+
66+
It is a statement about the program, not a smaller or faster variant of the
67+
implementation, and the consumer that knows which arrangement holds is the one
68+
that declares it.
69+
70+
## Where this system differs from Linux, and what each difference demonstrates
1771

1872
These are recorded because they are the return on writing a second
1973
implementation. Each is a place where an interface could have assumed a
2074
mechanism, and did not.
2175

22-
**The monotonic clock continues during suspension.** On the reference
23-
implementation it stops. A program measuring an interval across a suspension
24-
obtains different answers from the two, and no operation reports which it is
25-
dealing with. The property `prop_monotonic_suspends` exists for this, and this
26-
divergence is the clearest evidence in either implementation that the properties
27-
of clause 6.2 were necessary rather than decorative.
76+
**A failure is reported by a flag, not by the sign of the result.** The kernel's
77+
convention is not the one openkal's reference implementation meets, and openkal
78+
does not name a convention: an operation reports one of thirteen values from a
79+
closed set, and how the environment reported it is the implementation's business.
80+
81+
**The monotonic clock continues during suspension.** On Linux it stops. A
82+
program measuring an interval across a suspension obtains different answers from
83+
the two, and no operation reports which it is dealing with.
84+
`KAL_TIME_PROP_MONOTONIC_SUSPENDS` exists for this, and this divergence is the
85+
clearest evidence in either implementation that the properties of clause 6.2
86+
were necessary rather than decorative.
2887

2988
**Names are compared without regard to case.** A program that creates two names
3089
differing only in case succeeds on one implementation and not on the other.
31-
`prop_case_sensitive` reports it in advance, which no operation could.
32-
33-
**The spawn has no action that sets the working directory.** The reference
34-
implementation passes the directory as an attribute of the operation; this one
35-
applies it around the operation, which is correct for a single-context program
36-
and is not correct in general. The divergence is recorded in the source rather
37-
than concealed, because an interface one platform honours as an attribute and
38-
another can only approximate is an interface that has assumed a mechanism.
39-
40-
**There is no suspension primitive a program may use.** `openkal.task` declares
41-
the boundary as a wait upon a word, because on a system that provides it the
42-
synchronisation objects of a C library are built from it. Here the relation is
43-
inverted: the primitive is built from a mutex and a condition variable. This is
44-
the one place in the implementation that resembles a compatibility layer, and it
45-
is admitted because the alternatives are worse in both directions — an interface
46-
offering mutexes would oblige an implementation whose environment has none to
47-
construct them, and every C library above openkal would then be built upon a
48-
facility it does not need.
90+
`KAL_FS_PROP_CASE_SENSITIVE` reports it in advance, which no operation could.
91+
92+
**There is no kernel call that suspends for a duration.** What this system's own
93+
library uses is a wait upon an object a program carrying no other runtime does
94+
not have. So the wait `openkal.task` already requires is used, upon an address
95+
nothing ever wakes. That is not a substitute for a sleep; it is a sleep,
96+
expressed with the operation this system has.
97+
98+
**There is no kernel call that starts a program relative to a directory.** The
99+
directory is entered by the duplicate before it replaces itself — a duplicate
100+
that exists for the length of two calls and is not a resource the caller
101+
receives, which is exactly what openkal declines to offer as an operation of its
102+
own. The caller's working directory is untouched, which is the property the
103+
interface requires.
104+
105+
**The suspension primitive exists here too.** `openkal.task` declares its
106+
boundary as a wait upon a word. This kernel offers that operation under a
107+
different name and with no shared ancestry with the one Linux offers. That two
108+
unrelated systems provide it is the evidence that it is the shape of the thing
109+
rather than the shape of one kernel — and version 0.2, which built the primitive
110+
out of a mutex and a condition variable, had this backwards.
49111

50112
## Conformance
51113

52-
`mcpp test` runs the suite, which is the same suite the reference
114+
The suite lives in the specification package and is the same suite every
53115
implementation runs. That it is the same suite is the point: a conformance
54116
suite that differed between implementations would be testing implementations
55117
rather than the specification.
56118

119+
```bash
120+
git clone https://github.com/mcpplibs/openkal .spec
121+
bash .spec/tools/run-conformance.sh openkal-macos . full
122+
```
123+
124+
## Architectures
125+
126+
`arm64` and `x86_64`. The system-call numbers are the same on both; what differs
127+
is the calling convention, the register the current context's record is reached
128+
through, and one field of a signal context. All three are in `src/sys.h`.
129+
57130
## License
58131

59132
Apache-2.0.

0 commit comments

Comments
 (0)