Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions include/tvm/ffi/type_traits.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <tvm/ffi/error.h>
#include <tvm/ffi/object.h>

#include <limits>
#include <string>
#include <type_traits>
#include <utility>
Expand Down Expand Up @@ -279,6 +280,13 @@ struct TypeTraits<Int, std::enable_if_t<std::is_integral_v<Int>>> : public TypeT
static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIInt;

TVM_FFI_INLINE static void CopyToAnyView(const Int& src, TVMFFIAny* result) {
if constexpr (std::is_unsigned_v<Int> && sizeof(Int) >= sizeof(int64_t)) {
if (src > static_cast<Int>(std::numeric_limits<int64_t>::max())) {
TVM_FFI_THROW(OverflowError)
<< "Integer value " << src << " is too large to fit in int64_t. "
<< "Consider explicitly casting to int64_t first if this is intentional.";
}
}
result->type_index = TypeIndex::kTVMFFIInt;
result->zero_padding = 0;
result->v_int64 = static_cast<int64_t>(src);
Expand Down
24 changes: 18 additions & 6 deletions src/ffi/extra/structural_hash.cc
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,19 @@ class StructuralHashHandler {
std::swap(allow_free_var, map_free_vars_);
uint64_t hash_value = HashAny(val);
std::swap(allow_free_var, map_free_vars_);
return details::StableHashCombine(init_hash, hash_value);
return static_cast<int64_t>(details::StableHashCombine(init_hash, hash_value));
Copy link
Member

@tqchen tqchen Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need explicit comment.

// static_cast from int64_t to uint64_t will behave exactly like bit cast

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do the same for other places of the static cast

} else {
return details::StableHashCombine(init_hash, HashAny(val));
// we explicitly bitcast the result from `uint64_t` to `int64_t`.
// The range of `uint64_t` is too large to fit as `int64_t`, so if we don't bitcast,
// it will trigger an overflow error in `uint64_t` -> `Any` conversion.
return static_cast<int64_t>(details::StableHashCombine(init_hash, HashAny(val)));
}
});
}
hash_value = custom_s_hash[type_info->type_index]
.cast<ffi::Function>()(obj, hash_value, s_hash_callback_)
.cast<uint64_t>();
hash_value =
custom_s_hash[type_info->type_index]
.cast<ffi::Function>()(obj, static_cast<int64_t>(hash_value), s_hash_callback_)
.cast<uint64_t>();
}

if (structural_eq_hash_kind == kTVMFFISEqHashKindFreeVar) {
Expand Down Expand Up @@ -311,9 +315,17 @@ uint64_t StructuralHash::Hash(const Any& value, bool map_free_vars, bool skip_te
return handler.HashAny(value);
}

static int64_t FFIStructuralHash(const Any& value, bool map_free_vars, bool skip_tensor_content) {
uint64_t result = StructuralHash::Hash(value, map_free_vars, skip_tensor_content);
// we explicitly bitcast the result from `uint64_t` to `int64_t`.
// The range of `uint64_t` is too large to fit as `int64_t`, so if we don't bitcast,
// it will trigger an overflow error in `uint64_t` -> `Any` conversion.
return static_cast<int64_t>(result);
}

TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
refl::GlobalDef().def("ffi.StructuralHash", StructuralHash::Hash);
refl::GlobalDef().def("ffi.StructuralHash", FFIStructuralHash);
refl::EnsureTypeAttrColumn("__s_hash__");
}

Expand Down
16 changes: 16 additions & 0 deletions tests/cpp/test_any.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
#include <tvm/ffi/any.h>
#include <tvm/ffi/memory.h>

#include <limits>

#include "./testing_object.h"

namespace {
Expand Down Expand Up @@ -58,6 +60,20 @@ TEST(Any, Int) {
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) {
Expand Down
5 changes: 2 additions & 3 deletions tests/cpp/testing_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,8 @@ class TCustomFuncObj : public Object {
return true;
}

uint64_t SHash(uint64_t init_hash,
ffi::TypedFunction<uint64_t(AnyView, uint64_t, bool)> hash) const {
uint64_t hash_value = init_hash;
int64_t SHash(int64_t init_hash, ffi::TypedFunction<int64_t(AnyView, int64_t, bool)> hash) const {
int64_t hash_value = init_hash;
hash_value = hash(params, hash_value, true);
hash_value = hash(body, hash_value, false);
return hash_value;
Expand Down