Skip to content
Merged
48 changes: 16 additions & 32 deletions .github/workflows/continuous.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,59 +74,43 @@ jobs:
uses: SimenB/github-actions-cpu-cores@v2
id: cpu-cores

# FindSIMD, MeshFEMCore, and Tight-Inclusion all compile with
# -march=native, which bakes the building runner's ISA into every cached
# object file. ccache hashes the literal flag string, not what it expands
# to, so restored objects are only safe on a runner with the same ISA.
# The CPU model name has not been enough to guarantee that: EPYC 9V74
# runners restored each other's objects and crashed with SIGILL. On Linux
# we also hash the flags the compiler expands -march=native to, which is
# exactly what ends up in the object files. We keep the model name in the
# hash too, so the key is never weaker than before.
- name: CPU fingerprint (Linux/macOS)
if: runner.os != 'Windows'
run: |
if [ "$RUNNER_OS" = "Linux" ]; then
CPU=$(grep -m1 '^model name' /proc/cpuinfo | cut -d: -f2-)
ISA=$(echo | c++ -march=native -E -v - 2>&1 | grep -oE -- ' -m[a-z0-9=._+-]+' | sort -u | tr -d '\n')
echo "ISA:$ISA"
KEY=$(printf '%s\n%s' "$CPU" "$ISA" | sha256sum | cut -c1-12)
else
CPU=$(sysctl -n machdep.cpu.brand_string)
KEY=$(printf '%s' "$CPU" | shasum -a 256 | cut -c1-12)
fi
echo "CPU: $CPU"
echo "CPU_KEY=$KEY" >> "$GITHUB_ENV"

- name: CPU fingerprint (Windows)
if: runner.os == 'Windows'
# On Linux, explicitly build for x86-64-v3 instead of -march=native. This
# avoids issues with ccache restoring from a runner with a different ISA.
- name: Check x86-64-v3 support (Linux)
if: runner.os == 'Linux'
run: |
$cpu = Get-CimInstance Win32_Processor | Select-Object -First 1 -ExpandProperty Name
Write-Host "CPU: $cpu"
$stream = [IO.MemoryStream]::new([Text.Encoding]::UTF8.GetBytes($cpu))
$key = (Get-FileHash -InputStream $stream -Algorithm SHA256).Hash.Substring(0, 12)
"CPU_KEY=$key" | Out-File -FilePath $env:GITHUB_ENV -Append
for f in avx2 bmi1 bmi2 f16c fma abm movbe xsave; do
grep -qw "$f" /proc/cpuinfo || { echo "::error::Runner CPU lacks $f, which x86-64-v3 requires"; exit 1; }
done
echo "CPU: $(grep -m1 '^model name' /proc/cpuinfo | cut -d: -f2-)"
echo "SIMD_ARGS=-DSIMD_CXX_FLAGS=-march=x86-64-v3" >> "$GITHUB_ENV"

- name: Cache Build
id: cache-build
uses: actions/cache@v5
with:
path: ${{ env.CACHE_PATH }}
key: ${{ runner.os }}-${{ matrix.config }}-${{ env.CPU_KEY }}-cache
key: ${{ runner.os }}-${{ matrix.config }}-cache

- name: Prepare ccache
run: |
ccache --max-size=1.0G
ccache -V && ccache --show-config
ccache --show-stats && ccache --zero-stats

# Debug keeps the asserts (NDEBUG stays undefined) and -O0 but drops -g.
# Debug info made the Debug ccache about 20x larger than Release, and CI
# never reads it: assert and Catch2 messages carry __FILE__ and __LINE__.
- name: Configure (Linux/macOS)
if: runner.os != 'Windows'
run: |
mkdir -p build
cd build
cmake .. \
-DIPC_TOOLKIT_BUILD_TESTS=ON \
-DCMAKE_C_FLAGS_DEBUG=-O0 \
-DCMAKE_CXX_FLAGS_DEBUG=-O0 \
$SIMD_ARGS \
-DCMAKE_BUILD_TYPE=${{ matrix.config }}

- name: Configure (Windows)
Expand Down
29 changes: 11 additions & 18 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,35 +37,27 @@ jobs:
uses: SimenB/github-actions-cpu-cores@v2
id: cpu-cores

