Summary
The benchmarks job in .github/workflows/ci.yml switched from gcc-14 to clang-18 in commit 81cd0e4 because the GCC build of signalbench and packetstreambench was crashing with SIGILL (signal 4, exit code -4 from the Python runner) on the GitHub-hosted ubuntu-24.04 runner. This issue tracks the root cause.
Symptom
Build succeeds, binary runs to its hot loop, then dies with SIGILL.
[100%] Running icey microbenchmarks and writing bench/benchmarks.json
RuntimeError: /home/runner/work/icey/icey/build-bench/base/bench/signalbench failed with exit code -4
Scope
- Affects
signalbench and packetstreambench. Both exercise hot loops over Signal::emit / PacketStream emission.
- Compiler-specific: GCC 14.2.0 with
-DCMAKE_BUILD_TYPE=Release (-O3 -DNDEBUG).
- Platform-specific: only reproduces on the GitHub-hosted
ubuntu-24.04 runner. A local Linux GCC 14 container with the same source and flags runs cleanly. macOS clang (Apple) and clang-18 on the same GitHub runner both pass.
- Not seen in the regular Linux test jobs (
g++-14, clang++-18), the address/thread/undefined sanitizer jobs, the WebRTC + FFmpeg job, the macOS jobs, or the Windows MSVC job.
- First failure: 2026-04-21 (commit d3b3fee). Pre-dates all M4 work.
What was tried
- Per-target
-O2 -fno-tree-vectorize on signalbench (commit 3a589aa) — fixed signalbench, but packetstreambench then hit the same SIGILL.
- Generalised
-O2 -fno-tree-vectorize to all benches via icy_add_benchmark (commit 26a4c27) — packetstreambench still hit SIGILL.
- Switched the bench job from
gcc-14 to clang-18 (commit 81cd0e4) — passes.
The library-level GCC fallback in icy_add_benchmark (-O2 -fno-tree-vectorize) stays as defence in depth for anyone building bench binaries with GCC outside CI.
What's needed
Reproduce on a GCC-14 + ubuntu-24.04 runner profile, capture a core dump (ulimit -c unlimited) and objdump the failing instruction. Likely candidates given the symptom:
- GCC's auto-vectorizer or unswitch-loops pass emitting an instruction the runner CPU does not implement.
- A miscompile around the hot member-function-pointer call inside
Signal::emit (the recent linked-list-based slot storage in commit 0bed357 changed this hot path).
- A GCC trap (
ud2) for some new UB-detection path under -O3 interacting with the bench's tight loop.
Files
src/base/include/icy/signal.h
src/base/bench/signalbench.cpp
src/base/bench/packetstreambench.cpp
cmake/icey_modules.cmake (icy_add_benchmark, GCC fallback flags)
.github/workflows/ci.yml (benchmarks job)
Workaround in place
Bench job runs under clang-18. icey itself builds and tests cleanly under gcc-14 in the regular Linux job and under all sanitizers, so consumers compiling against icey with gcc are not affected.
Summary
The benchmarks job in
.github/workflows/ci.ymlswitched fromgcc-14toclang-18in commit 81cd0e4 because the GCC build ofsignalbenchandpacketstreambenchwas crashing with SIGILL (signal 4, exit code -4 from the Python runner) on the GitHub-hostedubuntu-24.04runner. This issue tracks the root cause.Symptom
Build succeeds, binary runs to its hot loop, then dies with SIGILL.
Scope
signalbenchandpacketstreambench. Both exercise hot loops overSignal::emit/PacketStreamemission.-DCMAKE_BUILD_TYPE=Release(-O3 -DNDEBUG).ubuntu-24.04runner. A local Linux GCC 14 container with the same source and flags runs cleanly. macOS clang (Apple) and clang-18 on the same GitHub runner both pass.g++-14,clang++-18), the address/thread/undefined sanitizer jobs, the WebRTC + FFmpeg job, the macOS jobs, or the Windows MSVC job.What was tried
-O2 -fno-tree-vectorizeonsignalbench(commit 3a589aa) — fixedsignalbench, butpacketstreambenchthen hit the same SIGILL.-O2 -fno-tree-vectorizeto all benches viaicy_add_benchmark(commit 26a4c27) —packetstreambenchstill hit SIGILL.gcc-14toclang-18(commit 81cd0e4) — passes.The library-level GCC fallback in
icy_add_benchmark(-O2 -fno-tree-vectorize) stays as defence in depth for anyone building bench binaries with GCC outside CI.What's needed
Reproduce on a GCC-14 + ubuntu-24.04 runner profile, capture a core dump (
ulimit -c unlimited) andobjdumpthe failing instruction. Likely candidates given the symptom:Signal::emit(the recent linked-list-based slot storage in commit 0bed357 changed this hot path).ud2) for some new UB-detection path under-O3interacting with the bench's tight loop.Files
src/base/include/icy/signal.hsrc/base/bench/signalbench.cppsrc/base/bench/packetstreambench.cppcmake/icey_modules.cmake(icy_add_benchmark, GCC fallback flags).github/workflows/ci.yml(benchmarksjob)Workaround in place
Bench job runs under clang-18. icey itself builds and tests cleanly under gcc-14 in the regular Linux job and under all sanitizers, so consumers compiling against icey with gcc are not affected.