Skip to content

Commit 5a7d696

Browse files
AntoinePrvserge-sans-paille
authored andcommitted
Add cpu_feature test
1 parent f9079f1 commit 5a7d696

File tree

7 files changed

+228
-8
lines changed

7 files changed

+228
-8
lines changed

.github/workflows/cross-sve.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ jobs:
4040
-DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/.github/toolchains/gcc-aarch64-linux-gnu.cmake
4141
- name: Build
4242
run: cmake --build _build
43+
- name: Set CPU feature test expectations
44+
run: |
45+
echo "XSIMD_TEST_CPU_ASSUME_SSE4_2=0" >> "$GITHUB_ENV"
46+
echo "XSIMD_TEST_CPU_ASSUME_NEON64=1" >> "$GITHUB_ENV"
47+
echo "XSIMD_TEST_CPU_ASSUME_SVE=1" >> "$GITHUB_ENV"
48+
echo "XSIMD_TEST_CPU_ASSUME_MANUFACTURER=unknown" >> "$GITHUB_ENV"
4349
- name: Testing xsimd
4450
run: qemu-aarch64 --cpu max,sve${{ matrix.vector_bits }}=on -L /usr/aarch64-linux-gnu/ ./test/test_xsimd
4551
working-directory: ${{ github.workspace }}/_build

.github/workflows/linux.yml

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,30 @@
1-
name: Linux build
1+
name: Linux x86 build
22
on: [push, pull_request]
33
concurrency:
44
group: ${{ github.workflow }}-${{ github.job }}-${{ github.ref }}
55
cancel-in-progress: true
66
defaults:
77
run:
88
shell: bash -l {0}
9+
910
jobs:
1011
build:
1112
runs-on: ubuntu-latest
1213
name: '${{ matrix.sys.compiler }} ${{ matrix.sys.version }} - ${{ matrix.sys.flags }}'
1314
strategy:
1415
matrix:
1516
sys:
16-
- { compiler: 'gcc', version: '12', flags: 'force_no_instr_set' }
17-
- { compiler: 'gcc', version: '13', flags: 'enable_xtl_complex' }
18-
- { compiler: 'gcc', version: '14', flags: 'avx' }
17+
- { compiler: 'gcc', version: '12', flags: 'force_no_instr_set' }
18+
- { compiler: 'gcc', version: '13', flags: 'enable_xtl_complex' }
19+
- { compiler: 'gcc', version: '14', flags: 'avx' }
1920
- { compiler: 'gcc', version: '13', flags: 'avx512' }
2021
- { compiler: 'gcc', version: '10', flags: 'avx512' }
2122
- { compiler: 'gcc', version: '12', flags: 'i386' }
2223
- { compiler: 'gcc', version: '13', flags: 'avx512pf' }
2324
- { compiler: 'gcc', version: '13', flags: 'avx512vbmi' }
2425
- { compiler: 'gcc', version: '14', flags: 'avx512vbmi2' }
2526
- { compiler: 'gcc', version: '13', flags: 'avx512vnni' }
26-
- { compiler: 'clang', version: '16', flags: 'force_no_instr_set' }
27+
- { compiler: 'clang', version: '16', flags: 'force_no_instr_set' }
2728
- { compiler: 'clang', version: '16', flags: 'enable_xtl_complex' }
2829
- { compiler: 'clang', version: '17', flags: 'avx' }
2930
- { compiler: 'clang', version: '17', flags: 'sse3' }
@@ -119,9 +120,17 @@ jobs:
119120
run: cmake --build _build
120121
- name: Test
121122
run: |
123+
# Set CPU feature test expectations, 0 is explicit absence of the feature
124+
export XSIMD_TEST_CPU_ASSUME_NEON64="0"
122125
cd _build/test
123126
if echo '${{ matrix.sys.flags }}' | grep -q 'avx512' ; then
127+
# Running with emulation, must have AVX512, lower tier are checked by implications in tests
128+
export XSIMD_TEST_CPU_ASSUME_AVX512F="1"
124129
../../sde-external-9.48.0-2024-11-25-lin/sde64 -tgl -- ./test_xsimd
125130
else
131+
export XSIMD_TEST_CPU_ASSUME_SSE4_2=$(grep -q 'sse4_2' /proc/cpuinfo && echo "1" || echo "0")
132+
export XSIMD_TEST_CPU_ASSUME_AVX=$(grep -q 'avx' /proc/cpuinfo && echo "1" || echo "0")
133+
export XSIMD_TEST_CPU_ASSUME_AVX512F=$(grep -q 'avx512f' /proc/cpuinfo && echo "1" || echo "0")
134+
export XSIMD_TEST_CPU_ASSUME_MANUFACTURER="intel,amd"
126135
./test_xsimd
127136
fi

