Skip to content

Commit a6cecdb

Browse files
committed
test: verify hosted openkal thread isolation and teardown
1 parent cd6d44b commit a6cecdb

10 files changed

Lines changed: 237 additions & 2 deletions

‎.agents/docs/2026-10-08-llvm-2313-linux-aarch64-ecosystem-part2-design.md‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,3 +619,21 @@ macOS 生成 Mach-O 不等于任意 Apple 框架、SDK 或所有 POSIX 功能可
619619
Linux aarch64 编译器进程和宿主构建程序。286 的单个原生 Linux
620620
静态程序也不证明 ARM64 到 Windows/macOS/x86 Linux 的交叉链。
621621
只有新增宿主列及目标系统运行全部通过后,才能公布这条开发路线已验证。
622+
623+
### 14.4 2026-10-08 托管线程验收补充
624+
625+
既有 same-source 示例验证容器、格式化、异常捕获与栈展开,没有
626+
启动线程,不能作为第 14.2 节 Windows TLS/线程能力的证明。
627+
四宿主三目标矩阵另编译同一托管线程示例,复用同一 LLVM、运行时
628+
源码与构建缓存。原有示例继续覆盖其既定行为及裸机生态用途。
629+
630+
线程示例启动并等待两个线程,检查各线程与主线程的 thread_local
631+
状态隔离、线程退出后析构完成、原子同步,以及每个线程中的异常
632+
捕获与 RAII 展开。目标 runner 同时运行原有示例与线程示例,不
633+
安装编译器或额外 runtime。运行时源码 SHA 与线程示例源码 SHA256
634+
分别记录;四宿主使用相同来源是矩阵准入的一部分。
635+
636+
ARM64 原生门在 286 中复用已解析的依赖和缓存,构建并运行同一
637+
线程源码。发布后的 CN SubOS 门执行相同原生检查,并将线程交叉
638+
产物交给三个目标系统运行。此补充属于验收要求,跨平台通过结果
639+
另由执行记录陈述。