# FindSIMD, MeshFEMCore, and Tight-Inclusion all compile with
# -march=native, which bakes the building runner's ISA into every cached
# object file. ccache hashes the literal flag string, not what it expands
# to, so restored objects are only safe on a runner with the same ISA.
# The CPU model name has not been enough to guarantee that: EPYC 9V74
# runners restored each other's objects and crashed with SIGILL. We also
# hash the flags the compiler expands -march=native to, which is exactly
# what ends up in the object files. We keep the model name in the hash too,
# so the key is never weaker than before.
- name: CPU fingerprint
# On Linux, explicitly build for x86-64-v3 instead of -march=native. This
# avoids issues with ccache restoring from a runner with a different ISA.
- name: Check x86-64-v3 support
run: |
CPU=$(grep -m1 '^model name' /proc/cpuinfo | cut -d: -f2-)
ISA=$(echo | c++ -march=native -E -v - 2>&1 | grep -oE -- ' -m[a-z0-9=._+-]+' | sort -u | tr -d '\n')
echo "CPU: $CPU"
echo "ISA:$ISA"
echo "CPU_KEY=$(printf '%s\n%s' "$CPU" "$ISA" | sha256sum | cut -c1-12)" >> "$GITHUB_ENV"
for f in avx2 bmi1 bmi2 f16c fma abm movbe xsave; do
grep -qw "$f" /proc/cpuinfo || { echo "::error::Runner CPU lacks $f, which x86-64-v3 requires"; exit 1; }
done
echo "CPU: $(grep -m1 '^model name' /proc/cpuinfo | cut -d: -f2-)"

- name: Cache Build
id: cache-build
uses: actions/cache@v5
with:
path: ${{ env.CACHE_PATH }}
key: coverage-${{ env.CPU_KEY }}-cache
key: coverage-cache

- name: Prepare ccache
run: |
ccache --max-size=1.0G
ccache -V && ccache --show-config
ccache --show-stats && ccache --zero-stats\
ccache --show-stats && ccache --zero-stats

- name: Configure
run: |
Expand All @@ -74,7 +66,8 @@ jobs:
cmake .. \
-DIPC_TOOLKIT_BUILD_TESTS=ON \
-DIPC_TOOLKIT_WITH_CODE_COVERAGE=ON \
-DCMAKE_BUILD_TYPE=Release \
-DSIMD_CXX_FLAGS=-march=x86-64-v3 \
-DCMAKE_BUILD_TYPE=Release

- name: Build
run: |
Expand Down
37 changes: 19 additions & 18 deletions .github/workflows/cuda.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,45 +51,46 @@ jobs:
uses: SimenB/github-actions-cpu-cores@v2
id: cpu-cores

# FindSIMD, MeshFEMCore, and Tight-Inclusion all compile with
# -march=native, which bakes the building runner's ISA into every cached
# object file. ccache hashes the literal flag string, not what it expands
# to, so restored objects are only safe on a runner with the same ISA.
# The CPU model name has not been enough to guarantee that: EPYC 9V74
# runners restored each other's objects and crashed with SIGILL. We also
# hash the flags the compiler expands -march=native to, which is exactly
# what ends up in the object files. We keep the model name in the hash too,
# so the key is never weaker than before.
- name: CPU fingerprint
# On Linux, explicitly build for x86-64-v3 instead of -march=native. This
# avoids issues with ccache restoring from a runner with a different ISA.
- name: Check x86-64-v3 support
run: |
CPU=$(grep -m1 '^model name' /proc/cpuinfo | cut -d: -f2-)
ISA=$(echo | c++ -march=native -E -v - 2>&1 | grep -oE -- ' -m[a-z0-9=._+-]+' | sort -u | tr -d '\n')
echo "CPU: $CPU"
echo "ISA:$ISA"
echo "CPU_KEY=$(printf '%s\n%s' "$CPU" "$ISA" | sha256sum | cut -c1-12)" >> "$GITHUB_ENV"
for f in avx2 bmi1 bmi2 f16c fma abm movbe xsave; do
grep -qw "$f" /proc/cpuinfo || { echo "::error::Runner CPU lacks $f, which x86-64-v3 requires"; exit 1; }
done
echo "CPU: $(grep -m1 '^model name' /proc/cpuinfo | cut -d: -f2-)"

