blob: df8ee11cc3ef70f74eaca07c0ac9928e0567a897 [file]
/*
* 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/list.h>
#include <tvm/ffi/container/map.h>
#include <tvm/ffi/error.h>
#include <tvm/ffi/extra/structural_visit.h>
#include <tvm/ffi/object.h>
#include <tvm/ffi/string.h>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include "../testing_object.h"
namespace {
using namespace tvm::ffi;
using namespace tvm::ffi::testing;
class TestVisitorObj : public StructuralVisitorObj {
public:
TestVisitorObj() : StructuralVisitorObj(VTable()) {}
std::vector<ObjectRef> visited;
std::vector<TVMFFIDefRegionKind> modes;
ObjectRef interrupt_on;
private:
static const StructuralVisitorVTable* VTable() {
static const StructuralVisitorVTable vtable{&TestVisitorObj::DispatchVisit};
return &vtable;
}
static TVMFFIAny DispatchVisit(StructuralVisitorObj* self, AnyView value) noexcept {
return static_cast<TestVisitorObj*>(self)->VisitImpl(value);
}
// NOLINTNEXTLINE(bugprone-exception-escape)
TVMFFIAny VisitImpl(AnyView value) noexcept {
if (value.type_index() >= TypeIndex::kTVMFFIStaticObjectBegin) {
ObjectRef value_ref = value.cast<ObjectRef>();
visited.push_back(value_ref);
modes.push_back(def_region_mode_);
if (interrupt_on.defined() && value_ref.same_as(interrupt_on)) {
Expected<Optional<VisitInterrupt>> interrupt =
Optional<VisitInterrupt>(VisitInterrupt(String("stop")));
return details::ExpectedUnsafe::MoveToTVMFFIAny(std::move(interrupt));
}
}
return details::ExpectedUnsafe::MoveToTVMFFIAny(DefaultVisitExpected(value));
}
};
StructuralVisitor MakeTestVisitor() { return StructuralVisitor(make_object<TestVisitorObj>()); }
TestVisitorObj* AsTestVisitor(const StructuralVisitor& visitor) {
return static_cast<TestVisitorObj*>(visitor.get());
}
void SetInterrupt(const StructuralVisitor& visitor, const ObjectRef& value) {
TestVisitorObj* test_visitor = AsTestVisitor(visitor);
test_visitor->interrupt_on = value;
}
void ExpectTrace(const std::vector<std::string>& actual,
std::initializer_list<const char*> expected) {
ASSERT_EQ(actual.size(), expected.size());
size_t i = 0;
for (const char* item : expected) {
EXPECT_EQ(actual[i], item);
++i;
}
}
// ---------------------------------------------------------------------------
// StructuralVisitor behavior.
// ---------------------------------------------------------------------------
TEST(StructuralVisitor, RecordsNode) {
ObjectRef leaf = TVar("leaf");
StructuralVisitor visitor = MakeTestVisitor();
Expected<Optional<VisitInterrupt>> result = visitor->VisitExpected(leaf);
ASSERT_TRUE(result.is_ok());
EXPECT_FALSE(result.value().has_value());
ASSERT_EQ(AsTestVisitor(visitor)->visited.size(), 1U);
EXPECT_TRUE(AsTestVisitor(visitor)->visited[0].same_as(leaf));
}
TEST(StructuralVisitor, PropagatesInterrupt) {
ObjectRef leaf = TVar("leaf");
StructuralVisitor visitor = MakeTestVisitor();
SetInterrupt(visitor, leaf);
Expected<Optional<VisitInterrupt>> result = visitor->VisitExpected(leaf);
ASSERT_TRUE(result.is_ok());
ASSERT_TRUE(result.value().has_value());
EXPECT_EQ(result.value().value()->value.cast<String>(), "stop");
}
TEST(StructuralVisitor, TraversesPair) {
ObjectRef lhs = TVar("lhs");
ObjectRef rhs = TVar("rhs");
ObjectRef root = TPair(lhs, rhs);
StructuralVisitor visitor = MakeTestVisitor();
Optional<VisitInterrupt> result = visitor->DefaultVisit(root);
EXPECT_FALSE(result.has_value());
ASSERT_EQ(AsTestVisitor(visitor)->visited.size(), 2U);
EXPECT_TRUE(AsTestVisitor(visitor)->visited[0].same_as(lhs));
EXPECT_TRUE(AsTestVisitor(visitor)->visited[1].same_as(rhs));
}
TEST(StructuralVisitor, TraversesFunction) {
TVar param("x");
ObjectRef body_value = TInt(1);
Array<TVar> params = {param};
Array<ObjectRef> body = {body_value};
ObjectRef root = TFunc(params, body, String("ignored function comment"));
StructuralVisitor visitor = MakeTestVisitor();
Optional<VisitInterrupt> result = visitor->DefaultVisit(root);
EXPECT_FALSE(result.has_value());
TestVisitorObj* test_visitor = AsTestVisitor(visitor);
ASSERT_EQ(test_visitor->visited.size(), 4U);
EXPECT_TRUE(test_visitor->visited[0].same_as(params));
EXPECT_EQ(test_visitor->modes[0], kTVMFFIDefRegionKindRecursive);
EXPECT_TRUE(test_visitor->visited[1].same_as(param));
EXPECT_EQ(test_visitor->modes[1], kTVMFFIDefRegionKindRecursive);
EXPECT_TRUE(test_visitor->visited[2].same_as(body));
EXPECT_EQ(test_visitor->modes[2], kTVMFFIDefRegionKindNone);
EXPECT_TRUE(test_visitor->visited[3].same_as(body_value));
EXPECT_EQ(test_visitor->modes[3], kTVMFFIDefRegionKindNone);
}
TEST(StructuralVisitor, StopsOnInterrupt) {
ObjectRef lhs = TVar("lhs");
ObjectRef rhs = TVar("rhs");
ObjectRef root = TPair(lhs, rhs);
StructuralVisitor visitor = MakeTestVisitor();
SetInterrupt(visitor, lhs);
Optional<VisitInterrupt> result = visitor->DefaultVisit(root);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.value()->value.cast<String>(), "stop");
ASSERT_EQ(AsTestVisitor(visitor)->visited.size(), 1U);
EXPECT_TRUE(AsTestVisitor(visitor)->visited[0].same_as(lhs));
}
TEST(StructuralVisitor, TraversesArray) {
ObjectRef lhs = TVar("lhs");
ObjectRef rhs = TVar("rhs");
Array<ObjectRef> root = {lhs, rhs};
StructuralVisitor visitor = MakeTestVisitor();
Optional<VisitInterrupt> result = visitor->DefaultVisit(root);
EXPECT_FALSE(result.has_value());
ASSERT_EQ(AsTestVisitor(visitor)->visited.size(), 2U);
EXPECT_TRUE(AsTestVisitor(visitor)->visited[0].same_as(lhs));
EXPECT_TRUE(AsTestVisitor(visitor)->visited[1].same_as(rhs));
}
TEST(StructuralVisitor, TraversesMap) {
ObjectRef key = TVar("key");
ObjectRef value = TVar("value");
Map<Any, Any> root{{key, value}};
StructuralVisitor visitor = MakeTestVisitor();
Optional<VisitInterrupt> result = visitor->DefaultVisit(root);
EXPECT_FALSE(result.has_value());
ASSERT_EQ(AsTestVisitor(visitor)->visited.size(), 2U);
EXPECT_TRUE(AsTestVisitor(visitor)->visited[0].same_as(key));
EXPECT_TRUE(AsTestVisitor(visitor)->visited[1].same_as(value));
}
TEST(StructuralVisitor, UsesFuncHook) {
TVar param("x");
ObjectRef body_value = TInt(1);
Array<TVar> params = {param};
Array<ObjectRef> body = {body_value};
ObjectRef root = TFunc(params, body, String("ignored function comment"));
StructuralVisitor visitor = MakeTestVisitor();
Optional<VisitInterrupt> result = visitor->Visit(root);
EXPECT_FALSE(result.has_value());
TestVisitorObj* test_visitor = AsTestVisitor(visitor);
ASSERT_EQ(test_visitor->visited.size(), 5U);
EXPECT_TRUE(test_visitor->visited[0].same_as(root));
EXPECT_EQ(test_visitor->modes[0], kTVMFFIDefRegionKindNone);
EXPECT_TRUE(test_visitor->visited[1].same_as(params));
EXPECT_EQ(test_visitor->modes[1], kTVMFFIDefRegionKindRecursive);
EXPECT_TRUE(test_visitor->visited[2].same_as(param));
EXPECT_EQ(test_visitor->modes[2], kTVMFFIDefRegionKindRecursive);
EXPECT_TRUE(test_visitor->visited[3].same_as(body));
EXPECT_EQ(test_visitor->modes[3], kTVMFFIDefRegionKindNone);
EXPECT_TRUE(test_visitor->visited[4].same_as(body_value));
EXPECT_EQ(test_visitor->modes[4], kTVMFFIDefRegionKindNone);
EXPECT_EQ(test_visitor->def_region_kind(), kTVMFFIDefRegionKindNone);
}
TEST(StructuralVisitor, RestoresFuncDefRegion) {
TVar param("x");
Array<TVar> params = {param};
Array<ObjectRef> body = {TInt(1)};
ObjectRef root = TFunc(params, body, String("ignored function comment"));
StructuralVisitor visitor = MakeTestVisitor();
SetInterrupt(visitor, param);
Optional<VisitInterrupt> result = visitor->Visit(root);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.value()->value.cast<String>(), "stop");
TestVisitorObj* test_visitor = AsTestVisitor(visitor);
ASSERT_EQ(test_visitor->visited.size(), 3U);
EXPECT_TRUE(test_visitor->visited[0].same_as(root));
EXPECT_EQ(test_visitor->modes[0], kTVMFFIDefRegionKindNone);
EXPECT_TRUE(test_visitor->visited[1].same_as(params));
EXPECT_EQ(test_visitor->modes[1], kTVMFFIDefRegionKindRecursive);
EXPECT_TRUE(test_visitor->visited[2].same_as(param));
EXPECT_EQ(test_visitor->modes[2], kTVMFFIDefRegionKindRecursive);
EXPECT_EQ(test_visitor->def_region_kind(), kTVMFFIDefRegionKindNone);
}
// ---------------------------------------------------------------------------
// StructuralWalk behavior.
// ---------------------------------------------------------------------------
TEST(StructuralVisitor, WalkSkipsChildren) {
ObjectRef lhs = TVar("lhs");
ObjectRef rhs = TVar("rhs");
ObjectRef root = TPair(lhs, rhs);
std::vector<std::string> visited;
Optional<VisitInterrupt> result = StructuralWalk<WalkOrder::kPreOrder>(
root, [&](const ObjectRef& node) -> Expected<WalkResult> {
if (node.same_as(root)) {
visited.emplace_back("pair");
return WalkResult::Skip();
}
visited.emplace_back("child");
return WalkResult::Advance();
});
EXPECT_FALSE(result.has_value());
ExpectTrace(visited, {"pair"});
}
TEST(StructuralVisitor, WalkPostOrder) {
ObjectRef lhs = TVar("lhs");
ObjectRef rhs = TVar("rhs");
ObjectRef root = TPair(lhs, rhs);
std::vector<std::string> visited;
Optional<VisitInterrupt> result = StructuralWalk<WalkOrder::kPostOrder>(
root, [&](const ObjectRef& node) -> Expected<WalkResult> {
if (node.same_as(lhs)) {
visited.emplace_back("lhs");
} else if (node.same_as(rhs)) {
visited.emplace_back("rhs");
} else if (node.same_as(root)) {
visited.emplace_back("pair");
}
return WalkResult::Advance();
});
EXPECT_FALSE(result.has_value());
ExpectTrace(visited, {"lhs", "rhs", "pair"});
}
TEST(StructuralVisitor, WalkInterrupts) {
ObjectRef lhs = TVar("lhs");
ObjectRef rhs = TVar("rhs");
ObjectRef root = TPair(lhs, rhs);
std::vector<std::string> visited;
Optional<VisitInterrupt> result = StructuralWalk<WalkOrder::kPreOrder>(
root, [&](const ObjectRef& node) -> Expected<WalkResult> {
if (node.same_as(lhs)) {
visited.emplace_back("lhs");
return WalkResult::Interrupt(VisitInterrupt(String("found lhs")));
}
if (node.same_as(rhs)) {
visited.emplace_back("rhs");
}
return WalkResult::Advance();
});
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.value()->value.cast<String>(), "found lhs");
ExpectTrace(visited, {"lhs"});
}
TEST(StructuralVisitor, WalkVisitsPOD) {
int64_t seen = 0;
Optional<VisitInterrupt> result =
StructuralWalk<WalkOrder::kPreOrder>(42, [&](int64_t value) -> Expected<WalkResult> {
seen = value;
return WalkResult::Advance();
});
EXPECT_FALSE(result.has_value());
EXPECT_EQ(seen, 42);
}
TEST(StructuralVisitor, WalkVisitsObjectPtr) {
TVar root("x");
std::vector<std::string> visited;
Optional<VisitInterrupt> result =
StructuralWalk<WalkOrder::kPreOrder>(root, [&](const TVarObj* var) -> Expected<WalkResult> {
visited.emplace_back(var->name);
return WalkResult::Advance();
});
EXPECT_FALSE(result.has_value());
ExpectTrace(visited, {"x"});
}
TEST(StructuralVisitor, WalkReceivesDefRegionKind) {
TVar x("x");
TVar y("y");
ObjectRef root = TFunc({x}, {x, y}, String("ignored function comment"));
std::vector<std::string> use_vars;
Optional<VisitInterrupt> result = StructuralWalk<WalkOrder::kPreOrder>(
root, [&](const TVarObj* var, TVMFFIDefRegionKind kind) -> Expected<WalkResult> {
if (kind == kTVMFFIDefRegionKindNone) {
use_vars.emplace_back(var->name);
}
return WalkResult::Advance();
});
EXPECT_FALSE(result.has_value());
ExpectTrace(use_vars, {"x", "y"});
}
TEST(StructuralVisitor, WalkReturnsError) {
ObjectRef lhs = TVar("lhs");
ObjectRef rhs = TVar("rhs");
ObjectRef root = TPair(lhs, rhs);
Expected<Optional<VisitInterrupt>> result = StructuralWalkExpected<WalkOrder::kPreOrder>(
root, [&](const ObjectRef& node) -> Expected<WalkResult> {
if (node.same_as(lhs)) {
return Unexpected(Error("ValueError", "walk callback failed", ""));
}
return WalkResult::Advance();
});
ASSERT_TRUE(result.is_err());
EXPECT_EQ(result.error().kind(), "ValueError");
EXPECT_EQ(result.error().message(), "walk callback failed");
}
TEST(StructuralVisitor, WalkCatchesError) {
ObjectRef root = TVar("root");
Expected<Optional<VisitInterrupt>> result = StructuralWalkExpected<WalkOrder::kPreOrder>(
root, [&](const ObjectRef&) -> Expected<WalkResult> {
TVM_FFI_THROW(ValueError) << "walk callback threw";
});
ASSERT_TRUE(result.is_err());
EXPECT_EQ(result.error().kind(), "ValueError");
EXPECT_EQ(result.error().message(), "walk callback threw");
}
TEST(StructuralVisitor, WalkFirstMatch) {
ObjectRef lhs = TVar("lhs");
ObjectRef rhs = TVar("rhs");
List<ObjectRef> list = {lhs};
Array<ObjectRef> root = {list, rhs};
std::vector<std::string> trace;
Optional<VisitInterrupt> result = StructuralWalk<WalkOrder::kPreOrder>(
root,
[&](const Array<ObjectRef>&) -> Expected<WalkResult> {
trace.emplace_back("array");
return WalkResult::Advance();
},
[&](const List<ObjectRef>&) -> Expected<WalkResult> {
trace.emplace_back("list");
return WalkResult::Advance();
},
[&](const ObjectRef&) -> Expected<WalkResult> {
trace.emplace_back("object");
return WalkResult::Advance();
});
EXPECT_FALSE(result.has_value());
ExpectTrace(trace, {"array", "list", "object", "object"});
}
TEST(StructuralVisitor, WalkFuncProgram) {
TVar m("m");
TVar n("n");
TVar acc("acc");
ObjectRef func =
TFunc({m, n}, {TInt(7), acc, TPair(m, TInt(1))}, String("ignored function comment"));
Array<Any> root = {func, String("metadata"), nullptr};
std::vector<std::string> trace;
Optional<VisitInterrupt> result = StructuralWalk<WalkOrder::kPreOrder>(
root,
[&](const TFuncObj*) -> Expected<WalkResult> {
trace.emplace_back("func*");
return WalkResult::Advance();
},
[&](const TVarObj* var) -> Expected<WalkResult> {
trace.emplace_back("var*:" + var->name);
return WalkResult::Advance();
},
[&](int64_t value) -> Expected<WalkResult> {
trace.emplace_back("int:" + std::to_string(value));
return WalkResult::Advance();
},
[&](const ObjectRef&) -> Expected<WalkResult> {
trace.emplace_back("object-ref");
return WalkResult::Advance();
},
[&](AnyView) -> Expected<WalkResult> {
trace.emplace_back("any-view");
return WalkResult::Advance();
});
EXPECT_FALSE(result.has_value());
ExpectTrace(trace,
{"object-ref", "func*", "object-ref", "var*:m", "var*:n", "object-ref", "object-ref",
"int:7", "var*:acc", "object-ref", "var*:m", "object-ref", "int:1", "object-ref"});
}
TEST(StructuralVisitor, WalkAnyFallback) {
Array<Any> root = {String("metadata"), nullptr};
std::vector<std::string> trace;
Optional<VisitInterrupt> result = StructuralWalk<WalkOrder::kPreOrder>(
root,
[&](const ObjectRef&) -> Expected<WalkResult> {
trace.emplace_back("object-ref");
return WalkResult::Advance();
},
[&](const Any& value) -> Expected<WalkResult> {
trace.emplace_back(value == nullptr ? "any:none" : "any:value");
return WalkResult::Advance();
});
EXPECT_FALSE(result.has_value());
ExpectTrace(trace, {"object-ref", "object-ref"});
}
} // namespace