Skip to content

Commit 17c3f5b

Browse files
committed
[REFACTOR][TARGET] Cleanup target config
This PR cleans up the target config handling infrastructure. We isolated the schema checking into a common conifg_schema and reorganizes parses into canonicalizer.
1 parent 2030db3 commit 17c3f5b

File tree

27 files changed

+1448
-1296
lines changed

27 files changed

+1448
-1296
lines changed

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,8 @@ tvm_file_glob(GLOB_RECURSE COMPILER_SRCS
306306
tvm_file_glob(GLOB CODEGEN_SRCS
307307
src/target/*.cc
308308
src/target/source/*.cc
309-
src/target/parsers/*.cc
309+
src/target/canonicalizer/*.cc
310+
src/target/canonicalizer/llvm/*.cc
310311
)
311312

312313
list(APPEND COMPILER_SRCS ${CODEGEN_SRCS})

include/tvm/ir/config_schema.h

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
/*!
21+
* \file tvm/ir/config_schema.h
22+
* \brief Minimal schema for dynamic config canonicalization and validation.
23+
*
24+
* This utility is intended for dynamic map-like configs (e.g. Target options),
25+
* where we still want type checking, optional defaulting, and canonicalization.
26+
*/
27+
#ifndef TVM_IR_CONFIG_SCHEMA_H_
28+
#define TVM_IR_CONFIG_SCHEMA_H_
29+
30+
#include <tvm/ffi/container/map.h>
31+
#include <tvm/ffi/function.h>
32+
#include <tvm/ffi/reflection/registry.h>
33+
34+
#include <sstream>
35+
#include <string>
36+
#include <type_traits>
37+
#include <unordered_map>
38+
#include <utility>
39+
#include <vector>
40+
41+
namespace tvm {
42+
namespace ir {
43+
44+
/*!
45+
* \brief Dynamic config schema for map-like options.
46+
*
47+
* The schema supports:
48+
* - Option declaration (`def_option<T>`)
49+
* - Optional canonicalizer (`set_canonicalizer`)
50+
* - Resolution (`Resolve`) that performs validation/defaulting, unknown-key policy,
51+
* and canonicalization (last).
52+
*/
53+
class ConfigSchema {
54+
public:
55+
using ConfigMap = ffi::Map<ffi::String, ffi::Any>;
56+
using Canonicalizer = ffi::TypedFunction<ConfigMap(ConfigMap)>;
57+
58+
/*! \brief Schema entry for one declared option. */
59+
struct OptionEntry {
60+
/*! \brief Option key. */
61+
ffi::String key;
62+
/*! \brief Type string for this option. */
63+
ffi::String type_str;
64+
/*! \brief Per-option validator/coercer (Any -> Any). */
65+
ffi::TypedFunction<ffi::Any(ffi::Any)> validator;
66+
/*! \brief Whether this option has a default value. */
67+
bool has_default = false;
68+
/*! \brief Default value (valid when has_default is true). */
69+
ffi::Any default_value;
70+
};
71+
72+
/*!
73+
* \brief Declare a typed option.
74+
*
75+
* Validation/coercion is implicitly generated from `T`.
76+
* Additional optional traits may be supplied (e.g. `refl::DefaultValue`, `const char*` doc).
77+
*
78+
* \tparam T The canonical value type of this option.
79+
* \tparam Traits Optional metadata/traits.
80+
* \param key Option key.
81+
* \param traits Optional traits.
82+
* \return Reference to `*this` for chaining.
83+
*/
84+
template <typename T, typename... Traits>
85+
ConfigSchema& def_option(const ffi::String& key, Traits&&... traits) {
86+
std::string skey(key);
87+
if (key_to_index_.count(skey)) {
88+
TVM_FFI_THROW(ValueError) << "Duplicate config option key: '" << key << "'";
89+
}
90+
key_to_index_[skey] = options_.size();
91+
options_.push_back(MakeEntry<T>(key, std::forward<Traits>(traits)...));
92+
return *this;
93+
}
94+
95+
/*! \brief Set whole-object canonicalizer. */
96+
void set_canonicalizer(Canonicalizer f) { canonicalizer_ = std::move(f); }
97+
98+
/*! \brief Set whether unknown keys trigger an error. */
99+
void set_error_on_unknown(bool value) { error_on_unknown_ = value; }
100+
101+
/*!
102+
* \brief Default/validate, then canonicalize a config object.
103+
*
104+
* Resolve flow:
105+
* 1) Validate/coerce declared options in declaration order.
106+
* 2) Materialize defaults and enforce required options.
107+
* 3) Apply unknown-key policy.
108+
* 4) Run canonicalizer as final step.
109+
*
110+
* \param config Input config object.
111+
* \return Canonical validated config object.
112+
* \throws ValueError/TypeError with option context.
113+
*/
114+
ConfigMap Resolve(ConfigMap config) const {
115+
ConfigMap result;
116+
117+
// Step 1: validate/coerce and materialize options in declaration order
118+
for (const auto& e : options_) {
119+
auto it = config.find(e.key);
120+
if (it != config.end()) {
121+
result.Set(e.key, e.validator((*it).second));
122+
} else if (e.has_default) {
123+
result.Set(e.key, e.default_value);
124+
}
125+
// else: missing non-required option, stays absent
126+
}
127+
128+
// Step 2: unknown-key policy
129+
if (error_on_unknown_) {
130+
for (const auto& kv : config) {
131+
if (!key_to_index_.count(std::string(kv.first))) {
132+
std::ostringstream os;
133+
os << "Unknown config option '" << kv.first << "'. Known options: ";
134+
bool first = true;
135+
for (const auto& e : options_) {
136+
if (!first) os << ", ";
137+
os << "'" << e.key << "'";
138+
first = false;
139+
}
140+
TVM_FFI_THROW(ValueError) << os.str();
141+
}
142+
}
143+
}
144+
145+
// Step 3: whole-object canonicalization (last)
146+
if (canonicalizer_ != nullptr) {
147+
result = canonicalizer_(result);
148+
}
149+
150+
return result;
151+
}
152+
153+
/*!
154+
* \brief List declared options in declaration order.
155+
* \return Const reference to the option entries vector.
156+
*/
157+
const std::vector<OptionEntry>& ListOptions() const { return options_; }
158+
159+
/*! \brief Check if an option with the given key exists. */
160+
bool HasOption(const ffi::String& key) const { return key_to_index_.count(std::string(key)) > 0; }
161+
162+
private:
163+
template <typename T>
164+
static ffi::TypedFunction<ffi::Any(ffi::Any)> MakeValidator(const ffi::String& key) {
165+
return ffi::TypedFunction<ffi::Any(ffi::Any)>([key](ffi::Any val) -> ffi::Any {
166+
auto opt = val.try_cast<T>();
167+
if (!opt.has_value()) {
168+
TVM_FFI_THROW(TypeError) << "Option '" << key << "': expected type '"
169+
<< ffi::TypeTraits<T>::TypeStr() << "' but got '"
170+
<< val.GetTypeKey() << "'";
171+
}
172+
return ffi::Any(opt.value());
173+
});
174+
}
175+
176+
template <typename Trait>
177+
static void ApplyTrait(ffi::reflection::FieldInfoBuilder* info, Trait&& trait) {
178+
using T = std::decay_t<Trait>;
179+
if constexpr (std::is_base_of_v<ffi::reflection::InfoTrait, T>) {
180+
trait.Apply(info);
181+
} else if constexpr (std::is_same_v<T, const char*> || std::is_same_v<T, char*>) {
182+
const char* doc = trait;
183+
if (doc != nullptr && doc[0] != '\0') {
184+
info->doc = TVMFFIByteArray{doc, std::char_traits<char>::length(doc)};
185+
}
186+
}
187+
}
188+
189+
template <typename T, typename... Traits>
190+
OptionEntry MakeEntry(const ffi::String& key, Traits&&... traits) {
191+
OptionEntry e;
192+
e.key = key;
193+
e.type_str = ffi::String(ffi::TypeTraits<T>::TypeStr());
194+
e.validator = MakeValidator<T>(key);
195+
// Apply traits through a temporary FieldInfoBuilder so existing
196+
// reflection traits (notably refl::DefaultValue) are reused unchanged.
197+
ffi::reflection::FieldInfoBuilder info{};
198+
info.flags = 0;
199+
info.default_value = ffi::AnyView(nullptr).CopyToTVMFFIAny();
200+
info.doc = TVMFFIByteArray{nullptr, 0};
201+
(ApplyTrait(&info, std::forward<Traits>(traits)), ...);
202+
if (info.flags & kTVMFFIFieldFlagBitMaskHasDefault) {
203+
e.has_default = true;
204+
e.default_value = ffi::AnyView::CopyFromTVMFFIAny(info.default_value);
205+
// Release the extra ref created by CopyToTVMFFIAny in Apply
206+
if (info.default_value.type_index >= TVMFFITypeIndex::kTVMFFIStaticObjectBegin) {
207+
ffi::details::ObjectUnsafe::DecRefObjectHandle(info.default_value.v_obj);
208+
}
209+
}
210+
return e;
211+
}
212+
213+
/*! \brief Declared options in declaration order. */
214+
std::vector<OptionEntry> options_;
215+
/*! \brief Map from key string to index in options_. */
216+
std::unordered_map<std::string, size_t> key_to_index_;
217+
/*! \brief Optional whole-config canonicalizer. */
218+
Canonicalizer canonicalizer_{nullptr};
219+
/*! \brief Whether unknown keys trigger an error. */
220+
bool error_on_unknown_ = true;
221+
};
222+
223+
} // namespace ir
224+
} // namespace tvm
225+
226+
#endif // TVM_IR_CONFIG_SCHEMA_H_

include/tvm/target/target.h

Lines changed: 3 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,12 @@
2727
#include <tvm/ffi/reflection/registry.h>
2828
#include <tvm/ir/expr.h>
2929
#include <tvm/ir/function.h>
30-
#include <tvm/node/attr_registry_map.h>
3130
#include <tvm/node/node.h>
3231
#include <tvm/runtime/device_api.h>
3332
#include <tvm/support/with.h>
3433
#include <tvm/target/target_kind.h>
3534

3635
#include <string>
37-
#include <unordered_set>
38-
#include <vector>
3936

4037
namespace tvm {
4138

@@ -56,18 +53,16 @@ class TargetNode : public Object {
5653
ffi::String tag;
5754
/*! \brief Keys for this target */
5855
ffi::Array<ffi::String> keys;
59-
/*! \brief Collection of attributes */
56+
/*! \brief Collection of attributes (includes feature.* keys set by canonicalizer) */
6057
ffi::Map<ffi::String, Any> attrs;
61-
/*! \brief Target features */
62-
ffi::Map<ffi::String, Any> features;
6358

6459
/*!
6560
* \brief The JSON string representation of the target
6661
* \return JSON string of the target configuration (e.g. {"kind": "llvm", "mcpu": "cortex-a53"})
6762
*/
6863
TVM_DLL const std::string& str() const;
6964
/*! \return Export target to JSON-like configuration */
70-
TVM_DLL ffi::Map<ffi::String, ffi::Any> Export() const;
65+
TVM_DLL ffi::Map<ffi::String, ffi::Any> ToConfig() const;
7166
/*! \return The ffi::Optional<Target> typed target host of the TargetNode */
7267
TVM_DLL ffi::Optional<Target> GetHost() const;
7368
/*! \return The device type for this target */
@@ -83,23 +78,13 @@ class TargetNode : public Object {
8378
*/
8479
TVM_DLL bool HasKey(const std::string& query_key) const;
8580

86-
/*!
87-
* \brief Returns a human readable representation of \p Target which includes all fields,
88-
* especially the host. Useful for diagnostic messages and debugging.
89-
*
90-
* TODO(mbs): The ReprPrinter version should perhaps switch to this form, however currently
91-
* code depends on str() and << being the same.
92-
*/
93-
ffi::String ToDebugString() const;
94-
9581
static void RegisterReflection() {
9682
namespace refl = tvm::ffi::reflection;
9783
refl::ObjectDef<TargetNode>()
9884
.def_ro("kind", &TargetNode::kind)
9985
.def_ro("tag", &TargetNode::tag)
10086
.def_ro("keys", &TargetNode::keys)
10187
.def_ro("attrs", &TargetNode::attrs)
102-
.def_ro("features", &TargetNode::features)
10388
.def_ro("host", &TargetNode::host);
10489
}
10590

@@ -133,47 +118,6 @@ class TargetNode : public Object {
133118
return GetAttr<TObjectRef>(attr_key, ffi::Optional<TObjectRef>(default_value));
134119
}
135120

136-
/*!
137-
* \brief Get a Target feature
138-
*
139-
* \param feature_key The feature key.
140-
* \param default_value The default value if the key does not exist, defaults to nullptr.
141-
*
142-
* \return The result
143-
*
144-
* \tparam TOBjectRef the expected object type.
145-
* \throw Error if the key exists but the value does not match TObjectRef
146-
*
147-
* \code
148-
*
149-
* void GetTargetFeature(const Target& target) {
150-
* Bool has_feature = target->GetFeature<Bool>("has_feature", false).value();
151-
* }
152-
*
153-
* \endcode
154-
*/
155-
template <typename TObjectRef>
156-
ffi::Optional<TObjectRef> GetFeature(
157-
const std::string& feature_key,
158-
ffi::Optional<TObjectRef> default_value = std::nullopt) const {
159-
if (auto feature = features.Get(feature_key)) {
160-
return Downcast<TObjectRef>(feature.value());
161-
} else {
162-
return default_value;
163-
}
164-
}
165-
// variant that uses TObjectRef to enable implicit conversion to default value.
166-
template <typename TObjectRef>
167-
ffi::Optional<TObjectRef> GetFeature(const std::string& attr_key,
168-
TObjectRef default_value) const {
169-
return GetFeature<TObjectRef>(attr_key, ffi::Optional<TObjectRef>(default_value));
170-
}
171-
172-
/*! \brief Get the keys for this target as a vector of string */
173-
TVM_DLL std::vector<std::string> GetKeys() const;
174-
/*! \brief Get the keys for this target as an unordered_set of string */
175-
TVM_DLL std::unordered_set<std::string> GetLibs() const;
176-
177121
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
178122
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("target.Target", TargetNode, Object);
179123

@@ -218,12 +162,7 @@ class Target : public ObjectRef {
218162
*/
219163
TVM_DLL explicit Target(Target target, Target host);
220164
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Target, ObjectRef, TargetNode);
221-
/*!
222-
* \brief Create a new Target object with given target (w.o host) and target host.
223-
* \param target The current Target typed object target, with or without host field.
224-
* \param host The given Target typed object target host
225-
* \return The new Target object with the given target and host field of given host.
226-
*/
165+
227166
static Target WithHost(const Target& target, const Target& host);
228167

229168
/*! \return The target with the host stripped out */

0 commit comments

Comments
 (0)