gemini-code-assist[bot] commented on code in PR #472:
URL: https://github.com/apache/tvm-ffi/pull/472#discussion_r2844480046


##########
src/ffi/extra/deep_copy.cc:
##########
@@ -155,9 +169,66 @@ class ObjectDeepCopier {
     });
   }
 
+  /*!
+   * \brief Replace stale original-object references inside mutable containers.
+   *
+   * When an immutable container (Array/Map) is in-progress and a mutable child
+   * (List/Dict) references it back, the original is stored as a placeholder.
+   * After all copies are built, this pass replaces those placeholders with
+   * the actual copies from copy_map_.
+   */
+  void FixupDeferredReferences() {
+    for (auto& [orig_ptr, copy_any] : copy_map_) {
+      const Object* copy_obj = copy_any.as<Object>();
+      if (!copy_obj) continue;
+      int32_t ti = copy_obj->type_index();
+      if (ti == TypeIndex::kTVMFFIList) {
+        FixupList(copy_any);
+      } else if (ti == TypeIndex::kTVMFFIDict) {
+        FixupDict(copy_any);
+      }
+    }
+  }
+
+  void FixupList(const Any& list_any) {
+    List<Any> list = list_any.cast<List<Any>>();
+    int64_t n = static_cast<int64_t>(list.size());
+    for (int64_t i = 0; i < n; ++i) {
+      const Any& elem = list[i];
+      if (elem.type_index() < TypeIndex::kTVMFFIStaticObjectBegin) continue;
+      const Object* elem_obj = elem.as<Object>();
+      if (!elem_obj) continue;
+      auto it = copy_map_.find(elem_obj);
+      if (it != copy_map_.end()) {
+        list.Set(i, it->second);
+      }
+    }
+  }

Review Comment:
   ![high](https://www.gstatic.com/codereviewagent/high-priority.svg)
   
   Update `FixupList` to take a non-const reference to `Any` and update it if 
the list was modified. This ensures that if COW occurred during `list.Set`, the 
updated list is reflected back in `copy_map_`.
   
   ```c
     void FixupList(Any& list_any) {
       List<Any> list = list_any.cast<List<Any>>();
       int64_t n = static_cast<int64_t>(list.size());
       bool changed = false;
       for (int64_t i = 0; i < n; ++i) {
         const Any& elem = list[i];
         if (elem.type_index() < TypeIndex::kTVMFFIStaticObjectBegin) continue;
         const Object* elem_obj = elem.as<Object>();
         if (!elem_obj) continue;
         auto it = copy_map_.find(elem_obj);
         if (it != copy_map_.end()) {
           list.Set(i, it->second);
           changed = true;
         }
       }
       if (changed) list_any = list;
     }
   ```



##########
src/ffi/extra/deep_copy.cc:
##########
@@ -155,9 +169,66 @@ class ObjectDeepCopier {
     });
   }
 
+  /*!
+   * \brief Replace stale original-object references inside mutable containers.
+   *
+   * When an immutable container (Array/Map) is in-progress and a mutable child
+   * (List/Dict) references it back, the original is stored as a placeholder.
+   * After all copies are built, this pass replaces those placeholders with
+   * the actual copies from copy_map_.
+   */
+  void FixupDeferredReferences() {
+    for (auto& [orig_ptr, copy_any] : copy_map_) {
+      const Object* copy_obj = copy_any.as<Object>();
+      if (!copy_obj) continue;
+      int32_t ti = copy_obj->type_index();
+      if (ti == TypeIndex::kTVMFFIList) {
+        FixupList(copy_any);
+      } else if (ti == TypeIndex::kTVMFFIDict) {
+        FixupDict(copy_any);
+      }
+    }
+  }

