Skip to content

Commit 747154f

Browse files
committed
docs(openkal): plan for industrial completeness, with musl and the compiler as validation
The core set is published; four questions were deferred and each blocks hosting a C library. This plan settles them and derives the interfaces that follow. The method is stated first because it was nearly got wrong. Deriving the interface set from what a compiler calls would have been the third instance of the error the specification already records: POSIX shaped by the C library of its time, WASI preview one shaped by POSIX, openkal shaped by a compiler. Programs validate a derivation; they do not produce one. Two measurements are load-bearing, and they point in opposite directions. GCC creates processes by spawning and calls neither fork nor execve. A specification that had copied the POSIX decomposition would have obliged an implementation on Windows to reproduce fork, which cannot be done faithfully and would have failed the specification's own admission criterion. The resource-derived form and the observed usage agree. GCC also assumes a global namespace of paths, which capability-based kernels do not provide. That is evidence about the program's portability assumptions rather than a reason to admit a global namespace, and it locates the path-resolution work where it belongs: in the C library, performed once. The two validation subjects form a stack rather than a pair. Porting musl is the enabling work; the compiler above it is then an end-to-end check that porting a C library once causes the software above it to run on every implementation. The capability mechanism is settled rather than deferred, by a rule that follows from a distinction the specification already draws: an operation that may be absent becomes an interface of its own, so that its absence is reported by the linker, while a property that varies is reported by a capability word, because a property cannot be called and an operation that is present and always fails is the defect clause 6.4 rejects. Also settles concurrency, ownership, and the interface inventory, and records why openkal.event stays reserved: readiness notification is where environments differ most, and a C library can be hosted without it.
1 parent 7e93b5f commit 747154f

1 file changed

Lines changed: 332 additions & 0 deletions

