From 5b387037b8dc4a50eae03d92736ab7acb09e8ca8 Mon Sep 17 00:00:00 2001 From: DarkSharpness <2040703891@qq.com> Date: Mon, 3 Nov 2025 22:41:21 +0800 Subject: [PATCH 01/13] feat: support C++ STL in ffi --- include/tvm/ffi/container/stl.h | 290 ++++++++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 include/tvm/ffi/container/stl.h diff --git a/include/tvm/ffi/container/stl.h b/include/tvm/ffi/container/stl.h new file mode 100644 index 00000000..1094d2c5 --- /dev/null +++ b/include/tvm/ffi/container/stl.h @@ -0,0 +1,290 @@ +/* + * 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 + * + * htT://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. + */ + +/*! + * \file tvm/ffi/container/stl.h + * \brief STL container support. + * + */ +#ifndef TVM_FFI_CONTAINER_STL_H_ +#define TVM_FFI_CONTAINER_STL_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace tvm { +namespace ffi { + +template +struct TypeTraits> : public TypeTraitsBase { + private: + using Self = std::array; + using Array = ::tvm::ffi::Array; + static_assert(Nm > 0, "Zero-length std::array is not supported."); + + public: + static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIArray; + + TVM_FFI_INLINE static void CopyToAnyView(const Self& src, TVMFFIAny* result) { + return TypeTraits::MoveToAny({src.begin(), src.end()}, result); + } + + TVM_FFI_INLINE static void MoveToAny(Self&& src, TVMFFIAny* result) { + return TypeTraits::MoveToAny( + {std::make_move_iterator(src.begin()), std::make_move_iterator(src.end())}, result); + } + + TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) { + if (src->type_index != TypeIndex::kTVMFFIArray) return false; + const ArrayObj& n = *reinterpret_cast(src->v_obj); + // check static length first + if (n.size_ != Nm) return false; + // then check element type + if constexpr (std::is_same_v) { + return true; + } else { + return std::all_of(n.begin(), n.end(), details::AnyUnsafe::CheckAnyStrict); + } + } + + TVM_FFI_INLINE static Self CopyFromAnyViewAfterCheck(const TVMFFIAny* src) { + auto array = TypeTraits::CopyFromAnyViewAfterCheck(src); + Self result; // no initialization to avoid overhead + std::copy_n(std::make_move_iterator(array.begin()), Nm, result.begin()); + return result; + } + + TVM_FFI_INLINE static Self MoveFromAnyAfterCheck(TVMFFIAny* src) { + auto array = TypeTraits::MoveFromAnyAfterCheck(src); + Self result; // no initialization to avoid overhead + std::copy_n(std::make_move_iterator(array.begin()), Nm, result.begin()); + return result; + } + + TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { + if (!CheckAnyStrict(src)) return std::nullopt; + return CopyFromAnyViewAfterCheck(src); + } + + TVM_FFI_INLINE static std::string TypeStr() { + return "std::array<" + details::Type2Str::v() + ", " + std::to_string(Nm) + ">"; + } + TVM_FFI_INLINE static std::string TypeSchema() { + return R"({"type":"std::array","args":[)" + details::TypeSchema::v() + "," + + std::to_string(Nm) + "]}"; + } +}; + +template +struct TypeTraits> : public TypeTraitsBase { + private: + using Self = std::vector; + using Array = ::tvm::ffi::Array; + + public: + static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIArray; + + TVM_FFI_INLINE static void CopyToAnyView(const Self& src, TVMFFIAny* result) { + return TypeTraits::MoveToAny({src.begin(), src.end()}, result); + } + + TVM_FFI_INLINE static void MoveToAny(Self&& src, TVMFFIAny* result) { + return TypeTraits::MoveToAny( + {std::make_move_iterator(src.begin()), std::make_move_iterator(src.end())}, result); + } + + TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) { + if (src->type_index != TypeIndex::kTVMFFIArray) return false; + const ArrayObj& n = *reinterpret_cast(src->v_obj); + if constexpr (std::is_same_v) { + return true; + } else { + return std::all_of(n.begin(), n.end(), details::AnyUnsafe::CheckAnyStrict); + } + } + + TVM_FFI_INLINE static Self CopyFromAnyViewAfterCheck(const TVMFFIAny* src) { + auto array = TypeTraits::CopyFromAnyViewAfterCheck(src); + return Self{std::make_move_iterator(array.begin()), std::make_move_iterator(array.end())}; + } + + TVM_FFI_INLINE static Self MoveFromAnyAfterCheck(TVMFFIAny* src) { + auto array = TypeTraits::MoveFromAnyAfterCheck(src); + return Self{std::make_move_iterator(array.begin()), std::make_move_iterator(array.end())}; + } + + TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { + if (!CheckAnyStrict(src)) return std::nullopt; + return CopyFromAnyViewAfterCheck(src); + } + + TVM_FFI_INLINE static std::string TypeStr() { + return "std::vector<" + details::Type2Str::v() + ">"; + } + TVM_FFI_INLINE static std::string TypeSchema() { + return R"({"type":"std::vector","args":[)" + details::TypeSchema::v() + "]}"; + } +}; + +template +struct TypeTraits> : public TypeTraitsBase { + private: + using Self = std::optional; + + public: + TVM_FFI_INLINE static void CopyToAnyView(const Self& src, TVMFFIAny* result) { + if (src.has_value()) { + TypeTraits::CopyToAnyView(*src, result); + } else { + TypeTraits::CopyToAnyView(nullptr, result); + } + } + TVM_FFI_INLINE static void MoveToAny(Self&& src, TVMFFIAny* result) { + if (src.has_value()) { + TypeTraits::MoveToAny(std::move(*src), result); + } else { + TypeTraits::MoveToAny(nullptr, result); + } + } + + TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) { + if (src->type_index == TypeIndex::kTVMFFINone) return true; + return TypeTraits::CheckAnyStrict(src); + } + + TVM_FFI_INLINE static Self CopyFromAnyViewAfterCheck(const TVMFFIAny* src) { + if (src->type_index == TypeIndex::kTVMFFINone) return Self{std::nullopt}; + return TypeTraits::CopyFromAnyViewAfterCheck(src); + } + + TVM_FFI_INLINE static Self MoveFromAnyAfterCheck(TVMFFIAny* src) { + if (src->type_index == TypeIndex::kTVMFFINone) return Self{std::nullopt}; + return TypeTraits::MoveFromAnyAfterCheck(src); + } + + TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { + std::cerr << "DEBUG optional::TryCastFromAnyView\n"; + if (src->type_index == TypeIndex::kTVMFFINone) return Self{std::nullopt}; + if (std::optional opt = TypeTraits::TryCastFromAnyView(src)) { + return Self{*std::move(opt)}; + } else { + // important to be explicit here + // because nullopt can convert to std::optional(nullopt) which indicate success + // return std::optional>() to indicate failure + return std::optional{}; + } + } + + TVM_FFI_INLINE static std::string GetMismatchTypeInfo(const TVMFFIAny* src) { + return TypeTraits::GetMismatchTypeInfo(src); + } + TVM_FFI_INLINE static std::string TypeStr() { + return "std::optional<" + TypeTraits::TypeStr() + ">"; + } + TVM_FFI_INLINE static std::string TypeSchema() { + return R"({"type":"std::optional","args":[)" + details::TypeSchema::v() + "]}"; + } +}; + +template +struct TypeTraits> : public TypeTraitsBase { + private: + using Self = std::tuple; + using Tuple = ::tvm::ffi::Tuple; + static constexpr std::size_t Nm = sizeof...(Args); + static_assert(Nm > 0, "Zero-length std::tuple is not supported."); + + template + static bool CheckTupleSubType(std::index_sequence, const ArrayObj& n) { + return (... && details::AnyUnsafe::CheckAnyStrict>(n[Is])); + } + + template + static Self MoveToTuple(std::index_sequence, Tuple&& tuple) { + return Self{std::move(tuple.template get())...}; + } + + public: + static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIArray; + + TVM_FFI_INLINE static void CopyToAnyView(const Self& src, TVMFFIAny* result) { + return TypeTraits::MoveToAny(Tuple{std::get(src)...}, result); + } + + TVM_FFI_INLINE static void MoveToAny(Self&& src, TVMFFIAny* result) { + return TypeTraits::MoveToAny(Tuple{std::move(std::get(src))...}, result); + } + + TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) { + if (src->type_index != TypeIndex::kTVMFFIArray) return false; + const ArrayObj& n = *reinterpret_cast(src->v_obj); + // check static length first + if (n.size_ != Nm) return false; + // then check element type + return CheckTupleSubType(std::make_index_sequence{}, n); + } + + TVM_FFI_INLINE static Self CopyFromAnyViewAfterCheck(const TVMFFIAny* src) { + auto tuple = TypeTraits::CopyFromAnyViewAfterCheck(src); + return MoveToTuple(std::make_index_sequence{}, std::move(tuple)); + } + + TVM_FFI_INLINE static Self MoveFromAnyAfterCheck(TVMFFIAny* src) { + auto tuple = TypeTraits::MoveFromAnyAfterCheck(src); + return MoveToTuple(std::make_index_sequence{}, std::move(tuple)); + } + + TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { + if (!CheckAnyStrict(src)) return std::nullopt; + return CopyFromAnyViewAfterCheck(src); + } + + TVM_FFI_INLINE static std::string TypeStr() { + std::stringstream os; + os << "std::tuple<"; + const char* sep = ""; + ((os << sep << details::Type2Str::v(), sep = ", "), ...); + os << ">"; + return std::move(os).str(); + } + + TVM_FFI_INLINE static std::string TypeSchema() { + std::stringstream os; + os << R"({"type":"std::tuple","args":[)"; + const char* sep = ""; + ((os << sep << details::TypeSchema::v(), sep = ", "), ...); + os << "]}"; + return std::move(os).str(); + } +}; + +} // namespace ffi +} // namespace tvm + +#endif // TVM_FFI_CONTAINER_STL_H_ From 9b1b661282a15761a2d035d4f16802c84505c003 Mon Sep 17 00:00:00 2001 From: DarkSharpness <2040703891@qq.com> Date: Mon, 3 Nov 2025 22:58:25 +0800 Subject: [PATCH 02/13] minor: remove debug msg --- include/tvm/ffi/container/stl.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/tvm/ffi/container/stl.h b/include/tvm/ffi/container/stl.h index 1094d2c5..b480e5d2 100644 --- a/include/tvm/ffi/container/stl.h +++ b/include/tvm/ffi/container/stl.h @@ -189,7 +189,6 @@ struct TypeTraits> : public TypeTraitsBase { } TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { - std::cerr << "DEBUG optional::TryCastFromAnyView\n"; if (src->type_index == TypeIndex::kTVMFFINone) return Self{std::nullopt}; if (std::optional opt = TypeTraits::TryCastFromAnyView(src)) { return Self{*std::move(opt)}; From 2eafc1a5d454e8ec18bf4b29ce8a7dd84c7da50a Mon Sep 17 00:00:00 2001 From: DarkSharpness <2040703891@qq.com> Date: Mon, 3 Nov 2025 23:16:20 +0800 Subject: [PATCH 03/13] fix: fix and improve tuple --- include/tvm/ffi/container/stl.h | 34 ++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/include/tvm/ffi/container/stl.h b/include/tvm/ffi/container/stl.h index b480e5d2..b1c9c4d4 100644 --- a/include/tvm/ffi/container/stl.h +++ b/include/tvm/ffi/container/stl.h @@ -225,19 +225,41 @@ struct TypeTraits> : public TypeTraitsBase { } template - static Self MoveToTuple(std::index_sequence, Tuple&& tuple) { + static Tuple CopyToFFITupleAux(std::index_sequence, const Self& tuple) { + return Tuple{std::get(tuple)...}; + } + + template + static Tuple MoveToFFITupleAux(std::index_sequence, Self&& tuple) { + return Tuple{std::get(std::move(tuple))...}; + } + + template + static Self MoveToSTDTupleAux(std::index_sequence, Tuple&& tuple) { return Self{std::move(tuple.template get())...}; } + static Tuple CopyToFFITuple(const Self& tuple) { + return CopyToFFITupleAux(std::make_index_sequence{}, tuple); + } + + static Tuple MoveToFFITuple(Self&& tuple) { + return MoveToFFITupleAux(std::make_index_sequence{}, std::move(tuple)); + } + + static Self MoveToSTDTuple(Tuple&& tuple) { + return MoveToSTDTupleAux(std::make_index_sequence{}, std::move(tuple)); + } + public: static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIArray; TVM_FFI_INLINE static void CopyToAnyView(const Self& src, TVMFFIAny* result) { - return TypeTraits::MoveToAny(Tuple{std::get(src)...}, result); + return TypeTraits::MoveToAny(CopyToFFITuple(src), result); } TVM_FFI_INLINE static void MoveToAny(Self&& src, TVMFFIAny* result) { - return TypeTraits::MoveToAny(Tuple{std::move(std::get(src))...}, result); + return TypeTraits::MoveToAny(MoveToFFITuple(std::move(src)), result); } TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) { @@ -250,13 +272,11 @@ struct TypeTraits> : public TypeTraitsBase { } TVM_FFI_INLINE static Self CopyFromAnyViewAfterCheck(const TVMFFIAny* src) { - auto tuple = TypeTraits::CopyFromAnyViewAfterCheck(src); - return MoveToTuple(std::make_index_sequence{}, std::move(tuple)); + return MoveToSTDTuple(TypeTraits::CopyFromAnyViewAfterCheck(src)); } TVM_FFI_INLINE static Self MoveFromAnyAfterCheck(TVMFFIAny* src) { - auto tuple = TypeTraits::MoveFromAnyAfterCheck(src); - return MoveToTuple(std::make_index_sequence{}, std::move(tuple)); + return MoveToSTDTuple(TypeTraits::MoveFromAnyAfterCheck(src)); } TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { From ded9096c0beded18dc21389f54145ae63aed5269 Mon Sep 17 00:00:00 2001 From: DarkSharpness <2040703891@qq.com> Date: Mon, 3 Nov 2025 23:20:46 +0800 Subject: [PATCH 04/13] minor: improve readability for optional --- include/tvm/ffi/container/stl.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/tvm/ffi/container/stl.h b/include/tvm/ffi/container/stl.h index b1c9c4d4..86a7b62a 100644 --- a/include/tvm/ffi/container/stl.h +++ b/include/tvm/ffi/container/stl.h @@ -190,14 +190,14 @@ struct TypeTraits> : public TypeTraitsBase { TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { if (src->type_index == TypeIndex::kTVMFFINone) return Self{std::nullopt}; + auto result = std::optional{}; if (std::optional opt = TypeTraits::TryCastFromAnyView(src)) { - return Self{*std::move(opt)}; + /// NOTE: std::optional is just what we want (Self). + result.emplace(std::move(opt)); } else { - // important to be explicit here - // because nullopt can convert to std::optional(nullopt) which indicate success - // return std::optional>() to indicate failure - return std::optional{}; + result.reset(); // failed to cast, indicate failure } + return result; } TVM_FFI_INLINE static std::string GetMismatchTypeInfo(const TVMFFIAny* src) { From 94c57dc11761765c77d1589f7e51b142bb10d77a Mon Sep 17 00:00:00 2001 From: DarkSharpness <2040703891@qq.com> Date: Wed, 5 Nov 2025 17:45:55 +0800 Subject: [PATCH 05/13] feat: support variant --- include/tvm/ffi/container/stl.h | 112 ++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/include/tvm/ffi/container/stl.h b/include/tvm/ffi/container/stl.h index 86a7b62a..4f674f7a 100644 --- a/include/tvm/ffi/container/stl.h +++ b/include/tvm/ffi/container/stl.h @@ -36,8 +36,13 @@ #include #include #include +#include +#include #include +#include "tvm/ffi/c_api.h" +#include "tvm/ffi/error.h" + namespace tvm { namespace ffi { @@ -303,6 +308,113 @@ struct TypeTraits> : public TypeTraitsBase { } }; +template +struct TypeTraits> : public TypeTraitsBase { + private: + using Self = std::variant; + using Tuple = std::tuple; + static constexpr std::size_t Nm = sizeof...(Args); + + template + static Self MoveUnsafeAux(TVMFFIAny* src) { + if constexpr (Is >= Nm) { + TVM_FFI_ICHECK(false) << "Unreachable: variant TryCast failed."; + } else { + using ElemType = std::tuple_element_t; + if (TypeTraits::CheckAnyStrict(src)) { + return Self{TypeTraits::MoveFromAnyAfterCheck(src)}; + } else { + return MoveUnsafeAux(src); + } + } + } + + template + static Self CopyUnsafeAux(const TVMFFIAny* src) { + if constexpr (Is >= Nm) { + TVM_FFI_ICHECK(false) << "Unreachable: variant TryCast failed."; + throw; // unreachable + } else { + using ElemType = std::tuple_element_t; + if (TypeTraits::CheckAnyStrict(src)) { + return Self{TypeTraits::CopyFromAnyViewAfterCheck(src)}; + } else { + return CopyUnsafeAux(src); + } + } + } + + template + static std::optional TryCastAux(const TVMFFIAny* src) { + if constexpr (Is >= Nm) { + return std::nullopt; + } else { + using ElemType = std::tuple_element_t; + if (TypeTraits::CheckAnyStrict(src)) { + return Self{TypeTraits::CopyFromAnyViewAfterCheck(src)}; + } else { + return TryCastAux(src); + } + } + } + + public: + TVM_FFI_INLINE static void CopyToAnyView(const Self& src, TVMFFIAny* result) { + return std::visit( + [&](const auto& value) { + using ValueType = std::decay_t; + TypeTraits::CopyToAnyView(value, result); + }, + src); + } + + TVM_FFI_INLINE static void MoveToAny(Self&& src, TVMFFIAny* result) { + return std::visit( + [&](auto&& value) { + using ValueType = std::decay_t; + TypeTraits::MoveToAny(std::forward(value), result); + }, + std::move(src)); + } + + TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) { + return (TypeTraits::CheckAnyStrict(src) || ...); + } + + TVM_FFI_INLINE static Self CopyFromAnyViewAfterCheck(const TVMFFIAny* src) { + // find the first possible type to copy + return CopyUnsafeAux(src); + } + + TVM_FFI_INLINE static Self MoveFromAnyAfterCheck(TVMFFIAny* src) { + // find the first possible type to move + return MoveUnsafeAux(src); + } + + TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { + // find the first possible type to copy + return TryCastAux(src); + } + + TVM_FFI_INLINE static std::string TypeStr() { + std::stringstream os; + os << "std::variant<"; + const char* sep = ""; + ((os << sep << details::Type2Str::v(), sep = ", "), ...); + os << ">"; + return std::move(os).str(); + } + + TVM_FFI_INLINE static std::string TypeSchema() { + std::stringstream os; + os << R"({"type":"std::variant","args":[)"; + const char* sep = ""; + ((os << sep << details::TypeSchema::v(), sep = ", "), ...); + os << "]}"; + return std::move(os).str(); + } +}; + } // namespace ffi } // namespace tvm From f8014d6a250638685d7bc7c148b5efc1309cb958 Mon Sep 17 00:00:00 2001 From: DarkSharpness <2040703891@qq.com> Date: Wed, 5 Nov 2025 17:57:49 +0800 Subject: [PATCH 06/13] fix: minor fix optional --- include/tvm/ffi/container/stl.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/tvm/ffi/container/stl.h b/include/tvm/ffi/container/stl.h index 4f674f7a..8f99b0e0 100644 --- a/include/tvm/ffi/container/stl.h +++ b/include/tvm/ffi/container/stl.h @@ -165,14 +165,14 @@ struct TypeTraits> : public TypeTraitsBase { public: TVM_FFI_INLINE static void CopyToAnyView(const Self& src, TVMFFIAny* result) { if (src.has_value()) { - TypeTraits::CopyToAnyView(*src, result); + TypeTraits::CopyToAnyView(*src, result); } else { TypeTraits::CopyToAnyView(nullptr, result); } } TVM_FFI_INLINE static void MoveToAny(Self&& src, TVMFFIAny* result) { if (src.has_value()) { - TypeTraits::MoveToAny(std::move(*src), result); + TypeTraits::MoveToAny(std::move(*src), result); } else { TypeTraits::MoveToAny(nullptr, result); } From dc440f0141d42f0bf4bd097ae4a153fb8d2e6b17 Mon Sep 17 00:00:00 2001 From: DarkSharpness <2040703891@qq.com> Date: Thu, 6 Nov 2025 03:46:11 +0800 Subject: [PATCH 07/13] minor: move to extra; add warning on usage --- include/tvm/ffi/{container => extra}/stl.h | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) rename include/tvm/ffi/{container => extra}/stl.h (96%) diff --git a/include/tvm/ffi/container/stl.h b/include/tvm/ffi/extra/stl.h similarity index 96% rename from include/tvm/ffi/container/stl.h rename to include/tvm/ffi/extra/stl.h index 8f99b0e0..6c79e618 100644 --- a/include/tvm/ffi/container/stl.h +++ b/include/tvm/ffi/extra/stl.h @@ -7,7 +7,7 @@ * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * - * htT://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -18,16 +18,23 @@ */ /*! - * \file tvm/ffi/container/stl.h + * \file tvm/ffi/extra/stl.h * \brief STL container support. + * \note This file is an extra extension of TVM FFI, + * which provides support for STL containers in C++ exported functions. * + * Whenever possible, prefer using tvm/ffi/container/ implementations, + * such as `tvm::ffi::Array` and `tvm::ffi::Tuple`, over STL containers + * in exported functions for better performance and compatibility. */ -#ifndef TVM_FFI_CONTAINER_STL_H_ -#define TVM_FFI_CONTAINER_STL_H_ +#ifndef TVM_FFI_EXTRA_STL_H_ +#define TVM_FFI_EXTRA_STL_H_ #include +#include #include #include +#include #include #include @@ -40,9 +47,6 @@ #include #include -#include "tvm/ffi/c_api.h" -#include "tvm/ffi/error.h" - namespace tvm { namespace ffi { @@ -418,4 +422,4 @@ struct TypeTraits> : public TypeTraitsBase { } // namespace ffi } // namespace tvm -#endif // TVM_FFI_CONTAINER_STL_H_ +#endif // TVM_FFI_EXTRA_STL_H_ From 00cd2ed00f5a44a67ef6e64ee3685edc430f6965 Mon Sep 17 00:00:00 2001 From: DarkSharpness <2040703891@qq.com> Date: Wed, 12 Nov 2025 21:47:32 +0800 Subject: [PATCH 08/13] refactor: eliminate dependency on ffi TypeTrait In the old code, we reuse traits like `TypeTrait>` and `TypeTrait>`. This may result in unwanted copy between FFI containers and STL containers. We fix all of them. --- include/tvm/ffi/extra/stl.h | 331 +++++++++++++++++++++--------------- 1 file changed, 190 insertions(+), 141 deletions(-) diff --git a/include/tvm/ffi/extra/stl.h b/include/tvm/ffi/extra/stl.h index 6c79e618..2493664a 100644 --- a/include/tvm/ffi/extra/stl.h +++ b/include/tvm/ffi/extra/stl.h @@ -41,6 +41,8 @@ #include #include #include +#include +#include #include #include #include @@ -49,24 +51,103 @@ namespace tvm { namespace ffi { +namespace details { -template -struct TypeTraits> : public TypeTraitsBase { - private: - using Self = std::array; - using Array = ::tvm::ffi::Array; - static_assert(Nm > 0, "Zero-length std::array is not supported."); +template +struct STLTypeTrait : public TypeTraitsBase { + public: + using TypeTraitsBase::convert_enabled; + using TypeTraitsBase::storage_enabled; + + protected: + // we always copy STL types into an Object first, then move the ObjectPtr to Any. + TVM_FFI_INLINE static void MoveToAnyImpl(ObjectPtr&& src, TVMFFIAny* result) { + TVMFFIObject* obj_ptr = ObjectUnsafe::MoveObjectPtrToTVMFFIObjectPtr(std::move(src)); + result->type_index = obj_ptr->type_index; + result->zero_padding = 0; + TVM_FFI_CLEAR_PTR_PADDING_IN_FFI_ANY(result); + result->v_obj = obj_ptr; + } + // we always construct STL types from an Object first, then copy from the ObjectPtr in Any. + TVM_FFI_INLINE static ObjectPtr CopyFromAnyViewAfterCheckImpl(const TVMFFIAny* src) { + return details::ObjectUnsafe::ObjectPtrFromUnowned(src->v_obj); + } +}; + +struct ContainerTemplate {}; + +} // namespace details + +template <> +struct TypeTraits : public details::STLTypeTrait<::tvm::ffi::ArrayObj> { public: static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIArray; + private: + template + TVM_FFI_INLINE static ObjectPtr CopyToTupleImpl(std::index_sequence, + Tuple&& src) { + auto array = ArrayObj::Empty(static_cast(sizeof...(Is))); + auto dst = array->MutableBegin(); + // increase size after each new to ensure exception safety + ((::new (dst++) Any(std::get(std::forward(src))), array->size_++), ...); + return array; + } + + template + TVM_FFI_INLINE static ObjectPtr CopyToArrayImpl(Iter src, std::size_t size) { + auto array = ArrayObj::Empty(static_cast(size)); + auto dst = array->MutableBegin(); + // increase size after each new to ensure exception safety + for (std::size_t i = 0; i < size; ++i) { + ::new (dst++) Any(*(src++)); + array->size_++; + } + return array; + } + + protected: + template + TVM_FFI_INLINE static ObjectPtr CopyToTuple(const Tuple& src) { + return CopyToTupleImpl(std::make_index_sequence>{}, src); + } + + template + TVM_FFI_INLINE static ObjectPtr MoveToTuple(Tuple&& src) { + return CopyToTupleImpl(std::make_index_sequence>{}, + std::forward(src)); + } + + template + TVM_FFI_INLINE static ObjectPtr CopyToArray(const Range& src) { + return CopyToArrayImpl(std::begin(src), std::size(src)); + } + + template + TVM_FFI_INLINE static ObjectPtr MoveToArray(Range&& src) { + return CopyToArrayImpl(std::make_move_iterator(std::begin(src)), std::size(src)); + } + + /// NOTE: STL types are not natively movable from Any, so we always make a new copy. + template + TVM_FFI_INLINE static T CopyFromAny(const Any& value) { + return details::AnyUnsafe::CopyFromAnyViewAfterCheck(value); + } +}; + +template +struct TypeTraits> : public TypeTraits { + public: + using Self = std::array; + static_assert(Nm > 0, "Zero-length std::array is not supported."); + TVM_FFI_INLINE static void CopyToAnyView(const Self& src, TVMFFIAny* result) { - return TypeTraits::MoveToAny({src.begin(), src.end()}, result); + return MoveToAnyImpl(CopyToArray(src), result); } TVM_FFI_INLINE static void MoveToAny(Self&& src, TVMFFIAny* result) { - return TypeTraits::MoveToAny( - {std::make_move_iterator(src.begin()), std::make_move_iterator(src.end())}, result); + return MoveToAnyImpl(MoveToArray(std::move(src)), result); } TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) { @@ -83,17 +164,17 @@ struct TypeTraits> : public TypeTraitsBase { } TVM_FFI_INLINE static Self CopyFromAnyViewAfterCheck(const TVMFFIAny* src) { - auto array = TypeTraits::CopyFromAnyViewAfterCheck(src); + auto array = CopyFromAnyViewAfterCheckImpl(src); + auto begin = array->MutableBegin(); Self result; // no initialization to avoid overhead - std::copy_n(std::make_move_iterator(array.begin()), Nm, result.begin()); + for (std::size_t i = 0; i < Nm; ++i) { + result[i] = CopyFromAny(begin[i]); + } return result; } TVM_FFI_INLINE static Self MoveFromAnyAfterCheck(TVMFFIAny* src) { - auto array = TypeTraits::MoveFromAnyAfterCheck(src); - Self result; // no initialization to avoid overhead - std::copy_n(std::make_move_iterator(array.begin()), Nm, result.begin()); - return result; + return CopyFromAnyViewAfterCheck(src); } TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { @@ -104,6 +185,7 @@ struct TypeTraits> : public TypeTraitsBase { TVM_FFI_INLINE static std::string TypeStr() { return "std::array<" + details::Type2Str::v() + ", " + std::to_string(Nm) + ">"; } + TVM_FFI_INLINE static std::string TypeSchema() { return R"({"type":"std::array","args":[)" + details::TypeSchema::v() + "," + std::to_string(Nm) + "]}"; @@ -111,21 +193,16 @@ struct TypeTraits> : public TypeTraitsBase { }; template -struct TypeTraits> : public TypeTraitsBase { - private: - using Self = std::vector; - using Array = ::tvm::ffi::Array; - +struct TypeTraits> : public TypeTraits { public: - static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIArray; + using Self = std::vector; TVM_FFI_INLINE static void CopyToAnyView(const Self& src, TVMFFIAny* result) { - return TypeTraits::MoveToAny({src.begin(), src.end()}, result); + return MoveToAnyImpl(CopyToArray(src), result); } TVM_FFI_INLINE static void MoveToAny(Self&& src, TVMFFIAny* result) { - return TypeTraits::MoveToAny( - {std::make_move_iterator(src.begin()), std::make_move_iterator(src.end())}, result); + return MoveToAnyImpl(MoveToArray(std::move(src)), result); } TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) { @@ -139,13 +216,19 @@ struct TypeTraits> : public TypeTraitsBase { } TVM_FFI_INLINE static Self CopyFromAnyViewAfterCheck(const TVMFFIAny* src) { - auto array = TypeTraits::CopyFromAnyViewAfterCheck(src); - return Self{std::make_move_iterator(array.begin()), std::make_move_iterator(array.end())}; + auto array = CopyFromAnyViewAfterCheckImpl(src); + auto begin = array->MutableBegin(); + auto result = Self{}; + auto length = array->size_; + result.reserve(length); + for (std::size_t i = 0; i < length; ++i) { + result.emplace_back(CopyFromAny(begin[i])); + } + return result; } TVM_FFI_INLINE static Self MoveFromAnyAfterCheck(TVMFFIAny* src) { - auto array = TypeTraits::MoveFromAnyAfterCheck(src); - return Self{std::make_move_iterator(array.begin()), std::make_move_iterator(array.end())}; + return CopyFromAnyViewAfterCheck(src); } TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { @@ -156,6 +239,7 @@ struct TypeTraits> : public TypeTraitsBase { TVM_FFI_INLINE static std::string TypeStr() { return "std::vector<" + details::Type2Str::v() + ">"; } + TVM_FFI_INLINE static std::string TypeSchema() { return R"({"type":"std::vector","args":[)" + details::TypeSchema::v() + "]}"; } @@ -163,10 +247,9 @@ struct TypeTraits> : public TypeTraitsBase { template struct TypeTraits> : public TypeTraitsBase { - private: + public: using Self = std::optional; - public: TVM_FFI_INLINE static void CopyToAnyView(const Self& src, TVMFFIAny* result) { if (src.has_value()) { TypeTraits::CopyToAnyView(*src, result); @@ -212,103 +295,13 @@ struct TypeTraits> : public TypeTraitsBase { TVM_FFI_INLINE static std::string GetMismatchTypeInfo(const TVMFFIAny* src) { return TypeTraits::GetMismatchTypeInfo(src); } - TVM_FFI_INLINE static std::string TypeStr() { - return "std::optional<" + TypeTraits::TypeStr() + ">"; - } - TVM_FFI_INLINE static std::string TypeSchema() { - return R"({"type":"std::optional","args":[)" + details::TypeSchema::v() + "]}"; - } -}; - -template -struct TypeTraits> : public TypeTraitsBase { - private: - using Self = std::tuple; - using Tuple = ::tvm::ffi::Tuple; - static constexpr std::size_t Nm = sizeof...(Args); - static_assert(Nm > 0, "Zero-length std::tuple is not supported."); - - template - static bool CheckTupleSubType(std::index_sequence, const ArrayObj& n) { - return (... && details::AnyUnsafe::CheckAnyStrict>(n[Is])); - } - - template - static Tuple CopyToFFITupleAux(std::index_sequence, const Self& tuple) { - return Tuple{std::get(tuple)...}; - } - - template - static Tuple MoveToFFITupleAux(std::index_sequence, Self&& tuple) { - return Tuple{std::get(std::move(tuple))...}; - } - - template - static Self MoveToSTDTupleAux(std::index_sequence, Tuple&& tuple) { - return Self{std::move(tuple.template get())...}; - } - - static Tuple CopyToFFITuple(const Self& tuple) { - return CopyToFFITupleAux(std::make_index_sequence{}, tuple); - } - - static Tuple MoveToFFITuple(Self&& tuple) { - return MoveToFFITupleAux(std::make_index_sequence{}, std::move(tuple)); - } - - static Self MoveToSTDTuple(Tuple&& tuple) { - return MoveToSTDTupleAux(std::make_index_sequence{}, std::move(tuple)); - } - - public: - static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIArray; - - TVM_FFI_INLINE static void CopyToAnyView(const Self& src, TVMFFIAny* result) { - return TypeTraits::MoveToAny(CopyToFFITuple(src), result); - } - - TVM_FFI_INLINE static void MoveToAny(Self&& src, TVMFFIAny* result) { - return TypeTraits::MoveToAny(MoveToFFITuple(std::move(src)), result); - } - - TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) { - if (src->type_index != TypeIndex::kTVMFFIArray) return false; - const ArrayObj& n = *reinterpret_cast(src->v_obj); - // check static length first - if (n.size_ != Nm) return false; - // then check element type - return CheckTupleSubType(std::make_index_sequence{}, n); - } - - TVM_FFI_INLINE static Self CopyFromAnyViewAfterCheck(const TVMFFIAny* src) { - return MoveToSTDTuple(TypeTraits::CopyFromAnyViewAfterCheck(src)); - } - - TVM_FFI_INLINE static Self MoveFromAnyAfterCheck(TVMFFIAny* src) { - return MoveToSTDTuple(TypeTraits::MoveFromAnyAfterCheck(src)); - } - - TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { - if (!CheckAnyStrict(src)) return std::nullopt; - return CopyFromAnyViewAfterCheck(src); - } TVM_FFI_INLINE static std::string TypeStr() { - std::stringstream os; - os << "std::tuple<"; - const char* sep = ""; - ((os << sep << details::Type2Str::v(), sep = ", "), ...); - os << ">"; - return std::move(os).str(); + return "std::optional<" + TypeTraits::TypeStr() + ">"; } TVM_FFI_INLINE static std::string TypeSchema() { - std::stringstream os; - os << R"({"type":"std::tuple","args":[)"; - const char* sep = ""; - ((os << sep << details::TypeSchema::v(), sep = ", "), ...); - os << "]}"; - return std::move(os).str(); + return R"({"type":"std::optional","args":[)" + details::TypeSchema::v() + "]}"; } }; @@ -316,30 +309,16 @@ template struct TypeTraits> : public TypeTraitsBase { private: using Self = std::variant; - using Tuple = std::tuple; + using ArgTuple = std::tuple; static constexpr std::size_t Nm = sizeof...(Args); - template - static Self MoveUnsafeAux(TVMFFIAny* src) { - if constexpr (Is >= Nm) { - TVM_FFI_ICHECK(false) << "Unreachable: variant TryCast failed."; - } else { - using ElemType = std::tuple_element_t; - if (TypeTraits::CheckAnyStrict(src)) { - return Self{TypeTraits::MoveFromAnyAfterCheck(src)}; - } else { - return MoveUnsafeAux(src); - } - } - } - template static Self CopyUnsafeAux(const TVMFFIAny* src) { if constexpr (Is >= Nm) { TVM_FFI_ICHECK(false) << "Unreachable: variant TryCast failed."; throw; // unreachable } else { - using ElemType = std::tuple_element_t; + using ElemType = std::tuple_element_t; if (TypeTraits::CheckAnyStrict(src)) { return Self{TypeTraits::CopyFromAnyViewAfterCheck(src)}; } else { @@ -353,7 +332,7 @@ struct TypeTraits> : public TypeTraitsBase { if constexpr (Is >= Nm) { return std::nullopt; } else { - using ElemType = std::tuple_element_t; + using ElemType = std::tuple_element_t; if (TypeTraits::CheckAnyStrict(src)) { return Self{TypeTraits::CopyFromAnyViewAfterCheck(src)}; } else { @@ -391,8 +370,8 @@ struct TypeTraits> : public TypeTraitsBase { } TVM_FFI_INLINE static Self MoveFromAnyAfterCheck(TVMFFIAny* src) { - // find the first possible type to move - return MoveUnsafeAux(src); + // find the first possible type to copy + return CopyUnsafeAux(src); } TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { @@ -419,6 +398,76 @@ struct TypeTraits> : public TypeTraitsBase { } }; +template +struct TypeTraits> : public TypeTraits { + private: + using Self = std::tuple; + static constexpr std::size_t Nm = sizeof...(Args); + static_assert(Nm > 0, "Zero-length std::tuple is not supported."); + + template + static bool CheckSubTypeAux(std::index_sequence, const ArrayObj& n) { + return (... && details::AnyUnsafe::CheckAnyStrict>(n[Is])); + } + + template + static Self ConstructTupleAux(std::index_sequence, const ArrayObj& n) { + return Self{CopyFromAny>(n[Is])...}; + } + + public: + static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIArray; + + TVM_FFI_INLINE static void CopyToAnyView(const Self& src, TVMFFIAny* result) { + return MoveToAnyImpl(CopyToTuple(src), result); + } + + TVM_FFI_INLINE static void MoveToAny(Self&& src, TVMFFIAny* result) { + return MoveToAnyImpl(MoveToTuple(std::move(src)), result); + } + + TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) { + if (src->type_index != TypeIndex::kTVMFFIArray) return false; + const ArrayObj& n = *reinterpret_cast(src->v_obj); + // check static length first + if (n.size_ != Nm) return false; + // then check element type + return CheckSubTypeAux(std::make_index_sequence{}, n); + } + + TVM_FFI_INLINE static Self CopyFromAnyViewAfterCheck(const TVMFFIAny* src) { + auto array = CopyFromAnyViewAfterCheckImpl(src); + return ConstructTupleAux(std::make_index_sequence{}, *array); + } + + TVM_FFI_INLINE static Self MoveFromAnyAfterCheck(TVMFFIAny* src) { + return CopyFromAnyViewAfterCheck(src); + } + + TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { + if (!CheckAnyStrict(src)) return std::nullopt; + return CopyFromAnyViewAfterCheck(src); + } + + TVM_FFI_INLINE static std::string TypeStr() { + std::stringstream os; + os << "std::tuple<"; + const char* sep = ""; + ((os << sep << details::Type2Str::v(), sep = ", "), ...); + os << ">"; + return std::move(os).str(); + } + + TVM_FFI_INLINE static std::string TypeSchema() { + std::stringstream os; + os << R"({"type":"std::tuple","args":[)"; + const char* sep = ""; + ((os << sep << details::TypeSchema::v(), sep = ", "), ...); + os << "]}"; + return std::move(os).str(); + } +}; + } // namespace ffi } // namespace tvm From 0b37fe37146a1c1b9f13166b574e526d97ec4d94 Mon Sep 17 00:00:00 2001 From: DarkSharpness <2040703891@qq.com> Date: Wed, 12 Nov 2025 21:50:36 +0800 Subject: [PATCH 09/13] test: add a simple python test --- tests/python/test_stl.cc | 73 ++++++++++++++++++++++++++++++++++++++++ tests/python/test_stl.py | 42 +++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 tests/python/test_stl.cc create mode 100644 tests/python/test_stl.py diff --git a/tests/python/test_stl.cc b/tests/python/test_stl.cc new file mode 100644 index 00000000..399678dc --- /dev/null +++ b/tests/python/test_stl.cc @@ -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 +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace { + +auto test_tuple(std::tuple arg) -> std::tuple { + return std::make_tuple(std::get<1>(arg), std::get<0>(arg)); +} + +auto test_vector(std::optional>> arg) + -> std::optional> { + if (arg) { + auto result = std::vector{}; + result.reserve(arg->size()); + for (const auto& row : *arg) { + result.push_back(std::accumulate(row.begin(), row.end(), 0)); + } + return result; + } else { + return std::nullopt; + } +} + +auto test_variant(std::variant>> arg) + -> std::variant> { + if (std::holds_alternative(arg)) { + return "int"; + } else if (std::holds_alternative(arg)) { + return "float"; + } else { + auto result = std::vector{}; + for (const auto& item : std::get>>(arg)) { + if (std::holds_alternative(item)) { + result.emplace_back("int"); + } else { + result.emplace_back("float"); + } + } + return result; + } +} + +TVM_FFI_DLL_EXPORT_TYPED_FUNC(test_tuple, test_tuple); +TVM_FFI_DLL_EXPORT_TYPED_FUNC(test_vector, test_vector); +TVM_FFI_DLL_EXPORT_TYPED_FUNC(test_variant, test_variant); + +} // namespace diff --git a/tests/python/test_stl.py b/tests/python/test_stl.py new file mode 100644 index 00000000..58867782 --- /dev/null +++ b/tests/python/test_stl.py @@ -0,0 +1,42 @@ +# 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. +import pathlib + +import pytest +import tvm_ffi.cpp +from tvm_ffi.module import Module + + +def test_stl() -> None: + cpp_path = pathlib.Path(__file__).parent.resolve() / "test_stl.cc" + output_lib_path = tvm_ffi.cpp.build( + name="test_stl", + cpp_files=[str(cpp_path)], + ) + + mod: Module = tvm_ffi.load_module(output_lib_path) + + assert list(mod.test_tuple([1, 2.5])) == [2.5, 1] + assert mod.test_vector(None) == None + assert list(mod.test_vector([[1, 2], [3, 4]])) == [3, 7] + assert mod.test_variant(1) == "int" + assert mod.test_variant(1.0) == "float" + assert list(mod.test_variant([1, 1.0])) == ["int", "float"] + + +if __name__ == "__main__": + pytest.main([__file__]) From 2c016fec4718ad70ed34f714baf4f2a9f32ad943 Mon Sep 17 00:00:00 2001 From: DarkSharpness <2040703891@qq.com> Date: Thu, 13 Nov 2025 03:57:10 +0800 Subject: [PATCH 10/13] feat: support map --- include/tvm/ffi/container/map.h | 3 + include/tvm/ffi/extra/stl.h | 327 ++++++++++++++++++++++---------- tests/python/test_stl.cc | 24 ++- tests/python/test_stl.py | 2 + 4 files changed, 250 insertions(+), 106 deletions(-) diff --git a/include/tvm/ffi/container/map.h b/include/tvm/ffi/container/map.h index b948fc8e..526b3455 100644 --- a/include/tvm/ffi/container/map.h +++ b/include/tvm/ffi/container/map.h @@ -246,6 +246,9 @@ class MapObj : public Object { // Reference class template friend class Map; + + template + friend struct TypeTraits; }; /*! \brief A specialization of small-sized hash map */ diff --git a/include/tvm/ffi/extra/stl.h b/include/tvm/ffi/extra/stl.h index 2493664a..41161fbc 100644 --- a/include/tvm/ffi/extra/stl.h +++ b/include/tvm/ffi/extra/stl.h @@ -33,7 +33,7 @@ #include #include #include -#include +#include #include #include #include @@ -42,10 +42,13 @@ #include #include #include +#include #include +#include #include #include #include +#include #include #include @@ -53,15 +56,18 @@ namespace tvm { namespace ffi { namespace details { -template +struct STLTypeMismatch : public std::exception { + const char* what() const noexcept override { return "STL type mismatch"; } +}; + struct STLTypeTrait : public TypeTraitsBase { public: - using TypeTraitsBase::convert_enabled; - using TypeTraitsBase::storage_enabled; + static constexpr bool storage_enabled = false; protected: - // we always copy STL types into an Object first, then move the ObjectPtr to Any. - TVM_FFI_INLINE static void MoveToAnyImpl(ObjectPtr&& src, TVMFFIAny* result) { + /// NOTE: we always copy STL types into an Object first, then move the ObjectPtr to Any. + template + TVM_FFI_INLINE static void MoveToAnyImpl(ObjectPtr&& src, TVMFFIAny* result) { TVMFFIObject* obj_ptr = ObjectUnsafe::MoveObjectPtrToTVMFFIObjectPtr(std::move(src)); result->type_index = obj_ptr->type_index; result->zero_padding = 0; @@ -69,18 +75,38 @@ struct STLTypeTrait : public TypeTraitsBase { result->v_obj = obj_ptr; } - // we always construct STL types from an Object first, then copy from the ObjectPtr in Any. - TVM_FFI_INLINE static ObjectPtr CopyFromAnyViewAfterCheckImpl(const TVMFFIAny* src) { - return details::ObjectUnsafe::ObjectPtrFromUnowned(src->v_obj); + /// NOTE: we always construct STL types from an Object first, then copy from the ObjectPtr in Any. + template + TVM_FFI_INLINE static ObjectPtr CopyFromAnyImpl(const TVMFFIAny* src) { + return ObjectUnsafe::ObjectPtrFromUnowned(src->v_obj); + } + + /// NOTE: STL types are not natively movable from Any, so we always make a new copy. + template + TVM_FFI_INLINE static T ConstructFromAny(const Any& value) { + using TypeTrait = TypeTraits; + if constexpr (std::is_same_v) { + return value; + } else if constexpr (auto any_ptr = AnyUnsafe::TVMFFIAnyPtrFromAny(value); + TypeTrait::storage_enabled) { + return TypeTrait::CopyFromAnyViewAfterCheck(any_ptr); + } else { // STL types allow TryCast + auto opt = TypeTrait::TryCastFromAnyView(any_ptr); + if (!opt.has_value()) { + throw STLTypeMismatch{}; + } + return std::move(*opt); + } } }; -struct ContainerTemplate {}; +struct ListTemplate {}; +struct MapTemplate {}; } // namespace details template <> -struct TypeTraits : public details::STLTypeTrait<::tvm::ffi::ArrayObj> { +struct TypeTraits : public details::STLTypeTrait { public: static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIArray; @@ -128,58 +154,76 @@ struct TypeTraits : public details::STLTypeTrait<::t TVM_FFI_INLINE static ObjectPtr MoveToArray(Range&& src) { return CopyToArrayImpl(std::make_move_iterator(std::begin(src)), std::size(src)); } - - /// NOTE: STL types are not natively movable from Any, so we always make a new copy. - template - TVM_FFI_INLINE static T CopyFromAny(const Any& value) { - return details::AnyUnsafe::CopyFromAnyViewAfterCheck(value); - } }; -template -struct TypeTraits> : public TypeTraits { +template <> +struct TypeTraits : public details::STLTypeTrait { public: - using Self = std::array; - static_assert(Nm > 0, "Zero-length std::array is not supported."); + static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIMap; - TVM_FFI_INLINE static void CopyToAnyView(const Self& src, TVMFFIAny* result) { - return MoveToAnyImpl(CopyToArray(src), result); + protected: + template + TVM_FFI_INLINE static ObjectPtr CopyToMap(const MapType& src) { + return MapObj::CreateFromRange(std::begin(src), std::end(src)); } - TVM_FFI_INLINE static void MoveToAny(Self&& src, TVMFFIAny* result) { - return MoveToAnyImpl(MoveToArray(std::move(src)), result); + template + TVM_FFI_INLINE static ObjectPtr MoveToMap(MapType&& src) { + return MapObj::CreateFromRange(std::make_move_iterator(std::begin(src)), + std::make_move_iterator(std::end(src))); } - TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) { + template + TVM_FFI_INLINE static MapType ConstructMap(const TVMFFIAny* src) { + using KeyType = typename MapType::key_type; + using ValueType = typename MapType::mapped_type; + auto result = MapType{}; + auto map_obj = CopyFromAnyImpl(src); + if constexpr (CanReserve) { + result.reserve(map_obj->size()); + } + for (const auto& [key, value] : *map_obj) { + result.try_emplace(ConstructFromAny(key), ConstructFromAny(value)); + } + return result; + } +}; + +template +struct TypeTraits> : public TypeTraits { + private: + using Self = std::array; + + TVM_FFI_INLINE static bool CheckAnyFast(const TVMFFIAny* src) { if (src->type_index != TypeIndex::kTVMFFIArray) return false; const ArrayObj& n = *reinterpret_cast(src->v_obj); - // check static length first - if (n.size_ != Nm) return false; - // then check element type - if constexpr (std::is_same_v) { - return true; - } else { - return std::all_of(n.begin(), n.end(), details::AnyUnsafe::CheckAnyStrict); - } + return n.size_ == Nm; } - TVM_FFI_INLINE static Self CopyFromAnyViewAfterCheck(const TVMFFIAny* src) { - auto array = CopyFromAnyViewAfterCheckImpl(src); - auto begin = array->MutableBegin(); - Self result; // no initialization to avoid overhead - for (std::size_t i = 0; i < Nm; ++i) { - result[i] = CopyFromAny(begin[i]); - } - return result; + public: + static_assert(Nm > 0, "Zero-length std::array is not supported."); + + TVM_FFI_INLINE static void CopyToAnyView(const Self& src, TVMFFIAny* result) { + return MoveToAnyImpl(CopyToArray(src), result); } - TVM_FFI_INLINE static Self MoveFromAnyAfterCheck(TVMFFIAny* src) { - return CopyFromAnyViewAfterCheck(src); + TVM_FFI_INLINE static void MoveToAny(Self&& src, TVMFFIAny* result) { + return MoveToAnyImpl(MoveToArray(std::move(src)), result); } TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { - if (!CheckAnyStrict(src)) return std::nullopt; - return CopyFromAnyViewAfterCheck(src); + if (!CheckAnyFast(src)) return std::nullopt; + try { + auto array = CopyFromAnyImpl(src); + auto begin = array->MutableBegin(); + Self result; // no initialization to avoid overhead + for (std::size_t i = 0; i < Nm; ++i) { + result[i] = ConstructFromAny(begin[i]); + } + return result; + } catch (const details::STLTypeMismatch&) { + return std::nullopt; + } } TVM_FFI_INLINE static std::string TypeStr() { @@ -193,10 +237,15 @@ struct TypeTraits> : public TypeTraits -struct TypeTraits> : public TypeTraits { - public: +struct TypeTraits> : public TypeTraits { + private: using Self = std::vector; + TVM_FFI_INLINE static bool CheckAnyFast(const TVMFFIAny* src) { + return src->type_index == TypeIndex::kTVMFFIArray; + } + + public: TVM_FFI_INLINE static void CopyToAnyView(const Self& src, TVMFFIAny* result) { return MoveToAnyImpl(CopyToArray(src), result); } @@ -205,35 +254,21 @@ struct TypeTraits> : public TypeTraitstype_index != TypeIndex::kTVMFFIArray) return false; - const ArrayObj& n = *reinterpret_cast(src->v_obj); - if constexpr (std::is_same_v) { - return true; - } else { - return std::all_of(n.begin(), n.end(), details::AnyUnsafe::CheckAnyStrict); - } - } - - TVM_FFI_INLINE static Self CopyFromAnyViewAfterCheck(const TVMFFIAny* src) { - auto array = CopyFromAnyViewAfterCheckImpl(src); - auto begin = array->MutableBegin(); - auto result = Self{}; - auto length = array->size_; - result.reserve(length); - for (std::size_t i = 0; i < length; ++i) { - result.emplace_back(CopyFromAny(begin[i])); - } - return result; - } - - TVM_FFI_INLINE static Self MoveFromAnyAfterCheck(TVMFFIAny* src) { - return CopyFromAnyViewAfterCheck(src); - } - TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { - if (!CheckAnyStrict(src)) return std::nullopt; - return CopyFromAnyViewAfterCheck(src); + if (!CheckAnyFast(src)) return std::nullopt; + try { + auto array = CopyFromAnyImpl(src); + auto begin = array->MutableBegin(); + auto result = Self{}; + auto length = array->size_; + result.reserve(length); + for (std::size_t i = 0; i < length; ++i) { + result.emplace_back(ConstructFromAny(begin[i])); + } + return result; + } catch (const details::STLTypeMismatch&) { + return std::nullopt; + } } TVM_FFI_INLINE static std::string TypeStr() { @@ -309,18 +344,17 @@ template struct TypeTraits> : public TypeTraitsBase { private: using Self = std::variant; - using ArgTuple = std::tuple; static constexpr std::size_t Nm = sizeof...(Args); template - static Self CopyUnsafeAux(const TVMFFIAny* src) { + TVM_FFI_INLINE static Self CopyUnsafeAux(const TVMFFIAny* src) { if constexpr (Is >= Nm) { TVM_FFI_ICHECK(false) << "Unreachable: variant TryCast failed."; throw; // unreachable } else { - using ElemType = std::tuple_element_t; + using ElemType = std::variant_alternative_t; if (TypeTraits::CheckAnyStrict(src)) { - return Self{TypeTraits::CopyFromAnyViewAfterCheck(src)}; + return Self{std::in_place_index, TypeTraits::CopyFromAnyViewAfterCheck(src)}; } else { return CopyUnsafeAux(src); } @@ -328,13 +362,28 @@ struct TypeTraits> : public TypeTraitsBase { } template - static std::optional TryCastAux(const TVMFFIAny* src) { + TVM_FFI_INLINE static Self MoveUnsafeAux(const TVMFFIAny* src) { if constexpr (Is >= Nm) { - return std::nullopt; + TVM_FFI_ICHECK(false) << "Unreachable: variant TryCast failed."; + throw; // unreachable } else { - using ElemType = std::tuple_element_t; + using ElemType = std::variant_alternative_t; if (TypeTraits::CheckAnyStrict(src)) { - return Self{TypeTraits::CopyFromAnyViewAfterCheck(src)}; + return Self{std::in_place_index, TypeTraits::MoveFromAnyAfterCheck(src)}; + } else { + return MoveUnsafeAux(src); + } + } + } + + template + TVM_FFI_INLINE static std::optional TryCastAux(const TVMFFIAny* src) { + if constexpr (Is >= Nm) { + return std::nullopt; + } else { + using ElemType = std::variant_alternative_t; + if (auto opt = TypeTraits::TryCastFromAnyView(src)) { + return Self{std::in_place_index, std::move(*opt)}; } else { return TryCastAux(src); } @@ -370,12 +419,12 @@ struct TypeTraits> : public TypeTraitsBase { } TVM_FFI_INLINE static Self MoveFromAnyAfterCheck(TVMFFIAny* src) { - // find the first possible type to copy - return CopyUnsafeAux(src); + // find the first possible type to move + return MoveUnsafeAux(src); } TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { - // find the first possible type to copy + // try to find the first possible type to copy return TryCastAux(src); } @@ -399,20 +448,21 @@ struct TypeTraits> : public TypeTraitsBase { }; template -struct TypeTraits> : public TypeTraits { +struct TypeTraits> : public TypeTraits { private: using Self = std::tuple; static constexpr std::size_t Nm = sizeof...(Args); static_assert(Nm > 0, "Zero-length std::tuple is not supported."); - template - static bool CheckSubTypeAux(std::index_sequence, const ArrayObj& n) { - return (... && details::AnyUnsafe::CheckAnyStrict>(n[Is])); + TVM_FFI_INLINE static bool CheckAnyFast(const TVMFFIAny* src) { + if (src->type_index != TypeIndex::kTVMFFIArray) return false; + const ArrayObj& n = *reinterpret_cast(src->v_obj); + return n.size_ == Nm; } template - static Self ConstructTupleAux(std::index_sequence, const ArrayObj& n) { - return Self{CopyFromAny>(n[Is])...}; + TVM_FFI_INLINE static Self ConstructTupleAux(std::index_sequence, const ArrayObj& n) { + return Self{ConstructFromAny>(n[Is])...}; } public: @@ -435,18 +485,14 @@ struct TypeTraits> : public TypeTraits{}, n); } - TVM_FFI_INLINE static Self CopyFromAnyViewAfterCheck(const TVMFFIAny* src) { - auto array = CopyFromAnyViewAfterCheckImpl(src); - return ConstructTupleAux(std::make_index_sequence{}, *array); - } - - TVM_FFI_INLINE static Self MoveFromAnyAfterCheck(TVMFFIAny* src) { - return CopyFromAnyViewAfterCheck(src); - } - TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { - if (!CheckAnyStrict(src)) return std::nullopt; - return CopyFromAnyViewAfterCheck(src); + if (!CheckAnyFast(src)) return std::nullopt; + try { + auto array = CopyFromAnyImpl(src); + return ConstructTupleAux(std::make_index_sequence{}, *array); + } catch (const details::STLTypeMismatch&) { + return std::nullopt; + } } TVM_FFI_INLINE static std::string TypeStr() { @@ -468,6 +514,79 @@ struct TypeTraits> : public TypeTraits +struct TypeTraits> : public TypeTraits { + private: + using Self = std::map; + TVM_FFI_INLINE static bool CheckAnyFast(const TVMFFIAny* src) { + return src->type_index == TypeIndex::kTVMFFIMap; + } + + public: + TVM_FFI_INLINE static void CopyToAnyView(const Self& src, TVMFFIAny* result) { + return MoveToAnyImpl(CopyToMap(src), result); + } + + TVM_FFI_INLINE static void MoveToAny(Self&& src, TVMFFIAny* result) { + return MoveToAnyImpl(MoveToMap(std::move(src)), result); + } + + TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { + if (!CheckAnyFast(src)) return std::nullopt; + try { + return ConstructMap(src); + } catch (const details::STLTypeMismatch&) { + return std::nullopt; + } + } + + TVM_FFI_INLINE static std::string TypeStr() { + return "std::map<" + details::Type2Str::v() + ", " + details::Type2Str::v() + ">"; + } + + TVM_FFI_INLINE static std::string TypeSchema() { + return R"({"type":"std::map","args":[)" + details::TypeSchema::v() + "," + + details::TypeSchema::v() + "]}"; + } +}; + +template +struct TypeTraits> : public TypeTraits { + private: + using Self = std::unordered_map; + TVM_FFI_INLINE static bool CheckAnyFast(const TVMFFIAny* src) { + return src->type_index == TypeIndex::kTVMFFIMap; + } + + public: + TVM_FFI_INLINE static void CopyToAnyView(const Self& src, TVMFFIAny* result) { + return MoveToAnyImpl(CopyToMap(src), result); + } + + TVM_FFI_INLINE static void MoveToAny(Self&& src, TVMFFIAny* result) { + return MoveToAnyImpl(MoveToMap(std::move(src)), result); + } + + TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { + if (!CheckAnyFast(src)) return std::nullopt; + try { + return ConstructMap(src); + } catch (const details::STLTypeMismatch&) { + return std::nullopt; + } + } + + TVM_FFI_INLINE static std::string TypeStr() { + return "std::unordered_map<" + details::Type2Str::v() + ", " + details::Type2Str::v() + + ">"; + } + + TVM_FFI_INLINE static std::string TypeSchema() { + return R"({"type":"std::unordered_map","args":[)" + details::TypeSchema::v() + "," + + details::TypeSchema::v() + "]}"; + } +}; + } // namespace ffi } // namespace tvm diff --git a/tests/python/test_stl.cc b/tests/python/test_stl.cc index 399678dc..4432c768 100644 --- a/tests/python/test_stl.cc +++ b/tests/python/test_stl.cc @@ -16,14 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -#include -#include #include +#include #include +#include #include #include #include +#include #include #include @@ -66,8 +67,27 @@ auto test_variant(std::variant> } } +auto test_map(const std::map& map) -> std::map { + auto result = std::map{}; + for (const auto& [key, value] : map) { + result[value] = key; + } + return result; +} + +auto test_map_2(const std::unordered_map& map) + -> std::unordered_map { + auto result = std::unordered_map{}; + for (const auto& [key, value] : map) { + result[value] = key; + } + return result; +} + TVM_FFI_DLL_EXPORT_TYPED_FUNC(test_tuple, test_tuple); TVM_FFI_DLL_EXPORT_TYPED_FUNC(test_vector, test_vector); TVM_FFI_DLL_EXPORT_TYPED_FUNC(test_variant, test_variant); +TVM_FFI_DLL_EXPORT_TYPED_FUNC(test_map, test_map); +TVM_FFI_DLL_EXPORT_TYPED_FUNC(test_map_2, test_map_2); } // namespace diff --git a/tests/python/test_stl.py b/tests/python/test_stl.py index 58867782..e54b821f 100644 --- a/tests/python/test_stl.py +++ b/tests/python/test_stl.py @@ -36,6 +36,8 @@ def test_stl() -> None: assert mod.test_variant(1) == "int" assert mod.test_variant(1.0) == "float" assert list(mod.test_variant([1, 1.0])) == ["int", "float"] + assert dict(mod.test_map({"a": 1, "b": 2})) == {1: "a", 2: "b"} + assert dict(mod.test_map_2({"a": 1, "b": 2})) == {1: "a", 2: "b"} if __name__ == "__main__": From dc85f56b392eced267721f2ce1886244354488ef Mon Sep 17 00:00:00 2001 From: DarkSharpness <2040703891@qq.com> Date: Thu, 13 Nov 2025 04:29:32 +0800 Subject: [PATCH 11/13] feat: support std::function --- include/tvm/ffi/extra/stl.h | 54 +++++++++++++++++++++++++++++++++++++ tests/python/test_stl.cc | 6 +++++ tests/python/test_stl.py | 2 ++ 3 files changed, 62 insertions(+) diff --git a/include/tvm/ffi/extra/stl.h b/include/tvm/ffi/extra/stl.h index 41161fbc..ce813720 100644 --- a/include/tvm/ffi/extra/stl.h +++ b/include/tvm/ffi/extra/stl.h @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,8 @@ #include #include +#include "tvm/ffi/function.h" + namespace tvm { namespace ffi { namespace details { @@ -587,6 +590,57 @@ struct TypeTraits> : public TypeTraits +struct TypeTraits> : TypeTraitsBase { + private: + using Self = std::function; + using Function = TypedFunction; + using ProxyTrait = TypeTraits>; + + TVM_FFI_INLINE static Self GetFunc(Function&& f) { + return [fn = std::move(f)](Args... args) -> Ret { return fn(std::forward(args)...); }; + } + + public: + static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIFunction; + static constexpr bool storage_enabled = false; + + TVM_FFI_INLINE static void CopyToAnyView(const Self& src, TVMFFIAny* result) { + return ProxyTrait::MoveToAny(Function{src}, result); + } + + TVM_FFI_INLINE static void MoveToAny(Self&& src, TVMFFIAny* result) { + return ProxyTrait::MoveToAny(Function{std::move(src)}, result); + } + + TVM_FFI_INLINE static std::optional TryCastFromAnyView(const TVMFFIAny* src) { + auto opt = ProxyTrait::TryCastFromAnyView(src); + if (opt.has_value()) { + return GetFunc(std::move(*opt)); + } else { + return std::nullopt; + } + } + + TVM_FFI_INLINE static std::string TypeStr() { + std::stringstream os; + os << "std::function<" << details::Type2Str::v() << "("; + const char* sep = ""; + ((os << sep << details::Type2Str::v(), sep = ", "), ...); + os << ")>"; + return std::move(os).str(); + } + + TVM_FFI_INLINE static std::string TypeSchema() { + std::stringstream os; + os << R"({"type":"std::function","args":[)" << details::TypeSchema::v() << ",["; + const char* sep = ""; + ((os << sep << details::TypeSchema::v(), sep = ", "), ...); + os << "]]}"; + return std::move(os).str(); + } +}; + } // namespace ffi } // namespace tvm diff --git a/tests/python/test_stl.cc b/tests/python/test_stl.cc index 4432c768..d7ed4211 100644 --- a/tests/python/test_stl.cc +++ b/tests/python/test_stl.cc @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -84,10 +85,15 @@ auto test_map_2(const std::unordered_map& map) return result; } +auto test_function(std::function f) -> std::function { + return [fn = std::move(f)] { return fn() + 1; }; +} + TVM_FFI_DLL_EXPORT_TYPED_FUNC(test_tuple, test_tuple); TVM_FFI_DLL_EXPORT_TYPED_FUNC(test_vector, test_vector); TVM_FFI_DLL_EXPORT_TYPED_FUNC(test_variant, test_variant); TVM_FFI_DLL_EXPORT_TYPED_FUNC(test_map, test_map); TVM_FFI_DLL_EXPORT_TYPED_FUNC(test_map_2, test_map_2); +TVM_FFI_DLL_EXPORT_TYPED_FUNC(test_function, test_function); } // namespace diff --git a/tests/python/test_stl.py b/tests/python/test_stl.py index e54b821f..dc71a631 100644 --- a/tests/python/test_stl.py +++ b/tests/python/test_stl.py @@ -38,6 +38,8 @@ def test_stl() -> None: assert list(mod.test_variant([1, 1.0])) == ["int", "float"] assert dict(mod.test_map({"a": 1, "b": 2})) == {1: "a", 2: "b"} assert dict(mod.test_map_2({"a": 1, "b": 2})) == {1: "a", 2: "b"} + assert mod.test_function(lambda: 0)() == 1 + assert mod.test_function(lambda: 10)() == 11 if __name__ == "__main__": From c46ea0f1ca45617b6c600b8ee482e1e211680205 Mon Sep 17 00:00:00 2001 From: DarkSharpness <2040703891@qq.com> Date: Thu, 13 Nov 2025 16:07:04 +0800 Subject: [PATCH 12/13] fix: fix type checking and add a test to check it --- include/tvm/ffi/extra/stl.h | 23 +++++++++++------------ tests/python/test_stl.py | 5 +++++ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/include/tvm/ffi/extra/stl.h b/include/tvm/ffi/extra/stl.h index ce813720..2046923d 100644 --- a/include/tvm/ffi/extra/stl.h +++ b/include/tvm/ffi/extra/stl.h @@ -90,11 +90,10 @@ struct STLTypeTrait : public TypeTraitsBase { using TypeTrait = TypeTraits; if constexpr (std::is_same_v) { return value; - } else if constexpr (auto any_ptr = AnyUnsafe::TVMFFIAnyPtrFromAny(value); - TypeTrait::storage_enabled) { - return TypeTrait::CopyFromAnyViewAfterCheck(any_ptr); - } else { // STL types allow TryCast - auto opt = TypeTrait::TryCastFromAnyView(any_ptr); + } else { + /// NOTE: Not all type support `CheckArgStrict` (e.g. std::string), + /// so we use `TryCast` instead (without any prior check). + auto opt = TypeTrait::TryCastFromAnyView(AnyUnsafe::TVMFFIAnyPtrFromAny(value)); if (!opt.has_value()) { throw STLTypeMismatch{}; } @@ -432,7 +431,7 @@ struct TypeTraits> : public TypeTraitsBase { } TVM_FFI_INLINE static std::string TypeStr() { - std::stringstream os; + std::ostringstream os; os << "std::variant<"; const char* sep = ""; ((os << sep << details::Type2Str::v(), sep = ", "), ...); @@ -441,7 +440,7 @@ struct TypeTraits> : public TypeTraitsBase { } TVM_FFI_INLINE static std::string TypeSchema() { - std::stringstream os; + std::ostringstream os; os << R"({"type":"std::variant","args":[)"; const char* sep = ""; ((os << sep << details::TypeSchema::v(), sep = ", "), ...); @@ -499,7 +498,7 @@ struct TypeTraits> : public TypeTraits::v(), sep = ", "), ...); @@ -508,7 +507,7 @@ struct TypeTraits> : public TypeTraits::v(), sep = ", "), ...); @@ -595,7 +594,7 @@ struct TypeTraits> : TypeTraitsBase { private: using Self = std::function; using Function = TypedFunction; - using ProxyTrait = TypeTraits>; + using ProxyTrait = TypeTraits; TVM_FFI_INLINE static Self GetFunc(Function&& f) { return [fn = std::move(f)](Args... args) -> Ret { return fn(std::forward(args)...); }; @@ -623,7 +622,7 @@ struct TypeTraits> : TypeTraitsBase { } TVM_FFI_INLINE static std::string TypeStr() { - std::stringstream os; + std::ostringstream os; os << "std::function<" << details::Type2Str::v() << "("; const char* sep = ""; ((os << sep << details::Type2Str::v(), sep = ", "), ...); @@ -632,7 +631,7 @@ struct TypeTraits> : TypeTraitsBase { } TVM_FFI_INLINE static std::string TypeSchema() { - std::stringstream os; + std::ostringstream os; os << R"({"type":"std::function","args":[)" << details::TypeSchema::v() << ",["; const char* sep = ""; ((os << sep << details::TypeSchema::v(), sep = ", "), ...); diff --git a/tests/python/test_stl.py b/tests/python/test_stl.py index dc71a631..6a24b37b 100644 --- a/tests/python/test_stl.py +++ b/tests/python/test_stl.py @@ -41,6 +41,11 @@ def test_stl() -> None: assert mod.test_function(lambda: 0)() == 1 assert mod.test_function(lambda: 10)() == 11 + with pytest.raises(TypeError): + mod.test_tuple([1.5, 2.5]) + with pytest.raises(TypeError): + mod.test_function(lambda: 0)(100) + if __name__ == "__main__": pytest.main([__file__]) From 9ac136056dc8bc55a08827bb13d5c724cc802cf4 Mon Sep 17 00:00:00 2001 From: DarkSharpness <2040703891@qq.com> Date: Thu, 20 Nov 2025 17:42:37 +0800 Subject: [PATCH 13/13] minor: docs and clean up --- include/tvm/ffi/extra/stl.h | 7 +++++-- tests/python/{ => cpp_src}/test_stl.cc | 0 tests/python/test_stl.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) rename tests/python/{ => cpp_src}/test_stl.cc (100%) diff --git a/include/tvm/ffi/extra/stl.h b/include/tvm/ffi/extra/stl.h index 2046923d..523f77fe 100644 --- a/include/tvm/ffi/extra/stl.h +++ b/include/tvm/ffi/extra/stl.h @@ -24,8 +24,11 @@ * which provides support for STL containers in C++ exported functions. * * Whenever possible, prefer using tvm/ffi/container/ implementations, - * such as `tvm::ffi::Array` and `tvm::ffi::Tuple`, over STL containers - * in exported functions for better performance and compatibility. + * such as `tvm::ffi::Array` and `tvm::ffi::Tuple`, over STL containers. + * + * Native ffi objects comes with stable data layout and can be directly accessed + * through compiled languages (Rust) and DSLs(via LLVM) with raw pointer access + * for better performance and compatibility. */ #ifndef TVM_FFI_EXTRA_STL_H_ #define TVM_FFI_EXTRA_STL_H_ diff --git a/tests/python/test_stl.cc b/tests/python/cpp_src/test_stl.cc similarity index 100% rename from tests/python/test_stl.cc rename to tests/python/cpp_src/test_stl.cc diff --git a/tests/python/test_stl.py b/tests/python/test_stl.py index 6a24b37b..4d87ff20 100644 --- a/tests/python/test_stl.py +++ b/tests/python/test_stl.py @@ -22,7 +22,7 @@ def test_stl() -> None: - cpp_path = pathlib.Path(__file__).parent.resolve() / "test_stl.cc" + cpp_path = pathlib.Path(__file__).parent.resolve() / "cpp_src" / "test_stl.cc" output_lib_path = tvm_ffi.cpp.build( name="test_stl", cpp_files=[str(cpp_path)],