LLVM OpenMP* Runtime Library
Loading...
Searching...
No Matches
kmp_traits.cpp
1/*
2 * kmp_traits.cpp -- Handle OpenMP context traits
3 *
4 * OpenMP 6.0 specifies the following trait sets:
5 * - construct
6 * - device
7 * - target device
8 * - implementation
9 * - extension
10 * - dynamic
11 * Currently, the implementation in this file supports traits from the (target)
12 * device and implementation trait sets that are relevant for implementing the
13 * OMP_DEFAULT_DEVICE and OMP_AVAILABLE_DEVICES environment variables.
14 */
15
16//===----------------------------------------------------------------------===//
17//
18// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
19// See https://llvm.org/LICENSE.txt for license information.
20// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
21//
22//===----------------------------------------------------------------------===//
23
24#include "kmp_traits.h"
25#include "kmp_i18n.h"
26
27using namespace kmp_traits;
28
29// OpenMP trait grammar (in EBNF), currently used for parsing the
30// OMP_DEFAULT_DEVICE/OMP_AVAILABLE_DEVICES environment variables
31//
32// Notes about the grammar:
33// - Device traits are going to be translated into device numbers (aka integers)
34// later in the runtime. The parser handles device numbers as device traits that
35// have already been translated.
36// - "*" is also not a trait, strictly speaking. But it's also supported by the
37// parser and converted into a "match any" wildcard trait.
38// - OpenMP 6.0 explicitly excludes "&&" and "||" from appearing in the same
39// grouping level.
40// - This grammar currently only supports plain integers for array subsripts /
41// sections, no expressions.
42// - TODO:
43// - Add support for more traits
44//
45// TODOs regarding the implementation (not the grammar):
46// - Implement array subscript/section parsing
47// - Implement grammar TODOs after they have been incorporated into the grammar
48//
49// list = [clause {',' clause}]
50// clause =
51// device_number
52// | "*" [index_expr]
53// | trait_expr_group
54// | trait_expr index_expr
55// device_number = ["-"] integer0
56// trait_expr_group =
57// trait_expr
58// | trait_expr {"&&" trait_expr}
59// | trait_expr {"||" trait_expr}
60// trait_expr = ["!"] (trait | trait_expr_group_paren)
61// trait_expr_group_paren = "(" trait_expr_group ")"
62// trait =
63// "uid" "(" uid_value ")"
64// uid_value = (letter | digit0 | symbol) {letter | digit0 | symbol}
65//
66// index_expr = "[" integer0 "]" | "[" array_section "]"
67// array_section =
68// lower_bound ":" length ":" stride
69// | lower_bound ":" length ":"
70// | lower_bound ":" length
71// | lower_bound "::" stride
72// | lower_bound "::"
73// | lower_bound ":"
74// | ":" length ":" stride
75// | ":" length ":"
76// | ":" length
77// | "::" stride
78// | "::"
79// | ":"
80// lower_bound = integer0
81// length = integer0
82// stride = integer
83//
84// integer0 = 0 | integer
85// integer = digit {digit0}
86//
87// letter =
88// "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L"
89// | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X"
90// | "Y" | "Z" | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j"
91// | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v"
92// | "w" | "x" | "y" | "z"
93// digit0 = "0" | digit
94// digit = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
95// symbol = "-" | "_"
96
97// A character that can appear in a word (uid keyword / uid_value / integer),
98// i.e. a letter, a digit, or one of the symbols "-" / "_".
99static bool is_word_char(char c) {
100 return isalnum(static_cast<unsigned char>(c)) || c == '-' || c == '_';
101}
102
103static bool is_digit(char c) {
104 return static_cast<bool>(isdigit(static_cast<unsigned char>(c)));
105}
106
107namespace lexer {
108
109token kmp_lexer::lex() {
110 scan.skip_space();
111
112 const char *start = scan.begin();
113 if (scan.empty())
114 return {token_kind::END, kmp_str_ref(start, 0)};
115
116 // Two-character operators.
117 if (scan.consume_front("&&"))
118 return {token_kind::AND, kmp_str_ref(start, 2)};
119 if (scan.consume_front("||"))
120 return {token_kind::OR, kmp_str_ref(start, 2)};
121
122 // Word characters form a single WORD.
123 kmp_str_ref word = scan.take_while(is_word_char);
124 if (!word.empty()) {
125 scan.drop_front(word.length());
126 return {token_kind::WORD, word};
127 }
128
129 // Single-character tokens.
130 token_kind kind;
131 switch (*start) {
132 case ',':
133 kind = token_kind::COMMA;
134 break;
135 case '*':
136 kind = token_kind::STAR;
137 break;
138 case '!':
139 kind = token_kind::NOT;
140 break;
141 case '(':
142 kind = token_kind::L_PAREN;
143 break;
144 case ')':
145 kind = token_kind::R_PAREN;
146 break;
147 case '[':
148 kind = token_kind::L_BRACKET;
149 break;
150 case ']':
151 kind = token_kind::R_BRACKET;
152 break;
153 case ':':
154 kind = token_kind::COLON;
155 break;
156 default:
157 kind = token_kind::UNKNOWN;
158 break;
159 }
160 scan.drop_front(1);
161 return {kind, kmp_str_ref(start, 1)};
162}
163
164} // namespace lexer
165
166namespace parser {
167
168constexpr int MAX_RECURSION_DEPTH = 64;
169
170using namespace kmp_traits;
171using namespace lexer;
172
173// Check whether a token is a WORD whose text equals the given keyword.
174static bool word_is(const token &tok, kmp_str_ref keyword) {
175 kmp_str_ref text = tok.text;
176 return tok.kind == token_kind::WORD && text.consume_front(keyword) &&
177 text.empty();
178}
179
180// Check whether a token is a WORD that has the shape of a device number, i.e.
181// device_number = ["-"] integer0 (an optional "-" followed by digits only).
182// The lexer emits every word-character run as a WORD, so it is the parser that
183// tells a device number apart from a name or uid_value (which may also contain
184// "-" and digits).
185static bool word_is_number(const token &tok) {
186 if (tok.kind != token_kind::WORD)
187 return false;
188 kmp_str_ref text = tok.text;
189 text.consume_front("-");
190 return !text.empty() && text.find_if_not(is_digit) == kmp_str_ref::npos;
191}
192
193// uid_value = (letter | digit0 | symbol) {letter | digit0 | symbol}
194// Consumes and returns a uid value.
195// An invalid uid value is a hard error.
196static kmp_str_ref consume_uid_value(kmp_lexer &lex, const char *dbg_name) {
197 const token &tok = lex.peek();
198 if (tok.kind != token_kind::WORD)
199 KMP_FATAL(TraitParserInvalidTraitValue, dbg_name, "uid", tok.text.copy());
200 kmp_str_ref uid = tok.text;
201 lex.next(); // consume the uid_value
202 return uid;
203}
204
205// trait = "uid" "(" uid_value ")"
206// (more traits will be added as needed in the future)
207// Returns false without consuming anything if the next token is not a
208// recognized trait name.
209// Once a trait name is consumed we are committed to a trait expression, so a
210// missing "(" or ")" or an invalid trait value is a hard error.
211static bool consume_trait(kmp_trait_expr_single &expr, kmp_lexer &lex,
212 const char *dbg_name) {
213 if (!word_is(lex.peek(), "uid"))
214 return false;
215 // Add more traits as needed in the future.
216
217 lex.next(); // consume trait name
218 if (lex.peek().kind != token_kind::L_PAREN)
219 KMP_FATAL(TraitParserError, dbg_name, "expected '(' after trait name");
220 lex.next(); // consume "("
221 kmp_str_ref uid = consume_uid_value(lex, dbg_name);
222 if (lex.peek().kind != token_kind::R_PAREN)
223 KMP_FATAL(TraitParserError, dbg_name, "expected ')' after trait value");
224 lex.next(); // consume ")"
225 expr.set_trait(new kmp_uid_trait(uid));
226 return true;
227}
228
229// forward declaration
230static bool consume_trait_expr_group(kmp_trait_expr_group &group,
231 kmp_lexer &lex, int max_recursion,
232 const char *dbg_name);
233
234// trait_expr_group_paren = "(" trait_expr_group ")"
235// Returns false without consuming anything if the next token is not "(", so the
236// caller can try other alternatives.
237// Once "(" is consumed we are committed to a parenthesized group, so a missing
238// group or ")" is a hard error.
239static bool consume_trait_expr_group_paren(kmp_trait_expr_group &group,
240 bool negated, kmp_lexer &lex,
241 int max_recursion,
242 const char *dbg_name) {
243 if (lex.peek().kind != token_kind::L_PAREN)
244 return false;
245 group.set_negated(negated);
246 lex.next(); // consume "("
247 if (!consume_trait_expr_group(group, lex, max_recursion, dbg_name))
248 KMP_FATAL(TraitParserError, dbg_name,
249 "expected trait expression after '('");
250 if (lex.peek().kind != token_kind::R_PAREN)
251 KMP_FATAL(TraitParserError, dbg_name,
252 "expected ')' after trait expression group");
253 lex.next(); // consume ")"
254 return true;
255}
256
257// trait_expr = ["!"] (trait | trait_expr_group_paren)
258// Returns false without consuming anything if neither a parenthesized group nor
259// a single trait can be consumed.
260// Once an optional leading "!" has been consumed we are committed to a trait
261// expression, so a missing trait or parenthesized group is a hard error.
262static bool consume_trait_expr(kmp_trait_expr *&expr, kmp_lexer &lex,
263 int max_recursion, const char *dbg_name) {
264 if (max_recursion-- <= 0)
265 KMP_FATAL(TraitParserMaxRecursion, dbg_name, MAX_RECURSION_DEPTH);
266
267 // Consume the optional leading "!"; it applies to whatever follows.
268 bool negated = lex.peek().kind == token_kind::NOT;
269 if (negated)
270 lex.next();
271
272 // Try a parenthesized group (starts with "(") ...
274 if (consume_trait_expr_group_paren(*group, negated, lex, max_recursion,
275 dbg_name)) {
276 expr = group;
277 return true;
278 }
279 delete group;
280
281 // ... otherwise it must be a single trait.
283 single->set_negated(negated);
284 if (consume_trait(*single, lex, dbg_name)) {
285 expr = single;
286 return true;
287 }
288 delete single;
289
290 // A leading "!" has already committed us to a trait expression, so its
291 // absence is an error; without it, nothing was consumed and the caller can
292 // recover/report.
293 if (negated)
294 KMP_FATAL(TraitParserError, dbg_name,
295 "expected trait expression after '!'");
296 return false;
297}
298
299// trait_expr_group =
300// trait_expr
301// | trait_expr {"&&" trait_expr}
302// | trait_expr {"||" trait_expr}
303// Returns false without consuming anything if no trait expression can be
304// consumed.
305// Any other missing or invalid tokens are a hard error.
306static bool consume_trait_expr_group(kmp_trait_expr_group &group,
307 kmp_lexer &lex, int max_recursion,
308 const char *dbg_name) {
309 if (max_recursion-- <= 0)
310 KMP_FATAL(TraitParserMaxRecursion, dbg_name, MAX_RECURSION_DEPTH);
311
312 kmp_trait_expr *expr = nullptr;
313 if (!consume_trait_expr(expr, lex, max_recursion, dbg_name))
314 return false;
315 group.add_expr(expr);
316
317 token_kind op;
318 if (lex.peek().kind == token_kind::OR) {
319 group.set_group_type(kmp_trait_expr_group::OR);
320 op = token_kind::OR;
321 } else if (lex.peek().kind == token_kind::AND) {
322 group.set_group_type(kmp_trait_expr_group::AND);
323 op = token_kind::AND;
324 } else {
325 return true; // single trait expression, no operator
326 }
327 lex.next(); // consume the operator
328
329 // Having consumed an operator, we are committed: at least one more trait
330 // expression must follow, so its absence is a hard error.
331 while (true) {
332 if (!consume_trait_expr(expr, lex, max_recursion, dbg_name))
333 KMP_FATAL(TraitParserError, dbg_name,
334 "expected trait expression after operator");
335 group.add_expr(expr);
336 if (lex.peek().kind != op)
337 break;
338 lex.next(); // consume the operator
339 }
340
341 return true;
342}
343
344// device_number = ["-"] integer0
345// Returns false without consuming anything if the next token is not shaped like
346// a device number. A WORD that has the shape of a device number is treated as a
347// device number, so once it is recognized it is committed: a value that is not
348// a valid device index (negative or too large to fit an int) is a hard error.
349static bool consume_device_number(kmp_trait_clause &clause, kmp_lexer &lex,
350 const char *dbg_name) {
351 if (!word_is_number(lex.peek()))
352 return false;
353 kmp_str_ref number = lex.peek().text;
354 int value;
355 if (!number.consume_integer(value))
356 KMP_FATAL(TraitParserError, dbg_name, "device number out of range");
357 lex.next();
358 clause.set_expr(new kmp_literal_trait(value));
359 return true;
360}
361
362// clause =
363// device_number
364// | "*" [index_expr]
365// | trait_expr_group
366// | trait_expr index_expr
367// Returns false without consuming anything if no clause can be consumed.
368// Any other missing or invalid tokens are a hard error.
369static bool consume_clause(kmp_trait_clause &clause, kmp_lexer &lex,
370 const char *dbg_name) {
371 // Parse wildcard "trait"
372 if (lex.peek().kind == token_kind::STAR) {
373 lex.next();
374 clause.set_expr(new kmp_wildcard_trait());
375 return true;
376 }
377
378 // Parse a literal device number. A WORD that is not shaped like a number is
379 // not a device number and starts a trait expression group instead.
380 if (consume_device_number(clause, lex, dbg_name))
381 return true;
382
383 // Parse a trait expression group
385 if (consume_trait_expr_group(*group, lex, MAX_RECURSION_DEPTH, dbg_name)) {
386 clause.set_expr(group);
387 return true;
388 }
389 delete group;
390
391 return false;
392}
393
394// list = [clause {',' clause}]
395static void consume_list(kmp_trait_context &context, kmp_lexer &lex,
396 const char *dbg_name) {
397 kmp_str_ref lex_pos = lex.remaining();
398
399 while (lex.peek().kind != token_kind::END) {
400 kmp_trait_clause *clause = new kmp_trait_clause();
401 if (!consume_clause(*clause, lex, dbg_name)) {
402 delete clause;
403 KMP_FATAL(TraitParserFailed, dbg_name, lex_pos.copy());
404 }
405 context.add_clause(clause);
406
407 lex_pos = lex.remaining();
408 if (lex.peek().kind == token_kind::COMMA)
409 lex.next();
410 else if (lex.peek().kind != token_kind::END)
411 KMP_FATAL(TraitParserFailed, dbg_name, lex_pos.copy());
412 }
413}
414
415} // namespace parser
416
417kmp_trait_context *kmp_trait_context::parse_from_spec(kmp_str_ref spec,
418 const char *dbg_name) {
419 kmp_trait_context *context = new kmp_trait_context();
420 lexer::kmp_lexer lex(spec);
421 parser::consume_list(*context, lex, dbg_name);
422 return context;
423}
kmp_str_ref is a non-owning string class (similar to llvm::StringRef).
Definition kmp_adt.h:34
kmp_str_ref take_while(const Fn &predicate) const
Definition kmp_adt.h:134
size_t find_if_not(const Fn &predicate) const
Definition kmp_adt.h:115
size_t length() const
Get the length of the string.
Definition kmp_adt.h:122
bool consume_front(kmp_str_ref prefix)
Definition kmp_adt.h:51
char * copy() const
Definition kmp_adt.cpp:56
bool consume_integer(int &value, bool allow_zero=true, bool allow_negative=false)
Definition kmp_adt.cpp:27
bool empty() const
Check if the string is empty.
Definition kmp_adt.h:98
void drop_front(size_t n)
Definition kmp_adt.h:83
Represents a specific device number.
Definition kmp_traits.h:135
Represents a single (possibly negated) trait.
Definition kmp_traits.h:250
Represents a wildcard trait that matches any device.
Definition kmp_traits.h:123