Branch data Line data Source code
1 : : // This file is a part of Julia. License is MIT: https://julialang.org/license
2 : :
3 : : // Processor feature detection
4 : :
5 : : #include "llvm-version.h"
6 : : #include <llvm/ADT/StringRef.h>
7 : : #include <llvm/Support/MathExtras.h>
8 : : #include <llvm/Support/raw_ostream.h>
9 : :
10 : : #include "processor.h"
11 : :
12 : : #include "julia.h"
13 : : #include "julia_internal.h"
14 : :
15 : : #include <map>
16 : : #include <algorithm>
17 : :
18 : : #include "julia_assert.h"
19 : :
20 : : // CPU target string is a list of strings separated by `;` each string starts with a CPU
21 : : // or architecture name and followed by an optional list of features separated by `,`.
22 : : // A "generic" or empty CPU name means the basic required feature set of the target ISA
23 : : // which is at least the architecture the C/C++ runtime is compiled with.
24 : :
25 : : // CPU dispatch needs to determine the version to be used by the sysimg as well as
26 : : // the target and feature used by the JIT. Currently the only limitation on JIT target
27 : : // and feature is matching register size between the sysimg and JIT so that SIMD vectors
28 : : // can be passed correctly. This means disabling AVX and AVX2 if AVX was not enabled
29 : : // in sysimg and disabling AVX512 if it was not enabled in sysimg.
30 : : // This also possibly means that SVE needs to be disabled on AArch64 if sysimg doesn't have it
31 : : // enabled.
32 : :
33 : : // CPU dispatch starts by first deciding the max feature set and CPU requested for JIT.
34 : : // This is the host or the target specified on the command line with features unavailable
35 : : // on the host disabled. All sysimg targets that require features not available in this set
36 : : // will be ignored.
37 : :
38 : : // The next step is matching CPU name.
39 : : // If exact name match with compatible feature set exists, all versions without name match
40 : : // are ignored.
41 : : // This step will query LLVM first so it can accept CPU names that is recognized by LLVM but
42 : : // not by us (yet) when LLVM is enabled.
43 : :
44 : : // If there are still more than one candidates, a feature match is performed.
45 : : // The ones with the largest register size will be used
46 : : // (i.e. AVX512 > AVX2/AVX > SSE, SVE > ASIMD). If there's a tie, the one with the most features
47 : : // enabled will be used. If there's still a tie the one that appears later in the list will be
48 : : // used. (i.e. the order in the version list is significant in this case).
49 : :
50 : : // Features that are not recognized will be passed to LLVM directly during codegen
51 : : // but ignored otherwise.
52 : :
53 : : // A few special features are supported:
54 : : // 1. `clone_all`
55 : : //
56 : : // This forces the target to have all functions in sysimg cloned.
57 : : // When used in negative form (i.e. `-clone_all`), this disables full clone that's
58 : : // enabled by default for certain targets.
59 : : //
60 : : // 2. `base([0-9]*)`
61 : : //
62 : : // This specifies the (0-based) base target index. The base target is the target
63 : : // that the current target is based on, i.e. the functions that are not being cloned
64 : : // will use the version in the base target. This option causes the base target to be
65 : : // fully cloned (as if `clone_all` is specified for it) if it is not the default target (0).
66 : : // The index can only be smaller than the current index.
67 : : //
68 : : // 3. `opt_size`
69 : : //
70 : : // Optimize for size with minimum performance impact. Clang/GCC's `-Os`.
71 : : //
72 : : // 4. `min_size`
73 : : //
74 : : // Optimize only for size. Clang's `-Oz`.
75 : :
76 : : JL_DLLEXPORT bool jl_processor_print_help = false;
77 : :
78 : : namespace {
79 : :
80 : : // Helper functions to test/set feature bits
81 : :
82 : : template<typename T1, typename T2, typename T3>
83 : 75 : static inline bool test_bits(T1 v, T2 mask, T3 test)
84 : : {
85 : 75 : return T3(v & mask) == test;
86 : : }
87 : :
88 : : template<typename T1, typename T2>
89 : 75 : static inline bool test_all_bits(T1 v, T2 mask)
90 : : {
91 : 75 : return test_bits(v, mask, mask);
92 : : }
93 : :
94 : : template<typename T1, typename T2>
95 : 3541 : static inline bool test_nbit(const T1 &bits, T2 _bitidx)
96 : : {
97 : 3541 : auto bitidx = static_cast<uint32_t>(_bitidx);
98 : 3541 : auto u32idx = bitidx / 32;
99 : 3541 : auto bit = bitidx % 32;
100 : 3541 : return (bits[u32idx] & (1 << bit)) != 0;
101 : : }
102 : :
103 : : template<typename T>
104 : 60 : static inline void unset_bits(T &bits)
105 : : {
106 : : (void)bits;
107 : 60 : }
108 : :
109 : : template<typename T, typename T1, typename... Rest>
110 : 345 : static inline void unset_bits(T &bits, T1 _bitidx, Rest... rest)
111 : : {
112 : 345 : auto bitidx = static_cast<uint32_t>(_bitidx);
113 : 345 : auto u32idx = bitidx / 32;
114 : 345 : auto bit = bitidx % 32;
115 : 345 : bits[u32idx] = bits[u32idx] & ~uint32_t(1 << bit);
116 : 345 : unset_bits(bits, rest...);
117 : 345 : }
118 : :
119 : : template<typename T, typename T1>
120 : 0 : static inline void set_bit(T &bits, T1 _bitidx, bool val)
121 : : {
122 : 0 : auto bitidx = static_cast<uint32_t>(_bitidx);
123 : 0 : auto u32idx = bitidx / 32;
124 : 0 : auto bit = bitidx % 32;
125 [ # # ]: 0 : if (val) {
126 : 0 : bits[u32idx] = bits[u32idx] | uint32_t(1 << bit);
127 : : }
128 : : else {
129 : 0 : bits[u32idx] = bits[u32idx] & ~uint32_t(1 << bit);
130 : : }
131 : 0 : }
132 : :
133 : : // Helper functions to create feature masks
134 : :
135 : : // This can be `std::array<uint32_t,n>` on C++14
136 : : template<size_t n>
137 : : struct FeatureList {
138 : : uint32_t eles[n];
139 : 1939 : uint32_t &operator[](size_t pos)
140 : : {
141 : 1939 : return eles[pos];
142 : : }
143 : 4249 : constexpr const uint32_t &operator[](size_t pos) const
144 : : {
145 : 4249 : return eles[pos];
146 : : }
147 : 2 : inline int nbits() const
148 : : {
149 : 2 : int cnt = 0;
150 [ + + ]: 24 : for (size_t i = 0; i < n; i++)
151 : 22 : cnt += llvm::countPopulation(eles[i]);
152 : 2 : return cnt;
153 : : }
154 : 2 : inline bool empty() const
155 : : {
156 [ + + ]: 24 : for (size_t i = 0; i < n; i++) {
157 [ - + ]: 22 : if (eles[i]) {
158 : 0 : return false;
159 : : }
160 : : }
161 : 2 : return true;
162 : : }
163 : : };
164 : :
165 : : static inline constexpr uint32_t add_feature_mask_u32(uint32_t mask, uint32_t u32idx)
166 : : {
167 : : return mask;
168 : : }
169 : :
170 : : template<typename T, typename... Rest>
171 : : static inline constexpr uint32_t add_feature_mask_u32(uint32_t mask, uint32_t u32idx,
172 : : T bit, Rest... args)
173 : : {
174 : : return add_feature_mask_u32(mask | ((int(bit) >= 0 && int(bit) / 32 == (int)u32idx) ?
175 : : (1 << (int(bit) % 32)) : 0),
176 : : u32idx, args...);
177 : : }
178 : :
179 : : template<typename... Args>
180 : : static inline constexpr uint32_t get_feature_mask_u32(uint32_t u32idx, Args... args)
181 : : {
182 : : return add_feature_mask_u32(uint32_t(0), u32idx, args...);
183 : : }
184 : :
185 : : template<uint32_t... Is> struct seq{};
186 : : template<uint32_t N, uint32_t... Is>
187 : : struct gen_seq : gen_seq<N-1, N-1, Is...>{};
188 : : template<uint32_t... Is>
189 : : struct gen_seq<0, Is...> : seq<Is...>{};
190 : :
191 : : template<size_t n, uint32_t... I, typename... Args>
192 : : static inline constexpr FeatureList<n>
193 : : _get_feature_mask(seq<I...>, Args... args)
194 : : {
195 : : return FeatureList<n>{{get_feature_mask_u32(I, args...)...}};
196 : : }
197 : :
198 : : template<size_t n, typename... Args>
199 : : static inline constexpr FeatureList<n> get_feature_masks(Args... args)
200 : : {
201 : : return _get_feature_mask<n>(gen_seq<n>(), args...);
202 : : }
203 : :
204 : : template<size_t n, uint32_t... I>
205 : : static inline constexpr FeatureList<n>
206 : : _feature_mask_or(seq<I...>, const FeatureList<n> &a, const FeatureList<n> &b)
207 : : {
208 : : return FeatureList<n>{{(a[I] | b[I])...}};
209 : : }
210 : :
211 : : template<size_t n>
212 : : static inline constexpr FeatureList<n> operator|(const FeatureList<n> &a, const FeatureList<n> &b)
213 : : {
214 : : return _feature_mask_or<n>(gen_seq<n>(), a, b);
215 : : }
216 : :
217 : : template<size_t n, uint32_t... I>
218 : : static inline constexpr FeatureList<n>
219 : 2 : _feature_mask_and(seq<I...>, const FeatureList<n> &a, const FeatureList<n> &b)
220 : : {
221 : 2 : return FeatureList<n>{{(a[I] & b[I])...}};
222 : : }
223 : :
224 : : template<size_t n>
225 : 2 : static inline constexpr FeatureList<n> operator&(const FeatureList<n> &a, const FeatureList<n> &b)
226 : : {
227 : 2 : return _feature_mask_and<n>(gen_seq<n>(), a, b);
228 : : }
229 : :
230 : : template<size_t n, uint32_t... I>
231 : : static inline constexpr FeatureList<n>
232 : : _feature_mask_not(seq<I...>, const FeatureList<n> &a)
233 : : {
234 : : return FeatureList<n>{{(~a[I])...}};
235 : : }
236 : :
237 : : template<size_t n>
238 : : static inline constexpr FeatureList<n> operator~(const FeatureList<n> &a)
239 : : {
240 : : return _feature_mask_not<n>(gen_seq<n>(), a);
241 : : }
242 : :
243 : : template<size_t n>
244 : 15 : static inline void mask_features(const FeatureList<n> masks, uint32_t *features)
245 : : {
246 [ + + ]: 180 : for (size_t i = 0; i < n; i++) {
247 : 165 : features[i] = features[i] & masks[i];
248 : : }
249 : 15 : }
250 : :
251 : : // Turn feature list to a string the LLVM accept
252 : 2 : static inline std::string join_feature_strs(const std::vector<std::string> &strs)
253 : : {
254 : 2 : size_t nstr = strs.size();
255 [ - + ]: 2 : if (!nstr)
256 : 0 : return std::string("");
257 : 4 : std::string str = strs[0];
258 [ + + ]: 156 : for (size_t i = 1; i < nstr; i++)
259 : 154 : str += ',' + strs[i];
260 : 2 : return str;
261 : : }
262 : :
263 : 2 : static inline void append_ext_features(std::string &features, const std::string &ext_features)
264 : : {
265 [ + - ]: 2 : if (ext_features.empty())
266 : 2 : return;
267 [ # # ]: 0 : if (!features.empty())
268 : 0 : features.push_back(',');
269 : 0 : features.append(ext_features);
270 : : }
271 : :
272 : 15 : static inline void append_ext_features(std::vector<std::string> &features,
273 : : const std::string &ext_features)
274 : : {
275 [ + - ]: 15 : if (ext_features.empty())
276 : 15 : return;
277 : 0 : const char *start = ext_features.c_str();
278 : 0 : const char *p = start;
279 [ # # ]: 0 : for (; *p; p++) {
280 [ # # ]: 0 : if (*p == ',') {
281 : 0 : features.emplace_back(start, p - start);
282 : 0 : start = p + 1;
283 : : }
284 : : }
285 [ # # ]: 0 : if (p > start) {
286 : 0 : features.emplace_back(start, p - start);
287 : : }
288 : : }
289 : :
290 : : /**
291 : : * Target specific type/constant definitions, always enable.
292 : : */
293 : :
294 : : struct FeatureName {
295 : : const char *name;
296 : : uint32_t bit; // bit index into a `uint32_t` array;
297 : : uint32_t llvmver; // 0 if it is available on the oldest LLVM version we support
298 : : };
299 : :
300 : : template<typename CPU, size_t n>
301 : : struct CPUSpec {
302 : : const char *name;
303 : : CPU cpu;
304 : : CPU fallback;
305 : : uint32_t llvmver;
306 : : FeatureList<n> features;
307 : : };
308 : :
309 : : struct FeatureDep {
310 : : uint32_t feature;
311 : : uint32_t dep;
312 : : };
313 : :
314 : : // Recursively enable all features that the current feature set depends on.
315 : : template<size_t n>
316 : 15 : static inline void enable_depends(FeatureList<n> &features, const FeatureDep *deps, size_t ndeps)
317 : : {
318 : 15 : bool changed = true;
319 [ + + ]: 30 : while (changed) {
320 : 15 : changed = false;
321 [ + + ]: 555 : for (ssize_t i = ndeps - 1; i >= 0; i--) {
322 : 540 : auto &dep = deps[i];
323 [ + + + - : 540 : if (!test_nbit(features, dep.feature) || test_nbit(features, dep.dep))
+ - ]
324 : 540 : continue;
325 : 0 : set_bit(features, dep.dep, true);
326 : 0 : changed = true;
327 : : }
328 : : }
329 : 15 : }
330 : :
331 : : // Recursively disable all features that the current feature set does not provide.
332 : : template<size_t n>
333 : 17 : static inline void disable_depends(FeatureList<n> &features, const FeatureDep *deps, size_t ndeps)
334 : : {
335 : 17 : bool changed = true;
336 [ + + ]: 34 : while (changed) {
337 : 17 : changed = false;
338 [ + + ]: 629 : for (ssize_t i = ndeps - 1; i >= 0; i--) {
339 : 612 : auto &dep = deps[i];
340 [ + + + - : 612 : if (!test_nbit(features, dep.feature) || test_nbit(features, dep.dep))
+ - ]
341 : 612 : continue;
342 : 0 : unset_bits(features, dep.feature);
343 : 0 : changed = true;
344 : : }
345 : : }
346 : 17 : }
347 : :
348 : : template<typename CPU, size_t n>
349 : 15 : static const CPUSpec<CPU,n> *find_cpu(uint32_t cpu, const CPUSpec<CPU,n> *cpus, uint32_t ncpus)
350 : : {
351 [ + - ]: 270 : for (uint32_t i = 0; i < ncpus; i++) {
352 [ + + ]: 270 : if (cpu == uint32_t(cpus[i].cpu)) {
353 : 15 : return &cpus[i];
354 : : }
355 : : }
356 : 0 : return nullptr;
357 : : }
358 : :
359 : : template<typename CPU, size_t n>
360 : 17 : static const CPUSpec<CPU,n> *find_cpu(llvm::StringRef name, const CPUSpec<CPU,n> *cpus,
361 : : uint32_t ncpus)
362 : : {
363 [ + - ]: 306 : for (uint32_t i = 0; i < ncpus; i++) {
364 [ + + ]: 306 : if (name == cpus[i].name) {
365 : 17 : return &cpus[i];
366 : : }
367 : : }
368 : 0 : return nullptr;
369 : : }
370 : :
371 : : template<typename CPU, size_t n>
372 : 15 : static const char *find_cpu_name(uint32_t cpu, const CPUSpec<CPU,n> *cpus, uint32_t ncpus)
373 : : {
374 [ + - ]: 15 : if (auto *spec = find_cpu(cpu, cpus, ncpus))
375 : 15 : return spec->name;
376 : 0 : return "generic";
377 : : }
378 : :
379 : 0 : JL_UNUSED static uint32_t find_feature_bit(const FeatureName *features, size_t nfeatures,
380 : : const char *str, size_t len)
381 : : {
382 [ # # ]: 0 : for (size_t i = 0; i < nfeatures; i++) {
383 : 0 : auto &feature = features[i];
384 [ # # # # ]: 0 : if (strncmp(feature.name, str, len) == 0 && feature.name[len] == 0) {
385 : 0 : return feature.bit;
386 : : }
387 : : }
388 : 0 : return (uint32_t)-1;
389 : : }
390 : :
391 : : // This is how we save the target identification.
392 : : // CPU name is saved as string instead of binary data like features because
393 : : // 1. CPU ID is less stable (they are not bound to hardware/OS API)
394 : : // 2. We need to support CPU names that are not recognized by us and therefore doesn't have an ID
395 : : // 3. CPU name is trivial to parse
396 : 2 : static inline std::vector<uint8_t> serialize_target_data(llvm::StringRef name,
397 : : uint32_t nfeature,
398 : : const uint32_t *features_en,
399 : : const uint32_t *features_dis,
400 : : llvm::StringRef ext_features)
401 : : {
402 : 2 : std::vector<uint8_t> res;
403 : 14 : auto add_data = [&] (const void *data, size_t sz) {
404 [ + + ]: 14 : if (sz == 0)
405 : 2 : return;
406 : 12 : size_t old_sz = res.size();
407 : 12 : res.resize(old_sz + sz);
408 : 12 : memcpy(&res[old_sz], data, sz);
409 : 2 : };
410 : 2 : add_data(&nfeature, 4);
411 : 2 : add_data(features_en, 4 * nfeature);
412 : 2 : add_data(features_dis, 4 * nfeature);
413 : 2 : uint32_t namelen = name.size();
414 : 2 : add_data(&namelen, 4);
415 : 2 : add_data(name.data(), namelen);
416 : 2 : uint32_t ext_features_len = ext_features.size();
417 : 2 : add_data(&ext_features_len, 4);
418 : 2 : add_data(ext_features.data(), ext_features_len);
419 : 2 : return res;
420 : : }
421 : :
422 : : template<size_t n>
423 : 2 : static inline std::vector<uint8_t> serialize_target_data(llvm::StringRef name,
424 : : const FeatureList<n> &features_en,
425 : : const FeatureList<n> &features_dis,
426 : : llvm::StringRef ext_features)
427 : : {
428 : 2 : return serialize_target_data(name, n, &features_en[0], &features_dis[0], ext_features);
429 : : }
430 : :
431 : : template<size_t n>
432 : : struct TargetData {
433 : : std::string name;
434 : : std::string ext_features;
435 : : struct {
436 : : FeatureList<n> features;
437 : : uint32_t flags;
438 : : } en, dis;
439 : : int base;
440 : : };
441 : :
442 : : // In addition to the serialized data, the first `uint32_t` gives the number of targets saved
443 : : // and each target has a `uint32_t` flag before the serialized target data.
444 : : template<size_t n>
445 : 2 : static inline std::vector<TargetData<n>> deserialize_target_data(const uint8_t *data)
446 : : {
447 : 16 : auto load_data = [&] (void *dest, size_t sz) {
448 : 14 : memcpy(dest, data, sz);
449 : 14 : data += sz;
450 : : };
451 : 14 : auto load_string = [&] () {
452 : : uint32_t len;
453 : 4 : load_data(&len, 4);
454 : 8 : std::string res((const char*)data, len);
455 : 4 : data += len;
456 : 4 : return res;
457 : : };
458 : : uint32_t ntarget;
459 : 2 : load_data(&ntarget, 4);
460 : 2 : std::vector<TargetData<n>> res(ntarget);
461 [ + + ]: 4 : for (uint32_t i = 0; i < ntarget; i++) {
462 : 2 : auto &target = res[i];
463 : 2 : load_data(&target.en.flags, 4);
464 : 2 : target.dis.flags = 0;
465 : : // Starting serialized target data
466 : : uint32_t nfeature;
467 : 2 : load_data(&nfeature, 4);
468 [ - + ]: 2 : assert(nfeature == n);
469 : 2 : load_data(&target.en.features[0], 4 * n);
470 : 2 : load_data(&target.dis.features[0], 4 * n);
471 : 2 : target.name = load_string();
472 : 2 : target.ext_features = load_string();
473 : 2 : target.base = 0;
474 : : }
475 : 2 : return res;
476 : : }
477 : :
478 : : // Try getting clone base argument. Return 1-based index. Return 0 if match failed.
479 : 0 : static inline int get_clone_base(const char *start, const char *end)
480 : : {
481 : 0 : const char *prefix = "base(";
482 : 0 : const int prefix_len = strlen(prefix);
483 [ # # ]: 0 : if (end - start <= prefix_len)
484 : 0 : return 0;
485 [ # # ]: 0 : if (memcmp(start, prefix, prefix_len) != 0)
486 : 0 : return 0;
487 : 0 : start += prefix_len;
488 [ # # # # ]: 0 : if (*start > '9' || *start < '0')
489 : 0 : return 0;
490 : : char *digit_end;
491 : 0 : auto idx = strtol(start, &digit_end, 10);
492 [ # # ]: 0 : if (idx < 0)
493 : 0 : return 0;
494 [ # # # # ]: 0 : if (*digit_end != ')' || digit_end + 1 != end)
495 : 0 : return 0;
496 : 0 : return (int)idx + 1;
497 : : }
498 : :
499 : : // Parse cmdline string. This handles `clone_all` and `base` special features.
500 : : // Other feature names will be passed to `feature_cb` for target dependent parsing.
501 : : template<size_t n, typename F>
502 : : static inline std::vector<TargetData<n>>
503 : 15 : parse_cmdline(const char *option, F &&feature_cb)
504 : : {
505 [ - + ]: 15 : if (!option)
506 : 0 : option = "native";
507 : 15 : std::vector<TargetData<n>> res;
508 : 30 : TargetData<n> arg{};
509 : 105 : auto reset_arg = [&] {
510 : 15 : res.push_back(arg);
511 : 15 : arg.name.clear();
512 : 15 : arg.ext_features.clear();
513 : 15 : memset(&arg.en.features[0], 0, 4 * n);
514 : 15 : memset(&arg.dis.features[0], 0, 4 * n);
515 : 15 : arg.en.flags = 0;
516 : 15 : arg.dis.flags = 0;
517 : : };
518 : 15 : const char *start = option;
519 : 105 : for (const char *p = option; ; p++) {
520 [ + + ]: 105 : switch (*p) {
521 : 15 : case ',':
522 : : case ';':
523 : : case '\0': {
524 : 15 : bool done = *p == '\0';
525 [ + - + - ]: 15 : bool next_target = *p == ';' || done;
526 [ + - ]: 15 : if (arg.name.empty()) {
527 [ - + ]: 15 : if (p == start)
528 : 0 : jl_error("Invalid target option: empty CPU name");
529 : 15 : arg.name.append(start, p - start);
530 [ - + ]: 15 : if (arg.name == "help") {
531 : 0 : arg.name = "native";
532 : 0 : jl_processor_print_help = true;
533 : : }
534 : 15 : start = p + 1;
535 [ + - ]: 15 : if (next_target)
536 : 15 : reset_arg();
537 [ + - ]: 15 : if (done)
538 : 15 : return res;
539 : 0 : continue;
540 : : }
541 : 0 : bool disable = false;
542 : 0 : const char *full = start;
543 : 0 : const char *fname = full;
544 : 0 : start = p + 1;
545 [ # # ]: 0 : if (*full == '-') {
546 : 0 : disable = true;
547 : 0 : fname++;
548 : : }
549 [ # # ]: 0 : else if (*full == '+') {
550 : 0 : fname++;
551 : : }
552 [ # # ]: 0 : if (llvm::StringRef(fname, p - fname) == "clone_all") {
553 [ # # ]: 0 : if (!disable) {
554 : 0 : arg.en.flags |= JL_TARGET_CLONE_ALL;
555 : 0 : arg.dis.flags &= ~JL_TARGET_CLONE_ALL;
556 : : }
557 : : else {
558 : 0 : arg.dis.flags |= JL_TARGET_CLONE_ALL;
559 : 0 : arg.en.flags &= ~JL_TARGET_CLONE_ALL;
560 : : }
561 : : }
562 [ # # ]: 0 : else if (llvm::StringRef(fname, p - fname) == "opt_size") {
563 [ # # ]: 0 : if (disable)
564 : 0 : jl_error("Invalid target option: disabled opt_size.");
565 [ # # ]: 0 : if (arg.en.flags & JL_TARGET_MINSIZE)
566 : 0 : jl_error("Conflicting target option: both opt_size and min_size are specified.");
567 : 0 : arg.en.flags |= JL_TARGET_OPTSIZE;
568 : : }
569 [ # # ]: 0 : else if (llvm::StringRef(fname, p - fname) == "min_size") {
570 [ # # ]: 0 : if (disable)
571 : 0 : jl_error("Invalid target option: disabled min_size.");
572 [ # # ]: 0 : if (arg.en.flags & JL_TARGET_OPTSIZE)
573 : 0 : jl_error("Conflicting target option: both opt_size and min_size are specified.");
574 : 0 : arg.en.flags |= JL_TARGET_MINSIZE;
575 : : }
576 [ # # ]: 0 : else if (int base = get_clone_base(fname, p)) {
577 [ # # ]: 0 : if (disable)
578 : 0 : jl_error("Invalid target option: disabled base index.");
579 : 0 : base -= 1;
580 [ # # ]: 0 : if (base >= (int)res.size())
581 : 0 : jl_error("Invalid target option: base index must refer to a previous target.");
582 [ # # # # ]: 0 : if (res[base].dis.flags & JL_TARGET_CLONE_ALL ||
583 [ # # ]: 0 : !(res[base].en.flags & JL_TARGET_CLONE_ALL))
584 : 0 : jl_error("Invalid target option: base target must be clone_all.");
585 : 0 : arg.base = base;
586 : : }
587 [ # # ]: 0 : else if (llvm::StringRef(fname, p - fname) == "help") {
588 : 0 : jl_processor_print_help = true;
589 : : }
590 : : else {
591 [ # # ]: 0 : FeatureList<n> &list = disable ? arg.dis.features : arg.en.features;
592 [ # # ]: 0 : if (!feature_cb(fname, p - fname, list)) {
593 [ # # ]: 0 : if (!arg.ext_features.empty())
594 : 0 : arg.ext_features += ',';
595 [ # # ]: 0 : arg.ext_features += disable ? '-' : '+';
596 : 0 : arg.ext_features.append(fname, p - fname);
597 : : }
598 : : }
599 [ # # ]: 0 : if (next_target)
600 : 0 : reset_arg();
601 [ # # ]: 0 : if (done) {
602 : 0 : return res;
603 : : }
604 : : }
605 : : JL_FALLTHROUGH;
606 : : default:
607 : 90 : continue;
608 : : }
609 : : }
610 : : }
611 : :
612 : : // Cached version of command line parsing
613 : : template<size_t n, typename F>
614 : 17 : static inline std::vector<TargetData<n>> &get_cmdline_targets(F &&feature_cb)
615 : : {
616 [ + + + - ]: 17 : static std::vector<TargetData<n>> targets =
617 : : parse_cmdline<n>(jl_options.cpu_target, std::forward<F>(feature_cb));
618 : 17 : return targets;
619 : : }
620 : :
621 : : // Load sysimg, use the `callback` for dispatch and perform all relocations
622 : : // for the selected target.
623 : : template<typename F>
624 : 2 : static inline jl_sysimg_fptrs_t parse_sysimg(void *hdl, F &&callback)
625 : : {
626 : 2 : jl_sysimg_fptrs_t res = {nullptr, 0, nullptr, 0, nullptr, nullptr};
627 : :
628 : : // .data base
629 : : char *data_base;
630 : 2 : jl_dlsym(hdl, "jl_sysimg_gvars_base", (void**)&data_base, 1);
631 : : // .text base
632 : : char *text_base;
633 : 2 : jl_dlsym(hdl, "jl_sysimg_fvars_base", (void**)&text_base, 1);
634 : 2 : res.base = text_base;
635 : :
636 : : int32_t *offsets;
637 : 2 : jl_dlsym(hdl, "jl_sysimg_fvars_offsets", (void**)&offsets, 1);
638 : 2 : uint32_t nfunc = offsets[0];
639 : 2 : res.offsets = offsets + 1;
640 : :
641 : : void *ids;
642 : 2 : jl_dlsym(hdl, "jl_dispatch_target_ids", &ids, 1);
643 : 2 : uint32_t target_idx = callback(ids);
644 : :
645 : : int32_t *reloc_slots;
646 : 2 : jl_dlsym(hdl, "jl_dispatch_reloc_slots", (void **)&reloc_slots, 1);
647 : 2 : const uint32_t nreloc = reloc_slots[0];
648 : 2 : reloc_slots += 1;
649 : : uint32_t *clone_idxs;
650 : : int32_t *clone_offsets;
651 : 2 : jl_dlsym(hdl, "jl_dispatch_fvars_idxs", (void**)&clone_idxs, 1);
652 : 2 : jl_dlsym(hdl, "jl_dispatch_fvars_offsets", (void**)&clone_offsets, 1);
653 : 2 : uint32_t tag_len = clone_idxs[0];
654 : 2 : clone_idxs += 1;
655 : :
656 [ - + ]: 2 : assert(tag_len & jl_sysimg_tag_mask);
657 : 4 : std::vector<const int32_t*> base_offsets = {res.offsets};
658 : : // Find target
659 [ - + ]: 2 : for (uint32_t i = 0;i < target_idx;i++) {
660 : 0 : uint32_t len = jl_sysimg_val_mask & tag_len;
661 [ # # ]: 0 : if (jl_sysimg_tag_mask & tag_len) {
662 [ # # ]: 0 : if (i != 0)
663 : 0 : clone_offsets += nfunc;
664 : 0 : clone_idxs += len + 1;
665 : : }
666 : : else {
667 : 0 : clone_offsets += len;
668 : 0 : clone_idxs += len + 2;
669 : : }
670 : 0 : tag_len = clone_idxs[-1];
671 [ # # ]: 0 : base_offsets.push_back(tag_len & jl_sysimg_tag_mask ? clone_offsets : nullptr);
672 : : }
673 : :
674 : 2 : bool clone_all = (tag_len & jl_sysimg_tag_mask) != 0;
675 : : // Fill in return value
676 [ + - ]: 2 : if (clone_all) {
677 : : // clone_all
678 [ - + ]: 2 : if (target_idx != 0) {
679 : 0 : res.offsets = clone_offsets;
680 : : }
681 : : }
682 : : else {
683 : 0 : uint32_t base_idx = clone_idxs[0];
684 [ # # ]: 0 : assert(base_idx < target_idx);
685 [ # # ]: 0 : if (target_idx != 0) {
686 : 0 : res.offsets = base_offsets[base_idx];
687 [ # # ]: 0 : assert(res.offsets);
688 : : }
689 : 0 : clone_idxs++;
690 : 0 : res.nclones = tag_len;
691 : 0 : res.clone_offsets = clone_offsets;
692 : 0 : res.clone_idxs = clone_idxs;
693 : : }
694 : : // Do relocation
695 : 2 : uint32_t reloc_i = 0;
696 : 2 : uint32_t len = jl_sysimg_val_mask & tag_len;
697 [ - + ]: 2 : for (uint32_t i = 0; i < len; i++) {
698 : 0 : uint32_t idx = clone_idxs[i];
699 : : int32_t offset;
700 [ # # ]: 0 : if (clone_all) {
701 : 0 : offset = res.offsets[idx];
702 : : }
703 [ # # ]: 0 : else if (idx & jl_sysimg_tag_mask) {
704 : 0 : idx = idx & jl_sysimg_val_mask;
705 : 0 : offset = clone_offsets[i];
706 : : }
707 : : else {
708 : 0 : continue;
709 : : }
710 : 0 : bool found = false;
711 [ # # ]: 0 : for (; reloc_i < nreloc; reloc_i++) {
712 : 0 : auto reloc_idx = ((const uint32_t*)reloc_slots)[reloc_i * 2];
713 [ # # ]: 0 : if (reloc_idx == idx) {
714 : 0 : found = true;
715 : 0 : auto slot = (const void**)(data_base + reloc_slots[reloc_i * 2 + 1]);
716 : 0 : *slot = offset + res.base;
717 : : }
718 [ # # ]: 0 : else if (reloc_idx > idx) {
719 : 0 : break;
720 : : }
721 : : }
722 [ # # ]: 0 : assert(found && "Cannot find GOT entry for cloned function.");
723 : : (void)found;
724 : : }
725 : :
726 : 2 : return res;
727 : : }
728 : :
729 : : template<typename T>
730 : 15 : static inline void check_cmdline(T &&cmdline, bool imaging)
731 : : {
732 [ - + ]: 15 : assert(cmdline.size() > 0);
733 : : // It's unclear what does specifying multiple target when not generating
734 : : // sysimg means. Make it an error for now.
735 [ + + ]: 15 : if (!imaging) {
736 [ - + ]: 10 : if (cmdline.size() > 1) {
737 : 0 : jl_error("More than one command line CPU targets specified "
738 : : "without a `--output-` flag specified");
739 : : }
740 [ - + ]: 10 : if (cmdline[0].en.flags & JL_TARGET_CLONE_ALL) {
741 : 0 : jl_error("\"clone_all\" feature specified "
742 : : "without a `--output-` flag specified");
743 : : }
744 [ - + ]: 10 : if (cmdline[0].en.flags & JL_TARGET_OPTSIZE) {
745 : 0 : jl_error("\"opt_size\" feature specified "
746 : : "without a `--output-` flag specified");
747 : : }
748 [ - + ]: 10 : if (cmdline[0].en.flags & JL_TARGET_MINSIZE) {
749 : 0 : jl_error("\"min_size\" feature specified "
750 : : "without a `--output-` flag specified");
751 : : }
752 : : }
753 : 15 : }
754 : :
755 : : struct SysimgMatch {
756 : : uint32_t best_idx{(uint32_t)-1};
757 : : int vreg_size{0};
758 : : };
759 : :
760 : : // Find the best match in the sysimg.
761 : : // Select the best one based on the largest vector register and largest compatible feature set.
762 : : template<typename S, typename T, typename F>
763 : 2 : static inline SysimgMatch match_sysimg_targets(S &&sysimg, T &&target, F &&max_vector_size)
764 : : {
765 : 2 : SysimgMatch match;
766 : 2 : bool match_name = false;
767 : 2 : int feature_size = 0;
768 [ + + ]: 4 : for (uint32_t i = 0; i < sysimg.size(); i++) {
769 : 2 : auto &imgt = sysimg[i];
770 [ - + ]: 2 : if (!(imgt.en.features & target.dis.features).empty()) {
771 : : // Check sysimg enabled features against runtime disabled features
772 : : // This is valid (and all what we can do)
773 : : // even if one or both of the targets are unknown.
774 : 0 : continue;
775 : : }
776 [ + - ]: 2 : if (imgt.name == target.name) {
777 [ + - ]: 2 : if (!match_name) {
778 : 2 : match_name = true;
779 : 2 : match.vreg_size = 0;
780 : 2 : feature_size = 0;
781 : : }
782 : : }
783 [ # # ]: 0 : else if (match_name) {
784 : 0 : continue;
785 : : }
786 : 2 : int new_vsz = max_vector_size(imgt.en.features);
787 [ - + ]: 2 : if (match.vreg_size > new_vsz)
788 : 0 : continue;
789 : 2 : int new_feature_size = imgt.en.features.nbits();
790 [ + - ]: 2 : if (match.vreg_size < new_vsz) {
791 : 2 : match.best_idx = i;
792 : 2 : match.vreg_size = new_vsz;
793 : 2 : feature_size = new_feature_size;
794 : 2 : continue;
795 : : }
796 [ # # ]: 0 : if (new_feature_size < feature_size)
797 : 0 : continue;
798 : 0 : match.best_idx = i;
799 : 0 : feature_size = new_feature_size;
800 : : }
801 [ - + ]: 2 : if (match.best_idx == (uint32_t)-1)
802 : 0 : jl_error("Unable to find compatible target in system image.");
803 : 2 : return match;
804 : : }
805 : :
806 : : // Debug helper
807 : :
808 : : template<typename CPU, size_t n>
809 : 0 : static inline void dump_cpu_spec(uint32_t cpu, const FeatureList<n> &features,
810 : : const FeatureName *feature_names, uint32_t nfeature_names,
811 : : const CPUSpec<CPU,n> *cpus, uint32_t ncpus)
812 : : {
813 : 0 : bool cpu_found = false;
814 [ # # ]: 0 : for (uint32_t i = 0;i < ncpus;i++) {
815 [ # # ]: 0 : if (cpu == uint32_t(cpus[i].cpu)) {
816 : 0 : cpu_found = true;
817 : 0 : jl_safe_printf("CPU: %s\n", cpus[i].name);
818 : 0 : break;
819 : : }
820 : : }
821 [ # # ]: 0 : if (!cpu_found)
822 : 0 : jl_safe_printf("CPU: generic\n");
823 : 0 : jl_safe_printf("Features:");
824 : 0 : bool first = true;
825 [ # # ]: 0 : for (uint32_t i = 0;i < nfeature_names;i++) {
826 [ # # ]: 0 : if (test_nbit(&features[0], feature_names[i].bit)) {
827 [ # # ]: 0 : if (first) {
828 : 0 : jl_safe_printf(" %s", feature_names[i].name);
829 : 0 : first = false;
830 : : }
831 : : else {
832 : 0 : jl_safe_printf(", %s", feature_names[i].name);
833 : : }
834 : : }
835 : : }
836 : 0 : jl_safe_printf("\n");
837 : 0 : }
838 : :
839 : : }
840 : :
841 : : #if defined(_CPU_X86_) || defined(_CPU_X86_64_)
842 : :
843 : : #include "processor_x86.cpp"
844 : :
845 : : #elif defined(_CPU_AARCH64_) || defined(_CPU_ARM_)
846 : :
847 : : #include "processor_arm.cpp"
848 : :
849 : : #else
850 : :
851 : : #include "processor_fallback.cpp"
852 : :
853 : : #endif
|