Skip to content

Commit 6cb3f99

Browse files
committed
Several improvements
- Improve multi_targets() method: * rename multi_target() to multi_targets() use plural to indicates handling more than one CPU target * Split the function into multiple smaller functions for better organization. * Modify the function to return a single library array instead of a list of libraries. * Avoid returning the result of testing the enabled features to keep it * simple. * Enhance debugging capabilities by printing the test results of enabled targets. * Avoid returns static_lib instead wraps it with another object to reduce the number of final generated objects also to sort the all objects based on lowest interest * validate the sort of dispatch features - fix mypy - init the module doc - allow disables the cache - fix method `features.new` name during trigger arguments errors - generate config files inside the builddir instead - add test cases - cache tests within coredata - fix duplicate baseline build - refactor module name from `feature` to `features` - fix python typying
1 parent 7d8bbe1 commit 6cb3f99

30 files changed

Lines changed: 1890 additions & 580 deletions

docs/markdown/Features-module.md

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
# Features module
2+
3+
## Overview
4+
5+
Dealing with numerous CPU features through C and C++ compilers is a challenging task,
6+
especially when aiming to support massive amount of CPU features for various architectures and multiple compilers
7+
Additionally, supporting both baseline features and additional features dispatched at runtime presents another dilemma.
8+
9+
Another issue that may arise is simplifying the implementations of generic interfaces while keeping the dirty work laid
10+
on the build system rather than using nested namespaces or recursive sources, relying on pragma or compiler targets attributes
11+
on top of complicated precompiled macros or meta templates, which can make debugging and maintenance difficult.
12+
13+
While this module doesn't force you to follow a specific approach, it instead paves the way to count on a
14+
practical multi-targets solution that can make managing CPU features easier and more reliable.
15+
16+
In a nutshell, this module helps you deliver the following concept:
17+
18+
```C
19+
// Brings the headers files of enabled CPU features
20+
#ifdef HAVE_SSE
21+
#include <xmmintrin.h>
22+
#endif
23+
#ifdef HAVE_SSE2
24+
#include <emmintrin.h>
25+
#endif
26+
#ifdef HAVE_SSE3
27+
#include <pmmintrin.h>
28+
#endif
29+
#ifdef HAVE_SSSE3
30+
#include <tmmintrin.h>
31+
#endif
32+
#ifdef HAVE_SSE41
33+
#include <smmintrin.h>
34+
#endif
35+
#ifdef HAVE_POPCNT
36+
#ifdef _MSC_VER
37+
#include <nmmintrin.h>
38+
#else
39+
#include <popcntintrin.h>
40+
#endif
41+
#endif
42+
#ifdef HAVE_AVX
43+
#include <immintrin.h>
44+
#endif
45+
46+
#ifdef HAVE_NEON
47+
#include <arm_neon.h>
48+
#endif
49+
50+
// MTARGETS_CURRENT defined as compiler argument via `features.multi_targets()`
51+
#ifdef MTARGETS_CURRENT
52+
#define TARGET_SFX(X) X##_##MTARGETS_CURRENT
53+
#else
54+
// baseline or when building source without this module.
55+
#define TARGET_SFX(X) X
56+
#endif
57+
58+
void TARGET_SFX(my_kernal)(const float *src, float *dst)
59+
{
60+
#ifdef HAVE_AVX512F
61+
// defeintions for implied features alawys present
62+
// no matter the compiler is
63+
#ifndef HAVE_AVX2
64+
#error "Alawys defined"
65+
#endif
66+
#elif defined(HAVE_AVX2) && defined(HAVE_FMA3)
67+
#ifndef HAVE_AVX
68+
#error "Alawys defined"
69+
#endif
70+
#elif defined(HAVE_SSE41)
71+
#ifndef HAVE_SSSE3
72+
#error "Alawys defined"
73+
#endif
74+
#elif defined(HAVE_SSE2)
75+
#ifndef HAVE_SSE2
76+
#error "Alawys defined"
77+
#endif
78+
#elif defined(HAVE_ASIMDHP)
79+
#if !defined(HAVE_NEON) || !defined(HAVE_ASIMD)
80+
#error "Alawys defined"
81+
#endif
82+
#elif defined(HAVE_ASIMD)
83+
#ifndef HAVE_NEON_VFPV4
84+
#error "Alawys defined"
85+
#endif
86+
#elif defined(HAVE_NEON_F16)
87+
#ifndef HAVE_NEON
88+
#error "Alawys defined"
89+
#endif
90+
#else
91+
// fallback to C scalar
92+
#endif
93+
}
94+
```
95+
96+
From the above code we can deduce the following:
97+
- The code is written on top features based definitions rather than counting clusters or
98+
features groups which gives the code more readability and flexibility.
99+
100+
- Avoid using compiler built-in defeintions no matters the enabled arguments allows you
101+
to easily manage the enabled/disabled features and to deal with any kind of compiler or features.
102+
Since compilers like MSVC for example doesn't provides defeintions for all CPU features.
103+
104+
- The code is not aware of how its going to be build it, that gives the code a great prodiblity to
105+
manage the generated objects which allow raising the baseline features at any time
106+
or reduce and increase the additional dispatched features without changing the code.
107+
108+
- Allow building a single source multiple of times simplifying the implementations
109+
of generic interfaces.
110+
111+
112+
## Usage
113+
114+
To use this module, just do: **`features = import('features')`**. The
115+
following functions will then be available as methods on the object
116+
with the name `features`. You can, of course, replace the name `features`
117+
with anything else.
118+
119+
### features.new()
120+
```meson
121+
features.new(string, int,
122+
implies: FeatureOject | FeatureObject[] = [],
123+
group: string | string[] = [],
124+
detect: string | {} | (string | {})[] = [],
125+
args: string | {} | (string | {})[] = [],
126+
test_code: string | File = '',
127+
extra_tests: {string: string | file} = {},
128+
disable: string = ''
129+
) -> FeatureObject
130+
```
131+
132+
This function plays a crucial role in the Features Module as it creates
133+
a new `FeatureObject` instance that is essential for the functioning of
134+
other methods within the module.
135+
136+
It takes two required positional arguments. The first one is the name of the feature,
137+
and the second is the interest level of the feature, which is used by
138+
the sort operation and priority of conflicting arguments.
139+
The rest of the kwargs arguments are explained as follows:
140+
141+
* `implies` **FeatureOject | FeatureObject[] = []**: One or an array of features objects
142+
representing predecessor features.
143+
144+
* `group` **string | string[] = []**: Optional one or an array of extra features names
145+
to be added as extra definitions that can passed to source.
146+
147+
* `args` **string | {} | (string | {})[] = []**: Optional one or an array of compiler
148+
arguments that are required to be enabled for this feature.
149+
Each argument can be a string or a dictionary that holds four items that allow dealing
150+
with the conflicts of the arguments of implied features:
151+
- `val` **string**: string, the compiler argument.
152+
- `match` **string | empty**: regex to match the arguments of implied features
153+
that need to be filtered or erased.
154+
- `mfilter` **string | empty**: regex to find certain strings from the matched arguments
155+
to be combined with `val`. If the value of `mfilter` is empty or undefined,
156+
any matches triggered by the value of `match` will not be combined with `val`.
157+
- `mjoin` **string | empty**: a separator used to join all the filtered arguments.
158+
If it's empty or undefined, the filtered arguments will be joined without a separator.
159+
160+
* `detect` **string | {} | (string | {})[] = []**: Optional one or an array of features names
161+
that required to be detect on runtime. If no features sepecfied then the values of `group`
162+
will be used if its provides otherwise the name of the feature will be used instead.
163+
Similar to `args`, each feature name can be a string or a dictionary that holds four items
164+
that allow dealing with the conflicts of the of implied features names.
165+
See `features.multi_targets()` or `features.test()` for more clearfications.
166+
167+
* `test_code` **string | File = ''**: Optional C/C++ code or the path to the source
168+
that needs to be tested against the compiler to consider this feature is supported.
169+
170+
* `extra_tests` **{string: string | file} = {}**: Optional dictionary holds extra tests where
171+
the key represents the test name, which is also added as a compiler definition if the test succeeded,
172+
and the value is C/C++ code or the path to the source that needs to be tested against the compiler.
173+
174+
* `disable` **string = ''**: Optional string to consider this feature disabled.
175+
176+
Returns a new instance of `FeatureObject`.
177+
178+
Example:
179+
180+
```Meson
181+
cpu = host_machine.cpu_family()
182+
features = import('features')
183+
184+
ASIMD = features.new(
185+
'ASIMD', 1, group: ['NEON', 'NEON_VFPV4', 'NEON_VFPV4'],
186+
args: cpu == 'aarch64' ? '' : [
187+
'-mfpu=neon-fp-armv8',
188+
'-march=armv8-a+simd'
189+
],
190+
disable: cpu in ['arm', 'aarch64'] ? '' : 'Not supported by ' + cpu
191+
)
192+
# ARMv8.2 half-precision & vector arithm
193+
ASIMDHP = features.new(
194+
'ASIMDHP', 2, implies: ASIMD,
195+
args: {
196+
'val': '-march=armv8.2-a+fp16',
197+
# search for any argument starts with `-match=`
198+
'match': '-march=',
199+
# gets any string starts with `+` and apended to the value of `val`
200+
'mfilter': '\+.*'
201+
}
202+
)
203+
## ARMv8.2 dot product
204+
ASIMDDP = features.new(
205+
'ASIMDDP', 3, implies: ASIMD,
206+
args: {'val': '-march=armv8.2-a+dotprod', 'match': '-march=.*', 'mfilter': '\+.*'}
207+
)
208+
## ARMv8.2 Single & half-precision Multiply
209+
ASIMDFHM = features.new(
210+
'ASIMDFHM', 4, implies: ASIMDHP,
211+
args: {'val': '-march=armv8.2-a+fp16fml', 'match': '-march=.*', 'mfilter': '\+.*'}
212+
)
213+
```
214+
215+
### features.test()
216+
```meson
217+
features.test(FeatureObject...,
218+
anyfet: bool = false,
219+
force_args: string | string[] | empty = empty,
220+
compiler: Compiler | empty = empty,
221+
cached: bool = true,
222+
) -> {}
223+
```
224+
225+
Test a one or set of features against the compiler and returns a dictionary
226+
contains all required information that needed for building a source that
227+
requires these features.
228+
229+
### features.multi_targets()
230+
```meson
231+
features.multi_targets(string, (
232+
str | File | CustomTarget | CustomTargetIndex |
233+
GeneratedList | StructuredSources | ExtractedObjects |
234+
BuildTarget
235+
)...,
236+
dispatch: (FeatureObject | FeatureObject[])[] = [],
237+
baseline: empty | FeatureObject[] = empty,
238+
prefix: string = '',
239+
compiler: empty | compiler = empty,
240+
cached: bool = True
241+
) [{}[], StaticLibrary[]]
242+
```
243+
244+
245+
### features.sort()
246+
```meson
247+
features.sort(FeatureObject..., reverse: bool = false) : FeatureObject[]
248+
```
249+
250+
### features.implicit()
251+
```meson
252+
features.implicit(FeatureObject...) : FeatureObject[]
253+
```
254+
255+
### features.implicit_c()
256+
```meson
257+
features.implicit_c(FeatureObject...) : FeatureObject[]
258+
```
259+

docs/sitemap.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ index.md
6161
Windows-module.md
6262
i18n-module.md
6363
Wayland-module.md
64+
Features-module.md
6465
Java.md
6566
Vala.md
6667
D.md

mesonbuild/modules/feature/__init__.py

Lines changed: 0 additions & 41 deletions
This file was deleted.

0 commit comments

Comments
 (0)