Skip to content

Latest commit

 

History

History
2394 lines (2070 loc) · 148 KB

SPEC.md

File metadata and controls

2394 lines (2070 loc) · 148 KB

openkal Specification, version 0.25.3

1. Scope

openkal defines an interface between a program and the environment that executes it. The interface is stated as a C application binary interface. It is distributed as a set of C headers that declare it and a set of C++ modules that export those declarations, so that a consumer written in either language reaches the same entities. Clause 4.2 records why the second way of reaching them is not a convenience.

The specification has two audiences. An implementation supplies the functions declared here and is judged by whether it can do so without constructing a compatibility layer. A consumer — an application, a C library, or a C++ standard library port — depends on those declarations and is judged by whether it can do so without knowing which implementation is present.

openkal does not describe an operating system, and it does not describe a program. It describes the boundary between them, and it is written so that the boundary can be crossed in either direction: an implementation may be built upon a C library, beneath one, or without one.

1.1 What crosses the boundary

openkal is three statements at once: an interface stated in C (clause 4), an application binary interface at the boundaries of clause 4.4, and, for an ok image, the binary interface at run time between the image and the loader that binds it (clause 12). In each, what crosses is what this specification states, and nothing else.

Nothing of the environment beneath crosses upward. A value, a layout, an error, a name or a behaviour a consumer observes is one this specification defines. An implementation translates what its environment answers into these terms --- an error into clause 5.2's values, a capability into clause 6.2's properties --- and where its environment answers something the specification has no term for, it reports the nearest outcome the specification states or declines the operation; it does not pass the environment's own answer through. An identifier an environment gives (a program's, a context's) crosses as an opaque value, and its meaning is the one stated here, not the environment's.

Implementations may differ beneath, and not above. One calls its kernel, another a C library; a third composes an operation from others, or emulates it, and they differ in cost. They do not differ in what a consumer can observe, beyond what this specification leaves open, and what it leaves open it names: a property an implementation claims or does not (clause 6.2), one outcome among those an operation states (as a lowered claim on the processors may be refused raising, clause 11 entry 41). A consumer written against the specification runs on every implementation that provides what it uses, which is what the ecosystem above openkal is built upon.

A consumer that reaches past openkal binds itself to what it reached. A program may call its environment directly --- a system library, a system call --- and this specification neither forbids nor observes it. Such a program is portable as far as the rest of it is, and what it reached for is its own to provide elsewhere. An ok image cannot reach past: it imports only the names this specification declares and those other images export (clause 12.2), which is what lets the same bytes run on every system a loader serves.

2. Conformance vocabulary

The words shall, shall not and may carry their usual normative force. A conforming implementation satisfies every shall that applies to the interfaces it provides. An implementation is not required to provide every interface.

3. Interface inventory

An interface is the unit of versioning and of provision. An implementation provides an interface in whole or not at all.

Interface Resource Tier S L X
openkal.abort termination core ✓ ✓ ✓
openkal.stream a byte stream core ✓ ✓ ✓
openkal.memory a region of the address space core ✓ ✓ ✓
openkal.env the parameters a program receives at inception optional ✓ ✓ ✓
openkal.time a time source optional ✓ ✓ ✓
openkal.random a source of unpredictable bytes optional ✓ ✓ ✓
openkal.fs a directory, and an open file optional ✓ ✓ ✓
openkal.process a program image that has been started optional ✓ ✓ ✓
openkal.task an execution context, and a suspension primitive optional ✓ ✓ ✓
openkal.exec a region of the address space a program may execute optional ✓ ✓ ✓
openkal.terminal an interactive stream's treatment of what is typed optional ✓ ✓ ✓
openkal.net a connection, and a listener for connections optional ✓ ✓ ✓
openkal.datagram a message with a boundary, sent without a connection optional ✓ ✓ ✓
openkal.space an address space, and a context executing in one optional ✓ ✓ ✓
openkal.timeout a bound upon operations that would otherwise wait optional ✓ ✓ ✓
openkal.map a part of a file's contents in the address space optional ✓ ✓ ✓
openkal.region a range of the address space, reserved and backed as it is used optional ✓ ✓ ✓
openkal.prof what a program states about its own work to a measurement optional ✓ ✓ ✓
openkal.debug the name of an address in a program's own code optional ✓ ✓ ✓
openkal.event readiness of a set of resources reserved

Version 0.15 specifies the core and optional interfaces, version 0.18 adds openkal.map (clause 11, entry 40), and version 0.20 adds openkal.region, openkal.prof and openkal.debug (entries 44 and 45); versions 0.21 to 0.23 add no interface. The reserved row is not specified, and its name shall not be used for other purposes.

The S, L and X columns state which boundaries an interface's declarations can cross. They are defined in clause 4.4 and are a statement about the shape of the declarations, not a requirement upon implementations: an implementation cannot violate them, and only a declaration can.

The five interfaces added in version 0.8 are optional in the sense clause 3 defines, and their optionality is not a concession. An environment with no network is not deficient for providing neither openkal.net nor openkal.datagram; an environment with no memory management unit cannot provide openkal.space and is not deficient either. Clause 6.1 expresses each absence as the absence of a definition at the link, so a program that requires one of them is refused when it is built rather than when it runs.

Core denotes an interface every implementation provides. Optional denotes every other, and how its absence reaches a consumer is one of two things:

how an optional interface is resolved mechanism
the implementation does not provide it at all clause 6.1 — the definitions are absent, and a consumer that uses one fails to link
the implementation provides it only to an artifact produced in a particular way a feature of the implementation's package, resolved at dependency resolution — clause 6.5

There were three tiers, and the middle one said that an implementation hosting a C library provides openkal.env, openkal.time, openkal.fs, openkal.process and openkal.task. It was false, and falsified within this specification's own ecosystem: an implementation for a machine with firmware and no operating system provides none of the last three, and a C library is hosted above it. The tier stated a fact that was not one, and it stated it about the consumer rather than about the interface.

The honest classification is the Resource column beside it. An interface exists where its resource exists — storage, a second image, a scheduler, a network, an address space that can be copied, entropy, an interactive stream, a bound upon a wait. Clause 6.1 makes the absence a fact a consumer learns; no tier is needed to predict it, and the prediction was wrong.

Informative, and carrying no requirement: an implementation of a system with storage and a scheduler ordinarily provides openkal.env, openkal.time, openkal.fs, openkal.process, openkal.task and openkal.timeout. This sentence describes what is common; nothing follows from it, and an implementation is not measured against it.

3.1 The basis of the core set

An interface belongs to the core set when every plausible implementation can supply it without simulating a facility the environment lacks. The distinction between supplying and simulating is drawn as follows.

An implementation that would leave its callers silently wrong is a simulation. An implementation that merely bounds what is available is a supply.

A bump allocator over a fixed region can be exhausted, and exhaustion is a defined outcome on every platform; such an allocator therefore supplies openkal.memory. A counter that does not advance in real time would make every timed wait incorrect without reporting it; such a counter would simulate openkal.time, and openkal.time is accordingly not in the core set.

It follows that the presence of a heap is not a property of the hardware. Any environment with writable memory can supply one, and the specification places openkal.memory in the core set on that basis.

3.2 The core set does not grow

An interface admitted to the core set is required of every implementation thereafter, including implementations of environments no one has yet written one for. The set is therefore closed at openkal.abort, openkal.stream and openkal.memory, and a later version shall place a new interface outside it even where every implementation that exists at the time would satisfy it.

The asymmetry is the reason. An interface wrongly placed in the core set excludes environments — a bare machine, a supervisor, firmware — from conformance altogether, and that exclusion is discovered by whoever tries to write the implementation rather than by the specification. An interface wrongly placed outside it costs a consumer one declared dependency. The first error is found late and by the wrong party; the second is found at dependency resolution and by the party that can act on it.

What this closes is the set of INTERFACES. kal_version and kal_interfaces are exported by every conforming implementation and belong to no interface: they provide no resource, and an implementation answers both with constants. They are the specification's own self-description, required by clause 9, and requiring them adds nothing to the core set that a bare machine would have to supply. A consumer that is linked never needs to call them; a consumer bound at load or across a boundary has no linker to ask, and asking an interface by calling into it is the act that must not happen first.

3.3 Naming a set of interfaces

An interface is provided or not provided, and clause 6.1 makes that a fact a consumer learns from the linker. A consumer must nevertheless be able to state what it needs before it is built, and stating it one interface at a time does not scale: the sets a program can be written against are not arbitrary subsets, and an ecosystem that could only enumerate them could not say "this package needs an environment of such-and-such a kind".

The specification names no such set, and the one it did name has been withdrawn. Version 0.8 named hosted — the core interfaces together with openkal.env, openkal.time, openkal.fs, openkal.process and openkal.task. The name described a class of environment, and a name that describes a class of environment is falsified by an environment nobody had in mind. This one was falsified inside its own ecosystem within a release: a C library is hosted above an implementation that provides none of the last three.

The scaling argument is also weaker than it read. There are fifteen interfaces, and a consumer that needs five names five. That is five lines, not a problem of scale, and it is what the ecosystem already does — a consumer states its required set per target, in its own package, which is where a convention among consumers belongs.

core remains, and is not a set a consumer states: it is the requirement upon implementations stated in clause 3.2. Nothing in this clause alters clause 6.1: an interface a consumer uses and an implementation does not provide is an undefined symbol.

3.4 Interfaces the specification declines to define

Naming is not factored into a separate interface. An earlier draft replaced openkal.fs and openkal.net with a single interface mapping names to resources, on the grounds that a file, a network connection and a serial port all yield a byte stream. That decomposition was withdrawn for three reasons.

  1. It required every implementation to parse an unbounded set of name schemes, including implementations that support none of them. That requirement is a compatibility layer, and clause 7.1 excludes it.
  2. It merged the operations of unrelated resources. Positioning applies to a file and not to a connection; half-closure applies to a connection and not to a file. The merged interface would have contained operations that some of its resources can never satisfy, which clause 6.4 identifies as a defect.
  3. The precedent cited in its support was misread. The WebAssembly System Interface, second preview, separates resource kinds into distinct interfaces and shares only the stream type between them.

The stream is therefore the shared currency of the specification and not its common entrance.

4. Organisation of the declarations

The specification package provides one header per interface, which is where the declarations are, and one module per interface, which exports them. An implementation provides neither.

Interface Module Header
shared definitions openkal.types openkal/types.h
openkal.abort openkal.abort openkal/abort.h
openkal.stream openkal.stream openkal/stream.h
openkal.memory openkal.memory openkal/memory.h
openkal.env openkal.env openkal/env.h
openkal.time openkal.time openkal/time.h
openkal.fs openkal.fs openkal/fs.h
openkal.process openkal.process openkal/process.h
openkal.task openkal.task openkal/task.h
openkal.random openkal.random openkal/random.h
openkal.exec openkal.exec openkal/exec.h
openkal.map openkal.map openkal/map.h

openkal.h includes every header, for a consumer that uses several.

A consumer imports the interface. An implementation imports the same interface, because it needs the declarations it is defining, and it exports nothing.

consumer ──imports──► openkal ◄──imports── implementation
     │                                          │
     └──────────── links against ───────────────┘

4.1 What an implementation contributes

Definitions of the functions the interface declares, and nothing else. An implementation that declares a module has misunderstood its role: the interface is the specification's, and an implementation that exported one would place a name the consumer relies upon under the control of a party the specification does not govern.

The consequence is that an implementation cannot extend the interface. This is not a restriction that must be enforced; it follows from the arrangement. An implementation may of course publish additional facilities, and it does so in its own modules, which a consumer that uses them must import by name.

4.2 One statement of the declarations, two ways to reach it

The contract is a C application binary interface. The specification package states it once, as a C header per interface, and provides a C++ module per interface that includes that header in its global module fragment and exports the names it declares.

The second way of reaching the declarations is not a convenience. A C++ consumer imports the module for the interface it uses; a consumer written in C cannot, because a C translation unit has no import. The canonical consumer written in C is a C library being ported onto openkal, which is the case clause 1 names first, so a specification reachable only by import would be unusable by the consumer it exists for. Version 0.4 was reachable only by import, and the omission was found by attempting that port.

The arrangement is deliberately not two statements that agree. A module that re-declared what the header declares would be a second declaration of the same contract, and two declarations drift; here there is one declaration, and the module exports it. A consumer that imports and a consumer that includes therefore obtain the same entities, and no procedure is required to keep them equal because they are not two things.

The headers include no header of their own, name no library, and obtain the width of a machine word from the compiler. The second way of reaching the declarations therefore adds nothing to what the specification package requires, which remains nothing.

What the module adds

The module is not a translation of the header. It carries what C++ can check and C cannot, and both are checks rather than facilities.

The frozen layouts are asserted. Clause 5.3 declares the layout of every structure immutable. A declaration that something shall not change is not a mechanism; static_assert is. A handle being one machine word is the difference between a value returned in a register and one returned through a hidden pointer, which is a change of calling convention that no declaration would report and that a consumer built against the earlier layout would not survive. kal_node_info is the one structure that is permitted to grow, and it carries self_size for precisely that reason — the assertion on it fixes the offsets of the fields that exist, not the size of the whole.

The capability words become types that cannot be mixed. Clause 6.2 gives each interface a word and positions within it, and every such word is a kal_uintptr. A program that tests a file-system position against the task word therefore compiles, runs, and answers a question nobody asked; the position numbers are small and several interfaces have assigned the same ones, so the answer is frequently the plausible one. In the module each interface's positions carry the interface in their type, the operations that compose them are defined only within one interface, and the mistake becomes a diagnostic. Nothing is stored beyond the word and every operation is constant-evaluated.

