Skip to content

GCC-14 + GitHub-hosted ubuntu-24.04 runner: SIGILL in benchmark inner loops under -O3 #325

Description

@auscaster

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions