Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 131 additions & 8 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,45 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest]
build_type: [Release]
c_compiler: [gcc-12, clang-15, cl, emcc]
c_compiler: [gcc-12, clang-18, cl, emcc]
# One leg per WEAVER_VECTORIZE backend, so every op-codegen change is
# actually compiled (and, where the runner's CPU allows it, run)
# against all of ref/SSE/AVX/AVX2/AVX512 rather than just the SSE 4.2
# default. NEON is not covered here: none of the hosted runners below
# are ARM.
vectorize: ['Generic', 'SSE 4.2', 'AVX', 'AVX 2', 'AVX 512']
include:
- os: windows-latest
c_compiler: cl
- os: ubuntu-latest
c_compiler: gcc-12
cxx_compiler: g++-12
- os: ubuntu-latest
c_compiler: clang-15
cxx_compiler: clang++-15
c_compiler: clang-18
cxx_compiler: clang++-18
exclude:
- os: windows-latest
c_compiler: gcc-12
- os: windows-latest
c_compiler: clang-15
c_compiler: clang-18
- os: windows-latest
c_compiler: emcc
- os: ubuntu-latest
c_compiler: cl
# Emscripten's headers only emulate up to SSE 4.2 over wasm32 SIMD128;
# there is no __m256/__m512 support to target AVX/AVX2/AVX512 with.
- c_compiler: emcc
vectorize: 'AVX'
- c_compiler: emcc
vectorize: 'AVX 2'
- c_compiler: emcc
vectorize: 'AVX 512'

steps:
- uses: actions/checkout@v3

- name: Setup build toolchain configuration
if: "${{ matrix.c_compiler == 'gcc-12' || matrix.c_compiler == 'clang-15' }}"
if: "${{ matrix.c_compiler == 'gcc-12' || matrix.c_compiler == 'clang-18' }}"
run: |
echo "CC=${{ matrix.c_compiler }}" >> $GITHUB_ENV
echo "CXX=${{ matrix.cxx_compiler }}" >> $GITHUB_ENV
Expand Down Expand Up @@ -99,21 +113,130 @@ jobs:
-B ${{ steps.strings.outputs.build-output-dir }}
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
-DCMAKE_TOOLCHAIN_FILE=${{ steps.strings.outputs.generators-dir }}/conan_toolchain.cmake
-DWEAVER_VECTORIZE="${{ matrix.vectorize }}"

- name: Build
# Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }}

- name: Test
if: "${{ matrix.c_compiler != 'emcc' }}"
- name: Test (Linux)
if: "${{ runner.os == 'Linux' && matrix.c_compiler != 'emcc' }}"
working-directory: ${{ steps.strings.outputs.build-output-dir }}
# Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
run: ctest --build-config ${{ matrix.build_type }}
#
# AVX-512 is special-cased: GitHub-hosted runners don't guarantee AVX-512
# CPU support even though the build above always compiles it (that's what
# catches cross-backend interface bugs). If this runner's CPU lacks it,
# skip running rather than fail the job on an unrelated SIGILL.
run: |
if [ "${{ matrix.vectorize }}" = "AVX 512" ]; then
if ! grep -qE '\bavx512f\b' /proc/cpuinfo || ! grep -qE '\bavx512vl\b' /proc/cpuinfo || ! grep -qE '\bavx512dq\b' /proc/cpuinfo; then
echo "::warning::Skipping AVX-512 tests: this runner's CPU does not support avx512f/avx512vl/avx512dq."
exit 0
fi
fi
ctest --build-config ${{ matrix.build_type }}

- name: Test (Windows)
if: "${{ runner.os == 'Windows' }}"
working-directory: ${{ steps.strings.outputs.build-output-dir }}
shell: pwsh
# Same AVX-512 caveat as the Linux step above, checked via the .NET
# intrinsics support flag instead of /proc/cpuinfo.
run: |
if ("${{ matrix.vectorize }}" -eq "AVX 512") {
if (-not [System.Runtime.Intrinsics.X86.Avx512F]::IsSupported) {
Write-Host "::warning::Skipping AVX-512 tests: this runner's CPU does not support AVX-512."
exit 0
}
}
ctest --build-config ${{ matrix.build_type }}
exit $LASTEXITCODE

- name: Test (Emscripten)
if: "${{ matrix.c_compiler == 'emcc' }}"
working-directory: ${{ steps.strings.outputs.build-output-dir }}
# Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
run: node weaver_tests.js