# The CUDA key gets its own prefix because continuous.yml uses the same
# <os>-<config>-<cpu> names, and cache entries are immutable. Whichever
# workflow saved first would otherwise own the entry for both builds.
# <os>-<config> names, and cache entries are immutable. Whichever workflow
# saved first would otherwise own the entry for both builds.
- name: Cache Build
id: cache-build
uses: actions/cache@v5
with:
path: ${{ env.CACHE_PATH }}
key: cuda-${{ runner.os }}-${{ matrix.config }}-${{ env.CPU_KEY }}-cache
key: cuda-${{ runner.os }}-${{ matrix.config }}-cache

- name: Prepare ccache
run: |
ccache --max-size=1.0G
ccache -V && ccache --show-config
ccache --show-stats && ccache --zero-stats

# Debug keeps the asserts (NDEBUG stays undefined) and -O0 but drops -g.
# Debug info made the Debug ccache about 20x larger than Release, and CI
# never reads it: assert and Catch2 messages carry __FILE__ and __LINE__.
# The CUDA Debug flags are emptied instead of set to -O0, which nvcc would
# also apply to the device code.
- name: Configure
run: |
mkdir -p build
cd build
cmake .. \
-DIPC_TOOLKIT_WITH_CUDA=ON \
-DSIMD_CXX_FLAGS=-march=x86-64-v3 \
-DCMAKE_C_FLAGS_DEBUG=-O0 \
-DCMAKE_CXX_FLAGS_DEBUG=-O0 \
-DCMAKE_CUDA_FLAGS_DEBUG= \
-DCMAKE_CUDA_ARCHITECTURES=75 \
-DSCALABLE_CCD_CUDA_ARCHITECTURES=75 \
-DIPC_TOOLKIT_BUILD_TESTS=ON \
Expand Down
37 changes: 29 additions & 8 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,24 @@ jobs:
- name: Requirements
run: |
sudo apt-get update
sudo apt-get install doxygen python3-sphinx graphviz
sudo apt-get install doxygen python3-sphinx graphviz ccache
echo 'CACHE_PATH=~/.cache/ccache' >> "$GITHUB_ENV"
python -m pip install --upgrade pip
pip install -r docs/requirements.txt

- name: Cache Build
id: cache-build
uses: actions/cache@v5
with:
path: ${{ env.CACHE_PATH }}
key: docs-ccache

- name: Prepare Ccache
run: |
ccache --max-size=1.0G
ccache -V && ccache --show-config
ccache --show-stats && ccache --zero-stats

- name: Copy release docs
run: |
git fetch --all --tags
Expand Down Expand Up @@ -90,14 +104,21 @@ jobs:
done
cd ..

# We build ipctk without ccache on purpose. Its dependencies compile with
# -march=native, and ccache hashes that flag string, not the CPU it expands
# to. A cached object could therefore be restored onto a runner that lacks
# some of its instructions, which crashed `import ipctk` with SIGILL when
# Sphinx started. The tradeoff is a cold build of about 5 minutes instead of
# about 1, which is fine because docs only build on pushes to main.
# On Linux, explicitly build for x86-64-v3 instead of -march=native. This
# avoids issues with ccache restoring from a runner with a different ISA.
- name: Check x86-64-v3 support
run: |
for f in avx2 bmi1 bmi2 f16c fma abm movbe xsave; do
grep -qw "$f" /proc/cpuinfo || { echo "::error::Runner CPU lacks $f, which x86-64-v3 requires"; exit 1; }
done
echo "CPU: $(grep -m1 '^model name' /proc/cpuinfo | cut -d: -f2-)"

- name: Build ipctk
run: pip install --verbose .
env:
CMAKE_ARGS: -DSIMD_CXX_FLAGS=-march=x86-64-v3
run: |
pip install --verbose .
ccache --show-stats