The same reasoning applies to the flags of kal_fs_open, which are an intent rather than a property and are a distinct type from either.

Arrangements considered and not adopted

Two declarations, compared by a procedure. Not adopted: it is the arrangement this clause exists to avoid, and a procedure that compares them is a thing that can be omitted, skipped or made to pass.

Generating the header from the modules. Not adopted: it makes the module form normative, which contradicts clause 1, and it introduces a generator, which is a build-time dependency the specification package is written to avoid having.

Leaving the C form to each implementation. Not adopted for the reason clause 4.1 gives: a name the consumer relies upon would be under the control of a party the specification does not govern, and a C library ported onto openkal would take its declarations from whichever implementation it happened to be built against.

A second package carrying the C form. Not adopted: one contract in two packages can be resolved at two versions, and the property a contract has is that there is one of it.

4.3 Absence of an implementation

A consumer that depends upon the specification and upon no implementation compiles and fails to link, and the diagnostic names the undefined functions:

undefined reference to `kal_stream_write'
undefined reference to `kal_stdout'

The failure is later than a compilation failure would be and is legible. An arrangement that reported it during compilation was considered and is described in clause 6.3, together with the reason it was not adopted.

4.4 The boundaries a declaration can cross

Compile-time portability is the floor of this specification and not its goal. Every interface, operation and structure is designed so that it can also be crossed at run time, and where one cannot the reason is recorded in the table of clause 3 rather than left to be discovered.

Three boundaries are distinguished, because "crossed at run time" gives opposite answers for two of them:

boundary the implementation is what it additionally forbids
S static chosen when the artifact is built, and linked into it nothing
L late-bound a separate object resolved at load, in the caller's address space; the artifact does not change when the implementation does a structure whose size the consumer fixed when it was built; a report whose value the consumer fixed when it was built
X crossed in another address space or at another privilege level; a call is a trap or a message a returned pointer into the implementation; a result wider than one machine word; a call back into the consumer

Returning a pointer into the implementation is admissible at L and inadmissible at X; so is a result of two machine words. An interface marked for one is not thereby marked for the other.

An interface is X-capable when all six of the following hold.

  1. It exports no object. Everything an implementation reports about itself is an operation (clause 6.2).
  2. No operation returns a pointer into the implementation's storage. A value is copied into a buffer the caller supplies.
  3. No result is wider than one machine word. An operation whose whole result is a count returns that count or the negated error value; an operation that produces a resource returns int and writes the resource through a pointer.
  4. Every structure that crosses carries its own size, or states why it cannot grow.
  5. An entry is an address at which a context begins, not a function that is called. No return address exists in the started context.
  6. Its absence is discoverable by an operation and not only by a linker (kal_interfaces, clause 3.2).

The first four are decidable from SURFACE.txt and the declarations, and clause 9 asserts them. A new interface is designed against this rule before it is specified. The reserved openkal.event is the first to which that applies.

5. Common definitions

5.1 Machine word

kal_uintptr is an unsigned integer type wide enough to hold a pointer. It is obtained from the compiler rather than from a header, because a freestanding target may provide no header that defines it.

5.2 Error values

The set of error values is closed. An implementation maps the error conditions of its environment onto this set; it does not extend the set.

Mapping is not simulation. A table that translates one error value into another preserves the property that the implementation is natural for its environment, whereas reproducing a foreign namespace of names, descriptors or paths does not.

Detail beyond these values is not available. A per-thread channel carrying the environment's own error value was considered and rejected: such a channel is global mutable state with the defects of errno, and control flow that consults it is not portable.

Version 0.5 adds five values, and records why, because the set being closed makes an addition to it the kind of change a reader is entitled to see argued.

Value Why the set could not express it
kal_err_not_found Clause 7.7 already required it. The value did not exist, so both implementations reported a missing name as kal_err_invalid, and a caller could not distinguish a name that is absent from a handle that is wrong. The defect was in the enumeration rather than in the implementations.
kal_err_exists kal_fs_open with exclusive, and kal_fs_mkdir, fail because the name is already there. It is an expected outcome, and mapping it to kal_err_io would report a medium failure for one.
kal_err_not_empty Removing a directory that is not empty. Same reasoning.
kal_err_is_directory A file operation applied to a directory.
kal_err_not_directory A directory operation applied to a file, and a component of a name that is not a directory.

Version 0.13 adds one value.

Value Why the set could not express it
kal_err_not_program A name that exists and may be started, and is not in a form the environment can start. Every environment has the condition natively (ENOEXEC, ERROR_BAD_EXE_FORMAT). Before this value an implementation reported it as kal_err_io, which states that a device failed when none did; a C library above could then not map the failure back to the condition its own callers act upon. Measured by a consumer: posix_spawn of such a file returned EIO above openkal and ENOEXEC on a host (issue 28).

The criterion for adding a value. The set is closed against implementations, which do not extend it; it is not closed against this specification, which adds to it under clause 8. A value is added only when both of the following hold, and the record of each addition states both:

  1. a caller acts differently upon the condition than upon every existing value, so that folding it into an existing value leaves the caller wrong rather than merely less informed; and
  2. every environment the specification targets has the condition natively, so that the mapping is one arm of a table and not a classification an implementation must construct (clause 7.1).

A condition for which either is not established is recorded and not added. The failures of a network connection are such a case today: the three implementations report them differently, and no measurement yet establishes which distinctions a caller acts upon.

The addition is governed by clause 8, which admits new declarations. A value already assigned retains its meaning, so a program compiled against version 0.4 observes the same values for the same conditions as before.

5.2.1 How an operation reports its result

There is one rule, and it holds for every interface.

An operation whose whole result is a count of bytes returns kal_intptr: the count, or the negated error value when no byte was produced. A count of zero is a count and not an error; for a read it denotes end of input.

An operation that produces a resource, or that produces nothing, returns int from kal_error and writes what it produced through a pointer.

Version 0.8 returned a structure of a count and an error from the operations that transfer. It is withdrawn, and the measurement that withdrew it is that every consumer of it collapsed the pair by hand and by this rule — report what was moved, or the condition when nothing was. The pair was never the shape a caller wanted, and a result of two words cannot be carried by a boundary that returns one (clause 4.4).

The collapse is sound because the error set of clause 5.2 is closed: a negated error occupies a small known range and can never be mistaken for a count. An open-ended error space would not permit it.

5.3 Structure layouts

The layout of every structure declared by this specification is frozen at version 0.3. The evolution rule of clause 8 admits new declarations and excludes changes to existing ones; a structure layout is not protected by that rule unless it is separately declared immutable, and it is so declared here.

One structure is excepted, and the exception is stated in clause 4.2: kal_node_info carries self_size, and fields may be appended to it. An appended field does not move a field that exists, and an implementation writes no more of the structure than the caller's self_size states. Version 0.13 appends executable and a reserved field that keeps the size a multiple of eight on every target.

5.4 The interface names its own types and no others

Every type that crosses this interface is one this specification declares. kal_uintptr, kal_u32, kal_u64, kal_i64 and the structures of clause 5.3 are the whole vocabulary, together with void, char and int, which have one meaning everywhere.

A declaration does not name long, size_t, uint64_t, or any other type whose definition belongs to a C library or to a data model. This is not a stylistic preference. sizeof(long) is eight on one common target and four on another; a signature naming it would be one signature with two meanings, and the property clause 1 exists for — that a program's source is invariant under a change of implementation — would hold only among implementations that shared a data model.

Clause 5.1 gives the reason the types are derived from the compiler rather than from a header. This clause states the consequence for everything built above: a layer above openkal uses nothing from a concrete backend, including its types. The question "how wide is long here" does not arise at this interface, and it does not arise because it cannot be asked.

The one layer for which this does not hold is a C library being ported onto openkal, and it does not hold there for a reason that is not an exception: such a library reconstructs POSIX, and POSIX itself names long. Its answer is a property of the target rather than of openkal, and it is configured per target accordingly.

This clause adds no declaration and alters none, so clause 8's rule is not engaged and the version does not advance on its account. Every declaration of version 0.6 already satisfies it — one hundred and one of them, examined by the procedure below — and what is new is that the property is now stated and checked rather than held by the care of whoever wrote each header. The five interfaces added in version 0.8 were examined by the same procedure and satisfy it also; the count is now one hundred and forty-six.

Clause 9's procedure examines this.

5.5 Word width

The declarations are the same on a target whose machine word is thirty-two bits wide, and they mean the same. kal_uintptr and kal_intptr are thirty-two bits there; a position in a file, a size of a file, a duration and an instant remain kal_i64 and kal_u64, so that a file larger than four gibibytes and a time after 2038 are as reachable as elsewhere. Four consequences follow, and each is a requirement upon an implementation for such a target rather than a change to a declaration.

  1. A count is a word. An operation that reports a count reports it in kal_intptr. A transfer whose length exceeds the largest such value is a partial transfer, reported as any partial transfer is (clause 7.4); it is not a wrapped count.
  2. A layout is the target's. A structure of clause 5.3 is laid out as the target's C ABI lays out its declaration; on i386 a kal_u64 field is aligned to four bytes and not eight. The immutability of clause 5.3 is that of the declaration, and both sides of the interface compile the same declaration with the same compiler, so they agree. An implementation does not copy a structure between a sixty-four bit and a thirty-two bit layout.
  3. A handle fits the word. A handle is a kal_uintptr. An implementation that packs more than an index into a handle --- a generation, a kind --- packs it into thirty-two bits on such a target, and a generation that wraps sooner is acceptable where clause 7.2's staleness is still detected for the reuse the environment can make within one wrap.
  4. Time is sixty-four bits at the environment, too. An environment that offers both a thirty-two bit and a sixty-four bit form of a time operation is called through the sixty-four bit form, where the environment has one: a thirty-two bit second count ends in 2038, and an answer that wraps there is a wrong answer, not an approximation.

Images (clause 12) are of one architecture; an image of a thirty-two bit architecture runs on a loader of that architecture. Clause 11, entry 62.

6. Capability model

6.1 Presence of an interface

An interface that an implementation does not provide is absent as a link-time definition, and a consumer that uses it fails to link. A conforming implementation shall not provide an interface whose operations report a lack of support at run time; the specification treats run-time refusal as a defect and not as a means of expressing partiality.

An interface that the specification does not define is absent as a module, and a consumer that imports it fails to compile.

6.2 Optional operations and varying properties

An operation an implementation may lack becomes an interface of its own, so that its absence is reported by the linker. A property that varies between implementations is reported by a capability word.

The two are not alike, and the distinction is the one clause 6.4 already draws. An operation that is present and always fails is a defect; the remedy is that its absence be expressed by its absence. A property cannot be called: whether names are compared case-sensitively, whether a clock advances while the machine is suspended, what the granularity of a measurement is. A program adapts to a property rather than invoking it.

Each interface that has properties declares a word named kal_<interface>_props and the positions within it. A position, once assigned, retains its meaning; a position that has not been assigned reads as zero, so that a program compiled against a later specification behaves correctly against an earlier implementation.

A position is therefore assigned the sense in which zero is the weaker claim. An implementation that has never heard of a position answers with zero, and that answer is a statement about the implementation which has to be true; a position spelled the other way about would make the silence of every earlier implementation assert the stronger thing. The rule is stated here because it was recognised while assigning one: version 0.14 states whether an environment reserves keystrokes for itself, and the spelling in which zero meant reserves none would have had every implementation released before it claim a guarantee that none of them provides.

A property that varies between the resources of an interface rather than between implementations cannot be a word, because there is no one answer to record in it. Such a property is reported by an enquiry taking the resource. kal_stream_props is the example: the same implementation answers differently for a terminal and for a file. The enquiry is not the defect clause 6.4 describes, because it is not an operation upon the resource --- nothing is transferred, and no resource can fail to answer.

Information therefore becomes available at three times, each being the earliest at which it exists.

Time Mechanism Question answered
dependency resolution the implementation package declares what it provides may this program be built against this implementation
link an undefined symbol was an interface used that the implementation does not provide
run a capability word how does this implementation behave within an interface it provides

6.3 Mechanisms considered and not adopted

The rule of clause 6.2 replaced three arrangements that were built before it was recognised. They are recorded because each is the arrangement a reader is likely to propose, and because the measurements that exclude them remain useful.

A module supplied by the implementation, declaring the optional operation, so that a consumer importing it without a supporting implementation fails to compile with the module named. Not adopted: it reintroduces, for the optional operation, the arrangement clause 4.1 rejects for the interface.

Fallback overloads in the interface, displaced by an implementation declaring its own, with presence detected through argument-dependent lookup. Not adopted: the arrangement requires an implementation's declarations to be visible to the consumer, which requires the implementation to own the module the consumer imports, which contradicts clause 4.

A record of capability flags, accompanied by a file in which an implementation declares them. Not adopted: a record can disagree with the code it describes, and the file is a second place in which a package is configured.

Two further arrangements were weighed while specifying version 0.8 and are recorded on the same basis.

Readiness notification. An interface reporting that a stream may be read, by waking a word as kal_task_wake does, was considered as the remedy for a context that would otherwise wait without end. It composes better than the bound this specification adopted: one operation covers every waitable resource, a single context may await many sources, and a library above it needs no read-ahead buffer because a notification consumes nothing.

