DPC++ Runtime
Runtime libraries for oneAPI DPC++
device_filter.cpp
Go to the documentation of this file.
1 //==------------------- device_filter.cpp ----------------------------------==//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include <detail/config.hpp>
10 #include <detail/device_impl.hpp>
12 #include <sycl/info/info_desc.hpp>
13 
14 #include <cstring>
15 #include <sstream>
16 #include <string_view>
17 
18 namespace sycl {
20 namespace detail {
21 
22 std::vector<std::string_view> tokenize(const std::string_view &Filter,
23  const std::string &Delim) {
24  std::vector<std::string_view> Tokens;
25  size_t Pos = 0;
26  size_t LastPos = 0;
27 
28  while ((Pos = Filter.find(Delim, LastPos)) != std::string::npos) {
29  std::string_view Tok(Filter.data() + LastPos, (Pos - LastPos));
30 
31  if (!Tok.empty()) {
32  Tokens.push_back(Tok);
33  }
34  // move the search starting index
35  LastPos = Pos + 1;
36  }
37 
38  // Add remainder if any
39  if (LastPos < Filter.size()) {
40  std::string_view Tok(Filter.data() + LastPos, Filter.size() - LastPos);
41  Tokens.push_back(Tok);
42  }
43  return Tokens;
44 }
45 
46 // ---------------------------------------
47 // ONEAPI_DEVICE_SELECTOR support
48 
49 static backend Parse_ODS_Backend(const std::string_view &BackendStr,
50  const std::string_view &FullEntry) {
51  // Check if the first entry matches with a known backend type
52  auto SyclBeMap =
53  getSyclBeMap(); // <-- std::array<std::pair<std::string, backend>>
54  // [{"level_zero", backend::level_zero}, {"*", ::all}, ...
55  auto It = std::find_if(
56  std::begin(SyclBeMap), std::end(SyclBeMap), [&](auto BePair) {
57  return std::string::npos != BackendStr.find(BePair.first);
58  });
59 
60  if (It == SyclBeMap.end()) {
61  // backend is required
62  std::stringstream ss;
63  ss << "ONEAPI_DEVICE_SELECTOR parsing error. Backend is required but "
64  "missing from \""
65  << FullEntry << "\"";
66  throw sycl::exception(sycl::make_error_code(errc::invalid), ss.str());
67  } else {
68  return It->second;
69  }
70 }
71 
72 static void Parse_ODS_Device(ods_target &Target,
73  const std::string_view &DeviceStr) {
74  // DeviceStr will be: 'gpu', '*', '0', '0.1', 'gpu.*', '0.*', or 'gpu.2', etc.
75  std::vector<std::string_view> DeviceSubTuple = tokenize(DeviceStr, ".");
76  std::string_view TopDeviceStr = DeviceSubTuple[0];
77 
78  // Handle explicit device type (e.g. 'gpu').
79  auto DeviceTypeMap =
80  getSyclDeviceTypeMap(); // <-- std::array<std::pair<std::string,
81  // info::device::type>>
82  auto It = std::find_if(
83  std::begin(DeviceTypeMap), std::end(DeviceTypeMap), [&](auto DtPair) {
84  return std::string::npos != TopDeviceStr.find(DtPair.first);
85  });
86  if (It != DeviceTypeMap.end()) {
87  Target.DeviceType = It->second;
88  // Handle wildcard.
89  if (TopDeviceStr[0] == '*') {
90  Target.HasDeviceWildCard = true;
91  Target.DeviceType = {};
92  }
93  } else { // Only thing left is a number.
94  std::string TDS(TopDeviceStr);
95  try {
96  Target.DeviceNum = std::stoi(TDS);
97  } catch (...) {
98  throw sycl::exception(sycl::make_error_code(errc::invalid),
99  "error parsing device number: " + TDS);
100  }
101  }
102 
103  if (DeviceSubTuple.size() >= 2) {
104  // We have a subdevice.
105  // The grammar for sub-devices is ... restrictive. Neither 'gpu.0' nor
106  // 'gpu.*' are allowed. If wanting a sub-device, then the device itself must
107  // be specified by a number or a wildcard, and if by wildcard, the only
108  // allowable sub-device is another wildcard.
109 
110  if (Target.DeviceType)
111  throw sycl::exception(
112  sycl::make_error_code(errc::invalid),
113  "sub-devices can only be requested when parent device is specified "
114  "by number or wildcard, not a device type like 'gpu'");
115 
116  std::string_view SubDeviceStr = DeviceSubTuple[1];
117  // SubDeviceStr is wildcard or number.
118  if (SubDeviceStr[0] == '*') {
119  Target.HasSubDeviceWildCard = true;
120  } else {
121  // sub-device requested by number. So parent device must be a number too
122  // or it's a parsing error.
123  if (Target.HasDeviceWildCard)
124  throw sycl::exception(sycl::make_error_code(errc::invalid),
125  "sub-device can't be requested by number if "
126  "parent device is specified by a wildcard.");
127 
128  std::string SDS(SubDeviceStr);
129  try {
130  Target.SubDeviceNum = std::stoi(SDS);
131  } catch (...) {
132  throw sycl::exception(sycl::make_error_code(errc::invalid),
133  "error parsing sub-device index: " + SDS);
134  }
135  }
136  }
137  if (DeviceSubTuple.size() == 3) {
138  // We have a sub-sub-device.
139  // Similar rules for sub-sub-devices as for sub-devices above.
140 
141  std::string_view SubSubDeviceStr = DeviceSubTuple[2];
142  if (SubSubDeviceStr[0] == '*') {
143  Target.HasSubSubDeviceWildCard = true;
144  } else {
145  // sub-sub-device requested by number. So partition above must be a number
146  // too or it's a parsing error.
147  if (Target.HasSubDeviceWildCard)
148  throw sycl::exception(sycl::make_error_code(errc::invalid),
149  "sub-sub-device can't be requested by number if "
150  "sub-device before is specified by a wildcard.");
151 
152  std::string SSDS(SubSubDeviceStr);
153  try {
154  Target.SubSubDeviceNum = std::stoi(SSDS);
155  } catch (...) {
156  throw sycl::exception(sycl::make_error_code(errc::invalid),
157  "error parsing sub-sub-device index: " + SSDS);
158  }
159  }
160  } else if (DeviceSubTuple.size() > 3) {
161  std::stringstream ss;
162  ss << "error parsing " << DeviceStr
163  << " Only two levels of sub-devices supported at this time";
164  throw sycl::exception(sycl::make_error_code(errc::invalid), ss.str());
165  }
166 }
167 
168 std::vector<ods_target>
169 Parse_ONEAPI_DEVICE_SELECTOR(const std::string &envStr) {
170  std::vector<ods_target> Result;
171  if (envStr.empty()) {
172  ods_target acceptAnything;
173  Result.push_back(acceptAnything);
174  return Result;
175  }
176 
177  std::vector<std::string_view> Entries = tokenize(envStr, ";");
178  unsigned int negative_filters = 0;
179  // Each entry: "level_zero:gpu" or "opencl:0.0,0.1" or "opencl:*" but NOT just
180  // "opencl".
181  for (const auto Entry : Entries) {
182  std::vector<std::string_view> Pair = tokenize(Entry, ":");
183  backend be = Parse_ODS_Backend(Pair[0], Entry); // Pair[0] is backend.
184 
185  if (Pair.size() == 1) {
186  std::stringstream ss;
187  ss << "Incomplete selector! Try '" << Pair[0]
188  << ":*' if all devices under the backend was original intention.";
189  throw sycl::exception(sycl::make_error_code(errc::invalid), ss.str());
190  } else if (Pair.size() == 2) {
191  std::vector<std::string_view> Targets = tokenize(Pair[1], ",");
192  for (auto TargetStr : Targets) {
193  ods_target DeviceTarget(be);
194  if (Entry[0] == '!') { // negative filter
195  DeviceTarget.IsNegativeTarget = true;
196  ++negative_filters;
197  } else { // positive filter
198  // no need to set IsNegativeTarget=false because it is so by default.
199  // ensure that no negative filter has been seen because all
200  // negative filters must come after all positive filters
201  if (negative_filters > 0) {
202  std::stringstream ss;
203  ss << "All negative(discarding) filters must appear after all "
204  "positive(accepting) filters!";
205  throw sycl::exception(sycl::make_error_code(errc::invalid),
206  ss.str());
207  }
208  }
209  Parse_ODS_Device(DeviceTarget, TargetStr);
210  Result.push_back(DeviceTarget);
211  }
212  } else if (Pair.size() > 2) {
213  std::stringstream ss;
214  ss << "Error parsing selector string \"" << Entry
215  << "\" Too many colons (:)";
216  throw sycl::exception(sycl::make_error_code(errc::invalid), ss.str());
217  }
218  }
219 
220  // This if statement handles the special case when the filter list
221  // contains at least one negative filter but no positive filters.
222  // This means that no devices will be available at all and so its as if
223  // the filter list was empty because the negative filters do not have any
224  // any effect. Hoewever, it is desirable to be able to set the
225  // ONEAPI_DEVICE_SELECTOR=!*:gpu to consider all devices except gpu
226  // devices so that we must implicitly add an acceptall target to the
227  // list of targets to make this work. So the result will be as if
228  // the filter string had the *:* string in it.
229  if (!Result.empty() && negative_filters == Result.size()) {
230  ods_target acceptAll{backend::all};
231  acceptAll.DeviceType = info::device_type::all;
232  Result.push_back(acceptAll);
233  }
234  return Result;
235 }
236 
237 std::ostream &operator<<(std::ostream &Out, const ods_target &Target) {
238  Out << Target.Backend;
239  if (Target.DeviceType) {
240  auto DeviceTypeMap = getSyclDeviceTypeMap();
241  auto Match = std::find_if(
242  DeviceTypeMap.begin(), DeviceTypeMap.end(),
243  [&](auto Pair) { return (Pair.second == Target.DeviceType); });
244  if (Match != DeviceTypeMap.end()) {
245  Out << ":" << Match->first;
246  } else {
247  Out << ":???";
248  }
249  }
250  if (Target.HasDeviceWildCard)
251  Out << ":*";
252  if (Target.DeviceNum)
253  Out << ":" << Target.DeviceNum.value();
254  if (Target.HasSubDeviceWildCard)
255  Out << ".*";
256  if (Target.SubDeviceNum)
257  Out << "." << Target.SubDeviceNum.value();
258 
259  return Out;
260 }
261 
262 ods_target_list::ods_target_list(const std::string &envStr) {
263  TargetList = Parse_ONEAPI_DEVICE_SELECTOR(envStr);
264 }
265 
266 // Backend is compatible with the SYCL_DEVICE_FILTER in the following cases.
267 // 1. Filter backend is '*' which means ANY backend.
268 // 2. Filter backend match exactly with the given 'Backend'
269 bool ods_target_list::backendCompatible(backend Backend) {
270 
271  bool isESIMD = Backend == backend::ext_intel_esimd_emulator;
272  return std::any_of(
273  TargetList.begin(), TargetList.end(), [&](ods_target &Target) {
274  backend TargetBackend = Target.Backend.value_or(backend::all);
275  return (TargetBackend == Backend) ||
276  (TargetBackend == backend::all && !isESIMD);
277  });
278 }
279 
280 // ---------------------------------------
281 // SYCL_DEVICE_FILTER support
282 
283 device_filter::device_filter(const std::string &FilterString) {
284  std::vector<std::string_view> Tokens = tokenize(FilterString, ":");
285  size_t TripleValueID = 0;
286 
287  auto FindElement = [&](auto Element) {
288  return std::string::npos != Tokens[TripleValueID].find(Element.first);
289  };
290 
291  // Handle the optional 1st field of the filter, backend
292  // Check if the first entry matches with a known backend type
293  auto It = std::find_if(std::begin(getSyclBeMap()), std::end(getSyclBeMap()),
294  FindElement);
295  // If no match is found, set the backend type backend::all
296  // which actually means 'any backend' will be a match.
297  if (It == getSyclBeMap().end())
298  Backend = backend::all;
299  else {
300  Backend = It->second;
301  TripleValueID++;
302 
303  if (Backend == backend::host)
304  std::cerr << "WARNING: The 'host' backend type is no longer supported in "
305  "device filter."
306  << std::endl;
307  }
308 
309  // Handle the optional 2nd field of the filter - device type.
310  // Check if the 2nd entry matches with any known device type.
311  if (TripleValueID >= Tokens.size()) {
312  DeviceType = info::device_type::all;
313  } else {
314  auto Iter = std::find_if(std::begin(getSyclDeviceTypeMap()),
315  std::end(getSyclDeviceTypeMap()), FindElement);
316  // If no match is found, set device_type 'all',
317  // which actually means 'any device_type' will be a match.
318  if (Iter == getSyclDeviceTypeMap().end())
319  DeviceType = info::device_type::all;
320  else {
321  DeviceType = Iter->second;
322  TripleValueID++;
323 
324  if (DeviceType == info::device_type::host)
325  std::cerr << "WARNING: The 'host' device type is no longer supported "
326  "in device filter."
327  << std::endl;
328  }
329  }
330 
331  // Handle the optional 3rd field of the filter, device number
332  // Try to convert the remaining string to an integer.
333  // If succeessful, the converted integer is the desired device num.
334  if (TripleValueID < Tokens.size()) {
335  try {
336  DeviceNum = std::stoi(Tokens[TripleValueID].data());
337  } catch (...) {
338  std::string Message =
339  std::string("Invalid device filter: ") + FilterString +
340  "\nPossible backend values are "
341  "{opencl,level_zero,cuda,hip,esimd_emulator,*}.\n"
342  "Possible device types are {cpu,gpu,acc,*}.\n"
343  "Device number should be an non-negative integer.\n";
344  throw sycl::invalid_parameter_error(Message, PI_ERROR_INVALID_VALUE);
345  }
346  }
347 }
348 
349 device_filter_list::device_filter_list(const std::string &FilterStr) {
350  // First, change the string in all lowercase.
351  // This means we allow the user to use both uppercase and lowercase strings.
352  std::string FilterString = FilterStr;
353  std::transform(FilterString.begin(), FilterString.end(), FilterString.begin(),
354  ::tolower);
355  // SYCL_DEVICE_FILTER can set multiple filters separated by commas.
356  // convert each filter triple string into an istance of device_filter class.
357  size_t Pos = 0;
358  while (Pos < FilterString.size()) {
359  size_t CommaPos = FilterString.find(",", Pos);
360  if (CommaPos == std::string::npos) {
361  CommaPos = FilterString.size();
362  }
363  std::string SubString = FilterString.substr(Pos, CommaPos - Pos);
364  FilterList.push_back(device_filter(SubString));
365  Pos = CommaPos + 1;
366  }
367 }
368 
369 device_filter_list::device_filter_list(device_filter &Filter) {
370  FilterList.push_back(Filter);
371 }
372 
373 void device_filter_list::addFilter(device_filter &Filter) {
374  FilterList.push_back(Filter);
375 }
376 
377 // Backend is compatible with the SYCL_DEVICE_FILTER in the following cases.
378 // 1. Filter backend is '*' which means ANY backend.
379 // 2. Filter backend match exactly with the given 'Backend'
380 bool device_filter_list::backendCompatible(backend Backend) {
381  return std::any_of(
382  FilterList.begin(), FilterList.end(), [&](device_filter &Filter) {
383  backend FilterBackend = Filter.Backend.value_or(backend::all);
384  return (FilterBackend == Backend) || (FilterBackend == backend::all);
385  });
386 }
387 
388 bool device_filter_list::deviceTypeCompatible(info::device_type DeviceType) {
389  return std::any_of(FilterList.begin(), FilterList.end(),
390  [&](device_filter &Filter) {
391  info::device_type FilterDevType =
392  Filter.DeviceType.value_or(info::device_type::all);
393  return (FilterDevType == DeviceType) ||
394  (FilterDevType == info::device_type::all);
395  });
396 }
397 
398 bool device_filter_list::deviceNumberCompatible(int DeviceNum) {
399  return std::any_of(
400  FilterList.begin(), FilterList.end(), [&](device_filter &Filter) {
401  return (!Filter.DeviceNum) || (Filter.DeviceNum.value() == DeviceNum);
402  });
403 }
404 
405 } // namespace detail
406 } // __SYCL_INLINE_VER_NAMESPACE(_V1)
407 } // namespace sycl
#define __SYCL_INLINE_VER_NAMESPACE(X)
__SYCL_EXTERN_STREAM_ATTRS ostream cerr
Linked to standard error (unbuffered)
static void Parse_ODS_Device(ods_target &Target, const std::string_view &DeviceStr)
std::vector< std::string_view > tokenize(const std::string_view &Filter, const std::string &Delim)
static backend Parse_ODS_Backend(const std::string_view &BackendStr, const std::string_view &FullEntry)
const std::array< std::pair< std::string, info::device_type >, 6 > & getSyclDeviceTypeMap()
Definition: config.cpp:162
std::vector< ods_target > Parse_ONEAPI_DEVICE_SELECTOR(const std::string &envStr)
const std::array< std::pair< std::string, backend >, 7 > & getSyclBeMap()
Definition: config.cpp:175
std::ostream & operator<<(std::ostream &Out, backend be)
std::error_code make_error_code(sycl::errc E) noexcept
Constructs an error code using e and sycl_category()
Definition: exception.cpp:91
---— Error handling, matching OpenCL plugin semantics.
Definition: access.hpp:14
bool any_of(const simd_mask< _Tp, _Abi > &) noexcept
std::optional< backend > Backend
std::optional< int > DeviceNum
std::optional< unsigned > SubDeviceNum
std::optional< info::device_type > DeviceType
std::optional< unsigned > SubSubDeviceNum