From 54bea593a16322f699bf35fb016af1951185fb4f Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 20 Aug 2026 09:34:20 +0800 Subject: [PATCH 1/8] openkal-windows: build under all three of this environment's toolchains This environment has two application binary interfaces and three toolchains that reach them. The package built under one. Three changes make it build under all three, and each of the three was a real assumption rather than a spelling. The width of a machine word was taken from a macro two of the three compilers publish. It is now taken from the specification, which resolves it for all three. A library was named in the spelling one of the two ABIs uses for it. The two spellings are now both present, each under the predicate that selects its ABI. Exceptions and run-time type information were disabled unconditionally, in a spelling one toolchain does not recognise. They are now disabled on the ABI where their absence is asserted --- the one where a program may have no C runtime to supply a personality routine --- and left to the toolchain's defaults on the ABI where a program has one. The third flag of that group, which suppressed the guard around a function-local static, is gone: every static in this package is initialised by a constant, so no guard was ever emitted. Continuous integration covers all three, and adds a job that cross-builds the package from a system that is not this one and runs the suite under an implementation of this environment's interfaces. That job is where the exported names are compared against SURFACE.txt and where the objects are asserted to reference no C runtime symbol, because the tools that read this object format are there and not here. --- .github/workflows/ci.yml | 203 +++++++++++++++++++++++++++++++++++++++ compile_commands.json | 86 ++++++++--------- mcpp.toml | 34 ++++++- src/win.h | 8 +- 4 files changed, 277 insertions(+), 54 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..fb757b9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,203 @@ +name: CI + +# What this workflow asserts. +# +# conformance the suite in the specification package runs against this +# implementation and every observation holds, under all three +# of this environment's toolchains +# surface the exported names are exactly the fifty-one the +# specification lists +# independence the objects reference no C runtime symbol +# +# This environment has two application binary interfaces and three toolchains +# that reach them, and an implementation that built under one of the three would +# not be an implementation for this environment. So all three are built: the two +# that produce the environment's own ABI, and the one that produces the other. + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +env: + MCPP_VERSION: 2026.8.19.4 + XLINGS_VERSION: v2026.8.17.2 + XLINGS_NON_INTERACTIVE: '1' + +jobs: + # --------------------------------------------------------------------------- + # On the system itself. + conformance: + name: conformance (${{ matrix.name }}) + runs-on: windows-2022 + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + include: + - { name: 'llvm, msvc ABI', toolchain: 'llvm@20.1.7', target: '' } + - { name: 'msvc', toolchain: 'msvc@system', target: '' } + - { name: 'gcc, gnu CRT', toolchain: 'gcc@16.1.0', target: 'x86_64-windows-gnu' } + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v4 + + # The specification is checked out at the branch under test where it has + # one, so that this run asserts what it is for: that the specification as + # written there and this implementation as written here agree today. + - name: The specification + run: | + git clone --quiet https://github.com/mcpplibs/openkal.git .spec + branch='${{ github.head_ref || github.ref_name }}' + if git -C .spec rev-parse --verify --quiet "origin/$branch" > /dev/null; then + git -C .spec checkout --quiet "origin/$branch" + echo "the specification is at $branch" + else + echo "the specification has no $branch; its default branch is used" + fi + + - name: Install xlings + shell: pwsh + run: | + irm https://d2learn.org/xlings-install.ps1.txt | iex + "$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + - name: Install mcpp + run: | + xlings update + xlings install "mcpp@$MCPP_VERSION" -y -g + mcpp --version + mcpp self config --mirror GLOBAL + + - name: Select the toolchain + run: | + spec='${{ matrix.toolchain }}' + case "$spec" in + msvc*) mcpp toolchain default msvc ;; + *) mcpp toolchain install "${spec%@*}" "${spec#*@}" + mcpp toolchain default "$spec" ;; + esac + mcpp toolchain list + + - name: Every interface, every kind of examination + run: | + extra='' + [ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}' + bash .spec/tools/run-conformance.sh openkal-windows . full $extra + + # --------------------------------------------------------------------------- + # From a system that is not this one. + # + # The same package, produced by a cross build and run under an implementation + # of this environment's interfaces rather than on the environment. It is here + # for two reasons: it is where the exported names can be examined with tools + # that read this environment's object format and are not on it, and a package + # that only builds on the system it targets is a package a contributor on any + # other system cannot work on. + cross: + name: cross build, run under wine, and examine the objects + runs-on: ubuntu-24.04 + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + + - name: The specification + run: | + git clone --quiet https://github.com/mcpplibs/openkal.git .spec + branch='${{ github.head_ref || github.ref_name }}' + if git -C .spec rev-parse --verify --quiet "origin/$branch" > /dev/null; then + git -C .spec checkout --quiet "origin/$branch" + fi + + - name: Install wine + run: | + sudo dpkg --add-architecture i386 + sudo apt-get update -qq + sudo apt-get install -y --no-install-recommends wine64 > /dev/null + wine64 --version + + - name: Install xlings + run: | + curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh \ + | bash -s "$XLINGS_VERSION" + echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" + + - name: Install mcpp + run: | + xlings update + xlings install "mcpp@$MCPP_VERSION" -y -g + mcpp self config --mirror GLOBAL + + - name: Every interface, every kind of examination + env: + OPENKAL_CONFORMANCE_RUNNER: wine64 + WINEDEBUG: '-all' + run: | + bash .spec/tools/run-conformance.sh openkal-windows . full \ + --target x86_64-windows-gnu + + - name: The exported surface is complete and contains nothing else + run: | + # Clause 9.3. The list and the checker come from the specification + # rather than from a copy kept here, so that the comparison has one + # source. --complete because this implementation claims every + # interface: a name it fails to export is a failure, not an interface + # it declines to provide. + rm -rf target && mcpp build --target x86_64-windows-gnu + objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o' | tr '\n' ' ')" + test -n "$objs" || { echo "no objects were found" >&2; exit 1; } + bash .spec/tools/check-surface.sh --complete .spec/SURFACE.txt $objs + + # The property this implementation exists to have. A program above openkal + # may supply a C runtime of its own; an implementation that reached for + # this environment's would have its calls resolve to the program's, and + # the program's would resolve back here. + - name: The objects reference no C runtime symbol + run: | + rm -rf target && mcpp build --features standalone --target x86_64-windows-gnu + objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o')" + test -n "$objs" || { echo "no objects were found; the check would pass vacuously" >&2; exit 1; } + + # The permitted set, and why each entry is in it. + # + # __imp_* this environment's own interfaces, reached through + # the import table. They are the environment, not a C runtime. + # Nt*, Rtl* the object manager, likewise. + # WaitOnAddress, WakeByAddress* the suspension primitive, which + # this environment publishes without an import stub. + # memcpy, memmove, memset, memcmp a compiler emits calls to these + # from ordinary loops. They compute rather than call, so none of + # them can re-enter this implementation. + # __libc_start_main, main the hand-over, undefined here by + # construction. + # kal_*, _ZN3okw* the interface and this package. + # __chkstk_ms, __udivti3, ... the compiler's own support + # routines, which are in the compiler's library and not in a C + # runtime. + permitted='^(__imp_[A-Za-z0-9_]+|Nt[A-Za-z]+|Rtl[A-Za-z]+|WaitOnAddress|WakeByAddress(All|Single)|memcpy|memmove|memset|memcmp|__libc_start_main|main|kal_[a-z_]+|_ZN3okw.*|_*chkstk[a-z_]*|__udiv[a-z0-9]*|__umod[a-z0-9]*|__divti3|__modti3|_GLOBAL_OFFSET_TABLE_)$' + bad=0 + for s in $(nm --undefined-only $objs | awk '{print $2}' | sort -u); do + [ -n "$s" ] || continue + printf '%s\n' "$s" | grep -qE "$permitted" || { + echo "the implementation references a symbol it must not: $s" >&2 + bad=1 + } + done + test "$bad" -eq 0 + echo "the implementation references no C runtime symbol" + + # A checker is only useful if it fails when it should. + - name: The independence check detects a dependence + run: | + printf 'extern "C" int puts(const char*);\nextern "C" void okw_probe(void) { puts("x"); }\n' > src/probe.cpp + rm -rf target && mcpp build --features standalone --target x86_64-windows-gnu + objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o')" + if ! nm --undefined-only $objs | awk '{print $2}' | grep -qx puts; then + echo "the probe did not produce the reference it was written to produce" >&2 + rm -f src/probe.cpp; exit 1 + fi + rm -f src/probe.cpp + echo "a dependence upon a C runtime is visible to the check" diff --git a/compile_commands.json b/compile_commands.json index c49da13..e5f4590 100644 --- a/compile_commands.json +++ b/compile_commands.json @@ -9,15 +9,14 @@ "-g", "-fno-exceptions", "-fno-rtti", - "-fno-threadsafe-statics", "-c", "/home/speak/workspace/github/mcpplibs/openkal-windows/src/abort.cpp", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/abort.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/abort.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal-windows/src/abort.cpp", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/abort.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/abort.o" }, { "arguments": [ @@ -29,15 +28,14 @@ "-g", "-fno-exceptions", "-fno-rtti", - "-fno-threadsafe-statics", "-c", "/home/speak/workspace/github/mcpplibs/openkal-windows/src/env.cpp", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/env.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/env.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal-windows/src/env.cpp", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/env.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/env.o" }, { "arguments": [ @@ -49,15 +47,14 @@ "-g", "-fno-exceptions", "-fno-rtti", - "-fno-threadsafe-statics", "-c", "/home/speak/workspace/github/mcpplibs/openkal-windows/src/fs.cpp", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/fs.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/fs.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal-windows/src/fs.cpp", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/fs.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/fs.o" }, { "arguments": [ @@ -69,15 +66,14 @@ "-g", "-fno-exceptions", "-fno-rtti", - "-fno-threadsafe-statics", "-c", "/home/speak/workspace/github/mcpplibs/openkal-windows/src/memory.cpp", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/memory.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/memory.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal-windows/src/memory.cpp", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/memory.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/memory.o" }, { "arguments": [ @@ -89,15 +85,14 @@ "-g", "-fno-exceptions", "-fno-rtti", - "-fno-threadsafe-statics", "-c", "/home/speak/workspace/github/mcpplibs/openkal-windows/src/process.cpp", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/process.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/process.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal-windows/src/process.cpp", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/process.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/process.o" }, { "arguments": [ @@ -109,15 +104,14 @@ "-g", "-fno-exceptions", "-fno-rtti", - "-fno-threadsafe-statics", "-c", "/home/speak/workspace/github/mcpplibs/openkal-windows/src/start.cpp", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/start.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/start.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal-windows/src/start.cpp", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/start.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/start.o" }, { "arguments": [ @@ -129,15 +123,14 @@ "-g", "-fno-exceptions", "-fno-rtti", - "-fno-threadsafe-statics", "-c", "/home/speak/workspace/github/mcpplibs/openkal-windows/src/stream.cpp", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/stream.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/stream.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal-windows/src/stream.cpp", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/stream.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/stream.o" }, { "arguments": [ @@ -149,15 +142,14 @@ "-g", "-fno-exceptions", "-fno-rtti", - "-fno-threadsafe-statics", "-c", "/home/speak/workspace/github/mcpplibs/openkal-windows/src/task.cpp", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/task.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/task.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal-windows/src/task.cpp", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/task.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/task.o" }, { "arguments": [ @@ -169,15 +161,14 @@ "-g", "-fno-exceptions", "-fno-rtti", - "-fno-threadsafe-statics", "-c", "/home/speak/workspace/github/mcpplibs/openkal-windows/src/time.cpp", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/time.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/time.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal-windows/src/time.cpp", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/time.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/time.o" }, { "arguments": [ @@ -189,15 +180,14 @@ "-g", "-fno-exceptions", "-fno-rtti", - "-fno-threadsafe-statics", "-c", "/home/speak/workspace/github/mcpplibs/openkal-windows/src/win.cpp", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/win.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/win.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal-windows/src/win.cpp", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/win.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/win.o" }, { "arguments": [ @@ -210,11 +200,11 @@ "-c", "/home/speak/workspace/github/mcpplibs/openkal/src/abort.cppm", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/mcpplibs_openkal/src/abort.m.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/mcpplibs_openkal/src/abort.m.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal/src/abort.cppm", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/mcpplibs_openkal/src/abort.m.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/mcpplibs_openkal/src/abort.m.o" }, { "arguments": [ @@ -227,11 +217,11 @@ "-c", "/home/speak/workspace/github/mcpplibs/openkal/src/env.cppm", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/mcpplibs_openkal/src/env.m.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/mcpplibs_openkal/src/env.m.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal/src/env.cppm", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/mcpplibs_openkal/src/env.m.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/mcpplibs_openkal/src/env.m.o" }, { "arguments": [ @@ -244,11 +234,11 @@ "-c", "/home/speak/workspace/github/mcpplibs/openkal/src/fs.cppm", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/mcpplibs_openkal/src/fs.m.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/mcpplibs_openkal/src/fs.m.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal/src/fs.cppm", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/mcpplibs_openkal/src/fs.m.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/mcpplibs_openkal/src/fs.m.o" }, { "arguments": [ @@ -261,11 +251,11 @@ "-c", "/home/speak/workspace/github/mcpplibs/openkal/src/memory.cppm", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/mcpplibs_openkal/src/memory.m.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/mcpplibs_openkal/src/memory.m.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal/src/memory.cppm", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/mcpplibs_openkal/src/memory.m.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/mcpplibs_openkal/src/memory.m.o" }, { "arguments": [ @@ -278,11 +268,11 @@ "-c", "/home/speak/workspace/github/mcpplibs/openkal/src/process.cppm", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/mcpplibs_openkal/src/process.m.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/mcpplibs_openkal/src/process.m.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal/src/process.cppm", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/mcpplibs_openkal/src/process.m.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/mcpplibs_openkal/src/process.m.o" }, { "arguments": [ @@ -295,11 +285,11 @@ "-c", "/home/speak/workspace/github/mcpplibs/openkal/src/stream.cppm", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/mcpplibs_openkal/src/stream.m.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/mcpplibs_openkal/src/stream.m.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal/src/stream.cppm", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/mcpplibs_openkal/src/stream.m.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/mcpplibs_openkal/src/stream.m.o" }, { "arguments": [ @@ -312,11 +302,11 @@ "-c", "/home/speak/workspace/github/mcpplibs/openkal/src/task.cppm", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/mcpplibs_openkal/src/task.m.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/mcpplibs_openkal/src/task.m.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal/src/task.cppm", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/mcpplibs_openkal/src/task.m.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/mcpplibs_openkal/src/task.m.o" }, { "arguments": [ @@ -329,11 +319,11 @@ "-c", "/home/speak/workspace/github/mcpplibs/openkal/src/time.cppm", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/mcpplibs_openkal/src/time.m.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/mcpplibs_openkal/src/time.m.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal/src/time.cppm", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/mcpplibs_openkal/src/time.m.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/mcpplibs_openkal/src/time.m.o" }, { "arguments": [ @@ -346,10 +336,10 @@ "-c", "/home/speak/workspace/github/mcpplibs/openkal/src/types.cppm", "-o", - "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/mcpplibs_openkal/src/types.m.o" + "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/mcpplibs_openkal/src/types.m.o" ], "directory": "/home/speak/workspace/github/mcpplibs/openkal-windows", "file": "/home/speak/workspace/github/mcpplibs/openkal/src/types.cppm", - "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/727304d7b32943b8/obj/mcpplibs_openkal/src/types.m.o" + "output": "/home/speak/workspace/github/mcpplibs/openkal-windows/target/x86_64-windows-gnu/653c8c4d83e7ebba/obj/mcpplibs_openkal/src/types.m.o" } ] diff --git a/mcpp.toml b/mcpp.toml index 34d2018..f4ce138 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -14,7 +14,6 @@ openkal = "0.5.0" # implements is declared by the specification package, which this package # includes and does not re-export. -[build] # The libraries this environment's own interfaces live in. # # ntdll carries the object manager, which is where a name relative to a @@ -23,12 +22,37 @@ openkal = "0.5.0" # operation that splits a command line into a vector, which is this # environment's own definition of what its argument vector is and is therefore # used rather than reimplemented. +# +# They are named twice because the two application binary interfaces this +# environment has spell a library differently: one names it as an argument to +# the linker and the other as an input file. Naming them once in the spelling of +# either would make this package build under two of the three toolchains it is +# written for. +[target.'cfg(all(windows, env = "gnu"))'.build] ldflags = ["-lntdll", "-lsynchronization", "-lshell32", "-lkernel32"] -flags = [ - { glob = "src/**", cxxflags = ["-fno-exceptions", "-fno-rtti", - "-fno-threadsafe-statics"] }, -] +# Exceptions and run-time type information, on the one ABI where their absence +# is asserted. +# +# This package throws nothing and has no virtual function, but a compiler still +# emits a landing pad for every function holding an object with a destructor, +# and each landing pad references its own runtime's personality routine. On this +# ABI the property that the objects reference no C runtime symbol is checked, and +# a program that selects `standalone' here has no such runtime to supply that +# routine --- so the landing pads are not emitted. +# +# On the other ABI the flags are absent, and deliberately. A program using that +# ABI has a C runtime; there the toolchain's defaults are correct, one of the two +# toolchains that reach it would not recognise these spellings, and openkal has +# nothing to say about how a program that has a runtime unwinds. +cxxflags = ["-fno-exceptions", "-fno-rtti"] + +[target.'cfg(all(windows, env = "msvc"))'.build] +ldflags = ["ntdll.lib", "synchronization.lib", "shell32.lib", "kernel32.lib"] + +# There is deliberately no guard flag for a function-local static. An earlier +# version disabled it; every static in this package is initialised by a constant, +# so no guard is emitted and the flag was doing nothing under any toolchain. [features] default = [] diff --git a/src/win.h b/src/win.h index 4f23df5..6ca0768 100644 --- a/src/win.h +++ b/src/win.h @@ -35,7 +35,13 @@ #include #include -using okw_uptr = __UINTPTR_TYPE__; +// The width of a machine word is taken from the specification rather than from +// the compiler, because the three compilers this package is built with do not +// all publish the compiler's own spelling of the type and openkal already +// resolves that. +#include + +using okw_uptr = kal_uintptr; using okw_u64 = unsigned long long; using okw_i64 = long long; using okw_u32 = unsigned int; From cfbea09bc1cb150d6ca73cc230d49dca18bcef11 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 20 Aug 2026 10:05:41 +0800 Subject: [PATCH 2/8] openkal-windows: the reserved name, and setting a file's modification time The object manager reads "." as a name to look up, finds no child so called, and reports that the argument is invalid --- where the other two environments reserve the same word and accept it wherever a name is accepted. What this one does accept is an empty name beside the directory's own handle, which denotes exactly the same thing, so the translation is in the one place every other difference between the two spellings of a name already is. kal_fs_set_modified is one record with one field written; every field of that record is a time and this environment reads a zero as "leave that one alone", so the record says nothing about the other three. --- src/fs.cpp | 21 ++++++++++++++++++--- src/task.cpp | 10 +++++----- src/win.cpp | 10 ++++++++++ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/fs.cpp b/src/fs.cpp index 80fc900..1ed43ad 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -228,7 +228,7 @@ kal_uintptr kal_fs_stream(kal_file f) { return h ? reinterpret_cast(h) : 0u; } -int kal_fs_seek(kal_file f, __INT64_TYPE__ offset, int whence, __UINT64_TYPE__* result) { +int kal_fs_seek(kal_file f, kal_i64 offset, int whence, kal_u64* result) { void* h = file_handle(f); if (!h) return kal_err_invalid; LARGE_INTEGER distance{}; distance.QuadPart = offset; @@ -238,11 +238,11 @@ int kal_fs_seek(kal_file f, __INT64_TYPE__ offset, int whence, __UINT64_TYPE__* else if (whence == KAL_SEEK_END) method = FILE_END; if (!SetFilePointerEx(h, distance, &arrived, method)) return okw::translate_win32(GetLastError()); - if (result) *result = static_cast<__UINT64_TYPE__>(arrived.QuadPart); + if (result) *result = static_cast(arrived.QuadPart); return kal_ok; } -int kal_fs_truncate(kal_file f, __UINT64_TYPE__ size) { +int kal_fs_truncate(kal_file f, kal_u64 size) { void* h = file_handle(f); if (!h) return kal_err_invalid; okw::io_status_block s{}; @@ -283,6 +283,21 @@ int kal_fs_file_info(kal_file f, kal_node_info* out) { return fill(h, out); } +int kal_fs_set_modified(kal_file f, kal_u64 modified_ns) { + void* h = file_handle(f); + if (!h) return kal_err_invalid; + okw::file_basic_information basic{}; + // Every field of this record is a time, and this environment reads a zero + // as "leave that one alone". So one field is written and the record says + // nothing about the other three, which is what the interface asked for. + basic.last_write_time = + static_cast(modified_ns / 100ull + kEpochDifference); + okw::io_status_block iosb{}; + const long r = okw::NtSetInformationFile(h, &iosb, &basic, sizeof basic, + okw::file_basic_information_class); + return okw::ok(r) ? kal_ok : okw::translate_nt(r); +} + int kal_fs_mkdir(kal_dir base, const char* name, kal_uintptr len) { void* root = dir_handle(base); if (!root || !okw::acceptable(name, len)) return kal_err_invalid; diff --git a/src/task.cpp b/src/task.cpp index 0bc1cfb..c15bc72 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -64,12 +64,12 @@ void kal_task_yield(void) { SwitchToThread(); } kal_uintptr kal_task_current(void) { return static_cast(GetCurrentThreadId()); } -int kal_task_wait(const __UINT32_TYPE__* word, __UINT32_TYPE__ expected, - __UINT64_TYPE__ timeout_ns) { - __UINT32_TYPE__ compare = expected; +int kal_task_wait(const kal_u32* word, kal_u32 expected, + kal_u64 timeout_ns) { + kal_u32 compare = expected; DWORD ms = INFINITE; if (timeout_ns != 0) { - const __UINT64_TYPE__ rounded = (timeout_ns + 999999ull) / 1000000ull; + const kal_u64 rounded = (timeout_ns + 999999ull) / 1000000ull; ms = rounded > 0xfffffffeull ? 0xfffffffeu : static_cast(rounded); } if (WaitOnAddress(const_cast(static_cast(word)), @@ -79,7 +79,7 @@ int kal_task_wait(const __UINT32_TYPE__* word, __UINT32_TYPE__ expected, return okw::translate_win32(e); } -int kal_task_wake(const __UINT32_TYPE__* word, kal_uintptr count, kal_uintptr* woken) { +int kal_task_wake(const kal_u32* word, kal_uintptr count, kal_uintptr* woken) { void* address = const_cast(static_cast(word)); if (count == 0) { if (woken) *woken = 0; return kal_ok; } if (count == 1) WakeByAddressSingle(address); diff --git a/src/win.cpp b/src/win.cpp index 635fb95..c644b26 100644 --- a/src/win.cpp +++ b/src/win.cpp @@ -46,6 +46,16 @@ wide_name::wide_name(const char* utf8, okw_uptr len) : ok(false) { string.buffer = buffer; string.length = 0; string.maximum = 0; + // The one reserved name, clause 7.12: "." denotes the directory itself. + // + // Two of the three environments openkal is implemented on reserve the same + // word in their own naming and accept it wherever a name is accepted. This + // one does not: its object manager reads "." as a name to look up, finds no + // child so called, and reports that the argument is invalid. What it does + // accept is an empty name beside the directory's own handle, which denotes + // exactly the same thing --- so the translation is here, where every other + // difference between the two spellings of a name already is. + if (len == 1 && utf8 != nullptr && utf8[0] == '.') len = 0; if (len == 0) { buffer[0] = 0; ok = true; return; } if (len > kMaxName / 2) return; const int produced = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, From f60c6d0841b6e07f5e3d75a7e18acdcae0dba1ca Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 20 Aug 2026 10:08:22 +0800 Subject: [PATCH 3/8] ci: name wine's own binary --- .github/workflows/ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb757b9..dc1c86f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,8 +116,12 @@ jobs: run: | sudo dpkg --add-architecture i386 sudo apt-get update -qq + # The package is named for the architecture and the program is not: + # this distribution's wine has one binary called `wine', and a script + # that calls `wine64' fails with "command not found" after a + # successful install. sudo apt-get install -y --no-install-recommends wine64 > /dev/null - wine64 --version + wine --version - name: Install xlings run: | @@ -133,7 +137,7 @@ jobs: - name: Every interface, every kind of examination env: - OPENKAL_CONFORMANCE_RUNNER: wine64 + OPENKAL_CONFORMANCE_RUNNER: wine WINEDEBUG: '-all' run: | bash .spec/tools/run-conformance.sh openkal-windows . full \ From c381298460d6623256aac992affb25363948469a Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 20 Aug 2026 10:10:32 +0800 Subject: [PATCH 4/8] openkal-windows: name the libraries where the ABI that needs them records it The manifest named them twice, once per application binary interface, and one of the two spellings is not a link-line argument at all: a bare `.lib' is a file name to a compiler driver of the GNU kind, and the two toolchains that produce this environment's own ABI are of two different kinds. The mechanism that ABI has is a directive the compiler records in the object it produces, and both of its toolchains read it. So the libraries are named in the source that uses them, and the manifest names them only for the ABI whose toolchain has no such mechanism. It is better than the arrangement it replaces for a reason beyond building at all: a library named in the object travels with it, so a program that links this package needs nothing in its own manifest, and the requirement cannot fall out of step with the source that creates it. --- mcpp.toml | 6 ++++-- src/win.cpp | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/mcpp.toml b/mcpp.toml index f4ce138..17da15c 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -47,8 +47,10 @@ ldflags = ["-lntdll", "-lsynchronization", "-lshell32", "-lkernel32"] # nothing to say about how a program that has a runtime unwinds. cxxflags = ["-fno-exceptions", "-fno-rtti"] -[target.'cfg(all(windows, env = "msvc"))'.build] -ldflags = ["ntdll.lib", "synchronization.lib", "shell32.lib", "kernel32.lib"] +# The other ABI names them in src/win.cpp instead, where its compilers record +# the requirement in the object they produce. A library named on the link line +# there would have to be named again by every program that links this package; +# a library named in the object travels with it. # There is deliberately no guard flag for a function-local static. An earlier # version disabled it; every static in this package is initialised by a constant, diff --git a/src/win.cpp b/src/win.cpp index c644b26..d5d2abb 100644 --- a/src/win.cpp +++ b/src/win.cpp @@ -1,6 +1,28 @@ #include "win.h" #include +// The libraries this environment's own interfaces live in, named for the ABI +// whose toolchains read a name from the object rather than from the link line. +// +// ntdll carries the object manager, which is where a name relative to a +// directory is opened --- the operation openkal declares and Win32 does not +// offer. synchronization carries the suspension primitive. shell32 carries the +// operation that splits a command line into a vector. +// +// It is here rather than in the manifest because the two ABIs this environment +// has do not merely spell a library differently: on one of them the compiler +// records the requirement in the object it produces, so a program that links +// this package needs nothing in its own manifest, and the requirement cannot +// fall out of step with the source that creates it. The other ABI has no such +// mechanism, and there the manifest names them. +#if defined(_MSC_VER) +#pragma comment(lib, "ntdll.lib") +#pragma comment(lib, "synchronization.lib") +#pragma comment(lib, "shell32.lib") +#pragma comment(lib, "kernel32.lib") +#endif + + namespace okw { okw_uptr length(const char* s) { okw_uptr n = 0; while (s && s[n]) ++n; return n; } From 0ec44eb033c0d9bfce7ff42c1ca756bd2ef135f8 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 20 Aug 2026 10:29:53 +0800 Subject: [PATCH 5/8] openkal-windows: an argument is not a name, and a name is not half a buffer Two bounds, both wrong, and the second only where a real environment is large enough to meet it. An argument and a named value were converted through the buffer names are converted through. A name is bounded by what a file system accepts; a search path is not, and one longer than that buffer reached the caller as "the argument is not valid" --- four operations away from the length that caused it. They are now converted straight into the buffer they are destined for, which also removes the substitution that had to be undone afterwards: that substitution is for names, and neither of these is a name. The name conversion itself refused anything longer than half its buffer. One byte of the caller's encoding never becomes more than one unit of this environment's --- a character outside the basic plane costs four bytes and two units, and every other costs at least as many bytes as units --- so the bound is the buffer's length and not half of it. The half was a guess. --- src/process.cpp | 42 ++++++++++++++++++++++++++++++------------ src/win.cpp | 7 ++++++- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/process.cpp b/src/process.cpp index d979f9a..7049e72 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -35,6 +35,25 @@ bool append_wide(wchar_t* out, okw_uptr cap, okw_uptr& at, const wchar_t* s, okw // passes one string and lets the started program split it. The quoting below is // the inverse of the splitting this environment defines, so that the two agree; // getting it wrong would alter the vector while appearing to pass it. +// One string of the caller's encoding, converted straight into the buffer it is +// destined for. +// +// Not through okw::wide_name, which is for names: a name is bounded by what a +// file system accepts and an argument or a named value is not. An environment's +// search path is routinely longer than any name, and converting it through a +// buffer sized for names refused it --- which reached the caller as "the +// argument is not valid", four operations away from the length that caused it. +bool append_utf8(wchar_t* out, okw_uptr cap, okw_uptr& at, const char* s, okw_uptr n) { + if (n == 0) return true; + if (at + n + 1 >= cap) return false; + const int produced = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s, + static_cast(n), out + at, + static_cast(cap - at - 1)); + if (produced <= 0) return false; + at += static_cast(produced); + return true; +} + bool append_quoted(wchar_t* out, okw_uptr cap, okw_uptr& at, const wchar_t* s, okw_uptr n) { if (!append_wide(out, cap, at, L"\"", 1)) return false; okw_uptr backslashes = 0; @@ -104,13 +123,16 @@ int kal_process_spawn(kal_dir base, okw_uptr used = 0; for (kal_uintptr i = 0; i < argc; ++i) { if (i && !append_wide(line, kCommandLine, used, L" ", 1)) return kal_err_no_space; - okw::wide_name w(argv[i], argv_lens[i]); - if (!w.ok) return kal_err_invalid; - // The separator substitution wide_name performs is for names, and an - // argument is not a name, so it is undone here. - for (okw_uptr k = 0; k < w.string.length / 2u; ++k) - if (argv[i][k] == '/') w.buffer[k] = L'/'; - if (!append_quoted(line, kCommandLine, used, w.buffer, w.string.length / 2u)) + // Converted into a scratch of its own so that it can be quoted, and + // quoted because this environment passes one string and lets the + // started program split it. Clause 7.6 requires the vector to arrive + // unaltered, and the quoting is the inverse of that splitting. + okw_uptr produced = 0; + wchar_t* one = line + used + 1; // beyond what is written + const okw_uptr room = kCommandLine - used - 2; + if (!append_utf8(one, room, produced, argv[i], argv_lens[i])) + return argv_lens[i] >= room ? kal_err_no_space : kal_err_invalid; + if (!append_quoted(line, kCommandLine, used, one, produced)) return kal_err_no_space; } line[used] = 0; @@ -121,11 +143,7 @@ int kal_process_spawn(kal_dir base, wchar_t* block = work->block; okw_uptr block_used = 0; for (kal_uintptr i = 0; i < envc; ++i) { - okw::wide_name w(envp[i], envp_lens[i]); - if (!w.ok) return kal_err_invalid; - for (okw_uptr k = 0; k < w.string.length / 2u; ++k) - if (envp[i][k] == '/') w.buffer[k] = L'/'; - if (!append_wide(block, kCommandLine, block_used, w.buffer, w.string.length / 2u)) + if (!append_utf8(block, kCommandLine, block_used, envp[i], envp_lens[i])) return kal_err_no_space; block[block_used++] = 0; } diff --git a/src/win.cpp b/src/win.cpp index d5d2abb..c5576ca 100644 --- a/src/win.cpp +++ b/src/win.cpp @@ -79,7 +79,12 @@ wide_name::wide_name(const char* utf8, okw_uptr len) : ok(false) { // difference between the two spellings of a name already is. if (len == 1 && utf8 != nullptr && utf8[0] == '.') len = 0; if (len == 0) { buffer[0] = 0; ok = true; return; } - if (len > kMaxName / 2) return; + // One byte of the caller's encoding never becomes more than one unit of + // this environment's: a character outside the basic plane costs four bytes + // and two units, and every other costs at least as many bytes as units. So + // the bound is the buffer's own length and not half of it. The half was a + // guess, and it refused names this environment accepts. + if (len >= kMaxName) return; const int produced = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8, static_cast(len), buffer, static_cast(kMaxName - 1)); From 8aeebeb557d329721296e6b13e9bd9a32bfb3b35 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 20 Aug 2026 10:34:06 +0800 Subject: [PATCH 6/8] openkal-windows: align the buffer this environment writes its directory records into --- src/fs.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/fs.cpp b/src/fs.cpp index 1ed43ad..a0bc145 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -126,7 +126,14 @@ struct listing { okw_uptr used; okw_uptr at; bool first; - unsigned char buffer[8192]; + // Aligned deliberately. The records this environment writes here begin with + // fields it reads as machine words, and it requires the buffer they are + // written into to be aligned for them. Placed after three words and a + // boolean, an array of bytes lands one past a word boundary, and the + // enumeration then reports nothing at all --- while an implementation of + // this environment's interfaces that does not check accepts it and returns + // the entries, which is the harder way to find this. + alignas(16) unsigned char buffer[8192]; // The name reported to the caller. It belongs to the enumeration rather // than to the context performing it: two contexts enumerating two // directories would otherwise share one buffer, and openkal requires that From f14a5e595a6aeae5782178064ce99b9eba17fdb3 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 20 Aug 2026 10:38:26 +0800 Subject: [PATCH 7/8] openkal-windows: a timeout is a floor, and wine is not always called wine64 This environment takes a whole number of milliseconds for a timed wait and measures it against a clock whose tick is longer than that, so a wait given thirty milliseconds returns after fifteen. A caller that asked to be suspended for a duration and was returned to before it elapsed has been given a wrong answer, and every timed wait built upon this one inherits it. The deadline is now computed once from the monotonic source and the wait re-entered until that source has passed it. Which package carries wine and what the program inside it is called differ between distributions and between releases of one. Both names are now tried and the one that exists is used, because a script that names one of them fails with "command not found" after a successful install --- which reads as a missing package rather than as a renamed program. --- .github/workflows/ci.yml | 19 +++++++++++------- src/task.cpp | 43 ++++++++++++++++++++++++++++++---------- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc1c86f..b59d6bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,12 +116,18 @@ jobs: run: | sudo dpkg --add-architecture i386 sudo apt-get update -qq - # The package is named for the architecture and the program is not: - # this distribution's wine has one binary called `wine', and a script - # that calls `wine64' fails with "command not found" after a - # successful install. - sudo apt-get install -y --no-install-recommends wine64 > /dev/null - wine --version + # Which package carries it and what the program is called differ + # between distributions and between releases of one, so both are + # tried and the name that exists is the one used. A script that names + # one of them fails with "command not found" after a successful + # install, which reads as a missing package rather than a renamed + # program. + sudo apt-get install -y --no-install-recommends wine wine64 > /dev/null 2>&1 \ + || sudo apt-get install -y --no-install-recommends wine > /dev/null + runner="$(command -v wine || command -v wine64)" + test -n "$runner" || { echo "no wine was installed" >&2; exit 1; } + "$runner" --version + echo "OPENKAL_CONFORMANCE_RUNNER=$runner" >> "$GITHUB_ENV" - name: Install xlings run: | @@ -137,7 +143,6 @@ jobs: - name: Every interface, every kind of examination env: - OPENKAL_CONFORMANCE_RUNNER: wine WINEDEBUG: '-all' run: | bash .spec/tools/run-conformance.sh openkal-windows . full \ diff --git a/src/task.cpp b/src/task.cpp index c15bc72..1604007 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -1,5 +1,6 @@ #include "win.h" #include +#include #include // Execution contexts, and the primitive they are built upon. @@ -66,17 +67,39 @@ kal_uintptr kal_task_current(void) { return static_cast(GetCurrentT int kal_task_wait(const kal_u32* word, kal_u32 expected, kal_u64 timeout_ns) { - kal_u32 compare = expected; - DWORD ms = INFINITE; - if (timeout_ns != 0) { - const kal_u64 rounded = (timeout_ns + 999999ull) / 1000000ull; - ms = rounded > 0xfffffffeull ? 0xfffffffeu : static_cast(rounded); + auto* address = const_cast(static_cast(word)); + + if (timeout_ns == 0) { + kal_u32 compare = expected; + if (WaitOnAddress(address, &compare, 4, INFINITE)) return kal_ok; + const unsigned long e = GetLastError(); + return e == ERROR_TIMEOUT ? kal_err_again : okw::translate_win32(e); + } + + // A timeout is a floor and not a hint. + // + // This environment takes a whole number of milliseconds and measures it + // against a clock whose tick is longer than that, so a wait given thirty + // milliseconds returns after fifteen. A caller that asked to be suspended + // for a duration and was returned to before it elapsed has been given a + // wrong answer, and every timed wait built upon this one inherits it. + // + // So the deadline is computed once from the monotonic source and the wait + // is re-entered until that source has passed it. The timeout is reported + // only when the time has genuinely gone. + const kal_u64 deadline = kal_time_monotonic() + timeout_ns; + for (;;) { + const kal_u64 now = kal_time_monotonic(); + if (now >= deadline) return kal_err_again; + const kal_u64 remaining = deadline - now; + const kal_u64 rounded = (remaining + 999999ull) / 1000000ull; + const DWORD ms = rounded > 0xfffffffeull ? 0xfffffffeu + : static_cast(rounded ? rounded : 1); + kal_u32 compare = expected; + if (WaitOnAddress(address, &compare, 4, ms)) return kal_ok; + const unsigned long e = GetLastError(); + if (e != ERROR_TIMEOUT) return okw::translate_win32(e); } - if (WaitOnAddress(const_cast(static_cast(word)), - &compare, 4, ms)) return kal_ok; - const unsigned long e = GetLastError(); - if (e == ERROR_TIMEOUT) return kal_err_again; - return okw::translate_win32(e); } int kal_task_wake(const kal_u32* word, kal_uintptr count, kal_uintptr* woken) { From f1eece5d27d04e0bf46f2d30fcbb65b412aaa906 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 20 Aug 2026 10:56:57 +0800 Subject: [PATCH 8/8] openkal-windows: supply every volume, because this system has no single root An implementation supplied the working directory and the volume that directory is on. On the other two systems that is enough, because a name on one device is still beneath the one root; here it is not. A program on one volume could not reach a program on another, and what a reader saw was "no such file" four operations after the resolution that had already failed. That is a confinement nobody chose. A hosted system does not confine an ordinary program, so the volumes this system has are what it supplies --- each opened by the whole name this environment uses and reported by as much of it as openkal's naming wants, so that the remainder of an absolute name beneath one does not begin with a separator. --- src/fs.cpp | 76 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/src/fs.cpp b/src/fs.cpp index a0bc145..67a3624 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -34,44 +34,68 @@ kal_duration to_nanoseconds(okw_i64 ticks) { // The supplied directories. // -// A hosted system does not confine an ordinary program, so it is supplied both -// the directory it was started in and the volume that directory is on. Each is -// reported under the name the environment knows it by, spelled the way openkal -// spells a name, because a C library above openkal must both resolve an -// absolute name and report one. +// A hosted system does not confine an ordinary program, so it is supplied the +// directory it was started in and every volume the system has. Each is reported +// under the name the environment knows it by, spelled the way openkal spells a +// name, because a C library above openkal must both resolve an absolute name +// and report one. +// +// Every volume, and not only the one the working directory is on. This system +// has no single root: a name on one volume is not beneath a name on another, +// and a program supplied only its own volume cannot reach a program installed +// elsewhere --- which is not a confinement anybody chose, and which the other +// two systems, having one root, do not impose. What made it visible was a +// program on one volume failing to start a program on another, reported four +// operations later as "no such file". struct preopen { char name[okw::kMaxName]; okw_uptr len; okw_uptr handle; }; +constexpr kal_uintptr kMaxPreopens = 27; // the working directory, and 26 volumes + preopen* table(kal_uintptr* count) { - static preopen t[2]; + static preopen t[kMaxPreopens]; + static kal_uintptr used = 0; static bool opened = false; if (!opened) { opened = true; - wchar_t cwd[okw::kMaxName]; - const DWORD n = GetCurrentDirectoryW(okw::kMaxName, cwd); - if (n > 0 && n < okw::kMaxName) { - t[0].len = okw::narrow(cwd, n, t[0].name, sizeof t[0].name); - void* h = CreateFileW(cwd, FILE_LIST_DIRECTORY | GENERIC_READ, + + // Opened by the whole name this environment uses, and reported by as + // much of it as openkal's naming wants: a volume is opened as "X:\\" + // and reported as "X:", so that the remainder of an absolute name + // beneath it does not begin with a separator. + const auto add = [](preopen& slot, const wchar_t* open_by, DWORD report) { + void* h = CreateFileW(open_by, FILE_LIST_DIRECTORY | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); - t[0].handle = h == INVALID_HANDLE_VALUE ? 0 : okw::pack(h); - - // The volume, named the way this environment names it. A program - // resolving an absolute name finds the longest supplied name that - // is a prefix of it, so both are needed and neither is a root in - // the sense another system would mean. - if (n >= 3 && cwd[1] == L':') { - wchar_t root[4] = { cwd[0], L':', L'\\', 0 }; - t[1].len = okw::narrow(root, 3, t[1].name, sizeof t[1].name); - void* r = CreateFileW(root, FILE_LIST_DIRECTORY | GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - nullptr, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, nullptr); - t[1].handle = r == INVALID_HANDLE_VALUE ? 0 : okw::pack(r); + if (h == INVALID_HANDLE_VALUE) return false; + slot.len = okw::narrow(open_by, report, slot.name, sizeof slot.name); + slot.handle = okw::pack(h); + return true; + }; + + wchar_t cwd[okw::kMaxName]; + const DWORD n = GetCurrentDirectoryW(okw::kMaxName, cwd); + if (n > 0 && n < okw::kMaxName && add(t[used], cwd, n)) ++used; + + // The volumes, in the form this environment reports them: a run of + // strings, each terminated, the run terminated again. + wchar_t volumes[512]; + const DWORD v = GetLogicalDriveStringsW(512, volumes); + if (v > 0 && v < 512) { + for (const wchar_t* p = volumes; *p && used < kMaxPreopens; ) { + DWORD length = 0; + while (p[length]) ++length; + // Reported as "X:\" and named here without the separator, so + // that a program resolving an absolute name finds the longest + // supplied name that is a prefix of it and the remainder does + // not begin with one. + const DWORD keep = (length >= 3 && p[length - 1] == L'\\') ? length - 1 : length; + if (add(t[used], p, keep)) ++used; + p += length + 1; } } } - if (count) *count = t[1].handle ? 2u : 1u; + if (count) *count = used; return t; }