‎.agents/docs/2026-10-08-llvm-2313-part2-execution-and-dependencies.md‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,3 +350,24 @@ E2E 48 通过。该测试修正仍须最终提交的 CI 验证。
350350
索引 PR 当前提交其余 CI 完成。四宿主三目标实际运行、正式发布、
351351
CN 镜像及发布版 SubOS 消费继续作为独立准入项;本节原生成功不
352352
替代这些证据。
353+
354+
## 16. 2026-10-08 当前原生准入与托管线程覆盖补充
355+
356+
mcpp 提交 `cd6d44b80b1e794c2ff6499c72a3317aff94dedd` 的原生候选门
357+
[37708843400](https://github.com/mcpp-community/mcpp/actions/runs/37708843400)
358+
与同一索引 `84c27c3014e676f2175868627b93494d6bd442e8` 全部通过,
359+
包括 GNU 自举完整单测 147/147、四个真实索引成员及原生 openkal。
360+
同一提交主 CI 的两个 macOS E2E 48 分片均通过,验证第 15 节断言
361+
修正。其 ARM64 公开安装门仍等待索引交付,不计作候选消费失败。
362+
363+
生态覆盖复核发现原有 same-source 示例没有启动线程。原生 GNU
364+
线程测试与主线程异常测试均不能替代 openkal 的托管 TLS/线程
365+
证明。新增的共同线程源码与构建 helper 已接入现有四宿主三目标
366+
矩阵、原生 286、候选 ARM64 门及发布版 CN SubOS 门。
367+
368+
本地 LLVM 23.1.3 与当前 mcpp 实际构建并运行线程示例,使用
369+
openkal-llvm-runtime 提交 `b4198d346cf0682c95dc0e51b5c32b096843ee83`。
370+
两个线程的状态隔离、退出析构与异常展开检查通过。286 原生静态
371+
闭包与原有输出检查通过后,复用同一栈再次构建线程源码,运行
372+
输出 `openkal hosted threads: isolation, destructors and concurrent unwind ok`。
373+
新增源码在 ARM64 与三个外部目标系统上的执行仍须最终 CI 验证。
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/bin/env bash
2+
# Build a hosted companion beside an existing runtime checkout/cache. Running
3+
# the resulting binary belongs to the target runner, which needs no toolchain.
4+
set -euo pipefail
5+
if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then
6+
echo "usage: $0 RUNTIME_CHECKOUT TARGET [OUTPUT_BINARY_PATH_FILE]" >&2
7+
exit 2
8+
fi
9+
repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
10+
runtime_root=$(cd "$1" && pwd)
11+
target=$2
12+
output_file=${3:-}
13+
if [ -n "$output_file" ] && [[ "$output_file" != /* ]]; then
14+
output_file="$PWD/$output_file"
15+
fi
16+
source "$repo_root/tests/e2e/_toolchain_env.sh"
17+
mcpp_bin=${MCPP:-mcpp}
18+
if [[ "$mcpp_bin" == */* ]] && [[ "$mcpp_bin" != /* ]]; then
19+
mcpp_bin="$PWD/$mcpp_bin"
20+
fi
21+
llvm_toolchain=${LLVM_TOOLCHAIN:-llvm@$LLVM_VERSION}
22+
fixture="$runtime_root/examples/mcpp-hosted-threads"
23+
mkdir -p "$fixture/src"
24+
cp "$repo_root/tests/fixtures/openkal-hosted-threads/mcpp.toml" "$fixture/mcpp.toml"
25+
cp "$repo_root/tests/fixtures/openkal-hosted-threads/src/main.cpp" "$fixture/src/main.cpp"
26+
cd "$fixture"
27+
"$mcpp_bin" build --target "$target" --toolchain "$llvm_toolchain"
28+
binaries=()
29+
while IFS= read -r path; do
30+
binaries+=("$path")
31+
done < <(find "target/$target" -type f \( -name openkal-hosted-threads -o -name openkal-hosted-threads.exe \))
32+
if [ "${#binaries[@]}" -ne 1 ]; then
33+
echo "expected one hosted threads binary for $target, found ${#binaries[@]}" >&2
34+
exit 1
35+
fi
36+
binary="$fixture/${binaries[0]}"
37+
[ -z "$output_file" ] || printf '%s\n' "$binary" > "$output_file"
38+
printf 'OPENKAL_HOSTED_THREADS_BINARY=%s\n' "$binary"

‎.github/tools/check_published_arm64_cn_ecosystem.sh‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ cd mcpp-source
8080
bash tests/e2e/286_the_openkal_stack_still_builds.sh | tee "$work/openkal-native.log"
8181
! grep -q 'SKIP' "$work/openkal-native.log"
8282
grep -qF 'OK: the openkal stack builds, links statically and runs' "$work/openkal-native.log"
83+
grep -qxF 'openkal hosted threads: isolation, destructors and concurrent unwind ok' "$work/openkal-native.log"
8384
cd "$work"
8485
git clone --depth 1 https://github.com/mcpp-community/mcpp-index index-source
8586
git -C index-source rev-parse HEAD | tee "$work/index-source-sha.txt"
@@ -112,6 +113,7 @@ cd "$work/openkal-source/examples/same-source"
112113
readobj="$MCPP_HOME/registry/data/xpkgs/xim-x-llvm/23.1.3/bin/llvm-readobj"
113114
[[ -x "$readobj" ]] || { echo 'LLVM readobj unavailable'; exit 1; }
114115
mkdir -p "$work/cross"
116+
sha256sum "$work/mcpp-source/tests/fixtures/openkal-hosted-threads/src/main.cpp" | cut -d ' ' -f 1 > "$work/cross/threads-source-sha256.txt"
115117
for target in x86_64-linux-gnu aarch64-macos x86_64-windows-gnu; do
116118
rm -rf target
117119
"$MCPP" build --target "$target" --toolchain llvm@23.1.3 | tee "$work/cross/$target.build.log"
@@ -131,6 +133,24 @@ for target in x86_64-linux-gnu aarch64-macos x86_64-windows-gnu; do
131133
esac
132134
cp "${artifacts[0]}" "$work/cross/$output"
133135
(cd "$work/cross" && sha256sum "$output") >> "$work/cross/SHA256SUMS"
136+
LLVM_TOOLCHAIN=llvm@23.1.3 bash "$work/mcpp-source/.github/tools/check_openkal_hosted_threads.sh" \
137+
"$work/openkal-source" "$target" "$work/threads-binary.txt"
138+
threads_binary=$(cat "$work/threads-binary.txt")
139+
case "$target" in
140+
x86_64-windows-gnu) threads_output=windows-threads.exe ;;
141+
aarch64-macos) threads_output=macos-threads ;;
142+
*) threads_output=linux-threads ;;
143+
esac
144+
cp "$threads_binary" "$work/cross/$threads_output"
145+
"$readobj" --file-headers "$threads_binary" > "$work/cross/$target.threads.headers.txt"
146+
case "$target" in
147+
aarch64-macos) threads_arch=aarch64; threads_format='Format: Mach-O' ;;
148+
x86_64-windows-gnu) threads_arch=x86_64; threads_format='Format: COFF' ;;
149+
*) threads_arch=x86_64; threads_format='Format: elf64-x86-64' ;;
150+
esac
151+
grep -q "Arch: $threads_arch" "$work/cross/$target.threads.headers.txt"
152+
grep -q "$threads_format" "$work/cross/$target.threads.headers.txt"
153+
(cd "$work/cross" && sha256sum "$threads_output") >> "$work/cross/SHA256SUMS"
134154
done
135155
echo 'RELEASE BUILD PASS: CN install/default/new/build/run/pack, native openkal, four consumers and three target artifacts.'
136156
cp "$work/openkal-source-sha.txt" "$work/cross/source-sha.txt"

‎.github/workflows/ci-aarch64-fresh-install.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ jobs:
391391
bash tests/e2e/286_the_openkal_stack_still_builds.sh 2>&1 | tee "$RUNNER_TEMP/native-llvm-arm64/openkal.log"
392392
! grep -q '^SKIP:' "$RUNNER_TEMP/native-llvm-arm64/openkal.log"
393393
grep -qF 'OK: the openkal stack builds, links statically and runs' "$RUNNER_TEMP/native-llvm-arm64/openkal.log"
394+
grep -qxF 'openkal hosted threads: isolation, destructors and concurrent unwind ok' "$RUNNER_TEMP/native-llvm-arm64/openkal.log"
394395
- uses: actions/upload-artifact@v4
395396
if: always()
396397
with:

‎.github/workflows/ci-arm64-published-cn.yml‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ jobs:
9191
sha256sum -c SHA256SUMS
9292
grep -Eq '^[0-9a-f]{40}$' source-sha.txt
9393
grep -Eq '^[0-9a-f]{40}$' engine-release-sha.txt
94+
grep -Eq '^[0-9a-f]{64}$' threads-source-sha256.txt
9495
chmod +x "$TARGET_FILE"
9596
if [ "$TARGET_SYSTEM" = macos ]; then
9697
codesign -dv "$TARGET_FILE" 2>&1 | grep -q 'adhoc\|Signature'
@@ -101,9 +102,22 @@ jobs:
101102
grep -q 'caught: 42' output.log
102103
grep -q 'unwound: true' output.log
103104
grep -q 'import std over openkal: ok' output.log
105+
case "$TARGET_SYSTEM" in
106+
windows) threads_file=windows-threads.exe ;;
107+
*) threads_file="$TARGET_SYSTEM-threads" ;;
108+
esac
109+
chmod +x "$threads_file"
110+
if [ "$TARGET_SYSTEM" = macos ]; then
111+
codesign -dv "$threads_file" 2>&1 | grep -q 'adhoc\|Signature'
112+
fi
113+
"./$threads_file" > threads.log 2>&1
114+
cat threads.log
115+
grep -qxF 'openkal hosted threads: isolation, destructors and concurrent unwind ok' threads.log
104116
- uses: actions/upload-artifact@v4
105117
if: always()
106118
with:
107119
name: published-cn-arm64-target-run-${{ matrix.system }}
108-
path: art/output.log
120+
path: |
121+
art/output.log
122+
art/threads.log
109123
if-no-files-found: warn

‎.github/workflows/openkal-cross.yml‎

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,10 @@ jobs:
255255
mkdir -p "$RUNNER_TEMP/out"
256256
git -C "$RUNNER_TEMP/okl" rev-parse HEAD > "$RUNNER_TEMP/out/source-sha.txt"
257257
echo "openkal-llvm-runtime source SHA: $(cat "$RUNNER_TEMP/out/source-sha.txt")"
258+
python3 - "$GITHUB_WORKSPACE/tests/fixtures/openkal-hosted-threads/src/main.cpp" "$RUNNER_TEMP/out/threads-source-sha256.txt" <<'PYHASH'
259+
import hashlib, pathlib, sys
260+
pathlib.Path(sys.argv[2]).write_text(hashlib.sha256(pathlib.Path(sys.argv[1]).read_bytes()).hexdigest() + "\n")
261+
PYHASH
258262
BIN="${MCPP_HOME:-$HOME/.mcpp}/registry/data/xpkgs/xim-x-llvm/23.1.3/bin"
259263
READOBJ="$BIN/llvm-readobj"
260264
[ -x "$READOBJ" ] || READOBJ="$BIN/llvm-readobj.exe"
@@ -284,6 +288,18 @@ jobs:
284288
|| { echo "::error::$t emitted the wrong architecture"; cat "$RUNNER_TEMP/out/$t.headers.txt"; exit 1; }
285289
echo "${{ matrix.host }} → $t : $(ls -l "$a" | awk '{print $5}') bytes"
286290
cat "$RUNNER_TEMP/out/$t.headers.txt"
291+
MCPP="$MCPP_UNDER_TEST" LLVM_TOOLCHAIN=llvm@23.1.3 \
292+
bash "$GITHUB_WORKSPACE/.github/tools/check_openkal_hosted_threads.sh" \
293+
"$RUNNER_TEMP/okl" "$t" "$RUNNER_TEMP/threads-binary.txt"
294+
thread_bin=$(cat "$RUNNER_TEMP/threads-binary.txt")
295+
case "$t" in
296+
x86_64-windows-gnu) thread_output=windows-threads.exe ;;
297+
aarch64-macos) thread_output=macos-threads ;;
298+
*) thread_output=linux-threads ;;
299+
esac
300+
cp "$thread_bin" "$RUNNER_TEMP/out/$thread_output"
301+
"$READOBJ" --file-headers "$thread_bin" > "$RUNNER_TEMP/out/$t.threads.headers.txt"
302+
grep -q "Arch: $arch" "$RUNNER_TEMP/out/$t.threads.headers.txt"
287303
done
288304
289305
# THE ONLY CRITERION THE `builtins` TOKEN HAS, AND WHY IT IS HERE.
@@ -442,6 +458,8 @@ jobs:
442458
set -euo pipefail
443459
fail=0
444460
source_sha=""
461+
threads_sha=""
462+
mkdir -p run-evidence
445463
for host in linux linux-aarch64 macos windows; do
446464
sha_file="art/openkal-built-on-$host/source-sha.txt"
447465
if [ ! -f "$sha_file" ]; then
@@ -456,6 +474,10 @@ jobs:
456474
echo "::error::$host built source $sha, expected $source_sha"; fail=1; continue
457475
fi
458476
echo "openkal-llvm-runtime source SHA: $sha"
477+
probe_sha=$(tr -d '\r\n' < "art/openkal-built-on-$host/threads-source-sha256.txt")
478+
[[ "$probe_sha" =~ ^[0-9a-f]{64}$ ]] || { echo '::error::invalid hosted probe digest'; exit 1; }
479+
if [ -z "$threads_sha" ]; then threads_sha="$probe_sha"; fi
480+
[ "$probe_sha" = "$threads_sha" ] || { echo '::error::hosted probe sources differ'; exit 1; }
459481
bin="art/openkal-built-on-$host/${{ matrix.file }}"
460482
echo "──────── built on $host, running on ${{ matrix.system }} ────────"
461483
if [ ! -f "$bin" ]; then
@@ -474,16 +496,36 @@ jobs:
474496
echo "::error::built on $host: it did not run"; cat out.log; fail=1; continue
475497
fi
476498
cat out.log
499+
cp out.log "run-evidence/$host.same-source.log"
477500
ok=1
478501
grep -q 'sorted: 2 4 7' out.log || ok=0
479502
grep -q 'caught: 42' out.log || ok=0
480503
# The line a link cannot fake.
481504
grep -q 'unwound: true' out.log || ok=0
482505
grep -q 'import std over openkal: ok' out.log || ok=0
483506
[ "$ok" = 1 ] || { echo "::error::built on $host: wrong output"; fail=1; }
507+
case "${{ matrix.system }}" in
508+
windows) threads_file=windows-threads.exe ;;
509+
*) threads_file="${{ matrix.system }}-threads" ;;
510+
esac
511+
threads_bin="art/openkal-built-on-$host/$threads_file"
512+
chmod +x "$threads_bin"
513+
if [ "${{ matrix.system }}" = macos ]; then
514+
codesign -dv "$threads_bin" 2>&1 | grep -q 'adhoc\|Signature'
515+
fi
516+
"./$threads_bin" > "run-evidence/$host.threads.log" 2>&1
517+
cat "run-evidence/$host.threads.log"
518+
grep -qxF 'openkal hosted threads: isolation, destructors and concurrent unwind ok' "run-evidence/$host.threads.log"
484519
done
485520
[ "$fail" = 0 ] || exit 1
486-
echo "four builds, one system, same source SHA and four lines"
521+
echo "four builds, one system, identical sources, exceptions and hosted TLS/threads"
522+
523+
- uses: actions/upload-artifact@v4
524+
if: always()
525+
with:
526+
name: openkal-target-execution-${{ matrix.system }}
527+
path: run-evidence/*.log
528+
if-no-files-found: warn
487529

488530
# ──────────────────────────────────────────────────────────────────
489531
# The e2e scripts that BUILD the openkal ecosystem, on a runner that
@@ -695,6 +737,7 @@ jobs:
695737
bash tests/e2e/286_the_openkal_stack_still_builds.sh 2>&1 | tee native-openkal.log
696738
grep -qF "ok native aarch64 ELF" native-openkal.log
697739
grep -qF "OK: the openkal stack builds, links statically and runs" native-openkal.log
740+
grep -qxF 'openkal hosted threads: isolation, destructors and concurrent unwind ok' native-openkal.log
698741
699742
- uses: actions/upload-artifact@v4
700743
if: always()

‎tests/e2e/286_the_openkal_stack_still_builds.sh‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/_toolchain_env.sh"
2727
# static image with no interpreter and no reference to the host's loader.
2828
set -e
2929

30+
hosted_threads_source="$(cd "$(dirname "${BASH_SOURCE[0]}")/../fixtures/openkal-hosted-threads/src" && pwd)/main.cpp"
3031
MCPP="${MCPP:-mcpp}"
3132
work="$(mktemp -d)"
3233
trap 'rm -rf "$work"' EXIT
@@ -147,4 +148,20 @@ else
147148
exit 1
148149
fi
149150

151+
# Reuse the resolved native stack and cache for hosted thread/TLS execution.
152+
cp "$hosted_threads_source" src/main.cpp
153+
if ! out="$("$MCPP" build 2>&1)"; then
154+
echo "FAIL: the hosted threads companion did not build: $out"
155+
exit 1
156+
fi
157+
bin="$(find target -type f -name okstack | head -1)"
158+
[ -n "$bin" ] || { echo "FAIL: no hosted threads artefact"; exit 1; }
159+
if ! out="$("$bin" 2>&1)"; then
160+
echo "FAIL: the hosted threads companion did not run: $out"
161+
exit 1
162+
fi
163+
expected="openkal hosted threads: isolation, destructors and concurrent unwind ok"
164+
[ "$out" = "$expected" ] || { echo "FAIL: wrong hosted threads output: $out"; exit 1; }
165+
echo "$out"
166+
150167
echo "OK: the openkal stack builds, links statically and runs"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[package]
2+
name = "openkal-hosted-threads"
3+
version = "0.1.0"
4+
5+
[dependencies]
6+
openkal-llvm-runtime = { path = "../.." }
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import std;
2+
3+
namespace {
4+
std::atomic<unsigned> arrived{0};
5+
std::atomic<unsigned> handling{0};
6+
std::atomic<unsigned> destroyed{0};
7+
std::atomic<unsigned> unwound{0};
8+
std::atomic<unsigned> passed{0};
9+
10+
struct local_state {
11+
unsigned value = 0;
12+
~local_state() { if (value) destroyed.fetch_or(value); }
13+
};
14+
thread_local local_state local;
15+
16+
struct frame {
17+
unsigned bit;
18+
~frame() { unwound.fetch_or(bit); }
19+
};
20+
21+
void worker(unsigned bit) {
22+
const bool initially_zero = local.value == 0;
23+
local.value = bit;
24+
arrived.fetch_add(1, std::memory_order_release);
25+
while (arrived.load(std::memory_order_acquire) != 2) std::this_thread::yield();
26+
bool caught = false;
27+
try {
28+
frame guard{bit};
29+
throw bit;
30+
} catch (unsigned value) {
31+
caught = value == bit;
32+
handling.fetch_add(1, std::memory_order_release);
33+
// Keep both exception handlers alive together: their TLS exception
34+
// state must remain independent as each worker observes its own value.
35+
while (handling.load(std::memory_order_acquire) != 2) std::this_thread::yield();
36+
}
37+
if (initially_zero && caught && local.value == bit && (unwound.load() & bit))
38+
passed.fetch_or(bit);
39+
}
40+
}
41+
42+
int main() {
43+
local.value = 4;
44+
std::thread first(worker, 1);
45+
std::thread second(worker, 2);
46+
first.join();
47+
second.join();
48+
// Joining must complete each worker's TLS teardown, while main's TLS stays
49+
// alive and independent. A main-thread-only exception cannot prove this.
50+
if (passed.load() != 3 || unwound.load() != 3 || destroyed.load() != 3
51+
|| local.value != 4) {
52+
std::println("openkal hosted threads: FAILED ({}, {}, {}, {})",
53+
passed.load(), unwound.load(), destroyed.load(), local.value);
54+
return 1;
55+
}
56+
std::println("openkal hosted threads: isolation, destructors and concurrent unwind ok");
57+
}

0 commit comments

Comments
 (0)