This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git
The following commit(s) were added to refs/heads/main by this push:
new 18afa9c8 [FIX] Construct small moved strings and bytes inline (#898)
18afa9c8 is described below
commit 18afa9c8ac90467b635171d208000ef5992471e4
Author: Tianqi Chen <[email protected]>
AuthorDate: Mon Nov 2 17:16:15 2026 -0500
[FIX] Construct small moved strings and bytes inline (#898)
Use inline storage when constructing `String` or `Bytes` from a small
`std::string` rvalue. Share the existing small-value capacity check and
retain the moved heap representation for larger inputs.
Existing externally supplied heap objects retain their representation.
---
include/tvm/ffi/string.h | 16 ++++++++++++----
tests/cpp/test_any.cc | 6 +++---
tests/cpp/test_string.cc | 27 +++++++++++++++++++++++++++
3 files changed, 42 insertions(+), 7 deletions(-)
diff --git a/include/tvm/ffi/string.h b/include/tvm/ffi/string.h
index a830d581..69fa37f0 100644
--- a/include/tvm/ffi/string.h
+++ b/include/tvm/ffi/string.h
@@ -185,7 +185,13 @@ class BytesBaseCell {
}
template <typename LargeObj>
- void InitFromStd(std::string&& other, int32_t large_type_index) {
+ void InitFromStd(std::string&& other, int32_t small_type_index, int32_t
large_type_index) {
+ if (other.size() <= kMaxSmallBytesLen) {
+ char* dest_data =
+ InitSpaceForSize<LargeObj>(other.size(), small_type_index,
large_type_index);
+ std::memcpy(dest_data, other.c_str(), other.size() + 1);
+ return;
+ }
// needs to be reset to none first for exception safety
data_.type_index = TypeIndex::kTVMFFINone;
data_.zero_padding = 0;
@@ -205,7 +211,6 @@ class BytesBaseCell {
*/
template <typename LargeObj>
char* InitSpaceForSize(size_t size, int32_t small_type_index, int32_t
large_type_index) {
- size_t kMaxSmallBytesLen = sizeof(int64_t) - 1;
// first zero the content, this is important for exception safety
data_.type_index = small_type_index;
data_.zero_padding = 0;
@@ -255,6 +260,7 @@ class BytesBaseCell {
}
private:
+ static constexpr size_t kMaxSmallBytesLen = sizeof(int64_t) - 1;
explicit BytesBaseCell(TVMFFIAny data) : data_(data) {}
/*! \brief internal backing data */
TVMFFIAny data_;
@@ -297,7 +303,8 @@ class Bytes {
* \param other a char array.
*/
Bytes(std::string&& other) { // NOLINT(*)
- data_.InitFromStd<detail::BytesObj>(std::move(other),
TypeIndex::kTVMFFIBytes);
+ data_.InitFromStd<detail::BytesObj>(std::move(other),
TypeIndex::kTVMFFISmallBytes,
+ TypeIndex::kTVMFFIBytes);
}
/*!
* \brief Swap this String with another string
@@ -498,7 +505,8 @@ class String {
String(std::string&& other) { // NOLINT(*)
// exception safety, first set to none so if exception is thrown
// destructor works correctly
- data_.InitFromStd<detail::StringObj>(std::move(other),
TypeIndex::kTVMFFIStr);
+ data_.InitFromStd<detail::StringObj>(std::move(other),
TypeIndex::kTVMFFISmallStr,
+ TypeIndex::kTVMFFIStr);
}
/*!
diff --git a/tests/cpp/test_any.cc b/tests/cpp/test_any.cc
index 73ef6de1..16b753fa 100644
--- a/tests/cpp/test_any.cc
+++ b/tests/cpp/test_any.cc
@@ -558,15 +558,15 @@ TEST(Any, ObjectMove) {
TEST(Any, AnyEqualHash) {
// small string
Any a = "a1";
- // on heap allocated string
- Any b = String(std::string("a1"));
+ // Explicit heap objects retain their representation, even for small
contents.
+ Any b = make_object<detail::BytesObjStdImpl<detail::StringObj>>("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"));
+ Any d = make_object<detail::BytesObjStdImpl<detail::BytesObj>>("a1");
EXPECT_EQ(c.type_index(), TypeIndex::kTVMFFISmallBytes);
EXPECT_EQ(d.type_index(), TypeIndex::kTVMFFIBytes);
EXPECT_TRUE(AnyEqual()(c, d));
diff --git a/tests/cpp/test_string.cc b/tests/cpp/test_string.cc
index 6df13cfe..38db118b 100644
--- a/tests/cpp/test_string.cc
+++ b/tests/cpp/test_string.cc
@@ -20,6 +20,8 @@
#include <tvm/ffi/any.h>
#include <tvm/ffi/string.h>
+#include <cstdint>
+
namespace {
using namespace tvm::ffi;
@@ -34,6 +36,31 @@ TEST(String, MoveFromStd) {
EXPECT_EQ(source.size(), 0); // NOLINT(bugprone-use-after-move)
}
+TEST(String, MoveFromStdStorage) {
+ for (const std::string& expected : {std::string(), std::string("a\0b", 3),
std::string(7, 'x'),
+ std::string(8, 'x'), std::string(64,
'x')}) {
+ std::string string_source = expected;
+ std::string bytes_source = expected;
+ const auto string_address =
reinterpret_cast<std::uintptr_t>(string_source.data());
+ const auto bytes_address =
reinterpret_cast<std::uintptr_t>(bytes_source.data());
+ String str(std::move(string_source));
+ Bytes bytes(std::move(bytes_source));
+ const bool small = expected.size() <= 7;
+ EXPECT_EQ(AnyView(str).type_index(),
+ small ? TypeIndex::kTVMFFISmallStr : TypeIndex::kTVMFFIStr);
+ EXPECT_EQ(AnyView(bytes).type_index(),
+ small ? TypeIndex::kTVMFFISmallBytes : TypeIndex::kTVMFFIBytes);
+ EXPECT_EQ(std::string(str), expected);
+ EXPECT_EQ(std::string(bytes), expected);
+ EXPECT_EQ(str.data()[str.size()], '\0');
+ EXPECT_EQ(bytes.data()[bytes.size()], '\0');
+ if (expected.size() == 64) {
+ EXPECT_EQ(reinterpret_cast<std::uintptr_t>(str.data()), string_address);
+ EXPECT_EQ(reinterpret_cast<std::uintptr_t>(bytes.data()), bytes_address);
+ }
+ }
+}
+
TEST(String, CopyFromStd) {
using namespace std;
string source = "this is a string";