From b48fe9c8df08fe94985368c8903b938ac491c5bd Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 2 Oct 2026 23:53:43 +0800 Subject: [PATCH] 0.13.0 --- the stack of the calling thread, asked of the library that built it `kal_task_stack` reports the stack of the CALLING context, and this implementation answers it from the library that arranged the thread rather than from anything computed: `_pthread_self` names the caller, and `pthread_get_stackaddr_np` and `pthread_get_stacksize_np` answer where its stack is. The pair is the library's own --- the reservation for a thread whose stack the limit sizes and the usable mapping for one that was allocated --- which is what the specification states and does not require an implementation to tell apart. THE THREE NAMES FOLLOW THIS FILE'S OWN RULE RATHER THAN A PREFERENCE. A name is reachable from here when no C library in this ecosystem defines it, because a program above may define every ordinary one and the call would then resolve into the program. `pthread_self` IS a name musl defines, so the underscored `_pthread_self` is the one called --- musl's own is `__pthread_self`, a static inline rather than a symbol --- and the ordinary spelling would have handed this system's library a thread record of another library's layout. Neither `pthread_get_stackaddr_np` nor `pthread_get_stacksize_np` is defined by any C library in this ecosystem, so both are called as they are spelled. `port/libSystem.tbd` GAINS THE THREE, AND EACH NAME IS THE OBJECT FORMAT'S SPELLING AND NOT THE SOURCE'S, which is one underscore more: `_pthread_self` in C is `__pthread_self` there, while the two `_np` names are not. A first attempt had the pair the wrong way round and the cross link said so, which is why the stub's note records how the list is produced rather than only what it contains. Both architectures link with a program that calls the enquiry, so the names are resolved rather than merely declared. The continuous integration check that reads the objects extends its permitted set by the same three, and the note beside it says why the underscored spelling is deliberate. --- .github/workflows/ci.yml | 18 +++++++++++---- README.md | 31 +++++++++++++++++++++++-- mcpp.toml | 4 ++-- port/libSystem.tbd | 25 +++++++++++++++----- src/task.cpp | 50 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86c9bfc..d5361c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -303,7 +303,7 @@ jobs: # The property version 0.3 exists for. The assertion is made against the # objects rather than against the source, because a source can reach a C # library through a macro. - - name: The objects reference nothing of a C library but the two named + - name: The objects reference nothing of a C library but the names named run: | # Both profiles: an optimizing compiler turns a counting loop into # `strlen', which the dev profile never shows (hence -fno-builtin). @@ -319,8 +319,16 @@ jobs: # from ordinary loops. They compute rather than call, so none of # them can re-enter this implementation. # clock_gettime_nsec_np, - # pthread_create_from_mach_thread the two names no C library - # defines, which is why they are reachable from here at all. + # pthread_create_from_mach_thread, + # _pthread_self, + # pthread_get_stackaddr_np, + # pthread_get_stacksize_np names no C library in this ecosystem + # defines, which is why they are reachable from here at all. The + # last three answer the stack of the calling thread (openkal + # 0.15, clause 11 entry 21), and the underscored spelling of the + # first is deliberate: `pthread_self' IS a name musl defines, and + # this system's library would be handed a thread record of + # another library's layout. # __libc_start_main, main, _main the hand-over, undefined here by # construction. # kal_* the interface itself. @@ -340,7 +348,7 @@ jobs: # ___dso_handle likewise the format's: emitted # beside the weak references above, defined by the image's own # start files. - permitted='^_?(memcpy|memmove|memset|memcmp|bzero|clock_gettime_nsec_np|pthread_create_from_mach_thread|pthread_create|pthread_join|__libc_start_main|main|kal_[a-z_]+|__stack_chk_guard|__stack_chk_fail|GCC_except_table.*|_ZN3okm.*|__Unwind_Resume|__dso_handle|section\$(start|end)\$__TEXT\$__(eh_frame|unwind_info))$' + permitted='^_?(memcpy|memmove|memset|memcmp|bzero|clock_gettime_nsec_np|pthread_create_from_mach_thread|pthread_create|pthread_join|_pthread_self|_pthread_get_stackaddr_np|_pthread_get_stacksize_np|__libc_start_main|main|kal_[a-z_]+|__stack_chk_guard|__stack_chk_fail|GCC_except_table.*|_ZN3okm.*|__Unwind_Resume|__dso_handle|section\$(start|end)\$__TEXT\$__(eh_frame|unwind_info))$' # Reported with the object that references it. A symbol without the # object it came from names a fault and not a place, and the first # time this check fired the answer was in the object rather than in @@ -356,7 +364,7 @@ jobs: done done test "$bad" -eq 0 - echo "the implementation reaches nothing of a C library but the two names that are named ($profile)" + echo "the implementation reaches nothing of a C library but the names that are named ($profile)" done # A checker is only useful if it fails when it should. diff --git a/README.md b/README.md index 95a07f6..29b41b9 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ written on the kernel's own calls. ```toml [dependencies] -openkal = "0.14.1" +openkal = "0.15.0" [target.'cfg(os = "macos")'.dependencies] -openkal-macos = "0.12.1" +openkal-macos = "0.13.0" ``` Its purpose is as much to test the specification as to be used. A specification @@ -156,6 +156,33 @@ unrelated systems provide it is the evidence that it is the shape of the thing rather than the shape of one kernel — and version 0.2, which built the primitive out of a mutex and a condition variable, had this backwards. +## The region a context stands on + +`kal_task_stack` reports the stack of the calling context, and this +implementation asks the library that arranged the thread rather than measuring +anything: `_pthread_self` names the caller and the two `_np` enquiries answer +where its stack is. Nothing here can be wrong in the way a computed bound can, +because no bound is computed. + +The three names are chosen the way this implementation chooses every name it +takes from that library: a name is reachable from here when no C library in this +ecosystem defines it. `pthread_self` is a name musl defines --- so the +underscored `_pthread_self` is the one called, and the ordinary spelling would +have handed this system's library a thread record of another library's layout. +Neither `pthread_get_stackaddr_np` nor `pthread_get_stacksize_np` is defined by +any C library here, so both are called as they are spelled. + +The answer is the library's own: the reservation for a thread whose stack the +limit sizes, and the usable mapping for one whose stack was allocated. The +specification does not require an implementation to tell the two apart, and this +one does not. + +The pair is what the cross-link finds, not what the source reads. A Mach-O +symbol carries one more underscore than its C name, so `_pthread_self` is +`__pthread_self` in `port/libSystem.tbd` while the two `_np` names are not --- +measured by linking a program that calls the enquiry and reading what the linker +asked for, which is also how the stub's list was produced. + ## Conformance The suite lives in the specification package and is the same suite every diff --git a/mcpp.toml b/mcpp.toml index 1998a33..d27e18b 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-macos" -version = "0.12.1" +version = "0.13.0" description = "An implementation of openkal for macOS, written on the kernel's own calls. Its purpose is as much to test the specification as to be used." license = "Apache-2.0" @@ -57,7 +57,7 @@ provides-interfaces = [ ] [dependencies] -openkal = "0.14.1" +openkal = "0.15.0" [build] # The flags are attached to this package's own sources rather than to the whole diff --git a/port/libSystem.tbd b/port/libSystem.tbd index 283cc2c..d74f948 100644 --- a/port/libSystem.tbd +++ b/port/libSystem.tbd @@ -28,12 +28,24 @@ targets: [ arm64-macos, x86_64-macos ] install-name: '/usr/lib/libSystem.B.dylib' current-version: 1351.0.0 # -# THREE NAMES, AND THE THIRD IS OF A DIFFERENT KIND. +# FIVE NAMES ARE CALLED AND ONE IS NOT. # -# The first two are names this implementation CALLS, and the enumeration above -# found them. `dyld_stub_binder' is not called by anything here or in any source: -# it is referenced by the LINKER, for the lazy binding it emits, and it is -# therefore a requirement of the object format rather than of this package. +# The first four are names this implementation CALLS, and the enumeration above +# found them. The three that 0.13.0 added answer the stack of the calling +# thread (openkal 0.15, clause 11 entry 21), and they follow the same rule as +# the two before them: this system's library defines them and no C library in +# this ecosystem does, which is what makes them reachable from an +# implementation whose ordinary names a program above may itself define. +# +# EACH NAME IS THE OBJECT FORMAT'S SPELLING AND NOT THE SOURCE'S, which is one +# underscore more: `_pthread_self' in C is `__pthread_self' here, while +# `pthread_get_stackaddr_np' in C is `_pthread_get_stackaddr_np'. Measured by +# linking a program that calls the enquiry and reading what the linker asked +# for, after a first attempt that had the pair the wrong way round. +# +# `dyld_stub_binder' is not called by anything here or in any source: it is +# referenced by the LINKER, for the lazy binding it emits, and it is therefore a +# requirement of the object format rather than of this package. # # It was missed because it depends on the linker's version. Measured # 2026-08-22: ld64.lld 22.1.8 links these objects without it, and ld64.lld 18 @@ -43,5 +55,6 @@ current-version: 1351.0.0 exports: - targets: [ arm64-macos, x86_64-macos ] symbols: [ _clock_gettime_nsec_np, _pthread_create_from_mach_thread, - dyld_stub_binder ] + _pthread_get_stackaddr_np, _pthread_get_stacksize_np, + __pthread_self, dyld_stub_binder ] ... diff --git a/src/task.cpp b/src/task.cpp index 1540bca..b04b40c 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -56,6 +56,29 @@ int pthread_create(void** thread, const void* attr, void* (*start)(void*), void* arg); int pthread_join(void* thread, void** value); #endif + +// THE STACK OF THE CALLING THREAD, AND THE THREE NAMES FOLLOW THE RULE THE +// COMMENT ABOVE STATES RATHER THAN A PREFERENCE: a name is reachable from here +// when no C library in this ecosystem defines it, because a program above may +// define every ordinary one and the call would then resolve into the program. +// +// `pthread_self' IS a name musl defines, so the UNDERSCORED `_pthread_self' is +// the one called: this system's library defines it, musl does not (musl's own +// is `__pthread_self', and it is a static inline rather than a symbol), and +// this system's library would otherwise be handed a thread record of another +// library's layout. +// +// Neither `pthread_get_stackaddr_np' nor `pthread_get_stacksize_np' is defined +// by any C library in this ecosystem, so both are taken as they are spelled. +// Each answers about the thread it is given, and the thread given is the +// caller's, because a context is the only resource its own code stands on +// (SPEC 0.15, clause 11 entry 21). The first answers the BASE, which on this +// system is the highest address of the stack; the second answers the distance +// from there to the lowest address the thread may use, the guard page below it +// excluded. +void* _pthread_self(void); +void* pthread_get_stackaddr_np(void* thread); +unsigned long pthread_get_stacksize_np(void* thread); } namespace { @@ -142,6 +165,33 @@ void kal_task_yield(void) { okm::relax(); } kal_uintptr kal_task_current(void) { return okm::current_context(); } +// The stack the calling context runs on. Version 0.15. +// +// ASKED OF THE LIBRARY THAT ALLOCATED IT, AND ABOUT THE CALLER. There is no +// measurement here to get wrong, and no record for this implementation to keep: +// the library that arranged the thread's state is the one that knows where the +// stack it arranged begins, and it is asked about the thread that is asking. +// The region is the library's own answer, which is the reservation for a thread +// whose stack is sized by the limit and the usable mapping for one whose stack +// was allocated --- exactly the two cases SPEC clause 11 entry 21 distinguishes +// and does not require an implementation to tell apart. +int kal_task_stack(void** base, kal_uintptr* size) { + if (base == nullptr || size == nullptr) return kal_err_invalid; + + void* self = _pthread_self(); + if (self == nullptr) return kal_err_io; + + auto* bottom = static_cast(pthread_get_stackaddr_np(self)); + const unsigned long bytes = pthread_get_stacksize_np(self); + if (bottom == nullptr || bytes == 0) return kal_err_io; + + // The base is where the stack stops, and the library answered with where it + // starts. + *base = bottom - bytes; + *size = static_cast(bytes); + return kal_ok; +} + int kal_task_wait(const kal_u32* word, kal_u32 expected, kal_u64 timeout_ns) { // The unit this system takes is the microsecond, and zero means no timeout.