| /* |
| * 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 |