It was not adopted because of what it asks of an implementation. On an environment whose readiness is discovered by polling a set of descriptors, an implementation would have to maintain that set and a context of its own to watch it. That is a mechanism reconstructed rather than a facility conveyed, which clause 7.1 excludes. openkal.timeout asks the same environment only for what it already does at the point of the call.

A space as a handle. An earlier form of openkal.space separated the copying of an address space from the starting of a context in it, so that a caller held a space and could start a context in it afterwards. It was withdrawn while the first implementation was being written.

No environment this specification targets has that pair as a primitive. The copy and the start are one act, and an implementation asked to separate them would have to start a context anyway, park it upon a waiting primitive, and build a channel by which to tell it what to run. Clause 7.1 identifies that as a fault in the shape of the specification rather than in the implementation, and the separated form was the shape at fault. The single operation that replaced it is what every such environment already performs.

An instant rather than a duration. openkal.timeout states a duration because kal_task_wait does. An instant would not accumulate drift when a caller retries in a loop, and was considered for that reason. It was not adopted because it would give one specification two spellings of one idea. A caller that requires an instant computes the remaining duration from kal_time_monotonic, so the cost falls upon the caller that has the requirement rather than upon every implementation.

The measurements that constrain any future proposal:

  1. A requires-expression naming a qualified entity that does not exist is ill-formed. Detection through unqualified lookup and argument-dependent lookup is well-formed and evaluates to false.
  2. Argument-dependent lookup does not reach a module the translation unit has not imported.
  3. A module whose name extends another module's name by a further component is treated by some build systems as a submodule of it, and the two then form a dependency cycle.

6.4 Operations that cannot be uniformly present

An operation that some resources of an interface can never satisfy shall not be placed in that interface. Positioning is the example the specification records. On a hosted system, whether a stream can be repositioned is a property of the individual stream rather than of the implementation: the same implementation succeeds for a regular file and fails for a pipe. An implementation could therefore neither claim the operation honestly nor withhold it usefully. Positioning accordingly belongs to openkal.fs, whose resource is a descriptor.

6.5 Availability settled by how the artifact is produced

An interface may be one the environment can provide only to a program that was produced in a particular way. openkal.exec is the instance: one system grants a program memory it may execute only when the program carries a signed declaration that it will ask for such memory, and that declaration is applied after the link, by whoever produces the artifact.

This is neither of the two partialities clause 6.2 distinguishes. The implementation does not lack the operation, so withholding the interface outright would deny it to programs for which it does work. Nor does it vary between the resources of the interface, which is clause 6.4's case; every region in a given program behaves alike.

Such an interface shall be provided or withheld at dependency resolution — the first of the three times clause 6.2 tabulates, and the earliest at which the question can be answered, because the party that decides how the artifact is produced is the party that resolves its dependencies. An implementation expresses it as a feature of its package. A program that asks for the feature gets the interface and an artifact produced so that it works; a program that does not ask gets neither, and using the interface fails to link, which is clause 6.1's ordinary report.

The rule this states is general: an operation whose availability is decided by how the artifact is produced is not reported at run time. Reporting it at run time would make every caller carry a path that most artifacts never take — and a path no artifact takes is a path nothing has verified.

The rule holds where the artifact is produced for its own environment, and inverts where it is not. An artifact that is distributed is produced once, by a party that decided for every environment it will meet, possibly long before and for a different system; the consumer resolves nothing. So availability settled this way is also reported at run time, by a position in the interface's property word — KAL_EXEC_PROP_AVAILABLE. The interface is provided either way, and whether it can be exercised is read.

This is not the shape clause 6.2 forbids. An operation that is present and always fails is a defect because the caller cannot tell, and a position the caller reads first is what tells it.

The objection above survives and is answered by clause 9 rather than dismissed: a path few artifacts take is a path little verified, so the conformance arrangement answers that position both ways. Until it did, the unavailable path had no producer at all.

6.6 Concurrency

An implementation shall permit concurrent operations upon distinct handles.

Concurrent operations upon one handle shall not damage the implementation's own state. The order in which they take effect, and whether the bytes of one transfer may be separated by those of another, are unspecified.

That permission concerns operations competing for one direction of a resource. A resource with two independent directions is not a resource with one: openkal.net states that a transfer waiting in one direction of a connection shall not delay a transfer in the other or a half-closure, and openkal.datagram states the same of sending and receiving (version 0.13). Without that statement an implementation that serialised every operation upon a socket conformed, and a full-duplex protocol above it waited for ever.

Atomicity below a threshold, which some systems guarantee for some resources, is not required. It is not universally implementable, and requiring it would oblige an implementation to introduce buffering it does not otherwise need.

6.7 Ownership

Handles obtained from the core interfaces are borrowed. They are not released, and no operation releases them.

Handles obtained from openkal.fs and openkal.process are owned. Each of those interfaces provides the operation that releases one, and an implementation shall not treat a released handle as valid.

The recommended construction divides the handle word into an index and a generation, incrementing the generation upon release. The specification requires the property and not the construction; this one achieves it without a lookup table, and therefore without the compatibility layer clause 7.1 excludes.

7. Requirements upon implementations

7.1 Naturalness

An implementation shall not require a compatibility layer. The test is mechanical: an implementation that must maintain a translation table, a registry, or a name resolver in order to satisfy this specification indicates that the specification has taken a shape borrowed from one environment, and the shape is at fault rather than the implementation.

7.2 Handles

A handle occupies one machine word and is opaque. An implementation stores whatever it uses natively: a descriptor, an operating-system handle, a pointer to a driver structure, or a capability index.

The width is fixed and the interpretation is not, and this is the property that allows an implementation to be placed above a C library, beneath one, or without one. A handle declared as a descriptor would oblige an implementation whose environment has no descriptors to invent a table; a handle declared as a pointer to a library structure would oblige an implementation with no such library to invent one.

An implementation shall not assume a process model, a division between privileged and unprivileged execution, or a namespace shared by all callers. A handle shall be meaningful in the context of the caller that obtained it.

An implementation shall refuse a word that is not a handle it issued, and shall not act upon it as though it were one it did. The preceding paragraph constrains a handle's scope; this one constrains its validity, and the two are not the same requirement. Under a static link the distinction is academic — the caller and the implementation are one program. Where the implementation is on the far side of a boundary it is the boundary itself, and an implementation that acted upon an arbitrary word would let a caller name a resource it was never given. The division of a handle into an index and a generation, which clause 7.2 already recommends for release, satisfies this as well.

7.3 Allocation

Where the environment already provides an allocator, kal_alloc shall be built upon it and shall not be built beside it.

The requirement addresses a defect that appears only under load. A C library's formatted output is commonly coupled to that library's own allocator; an implementation that introduces a second allocator therefore places two independent claimants on one region of memory, and on a system whose heap grows by extending a single region the two will interfere.

7.4 Transfers

kal_stream_write shall transfer the whole buffer or report the condition that prevented it. A partial transfer is not a successful outcome.

The alternative convention, under which the caller inspects a count and repeats the call, places one loop in every caller. Interfaces that adopted it have been a recurring source of defects. The loop belongs in the implementation, which is written once.

kal_stream_read shall report the number of bytes transferred, which may be fewer than requested. A result of zero denotes end of input. Unlike a partial write, a partial read carries information the caller requires.

Both report that number, or the negated error value when no byte was moved, in one signed machine word — clause 5.2.1. An operation that moved bytes and then met a condition reports the bytes; the condition is reported by the next call, which is what every consumer of the earlier two-word form already did by hand.

7.5 Interruption

An implementation shall retry an operation that its environment interrupts, and shall not report the interruption. A caller cannot distinguish an interrupted call from a genuine failure without knowledge of the environment, and an implementation that reports it produces short transfers on any system that delivers asynchronous notifications.

7.6 Argument vectors

The vector supplied to kal_process_spawn is complete: its first element is the name the started program shall observe as its own, and an implementation shall pass the vector unaltered. An implementation shall not derive the first element from the path argument, and shall not prepend, append or reorder elements.

The rule exists because the two sides must agree. A started program reads its own name through kal_env_arg(0), so a caller that did not supply it could not predict what the program would read; and the name a program observes as its own is behaviour on every environment that has an argument vector, so the choice belongs to the caller rather than to the implementation.

An implementation whose environment has no argument vector shall report kal_err_unsupported from kal_process_spawn rather than discard the vector.

An environment that gives a started program one string, which the program splits by rules of its own, passes the vector by quoting each element so that the conventional splitting recovers it. A program whose rules are not the convention --- the command interpreter, whose /c takes the rest of the string as a command line of its own grammar --- cannot be given every argument through that quoting. KAL_SPAWN_ONE_STRING asks for the first element quoted as usual and the others joined after it by single spaces, unaltered; an implementation claims it with KAL_PROCESS_PROP_ONE_STRING, and one whose environment gives a program a vector reports kal_err_not_supported (version 0.17.1).

This rule was absent from version 0.3, and both implementations existing at the time prepended the path. They agreed only because they shared an author; a third implementer had nothing to consult. The defect was found by writing one program against the specification and running it, which is what clause 9 exists to require.

7.7 Absence as an answer

kal_fs_info shall report a name that does not exist by returning kal_ok and setting kind to kal_node_absent. It shall not report absence as an error.

Enquiry and access are different operations. A caller that asks what a name refers to has been answered when told that it refers to nothing, and an implementation that reports absence as a failure obliges every caller to distinguish that failure from the ones that denote a broken enquiry — a directory it may not read, a name it may not resolve.

kal_fs_open and kal_fs_open_dir are access rather than enquiry, and shall report a name that does not exist as kal_err_not_found.

The distinction is the same one openkal.env draws between a variable that is absent and one whose value is empty, and it is drawn for the same reason: a caller that cannot tell them apart cannot act correctly upon either.

7.8 Stating the whole of an intent when opening

kal_fs_open takes a flags word. The two-flag form of version 0.3 remains specified and remains available, and an implementation ordinarily defines it in terms of the flags form.

The reason for the addition is that three of the conditions a C library must express cannot be reconstructed above the two-flag form without making the caller silently wrong, which clause 3.1 identifies as the boundary between a supply and a simulation.

Condition What reconstruction above the two-flag form produces
truncate Opening and then setting the length is two operations. A program that stops between them leaves the tail of a longer previous contents behind, and the file is neither the old one nor the new one.
exclusive Enquiring and then opening is not exclusion. Between the two, another context creates the name, and the caller that asked to be the creator is not.
append Seeking to the end and then writing is not appending. A second writer between the two produces an overwrite, and neither writer observes that it happened.

kal_fs_truncate and kal_fs_file_info are added for the same reason and are recorded here rather than in a list of conveniences. Setting the length of an open file is not expressible through any other operation. Enquiring about an open file is not expressible through kal_fs_info: the name a file was opened by may since have been removed or given to another file, so a C library answering the enquiry from the name would answer about a different file than the one the caller holds.

7.9 Termination

kal_exit shall terminate immediately. Registered exit handlers and static destructors shall not run. An implementation that runs them prevents a caller from reasoning about what executes after the call.

7.10 Thread-local storage in a started context

openkal.task reports, through kal_task_props, whether a context started by kal_task_start observes the thread-local storage of the toolchain that compiled the program.

It is reported rather than provided. The register convention that delivers thread-local storage belongs to openarch (clause 10), and an operation here that installed a thread pointer would place a processor's calling convention in an interface that is meant to be independent of it.

The property is reported because a C library ported onto openkal keeps its per-context state --- its error value, its locale, its cancellation state --- in one such variable, and therefore cannot be ported onto an implementation whose contexts lack it. Reporting the property allows the library to state that requirement. An implementation whose contexts are the host's threads has the property without doing anything; an implementation upon a scheduler of its own has it only if it establishes the convention, and one that does not shall report the position as zero rather than leave the question to be discovered.

7.11 The inverse of an enquiry

An interface that reports a property of a resource and offers no way to set it is incomplete wherever the property is one the environment records rather than derives.

kal_fs_file_info reports the time at which a file was last modified. kal_fs_set_modified sets it. The operation was added in version 0.5 because three ordinary programs — one that copies a file and preserves its dates, one that extracts an archive, and one whose whole purpose is to mark a file as current — could not be written above the interface without it, and each of them is a program a C library above openkal is expected to host. The absence was not visible in the specification text; it became visible when such programs were compiled above an implementation.

It takes the open file rather than the name, for the reason given at kal_fs_file_info: the name may since refer to something else, and setting the time of the wrong file is a worse outcome than not setting it.

The three environments openkal has been implemented on record the time to a nanosecond, to a microsecond and to a hundred nanoseconds respectively. The interface states the value in nanoseconds and does not require that it be returned unchanged; the conformance procedure compares whole seconds, which is the resolution every environment that records the time at all agrees upon. An interface that required more would be requiring of every environment what one of them happens to provide.

An implementation whose environment does not record the time at all does not claim KAL_FS_PROP_MODIFIED_TIME and reports kal_err_not_supported. An implementation that claims the position is required to perform the operation: clause 6.2 exists so that a claim is a claim about what can be done.

7.12 One reserved name

openkal.fs names things relative to a directory the program holds, and every operation that answers a question about a thing takes a name. A program holding a directory therefore had no way to ask a question about that directory: what it is, when it last changed, whether it can be written. The handle is not a name, and no operation took a handle alone.

