chore: import upstream snapshot with attribution
Lint / lint (push) Waiting to run
CI / MacOS (push) Waiting to run
CI / Windows (push) Waiting to run

This commit is contained in:
wehub-resource-sync
2026-07-13 13:36:25 +08:00
commit 26446540fa
3151 changed files with 974126 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
unittest
*.d
*_test
+23
View File
@@ -0,0 +1,23 @@
<!--- 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. -->
# tests/cpp
This folder contains some unit tests for C++ utilities in the codebase.
In principle we aim to do most compiler related tests in the python to
bring more development velocity, and only use this folder for low-level unit-tests.
All tests should finish fast and not dependent on a presence of an accelerator device.
+117
View File
@@ -0,0 +1,117 @@
/*
* 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.
*/
#include <gtest/gtest.h>
#include <tvm/arith/analyzer.h>
#include <tvm/ffi/extra/structural_equal.h>
#include <tvm/runtime/logging.h>
#include <tvm/te/operation.h>
TEST(Simplify, MinMax) {
tvm::arith::Analyzer ana;
auto x = tvm::te::var("x");
auto e1 = (tvm::max(x, 1) - tvm::max(x, 1));
auto e1s = ana->canonical_simplify(e1);
TVM_FFI_ICHECK(tvm::tirx::is_zero(e1s));
auto e2 = (x * tvm::min(x, 1)) - (x * tvm::min(x, 1));
auto e2s = ana->canonical_simplify(e2);
TVM_FFI_ICHECK(tvm::tirx::is_zero(e2s));
}
TEST(Simplify, Mul) {
tvm::arith::Analyzer ana;
auto x = tvm::te::var("x");
auto e = (x * x) - (x * x);
auto es = ana->canonical_simplify(e);
TVM_FFI_ICHECK(tvm::tirx::is_zero(es));
}
TEST(Simplify, Mod) {
tvm::arith::Analyzer ana;
auto x = tvm::IntImm::Int32(10);
auto y = tvm::IntImm::Int32(12);
// Mod::make is used instead of % to avoid constant folding during
// calling operator%(x,y). Mod::make doesn't try constant folding,
// and therefore, the constant folding will be attempted in CanonicalSimplify
auto mod = ana->canonical_simplify(tvm::tirx::Mod(x, y));
auto es = ana->canonical_simplify(mod - x);
TVM_FFI_ICHECK(tvm::tirx::is_zero(es));
}
TEST(AnalyzerObjectRef, CopySharesMutableState) {
tvm::arith::Analyzer analyzer;
tvm::arith::Analyzer copy = analyzer;
auto x = tvm::te::var("x");
copy->Bind(x, tvm::Range::FromMinExtent(0, 8));
TVM_FFI_ICHECK(analyzer->CanProve(x < 8));
}
TEST(AnalyzerObjectRef, ConstHandleRefCanMutateAnalyzerState) {
tvm::arith::Analyzer analyzer;
const tvm::arith::Analyzer& analyzer_ref = analyzer;
auto x = tvm::te::var("x");
analyzer_ref->Bind(x, tvm::Range::FromMinExtent(0, 8));
TVM_FFI_ICHECK(analyzer->CanProve(x < 8));
}
TEST(AnalyzerObjectRef, CloneIsIndependent) {
tvm::arith::Analyzer analyzer;
auto x = tvm::te::var("x");
auto y = tvm::te::var("y");
analyzer->Bind(x, tvm::Range::FromMinExtent(0, 8));
analyzer->modular_set.Update(x, tvm::arith::ModularSet(4, 0));
tvm::arith::Analyzer clone = analyzer->Clone();
TVM_FFI_ICHECK(clone->CanProve(x < 8));
TVM_FFI_ICHECK(clone->modular_set(x)->coeff == 4);
clone->Bind(y, tvm::Range::FromMinExtent(0, 4));
clone->modular_set.Update(x, tvm::arith::ModularSet(8, 0), true);
TVM_FFI_ICHECK(clone->CanProve(y < 4));
TVM_FFI_ICHECK(!analyzer->CanProve(y < 4));
TVM_FFI_ICHECK(analyzer->CanProve(x < 8));
TVM_FFI_ICHECK(analyzer->modular_set(x)->coeff == 4);
TVM_FFI_ICHECK(clone->modular_set(x)->coeff == 8);
}
TEST(ConstantFold, Broadcast) {
tvm::ffi::StructuralEqual checker;
auto i32x4 = tvm::tirx::Broadcast(tvm::IntImm::Int32(10), 4);
auto i64x4 = tvm::cast(i32x4.ty().WithBits(64), i32x4);
auto i64x4_expected = tvm::tirx::Broadcast(tvm::IntImm::Int64(10), 4);
ASSERT_TRUE(checker(i64x4, i64x4_expected));
}
TEST(ConstantFold, Ramp) {
tvm::ffi::StructuralEqual checker;
auto i32x4 = tvm::tirx::Ramp(tvm::IntImm::Int32(10), tvm::IntImm::Int32(1), 4);
auto i64x4 = tvm::cast(i32x4.ty().WithBits(64), i32x4);
auto i64x4_expected = tvm::tirx::Ramp(tvm::IntImm::Int64(10), tvm::IntImm::Int64(1), 4);
ASSERT_TRUE(checker(i64x4, i64x4_expected));
auto f32x4 = tvm::cast(tvm::PrimType::Float(32, 4), i32x4);
auto f32x4_expected = tvm::tirx::Cast(tvm::PrimType::Float(32, 4), i32x4);
ASSERT_TRUE(checker(f32x4, f32x4_expected));
}
+323
View File
@@ -0,0 +1,323 @@
/*
* 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.
*/
#include <gtest/gtest.h>
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/container/map.h>
#include <tvm/ffi/string.h>
#include <tvm/ir/config_schema.h>
using namespace tvm;
using namespace tvm::ir;
namespace refl = tvm::ffi::reflection;
// Basic int/bool/string option resolution
TEST(ConfigSchema, BasicTypes) {
ConfigSchema schema;
schema.def_option<int64_t>("num_threads");
schema.def_option<bool>("verbose");
schema.def_option<ffi::String>("name");
ffi::Map<ffi::String, ffi::Any> config = {
{"num_threads", int64_t(8)},
{"verbose", true},
{"name", ffi::String("test")},
};
auto resolved = schema.Resolve(config);
ASSERT_EQ(resolved.at("num_threads").cast<int64_t>(), 8);
ASSERT_EQ(resolved.at("verbose").cast<bool>(), true);
ASSERT_EQ(resolved.at("name").cast<ffi::String>(), "test");
}
// Type coercion: correct type passes
TEST(ConfigSchema, TypeCoercionPasses) {
ConfigSchema schema;
schema.def_option<int64_t>("count");
ffi::Map<ffi::String, ffi::Any> config = {{"count", int64_t(42)}};
auto resolved = schema.Resolve(config);
ASSERT_EQ(resolved.at("count").cast<int64_t>(), 42);
}
// Type mismatch rejection (TypeError)
TEST(ConfigSchema, TypeMismatchRejection) {
ConfigSchema schema;
schema.def_option<int64_t>("count");
// Pass a string where int64_t is expected
ffi::Map<ffi::String, ffi::Any> config = {{"count", ffi::String("not_a_number")}};
try {
schema.Resolve(config);
FAIL() << "Expected TypeError";
} catch (const ffi::Error& e) {
EXPECT_EQ(e.kind(), "TypeError");
EXPECT_NE(std::string(e.message()).find("count"), std::string::npos);
}
}
TEST(ConfigSchema, TypeMismatchBoolForInt) {
ConfigSchema schema;
schema.def_option<bool>("flag");
// Pass a string where bool is expected
ffi::Map<ffi::String, ffi::Any> config = {{"flag", ffi::String("true")}};
try {
schema.Resolve(config);
FAIL() << "Expected TypeError";
} catch (const ffi::Error& e) {
EXPECT_EQ(e.kind(), "TypeError");
EXPECT_NE(std::string(e.message()).find("flag"), std::string::npos);
}
}
// Default value materialization via refl::DefaultValue
TEST(ConfigSchema, DefaultValueMaterialization) {
ConfigSchema schema;
schema.def_option<int64_t>("max_threads", refl::DefaultValue(int64_t(256)));
schema.def_option<bool>("debug", refl::DefaultValue(false));
// Empty config -> defaults are applied
ffi::Map<ffi::String, ffi::Any> config = {};
auto resolved = schema.Resolve(config);
ASSERT_EQ(resolved.at("max_threads").cast<int64_t>(), 256);
ASSERT_EQ(resolved.at("debug").cast<bool>(), false);
}
// Default value materialization via refl::DefaultValue
TEST(ConfigSchema, DefaultValueViaTrait) {
ConfigSchema schema;
schema.def_option<int64_t>("max_threads", refl::DefaultValue(int64_t(128)));
ffi::Map<ffi::String, ffi::Any> config = {};
auto resolved = schema.Resolve(config);
ASSERT_EQ(resolved.at("max_threads").cast<int64_t>(), 128);
}
// Explicit value overrides default
TEST(ConfigSchema, ExplicitOverridesDefault) {
ConfigSchema schema;
schema.def_option<int64_t>("max_threads", refl::DefaultValue(int64_t(256)));
ffi::Map<ffi::String, ffi::Any> config = {{"max_threads", int64_t(512)}};
auto resolved = schema.Resolve(config);
ASSERT_EQ(resolved.at("max_threads").cast<int64_t>(), 512);
}
// Missing non-required option stays absent
TEST(ConfigSchema, MissingNonRequired) {
ConfigSchema schema;
schema.def_option<ffi::String>("optional_name"); // no default
ffi::Map<ffi::String, ffi::Any> config = {};
auto resolved = schema.Resolve(config);
ASSERT_EQ(resolved.find("optional_name"), resolved.end());
}
// Unknown key rejection (ValueError)
TEST(ConfigSchema, UnknownKeyRejection) {
ConfigSchema schema;
schema.def_option<int64_t>("known_key");
ffi::Map<ffi::String, ffi::Any> config = {
{"known_key", int64_t(1)},
{"unknown_key", ffi::String("surprise")},
};
try {
schema.Resolve(config);
FAIL() << "Expected ValueError";
} catch (const ffi::Error& e) {
EXPECT_EQ(e.kind(), "ValueError");
EXPECT_NE(std::string(e.message()).find("unknown_key"), std::string::npos);
}
}
// Unknown key allowed when error_on_unknown is false
TEST(ConfigSchema, UnknownKeyAllowed) {
ConfigSchema schema;
schema.def_option<int64_t>("known_key");
schema.set_error_on_unknown(false);
ffi::Map<ffi::String, ffi::Any> config = {
{"known_key", int64_t(1)},
{"unknown_key", ffi::String("allowed")},
};
auto resolved = schema.Resolve(config);
// Unknown keys are silently dropped (not validated, not forwarded)
ASSERT_EQ(resolved.at("known_key").cast<int64_t>(), 1);
}
// Canonicalizer runs as final step
TEST(ConfigSchema, CanonicalizerRunsLast) {
ConfigSchema schema;
schema.def_option<ffi::String>("mcpu");
auto canonicalizer = [](ffi::Map<ffi::String, ffi::Any> config) {
ffi::String mcpu = config.at("mcpu").cast<ffi::String>();
config.Set("mcpu", ffi::String("canonical_") + mcpu);
// Canonicalizer can add new keys
config.Set("feature.is_fast", true);
return config;
};
schema.set_canonicalizer(canonicalizer);
ffi::Map<ffi::String, ffi::Any> config = {{"mcpu", ffi::String("arm")}};
auto resolved = schema.Resolve(config);
ASSERT_EQ(resolved.at("mcpu").cast<ffi::String>(), "canonical_arm");
ASSERT_EQ(resolved.at("feature.is_fast").cast<bool>(), true);
}
// Option declaration order is preserved in output
TEST(ConfigSchema, DeclarationOrderPreserved) {
ConfigSchema schema;
schema.def_option<ffi::String>("zebra");
schema.def_option<ffi::String>("alpha");
schema.def_option<ffi::String>("middle");
auto& options = schema.ListOptions();
ASSERT_EQ(options.size(), 3);
ASSERT_EQ(std::string(options[0].key), "zebra");
ASSERT_EQ(std::string(options[1].key), "alpha");
ASSERT_EQ(std::string(options[2].key), "middle");
}
// Doc string trait accepted without affecting behavior
TEST(ConfigSchema, DocStringTrait) {
ConfigSchema schema;
schema.def_option<int64_t>("count", "Number of items to process");
ffi::Map<ffi::String, ffi::Any> config = {{"count", int64_t(5)}};
auto resolved = schema.Resolve(config);
ASSERT_EQ(resolved.at("count").cast<int64_t>(), 5);
}
// Array option type
TEST(ConfigSchema, ArrayOptionType) {
ConfigSchema schema;
schema.def_option<ffi::Array<ffi::String>>("keys");
ffi::Map<ffi::String, ffi::Any> config = {
{"keys", ffi::Array<ffi::String>{"cpu", "gpu"}},
};
auto resolved = schema.Resolve(config);
auto keys = resolved.at("keys").cast<ffi::Array<ffi::String>>();
ASSERT_EQ(keys.size(), 2);
ASSERT_EQ(keys[0], "cpu");
ASSERT_EQ(keys[1], "gpu");
}
// Map option type
TEST(ConfigSchema, MapOptionType) {
ConfigSchema schema;
schema.def_option<ffi::Map<ffi::String, int64_t>>("params");
ffi::Map<ffi::String, int64_t> params = {{"a", 1}, {"b", 2}};
ffi::Map<ffi::String, ffi::Any> config = {{"params", params}};
auto resolved = schema.Resolve(config);
auto result = resolved.at("params").cast<ffi::Map<ffi::String, int64_t>>();
ASSERT_EQ(result.size(), 2);
ASSERT_EQ(result["a"], 1);
ASSERT_EQ(result["b"], 2);
}
// Duplicate option key rejected
TEST(ConfigSchema, DuplicateKeyRejected) {
ConfigSchema schema;
schema.def_option<int64_t>("key1");
try {
schema.def_option<int64_t>("key1");
FAIL() << "Expected ValueError";
} catch (const ffi::Error& e) {
EXPECT_EQ(e.kind(), "ValueError");
EXPECT_NE(std::string(e.message()).find("key1"), std::string::npos);
}
}
// ListOptions returns option entries in declaration order
TEST(ConfigSchema, ListOptionsNames) {
ConfigSchema schema;
schema.def_option<int64_t>("no_default");
schema.def_option<bool>("with_default", refl::DefaultValue(true));
auto& options = schema.ListOptions();
ASSERT_EQ(options.size(), 2);
ASSERT_EQ(std::string(options[0].key), "no_default");
ASSERT_EQ(std::string(options[1].key), "with_default");
ASSERT_EQ(std::string(options[0].type_str), "int");
ASSERT_EQ(std::string(options[1].type_str), "bool");
// Check default_value
ASSERT_FALSE(options[0].has_default);
ASSERT_TRUE(options[1].has_default);
ASSERT_EQ(options[1].default_value.cast<bool>(), true);
}
// OptionEntry stores correct type strings
TEST(ConfigSchema, OptionEntryTypeStr) {
ConfigSchema schema;
schema.def_option<int64_t>("count");
schema.def_option<bool>("flag");
auto& options = schema.ListOptions();
ASSERT_EQ(std::string(options[0].type_str), "int");
ASSERT_EQ(std::string(options[1].type_str), "bool");
}
// HasOption query
TEST(ConfigSchema, HasOption) {
ConfigSchema schema;
schema.def_option<int64_t>("exists");
ASSERT_TRUE(schema.HasOption("exists"));
ASSERT_FALSE(schema.HasOption("does_not_exist"));
}
// Empty config with no options
TEST(ConfigSchema, EmptySchemaEmptyConfig) {
ConfigSchema schema;
ffi::Map<ffi::String, ffi::Any> config = {};
auto resolved = schema.Resolve(config);
ASSERT_EQ(resolved.size(), 0);
}
// Canonicalizer with defaults
TEST(ConfigSchema, CanonicalizerWithDefaults) {
ConfigSchema schema;
schema.def_option<int64_t>("threads", refl::DefaultValue(int64_t(4)));
auto canonicalizer = [](ffi::Map<ffi::String, ffi::Any> config) {
int64_t threads = config.at("threads").cast<int64_t>();
config.Set("threads", threads * 2);
return config;
};
schema.set_canonicalizer(canonicalizer);
// Empty config: default 4 applied, then canonicalizer doubles it
ffi::Map<ffi::String, ffi::Any> config = {};
auto resolved = schema.Resolve(config);
ASSERT_EQ(resolved.at("threads").cast<int64_t>(), 8);
}
// Combined doc + default traits
TEST(ConfigSchema, DocAndDefaultTraits) {
ConfigSchema schema;
schema.def_option<int64_t>("num_cores", "CPU core count", refl::DefaultValue(int64_t(4)));
ffi::Map<ffi::String, ffi::Any> config = {};
auto resolved = schema.Resolve(config);
ASSERT_EQ(resolved.at("num_cores").cast<int64_t>(), 4);
}
+65
View File
@@ -0,0 +1,65 @@
/*
* 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.
*/
#include <gtest/gtest.h>
#include <tvm/ffi/cast.h>
#include <tvm/ffi/extra/structural_equal.h>
#include <tvm/runtime/logging.h>
#include <tvm/te/operation.h>
TEST(Expr, Basic) {
using namespace tvm;
using namespace tvm::tirx;
PrimVar x("x");
auto z = max(x + 1 + 2, 100);
ffi::ObjectRef tmp = z;
PrimExpr zz = tmp.as_or_throw<PrimExpr>();
std::ostringstream os;
os << z;
TVM_FFI_ICHECK(zz.same_as(z));
TVM_FFI_ICHECK(os.str() == "T.max(x + 1 + 2, 100)");
}
TEST(Expr, VarTypeAnnotation) {
using namespace tvm;
using namespace tvm::tirx;
PrimVar x("x", PrimType::Float(32));
PrimVar y("y", PrimType::Float(32));
tvm::ffi::StructuralEqual checker;
TVM_FFI_ICHECK(checker(x.ty(), y.ty()));
TVM_FFI_ICHECK(checker(x->ty, y->ty));
}
TEST(Expr, PrimTypeBoolLanes) {
using namespace tvm;
PrimType boolx4 = PrimType::Bool(4);
TVM_FFI_ICHECK(boolx4.IsFixedLengthVector());
TVM_FFI_ICHECK(boolx4.MatchesCode(DLDataTypeCode::kDLBool));
TVM_FFI_ICHECK_EQ(boolx4.lanes(), 4);
TVM_FFI_ICHECK(boolx4.MatchesElementType(DLDataTypeCode::kDLBool, 8));
}
TEST(ExprNodeRef, Basic) {
using namespace tvm;
using namespace tvm::tirx;
PrimVar x("x");
PrimExpr z = max(x + 1 + 2, 100);
const tirx::MaxNode* op = z.as<tirx::MaxNode>();
TVM_FFI_ICHECK(ffi::GetRef<ffi::ObjectRef>(op).same_as(z));
}
+379
View File
@@ -0,0 +1,379 @@
/*
* 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.
*/
#include <gtest/gtest.h>
#include <tvm/ir/module.h>
#include <tvm/ir/node_functor.h>
#include <tvm/runtime/logging.h>
#include <tvm/tirx/analysis.h>
#include <tvm/tirx/builtin.h>
#include <tvm/tirx/expr.h>
#include <tvm/tirx/expr_functor.h>
#include <tvm/tirx/function.h>
#include <tvm/tirx/op.h>
#include <tvm/tirx/stmt_functor.h>
TEST(IRF, Basic) {
using namespace tvm;
using namespace tvm::tirx;
PrimVar x("x");
auto z = x + 1;
NodeFunctor<int(const ffi::ObjectRef& n, int b)> f;
f.set_dispatch<VarNode>([](const ffi::ObjectRef& n, int b) { return b; });
f.set_dispatch<AddNode>([](const ffi::ObjectRef& n, int b) { return b + 2; });
TVM_FFI_ICHECK_EQ(f(x, 2), 2);
TVM_FFI_ICHECK_EQ(f(z, 2), 4);
}
TEST(IRF, CountVar) {
using namespace tvm;
using namespace tvm::tirx;
int n_var = 0;
PrimVar x("x"), y("y");
auto z = x + 1 + y + y;
tirx::PostOrderVisit(z, [&n_var](const ffi::ObjectRef& n) {
if (n.as<VarNode>()) ++n_var;
});
TVM_FFI_ICHECK_EQ(n_var, 2);
}
TEST(IRF, PreOrderVisit) {
using namespace tvm;
using namespace tvm::tirx;
Stmt init =
IfThenElse(IntImm::Bool(true), Evaluate(IntImm::Int32(0)), Evaluate(IntImm::Int32(0)));
Stmt body = Evaluate(IntImm::Int32(1));
SBlock block(/*iter_vars=*/{}, /*reads=*/{},
/*writes=*/{}, /*name_hint=*/"block", /*body=*/body,
/*init=*/init);
bool init_visited = false;
bool stopped_at_if = true;
bool body_visited = false;
PreOrderVisit(block, [&](const ffi::ObjectRef& n) -> bool {
if (n->IsInstance<IfThenElseNode>()) {
init_visited = true;
return false;
}
if (const auto* eval = n.as<EvaluateNode>()) {
if (const auto* int_imm = eval->value.as<IntImmNode>()) {
if (int_imm->value == 0) {
stopped_at_if = false;
} else if (int_imm->value == 1) {
body_visited = true;
} else {
TVM_FFI_THROW(InternalError) << "Unreachable";
}
}
}
return true;
});
ASSERT_EQ(init_visited, true);
ASSERT_EQ(stopped_at_if, true);
ASSERT_EQ(body_visited, true);
}
TEST(IRF, ExprTransform) {
using namespace tvm;
using namespace tvm::tirx;
PrimVar x("x");
auto z = x + 1;
class MyExprFunctor : public tirx::ExprFunctor<int(const Expr&, int)> {
public:
int VisitExpr_(const VarNode* op, int b) final { return b; }
int VisitExpr_(const IntImmNode* op, int b) final { return op->value; }
int VisitExpr_(const AddNode* op, int b) final {
return VisitExpr(op->a, b) + VisitExpr(op->b, b);
}
};
MyExprFunctor f;
TVM_FFI_ICHECK_EQ(f(x, 2), 2);
TVM_FFI_ICHECK_EQ(f(z, 2), 3);
try {
f(z - 1, 2);
TVM_FFI_THROW(InternalError) << "should fail";
} catch (tvm::ffi::Error&) {
}
}
TEST(IRF, ExprVisit) {
using namespace tvm;
using namespace tvm::tirx;
PrimVar x("x");
auto z = x + 1;
class MyVisitor : public tirx::ExprFunctor<void(const Expr&)>,
public tirx::StmtFunctor<void(const Stmt&)> {
public:
int count = 0;
// implementation
void VisitExpr_(const VarNode* op) final { ++count; }
void VisitExpr_(const IntImmNode* op) final {}
void VisitExpr_(const AddNode* op) final {
VisitExpr(op->a);
VisitExpr(op->b);
}
void VisitStmt_(const EvaluateNode* op) final { VisitExpr(op->value); }
};
MyVisitor v;
v.VisitStmt(Evaluate(z));
TVM_FFI_ICHECK_EQ(v.count, 1);
}
TEST(IRF, StmtVisitor) {
using namespace tvm;
using namespace tvm::tirx;
PrimVar x("x");
class MyVisitor : public StmtExprVisitor {
public:
int count = 0;
// implementation
void VisitExpr_(const VarNode* op) final { ++count; }
};
MyVisitor v;
auto fmaketest = [&]() {
auto z = x + 1;
Stmt eval_body = Evaluate(z);
PrimType dtype = PrimType::Float(32);
Var data_var("b", PointerType(dtype));
Buffer buf(data_var, dtype, {z, z}, {}, PrimExpr(), "b", 0, 0, BufferType::kDefault);
// AllocBuffer is flat (no body). Return as SeqStmt with eval.
return SeqStmt({AllocBuffer(buf), eval_body});
};
v(fmaketest());
// AllocBuffer visits buffer shape via VisitBufferDef.
// shape = {z, z} where z = x + 1, so x is visited twice from shape + once from eval = 3
TVM_FFI_ICHECK_EQ(v.count, 3);
{
// tests for block and block_realize
Stmt body = fmaketest();
PrimType dtype = PrimType::Float(32);
Var buf_var("b", PointerType(dtype));
Buffer buffer = decl_buffer({16});
body = SeqStmt({DeclBuffer(buffer), std::move(body)});
BufferRegion buffer_region(buffer, {Range::FromMinExtent(x + 1, 1)});
MatchBufferRegion match_buffer_region(decl_buffer({1}), buffer_region);
// construct block and block_realize
SBlock block = SBlock({}, {buffer_region}, {buffer_region}, "block", body, body, {},
{match_buffer_region});
Stmt block_realize = SBlockRealize({}, IntImm::Bool(true), block);
v.count = 0;
v(block_realize);
// x visited in: reads range (1), writes range (1), match_buffers range (1),
// init DeclBuffer(0) + AllocBuffer shape(2) + Evaluate(1) = 3,
// body DeclBuffer(0) + AllocBuffer shape(2) + Evaluate(1) = 3.
// Total: 1 + 1 + 1 + 3 + 3 = 9.
TVM_FFI_ICHECK_EQ(v.count, 9);
}
}
TEST(IRF, StmtMutator) {
using namespace tvm;
using namespace tvm::tirx;
PrimVar x("x");
class MyVisitor : public tirx::StmtMutator, public tirx::ExprMutator {
public:
using StmtMutator::operator();
using ExprMutator::operator();
protected:
// implementation
Expr VisitExpr_(const AddNode* op) final { return op->a; }
Stmt VisitStmt_(const SeqStmtNode* op) final { return StmtMutator::VisitSeqStmt_(op, true); }
Expr VisitExpr(const Expr& expr) final { return ExprMutator::VisitExpr(expr); }
};
auto fmakealloc = [&]() {
auto z = x + 1;
PrimType dtype = PrimType::Float(32);
Var data_var("b", PointerType(dtype));
Buffer buf(data_var, dtype, {1, z}, {}, PrimExpr(), "b", 0, 0, BufferType::kDefault);
return AllocBuffer(buf);
};
auto fmakeif = [&]() {
auto z = x + 1;
Stmt body = Evaluate(z);
return IfThenElse(x, Evaluate(0), body);
};
MyVisitor v;
{
auto alloc = fmakealloc();
Stmt body2 = Evaluate(1);
auto* bufptr = alloc.as<AllocBufferNode>()->buffer.get();
ffi::Array<Stmt> arr{std::move(alloc), body2, body2};
auto* arrptr = arr.get();
arr.MutateByApply([&](Stmt s) { return v(std::move(s)); });
TVM_FFI_ICHECK(arr.get() == arrptr);
// buffer IS mutated now (AllocBuffer mutator visits buffer shape via VisitBufferDef)
// shape was {1, x+1}, mutator transforms x+1 -> x, so buffer changes
TVM_FFI_ICHECK(arr[0].as<AllocBufferNode>()->buffer.get() != bufptr);
}
{
ffi::Array<Stmt> arr{fmakealloc()};
// mutate array get reference by another one, trigger copy.
ffi::Array<Stmt> arr2 = arr;
auto* arrptr = arr.get();
arr.MutateByApply([&](Stmt s) { return v(std::move(s)); });
TVM_FFI_ICHECK(arr.get() != arrptr);
// buffer is mutated in arr but not in arr2
TVM_FFI_ICHECK(arr[0].as<AllocBufferNode>()->buffer.get() !=
arr2[0].as<AllocBufferNode>()->buffer.get());
// mutate but no content change.
arr2 = arr;
arr.MutateByApply([&](Stmt s) { return v(std::move(s)); });
TVM_FFI_ICHECK(arr2.get() == arr.get());
}
{
ffi::Array<Stmt> arr{fmakeif()};
arr.MutateByApply([&](Stmt s) { return v(std::move(s)); });
TVM_FFI_ICHECK(arr[0].as<IfThenElseNode>()->else_case.as<EvaluateNode>()->value.same_as(x));
// mutate but no content change.
auto arr2 = arr;
arr.MutateByApply([&](Stmt s) { return v(std::move(s)); });
TVM_FFI_ICHECK(arr2.get() == arr.get());
}
{
auto body =
Evaluate(Call(PrimType::Int(32), builtin::call_extern(), {StringImm("xyz"), x + 1}));
auto res = v(std::move(body));
TVM_FFI_ICHECK(res.as<EvaluateNode>()->value.as<CallNode>()->args[1].same_as(x));
}
{
Stmt body = fmakealloc();
Stmt body2 = Evaluate(1);
auto* ref2 = body2.get();
auto* bufptr = body.as<AllocBufferNode>()->buffer.get();
// construct a recursive SeqStmt.
body = SeqStmt({body, body2});
body = SeqStmt({body, body2});
body = v(std::move(body));
// the seq get flattened
TVM_FFI_ICHECK(body.as<SeqStmtNode>()->size() == 3);
// buffer is now mutated (shape x+1 -> x via VisitBufferDef)
TVM_FFI_ICHECK(body.as<SeqStmtNode>()->seq[0].as<AllocBufferNode>()->buffer.get() != bufptr);
TVM_FFI_ICHECK(body.as<SeqStmtNode>()->seq[1].get() == ref2);
}
{
// Cannot cow because of bref
Stmt body = fmakealloc();
Stmt body2 = Evaluate(1);
// construct a recursive SeqStmt.
body = SeqStmt({body, body2});
auto bref = body;
body = SeqStmt({body, body2});
body = v(std::move(body));
// the seq get flattened
TVM_FFI_ICHECK(body.as<SeqStmtNode>()->size() == 3);
// buffer is mutated (shape x+1 -> x via VisitBufferDef)
TVM_FFI_ICHECK(body.as<SeqStmtNode>()->seq[0].as<AllocBufferNode>() != nullptr);
// bref still holds the old SeqStmt (not shared with new one due to copy)
TVM_FFI_ICHECK(!bref.same_as(body));
}
{
// tests for block and block_realize
// AllocBuffer and DeclBuffer are flat (no body), placed as siblings in SeqStmt
Stmt eval_body = Evaluate(x + 1);
Buffer buffer = decl_buffer({16});
Stmt decl = DeclBuffer(buffer);
Stmt alloc = fmakealloc();
// body is: DeclBuffer, AllocBuffer, Evaluate
Stmt body = SeqStmt({decl, alloc, eval_body});
BufferRegion buffer_region(buffer, {Range::FromMinExtent(x + 1, 1)});
MatchBufferRegion match_buffer_region(decl_buffer({1}), buffer_region);
// construct block and block_realize
SBlock block = SBlock({}, {buffer_region}, {buffer_region}, "block", body, body, {},
{match_buffer_region});
Stmt block_realize = SBlockRealize({}, IntImm::Bool(true), block);
body = v(std::move(block_realize));
// the body should be changed
SBlock new_block = body.as<SBlockRealizeNode>()->block;
// body is a SeqStmt; the Evaluate(x+1) -> Evaluate(x)
auto* seq = new_block->body.as<SeqStmtNode>();
TVM_FFI_ICHECK(seq != nullptr);
TVM_FFI_ICHECK(seq->seq[2].as<EvaluateNode>()->value.same_as(x));
auto* init_seq = new_block->init.value().as<SeqStmtNode>();
TVM_FFI_ICHECK(init_seq != nullptr);
TVM_FFI_ICHECK(init_seq->seq[2].as<EvaluateNode>()->value.same_as(x));
// buffer region min is mutated: x+1 -> x
TVM_FFI_ICHECK(new_block->reads[0]->region[0]->min.same_as(x));
TVM_FFI_ICHECK(new_block->writes[0]->region[0]->min.same_as(x));
TVM_FFI_ICHECK(new_block->match_buffers[0]->source->region[0]->min.same_as(x));
}
}
TEST(IRF, Substitute) {
using namespace tvm;
using namespace tvm::tirx;
PrimType dtype = PrimType::Float(32);
Var x("x", PointerType(dtype, ""));
PrimVar n("n", PrimType::Int(32));
auto fmakebuffer = [&]() {
return Buffer{/*data=*/x,
/*dtype=*/PrimType::Float(32),
/*shape=*/{n},
/*strides=*/{},
/*elem_offset=*/PrimExpr(),
/*name=*/"buf",
/*data_alignment=*/1,
/*offset_factor=*/1,
/*buffer_type=*/BufferType::kDefault};
};
{
// test substitute buffer data var and shape var via DeclBuffer
Var y = x.CopyWithSuffix("subst");
PrimVar m("m", PrimType::Int(32));
Buffer buffer = fmakebuffer();
Stmt store = BufferStore(buffer, FloatImm(dtype, 0), {IntImm::Int32(0)});
Stmt decl = SeqStmt({DeclBuffer(buffer), store});
auto f_subst = [&](const Var& var) -> ffi::Optional<Expr> {
if (var.same_as(x)) return Expr(y);
if (var.same_as(n)) return Expr(m);
return std::nullopt;
};
Stmt new_decl = Substitute(decl, f_subst);
auto* seq_node = new_decl.as<SeqStmtNode>();
TVM_FFI_ICHECK(seq_node != nullptr);
auto* decl_node = seq_node->seq[0].as<DeclBufferNode>();
TVM_FFI_ICHECK(decl_node != nullptr);
TVM_FFI_ICHECK(decl_node->buffer->data.same_as(y));
TVM_FFI_ICHECK(decl_node->buffer->shape[0].same_as(m));
}
{
// test identity substitution on expression
Buffer buffer = fmakebuffer();
PrimExpr expr = BufferLoad(buffer, {IntImm::Int32(0)});
auto f_subst = [&](const Var& var) -> ffi::Optional<Expr> { return Expr(var); };
PrimExpr new_expr = Substitute(expr, f_subst);
// the expression is not changed
TVM_FFI_ICHECK(new_expr.same_as(expr));
}
}
+73
View File
@@ -0,0 +1,73 @@
/*
* 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.
*/
#include <gtest/gtest.h>
#include <tvm/runtime/logging.h>
#include <tvm/runtime/tensor.h>
using namespace tvm;
TEST(TensorTest, IsContiguous_ContiguousStride) {
auto array = runtime::Tensor::Empty({5, 10}, DLDataType{kDLFloat, 32, 1}, {kDLCPU});
DLManagedTensor* managed_tensor = array.ToDLPack();
int64_t strides[] = {10, 1};
managed_tensor->dl_tensor.strides = strides;
TVM_FFI_ICHECK(ffi::IsContiguous(managed_tensor->dl_tensor));
managed_tensor->deleter(managed_tensor);
}
TEST(TensorTest, IsContiguous_NullStride) {
auto array = runtime::Tensor::Empty({5, 10}, DLDataType{kDLFloat, 32, 1}, {kDLCPU});
DLManagedTensor* managed_tensor = array.ToDLPack();
managed_tensor->dl_tensor.strides = nullptr;
TVM_FFI_ICHECK(ffi::IsContiguous(managed_tensor->dl_tensor));
managed_tensor->deleter(managed_tensor);
}
TEST(TensorTest, IsContiguous_AnyStrideForSingular) {
auto array = runtime::Tensor::Empty({5, 1, 10}, DLDataType{kDLFloat, 32, 1}, {kDLCPU});
DLManagedTensor* managed_tensor = array.ToDLPack();
int64_t strides[] = {10, 1, 1}; // strides[1] is normalized to 1 because shape[1] == 1.
managed_tensor->dl_tensor.strides = strides;
TVM_FFI_ICHECK(ffi::IsContiguous(managed_tensor->dl_tensor));
managed_tensor->dl_tensor.strides = nullptr;
managed_tensor->deleter(managed_tensor);
}
TEST(TensorTest, IsContiguous_UncontiguousStride) {
auto array = runtime::Tensor::Empty({5, 1, 10}, DLDataType{kDLFloat, 32, 1}, {kDLCPU});
DLManagedTensor* managed_tensor = array.ToDLPack();
int64_t strides[] = {1, 1, 1};
managed_tensor->dl_tensor.strides = strides;
TVM_FFI_ICHECK(!ffi::IsContiguous(managed_tensor->dl_tensor));
managed_tensor->dl_tensor.strides = nullptr;
managed_tensor->deleter(managed_tensor);
}
+343
View File
@@ -0,0 +1,343 @@
/*
* 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.
*/
#include <gtest/gtest.h>
#include <tvm/ffi/dtype.h>
#include <tvm/ffi/extra/structural_equal.h>
#include <tvm/relax/block_builder.h>
#include <tvm/relax/nested_msg.h>
#include <tvm/relax/type.h>
#include <tvm/runtime/logging.h>
#include <tvm/tirx/expr.h>
#include <algorithm>
#include <array>
#include <cstring>
#include <functional>
#include <iterator>
#include <new>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
using namespace tvm;
using namespace tvm::relax;
namespace {
TensorType ScalarTensorType(PrimType dtype) {
auto n = tvm::ffi::make_object<TensorTypeNode>();
n->dtype = std::move(dtype);
n->ndim = 0;
return TensorType(n);
}
} // namespace
TEST(NestedMsg, Basic) {
// start with no annotation
relax::Var x("x", std::nullopt), y("y", std::nullopt);
// constructor from array, T and nullopt.
NestedMsg<relax::Expr> msg({x, std::nullopt, x});
EXPECT_TRUE(msg.IsNested());
EXPECT_FALSE(msg.IsLeaf());
EXPECT_TRUE(msg != nullptr);
EXPECT_ANY_THROW(msg.LeafValue());
auto arr = msg.NestedArray();
EXPECT_TRUE(arr[0].LeafValue().same_as(x));
EXPECT_TRUE(arr[1] == nullptr);
EXPECT_TRUE(arr[1].IsNull());
EXPECT_TRUE(arr[2].LeafValue().same_as(x));
auto a0 = arr[0];
EXPECT_TRUE(a0.IsLeaf());
// assignment
// assign null
a0 = std::nullopt;
EXPECT_TRUE(a0 == nullptr);
// assign array
a0 = {x, {x, std::nullopt, y}};
EXPECT_TRUE(a0.IsNested());
auto t0 = a0.NestedArray()[1];
EXPECT_TRUE(t0.IsNested());
EXPECT_TRUE(t0.NestedArray()[2].LeafValue().same_as(y));
// assign leaf
a0 = x;
EXPECT_TRUE(a0.IsLeaf());
EXPECT_TRUE(a0.LeafValue().same_as(x));
}
TEST(NestedMsg, IntAndAny) {
NestedMsg<int64_t> msg({1, std::nullopt, 2});
Any any_msg = msg;
NestedMsg<int64_t> msg2 = any_msg.cast<NestedMsg<int64_t>>();
EXPECT_TRUE(msg2.IsNested());
EXPECT_EQ(msg2.NestedArray()[0].LeafValue(), 1);
EXPECT_TRUE(msg2.NestedArray()[1].IsNull());
EXPECT_EQ(msg2.NestedArray()[2].LeafValue(), 2);
}
TEST(NestedMsg, ForEachLeaf) {
relax::Var x("x", std::nullopt), y("y", std::nullopt);
NestedMsg<Expr> msg = {x, {x, y}, std::nullopt, {x, {x, y}}};
int x_count = 0, y_count = 0;
ForEachLeaf(msg, [&](const Expr& v) {
if (v.same_as(x)) ++x_count;
if (v.same_as(y)) ++y_count;
});
EXPECT_EQ(x_count, 4);
EXPECT_EQ(y_count, 2);
}
TEST(NestedMsg, Equal) {
relax::Var x("x", std::nullopt), y("y", std::nullopt);
relax::Var z("z", std::nullopt);
auto fequal = [](Expr lhs, Expr rhs) { return lhs.same_as(rhs); };
using M = NestedMsg<relax::Expr>;
EXPECT_TRUE(Equal(M(std::nullopt), M(std::nullopt), fequal));
EXPECT_TRUE(Equal(M(x), M(x), fequal));
EXPECT_TRUE(Equal(M({x, y}), M({x, y}), fequal));
EXPECT_TRUE(Equal(M({x, std::nullopt}), M({x, std::nullopt}), fequal));
EXPECT_TRUE(Equal(M({x, {std::nullopt, y}}), M({x, {std::nullopt, y}}), fequal));
EXPECT_TRUE(Equal(M({x, {std::nullopt, y}, {x, z}}), M({x, {std::nullopt, y}, {x, z}}), fequal));
// type mismatch
EXPECT_FALSE(Equal(M({x, {std::nullopt, y}, x}), M({x, {std::nullopt, y}, {x, z}}), fequal));
EXPECT_FALSE(Equal(M({x, {std::nullopt, y}, {x, std::nullopt}}),
M({x, {std::nullopt, y}, {x, z}}), fequal));
EXPECT_FALSE(Equal(M({x, {std::nullopt, y}}), M({x, {std::nullopt, y}, {x, z}}), fequal));
EXPECT_FALSE(Equal(M(x), M(std::nullopt), fequal));
EXPECT_FALSE(Equal(M(std::nullopt), M(x), fequal));
EXPECT_FALSE(Equal(M(x), M(ffi::Array<M>({x})), fequal));
EXPECT_FALSE(Equal(M(ffi::Array<M>({x})), M(x), fequal));
}
TEST(NestedMsg, MapAndDecompose) {
relax::Var x("x", PrimType::Int(16));
relax::Var y("y", PrimType::Int(32));
relax::Var z("z", PrimType::Int(64));
BlockBuilder bb = BlockBuilder::Create(std::nullopt);
relax::Expr t0 = bb->Normalize(Tuple({x, y}));
relax::Expr t1 = bb->Normalize(Tuple({t0, x, z, t0}));
auto c0 = IntImm::Int32(0);
auto c1 = IntImm::Int32(1);
auto c2 = IntImm::Int32(2);
auto output = MapToNestedMsg<IntImm>(t1, [&](Expr value) {
if (value.same_as(x)) return c0;
if (value.same_as(y)) return c1;
return c2;
});
NestedMsg<IntImm> expected = {{c0, c1}, c0, c2, {c0, c1}};
EXPECT_TRUE(Equal(output, expected,
[](IntImm lhs, IntImm rhs) -> bool { return lhs->value == rhs->value; }));
auto output2 = MapToNestedMsg<IntImm>(GetType(t1), [&](Type ty) -> NestedMsg<IntImm> {
const auto* prim_ty = ty.as<PrimTypeNode>();
if (prim_ty == nullptr) return std::nullopt;
int bits = prim_ty->dtype.bits;
if (bits == 16) return c0;
if (bits == 32) return c1;
if (bits == 64) return c2;
return std::nullopt;
});
EXPECT_TRUE(Equal(output2, expected,
[](IntImm lhs, IntImm rhs) -> bool { return lhs->value == rhs->value; }));
int x_count = 0, y_count = 0, z_count = 0;
DecomposeNestedMsg(t1, expected, [&](Expr value, NestedMsg<IntImm> msg) {
if (value.same_as(x)) {
EXPECT_TRUE(msg.LeafValue().same_as(c0));
++x_count;
} else if (value.same_as(y)) {
EXPECT_TRUE(msg.LeafValue().same_as(c1));
++y_count;
} else {
EXPECT_TRUE(msg.LeafValue().same_as(c2));
++z_count;
}
});
EXPECT_EQ(x_count, 3);
EXPECT_EQ(y_count, 2);
EXPECT_EQ(z_count, 1);
}
TEST(NestedMsg, MapToNestedMsgByType) {
auto sf0 = ScalarTensorType(PrimType::Float(32));
auto sf1 = TupleType({sf0, sf0});
auto sf2 = TupleType({sf0, sf0});
auto x = relax::Var("x", TupleType({sf1, sf2, sf0}));
auto msg = MapToNestedMsgByType<Expr>(x, [](Expr value) { return value; });
EXPECT_TRUE(msg.IsNested());
auto arr = msg.NestedArray();
EXPECT_TRUE(arr[1].IsNested());
auto arr1 = arr[1].NestedArray();
EXPECT_TRUE(arr1[0].IsLeaf());
EXPECT_TRUE(
tvm::ffi::StructuralEqual()(arr1[0].LeafValue(), TupleGetItem(TupleGetItem(x, 1), 0)));
EXPECT_TRUE(arr[2].IsLeaf());
EXPECT_TRUE(tvm::ffi::StructuralEqual()(arr[2].LeafValue(), TupleGetItem(x, 2)));
}
TEST(NestedMsg, NestedMsgToExpr) {
auto sf0 = ScalarTensorType(PrimType::Float(32));
auto sf1 = TupleType({sf0, sf0});
auto c0 = IntImm::Int32(0);
auto c1 = IntImm::Int32(1);
auto c2 = IntImm::Int32(2);
relax::Var x("x", sf0), y("y", sf0), z("z", sf0);
NestedMsg<IntImm> msg = {c0, {c0, c1}, {c0, {c1, c2}}};
auto expr = NestedMsgToExpr<IntImm>(msg, [&](ffi::Optional<IntImm> leaf) {
TVM_FFI_ICHECK(leaf.has_value());
int value = leaf.value()->value;
switch (value) {
case 0:
return x;
case 1:
return y;
default:
return z;
}
});
Expr expected = Tuple({x, Tuple({x, y}), Tuple({x, Tuple({y, z})})});
EXPECT_TRUE(tvm::ffi::StructuralEqual()(expr, expected));
// test simplified
relax::Var t("t", sf1);
NestedMsg<Expr> msg1 = {TupleGetItem(t, 0), TupleGetItem(t, 1)};
auto expr1 = NestedMsgToExpr<Expr>(msg1, [](ffi::Optional<Expr> leaf) { return leaf.value(); });
EXPECT_TRUE(tvm::ffi::StructuralEqual()(expr1, t));
}
TEST(NestedMsg, CombineNestedMsg) {
auto c0 = IntImm::Int32(0);
auto c1 = IntImm::Int32(1);
auto c2 = IntImm::Int32(2);
NestedMsg<IntImm> lhs = {c0, {c0, c1}, std::nullopt, {c0, {c1, c2}}};
NestedMsg<IntImm> rhs = {c1, {c2, std::nullopt}, std::nullopt, {c1, {c2, c2}}};
NestedMsg<IntImm> expected = {c1, {c2, c1}, std::nullopt, {c1, {c2, c2}}};
auto output = CombineNestedMsg(lhs, rhs, [](IntImm x, IntImm y) {
if (x->value > y->value) return x;
return y;
});
EXPECT_TRUE(Equal(output, expected,
[](IntImm lhs, IntImm rhs) -> bool { return lhs->value == rhs->value; }));
}
TEST(NestedMsg, MapNestedMsg) {
auto c0 = IntImm::Int32(0);
auto c1 = IntImm::Int32(1);
auto c2 = IntImm::Int32(2);
auto c3 = IntImm::Int32(3);
NestedMsg<IntImm> msg = {c0, {c0, c1}, std::nullopt, {c0, {c2, c1}}};
NestedMsg<IntImm> expected = {c3, {c3, std::nullopt}, std::nullopt, {c3, {c2, std::nullopt}}};
auto output = MapNestedMsg(msg, [](IntImm x) {
if (x->value == 0) {
return NestedMsg<IntImm>(IntImm::Int32(3));
} else if (x->value == 1) {
return NestedMsg<IntImm>();
} else {
return NestedMsg<IntImm>(x);
}
});
EXPECT_TRUE(Equal(output, expected,
[](IntImm lhs, IntImm rhs) -> bool { return lhs->value == rhs->value; }));
}
TEST(NestedMsg, TransformTupleLeaf) {
auto c0 = IntImm::Int32(0);
auto c1 = IntImm::Int32(1);
auto c2 = IntImm::Int32(2);
using NInt = NestedMsg<IntImm>;
NInt msg1 = {c0, {c0, c1}, c2, {c0, {c1, c2}}};
NInt msg2 = {c1, {c2, c0}, c2, {c1, {c2, c0}}};
PrimType s = PrimType::Int(32);
relax::Var x("x", s), y("y", s), z("z", s);
BlockBuilder bb = BlockBuilder::Create(std::nullopt);
Expr expr = bb->Normalize(Tuple({x, Tuple({x, x}), x, Tuple({x, Tuple({x, x})})}));
auto ftransleaf = [&](Expr value, std::array<NInt, 2> msgs) -> Expr {
int lhs = msgs[0].LeafValue().as_or_throw<IntImm>()->value;
int rhs = msgs[1].LeafValue().as_or_throw<IntImm>()->value;
if (lhs > rhs)
return z;
else if (lhs == rhs)
return value;
else
return y;
};
Expr expected = Tuple({y, Tuple({y, z}), x, Tuple({y, Tuple({y, z})})});
EXPECT_TRUE(tvm::ffi::StructuralEqual()(
TransformTupleLeaf(expr, std::array<NInt, 2>({msg1, msg2}), ftransleaf), expected));
EXPECT_TRUE(
expr.same_as(TransformTupleLeaf(expr, std::array<NInt, 2>({msg1, msg1}), ftransleaf)));
}
+85
View File
@@ -0,0 +1,85 @@
/*
* 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.
*/
#include <gtest/gtest.h>
#include <tvm/ffi/memory.h>
#include <tvm/runtime/logging.h>
namespace tvm {
namespace test {
using namespace tvm::runtime;
class ObjBase : public ffi::Object {
public:
// dynamically allocate slow
static constexpr const uint32_t _type_child_slots = 1;
TVM_FFI_DECLARE_OBJECT_INFO("test.ObjBase", ObjBase, ffi::Object);
};
class ObjA : public ObjBase {
public:
static constexpr const uint32_t _type_child_slots = 0;
TVM_FFI_DECLARE_OBJECT_INFO("test.ObjA", ObjA, ObjBase);
};
class ObjB : public ObjBase {
public:
static constexpr const uint32_t _type_child_slots = 0;
TVM_FFI_DECLARE_OBJECT_INFO("test.ObjB", ObjB, ObjBase);
};
class ObjAA : public ObjA {
public:
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.ObjAA", ObjAA, ObjA);
};
} // namespace test
} // namespace tvm
TEST(ObjectHierachy, Basic) {
using namespace tvm;
using namespace tvm::runtime;
using namespace tvm::test;
using namespace tvm::ffi;
ffi::ObjectRef refA(ffi::make_object<ObjA>());
TVM_FFI_ICHECK_EQ(refA->type_index(), ObjA::RuntimeTypeIndex());
TVM_FFI_ICHECK(refA.as<ffi::Object>() != nullptr);
TVM_FFI_ICHECK(refA.as<ObjA>() != nullptr);
TVM_FFI_ICHECK(refA.as<ObjBase>() != nullptr);
TVM_FFI_ICHECK(refA.as<ObjB>() == nullptr);
TVM_FFI_ICHECK(refA.as<ObjAA>() == nullptr);
ffi::ObjectRef refAA(ffi::make_object<ObjAA>());
TVM_FFI_ICHECK_EQ(refAA->type_index(), ObjAA::RuntimeTypeIndex());
TVM_FFI_ICHECK(refAA.as<ffi::Object>() != nullptr);
TVM_FFI_ICHECK(refAA.as<ObjBase>() != nullptr);
TVM_FFI_ICHECK(refAA.as<ObjA>() != nullptr);
TVM_FFI_ICHECK(refAA.as<ObjAA>() != nullptr);
TVM_FFI_ICHECK(refAA.as<ObjB>() == nullptr);
ffi::ObjectRef refB(ffi::make_object<ObjB>());
TVM_FFI_ICHECK_EQ(refB->type_index(), ObjB::RuntimeTypeIndex());
TVM_FFI_ICHECK(refB.as<ffi::Object>() != nullptr);
TVM_FFI_ICHECK(refB.as<ObjBase>() != nullptr);
TVM_FFI_ICHECK(refB.as<ObjA>() == nullptr);
TVM_FFI_ICHECK(refB.as<ObjAA>() == nullptr);
TVM_FFI_ICHECK(refB.as<ObjB>() != nullptr);
}
+170
View File
@@ -0,0 +1,170 @@
/*
* 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.
*/
#include "../src/arith/pattern_match.h"
#include <gtest/gtest.h>
#include <tvm/tirx/analysis.h>
TEST(Pattern, Basic) {
using namespace tvm;
using namespace tvm::tirx;
using namespace tvm::arith;
tvm::tirx::PrimVar x("x"), y("y"), z("z");
PrimExpr scalable_lanes = Mul(Call(PrimType::Int(32), builtin::vscale(), {}), 4);
arith::PVar<PrimExpr> px, py, pz;
arith::PVar<DLDataType> pt;
arith::PVar<PrimExpr> planes;
arith::PCallExpr<PVscaleOp> vscale;
// arithmetics
auto r = 1 + (y + 1);
TVM_FFI_ICHECK(!(px + (px + px)).Match(r));
TVM_FFI_ICHECK(!(px + (py + py)).Match(r));
TVM_FFI_ICHECK((px + (py + pz)).Match(r));
auto pattern = px + (py + pz);
TVM_FFI_ICHECK(pattern.Match(r));
{
TVM_FFI_ICHECK((px + (py + px)).Match(r));
auto rr = (px + py).Eval();
TVM_FFI_ICHECK(tirx::ExprDeepEqual()(rr, 1 + y));
TVM_FFI_ICHECK(tirx::ExprDeepEqual()(px.Eval() + py.Eval(), 1 + y));
}
{
TVM_FFI_ICHECK((px + max(py, px)).Match((x + 1) + max(y, (x + 1))));
TVM_FFI_ICHECK(tirx::ExprDeepEqual()(px.Eval(), x + 1));
}
TVM_FFI_ICHECK(!(px + min(py, px)).Match((x + 1) + max(y, (x + 1))));
TVM_FFI_ICHECK((px + min(py, px)).Match(z + min(y, z)));
TVM_FFI_ICHECK((px + truncdiv(py, px * py)).Match(x + truncdiv(2, x * 2)));
TVM_FFI_ICHECK((px - truncmod(py, px * pz)).Match(x - truncmod(2, x * 2)));
TVM_FFI_ICHECK((px - floormod(py, px * PConst<PrimExpr>(2))).Match(x - floormod(2, x * 2)));
// logicals
TVM_FFI_ICHECK((px == pz).Match(x == 1));
TVM_FFI_ICHECK((px != pz).Match(x != 1));
TVM_FFI_ICHECK((px > py).Match(x > y));
TVM_FFI_ICHECK((px < py).Match(x < y));
TVM_FFI_ICHECK((px <= py).Match(x <= y));
TVM_FFI_ICHECK((px >= py).Match(x >= y));
TVM_FFI_ICHECK((px >= py && px < pz).Match(x >= y && x < z));
TVM_FFI_ICHECK((!(px > py || px != py)).Match(!(x > y || x != y)));
{
TVM_FFI_ICHECK(select(px >= pz, py, py + pz).Match(tirx::Select((x + 1) >= 1, y, y + 1)));
TVM_FFI_ICHECK(tirx::ExprDeepEqual()(px.Eval(), x + 1));
}
// bit intrinsics
{
TVM_FFI_ICHECK((px >> pz).Match(x >> 1));
TVM_FFI_ICHECK(is_const_int(pz.Eval(), 1));
}
TVM_FFI_ICHECK(!(px >> pz).Match(x << 1));
TVM_FFI_ICHECK((px << pz).Match(x << 1));
TVM_FFI_ICHECK((px & pz).Match(x & 1));
TVM_FFI_ICHECK((px | pz).Match(x | 1));
TVM_FFI_ICHECK((px ^ pz).Match(x ^ 1));
TVM_FFI_ICHECK((px - (~(py | (px * pz)))).Match(x - (~(2 | (x * 2)))));
// select
{
TVM_FFI_ICHECK(select(px > pz, py, py + pz).Match(tirx::Select(x > 1, y, y + 1)));
TVM_FFI_ICHECK(is_const_int(pz.Eval(), 1));
}
TVM_FFI_ICHECK(!select(px > pz, py, py + pz).Match(tirx::Select(x > 2, y, y + 1)));
TVM_FFI_ICHECK(!select(px > pz, py, py).Match(tirx::Select(x > 2, y, y + 1)));
{
TVM_FFI_ICHECK(select(px, py, pz).Match(tirx::Select(x > 2, y, y + 1)));
TVM_FFI_ICHECK(tirx::ExprDeepEqual()(pz.Eval(), y + 1));
}
// if_then_else
{
TVM_FFI_ICHECK(if_then_else(px > pz, py, py + pz).Match(if_then_else(x > 1, y, y + 1)));
TVM_FFI_ICHECK(is_const_int(pz.Eval(), 1));
}
// cast pattern
{
TVM_FFI_ICHECK(!cast(PConst<DLDataType>(DLDataType{kDLInt, 32, 1}), px)
.Match(tirx::Cast(PrimType::Float(64), x)));
TVM_FFI_ICHECK(cast(pt, px).Match(tirx::Cast(PrimType::Float(64), x)));
TVM_FFI_ICHECK((pt.Eval() == DLDataType{kDLFloat, 64, 1}));
auto zz = cast(pt, px).Eval();
TVM_FFI_ICHECK(
(cast(pt, px) - cast(pt, py))
.Match(tirx::Cast(PrimType::Float(64), x) - tirx::Cast(PrimType::Int(64), x)));
auto expr = tirx::Cast(PrimType::Int(32), tirx::Cast(PrimType::Float(64), x));
TVM_FFI_ICHECK(!(cast(pt, cast(pt, px))).Match(expr));
}
// ramp pattern
{
TVM_FFI_ICHECK(ramp(px, PConst<PrimExpr>(1), planes).Match(tirx::Ramp(x, 1, 10)));
TVM_FFI_ICHECK(planes.Eval().as<IntImmNode>()->value == 10);
TVM_FFI_ICHECK(ramp(px, PConst<PrimExpr>(1), planes).Match(tirx::Ramp(x, 1, scalable_lanes)));
TVM_FFI_ICHECK((vscale * PConst<PrimExpr>(4)).Match(planes.Eval()));
TVM_FFI_ICHECK(!ramp(px, PConst<PrimExpr>(1), planes).Match(tirx::Ramp(x, 2, 10)));
}
// broadcast pattern
{
TVM_FFI_ICHECK(broadcast(px, planes).Match(tirx::Broadcast(x, 10)));
TVM_FFI_ICHECK(planes.Eval().as<IntImmNode>()->value == 10);
TVM_FFI_ICHECK(broadcast(px * py, planes).Match(tirx::Broadcast(x * 10, 10)));
TVM_FFI_ICHECK(broadcast(px, planes).Match(tirx::Broadcast(x, scalable_lanes)));
TVM_FFI_ICHECK((vscale * PConst<PrimExpr>(4)).Match(planes.Eval()));
}
}
TEST(Pattern, IntImm) {
using namespace tvm;
tirx::PrimVar tx("tx"), ty("ty");
arith::PVar<IntImm> c;
arith::PVar<tirx::Var> v;
{
// We can match integer and Var, both of which are
// special case container of Expr
TVM_FFI_ICHECK((v * c).Match(tx * 3));
TVM_FFI_ICHECK_EQ(c.Eval()->value, 3);
TVM_FFI_ICHECK((v * 3).Match(tx * 3));
}
// cannot match c to ty
TVM_FFI_ICHECK(!(v * c).Match(tx * ty));
// cannot match tx + 1 to v
TVM_FFI_ICHECK(!(v * c).Match((tx + 1) * 3));
}
TEST(Pattern, MatchWithType) {
using namespace tvm;
// match expr with specified dtype
arith::PVarWithDataType<PrimExpr, arith::PConst<DLDataType>> pat(DLDataType{kDLFloat, 32, 1});
tirx::PrimVar x("x", PrimType::Float(32));
tirx::PrimVar y("y", PrimType::Float(32));
tirx::PrimVar x_int("x", PrimType::Int(32));
tirx::PrimVar y_int("y", PrimType::Int(32));
TVM_FFI_ICHECK(pat.Match(x + y * 2.0f));
TVM_FFI_ICHECK(!pat.Match(x_int + y_int * 2));
// match vectorized expr with specified element dtype
arith::PVecDataType vec_ty(DLDataType{kDLFloat, 32, 1});
arith::PVarWithDataType<PrimExpr, arith::PVecDataType> vpat(vec_ty);
tirx::PrimVar vx("x", PrimType::Float(32, 8));
tirx::PrimVar vy("y", PrimType::Float(32, 8));
tirx::PrimVar vx_int("x", PrimType::Int(32, 8));
tirx::PrimVar vy_int("y", PrimType::Int(32, 8));
TVM_FFI_ICHECK(vpat.Match(vx + vy * tirx::Broadcast(2.0f, 8)));
TVM_FFI_ICHECK(!vpat.Match(vx_int + vy_int * tirx::Broadcast(2, 8)));
}
+65
View File
@@ -0,0 +1,65 @@
/*
* 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.
*/
#include <gtest/gtest.h>
#include <tvm/runtime/logging.h>
#include <tvm/s_tir/random_engine.h>
TEST(RandomEngine, Randomness) {
int64_t rand_state = 0;
tvm::s_tir::LinearCongruentialEngine rng(&rand_state);
rng.Seed(0x114514);
bool covered[100];
memset(covered, 0, sizeof(covered));
for (int i = 0; i < 100000; i++) {
covered[rng() % 100] = true;
}
for (int i = 0; i < 100; i++) {
TVM_FFI_ICHECK(covered[i]);
}
}
TEST(RandomEngine, Reproducibility) {
int64_t rand_state_a = 0, rand_state_b = 0;
tvm::s_tir::LinearCongruentialEngine rng_a(&rand_state_a), rng_b(&rand_state_b);
rng_a.Seed(0x23456789);
rng_b.Seed(0x23456789);
for (int i = 0; i < 100000; i++) {
TVM_FFI_ICHECK_EQ(rng_a(), rng_b());
}
}
TEST(RandomEngine, Serialization) {
int64_t rand_state_a = 0, rand_state_b = 0;
tvm::s_tir::LinearCongruentialEngine rng_a(&rand_state_a), rng_b(&rand_state_b);
rng_a.Seed(0x56728);
rand_state_b = rand_state_a;
for (int i = 0; i < 100000; i++) TVM_FFI_ICHECK_EQ(rng_a(), rng_b());
for (int i = 0; i < 123456; i++) rng_a();
rand_state_b = rand_state_a;
for (int i = 0; i < 100000; i++) TVM_FFI_ICHECK_EQ(rng_a(), rng_b());
}
+102
View File
@@ -0,0 +1,102 @@
/*
* 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.
*/
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <tvm/runtime/logging.h>
namespace tvm {
namespace runtime {
namespace detail {
namespace {
TEST(TvmLogDebugSettings, Disabled) {
TvmLogDebugSettings settings = TvmLogDebugSettings::ParseSpec(nullptr);
EXPECT_FALSE(settings.dlog_enabled());
settings = TvmLogDebugSettings::ParseSpec("");
EXPECT_FALSE(settings.dlog_enabled());
settings = TvmLogDebugSettings::ParseSpec("0");
EXPECT_FALSE(settings.dlog_enabled());
}
TEST(TvmLogDebugSettings, DlogOnly) {
TvmLogDebugSettings settings = TvmLogDebugSettings::ParseSpec("1");
EXPECT_TRUE(settings.dlog_enabled());
EXPECT_FALSE(settings.VerboseEnabled("my/filesytem/src/foo/bar.cc", 0));
}
TEST(TvmLogDebugSettings, VLogEnabledDefault) {
TvmLogDebugSettings settings = TvmLogDebugSettings::ParseSpec("DEFAULT=3");
EXPECT_TRUE(settings.dlog_enabled());
EXPECT_TRUE(settings.VerboseEnabled("my/filesytem/src/foo/bar.cc", 3));
EXPECT_FALSE(settings.VerboseEnabled("my/filesytem/src/foo/bar.cc", 4));
}
TEST(TvmLogDebugSettings, VLogEnabledComplex) {
TvmLogDebugSettings settings =
TvmLogDebugSettings::ParseSpec("foo/bar.cc=3,baz.cc=-1,DEFAULT=2,another/file.cc=4");
EXPECT_TRUE(settings.dlog_enabled());
EXPECT_TRUE(settings.VerboseEnabled("my/filesystem/src/foo/bar.cc", 3));
EXPECT_FALSE(settings.VerboseEnabled("my/filesystem/src/foo/bar.cc", 4));
EXPECT_TRUE(settings.VerboseEnabled("my/filesystem/src/foo/other.cc", 2));
EXPECT_FALSE(settings.VerboseEnabled("my/filesystem/src/foo/other.cc", 3));
EXPECT_FALSE(settings.VerboseEnabled("my/filesystem/src/baz.cc", 0));
}
#define MATCH_THROW(stmt, err_type, matcher) \
try { \
stmt; \
} catch (const err_type& e) { \
EXPECT_THAT(e.what(), matcher); \
} catch (...) { \
EXPECT_FALSE("stmt threw an unexpected exception"); \
}
TEST(TvmLogDebugSettings, IllFormed) {
MATCH_THROW(
TvmLogDebugSettings::ParseSpec("foo/bar.cc=bogus;"), tvm::ffi::Error,
::testing::HasSubstr("TVM_LOG_DEBUG ill-formed at position 11: invalid level: \"bogus;\""));
MATCH_THROW(TvmLogDebugSettings::ParseSpec("DEFAULT=2;bar/baz.cc=2"), tvm::ffi::Error,
::testing::HasSubstr(
"TVM_LOG_DEBUG ill-formed at position 8: invalid level: \"2;bar/baz.cc=2\""));
MATCH_THROW(TvmLogDebugSettings::ParseSpec("DEFAULT=2,bar/baz.cc+2"), tvm::ffi::Error,
::testing::HasSubstr("TVM_LOG_DEBUG ill-formed at position 22: expecting "
"\"=<level>\" after \"bar/baz.cc+2\""));
}
TEST(TvmLogDebugSettings, SpecPrefix) {
TvmLogDebugSettings settings = TvmLogDebugSettings::ParseSpec(
"../src/foo/bar.cc=3,src/baz.cc=3,foo/bar/src/another/file.cc=4");
EXPECT_TRUE(settings.dlog_enabled());
EXPECT_TRUE(settings.VerboseEnabled("my/filesystem/src/foo/bar.cc", 3));
EXPECT_TRUE(settings.VerboseEnabled("foo/bar.cc", 3));
EXPECT_TRUE(settings.VerboseEnabled("my/filesystem/src/baz.cc", 3));
EXPECT_TRUE(settings.VerboseEnabled("baz.cc", 3));
EXPECT_TRUE(settings.VerboseEnabled("my/filesystem/src/another/file.cc", 4));
EXPECT_TRUE(settings.VerboseEnabled("another/file.cc", 4));
}
} // namespace
} // namespace detail
} // namespace runtime
} // namespace tvm
+68
View File
@@ -0,0 +1,68 @@
/*
* 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.
*/
#include "../../../src/support/ring_buffer.h"
#include <gtest/gtest.h>
namespace tvm {
namespace support {
namespace {
TEST(RingBuffer, ReadWrite) {
RingBuffer buffer;
std::vector<int> data = {1, 2, 3, 4};
std::vector<int> output;
buffer.Write(data.data(), data.size() * 4);
ASSERT_EQ(buffer.bytes_available(), data.size() * 4);
output.resize(4);
buffer.Read(output.data(), data.size() * 4);
for (size_t i = 0; i < output.size(); ++i) {
ASSERT_EQ(output[i], data[i]);
}
}
TEST(RingBuffer, ReadWithCallback) {
RingBuffer buffer;
std::vector<int> data = {1, 2, 3, 4};
std::vector<int> output;
buffer.Write(data.data(), data.size() * 4);
auto callback0 = [](const char* data, size_t size) -> size_t {
const int* iptr = reinterpret_cast<const int*>(data);
TVM_FFI_ICHECK_EQ(iptr[0], 1);
TVM_FFI_ICHECK_EQ(iptr[1], 2);
return size;
};
buffer.ReadWithCallback(callback0, 2 * sizeof(int));
auto callback1 = [](const char* data, size_t size) -> size_t {
const int* iptr = reinterpret_cast<const int*>(data);
TVM_FFI_ICHECK_EQ(iptr[0], 3);
TVM_FFI_ICHECK_EQ(iptr[1], 4);
return size;
};
buffer.ReadWithCallback(callback1, 2 * sizeof(int));
}
} // namespace
} // namespace support
} // namespace tvm
+47
View File
@@ -0,0 +1,47 @@
/*
* 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.
*/
#include <gtest/gtest.h>
#include <tvm/runtime/logging.h>
#include "../../src/support/utils.h"
namespace tvm {
namespace test {
TEST(HashTests, HashStability) {
size_t a = 345292;
int b = 795620;
EXPECT_EQ(::tvm::support::HashCombine(a, b), 2677237020);
uint64_t c = 12345;
int d = 987654432;
EXPECT_EQ(::tvm::support::HashCombine(c, d), 3642871070);
size_t e = 1010101;
size_t f = 3030303;
EXPECT_EQ(::tvm::support::HashCombine(e, f), 2722928432);
}
TEST(StartsWithTests, Basic) {
EXPECT_TRUE(::tvm::support::StartsWith("abc", "abc"));
EXPECT_TRUE(::tvm::support::StartsWith("abcd", "abc"));
EXPECT_FALSE(::tvm::support::StartsWith("abc", "abcd"));
}
} // namespace test
} // namespace tvm
+129
View File
@@ -0,0 +1,129 @@
/*
* 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.
*/
#include <gtest/gtest.h>
#include <tvm/target/target.h>
#include <tvm/target/virtual_device.h>
namespace tvm {
namespace {
TEST(VirtualDevice, Join_Defined) {
{
Target target_a = Target("cuda");
VirtualDevice lhs = VirtualDevice(kDLCUDA, 3);
VirtualDevice rhs = VirtualDevice(kDLCUDA, -1, target_a, "global");
ffi::Optional<VirtualDevice> actual = VirtualDevice::Join(lhs, rhs);
EXPECT_TRUE(actual.operator bool());
VirtualDevice expected = VirtualDevice(kDLCUDA, 3, target_a, "global");
EXPECT_TRUE(tvm::ffi::StructuralEqual()(actual.value(), expected));
}
{
Target target_a = Target("cuda");
VirtualDevice lhs = VirtualDevice(kDLCUDA, -1, target_a, "global");
VirtualDevice rhs = VirtualDevice(kDLCUDA, 3);
ffi::Optional<VirtualDevice> actual = VirtualDevice::Join(lhs, rhs);
EXPECT_TRUE(actual.operator bool());
VirtualDevice expected = VirtualDevice(kDLCUDA, 3, target_a, "global");
EXPECT_TRUE(tvm::ffi::StructuralEqual()(actual.value(), expected));
}
{
Target target_a = Target("cuda");
VirtualDevice lhs = VirtualDevice(kDLCUDA);
VirtualDevice rhs = VirtualDevice(kDLCUDA, 2, target_a);
ffi::Optional<VirtualDevice> actual = VirtualDevice::Join(lhs, rhs);
EXPECT_TRUE(actual.operator bool());
VirtualDevice expected = VirtualDevice(kDLCUDA, 2, target_a);
EXPECT_TRUE(tvm::ffi::StructuralEqual()(actual.value(), expected));
}
{
Target target_a = Target("cuda");
VirtualDevice lhs = VirtualDevice();
VirtualDevice rhs = VirtualDevice(kDLCUDA, 3, target_a, "global");
ffi::Optional<VirtualDevice> actual = VirtualDevice::Join(lhs, rhs);
EXPECT_TRUE(actual.operator bool());
VirtualDevice expected = rhs;
EXPECT_TRUE(tvm::ffi::StructuralEqual()(actual.value(), expected));
}
}
TEST(VirtualDevice, Join_Undefined) {
{
VirtualDevice lhs = VirtualDevice(kDLCUDA);
VirtualDevice rhs = VirtualDevice(kDLCPU);
ffi::Optional<VirtualDevice> actual = VirtualDevice::Join(lhs, rhs);
EXPECT_FALSE(actual);
}
{
VirtualDevice lhs = VirtualDevice(kDLCUDA, 3);
VirtualDevice rhs = VirtualDevice(kDLCUDA, 4);
ffi::Optional<VirtualDevice> actual = VirtualDevice::Join(lhs, rhs);
EXPECT_FALSE(actual);
}
{
VirtualDevice lhs = VirtualDevice(kDLCUDA, 3, Target("cuda"));
VirtualDevice rhs = VirtualDevice(kDLCUDA, 3, Target("cuda"));
ffi::Optional<VirtualDevice> actual = VirtualDevice::Join(lhs, rhs);
EXPECT_FALSE(actual);
}
{
VirtualDevice lhs = VirtualDevice(kDLCUDA, 3, Target("cuda"), "local");
VirtualDevice rhs = VirtualDevice(kDLCUDA, 3, Target("cuda"), "global");
ffi::Optional<VirtualDevice> actual = VirtualDevice::Join(lhs, rhs);
EXPECT_FALSE(actual);
}
}
TEST(VirtualDevice, Default) {
Target target_a = Target("cuda");
VirtualDevice lhs = VirtualDevice(kDLCUDA, -1, Target(), "global");
VirtualDevice rhs = VirtualDevice(kDLCUDA, 3, target_a, "local");
VirtualDevice actual = VirtualDevice::Default(lhs, rhs);
VirtualDevice expected = VirtualDevice(kDLCUDA, 3, target_a, "global");
EXPECT_TRUE(tvm::ffi::StructuralEqual()(actual, expected));
}
TEST(VirtualDevice, Constructor_Invalid) {
EXPECT_ANY_THROW(VirtualDevice(kDLCPU, -1, Target("cuda")));
}
TEST(VirtualDeviceCache, Memoized) {
VirtualDeviceCache cache;
Target target_a = Target("cuda");
Target target_b = Target("llvm");
Target target_c = Target("cuda");
VirtualDevice virtual_device_a = cache.Make(kDLCUDA, 3, target_a, "local");
VirtualDevice virtual_device_b = cache.Make(kDLCPU, 1, target_b, "global");
EXPECT_EQ(cache.Make(kDLCUDA, 3, target_a, "local"), virtual_device_a);
EXPECT_EQ(cache.Make(kDLCPU, 1, target_b, "global"), virtual_device_b);
EXPECT_NE(cache.Make(kDLCUDA, 2, target_a, "local"), virtual_device_a);
EXPECT_NE(cache.Make(kDLCPU, 3, target_b, "local"), virtual_device_a);
EXPECT_NE(cache.Make(kDLCUDA, 3, target_a, "global"), virtual_device_a);
EXPECT_EQ(cache.Make(kDLCUDA, 3, Target("cuda"), "local"), virtual_device_a);
EXPECT_NE(
cache.Make(kDLCUDA, 3,
Target(ffi::Map<ffi::String, ffi::Any>{{"kind", ffi::String("cuda")},
{"max_threads_per_block", int64_t(4096)}}),
"local"),
virtual_device_a);
}
} // namespace
} // namespace tvm
+396
View File
@@ -0,0 +1,396 @@
/*
* 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.
*/
#include <gtest/gtest.h>
#include <tvm/ir/expr.h>
#include <tvm/runtime/logging.h>
#include <tvm/target/target.h>
#include <cmath>
#include <string>
using namespace tvm;
TVM_REGISTER_TARGET_KIND("TestTargetKind", kDLCPU)
.set_attr<std::string>("Attr1", "Value1")
.add_attr_option<bool>("my_bool")
.add_attr_option<ffi::Array<ffi::String>>("your_names")
.add_attr_option<ffi::Map<ffi::String, int64_t>>("her_maps");
ffi::Map<ffi::String, ffi::Any> TestTargetParser(ffi::Map<ffi::String, ffi::Any> target) {
ffi::String mcpu = target.at("mcpu").as_or_throw<ffi::String>();
target.Set("mcpu", ffi::String("super_") + mcpu);
target.Set("keys", ffi::Array<ffi::String>({"super"}));
target.Set("feature.test", true);
return target;
}
ffi::Map<ffi::String, ffi::Any> TestAttrsPreProcessor(ffi::Map<ffi::String, ffi::Any> attrs) {
attrs.Set("mattr", ffi::String("woof"));
return attrs;
}
TVM_REGISTER_TARGET_KIND("TestTargetParser", kDLCPU)
.add_attr_option<ffi::String>("mattr")
.add_attr_option<ffi::String>("mcpu")
.set_default_keys({"cpu"})
.set_target_canonicalizer(TestTargetParser);
TVM_REGISTER_TARGET_KIND("TestAttrsPreprocessor", kDLCPU)
.add_attr_option<ffi::String>("mattr")
.set_default_keys({"cpu"})
.set_target_canonicalizer(TestAttrsPreProcessor);
TVM_REGISTER_TARGET_KIND("TestClashingPreprocessor", kDLCPU)
.add_attr_option<ffi::String>("mattr")
.add_attr_option<ffi::String>("mcpu")
.set_default_keys({"cpu"})
.set_target_canonicalizer(TestTargetParser);
TEST(TargetKind, GetAttrMap) {
auto map = tvm::TargetKind::GetAttrMap<std::string>("Attr1");
auto target_kind = tvm::TargetKind::Get("TestTargetKind").value();
std::string result = map[target_kind];
TVM_FFI_ICHECK_EQ(result, "Value1");
}
TEST(TargetCreation, NestedConfig) {
ffi::Map<ffi::String, ffi::Any> config = {
{"my_bool", true},
{"your_names", ffi::Array<ffi::String>{"junru", "jian"}},
{"kind", ffi::String("TestTargetKind")},
{
"her_maps",
ffi::Map<ffi::String, int64_t>{
{"a", 1},
{"b", 2},
},
},
};
Target target = Target(config);
TVM_FFI_ICHECK_EQ(target->kind, TargetKind::Get("TestTargetKind").value());
TVM_FFI_ICHECK_EQ(target->tag, "");
TVM_FFI_ICHECK(target->keys.empty());
bool my_bool = target->GetAttr<bool>("my_bool").value();
TVM_FFI_ICHECK_EQ(my_bool, true);
ffi::Array<ffi::String> your_names =
target->GetAttr<ffi::Array<ffi::String>>("your_names").value();
TVM_FFI_ICHECK_EQ(your_names.size(), 2U);
TVM_FFI_ICHECK_EQ(your_names[0], "junru");
TVM_FFI_ICHECK_EQ(your_names[1], "jian");
ffi::Map<ffi::String, int64_t> her_maps =
target->GetAttr<ffi::Map<ffi::String, int64_t>>("her_maps").value();
TVM_FFI_ICHECK_EQ(her_maps.size(), 2U);
TVM_FFI_ICHECK_EQ(her_maps["a"], 1);
TVM_FFI_ICHECK_EQ(her_maps["b"], 2);
}
TEST(TargetCreationFail, UnrecognizedConfigOption) {
ffi::Map<ffi::String, ffi::Any> config = {
{"my_bool", true},
{"your_names", ffi::Array<ffi::String>{"junru", "jian"}},
{"kind", ffi::String("TestTargetKind")},
{"bad", ffi::ObjectRef(nullptr)},
{
"her_maps",
ffi::Map<ffi::String, int64_t>{
{"a", 1},
{"b", 2},
},
},
};
bool failed = false;
try {
Target tgt(config);
} catch (...) {
failed = true;
}
ASSERT_EQ(failed, true);
}
TEST(TargetCreationFail, TypeMismatch) {
ffi::Map<ffi::String, ffi::Any> config = {
{"my_bool", ffi::String("true")},
{"your_names", ffi::Array<ffi::String>{"junru", "jian"}},
{"kind", ffi::String("TestTargetKind")},
{
"her_maps",
ffi::Map<ffi::String, int64_t>{
{"a", 1},
{"b", 2},
},
},
};
bool failed = false;
try {
Target tgt(config);
} catch (...) {
failed = true;
}
ASSERT_EQ(failed, true);
}
TEST(TargetCreationFail, TargetKindNotFound) {
ffi::Map<ffi::String, ffi::Any> config = {
{"my_bool", "true"},
{"your_names", ffi::Array<ffi::String>{"junru", "jian"}},
{
"her_maps",
ffi::Map<ffi::String, int64_t>{
{"a", 1},
{"b", 2},
},
},
};
bool failed = false;
try {
Target tgt(config);
} catch (...) {
failed = true;
}
ASSERT_EQ(failed, true);
}
TEST(TargetCreation, TargetParser) {
Target test_target(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestTargetParser")},
{"mcpu", ffi::String("woof")},
});
ASSERT_EQ(test_target->GetAttr<ffi::String>("mcpu").value(), "super_woof");
ASSERT_EQ(test_target->keys.size(), 1);
ASSERT_EQ(test_target->keys[0], "super");
}
TEST(TargetCreation, TargetFeatures) {
Target test_target_with_parser(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestTargetParser")},
{"mcpu", ffi::String("woof")},
});
// Features are stored as "feature.xxx" keys in attrs
ASSERT_EQ(test_target_with_parser->GetAttr<bool>("feature.test").value(), true);
Target test_target_no_parser("TestTargetKind");
ASSERT_EQ(test_target_no_parser->GetAttr<bool>("feature.test"), std::nullopt);
ASSERT_EQ(test_target_no_parser->GetAttr<bool>("feature.test", true).value(), true);
}
TEST(TargetCreation, TargetFeaturesSetByCanonicalizer) {
// feature.* keys are set by the canonicalizer, not by user input.
Target test_target(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestTargetParser")},
{"mcpu", ffi::String("woof")},
});
// TestTargetParser sets "feature.test" = true
ASSERT_EQ(test_target->GetAttr<bool>("feature.test").value(), true);
// Non-existent features return nullopt
ASSERT_EQ(test_target->GetAttr<bool>("feature.nonexistent"), std::nullopt);
}
TEST(TargetCreation, TargetAttrsPreProcessor) {
Target test_target(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestAttrsPreprocessor")},
{"mattr", ffi::String("cake")},
});
ASSERT_EQ(test_target->GetAttr<ffi::String>("mattr").value(), "woof");
}
TEST(TargetCreation, TargetParserProcessing) {
Target test_target(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestClashingPreprocessor")},
{"mcpu", ffi::String("woof")},
{"mattr", ffi::String("cake")},
});
ASSERT_EQ(test_target->GetAttr<ffi::String>("mcpu").value(), "super_woof");
ASSERT_EQ(test_target->GetAttr<ffi::String>("mattr").value(), "cake");
}
TEST(TargetCreation, RoundTripCanonicalizerFeatures) {
// Construct a target whose canonicalizer sets feature.test and transforms mcpu
Target original(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestTargetParser")},
{"mcpu", ffi::String("woof")},
});
ASSERT_EQ(original->GetAttr<ffi::String>("mcpu").value(), "super_woof");
ASSERT_EQ(original->GetAttr<bool>("feature.test").value(), true);
// Export to config and reconstruct
ffi::Map<ffi::String, ffi::Any> exported = original->ToConfig();
Target reconstructed(exported);
// Canonicalized attrs must survive the round-trip
// Note: mcpu gets canonicalized again (super_super_woof) because the canonicalizer runs
ASSERT_TRUE(reconstructed->GetAttr<ffi::String>("mcpu").has_value());
ASSERT_EQ(reconstructed->GetAttr<bool>("feature.test").value(), true);
ASSERT_EQ(reconstructed->keys.size(), 1);
ASSERT_EQ(reconstructed->keys[0], "super");
}
TEST(TargetCreation, RoundTripCanonicalizerFeaturesNestedHost) {
// Construct a host target whose canonicalizer sets feature.test
Target host(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestTargetParser")},
{"mcpu", ffi::String("woof")},
});
ASSERT_EQ(host->GetAttr<bool>("feature.test").value(), true);
// Attach it as host to another target
Target outer(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestTargetKind")},
{"my_bool", true},
});
Target combined(outer, host);
// Export the outer target (includes nested host) and reconstruct
ffi::Map<ffi::String, ffi::Any> exported = combined->ToConfig();
Target reconstructed(exported);
// The nested host must reconstruct successfully with feature.* preserved
ffi::Optional<Target> reconstructed_host = reconstructed->GetHost();
ASSERT_TRUE(reconstructed_host.has_value());
ASSERT_EQ(reconstructed_host.value()->GetAttr<bool>("feature.test").value(), true);
ASSERT_TRUE(reconstructed_host.value()->GetAttr<ffi::String>("mcpu").has_value());
}
TEST(TargetCreationFail, UnknownNonFeatureKeyStillFails) {
// Verify that unknown non-feature.* keys still fail schema validation
ffi::Map<ffi::String, ffi::Any> config = {
{"kind", ffi::String("TestTargetParser")},
{"mcpu", ffi::String("woof")},
{"unknown_key", ffi::String("bad")},
};
ASSERT_THROW({ Target{config}; }, tvm::ffi::Error);
}
TVM_REGISTER_TARGET_KIND("TestStringKind", kDLCPU)
.add_attr_option<ffi::String>("single")
.add_attr_option<ffi::Array<ffi::String>>("array")
.add_attr_option<ffi::Array<ffi::Array<ffi::String>>>("nested-array")
.add_attr_option<ffi::Array<ffi::Array<ffi::Array<ffi::String>>>>("nested2-array");
TEST(TargetCreation, ProcessStrings) {
// Test single string attribute
Target test_target1(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestStringKind")},
{"single", ffi::String("'string with single quote")},
});
ASSERT_TRUE(test_target1->GetAttr<ffi::String>("single"));
ffi::String string1 = test_target1->GetAttr<ffi::String>("single").value();
ASSERT_EQ(string1, "'string with single quote");
// Test array of strings
Target test_target3(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestStringKind")},
{"array", ffi::Array<ffi::String>{"-danny", "-sammy=1", "-kirby=string with space"}},
});
ASSERT_TRUE(test_target3->GetAttr<ffi::Array<ffi::String>>("array"));
ffi::Array<ffi::String> array3 = test_target3->GetAttr<ffi::Array<ffi::String>>("array").value();
ASSERT_EQ(array3[0], "-danny");
ASSERT_EQ(array3[1], "-sammy=1");
ASSERT_EQ(array3[2], "-kirby=string with space");
// Test nested array of strings
Target test_target6(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestStringKind")},
{"nested-array",
ffi::Array<ffi::Array<ffi::String>>{
ffi::Array<ffi::String>{"foo0", "foo1", "foo2"},
ffi::Array<ffi::String>{"bar0", "bar1", "bar2"},
ffi::Array<ffi::String>{"baz0", "baz1"},
}},
});
ASSERT_TRUE(test_target6->GetAttr<ffi::Array<ffi::Array<ffi::String>>>("nested-array"));
ffi::Array<ffi::Array<ffi::String>> array6 =
test_target6->GetAttr<ffi::Array<ffi::Array<ffi::String>>>("nested-array").value();
ASSERT_EQ(array6[0][0], "foo0");
ASSERT_EQ(array6[0][1], "foo1");
ASSERT_EQ(array6[0][2], "foo2");
ASSERT_EQ(array6[1][0], "bar0");
ASSERT_EQ(array6[1][1], "bar1");
ASSERT_EQ(array6[1][2], "bar2");
ASSERT_EQ(array6[2][0], "baz0");
ASSERT_EQ(array6[2][1], "baz1");
// Test doubly-nested array of strings
Target test_target7(ffi::Map<ffi::String, ffi::Any>{
{"kind", ffi::String("TestStringKind")},
{"nested2-array",
ffi::Array<ffi::Array<ffi::Array<ffi::String>>>{
ffi::Array<ffi::Array<ffi::String>>{
ffi::Array<ffi::String>{"foo0", "foo1"},
ffi::Array<ffi::String>{"bar0", "bar1"},
ffi::Array<ffi::String>{"baz0", "baz1"},
},
ffi::Array<ffi::Array<ffi::String>>{
ffi::Array<ffi::String>{"zing0", "zing1"},
ffi::Array<ffi::String>{"fred"},
},
}},
});
ASSERT_TRUE(
test_target7->GetAttr<ffi::Array<ffi::Array<ffi::Array<ffi::String>>>>("nested2-array"));
ffi::Array<ffi::Array<ffi::Array<ffi::String>>> array7 =
test_target7->GetAttr<ffi::Array<ffi::Array<ffi::Array<ffi::String>>>>("nested2-array")
.value();
ASSERT_EQ(array7.size(), 2);
ASSERT_EQ(array7[0].size(), 3);
ASSERT_EQ(array7[0][0].size(), 2);
ASSERT_EQ(array7[0][1].size(), 2);
ASSERT_EQ(array7[0][2].size(), 2);
ASSERT_EQ(array7[1].size(), 2);
ASSERT_EQ(array7[1][0].size(), 2);
ASSERT_EQ(array7[1][1].size(), 1);
ASSERT_EQ(array7[0][0][0], "foo0");
ASSERT_EQ(array7[0][0][1], "foo1");
ASSERT_EQ(array7[0][1][0], "bar0");
ASSERT_EQ(array7[0][1][1], "bar1");
ASSERT_EQ(array7[0][2][0], "baz0");
ASSERT_EQ(array7[0][2][1], "baz1");
ASSERT_EQ(array7[1][0][0], "zing0");
ASSERT_EQ(array7[1][0][1], "zing1");
ASSERT_EQ(array7[1][1][0], "fred");
}
TEST(TargetCreation, DeduplicateKeys) {
ffi::Map<ffi::String, ffi::Any> config = {
{"kind", ffi::String("llvm")},
{"keys", ffi::Array<ffi::String>{"cpu", "arm_cpu"}},
{"device", ffi::String("arm_cpu")},
};
Target target = Target(config);
TVM_FFI_ICHECK_EQ(target->kind, TargetKind::Get("llvm").value());
TVM_FFI_ICHECK_EQ(target->tag, "");
TVM_FFI_ICHECK_EQ(target->keys.size(), 2U);
TVM_FFI_ICHECK_EQ(target->keys[0], "cpu");
TVM_FFI_ICHECK_EQ(target->keys[1], "arm_cpu");
TVM_FFI_ICHECK_EQ(target->attrs.count("keys"), 0U);
TVM_FFI_ICHECK_EQ(target->GetAttr<ffi::String>("device"), "arm_cpu");
}
TEST(TargetKindRegistry, ListTargetKinds) {
ffi::Array<ffi::String> names = TargetKindRegEntry::ListTargetKinds();
TVM_FFI_ICHECK_EQ(names.empty(), false);
TVM_FFI_ICHECK_EQ(std::count(std::begin(names), std::end(names), "llvm"), 1);
}
TEST(TargetKindRegistry, ListTargetOptions) {
TargetKind llvm = TargetKind::Get("llvm").value();
ffi::Map<ffi::String, ffi::String> attrs = TargetKindRegEntry::ListTargetKindOptions(llvm);
TVM_FFI_ICHECK_EQ(attrs.empty(), false);
}
+58
View File
@@ -0,0 +1,58 @@
/*
* 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.
*/
#include <gtest/gtest.h>
#include <tvm/runtime/logging.h>
#include <tvm/te/operation.h>
TEST(Tensor, Basic) {
using namespace tvm;
using namespace tvm::te;
PrimVar m("m"), n("n"), l("l");
Tensor A = placeholder({m, l}, PrimType::Float(32), "A");
Tensor B = placeholder({n, l}, PrimType::Float(32), "B");
auto C = compute({m, n}, [&](PrimVar i, PrimVar j) { return A[i][j]; }, "C");
Tensor::Slice x = A[n];
}
TEST(Tensor, Reduce) {
using namespace tvm;
using namespace tvm::te;
PrimVar m("m"), n("n"), l("l");
te::Tensor A = te::placeholder({m, l}, PrimType::Float(32), "A");
te::Tensor B = te::placeholder({n, l}, PrimType::Float(32), "B");
IterVar rv = reduce_axis(Range{0, l}, "k");
auto C = te::compute(
{m, n}, [&](PrimVar i, PrimVar j) { return sum(max(1 + A[i][rv] + 1, B[j][rv]), {rv}); },
"C");
}
TEST(Tensor, Indexing) {
using namespace tvm;
using namespace tvm::te;
PrimVar x("x"), y("y");
te::Tensor A = te::placeholder({x, y}, PrimType::Float(32), "A");
}
+196
View File
@@ -0,0 +1,196 @@
/*
* 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.
*/
#include "../../src/runtime/threading_backend.h"
#include <gtest/gtest.h>
#include <tvm/runtime/c_backend_api.h>
#include <tvm/runtime/logging.h>
#include <atomic>
#include <memory>
#include <mutex>
#include <sstream>
#include <thread>
#include <unordered_map>
#include <unordered_set>
constexpr size_t N = 128;
void AtomicCompute(int task_id, size_t n, std::atomic<size_t>* acc, TVMParallelGroupEnv* penv) {
const size_t N_per_task = (n + penv->num_task - 1) / penv->num_task;
for (size_t i = task_id * N_per_task; i < n && i < (task_id + 1) * N_per_task; ++i) {
acc->fetch_add(i, std::memory_order_relaxed);
}
return;
}
class AffinityCheck {
public:
AffinityCheck(uint32_t parent_id, int max_concurrency, std::atomic<size_t>* acc)
: id_(parent_id), max_concurrency_(max_concurrency), acc_(acc) {}
void Compute(int task_id, size_t n, TVMParallelGroupEnv* penv) {
AtomicCompute(task_id, n, acc_, penv);
}
int GetComputeResult() { return acc_->load(std::memory_order_relaxed); }
void GetAffinity(int task_id) {
#if defined(__linux__)
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
std::lock_guard<std::mutex> lock(mutex_);
thread_affinity_[task_id] = cpuset;
// Printing the current thread CPU affinity.
std::ostringstream str;
for (int i = 0; i < max_concurrency_; i++) {
if (CPU_ISSET(i, &cpuset)) {
str << i << ",";
}
}
#endif
}
bool VerifyAffinity(const std::vector<uint32_t>& cpus) {
#if defined(__linux__)
std::unordered_set<uint32_t> uset;
cpu_set_t cpu_mask;
CPU_ZERO(&cpu_mask);
for (auto x : cpus) {
CPU_SET(x, &cpu_mask);
uset.insert(x);
}
for (auto x : thread_affinity_) {
if (!CPU_EQUAL(&cpu_mask, &x.second)) {
bool cpu_find = false;
for (auto cpu : uset) {
CPU_ISSET(cpu, &x.second);
uset.erase(cpu);
cpu_find = true;
break;
}
if (!cpu_find) return false;
}
}
#endif
return true;
}
private:
uint32_t id_;
int max_concurrency_;
std::atomic<size_t>* acc_;
std::mutex mutex_;
#if defined(__linux__)
std::unordered_map<int, cpu_set_t> thread_affinity_;
#endif
};
static FTVMParallelLambda atomic_add_task_id = [](int task_id, TVMParallelGroupEnv* penv,
void* cdata) -> int {
auto* data = reinterpret_cast<std::atomic<size_t>*>(cdata);
AtomicCompute(task_id, N, data, penv);
return 0;
};
static FTVMParallelLambda affinity_check_task_id = [](int task_id, TVMParallelGroupEnv* penv,
void* cdata) -> int {
auto* data = reinterpret_cast<AffinityCheck*>(cdata);
data->Compute(task_id, N, penv);
data->GetAffinity(task_id);
return 0;
};
TEST(ThreadingBackend, TVMBackendParallelLaunch) {
std::atomic<size_t> acc(0);
TVMBackendParallelLaunch(atomic_add_task_id, &acc, 0);
EXPECT_EQ(acc.load(std::memory_order_relaxed), N * (N - 1) / 2);
}
TEST(ThreadingBackend, TVMBackendParallelLaunchMultipleThreads) {
// TODO(tulloch) use parameterised tests when available.
size_t num_jobs_per_thread = 3;
size_t max_num_threads = 2;
for (size_t num_threads = 1; num_threads < max_num_threads; ++num_threads) {
std::vector<std::unique_ptr<std::thread>> ts;
for (size_t i = 0; i < num_threads; ++i) {
ts.emplace_back(new std::thread([&]() {
for (size_t j = 0; j < num_jobs_per_thread; ++j) {
std::atomic<size_t> acc(0);
TVMBackendParallelLaunch(atomic_add_task_id, &acc, 0);
EXPECT_EQ(acc.load(std::memory_order_relaxed), N * (N - 1) / 2);
}
}));
}
for (auto& t : ts) {
t->join();
}
}
}
TEST(ThreadingBackend, TVMBackendAffinityConfigure) {
int max_concurrency = tvm::runtime::threading::MaxConcurrency();
std::vector<std::unique_ptr<std::thread>> ts;
// Returning as there is only one CPU available.
if (max_concurrency <= 1) {
return;
}
// Creating two threads to test the 'CPU list affinity' feature.
const int threads_num = 2;
// Getting the maximum number of CPUs which are available to each thread.
const int cpus_num_per_thread = max_concurrency / threads_num;
// Testing two mode of affinity.,
std::vector<tvm::runtime::threading::ThreadGroup::AffinityMode> modes = {
tvm::runtime::threading::ThreadGroup::kSpecifyOneCorePerThread,
tvm::runtime::threading::ThreadGroup::kSpecifyThreadShareAllCore};
for (auto mode : modes) {
for (int thread_pool_idx = 0; thread_pool_idx < threads_num; thread_pool_idx++) {
ts.emplace_back(new std::thread(
[&](int thread_pool_index, int sys_max_concurrency,
tvm::runtime::threading::ThreadGroup::AffinityMode affinity_mode) {
std::atomic<size_t> acc(0);
AffinityCheck ac(thread_pool_index, sys_max_concurrency, &acc);
std::vector<unsigned int> cpus;
for (int k = 0; k < cpus_num_per_thread; k++) {
cpus.push_back(thread_pool_index * cpus_num_per_thread + k);
}
tvm::runtime::threading ::Configure(affinity_mode, 0, cpus);
TVMBackendParallelLaunch(affinity_check_task_id, &ac, 0);
EXPECT_EQ(ac.GetComputeResult(), N * (N - 1) / 2);
EXPECT_EQ(ac.VerifyAffinity(cpus), true);
},
thread_pool_idx, max_concurrency, mode));
}
}
for (auto& t : ts) {
t->join();
}
}
TEST(ThreadingBackend, TVMBackendParallelForWithThreadingBackend) {
int n = 100;
std::vector<int> vec(/*size=*/n, /*value=*/0);
tvm::runtime::parallel_for_with_threading_backend([&vec](int i) { vec[i] = i; }, 0, n);
for (int i = 0; i < n; ++i) {
EXPECT_EQ(vec[i], i);
}
}
+36
View File
@@ -0,0 +1,36 @@
/*
* 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.
*/
#include <gtest/gtest.h>
#include <tvm/runtime/logging.h>
#include <tvm/te/operation.h>
#include <tvm/tirx/analysis.h>
#include <tvm/tirx/builtin.h>
TEST(SimplePasses, SideEffect) {
using namespace tvm;
auto buf = tirx::decl_buffer({16}, PrimType::Float(32));
auto i = tirx::PrimVar("i", PrimType::Int(32));
TVM_FFI_ICHECK(tirx::SideEffect(tirx::BufferLoad(buf, {i})) == tirx::CallEffectKind::kReadState);
TVM_FFI_ICHECK(tirx::SideEffect(exp(tirx::Cast(PrimType::Float(32), i + 1))) ==
tirx::CallEffectKind::kPure);
TVM_FFI_ICHECK(tirx::SideEffect(tvm::Call(PrimType::Void(), tirx::builtin::tvm_storage_sync(), {})
.as_or_throw<PrimExpr>()) ==
tirx::CallEffectKind::kUpdateState);
}
+212
View File
@@ -0,0 +1,212 @@
/*
* 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.
*/
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <tvm/ffi/dtype.h>
#include <tvm/script/printer/printer.h>
#include <tvm/tirx/builtin.h>
#include <tvm/tirx/expr.h>
#ifdef TVM_LLVM_VERSION
#include <llvm/IR/Intrinsics.h>
#endif
#include "../../src/script/printer/utils.h"
using ::testing::HasSubstr;
// ---------
// Prim Type
// ---------
TEST(ScalablePrimType, TestCreateScalableType) {
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLInt, 32, 4);
ASSERT_EQ(scalable_type.code(), kDLInt);
ASSERT_EQ(scalable_type.bits(), 32);
ASSERT_EQ(scalable_type.VScaleFactor(), 4);
ASSERT_TRUE(scalable_type.IsScalableVector());
ASSERT_TRUE(scalable_type.IsScalableVector() || scalable_type.IsFixedLengthVector());
}
TEST(ScalablePrimType, TestScalableWithBits) {
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLInt, 1, 8);
scalable_type = scalable_type.WithBits(32);
ASSERT_EQ(scalable_type.bits(), 32);
ASSERT_TRUE(scalable_type.IsScalableVector());
ASSERT_TRUE(scalable_type.IsScalableVector() || scalable_type.IsFixedLengthVector());
}
TEST(ScalablePrimType, TestScalableWithVscaleFactor) {
tvm::PrimType type = tvm::PrimType::Int(32);
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(type.code(), type.bits(), 4);
ASSERT_EQ(scalable_type.VScaleFactor(), 4);
ASSERT_TRUE(scalable_type.IsScalableVector());
ASSERT_TRUE(scalable_type.IsScalableVector() || scalable_type.IsFixedLengthVector());
}
TEST(ScalablePrimType, TestAssignScalablePrimType) {
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLInt, 32, 2);
tvm::PrimType scalable_type_copy = scalable_type;
ASSERT_TRUE(scalable_type_copy.IsScalableVector());
ASSERT_TRUE(scalable_type_copy.IsScalableVector() || scalable_type_copy.IsFixedLengthVector());
}
TEST(ScalablePrimType, TestScalablePrimTypeEquality) {
ASSERT_TRUE(tvm::PrimType::ScalableVector(kDLInt, 32, 4) ==
tvm::PrimType::ScalableVector(kDLInt, 32, 4));
}
TEST(ScalablePrimType, TestScalablePrimTypeAndNonScalablePrimTypeInequality) {
ASSERT_FALSE(tvm::PrimType::ScalableVector(kDLInt, 32, 4) == tvm::PrimType::Int(32, 4));
}
TEST(ScalablePrimType, TestIsScalar) {
ASSERT_FALSE(tvm::PrimType::ScalableVector(kDLInt, 32, 4).IsScalar());
ASSERT_TRUE(tvm::PrimType::Int(32).IsScalar());
ASSERT_FALSE(tvm::PrimType::Int(32, 4).IsScalar());
ASSERT_FALSE(tvm::PrimType::Void().IsScalar());
}
TEST(ScalablePrimType, TestScalablePrimTypeToString) {
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLInt, 32, 4);
EXPECT_EQ(tvm::ffi::DLDataTypeToString(scalable_type->dtype), "int32xvscalex4");
}
TEST(ScalablePrimType, TestStringToScalablePrimType) {
std::string scalable_type_str = "int32xvscalex4";
EXPECT_EQ(tvm::PrimType(tvm::ffi::StringToDLDataType(scalable_type_str)),
tvm::PrimType::ScalableVector(kDLInt, 32, 4));
}
TEST(ScalablePrimType, TestInvalidStringToScalablePrimType) {
std::string scalable_type_str = "int32x4xvscale";
EXPECT_THROW(
{
try {
tvm::ffi::StringToDLDataType(scalable_type_str);
} catch (const tvm::ffi::Error& e) {
EXPECT_THAT(e.what(), HasSubstr("unknown dtype `int32x4xvscale`"));
throw;
}
},
tvm::ffi::Error);
}
TEST(ScalablePrimType, TestGetScalableVectorBytes) {
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLInt, 32, 4);
EXPECT_THROW(
{
try {
int bytes = (scalable_type.bits() * scalable_type.lanes() + 7) / 8;
static_cast<void>(bytes);
} catch (const tvm::ffi::Error& e) {
EXPECT_THAT(e.what(),
HasSubstr("Can't fetch the lanes of a scalable vector at a compile time"));
throw;
}
},
tvm::ffi::Error);
}
TEST(ScalablePrimType, TestScalablePrimTypeInvalidLanesError) {
EXPECT_THROW(
{
try {
tvm::PrimType::ScalableVector(kDLFloat, 62, 1);
} catch (const tvm::ffi::Error& e) {
EXPECT_THAT(e.what(), HasSubstr("Invalid value for vscale factor"));
throw;
}
},
tvm::ffi::Error);
}
TEST(ScalablePrimType, TestScalablePrimTypeInvalidVscaleFactorAccess) {
tvm::PrimType fixed_length_type = tvm::PrimType::Float(32, 4);
ASSERT_TRUE(fixed_length_type.IsFixedLengthVector());
ASSERT_TRUE(fixed_length_type.IsScalableVector() || fixed_length_type.IsFixedLengthVector());
EXPECT_THROW(
{
try {
fixed_length_type.VScaleFactor();
} catch (const tvm::ffi::Error& e) {
EXPECT_THAT(e.what(), HasSubstr("A fixed length vector doesn't have a vscale factor"));
throw;
}
},
tvm::ffi::Error);
}
TEST(ScalablePrimType, TestScalablePrimTypeInvalidLanesAccess) {
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLFloat, 32, 4);
EXPECT_THROW(
{
try {
scalable_type.lanes();
} catch (const tvm::ffi::Error& e) {
EXPECT_THAT(e.what(),
HasSubstr("Can't fetch the lanes of a scalable vector at a compile time"));
throw;
}
},
tvm::ffi::Error);
}
TEST(ScalablePrimType, TestScalableBool) {
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLBool, 8, 4);
ASSERT_EQ(scalable_type.code(), kDLBool);
ASSERT_EQ(scalable_type.bits(), 8);
ASSERT_EQ(scalable_type.VScaleFactor(), 4);
ASSERT_TRUE(scalable_type.IsScalableVector());
}
TEST(ScalablePrimType, TestScalableUInt) {
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLUInt, 1, 4);
ASSERT_EQ(scalable_type.code(), kDLUInt);
ASSERT_EQ(scalable_type.bits(), 1);
ASSERT_EQ(scalable_type.VScaleFactor(), 4);
ASSERT_TRUE(scalable_type.IsScalableVector());
}
// -----------
// Integration
// -----------
#ifdef TVM_LLVM_VERSION
TEST(ScalablePrimType, TestScalableIntrinCall) {
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLInt, 32, 4);
tvm::Call call = tvm::Call(scalable_type, tvm::tirx::builtin::call_llvm_intrin(),
#if TVM_LLVM_VERSION >= 200
{tvm::IntImm::Int32(::llvm::Intrinsic::stepvector)});
#else
{tvm::IntImm::Int32(::llvm::Intrinsic::experimental_stepvector)});
#endif
ASSERT_EQ(call->ty.as_or_throw<tvm::PrimType>(), scalable_type);
ASSERT_EQ(tvm::Script(call),
#if TVM_LLVM_VERSION >= 200
"T.call_llvm_intrin(\"int32xvscalex4\", \"llvm.stepvector\")");
#else
"T.call_llvm_intrin(\"int32xvscalex4\", \"llvm.experimental.stepvector\")");
#endif
}
#endif
TEST(ScalablePrimType, TestTIRScriptScalableDtype2Str) {
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLInt, 32, 4);
ASSERT_EQ(tvm::script::printer::DType2Str(scalable_type->dtype), "int32xvscalex4");
}
+33
View File
@@ -0,0 +1,33 @@
/*
* 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.
*/
#include <gtest/gtest.h>
#include <tvm/te/operation.h>
#include <tvm/topi/elemwise.h>
namespace tvm {
namespace topi {
TEST(Tensor, Basic) {
using namespace tvm;
PrimVar m("m"), l("l");
Tensor A = placeholder({m, l}, PrimType::Float(32), "A");
auto C = topi::exp(A);
}
} // namespace topi
} // namespace tvm