From ce73e6a9ea75ee02c28f9e91442b477a24567051 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sun, 8 Nov 2026 05:35:05 +0900 Subject: [PATCH] 0.5.3 --- an image is entered in its convention's floating-point state Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- .github/workflows/ci.yml | 8 ++++- README.md | 10 ++++++ bind/mcpp.toml | 2 +- bind/src/openkal_bind.cppm | 9 ++++- docs/bind.md | 1 + examples/bind/app/src/main.cpp | 27 ++++++++++++++- examples/bind/mathlib/image/mathlib.c | 14 ++++++++ examples/bind/mathlib/include/mathlib.h | 3 ++ examples/fpstate/app.c | 46 +++++++++++++++++++++++++ ld/mcpp.toml | 2 +- run/include/okrun.h | 7 ++++ run/mcpp.toml | 2 +- run/src/bindings.cpp | 3 ++ run/src/image_abi.h | 39 +++++++++++++++++++++ run/src/okrun.cppm | 2 ++ run/src/run.cpp | 13 ++++++- tools/mcpp.toml | 2 +- 17 files changed, 182 insertions(+), 8 deletions(-) create mode 100644 examples/fpstate/app.c diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55f4ee3..06656b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,6 +81,8 @@ jobs: || { echo "::error::a weak import made the image require its interface"; exit 1; } # 6 MiB of frames on the main thread, the runner's on Windows (0.5.2). bash examples/build-image.sh "$arch" "out/$arch/stack.oke" examples/stack/app.c + # The convention's floating-point state at the entry and in a context (0.5.3). + bash examples/build-image.sh "$arch" "out/$arch/fpstate.oke" examples/fpstate/app.c cp "out/$arch/hello.oke" "out/$arch/signed.oke" # The library examples/bind binds for a program of each system. bash examples/build-image.sh "$arch" "out/$arch/libmathlib.okso" examples/bind/mathlib/image/mathlib.c @@ -281,6 +283,10 @@ jobs: # reservation, which was the linker's 1 MiB default. st=$("$OKRUN" "out/$a/stack.oke" | tr -d '\r') test "$st" = 'stack: 6 MiB on the main thread' || { echo "::error::the image's main thread has less than a thread's stack: $st"; exit 1; } + # THE CONVENTION'S FLOATING-POINT STATE (0.5.3): on Windows a thread + # begins with the x87 at double precision. + fp=$("$OKRUN" "out/$a/fpstate.oke" | tr -d '\r') + test "$fp" = "fp: the convention's at the entry, the convention's in a context" || { echo "::error::the image's floating-point state: $fp"; exit 1; } w=$("$OKRUN" "out/$a/weak.oke" | tr -d '\r') echo "$w" grep -q "weak: openkal.net bound" <<<"$w" || { echo "::error::a weak import the implementation provides was not bound"; exit 1; } @@ -476,7 +482,7 @@ jobs: run: | for a in x86_64 aarch64; do mkdir -p "examples/bind/mathlib/lib/$a"; cp "out/$a/libmathlib.okso" "examples/bind/mathlib/lib/$a/"; done (cd examples/bind/app && mcpp run) | tr -d '\r' | tee bind.txt - for want in "add: 42" "scale: 6.00" "mirror: -3 7" "length: 7" "each: 4 visited, sum 10"; do + for want in "add: 42" "scale: 6.00" "mirror: -3 7" "length: 7" "each: 4 visited, sum 10" "fp: the convention's in the image, the caller's here"; do grep -q "$want" bind.txt || { echo "::error::the binding: $want"; exit 1; } done bash examples/bind/check-refusals.sh diff --git a/README.md b/README.md index 14323a1..3c3a127 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,16 @@ thread's default under openkal-windows, as Linux and macOS give the main thread 8 MiB. The `stack` example recurses through 6 MiB on the main thread on every system. +Version 0.5.3 enters an image in its convention's floating-point state (SPEC +12.2): on x86-64 the x87 control word 0x037F and MXCSR 0x1F80, on AArch64 FPCR +zero. A thread of Windows begins with the x87 at double precision (0x027F), +under which a C library that computes in long double read decimal numbers +otherwise (`strtod("0.4")` gave 0x1.99999999998p-2). The runner establishes the +state at the entry, around initialisers and finalisers, and in each context the +image starts; a binding makes each call from a program of the system in it and +restores the caller's after it (`okrun_call_enter`, `okrun_call_leave`). The +`fpstate` example and the `bind` example check it on every system. + ## An image ```sh diff --git a/bind/mcpp.toml b/bind/mcpp.toml index eb05ff0..58ebbc1 100644 --- a/bind/mcpp.toml +++ b/bind/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-bind" -version = "0.5.2" +version = "0.5.3" # `mcpp::xpkg_request', `mcpp::decision' (protocol 15). mcpp = ">=2026.10.1.3" description = "A build plugin: a library published as an ok image (.okso) and a C header is called from a program of any system, through a binding generated from the header and libokrun." diff --git a/bind/src/openkal_bind.cppm b/bind/src/openkal_bind.cppm index 9eba60b..b27b439 100644 --- a/bind/src/openkal_bind.cppm +++ b/bind/src/openkal_bind.cppm @@ -619,7 +619,14 @@ std::string generate(const declarations& d, const std::string& header_name, cons o += std::format("okbind_{0}_r {0}({1}) {{\n", f.name, params); o += std::format(" okbind_{0}_t f = (okbind_{0}_t)__atomic_load_n(&okbind_{0}_entry, __ATOMIC_ACQUIRE);\n", f.name); o += std::format(" if (!f) {{ f = (okbind_{0}_t)okbind_symbol(\"{0}\"); __atomic_store_n(&okbind_{0}_entry, (void*)f, __ATOMIC_RELEASE); }}\n", f.name); - o += std::format(" {}f({});\n}}\n\n", f.ret == "void" ? "" : "return ", args); + // In the image's floating-point state, and the caller's again after it (SPEC 12.2). + o += " const unsigned long long okbind_state = okrun_call_enter();\n"; + if (f.ret == "void") { + o += std::format(" f({});\n okrun_call_leave(okbind_state);\n}}\n\n", args); + } else { + o += std::format(" okbind_{}_r okbind_r = f({});\n okrun_call_leave(okbind_state);\n" + " return okbind_r;\n}}\n\n", f.name, args); + } } // The entries' slots, declared before the functions that use them. std::string slots; diff --git a/docs/bind.md b/docs/bind.md index 7152d4c..c15fef6 100644 --- a/docs/bind.md +++ b/docs/bind.md @@ -111,6 +111,7 @@ llvm toolchain's clang converts the call. | 0.5.0 | The binding format is unchanged; the runner selects OpenKal 0.24 and binds its four process-unit operations. | | 0.5.1 | No change of its own; released with the archive, whose runner is built over openkal-macos 0.22.1 (confinement rules sized to their grants). | | 0.5.2 | No change of its own; released with the archive, whose runner on Windows reserves a thread's stack for the image's main thread. | +| 0.5.3 | Each call is made in the image's floating-point state (SPEC 12.2) and restores the caller's after it, through `okrun_call_enter` and `okrun_call_leave`; a callback the image calls runs in the image's state. | | 0.4.0 | No change of its own; released with the archive, whose loader places an image at the base `okld place` chose for it (openkal 0.23, `kal_exec_alloc_at`) and then applies only the relocations that name a symbol. | | 0.4.1 | No change of its own; released with the archive, whose runner on macOS is built over openkal-macos 0.21.2 (a sleep is no longer a spin of the processor; the granularity is the same at every start). | | 0.4.2 | No change of its own; released with the archive, whose runner on macOS is built over openkal-macos 0.21.3 (asked before the implementation's initialiser ran, it answers from the loader's record). | diff --git a/examples/bind/app/src/main.cpp b/examples/bind/app/src/main.cpp index fa624ef..96f40e6 100644 --- a/examples/bind/app/src/main.cpp +++ b/examples/bind/app/src/main.cpp @@ -5,6 +5,18 @@ namespace { // A callback the image calls: on x86-64 Windows it is reached through a // System V entry the binding gives the image. void visit(int32_t value, void* context) { *static_cast(context) += value; } + +// The caller's floating-point state, which the binding restores after a call. +// On x86-64 it is set as a thread of Windows begins, the x87 at double +// precision, so that every system shows the change and its restoration. +#if defined(__x86_64__) +unsigned short x87_word() { + unsigned short word = 0; + __asm__ volatile("fnstcw %0" : "=m"(word)); + return word; +} +void set_x87_word(unsigned short word) { __asm__ volatile("fldcw %0" : : "m"(word)); } +#endif } int main() { @@ -18,5 +30,18 @@ int main() { long long total = 0; const int32_t n = mathlib_each(values, 4, &visit, &total); std::printf("each: %d visited, sum %lld\n", n, total); - return sum == 42 && p.x == -3 && p.y == 7 && n == 4 && total == 10 ? 0 : 1; +#if defined(__x86_64__) + const unsigned short own = x87_word(); + set_x87_word(0x027F); + const int32_t in_image = mathlib_conventional_fp(); + const bool restored = x87_word() == 0x027F; + set_x87_word(own); +#else + const int32_t in_image = mathlib_conventional_fp(); + const bool restored = true; +#endif + const bool fp = in_image && restored; + std::printf("fp: %s in the image, %s here\n", in_image ? "the convention's" : "another", + restored ? "the caller's" : "another"); + return sum == 42 && p.x == -3 && p.y == 7 && n == 4 && total == 10 && fp ? 0 : 1; } diff --git a/examples/bind/mathlib/image/mathlib.c b/examples/bind/mathlib/image/mathlib.c index 8fd6616..5412409 100644 --- a/examples/bind/mathlib/image/mathlib.c +++ b/examples/bind/mathlib/image/mathlib.c @@ -19,3 +19,17 @@ MATHLIB_API int32_t mathlib_each(const int32_t* values, size_t count, mathlib_vi for (size_t i = 0; i < count; ++i) visit(values[i], context); return (int32_t)count; } +MATHLIB_API int32_t mathlib_conventional_fp(void) { +#if defined(__x86_64__) + uint16_t x87; + uint32_t mxcsr; + __asm__ volatile("fnstcw %0\n\tstmxcsr %1" : "=m"(x87), "=m"(mxcsr)); + return x87 == 0x037F && (mxcsr & 0xFFC0) == 0x1F80; +#elif defined(__aarch64__) + uint64_t fpcr; + __asm__ volatile("mrs %0, fpcr" : "=r"(fpcr)); + return fpcr == 0; +#else + return 1; +#endif +} diff --git a/examples/bind/mathlib/include/mathlib.h b/examples/bind/mathlib/include/mathlib.h index c1c1dd2..7b0325d 100644 --- a/examples/bind/mathlib/include/mathlib.h +++ b/examples/bind/mathlib/include/mathlib.h @@ -21,6 +21,9 @@ double mathlib_scale(double value, double by); struct mathlib_point mathlib_mirror(struct mathlib_point p); size_t mathlib_length(const char* text); int32_t mathlib_each(const int32_t* values, size_t count, mathlib_visit visit, void* context); +/* Non-zero where the call runs in the convention's floating-point state + * (SPEC 12.2): what a C library in the image that computes in long double needs. */ +int32_t mathlib_conventional_fp(void); #ifdef __cplusplus } diff --git a/examples/fpstate/app.c b/examples/fpstate/app.c new file mode 100644 index 0000000..d3f3707 --- /dev/null +++ b/examples/fpstate/app.c @@ -0,0 +1,46 @@ +/* The image is entered in its convention's floating-point state (SPEC 12.2, + * openkal-image 0.5.3): on x86-64 the x87 control word 0x037F and MXCSR + * 0x1F80, on AArch64 FPCR zero --- at its entry and in each context it starts. + * Windows starts a thread with the x87 at double precision (0x027F), under + * which a C library that computes in long double reads "0.4" as + * 0x1.99999999998p-2. The entry is `int ok_main(void)' (SPEC 12.2). */ +typedef unsigned long kal_uintptr; +typedef long kal_intptr; +struct kal_stream { kal_uintptr h; }; +struct kal_task { kal_uintptr h; }; + +struct kal_stream kal_stdout(void); +kal_intptr kal_stream_write(struct kal_stream, const void*, kal_uintptr); +int kal_task_start(void (*entry)(void*), void* arg, struct kal_task* out); +int kal_task_join(struct kal_task); + +static void put(const char* s) { + unsigned long n = 0; while (s[n]) ++n; + kal_stream_write(kal_stdout(), s, n); +} + +/* Non-zero where the state is the convention's. */ +static int conventional(void) { +#if defined(__x86_64__) + unsigned short x87; unsigned mxcsr; + __asm__ volatile("fnstcw %0\n\tstmxcsr %1" : "=m"(x87), "=m"(mxcsr)); + return x87 == 0x037F && (mxcsr & 0xFFC0) == 0x1F80; +#elif defined(__aarch64__) + unsigned long fpcr; + __asm__ volatile("mrs %0, fpcr" : "=r"(fpcr)); + return fpcr == 0; +#else + return 1; +#endif +} + +static void context(void* seen) { *(int*)seen = conventional(); } + +__attribute__((visibility("default"))) int ok_main(void) { + int at_entry = conventional(), in_context = 0; + struct kal_task t; + if (kal_task_start(context, &in_context, &t) == 0) kal_task_join(t); + put(at_entry ? "fp: the convention's at the entry" : "fp: another at the entry"); + put(in_context ? ", the convention's in a context\n" : ", another in a context\n"); + return at_entry && in_context ? 0 : 3; +} diff --git a/ld/mcpp.toml b/ld/mcpp.toml index e2e9faa..4e564ab 100644 --- a/ld/mcpp.toml +++ b/ld/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-ld" -version = "0.5.2" +version = "0.5.3" description = "libokld: the ok format (openkal image format) --- its definition, a reader of bytes, a checker, a signer, and a loader. Freestanding: no C library, no exceptions." license = "Apache-2.0" authors = ["mcpplibs"] diff --git a/run/include/okrun.h b/run/include/okrun.h index 3934b48..6be83a8 100644 --- a/run/include/okrun.h +++ b/run/include/okrun.h @@ -95,6 +95,13 @@ void* okrun_symbol(struct okrun_library* library, const char* name); /* Runs the library's finalisers and unloads it, and the images it needed that * nothing else holds. */ void okrun_close(struct okrun_library* library); +/* A call into a library's function from a thread of this system (0.5.3) is + * made in the image's floating-point state (SPEC 12.2), and the caller's is + * restored after it: `okrun_call_enter' establishes the image's and returns the + * caller's, which `okrun_call_leave' restores. A binding (openkal-bind) makes + * each call between the two. */ +unsigned long long okrun_call_enter(void); +void okrun_call_leave(unsigned long long state); /* --- a measurement (0.2) --------------------------------------------------- */ diff --git a/run/mcpp.toml b/run/mcpp.toml index 2183bd7..6ffe153 100644 --- a/run/mcpp.toml +++ b/run/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-run" -version = "0.5.2" +version = "0.5.3" description = "libokrun: the runner's core --- an ok image's names bound to this process's one openkal implementation, in the image's calling convention, and its entry entered." license = "Apache-2.0" authors = ["mcpplibs"] diff --git a/run/src/bindings.cpp b/run/src/bindings.cpp index 7b7ef37..e70e750 100644 --- a/run/src/bindings.cpp +++ b/run/src/bindings.cpp @@ -43,9 +43,12 @@ struct counted { using image_entry = void (OKRUN_IMAGE_ABI*)(void*); struct box { image_entry fn; void* arg; }; +// The context is the image's from its start, in the convention's +// floating-point state (SPEC 12.2). void enter_image(void* p) { const box b = *static_cast(p); kal_free(p, sizeof(box), alignof(box)); + okrun_fp_enter(); b.fn(b.arg); } diff --git a/run/src/image_abi.h b/run/src/image_abi.h index a605e4a..64391ca 100644 --- a/run/src/image_abi.h +++ b/run/src/image_abi.h @@ -4,13 +4,52 @@ // architecture appears in this library. #pragma once +// THE CONVENTION'S FLOATING-POINT STATE (SPEC 12.2, 0.5.3) is part of the +// convention: on x86-64 the x87 control word 0x037F (extended precision, every +// exception masked) and MXCSR 0x1F80, on AArch64 FPCR zero. `okrun_fp_enter' +// establishes it and returns the state it found, which `okrun_fp_leave' +// restores; each writes only a word that differs. A thread of Windows begins +// with the x87 at double precision (0x027F), under which a C library that +// computes in long double reads decimal numbers otherwise. #if defined(__x86_64__) #define OKRUN_IMAGE_ABI __attribute__((sysv_abi)) inline constexpr char okrun_arch[] = "x86_64"; +inline unsigned long long okrun_fp_enter() { + unsigned short x87 = 0; + unsigned mxcsr = 0; + __asm__ volatile("fnstcw %0\n\tstmxcsr %1" : "=m"(x87), "=m"(mxcsr)); + const unsigned short image_x87 = 0x037F; + const unsigned image_mxcsr = 0x1F80; + if (x87 != image_x87) __asm__ volatile("fldcw %0" : : "m"(image_x87)); + if ((mxcsr & 0xFFC0) != image_mxcsr) __asm__ volatile("ldmxcsr %0" : : "m"(image_mxcsr)); + return static_cast(mxcsr) << 16 | x87; +} +inline void okrun_fp_leave(unsigned long long state) { + const unsigned short x87 = static_cast(state & 0xFFFF); + const unsigned mxcsr = static_cast(state >> 16); + unsigned short now_x87 = 0; + unsigned now_mxcsr = 0; + __asm__ volatile("fnstcw %0\n\tstmxcsr %1" : "=m"(now_x87), "=m"(now_mxcsr)); + if (now_x87 != x87) __asm__ volatile("fldcw %0" : : "m"(x87)); + if ((now_mxcsr & 0xFFC0) != (mxcsr & 0xFFC0)) __asm__ volatile("ldmxcsr %0" : : "m"(mxcsr)); +} #elif defined(__aarch64__) #define OKRUN_IMAGE_ABI inline constexpr char okrun_arch[] = "aarch64"; +inline unsigned long long okrun_fp_enter() { + unsigned long long fpcr = 0; + __asm__ volatile("mrs %0, fpcr" : "=r"(fpcr)); + if (fpcr != 0) __asm__ volatile("msr fpcr, %0" : : "r"(0ULL)); + return fpcr; +} +inline void okrun_fp_leave(unsigned long long state) { + unsigned long long fpcr = 0; + __asm__ volatile("mrs %0, fpcr" : "=r"(fpcr)); + if (fpcr != state) __asm__ volatile("msr fpcr, %0" : : "r"(state)); +} #else #define OKRUN_IMAGE_ABI inline constexpr char okrun_arch[] = ""; +inline unsigned long long okrun_fp_enter() { return 0; } +inline void okrun_fp_leave(unsigned long long) {} #endif diff --git a/run/src/okrun.cppm b/run/src/okrun.cppm index bd93f40..324a6f8 100644 --- a/run/src/okrun.cppm +++ b/run/src/okrun.cppm @@ -17,6 +17,8 @@ export using ::okrun_open_library; export using ::okrun_open_library_bytes; export using ::okrun_symbol; export using ::okrun_close; +export using ::okrun_call_enter; +export using ::okrun_call_leave; export using ::okrun_profile; export using ::okrun_cache_list; export using ::okrun_cache_reclaim; diff --git a/run/src/run.cpp b/run/src/run.cpp index e078f7a..80b183f 100644 --- a/run/src/run.cpp +++ b/run/src/run.cpp @@ -1250,7 +1250,13 @@ void start_making(const local_place& lp) { using image_function = void (OKRUN_IMAGE_ABI*)(void); using image_main = int (OKRUN_IMAGE_ABI*)(void); -void call_image(void* fn, void*) { reinterpret_cast(fn)(); } +// An initialiser or finaliser, in the image's floating-point state and then the +// caller's again: a library's are called on a thread of the program that opens it. +void call_image(void* fn, void*) { + const unsigned long long state = okrun_fp_enter(); + reinterpret_cast(fn)(); + okrun_fp_leave(state); +} // --- the runner that makes a snapshot ------------------------------------------------ @@ -1607,6 +1613,8 @@ int enter(search& s, const void* bytes, unsigned long long size, const char* nam profile_note("snapshot", "not used (OKRUN_SNAPSHOT=0)", 0); } profile_begin_run(); + // The image's floating-point state on this thread, from here to its end. + okrun_fp_enter(); if (from_snapshot) { // THE SECOND PHASE: the C library takes what this start supplies. A // measurement of the start's phases names it. @@ -1938,6 +1946,9 @@ bool has_separator(const char* s) { for (; *s; ++s) if (separator(*s)) return tr } // namespace +extern "C" unsigned long long okrun_call_enter(void) { return okrun_fp_enter(); } +extern "C" void okrun_call_leave(unsigned long long state) { okrun_fp_leave(state); } + extern "C" okrun_library* okrun_open_library(const char* path, const okrun_options* options, char* why, unsigned long long cap) { okrun_library* lib = new_library(options); diff --git a/tools/mcpp.toml b/tools/mcpp.toml index 80c09ac..63399e3 100644 --- a/tools/mcpp.toml +++ b/tools/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-tools" -version = "0.5.2" +version = "0.5.3" description = "okrun, which runs an ok image on this system, and okld, the format's tool on a build machine (check, note, sign, inspect)." license = "Apache-2.0" authors = ["mcpplibs"]