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 {
19 inline namespace _V1 {
20 namespace detail {
21 
22 std::vector<std::string_view> tokenize(const std::string_view &Filter,
23  const std::string &Delim,
24  bool ProhibitEmptyTokens = false) {
25  std::vector<std::string_view> Tokens;
26  size_t Pos = 0;
27  size_t LastPos = 0;
28 
29  while ((Pos = Filter.find(Delim, LastPos)) != std::string::npos) {
30  std::string_view Tok(Filter.data() + LastPos, (Pos - LastPos));
31 
32  if (!Tok.empty()) {
33  Tokens.push_back(Tok);
34  } else if (ProhibitEmptyTokens) {
35  throw sycl::exception(
37  "ONEAPI_DEVICE_SELECTOR parsing error. Empty input before '" + Delim +
38  "' delimiter is not allowed.");
39  }
40  // move the search starting index
41  LastPos = Pos + 1;
42  }
43 
44  // Add remainder if any
45  if (LastPos < Filter.size()) {
46  std::string_view Tok(Filter.data() + LastPos, Filter.size() - LastPos);
47  Tokens.push_back(Tok);
48  } else if ((LastPos != 0) && ProhibitEmptyTokens) {
49  // if delimiter is the last sybmol in the string.
50  throw sycl::exception(
52  "ONEAPI_DEVICE_SELECTOR parsing error. Empty input after '" + Delim +
53  "' delimiter is not allowed.");
54  }
55  return Tokens;
56 }
57 
58 // ---------------------------------------
59 // ONEAPI_DEVICE_SELECTOR support
60 
61 static backend Parse_ODS_Backend(const std::string_view &BackendStr,
62  const std::string_view &FullEntry) {
63  // Check if the first entry matches with a known backend type
64  auto SyclBeMap =
65  getSyclBeMap(); // <-- std::array<std::pair<std::string, backend>>
66  // [{"level_zero", backend::level_zero}, {"*", ::all}, ...
67  auto It =
68  std::find_if(std::begin(SyclBeMap), std::end(SyclBeMap),
69  [&](auto BePair) { return BackendStr == BePair.first; });
70 
71  if (It == SyclBeMap.end()) {
72  // backend is required
73  std::stringstream ss;
74  ss << "ONEAPI_DEVICE_SELECTOR parsing error. Backend is required but "
75  "missing from \""
76  << FullEntry << "\"";
78  } else {
79  return It->second;
80  }
81 }
82 
83 static void Parse_ODS_Device(ods_target &Target,
84  const std::string_view &DeviceStr) {
85  // DeviceStr will be: 'gpu', '*', '0', '0.1', 'gpu.*', '0.*', or 'gpu.2', etc.
86  std::vector<std::string_view> DeviceSubTuple =
87  tokenize(DeviceStr, ".", true /* ProhibitEmptyTokens */);
88  if (DeviceSubTuple.empty())
89  throw sycl::exception(
91  "ONEAPI_DEVICE_SELECTOR parsing error. Device must be specified.");
92 
93  std::string_view TopDeviceStr = DeviceSubTuple[0];
94 
95  // Handle explicit device type (e.g. 'gpu').
96  auto DeviceTypeMap = getSyclDeviceTypeMap();
97 
98  auto It =
99  std::find_if(std::begin(DeviceTypeMap), std::end(DeviceTypeMap),
100  [&](auto DtPair) { return TopDeviceStr == DtPair.first; });
101  if (It != DeviceTypeMap.end()) {
102  Target.DeviceType = It->second;
103  // Handle wildcard.
104  if (TopDeviceStr[0] == '*') {
105  Target.HasDeviceWildCard = true;
106  Target.DeviceType = {};
107  }
108  } else { // Only thing left is a number.
109  std::string TDS(TopDeviceStr);
110  try {
111  Target.DeviceNum = std::stoi(TDS);
112  } catch (...) {
114  "error parsing device number: " + TDS);
115  }
116  }
117 
118  if (DeviceSubTuple.size() >= 2) {
119  // We have a subdevice.
120  // The grammar for sub-devices is ... restrictive. Neither 'gpu.0' nor
121  // 'gpu.*' are allowed. If wanting a sub-device, then the device itself must
122  // be specified by a number or a wildcard, and if by wildcard, the only
123  // allowable sub-device is another wildcard.
124 
125  if (Target.DeviceType)
126  throw sycl::exception(
128  "sub-devices can only be requested when parent device is specified "
129  "by number or wildcard, not a device type like 'gpu'");
130 
131  std::string_view SubDeviceStr = DeviceSubTuple[1];
132  // SubDeviceStr is wildcard or number.
133  if (SubDeviceStr[0] == '*') {
134  Target.HasSubDeviceWildCard = true;
135  } else {
136  // sub-device requested by number. So parent device must be a number too
137  // or it's a parsing error.
138  if (Target.HasDeviceWildCard)
140  "sub-device can't be requested by number if "
141  "parent device is specified by a wildcard.");
142 
143  std::string SDS(SubDeviceStr);
144  try {
145  Target.SubDeviceNum = std::stoi(SDS);
146  } catch (...) {
148  "error parsing sub-device index: " + SDS);
149  }
150  }
151  }
152  if (DeviceSubTuple.size() == 3) {
153  // We have a sub-sub-device.
154  // Similar rules for sub-sub-devices as for sub-devices above.
155 
156  std::string_view SubSubDeviceStr = DeviceSubTuple[2];
157  if (SubSubDeviceStr[0] == '*') {
158  Target.HasSubSubDeviceWildCard = true;
159  } else {
160  // sub-sub-device requested by number. So partition above must be a number
161  // too or it's a parsing error.
162  if (Target.HasSubDeviceWildCard)
164  "sub-sub-device can't be requested by number if "
165  "sub-device before is specified by a wildcard.");
166 
167  std::string SSDS(SubSubDeviceStr);
168  try {
169  Target.SubSubDeviceNum = std::stoi(SSDS);
170  } catch (...) {
172  "error parsing sub-sub-device index: " + SSDS);
173  }
174  }
175  } else if (DeviceSubTuple.size() > 3) {
176  std::stringstream ss;
177  ss << "error parsing " << DeviceStr
178  << " Only two levels of sub-devices supported at this time";
180  }
181 }
182 
183 std::vector<ods_target>
184 Parse_ONEAPI_DEVICE_SELECTOR(const std::string &envString) {
185  // lowercase
186  std::string envStr = envString;
187  std::transform(envStr.begin(), envStr.end(), envStr.begin(), ::tolower);
188 
189  std::vector<ods_target> Result;
190  if (envStr.empty()) {
191  ods_target acceptAnything;
192  Result.push_back(acceptAnything);
193  return Result;
194  }
195 
196  std::vector<std::string_view> Entries = tokenize(envStr, ";");
197  unsigned int negative_filters = 0;
198  // Each entry: "level_zero:gpu" or "opencl:0.0,0.1" or "opencl:*" but NOT just
199  // "opencl".
200  for (const auto Entry : Entries) {
201  std::vector<std::string_view> Pair =
202  tokenize(Entry, ":", true /* ProhibitEmptyTokens */);
203 
204  // Error handling. ONEAPI_DEVICE_SELECTOR terms should be in the
205  // format: <backend>:<devices>.
206  if (Pair.empty()) {
207  std::stringstream ss;
208  ss << "Incomplete selector! Backend and device must be specified.";
210  } else if (Pair.size() == 1) {
211  std::stringstream ss;
212  ss << "Incomplete selector! Try '" << Pair[0]
213  << ":*' if all devices under the backend was original intention.";
215  } else if (Pair.size() > 2) {
216  std::stringstream ss;
217  ss << "Error parsing selector string \"" << Entry
218  << "\" Too many colons (:)";
220  }
221 
222  // Parse ONEAPI_DEVICE_SELECTOR terms for Pair.size() == 2.
223  else {
224 
225  // Remove `!` from input backend string if it is present.
226  std::string_view input_be = Pair[0];
227  if (Pair[0][0] == '!')
228  input_be = Pair[0].substr(1);
229 
230  backend be = Parse_ODS_Backend(input_be, Entry);
231 
232  // For each backend, we can have multiple targets, seperated by ','.
233  std::vector<std::string_view> Targets = tokenize(Pair[1], ",");
234  for (auto TargetStr : Targets) {
235  ods_target DeviceTarget(be);
236  if (Entry[0] == '!') { // negative filter
237  DeviceTarget.IsNegativeTarget = true;
238  ++negative_filters;
239  } else { // positive filter
240  // no need to set IsNegativeTarget=false because it is so by default.
241  // ensure that no negative filter has been seen because all
242  // negative filters must come after all positive filters
243  if (negative_filters > 0) {
244  std::stringstream ss;
245  ss << "All negative(discarding) filters must appear after all "
246  "positive(accepting) filters!";
248  ss.str());
249  }
250  }
251  Parse_ODS_Device(DeviceTarget, TargetStr);
252  Result.push_back(DeviceTarget);
253  }
254  }
255  }
256 
257  // This if statement handles the special case when the filter list
258  // contains at least one negative filter but no positive filters.
259  // This means that no devices will be available at all and so its as if
260  // the filter list was empty because the negative filters do not have any
261  // any effect. Hoewever, it is desirable to be able to set the
262  // ONEAPI_DEVICE_SELECTOR=!*:gpu to consider all devices except gpu
263  // devices so that we must implicitly add an acceptall target to the
264  // list of targets to make this work. So the result will be as if
265  // the filter string had the *:* string in it.
266  if (!Result.empty() && negative_filters == Result.size()) {
267  ods_target acceptAll{backend::all};
268  acceptAll.DeviceType = info::device_type::all;
269  Result.push_back(acceptAll);
270  }
271  return Result;
272 }
273 
274 std::ostream &operator<<(std::ostream &Out, const ods_target &Target) {
275  Out << Target.Backend;
276  if (Target.DeviceType) {
277  auto DeviceTypeMap = getSyclDeviceTypeMap();
278  auto Match = std::find_if(
279  DeviceTypeMap.begin(), DeviceTypeMap.end(),
280  [&](auto Pair) { return (Pair.second == Target.DeviceType); });
281  if (Match != DeviceTypeMap.end()) {
282  Out << ":" << Match->first;
283  } else {
284  Out << ":???";
285  }
286  }
287  if (Target.HasDeviceWildCard)
288  Out << ":*";
289  if (Target.DeviceNum)
290  Out << ":" << Target.DeviceNum.value();
291  if (Target.HasSubDeviceWildCard)
292  Out << ".*";
293  if (Target.SubDeviceNum)
294  Out << "." << Target.SubDeviceNum.value();
295 
296  return Out;
297 }
298 
299 ods_target_list::ods_target_list(const std::string &envStr) {
300  TargetList = Parse_ONEAPI_DEVICE_SELECTOR(envStr);
301 }
302 
303 // Backend is compatible with the ONEAPI_DEVICE_SELECTOR in the following cases.
304 // 1. Filter backend is '*' which means ANY backend.
305 // 2. Filter backend match exactly with the given 'Backend'
307 
308  return std::any_of(
309  TargetList.begin(), TargetList.end(), [&](ods_target &Target) {
310  backend TargetBackend = Target.Backend.value_or(backend::all);
311  return (TargetBackend == Backend) || (TargetBackend == backend::all);
312  });
313 }
314 } // namespace detail
315 } // namespace _V1
316 } // namespace sycl
bool backendCompatible(backend Backend)
static void Parse_ODS_Device(ods_target &Target, const std::string_view &DeviceStr)
const std::array< std::pair< std::string, backend >, 8 > & getSyclBeMap()
Definition: config.cpp:169
static backend Parse_ODS_Backend(const std::string_view &BackendStr, const std::string_view &FullEntry)
std::ostream & operator<<(std::ostream &os, std::optional< T > const &opt)
const std::array< std::pair< std::string, info::device_type >, 6 > & getSyclDeviceTypeMap()
Definition: config.hpp:239
std::vector< ods_target > Parse_ONEAPI_DEVICE_SELECTOR(const std::string &envStr)
std::vector< std::string_view > tokenize(const std::string_view &Filter, const std::string &Delim, bool ProhibitEmptyTokens=false)
std::error_code make_error_code(sycl::errc E) noexcept
Constructs an error code using e and sycl_category()
Definition: exception.cpp:93
Definition: access.hpp:18
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