- name: Build docs
run: |
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ jobs:
CIBW_ARCHS_MACOS: ${{ matrix.arch }}
CIBW_BUILD: ${{ matrix.python-version }}-*
CIBW_SKIP: "cp38-* pp* *musllinux* *manylinux1* *-win32 *linux_i686 *linux_aarch64 *linux_ppc64le *linux_s390x"
# Without this the wheel takes -march=native from whichever runner built it,
# and can contain AVX-512 that users' CPUs lack. x86-64-v3 is the baseline
# the 1.6.0 wheels already required (AVX2, FMA, BMI1/2, LZCNT). The build
# runs in a manylinux container, so it has to go through cibuildwheel.
CIBW_ENVIRONMENT_LINUX: CMAKE_ARGS="-DSIMD_CXX_FLAGS=-march=x86-64-v3"

- uses: actions/upload-artifact@v7
with:
Expand Down
43 changes: 10 additions & 33 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,45 +64,22 @@ jobs:
choco install ccache
"CACHE_PATH=${env:LOCALAPPDATA}\ccache" | Out-File -FilePath $env:GITHUB_ENV -Append

# FindSIMD, MeshFEMCore, and Tight-Inclusion all compile with
# -march=native, which bakes the building runner's ISA into every cached
# object file. ccache hashes the literal flag string, not what it expands
# to, so restored objects are only safe on a runner with the same ISA.
# The CPU model name has not been enough to guarantee that: EPYC 9V74
# runners restored each other's objects and crashed with SIGILL. On Linux
# we also hash the flags the compiler expands -march=native to, which is
# exactly what ends up in the object files. We keep the model name in the
# hash too, so the key is never weaker than before.
- name: CPU fingerprint (Linux/macOS)
if: runner.os != 'Windows'
run: |
if [ "$RUNNER_OS" = "Linux" ]; then
CPU=$(grep -m1 '^model name' /proc/cpuinfo | cut -d: -f2-)
ISA=$(echo | c++ -march=native -E -v - 2>&1 | grep -oE -- ' -m[a-z0-9=._+-]+' | sort -u | tr -d '\n')
echo "ISA:$ISA"
KEY=$(printf '%s\n%s' "$CPU" "$ISA" | sha256sum | cut -c1-12)
else
CPU=$(sysctl -n machdep.cpu.brand_string)
KEY=$(printf '%s' "$CPU" | shasum -a 256 | cut -c1-12)
fi
echo "CPU: $CPU"
echo "CPU_KEY=$KEY" >> "$GITHUB_ENV"

- name: CPU fingerprint (Windows)
if: runner.os == 'Windows'
# On Linux, explicitly build for x86-64-v3 instead of -march=native. This
# avoids issues with ccache restoring from a runner with a different ISA.
- name: Check x86-64-v3 support (Linux)
if: runner.os == 'Linux'
run: |
$cpu = Get-CimInstance Win32_Processor | Select-Object -First 1 -ExpandProperty Name
Write-Host "CPU: $cpu"
$stream = [IO.MemoryStream]::new([Text.Encoding]::UTF8.GetBytes($cpu))
$key = (Get-FileHash -InputStream $stream -Algorithm SHA256).Hash.Substring(0, 12)
"CPU_KEY=$key" | Out-File -FilePath $env:GITHUB_ENV -Append
for f in avx2 bmi1 bmi2 f16c fma abm movbe xsave; do
grep -qw "$f" /proc/cpuinfo || { echo "::error::Runner CPU lacks $f, which x86-64-v3 requires"; exit 1; }
done
echo "CPU: $(grep -m1 '^model name' /proc/cpuinfo | cut -d: -f2-)"

- name: Cache Build
id: cache-build
uses: actions/cache@v5
with:
path: ${{ env.CACHE_PATH }}
key: ${{ runner.os }}-Python${{ matrix.python-version }}-${{ env.CPU_KEY }}-cache
key: ${{ runner.os }}-Python${{ matrix.python-version }}-cache

- name: Prepare Ccache
run: |
Expand All @@ -112,7 +89,7 @@ jobs:

- name: Build and Install
env:
IPCTK_WITH_SIMD: ${{ runner.os == 'Linux' && '0' || '1' }}
CMAKE_ARGS: ${{ runner.os == 'Linux' && '-DSIMD_CXX_FLAGS=-march=x86-64-v3' || '' }}
run: |
pip install --verbose . && ccache --show-stats

