1 #include "all_benchmarks.h"
6 #include <initializer_list>
12 #include <string_view>
13 #include <system_error>
14 #include <type_traits>
15 #include <unordered_set>
18 using namespace std::literals
;
20 enum class OptionValueKind
30 char short_name
= '\0';
31 std::string_view long_name
= "", description
= "";
32 bool required
= false;
33 bool all_other_args_not_required
= false;
34 OptionValueKind value_kind
= OptionValueKind::None
;
35 std::function
<void(OptionsParser
&parser
,
36 std::optional
<std::string_view
> value
)>
38 bool has_short_name() const
40 return short_name
!= '\0';
42 bool has_long_name() const
44 return !long_name
.empty();
46 friend std::ostream
&operator<<(std::ostream
&os
, const Option
&option
)
48 if (option
.has_long_name())
50 os
<< "--" << option
.long_name
;
52 else if (option
.has_short_name())
54 os
<< "-" << option
.short_name
;
66 friend class OptionsParser
;
69 std::vector
<Option
> options
;
70 std::map
<char, std::size_t> short_options
;
71 std::map
<std::string_view
, std::size_t> long_options
;
74 Options(std::initializer_list
<Option
> options_
)
75 : options(options_
), short_options(), long_options()
77 for (std::size_t i
= 0; i
< options
.size(); i
++)
79 auto &option
= options
[i
];
80 if (option
.has_short_name())
81 short_options
[option
.short_name
] = i
;
82 if (option
.has_long_name())
83 long_options
[option
.long_name
] = i
;
88 std::vector
<std::string_view
> parse(const char *const *argv
) const;
91 class OptionsParser final
94 const Options
&options
;
95 const char *const *argv
;
96 std::string_view argv0
;
97 std::vector
<bool> seen_options
;
98 bool check_required
= true;
99 std::optional
<std::size_t> current_option_index
;
102 const Option
¤t_option()
104 static const Option default_option
{};
105 if (current_option_index
)
107 return options
.options
[*current_option_index
];
111 return default_option
;
116 OptionsParser(const Options
&options
, const char *const *argv
)
117 : options(options
), argv(argv
+ 1),
118 argv0(argv
[0] ? argv
[0] : "<none>"),
119 seen_options(options
.options
.size(), false), current_option_index()
123 static const char *null
[] = {nullptr};
125 help_and_exit("missing program name");
130 void parse_option(std::size_t option_index
, std::string_view prefix
,
131 std::string_view name
,
132 std::optional
<std::string_view
> value
)
134 auto &option
= options
.options
[option_index
];
135 switch (option
.value_kind
)
137 case OptionValueKind::None
:
140 help_and_exit("value not allowed for ", prefix
, name
);
143 case OptionValueKind::Required
:
152 help_and_exit("missing value for ", prefix
, name
);
157 seen_options
[option_index
] = true;
158 if (option
.all_other_args_not_required
)
159 check_required
= false;
160 current_option_index
= option_index
;
161 option
.parse_value(*this, value
);
162 current_option_index
= std::nullopt
;
166 std::vector
<std::string_view
> parse()
168 std::vector
<std::string_view
> retval
;
169 constexpr auto npos
= std::string_view::npos
;
172 std::string_view arg
= *argv
++;
173 static constexpr std::string_view long_prefix
= "--"sv
;
174 static constexpr std::string_view short_prefix
= "-"sv
;
175 if (arg
.rfind(long_prefix
, 0) == 0) // it starts with `--`
177 arg
.remove_prefix(long_prefix
.size());
178 auto eq
= arg
.find('=');
183 if (arg
.empty()) // just `--`
187 retval
.push_back(*argv
++);
191 auto name
= arg
.substr(0, eq
);
192 std::optional
<std::string_view
> value
;
195 value
= arg
.substr(eq
+ 1);
197 auto iter
= options
.long_options
.find(name
);
198 if (iter
== options
.long_options
.end())
200 help_and_exit("unknown option: ", long_prefix
, name
);
202 auto option_index
= iter
->second
;
203 parse_option(option_index
, long_prefix
, name
, value
);
206 else if (arg
.rfind(short_prefix
, 0) == 0) // it starts with `-`
208 arg
.remove_prefix(short_prefix
.size());
209 if (arg
.empty()) // just `-`
211 retval
.push_back(short_prefix
);
216 auto name
= arg
.substr(0, 1);
217 arg
.remove_prefix(1);
218 auto iter
= options
.short_options
.find(name
[0]);
219 if (iter
== options
.short_options
.end())
221 help_and_exit("unknown option: ", short_prefix
, name
);
223 auto option_index
= iter
->second
;
224 std::optional
<std::string_view
> value
;
225 switch (options
.options
[option_index
].value_kind
)
227 case OptionValueKind::None
:
229 case OptionValueKind::Required
:
230 auto eq
= arg
.rfind('=', 0);
233 value
= arg
.substr(eq
+ 1);
235 else if (!arg
.empty())
242 parse_option(option_index
, short_prefix
, name
, value
);
248 retval
.push_back(arg
);
253 for (std::size_t i
= 0; i
< options
.options
.size(); i
++)
255 auto &option
= options
.options
[i
];
256 if (option
.required
&& !seen_options
[i
])
258 help_and_exit("missing required option ", option
);
264 template <typename
... Options
>
265 [[noreturn
]] void help_and_exit(const Options
&...error_msg
) const
267 auto &os
= sizeof...(error_msg
) == 0 ? std::cout
: std::cerr
;
268 if (sizeof...(error_msg
) != 0)
270 ((os
<< "Error: ") << ... << error_msg
) << std::endl
;
272 os
<< "Usage: " << argv0
;
273 for (auto &option
: options
.options
)
276 if (!option
.required
)
281 if (option
.has_short_name())
283 os
<< "-" << option
.short_name
;
286 if (option
.has_long_name())
288 os
<< sep
<< "--" << option
.long_name
;
290 switch (option
.value_kind
)
292 case OptionValueKind::None
:
294 // TODO: case OptionValueKind::Optional:
295 case OptionValueKind::Required
:
299 if (!option
.required
)
304 os
<< std::endl
<< "Options:" << std::endl
;
305 for (auto &option
: options
.options
)
308 if (option
.has_short_name())
310 os
<< "-" << option
.short_name
;
313 if (option
.has_long_name())
315 os
<< sep
<< "--" << option
.long_name
;
317 os
<< " " << option
.description
<< std::endl
;
319 std::exit(sizeof...(error_msg
) == 0 ? 0 : 1);
321 template <typename Int
, typename
= void> struct ParseIntLimits
;
322 template <typename Int
>
323 struct ParseIntLimits
<Int
, std::enable_if_t
<std::is_integral_v
<Int
>>> final
325 Int min_value
= std::numeric_limits
<Int
>::min();
326 Int max_value
= std::numeric_limits
<Int
>::max();
328 template <typename Int
>
329 std::enable_if_t
<std::is_integral_v
<Int
>, void> parse_int(
330 std::optional
<std::string_view
> value
, Int
&i_value
,
331 ParseIntLimits
<Int
> limits
= {})
336 help_and_exit("missing value for ", current_option());
340 if (0 == str
.rfind("0x", 0) || 0 == str
.rfind("0X", 0))
343 str
.remove_prefix(2);
345 else if (0 == str
.rfind("0o", 0) || 0 == str
.rfind("0O", 0))
348 str
.remove_prefix(2);
350 else if (0 == str
.rfind("0b", 0) || 0 == str
.rfind("0B", 0))
353 str
.remove_prefix(2);
355 std::from_chars_result result
= std::from_chars(
356 str
.data(), str
.data() + str
.size(), i_value
, base
);
357 if (result
.ptr
!= str
.data() + str
.size())
359 result
.ec
= std::errc::invalid_argument
;
361 if (result
.ec
== std::errc())
363 if (i_value
< limits
.min_value
|| i_value
> limits
.max_value
)
365 result
.ec
= std::errc::result_out_of_range
;
368 if (result
.ec
== std::errc::result_out_of_range
)
370 help_and_exit("value out of range: ", current_option(), "=",
373 else if (result
.ec
!= std::errc())
375 help_and_exit("invalid value for: ", current_option());
378 template <typename Int
>
379 std::enable_if_t
<std::is_integral_v
<Int
>, void> parse_int(
380 std::optional
<std::string_view
> value
, std::optional
<Int
> &i_value
,
381 bool required
= true, ParseIntLimits
<Int
> limits
= {})
383 if (!required
&& !value
)
385 i_value
= std::nullopt
;
389 this->parse_int(value
, i_value
.value(), limits
);
393 inline std::vector
<std::string_view
> Options::parse(
394 const char *const *argv
) const
396 return OptionsParser(*this, argv
).parse();
399 int main(int, char **argv
)
402 std::optional
<std::unordered_set
<std::string
>> enabled_benchmarks
;
407 .description
= "Display usage and exit.",
408 .all_other_args_not_required
= true,
409 .parse_value
= [](OptionsParser
&parser
,
410 auto) { parser
.help_and_exit(); },
412 Option
{.short_name
= 'j',
413 .long_name
= "thread-count",
414 .description
= "Number of threads to run on",
415 .value_kind
= OptionValueKind::Required
,
417 [&](OptionsParser
&parser
, auto value
) {
418 parser
.parse_int(value
, config
.thread_count
);
420 Option
{.short_name
= 'n',
421 .long_name
= "iter-count",
422 .description
= "Number of iterations to run per thread",
423 .value_kind
= OptionValueKind::Required
,
425 [&](OptionsParser
&parser
, auto value
) {
426 parser
.parse_int(value
, config
.iteration_count
);
428 Option
{.long_name
= "log2-mem-loc-count",
430 "Log base 2 of the number of memory locations to access",
431 .value_kind
= OptionValueKind::Required
,
433 [&](OptionsParser
&parser
, auto value
) {
435 value
, config
.log2_memory_location_count
,
437 Config::max_sum_log2_mem_loc_count_and_stride
-
438 config
.log2_stride
});
441 .long_name
= "log2-stride",
443 "Log base 2 of the stride used for accessing memory locations",
444 .value_kind
= OptionValueKind::Required
,
446 [&](OptionsParser
&parser
, auto value
) {
448 value
, config
.log2_stride
,
450 Config::max_sum_log2_mem_loc_count_and_stride
-
451 config
.log2_memory_location_count
});
455 .long_name
= "bench",
456 .description
= "List of benchmarks that should be run",
457 .value_kind
= OptionValueKind::Required
,
459 [&](OptionsParser
&, std::optional
<std::string_view
> value
) {
460 if (!enabled_benchmarks
)
462 enabled_benchmarks
.emplace();
464 enabled_benchmarks
->emplace(value
.value_or(""));
467 OptionsParser
parser(options
, argv
);
468 auto args
= parser
.parse();
471 parser
.help_and_exit("unexpected argument");
473 auto benchmarks
= all_benchmarks(config
);
474 if (enabled_benchmarks
)
476 enabled_benchmarks
->erase("");
477 enabled_benchmarks
->erase("help");
478 enabled_benchmarks
->erase("list");
479 if (enabled_benchmarks
->empty())
481 std::cout
<< "Available Benchmarks:\n";
482 for (auto &benchmark
: benchmarks
)
484 std::cout
<< benchmark
.name() << "\n";
486 std::cout
<< std::endl
;
489 std::unordered_set
<std::string
> unknown_benchmarks
=
491 for (auto &benchmark
: benchmarks
)
493 unknown_benchmarks
.erase(benchmark
.name());
495 if (!unknown_benchmarks
.empty())
497 parser
.help_and_exit(
498 "unknown benchmark: ", *unknown_benchmarks
.begin(),
499 "\nrun with `--bench=list` to see all supported benchmarks.");
502 auto thread_cache
= BenchHarnessBase::get_thread_cache();
503 for (auto &benchmark
: benchmarks
)
505 if (enabled_benchmarks
&& !enabled_benchmarks
->count(benchmark
.name()))
509 std::cout
<< "Running: " << benchmark
.name() << std::endl
;
510 benchmark
.run(config
);
512 std::cout
<< std::endl
;