The remedy is one reserved word rather than five more operations. "." denotes the directory itself, wherever a name is accepted. It does not introduce a way to ascend, so the confinement clause 7.1 depends upon is unaffected; and every environment openkal has been implemented on can express it, two of them because their own naming already reserves the same word and the third because its object manager expresses the same thing as an empty name beside the directory's handle.

".." remains invalid. The asymmetry is the point: the first names the thing the program already holds, and the second names something it does not.

7.13 What a started program receives

A program started by kal_process_spawn shall receive the three streams and the directories its caller granted, and no other handle. The requirement includes a handle the calling program itself received from its own environment: such a handle was not granted by the caller, and a caller that starts programs inside a confinement relies on nothing reaching them that it did not name.

Where the environment's mechanism for starting a program that needs an interpreter requires a handle to survive the start, an implementation may leave one. It shall name no more than the program being started, and shall be left only for a program that needs it.

A granted directory is received as a preopen: the started program enumerates, through kal_fs_preopen, exactly the directories granted, in the order given and under the names given, and a grant of none leaves it none. The first grant is the directory the started program regards as the one it was started in, as the first preopen is for every program. How an implementation conveys a grant is its own concern; a grant confines a program that uses only its preopens, and the confinement of one that does not is the environment's responsibility (clause 11, entry 6).

A granted directory may be an attenuated handle (kal_fs_dir_attenuate, version 0.23): the started program receives it with its rights and statements, and reads them in kal_fs_props of its preopen. A caller that sets KAL_SPAWN_CONFINE asks the environment to hold the started program to its grants and their rights even where it reaches files without this interface; an implementation that claims KAL_PROCESS_PROP_CONFINE does so before the program runs its first instruction, and one that does not refuses the flag and starts nothing (clause 11, entry 53).

The requirement was descriptive in version 0.13, in clause 11, entry 18, and two implementations departed from it without any observation reporting the departure. One conveyed every inheritable handle of the caller; another conveyed a handle for the directory a program was found in, which reached the whole file system from inside a confinement. Each implementation observes the first paragraph in its own tests, since positions and handle tables are not portable; the conformance suite observes the third.

7.14 Removing a name that is held open

kal_fs_remove of a file another handle holds open removes the name when the call returns, on a volume for which the implementation claims KAL_FS_PROP_REMOVE_OPEN. The open file remains usable through its handles until the last of them is closed; the directory that held the name is then empty and may be removed, and the name may be created again.

The behaviour is the one a C library's unlink states, and a program relies on it without naming it: a lock file held for the program's lifetime, a temporary file kept open after its name is removed, a tree removed while one of its files is still open. One environment defers the removal by default --- the name remains, refusing every open, until the last handle is closed --- and offers the immediate form on the volumes that record it. Measured above an implementation that used the deferred form: a C++ program's remove_all of a directory holding its own lock file failed with "directory not empty" on that environment alone, while the same program built for the environment removed it.

A volume that cannot remove a held name at once is answered before the call (clause 6.2): the position is not claimed, and a caller that needs the directory gone closes what it holds first.

7.15 The null device at a position

A position of kal_spawn_streams is inherited, or receives a stream the caller placed. KAL_SPAWN_NULL_IN, KAL_SPAWN_NULL_OUT and KAL_SPAWN_NULL_ERR give it the environment's null device: at that position the started program reads the end of input, and every write is accepted in full and discarded. An implementation claims the three with KAL_PROCESS_PROP_NULL_STREAMS; one that does not reports kal_err_not_supported and starts nothing. A flag beside a non-zero stream at the same position is kal_err_invalid. The device occupies its position and is not a further handle (clause 7.13), and an implementation that opens it for the start leaves the caller holding nothing of it.

The device is a resource of every environment that starts programs, and each names it in its own spelling: /dev/null on Linux and macOS, NUL on Windows. This specification names nothing outside the directories it supplies, so the device is reached by the position it is given at and not by a name; a C library maps its own spelling of the device onto the flags. A library cannot compose the device from what precedes it: a placed stream is a file's or a channel's end; a file keeps what is written, and a channel's far end either ends the started program's writes with an error once its reader is released, or keeps a reader in the caller for as long as the program runs.

7.16 A handle with less

kal_fs_dir_attenuate derives from a directory handle one that reaches the same directory with less: read-only (KAL_DIR_READ_ONLY), with the holder's statement that nothing beneath changes (KAL_DIR_UNCHANGING), or recording the names reached beneath it (KAL_DIR_RECORDED). A derived handle has the rights of the one it came from less those withdrawn, and the statements of both; nothing restores a right. A directory opened beneath it has its rights and statements, and so does a program it is granted to. The rights are held by the implementation on every operation through the handle; an implementation that answers from what it read before, under KAL_DIR_UNCHANGING, is answering a question the holder declared settled. The record is the holder's file, appended in whole entries by every program holding a handle on it, and ended by e for each program that ended normally (fs.h). Clause 11, entry 53.

7.17 A preference about cost

A program's resource preference --- balanced, performance (memory and storage spent for time) or economy (time spent for memory and storage) --- is stated by the program's caller (KAL_SPAWN_RESOURCES_*), inherited where nothing is stated, and read with kal_env_resources. No operation of this specification answers otherwise for it: each layer of the program, and the implementation, chooses within itself where it trades one resource for another, and the environment applies its own policy for memory where it has one. A balanced program sizes what it keeps by what kal_memory_info reports available. Clause 11, entry 57.

7.18 Observable process units

An implementation claiming KAL_PROCESS_PROP_JOB_OBSERVE shall provide a caller-owned unit before its external members start, an opaque observation reference, and the three states unknown, occupied and empty. kal_process_job_create adopts or establishes the caller's private unit; subsequent starts use the existing kal_spawn.job field. Observation shall validate the original native identity. A dead leader, a missing name or the loss of every native reference is not evidence that all members have ended.

kal_process_job_open obtains observation and close authority only. This is a caller precondition where the native handle is numeric; it does not convert a reusable numeric identifier into a kernel-enforced read-only capability.

Before kal_process_job_retire, the caller shall close admission, including concurrent starts. Retirement proves that no external member remains and, where necessary, leaves the original unit for the unit of the same live program identified by continuation. Its identifier and birth are verified before and after that transition. An inaccessible, dead, reused or unreachable continuation yields unknown. Retirement conveys no admission or termination authority over the continuation's unit and never reopens the original unit. The caller may remain alive to finish its own bookkeeping. Clause 11, entry 58.

7.19 A started program that runs to its end first

Some environments have one task: a firmware's boot services, a disk operating system, a boot chain. In each, a program that starts another is suspended until the started program has ended --- StartImage and the EXEC function return when the image they started returns --- and nothing in the environment can make the two run together. Such an environment can provide openkal.process, and the text of this interface did not exclude it; what it did not say is how a caller learns that the program it started has already ended.

An implementation claiming KAL_PROCESS_PROP_RUNS_TO_END returns from kal_process_spawn only after the started program has ended. Its status is then known: kal_process_wait answers it at once, the first time it is asked. Such an implementation does not claim KAL_PROCESS_PROP_TERMINATE, for there is no running program to request the end of, nor KAL_PROCESS_PROP_CHANNEL, for neither end of a channel could read what the other writes while it runs; a stream passed to the started program is written by it and read by its starter afterwards, and a pipe's capacity bounds what passes that way.

A caller that starts a program and then waits for something the program does while it runs --- a line on a channel, a file it creates --- reads this property first. A caller that only starts and waits needs nothing: the two operations mean the same under the property as without it. An implementation that does not claim the property starts a program that runs alongside its starter, as before. Clause 11, entry 61.

8. Evolution

Each interface is versioned independently. A revision may add declarations and shall not alter existing ones. Structure layouts are immutable as stated in clause 5.3.

A patch release (the third number) adds only what an implementation may decline: an optional property, an operation an implementation may refuse with kal_err_not_supported, or text that states what was already required. An implementation written against an earlier patch of the same minor version conforms to it, and the conformance suite's observations of the addition run only where an implementation claims what they observe. A requirement every implementation must meet advances the minor version. Version 0.25.1 is the first patch release under this rule (clause 11, entries 61 and 62).

A consumer declares a dependency upon the specification package, which is the package it imports, and a dependency upon an implementation, which is the package that supplies the definitions. The first fixes the version of the contract; the second is ordinarily conditional upon the target, so that changing implementation is a change to one line of the manifest and to no line of the source.

9. Conformance procedure

A conformance suite shall verify both halves of an implementation's claim, and the specification package shall verify the third.

  1. Behaviour. Every operation the implementation declares shall behave as this specification requires.

  2. Surface. The names an implementation exports beginning with kal_ shall be compared against SURFACE.txt. An implementation exports the names of the interfaces it provides and no other. This half is a static examination of the artefact and is therefore neither slow nor susceptible to incomplete coverage, and it is the half that detects an implementation which has extended the interface rather than implemented it.

  3. Declarations. The declarations, clause 4.2, shall be compared against SURFACE.txt by compiling a translation unit that names every entity the list contains. This half belongs to the specification package rather than to an implementation. It does not compare the two ways of reaching the declarations with each other, because there is one declaration and no comparison to make; what it detects is a name the list requires and the header does not declare. The translation unit is compiled without the environment's headers, because the consumer the header exists for is.

  4. Types. The declarations shall be examined for the types they are written with, and shall name none that clause 5.4 excludes. This half also belongs to the specification package. It is distinct from the second: an implementation may export exactly the names SURFACE.txt requires and still declare one of them taking a size_t, and the surface comparison would report conformance.

    The examination asks a compiler what it parsed rather than searching the text, so that a type reached through a macro or an include is seen, and the typedef names are read as written rather than resolved to the underlying type — a declaration written kal_u64 and one written uint64_t denote the same type on one target and different types on another, which is the whole subject of clause 5.4.

    A check that reports conformance by finding nothing reports it identically when it has read nothing. The procedure therefore establishes that the declarations were parsed before it is permitted to report success, and the specification package demonstrates that the check fails when an excluded type is present.

Behavioural conformance cannot be established exhaustively, and this specification does not claim otherwise. An implementation may export the correct names, satisfy every test, and fail on an untested input. The residue is the coverage of the suite, and no arrangement of declarations removes it.

10. Relationship to adjacent specifications

openkal is one of three boundaries and is distinguished from the other two by the number of implementations that coexist within one program.

Specification Implementations in one program Form of the contract
openarch one, being the processor concepts, with link-time optimisation required
openkal one per interface a C application binary interface
openhal many, being the devices concepts

openarch and openhal both express their contracts as concepts, and for opposite reasons. An openarch operation such as disabling interrupts is a single instruction, and the cost of a function call is accordingly prohibitive. An openhal contract must admit several implementations within one program, which a set of C functions cannot. openkal is subject to neither pressure: an openkal operation ordinarily crosses a protection boundary, so the cost of a call is immaterial, and one implementation per interface suffices.

A device supplied through openhal may serve as the implementation of an openkal interface. The relationship is one of connection rather than of layering, and the connection is made by the package that describes the board.

10.1 One implementation in one process

A process holds one implementation of openkal. The table above says one per interface; this states the unit it is counted in. An implementation keeps state that belongs to the process and not to a caller --- the word of kal_process_stop_requested, the table its owned handles index (clause 6.7), the treatment it installed for the environment's requests --- and two implementations in one process would each believe they own it. A handle issued by one is not a handle of the other (clause 7.2), so two parts of one program that each linked an implementation of their own cannot pass a file between them, and two treatments of one request to end leave it delivered to whichever was installed last.

