1 /* 2 Copyright (c) 2005-2021 Intel Corporation 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 #ifndef TBB_examples_utility_H 18 #define TBB_examples_utility_H 19 20 #include <cassert> 21 #include <cstring> 22 #include <cstdlib> 23 24 #include <utility> 25 #include <string> 26 #include <vector> 27 #include <map> 28 #include <set> 29 #include <algorithm> 30 #include <sstream> 31 #include <numeric> 32 #include <stdexcept> 33 #include <memory> 34 #include <iostream> 35 // TBB headers should not be used, as some examples may need to be built without TBB. 36 37 namespace utility { 38 namespace internal { 39 40 //TODO: add tcs 41 template <class dest_type> 42 dest_type& string_to(std::string const& s, dest_type& result) { 43 std::stringstream stream(s); 44 stream >> result; 45 if ((!stream) || (stream.fail())) { 46 throw std::invalid_argument("error converting string '" + std::string(s) + "'"); 47 } 48 return result; 49 } 50 51 template <class dest_type> 52 dest_type string_to(std::string const& s) { 53 dest_type result; 54 return string_to(s, result); 55 } 56 57 template <typename> 58 struct is_bool { 59 static bool value() { 60 return false; 61 } 62 }; 63 template <> 64 struct is_bool<bool> { 65 static bool value() { 66 return true; 67 } 68 }; 69 70 class type_base { 71 type_base& operator=(const type_base&); 72 73 public: 74 const std::string name; 75 const std::string description; 76 77 type_base(std::string a_name, std::string a_description) 78 : name(a_name), 79 description(a_description) {} 80 virtual void parse_and_store(const std::string& s) = 0; 81 virtual std::string value() const = 0; 82 virtual std::unique_ptr<type_base> clone() const = 0; 83 virtual ~type_base() {} 84 }; 85 template <typename type> 86 class type_impl : public type_base { 87 private: 88 type_impl(const type_impl& src) 89 : type_base(src.name, src.description), 90 target(src.target), 91 validating_function(src.validating_function) {} 92 type_impl& operator=(const type_impl&); 93 typedef bool (*validating_function_type)(const type&); 94 type& target; 95 validating_function_type validating_function; 96 97 public: 98 type_impl(std::string a_name, 99 std::string a_description, 100 type& a_target, 101 validating_function_type a_validating_function = nullptr) 102 : type_base(a_name, a_description), 103 target(a_target), 104 validating_function(a_validating_function){}; 105 void parse_and_store(const std::string& s) /*override*/ { 106 try { 107 const bool is_bool = internal::is_bool<type>::value(); 108 if (is_bool && s.empty()) { 109 //to avoid directly assigning true 110 //(as it will impose additional layer of indirection) 111 //so, simply pass it as string 112 internal::string_to("1", target); 113 } 114 else { 115 internal::string_to(s, target); 116 } 117 } 118 catch (std::invalid_argument& e) { 119 std::stringstream str; 120 str << "'" << s << "' is incorrect input for argument '" << name << "'" 121 << " (" << e.what() << ")"; 122 throw std::invalid_argument(str.str()); 123 } 124 if (validating_function) { 125 if (!((validating_function)(target))) { 126 std::stringstream str; 127 str << "'" << target << "' is invalid value for argument '" << name << "'"; 128 throw std::invalid_argument(str.str()); 129 } 130 } 131 } 132 template <typename t> 133 static bool is_null_c_str(t&) { 134 return false; 135 } 136 static bool is_null_c_str(char* s) { 137 return s == nullptr; 138 } 139 std::string value() const /*override*/ { 140 std::stringstream str; 141 if (!is_null_c_str(target)) 142 str << target; 143 return str.str(); 144 } 145 std::unique_ptr<type_base> clone() const /*override*/ { 146 return std::unique_ptr<type_base>(new type_impl(*this)); 147 } 148 }; 149 150 class argument { 151 private: 152 std::unique_ptr<type_base> p_type; 153 bool matched_; 154 155 public: 156 argument(argument const& other) 157 : p_type(other.p_type.get() ? (other.p_type->clone()).release() : nullptr), 158 matched_(other.matched_) {} 159 argument& operator=(argument a) { 160 this->swap(a); 161 return *this; 162 } 163 void swap(argument& other) { 164 std::swap(p_type, other.p_type); 165 std::swap(matched_, other.matched_); 166 } 167 template <class type> 168 argument(std::string a_name, 169 std::string a_description, 170 type& dest, 171 bool (*a_validating_function)(const type&) = nullptr) 172 : p_type(new type_impl<type>(a_name, a_description, dest, a_validating_function)), 173 matched_(false) {} 174 std::string value() const { 175 return p_type->value(); 176 } 177 std::string name() const { 178 return p_type->name; 179 } 180 std::string description() const { 181 return p_type->description; 182 } 183 void parse_and_store(const std::string& s) { 184 p_type->parse_and_store(s); 185 matched_ = true; 186 } 187 bool is_matched() const { 188 return matched_; 189 } 190 }; 191 } // namespace internal 192 193 class cli_argument_pack { 194 typedef std::map<std::string, internal::argument> args_map_type; 195 typedef std::vector<std::string> args_display_order_type; 196 typedef std::vector<std::string> positional_arg_names_type; 197 198 private: 199 args_map_type args_map; 200 args_display_order_type args_display_order; 201 positional_arg_names_type positional_arg_names; 202 std::set<std::string> bool_args_names; 203 204 private: 205 void add_arg(internal::argument const& a) { 206 std::pair<args_map_type::iterator, bool> result = 207 args_map.insert(std::make_pair(a.name(), a)); 208 if (!result.second) { 209 throw std::invalid_argument("argument with name: '" + a.name() + 210 "' already registered"); 211 } 212 args_display_order.push_back(a.name()); 213 } 214 215 public: 216 template <typename type> 217 cli_argument_pack& arg(type& dest, 218 std::string const& name, 219 std::string const& description, 220 bool (*validate)(const type&) = nullptr) { 221 internal::argument a(name, description, dest, validate); 222 add_arg(a); 223 if (internal::is_bool<type>::value()) { 224 bool_args_names.insert(name); 225 } 226 return *this; 227 } 228 229 //Positional means that argument name can be omitted in actual CL 230 //only key to match values for parameters with 231 template <typename type> 232 cli_argument_pack& positional_arg(type& dest, 233 std::string const& name, 234 std::string const& description, 235 bool (*validate)(const type&) = nullptr) { 236 internal::argument a(name, description, dest, validate); 237 add_arg(a); 238 if (internal::is_bool<type>::value()) { 239 bool_args_names.insert(name); 240 } 241 positional_arg_names.push_back(name); 242 return *this; 243 } 244 245 void parse(std::size_t argc, char const* argv[]) { 246 { 247 std::size_t current_positional_index = 0; 248 for (std::size_t j = 1; j < argc; j++) { 249 internal::argument* pa = nullptr; 250 std::string argument_value; 251 252 const char* const begin = argv[j]; 253 const char* const end = begin + std::strlen(argv[j]); 254 255 const char* const assign_sign = std::find(begin, end, '='); 256 257 struct throw_unknown_parameter { 258 static void _(std::string const& location) { 259 throw std::invalid_argument(std::string("unknown parameter starting at:'") + 260 location + "'"); 261 } 262 }; 263 //first try to interpret it like parameter=value string 264 if (assign_sign != end) { 265 std::string name_found = std::string(begin, assign_sign); 266 args_map_type::iterator it = args_map.find(name_found); 267 268 if (it != args_map.end()) { 269 pa = &((*it).second); 270 argument_value = std::string(assign_sign + 1, end); 271 } 272 else { 273 throw_unknown_parameter::_(argv[j]); 274 } 275 } 276 //then see is it a named flag 277 else { 278 args_map_type::iterator it = args_map.find(argv[j]); 279 if (it != args_map.end()) { 280 pa = &((*it).second); 281 argument_value = ""; 282 } 283 //then try it as positional argument without name specified 284 else if (current_positional_index < positional_arg_names.size()) { 285 std::stringstream str(argv[j]); 286 args_map_type::iterator found_positional_arg = 287 args_map.find(positional_arg_names.at(current_positional_index)); 288 //TODO: probably use of smarter assert would help here 289 assert( 290 found_positional_arg != 291 args_map 292 .end() /*&&"positional_arg_names and args_map are out of sync"*/); 293 if (found_positional_arg == args_map.end()) { 294 throw std::logic_error( 295 "positional_arg_names and args_map are out of sync"); 296 } 297 pa = &((*found_positional_arg).second); 298 argument_value = argv[j]; 299 300 current_positional_index++; 301 } 302 else { 303 //TODO: add tc to check 304 throw_unknown_parameter::_(argv[j]); 305 } 306 } 307 assert(pa); 308 if (pa->is_matched()) { 309 throw std::invalid_argument(std::string("several values specified for: '") + 310 pa->name() + "' argument"); 311 } 312 pa->parse_and_store(argument_value); 313 } 314 } 315 } 316 std::string usage_string(const std::string& binary_name) const { 317 std::string command_line_params; 318 std::string summary_description; 319 320 for (args_display_order_type::const_iterator it = args_display_order.begin(); 321 it != args_display_order.end(); 322 ++it) { 323 const bool is_bool = (0 != bool_args_names.count((*it))); 324 args_map_type::const_iterator argument_it = args_map.find(*it); 325 //TODO: probably use of smarter assert would help here 326 assert(argument_it != 327 args_map.end() /*&&"args_display_order and args_map are out of sync"*/); 328 if (argument_it == args_map.end()) { 329 throw std::logic_error("args_display_order and args_map are out of sync"); 330 } 331 const internal::argument& a = (*argument_it).second; 332 command_line_params += " [" + a.name() + (is_bool ? "" : "=value") + "]"; 333 summary_description += 334 " " + a.name() + " - " + a.description() + " (" + a.value() + ")" + "\n"; 335 } 336 337 std::string positional_arg_cl; 338 for (positional_arg_names_type::const_iterator it = positional_arg_names.begin(); 339 it != positional_arg_names.end(); 340 ++it) { 341 positional_arg_cl += " [" + (*it); 342 } 343 for (std::size_t i = 0; i < positional_arg_names.size(); ++i) { 344 positional_arg_cl += "]"; 345 } 346 command_line_params += positional_arg_cl; 347 std::stringstream str; 348 str << " Program usage is:" 349 << "\n" 350 << " " << binary_name << command_line_params << "\n" 351 << "\n" 352 << " where:" 353 << "\n" 354 << summary_description; 355 return str.str(); 356 } 357 }; // class cli_argument_pack 358 359 namespace internal { 360 template <typename T> 361 bool is_power_of_2(T val) { 362 std::size_t intval = std::size_t(val); 363 return (intval & (intval - 1)) == std::size_t(0); 364 } 365 int step_function_plus(int previous, double step) { 366 return static_cast<int>(previous + step); 367 } 368 int step_function_multiply(int previous, double multiply) { 369 return static_cast<int>(previous * multiply); 370 } 371 // "Power-of-2 ladder": nsteps is the desired number of steps between any subsequent powers of 2. 372 // The actual step is the quotient of the nearest smaller power of 2 divided by that number (but at least 1). 373 // E.g., '1:32:#4' means 1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32 374 int step_function_power2_ladder(int previous, double nsteps) { 375 int steps = int(nsteps); 376 assert(is_power_of_2(steps)); // must be a power of 2 377 // The actual step is 1 until the value is twice as big as nsteps 378 if (previous < 2 * steps) 379 return previous + 1; 380 // calculate the previous power of 2 381 int prev_power2 = previous / 2; // start with half the given value 382 int rshift = 1; // and with the shift of 1; 383 while (int shifted = 384 prev_power2 >> rshift) { // shift the value right; while the result is non-zero, 385 prev_power2 |= shifted; // add the bits set in 'shifted'; 386 rshift <<= 1; // double the shift, as twice as many top bits are set; 387 } // repeat. 388 ++prev_power2; // all low bits set; now it's just one less than the desired power of 2 389 assert(is_power_of_2(prev_power2)); 390 assert((prev_power2 <= previous) && (2 * prev_power2 > previous)); 391 // The actual step value is the previous power of 2 divided by steps 392 return previous + (prev_power2 / steps); 393 } 394 typedef int (*step_function_ptr_type)(int, double); 395 396 struct step_function_descriptor { 397 char mnemonic; 398 step_function_ptr_type function; 399 400 public: 401 step_function_descriptor(char a_mnemonic, step_function_ptr_type a_function) 402 : mnemonic(a_mnemonic), 403 function(a_function) {} 404 405 private: 406 void operator=(step_function_descriptor const&); 407 }; 408 step_function_descriptor step_function_descriptors[] = { 409 step_function_descriptor('*', step_function_multiply), 410 step_function_descriptor('+', step_function_plus), 411 step_function_descriptor('#', step_function_power2_ladder) 412 }; 413 414 template <typename T, std::size_t N> 415 inline std::size_t array_length(const T (&)[N]) { 416 return N; 417 } 418 419 struct thread_range_step { 420 step_function_ptr_type step_function; 421 double step_function_argument; 422 423 thread_range_step(step_function_ptr_type step_function_, double step_function_argument_) 424 : step_function(step_function_), 425 step_function_argument(step_function_argument_) { 426 if (!step_function_) 427 throw std::invalid_argument( 428 "step_function for thread range step should not be nullptr"); 429 } 430 int operator()(int previous) const { 431 assert(0 <= previous); // test 0<=first and loop discipline 432 const int ret = step_function(previous, step_function_argument); 433 assert(previous < ret); 434 return ret; 435 } 436 friend std::istream& operator>>(std::istream& input_stream, thread_range_step& step) { 437 char function_char; 438 double function_argument; 439 input_stream >> function_char >> function_argument; 440 std::size_t i = 0; 441 while ((i < array_length(step_function_descriptors)) && 442 (step_function_descriptors[i].mnemonic != function_char)) 443 ++i; 444 if (i >= array_length(step_function_descriptors)) { 445 throw std::invalid_argument("unknown step function mnemonic: " + 446 std::string(1, function_char)); 447 } 448 else if ((function_char == '#') && !is_power_of_2(function_argument)) { 449 throw std::invalid_argument("the argument of # should be a power of 2"); 450 } 451 step.step_function = step_function_descriptors[i].function; 452 step.step_function_argument = function_argument; 453 return input_stream; 454 } 455 }; 456 } // namespace internal 457 458 struct thread_number_range { 459 int (*auto_number_of_threads)(); 460 int first; // 0<=first (0 can be used as a special value) 461 int last; // first<=last 462 463 ::utility::internal::thread_range_step step; 464 465 thread_number_range( 466 int (*auto_number_of_threads_)(), 467 int low_ = 1, 468 int high_ = -1, 469 ::utility::internal::thread_range_step step_ = 470 ::utility::internal::thread_range_step(::utility::internal::step_function_power2_ladder, 471 4)) 472 : auto_number_of_threads(auto_number_of_threads_), 473 first(low_), 474 last((high_ > -1) ? high_ : auto_number_of_threads_()), 475 step(step_) { 476 if (first < 0) { 477 throw std::invalid_argument("negative value not allowed"); 478 } 479 if (first > last) { 480 throw std::invalid_argument("decreasing sequence not allowed"); 481 } 482 } 483 friend std::istream& operator>>(std::istream& i, thread_number_range& range) { 484 try { 485 std::string s; 486 i >> s; 487 struct string_to_number_of_threads { 488 int auto_value; 489 string_to_number_of_threads(int auto_value_) : auto_value(auto_value_) {} 490 int operator()(const std::string& value) const { 491 return (value == "auto") ? auto_value : internal::string_to<int>(value); 492 } 493 }; 494 string_to_number_of_threads string_to_number_of_threads(range.auto_number_of_threads()); 495 int low, high; 496 std::size_t colon = s.find(':'); 497 if (colon == std::string::npos) { 498 low = high = string_to_number_of_threads(s); 499 } 500 else { 501 //it is a range 502 std::size_t second_colon = s.find(':', colon + 1); 503 504 low = string_to_number_of_threads(std::string(s, 0, colon)); //not copying the colon 505 high = string_to_number_of_threads( 506 std::string(s, colon + 1, second_colon - (colon + 1))); //not copying the colons 507 if (second_colon != std::string::npos) { 508 internal::string_to(std::string(s, second_colon + 1), range.step); 509 } 510 } 511 range = thread_number_range(range.auto_number_of_threads, low, high, range.step); 512 } 513 catch (std::invalid_argument&) { 514 i.setstate(std::ios::failbit); 515 throw; 516 } 517 return i; 518 } 519 friend std::ostream& operator<<(std::ostream& o, thread_number_range const& range) { 520 using namespace internal; 521 std::size_t i = 0; 522 for (; i < array_length(step_function_descriptors) && 523 step_function_descriptors[i].function != range.step.step_function; 524 ++i) { 525 } 526 if (i >= array_length(step_function_descriptors)) { 527 throw std::invalid_argument("unknown step function for thread range"); 528 } 529 o << range.first << ":" << range.last << ":" << step_function_descriptors[i].mnemonic 530 << range.step.step_function_argument; 531 return o; 532 } 533 }; // struct thread_number_range 534 //TODO: fix unused warning here 535 static const char* thread_number_range_desc = 536 "number of threads to use; a range of the form low[:high[:(+|*|#)step]]," 537 "\n\twhere low and optional high are non-negative integers or 'auto' for the default choice," 538 "\n\tand optional step expression specifies how thread numbers are chosen within the range."; 539 540 inline void report_elapsed_time(double seconds) { 541 std::cout << "elapsed time : " << seconds << " seconds" 542 << "\n"; 543 } 544 545 inline void report_skipped() { 546 std::cout << "skip" 547 << "\n"; 548 } 549 550 inline void parse_cli_arguments(int argc, const char* argv[], utility::cli_argument_pack cli_pack) { 551 bool show_help = false; 552 cli_pack.arg(show_help, "-h", "show this message"); 553 554 bool invalid_input = false; 555 try { 556 cli_pack.parse(argc, argv); 557 } 558 catch (std::exception& e) { 559 std::cerr << "error occurred while parsing command line." 560 << "\n" 561 << "error text: " << e.what() << "\n" 562 << std::flush; 563 invalid_input = true; 564 } 565 if (show_help || invalid_input) { 566 std::cout << cli_pack.usage_string(argv[0]) << std::flush; 567 std::exit(0); 568 } 569 } 570 inline void parse_cli_arguments(int argc, char* argv[], utility::cli_argument_pack cli_pack) { 571 parse_cli_arguments(argc, const_cast<const char**>(argv), cli_pack); 572 } 573 } // namespace utility 574 575 #endif /* TBB_examples_utility_H */ 576