# No GitHub-hosted runner is ARM, so the NEON backend is cross-compiled here
# on an x86 runner and its tests are run through QEMU user-mode emulation
# instead. CMAKE_CROSSCOMPILING_EMULATOR (set explicitly below, since Conan
# doesn't know about QEMU) makes both `cmake --build` and `ctest` transparently
# exec test binaries as e.g. `qemu-arm -L /usr/arm-linux-gnueabihf <binary>`.
build-arm:
runs-on: ubuntu-latest

strategy:
fail-fast: false

matrix:
include:
- arch: arm
triple: arm-linux-gnueabihf
qemu: qemu-arm
profile: cmake-conan/arm_profile
- arch: aarch64
triple: aarch64-linux-gnu
qemu: qemu-aarch64
profile: cmake-conan/aarch64_profile

steps:
- uses: actions/checkout@v3

- name: Install cross-compilation toolchain and QEMU
run: |
sudo apt-get update
sudo apt-get install -y qemu-user "gcc-12-${{ matrix.triple }}" "g++-12-${{ matrix.triple }}"

- name: Set reusable strings
id: strings
shell: bash
run: |
echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
echo "generators-dir=${{ github.workspace }}/build/build/Release/generators" >> "$GITHUB_OUTPUT"

- name: Cache pip packages
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('**/requirements.txt') }}
restore-keys: pip-${{ runner.os }}-

- name: Install Conan2
run: pip install conan

- name: Cache Conan dependencies
id: conan-cache
uses: actions/cache@v3
with:
path: ~/.conan2
key: conan2-${{ runner.os }}-${{ matrix.arch }}-${{ hashFiles('**/conanfile.*') }}
restore-keys: conan2-${{ runner.os }}-${{ matrix.arch }}-

- name: Detect default Conan profile
if: steps.conan-cache.outputs.cache-hit != 'true'
run: conan profile detect

- name: Install Conan Dependencies
run: >
conan install --build=missing -pr:h ${{ matrix.profile }} -of ${{ steps.strings.outputs.build-output-dir }} ${{ github.workspace }}

- name: Configure CMake
run: >
cmake -S ${{ github.workspace }}
-B ${{ steps.strings.outputs.build-output-dir }}
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_TOOLCHAIN_FILE=${{ steps.strings.outputs.generators-dir }}/conan_toolchain.cmake
-DWEAVER_VECTORIZE=NEON
-DCMAKE_CROSSCOMPILING_EMULATOR="${{ matrix.qemu }};-L;/usr/${{ matrix.triple }}"

- name: Build
run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config Release

- name: Test (QEMU)
working-directory: ${{ steps.strings.outputs.build-output-dir }}
# Runs transparently under QEMU via CMAKE_CROSSCOMPILING_EMULATOR above.
run: ctest --build-config Release --output-on-failure
45 changes: 42 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,23 @@ cmake_policy(SET CMP0048 NEW)

project(FloatWeaver VERSION 0.1.0.0 LANGUAGES CXX)

