Skip to content

Commit 2bd766b

Browse files
authored
openkal-windows: build under all three of this environment's toolchains (#1)
* 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. * 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. * ci: name wine's own binary * 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. * 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. * openkal-windows: align the buffer this environment writes its directory records into * 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. * 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.
1 parent f33cf25 commit 2bd766b

8 files changed

Lines changed: 468 additions & 110 deletions

File tree

.github/workflows/ci.yml

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
name: CI
2+
3+
# What this workflow asserts.
4+
#
5+
# conformance the suite in the specification package runs against this
6+
# implementation and every observation holds, under all three
7+
# of this environment's toolchains
8+
# surface the exported names are exactly the fifty-one the
9+
# specification lists
10+
# independence the objects reference no C runtime symbol
11+
#
12+
# This environment has two application binary interfaces and three toolchains
13+
# that reach them, and an implementation that built under one of the three would
14+
# not be an implementation for this environment. So all three are built: the two
15+
# that produce the environment's own ABI, and the one that produces the other.
16+
17+
on:
18+
push:
19+
branches: [main]
20+
pull_request:
21+
workflow_dispatch:
22+
23+
env:
24+
MCPP_VERSION: 2026.8.19.4
25+
XLINGS_VERSION: v2026.8.17.2
26+
XLINGS_NON_INTERACTIVE: '1'
27+
28+
jobs:
29+
# ---------------------------------------------------------------------------
30+
# On the system itself.
31+
conformance:
32+
name: conformance (${{ matrix.name }})
33+
runs-on: windows-2022
34+
timeout-minutes: 60
35+
strategy:
36+
fail-fast: false
37+
matrix:
38+
include:
39+
- { name: 'llvm, msvc ABI', toolchain: 'llvm@20.1.7', target: '' }
40+
- { name: 'msvc', toolchain: 'msvc@system', target: '' }
41+
- { name: 'gcc, gnu CRT', toolchain: 'gcc@16.1.0', target: 'x86_64-windows-gnu' }
42+
defaults:
43+
run:
44+
shell: bash
45+
steps:
46+
- uses: actions/checkout@v4
47+
48+
# The specification is checked out at the branch under test where it has
49+
# one, so that this run asserts what it is for: that the specification as
50+
# written there and this implementation as written here agree today.
51+
- name: The specification
52+
run: |
53+
git clone --quiet https://github.com/mcpplibs/openkal.git .spec
54+
branch='${{ github.head_ref || github.ref_name }}'
55+
if git -C .spec rev-parse --verify --quiet "origin/$branch" > /dev/null; then
56+
git -C .spec checkout --quiet "origin/$branch"
57+
echo "the specification is at $branch"
58+
else
59+
echo "the specification has no $branch; its default branch is used"
60+
fi
61+
62+
- name: Install xlings
63+
shell: pwsh
64+
run: |
65+
irm https://d2learn.org/xlings-install.ps1.txt | iex
66+
"$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
67+
68+
- name: Install mcpp
69+
run: |
70+
xlings update
71+
xlings install "mcpp@$MCPP_VERSION" -y -g
72+
mcpp --version
73+
mcpp self config --mirror GLOBAL
74+
75+
- name: Select the toolchain
76+
run: |
77+
spec='${{ matrix.toolchain }}'
78+
case "$spec" in
79+
msvc*) mcpp toolchain default msvc ;;
80+
*) mcpp toolchain install "${spec%@*}" "${spec#*@}"
81+
mcpp toolchain default "$spec" ;;
82+
esac
83+
mcpp toolchain list
84+
85+
- name: Every interface, every kind of examination
86+
run: |
87+
extra=''
88+
[ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}'
89+
bash .spec/tools/run-conformance.sh openkal-windows . full $extra
90+
91+
# ---------------------------------------------------------------------------
92+
# From a system that is not this one.
93+
#
94+
# The same package, produced by a cross build and run under an implementation
95+
# of this environment's interfaces rather than on the environment. It is here
96+
# for two reasons: it is where the exported names can be examined with tools
97+
# that read this environment's object format and are not on it, and a package
98+
# that only builds on the system it targets is a package a contributor on any
99+
# other system cannot work on.
100+
cross:
101+
name: cross build, run under wine, and examine the objects
102+
runs-on: ubuntu-24.04
103+
timeout-minutes: 60
104+
steps:
105+
- uses: actions/checkout@v4
106+
107+
- name: The specification
108+
run: |
109+
git clone --quiet https://github.com/mcpplibs/openkal.git .spec
110+
branch='${{ github.head_ref || github.ref_name }}'
111+
if git -C .spec rev-parse --verify --quiet "origin/$branch" > /dev/null; then
112+
git -C .spec checkout --quiet "origin/$branch"
113+
fi
114+
115+
- name: Install wine
116+
run: |
117+
sudo dpkg --add-architecture i386
118+
sudo apt-get update -qq
119+
# Which package carries it and what the program is called differ
120+
# between distributions and between releases of one, so both are
121+
# tried and the name that exists is the one used. A script that names
122+
# one of them fails with "command not found" after a successful
123+
# install, which reads as a missing package rather than a renamed
124+
# program.
125+
sudo apt-get install -y --no-install-recommends wine wine64 > /dev/null 2>&1 \
126+
|| sudo apt-get install -y --no-install-recommends wine > /dev/null
127+
runner="$(command -v wine || command -v wine64)"
128+
test -n "$runner" || { echo "no wine was installed" >&2; exit 1; }
129+
"$runner" --version
130+
echo "OPENKAL_CONFORMANCE_RUNNER=$runner" >> "$GITHUB_ENV"
131+
132+
- name: Install xlings
133+
run: |
134+
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh \
135+
| bash -s "$XLINGS_VERSION"
136+
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"
137+
138+
- name: Install mcpp
139+
run: |
140+
xlings update
141+
xlings install "mcpp@$MCPP_VERSION" -y -g
142+
mcpp self config --mirror GLOBAL
143+
144+
- name: Every interface, every kind of examination
145+
env:
146+
WINEDEBUG: '-all'
147+
run: |
148+
bash .spec/tools/run-conformance.sh openkal-windows . full \
149+
--target x86_64-windows-gnu
150+
151+
- name: The exported surface is complete and contains nothing else
152+
run: |
153+
# Clause 9.3. The list and the checker come from the specification
154+
# rather than from a copy kept here, so that the comparison has one
155+
# source. --complete because this implementation claims every
156+
# interface: a name it fails to export is a failure, not an interface
157+
# it declines to provide.
158+
rm -rf target && mcpp build --target x86_64-windows-gnu
159+
objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o' | tr '\n' ' ')"
160+
test -n "$objs" || { echo "no objects were found" >&2; exit 1; }
161+
bash .spec/tools/check-surface.sh --complete .spec/SURFACE.txt $objs
162+
163+
# The property this implementation exists to have. A program above openkal
164+
# may supply a C runtime of its own; an implementation that reached for
165+
# this environment's would have its calls resolve to the program's, and
166+
# the program's would resolve back here.
167+
- name: The objects reference no C runtime symbol
168+
run: |
169+
rm -rf target && mcpp build --features standalone --target x86_64-windows-gnu
170+
objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o')"
171+
test -n "$objs" || { echo "no objects were found; the check would pass vacuously" >&2; exit 1; }
172+
173+
# The permitted set, and why each entry is in it.
174+
#
175+
# __imp_* this environment's own interfaces, reached through
176+
# the import table. They are the environment, not a C runtime.
177+
# Nt*, Rtl* the object manager, likewise.
178+
# WaitOnAddress, WakeByAddress* the suspension primitive, which
179+
# this environment publishes without an import stub.
180+
# memcpy, memmove, memset, memcmp a compiler emits calls to these
181+
# from ordinary loops. They compute rather than call, so none of
182+
# them can re-enter this implementation.
183+
# __libc_start_main, main the hand-over, undefined here by
184+
# construction.
185+
# kal_*, _ZN3okw* the interface and this package.
186+
# __chkstk_ms, __udivti3, ... the compiler's own support
187+
# routines, which are in the compiler's library and not in a C
188+
# runtime.
189+
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_)$'
190+
bad=0
191+
for s in $(nm --undefined-only $objs | awk '{print $2}' | sort -u); do
192+
[ -n "$s" ] || continue
193+
printf '%s\n' "$s" | grep -qE "$permitted" || {
194+
echo "the implementation references a symbol it must not: $s" >&2
195+
bad=1
196+
}
197+
done
198+
test "$bad" -eq 0
199+
echo "the implementation references no C runtime symbol"
200+
201+
# A checker is only useful if it fails when it should.
202+
- name: The independence check detects a dependence
203+
run: |
204+
printf 'extern "C" int puts(const char*);\nextern "C" void okw_probe(void) { puts("x"); }\n' > src/probe.cpp
205+
rm -rf target && mcpp build --features standalone --target x86_64-windows-gnu
206+
objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o')"
207+
if ! nm --undefined-only $objs | awk '{print $2}' | grep -qx puts; then
208+
echo "the probe did not produce the reference it was written to produce" >&2
209+
rm -f src/probe.cpp; exit 1
210+
fi
211+
rm -f src/probe.cpp
212+
echo "a dependence upon a C runtime is visible to the check"

0 commit comments

Comments
 (0)