/******************************************************************************* * * MIT License * * Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * *******************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include namespace Tensile { namespace Client { namespace po { inline std::vector split_string(std::string const& s, char const* sep = ",;") { std::vector out; std::string::size_type pos = 0; for(;;) { auto next = s.find_first_of(sep, pos); if(next == std::string::npos) { if(pos < s.size()) { std::string part = s.substr(pos); if(!part.empty()) out.push_back(std::move(part)); } break; } if(next > pos) out.push_back(s.substr(pos, next - pos)); pos = next + 1; } return out; } inline bool parse_bool_string(std::string const& s) { std::string lower; lower.reserve(s.size()); for(char c : s) lower.push_back(static_cast(std::tolower(static_cast(c)))); if(lower == "1" || lower == "true" || lower == "yes") return true; if(lower == "0" || lower == "false" || lower == "no") return false; throw std::runtime_error("Failed to parse bool: " + s); } inline std::string trim_opt(std::string s) { auto start = s.find_first_not_of(" \t"); if(start == std::string::npos) return {}; auto end = s.find_last_not_of(" \t"); return s.substr(start, end == std::string::npos ? end : end - start + 1); } inline std::string get_canonical_option_name(std::string const& opts) { auto pos = opts.find(','); if(pos == std::string::npos) return trim_opt(opts); return trim_opt(opts.substr(0, pos)); } class variable_value { std::any m_value; public: variable_value() = default; variable_value(variable_value const& other); variable_value(variable_value&& other) noexcept; variable_value& operator=(variable_value const& other); variable_value& operator=(variable_value&& other) noexcept; explicit variable_value(std::any v) : m_value(std::move(v)) { } bool empty() const { return !m_value.has_value(); } std::any& value() { return m_value; } std::any const& value() const { return m_value; } template T const& as() const { if(!m_value.has_value()) { // Match Boost: unset option yields default-constructed T when .as() is used static T const defaultVal{}; return defaultVal; } try { return std::any_cast(m_value); } catch(std::bad_any_cast const&) { // Allow bool option stored as string (e.g. "True"/"False" from config) if constexpr(std::is_same_v) { if(m_value.type() == typeid(std::string)) { return parse_bool_string( std::any_cast(m_value)); } } throw std::runtime_error( "program_options: bad_any_cast - stored option value has a different " "type than the requested .as()"); } } }; class variables_map { std::map m_map; static variable_value const s_empty; public: using key_type = std::string; using mapped_type = variable_value; using value_type = std::pair; using iterator = std::map::iterator; using const_iterator = std::map::const_iterator; variable_value& operator[](key_type const& k) { return m_map[k]; } variable_value const& operator[](key_type const& k) const { auto it = m_map.find(k); if(it != m_map.end()) return it->second; return s_empty; } variable_value& at(key_type const& k) { return m_map.at(k); } variable_value const& at(key_type const& k) const { return m_map.at(k); } size_t count(key_type const& k) const { return m_map.count(k); } iterator begin() { return m_map.begin(); } iterator end() { return m_map.end(); } const_iterator begin() const { return m_map.begin(); } const_iterator end() const { return m_map.end(); } const_iterator find(key_type const& k) const { return m_map.find(k); } iterator find(key_type const& k) { return m_map.find(k); } std::pair insert(value_type const& v) { return m_map.insert(v); } void clear() { m_map.clear(); } }; struct option_value_base { virtual ~option_value_base() = default; virtual std::any get_default() const = 0; virtual std::function parser() const = 0; /** Parse one line from config file. Default: parser()(value). Override for Boost-compatible * behaviour where each config line is one value (no comma split). */ virtual std::any parse_config_line(std::string const& value) const { return parser()(value); } virtual bool is_multivalued() const { return false; } virtual bool is_flag() const { return false; } virtual bool has_explicit_default() const { return false; } virtual bool has_implicit_value() const { return false; } virtual std::any get_implicit_value() const { return std::any(); } virtual void merge_into(std::any& current, std::any const& new_val) const { current = new_val; } }; template struct typed_value_holder : option_value_base { T m_default{}; bool m_has_explicit_default = false; typed_value_holder* default_value(T v) { m_default = std::move(v); m_has_explicit_default = true; return this; } typed_value_holder* default_value(T v, std::string const&) { m_default = std::move(v); m_has_explicit_default = true; return this; } bool has_explicit_default() const override { return m_has_explicit_default; } std::any get_default() const override { return std::any(m_default); } std::function parser() const override { return [](std::string const& s) { T t; std::istringstream ss(s); ss >> t; if(!ss && !ss.eof()) throw std::runtime_error("Failed to parse option value: " + s); return std::any(t); }; } bool is_flag() const override { return false; } }; template <> struct typed_value_holder : option_value_base { bool m_default = false; bool m_has_explicit_default = false; bool m_has_implicit = false; bool m_implicit_value = false; typed_value_holder* default_value(bool v) { m_default = v; m_has_explicit_default = true; return this; } typed_value_holder* default_value(bool v, std::string const&) { m_default = v; m_has_explicit_default = true; return this; } typed_value_holder* implicit_value(bool v) { m_has_implicit = true; m_implicit_value = v; return this; } bool has_explicit_default() const override { return m_has_explicit_default; } bool has_implicit_value() const override { return m_has_implicit; } std::any get_implicit_value() const override { return std::any(m_implicit_value); } std::any get_default() const override { return std::any(m_default); } std::function parser() const override { return [](std::string const& s) { return std::any(parse_bool_string(s)); }; } bool is_flag() const override { return true; } }; template struct typed_value_holder> : option_value_base { std::vector m_default; bool m_has_explicit_default = false; typed_value_holder* default_value(std::vector v) { m_default = std::move(v); m_has_explicit_default = true; return this; } typed_value_holder* default_value(std::vector v, std::string const&) { m_default = std::move(v); m_has_explicit_default = true; return this; } bool has_explicit_default() const override { return m_has_explicit_default; } std::any get_default() const override { return std::any(m_default); } bool is_multivalued() const override { return true; } std::function parser() const override { return [](std::string const& s) { auto parts = split_string(s); std::vector out; out.reserve(parts.size()); for(auto const& p : parts) { if(p.empty()) continue; U u; std::istringstream ss(p); ss >> u; if(!ss && !ss.eof()) throw std::runtime_error("Failed to parse: " + p); out.push_back(std::move(u)); } return std::any(out); }; } void merge_into(std::any& current, std::any const& new_val) const override { auto& c = std::any_cast&>(current); auto const& n = std::any_cast const&>(new_val); c.insert(c.end(), n.begin(), n.end()); } }; template <> struct typed_value_holder> : option_value_base { std::vector m_default; bool m_has_explicit_default = false; typed_value_holder* default_value(std::vector v) { m_default = std::move(v); m_has_explicit_default = true; return this; } typed_value_holder* default_value(std::vector v, std::string const&) { m_default = std::move(v); m_has_explicit_default = true; return this; } bool has_explicit_default() const override { return m_has_explicit_default; } std::any get_default() const override { return std::any(m_default); } bool is_multivalued() const override { return true; } std::function parser() const override { return [](std::string const& s) { return std::any(split_string(s)); }; } /** Boost config file behaviour: each line is one value (no comma split). */ std::any parse_config_line(std::string const& value) const override { std::string t = trim_opt(value); if(t.empty()) return std::any(std::vector{}); return std::any(std::vector{std::move(t)}); } void merge_into(std::any& current, std::any const& new_val) const override { auto& c = std::any_cast&>(current); auto const& n = std::any_cast const&>(new_val); c.insert(c.end(), n.begin(), n.end()); } }; template <> struct typed_value_holder> : option_value_base { std::vector m_default; bool m_has_explicit_default = false; typed_value_holder* default_value(std::vector v) { m_default = std::move(v); m_has_explicit_default = true; return this; } typed_value_holder* default_value(std::vector v, std::string const&) { m_default = std::move(v); m_has_explicit_default = true; return this; } bool has_explicit_default() const override { return m_has_explicit_default; } std::any get_default() const override { return std::any(m_default); } bool is_multivalued() const override { return true; } std::function parser() const override { return [](std::string const& s) { auto parts = split_string(s); std::vector out; out.reserve(parts.size()); for(auto const& p : parts) { if(p.empty()) continue; out.push_back(parse_bool_string(p)); } return std::any(out); }; } void merge_into(std::any& current, std::any const& new_val) const override { auto& c = std::any_cast&>(current); auto const& n = std::any_cast const&>(new_val); c.insert(c.end(), n.begin(), n.end()); } }; template using typed_value = typed_value_holder; template typed_value_holder* value() { return new typed_value_holder(); } class options_description { public: struct option_entry { std::string opts; // e.g. "help,h" std::string description; std::unique_ptr value_semantic; }; using option_list = std::vector; explicit options_description(std::string const&) {} class add_options_helper { option_list& m_list; public: explicit add_options_helper(option_list& list) : m_list(list) { } add_options_helper& operator()(std::string const& opts, std::string const& desc) { auto* e = new typed_value_holder(); e->default_value(false); m_list.push_back({opts, desc, std::unique_ptr(e)}); return *this; } template add_options_helper& operator()(std::string const& opts, typed_value_holder* val, std::string const& desc) { m_list.push_back({opts, desc, std::unique_ptr(val)}); return *this; } }; add_options_helper add_options() { return add_options_helper(m_options); } option_list const& options() const { return m_options; } private: option_list m_options; }; variables_map parse_command_line(int argc, char const* const argv[], options_description const& desc); variables_map parse_config_file(std::istream& is, options_description const& desc); inline void store(variables_map const& from, variables_map& to) { for(auto const& kv : from) to[kv.first].value() = kv.second.value(); } inline void notify(variables_map const&) {} std::ostream& operator<<(std::ostream& os, options_description const& desc); } // namespace po } // namespace Client } // namespace Tensile // Specialize std traits so HIP-compiled TUs don't instantiate // __is_constructible_impl, which can hit "incomplete type" // when the HIP runtime wrapper pulls in before the type is complete. namespace std { template <> struct is_copy_constructible : true_type { }; template <> struct is_move_constructible : true_type { }; } // namespace std