| /* |
| * 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/memory.h> |
| |
| #include <limits> |
| |
| #include "./testing_object.h" |
| |
| namespace { |
| |
| using namespace tvm::ffi; |
| using namespace tvm::ffi::testing; |
| |
| TEST(Any, Int) { |
| AnyView view0; |
| EXPECT_EQ(view0.CopyToTVMFFIAny().type_index, TypeIndex::kTVMFFINone); |
| |
| Optional<int64_t> opt_v0 = view0.as<int64_t>(); |
| EXPECT_TRUE(!opt_v0.has_value()); |
| |
| EXPECT_THROW( |
| { |
| try { |
| [[maybe_unused]] auto v0 = view0.cast<int>(); |
| } catch (const Error& error) { |
| EXPECT_EQ(error.kind(), "TypeError"); |
| std::string what = error.what(); |
| EXPECT_NE(what.find("Cannot convert from type `None` to `int`"), std::string::npos); |
| throw; |
| } |
| }, |
| ::tvm::ffi::Error); |
| |
| AnyView view1 = 1; |
| EXPECT_EQ(view1.CopyToTVMFFIAny().type_index, TypeIndex::kTVMFFIInt); |
| EXPECT_EQ(view1.CopyToTVMFFIAny().v_int64, 1); |
| |
| auto int_v1 = view1.cast<int>(); |
| EXPECT_EQ(int_v1, 1); |
| |
| int64_t v1 = 2; |
| view0 = v1; |
| EXPECT_EQ(view0.CopyToTVMFFIAny().type_index, TypeIndex::kTVMFFIInt); |
| EXPECT_EQ(view0.CopyToTVMFFIAny().v_int64, 2); |
| |
| uint64_t v2 = static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) + 1; |
| EXPECT_THROW( |
| { |
| try { |
| view0 = v2; |
| } catch (const Error& error) { |
| EXPECT_EQ(error.kind(), "OverflowError"); |
| std::string what = error.what(); |
| EXPECT_NE(what.find("is too large to fit in int64_t"), std::string::npos); |
| throw; |
| } |
| }, |
| ::tvm::ffi::Error); |
| } |
| |
| TEST(Any, Enum) { |
| enum class ENum : int { |
| A = 1, |
| B = 2, |
| }; |
| |
| AnyView view0; |
| Optional<ENum> opt_v0 = view0.as<ENum>(); |
| EXPECT_TRUE(!opt_v0.has_value()); |
| |
| AnyView view1 = ENum::A; |
| EXPECT_EQ(view1.CopyToTVMFFIAny().type_index, TypeIndex::kTVMFFIInt); |
| EXPECT_EQ(view1.CopyToTVMFFIAny().v_int64, 1); |
| |
| ENum v1 = view1.cast<ENum>(); |
| EXPECT_EQ(v1, ENum::A); |
| } |
| |
| TEST(Any, bool) { |
| AnyView view0; |
| Optional<bool> opt_v0 = view0.as<bool>(); |
| EXPECT_TRUE(!opt_v0.has_value()); |
| |
| EXPECT_THROW( |
| { |
| try { |
| [[maybe_unused]] auto v0 = view0.cast<bool>(); |
| } catch (const Error& error) { |
| EXPECT_EQ(error.kind(), "TypeError"); |
| std::string what = error.what(); |
| EXPECT_NE(what.find("Cannot convert from type `None` to `bool`"), std::string::npos); |
| throw; |
| } |
| }, |
| ::tvm::ffi::Error); |
| |
| AnyView view1 = true; |
| EXPECT_EQ(view1.CopyToTVMFFIAny().type_index, TypeIndex::kTVMFFIBool); |
| EXPECT_EQ(view1.CopyToTVMFFIAny().v_int64, 1); |
| |
| auto int_v1 = view1.cast<int>(); |
| EXPECT_EQ(int_v1, 1); |
| |
| bool v1 = false; |
| view0 = v1; |
| EXPECT_EQ(view0.CopyToTVMFFIAny().type_index, TypeIndex::kTVMFFIBool); |
| EXPECT_EQ(view0.CopyToTVMFFIAny().v_int64, 0); |
| } |
| |
| TEST(Any, nullptrcmp) { |
| AnyView view0; |
| EXPECT_EQ(view0.CopyToTVMFFIAny().type_index, TypeIndex::kTVMFFINone); |
| EXPECT_TRUE(view0 == nullptr); |
| EXPECT_FALSE(view0 != nullptr); |
| |
| view0 = 1; |
| EXPECT_TRUE(view0 != nullptr); |
| EXPECT_FALSE(view0 == nullptr); |
| |
| Any any0 = view0; |
| EXPECT_TRUE(any0 != nullptr); |
| EXPECT_FALSE(any0 == nullptr); |
| |
| any0 = nullptr; |
| EXPECT_TRUE(any0 == nullptr); |
| EXPECT_FALSE(any0 != nullptr); |
| } |
| |
| TEST(Any, Float) { |
| AnyView view0; |
| EXPECT_EQ(view0.CopyToTVMFFIAny().type_index, TypeIndex::kTVMFFINone); |
| |
| Optional<double> opt_v0 = view0.as<double>(); |
| EXPECT_TRUE(!opt_v0.has_value()); |
| |
| EXPECT_THROW( |
| { |
| try { |
| [[maybe_unused]] auto v0 = view0.cast<double>(); |
| } catch (const Error& error) { |
| EXPECT_EQ(error.kind(), "TypeError"); |
| std::string what = error.what(); |
| EXPECT_NE(what.find("Cannot convert from type `None` to `float`"), std::string::npos); |
| throw; |
| } |
| }, |
| ::tvm::ffi::Error); |
| |
| AnyView view1_int = 1; |
| auto float_v1 = view1_int.cast<float>(); |
| EXPECT_EQ(float_v1, 1); |
| |
| AnyView view2 = 2.2; |
| EXPECT_EQ(view2.CopyToTVMFFIAny().type_index, TypeIndex::kTVMFFIFloat); |
| EXPECT_EQ(view2.CopyToTVMFFIAny().v_float64, 2.2); |
| |
| float v1 = 2; |
| view0 = v1; |
| EXPECT_EQ(view0.CopyToTVMFFIAny().type_index, TypeIndex::kTVMFFIFloat); |
| EXPECT_EQ(view0.CopyToTVMFFIAny().v_float64, 2); |
| } |
| |
| TEST(Any, Device) { |
| AnyView view0; |
| EXPECT_EQ(view0.CopyToTVMFFIAny().type_index, TypeIndex::kTVMFFINone); |
| |
| Optional<DLDevice> opt_v0 = view0.as<DLDevice>(); |
| EXPECT_TRUE(!opt_v0.has_value()); |
| |
| EXPECT_THROW( |
| { |
| try { |
| [[maybe_unused]] auto v0 = view0.cast<DLDevice>(); |
| } catch (const Error& error) { |
| EXPECT_EQ(error.kind(), "TypeError"); |
| std::string what = error.what(); |
| EXPECT_NE(what.find("Cannot convert from type `None` to `Device`"), std::string::npos); |
| throw; |
| } |
| }, |
| ::tvm::ffi::Error); |
| |
| DLDevice device{kDLCUDA, 1}; |
| |
| AnyView view1_device = device; |
| auto dtype_v1 = view1_device.cast<DLDevice>(); |
| EXPECT_EQ(dtype_v1.device_type, kDLCUDA); |
| EXPECT_EQ(dtype_v1.device_id, 1); |
| |
| Any any2 = DLDevice{kDLCPU, 0}; |
| TVMFFIAny ffi_v2 = details::AnyUnsafe::MoveAnyToTVMFFIAny(std::move(any2)); |
| EXPECT_EQ(ffi_v2.type_index, TypeIndex::kTVMFFIDevice); |
| EXPECT_EQ(ffi_v2.v_device.device_type, kDLCPU); |
| EXPECT_EQ(ffi_v2.v_device.device_id, 0); |
| } |
| |
| TEST(Any, DLTensor) { |
| AnyView view0; |
| |
| Optional<DLTensor*> opt_v0 = view0.as<DLTensor*>(); |
| EXPECT_TRUE(!opt_v0.has_value()); |
| |
| EXPECT_THROW( |
| { |
| try { |
| [[maybe_unused]] auto v0 = view0.cast<DLTensor*>(); |
| } catch (const Error& error) { |
| EXPECT_EQ(error.kind(), "TypeError"); |
| std::string what = error.what(); |
| EXPECT_NE(what.find("Cannot convert from type `None` to `DLTensor*`"), std::string::npos); |
| throw; |
| } |
| }, |
| ::tvm::ffi::Error); |
| |
| DLTensor dltensor; |
| |
| AnyView view1_dl = &dltensor; |
| auto dl_v1 = view1_dl.cast<DLTensor*>(); |
| EXPECT_EQ(dl_v1, &dltensor); |
| } |
| |
| TEST(Any, Object) { |
| AnyView view0; |
| EXPECT_EQ(view0.CopyToTVMFFIAny().type_index, TypeIndex::kTVMFFINone); |
| |
| // int object is not nullable |
| Optional<TInt> opt_v0 = view0.as<TInt>(); |
| EXPECT_TRUE(!opt_v0.has_value()); |
| |
| TInt v1(11); |
| EXPECT_EQ(v1.use_count(), 1); |
| // view won't increase refcount |
| AnyView view1 = v1; |
| EXPECT_EQ(v1.use_count(), 1); |
| // any will trigger ref count increase |
| Any any1 = v1; |
| EXPECT_EQ(v1.use_count(), 2); |
| // copy to another view |
| AnyView view2 = any1; |
| EXPECT_EQ(v1.use_count(), 2); |
| |
| // convert to weak raw object ptr |
| const TIntObj* v1_ptr = view2.cast<const TIntObj*>(); |
| EXPECT_EQ(v1.use_count(), 2); |
| EXPECT_EQ(v1_ptr->value, 11); |
| Any any2 = v1_ptr; |
| EXPECT_EQ(v1.use_count(), 3); |
| EXPECT_TRUE(any2.as<TInt>().has_value()); |
| |
| any2 = const_cast<TIntObj*>(v1_ptr); |
| EXPECT_TRUE(any2.as<TInt>().has_value()); |
| |
| // convert to raw opaque ptr |
| void* raw_v1_ptr = const_cast<TIntObj*>(v1_ptr); |
| any2 = raw_v1_ptr; |
| EXPECT_TRUE(any2.as<void*>().value() == v1_ptr); // NOLINT(bugprone-unchecked-optional-access) |
| |
| // convert to ObjectRef |
| { |
| auto v1_obj_ref = view2.cast<TNumber>(); |
| EXPECT_EQ(v1.use_count(), 3); |
| any2 = v1_obj_ref; |
| EXPECT_EQ(v1.use_count(), 4); |
| EXPECT_TRUE(any2.as<TInt>().has_value()); |
| any2.reset(); |
| } |
| |
| // convert that triggers error |
| EXPECT_THROW( |
| { |
| try { |
| [[maybe_unused]] auto v0 = view1.cast<TFloat>(); |
| } catch (const Error& error) { |
| EXPECT_EQ(error.kind(), "TypeError"); |
| std::string what = error.what(); |
| std::cout << what; |
| EXPECT_NE(what.find("Cannot convert from type `test.Int` to `test.Float`"), |
| std::string::npos); |
| throw; |
| } |
| }, |
| ::tvm::ffi::Error); |
| // Try to convert to number |
| auto number0 = any1.cast<TNumber>(); |
| EXPECT_EQ(v1.use_count(), 3); |
| EXPECT_TRUE(number0.as<TIntObj>()); |
| EXPECT_EQ(number0.as<TIntObj>()->value, 11); |
| EXPECT_TRUE(!any1.as<int>().has_value()); |
| |
| auto int1 = view2.cast<TInt>(); |
| EXPECT_EQ(v1.use_count(), 4); |
| any1.reset(); |
| EXPECT_EQ(v1.use_count(), 3); |
| } |
| |
| TEST(Any, ObjectRefWithFallbackTraits) { |
| // Test case for TPrimExpr fallback from Any |
| Any any1 = TPrimExpr("float32", 3.14); |
| auto v0 = any1.cast<TPrimExpr>(); |
| EXPECT_EQ(v0->value, 3.14); |
| EXPECT_EQ(v0->dtype, "float32"); |
| |
| any1 = true; |
| auto v1 = any1.cast<TPrimExpr>(); |
| EXPECT_EQ(v1->value, 1); |
| EXPECT_EQ(v1->dtype, "bool"); |
| |
| any1 = static_cast<int64_t>(42); |
| auto v2 = any1.cast<TPrimExpr>(); |
| EXPECT_EQ(v2->value, 42); |
| EXPECT_EQ(v2->dtype, "int64"); |
| |
| any1 = 2.718; |
| auto v3 = any1.cast<TPrimExpr>(); |
| EXPECT_EQ(v3->value, 2.718); |
| EXPECT_EQ(v3->dtype, "float32"); |
| |
| // Test case for TPrimExpr fallback from AnyView |
| TPrimExpr texpr1("float32", 3.14); |
| AnyView view1 = texpr1; |
| auto v4 = view1.cast<TPrimExpr>(); |
| EXPECT_EQ(v4->value, 3.14); |
| EXPECT_EQ(v4->dtype, "float32"); |
| |
| view1 = true; |
| auto v5 = view1.cast<TPrimExpr>(); |
| EXPECT_EQ(v5->value, 1); |
| EXPECT_EQ(v5->dtype, "bool"); |
| |
| view1 = static_cast<int64_t>(42); |
| auto v6 = view1.cast<TPrimExpr>(); |
| EXPECT_EQ(v6->value, 42); |
| EXPECT_EQ(v6->dtype, "int64"); |
| |
| view1 = 2.718; |
| auto v7 = view1.cast<TPrimExpr>(); |
| EXPECT_EQ(v7->value, 2.718); |
| EXPECT_EQ(v7->dtype, "float32"); |
| |
| // Test case for TPrimExpr fallback from Any with String |
| any1 = std::string("test_string"); |
| auto v8 = any1.cast<TPrimExpr>(); |
| EXPECT_EQ(v8->dtype, "test_string"); |
| EXPECT_EQ(v8->value, 0); |
| |
| // Test case for TPrimExpr fallback from AnyView with String |
| view1 = "test_string"; |
| auto v9 = view1.cast<TPrimExpr>(); |
| EXPECT_EQ(v9->dtype, "test_string"); |
| EXPECT_EQ(v9->value, 0); |
| } |
| |
| TEST(Any, CastVsAs) { |
| AnyView view0 = 1; |
| // as only runs strict check |
| auto opt_v0 = view0.as<int64_t>(); |
| EXPECT_TRUE(opt_v0.has_value()); |
| EXPECT_EQ(opt_v0.value(), 1); // NOLINT(bugprone-unchecked-optional-access) |
| |
| auto opt_v1 = view0.as<bool>(); |
| EXPECT_TRUE(!opt_v1.has_value()); |
| auto opt_v2 = view0.as<double>(); |
| EXPECT_TRUE(!opt_v2.has_value()); |
| |
| // try_cast will try run the conversion. |
| auto opt_v3 = view0.try_cast<bool>(); |
| EXPECT_TRUE(opt_v3.has_value()); |
| EXPECT_EQ(opt_v3.value(), 1); // NOLINT(bugprone-unchecked-optional-access) |
| auto opt_v4 = view0.try_cast<double>(); |
| EXPECT_TRUE(opt_v4.has_value()); |
| EXPECT_EQ(opt_v4.value(), 1); // NOLINT(bugprone-unchecked-optional-access) |
| |
| Any any1 = true; |
| auto opt_v5 = any1.as<bool>(); |
| EXPECT_TRUE(opt_v5.has_value()); |
| EXPECT_EQ(opt_v5.value(), 1); // NOLINT(bugprone-unchecked-optional-access) |
| |
| auto opt_v6 = any1.try_cast<int>(); |
| EXPECT_TRUE(opt_v6.has_value()); |
| EXPECT_EQ(opt_v6.value(), 1); // NOLINT(bugprone-unchecked-optional-access) |
| |
| auto opt_v7 = any1.try_cast<double>(); |
| EXPECT_TRUE(opt_v7.has_value()); |
| } |
| |
| TEST(Any, ObjectMove) { |
| Any any1 = TPrimExpr("float32", 3.14); |
| auto v0 = std::move(any1).cast<TPrimExpr>(); |
| EXPECT_EQ(v0->value, 3.14); |
| EXPECT_EQ(v0.use_count(), 1); |
| EXPECT_TRUE(any1 == nullptr); // NOLINT(bugprone-use-after-move) |
| } |
| |
| TEST(Any, AnyEqualHash) { |
| // small string |
| Any a = "a1"; |
| // on heap allocated string |
| Any b = String(std::string("a1")); |
| EXPECT_EQ(a.type_index(), TypeIndex::kTVMFFISmallStr); |
| EXPECT_EQ(b.type_index(), TypeIndex::kTVMFFIStr); |
| EXPECT_TRUE(AnyEqual()(a, b)); |
| EXPECT_EQ(AnyHash()(a), AnyHash()(b)); |
| |
| Any c = Bytes("a1", 2); |
| Any d = Bytes(std::string("a1")); |
| EXPECT_EQ(c.type_index(), TypeIndex::kTVMFFISmallBytes); |
| EXPECT_EQ(d.type_index(), TypeIndex::kTVMFFIBytes); |
| EXPECT_TRUE(AnyEqual()(c, d)); |
| EXPECT_EQ(AnyHash()(c), AnyHash()(d)); |
| } |
| |
| TEST(Any, CustomAnyHash) { |
| // Covers the OpaquePtr custom hash branch. |
| Any int_src = TInt(7); |
| uint64_t int_expected = details::StableHashCombine( |
| int_src.type_index(), static_cast<uint64_t>(TInt::CustomAnyHash(int_src))); |
| EXPECT_EQ(AnyHash()(int_src), int_expected); |
| |
| // Covers the ffi.Function custom hash branch. |
| Any float_src = TFloat(3.5); |
| uint64_t float_expected = details::StableHashCombine( |
| float_src.type_index(), static_cast<uint64_t>(TFloat::CustomAnyHash(float_src))); |
| EXPECT_EQ(AnyHash()(float_src), float_expected); |
| } |
| |
| TEST(Any, CustomAnyEqual) { |
| // Covers the OpaquePtr custom equal branch. |
| Any int_lhs = TInt(7); |
| Any int_rhs = TInt(7); |
| Any int_diff = TInt(8); |
| EXPECT_TRUE(AnyEqual()(int_lhs, int_rhs)); |
| EXPECT_FALSE(AnyEqual()(int_lhs, int_diff)); |
| |
| // Covers the ffi.Function custom equal branch. |
| Any float_lhs = TFloat(3.5); |
| Any float_rhs = TFloat(3.5); |
| Any float_diff = TFloat(4.5); |
| EXPECT_TRUE(AnyEqual()(float_lhs, float_rhs)); |
| EXPECT_FALSE(AnyEqual()(float_lhs, float_diff)); |
| } |
| |
| } // namespace |