A program linked as one artifact satisfies this by construction. A program composed of several --- a program and the libraries it loads, a loader and the images it starts (clause 12) --- satisfies it when the implementation is in one of them and the others reach it, which is the shared form of an implementation: an artifact the others load, whose exported names are exactly SURFACE.txt (clause 9's second half applies to it unchanged), and whose name carries the major version of the specification it implements, so that two artifacts written against incompatible versions do not bind to one. An implementation that offers a shared form states it in its package; it is not required to offer one, and a program that is one artifact needs none.

What a program cannot do is load a second implementation beside the first and use both. Nothing in this specification detects it, because nothing can: the two are each conforming, and the defect is in the composition. It is recorded here so that a composition is examined for it rather than discovering it as a handle that one half of a program refuses.

11. Matters this version does not settle

The following are recorded so that they are not mistaken for oversights.

  1. Concurrency. The behaviour of concurrent operations upon one handle is unspecified. The question becomes unavoidable when openkal.task is specified, and version 0.3 does not specify it.

  2. Ownership. Every handle in the core interfaces is borrowed. Owned handles arrive with openkal.fs, and the rules for their release, including the effect of releasing one twice, are deferred to that interface.

  3. Symbol versioning. The C application binary interface carries no version in its symbol names. Clause 8 protects the interface by prohibiting change rather than by permitting coexistence, and an ecosystem that outgrows that prohibition will require a mechanism this version does not define.

  4. Readiness. Awaiting one of several sources is not an operation of this specification. It is reached above the interface, from openkal.task and a bound upon each wait; clause 6.3 records the alternative that was weighed and the property of implementations that excluded it.

  5. Name resolution. openkal.net and openkal.datagram carry an address and a port. Turning a name into one is excluded by clause 3.4 and remains so: an implementation shall not be required to parse an unbounded set of name schemes.

  6. Permission and ownership of files. Not defined, and not a deferral. A permission presupposes an identity, and the environments this specification targets do not agree that one exists.

    And what a program should write instead, which was not recorded and is now. A program that means "only I may read this" is stating it in a vocabulary this environment does not have. It has three answers, and none of them is a mode:

    • against another part of the same program, the capability already does it: a handle not given cannot be reached;
    • against another user of the machine, it is the environment's responsibility — the party that starts the program supplies a preopen that others do not have;
    • against a location the program does not trust, it encrypts the contents.

    The measurement that settles the alternative: a mode word is not stored by several filesystems a hosted implementation will meet, and on such a volume chmod reports success and changes nothing, which is the outcome this specification exists to refuse. kal_node_info.writable — one boolean — is not a simplification of a mode word; it is the intersection of what those formats store.

    Whether a node may be started is not a permission, and version 0.13 defines it. KAL_INFO_EXECUTABLE and kal_fs_set_executable_at record one property of a node, and the property presupposes no principal: a volume that stores it stores it once for the node. It satisfies the criteria this entry applies to a mode word, which the mode word fails. It is answered per volume by kal_fs_props, so a volume that does not store it is asked before it is written to, and an operation upon one refuses rather than reporting success and changing nothing. What remains undefined is every distinction between classes of caller, which is the part that presupposes an identity.

  7. Creation and reading of links. Settled in 0.9. kal_fs_link_create and kal_fs_link_read are operations of openkal.fs, and they are operations of it rather than an interface of their own for exactly the reason this entry used to give: whether a volume has such nodes is a property of the format and not of the environment, so the variability is between the resources of one implementation. Clause 6.2 says such a property is answered by an enquiry taking the resource, and kal_fs_props is now that enquiry — which is what makes the operations admissible, because a caller can ask before it calls.

    Resolution is stated in fs.h beside each operation it governs, and no longer only here. It was here alone, and two call sites of one implementation consequently chose opposite directions: opening resolved a link while asking did not, so a program was told that a name referred to a link when opening it would have reached a file.

  8. Duplication of the calling image. fork is refused by clause 7.1 and that refusal stands. It is refused as an OPERATION. The atomic capabilities from which a library may compose it are specified: openkal.space clones an address space and starts a context in one, and KAL_SPACE_PROP_CLONE_HANDLES states whether the handles accompany the memory. What this specification declines to do is duplicate execution state, which a library above the interface performs with the compiler's own facilities. A sentence reading "openkal will not have fork" would have buried that distinction, and this entry exists so that it is not written.

  9. Transfer of a handle between address spaces. kal_space_start conveys no handle, and kal_process_channel conveys a stream only across a spawn. A general mechanism for passing a handle to a context in another space is not defined by this version. It is the question openkal.space reaches first and is not peculiar to it.

    What this costs, named in 0.12 because an implementation paid it. A unit is established by whoever starts a program, at the moment of starting it: kal_spawn.job is where a caller says which unit a started program belongs to, and the implementation performs the placement. It follows that a program cannot name a unit that a copy of itself went on to form — the copy's handle is the copy's, constructed by clause 6.7 from an index into the copy's own table, and nothing above conveys it back. So the sequence a C library reaches for when it composes job control — duplicate the image, have the copy form a unit of its own, then signal that unit from the original — has no closure here, and this is the reason rather than an omission.

    The failure this produces is not a refusal, which is why it is recorded. An implementation that meets the dead end is invited to reach for the nearest unit it can name, which is its own; openkal-musl 0.12.0 did exactly that, and every negative identifier that matched no child named the caller's own unit. A program asking after a unit that did not exist was told it did, and one signalling it ended itself together with everything it led. The composition that does work is the one this entry began with: start the program with kal_spawn.job, where the unit and the caller that knows its name are on the same side of the boundary.

  10. Exclusion upon a range of a file. Settled in 0.10. kal_fs_lock and kal_fs_unlock are operations of openkal.fs, admitted on exactly the grounds entry 7 records for links: whether a volume can exclude is a property of the format rather than of the environment, so kal_fs_props answers it and a caller asks before it calls.

    And this one is the opposite of entry 6, which is why the two are next to each other. Permission was declined because the environments do not agree that an identity exists. Every environment this specification targets locks a byte range, and spells it almost identically. What was missing was a word, not a capability.

    What its absence cost, and it was not a refusal. A C library above this interface answers fcntl(F_SETLK). With nothing here to answer it with, one returned success and took no lock — two programs held one exclusive lock and neither could find out. Measured against a host. This entry exists so that "a specification with no operation for X" is not mistaken for "consumers of it will simply not do X".

    Release upon the holder's end is required rather than observed, and that requirement is the whole reason the operation cannot be composed above the line: a caller can build exclusion out of KAL_OPEN_EXCLUSIVE and a name, and nothing then releases that name when its holder dies.

  11. A started program that does not outlive its caller. Settled in 0.10 as kal_process_spawn_bound, and respelled in 0.11 as KAL_SPAWN_BOUND_LIFETIME — see entry 14.

    Clause 7.1 declines to replace a running image, and that stands. The consequence, which this entry did not previously record, is that a C library asked for execve composes it — and the composition leaves three images where a system with the operation has two: the caller, a copy that waits, and the program.

    A signal reaches the middle one. kal_process_terminate upon the identifier the caller holds terminates the waiter; measured with a host as control, the caller is told the program died on the signal it sent while the program runs to completion, unsupervised. The termination operation was not at fault — it was asked to terminate one started program and did. What was missing was a way to say the thing execve means.

  12. How many contexts run at once. Settled in 0.10. kal_task_parallelism. KAL_TASK_PROP_PARALLEL says whether and not how many, and a C library above had nowhere else to look: hardware_concurrency answered 1 with no error, so a program sizing a pool of workers got one worker and no way to know. Zero means "cannot say" and is distinct from one, because an environment with one processor and an environment that will not answer call for different behaviour.

  13. The time of a name, and the size of a volume. Settled in 0.10. kal_fs_set_modified_at and kal_fs_capacity. The first exists because kal_fs_set_modified is stated on an open file while a directory is opened as a kal_dir — so this interface had no route to a directory's time at all, and a consumer that stamps a lock directory drove one implementation to open the directory for reading and set the time on that, outside anything stated here. A divergence caused by a missing declaration is a defect of the specification and is recorded as one.

    The second, kal_fs_capacity, exists for the reason that keeps it in this interface rather than in openkal.space: how much room a volume has is a property of the names a caller can already reach, not of the memory a program runs in. A C library above answers statvfs, and with nothing here to answer it with it reported a fixed number — which is worse than refusing, because a program that checks for room before writing was told there was room.

    It answers bytes and not blocks, and that is the whole of the design decision. Every environment this specification targets states a block count and a block size, in units of its own choosing, and every one of them differs; a caller wanting bytes multiplies two numbers whose meaning it must first look up. Bytes are what the caller is deciding about, so bytes are what this returns and the multiplication happens once, in the implementation, where the units are known.

  14. Starting a program was becoming a family, and 0.11 stopped it. This is the first entry that records a REMOVAL, and it is recorded here for the same reason the additions are: so that the next reader meets the reasoning in the specification rather than in a diff.

    0.10 ended with three declarations — kal_process_spawn, kal_process_spawn_with and kal_process_spawn_bound — which were never three operations. They were one operation and three modifiers of it, spelled apart for exactly one reason: clause 8 forbids adding an argument to a declaration that exists, so each modifier had to arrive as a new name.

    The 0.10 text on ..._bound predicted where that ends, in terms: "Declaring every combination is how an interface acquires four spawns and then eight." Two more modifiers then arrived together — the directory a program runs in, and terminating what a started program itself started — and the evidence that they arrive together was a single consumer signature whose child calls setpgid(0, 0) and chdir(cwd) on adjacent lines. Four orthogonal modifiers is sixteen declarations, each written in every implementation.

    ⇒ So the modifiers moved into struct kal_spawn and the family collapsed to one declaration. ..._with and ..._bound were removed rather than kept beside it. openkal has no users outside this ecosystem, so there was no one to keep a second spelling for; an interface that offers two ways to say one thing must explain the difference for ever, and this one had no difference to explain. A caller written against the old shape fails to compile — different arity, different types — which is the loud failure and not the quiet one.

    What this costs, stated rather than glossed. struct kal_spawn is frozen by clause 5.3, so a future modifier that carries a parameter rather than a flag cannot be added to it and will need its own declaration after all. This entry does not claim to have solved that; it claims that one general form plus a flag word is a better place to meet the problem than sixteen names, and that the modifiers wanted so far are all flags.

    And clause 8 was set aside to do it, deliberately and once. Clause 8 exists to protect written code, and 0.11 is the last version at which there is none to protect. An equivalent change after this specification has consumers is not permitted by clause 8 and this entry is not a precedent for one.

  15. KAL_SPAWN_OWN_JOB has a shape borrowed from one environment, and clause 7.1 is what says so. Open, and recorded before it is settled — because the flag ships in 0.11 with one implementation refusing it, and a reader is owed the reason.

    The flag asks that a started program and its descendants form one unit, and kal_process_terminate end the unit. Two implementations satisfy it with no stored state: a program that called setpgid(0, 0) has a group identifier equal to its own, so getpgid(pid) == pid recovers the association from the kernel.

    openkal-windows cannot recover it. It can form the unit — a job object is exactly this — but there is no call there that answers "which job is this process in" with a handle, and kal_process is one word already holding the process. Satisfying the flag would require a registry keyed by process handle.

    Clause 7.1 states the consequence mechanically: an implementation that must maintain a translation table, a registry, or a name resolver in order to satisfy this specification indicates that the specification has taken a shape borrowed from one environment, and the shape is at fault rather than the implementation. So this entry is a defect of the flag, not of openkal-windows, and openkal-windows refusing is the correct behaviour under clause 6.2 meanwhile.

    ⇒ The natural shape makes the unit a resource the caller holds, created before its members and terminated as a unit, so that nothing has to be recovered on either kind of system. It is not settled here because the two kinds of system create such a unit differently — Windows makes it externally and assigns members, while a process group is made by a member from inside — and a shape that is natural to one and not the other is the same defect again, spelled the other way round.

  16. A node that may be started, and a start that could not happen. Settled in 0.13. Two consumers of openkal-musl met the same gap from its two sides. A packaging tool that unpacks a program could not leave the result startable, and a C library could not report why a start failed: ENOEXEC arrived above the interface as kal_err_io. The first is answered by KAL_INFO_EXECUTABLE, KAL_FS_PROP_EXECUTABLE and kal_fs_set_executable_at (entry 6 states why this is not a permission); the second by kal_err_not_program (clause 5.2 states the criterion it meets).

    The same examination found that one implementation reported kal_ok and a handle for a start that did not happen, including for a name that does not exist. kal_process_spawn now states that a start that did not happen is reported, and the conformance suite observes it with a name that does not exist, which requires no file to be created.

  17. Two directions of one resource. Settled in 0.13; see clause 6.6. The conformance suite observes it with two contexts and a bound, so that an implementation that serialises the directions is reported rather than left waiting.

  18. A stream at an arbitrary position of a started program. Considered in 0.13 and not defined. A started program receives three streams, and a descriptor at any other position is not conveyed; version 0.14.1 states this as a requirement in clause 7.13. A position is the shape of one environment's descriptor table (clause 7.1): another environment conveys handles to a started program as a list of values and has no positions, and a started program would still need to be told where to look. Were the need measured, the shape consistent with this specification is the one kal_preopen has, a named grant rather than a numbered position. No consumer is silently wrong today: openkal-musl refuses the placement it cannot express.

  19. A stream handle of zero. Recorded at kal_spawn_streams in 0.12 and not changed in 0.13. Zero denotes inheritance there and is a valid stream on an implementation whose streams are descriptors. The specification permits an implementation to remove the ambiguity by never answering a stream enquiry with zero and does not require it; a caller that cannot tolerate the ambiguity reports the request as unsupported, which is what openkal-musl does.

  20. A keystroke the environment keeps for itself. Settled in 0.14. KAL_TERM_PASS_CONTROL states whether the environment reserves any keystroke for an action of its own, and a program that reads what is typed asks for the position and reads the mode back. Admitted on the grounds entry 10 records for locking: every environment this specification has been implemented on reserves such keystrokes and spells the switch almost identically — ISIG and the flags beside it, ENABLE_PROCESSED_INPUT — so what was missing was a word and not a capability. The admission test of clause 7.11 is met by three ordinary programs that cannot be written above the interface without it: an editor, a pager, and a prompt with line editing of its own.

    What its absence cost, and it was not a refusal. A C environment above this interface answers tcsetattr with the mode word. With no position for the reserved keystrokes, a program that cleared line assembly and the echo was still ended by the interrupt key, and no operation had misbehaved: the implementation preserved what the specification had not named. Measured against a host C library upon one terminal, and reported by the consumer who met it.

    The position is spelled in the sense clause 6.2 now states, so an implementation released before it reads as zero and thereby says something true about itself. The arrangement in which zero would have been the guarantee was considered and not adopted: it required an exception to clause 6.2 and a property position beside the mode position, to repair a hazard that the spelling itself removes.

    What one position cannot record. An environment may reserve several classes of keystroke through separate mechanisms of its own, and a terminal upon which some of them had been released is restored to the set the environment ordinarily reserves. A position for each class was weighed and declined: one of the three environments governs the classes with a single switch, so the finer interface would have required of it a distinction it does not have (clause 6.4).

  21. The stack a running context stands on. Settled in 0.15. kal_task_stack reports the region the calling context's stack occupies. It was asked for by the C library this specification is implemented beneath: pthread_getattr_np had no honest answer above an implementation of openkal.task, and the port replaced musl's two wrong answers with a refusal (mcpplibs/openkal-musl#49). A refusal is correct and is still not an answer, and the callers that want one are ordinary rather than exotic — a run-time that keeps its own stacks off the native stack's end, a collector that scans the region it stands on, an interpreter that checks for room before it recurses. Whether the need is measured here is not what admits the operation; clause 6.4 is, and it is answered below.

    It asks about the caller and takes no handle, and clause 7.2 is why. A handle is meaningful in the context of the party that obtained it, and kal_task_current reports an identity rather than a handle — so a context is the one resource its own code always stands on and can never hold. An enquiry taking a context would have to answer for a context the implementation does not control, which is the registry clause 7.1 refuses; addressed this way, the implementation answers about the resource it is running on and records nothing about any other.

    Every resource answers, which is what clause 6.4 requires. A context that is running can find its own stack in every environment openkal.task is provided for, and the three mainstream systems are the evidence: one answers a limit it grows the mapping against, one answers the region it allocated for the thread, and one answers the bounds it built the thread's stack from. The context a program was started on is the easiest case rather than the hardest, because the program's first code is already running on it. An environment that genuinely cannot answer does not provide openkal.task, which clause 3 already expresses as an absence at the link: a machine with firmware and no operating system provides none of that interface and is unaffected, and an implementation that provides the interface only when it can answer provides it whole, as openkal-emscripten already does by defining the interface under one feature and nothing without it.

    The size is the region the implementation vouches for, and where an environment separates a reservation from a commitment it is the reservation. A caller places a guard below the region or measures the room above it, and a bound that moved as the stack grew would answer the second question wrongly and the first one not at all. The consequence is stated where it can be met: an implementation that reports the reservation and an implementation that reports only what is committed both conform, and a caller is told which region it has rather than how large the environment would have let it become.

    What this costs. A context can be asked only about itself, so the C library above answers pthread_getattr_np for the calling thread and refuses every other thread rather than describing one it cannot see. That is a smaller surface than the name suggests, it is uniform across the contexts an implementation did not create, and it is recorded in that port's own table of what it does not supply. The alternative — an answer composed from a record the implementation kept about contexts it does not control — is the shape clause 7.1 excludes, and the defect this entry follows was a wrong range rather than a missing one.

  22. A least size for a started context's stack. Settled in 0.16. kal_task_start_sized starts a context whose stack is at least the size asked for. Version 0.15 said the size was "a property rather than a parameter" and offered no enquiry that reported it, so it was neither: a C library above accepted pthread_attr_setstacksize, ran every thread on the implementation's own stack whatever was asked, and could not say how large that was (mcpplibs/openkal-musl#48). The reason given — that an environment which does not allocate stacks separately cannot honour a request — named no environment, and every environment openkal.task is implemented on takes a size natively: two of them as an argument of the call that starts a thread, and the third because the implementation maps the region itself.

    A least size, and a request beyond what the environment can give is kal_err_no_memory. Clause 3.1 is why this is a supply: a stack is memory, and an environment that bounds its size bounds what is available, which is the outcome kal_alloc reports when it is exhausted. A position in kal_task_props saying whether requests are honoured was considered and not adopted: it would have admitted an operation that a provided interface answers with a refusal, which clause 6.1 treats as a defect, and there is no environment it would have described. An exact size was considered and not adopted, because the C library above asks for its own default on every thread and that default is smaller than what the implementations beneath it provide.

    There is no form taking a region, for the reason the declaration gives, and the C library above refuses a thread whose attributes name a region rather than run it somewhere else and report success.

  23. Whether input is altered on its way. Settled in 0.16. KAL_TERM_PASS_INPUT states that what is typed reaches the program as the keystrokes produced it. Without it the return key reached a program in raw mode as a line feed in two of the three environments, the same byte another key produces, and in the third Ctrl+J produced no byte at all; a program that inserts a line break on one key and submits on the other could not be written (mcpplibs/openkal#48, measured against a host C library on one terminal, and on the third environment's console with the keys written into it as key events). With the position set, all three deliver the return key as 0x0d and Ctrl+J as 0x0a.

    It is a position of its own and not a widening of KAL_TERM_PASS_CONTROL. That position was assigned in 0.14 and retains its meaning (clause 6.2): implementations released before this one report it set whenever no keystroke is reserved, and a reading that included the alteration of input would have had each of them claim something about it that it had never established. The two questions are also independent — whether a keystroke is kept, and whether one that is delivered is rewritten — and a program performing line editing of its own wants the second without the first.

    It is not entry 20's declined finer position. That entry declined a position for each class of reserved keystroke, because one environment governs the classes with a single switch. This position is a different dimension, and it is spelled at the same granularity entry 20 chose: one position covers every mechanism by which the environment alters input.

    An environment whose line assembly alters input whatever it is asked reports the position clear while it assembles lines, and a request leaves it so; the program asking is told by kal_terminal_get_mode. The alterations an environment applies when the position is clear are its own and differ between the three, so the C library above, which knows what its caller asked for, asks for unaltered input in raw mode and performs the rewriting its caller asked for itself. That is how a caller of the C library reads the same bytes on every environment.

  24. What closing a directory does to its enumerations. Stated in 0.16. openkal.fs said from its first version that closing a directory releases the iterators it produced, and no observation reached the case. One implementation released them only at the end, so a C library above that stopped reading a directory early kept a descriptor for every directory it closed, until a program that polled a directory ran out of them (mcpplibs/openkal-linux#34). The declaration now states the consequence for a caller as well: the directory is a released handle, and kal_fs_list_next given it refuses rather than using an iterator the close has released. Each implementation observes the release in its own tests, because a count of open handles is not portable — clause 7.13 observes its first paragraph the same way, for the same reason.

  25. A rename that does not replace. Settled in 0.17. kal_fs_rename_flags with KAL_RENAME_NO_REPLACE, answered per volume by KAL_FS_PROP_RENAME_NO_REPLACE. Three consumers above this interface publish a file under a name only if nobody else has --- a package store, a build cache several programs fill, an installer --- and each composed it from an enquiry and a rename, which loses the name to a second publisher between the two. The composition through KAL_OPEN_EXCLUSIVE does not help: it creates the name and the rename then replaces what was created. Every environment this specification targets performs the operation natively where the volume can, so it is a word that was missing (entry 10's criterion), and a volume that cannot is answered before the call (clause 6.2). A second declaration rather than a flags argument added to kal_fs_rename, because clause 8 forbids altering the first.

  26. Ending as the request to end would have. Settled in 0.17. kal_process_end_as_requested. A program that takes the request over with kal_process_stop_requested and finishes its work had no way to end as the request would have ended it: kal_exit with any status reports a program that chose to end, a shell running a loop then starts the next program, and a starter that waits cannot tell a cancelled build from a failed one. The report it wanted is the environment's own report of an end the environment caused, which nothing above this interface can produce. Measured by the consumer that met it: a build tool's cancelled run was reported to its shell as an exit status, and the shell's loop continued.

    The take-over is stated with it, in process.h: from the first call of kal_process_stop_requested, a request reaches the program only through the word, and a request its starter arranged not to deliver stays undelivered. Two implementations restored the delivery of a request their starter had declined, which ended background programs that were meant to survive it.

  27. A rendezvous at a name. Settled in 0.17. kal_net_listen_at and kal_net_connect_at, answered per directory by KAL_FS_PROP_LOCAL_ENDPOINT. Two programs on one machine --- an editor and the server it starts, two instances of one program deciding which is the first --- met at an address on the loopback, which every program on the machine can reach, so each protocol carried its own access control. A name in a directory is reached by a program that holds the directory, which is the confinement of clause 7.13 applied to a connection. Every environment this specification targets binds such a connection to a name natively. It is admitted as two operations of openkal.net rather than an interface of its own because the resources are the ones the interface already has --- a listener and a connection --- and only where they meet is new. Passing a handle across such a connection is not admitted, for the reason entry 9 gives.

  28. Publishing part of a region. Settled in 0.17. kal_exec_publish_part and kal_exec_granularity. exec.h declined a partial form on the ground that it would report the environment's granularity through the shape of the interface, and said in the same header that a loader belongs above openkal and needs only this interface. The two statements could not both hold: an image's instructions and data lie at fixed distances in one region, so a loader must make the first executable and leave the second writable. Measured by writing the loader (a C library's dlopen above three implementations). The granularity is reported by its own operation because it is not kal_memory_granularity: one environment reserves in sixty-four kibibytes and protects in four.

  29. What is known of a running program. Settled in 0.17. kal_process_self, kal_process_open_id and kal_process_info, with kal_program_info in the shape of kal_node_info. A language server is told its client's identifier and must notice when the client is gone; a lease names the program that holds it; a bound on the processor time a program may use is checked against what it has used. None could be answered above this interface, and the consumer that needed all three kept seven declarations of one environment's functions and read another's process table by name. The identifier is opaque and is given again, so it is reported with the moment the program started, and the pair is what names one program. A program the caller did not start can be asked about and not waited for or ended: the second would end a program by a number that may since name another.

  30. The image a program was started from, and the system it runs upon. Settled in 0.17. kal_env_image and kal_env_system, and kal_env_loader for an image (clause 12): what starts the same program again is a loader and an image there, and the loader is named nowhere else. The first is a fact every environment that starts named images tells the program and that the program cannot recover otherwise; argument zero is the starter's choice (clause 7.6). The second exists for clause 12: a program built once and run upon several systems must still follow each system's conventions --- where a user's configuration is kept, the suffix of a program, the separator of a list of directories --- and those are the system's decisions rather than resources of this interface. It is restricted to conventions in its declaration, because a name of a class of environment used to decide what an implementation can do is the error clause 3.3 records; capability words answer that, about the implementation in front of the program.

  31. Whether written control sequences are obeyed. Settled in 0.17. KAL_TERM_SEQUENCES, a mode position of the stream a program writes. A program that colours its output could not tell an environment that obeys ECMA-48 from one that prints it, and one implementation wrote bytes to its console that the console then displayed. ECMA-48 is a standard of its own and not the shape of an environment; one environment of the three obeys it only once asked, and the position is how a program asks.

  32. The direction of a channel, and the form of an environment entry. Stated in 0.17. kal_process_channel named no direction and both implementations made mine the reading end; an environment entry with no = was refused by one implementation and passed on by another. Both are now stated in process.h, and the second is refused by all.

  33. One implementation in one process. Stated in 0.17, clause 10.1, with the shared form that lets a program of several artifacts satisfy it. It was assumed rather than stated, and became a question when a loader above this interface began starting images that themselves call kal_* (clause 12).

  34. Images. Specified in 0.17, clause 12. Until this version a program above openkal was portable in its source: the same source, built once per environment. The clause that follows makes it portable in its bytes for one architecture, and states why that needed no operation that did not already exist except the three of entries 28 and 30.

  35. A directory supplied after inception. Stated in 0.17, at kal_fs_preopen. The set of supplied directories was fixed when the program started, and on the environment whose volumes are separate roots a volume mounted later could not be reached by any name. An environment may now append such a directory; a position once given keeps its directory and its name, so nothing a program holds changes meaning. The same entry records the root of a network's names, which that environment now supplies as a directory of its own (//).

  36. An image's entry, and an import it can do without. Stated in 0.17, clause 12.2 and 12.4. An image's entry takes nothing and returns its status, and the loader ends with that status after the finalisers: the arguments are kal_env_arg's, as in every other arrangement. A weak import is bound when it is provided and is zero otherwise, and it does not enter the requirements note --- so a C library can keep a table of operations it uses when present (a dlopen beside the image, an interface a later version adds) without making every image refuse to load where they are absent.

  37. Removing a name that is held open. Stated in 0.17, clause 7.14, answered per volume by KAL_FS_PROP_REMOVE_OPEN. The specification said what a removal removes and not when; two environments removed the name at once and the third when the last handle closed, and a tree holding an open file was removable on two of them. The immediate form is native on every volume that records it, so it is a statement of what was already the common behaviour, and a volume without it is answered before the call.

  38. Ending a read that waits. Stated in 0.17, at kal_net_shutdown. Ending the read direction of a connection ends a read waiting in it, with the end of input. Two environments did so and the third left the read pending, so a program that ended a connection from another context waited for its reader there alone; the release of the connection is not the remedy, since a handle in use is not to be released (clause 7.2).

  39. A command interpreter's command line. Stated in 0.17.1, clause 7.6, with KAL_SPAWN_ONE_STRING. A program that ran a person's command through the command interpreter of an environment that passes one string had no way to give it the command: the quoting that recovers a vector put escapes the interpreter does not read into every command that held a quotation mark, which a path with a space in it does.

  40. A part of a file in the address space. Specified in 0.18, as the optional interface openkal.map (map.h). Three consumers needed it and none could compose it: a compiler run as an image read every file it opened into memory because its C library's mmap of a file answered ENOSYS, and kept a private copy of pages a native program shares; the reference loader read each image whole into a buffer it never released; and two programs that share a region of data (a message bus, a model's weights) had no means at all, since reading is copying. Every environment this specification targets maps a file in one call, so it meets the admission criterion of version 0.8. It is an interface of its own rather than operations of openkal.fs because an environment can keep files and be unable to map them (clause 6.2); the region is an address and a length, as an allocation is, because a handle would oblige an implementation to keep a table (clause 7.1); an offset is a multiple of a constant rather than of an enquired granularity, because a file is laid out for mapping by a program that does not know where it will be mapped (clause 6.5's second paragraph); and no region of a file is executable, because instructions run only from memory openkal.exec published. A file shortened beneath a shared region refuses the shortening, or faults or reads zero beyond its new end, whichever the environment does, and no access reads the bytes that were removed; a private region may go on showing what it showed, as for any change to its file.

  41. Physical cores, and a context's claim on the processors. Specified in 0.19, as kal_task_cores and kal_task_set_priority in openkal.task (task.h). A compiler run as an ok image prepared one module at a time where the same machine's own program prepared two: it sizes heavy work by physical cores, its C library had nowhere to ask, and the answer "unknown" became one. And its background indexing competed with what its user waited for, which on its own system it marks as background work. Every environment this specification targets states its cores and lowers a thread's claim in one call each, so both meet the admission criterion of version 0.8. They are operations of openkal.task because they are about the contexts that interface starts, as kal_task_parallelism is (entry 12). Zero cores is "cannot say", as zero contexts is. Three claims rather than a number, because a number would oblige an implementation to map a scale onto environments that have none (clause 7.1), and three are what Linux's scheduling policies, macOS's quality-of-service classes and Windows' background mode can all say. A context that lowered its claim may be refused the raising of it, which is kal_err_permission; an environment without the operation does not claim KAL_TASK_PROP_PRIORITY.

  42. A transfer at an offset. Specified in 0.20, as kal_fs_read_at and kal_fs_write_at in openkal.fs (fs.h). A C library above this interface composed pread from a seek, a read and a seek back --- three calls where one suffices, under a lock of its own so that a second context could not move the position in between, and still wrong for a sequential transfer the same file was making. Measured on a compiler run as an image: 66 thousand reads and 71 thousand seeks where the same program built for its environment made 648 thousand positional reads and no seeks, and the lock's waits were a further 73 thousand calls. Every environment this specification targets transfers at an offset in one call, so both meet the admission criterion of version 0.8. One of them moves a handle's position when a transfer on it is given an offset; an implementation there transfers at the offset through a second handle to the same file, opened when it is first needed, whose position nothing observes --- so the position a program and the programs it gave the file to share is the environment's own, as it is elsewhere (clause 1.1). Neither operation uses or changes the position; where a write at an offset lands on a file opened for appending is the environment's, and is named rather than reconciled, because reconciling it would oblige every implementation to remember how each file was opened (clause 7.1).

  43. What a unit uses, and a limit on it. Specified in 0.20, as kal_process_job_usage, kal_process_job_limit and the memory of kal_program_info in openkal.process (process.h). A program that starts workers in a unit (entry 15) bounds what they may use together, so that a burst of work does not take the machine: a language server on one environment placed its workers in a control group with a memory ceiling and on the others ran them unbounded or not at all. The three environments state a program's memory in one call each, and hold a set of programs to a ceiling in two ways: one family of systems lets a program create such a group where it has been delegated the right, the other places a ceiling on a job object it already uses for the unit, and the third has no such group. So the specification states the limit and names the two ways it can be held: HARD, where the environment holds it, and SUPERVISED, where the implementation observes the unit at an interval it states and ends it on seeing the excess. Both end the WHOLE unit, because a unit ends together; an environment whose own ceiling ends one program or fails an allocation is brought to that outcome by its implementation. Which memory is counted is the environment's (resident, footprint, committed), stated by the implementation, because a common definition would have to be computed from something no environment reports (clause 7.1); what is common is the unit, a byte, a measure that does not change between calls, and, for one program, a peak that never falls. A unit's usage counts every program in it, which every environment records (a control group, a process group, a job object), and states no peak, because only one environment records a unit's peak and reporting one elsewhere would oblige an implementation to remember every unit (clause 7.1). kal_process_wait reports a program a limit ended with terminated set to KAL_TERMINATED_LIMIT, which a caller that tested for non-zero already treats as an end it did not choose.

  44. A range of the address space reserved and backed as it is used. Specified in 0.20, as the optional interface openkal.region (region.h). A C library above this interface built its allocator on kal_alloc, which obtains memory usable at once and returns it whole: every group of small allocations freed became a release, and its next use a fresh request whose pages were faulted in and zeroed again --- 792 thousand faults where the same program built for its environment took 55 thousand. What an allocator of a general kind needs is the other half of an address space: a large aligned reservation, backing a part of it as it is used, returning the backing while keeping the addresses, and releasing the whole. Every environment with a page table does each in one call, so the five operations meet the admission criterion of version 0.8. It is an interface of its own rather than operations of openkal.memory, whose place in the core set (clause 3.2) would require them of an environment without a page table, which could only refuse them (clause 6.1). Two returns are distinguished because environments distinguish them and an allocator uses both: after kal_region_decommit a part is backed again only by kal_region_commit and then reads as zero; after kal_region_discard it stays usable and its contents are unspecified, which lets an environment take the memory back lazily. KAL_REGION_PROP_OVERCOMMIT states the environments in which a reservation is usable at once and backing is not charged apart from it. No protection is stated, as none is anywhere in this specification. A C library builds its anonymous mmap, munmap and madvise upon it, which is the test that the operations are general rather than one allocator's.

  45. What a program states to a measurement, and the name of an address. Specified in 0.20, as the optional interfaces openkal.prof (prof.h) and openkal.debug (debug.h). A program run as an image could be measured only as an anonymous region: a profiler of its environment saw addresses it could not name, and nothing told it what the program's layers were doing --- how often the allocator went to the environment, how long a context waited for a lock. A loader holds what is missing (it binds every call across this interface, and it read the image's symbol table), so the two interfaces are a loader's to provide (clause 12.3). openkal.prof is designed to cost nothing unmeasured: kal_prof_active is asked once, a counter is the program's own cell read by the measurement when it records, and a phase is a pair of marks. openkal.debug names an address from the symbol table; it does not walk frames, because a program unwinds itself from tables it already holds (clause 12.2), and the name is returned as the table states it. Neither carries anything of the environment (clause 1.1). A cycle counter was considered for openkal.prof and not adopted: it would be a second time base beside kal_time_monotonic, which a measurement already has.

  46. Instructions from a file. Specified in 0.21, as kal_exec_map_part, KAL_EXEC_MAP_EXECUTE and KAL_EXEC_PROP_FILE in openkal.exec (exec.h). A loader copied every segment of an image into the region it reserved, so each start of a program wrote and made resident the whole of its instructions and read-only data: measured for a language server, 54 MB and about 17 ms before its first instruction, paid again by each of the 176 programs a build of its modules starts, where a program of the environment maps its file and pays for the pages it touches. An image's instructions and read-only data are the file's bytes unchanged (clause 12.2 allows no text relocation). It is an operation of openkal.exec and not a region of openkal.map that may be executed, because instructions run only from memory openkal.exec provides (entry 40): it is a second way to obtain that memory, and a part made a file's is never writable, so no part of a region is both. It is a part of a reserved region and not a region of its own because an image's segments lie at fixed distances (entry 28). It is a position and not required, because one environment makes a part of a reservation a file's view only where the reservation was made of placeholders, which would make every region a table of pieces (clause 7.1). With it, the processor time of an ended program is stated to remain while its handle is held (kal_program_info), as its peak memory does since 0.20: every environment keeps it in the record of the ended program, and a C library's wait4 reports the two together.

  47. The null device at a position. Specified in 0.22, clause 7.15, as KAL_SPAWN_NULL_IN, KAL_SPAWN_NULL_OUT, KAL_SPAWN_NULL_ERR and KAL_PROCESS_PROP_NULL_STREAMS in openkal.process (process.h). A language server run as an image started each worker that builds a module with its standard input and output sent to /dev/null, as LLVM's ExecuteNoWait sends an empty redirection; on the one environment whose preopens are volumes and not a root, the C library found no such name, and every start failed with ENOENT. The device is the environment's and not the library's: a started program needs a handle of the environment at the position, which a library could otherwise supply only as a channel drained by the caller for as long as the program runs. It is a flag of the start and not a stream a program opens, because a stream would need a release of its own beside kal_process_channel_close, and a program that reads or writes the device itself is served by its C library without the environment. It is a position and not required, because an environment that starts no programs has nothing to give it to.

  48. An image placed at its base. Specified in 0.23, as kal_exec_alloc_at and KAL_EXEC_PROP_PLACE in openkal.exec (exec.h), with the placement note (clause 12.4, version 1.1) and its use by a loader (clause 12.3). An image is position-independent (12.2), so a loader wrote, on every start, each address its relative relocations compute: measured for a language server's image, 164 000 relocations over two mebibytes of read-only-after-relocation data, made resident in each of the 176 programs a build of its modules starts --- about 900 page faults a program more than the same server built for its environment at one address, which on a virtual machine of a continuous-integration service cost a fifth of the build's processor time. An image computed once for one base and placed there is the file's pages, as such a program is. The reservation at an address is an operation and not a hint to kal_exec_alloc, because a loader that is refused must know it before it writes anything; it is a position, because an environment may decide where every program's memory lies; and the address is the image's, chosen when it is sealed, not the environment's.

  49. A name with every link followed. Specified in 0.23, as kal_fs_resolved_name and KAL_FS_PROP_RESOLVED_NAME in openkal.fs (fs.h). A C library's realpath asked about each component of a name in turn whether it is a link: measured in a language server's session run as an image, half a million enquiries, where the program of its environment asked the environment once for each file it opened (/proc/self/fd on one system, F_GETPATH on another). Every environment that keeps links answers the question for a name it has resolved, and nothing above it can compose the enquiries into one. The name is absolute and in the environment's spelling, as kal_env_image states one: a C library maps it onto its own names as it maps that one. It is a position of the volume, as the other operations of names are.

  50. A region is zero when it is returned. Stated in 0.23 of kal_exec_alloc and kal_exec_alloc_at (exec.h); every implementation gave fresh pages before. A loader that wrote the part of a segment beyond its file's bytes made every page of it resident before the program touched one; measured for a language server's worker, 780 KiB of zeros written on every start.

  51. A part sealed read-only. Specified in 0.23, as kal_exec_seal_part, KAL_EXEC_SEAL_FOREVER and KAL_EXEC_PROP_SEAL_FOREVER in openkal.exec. A loader relocated an image's read-only-after-relocation data and left it writable: its global offset table and its tables of virtual functions, the words an attacker who can write one word overwrites, where a program of the environment has them made read-only by its loader. Sealing for ever is a position, because the environment decides whether a protection can be made permanent.

  52. A part read in at once, a part written privately, and branch targets enforced. Specified in 0.23, as KAL_EXEC_MAP_PREFETCH, KAL_EXEC_MAP_WRITE, KAL_EXEC_MAP_BTI and KAL_EXEC_PROP_BTI. The first gathers the page faults a program takes on its first touch of each page of a part into one request: where a fault is dear --- a virtual machine, measured at 24 microseconds each --- the pages an image states it touches when it starts (clause 12.4, type 4) are read in with the part. The second makes a part the file's bytes until the program writes them, each page it writes its own copy and none executed: an image's data at its base (type 3) is then the file's pages, shared by every program and resident where it is touched, as the data of a program of the environment is, where a copy made the whole of it every program's own. The third maps instructions built with branch-target marks so that an indirect branch landing elsewhere faults, where the environment enforces it.

  53. A handle with less, and a program held to its grants. Specified in 0.23, as kal_fs_dir_attenuate, struct kal_dir_rights, KAL_DIR_* and KAL_FS_PROP_ATTENUATE, _READ_ONLY, _UNCHANGING in openkal.fs, and KAL_SPAWN_CONFINE and KAL_PROCESS_PROP_CONFINE in openkal.process (clauses 7.13 and 7.16). Three needs met by one operation upon a capability. A language server's engine probed, in each of its workers and for each compile command, the compilers installed on the machine: three hundred thousand failed enquiries in a session, which a program granted only the directories its build names answers in its own C library without asking, and which a tree its holder states does not change lets an implementation answer from what it listed. A tool that decides when a cached result is stale needs every name a program reached, including those it found absent, which the program's own report of its inputs omits: a header added earlier on a search path changes the result and appears in no list of what was read. And a program that parses what it was given --- a source file of a repository it did not choose --- is held, if subverted, to what its caller granted. The record and the statement are attributes of the handle, not of a runner: the environment answers them, whatever runs the program. Confinement is the caller's request and not a consequence of granting, because a program built for its environment needs directories it was not granted.

  54. A listing with attributes. Specified in 0.23, as kal_fs_list_next_info and KAL_FS_PROP_LIST_INFO. A program that lists a directory and asks of each entry what it is crossed this interface twice for each entry; two environments return a batch of entries with all of their attributes in one request, and a kernel that implements this interface crosses its boundary once for both. The same version states what the end of a listing reports --- kal_ok, the iterator zero and the name empty --- which one implementation had reported as kal_err_not_found.

  55. Page faults counted. Specified in 0.23, as faults_minor, faults_major, KAL_PINFO_FAULTS and KAL_PINFO_MAJOR appended to struct kal_program_info. Where a fault is dear, the faults of a phase and not its processor time are what a phase of starting a program spends; one environment counts only the sum, which is stated.

  56. A fault reported, and samples and touches taken, by the environment. Specified in 0.23, as kal_debug_props and KAL_DEBUG_PROP_FAULT_REPORT, and kal_prof_props, KAL_PROF_PROP_SAMPLES, struct kal_prof_sample and the three sampling operations, and KAL_PROF_PROP_TOUCHES and the three operations that record the order in which a program first executes the pages of a part of a region; an implementation provides openkal.debug and openkal.prof to a program built for its environment from this version. Interrupting a running context and reading where it stands needs a timer delivered to it or another context that may suspend it, and the first execution of a page is a fault the environment takes: only the environment has either. The environment's own profilers do not name an image's addresses on two of the three systems, and the party that ends a program for a fault is the one that can still say where. Frames are those of the chain of frame pointers, which the platform's build keeps. A loader above this interface measures touches through it, and does not install handlers of its own.

  57. A preference about cost, and what is available. Specified in 0.23, as KAL_SPAWN_RESOURCES_BALANCED, _PERFORMANCE and _ECONOMY in openkal.process, kal_env_resources in openkal.env, and kal_memory_info and struct kal_memory_status in openkal.memory (clause 7.17). Each layer of a program holds caches and reserves whose size trades memory or storage for time --- a loader's prepared images, a C library's free memory kept for reuse, an implementation's answers --- and each had chosen one point for every program. The party that knows what a program is for states it once; a balanced program adapts to what is available. It is a preference and not a limit, and no operation answers differently for it, as kal_task_set_priority changes no result.

  58. A declared unit whose external writers have ended. Specified in 0.24, as kal_process_job_create, kal_process_job_open, kal_process_job_observe and kal_process_job_retire (clause 7.18). Termination of one program does not establish termination of its descendants. A retained native reference can establish the original unit's state without a process registry; when the environment cannot establish that identity, the answer is unknown. A helper is itself a member, so successful retirement first excludes external members and then leaves its original unit where the environment requires it. A live continuation names the surviving program, not a remembered numeric group. The existing spawn form and immutable structure layouts remain unchanged.

  59. A second name for a file. Specified in 0.25, as kal_fs_hard_link and KAL_FS_PROP_HARD_LINKS in openkal.fs. A C library's link and linkat, and a C++ library's std::filesystem::create_hard_link, could not be written above this interface: no operation gave an existing file a second name, and copying the file is a different file with a different identity. Every environment this specification targets has the operation natively where the volume's format records several names for one node (linkat, CreateHardLinkW), so it is answered per volume by kal_fs_props, as the operations upon links are (entry 7), and a volume that does not claim it refuses the request rather than copying. The name being given another is not resolved, for the reason kal_fs_remove and kal_fs_rename act upon a name: a program that names a link means the link.

  60. The requests to end, counted. Specified in 0.25, in kal_process_stop_requested. The word was set once and never cleared, so a program that took the requests over and went on after the first could not learn of a second: an interactive program to which each interrupt keystroke is a request to abandon the current command observed the first keystroke and none after it. The word now counts the requests, skipping zero when the count wraps, so the earlier statement --- zero until a request, non-zero afterwards, never cleared --- remains true of every implementation of 0.25, and a program written against 0.24 reads it as before.

  61. A started program that runs to its end first. Specified in 0.25.1, as KAL_PROCESS_PROP_RUNS_TO_END in openkal.process (clause 7.19). Three environments the ecosystem prepares an implementation for --- a firmware's boot services, a disk operating system and a boot chain --- have one task, and in each the starter of a program resumes when the started program returns. The interface's text did not forbid such an implementation, and the concurrency the other properties describe is already optional; what was missing was a way for a caller to know. The property is the inverse of the behaviour every earlier implementation has, so no implementation of 0.25 changes by it, and an implementation that does not claim it is read as before.

  62. Thirty-two bit targets. Stated in 0.25.1 (clauses 5.5 and 12.2). The declarations were already written from the compiler's word and fixed-width types (clauses 5.1 and 5.4); what a thirty-two bit target requires of an implementation --- counts in a word, layouts the target's, handles that fit, time through the environment's sixty-four bit forms --- and what an image of such an architecture is, were not written down. No declaration changes.

  63. The surface the conformance suite links. Changed in 0.25.2; no clause and no declaration changes. A link that discards definitions nothing reaches kept of an implementation only what the suite calls, and the comparison of clause 9 then reported an interface absent whose definitions the implementation holds. The suite now reaches every name without requiring it, so a defined name is kept and an absent one is null.

12. Images

A program written against this specification is built for an environment: its implementation is linked into it (clause 4.4, S), or loaded beside it (L). This clause specifies a third arrangement, in which the program is an image --- one artifact per processor architecture --- and a loader running in the environment holds the implementation, maps the image, binds the names it imports to that implementation, and transfers control to it. The same bytes then run in every environment of that architecture for which a loader exists.

It is an arrangement and not a requirement: an implementation is not required to be reachable from an image, and a program built for its environment is unaffected by this clause. The format is called the ok format (openkal image format); an executable image is conventionally named *.oke, and an image that is loaded by another *.okso.

12.1 What makes it possible, and what does not

An environment's kernel executes only its own format --- each recognises a different header at the start of the file --- and a file cannot be all of them. What is above the kernel is a loader in every environment, and a loader can be written for any format. An image is therefore never started by the environment directly. It is started by a loader that is itself a program of the environment, or by a program of the environment that carries the image within it and starts it the same way.

Nothing else is environment-specific. An image calls only kal_*, so it needs from the loader only what this specification already declares, together with the operations a loader needs and a program could not otherwise reach (clause 11, entries 28, 30 and 46).

12.2 The image

Item Requirement
container ELF, position-independent (ET_DYN), little-endian, of the architecture's class; no text relocation
alignment every loadable segment aligned to at least 65536 bytes, which is the largest protection or reservation granularity among the environments this specification targets, and KAL_MAP_ALIGNMENT; a loader publishes each executable segment with kal_exec_publish_part, or makes it the file's with kal_exec_map_part
relocations only the architecture's relative, absolute, global-data and procedure-linkage relocations, and the packed relative form; a loader refuses any other
imports only names this specification declares, and names other images export; a name of a C library or of an environment is refused. A weak import is optional: it is bound when the implementation or an image the image needs provides it, and is zero otherwise
entry an executable image's ELF entry is a function of no parameters returning int, in the image's convention; its value is the image's status. An image that another loads has no entry
calling convention the architecture's, fixed for every environment: the System V convention on x86-64, the procedure call standard on AArch64, the System V i386 convention on i386 (arguments on the stack, the stack aligned to sixteen bytes at a call, the result in eax, or edx:eax for sixty-four bits); an implementation whose environment uses another presents entries in this one to the image (12.3)
floating-point state the convention's at the entry and in each context: on x86-64 the x87 control word 0x037F (extended precision, every exception masked) and MXCSR 0x1F80, on AArch64 FPCR zero, on i386 the x87 control word 0x037F and, where the processor has SSE, MXCSR 0x1F80; an image's C library computes in that state (a long double of x86-64 and of i386 is the x87's extended format)
architecture an image is of one architecture and runs on a loader of that architecture: an i386 image (ELFCLASS32, EM_386) on an i386 loader, never inside a loader of another (clause 5.5)
platform register an AArch64 image does not use x18, which two environments reserve
stack a frame larger than a page is touched page by page, in order: one environment commits a context's stack as its guard page is touched, and a frame that steps over the guard ends the program
thread-local storage through the emulated form, in which a variable is found by a function the image itself supplies; an image does not address the thread pointer, which one environment does not let a program set and another does not let it change
unwinding exceptions and unwinding do not cross from an image into a kal_* operation; the interface is a C interface (clause 1)
note an OPENKAL note (12.4)

The thread-local storage of an image is kept by the image's own C library, above kal_task_current. kal_task_current names a context only while it runs, so the library releases what it kept for a context when a context it started ends; a context the library did not start --- the first, and any an embedding program started --- keeps its storage for the life of the image. No operation is added for it. The cost is a function call where a register would have been read; it is stated rather than hidden, and a program for which it matters is built for its environment.

12.3 The loader's side

A loader holds the process's one implementation (clause 10.1). It maps the image into a region from kal_exec_alloc, applies the relocations, binds each imported name to that implementation's entry for it, publishes the executable segments, runs the image's initialisers and transfers control to its entry; when the entry returns, the loader runs the image's finalisers and ends with the status the entry returned (kal_exit). The image's kal_env_* operations answer for the image: arguments as the loader received them after its own, kal_env_image the image's name, and kal_env_loader the loader's own, so that the program can be started again (a loader, then an image).

Where the implementation sets KAL_EXEC_PROP_FILE (version 0.21), a loader makes each segment that no relocation writes the file's bytes (kal_exec_map_part) rather than a copy of them: the instructions executed and the read-only data read. A program then starts as a program of its environment does, paying for the pages it touches; a loader copies where the position is zero, where the image's bytes are not a file's at a multiple of KAL_MAP_ALIGNMENT (an image a program carries in its own bytes), and where a relocation writes the segment.

Where the implementation sets KAL_EXEC_PROP_PLACE (version 0.23) and the image carries a placement note (12.4), a loader asks for the image's span at the note's base (kal_exec_alloc_at). Given it, the loader takes each stored segment's bytes instead of the file's and applies only the relocations that name a symbol: a part of a stored segment that is read-only after relocation (the image's PT_GNU_RELRO) and that no such relocation writes is the file's bytes (kal_exec_map_part) where KAL_EXEC_PROP_FILE is set, and the rest is copied. Refused, or where the position is zero, the loader reserves the span anywhere and relocates the file's segments as before. The program observes the same memory either way; what differs is that a program placed at its base writes no address its relative relocations compute, as a program of its environment linked for one address does not.

Where an environment's own convention differs from the image's --- on x86-64, one environment passes arguments in other registers --- the loader presents each entry in the image's convention, and a function the image passes to the implementation (kal_task_start's entry, kal_space_start's) is entered in the image's convention. The convention includes the floating-point state (12.2): the loader establishes it at the entry, for the initialisers and finalisers, and in each context the image starts, and a call that a program of the environment makes into an image's function is made in it, the caller's own restored after it; one environment begins a thread with the x87 at double precision. The presentation is mechanical and is generated from SURFACE.txt and the declarations, so that it cannot omit a name. It is the loader's and not the implementation's: an implementation is written in its environment's convention, as every program of that environment is, and a convention of images is a fact about images.

A loader may provide openkal.prof and openkal.debug to the images it binds, beside the implementation's interfaces (version 0.20, clause 11 entry 45); from version 0.23 an implementation also provides them to a program built for its environment, and a loader forwards an image's samples to its own implementation (clause 11, entry 56). Both are about the program as the loader placed it --- its calls, its counters, its symbol table --- and the loader is the party that holds those, so this is not a second implementation in the process (clause 10.1): neither interface has a resource of the environment, and neither keeps state an implementation keeps. The image's kal_interfaces then answers the implementation's positions and the loader's together. A loader that measures counts each call an image makes across this interface by binding the image's import to a counting entry instead of the implementation's; one that does not binds the implementation's entry, so a program that is not measured pays nothing.

A loader does not write the part of a segment beyond its file's bytes: the region is zero (clause 11, entry 50), and a page of it the program never touches is never made resident. Once it has applied an image's relocations, it seals the image's PT_GNU_RELRO (kal_exec_seal_part), for ever for an image it will not unload (entry 51). Where an image states the pages it touches when it starts (12.4, type 4), it maps those parts with KAL_EXEC_MAP_PREFETCH; where an image's program property note states that every object carries branch-target marks, it maps the instructions with KAL_EXEC_MAP_BTI (entry 52).

12.4 The note

An image carries notes whose owner is OPENKAL, in a section .note.openkal:

type descriptor
1, requirements kal_u32 major and kal_u32 minor version of this clause (1 and 1; 1 and 0 before version 0.23 added the placement note), then a kal_u64 of KAL_IFACE_* positions: the interfaces the image requires, derived from the names it imports other than weakly
2, signature 32 bytes of an Ed25519 public key, then 64 bytes of the signature, over the whole file with these 96 bytes read as zero
3, placement a kal_u64 base, a multiple of 65536; a kal_u32 count and a kal_u32 zero; then, for each of count writable loadable segments that have bytes in the file, in ascending order of address, three kal_u64: the segment's address in the image, the file offset of its stored bytes, congruent to that address modulo 65536, and their length, the segment's size in the file. The stored bytes are the segment's file bytes with each relative relocation, and each of the packed relative form, applied for base; a relocation that names a symbol is not applied. Version 1.1 of this clause
4, touch a kal_u32 count and a kal_u32 zero; then, for each of count ranges, two kal_u32: the first 65536-byte block of the range in the file and the number of blocks, in the order the image first touches them when it starts. Version 1.1 of this clause
5, prepared reserved for a loader's own prepared copy of an image --- its segments computed for another base, its state after its initialisers --- which a loader writes for itself and does not distribute; an image as distributed carries no note of this type, and a loader refuses one that does

The first is required and the others are not. A count of zero states no placement, and a loader reads a placement note only after the requirements note states version 1.1 or higher. A loader compares the required interfaces with kal_interfaces and refuses to load an image that requires one the implementation does not provide, naming the interface. That is clause 6.1 in this arrangement: the loader binds at load what a linker binds at link, so it reports at load what a linker reports at link, and no operation that reports a lack of support at run time is introduced. A loader refuses an image of a higher major version of this clause. Whether a signature is required, and whose, is the loader's policy.

12.5 What is not specified

The loader, the format's tools, and the C library and C++ run-time inside an image are not specified: they are programs above this interface, and an image may carry any of them, or none. The reference loader and tools are published beside this specification (mcpplibs/openkal-image) and are not normative.