Review Comment:
   ![high](https://www.gstatic.com/codereviewagent/high-priority.svg)
   
   The deferred fixup logic should handle potential Copy-On-Write (COW) 
behavior in `List` and `Dict`. When `Set` is called on a container with a 
reference count greater than 1 (which is the case here as it's stored in 
`copy_map_`), it may create a new underlying object. The `Any` stored in 
`copy_map_` must be updated to point to this new object to ensure the final 
deep copy result is correct.



##########
src/ffi/extra/recursive_compare.cc:
##########
@@ -0,0 +1,373 @@
+/*
+ * 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.
+ */
+/*
+ * \file src/ffi/extra/recursive_compare.cc
+ *
+ * \brief Reflection-based recursive comparison (Eq, Lt, Le, Gt, Ge).
+ */
+#include <tvm/ffi/any.h>
+#include <tvm/ffi/container/array.h>
+#include <tvm/ffi/container/dict.h>
+#include <tvm/ffi/container/list.h>
+#include <tvm/ffi/container/map.h>
+#include <tvm/ffi/container/shape.h>
+#include <tvm/ffi/container/tensor.h>
+#include <tvm/ffi/dtype.h>
+#include <tvm/ffi/error.h>
+#include <tvm/ffi/reflection/accessor.h>
+#include <tvm/ffi/reflection/registry.h>
+
+#include <cmath>
+
+namespace tvm {
+namespace ffi {
+
+namespace {
+
+/*!
+ * \brief Three-way recursive comparer.
+ *
+ * Returns int32_t: -1 (lhs < rhs), 0 (equal), +1 (lhs > rhs).
+ *
+ * When eq_only_ is true, type mismatches return non-zero instead of
+ * throwing, and Map/Dict ordering is not attempted.
+ */
+class RecursiveComparer {
+ public:
+  explicit RecursiveComparer(bool eq_only) : eq_only_(eq_only) {}
+
+  int32_t CompareAny(const Any& lhs, const Any& rhs) {
+    ++depth_;
+    struct DepthGuard {
+      int32_t& d;
+      ~DepthGuard() { --d; }
+    } guard{depth_};
+    if (depth_ > kMaxDepth) {
+      TVM_FFI_THROW(ValueError) << "RecursiveCompare: maximum recursion depth 
(" << kMaxDepth
+                                << ") exceeded; possible cycle";
+    }
+    using details::AnyUnsafe;
+    const TVMFFIAny* lhs_data = AnyUnsafe::TVMFFIAnyPtrFromAny(lhs);
+    const TVMFFIAny* rhs_data = AnyUnsafe::TVMFFIAnyPtrFromAny(rhs);
+    int32_t lti = lhs_data->type_index;
+    int32_t rti = rhs_data->type_index;
+
+    // Handle None specially: None == None, None < any non-None
+    if (lti == TypeIndex::kTVMFFINone && rti == TypeIndex::kTVMFFINone) return 
0;
+    if (lti == TypeIndex::kTVMFFINone) return -1;
+    if (rti == TypeIndex::kTVMFFINone) return 1;
+
+    // Handle String (Str/SmallStr cross-variant) before type-mismatch check
+    if (IsStringType(lti) && IsStringType(rti)) {
+      return CompareString(lhs, rhs, lhs_data, rhs_data, lti, rti);
+    }
+    // Handle Bytes (Bytes/SmallBytes cross-variant) before type-mismatch check
+    if (IsBytesType(lti) && IsBytesType(rti)) {
+      return CompareBytes(lhs, rhs, lhs_data, rhs_data, lti, rti);
+    }
+
+    // Type mismatch
+    if (lti != rti) {
+      if (eq_only_) return 1;  // not equal
+      TVM_FFI_THROW(TypeError) << "Cannot compare values of different types: " 
<< lhs.GetTypeKey()
+                               << " vs " << rhs.GetTypeKey();
+    }
+
+    // Same type — POD dispatch
+    if (lti < TypeIndex::kTVMFFIStaticObjectBegin) {
+      return ComparePOD(lhs, rhs, lhs_data, rhs_data, lti);
+    }
+
+    // Object types — check pointer identity first
+    const Object* lhs_obj = static_cast<const Object*>(lhs.as<Object>());
+    const Object* rhs_obj = static_cast<const Object*>(rhs.as<Object>());
+    if (lhs_obj == rhs_obj) return 0;
+    if (lhs_obj == nullptr) return -1;
+    if (rhs_obj == nullptr) return 1;
+
+    switch (lti) {
+      case TypeIndex::kTVMFFIStr:
+        return CompareString(lhs, rhs, lhs_data, rhs_data, lti, rti);
+      case TypeIndex::kTVMFFIBytes:
+        return CompareBytes(lhs, rhs, lhs_data, rhs_data, lti, rti);
+      case TypeIndex::kTVMFFIArray:
+        return 
CompareSequence(AnyUnsafe::CopyFromAnyViewAfterCheck<Array<Any>>(lhs),
+                               
AnyUnsafe::CopyFromAnyViewAfterCheck<Array<Any>>(rhs));
+      case TypeIndex::kTVMFFIList:
+        return 
CompareSequence(AnyUnsafe::CopyFromAnyViewAfterCheck<List<Any>>(lhs),
+                               
AnyUnsafe::CopyFromAnyViewAfterCheck<List<Any>>(rhs));
+      case TypeIndex::kTVMFFIMap:
+        return CompareMap(AnyUnsafe::CopyFromAnyViewAfterCheck<Map<Any, 
Any>>(lhs),
+                          AnyUnsafe::CopyFromAnyViewAfterCheck<Map<Any, 
Any>>(rhs));
+      case TypeIndex::kTVMFFIDict:
+        return CompareMap(AnyUnsafe::CopyFromAnyViewAfterCheck<Dict<Any, 
Any>>(lhs),
+                          AnyUnsafe::CopyFromAnyViewAfterCheck<Dict<Any, 
Any>>(rhs));
+      case TypeIndex::kTVMFFIShape:
+        return CompareShape(AnyUnsafe::CopyFromAnyViewAfterCheck<Shape>(lhs),
+                            AnyUnsafe::CopyFromAnyViewAfterCheck<Shape>(rhs));
+      default:
+        return CompareObject(lhs_obj, rhs_obj);
+    }
+  }
+
+ private:
+  static constexpr int32_t kMaxDepth = 128;
+  bool eq_only_;
+  int32_t depth_ = 0;
+
+  static bool IsStringType(int32_t ti) {
+    return ti == TypeIndex::kTVMFFIStr || ti == TypeIndex::kTVMFFISmallStr;
+  }
+
+  static bool IsBytesType(int32_t ti) {
+    return ti == TypeIndex::kTVMFFIBytes || ti == TypeIndex::kTVMFFISmallBytes;
+  }
+
+  static int32_t Sign(int64_t v) { return (v > 0) - (v < 0); }
+
+  // ---------- POD comparison ----------
+
+  int32_t ComparePOD(const Any& lhs, const Any& rhs, const TVMFFIAny* lhs_data,
+                     const TVMFFIAny* rhs_data, int32_t ti) {
+    switch (ti) {
+      case TypeIndex::kTVMFFIBool: {
+        bool a = lhs_data->v_int64 != 0;
+        bool b = rhs_data->v_int64 != 0;
+        return static_cast<int32_t>(a) - static_cast<int32_t>(b);
+      }
+      case TypeIndex::kTVMFFIInt: {
+        int64_t a = lhs_data->v_int64;
+        int64_t b = rhs_data->v_int64;
+        if (a < b) return -1;
+        if (a > b) return 1;
+        return 0;
+      }
+      case TypeIndex::kTVMFFIFloat: {
+        double a = lhs_data->v_float64;
+        double b = rhs_data->v_float64;
+        // NaN == NaN for equality
+        if (std::isnan(a) && std::isnan(b)) {
+          if (eq_only_) return 0;
+          TVM_FFI_THROW(TypeError) << "Cannot order NaN values";
+        }
+        if (std::isnan(a) || std::isnan(b)) {
+          if (eq_only_) return 1;  // not equal
+          TVM_FFI_THROW(TypeError) << "Cannot order NaN values";
+        }
+        if (a < b) return -1;
+        if (a > b) return 1;
+        return 0;
+      }
+      case TypeIndex::kTVMFFIDataType: {
+        DLDataType a = lhs_data->v_dtype;
+        DLDataType b = rhs_data->v_dtype;
+        if (a.code != b.code) return (a.code < b.code) ? -1 : 1;
+        if (a.bits != b.bits) return (a.bits < b.bits) ? -1 : 1;
+        if (a.lanes != b.lanes) return (a.lanes < b.lanes) ? -1 : 1;
+        return 0;
+      }
+      case TypeIndex::kTVMFFIDevice: {
+        DLDevice a = lhs_data->v_device;
+        DLDevice b = rhs_data->v_device;
+        if (a.device_type != b.device_type) return (a.device_type < 
b.device_type) ? -1 : 1;
+        if (a.device_id != b.device_id) return (a.device_id < b.device_id) ? 
-1 : 1;
+        return 0;
+      }
+      default: {
+        // Other POD types: bitwise equality only
+        if (lhs_data->zero_padding == rhs_data->zero_padding &&
+            lhs_data->v_int64 == rhs_data->v_int64) {
+          return 0;
+        }
+        if (eq_only_) return 1;
+        TVM_FFI_THROW(TypeError) << "Cannot order values of type " << 
lhs.GetTypeKey();
+      }
+    }
+    TVM_FFI_UNREACHABLE();
+  }
+
+  // ---------- String comparison (handles SmallStr cross-variant) ----------
+
+  int32_t CompareString(const Any& lhs, const Any& rhs, const TVMFFIAny* 
lhs_data,
+                        const TVMFFIAny* rhs_data, int32_t lti, int32_t rti) {
+    const char* lhs_ptr;
+    size_t lhs_len;
+    const char* rhs_ptr;
+    size_t rhs_len;
+    GetStringData(lhs, lhs_data, lti, &lhs_ptr, &lhs_len);
+    GetStringData(rhs, rhs_data, rti, &rhs_ptr, &rhs_len);
+    return SignFromMemncmp(Bytes::memncmp(lhs_ptr, rhs_ptr, lhs_len, rhs_len));
+  }
+
+  // ---------- Bytes comparison (handles SmallBytes cross-variant) ----------
+
+  int32_t CompareBytes(const Any& lhs, const Any& rhs, const TVMFFIAny* 
lhs_data,
+                       const TVMFFIAny* rhs_data, int32_t lti, int32_t rti) {
+    const char* lhs_ptr;
+    size_t lhs_len;
+    const char* rhs_ptr;
+    size_t rhs_len;
+    GetBytesData(lhs, lhs_data, lti, &lhs_ptr, &lhs_len);
+    GetBytesData(rhs, rhs_data, rti, &rhs_ptr, &rhs_len);
+    return SignFromMemncmp(Bytes::memncmp(lhs_ptr, rhs_ptr, lhs_len, rhs_len));
+  }
+
+  static void GetStringData(const Any& val, const TVMFFIAny* data, int32_t ti, 
const char** out_ptr,
+                            size_t* out_len) {
+    if (ti == TypeIndex::kTVMFFISmallStr) {
+      *out_ptr = data->v_bytes;
+      *out_len = data->small_str_len;
+    } else {
+      const auto* obj =
+          details::AnyUnsafe::CopyFromAnyViewAfterCheck<const 
details::BytesObjBase*>(val);
+      *out_ptr = obj->data;
+      *out_len = obj->size;
+    }
+  }
+
+  static void GetBytesData(const Any& val, const TVMFFIAny* data, int32_t ti, 
const char** out_ptr,
+                           size_t* out_len) {
+    if (ti == TypeIndex::kTVMFFISmallBytes) {
+      *out_ptr = data->v_bytes;
+      *out_len = data->small_str_len;
+    } else {
+      const auto* obj =
+          details::AnyUnsafe::CopyFromAnyViewAfterCheck<const 
details::BytesObjBase*>(val);
+      *out_ptr = obj->data;
+      *out_len = obj->size;
+    }
+  }
+
+  static int32_t SignFromMemncmp(int v) {
+    if (v < 0) return -1;
+    if (v > 0) return 1;
+    return 0;
+  }
+
+  // ---------- Sequence comparison (Array / List) ----------
+
+  template <typename SeqType>
+  int32_t CompareSequence(const SeqType& lhs, const SeqType& rhs) {
+    size_t min_len = std::min(lhs.size(), rhs.size());
+    for (size_t i = 0; i < min_len; ++i) {
+      int32_t cmp = CompareAny(lhs[i], rhs[i]);
+      if (cmp != 0) return cmp;
+    }
+    if (lhs.size() < rhs.size()) return -1;
+    if (lhs.size() > rhs.size()) return 1;
+    return 0;
+  }
+
+  // ---------- Map / Dict comparison (equality only) ----------
+
+  template <typename MapType>
+  int32_t CompareMap(const MapType& lhs, const MapType& rhs) {
+    if (lhs.size() != rhs.size()) {
+      if (eq_only_) return 1;
+      TVM_FFI_THROW(TypeError) << "Cannot order Map/Dict values";
+    }
+    for (const auto& kv : lhs) {
+      auto it = rhs.find(kv.first);
+      if (it == rhs.end()) {
+        if (eq_only_) return 1;
+        TVM_FFI_THROW(TypeError) << "Cannot order Map/Dict values";
+      }
+      int32_t cmp = CompareAny(kv.second, (*it).second);
+      if (cmp != 0) {
+        if (!eq_only_) {
+          TVM_FFI_THROW(TypeError) << "Cannot order Map/Dict values";
+        }
+        return cmp;
+      }
+    }
+    return 0;
+  }
+
+  // ---------- Shape comparison ----------
+
+  int32_t CompareShape(const Shape& lhs, const Shape& rhs) {
+    size_t min_len = std::min(lhs.size(), rhs.size());
+    for (size_t i = 0; i < min_len; ++i) {
+      if (lhs[i] < rhs[i]) return -1;
+      if (lhs[i] > rhs[i]) return 1;
+    }
+    if (lhs.size() < rhs.size()) return -1;
+    if (lhs.size() > rhs.size()) return 1;
+    return 0;
+  }
+
+  // ---------- Reflected Object comparison ----------
+
+  int32_t CompareObject(const Object* lhs, const Object* rhs) {
+    // Different type indices
+    if (lhs->type_index() != rhs->type_index()) {
+      if (eq_only_) return 1;
+      const TVMFFITypeInfo* lhs_info = TVMFFIGetTypeInfo(lhs->type_index());
+      const TVMFFITypeInfo* rhs_info = TVMFFIGetTypeInfo(rhs->type_index());
+      TVM_FFI_THROW(TypeError) << "Cannot compare objects of different types: "
+                               << String(lhs_info->type_key) << " vs "
+                               << String(rhs_info->type_key);
+    }
+    const TVMFFITypeInfo* type_info = TVMFFIGetTypeInfo(lhs->type_index());
+    int32_t result = 0;
+    reflection::ForEachFieldInfoWithEarlyStop(type_info, [&](const 
TVMFFIFieldInfo* finfo) {

Review Comment:
   ![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg)
   
   Potential null pointer dereference if `TVMFFIGetTypeInfo` returns `NULL`. 
Please add a null check before passing `type_info` to 
`ForEachFieldInfoWithEarlyStop`.
   
   ```c
       const TVMFFITypeInfo* type_info = TVMFFIGetTypeInfo(lhs->type_index());
       if (!type_info) return 0;
       int32_t result = 0;
       reflection::ForEachFieldInfoWithEarlyStop(type_info, [&](const 
TVMFFIFieldInfo* finfo) {
   ```



##########
src/ffi/extra/recursive_hash.cc:
##########
@@ -0,0 +1,325 @@
+/*
+ * 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.
+ */
+/*
+ * \file src/ffi/extra/recursive_hash.cc
+ *
+ * \brief Reflection-based recursive hash (companion to RecursiveEq).
+ */
+#include <tvm/ffi/any.h>
+#include <tvm/ffi/base_details.h>
+#include <tvm/ffi/container/array.h>
+#include <tvm/ffi/container/dict.h>
+#include <tvm/ffi/container/list.h>
+#include <tvm/ffi/container/map.h>
+#include <tvm/ffi/container/shape.h>
+#include <tvm/ffi/container/tensor.h>
+#include <tvm/ffi/dtype.h>
+#include <tvm/ffi/error.h>
+#include <tvm/ffi/reflection/accessor.h>
+#include <tvm/ffi/reflection/registry.h>
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+namespace tvm {
+namespace ffi {
+
+namespace {
+
+/*!
+ * \brief Reflection-based recursive hasher.
+ *
+ * Computes a deterministic hash consistent with RecursiveEq:
+ *   RecursiveEq(a, b) => RecursiveHash(a) == RecursiveHash(b)
+ */
+class RecursiveHasher {
+ public:
+  uint64_t HashAny(const Any& value) {
+    ++depth_;
+    struct DepthGuard {
+      int32_t& d;
+      ~DepthGuard() { --d; }
+    } guard{depth_};
+    if (depth_ > kMaxDepth) {
+      TVM_FFI_THROW(ValueError) << "RecursiveHash: maximum recursion depth (" 
<< kMaxDepth
+                                << ") exceeded; possible cycle";
+    }
+    using details::AnyUnsafe;
+    const TVMFFIAny* data = AnyUnsafe::TVMFFIAnyPtrFromAny(value);
+    int32_t ti = data->type_index;
+
+    // None
+    if (ti == TypeIndex::kTVMFFINone) {
+      return details::StableHashCombine(uint64_t{0}, uint64_t{0});
+    }
+
+    // String (Str/SmallStr cross-variant)
+    if (IsStringType(ti)) {
+      return HashString(value, data, ti);
+    }
+    // Bytes (Bytes/SmallBytes cross-variant)
+    if (IsBytesType(ti)) {
+      return HashBytes(value, data, ti);
+    }
+
+    // POD types
+    if (ti < TypeIndex::kTVMFFIStaticObjectBegin) {
+      return HashPOD(value, data, ti);
+    }
+
+    // Object types — memoization + cycle detection.
+    const Object* obj = static_cast<const Object*>(value.as<Object>());
+    if (obj == nullptr) {
+      return details::StableHashCombine(uint64_t{0}, uint64_t{0});
+    }
+    // Return memoized hash if this object was already fully hashed.
+    auto memo_it = memo_.find(obj);
+    if (memo_it != memo_.end()) return memo_it->second;
+    // Cycle detection: if this object is currently on the call stack,
+    // return a sentinel to break the cycle.
+    auto [stack_it, inserted] = on_stack_.insert(obj);
+    if (!inserted) {
+      return TVMFFIGetTypeInfo(obj->type_index())->type_key_hash;
+    }
+    struct StackGuard {
+      std::unordered_set<const Object*>& s;
+      const Object* p;
+      ~StackGuard() { s.erase(p); }
+    } stack_guard{on_stack_, obj};
+
+    uint64_t result;
+    switch (ti) {
+      case TypeIndex::kTVMFFIStr:
+        result = HashString(value, data, ti);
+        break;
+      case TypeIndex::kTVMFFIBytes:
+        result = HashBytes(value, data, ti);
+        break;
+      case TypeIndex::kTVMFFIArray:
+        result = 
HashSequence(AnyUnsafe::CopyFromAnyViewAfterCheck<Array<Any>>(value));
+        break;
+      case TypeIndex::kTVMFFIList:
+        result = 
HashSequence(AnyUnsafe::CopyFromAnyViewAfterCheck<List<Any>>(value));
+        break;
+      case TypeIndex::kTVMFFIMap:
+        result = HashMap(AnyUnsafe::CopyFromAnyViewAfterCheck<Map<Any, 
Any>>(value));
+        break;
+      case TypeIndex::kTVMFFIDict:
+        result = HashMap(AnyUnsafe::CopyFromAnyViewAfterCheck<Dict<Any, 
Any>>(value));
+        break;
+      case TypeIndex::kTVMFFIShape:
+        result = HashShape(AnyUnsafe::CopyFromAnyViewAfterCheck<Shape>(value));
+        break;
+      default:
+        result = HashObject(obj);
+        break;
+    }
+    memo_[obj] = result;
+    return result;
+  }
+
+ private:
+  static constexpr int32_t kMaxDepth = 128;
+  int32_t depth_ = 0;
+  std::unordered_set<const Object*> on_stack_;
+  std::unordered_map<const Object*, uint64_t> memo_;
+
+  static bool IsStringType(int32_t ti) {
+    return ti == TypeIndex::kTVMFFIStr || ti == TypeIndex::kTVMFFISmallStr;
+  }
+
+  static bool IsBytesType(int32_t ti) {
+    return ti == TypeIndex::kTVMFFIBytes || ti == TypeIndex::kTVMFFISmallBytes;
+  }
+
+  // ---------- POD hashing ----------
+
+  uint64_t HashPOD(const Any& value, const TVMFFIAny* data, int32_t ti) {
+    switch (ti) {
+      case TypeIndex::kTVMFFIBool: {
+        uint64_t v = data->v_int64 != 0 ? 1 : 0;
+        return details::StableHashCombine(static_cast<uint64_t>(ti), v);
+      }
+      case TypeIndex::kTVMFFIInt: {
+        return details::StableHashCombine(static_cast<uint64_t>(ti),
+                                          
static_cast<uint64_t>(data->v_int64));
+      }
+      case TypeIndex::kTVMFFIFloat: {
+        double v = data->v_float64;
+        uint64_t bits;
+        if (std::isnan(v)) {
+          // All NaN payloads hash the same (consistent with RecursiveEq)
+          double canonical = std::numeric_limits<double>::quiet_NaN();
+          std::memcpy(&bits, &canonical, sizeof(bits));
+        } else if (v == 0.0) {
+          // +0.0 and -0.0 hash the same (consistent with RecursiveEq)
+          double pos_zero = 0.0;
+          std::memcpy(&bits, &pos_zero, sizeof(bits));
+        } else {
+          std::memcpy(&bits, &v, sizeof(bits));
+        }
+        return details::StableHashCombine(static_cast<uint64_t>(ti), bits);
+      }
+      case TypeIndex::kTVMFFIDataType: {
+        DLDataType dt = data->v_dtype;
+        uint64_t h =
+            details::StableHashCombine(static_cast<uint64_t>(ti), 
static_cast<uint64_t>(dt.code));
+        h = details::StableHashCombine(h, static_cast<uint64_t>(dt.bits));
+        h = details::StableHashCombine(h, static_cast<uint64_t>(dt.lanes));
+        return h;
+      }
+      case TypeIndex::kTVMFFIDevice: {
+        DLDevice dev = data->v_device;
+        uint64_t h = details::StableHashCombine(static_cast<uint64_t>(ti),
+                                                
static_cast<uint64_t>(dev.device_type));
+        h = details::StableHashCombine(h, 
static_cast<uint64_t>(dev.device_id));
+        return h;
+      }
+      default: {
+        return details::StableHashCombine(static_cast<uint64_t>(ti),
+                                          
static_cast<uint64_t>(data->v_uint64));
+      }
+    }
+  }
+
+  // ---------- String hashing (handles SmallStr cross-variant) ----------
+
+  uint64_t HashString(const Any& value, const TVMFFIAny* data, int32_t ti) {
+    const char* ptr;
+    size_t len;
+    GetStringData(value, data, ti, &ptr, &len);
+    // Use kTVMFFIStr as the type key so SmallStr and Str hash the same
+    return 
details::StableHashCombine(static_cast<uint64_t>(TypeIndex::kTVMFFIStr),
+                                      details::StableHashBytes(ptr, len));
+  }
+
+  // ---------- Bytes hashing (handles SmallBytes cross-variant) ----------
+
+  uint64_t HashBytes(const Any& value, const TVMFFIAny* data, int32_t ti) {
+    const char* ptr;
+    size_t len;
+    GetBytesData(value, data, ti, &ptr, &len);
+    // Use kTVMFFIBytes as the type key so SmallBytes and Bytes hash the same
+    return 
details::StableHashCombine(static_cast<uint64_t>(TypeIndex::kTVMFFIBytes),
+                                      details::StableHashBytes(ptr, len));
+  }
+
+  static void GetStringData(const Any& val, const TVMFFIAny* data, int32_t ti, 
const char** out_ptr,
+                            size_t* out_len) {
+    if (ti == TypeIndex::kTVMFFISmallStr) {
+      *out_ptr = data->v_bytes;
+      *out_len = data->small_str_len;
+    } else {
+      const auto* obj =
+          details::AnyUnsafe::CopyFromAnyViewAfterCheck<const 
details::BytesObjBase*>(val);
+      *out_ptr = obj->data;
+      *out_len = obj->size;
+    }
+  }
+
+  static void GetBytesData(const Any& val, const TVMFFIAny* data, int32_t ti, 
const char** out_ptr,
+                           size_t* out_len) {
+    if (ti == TypeIndex::kTVMFFISmallBytes) {
+      *out_ptr = data->v_bytes;
+      *out_len = data->small_str_len;
+    } else {
+      const auto* obj =
+          details::AnyUnsafe::CopyFromAnyViewAfterCheck<const 
details::BytesObjBase*>(val);
+      *out_ptr = obj->data;
+      *out_len = obj->size;
+    }
+  }
+
+  // ---------- Sequence hashing (Array / List) ----------
+
+  template <typename SeqType>
+  uint64_t HashSequence(const SeqType& seq) {
+    uint64_t h = details::StableHashCombine(seq->GetTypeKeyHash(), seq.size());
+    for (size_t i = 0; i < seq.size(); ++i) {
+      // Mix element hash with its index for stronger position-dependent 
hashing.
+      h = details::StableHashCombine(h, 
details::StableHashCombine(HashAny(seq[i]), i));
+    }
+    return h;
+  }
+
+  // ---------- Map / Dict hashing (order-independent) ----------
+
+  template <typename MapType>
+  uint64_t HashMap(const MapType& map) {
+    uint64_t h = details::StableHashCombine(map->GetTypeKeyHash(), map.size());
+    // Sort per-entry hashes then combine sequentially with StableHashCombine.
+    // Sorting makes the result order-independent while sequential combining
+    // provides full avalanche mixing (unlike XOR or addition).
+    std::vector<uint64_t> entry_hashes;
+    entry_hashes.reserve(map.size());
+    for (const auto& kv : map) {
+      entry_hashes.push_back(details::StableHashCombine(HashAny(kv.first), 
HashAny(kv.second)));
+    }
+    std::sort(entry_hashes.begin(), entry_hashes.end());
+    for (uint64_t eh : entry_hashes) {
+      h = details::StableHashCombine(h, eh);
+    }
+    return h;
+  }
+
+  // ---------- Shape hashing ----------
+
+  uint64_t HashShape(const Shape& shape) {
+    uint64_t h = details::StableHashCombine(shape->GetTypeKeyHash(), 
shape.size());
+    for (int64_t dim : shape) {
+      h = details::StableHashCombine(h, static_cast<uint64_t>(dim));
+    }
+    return h;
+  }
+
+  // ---------- Reflected Object hashing ----------
+
+  uint64_t HashObject(const Object* obj) {
+    const TVMFFITypeInfo* type_info = TVMFFIGetTypeInfo(obj->type_index());
+    uint64_t h = type_info->type_key_hash;
+    reflection::ForEachFieldInfo(type_info, [&](const TVMFFIFieldInfo* finfo) {

Review Comment:
   ![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg)
   
   Potential null pointer dereference if `TVMFFIGetTypeInfo` returns `NULL`. 
Please add a null check before accessing `type_info->type_key_hash`.
   
   ```c
       const TVMFFITypeInfo* type_info = TVMFFIGetTypeInfo(obj->type_index());
       if (!type_info) return static_cast<uint64_t>(obj->type_index());
       uint64_t h = type_info->type_key_hash;
       reflection::ForEachFieldInfo(type_info, [&](const TVMFFIFieldInfo* 
finfo) {
   ```



##########
src/ffi/extra/recursive_hash.cc:
##########
@@ -0,0 +1,325 @@
+/*
+ * 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.
+ */
+/*
+ * \file src/ffi/extra/recursive_hash.cc
+ *
+ * \brief Reflection-based recursive hash (companion to RecursiveEq).
+ */
+#include <tvm/ffi/any.h>
+#include <tvm/ffi/base_details.h>
+#include <tvm/ffi/container/array.h>
+#include <tvm/ffi/container/dict.h>
+#include <tvm/ffi/container/list.h>
+#include <tvm/ffi/container/map.h>
+#include <tvm/ffi/container/shape.h>
+#include <tvm/ffi/container/tensor.h>
+#include <tvm/ffi/dtype.h>
+#include <tvm/ffi/error.h>
+#include <tvm/ffi/reflection/accessor.h>
+#include <tvm/ffi/reflection/registry.h>
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+namespace tvm {
+namespace ffi {
+
+namespace {
+
+/*!
+ * \brief Reflection-based recursive hasher.
+ *
+ * Computes a deterministic hash consistent with RecursiveEq:
+ *   RecursiveEq(a, b) => RecursiveHash(a) == RecursiveHash(b)
+ */
+class RecursiveHasher {
+ public:
+  uint64_t HashAny(const Any& value) {
+    ++depth_;
+    struct DepthGuard {
+      int32_t& d;
+      ~DepthGuard() { --d; }
+    } guard{depth_};
+    if (depth_ > kMaxDepth) {
+      TVM_FFI_THROW(ValueError) << "RecursiveHash: maximum recursion depth (" 
<< kMaxDepth
+                                << ") exceeded; possible cycle";
+    }
+    using details::AnyUnsafe;
+    const TVMFFIAny* data = AnyUnsafe::TVMFFIAnyPtrFromAny(value);
+    int32_t ti = data->type_index;
+
+    // None
+    if (ti == TypeIndex::kTVMFFINone) {
+      return details::StableHashCombine(uint64_t{0}, uint64_t{0});
+    }
+
+    // String (Str/SmallStr cross-variant)
+    if (IsStringType(ti)) {
+      return HashString(value, data, ti);
+    }
+    // Bytes (Bytes/SmallBytes cross-variant)
+    if (IsBytesType(ti)) {
+      return HashBytes(value, data, ti);
+    }
+
+    // POD types
+    if (ti < TypeIndex::kTVMFFIStaticObjectBegin) {
+      return HashPOD(value, data, ti);
+    }
+
+    // Object types — memoization + cycle detection.
+    const Object* obj = static_cast<const Object*>(value.as<Object>());
+    if (obj == nullptr) {
+      return details::StableHashCombine(uint64_t{0}, uint64_t{0});
+    }
+    // Return memoized hash if this object was already fully hashed.
+    auto memo_it = memo_.find(obj);
+    if (memo_it != memo_.end()) return memo_it->second;
+    // Cycle detection: if this object is currently on the call stack,
+    // return a sentinel to break the cycle.
+    auto [stack_it, inserted] = on_stack_.insert(obj);
+    if (!inserted) {
+      return TVMFFIGetTypeInfo(obj->type_index())->type_key_hash;

Review Comment:
   ![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg)
   
   Potential null pointer dereference if `TVMFFIGetTypeInfo` returns `NULL`. 
Please add a null check before accessing `type_key_hash`.
   
   ```c
         const TVMFFITypeInfo* info = TVMFFIGetTypeInfo(obj->type_index());
         return info ? info->type_key_hash : 
static_cast<uint64_t>(obj->type_index());
   ```



##########
src/ffi/extra/deep_copy.cc:
##########
@@ -155,9 +169,66 @@ class ObjectDeepCopier {
     });
   }
 
+  /*!
+   * \brief Replace stale original-object references inside mutable containers.
+   *
+   * When an immutable container (Array/Map) is in-progress and a mutable child
+   * (List/Dict) references it back, the original is stored as a placeholder.
+   * After all copies are built, this pass replaces those placeholders with
+   * the actual copies from copy_map_.
+   */
+  void FixupDeferredReferences() {
+    for (auto& [orig_ptr, copy_any] : copy_map_) {
+      const Object* copy_obj = copy_any.as<Object>();
+      if (!copy_obj) continue;
+      int32_t ti = copy_obj->type_index();
+      if (ti == TypeIndex::kTVMFFIList) {
+        FixupList(copy_any);
+      } else if (ti == TypeIndex::kTVMFFIDict) {
+        FixupDict(copy_any);
+      }
+    }
+  }
+
+  void FixupList(const Any& list_any) {
+    List<Any> list = list_any.cast<List<Any>>();
+    int64_t n = static_cast<int64_t>(list.size());
+    for (int64_t i = 0; i < n; ++i) {
+      const Any& elem = list[i];
+      if (elem.type_index() < TypeIndex::kTVMFFIStaticObjectBegin) continue;
+      const Object* elem_obj = elem.as<Object>();
+      if (!elem_obj) continue;
+      auto it = copy_map_.find(elem_obj);
+      if (it != copy_map_.end()) {
+        list.Set(i, it->second);
+      }
+    }
+  }
+
+  void FixupDict(const Any& dict_any) {
+    Dict<Any, Any> dict = dict_any.cast<Dict<Any, Any>>();
+    const DictObj* dict_obj = dict_any.as<DictObj>();
+    // Collect value fixups (safe to apply in-place since keys don't change).
+    std::vector<std::pair<Any, Any>> fixups;
+    for (const auto& [k, v] : *dict_obj) {
+      if (v.type_index() < TypeIndex::kTVMFFIStaticObjectBegin) continue;
+      const Object* v_obj = v.as<Object>();
+      if (!v_obj) continue;
+      auto it = copy_map_.find(v_obj);
+      if (it != copy_map_.end()) {
+        fixups.emplace_back(k, it->second);
+      }
+    }
+    for (auto& [key, new_val] : fixups) {
+      dict.Set(key, new_val);
+    }
+  }

Review Comment:
   ![high](https://www.gstatic.com/codereviewagent/high-priority.svg)
   
   The `FixupDict` implementation is missing logic to fix up keys that might be 
placeholders. Additionally, it should handle potential COW by updating the 
`dict_any` reference if changes occur.
   
   ```c
     void FixupDict(Any& dict_any) {
       Dict<Any, Any> dict = dict_any.cast<Dict<Any, Any>>();
       const DictObj* dict_obj = dict_any.as<DictObj>();
       std::vector<std::pair<Any, Any>> value_fixups;
       std::vector<std::pair<Any, Any>> key_fixups;
       for (const auto& [k, v] : *dict_obj) {
         if (v.type_index() >= TypeIndex::kTVMFFIStaticObjectBegin) {
           if (const Object* v_obj = v.as<Object>()) {
             auto it = copy_map_.find(v_obj);
             if (it != copy_map_.end()) value_fixups.emplace_back(k, 
it->second);
           }
         }
         if (k.type_index() >= TypeIndex::kTVMFFIStaticObjectBegin) {
           if (const Object* k_obj = k.as<Object>()) {
             auto it = copy_map_.find(k_obj);
             if (it != copy_map_.end()) key_fixups.emplace_back(k, it->second);
           }
         }
       }
       bool changed = false;
       for (auto& [key, new_val] : value_fixups) {
         dict.Set(key, new_val);
         changed = true;
       }
       for (auto& [old_key, new_key] : key_fixups) {
         Any val = dict[old_key];
         dict.Erase(old_key);
         dict.Set(new_key, val);
         changed = true;
       }
       if (changed) dict_any = dict;
     }
   ```



##########
src/ffi/extra/recursive_compare.cc:
##########
@@ -0,0 +1,373 @@
+/*
+ * 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.
+ */
+/*
+ * \file src/ffi/extra/recursive_compare.cc
+ *
+ * \brief Reflection-based recursive comparison (Eq, Lt, Le, Gt, Ge).
+ */
+#include <tvm/ffi/any.h>
+#include <tvm/ffi/container/array.h>
+#include <tvm/ffi/container/dict.h>
+#include <tvm/ffi/container/list.h>
+#include <tvm/ffi/container/map.h>
+#include <tvm/ffi/container/shape.h>
+#include <tvm/ffi/container/tensor.h>
+#include <tvm/ffi/dtype.h>
+#include <tvm/ffi/error.h>
+#include <tvm/ffi/reflection/accessor.h>
+#include <tvm/ffi/reflection/registry.h>
+
+#include <cmath>
+
+namespace tvm {
+namespace ffi {
+
+namespace {
+
+/*!
+ * \brief Three-way recursive comparer.
+ *
+ * Returns int32_t: -1 (lhs < rhs), 0 (equal), +1 (lhs > rhs).
+ *
+ * When eq_only_ is true, type mismatches return non-zero instead of
+ * throwing, and Map/Dict ordering is not attempted.
+ */
+class RecursiveComparer {
+ public:
+  explicit RecursiveComparer(bool eq_only) : eq_only_(eq_only) {}
+
+  int32_t CompareAny(const Any& lhs, const Any& rhs) {
+    ++depth_;
+    struct DepthGuard {
+      int32_t& d;
+      ~DepthGuard() { --d; }
+    } guard{depth_};
+    if (depth_ > kMaxDepth) {
+      TVM_FFI_THROW(ValueError) << "RecursiveCompare: maximum recursion depth 
(" << kMaxDepth
+                                << ") exceeded; possible cycle";
+    }
+    using details::AnyUnsafe;
+    const TVMFFIAny* lhs_data = AnyUnsafe::TVMFFIAnyPtrFromAny(lhs);
+    const TVMFFIAny* rhs_data = AnyUnsafe::TVMFFIAnyPtrFromAny(rhs);
+    int32_t lti = lhs_data->type_index;
+    int32_t rti = rhs_data->type_index;
+
+    // Handle None specially: None == None, None < any non-None
+    if (lti == TypeIndex::kTVMFFINone && rti == TypeIndex::kTVMFFINone) return 
0;
+    if (lti == TypeIndex::kTVMFFINone) return -1;
+    if (rti == TypeIndex::kTVMFFINone) return 1;
+
+    // Handle String (Str/SmallStr cross-variant) before type-mismatch check
+    if (IsStringType(lti) && IsStringType(rti)) {
+      return CompareString(lhs, rhs, lhs_data, rhs_data, lti, rti);
+    }
+    // Handle Bytes (Bytes/SmallBytes cross-variant) before type-mismatch check
+    if (IsBytesType(lti) && IsBytesType(rti)) {
+      return CompareBytes(lhs, rhs, lhs_data, rhs_data, lti, rti);
+    }
+
+    // Type mismatch
+    if (lti != rti) {
+      if (eq_only_) return 1;  // not equal
+      TVM_FFI_THROW(TypeError) << "Cannot compare values of different types: " 
<< lhs.GetTypeKey()
+                               << " vs " << rhs.GetTypeKey();
+    }
+
+    // Same type — POD dispatch
+    if (lti < TypeIndex::kTVMFFIStaticObjectBegin) {
+      return ComparePOD(lhs, rhs, lhs_data, rhs_data, lti);
+    }
+
+    // Object types — check pointer identity first
+    const Object* lhs_obj = static_cast<const Object*>(lhs.as<Object>());
+    const Object* rhs_obj = static_cast<const Object*>(rhs.as<Object>());
+    if (lhs_obj == rhs_obj) return 0;
+    if (lhs_obj == nullptr) return -1;
+    if (rhs_obj == nullptr) return 1;
+
+    switch (lti) {
+      case TypeIndex::kTVMFFIStr:
+        return CompareString(lhs, rhs, lhs_data, rhs_data, lti, rti);
+      case TypeIndex::kTVMFFIBytes:
+        return CompareBytes(lhs, rhs, lhs_data, rhs_data, lti, rti);
+      case TypeIndex::kTVMFFIArray:
+        return 
CompareSequence(AnyUnsafe::CopyFromAnyViewAfterCheck<Array<Any>>(lhs),
+                               
AnyUnsafe::CopyFromAnyViewAfterCheck<Array<Any>>(rhs));
+      case TypeIndex::kTVMFFIList:
+        return 
CompareSequence(AnyUnsafe::CopyFromAnyViewAfterCheck<List<Any>>(lhs),
+                               
AnyUnsafe::CopyFromAnyViewAfterCheck<List<Any>>(rhs));
+      case TypeIndex::kTVMFFIMap:
+        return CompareMap(AnyUnsafe::CopyFromAnyViewAfterCheck<Map<Any, 
Any>>(lhs),
+                          AnyUnsafe::CopyFromAnyViewAfterCheck<Map<Any, 
Any>>(rhs));
+      case TypeIndex::kTVMFFIDict:
+        return CompareMap(AnyUnsafe::CopyFromAnyViewAfterCheck<Dict<Any, 
Any>>(lhs),
+                          AnyUnsafe::CopyFromAnyViewAfterCheck<Dict<Any, 
Any>>(rhs));
+      case TypeIndex::kTVMFFIShape:
+        return CompareShape(AnyUnsafe::CopyFromAnyViewAfterCheck<Shape>(lhs),
+                            AnyUnsafe::CopyFromAnyViewAfterCheck<Shape>(rhs));
+      default:
+        return CompareObject(lhs_obj, rhs_obj);
+    }
+  }
+
+ private:
+  static constexpr int32_t kMaxDepth = 128;
+  bool eq_only_;
+  int32_t depth_ = 0;
+
+  static bool IsStringType(int32_t ti) {
+    return ti == TypeIndex::kTVMFFIStr || ti == TypeIndex::kTVMFFISmallStr;
+  }
+
+  static bool IsBytesType(int32_t ti) {
+    return ti == TypeIndex::kTVMFFIBytes || ti == TypeIndex::kTVMFFISmallBytes;
+  }
+
+  static int32_t Sign(int64_t v) { return (v > 0) - (v < 0); }
+
+  // ---------- POD comparison ----------
+
+  int32_t ComparePOD(const Any& lhs, const Any& rhs, const TVMFFIAny* lhs_data,
+                     const TVMFFIAny* rhs_data, int32_t ti) {
+    switch (ti) {
+      case TypeIndex::kTVMFFIBool: {
+        bool a = lhs_data->v_int64 != 0;
+        bool b = rhs_data->v_int64 != 0;
+        return static_cast<int32_t>(a) - static_cast<int32_t>(b);
+      }
+      case TypeIndex::kTVMFFIInt: {
+        int64_t a = lhs_data->v_int64;
+        int64_t b = rhs_data->v_int64;
+        if (a < b) return -1;
+        if (a > b) return 1;
+        return 0;
+      }
+      case TypeIndex::kTVMFFIFloat: {
+        double a = lhs_data->v_float64;
+        double b = rhs_data->v_float64;
+        // NaN == NaN for equality
+        if (std::isnan(a) && std::isnan(b)) {
+          if (eq_only_) return 0;
+          TVM_FFI_THROW(TypeError) << "Cannot order NaN values";
+        }
+        if (std::isnan(a) || std::isnan(b)) {
+          if (eq_only_) return 1;  // not equal
+          TVM_FFI_THROW(TypeError) << "Cannot order NaN values";
+        }
+        if (a < b) return -1;
+        if (a > b) return 1;
+        return 0;
+      }
+      case TypeIndex::kTVMFFIDataType: {
+        DLDataType a = lhs_data->v_dtype;
+        DLDataType b = rhs_data->v_dtype;
+        if (a.code != b.code) return (a.code < b.code) ? -1 : 1;
+        if (a.bits != b.bits) return (a.bits < b.bits) ? -1 : 1;
+        if (a.lanes != b.lanes) return (a.lanes < b.lanes) ? -1 : 1;
+        return 0;
+      }
+      case TypeIndex::kTVMFFIDevice: {
+        DLDevice a = lhs_data->v_device;
+        DLDevice b = rhs_data->v_device;
+        if (a.device_type != b.device_type) return (a.device_type < 
b.device_type) ? -1 : 1;
+        if (a.device_id != b.device_id) return (a.device_id < b.device_id) ? 
-1 : 1;
+        return 0;
+      }
+      default: {
+        // Other POD types: bitwise equality only
+        if (lhs_data->zero_padding == rhs_data->zero_padding &&
+            lhs_data->v_int64 == rhs_data->v_int64) {
+          return 0;
+        }
+        if (eq_only_) return 1;
+        TVM_FFI_THROW(TypeError) << "Cannot order values of type " << 
lhs.GetTypeKey();
+      }
+    }
+    TVM_FFI_UNREACHABLE();
+  }
+
+  // ---------- String comparison (handles SmallStr cross-variant) ----------
+
+  int32_t CompareString(const Any& lhs, const Any& rhs, const TVMFFIAny* 
lhs_data,
+                        const TVMFFIAny* rhs_data, int32_t lti, int32_t rti) {
+    const char* lhs_ptr;
+    size_t lhs_len;
+    const char* rhs_ptr;
+    size_t rhs_len;
+    GetStringData(lhs, lhs_data, lti, &lhs_ptr, &lhs_len);
+    GetStringData(rhs, rhs_data, rti, &rhs_ptr, &rhs_len);
+    return SignFromMemncmp(Bytes::memncmp(lhs_ptr, rhs_ptr, lhs_len, rhs_len));
+  }
+
+  // ---------- Bytes comparison (handles SmallBytes cross-variant) ----------
+
+  int32_t CompareBytes(const Any& lhs, const Any& rhs, const TVMFFIAny* 
lhs_data,
+                       const TVMFFIAny* rhs_data, int32_t lti, int32_t rti) {
+    const char* lhs_ptr;
+    size_t lhs_len;
+    const char* rhs_ptr;
+    size_t rhs_len;
+    GetBytesData(lhs, lhs_data, lti, &lhs_ptr, &lhs_len);
+    GetBytesData(rhs, rhs_data, rti, &rhs_ptr, &rhs_len);
+    return SignFromMemncmp(Bytes::memncmp(lhs_ptr, rhs_ptr, lhs_len, rhs_len));
+  }
+
+  static void GetStringData(const Any& val, const TVMFFIAny* data, int32_t ti, 
const char** out_ptr,
+                            size_t* out_len) {
+    if (ti == TypeIndex::kTVMFFISmallStr) {
+      *out_ptr = data->v_bytes;
+      *out_len = data->small_str_len;
+    } else {
+      const auto* obj =
+          details::AnyUnsafe::CopyFromAnyViewAfterCheck<const 
details::BytesObjBase*>(val);
+      *out_ptr = obj->data;
+      *out_len = obj->size;
+    }
+  }
+
+  static void GetBytesData(const Any& val, const TVMFFIAny* data, int32_t ti, 
const char** out_ptr,
+                           size_t* out_len) {
+    if (ti == TypeIndex::kTVMFFISmallBytes) {
+      *out_ptr = data->v_bytes;
+      *out_len = data->small_str_len;
+    } else {
+      const auto* obj =
+          details::AnyUnsafe::CopyFromAnyViewAfterCheck<const 
details::BytesObjBase*>(val);
+      *out_ptr = obj->data;
+      *out_len = obj->size;
+    }
+  }
+
+  static int32_t SignFromMemncmp(int v) {
+    if (v < 0) return -1;
+    if (v > 0) return 1;
+    return 0;
+  }
+
+  // ---------- Sequence comparison (Array / List) ----------
+
+  template <typename SeqType>
+  int32_t CompareSequence(const SeqType& lhs, const SeqType& rhs) {
+    size_t min_len = std::min(lhs.size(), rhs.size());
+    for (size_t i = 0; i < min_len; ++i) {
+      int32_t cmp = CompareAny(lhs[i], rhs[i]);
+      if (cmp != 0) return cmp;
+    }
+    if (lhs.size() < rhs.size()) return -1;
+    if (lhs.size() > rhs.size()) return 1;
+    return 0;
+  }
+
+  // ---------- Map / Dict comparison (equality only) ----------
+
+  template <typename MapType>
+  int32_t CompareMap(const MapType& lhs, const MapType& rhs) {
+    if (lhs.size() != rhs.size()) {
+      if (eq_only_) return 1;
+      TVM_FFI_THROW(TypeError) << "Cannot order Map/Dict values";
+    }
+    for (const auto& kv : lhs) {
+      auto it = rhs.find(kv.first);
+      if (it == rhs.end()) {
+        if (eq_only_) return 1;
+        TVM_FFI_THROW(TypeError) << "Cannot order Map/Dict values";
+      }
+      int32_t cmp = CompareAny(kv.second, (*it).second);
+      if (cmp != 0) {
+        if (!eq_only_) {
+          TVM_FFI_THROW(TypeError) << "Cannot order Map/Dict values";
+        }
+        return cmp;
+      }
+    }
+    return 0;
+  }
+
+  // ---------- Shape comparison ----------
+
+  int32_t CompareShape(const Shape& lhs, const Shape& rhs) {
+    size_t min_len = std::min(lhs.size(), rhs.size());
+    for (size_t i = 0; i < min_len; ++i) {
+      if (lhs[i] < rhs[i]) return -1;
+      if (lhs[i] > rhs[i]) return 1;
+    }
+    if (lhs.size() < rhs.size()) return -1;
+    if (lhs.size() > rhs.size()) return 1;
+    return 0;
+  }
+
+  // ---------- Reflected Object comparison ----------
+
+  int32_t CompareObject(const Object* lhs, const Object* rhs) {
+    // Different type indices
+    if (lhs->type_index() != rhs->type_index()) {
+      if (eq_only_) return 1;
+      const TVMFFITypeInfo* lhs_info = TVMFFIGetTypeInfo(lhs->type_index());
+      const TVMFFITypeInfo* rhs_info = TVMFFIGetTypeInfo(rhs->type_index());
+      TVM_FFI_THROW(TypeError) << "Cannot compare objects of different types: "
+                               << String(lhs_info->type_key) << " vs "
+                               << String(rhs_info->type_key);

Review Comment:
   ![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg)
   
   Using `Any(lhs).GetTypeKey()` is safer and more concise than manually 
fetching `TVMFFITypeInfo` and checking for null pointers.
   
   ```c
         TVM_FFI_THROW(TypeError) << "Cannot compare objects of different 
types: "
                                  << Any(lhs).GetTypeKey() << " vs "
                                  << Any(rhs).GetTypeKey();
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to