find_package(Eigen3 REQUIRED)
# Eigen3: use a Conan/system package if one is available; otherwise fall back
# to the "external/eigen" git submodule, so the project also builds with
# nothing more than `git submodule update --init`.
find_package(Eigen3 3.4 QUIET)
if(NOT TARGET Eigen3::Eigen)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/eigen/CMakeLists.txt")
message(STATUS "Eigen3 package not found, building from external/eigen submodule")
set(EIGEN3_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/eigen")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
find_package(Eigen3 3.4 MODULE REQUIRED)
else()
message(FATAL_ERROR
"Eigen3 was not found, and external/eigen is not checked out. Either "
"install Eigen3 (e.g. 'conan install .') or run "
"'git submodule update --init external/eigen'.")
endif()
endif()

include(cmake/config.cmake)

Expand Down Expand Up @@ -88,7 +104,21 @@ weaver_target_sources(weaver "." ${SRC})

# Only build tests when building as standalone project
if("^${CMAKE_SOURCE_DIR}$" STREQUAL "^${PROJECT_SOURCE_DIR}$")
find_package(Catch2 REQUIRED)
# Catch2: use a Conan/system package if one is available; otherwise fall
# back to the "external/catch2" git submodule.
find_package(Catch2 3 QUIET)
if(NOT TARGET Catch2::Catch2WithMain)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/catch2/CMakeLists.txt")
message(STATUS "Catch2 package not found, building from external/catch2 submodule")
add_subdirectory(external/catch2 EXCLUDE_FROM_ALL)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/external/catch2/extras")
else()
message(FATAL_ERROR
"Catch2 was not found, and external/catch2 is not checked out. Either "
"install Catch2 (e.g. 'conan install .') or run "
"'git submodule update --init external/catch2'.")
endif()
endif()

add_executable(weaver_tests "")
set(WEAVER_TEST_SRC
Expand All @@ -97,6 +127,9 @@ if("^${CMAKE_SOURCE_DIR}$" STREQUAL "^${PROJECT_SOURCE_DIR}$")
src/tests/int.cpp

src/tests/common.h
src/tests/common_bool.h
src/tests/common_float.h
src/tests/common_int.h

src/tests/conditional.cpp
src/tests/eigen_alignedbox.cpp
Expand All @@ -116,7 +149,13 @@ if("^${CMAKE_SOURCE_DIR}$" STREQUAL "^${PROJECT_SOURCE_DIR}$")
)
target_link_libraries(weaver_tests weaver Catch2::Catch2WithMain)

if(NOT CMAKE_CROSSCOMPILING)
# catch_discover_tests() needs to run the freshly-built test executable to
# enumerate its test cases. When cross-compiling that only works if CMake
# knows how to execute foreign-architecture binaries, i.e. when
# CMAKE_CROSSCOMPILING_EMULATOR is set (e.g. "qemu-arm -L ..." for a NEON
# build cross-compiled for ARM/AArch64). Plain Emscripten builds don't set
# it -- CI drives those through `node weaver_tests.js` directly instead.
if(NOT CMAKE_CROSSCOMPILING OR (CMAKE_CROSSCOMPILING_EMULATOR AND NOT EMSCRIPTEN))
include(CTest)
include(Catch)
catch_discover_tests(weaver_tests)
Expand Down
11 changes: 11 additions & 0 deletions cmake-conan/aarch64_profile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[settings]
os=Linux
arch=armv8
compiler=gcc
compiler.version=12
compiler.libcxx=libstdc++11
compiler.cppstd=17
build_type=Release

[conf]
tools.build:compiler_executables={"c": "aarch64-linux-gnu-gcc-12", "cpp": "aarch64-linux-gnu-g++-12"}
11 changes: 11 additions & 0 deletions cmake-conan/arm_profile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[settings]
os=Linux
arch=armv7hf
compiler=gcc
compiler.version=12
compiler.libcxx=libstdc++11
compiler.cppstd=17
build_type=Release

[conf]
tools.build:compiler_executables={"c": "arm-linux-gnueabihf-gcc-12", "cpp": "arm-linux-gnueabihf-g++-12"}
57 changes: 57 additions & 0 deletions cmake/FindEigen3.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# - Try to find Eigen3, without relying on an Eigen3Config.cmake being installed.
#
# Used as a fallback when Eigen3 isn't available as a package (Conan, system
# install): point EIGEN3_INCLUDE_DIR at a source checkout (e.g. the
# external/eigen git submodule) before calling find_package(Eigen3 MODULE),
# and this module will pick up the version from the headers directly.
#
# Once done this will define
#
# EIGEN3_FOUND - Eigen3 was found
# EIGEN3_INCLUDE_DIR - the Eigen3 include directory
# EIGEN3_VERSION - Eigen3 version
# Eigen3::Eigen - imported target, usable like the Conan/CMake package
#
# Reads hints about search locations from the EIGEN3_ROOT / EIGEN3_ROOT_DIR
# environment variables when EIGEN3_INCLUDE_DIR isn't already set.

if(NOT EIGEN3_INCLUDE_DIR)
find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library
HINTS
ENV EIGEN3_ROOT
ENV EIGEN3_ROOT_DIR
PATHS
${CMAKE_INSTALL_PREFIX}/include
PATH_SUFFIXES eigen3 eigen
)
endif()

if(EIGEN3_INCLUDE_DIR AND EXISTS "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h")
file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header)

string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _ "${_eigen3_version_header}")
set(_eigen3_world_version "${CMAKE_MATCH_1}")
string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _ "${_eigen3_version_header}")
set(_eigen3_major_version "${CMAKE_MATCH_1}")
string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _ "${_eigen3_version_header}")
set(_eigen3_minor_version "${CMAKE_MATCH_1}")