Expand Down
34 changes: 24 additions & 10 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -170,17 +170,31 @@ endif()

# SIMD support
if(IPC_TOOLKIT_WITH_SIMD)
# Figure out SIMD support
message(STATUS "Testing SIMD capabilities...")
find_package(SIMD)
if (SIMD_CXX_FLAGS)
message(STATUS "SIMD support found: ${SIMD_CXX_FLAGS}")
if(SIMD_CXX_FLAGS)
# The flags were given on the command line, or cached by an earlier
# configure, so we use them as they are instead of probing the host. We only
# check that the compiler accepts them. If they are rejected we stop with an
# error instead of falling back to a build without SIMD, because a silent
# fallback would let a typo in the flags pass as a working non-SIMD build.
include(ipc_toolkit_filter_flags)
ipc_toolkit_check_cxx_flags(SIMD_CXX_FLAGS SIMD_CXX_FLAGS_ARE_VALID)
if(NOT SIMD_CXX_FLAGS_ARE_VALID)
message(FATAL_ERROR "SIMD_CXX_FLAGS=\"${SIMD_CXX_FLAGS}\" is not accepted by the compiler. Unset it (-USIMD_CXX_FLAGS) to auto-detect the flags, or pass -DIPC_TOOLKIT_WITH_SIMD=OFF to build without SIMD.")
endif()
message(STATUS "Using SIMD flags: ${SIMD_CXX_FLAGS}")
else()
message(WARNING "SIMD support requested but not found. Continuing without SIMD.")
# We shadow the cache entry with a normal variable instead of forcing the
# cache off. Forcing would overwrite the user's request, and the option
# would stay OFF in this build directory even after SIMD becomes available.
set(IPC_TOOLKIT_WITH_SIMD OFF)
# Figure out SIMD support
message(STATUS "Testing SIMD capabilities...")
find_package(SIMD)
if (SIMD_CXX_FLAGS)
message(STATUS "SIMD support found: ${SIMD_CXX_FLAGS}")
else()
message(WARNING "SIMD support requested but not found. Continuing without SIMD.")
# We shadow the cache entry with a normal variable instead of forcing the
# cache off. Forcing would overwrite the user's request, and the option
# would stay OFF in this build directory even after SIMD becomes available.
set(IPC_TOOLKIT_WITH_SIMD OFF)
endif()
endif()
endif()

Expand Down
23 changes: 23 additions & 0 deletions cmake/ipc_toolkit/ipc_toolkit_filter_flags.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,29 @@ function(ipc_toolkit_filter_flags flags)
set(${flags} ${output_flags} PARENT_SCOPE)
endfunction()

# Check that the C++ compiler accepts all of `flags` together, and set `result`
# to TRUE or FALSE in the caller's scope. Both arguments are variable names.
#
# Unlike ipc_toolkit_filter_flags(), which tests each flag on its own and drops
# the ones it rejects, this tests the whole list at once and reports a single
# answer. We use it when the flags were chosen by the user, where silently
# dropping a flag would hide a mistake.
#
# We only compile a trivial program and never run it, so flags that target a
# different CPU than the one doing the build still pass. The answer is cached
# under a name built from the flags, so changing the flags re-runs the check
# instead of reusing an old result.
function(ipc_toolkit_check_cxx_flags flags result)
include(CheckCXXSourceCompiles)
string(MAKE_C_IDENTIFIER "${${flags}}" flags_id)
# CMAKE_REQUIRED_FLAGS wants one space-separated string, not a CMake list. We
# set it inside this function so it does not leak into the caller's scope.
string(REPLACE ";" " " CMAKE_REQUIRED_FLAGS "${${flags}}")
check_cxx_source_compiles(
"int main() { return 0; }" IPC_TOOLKIT_CXX_FLAGS_OK${flags_id})
set(${result} ${IPC_TOOLKIT_CXX_FLAGS_OK${flags_id}} PARENT_SCOPE)
endfunction()

# The nvcc counterpart of ipc_toolkit_filter_flags(): keep the flags of `flags`
# that nvcc's host compiler accepts, checked by actually compiling with nvcc and
# `-Xcompiler=<flag>`, and wrap each so it applies only to CUDA sources compiled
Expand Down
Loading