.github/workflows/macos.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ on: [push, pull_request]
33
concurrency:
44
group: ${{ github.workflow }}-${{ github.job }}-${{ github.ref }}
55
cancel-in-progress: true
6+
67
jobs:
78
build:
89
strategy:
@@ -21,5 +22,16 @@ jobs:
2122
run: cmake --build _build --verbose
2223
- name: Testing sequential
2324
run: cmake --build _build --target xbenchmark --verbose
25+
- name: Set CPU feature test expectations
26+
run: |
27+
if echo '${{ matrix.os }}' | grep -q intel; then
28+
echo "XSIMD_TEST_CPU_ASSUME_NEON64=0" >> "$GITHUB_ENV"
29+
echo "XSIMD_TEST_CPU_ASSUME_SSE4_2=1" >> "$GITHUB_ENV"
30+
echo "XSIMD_TEST_CPU_ASSUME_MANUFACTURER=intel" >> "$GITHUB_ENV"
31+
else
32+
echo "XSIMD_TEST_CPU_ASSUME_NEON64=1" >> "$GITHUB_ENV"
33+
echo "XSIMD_TEST_CPU_ASSUME_SSE4_2=0" >> "$GITHUB_ENV"
34+
echo "XSIMD_TEST_CPU_ASSUME_MANUFACTURER=unknown" >> "$GITHUB_ENV"
35+
fi
2436
- name: Testing xsimd
2537
run: ${{github.workspace}}/_build/test/test_xsimd

.github/workflows/windows.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ on: [push, pull_request]
33
concurrency:
44
group: ${{ github.workflow }}-${{ github.job }}-${{ github.ref }}
55
cancel-in-progress: true
6+
67
jobs:
7-
build:
8+
build-windows-x86:
89
name: 'MSVC ${{ matrix.os }}, ${{ matrix.target }} ${{ matrix.sys.set }}'
910
defaults:
1011
run:
@@ -48,6 +49,13 @@ jobs:
4849
run: cmake --build _build
4950
- name: Testing xsimd
5051
if: ${{ !startsWith(matrix.sys.set, 'AVX512') }}
52+
env:
53+
# Set CPU feature test expectations
54+
# Assuming the runner always has AVX2 (independent of compilation option)
55+
XSIMD_TEST_CPU_ASSUME_NEON64: "0"
56+
XSIMD_TEST_CPU_ASSUME_SSE4_2: "1"
57+
XSIMD_TEST_CPU_ASSUME_AVX2: "1"
58+
XSIMD_TEST_CPU_ASSUME_MANUFACTURER: "intel,amd"
5159
run: ./_build/test/test_xsimd
5260

5361
build-windows-mingw:

include/xsimd/config/xsimd_cpu_features_x86.hpp

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,33 @@ namespace xsimd
266266
return x86_manufacturer::unknown;
267267
};
268268

