diff --git a/CMakeLists.txt b/CMakeLists.txt index 913baca1b568..ecff20a9a773 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -306,7 +306,8 @@ tvm_file_glob(GLOB_RECURSE COMPILER_SRCS tvm_file_glob(GLOB CODEGEN_SRCS src/target/*.cc src/target/source/*.cc - src/target/parsers/*.cc + src/target/canonicalizer/*.cc + src/target/canonicalizer/llvm/*.cc ) list(APPEND COMPILER_SRCS ${CODEGEN_SRCS}) diff --git a/include/tvm/ir/config_schema.h b/include/tvm/ir/config_schema.h new file mode 100644 index 000000000000..9720206b1d00 --- /dev/null +++ b/include/tvm/ir/config_schema.h @@ -0,0 +1,235 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file tvm/ir/config_schema.h + * \brief Minimal schema for dynamic config canonicalization and validation. + * + * This utility is intended for dynamic map-like configs (e.g. Target options), + * where we still want type checking, optional defaulting, and canonicalization. + */ +#ifndef TVM_IR_CONFIG_SCHEMA_H_ +#define TVM_IR_CONFIG_SCHEMA_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace tvm { +namespace ir { + +/*! + * \brief Dynamic config schema for map-like options. + * + * The schema supports: + * - Option declaration (`def_option`) + * - Optional canonicalizer (`set_canonicalizer`) + * - Resolution (`Resolve`) that performs validation/defaulting, unknown-key policy, + * and canonicalization (last). + */ +class ConfigSchema { + public: + using ConfigMap = ffi::Map; + using Canonicalizer = ffi::TypedFunction; + + /*! \brief Schema entry for one declared option. */ + struct OptionEntry { + /*! \brief Option key. */ + ffi::String key; + /*! \brief Type string for this option. */ + ffi::String type_str; + /*! \brief Per-option validator/coercer (Any -> Any). */ + ffi::TypedFunction validator; + /*! \brief Whether this option has a default value. */ + bool has_default = false; + /*! \brief Default value (valid when has_default is true). */ + ffi::Any default_value; + }; + + /*! + * \brief Declare a typed option. + * + * Validation/coercion is implicitly generated from `T`. + * Additional optional traits may be supplied (e.g. `refl::DefaultValue`, `const char*` doc). + * + * \tparam T The canonical value type of this option. + * \tparam Traits Optional metadata/traits. + * \param key Option key. + * \param traits Optional traits. + * \return Reference to `*this` for chaining. + */ + template + ConfigSchema& def_option(const ffi::String& key, Traits&&... traits) { + std::string skey(key); + if (key_to_index_.count(skey)) { + TVM_FFI_THROW(ValueError) << "Duplicate config option key: '" << key << "'"; + } + key_to_index_[skey] = options_.size(); + options_.push_back(MakeEntry(key, std::forward(traits)...)); + return *this; + } + + /*! \brief Set whole-object canonicalizer. */ + void set_canonicalizer(Canonicalizer f) { canonicalizer_ = std::move(f); } + + /*! \brief Trait to set a custom validator for a config option. */ + struct AttrValidator { + ffi::TypedFunction func; + explicit AttrValidator(ffi::TypedFunction f) : func(std::move(f)) {} + }; + + /*! \brief Set whether unknown keys trigger an error. */ + void set_error_on_unknown(bool value) { error_on_unknown_ = value; } + + /*! + * \brief Default/validate, then canonicalize a config object. + * + * Resolve flow: + * 1) Validate/coerce declared options in declaration order. + * 2) Materialize defaults and enforce required options. + * 3) Apply unknown-key policy. + * 4) Run canonicalizer as final step. + * + * \param config Input config object. + * \return Canonical validated config object. + * \throws ValueError/TypeError with option context. + */ + ConfigMap Resolve(ConfigMap config) const { + ConfigMap result; + + // Step 1: validate/coerce and materialize options in declaration order + for (const auto& e : options_) { + auto it = config.find(e.key); + if (it != config.end()) { + result.Set(e.key, e.validator((*it).second)); + } else if (e.has_default) { + result.Set(e.key, e.default_value); + } + // else: missing non-required option, stays absent + } + + // Step 2: unknown-key policy + if (error_on_unknown_) { + for (const auto& kv : config) { + if (!key_to_index_.count(std::string(kv.first))) { + std::ostringstream os; + os << "Unknown config option '" << kv.first << "'. Known options: "; + bool first = true; + for (const auto& e : options_) { + if (!first) os << ", "; + os << "'" << e.key << "'"; + first = false; + } + TVM_FFI_THROW(ValueError) << os.str(); + } + } + } + + // Step 3: whole-object canonicalization (last) + if (canonicalizer_ != nullptr) { + result = canonicalizer_(result); + } + + return result; + } + + /*! + * \brief List declared options in declaration order. + * \return Const reference to the option entries vector. + */ + const std::vector& ListOptions() const { return options_; } + + /*! \brief Check if an option with the given key exists. */ + bool HasOption(const ffi::String& key) const { return key_to_index_.count(std::string(key)) > 0; } + + private: + template + static ffi::TypedFunction MakeValidator(const ffi::String& key) { + return ffi::TypedFunction([key](ffi::Any val) -> ffi::Any { + auto opt = val.try_cast(); + if (!opt.has_value()) { + TVM_FFI_THROW(TypeError) << "Option '" << key << "': expected type '" + << ffi::TypeTraits::TypeStr() << "' but got '" + << val.GetTypeKey() << "'"; + } + return ffi::Any(opt.value()); + }); + } + + template + static void ApplyTrait(OptionEntry* entry, ffi::reflection::FieldInfoBuilder* info, + Trait&& trait) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + entry->validator = std::move(trait.func); + } else if constexpr (std::is_base_of_v) { + trait.Apply(info); + } else if constexpr (std::is_same_v || std::is_same_v) { + const char* doc = trait; + if (doc != nullptr && doc[0] != '\0') { + info->doc = TVMFFIByteArray{doc, std::char_traits::length(doc)}; + } + } + } + + template + OptionEntry MakeEntry(const ffi::String& key, Traits&&... traits) { + OptionEntry e; + e.key = key; + e.type_str = ffi::String(ffi::TypeTraits::TypeStr()); + e.validator = MakeValidator(key); + // Apply traits through a temporary FieldInfoBuilder so existing + // reflection traits (notably refl::DefaultValue) are reused unchanged. + ffi::reflection::FieldInfoBuilder info{}; + info.flags = 0; + info.default_value = ffi::AnyView(nullptr).CopyToTVMFFIAny(); + info.doc = TVMFFIByteArray{nullptr, 0}; + (ApplyTrait(&e, &info, std::forward(traits)), ...); + if (info.flags & kTVMFFIFieldFlagBitMaskHasDefault) { + e.has_default = true; + e.default_value = ffi::AnyView::CopyFromTVMFFIAny(info.default_value); + // Release the extra ref created by CopyToTVMFFIAny in Apply + if (info.default_value.type_index >= TVMFFITypeIndex::kTVMFFIStaticObjectBegin) { + ffi::details::ObjectUnsafe::DecRefObjectHandle(info.default_value.v_obj); + } + } + return e; + } + + /*! \brief Declared options in declaration order. */ + std::vector options_; + /*! \brief Map from key string to index in options_. */ + std::unordered_map key_to_index_; + /*! \brief Optional whole-config canonicalizer. */ + Canonicalizer canonicalizer_{nullptr}; + /*! \brief Whether unknown keys trigger an error. */ + bool error_on_unknown_ = true; +}; + +} // namespace ir +} // namespace tvm + +#endif // TVM_IR_CONFIG_SCHEMA_H_ diff --git a/include/tvm/target/target.h b/include/tvm/target/target.h index 50839dc1aba1..9a0bedd1cc4b 100644 --- a/include/tvm/target/target.h +++ b/include/tvm/target/target.h @@ -27,15 +27,12 @@ #include #include #include -#include #include #include #include #include #include -#include -#include namespace tvm { @@ -56,10 +53,8 @@ class TargetNode : public Object { ffi::String tag; /*! \brief Keys for this target */ ffi::Array keys; - /*! \brief Collection of attributes */ + /*! \brief Collection of attributes (includes feature.* keys set by canonicalizer) */ ffi::Map attrs; - /*! \brief Target features */ - ffi::Map features; /*! * \brief The JSON string representation of the target @@ -67,7 +62,7 @@ class TargetNode : public Object { */ TVM_DLL const std::string& str() const; /*! \return Export target to JSON-like configuration */ - TVM_DLL ffi::Map Export() const; + TVM_DLL ffi::Map ToConfig() const; /*! \return The ffi::Optional typed target host of the TargetNode */ TVM_DLL ffi::Optional GetHost() const; /*! \return The device type for this target */ @@ -83,15 +78,6 @@ class TargetNode : public Object { */ TVM_DLL bool HasKey(const std::string& query_key) const; - /*! - * \brief Returns a human readable representation of \p Target which includes all fields, - * especially the host. Useful for diagnostic messages and debugging. - * - * TODO(mbs): The ReprPrinter version should perhaps switch to this form, however currently - * code depends on str() and << being the same. - */ - ffi::String ToDebugString() const; - static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef() @@ -99,7 +85,6 @@ class TargetNode : public Object { .def_ro("tag", &TargetNode::tag) .def_ro("keys", &TargetNode::keys) .def_ro("attrs", &TargetNode::attrs) - .def_ro("features", &TargetNode::features) .def_ro("host", &TargetNode::host); } @@ -133,47 +118,6 @@ class TargetNode : public Object { return GetAttr(attr_key, ffi::Optional(default_value)); } - /*! - * \brief Get a Target feature - * - * \param feature_key The feature key. - * \param default_value The default value if the key does not exist, defaults to nullptr. - * - * \return The result - * - * \tparam TOBjectRef the expected object type. - * \throw Error if the key exists but the value does not match TObjectRef - * - * \code - * - * void GetTargetFeature(const Target& target) { - * Bool has_feature = target->GetFeature("has_feature", false).value(); - * } - * - * \endcode - */ - template - ffi::Optional GetFeature( - const std::string& feature_key, - ffi::Optional default_value = std::nullopt) const { - if (auto feature = features.Get(feature_key)) { - return Downcast(feature.value()); - } else { - return default_value; - } - } - // variant that uses TObjectRef to enable implicit conversion to default value. - template - ffi::Optional GetFeature(const std::string& attr_key, - TObjectRef default_value) const { - return GetFeature(attr_key, ffi::Optional(default_value)); - } - - /*! \brief Get the keys for this target as a vector of string */ - TVM_DLL std::vector GetKeys() const; - /*! \brief Get the keys for this target as an unordered_set of string */ - TVM_DLL std::unordered_set GetLibs() const; - static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode; TVM_FFI_DECLARE_OBJECT_INFO_FINAL("target.Target", TargetNode, Object); @@ -218,12 +162,7 @@ class Target : public ObjectRef { */ TVM_DLL explicit Target(Target target, Target host); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Target, ObjectRef, TargetNode); - /*! - * \brief Create a new Target object with given target (w.o host) and target host. - * \param target The current Target typed object target, with or without host field. - * \param host The given Target typed object target host - * \return The new Target object with the given target and host field of given host. - */ + static Target WithHost(const Target& target, const Target& host); /*! \return The target with the host stripped out */ diff --git a/include/tvm/target/target_kind.h b/include/tvm/target/target_kind.h index f2de1d6795d3..5dc261d1fd6e 100644 --- a/include/tvm/target/target_kind.h +++ b/include/tvm/target/target_kind.h @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -39,24 +40,13 @@ namespace tvm { class Target; /*! - * \brief Map containing parsed features of a specific Target - */ -using TargetFeatures = ffi::Map; - -/*! - * \brief TargetParser to apply on instantiation of a given TargetKind - * - * \param target_json Target in JSON format to be transformed during parsing. + * \brief Target canonicalizer applied on instantiation of a given TargetKind. * + * \param target_json Target in JSON format to be transformed during canonicalization. * \return The transformed Target JSON object. */ -using TargetJSON = ffi::Map; -using FTVMTargetParser = ffi::TypedFunction; - -namespace detail { -template -struct ValueTypeInfoMaker; -} +using FTargetCanonicalizer = + ffi::TypedFunction(ffi::Map)>; class TargetInternal; @@ -72,10 +62,8 @@ class TargetKindNode : public Object { int default_device_type; /*! \brief Default keys of the target */ ffi::Array default_keys; - /*! \brief Function used to preprocess on target creation */ - ffi::Function preprocessor; - /*! \brief Function used to parse a JSON target during creation */ - FTVMTargetParser target_parser; + /*! \brief Function used to canonicalize a JSON target during creation */ + FTargetCanonicalizer target_canonicalizer; static void RegisterReflection() { namespace refl = tvm::ffi::reflection; @@ -95,22 +83,11 @@ class TargetKindNode : public Object { uint32_t AttrRegistryIndex() const { return index_; } /*! \brief Return the name stored in attr registry */ ffi::String AttrRegistryName() const { return name; } - /*! \brief Stores the required type_key and type_index of a specific attr of a target */ - struct ValueTypeInfo { - ffi::String type_key; - int32_t type_index; - std::unique_ptr key; - std::unique_ptr val; - }; - /*! \brief A hash table that stores the type information of each attr of the target key */ - std::unordered_map key2vtype_; - /*! \brief A hash table that stores the default value of each attr of the target key */ - std::unordered_map key2default_; + /*! \brief ConfigSchema for validating and resolving target attributes */ + ir::ConfigSchema schema_; /*! \brief Index used for internal lookup of attribute registry */ uint32_t index_; - template - friend struct detail::ValueTypeInfoMaker; template friend class AttrRegistry; template @@ -203,32 +180,19 @@ class TargetKindRegEntry { */ inline TargetKindRegEntry& set_default_keys(std::vector keys); /*! - * \brief Set the pre-processing function applied upon target creation - * \tparam FLambda Type of the function - * \param f The pre-processing function + * \brief Set the canonicalizer function applied upon target creation. + * \param canonicalizer The target canonicalizer function. */ - template - inline TargetKindRegEntry& set_attrs_preprocessor(FLambda f); - /*! - * \brief Set the parsing function applied upon target creation - * \param parser The Target parsing function - */ - inline TargetKindRegEntry& set_target_parser(FTVMTargetParser parser); + inline TargetKindRegEntry& set_target_canonicalizer(FTargetCanonicalizer canonicalizer); /*! * \brief Register a valid configuration option and its ValueType for validation * \param key The configuration key + * \param traits Optional traits (e.g. refl::DefaultValue, doc string, or raw default value) * \tparam ValueType The value type to be registered + * \tparam Traits Optional trait types */ - template - inline TargetKindRegEntry& add_attr_option(const ffi::String& key); - /*! - * \brief Register a valid configuration option and its ValueType for validation - * \param key The configuration key - * \param default_value The default value of the key - * \tparam ValueType The value type to be registered - */ - template - inline TargetKindRegEntry& add_attr_option(const ffi::String& key, ffi::Any default_value); + template + inline TargetKindRegEntry& add_attr_option(const ffi::String& key, Traits&&... traits); /*! \brief Set name of the TargetKind to be the same as registry if it is empty */ inline TargetKindRegEntry& set_name(); /*! @@ -269,85 +233,6 @@ class TargetKindRegEntry { friend class TargetKind; }; -namespace detail { -template class Container> -struct is_specialized : std::false_type { - using type = std::false_type; -}; - -template