1 | //===- PassOptions.h - Pass Option Utilities --------------------*- C++ -*-===// |
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 | // This file contains utilities for registering options with compiler passes and |
10 | // pipelines. |
11 | // |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #ifndef MLIR_PASS_PASSOPTIONS_H_ |
15 | #define MLIR_PASS_PASSOPTIONS_H_ |
16 | |
17 | #include "mlir/Support/LLVM.h" |
18 | #include "mlir/Support/LogicalResult.h" |
19 | #include "llvm/ADT/FunctionExtras.h" |
20 | #include "llvm/ADT/StringRef.h" |
21 | #include "llvm/Support/CommandLine.h" |
22 | #include "llvm/Support/Compiler.h" |
23 | #include <memory> |
24 | |
25 | namespace mlir { |
26 | class OpPassManager; |
27 | |
28 | namespace detail { |
29 | namespace pass_options { |
30 | /// Parse a string containing a list of comma-delimited elements, invoking the |
31 | /// given parser for each sub-element and passing them to the provided |
32 | /// element-append functor. |
33 | LogicalResult |
34 | parseCommaSeparatedList(llvm::cl::Option &opt, StringRef argName, |
35 | StringRef optionStr, |
36 | function_ref<LogicalResult(StringRef)> elementParseFn); |
37 | template <typename ElementParser, typename ElementAppendFn> |
38 | LogicalResult parseCommaSeparatedList(llvm::cl::Option &opt, StringRef argName, |
39 | StringRef optionStr, |
40 | ElementParser &elementParser, |
41 | ElementAppendFn &&appendFn) { |
42 | return parseCommaSeparatedList( |
43 | opt, argName, optionStr, [&](StringRef valueStr) { |
44 | typename ElementParser::parser_data_type value = {}; |
45 | if (elementParser.parse(opt, argName, valueStr, value)) |
46 | return failure(); |
47 | appendFn(value); |
48 | return success(); |
49 | }); |
50 | } |
51 | |
52 | /// Trait used to detect if a type has a operator<< method. |
53 | template <typename T> |
54 | using has_stream_operator_trait = |
55 | decltype(std::declval<raw_ostream &>() << std::declval<T>()); |
56 | template <typename T> |
57 | using has_stream_operator = llvm::is_detected<has_stream_operator_trait, T>; |
58 | |
59 | /// Utility methods for printing option values. |
60 | template <typename ParserT> |
61 | static void printOptionValue(raw_ostream &os, const bool &value) { |
62 | os << (value ? StringRef("true" ) : StringRef("false" )); |
63 | } |
64 | template <typename ParserT, typename DataT> |
65 | static std::enable_if_t<has_stream_operator<DataT>::value> |
66 | printOptionValue(raw_ostream &os, const DataT &value) { |
67 | os << value; |
68 | } |
69 | template <typename ParserT, typename DataT> |
70 | static std::enable_if_t<!has_stream_operator<DataT>::value> |
71 | printOptionValue(raw_ostream &os, const DataT &value) { |
72 | // If the value can't be streamed, fallback to checking for a print in the |
73 | // parser. |
74 | ParserT::print(os, value); |
75 | } |
76 | } // namespace pass_options |
77 | |
78 | /// Base container class and manager for all pass options. |
79 | class PassOptions : protected llvm::cl::SubCommand { |
80 | private: |
81 | /// This is the type-erased option base class. This provides some additional |
82 | /// hooks into the options that are not available via llvm::cl::Option. |
83 | class OptionBase { |
84 | public: |
85 | virtual ~OptionBase() = default; |
86 | |
87 | /// Out of line virtual function to provide home for the class. |
88 | virtual void anchor(); |
89 | |
90 | /// Print the name and value of this option to the given stream. |
91 | virtual void print(raw_ostream &os) = 0; |
92 | |
93 | /// Return the argument string of this option. |
94 | StringRef getArgStr() const { return getOption()->ArgStr; } |
95 | |
96 | /// Returns true if this option has any value assigned to it. |
97 | bool hasValue() const { return optHasValue; } |
98 | |
99 | protected: |
100 | /// Return the main option instance. |
101 | virtual const llvm::cl::Option *getOption() const = 0; |
102 | |
103 | /// Copy the value from the given option into this one. |
104 | virtual void copyValueFrom(const OptionBase &other) = 0; |
105 | |
106 | /// Flag indicating if this option has a value. |
107 | bool optHasValue = false; |
108 | |
109 | /// Allow access to private methods. |
110 | friend PassOptions; |
111 | }; |
112 | |
113 | /// This is the parser that is used by pass options that use literal options. |
114 | /// This is a thin wrapper around the llvm::cl::parser, that exposes some |
115 | /// additional methods. |
116 | template <typename DataType> |
117 | struct GenericOptionParser : public llvm::cl::parser<DataType> { |
118 | using llvm::cl::parser<DataType>::parser; |
119 | |
120 | /// Returns an argument name that maps to the specified value. |
121 | std::optional<StringRef> findArgStrForValue(const DataType &value) { |
122 | for (auto &it : this->Values) |
123 | if (it.V.compare(value)) |
124 | return it.Name; |
125 | return std::nullopt; |
126 | } |
127 | }; |
128 | |
129 | /// Utility methods for printing option values. |
130 | template <typename DataT> |
131 | static void printValue(raw_ostream &os, GenericOptionParser<DataT> &parser, |
132 | const DataT &value) { |
133 | if (std::optional<StringRef> argStr = parser.findArgStrForValue(value)) |
134 | os << *argStr; |
135 | else |
136 | llvm_unreachable("unknown data value for option" ); |
137 | } |
138 | template <typename DataT, typename ParserT> |
139 | static void printValue(raw_ostream &os, ParserT &parser, const DataT &value) { |
140 | detail::pass_options::printOptionValue<ParserT>(os, value); |
141 | } |
142 | |
143 | public: |
144 | /// The specific parser to use depending on llvm::cl parser used. This is only |
145 | /// necessary because we need to provide additional methods for certain data |
146 | /// type parsers. |
147 | /// TODO: We should upstream the methods in GenericOptionParser to avoid the |
148 | /// need to do this. |
149 | template <typename DataType> |
150 | using OptionParser = |
151 | std::conditional_t<std::is_base_of<llvm::cl::generic_parser_base, |
152 | llvm::cl::parser<DataType>>::value, |
153 | GenericOptionParser<DataType>, |
154 | llvm::cl::parser<DataType>>; |
155 | |
156 | /// This class represents a specific pass option, with a provided data type. |
157 | template <typename DataType, typename OptionParser = OptionParser<DataType>> |
158 | class Option |
159 | : public llvm::cl::opt<DataType, /*ExternalStorage=*/false, OptionParser>, |
160 | public OptionBase { |
161 | public: |
162 | template <typename... Args> |
163 | Option(PassOptions &parent, StringRef arg, Args &&...args) |
164 | : llvm::cl::opt<DataType, /*ExternalStorage=*/false, OptionParser>( |
165 | arg, llvm::cl::sub(parent), std::forward<Args>(args)...) { |
166 | assert(!this->isPositional() && !this->isSink() && |
167 | "sink and positional options are not supported" ); |
168 | parent.options.push_back(this); |
169 | |
170 | // Set a callback to track if this option has a value. |
171 | this->setCallback([this](const auto &) { this->optHasValue = true; }); |
172 | } |
173 | ~Option() override = default; |
174 | using llvm::cl::opt<DataType, /*ExternalStorage=*/false, |
175 | OptionParser>::operator=; |
176 | Option &operator=(const Option &other) { |
177 | *this = other.getValue(); |
178 | return *this; |
179 | } |
180 | |
181 | private: |
182 | /// Return the main option instance. |
183 | const llvm::cl::Option *getOption() const final { return this; } |
184 | |
185 | /// Print the name and value of this option to the given stream. |
186 | void print(raw_ostream &os) final { |
187 | os << this->ArgStr << '='; |
188 | printValue(os, this->getParser(), this->getValue()); |
189 | } |
190 | |
191 | /// Copy the value from the given option into this one. |
192 | void copyValueFrom(const OptionBase &other) final { |
193 | this->setValue(static_cast<const Option<DataType, OptionParser> &>(other) |
194 | .getValue()); |
195 | optHasValue = other.optHasValue; |
196 | } |
197 | }; |
198 | |
199 | /// This class represents a specific pass option that contains a list of |
200 | /// values of the provided data type. The elements within the textual form of |
201 | /// this option are parsed assuming they are comma-separated. Delimited |
202 | /// sub-ranges within individual elements of the list may contain commas that |
203 | /// are not treated as separators for the top-level list. |
204 | template <typename DataType, typename OptionParser = OptionParser<DataType>> |
205 | class ListOption |
206 | : public llvm::cl::list<DataType, /*StorageClass=*/bool, OptionParser>, |
207 | public OptionBase { |
208 | public: |
209 | template <typename... Args> |
210 | ListOption(PassOptions &parent, StringRef arg, Args &&...args) |
211 | : llvm::cl::list<DataType, /*StorageClass=*/bool, OptionParser>( |
212 | arg, llvm::cl::sub(parent), std::forward<Args>(args)...), |
213 | elementParser(*this) { |
214 | assert(!this->isPositional() && !this->isSink() && |
215 | "sink and positional options are not supported" ); |
216 | assert(!(this->getMiscFlags() & llvm::cl::MiscFlags::CommaSeparated) && |
217 | "ListOption is implicitly comma separated, specifying " |
218 | "CommaSeparated is extraneous" ); |
219 | parent.options.push_back(this); |
220 | elementParser.initialize(); |
221 | } |
222 | ~ListOption() override = default; |
223 | ListOption<DataType, OptionParser> & |
224 | operator=(const ListOption<DataType, OptionParser> &other) { |
225 | *this = ArrayRef<DataType>(other); |
226 | this->optHasValue = other.optHasValue; |
227 | return *this; |
228 | } |
229 | |
230 | bool handleOccurrence(unsigned pos, StringRef argName, |
231 | StringRef arg) override { |
232 | if (this->isDefaultAssigned()) { |
233 | this->clear(); |
234 | this->overwriteDefault(); |
235 | } |
236 | this->optHasValue = true; |
237 | return failed(detail::pass_options::parseCommaSeparatedList( |
238 | *this, argName, arg, elementParser, |
239 | [&](const DataType &value) { this->addValue(value); })); |
240 | } |
241 | |
242 | /// Allow assigning from an ArrayRef. |
243 | ListOption<DataType, OptionParser> &operator=(ArrayRef<DataType> values) { |
244 | ((std::vector<DataType> &)*this).assign(values.begin(), values.end()); |
245 | optHasValue = true; |
246 | return *this; |
247 | } |
248 | |
249 | /// Allow accessing the data held by this option. |
250 | MutableArrayRef<DataType> operator*() { |
251 | return static_cast<std::vector<DataType> &>(*this); |
252 | } |
253 | ArrayRef<DataType> operator*() const { |
254 | return static_cast<const std::vector<DataType> &>(*this); |
255 | } |
256 | |
257 | private: |
258 | /// Return the main option instance. |
259 | const llvm::cl::Option *getOption() const final { return this; } |
260 | |
261 | /// Print the name and value of this option to the given stream. |
262 | void print(raw_ostream &os) final { |
263 | // Don't print the list if empty. An empty option value can be treated as |
264 | // an element of the list in certain cases (e.g. ListOption<std::string>). |
265 | if ((**this).empty()) |
266 | return; |
267 | |
268 | os << this->ArgStr << '='; |
269 | auto printElementFn = [&](const DataType &value) { |
270 | printValue(os, this->getParser(), value); |
271 | }; |
272 | llvm::interleave(*this, os, printElementFn, "," ); |
273 | } |
274 | |
275 | /// Copy the value from the given option into this one. |
276 | void copyValueFrom(const OptionBase &other) final { |
277 | *this = static_cast<const ListOption<DataType, OptionParser> &>(other); |
278 | } |
279 | |
280 | /// The parser to use for parsing the list elements. |
281 | OptionParser elementParser; |
282 | }; |
283 | |
284 | PassOptions() = default; |
285 | /// Delete the copy constructor to avoid copying the internal options map. |
286 | PassOptions(const PassOptions &) = delete; |
287 | PassOptions(PassOptions &&) = delete; |
288 | |
289 | /// Copy the option values from 'other' into 'this', where 'other' has the |
290 | /// same options as 'this'. |
291 | void copyOptionValuesFrom(const PassOptions &other); |
292 | |
293 | /// Parse options out as key=value pairs that can then be handed off to the |
294 | /// `llvm::cl` command line passing infrastructure. Everything is space |
295 | /// separated. |
296 | LogicalResult parseFromString(StringRef options, |
297 | raw_ostream &errorStream = llvm::errs()); |
298 | |
299 | /// Print the options held by this struct in a form that can be parsed via |
300 | /// 'parseFromString'. |
301 | void print(raw_ostream &os); |
302 | |
303 | /// Print the help string for the options held by this struct. `descIndent` is |
304 | /// the indent that the descriptions should be aligned. |
305 | void printHelp(size_t indent, size_t descIndent) const; |
306 | |
307 | /// Return the maximum width required when printing the help string. |
308 | size_t getOptionWidth() const; |
309 | |
310 | private: |
311 | /// A list of all of the opaque options. |
312 | std::vector<OptionBase *> options; |
313 | }; |
314 | } // namespace detail |
315 | |
316 | //===----------------------------------------------------------------------===// |
317 | // PassPipelineOptions |
318 | //===----------------------------------------------------------------------===// |
319 | |
320 | /// Subclasses of PassPipelineOptions provide a set of options that can be used |
321 | /// to initialize a pass pipeline. See PassPipelineRegistration for usage |
322 | /// details. |
323 | /// |
324 | /// Usage: |
325 | /// |
326 | /// struct MyPipelineOptions : PassPipelineOptions<MyPassOptions> { |
327 | /// ListOption<int> someListFlag{*this, "flag-name", llvm::cl::desc("...")}; |
328 | /// }; |
329 | template <typename T> |
330 | class PassPipelineOptions : public detail::PassOptions { |
331 | public: |
332 | /// Factory that parses the provided options and returns a unique_ptr to the |
333 | /// struct. |
334 | static std::unique_ptr<T> createFromString(StringRef options) { |
335 | auto result = std::make_unique<T>(); |
336 | if (failed(result->parseFromString(options))) |
337 | return nullptr; |
338 | return result; |
339 | } |
340 | }; |
341 | |
342 | /// A default empty option struct to be used for passes that do not need to take |
343 | /// any options. |
344 | struct EmptyPipelineOptions : public PassPipelineOptions<EmptyPipelineOptions> { |
345 | }; |
346 | } // namespace mlir |
347 | |
348 | //===----------------------------------------------------------------------===// |
349 | // MLIR Options |
350 | //===----------------------------------------------------------------------===// |
351 | |
352 | namespace llvm { |
353 | namespace cl { |
354 | //===----------------------------------------------------------------------===// |
355 | // std::vector+SmallVector |
356 | |
357 | namespace detail { |
358 | template <typename VectorT, typename ElementT> |
359 | class VectorParserBase : public basic_parser_impl { |
360 | public: |
361 | VectorParserBase(Option &opt) : basic_parser_impl(opt), elementParser(opt) {} |
362 | |
363 | using parser_data_type = VectorT; |
364 | |
365 | bool parse(Option &opt, StringRef argName, StringRef arg, |
366 | parser_data_type &vector) { |
367 | if (!arg.consume_front(Prefix: "[" ) || !arg.consume_back(Suffix: "]" )) { |
368 | return opt.error(Message: "expected vector option to be wrapped with '[]'" , |
369 | ArgName: argName); |
370 | } |
371 | |
372 | return failed(mlir::detail::pass_options::parseCommaSeparatedList( |
373 | opt, argName, arg, elementParser, |
374 | [&](const ElementT &value) { vector.push_back(value); })); |
375 | } |
376 | |
377 | static void print(raw_ostream &os, const VectorT &vector) { |
378 | llvm::interleave( |
379 | vector, os, |
380 | [&](const ElementT &value) { |
381 | mlir::detail::pass_options::printOptionValue< |
382 | llvm::cl::parser<ElementT>>(os, value); |
383 | }, |
384 | "," ); |
385 | } |
386 | |
387 | void printOptionInfo(const Option &opt, size_t globalWidth) const { |
388 | // Add the `vector<>` qualifier to the option info. |
389 | outs() << " --" << opt.ArgStr; |
390 | outs() << "=<vector<" << elementParser.getValueName() << ">>" ; |
391 | Option::printHelpStr(HelpStr: opt.HelpStr, Indent: globalWidth, FirstLineIndentedBy: getOptionWidth(opt)); |
392 | } |
393 | |
394 | size_t getOptionWidth(const Option &opt) const { |
395 | // Add the `vector<>` qualifier to the option width. |
396 | StringRef vectorExt("vector<>" ); |
397 | return elementParser.getOptionWidth(opt) + vectorExt.size(); |
398 | } |
399 | |
400 | private: |
401 | llvm::cl::parser<ElementT> elementParser; |
402 | }; |
403 | } // namespace detail |
404 | |
405 | template <typename T> |
406 | class parser<std::vector<T>> |
407 | : public detail::VectorParserBase<std::vector<T>, T> { |
408 | public: |
409 | parser(Option &opt) : detail::VectorParserBase<std::vector<T>, T>(opt) {} |
410 | }; |
411 | template <typename T, unsigned N> |
412 | class parser<SmallVector<T, N>> |
413 | : public detail::VectorParserBase<SmallVector<T, N>, T> { |
414 | public: |
415 | parser(Option &opt) : detail::VectorParserBase<SmallVector<T, N>, T>(opt) {} |
416 | }; |
417 | |
418 | //===----------------------------------------------------------------------===// |
419 | // OpPassManager: OptionValue |
420 | |
421 | template <> |
422 | struct OptionValue<mlir::OpPassManager> final : GenericOptionValue { |
423 | using WrapperType = mlir::OpPassManager; |
424 | |
425 | OptionValue(); |
426 | OptionValue(const OptionValue<mlir::OpPassManager> &rhs); |
427 | OptionValue(const mlir::OpPassManager &value); |
428 | OptionValue<mlir::OpPassManager> &operator=(const mlir::OpPassManager &rhs); |
429 | ~OptionValue(); |
430 | |
431 | /// Returns if the current option has a value. |
432 | bool hasValue() const { return value.get(); } |
433 | |
434 | /// Returns the current value of the option. |
435 | mlir::OpPassManager &getValue() const { |
436 | assert(hasValue() && "invalid option value" ); |
437 | return *value; |
438 | } |
439 | |
440 | /// Set the value of the option. |
441 | void setValue(const mlir::OpPassManager &newValue); |
442 | void setValue(StringRef pipelineStr); |
443 | |
444 | /// Compare the option with the provided value. |
445 | bool compare(const mlir::OpPassManager &rhs) const; |
446 | bool compare(const GenericOptionValue &rhs) const override { |
447 | const auto &rhsOV = |
448 | static_cast<const OptionValue<mlir::OpPassManager> &>(rhs); |
449 | if (!rhsOV.hasValue()) |
450 | return false; |
451 | return compare(rhs: rhsOV.getValue()); |
452 | } |
453 | |
454 | private: |
455 | void anchor() override; |
456 | |
457 | /// The underlying pass manager. We use a unique_ptr to avoid the need for the |
458 | /// full type definition. |
459 | std::unique_ptr<mlir::OpPassManager> value; |
460 | }; |
461 | |
462 | //===----------------------------------------------------------------------===// |
463 | // OpPassManager: Parser |
464 | |
465 | extern template class basic_parser<mlir::OpPassManager>; |
466 | |
467 | template <> |
468 | class parser<mlir::OpPassManager> : public basic_parser<mlir::OpPassManager> { |
469 | public: |
470 | /// A utility struct used when parsing a pass manager that prevents the need |
471 | /// for a default constructor on OpPassManager. |
472 | struct ParsedPassManager { |
473 | ParsedPassManager(); |
474 | ParsedPassManager(ParsedPassManager &&); |
475 | ~ParsedPassManager(); |
476 | operator const mlir::OpPassManager &() const { |
477 | assert(value && "parsed value was invalid" ); |
478 | return *value; |
479 | } |
480 | |
481 | std::unique_ptr<mlir::OpPassManager> value; |
482 | }; |
483 | using parser_data_type = ParsedPassManager; |
484 | using OptVal = OptionValue<mlir::OpPassManager>; |
485 | |
486 | parser(Option &opt) : basic_parser(opt) {} |
487 | |
488 | bool parse(Option &, StringRef, StringRef arg, ParsedPassManager &value); |
489 | |
490 | /// Print an instance of the underling option value to the given stream. |
491 | static void print(raw_ostream &os, const mlir::OpPassManager &value); |
492 | |
493 | // Overload in subclass to provide a better default value. |
494 | StringRef getValueName() const override { return "pass-manager" ; } |
495 | |
496 | void printOptionDiff(const Option &opt, mlir::OpPassManager &pm, |
497 | const OptVal &defaultValue, size_t globalWidth) const; |
498 | |
499 | // An out-of-line virtual method to provide a 'home' for this class. |
500 | void anchor() override; |
501 | }; |
502 | |
503 | } // namespace cl |
504 | } // namespace llvm |
505 | |
506 | #endif // MLIR_PASS_PASSOPTIONS_H_ |
507 | |