blob: 47b2d0fcc7c7e8bb95dfa2d4e854cf237c9ae6fb [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/any.h>
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/container/map.h>
#include <tvm/ffi/container/tensor.h>
#include <tvm/ffi/container/tuple.h>
#include <tvm/ffi/container/variant.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/registry.h>
// test-cases used in example code
namespace {
void ExampleAny() {
namespace ffi = tvm::ffi;
// Create an Any from various types
ffi::Any int_value = 42;
ffi::Any float_value = 3.14;
ffi::Any string_value = "hello world";
// AnyView provides a lightweight view without ownership
ffi::AnyView view = int_value;
// we can cast Any/AnyView to a specific type
int extracted = view.cast<int>();
EXPECT_EQ(extracted, 42);
// If we are not sure about the type
// we can use as to get an optional value
std::optional<int> maybe_int = view.as<int>();
if (maybe_int.has_value()) {
EXPECT_EQ(maybe_int.value(), 42);
}
// Try cast is another version that will try to run the type
// conversion even if the type does not exactly match
std::optional<int> maybe_int_try = view.try_cast<int>();
if (maybe_int_try.has_value()) {
EXPECT_EQ(maybe_int_try.value(), 42);
}
}
TEST(Example, Any) { ExampleAny(); }
void ExampleFunctionFromPacked() {
namespace ffi = tvm::ffi;
// Create a function from a typed lambda
ffi::Function fadd1 =
ffi::Function::FromPacked([](const ffi::AnyView* args, int32_t num_args, ffi::Any* rv) {
TVM_FFI_ICHECK_EQ(num_args, 1);
int a = args[0].cast<int>();
*rv = a + 1;
});
int b = fadd1(1).cast<int>();
EXPECT_EQ(b, 2);
}
void ExampleFunctionFromTyped() {
namespace ffi = tvm::ffi;
// Create a function from a typed lambda
ffi::Function fadd1 = ffi::Function::FromTyped([](const int a) -> int { return a + 1; });
int b = fadd1(1).cast<int>();
EXPECT_EQ(b, 2);
}
void ExampleFunctionPassFunction() {
namespace ffi = tvm::ffi;
// Create a function from a typed lambda
ffi::Function fapply = ffi::Function::FromTyped(
// NOLINTNEXTLINE(performance-unnecessary-value-param)
[](const ffi::Function f, ffi::Any param) { return f(param.cast<int>()); });
ffi::Function fadd1 = ffi::Function::FromTyped( //
[](const int a) -> int { return a + 1; });
int b = fapply(fadd1, 2).cast<int>();
EXPECT_EQ(b, 3);
}
void ExamplegGlobalFunctionRegistry() {
namespace ffi = tvm::ffi;
ffi::reflection::GlobalDef().def("xyz.add1", [](const int a) -> int { return a + 1; });
ffi::Function fadd1 = ffi::Function::GetGlobalRequired("xyz.add1");
int b = fadd1(1).cast<int>();
EXPECT_EQ(b, 2);
}
void FuncThrowError() {
namespace ffi = tvm::ffi;
TVM_FFI_THROW(TypeError) << "test0";
}
void ExampleErrorHandling() {
namespace ffi = tvm::ffi;
try {
FuncThrowError();
} catch (const ffi::Error& e) {
EXPECT_EQ(e.kind(), "TypeError");
EXPECT_EQ(e.message(), "test0");
std::cout << e.TracebackMostRecentCallLast() << std::endl;
}
}
TEST(Example, Function) {
ExampleFunctionFromPacked();
ExampleFunctionFromTyped();
ExampleFunctionPassFunction();
ExamplegGlobalFunctionRegistry();
ExampleErrorHandling();
}
struct CPUNDAlloc {
void AllocData(DLTensor* tensor) { tensor->data = malloc(tvm::ffi::GetDataSize(*tensor)); }
void FreeData(DLTensor* tensor) { free(tensor->data); }
};
void ExampleTensor() {
namespace ffi = tvm::ffi;
ffi::Shape shape = {1, 2, 3};
DLDataType dtype = {kDLFloat, 32, 1};
DLDevice device = {kDLCPU, 0};
ffi::Tensor tensor = ffi::Tensor::FromNDAlloc(CPUNDAlloc(), shape, dtype, device);
}
void ExampleTensorDLPack() {
namespace ffi = tvm::ffi;
ffi::Shape shape = {1, 2, 3};
DLDataType dtype = {kDLFloat, 32, 1};
DLDevice device = {kDLCPU, 0};
ffi::Tensor tensor = ffi::Tensor::FromNDAlloc(CPUNDAlloc(), shape, dtype, device);
// convert to DLManagedTensorVersioned
DLManagedTensorVersioned* dlpack = tensor.ToDLPackVersioned();
// load back from DLManagedTensorVersioned
ffi::Tensor tensor2 = ffi::Tensor::FromDLPackVersioned(dlpack);
}
TEST(Example, Tensor) {
ExampleTensor();
ExampleTensorDLPack();
}
void ExampleString() {
namespace ffi = tvm::ffi;
ffi::String str = "hello world";
EXPECT_EQ(str.size(), 11);
std::string std_str = str;
EXPECT_EQ(std_str, "hello world");
}
TEST(Example, String) { ExampleString(); }
void ExampleArray() {
namespace ffi = tvm::ffi;
ffi::Array<int> numbers = {1, 2, 3};
EXPECT_EQ(numbers.size(), 3);
EXPECT_EQ(numbers[0], 1);
// NOLINTNEXTLINE(performance-unnecessary-value-param)
ffi::Function head = ffi::Function::FromTyped([](const ffi::Array<int> a) { return a[0]; });
EXPECT_EQ(head(numbers).cast<int>(), 1);
try {
// throw an error because 2.2 is not int
head(ffi::Array<ffi::Any>({1, 2.2}));
} catch (const ffi::Error& e) {
EXPECT_EQ(e.kind(), "TypeError");
}
}
void ExampleTuple() {
namespace ffi = tvm::ffi;
ffi::Tuple<int, ffi::String, bool> tup(42, "hello", true);
EXPECT_EQ(tup.get<0>(), 42);
EXPECT_EQ(tup.get<1>(), "hello");
EXPECT_EQ(tup.get<2>(), true);
}
TEST(Example, Array) {
ExampleArray();
ExampleTuple();
}
void ExampleMap() {
namespace ffi = tvm::ffi;
ffi::Map<ffi::String, int> map0 = {{"Alice", 100}, {"Bob", 95}};
EXPECT_EQ(map0.size(), 2);
EXPECT_EQ(map0.at("Alice"), 100);
EXPECT_EQ(map0.count("Alice"), 1);
}
TEST(Example, Map) { ExampleMap(); }
void ExampleOptional() {
namespace ffi = tvm::ffi;
ffi::Optional<int> opt0 = 100;
EXPECT_EQ(opt0.has_value(), true);
EXPECT_EQ(opt0.value(), 100);
ffi::Optional<ffi::String> opt1;
EXPECT_EQ(opt1.has_value(), false);
EXPECT_EQ(opt1.value_or("default"), "default");
}
TEST(Example, Optional) { ExampleOptional(); }
void ExampleVariant() {
namespace ffi = tvm::ffi;
ffi::Variant<int, ffi::String> var0 = 100;
EXPECT_EQ(var0.get<int>(), 100);
var0 = ffi::String("hello");
std::optional<ffi::String> maybe_str = var0.as<ffi::String>();
EXPECT_EQ(maybe_str.value(), "hello"); // NOLINT(bugprone-unchecked-optional-access)
std::optional<int> maybe_int2 = var0.as<int>();
EXPECT_EQ(maybe_int2.has_value(), false);
}
TEST(Example, Variant) { ExampleVariant(); }
// Step 1: Define the object class (stores the actual data)
class MyIntPairObj : public tvm::ffi::Object {
public:
int64_t a;
int64_t b;
MyIntPairObj() = default;
MyIntPairObj(int64_t a, int64_t b) : a(a), b(b) {}
// Required: declare type information
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("example.MyIntPair", MyIntPairObj, tvm::ffi::Object);
};
// Step 2: Define the reference wrapper (user-facing interface)
class MyIntPair : public tvm::ffi::ObjectRef {
public:
// Constructor
explicit MyIntPair(int64_t a, int64_t b) { data_ = tvm::ffi::make_object<MyIntPairObj>(a, b); }
// Required: define object reference methods
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(MyIntPair, tvm::ffi::ObjectRef, MyIntPairObj);
};
void ExampleObjectPtr() {
namespace ffi = tvm::ffi;
ffi::ObjectPtr<MyIntPairObj> obj = ffi::make_object<MyIntPairObj>(100, 200);
EXPECT_EQ(obj->a, 100);
EXPECT_EQ(obj->b, 200);
}
void ExampleObjectRef() {
namespace ffi = tvm::ffi;
MyIntPair pair(100, 200);
EXPECT_EQ(pair->a, 100);
EXPECT_EQ(pair->b, 200);
}
void ExampleObjectRefAny() {
namespace ffi = tvm::ffi;
MyIntPair pair(100, 200);
ffi::Any any = pair;
MyIntPair pair2 = any.cast<MyIntPair>();
EXPECT_EQ(pair2->a, 100);
EXPECT_EQ(pair2->b, 200);
}
TEST(Example, ObjectPtr) {
ExampleObjectPtr();
ExampleObjectRef();
ExampleObjectRefAny();
}
} // namespace