CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
584 lines
21 KiB
C++
584 lines
21 KiB
C++
/*
|
|
Copyright (c) 2005-2025 Intel Corporation
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
#ifndef TBB_examples_utility_H
|
|
#define TBB_examples_utility_H
|
|
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <cstdlib>
|
|
#include <cmath>
|
|
|
|
#include <utility>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <set>
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
#include <numeric>
|
|
#include <stdexcept>
|
|
#include <memory>
|
|
#include <iostream>
|
|
#include <chrono>
|
|
// TBB headers should not be used, as some examples may need to be built without TBB.
|
|
|
|
namespace utility {
|
|
namespace internal {
|
|
|
|
//TODO: add tcs
|
|
template <class dest_type>
|
|
dest_type& string_to(std::string const& s, dest_type& result) {
|
|
std::stringstream stream(s);
|
|
stream >> result;
|
|
if ((!stream) || (stream.fail())) {
|
|
throw std::invalid_argument("error converting string '" + std::string(s) + "'");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template <class dest_type>
|
|
dest_type string_to(std::string const& s) {
|
|
dest_type result;
|
|
return string_to(s, result);
|
|
}
|
|
|
|
template <typename>
|
|
struct is_bool {
|
|
static bool value() {
|
|
return false;
|
|
}
|
|
};
|
|
template <>
|
|
struct is_bool<bool> {
|
|
static bool value() {
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class type_base {
|
|
type_base& operator=(const type_base&);
|
|
|
|
public:
|
|
const std::string name;
|
|
const std::string description;
|
|
|
|
type_base(std::string a_name, std::string a_description)
|
|
: name(a_name),
|
|
description(a_description) {}
|
|
virtual void parse_and_store(const std::string& s) = 0;
|
|
virtual std::string value() const = 0;
|
|
virtual std::unique_ptr<type_base> clone() const = 0;
|
|
virtual ~type_base() {}
|
|
};
|
|
template <typename type>
|
|
class type_impl : public type_base {
|
|
private:
|
|
type_impl(const type_impl& src)
|
|
: type_base(src.name, src.description),
|
|
target(src.target),
|
|
validating_function(src.validating_function) {}
|
|
type_impl& operator=(const type_impl&);
|
|
typedef bool (*validating_function_type)(const type&);
|
|
type& target;
|
|
validating_function_type validating_function;
|
|
|
|
public:
|
|
type_impl(std::string a_name,
|
|
std::string a_description,
|
|
type& a_target,
|
|
validating_function_type a_validating_function = nullptr)
|
|
: type_base(a_name, a_description),
|
|
target(a_target),
|
|
validating_function(a_validating_function){};
|
|
void parse_and_store(const std::string& s) /*override*/ {
|
|
try {
|
|
const bool is_bool = internal::is_bool<type>::value();
|
|
if (is_bool && s.empty()) {
|
|
//to avoid directly assigning true
|
|
//(as it will impose additional layer of indirection)
|
|
//so, simply pass it as string
|
|
internal::string_to("1", target);
|
|
}
|
|
else {
|
|
internal::string_to(s, target);
|
|
}
|
|
}
|
|
catch (std::invalid_argument& e) {
|
|
std::stringstream str;
|
|
str << "'" << s << "' is incorrect input for argument '" << name << "'"
|
|
<< " (" << e.what() << ")";
|
|
throw std::invalid_argument(str.str());
|
|
}
|
|
if (validating_function) {
|
|
if (!((validating_function)(target))) {
|
|
std::stringstream str;
|
|
str << "'" << target << "' is invalid value for argument '" << name << "'";
|
|
throw std::invalid_argument(str.str());
|
|
}
|
|
}
|
|
}
|
|
template <typename t>
|
|
static bool is_null_c_str(t&) {
|
|
return false;
|
|
}
|
|
static bool is_null_c_str(char* s) {
|
|
return s == nullptr;
|
|
}
|
|
std::string value() const /*override*/ {
|
|
std::stringstream str;
|
|
if (!is_null_c_str(target))
|
|
str << target;
|
|
return str.str();
|
|
}
|
|
std::unique_ptr<type_base> clone() const /*override*/ {
|
|
return std::unique_ptr<type_base>(new type_impl(*this));
|
|
}
|
|
};
|
|
|
|
class argument {
|
|
private:
|
|
std::unique_ptr<type_base> p_type;
|
|
bool matched_;
|
|
|
|
public:
|
|
argument(argument const& other)
|
|
: p_type(other.p_type.get() ? (other.p_type->clone()).release() : nullptr),
|
|
matched_(other.matched_) {}
|
|
argument& operator=(argument a) {
|
|
this->swap(a);
|
|
return *this;
|
|
}
|
|
void swap(argument& other) {
|
|
std::swap(p_type, other.p_type);
|
|
std::swap(matched_, other.matched_);
|
|
}
|
|
template <class type>
|
|
argument(std::string a_name,
|
|
std::string a_description,
|
|
type& dest,
|
|
bool (*a_validating_function)(const type&) = nullptr)
|
|
: p_type(new type_impl<type>(a_name, a_description, dest, a_validating_function)),
|
|
matched_(false) {}
|
|
std::string value() const {
|
|
return p_type->value();
|
|
}
|
|
std::string name() const {
|
|
return p_type->name;
|
|
}
|
|
std::string description() const {
|
|
return p_type->description;
|
|
}
|
|
void parse_and_store(const std::string& s) {
|
|
p_type->parse_and_store(s);
|
|
matched_ = true;
|
|
}
|
|
bool is_matched() const {
|
|
return matched_;
|
|
}
|
|
};
|
|
} // namespace internal
|
|
|
|
class cli_argument_pack {
|
|
typedef std::map<std::string, internal::argument> args_map_type;
|
|
typedef std::vector<std::string> args_display_order_type;
|
|
typedef std::vector<std::string> positional_arg_names_type;
|
|
|
|
private:
|
|
args_map_type args_map;
|
|
args_display_order_type args_display_order;
|
|
positional_arg_names_type positional_arg_names;
|
|
std::set<std::string> bool_args_names;
|
|
|
|
private:
|
|
void add_arg(internal::argument const& a) {
|
|
std::pair<args_map_type::iterator, bool> result =
|
|
args_map.insert(std::make_pair(a.name(), a));
|
|
if (!result.second) {
|
|
throw std::invalid_argument("argument with name: '" + a.name() +
|
|
"' already registered");
|
|
}
|
|
args_display_order.push_back(a.name());
|
|
}
|
|
|
|
public:
|
|
template <typename type>
|
|
cli_argument_pack& arg(type& dest,
|
|
std::string const& name,
|
|
std::string const& description,
|
|
bool (*validate)(const type&) = nullptr) {
|
|
internal::argument a(name, description, dest, validate);
|
|
add_arg(a);
|
|
if (internal::is_bool<type>::value()) {
|
|
bool_args_names.insert(name);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
//Positional means that argument name can be omitted in actual CL
|
|
//only key to match values for parameters with
|
|
template <typename type>
|
|
cli_argument_pack& positional_arg(type& dest,
|
|
std::string const& name,
|
|
std::string const& description,
|
|
bool (*validate)(const type&) = nullptr) {
|
|
internal::argument a(name, description, dest, validate);
|
|
add_arg(a);
|
|
if (internal::is_bool<type>::value()) {
|
|
bool_args_names.insert(name);
|
|
}
|
|
positional_arg_names.push_back(name);
|
|
return *this;
|
|
}
|
|
|
|
void parse(std::size_t argc, char const* argv[]) {
|
|
{
|
|
std::size_t current_positional_index = 0;
|
|
for (std::size_t j = 1; j < argc; j++) {
|
|
internal::argument* pa = nullptr;
|
|
std::string argument_value;
|
|
|
|
const char* const begin = argv[j];
|
|
const char* const end = begin + std::strlen(argv[j]);
|
|
|
|
const char* const assign_sign = std::find(begin, end, '=');
|
|
|
|
struct throw_unknown_parameter {
|
|
static void _(std::string const& location) {
|
|
throw std::invalid_argument(std::string("unknown parameter starting at:'") +
|
|
location + "'");
|
|
}
|
|
};
|
|
//first try to interpret it like parameter=value string
|
|
if (assign_sign != end) {
|
|
std::string name_found = std::string(begin, assign_sign);
|
|
args_map_type::iterator it = args_map.find(name_found);
|
|
|
|
if (it != args_map.end()) {
|
|
pa = &((*it).second);
|
|
argument_value = std::string(assign_sign + 1, end);
|
|
}
|
|
else {
|
|
throw_unknown_parameter::_(argv[j]);
|
|
}
|
|
}
|
|
//then see is it a named flag
|
|
else {
|
|
args_map_type::iterator it = args_map.find(argv[j]);
|
|
if (it != args_map.end()) {
|
|
pa = &((*it).second);
|
|
argument_value = "";
|
|
}
|
|
//then try it as positional argument without name specified
|
|
else if (current_positional_index < positional_arg_names.size()) {
|
|
std::stringstream str(argv[j]);
|
|
args_map_type::iterator found_positional_arg =
|
|
args_map.find(positional_arg_names.at(current_positional_index));
|
|
//TODO: probably use of smarter assert would help here
|
|
assert(
|
|
found_positional_arg !=
|
|
args_map
|
|
.end() /*&&"positional_arg_names and args_map are out of sync"*/);
|
|
if (found_positional_arg == args_map.end()) {
|
|
throw std::logic_error(
|
|
"positional_arg_names and args_map are out of sync");
|
|
}
|
|
pa = &((*found_positional_arg).second);
|
|
argument_value = argv[j];
|
|
|
|
current_positional_index++;
|
|
}
|
|
else {
|
|
//TODO: add tc to check
|
|
throw_unknown_parameter::_(argv[j]);
|
|
}
|
|
}
|
|
assert(pa);
|
|
if (pa->is_matched()) {
|
|
throw std::invalid_argument(std::string("several values specified for: '") +
|
|
pa->name() + "' argument");
|
|
}
|
|
pa->parse_and_store(argument_value);
|
|
}
|
|
}
|
|
}
|
|
std::string usage_string(const std::string& binary_name) const {
|
|
std::string command_line_params;
|
|
std::string summary_description;
|
|
|
|
for (args_display_order_type::const_iterator it = args_display_order.begin();
|
|
it != args_display_order.end();
|
|
++it) {
|
|
const bool is_bool = (0 != bool_args_names.count((*it)));
|
|
args_map_type::const_iterator argument_it = args_map.find(*it);
|
|
//TODO: probably use of smarter assert would help here
|
|
assert(argument_it !=
|
|
args_map.end() /*&&"args_display_order and args_map are out of sync"*/);
|
|
if (argument_it == args_map.end()) {
|
|
throw std::logic_error("args_display_order and args_map are out of sync");
|
|
}
|
|
const internal::argument& a = (*argument_it).second;
|
|
command_line_params += " [" + a.name() + (is_bool ? "" : "=value") + "]";
|
|
summary_description +=
|
|
" " + a.name() + " - " + a.description() + " (" + a.value() + ")" + "\n";
|
|
}
|
|
|
|
std::string positional_arg_cl;
|
|
for (positional_arg_names_type::const_iterator it = positional_arg_names.begin();
|
|
it != positional_arg_names.end();
|
|
++it) {
|
|
positional_arg_cl += " [" + (*it);
|
|
}
|
|
for (std::size_t i = 0; i < positional_arg_names.size(); ++i) {
|
|
positional_arg_cl += "]";
|
|
}
|
|
command_line_params += positional_arg_cl;
|
|
std::stringstream str;
|
|
str << " Program usage is:"
|
|
<< "\n"
|
|
<< " " << binary_name << command_line_params << "\n"
|
|
<< "\n"
|
|
<< " where:"
|
|
<< "\n"
|
|
<< summary_description;
|
|
return str.str();
|
|
}
|
|
}; // class cli_argument_pack
|
|
|
|
namespace internal {
|
|
template <typename T>
|
|
bool is_power_of_2(T val) {
|
|
std::size_t intval = std::size_t(val);
|
|
return (intval & (intval - 1)) == std::size_t(0);
|
|
}
|
|
int step_function_plus(int previous, double step) {
|
|
return static_cast<int>(previous + step);
|
|
}
|
|
int step_function_multiply(int previous, double multiply) {
|
|
return static_cast<int>(previous * multiply);
|
|
}
|
|
// "Power-of-2 ladder": nsteps is the desired number of steps between any subsequent powers of 2.
|
|
// The actual step is the quotient of the nearest smaller power of 2 divided by that number (but at least 1).
|
|
// E.g., '1:32:#4' means 1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32
|
|
int step_function_power2_ladder(int previous, double nsteps) {
|
|
int steps = int(nsteps);
|
|
assert(is_power_of_2(steps)); // must be a power of 2
|
|
// The actual step is 1 until the value is twice as big as nsteps
|
|
if (previous < 2 * steps)
|
|
return previous + 1;
|
|
// calculate the previous power of 2
|
|
int prev_power2 = previous / 2; // start with half the given value
|
|
int rshift = 1; // and with the shift of 1;
|
|
while (int shifted =
|
|
prev_power2 >> rshift) { // shift the value right; while the result is non-zero,
|
|
prev_power2 |= shifted; // add the bits set in 'shifted';
|
|
rshift <<= 1; // double the shift, as twice as many top bits are set;
|
|
} // repeat.
|
|
++prev_power2; // all low bits set; now it's just one less than the desired power of 2
|
|
assert(is_power_of_2(prev_power2));
|
|
assert((prev_power2 <= previous) && (2 * prev_power2 > previous));
|
|
// The actual step value is the previous power of 2 divided by steps
|
|
return previous + (prev_power2 / steps);
|
|
}
|
|
typedef int (*step_function_ptr_type)(int, double);
|
|
|
|
struct step_function_descriptor {
|
|
char mnemonic;
|
|
step_function_ptr_type function;
|
|
|
|
public:
|
|
step_function_descriptor(char a_mnemonic, step_function_ptr_type a_function)
|
|
: mnemonic(a_mnemonic),
|
|
function(a_function) {}
|
|
|
|
private:
|
|
void operator=(step_function_descriptor const&);
|
|
};
|
|
step_function_descriptor step_function_descriptors[] = {
|
|
step_function_descriptor('*', step_function_multiply),
|
|
step_function_descriptor('+', step_function_plus),
|
|
step_function_descriptor('#', step_function_power2_ladder)
|
|
};
|
|
|
|
template <typename T, std::size_t N>
|
|
inline std::size_t array_length(const T (&)[N]) {
|
|
return N;
|
|
}
|
|
|
|
struct thread_range_step {
|
|
step_function_ptr_type step_function;
|
|
double step_function_argument;
|
|
|
|
thread_range_step(step_function_ptr_type step_function_, double step_function_argument_)
|
|
: step_function(step_function_),
|
|
step_function_argument(step_function_argument_) {
|
|
if (!step_function_)
|
|
throw std::invalid_argument(
|
|
"step_function for thread range step should not be nullptr");
|
|
}
|
|
int operator()(int previous) const {
|
|
assert(0 <= previous); // test 0<=first and loop discipline
|
|
const int ret = step_function(previous, step_function_argument);
|
|
assert(previous < ret);
|
|
return ret;
|
|
}
|
|
friend std::istream& operator>>(std::istream& input_stream, thread_range_step& step) {
|
|
char function_char;
|
|
double function_argument;
|
|
input_stream >> function_char >> function_argument;
|
|
std::size_t i = 0;
|
|
while ((i < array_length(step_function_descriptors)) &&
|
|
(step_function_descriptors[i].mnemonic != function_char))
|
|
++i;
|
|
if (i >= array_length(step_function_descriptors)) {
|
|
throw std::invalid_argument("unknown step function mnemonic: " +
|
|
std::string(1, function_char));
|
|
}
|
|
else if ((function_char == '#') && !is_power_of_2(function_argument)) {
|
|
throw std::invalid_argument("the argument of # should be a power of 2");
|
|
}
|
|
step.step_function = step_function_descriptors[i].function;
|
|
step.step_function_argument = function_argument;
|
|
return input_stream;
|
|
}
|
|
};
|
|
} // namespace internal
|
|
|
|
struct thread_number_range {
|
|
int (*auto_number_of_threads)();
|
|
int first; // 0<=first (0 can be used as a special value)
|
|
int last; // first<=last
|
|
|
|
::utility::internal::thread_range_step step;
|
|
|
|
thread_number_range(
|
|
int (*auto_number_of_threads_)(),
|
|
int low_ = 1,
|
|
int high_ = -1,
|
|
::utility::internal::thread_range_step step_ =
|
|
::utility::internal::thread_range_step(::utility::internal::step_function_power2_ladder,
|
|
4))
|
|
: auto_number_of_threads(auto_number_of_threads_),
|
|
first(low_),
|
|
last((high_ > -1) ? high_ : auto_number_of_threads_()),
|
|
step(step_) {
|
|
if (first < 0) {
|
|
throw std::invalid_argument("negative value not allowed");
|
|
}
|
|
if (first > last) {
|
|
throw std::invalid_argument("decreasing sequence not allowed");
|
|
}
|
|
}
|
|
friend std::istream& operator>>(std::istream& i, thread_number_range& range) {
|
|
try {
|
|
std::string s;
|
|
i >> s;
|
|
struct string_to_number_of_threads {
|
|
int auto_value;
|
|
string_to_number_of_threads(int auto_value_) : auto_value(auto_value_) {}
|
|
int operator()(const std::string& value) const {
|
|
return (value == "auto") ? auto_value : internal::string_to<int>(value);
|
|
}
|
|
};
|
|
string_to_number_of_threads string_to_number_of_threads(range.auto_number_of_threads());
|
|
int low, high;
|
|
std::size_t colon = s.find(':');
|
|
if (colon == std::string::npos) {
|
|
low = high = string_to_number_of_threads(s);
|
|
}
|
|
else {
|
|
//it is a range
|
|
std::size_t second_colon = s.find(':', colon + 1);
|
|
|
|
low = string_to_number_of_threads(std::string(s, 0, colon)); //not copying the colon
|
|
high = string_to_number_of_threads(
|
|
std::string(s, colon + 1, second_colon - (colon + 1))); //not copying the colons
|
|
if (second_colon != std::string::npos) {
|
|
internal::string_to(std::string(s, second_colon + 1), range.step);
|
|
}
|
|
}
|
|
range = thread_number_range(range.auto_number_of_threads, low, high, range.step);
|
|
}
|
|
catch (std::invalid_argument&) {
|
|
i.setstate(std::ios::failbit);
|
|
throw;
|
|
}
|
|
return i;
|
|
}
|
|
friend std::ostream& operator<<(std::ostream& o, thread_number_range const& range) {
|
|
using namespace internal;
|
|
std::size_t i = 0;
|
|
for (; i < array_length(step_function_descriptors) &&
|
|
step_function_descriptors[i].function != range.step.step_function;
|
|
++i) {
|
|
}
|
|
if (i >= array_length(step_function_descriptors)) {
|
|
throw std::invalid_argument("unknown step function for thread range");
|
|
}
|
|
o << range.first << ":" << range.last << ":" << step_function_descriptors[i].mnemonic
|
|
<< range.step.step_function_argument;
|
|
return o;
|
|
}
|
|
}; // struct thread_number_range
|
|
//TODO: fix unused warning here
|
|
static const char* thread_number_range_desc =
|
|
"number of threads to use; a range of the form low[:high[:(+|*|#)step]],"
|
|
"\n\twhere low and optional high are non-negative integers or 'auto' for the default choice,"
|
|
"\n\tand optional step expression specifies how thread numbers are chosen within the range.";
|
|
|
|
inline void report_elapsed_time(double seconds) {
|
|
std::cout << "elapsed time : " << seconds << " seconds"
|
|
<< "\n";
|
|
}
|
|
|
|
inline void report_skipped() {
|
|
std::cout << "skip"
|
|
<< "\n";
|
|
}
|
|
|
|
inline void report_relative_error(double err,
|
|
const std::string& prefix = "Relative_Err : ",
|
|
const std::string& suffix = " %") {
|
|
std::cout << prefix << err << suffix << std::endl;
|
|
}
|
|
|
|
inline void parse_cli_arguments(int argc, const char* argv[], utility::cli_argument_pack cli_pack) {
|
|
bool show_help = false;
|
|
cli_pack.arg(show_help, "-h", "show this message");
|
|
|
|
bool invalid_input = false;
|
|
try {
|
|
cli_pack.parse(argc, argv);
|
|
}
|
|
catch (std::exception& e) {
|
|
std::cerr << "error occurred while parsing command line."
|
|
<< "\n"
|
|
<< "error text: " << e.what() << "\n"
|
|
<< std::flush;
|
|
invalid_input = true;
|
|
}
|
|
if (show_help || invalid_input) {
|
|
std::cout << cli_pack.usage_string(argv[0]) << std::flush;
|
|
std::exit(0);
|
|
}
|
|
}
|
|
inline void parse_cli_arguments(int argc, char* argv[], utility::cli_argument_pack cli_pack) {
|
|
parse_cli_arguments(argc, const_cast<const char**>(argv), cli_pack);
|
|
}
|
|
} // namespace utility
|
|
|
|
#endif /* TBB_examples_utility_H */
|