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 &envString) {
170  // lowercase
171  std::string envStr = envString;
172  std::transform(envStr.begin(), envStr.end(), envStr.begin(), ::tolower);
173 
174  std::vector<ods_target> Result;
175  if (envStr.empty()) {
176  ods_target acceptAnything;
177  Result.push_back(acceptAnything);
178  return Result;
179  }
180 
181  std::vector<std::string_view> Entries = tokenize(envStr, ";");
182  unsigned int negative_filters = 0;
183  // Each entry: "level_zero:gpu" or "opencl:0.0,0.1" or "opencl:*" but NOT just
184  // "opencl".
185  for (const auto Entry : Entries) {
186  std::vector<std::string_view> Pair = tokenize(Entry, ":");
187  backend be = Parse_ODS_Backend(Pair[0], Entry); // Pair[0] is backend.
188 
189  if (Pair.size() == 1) {
190  std::stringstream ss;
191  ss << "Incomplete selector! Try '" << Pair[0]
192  << ":*' if all devices under the backend was original intention.";
193  throw sycl::exception(sycl::make_error_code(errc::invalid), ss.str());
194  } else if (Pair.size() == 2) {
195  std::vector<std::string_view> Targets = tokenize(Pair[1], ",");
196  for (auto TargetStr : Targets) {
197  ods_target DeviceTarget(be);
198  if (Entry[0] == '!') { // negative filter
199  DeviceTarget.IsNegativeTarget = true;
200  ++negative_filters;
201  } else { // positive filter
202  // no need to set IsNegativeTarget=false because it is so by default.
203  // ensure that no negative filter has been seen because all
204  // negative filters must come after all positive filters
205  if (negative_filters > 0) {
206  std::stringstream ss;
207  ss << "All negative(discarding) filters must appear after all "
208  "positive(accepting) filters!";
209  throw sycl::exception(sycl::make_error_code(errc::invalid),
210  ss.str());
211  }
212  }
213  Parse_ODS_Device(DeviceTarget, TargetStr);
214  Result.push_back(DeviceTarget);
215  }
216  } else if (Pair.size() > 2) {
217  std::stringstream ss;
218  ss << "Error parsing selector string \"" << Entry
219  << "\" Too many colons (:)";
220  throw sycl::exception(sycl::make_error_code(errc::invalid), ss.str());
221  }
222  }
223 
224  // This if statement handles the special case when the filter list
225  // contains at least one negative filter but no positive filters.
226  // This means that no devices will be available at all and so its as if
227  // the filter list was empty because the negative filters do not have any
228  // any effect. Hoewever, it is desirable to be able to set the
229  // ONEAPI_DEVICE_SELECTOR=!*:gpu to consider all devices except gpu
230  // devices so that we must implicitly add an acceptall target to the
231  // list of targets to make this work. So the result will be as if
232  // the filter string had the *:* string in it.
233  if (!Result.empty() && negative_filters == Result.size()) {
234  ods_target acceptAll{backend::all};
235  acceptAll.DeviceType = info::device_type::all;
236  Result.push_back(acceptAll);
237  }
238  return Result;
239 }
240 
241 std::ostream &operator<<(std::ostream &Out, const ods_target &Target) {
242  Out << Target.Backend;
243  if (Target.DeviceType) {
244  auto DeviceTypeMap = getSyclDeviceTypeMap();
245  auto Match = std::find_if(
246  DeviceTypeMap.begin(), DeviceTypeMap.end(),
247  [&](auto Pair) { return (Pair.second == Target.DeviceType); });
248  if (Match != DeviceTypeMap.end()) {
249  Out << ":" << Match->first;
250  } else {
251  Out << ":???";
252  }
253  }
254  if (Target.HasDeviceWildCard)
255  Out << ":*";
256  if (Target.DeviceNum)
257  Out << ":" << Target.DeviceNum.value();
258  if (Target.HasSubDeviceWildCard)
259  Out << ".*";
260  if (Target.SubDeviceNum)
261  Out << "." << Target.SubDeviceNum.value();
262 
263  return Out;
264 }
265 
266 ods_target_list::ods_target_list(const std::string &envStr) {
267  TargetList = Parse_ONEAPI_DEVICE_SELECTOR(envStr);
268 }
269 
270 // Backend is compatible with the SYCL_DEVICE_FILTER in the following cases.
271 // 1. Filter backend is '*' which means ANY backend.
272 // 2. Filter backend match exactly with the given 'Backend'
273 bool ods_target_list::backendCompatible(backend Backend) {
274 
275  bool isESIMD = Backend == backend::ext_intel_esimd_emulator;
276  return std::any_of(
277  TargetList.begin(), TargetList.end(), [&](ods_target &Target) {
278  backend TargetBackend = Target.Backend.value_or(backend::all);
279  return (TargetBackend == Backend) ||
280  (TargetBackend == backend::all && !isESIMD);
281  });
282 }
283 
284 // ---------------------------------------
285 // SYCL_DEVICE_FILTER support
286 
287 device_filter::device_filter(const std::string &FilterString) {
288  std::vector<std::string_view> Tokens = tokenize(FilterString, ":");
289  size_t TripleValueID = 0;
290 
291  auto FindElement = [&](auto Element) {
292  return std::string::npos != Tokens[TripleValueID].find(Element.first);
293  };
294 
295  // Handle the optional 1st field of the filter, backend
296  // Check if the first entry matches with a known backend type
297  auto It = std::find_if(std::begin(getSyclBeMap()), std::end(getSyclBeMap()),
298  FindElement);
299  // If no match is found, set the backend type backend::all
300  // which actually means 'any backend' will be a match.
301  if (It == getSyclBeMap().end())
302  Backend = backend::all;
303  else {
304  Backend = It->second;
305  TripleValueID++;
306 
307  if (Backend == backend::host)
308  std::cerr << "WARNING: The 'host' backend type is no longer supported in "
309  "device filter."
310  << std::endl;
311  }
312 
313  // Handle the optional 2nd field of the filter - device type.
314  // Check if the 2nd entry matches with any known device type.
315  if (TripleValueID >= Tokens.size()) {
316  DeviceType = info::device_type::all;
317  } else {
318  auto Iter = std::find_if(std::begin(getSyclDeviceTypeMap()),
319  std::end(getSyclDeviceTypeMap()), FindElement);
320  // If no match is found, set device_type 'all',
321  // which actually means 'any device_type' will be a match.
322  if (Iter == getSyclDeviceTypeMap().end())
323  DeviceType = info::device_type::all;
324  else {
325  DeviceType = Iter->second;
326  TripleValueID++;
327 
328  if (DeviceType == info::device_type::host)
329  std::cerr << "WARNING: The 'host' device type is no longer supported "
330  "in device filter."
331  << std::endl;
332  }
333  }
334 
335  // Handle the optional 3rd field of the filter, device number
336  // Try to convert the remaining string to an integer.
337  // If succeessful, the converted integer is the desired device num.
338  if (TripleValueID < Tokens.size()) {
339  try {
340  DeviceNum = std::stoi(Tokens[TripleValueID].data());
341  } catch (...) {
342  std::string Message =
343  std::string("Invalid device filter: ") + FilterString +
344  "\nPossible backend values are "
345  "{opencl,level_zero,cuda,hip,esimd_emulator,*}.\n"
346  "Possible device types are {cpu,gpu,acc,*}.\n"
347  "Device number should be an non-negative integer.\n";
348  throw sycl::invalid_parameter_error(Message, PI_ERROR_INVALID_VALUE);
349  }
350  }
351 }
352 
353 device_filter_list::device_filter_list(const std::string &FilterStr) {
354  // First, change the string in all lowercase.
355  // This means we allow the user to use both uppercase and lowercase strings.
356  std::string FilterString = FilterStr;
357  std::transform(FilterString.begin(), FilterString.end(), FilterString.begin(),
358  ::tolower);
359  // SYCL_DEVICE_FILTER can set multiple filters separated by commas.
360  // convert each filter triple string into an istance of device_filter class.
361  size_t Pos = 0;
362  while (Pos < FilterString.size()) {
363  size_t CommaPos = FilterString.find(",", Pos);
364  if (CommaPos == std::string::npos) {
365  CommaPos = FilterString.size();
366  }
367  std::string SubString = FilterString.substr(Pos, CommaPos - Pos);
368  FilterList.push_back(device_filter(SubString));
369  Pos = CommaPos + 1;
370  }
371 }
372 
373 device_filter_list::device_filter_list(device_filter &Filter) {
374  FilterList.push_back(Filter);
375 }
376 
377 void device_filter_list::addFilter(device_filter &Filter) {
378  FilterList.push_back(Filter);
379 }
380 
381 // Backend is compatible with the SYCL_DEVICE_FILTER in the following cases.
382 // 1. Filter backend is '*' which means ANY backend.
383 // 2. Filter backend match exactly with the given 'Backend'
384 bool device_filter_list::backendCompatible(backend Backend) {
385  return std::any_of(
386  FilterList.begin(), FilterList.end(), [&](device_filter &Filter) {
387  backend FilterBackend = Filter.Backend.value_or(backend::all);
388  return (FilterBackend == Backend) || (FilterBackend == backend::all);
389  });
390 }
391 
392 bool device_filter_list::deviceTypeCompatible(info::device_type DeviceType) {
393  return std::any_of(FilterList.begin(), FilterList.end(),
394  [&](device_filter &Filter) {
395  info::device_type FilterDevType =
396  Filter.DeviceType.value_or(info::device_type::all);
397  return (FilterDevType == DeviceType) ||
398  (FilterDevType == info::device_type::all);
399  });
400 }
401 
402 bool device_filter_list::deviceNumberCompatible(int DeviceNum) {
403  return std::any_of(
404  FilterList.begin(), FilterList.end(), [&](device_filter &Filter) {
405  return (!Filter.DeviceNum) || (Filter.DeviceNum.value() == DeviceNum);
406  });
407 }
408 
409 } // namespace detail
410 } // __SYCL_INLINE_VER_NAMESPACE(_V1)
411 } // namespace sycl
sycl::_V1::detail::ods_target::SubDeviceNum
std::optional< unsigned > SubDeviceNum
Definition: device_filter.hpp:43
sycl::_V1::backend
backend
Definition: backend_types.hpp:21
device_filter.hpp
sycl::_V1::make_error_code
std::error_code make_error_code(sycl::errc E) noexcept
Constructs an error code using e and sycl_category()
Definition: exception.cpp:92
config.hpp
sycl::_V1::detail::ods_target::HasDeviceWildCard
bool HasDeviceWildCard
Definition: device_filter.hpp:39
sycl::_V1::detail::getSyclBeMap
const std::array< std::pair< std::string, backend >, 8 > & getSyclBeMap()
Definition: config.cpp:175
__SYCL_INLINE_VER_NAMESPACE
#define __SYCL_INLINE_VER_NAMESPACE(X)
Definition: defines_elementary.hpp:11
sycl::_V1::detail::Parse_ONEAPI_DEVICE_SELECTOR
std::vector< ods_target > Parse_ONEAPI_DEVICE_SELECTOR(const std::string &envStr)
Definition: device_filter.cpp:169
sycl::_V1::detail::ods_target::SubSubDeviceNum
std::optional< unsigned > SubSubDeviceNum
Definition: device_filter.hpp:46
sycl::_V1::detail::ods_target
Definition: device_filter.hpp:34
sycl
---— Error handling, matching OpenCL plugin semantics.
Definition: access.hpp:14
device_impl.hpp
sycl::_V1::detail::tokenize
std::vector< std::string_view > tokenize(const std::string_view &Filter, const std::string &Delim)
Definition: device_filter.cpp:22
sycl::_V1::detail::ods_target::IsNegativeTarget
bool IsNegativeTarget
Definition: device_filter.hpp:48
sycl::_V1::detail::ods_target::HasSubSubDeviceWildCard
bool HasSubSubDeviceWildCard
Definition: device_filter.hpp:45
sycl::_V1::detail::ods_target::HasSubDeviceWildCard
bool HasSubDeviceWildCard
Definition: device_filter.hpp:42
std::cerr
__SYCL_EXTERN_STREAM_ATTRS ostream cerr
Linked to standard error (unbuffered)
sycl::_V1::detail::ods_target::Backend
std::optional< backend > Backend
Definition: device_filter.hpp:36
sycl::_V1::info::device_type
device_type
Definition: info_desc.hpp:44
sycl::_V1::detail::ods_target::DeviceType
std::optional< info::device_type > DeviceType
Definition: device_filter.hpp:37
sycl::_V1::detail::device_filter
Definition: device_filter.hpp:72
sycl::_V1::detail::getSyclDeviceTypeMap
const std::array< std::pair< std::string, info::device_type >, 6 > & getSyclDeviceTypeMap()
Definition: config.cpp:162
sycl::_V1::detail::Parse_ODS_Device
static void Parse_ODS_Device(ods_target &Target, const std::string_view &DeviceStr)
Definition: device_filter.cpp:72
any_of
bool any_of(const simd_mask< _Tp, _Abi > &) noexcept
info_desc.hpp
sycl::_V1::operator<<
std::ostream & operator<<(std::ostream &Out, backend be)
Definition: backend_types.hpp:47
sycl::_V1::detail::ods_target::DeviceNum
std::optional< int > DeviceNum
Definition: device_filter.hpp:40
sycl::_V1::detail::Parse_ODS_Backend
static backend Parse_ODS_Backend(const std::string_view &BackendStr, const std::string_view &FullEntry)
Definition: device_filter.cpp:49