269+
/** Return a string representation of an @ref x86_manufacturer value. */
270+
constexpr const char* x86_manufacturer_name(x86_manufacturer m) noexcept
271+
{
272+
switch (m)
273+
{
274+
case x86_manufacturer::intel:
275+
return "intel";
276+
case x86_manufacturer::amd:
277+
return "amd";
278+
case x86_manufacturer::via:
279+
return "via";
280+
case x86_manufacturer::zhaoxin:
281+
return "zhaoxin";
282+
case x86_manufacturer::hygon:
283+
return "hygon";
284+
case x86_manufacturer::transmeta:
285+
return "transmeta";
286+
case x86_manufacturer::elbrus:
287+
return "elbrus";
288+
case x86_manufacturer::microsoft_vpc:
289+
return "microsoft_vpc";
290+
case x86_manufacturer::unknown:
291+
return "unknown";
292+
}
293+
return "invalid";
294+
}
295+
269296
struct x86_cpuid_leaf1_traits
270297
{
271298
static constexpr detail::x86_reg32_t leaf = 1;
@@ -567,8 +594,9 @@ namespace xsimd
567594

568595
inline bool avx512_enabled() const noexcept
569596
{
570-
// Check all SSE, AVX, and AVX512 bits even though AVX512 must imply AVX and SSE
571-
return xcr0().all_bits_set<x86_xcr0::xcr0::sse, x86_xcr0::xcr0::avx, x86_xcr0::xcr0::zmm_hi256>();
597+
// Check all SSE, AVX, optmask, and AVX512 bits even though AVX512 must
598+
// imply AVX, SSE, and masked operations.
599+
return xcr0().all_bits_set<x86_xcr0::xcr0::sse, x86_xcr0::xcr0::avx, x86_xcr0::xcr0::opmask, x86_xcr0::xcr0::zmm_hi256>();
572600
}
573601

574602
/**

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ set(XSIMD_TESTS
147147
test_complex_power.cpp
148148
test_complex_trigonometric.cpp
149149
test_conversion.cpp
150+
test_cpu_features.cpp
150151
test_custom_default_arch.cpp
151152
test_error_gamma.cpp
152153
test_explicit_batch_instantiation.cpp

test/test_cpu_features.cpp

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/***************************************************************************
2+
* Copyright (c) Johan Mabille, Sylvain Corlay, Wolf Vollprecht and *
3+
* Martin Renou *
4+
* Copyright (c) QuantStack *
5+
* Copyright (c) Serge Guelton *
6+
* *
7+
* Distributed under the terms of the BSD 3-Clause License. *
8+
* *
9+
* The full license is in the file LICENSE, distributed with this software. *
10+
****************************************************************************/
11+
12+
#include <algorithm>
13+
#include <array>
14+
#include <cstdlib>
15+
#include <string>
16+
17+
#include <doctest/doctest.h>
18+
19+
#include "xsimd/xsimd.hpp"
20+
21+
#define CHECK_IMPLICATION(a, b) CHECK_UNARY(!(a) || (b))
22+
23+
namespace detail
24+
{
25+
void check_env_flag(const char* env_var, const char* feature_name, bool actual)
26+
{
27+
if (const char* val = std::getenv(env_var))
28+
{
29+
// Doctest struggles with string literals and const char *
30+
// TODO(c++20): use std::format
31+
auto msg = std::string(env_var) + " = " + val + ", " + feature_name + " = " + (actual ? "true" : "false");
32+
INFO(msg);
33+
CHECK_EQ(actual, val[0] == '1');
34+
}
35+
}
36+
37+
// TODO(c++23): use str.contains
38+
bool contains(const std::string& haystack, const char* needle)
39+
{
40+
return haystack.find(needle) != std::string::npos;
41+
}
42+
}
43+
44+
#define CHECK_ENV_FEATURE(env_var, feature) detail::check_env_flag(env_var, #feature, feature)
45+
46+
/**
47+
* Tests that x86_cpu_features respects the architectural implication chains.
48+
*
49+
* These are "always true" assertions: if a higher feature is reported, all
50+
* features it architecturally implies must also be reported. The test reads
51+
* the current CPU's features at runtime and verifies every implication.
52+
*/
53+
TEST_CASE("[cpu_features] x86 implication chains")
54+
{
55+
xsimd::x86_cpu_features cpu;
56+
57+
// SSE implication chain
58+
CHECK_IMPLICATION(cpu.sse4_2(), cpu.sse4_1());
59+
CHECK_IMPLICATION(cpu.sse4_1(), cpu.ssse3());
60+
CHECK_IMPLICATION(cpu.ssse3(), cpu.sse3());
61+
CHECK_IMPLICATION(cpu.sse3(), cpu.sse2());
62+
63+
// AVX implication chain
64+
CHECK_IMPLICATION(cpu.avx(), cpu.sse4_2());
65+
CHECK_IMPLICATION(cpu.avx2(), cpu.avx());
66+
CHECK_IMPLICATION(cpu.fma4(), cpu.avx());
67+
CHECK_IMPLICATION(cpu.fma3(), cpu.avx());
68+
69+
// AVX-512 iplication chain
70+
CHECK_IMPLICATION(cpu.avx512f(), cpu.avx2());
71+
CHECK_IMPLICATION(cpu.avx512dq(), cpu.avx512f());
72+
CHECK_IMPLICATION(cpu.avx512ifma(), cpu.avx512f());
73+
CHECK_IMPLICATION(cpu.avx512pf(), cpu.avx512f());
74+
CHECK_IMPLICATION(cpu.avx512er(), cpu.avx512f());
75+
CHECK_IMPLICATION(cpu.avx512cd(), cpu.avx512f());
76+
CHECK_IMPLICATION(cpu.avx512bw(), cpu.avx512f());
77+
CHECK_IMPLICATION(cpu.avx512vbmi(), cpu.avx512f());
78+
CHECK_IMPLICATION(cpu.avx512vbmi2(), cpu.avx512f());
79+
CHECK_IMPLICATION(cpu.avx512vnni_bw(), cpu.avx512bw());
80+
CHECK_IMPLICATION(cpu.avxvnni(), cpu.avx2());
81+
}
82+
83+
TEST_CASE("[cpu_features] x86 manufacturer from environment")
84+
{
85+
xsimd::x86_cpu_features cpu;
86+
87+
const char* val = std::getenv("XSIMD_TEST_CPU_ASSUME_MANUFACTURER");
88+
if (val)
89+
{
90+
struct entry
91+
{
92+
const char* name;
93+
xsimd::x86_manufacturer value;
94+
};
95+
std::array<entry, 9> manufacturers = { {
96+
{ "intel", xsimd::x86_manufacturer::intel },
97+
{ "amd", xsimd::x86_manufacturer::amd },
98+
{ "via", xsimd::x86_manufacturer::via },
99+
{ "zhaoxin", xsimd::x86_manufacturer::zhaoxin },
100+
{ "hygon", xsimd::x86_manufacturer::hygon },
101+
{ "transmeta", xsimd::x86_manufacturer::transmeta },
102+
{ "elbrus", xsimd::x86_manufacturer::elbrus },
103+
{ "microsoft_vpc", xsimd::x86_manufacturer::microsoft_vpc },
104+
{ "unknown", xsimd::x86_manufacturer::unknown },
105+
} };
106+
107+
auto manufacturer = cpu.known_manufacturer();
108+
const std::string allowed(val);
109+
bool match = std::any_of(manufacturers.begin(), manufacturers.end(), [&](const entry& e)
110+
{ return e.value == manufacturer && detail::contains(allowed, e.name); });
111+
112+
auto const msg = std::string("XSIMD_TEST_CPU_ASSUME_MANUFACTURER = ") + val
113+
+ ", actual = " + xsimd::x86_manufacturer_name(manufacturer);
114+
INFO(msg);
115+
CHECK_UNARY(match);
116+
}
117+
}
118+
119+
TEST_CASE("[cpu_features] x86 features from environment")
120+
{
121+
xsimd::x86_cpu_features cpu;
122+
123+
CHECK_ENV_FEATURE("XSIMD_TEST_CPU_ASSUME_SSE2", cpu.sse2());
124+
CHECK_ENV_FEATURE("XSIMD_TEST_CPU_ASSUME_SSE3", cpu.sse3());
125+
CHECK_ENV_FEATURE("XSIMD_TEST_CPU_ASSUME_SSSE3", cpu.ssse3());
126+
CHECK_ENV_FEATURE("XSIMD_TEST_CPU_ASSUME_SSE4_1", cpu.sse4_1());
127+
CHECK_ENV_FEATURE("XSIMD_TEST_CPU_ASSUME_SSE4_2", cpu.sse4_2());
128+
CHECK_ENV_FEATURE("XSIMD_TEST_CPU_ASSUME_FMA3", cpu.fma3());
129+
CHECK_ENV_FEATURE("XSIMD_TEST_CPU_ASSUME_FMA4", cpu.fma4());
130+
CHECK_ENV_FEATURE("XSIMD_TEST_CPU_ASSUME_AVX", cpu.avx());
131+
CHECK_ENV_FEATURE("XSIMD_TEST_CPU_ASSUME_AVX2", cpu.avx2());
132+
CHECK_ENV_FEATURE("XSIMD_TEST_CPU_ASSUME_AVX512F", cpu.avx512f());
133+
CHECK_ENV_FEATURE("XSIMD_TEST_CPU_ASSUME_AVX512BW", cpu.avx512bw());
134+
CHECK_ENV_FEATURE("XSIMD_TEST_CPU_ASSUME_AVX512CD", cpu.avx512cd());
135+
CHECK_ENV_FEATURE("XSIMD_TEST_CPU_ASSUME_AVX512DQ", cpu.avx512dq());
136+
CHECK_ENV_FEATURE("XSIMD_TEST_CPU_ASSUME_AVXVNNI", cpu.avxvnni());
137+
}
138+
139+
TEST_CASE("[cpu_features] arm implication chains")
140+
{
141+
xsimd::arm_cpu_features cpu;
142+
143+
CHECK_IMPLICATION(cpu.neon64(), cpu.neon());
144+
CHECK_IMPLICATION(cpu.sve(), cpu.neon64());
145+
CHECK_IMPLICATION(cpu.i8mm(), cpu.neon64());
146+
}
147+
148+
TEST_CASE("[cpu_features] arm features from environment")
149+
{
150+
xsimd::arm_cpu_features cpu;
151+
152+
CHECK_ENV_FEATURE("XSIMD_TEST_CPU_ASSUME_NEON", cpu.neon());
153+
CHECK_ENV_FEATURE("XSIMD_TEST_CPU_ASSUME_NEON64", cpu.neon64());
154+
CHECK_ENV_FEATURE("XSIMD_TEST_CPU_ASSUME_SVE", cpu.sve());
155+
CHECK_ENV_FEATURE("XSIMD_TEST_CPU_ASSUME_I8MM", cpu.i8mm());
156+
}

0 commit comments

Comments
 (0)