set(EIGEN3_VERSION "${_eigen3_world_version}.${_eigen3_major_version}.${_eigen3_minor_version}")
unset(_eigen3_version_header)
unset(_eigen3_world_version)
unset(_eigen3_major_version)
unset(_eigen3_minor_version)
endif()

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Eigen3
REQUIRED_VARS EIGEN3_INCLUDE_DIR
VERSION_VAR EIGEN3_VERSION
)

if(EIGEN3_FOUND AND NOT TARGET Eigen3::Eigen)
add_library(Eigen3::Eigen INTERFACE IMPORTED)
target_include_directories(Eigen3::Eigen INTERFACE "${EIGEN3_INCLUDE_DIR}")
endif()

mark_as_advanced(EIGEN3_INCLUDE_DIR)
5 changes: 4 additions & 1 deletion cmake/config.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ function(weaver_configure_target tgt)

# Make SIMD available
if(WEAVER_VECTORIZE_AVX512)
target_compile_options(${tgt} PUBLIC "-mavx512f" "-mavx512vl" "-mavx512dq")
# Every real AVX512F CPU also has AVX2/FMA3; Eigen's
# ConfigureVectorization.h refuses to compile AVX-512 code without
# -mfma since GCC/Clang can otherwise miscompile it.
target_compile_options(${tgt} PUBLIC "-mavx512f" "-mavx512vl" "-mavx512dq" "-mavx2" "-mfma")
elseif(WEAVER_VECTORIZE_AVX2)
target_compile_options(${tgt} PUBLIC "-mavx2" "-mfma")
elseif(WEAVER_VECTORIZE_AVX)
Expand Down
1 change: 1 addition & 0 deletions include/weaver/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <cassert>
#include <cmath>
#include <type_traits>
#include <utility>

// FloatWeaver
#include <weaver/config.h>
Expand Down
4 changes: 3 additions & 1 deletion include/weaver/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,9 @@ extern "C"
# define WEAVER_VECTORIZE_SSE
# endif
# endif
# include <nmmintrin.h>
# ifdef WEAVER_VECTORIZE_SSE
# include <nmmintrin.h>
# endif
#endif

#if defined WEAVER_COMPILER_MSVC
Expand Down
6 changes: 3 additions & 3 deletions include/weaver/float16_neon.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ namespace weaver {
WEAVER_SIMD_BINARY_OP(operator+, vaddq_f32, 4)
WEAVER_SIMD_BINARY_OP(operator-, vsubq_f32, 4)
WEAVER_SIMD_BINARY_OP(operator*, vmulq_f32, 4)
WEAVER_SIMD_BINARY_OP(operator/, vdivq_f32, 4)
WEAVER_SIMD_BINARY_OP(operator/, weaver_vdivq_f32, 4)

WEAVER_SIMD_ASSIGN_OP(operator+=, vaddq_f32, 4)
WEAVER_SIMD_ASSIGN_OP(operator-=, vsubq_f32, 4)
WEAVER_SIMD_ASSIGN_OP(operator*=, vmulq_f32, 4)
WEAVER_SIMD_ASSIGN_OP(operator/=, vdivq_f32, 4)
WEAVER_SIMD_ASSIGN_OP(operator/=, weaver_vdivq_f32, 4)

WEAVER_SIMD_COMP_OP(operator==, vceqq_f32, 4)
WEAVER_SIMD_COMP_OP(operator!=, vcneqq_f32, 4)
Expand All @@ -40,7 +40,7 @@ namespace weaver {
WEAVER_SIMD_UNARY_OP(exp, vexpq_f32, 4)
WEAVER_SIMD_UNARY_OP(log, vlogq_f32, 4)
WEAVER_SIMD_UNARY_OP(sgn, vsgnq_f32, 4)
WEAVER_SIMD_UNARY_OP(sqrt, vsqrtq_f32, 4)
WEAVER_SIMD_UNARY_OP(sqrt, weaver_vsqrtq_f32, 4)
WEAVER_SIMD_UNARY_OP(rcp, vrcpq_f32, 4)
WEAVER_SIMD_UNARY_OP(rsqrt, vrsqrtq_f32, 4)

Expand Down
Loading
Loading