File tree

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
# openkal: plan for industrial completeness
2+
3+
This document plans the extension of openkal from its core set to a
4+
specification sufficient to host a C library, and through it the ordinary
5+
software of a hosted system. It does not restate the decisions already taken;
6+
those are recorded in [`2026-08-20-openkal-design.md`](2026-08-20-openkal-design.md)
7+
and in the specification itself, and this plan treats them as given.
8+
9+
## 1. Method
10+
11+
The interfaces are derived from the kinds of resource an execution environment
12+
provides. They are not derived from what a particular program calls.
13+
14+
The distinction is not stylistic, and the specification already records why: a
15+
boundary shaped by one client acquires that client's assumptions and obliges
16+
every other environment to reproduce them. POSIX was shaped by the C library of
17+
its time; the first preview of the WebAssembly System Interface was shaped by
18+
POSIX; an openkal shaped by a compiler would be the third instance of the same
19+
error.
20+
21+
Programs are therefore used to **validate** the derivation and never to produce
22+
it. A program that cannot be hosted indicates that a resource kind has been
23+
omitted or decomposed incorrectly. A program that can be hosted only by
24+
reproducing its own assumptions indicates nothing about openkal, and something
25+
about the program.
26+
27+
### 1.1 The two validation subjects, and why they form a stack
28+
29+
```
30+
an ordinary POSIX program, such as the GCC toolchain
31+
│ the C standard library and POSIX
32+
33+
musl, ported to openkal
34+
│ the openkal C application binary interface
35+
36+
an openkal implementation: Linux, Windows, a microkernel, bare metal
37+
```
38+
39+
The two subjects are not parallel. Porting a C library is the enabling work,
40+
and porting it once causes the software above it to run on every openkal
41+
implementation without further porting. The compiler is then an end-to-end
42+
check of that claim rather than a separate exercise.
43+
44+
The arrangement also settles a question the derivation raises. A compiler
45+
assumes a global namespace of paths; that assumption is not universally
46+
satisfiable, since capability-based kernels and the second preview of the
47+
WebAssembly System Interface have no such namespace. The resolution of a path
48+
against a root the environment supplies is therefore work that belongs in the C
49+
library, performed once, rather than in every program or in the specification.
50+
51+
### 1.2 Evidence gathered
52+
53+
Measurements taken while preparing this plan. They inform coverage, which is a
54+
question about sufficiency; they do not inform shape.
55+
56+
| Subject | Measurement |
57+
| --- | --- |
58+
| GCC driver and `cc1plus` | 189 undefined symbols, of which 149 face the environment |
59+
| Process creation in GCC | `posix_spawn` and `posix_spawnp`; neither `fork` nor `execve` appears |
60+
| Path operations in GCC | `open`, `stat`, `lstat`, `mkdir`, `unlink` alongside `fstatat`: a global namespace is assumed |
61+
| musl, static archive | file and directory operations 48, process and signal 21, memory 7, threads 81, time 8, network 23 |
62+
| musl thread construction | the pthread layer is built upon a wait-and-wake primitive rather than upon kernel mutexes |
63+
64+
Two of these are load-bearing.
65+
66+
The first is that GCC creates processes by spawning rather than by duplicating
67+
the calling image. A specification that copied the POSIX decomposition would
68+
have obliged an implementation on Windows to reproduce `fork`, which cannot be
69+
done faithfully, and would therefore have failed the specification's own
70+
admission criterion. The resource-derived form and the observed usage agree,
71+
which is corroboration rather than derivation.
72+
73+
The second is that musl constructs mutexes and condition variables from a
74+
primitive that suspends an execution context until another context wakes it.
75+
The kernel boundary is therefore the primitive, and the synchronisation objects
76+
are library constructions above it. An interface offering mutexes would be
77+
placing a library at the kernel boundary.
78+
79+
## 2. The four decisions this plan settles
80+
81+
The core set is specified and published. Four questions were deferred, and each
82+
must be answered before the specification can host a C library.
83+
84+
### 2.1 Optional capabilities
85+
86+
An earlier draft deferred the mechanism. The rule adopted here follows from a
87+
distinction the specification already draws for another purpose.
88+
89+
> An **operation** that an implementation may lack becomes an interface of its
90+
> own. A **property** that varies between implementations is reported by a
91+
> capability word.
92+
93+
The justification is that the two are not alike. An operation that is present
94+
and always fails is the defect the specification rejects in clause 6.4; the
95+
remedy is that its absence is expressed by its absence, which at the granularity
96+
of an interface means the linker reports it. A property, by contrast, cannot be
97+
called. Whether paths are compared case-sensitively, whether a clock advances
98+
while the machine is suspended, what the granularity of an allocation is: these
99+
are facts, and a program adapts to them rather than invoking them.
100+
101+
Capability words are the ordinary mechanism of every system that has faced this
102+
question, including `sysconf`, `pathconf`, the auxiliary vector and the
103+
processor identification instruction. They are also the only mechanism available
104+
once the implementation is chosen at link time, which the specification's
105+
layering requires.
106+
107+
Information therefore becomes available at three times, each being the earliest
108+
at which it exists.
109+
110+
| Time | Mechanism | What it answers |
111+
| --- | --- | --- |
112+
| Dependency resolution | the implementation package declares the interfaces it provides | may this program be built against this implementation |
113+
| Link | an undefined symbol | was an interface used that the implementation does not provide |
114+
| Run | a capability word | how does this implementation behave within an interface it provides |
115+
116+
### 2.2 Interfaces beyond the core
117+
118+
Derived from resource kinds. The core set is unchanged.
119+
120+
| Interface | Resource | Class |
121+
| --- | --- | --- |
122+
| `openkal.abort` | termination | core |
123+
| `openkal.memory` | a region of the address space | core |
124+
| `openkal.stream` | a byte stream | core |
125+
| `openkal.env` | the parameters a program receives at inception | standard |
126+
| `openkal.time` | a time source | standard |
127+
| `openkal.fs` | a directory, and an open file | standard |
128+
| `openkal.process` | a program image that has been started | standard |
129+
| `openkal.task` | an execution context, and a suspension primitive | standard |
130+
| `openkal.net` | an endpoint | optional |
131+
| `openkal.module` | a code image that has been loaded | optional |
132+
| `openkal.entropy` | a source of unpredictable bits | optional |
133+
| `openkal.event` | readiness of a set of resources | reserved |
134+
135+
*Standard* denotes an interface an implementation hosting a C library provides.
136+
*Optional* denotes one it may omit without ceasing to host a C library, at the
137+
cost of the facilities built upon it. *Reserved* denotes a name whose contents
138+
are not yet normative.
139+
140+
`openkal.event` is reserved rather than specified because readiness
141+
notification is the interface at which environments differ most, and because a
142+
C library can be hosted without it: blocking operations suffice, and the
143+
facilities that require readiness are those a program uses when it declines to
144+
block. Specifying it prematurely would produce an interface shaped by whichever
145+
environment was consulted first.
146+
147+
#### 2.2.1 `openkal.fs`
148+
149+
The resource is a directory or an open file, and operations are relative to a
150+
directory the program holds. There is no global namespace of paths.
151+
152+
The reason is the admission criterion. A global namespace is not available in a
153+
capability-based kernel, and an implementation on such a kernel would have to
154+
construct one. Relative operations, by contrast, are natural on every
155+
environment considered: they are the primitive on capability systems, and on
156+
POSIX they are the `…at` family, which musl already uses to implement the
157+
global forms.
158+
159+
The consequence is that the resolution of an absolute path is work performed by
160+
the C library against a root directory the environment supplies at inception.
161+
This is the arrangement the second preview of the WebAssembly System Interface
162+
adopts, and it is what allows a program to be confined without its cooperation.
163+
164+
#### 2.2.2 `openkal.process`
165+
166+
The resource is a program image that has been started. The operations are to
167+
start one, to wait for it, and to request its termination.
168+
169+
Duplication of the calling image is not among them. It is not universally
170+
implementable, and the observed behaviour of the validation subject does not
171+
require it.
172+
173+
#### 2.2.3 `openkal.task`
174+
175+
The resource is an execution context sharing the address space, together with a
176+
primitive that suspends a context until another wakes it.
177+
178+
Mutexes and condition variables are not among the operations. They are
179+
constructions above the primitive, as the measurement of musl demonstrates, and
180+
placing them at the kernel boundary would place a library there.
181+
182+
### 2.3 Concurrency
183+
184+
An implementation shall permit concurrent operations upon distinct handles.
185+
186+
Concurrent operations upon one handle shall not damage the implementation's own
187+
state. The order in which they take effect, and whether the bytes of one
188+
transfer may be separated by those of another, are unspecified.
189+
190+
Atomicity below a threshold, which POSIX guarantees for pipes, is not required.
191+
It is not universally implementable, and a specification that required it would
192+
oblige an implementation to introduce buffering it does not otherwise need.
193+
194+
### 2.4 Ownership
195+
196+
Handles obtained from the core interfaces are borrowed and are not released.
197+
198+
Handles obtained from `openkal.fs`, `openkal.process`, `openkal.net` and
199+
`openkal.module` are owned, and each of those interfaces provides the operation
200+
that releases one. An implementation shall not treat a released handle as valid.
201+
202+
The recommended construction divides the handle word into an index and a
203+
generation, incrementing the generation on release. The specification does not
204+
require it: it requires only the property, which this construction achieves
205+
without a lookup table and therefore without the compatibility layer that clause
206+
7.1 excludes.
207+
208+
## 3. Work breakdown
209+
210+
### 3.1 Groups and their dependencies
211+
212+
```
213+
S. specification: clauses for the four decisions ─┬─► I. interface modules ─┬─► L. Linux implementation ─┬─► M. musl port ──► G. compiler validation
214+
│ │ │
215+
└─► C. conformance ──────┴────────────────────────────┘
216+
```
217+
218+
| Group | Tasks | Depends upon |
219+
| --- | --- | --- |
220+
| S | capability rule, interface inventory, concurrency, ownership | this plan |
221+
| I | `env`, `time`, `fs`, `process`, `task` declaration modules | S |
222+
| L | the same five for Linux | I |
223+
| C | behavioural suites, surface comparison, capability-word agreement | I |
224+
| M | musl retargeted onto openkal | L, C |
225+
| G | the compiler built and run above the ported library | M |
226+
227+
Groups I and C are independent of each other and both depend upon S. Group L
228+
may begin as soon as the declarations of a given interface exist, so the five
229+
interfaces proceed in parallel rather than in sequence.
230+
231+
### 3.2 Sequence within an interface
232+
233+
Each interface is taken through the same five steps, and the order is not
234+
interchangeable.
235+
236+
1. State the resource and the operations that follow from it.
237+
2. Determine which operations may be absent, and separate those into their own
238+
interface.
239+
3. Determine which properties vary, and assign them positions in the capability
240+
word.
241+
4. Write the Linux implementation, which is where a decomposition error becomes
242+
visible.
243+
5. Write the conformance suite, which is where a claim becomes checkable.
244+
245+
Step 4 precedes step 5 deliberately. The implementation of the core set
246+
demonstrated that a decomposition error is visible to an implementer and not to
247+
a reader of the specification: positioning was excluded from the stream
248+
interface by argument, and the exclusion was confirmed when the Linux
249+
implementation showed that positioning is a property of the individual
250+
descriptor.
251+
252+
## 4. Assessment
253+
254+
### 4.1 Architecture
255+
256+
The interface remains the unit of provision and of versioning, and the
257+
specification retains ownership of every module. The extension adds interfaces;
258+
it does not add mechanisms, with the single exception of the capability word,
259+
which is confined to properties.
260+
261+
### 4.2 Stability
262+
263+
Structure layouts remain frozen. The capability word introduces a new obligation:
264+
a position once assigned retains its meaning, and a property that ceases to vary
265+
is not reclaimed. Unassigned positions are reserved and read as zero, so that a
266+
program compiled against a later specification behaves correctly against an
267+
earlier implementation.
268+
269+
### 4.3 Simplicity
270+
271+
The rule distinguishing operations from properties replaces the three mechanisms
272+
that were drafted and withdrawn: a record of capability flags, a configuration
273+
file that accompanied it, and a set of fallback overloads detected through
274+
argument-dependent lookup. Each was more elaborate than the rule that replaces
275+
them, and each solved a problem that a correct decomposition does not have.
276+
277+
### 4.4 Consumer experience
278+
279+
A program declares the interfaces it requires. A program built against an
280+
implementation that lacks one fails during dependency resolution, with the
281+
interface named. A program that reaches an interface it did not declare fails
282+
at link, with the function named. Neither failure is deferred to run time.
283+
284+
### 4.5 Compatibility
285+
286+
The core set does not change. An implementation of the core set that predates
287+
this extension remains conforming, and a program that uses only the core set is
288+
unaffected.
289+
290+
### 4.6 Portability
291+
292+
The interfaces are derived rather than borrowed, and each is checked against
293+
four environments before it is specified: a hosted system with a global
294+
namespace, a hosted system without one, a capability-based kernel, and a
295+
freestanding target. An interface that any of the four could satisfy only by
296+
constructing a compatibility layer is decomposed again rather than admitted.
297+
298+
### 4.7 Upgrade
299+
300+
An implementation adds interfaces without altering those it already provides. A
301+
program observes the addition through dependency resolution. No existing
302+
manifest requires modification.
303+
304+
## 5. Criteria for completeness
305+
306+
The extension is complete when the following hold. Each is an observation rather
307+
than a judgement.
308+
309+
1. musl, retargeted onto openkal, builds and passes its own test suite against
310+
the Linux implementation.
311+
2. The compiler toolchain, built against that library, compiles and links a
312+
program, and the program runs.
313+
3. A second implementation exists for an environment without a global path
314+
namespace, and the same library binary interface is satisfied by it.
315+
4. The conformance suite verifies, for every interface, the behaviour of the
316+
operations provided, the absence of those not provided, and the agreement
317+
between each capability word and the behaviour it describes.
318+
5. No implementation requires a table, a registry or a name resolver in order to
319+
satisfy the specification.
320+
321+
Criterion 3 is the one that distinguishes this work from a portable C library.
322+
A specification satisfied only by environments resembling the one it was written
323+
against has not been validated, however many programs it hosts.
324+
325+
## 6. Matters this plan does not settle
326+
327+
| Matter | Status |
328+
| --- | --- |
329+
| `openkal.event` | reserved; see 2.2 |
330+
| Symbol versioning | the evolution rule prohibits change rather than permitting coexistence, and an ecosystem that outgrows the prohibition will require a mechanism this specification does not define |
331+
| Locale and character encoding | properties of a C library rather than of a kernel boundary; the specification does not address them |
332+
| Signals as a general mechanism | the process interface provides termination; asynchronous delivery to a running program is deferred with `openkal.event` |

0 commit comments

Comments
 (0)