Skip to content

Commit 9216b49

Browse files
[SYCL] Improve SYCL_DEVICE_ALLOWLIST (#3826)
This patch adds more stability to SYCL_DEVICE_ALLOWLIST: 1. Introduce 3 new keys `BackendName`, `DeviceType` and `DeviceVendorId` which should be used instead of `DeviceName` and `PlatformName`. These 3 new keys are more stable. 2. Refactor the implementation of SYCL_DEVICE_ALLOWLIST to make it more stable, to fix std::bad_alloc crash, and to make the code testable 3. Add unit tests for parsing SYCL_DEVICE_ALLOWLIST value and for functionality which allows device to use or reject it.
1 parent 61b81ff commit 9216b49

File tree

11 files changed

+907
-176
lines changed

11 files changed

+907
-176
lines changed

sycl/doc/EnvironmentVariables.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ subject to change. Do not rely on these variables in production code.
2323
| `SYCL_DISABLE_EXECUTION_GRAPH_CLEANUP` | Any(\*) | Disable cleanup of finished command nodes at host-device synchronization points. |
2424
| `SYCL_THROW_ON_BLOCK` | Any(\*) | Throw an exception on attempt to wait for a blocked command. |
2525
| `SYCL_DEVICELIB_INHIBIT_NATIVE` | String of device library extensions (separated by a whitespace) | Do not rely on device native support for devicelib extensions listed in this option. |
26-
| `SYCL_DEVICE_ALLOWLIST` | A list of devices and their driver version following the pattern: DeviceName:{{XXX}},DriverVersion:{{X.Y.Z.W}}. Also may contain PlatformName and PlatformVersion | Filter out devices that do not match the pattern specified. Regular expression can be passed and the DPC++ runtime will select only those devices which satisfy the regex. Special characters, such as parenthesis, must be escaped. More than one device can be specified using the piping symbol "\|".|
26+
| `SYCL_DEVICE_ALLOWLIST` | A list of devices and their driver version following the pattern: `BackendName:XXX,DeviceType:YYY,DeviceVendorId:0xXYZW,DriverVersion:{{X.Y.Z.W}}`. Also may contain `PlatformVersion`, `DeviceName` and `PlatformName`. There is no fixed order of properties in the pattern. | Filter out devices that do not match the pattern specified. `BackendName` accepts `host`, `opencl`, `level_zero` or `cuda`. `DeviceType` accepts `host`, `cpu`, `gpu` or `acc`. `DeviceVendorId` accepts uint32_t in hex form (`0xXYZW`). `DriverVersion`, `PlatformVersion`, `DeviceName` and `PlatformName` accept regular expression. Special characters, such as parenthesis, must be escaped. DPC++ runtime will select only those devices which satisfy provided values above and regex. More than one device can be specified using the piping symbol "\|".|
2727
| `SYCL_QUEUE_THREAD_POOL_SIZE` | Positive integer | Number of threads in thread pool of queue. |
2828
| `SYCL_DEVICELIB_NO_FALLBACK` | Any(\*) | Disable loading and linking of device library images |
2929
| `SYCL_PI_LEVEL_ZERO_MAX_COMMAND_LIST_CACHE` | Positive integer | Maximum number of oneAPI Level Zero Command lists that can be allocated with no reuse before throwing an "out of resources" error. Default is 20000, threshold may be increased based on resource availabilty and workload demand. |

sycl/source/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ set(SYCL_SOURCES
102102
"backend/level_zero.cpp"
103103
"backend.cpp"
104104
"detail/accessor_impl.cpp"
105+
"detail/allowlist.cpp"
105106
"detail/buffer_impl.cpp"
106107
"detail/builtins_common.cpp"
107108
"detail/builtins_geometric.cpp"

sycl/source/detail/allowlist.cpp

Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,362 @@
1+
//==-------------- allowlist.cpp - SYCL_DEVICE_ALLOWLIST -------------------==//
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/allowlist.hpp>
10+
#include <detail/config.hpp>
11+
#include <detail/device_impl.hpp>
12+
#include <detail/platform_info.hpp>
13+
14+
#include <regex>
15+
16+
__SYCL_INLINE_NAMESPACE(cl) {
17+
namespace sycl {
18+
namespace detail {
19+
20+
constexpr char BackendNameKeyName[] = "BackendName";
21+
constexpr char DeviceTypeKeyName[] = "DeviceType";
22+
constexpr char DeviceVendorIdKeyName[] = "DeviceVendorId";
23+
constexpr char DriverVersionKeyName[] = "DriverVersion";
24+
constexpr char PlatformVersionKeyName[] = "PlatformVersion";
25+
constexpr char DeviceNameKeyName[] = "DeviceName";
26+
constexpr char PlatformNameKeyName[] = "PlatformName";
27+
28+
constexpr std::array<const char *, 7> SupportedAllowListKeyNames{
29+
BackendNameKeyName, DeviceTypeKeyName, DeviceVendorIdKeyName,
30+
DriverVersionKeyName, PlatformVersionKeyName, DeviceNameKeyName,
31+
PlatformNameKeyName};
32+
33+
// Parsing and validating SYCL_DEVICE_ALLOWLIST variable value.
34+
//
35+
// The value has the following form:
36+
// DeviceDesc1|DeviceDesc2|<...>|DeviceDescN
37+
// DeviceDescN is the set of descriptions for the device which should be
38+
// allowed. The sets of device descriptions are separated by '|' symbol. The set
39+
// of descriptions has the following structure:
40+
// DeviceDescN = Key1:Value1,Key2:Value2,...,KeyN:ValueN
41+
// Device descriptions are separated by ',' symbol.
42+
// Key and value of a device description are separated by ":" symbol.
43+
// KeyN is the key of a device description, it could be one of the following
44+
// from SupportedAllowListKeyNames vector above.
45+
// DeviceName and PlatformName device descriptions are deprecated and will be
46+
// removed in one of the future releases.
47+
// ValueN is the value of a device description, it could be regex and some fixed
48+
// string.
49+
// Function should return parsed SYCL_DEVICE_ALLOWLIST variable value as
50+
// AllowListParsedT type (vector of maps), e.g.:
51+
// {{Key1: Value1, Key2: Value2}, ..., {Key1: Value1, ..., KeyN: ValueN}}
52+
AllowListParsedT parseAllowList(const std::string &AllowListRaw) {
53+
if (AllowListRaw.empty())
54+
return {};
55+
56+
AllowListParsedT AllowListParsed;
57+
AllowListParsed.emplace_back();
58+
59+
constexpr std::array<const char *, 3> SupportedKeyNamesHaveFixedValue{
60+
BackendNameKeyName, DeviceTypeKeyName, DeviceVendorIdKeyName};
61+
constexpr std::array<const char *, 4> SupportedKeyNamesRequireRegexValue{
62+
DriverVersionKeyName, PlatformVersionKeyName, DeviceNameKeyName,
63+
PlatformNameKeyName};
64+
65+
size_t KeyStart = 0, KeyEnd = 0, ValueStart = 0, ValueEnd = 0,
66+
DeviceDescIndex = 0;
67+
68+
const char DelimeterBtwKeyAndValue = ':';
69+
const char DelimeterBtwItemsInDeviceDesc = ',';
70+
const char DelimeterBtwDeviceDescs = '|';
71+
72+
while ((KeyEnd = AllowListRaw.find(DelimeterBtwKeyAndValue, KeyStart)) !=
73+
std::string::npos) {
74+
if ((ValueStart = AllowListRaw.find_first_not_of(
75+
DelimeterBtwKeyAndValue, KeyEnd)) == std::string::npos)
76+
break;
77+
const std::string &Key = AllowListRaw.substr(KeyStart, KeyEnd - KeyStart);
78+
79+
// check that provided key is supported
80+
if (std::find(SupportedAllowListKeyNames.begin(),
81+
SupportedAllowListKeyNames.end(),
82+
Key) == SupportedAllowListKeyNames.end()) {
83+
throw sycl::runtime_error(
84+
"Unrecognized key in SYCL_DEVICE_ALLOWLIST. For details, please "
85+
"refer to "
86+
"https://github.com/intel/llvm/blob/sycl/sycl/doc/"
87+
"EnvironmentVariables.md",
88+
PI_INVALID_VALUE);
89+
}
90+
91+
bool ShouldAllocateNewDeviceDescMap = false;
92+
93+
std::string Value;
94+
95+
auto &DeviceDescMap = AllowListParsed[DeviceDescIndex];
96+
97+
// check if Key is not already defined in DeviceDescMap, e.g., caused by the
98+
// following invalid syntax: Key1:Value1,Key2:Value2,Key1:Value3
99+
if (DeviceDescMap.find(Key) == DeviceDescMap.end()) {
100+
// calculate and validate value which has fixed format
101+
if (std::find(SupportedKeyNamesHaveFixedValue.begin(),
102+
SupportedKeyNamesHaveFixedValue.end(),
103+
Key) != SupportedKeyNamesHaveFixedValue.end()) {
104+
ValueEnd = AllowListRaw.find(DelimeterBtwItemsInDeviceDesc, ValueStart);
105+
// check if it is the last Key:Value pair in the device description, and
106+
// correct end position of that value
107+
if (size_t ValueEndCand =
108+
AllowListRaw.find(DelimeterBtwDeviceDescs, ValueStart);
109+
(ValueEndCand != std::string::npos) && (ValueEndCand < ValueEnd)) {
110+
ValueEnd = ValueEndCand;
111+
ShouldAllocateNewDeviceDescMap = true;
112+
}
113+
if (ValueEnd == std::string::npos)
114+
ValueEnd = AllowListRaw.length();
115+
116+
Value = AllowListRaw.substr(ValueStart, ValueEnd - ValueStart);
117+
118+
// post-processing checks for some values
119+
120+
auto ValidateEnumValues = [&](std::string CheckingKeyName,
121+
auto SourceOfSupportedValues) {
122+
if (Key == CheckingKeyName) {
123+
bool ValueIsValid = false;
124+
for (const auto &Item : SourceOfSupportedValues)
125+
if (Value == Item.first) {
126+
ValueIsValid = true;
127+
break;
128+
}
129+
if (!ValueIsValid)
130+
throw sycl::runtime_error(
131+
"Value " + Value + " for key " + Key +
132+
" is not valid in "
133+
"SYCL_DEVICE_ALLOWLIST. For details, please refer to "
134+
"https://github.com/intel/llvm/blob/sycl/sycl/doc/"
135+
"EnvironmentVariables.md",
136+
PI_INVALID_VALUE);
137+
}
138+
};
139+
140+
// check that values of keys, which should have some fixed format, are
141+
// valid. E.g., for BackendName key, the allowed values are only ones
142+
// described in SyclBeMap
143+
ValidateEnumValues(BackendNameKeyName, SyclBeMap);
144+
ValidateEnumValues(DeviceTypeKeyName, SyclDeviceTypeMap);
145+
146+
if (Key == DeviceVendorIdKeyName) {
147+
// DeviceVendorId should have hex format
148+
if (!std::regex_match(Value, std::regex("0[xX][0-9a-fA-F]+"))) {
149+
throw sycl::runtime_error(
150+
"Value " + Value + " for key " + Key +
151+
" is not valid in "
152+
"SYCL_DEVICE_ALLOWLIST. It should have the hex format. For "
153+
"details, please refer to "
154+
"https://github.com/intel/llvm/blob/sycl/sycl/doc/"
155+
"EnvironmentVariables.md",
156+
PI_INVALID_VALUE);
157+
}
158+
}
159+
}
160+
// calculate and validate value which has regex format
161+
else if (std::find(SupportedKeyNamesRequireRegexValue.begin(),
162+
SupportedKeyNamesRequireRegexValue.end(),
163+
Key) != SupportedKeyNamesRequireRegexValue.end()) {
164+
const std::string Prefix("{{");
165+
// TODO: can be changed to string_view::starts_with after switching
166+
// DPC++ RT to C++20
167+
if (Prefix != AllowListRaw.substr(ValueStart, Prefix.length())) {
168+
throw sycl::runtime_error("Key " + Key +
169+
" of SYCL_DEVICE_ALLOWLIST should have "
170+
"value which starts with " +
171+
Prefix,
172+
PI_INVALID_VALUE);
173+
}
174+
// cut off prefix from the value
175+
ValueStart += Prefix.length();
176+
177+
ValueEnd = ValueStart;
178+
const std::string Postfix("}}");
179+
for (; ValueEnd < AllowListRaw.length() - Postfix.length() + 1;
180+
++ValueEnd) {
181+
if (Postfix == AllowListRaw.substr(ValueEnd, Postfix.length()))
182+
break;
183+
// if it is the last iteration and next 2 symbols are not a postfix,
184+
// throw exception
185+
if (ValueEnd == AllowListRaw.length() - Postfix.length())
186+
throw sycl::runtime_error(
187+
"Key " + Key +
188+
" of SYCL_DEVICE_ALLOWLIST should have "
189+
"value which ends with " +
190+
Postfix,
191+
PI_INVALID_VALUE);
192+
}
193+
size_t NextExpectedDelimeterPos = ValueEnd + Postfix.length();
194+
// if it is not the end of the string, check that symbol next to a
195+
// postfix is a delimeter (, or ;)
196+
if ((AllowListRaw.length() != NextExpectedDelimeterPos) &&
197+
(AllowListRaw[NextExpectedDelimeterPos] !=
198+
DelimeterBtwItemsInDeviceDesc) &&
199+
(AllowListRaw[NextExpectedDelimeterPos] != DelimeterBtwDeviceDescs))
200+
throw sycl::runtime_error(
201+
"Unexpected symbol on position " +
202+
std::to_string(NextExpectedDelimeterPos) + ": " +
203+
AllowListRaw[NextExpectedDelimeterPos] +
204+
". Should be either " + DelimeterBtwItemsInDeviceDesc +
205+
" or " + DelimeterBtwDeviceDescs,
206+
PI_INVALID_VALUE);
207+
208+
if (AllowListRaw[NextExpectedDelimeterPos] == DelimeterBtwDeviceDescs)
209+
ShouldAllocateNewDeviceDescMap = true;
210+
211+
Value = AllowListRaw.substr(ValueStart, ValueEnd - ValueStart);
212+
213+
ValueEnd += Postfix.length();
214+
} else
215+
assert(false &&
216+
"Key should be either in SupportedKeyNamesHaveFixedValue "
217+
"or SupportedKeyNamesRequireRegexValue");
218+
219+
// add key and value to the map
220+
DeviceDescMap.emplace(Key, Value);
221+
} else
222+
throw sycl::runtime_error("Re-definition of key " + Key +
223+
" is not allowed in "
224+
"SYCL_DEVICE_ALLOWLIST",
225+
PI_INVALID_VALUE);
226+
227+
KeyStart = ValueEnd;
228+
if (KeyStart != std::string::npos)
229+
++KeyStart;
230+
if (ShouldAllocateNewDeviceDescMap) {
231+
++DeviceDescIndex;
232+
AllowListParsed.emplace_back();
233+
}
234+
}
235+
236+
return AllowListParsed;
237+
}
238+
239+
// Checking if we can allow device with device description DeviceDesc
240+
bool deviceIsAllowed(const DeviceDescT &DeviceDesc,
241+
const AllowListParsedT &AllowListParsed) {
242+
for (const auto &SupportedKeyName : SupportedAllowListKeyNames)
243+
assert((DeviceDesc.find(SupportedKeyName) != DeviceDesc.end()) &&
244+
"DeviceDesc map should have all supported keys for "
245+
"SYCL_DEVICE_ALLOWLIST.");
246+
auto EqualityComp = [&](const std::string &KeyName,
247+
const DeviceDescT &AllowListDeviceDesc) {
248+
// change to map::contains after switching DPC++ RT to C++20
249+
if (AllowListDeviceDesc.find(KeyName) != AllowListDeviceDesc.end())
250+
if (AllowListDeviceDesc.at(KeyName) != DeviceDesc.at(KeyName))
251+
return false;
252+
return true;
253+
};
254+
auto RegexComp = [&](const std::string &KeyName,
255+
const DeviceDescT &AllowListDeviceDesc) {
256+
if (AllowListDeviceDesc.find(KeyName) != AllowListDeviceDesc.end())
257+
if (!std::regex_match(DeviceDesc.at(KeyName),
258+
std::regex(AllowListDeviceDesc.at(KeyName))))
259+
return false;
260+
return true;
261+
};
262+
263+
bool ShouldDeviceBeAllowed = false;
264+
265+
for (const auto &AllowListDeviceDesc : AllowListParsed) {
266+
if (!EqualityComp(BackendNameKeyName, AllowListDeviceDesc))
267+
continue;
268+
if (!EqualityComp(DeviceTypeKeyName, AllowListDeviceDesc))
269+
continue;
270+
if (!EqualityComp(DeviceVendorIdKeyName, AllowListDeviceDesc))
271+
continue;
272+
if (!RegexComp(DriverVersionKeyName, AllowListDeviceDesc))
273+
continue;
274+
if (!RegexComp(PlatformVersionKeyName, AllowListDeviceDesc))
275+
continue;
276+
if (!RegexComp(DeviceNameKeyName, AllowListDeviceDesc))
277+
continue;
278+
if (!RegexComp(PlatformNameKeyName, AllowListDeviceDesc))
279+
continue;
280+
281+
// no any continue was called on this iteration, so all parameters matched
282+
// successfully, so allow this device to use
283+
ShouldDeviceBeAllowed = true;
284+
break;
285+
}
286+
287+
return ShouldDeviceBeAllowed;
288+
}
289+
290+
void applyAllowList(std::vector<RT::PiDevice> &PiDevices,
291+
RT::PiPlatform PiPlatform, const plugin &Plugin) {
292+
AllowListParsedT AllowListParsed =
293+
parseAllowList(SYCLConfig<SYCL_DEVICE_ALLOWLIST>::get());
294+
if (AllowListParsed.empty())
295+
return;
296+
297+
DeviceDescT DeviceDesc;
298+
299+
// get BackendName value and put it to DeviceDesc
300+
sycl::backend Backend = Plugin.getBackend();
301+
for (const auto &SyclBe : SyclBeMap) {
302+
if (SyclBe.second == Backend) {
303+
DeviceDesc.emplace(BackendNameKeyName, SyclBe.first);
304+
break;
305+
}
306+
}
307+
// get PlatformVersion value and put it to DeviceDesc
308+
DeviceDesc.emplace(
309+
PlatformVersionKeyName,
310+
sycl::detail::get_platform_info<std::string,
311+
info::platform::version>::get(PiPlatform,
312+
Plugin));
313+
// get PlatformName value and put it to DeviceDesc
314+
DeviceDesc.emplace(
315+
PlatformNameKeyName,
316+
sycl::detail::get_platform_info<std::string, info::platform::name>::get(
317+
PiPlatform, Plugin));
318+
319+
int InsertIDx = 0;
320+
for (RT::PiDevice Device : PiDevices) {
321+
// get DeviceType value and put it to DeviceDesc
322+
RT::PiDeviceType PiDevType;
323+
Plugin.call<PiApiKind::piDeviceGetInfo>(Device, PI_DEVICE_INFO_TYPE,
324+
sizeof(RT::PiDeviceType),
325+
&PiDevType, nullptr);
326+
sycl::info::device_type DeviceType = pi::cast<info::device_type>(PiDevType);
327+
for (const auto &SyclDeviceType : SyclDeviceTypeMap) {
328+
if (SyclDeviceType.second == DeviceType) {
329+
const auto &DeviceTypeValue = SyclDeviceType.first;
330+
DeviceDesc[DeviceTypeKeyName] = DeviceTypeValue;
331+
break;
332+
}
333+
}
334+
// get DeviceVendorId value and put it to DeviceDesc
335+
uint32_t DeviceVendorIdUInt =
336+
sycl::detail::get_device_info<uint32_t, info::device::vendor_id>::get(
337+
Device, Plugin);
338+
std::stringstream DeviceVendorIdHexStringStream;
339+
DeviceVendorIdHexStringStream << "0x" << std::hex << DeviceVendorIdUInt;
340+
const auto &DeviceVendorIdValue = DeviceVendorIdHexStringStream.str();
341+
DeviceDesc[DeviceVendorIdKeyName] = DeviceVendorIdValue;
342+
// get DriverVersion value and put it to DeviceDesc
343+
const auto &DriverVersionValue = sycl::detail::get_device_info<
344+
std::string, info::device::driver_version>::get(Device, Plugin);
345+
DeviceDesc[DriverVersionKeyName] = DriverVersionValue;
346+
// get DeviceName value and put it to DeviceDesc
347+
const auto &DeviceNameValue =
348+
sycl::detail::get_device_info<std::string, info::device::name>::get(
349+
Device, Plugin);
350+
DeviceDesc[DeviceNameKeyName] = DeviceNameValue;
351+
352+
// check if we can allow device with such device description DeviceDesc
353+
if (deviceIsAllowed(DeviceDesc, AllowListParsed)) {
354+
PiDevices[InsertIDx++] = Device;
355+
}
356+
}
357+
PiDevices.resize(InsertIDx);
358+
}
359+
360+
} // namespace detail
361+
} // namespace sycl
362+
} // __SYCL_INLINE_NAMESPACE(cl)

0 commit comments

Comments
 (0)