diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 322be2e..b9a71a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -728,11 +728,16 @@ jobs: fi echo "openkal $here is in ${{ matrix.implementation }}'s range $there" elif [ -n "$there" ]; then - if [ "$here" != "$there" ]; then + # A PATCH RELEASE OF THE SPECIFICATION ADDS ONLY WHAT AN IMPLEMENTATION + # MAY DECLINE (clause 8, 0.25.1): an implementation written against an + # earlier patch of the same minor version conforms to it, and the + # suite's new observations run only where it claims what they observe. + # So the two are in step when their major and minor versions agree. + if [ "${here%.*}" != "${there%.*}" ]; then echo "::error::this is openkal $here and ${{ matrix.implementation }} is written against openkal $there. Nothing is wrong with either; they are not in step. Re-run after the implementation's branch has the matching version." >&2 exit 1 fi - echo "both describe openkal $here" + echo "openkal $here and ${{ matrix.implementation }}'s openkal $there are one minor version" else impl_branch="$(sed -n 's/^openkal[[:space:]]*=[[:space:]]*{.*branch[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' .impl/mcpp.toml | head -1)" if [ -z "$impl_branch" ]; then diff --git a/README.md b/README.md index e29df8f..0b811f0 100644 --- a/README.md +++ b/README.md @@ -96,16 +96,16 @@ conditional on the target. ```toml [dependencies] -openkal = "0.25.0" +openkal = "0.25.1" [target.'cfg(os = "linux")'.dependencies] -openkal-linux = "0.26.0" +openkal-linux = "0.26.1" [target.'cfg(os = "macos")'.dependencies] -openkal-macos = "0.23.0" +openkal-macos = "0.23.1" [target.'cfg(windows)'.dependencies] -openkal-windows = "0.21.0" +openkal-windows = "0.21.1" ``` The program imports the interface and names no implementation. diff --git a/SPEC.md b/SPEC.md index db0dc28..84a584d 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1,4 +1,4 @@ -# openkal Specification, version 0.25 +# openkal Specification, version 0.25.1 ## 1. Scope @@ -533,6 +533,40 @@ 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 @@ -1111,12 +1145,48 @@ 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 @@ -2134,6 +2204,26 @@ The following are recorded so that they are not mistaken for oversights. 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. + ## 12. Images A program written against this specification is built for an environment: its @@ -2175,8 +2265,9 @@ the operations a loader needs and a program could not otherwise reach | 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; 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; an image's C library computes in that state (a `long double` of x86-64 is the x87's extended format) | +| 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 | diff --git a/conformance/mcpp.toml b/conformance/mcpp.toml index 7c4c258..b173c7b 100644 --- a/conformance/mcpp.toml +++ b/conformance/mcpp.toml @@ -18,7 +18,7 @@ repo = "https://github.com/mcpplibs/openkal" # What it depends upon is therefore openkal and the language. The formatting in # okc.report is the price of that, and it is sixty lines. [dependencies] -openkal = "0.25.0" +openkal = "0.25.1" # The implementation under examination is not named here. # diff --git a/conformance/src/sections/exec.cpp b/conformance/src/sections/exec.cpp index 0f6e86e..114cd27 100644 --- a/conformance/src/sections/exec.cpp +++ b/conformance/src/sections/exec.cpp @@ -20,7 +20,8 @@ namespace { // The value is checked rather than merely the fact of returning, because a // region that was published without its contents reaching memory would still // transfer control and would return whatever the register happened to hold. -#if defined(__x86_64__) +#if defined(__x86_64__) || defined(__i386__) +// mov $42, %eax; ret --- the same encoding in both modes (0.25.1, clause 5.5) const unsigned char kReturns42[] = { 0xB8, 0x2A, 0x00, 0x00, 0x00, 0xC3 }; #elif defined(__aarch64__) const unsigned char kReturns42[] = { 0x40, 0x05, 0x80, 0x52, // mov w0, #42 @@ -33,7 +34,7 @@ const unsigned char* const kReturns42 = nullptr; const unsigned long kReturns42Size = 0; #endif -#if defined(__x86_64__) || defined(__aarch64__) || (defined(__riscv) && __riscv_xlen == 64) +#if defined(__x86_64__) || defined(__i386__) || defined(__aarch64__) || (defined(__riscv) && __riscv_xlen == 64) constexpr unsigned long kReturns42Size = sizeof(kReturns42); #endif diff --git a/conformance/src/sections/process.cpp b/conformance/src/sections/process.cpp index d914569..c1b507a 100644 --- a/conformance/src/sections/process.cpp +++ b/conformance/src/sections/process.cpp @@ -327,6 +327,35 @@ void run() { "an unclaimed channel is refused rather than half provided"); } + // A STARTED PROGRAM THAT RUNS TO ITS END FIRST (0.25.1, clause 7.19). The + // property is claimed only where the environment has one task; there the + // started program has ended when the start returns, so nothing can be + // terminated or exchanged with it while it runs, and its status is known. + if (kal::process::has(kal::process::runs_to_end)) { + observe(kind::behaviour, !kal::process::has(kal::process::terminate), + "a program that runs to its end first is not claimed terminable"); + observe(kind::behaviour, !kal::process::has(kal::process::channel), + "a program that runs to its end first is not claimed to share a channel"); + kal_process child{}; + if (start_copy_running("openkal-conformance-child", argument_for(errand::exit_with_33), child)) { +#ifdef MCPP_FEATURE_TIME + const kal_u64 before = kal_time_monotonic(); +#endif + int status = -1, terminated = -1; + const int rc = kal_process_wait(child, &status, &terminated); + observe(kind::behaviour, rc == kal_ok && status == 33 && terminated == 0, + "the status of a program that ran to its end is the one it ended with"); +#ifdef MCPP_FEATURE_TIME + observe(kind::behaviour, kal_time_monotonic() - before < kal_u64{100} * 1000u * 1000u, + "the status of a program that ran to its end is answered at once"); +#endif + kal_process_close(child); + } else { + unobserved(kind::behaviour, "a program that runs to its end first", + "a copy of this program could not be started"); + } + } + // WHAT A UNIT USES, AND A LIMIT ON IT (version 0.20). Both are observed // on a copy that writes its memory through, so that every environment's // measure sees it. diff --git a/examples/portable/mcpp.toml b/examples/portable/mcpp.toml index 689d300..2167baf 100644 --- a/examples/portable/mcpp.toml +++ b/examples/portable/mcpp.toml @@ -9,7 +9,7 @@ name = "portable" version = "0.1.0" [dependencies] -openkal = "0.25.0" +openkal = "0.25.1" # AND THE VERSIONS HAVE TO FOLLOW THE SOURCE, which is the reason this example # is a criterion rather than an assumption. @@ -25,13 +25,13 @@ openkal = "0.25.0" # so no repository's continuous integration compiled it. The platform legs # below exist so that it cannot go stale silently again. [target.'cfg(os = "linux")'.dependencies] -openkal-linux = "0.25.0" +openkal-linux = "0.26.1" [target.'cfg(os = "macos")'.dependencies] -openkal-macos = "0.22.0" +openkal-macos = "0.23.1" [target.'cfg(windows)'.dependencies] -openkal-windows = "0.20.0" +openkal-windows = "0.21.1" # ANDROID IS NOT A LINE HERE, AND THAT IS THE POINT. # @@ -57,7 +57,7 @@ openkal-windows = "0.20.0" # All three iOS rows match: the device and both simulator arches carry # `os = "ios"`. [target.'cfg(os = "ios")'.dependencies] -openkal-macos = "0.22.0" +openkal-macos = "0.23.1" # THE WEB IS THE ONE PLATFORM THAT NEEDED NEW SOFTWARE. # @@ -82,4 +82,4 @@ openkal-macos = "0.22.0" # error would have produced a program that links, runs, and fails where a # reader cannot connect it to a missing facility. See the README. [target.'cfg(os = "emscripten")'.dependencies] -openkal-emscripten = "0.1.0" +openkal-emscripten = "0.14.1" diff --git a/include/openkal/process.h b/include/openkal/process.h index ddd71f3..8008393 100644 --- a/include/openkal/process.h +++ b/include/openkal/process.h @@ -106,6 +106,10 @@ struct kal_preopen { #define KAL_PROCESS_PROP_CONFINE ((kal_uintptr)1u << 14) /* Declared units, portable references and retirement. Version 0.24. */ #define KAL_PROCESS_PROP_JOB_OBSERVE ((kal_uintptr)1u << 15) +/* The environment has one task: kal_process_spawn returns after the started + * program has ended, and kal_process_wait answers its status at once. Claimed + * without TERMINATE and CHANNEL. Clause 7.19. Version 0.25.1. */ +#define KAL_PROCESS_PROP_RUNS_TO_END ((kal_uintptr)1u << 16) /* HOW A PROGRAM IS STARTED, AND IT IS ONE OPERATION BECAUSE 0.11 STOPPED * MAKING IT A FAMILY. diff --git a/include/openkal/version.h b/include/openkal/version.h index 0801ff6..d8c7a59 100644 --- a/include/openkal/version.h +++ b/include/openkal/version.h @@ -43,7 +43,7 @@ * consumer actually asks for was checked by nobody. */ #define KAL_VERSION_MAJOR 0u #define KAL_VERSION_MINOR 25u -#define KAL_VERSION_PATCH 0u +#define KAL_VERSION_PATCH 1u #define KAL_VERSION_MAKE(major, minor, patch) \ (((kal_u64)(major) << 32) | ((kal_u64)(minor) << 16) | \ diff --git a/mcpp.toml b/mcpp.toml index 72da29f..932edb9 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal" -version = "0.25.0" +version = "0.25.1" description = "openkal: a portable kernel ABI specification. This package carries the normative declarations; implementations are separate packages." license = "Apache-2.0" authors = ["mcpplibs"] diff --git a/src/macros.cppm b/src/macros.cppm index 7d46c2a..89bc2c5 100644 --- a/src/macros.cppm +++ b/src/macros.cppm @@ -138,6 +138,7 @@ inline constexpr kal_uintptr KAL_PROCESS_PROP_JOB_USAGE_M = (kal_uintptr)( inline constexpr kal_uintptr KAL_PROCESS_PROP_NULL_STREAMS_M = (kal_uintptr)(KAL_PROCESS_PROP_NULL_STREAMS); inline constexpr kal_uintptr KAL_PROCESS_PROP_ONE_STRING_M = (kal_uintptr)(KAL_PROCESS_PROP_ONE_STRING); inline constexpr kal_uintptr KAL_PROCESS_PROP_OPEN_ID_M = (kal_uintptr)(KAL_PROCESS_PROP_OPEN_ID); +inline constexpr kal_uintptr KAL_PROCESS_PROP_RUNS_TO_END_M = (kal_uintptr)(KAL_PROCESS_PROP_RUNS_TO_END); inline constexpr kal_uintptr KAL_PROCESS_PROP_STOP_REQUESTED_M = (kal_uintptr)(KAL_PROCESS_PROP_STOP_REQUESTED); inline constexpr kal_uintptr KAL_PROCESS_PROP_STREAM_PASSING_M = (kal_uintptr)(KAL_PROCESS_PROP_STREAM_PASSING); inline constexpr kal_uintptr KAL_PROCESS_PROP_TERMINATE_M = (kal_uintptr)(KAL_PROCESS_PROP_TERMINATE); diff --git a/src/process.cppm b/src/process.cppm index dac4f5a..5843551 100644 --- a/src/process.cppm +++ b/src/process.cppm @@ -94,6 +94,7 @@ inline constexpr props job_limit_supervised{KAL_PROCESS_PROP_JOB_LIMIT_SUPERVISE inline constexpr props null_streams {KAL_PROCESS_PROP_NULL_STREAMS}; // 0.22 inline constexpr props confine {KAL_PROCESS_PROP_CONFINE}; // 0.23 inline constexpr props job_observe {KAL_PROCESS_PROP_JOB_OBSERVE}; // 0.24 +inline constexpr props runs_to_end {KAL_PROCESS_PROP_RUNS_TO_END}; // 0.25.1 namespace job_state { inline constexpr kal_u32 unknown = KAL_JOB_UNKNOWN; inline constexpr kal_u32 occupied = KAL_JOB_OCCUPIED;