This is an automated email from the ASF dual-hosted git repository.

yongwww 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 12dbf053 [FFI] Keep object optionals pointer-sized (#701)
12dbf053 is described below

commit 12dbf053b3d9ba4ebd9da3123b1aeca79cf74229
Author: Tianqi Chen <[email protected]>
AuthorDate: Wed Aug 5 06:31:44 2026 +0800

    [FFI] Keep object optionals pointer-sized (#701)
    
    This restores the established pointer-sized ABI for object optionals
    while preserving the Any-backed representation for non-object values.
---
 addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.cc |   4 +-
 addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.h  |   2 +-
 docs/guides/cpp_lang_guide.md                 |   2 +-
 docs/guides/export_func_cls.rst               |   2 +-
 examples/quickstart/load/load_cpp.cc          |   2 +-
 examples/quickstart/load/load_cuda.cc         |   2 +-
 examples/stable_c_abi/src/load.c              |   2 +-
 include/tvm/ffi/extra/module.h                |  11 +-
 include/tvm/ffi/object.h                      |   6 +-
 include/tvm/ffi/optional.h                    | 330 ++++++++++++++++++++++++--
 rust/tvm-ffi/src/lib.rs                       |   2 +-
 rust/tvm-ffi/src/optional.rs                  |  94 ++++++--
 rust/tvm-ffi/tests/test_optional.rs           |  53 ++---
 src/ffi/extra/library_module.cc               |   4 +-
 src/ffi/extra/module.cc                       |  13 +-
 src/ffi/extra/module_internal.h               |  14 +-
 tests/cpp/test_object.cc                      |  11 +-
 tests/cpp/test_object_ptr.cc                  |  24 ++
 tests/cpp/test_optional.cc                    |  46 +++-
 tests/python/test_dataclass_gen_abi_cpp.py    |   6 +-
 20 files changed, 504 insertions(+), 126 deletions(-)

diff --git a/addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.cc 
b/addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.cc
index f7f8e38c..1de989df 100644
--- a/addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.cc
+++ b/addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.cc
@@ -333,7 +333,7 @@ llvm::orc::JITDylib& ORCJITDynamicLibraryObj::GetJITDylib() 
{
   return *dylib_;
 }
 
-Function ORCJITDynamicLibraryObj::GetFunction(const String& name) {
+Optional<Function> ORCJITDynamicLibraryObj::GetFunction(const String& name) {
   // Pure symbol lookup. Context symbols were injected once at load time (see
   // Finalize), so this holds no lock and does no refresh — the returned
   // Function, once resolved, is invoked lock-free on the hot path.
@@ -345,7 +345,7 @@ Function ORCJITDynamicLibraryObj::GetFunction(const String& 
name) {
     auto* wrapper = new DylibFnContextWithModule{GetRef<Module>(this)};
     return Function::FromExternC(wrapper, c_func, 
DeleteDylibFnContextWithModule);
   }
-  return nullptr;
+  return std::nullopt;
 }
 
 //-------------------------------------
diff --git a/addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.h 
b/addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.h
index 9ca8cc25..b458b127 100644
--- a/addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.h
+++ b/addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.h
@@ -63,7 +63,7 @@ class ORCJITDynamicLibraryObj : public ModuleObj {
 
   const char* kind() const final { return "orcjit"; }
 
-  Function GetFunction(const String& name) override;
+  Optional<Function> GetFunction(const String& name) override;
 
  private:
   /*!
diff --git a/docs/guides/cpp_lang_guide.md b/docs/guides/cpp_lang_guide.md
index b6e1c019..d964421c 100644
--- a/docs/guides/cpp_lang_guide.md
+++ b/docs/guides/cpp_lang_guide.md
@@ -336,7 +336,7 @@ The metadata contains:
 ffi::Module mod = ffi::Module::LoadFromFile("path/to/export_lib.so");
 
 // Get the function
-ffi::Function func = mod->GetFunction("add_one");
+ffi::Function func = mod->GetFunction("add_one").value();
 
 // Query metadata (type schema information)
 ffi::Optional<ffi::String> metadata = mod->GetFunctionMetadata("add_one");
diff --git a/docs/guides/export_func_cls.rst b/docs/guides/export_func_cls.rst
index 2d74ee2f..ac528450 100644
--- a/docs/guides/export_func_cls.rst
+++ b/docs/guides/export_func_cls.rst
@@ -101,7 +101,7 @@ library and retrieve functions by name:
    namespace ffi = tvm::ffi;
 
    ffi::Module mod = ffi::Module::LoadFromFile("path/to/library.so");
-   ffi::Function func = mod->GetFunction("add_two");
+   ffi::Function func = mod->GetFunction("add_two").value();
    int result = func(40).cast<int>();  // -> 42
 
 
diff --git a/examples/quickstart/load/load_cpp.cc 
b/examples/quickstart/load/load_cpp.cc
index 5278c73c..cee261ed 100644
--- a/examples/quickstart/load/load_cpp.cc
+++ b/examples/quickstart/load/load_cpp.cc
@@ -32,7 +32,7 @@ void Run(tvm::ffi::TensorView x, tvm::ffi::TensorView y) {
   // Load shared library `build/add_one_cpu.so`
   ffi::Module mod = ffi::Module::LoadFromFile("build/add_one_cpu.so");
   // Look up `add_one_cpu` function
-  ffi::Function add_one_cpu = mod->GetFunction("add_one_cpu");
+  ffi::Function add_one_cpu = mod->GetFunction("add_one_cpu").value();
   // Call the function
   add_one_cpu(x, y);
 }
diff --git a/examples/quickstart/load/load_cuda.cc 
b/examples/quickstart/load/load_cuda.cc
index db83d06e..07e43ffa 100644
--- a/examples/quickstart/load/load_cuda.cc
+++ b/examples/quickstart/load/load_cuda.cc
@@ -32,7 +32,7 @@ void Run(tvm::ffi::TensorView x, tvm::ffi::TensorView y) {
   // Load shared library `build/add_one_cuda.so`
   ffi::Module mod = ffi::Module::LoadFromFile("build/add_one_cuda.so");
   // Look up `add_one_cuda` function
-  ffi::Function add_one_cuda = mod->GetFunction("add_one_cuda");
+  ffi::Function add_one_cuda = mod->GetFunction("add_one_cuda").value();
   // Call the function with CUDA tensors
   add_one_cuda(x, y);
 }
diff --git a/examples/stable_c_abi/src/load.c b/examples/stable_c_abi/src/load.c
index b384a4e4..5f207e90 100644
--- a/examples/stable_c_abi/src/load.c
+++ b/examples/stable_c_abi/src/load.c
@@ -45,7 +45,7 @@ int Run(DLTensor* x, DLTensor* y) {
 
   // Step 2. Get function `add_one_cpu` from module
   // Equivalent to:
-  //    func = mod->GetFunction("add_one_cpu", /*query_imports=*/false)
+  //    func = mod->GetFunction("add_one_cpu", /*query_imports=*/false).value()
   call_args[0] = (TVMFFIAny){.type_index = mod.type_index, .v_obj = mod.v_obj};
   call_args[1] = (TVMFFIAny){.type_index = kTVMFFIRawStr, .v_c_str = 
"add_one_cpu"};
   call_args[2] = (TVMFFIAny){.type_index = kTVMFFIBool, .v_int64 = 0};
diff --git a/include/tvm/ffi/extra/module.h b/include/tvm/ffi/extra/module.h
index d7b07c19..8a824a3e 100644
--- a/include/tvm/ffi/extra/module.h
+++ b/include/tvm/ffi/extra/module.h
@@ -57,10 +57,9 @@ class TVM_FFI_EXTRA_CXX_API ModuleObj : public Object {
   /*!
    * \brief Get a ffi::Function from the module.
    * \param name The name of the function.
-   * \return The function, or nullptr if it is not found.
-   * \note The nullable Function return keeps this virtual interface 
pointer-sized.
+   * \return The function.
    */
-  virtual Function GetFunction(const String& name) = 0;
+  virtual Optional<Function> GetFunction(const String& name) = 0;
   /*!
    * \brief Returns true if this module has a definition for a function of \p 
name.
    *
@@ -71,7 +70,7 @@ class TVM_FFI_EXTRA_CXX_API ModuleObj : public Object {
    * \param name The name of the function.
    * \return True if the module implements the function, false otherwise.
    */
-  virtual bool ImplementsFunction(const String& name) { return 
GetFunction(name) != nullptr; }
+  virtual bool ImplementsFunction(const String& name) { return 
GetFunction(name).has_value(); }
   /*!
    * \brief Get the docstring of the function, if available.
    * \param name The name of the function.
@@ -143,9 +142,9 @@ class TVM_FFI_EXTRA_CXX_API ModuleObj : public Object {
    * \brief Overloaded function to optionally query from imports.
    * \param name The name of the function.
    * \param query_imports Whether to query imported modules.
-   * \return The function, or nullptr if it is not found.
+   * \return The function.
    */
-  Function GetFunction(const String& name, bool query_imports);
+  Optional<Function> GetFunction(const String& name, bool query_imports);
   /*!
    * \brief Overloaded function to optionally query from imports.
    * \param name The name of the function.
diff --git a/include/tvm/ffi/object.h b/include/tvm/ffi/object.h
index 25ee7699..988a291a 100644
--- a/include/tvm/ffi/object.h
+++ b/include/tvm/ffi/object.h
@@ -780,9 +780,9 @@ class WeakObjectPtr {
  * \brief Optional data type in FFI.
  * \tparam T The underlying type of the optional.
  *
- * \note For storage-enabled T, Optional<T> is backed by a single TVMFFIAny 
(Any)
- *   and uses kTVMFFINone to represent nullopt, so its layout is independent 
of T.
- *   For non-storage-enabled T it falls back to std::optional<T>.
+ * \note ObjectRef, ObjectPtr, and Arc values use a nullable ObjectPtr-backed
+ *   representation. Other storage-enabled T use one TVMFFIAny, while
+ *   non-storage-enabled T fall back to std::optional<T>.
  */
 template <typename T, typename = void>
 class Optional;
diff --git a/include/tvm/ffi/optional.h b/include/tvm/ffi/optional.h
index c79d5328..8f46b991 100644
--- a/include/tvm/ffi/optional.h
+++ b/include/tvm/ffi/optional.h
@@ -20,12 +20,11 @@
 /*!
  * \file tvm/ffi/optional.h
  * \brief Runtime Optional container types.
- * \note Optional<T> uses a hybrid representation. For types that enable Any
- *       storage (`TypeTraits<T>::storage_enabled`), it is backed by a single
- *       TVMFFIAny (Any) with nullopt represented as kTVMFFINone, mirroring
- *       Variant<...>; the layout is then independent of T (sizeof == 
sizeof(Any))
- *       which keeps the ABI stable. For types that do not enable storage (e.g.
- *       non-owning view types) it falls back to std::optional<T>.
+ * \note Optional<T> uses a hybrid representation. ObjectRef, ObjectPtr, and 
Arc
+ *       values keep the established one-pointer ObjectPtr representation, with
+ *       nullptr representing nullopt. Other types that enable Any storage are
+ *       backed by one TVMFFIAny. Types that do not enable storage (for 
example,
+ *       non-owning view types) fall back to std::optional<T>.
  */
 #ifndef TVM_FFI_OPTIONAL_H_
 #define TVM_FFI_OPTIONAL_H_
@@ -50,13 +49,37 @@ inline constexpr bool is_optional_type_v = false;
 
 template <typename T>
 inline constexpr bool is_optional_type_v<Optional<T>> = true;
+
+// ObjectRef values have historically used their nullable ObjectPtr storage
+// directly. Keep nested Optional<Optional<T>> out of this specialization so
+// the outer Optional still has a distinct Any-backed representation.
+template <typename T>
+inline constexpr bool use_object_ref_optional_v =
+    std::is_base_of_v<ObjectRef, T> && !is_optional_type_v<T>;
+
+template <typename T>
+inline constexpr bool is_object_ptr_type_v = false;
+
+template <typename TObject>
+inline constexpr bool is_object_ptr_type_v<ObjectPtr<TObject>> = true;
+
+template <typename T>
+inline constexpr bool is_arc_type_v = false;
+
+template <typename TObject>
+inline constexpr bool is_arc_type_v<Arc<TObject>> = true;
+
+template <typename T>
+inline constexpr bool use_object_ptr_optional_v =
+    use_object_ref_optional_v<T> || is_object_ptr_type_v<T> || 
is_arc_type_v<T>;
 /// \endcond
 
 // Fallback specialization for types that do NOT enable Any storage
 // (`TypeTraits<T>::storage_enabled == false`), such as non-owning view types
 // that cannot be moved into an Any. These simply reuse std::optional<T>.
 template <typename T>
-class Optional<T, std::enable_if_t<!TypeTraits<T>::storage_enabled>> {
+class Optional<T,
+               std::enable_if_t<!TypeTraits<T>::storage_enabled && 
!use_object_ptr_optional_v<T>>> {
  public:
   // default constructors.
   Optional() = default;
@@ -149,7 +172,8 @@ class Optional<T, 
std::enable_if_t<!TypeTraits<T>::storage_enabled>> {
  * \tparam T The underlying value type (must enable Any storage).
  */
 template <typename T>
-class Optional<T, std::enable_if_t<TypeTraits<T>::storage_enabled>> {
+class Optional<T,
+               std::enable_if_t<TypeTraits<T>::storage_enabled && 
!use_object_ptr_optional_v<T>>> {
  public:
   /*! \brief default constructor, represents nullopt (Any() is kTVMFFINone). */
   Optional() = default;
@@ -331,19 +355,293 @@ class Optional<T, 
std::enable_if_t<TypeTraits<T>::storage_enabled>> {
   Any data_;
 };
 
+/*!
+ * \brief Pointer-sized Optional specialization for ObjectRef types.
+ *
+ * ObjectRef already owns an ObjectPtr<Object>. Reusing that storage keeps the
+ * long-standing nullable-pointer ABI: nullptr is nullopt, and an engaged value
+ * is represented by the object's pointer with no TVMFFIAny wrapper.
+ */
+template <typename T>
+class Optional<T, std::enable_if_t<use_object_ref_optional_v<T>>> : public 
ObjectRef {
+ public:
+  using ContainerType = typename T::ContainerType;
+  static constexpr bool _type_container_is_exact = T::_type_container_is_exact;
+
+  Optional() = default;
+  // NOLINTBEGIN(google-explicit-constructor)
+  Optional(const Optional&) = default;
+  Optional(Optional&&) noexcept = default;
+  explicit Optional(UnsafeInit tag) : ObjectRef(tag) {}
+  Optional(std::nullopt_t) {}
+  Optional(std::nullptr_t) {}
+  Optional(std::optional<T> other) {
+    if (other.has_value()) {
+      *this = *std::move(other);
+    }
+  }
+  Optional(T other) : ObjectRef(std::move(other)) {}
+  // NOLINTEND(google-explicit-constructor)
+
+  Optional& operator=(const Optional&) = default;
+  Optional& operator=(Optional&&) noexcept = default;
+
+  TVM_FFI_INLINE Optional& operator=(T other) {
+    ObjectRef::operator=(std::move(other));
+    return *this;
+  }
+
+  TVM_FFI_INLINE Optional& operator=(std::nullopt_t) {
+    data_ = nullptr;
+    return *this;
+  }
+
+  TVM_FFI_INLINE Optional& operator=(std::nullptr_t) {
+    data_ = nullptr;
+    return *this;
+  }
+
+  TVM_FFI_INLINE T value() const& {
+    if (TVM_FFI_PREDICT_FALSE(!has_value())) {
+      TVM_FFI_THROW(RuntimeError) << "Back optional access";
+    }
+    return details::ObjectUnsafe::ObjectRefFromObjectPtr<T>(data_);
+  }
+
+  TVM_FFI_INLINE T value() && {
+    if (TVM_FFI_PREDICT_FALSE(!has_value())) {
+      TVM_FFI_THROW(RuntimeError) << "Back optional access";
+    }
+    return details::ObjectUnsafe::ObjectRefFromObjectPtr<T>(std::move(data_));
+  }
+
+  template <typename U = std::remove_cv_t<T>>
+  TVM_FFI_INLINE T value_or(U&& default_value) const {
+    return has_value() ? 
details::ObjectUnsafe::ObjectRefFromObjectPtr<T>(data_)
+                       : T(std::forward<U>(default_value));
+  }
+
+  TVM_FFI_INLINE explicit operator bool() const noexcept { return has_value(); 
}
+  TVM_FFI_INLINE bool has_value() const noexcept { return data_ != nullptr; }
+
+  TVM_FFI_INLINE T operator*() const& noexcept {
+    return details::ObjectUnsafe::ObjectRefFromObjectPtr<T>(data_);
+  }
+
+  TVM_FFI_INLINE T operator*() && noexcept {
+    return details::ObjectUnsafe::ObjectRefFromObjectPtr<T>(std::move(data_));
+  }
+
+  TVM_FFI_INLINE bool operator==(std::nullopt_t) const noexcept { return 
!has_value(); }
+  TVM_FFI_INLINE bool operator!=(std::nullopt_t) const noexcept { return 
has_value(); }
+  TVM_FFI_INLINE bool operator==(std::nullptr_t) const noexcept { return 
!has_value(); }
+  TVM_FFI_INLINE bool operator!=(std::nullptr_t) const noexcept { return 
has_value(); }
+
+  TVM_FFI_INLINE auto operator==(const Optional& other) const { return 
EQToOptional(other); }
+  TVM_FFI_INLINE auto operator!=(const Optional& other) const { return 
NEToOptional(other); }
+
+  TVM_FFI_INLINE auto operator==(const std::optional<T>& other) const {
+    return EQToOptional(other);
+  }
+  TVM_FFI_INLINE auto operator!=(const std::optional<T>& other) const {
+    return NEToOptional(other);
+  }
+
+  TVM_FFI_INLINE auto operator==(const T& other) const {
+    using RetType = decltype(value() == other);
+    if (!has_value()) return RetType(false);
+    if (same_as(other)) return RetType(true);
+    return operator*() == other;
+  }
+
+  TVM_FFI_INLINE auto operator!=(const T& other) const { return !(*this == 
other); }
+
+  template <typename U>
+  TVM_FFI_INLINE auto operator==(const U& other) const {
+    using RetType = decltype(value() == other);
+    if (!has_value()) return RetType(false);
+    return operator*() == other;
+  }
+
+  template <typename U>
+  TVM_FFI_INLINE auto operator!=(const U& other) const {
+    using RetType = decltype(value() != other);
+    if (!has_value()) return RetType(true);
+    return operator*() != other;
+  }
+
+  TVM_FFI_INLINE const ContainerType* get() const {
+    return static_cast<ContainerType*>(data_.get());
+  }
+
+ private:
+  template <typename U>
+  TVM_FFI_INLINE auto EQToOptional(const U& other) const {
+    using RetType = decltype(operator*() == *other);
+    if (!has_value() || !other.has_value()) {
+      return RetType(has_value() == other.has_value());
+    }
+    if (same_as(*other)) return RetType(true);
+    return operator*() == *other;
+  }
+
+  template <typename U>
+  TVM_FFI_INLINE auto NEToOptional(const U& other) const {
+    using RetType = decltype(operator*() != *other);
+    if (!has_value() || !other.has_value()) {
+      return RetType(has_value() != other.has_value());
+    }
+    if (same_as(*other)) return RetType(false);
+    return operator*() != *other;
+  }
+};
+
+namespace details {
+
+template <typename T>
+struct OptionalObjectPtrTraits;
+
+template <typename TObject>
+struct OptionalObjectPtrTraits<ObjectPtr<TObject>> {
+  using ContainerType = TObject;
+  using StorageType = ObjectPtr<TObject>;
+
+  TVM_FFI_INLINE static ObjectPtr<TObject> Copy(const StorageType& value) { 
return value; }
+  TVM_FFI_INLINE static ObjectPtr<TObject> Move(StorageType&& value) { return 
std::move(value); }
+};
+
+template <typename TObject>
+struct OptionalObjectPtrTraits<Arc<TObject>> {
+  using ContainerType = TObject;
+  using StorageType = ObjectPtr<TObject>;
+
+  TVM_FFI_INLINE static Arc<TObject> Copy(const StorageType& value) {
+    return ObjectUnsafe::ArcFromObjectPtr(StorageType(value));
+  }
+  TVM_FFI_INLINE static Arc<TObject> Move(StorageType&& value) {
+    return ObjectUnsafe::ArcFromObjectPtr(std::move(value));
+  }
+};
+
+}  // namespace details
+
+/*!
+ * \brief Pointer-sized Optional specialization for ObjectPtr and Arc values.
+ *
+ * Both pointer classes share ObjectPtr<T>'s one-pointer representation. Arc is
+ * non-null in its public API; Optional<Arc<T>> adds nullptr as the disengaged
+ * state while returning Arc<T> only after a presence check.
+ */
+template <typename T>
+class Optional<T, std::enable_if_t<is_object_ptr_type_v<T> || 
is_arc_type_v<T>>>
+    : public details::OptionalObjectPtrTraits<T>::StorageType {
+ private:
+  using Traits = details::OptionalObjectPtrTraits<T>;
+  using StorageType = typename Traits::StorageType;
+
+ public:
+  using ContainerType = typename Traits::ContainerType;
+
+  Optional() = default;
+  // NOLINTBEGIN(google-explicit-constructor)
+  Optional(const Optional&) = default;
+  Optional(Optional&&) noexcept = default;
+  Optional(std::nullopt_t) : StorageType(nullptr) {}
+  Optional(std::nullptr_t) : StorageType(nullptr) {}
+  Optional(std::optional<T> other) {
+    if (other.has_value()) {
+      static_cast<StorageType&>(*this) = StorageType(std::move(*other));
+    }
+  }
+  Optional(T value) : StorageType(std::move(value)) {}
+  // NOLINTEND(google-explicit-constructor)
+
+  Optional& operator=(const Optional&) = default;
+  Optional& operator=(Optional&&) noexcept = default;
+
+  TVM_FFI_INLINE Optional& operator=(T value) {
+    static_cast<StorageType&>(*this) = StorageType(std::move(value));
+    return *this;
+  }
+
+  TVM_FFI_INLINE Optional& operator=(std::nullopt_t) {
+    StorageType::reset();
+    return *this;
+  }
+
+  TVM_FFI_INLINE Optional& operator=(std::nullptr_t) {
+    StorageType::reset();
+    return *this;
+  }
+
+  TVM_FFI_INLINE T value() const& {
+    if (TVM_FFI_PREDICT_FALSE(!has_value())) {
+      TVM_FFI_THROW(RuntimeError) << "Back optional access";
+    }
+    return Traits::Copy(static_cast<const StorageType&>(*this));
+  }
+
+  TVM_FFI_INLINE T value() && {
+    if (TVM_FFI_PREDICT_FALSE(!has_value())) {
+      TVM_FFI_THROW(RuntimeError) << "Back optional access";
+    }
+    return Traits::Move(std::move(static_cast<StorageType&>(*this)));
+  }
+
+  template <typename U = T>
+  TVM_FFI_INLINE T value_or(U&& default_value) const {
+    return has_value() ? Traits::Copy(static_cast<const StorageType&>(*this))
+                       : T(std::forward<U>(default_value));
+  }
+
+  TVM_FFI_INLINE explicit operator bool() const noexcept { return has_value(); 
}
+  TVM_FFI_INLINE bool has_value() const noexcept { return StorageType::get() 
!= nullptr; }
+
+  TVM_FFI_INLINE T operator*() const& noexcept {
+    return Traits::Copy(static_cast<const StorageType&>(*this));
+  }
+
+  TVM_FFI_INLINE T operator*() && noexcept {
+    return Traits::Move(std::move(static_cast<StorageType&>(*this)));
+  }
+
+  TVM_FFI_INLINE bool operator==(std::nullopt_t) const noexcept { return 
!has_value(); }
+  TVM_FFI_INLINE bool operator!=(std::nullopt_t) const noexcept { return 
has_value(); }
+  TVM_FFI_INLINE bool operator==(std::nullptr_t) const noexcept { return 
!has_value(); }
+  TVM_FFI_INLINE bool operator!=(std::nullptr_t) const noexcept { return 
has_value(); }
+
+  TVM_FFI_INLINE bool operator==(const Optional& other) const noexcept {
+    return StorageType::get() == other.get();
+  }
+  TVM_FFI_INLINE bool operator!=(const Optional& other) const noexcept { 
return !(*this == other); }
+  TVM_FFI_INLINE bool operator==(const T& other) const noexcept {
+    return StorageType::get() == other.get();
+  }
+  TVM_FFI_INLINE bool operator!=(const T& other) const noexcept { return 
!(*this == other); }
+
+  TVM_FFI_INLINE bool same_as(const Optional& other) const noexcept {
+    return StorageType::get() == other.get();
+  }
+  TVM_FFI_INLINE bool same_as(const T& other) const noexcept {
+    return StorageType::get() == other.get();
+  }
+
+  using StorageType::get;
+  using StorageType::unique;
+  using StorageType::use_count;
+};
+
 template <typename T>
 inline constexpr bool use_default_type_traits_v<Optional<T>> = false;
 
 template <typename T>
 struct TypeTraits<Optional<T>> : public TypeTraitsBase {
-  // storage_enabled propagates from T: Optional<T> can live in an Any exactly
-  // when T can. This keeps nested Optional<Optional<T>> and Optional<T> used
-  // inside Variant<...>/containers Any-backed iff T is storage-enabled.
+  // Optional<T> can live in Any exactly when T can, independently of whether
+  // its in-memory representation is Any-backed or ObjectPtr-backed.
   static constexpr bool storage_enabled = TypeTraits<T>::storage_enabled;
 
   TVM_FFI_INLINE static void CopyToAnyView(const Optional<T>& src, TVMFFIAny* 
result) {
-    if constexpr (TypeTraits<T>::storage_enabled) {
-      // Storage-enabled: the Any already holds the exact representation.
+    if constexpr (TypeTraits<T>::storage_enabled && 
!use_object_ptr_optional_v<T>) {
       *result = src.ToAnyView().CopyToTVMFFIAny();
     } else {
       if (src.has_value()) {
@@ -355,7 +653,7 @@ struct TypeTraits<Optional<T>> : public TypeTraitsBase {
   }
 
   TVM_FFI_INLINE static void MoveToAny(Optional<T> src, TVMFFIAny* result) {
-    if constexpr (TypeTraits<T>::storage_enabled) {
+    if constexpr (TypeTraits<T>::storage_enabled && 
!use_object_ptr_optional_v<T>) {
       *result = 
details::AnyUnsafe::MoveAnyToTVMFFIAny(std::move(src).MoveToAny());
     } else {
       if (src.has_value()) {
@@ -372,7 +670,7 @@ struct TypeTraits<Optional<T>> : public TypeTraitsBase {
   }
 
   TVM_FFI_INLINE static Optional<T> CopyFromAnyViewAfterCheck(const TVMFFIAny* 
src) {
-    if constexpr (TypeTraits<T>::storage_enabled) {
+    if constexpr (TypeTraits<T>::storage_enabled && 
!use_object_ptr_optional_v<T>) {
       return Optional<T>(Any(AnyView::CopyFromTVMFFIAny(*src)));
     } else {
       if (src->type_index == TypeIndex::kTVMFFINone) return 
Optional<T>(std::nullopt);
@@ -381,7 +679,7 @@ struct TypeTraits<Optional<T>> : public TypeTraitsBase {
   }
 
   TVM_FFI_INLINE static Optional<T> MoveFromAnyAfterCheck(TVMFFIAny* src) {
-    if constexpr (TypeTraits<T>::storage_enabled) {
+    if constexpr (TypeTraits<T>::storage_enabled && 
!use_object_ptr_optional_v<T>) {
       return Optional<T>(details::AnyUnsafe::MoveTVMFFIAnyToAny(src));
     } else {
       if (src->type_index == TypeIndex::kTVMFFINone) return 
Optional<T>(std::nullopt);
diff --git a/rust/tvm-ffi/src/lib.rs b/rust/tvm-ffi/src/lib.rs
index 86579857..03540b0f 100644
--- a/rust/tvm-ffi/src/lib.rs
+++ b/rust/tvm-ffi/src/lib.rs
@@ -54,7 +54,7 @@ pub use crate::extra::structural_visit::{
 pub use crate::function::Function;
 pub use crate::object::ObjectRefCast;
 pub use crate::object::{Object, ObjectArc, ObjectCore, 
ObjectCoreWithExtraItems, ObjectRefCore};
-pub use crate::optional::Optional;
+pub use crate::optional::{Optional, OptionalCompatible};
 pub use crate::string::{Bytes, String};
 pub use crate::type_traits::AnyCompatible;
 pub use tvm_ffi_macros::{dispatch, match_any};
diff --git a/rust/tvm-ffi/src/optional.rs b/rust/tvm-ffi/src/optional.rs
index 33c85231..907127df 100644
--- a/rust/tvm-ffi/src/optional.rs
+++ b/rust/tvm-ffi/src/optional.rs
@@ -16,31 +16,77 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-//! In-place mirror of C++ `ffi::Optional<T>` (`include/tvm/ffi/optional.h`).
+//! In-place mirror of C++ `ffi::Optional<T>` for non-object-pointer `T`
+//! (`include/tvm/ffi/optional.h`).
 //!
-//! C++ `ffi::Optional<T>` is uniformly backed by a single 16-byte `TVMFFIAny`
-//! regardless of `T`, with `type_index == kTVMFFINone` meaning `nullopt`. This
-//! makes the layout independent of the contained type, so a single Rust type
-//! mirrors every `T`.
+//! C++ `ffi::Optional<T>` is backed by one 16-byte `TVMFFIAny` for scalar,
+//! string, and other non-object-pointer values, with
+//! `type_index == kTVMFFINone` meaning `nullopt`. [`Optional<T>`] mirrors that
+//! representation.
 //!
-//! [`Optional<T>`] is `#[repr(transparent)]` over [`Any`] (the same 16-byte
-//! `TVMFFIAny` cell) and decodes such a field's bytes in place — no FFI call, 
no
-//! reflection getter/setter. It is named `Optional` (not `Option`) to 
distinguish
-//! it from Rust's [`std::option::Option`], matching the C++ `ffi::Optional` 
name.
+//! ObjectRef, ObjectPtr, and Arc cases are intentionally excluded. Their C++
+//! optional is one nullable object pointer, which Rust's niche-optimized
+//! [`std::option::Option<X>`] already mirrors. Using `Optional<X>` for an 
object
+//! class is rejected at compile time so the two ABI layouts cannot be 
confused.
 //!
-//! It replaces the earlier per-`T` mirrors (`OptionPod<T>` / `OptionStr` /
-//! `OptionObjRef<T>`): those tracked the three now-removed C++ storage layouts
-//! (`std::optional<T>`, the `String`/`Bytes` sentinel cell, and an `ObjectRef`
-//! pointer). With the uniform `TVMFFIAny` backing they collapse into this one
-//! type.
+//! `Optional<T>` is `#[repr(transparent)]` over [`Any`] and decodes the cell 
in
+//! place. It is named `Optional` (not `Option`) to match the C++ type while
+//! keeping the pointer-backed object case visually distinct.
 
 use crate::any::Any;
-use crate::string::String;
+use crate::string::{Bytes, String};
 use crate::type_traits::AnyCompatible;
 use std::fmt::{self, Debug};
 use std::marker::PhantomData;
 use tvm_ffi_sys::TVMFFITypeIndex as TypeIndex;
 
+/// Marker for values whose C++ `ffi::Optional<T>` uses the 16-byte
+/// `TVMFFIAny` representation.
+///
+/// Object classes deliberately do not implement this trait. Use `Option<X>`
+/// for those values; it is the pointer-sized mirror of C++'s nullable
+/// `ObjectPtr`-backed optional.
+///
+/// ```compile_fail,E0277
+/// use tvm_ffi::{Array, Optional};
+/// let _ = Optional::<Array<i64>>::none();
+/// ```
+#[diagnostic::on_unimplemented(
+    message = "`Optional<{Self}>` only mirrors non-object-pointer 
`ffi::Optional` values",
+    label = "`{Self}` uses the object-pointer optional representation",
+    note = "use `Option<{Self}>` for object classes; it is the compatible 
nullable-pointer layout"
+)]
+pub unsafe trait OptionalCompatible: AnyCompatible {}
+
+macro_rules! impl_optional_compatible {
+    ($($t:ty),* $(,)?) => {
+        $(unsafe impl OptionalCompatible for $t {})*
+    };
+}
+
+impl_optional_compatible!(
+    bool,
+    i8,
+    i16,
+    i32,
+    i64,
+    isize,
+    u8,
+    u16,
+    u32,
+    u64,
+    usize,
+    f32,
+    f64,
+    *mut core::ffi::c_void,
+    crate::DLDataType,
+    crate::DLDevice,
+    String,
+    Bytes,
+);
+
+unsafe impl<T: OptionalCompatible> OptionalCompatible for Option<T> {}
+
 /// In-place mirror of C++ `ffi::Optional<T>`: a single 16-byte `TVMFFIAny` 
cell
 /// (wrapped as [`Any`]) whose `type_index == kTVMFFINone` encodes `nullopt`.
 ///
@@ -48,7 +94,7 @@ use tvm_ffi_sys::TVMFFITypeIndex as TypeIndex;
 /// docs](self). Reuses [`Any`]'s reference-counting `Clone`/`Drop`, which are 
a
 /// no-op on the `nullopt` cell (`type_index` below 
`kTVMFFIStaticObjectBegin`).
 #[repr(transparent)]
-pub struct Optional<T: AnyCompatible> {
+pub struct Optional<T: OptionalCompatible> {
     // Holds either the value's `TVMFFIAny` representation or a `kTVMFFINone` 
cell.
     data: Any,
     _marker: PhantomData<T>,
@@ -62,7 +108,7 @@ const _: () = assert!(
         && std::mem::align_of::<Optional<i64>>() == 
std::mem::align_of::<crate::TVMFFIAny>()
 );
 
-impl<T: AnyCompatible> Optional<T> {
+impl<T: OptionalCompatible> Optional<T> {
     /// An engaged optional holding `value`.
     #[inline]
     pub fn some(value: T) -> Self {
@@ -145,7 +191,7 @@ impl Optional<String> {
     }
 }
 
-impl<T: AnyCompatible> Default for Optional<T> {
+impl<T: OptionalCompatible> Default for Optional<T> {
     /// `nullopt`, matching the C++ default constructor.
     #[inline]
     fn default() -> Self {
@@ -153,7 +199,7 @@ impl<T: AnyCompatible> Default for Optional<T> {
     }
 }
 
-impl<T: AnyCompatible> Clone for Optional<T> {
+impl<T: OptionalCompatible> Clone for Optional<T> {
     #[inline]
     fn clone(&self) -> Self {
         Self {
@@ -164,16 +210,16 @@ impl<T: AnyCompatible> Clone for Optional<T> {
     }
 }
 
-impl<T: AnyCompatible + PartialEq> PartialEq for Optional<T> {
+impl<T: OptionalCompatible + PartialEq> PartialEq for Optional<T> {
     #[inline]
     fn eq(&self, other: &Self) -> bool {
         self.get() == other.get()
     }
 }
 
-impl<T: AnyCompatible + Eq> Eq for Optional<T> {}
+impl<T: OptionalCompatible + Eq> Eq for Optional<T> {}
 
-impl<T: AnyCompatible> From<Option<T>> for Optional<T> {
+impl<T: OptionalCompatible> From<Option<T>> for Optional<T> {
     #[inline]
     fn from(value: Option<T>) -> Self {
         match value {
@@ -183,14 +229,14 @@ impl<T: AnyCompatible> From<Option<T>> for Optional<T> {
     }
 }
 
-impl<T: AnyCompatible> From<Optional<T>> for Option<T> {
+impl<T: OptionalCompatible> From<Optional<T>> for Option<T> {
     #[inline]
     fn from(value: Optional<T>) -> Self {
         value.into_option()
     }
 }
 
-impl<T: AnyCompatible + Debug> Debug for Optional<T> {
+impl<T: OptionalCompatible + Debug> Debug for Optional<T> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self.get() {
             Some(v) => write!(f, "Optional::Some({v:?})"),
diff --git a/rust/tvm-ffi/tests/test_optional.rs 
b/rust/tvm-ffi/tests/test_optional.rs
index c90ca468..d69e7d4e 100644
--- a/rust/tvm-ffi/tests/test_optional.rs
+++ b/rust/tvm-ffi/tests/test_optional.rs
@@ -20,7 +20,7 @@ use tvm_ffi::*;
 
 /// The 16-byte `TVMFFIAny` cell backing an `Optional<T>` (type_index@0,
 /// small_str_len@4, union@8); no padding.
-fn cell_image<T: AnyCompatible>(opt: &Optional<T>) -> [u8; 16] {
+fn cell_image<T: OptionalCompatible>(opt: &Optional<T>) -> [u8; 16] {
     let p = opt as *const Optional<T> as *const u8;
     let mut b = [0u8; 16];
     // `Optional<T>` is a fully-initialized 16-byte cell.
@@ -29,26 +29,42 @@ fn cell_image<T: AnyCompatible>(opt: &Optional<T>) -> [u8; 
16] {
 }
 
 #[test]
-fn layout_is_uniform_16_bytes() {
-    // Independent of `T`: every `Optional<T>` is the 16-byte `TVMFFIAny` cell.
+fn non_object_layout_is_uniform_16_bytes() {
+    // Every supported non-object Optional<T> is the 16-byte TVMFFIAny cell.
     assert_eq!(std::mem::size_of::<Optional<i32>>(), 16);
     assert_eq!(std::mem::size_of::<Optional<i64>>(), 16);
     assert_eq!(std::mem::size_of::<Optional<bool>>(), 16);
     assert_eq!(std::mem::size_of::<Optional<f64>>(), 16);
     assert_eq!(std::mem::size_of::<Optional<String>>(), 16);
-    assert_eq!(std::mem::size_of::<Optional<Array<i64>>>(), 16);
     assert_eq!(
         std::mem::align_of::<Optional<i32>>(),
-        std::mem::align_of::<Optional<Array<i64>>>()
+        std::mem::align_of::<Optional<String>>()
     );
 }
 
+#[test]
+fn object_option_uses_nullable_pointer_layout() {
+    // Object classes intentionally use Rust Option<X>, not Optional<X>.
+    // ObjectArc is non-null, so Option<Array<_>> uses its null-pointer niche.
+    assert_eq!(
+        std::mem::size_of::<Option<Array<i64>>>(),
+        std::mem::size_of::<Array<i64>>()
+    );
+    assert_eq!(
+        std::mem::size_of::<Option<Array<i64>>>(),
+        std::mem::size_of::<*mut ()>()
+    );
+    let none: Option<Array<i64>> = None;
+    assert!(none.is_none());
+    let some = Some(Array::new(vec![1i64, 2, 3]));
+    assert_eq!(some.as_ref().expect("engaged").len(), 3);
+}
+
 #[test]
 fn none_is_all_zero_cell() {
     // `kTVMFFINone == 0` and the union is zeroed, so `nullopt` is 16 zero 
bytes.
     assert_eq!(cell_image(&Optional::<i32>::none()), [0u8; 16]);
     assert_eq!(cell_image(&Optional::<String>::none()), [0u8; 16]);
-    assert_eq!(cell_image(&Optional::<Array<i64>>::none()), [0u8; 16]);
 }
 
 #[test]
@@ -72,7 +88,7 @@ fn byte_image_some_int_matches_ffi_any() {
 
 #[test]
 fn pod_roundtrip_all_supported_types() {
-    fn check<T: AnyCompatible + PartialEq + Copy + std::fmt::Debug>(val: T) {
+    fn check<T: OptionalCompatible + PartialEq + Copy + std::fmt::Debug>(val: 
T) {
         let ty = std::any::type_name::<T>();
         let some = Optional::<T>::some(val);
         assert!(some.has_value(), "engaged has_value for {ty}");
@@ -212,26 +228,3 @@ fn string_set_in_place() {
     o.set(Some(String::from("x")));
     assert_eq!(o.as_str(), Some("x"));
 }
-
-#[test]
-fn object_ref_payload_roundtrip_and_clone() {
-    // An ObjectRef payload is now the same 16-byte cell (was an 8-byte 
pointer).
-    let arr = Array::new(vec![1i64, 2, 3]);
-    let opt = Optional::<Array<i64>>::some(arr);
-    assert!(opt.has_value());
-    let got = opt.get().expect("engaged");
-    assert_eq!(got.len(), 3);
-
-    // clone shares the underlying object; both drop without double-free.
-    let cloned = opt.clone();
-    assert!(cloned.has_value());
-    assert_eq!(cloned.get().expect("engaged").len(), 3);
-
-    // move the payload out
-    let moved: Option<Array<i64>> = opt.into_option();
-    assert_eq!(moved.expect("moved").len(), 3);
-
-    let none = Optional::<Array<i64>>::none();
-    assert!(none.is_none());
-    assert!(none.get().is_none());
-}
diff --git a/src/ffi/extra/library_module.cc b/src/ffi/extra/library_module.cc
index c69518eb..cc992aa2 100644
--- a/src/ffi/extra/library_module.cc
+++ b/src/ffi/extra/library_module.cc
@@ -42,7 +42,7 @@ class LibraryModuleObj final : public ModuleObj {
   /*! \brief Get the property of the runtime module .*/
   int GetPropertyMask() const final { return Module::kBinarySerializable | 
Module::kRunnable; };
 
-  ffi::Function GetFunction(const String& name) final {
+  Optional<ffi::Function> GetFunction(const String& name) final {
     TVMFFISafeCallType faddr;
     faddr = 
reinterpret_cast<TVMFFISafeCallType>(lib_->GetSymbolWithSymbolPrefix(name));
     // ensure the function keeps the Library Module alive
@@ -55,7 +55,7 @@ class LibraryModuleObj final : public ModuleObj {
                                          args.size(), 
reinterpret_cast<TVMFFIAny*>(rv)));
       });
     }
-    return nullptr;
+    return std::nullopt;
   }
 
   Optional<String> GetFunctionMetadata(const String& name) final {
diff --git a/src/ffi/extra/module.cc b/src/ffi/extra/module.cc
index 9c254a12..31bb95bb 100644
--- a/src/ffi/extra/module.cc
+++ b/src/ffi/extra/module.cc
@@ -58,19 +58,18 @@ class ModuleGlobals {
   std::mutex mutex_;
 };
 
-Function ModuleObj::GetFunction(const String& name, bool query_imports) {
-  if (Function func = this->GetFunction(name); func != nullptr) {
-    return func;
+Optional<Function> ModuleObj::GetFunction(const String& name, bool 
query_imports) {
+  if (auto opt_func = this->GetFunction(name)) {
+    return opt_func;
   }
   if (query_imports) {
     for (const Any& import : imports_) {
-      if (Function func = import.cast<Module>()->GetFunction(name, 
query_imports);
-          func != nullptr) {
-        return func;
+      if (auto opt_func = import.cast<Module>()->GetFunction(name, 
query_imports)) {
+        return *opt_func;
       }
     }
   }
-  return nullptr;
+  return std::nullopt;
 }
 
 Optional<String> ModuleObj::GetFunctionMetadata(const String& name, bool 
query_imports) {
diff --git a/src/ffi/extra/module_internal.h b/src/ffi/extra/module_internal.h
index 1a5742ab..4519d9ad 100644
--- a/src/ffi/extra/module_internal.h
+++ b/src/ffi/extra/module_internal.h
@@ -76,21 +76,21 @@ struct ModuleObj::InternalUnsafe {
       return const_cast<FunctionObj*>((*it).second.operator->());
     }
 
-    Function func = [&]() -> Function {
+    auto opt_func = [&]() -> std::optional<Function> {
       for (const Any& import : module->imports_) {
-        if (Function func = import.cast<Module>()->GetFunction(s_name, true); 
func != nullptr) {
-          return func;
+        if (auto opt_func = import.cast<Module>()->GetFunction(s_name, true)) {
+          return *opt_func;
         }
       }
       // try global at last
-      return tvm::ffi::Function::GetGlobal(s_name).value_or(nullptr);
+      return tvm::ffi::Function::GetGlobal(s_name);
     }();
-    if (func == nullptr) {
+    if (!opt_func.has_value()) {
       TVM_FFI_THROW(RuntimeError) << "Cannot find function " << name
                                   << " in the imported modules or global 
registry.";
     }
-    module->import_lookup_cache_.Set(s_name, func);
-    return const_cast<FunctionObj*>(func.operator->());
+    module->import_lookup_cache_.Set(s_name, *opt_func);
+    return const_cast<FunctionObj*>((*opt_func).operator->());
   }
 
   static void RegisterReflection() {
diff --git a/tests/cpp/test_object.cc b/tests/cpp/test_object.cc
index b4e70359..0c4b066a 100644
--- a/tests/cpp/test_object.cc
+++ b/tests/cpp/test_object.cc
@@ -82,9 +82,9 @@ inline constexpr bool object_ref_contains_is_enabled_v<
 static_assert(ObjectRef::_type_container_is_exact);
 static_assert(TNumber::_type_container_is_exact);
 static_assert(TInt::_type_container_is_exact);
-// Optional<T> is uniformly Any-backed and is no longer an ObjectRef, so it 
does
-// not participate in the ObjectRef container concept.
-static_assert(!std::is_base_of_v<ObjectRef, Optional<TInt>>);
+// ObjectRef optionals retain the original ObjectRef/ObjectPtr representation
+// and continue to participate in the ObjectRef container concept.
+static_assert(std::is_base_of_v<ObjectRef, Optional<TInt>>);
 static_assert(!TIntOrFloatRef::_type_container_is_exact);
 static_assert(!Array<TInt>::_type_container_is_exact);
 static_assert(!List<TInt>::_type_container_is_exact);
@@ -95,15 +95,14 @@ static_assert(!Variant<TInt, 
TFloat>::_type_container_is_exact);
 
 static_assert(object_ref_contains_v<TNumber, TIntObj>);
 static_assert(object_ref_contains_v<TInt, TIntObj>);
-// Optional<T> is no longer an ObjectRef, so it is outside the 
object-ref-contains
-// concept entirely (the trait is not even enabled for it).
-static_assert(!object_ref_contains_is_enabled_v<Optional<TInt>, TIntObj>);
+static_assert(object_ref_contains_v<Optional<TInt>, TIntObj>);
 static_assert(!object_ref_contains_v<TInt, TFloatObj>);
 static_assert(!object_ref_contains_v<Array<TInt>, ArrayObj>);
 static_assert(object_ref_contains_v<TIntOrFloatRef, TIntObj>);
 static_assert(object_ref_contains_v<TIntOrFloatRef, TFloatObj>);
 static_assert(!object_ref_contains_v<TIntOrFloatRef, TNumberObj>);
 static_assert(object_ref_contains_is_enabled_v<TInt, TIntObj>);
+static_assert(object_ref_contains_is_enabled_v<Optional<TInt>, TIntObj>);
 static_assert(object_ref_contains_is_enabled_v<TIntOrFloatRef, TIntObj>);
 static_assert(!object_ref_contains_is_enabled_v<int, TIntObj>);
 static_assert(!object_ref_contains_is_enabled_v<TIntObj, TIntObj>);
diff --git a/tests/cpp/test_object_ptr.cc b/tests/cpp/test_object_ptr.cc
index 6e87ccc0..4f77fc7b 100644
--- a/tests/cpp/test_object_ptr.cc
+++ b/tests/cpp/test_object_ptr.cc
@@ -316,6 +316,13 @@ TEST(Arc, ConstructionOwnershipAndUpcast) {
 
 TEST(Arc, AnyRoundTripAndSchemas) {
   Arc<GeneratedDerivedObj> derived = make_arc<GeneratedDerivedObj>(7);
+  using BasePtr = ObjectPtr<GeneratedBaseObj>;
+  using BaseArc = Arc<GeneratedBaseObj>;
+  static_assert(sizeof(Optional<BasePtr>) == sizeof(BasePtr));
+  static_assert(alignof(Optional<BasePtr>) == alignof(BasePtr));
+  static_assert(sizeof(Optional<BaseArc>) == sizeof(BasePtr));
+  static_assert(alignof(Optional<BaseArc>) == alignof(BasePtr));
+
   Any value = derived;
   EXPECT_EQ(derived.use_count(), 2);
 
@@ -337,9 +344,26 @@ TEST(Arc, AnyRoundTripAndSchemas) {
   EXPECT_EQ(TypeTraits<Optional<Arc<GeneratedBaseObj>>>::TypeSchema(),
             
R"({"type":"Optional","args":[{"type":"testing.GeneratedBase"}]})");
 
+  BasePtr ptr = derived;
+  Optional<BasePtr> optional_ptr = ptr;
+  ASSERT_TRUE(optional_ptr.has_value());
+  EXPECT_EQ(optional_ptr.get(), ptr.get());
+  Optional<BasePtr> ptr_roundtrip = 
Any(optional_ptr).cast<Optional<BasePtr>>();
+  ASSERT_TRUE(ptr_roundtrip.has_value());
+  EXPECT_EQ(ptr_roundtrip.get(), ptr.get());
+  BasePtr moved_ptr = std::move(optional_ptr).value();
+  EXPECT_EQ(moved_ptr.get(), ptr.get());
+  // NOLINTNEXTLINE(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
+  EXPECT_FALSE(optional_ptr.has_value());
+
   Optional<Arc<GeneratedBaseObj>> present = Arc<GeneratedBaseObj>(derived);
   ASSERT_TRUE(present.has_value());
+  EXPECT_EQ(present.get(), static_cast<GeneratedBaseObj*>(derived.get()));
   EXPECT_EQ(present.value().get(), 
static_cast<GeneratedBaseObj*>(derived.get()));
+  Arc<GeneratedBaseObj> moved_arc = std::move(present).value();
+  EXPECT_EQ(moved_arc.get(), static_cast<GeneratedBaseObj*>(derived.get()));
+  // NOLINTNEXTLINE(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
+  EXPECT_FALSE(present.has_value());
   Optional<Arc<GeneratedBaseObj>> absent = std::nullopt;
   
EXPECT_FALSE(Any(absent).cast<Optional<Arc<GeneratedBaseObj>>>().has_value());
 }
diff --git a/tests/cpp/test_optional.cc b/tests/cpp/test_optional.cc
index e0a696e5..2b562a97 100644
--- a/tests/cpp/test_optional.cc
+++ b/tests/cpp/test_optional.cc
@@ -47,9 +47,12 @@ 
static_assert(TypeTraits<Optional<TensorView>>::storage_enabled ==
               TypeTraits<TensorView>::storage_enabled);
 static_assert(!TypeTraits<Optional<TensorView>>::storage_enabled,
               "Optional<view> must not be storage-enabled");
-// Because Optional<int>/Optional<TInt> are storage-enabled, the outer
-// Optional<Optional<T>> uses the Any-backed representation (sizeof == 
sizeof(Any)),
-// and Optional<T> is accepted as a storage type (e.g. inside 
Variant/containers).
+// ObjectRef optionals are pointer-backed, while an outer Optional<Optional<T>>
+// remains Any-backed because nested optionals are intentionally distinct.
+static_assert(sizeof(Optional<TInt>) == sizeof(ObjectPtr<Object>));
+static_assert(alignof(Optional<TInt>) == alignof(ObjectPtr<Object>));
+static_assert(sizeof(Optional<Function>) == sizeof(Function));
+static_assert(alignof(Optional<Function>) == alignof(Function));
 static_assert(sizeof(Optional<Optional<int>>) == sizeof(Any));
 static_assert(sizeof(Optional<Optional<TInt>>) == sizeof(Any));
 static_assert(details::storage_enabled_v<Optional<int>>);
@@ -67,8 +70,7 @@ TEST(Optional, StorageEnabledPassThrough) {
 TEST(Optional, TInt) {
   Optional<TInt> x;
   Optional<TInt> y = TInt(11);
-  // Optional<T> is uniformly backed by a single Any (TVMFFIAny) regardless of 
T.
-  static_assert(sizeof(Optional<TInt>) == sizeof(Any));
+  static_assert(sizeof(Optional<TInt>) == sizeof(ObjectPtr<Object>));
 
   EXPECT_TRUE(!x.has_value());
   EXPECT_EQ(x.value_or(TInt(12))->value, 12);
@@ -88,6 +90,26 @@ TEST(Optional, TInt) {
   EXPECT_EQ(y2.value_or(TInt(12))->value, 11);
 }
 
+TEST(Optional, ObjectRefPointerABI) {
+  static_assert(sizeof(Optional<ObjectRef>) == sizeof(ObjectPtr<Object>));
+  static_assert(alignof(Optional<ObjectRef>) == alignof(ObjectPtr<Object>));
+
+  ObjectRef value = TInt(19);
+  Optional<ObjectRef> present = value;
+  ASSERT_TRUE(present.has_value());
+  EXPECT_TRUE(present.same_as(value));
+  EXPECT_EQ(present.use_count(), value.use_count());
+
+  Any encoded = present;
+  Optional<ObjectRef> decoded = encoded.cast<Optional<ObjectRef>>();
+  ASSERT_TRUE(decoded.has_value());
+  EXPECT_TRUE(decoded.same_as(value));
+
+  Optional<ObjectRef> absent = std::nullopt;
+  EXPECT_EQ(absent.get(), nullptr);
+  EXPECT_FALSE(Any(absent).cast<Optional<ObjectRef>>().has_value());
+}
+
 TEST(Optional, double) {
   Optional<double> x;
   Optional<double> y = 11.0;
@@ -250,15 +272,13 @@ TEST(Optional, Bytes) {
   static_assert(sizeof(Optional<Bytes>) == sizeof(Any));
 }
 
-// Optional<T> is uniformly backed by a single TVMFFIAny (Any), so its layout 
is
-// independent of the contained type: every Optional<T> has the size and
-// alignment of a TVMFFIAny. (The Rust binding's in-place Optional<T> mirror 
is a
-// separate follow-up that adopts this uniform 16-byte representation.)
+// Non-object values keep the uniform TVMFFIAny representation. Object pointer
+// values are covered above by pointer-size ABI assertions.
 template <typename... T>
-constexpr bool all_optional_layouts_uniform_v =
+constexpr bool all_non_object_optional_layouts_uniform_v =
     ((sizeof(Optional<T>) == sizeof(TVMFFIAny) && alignof(Optional<T>) == 
alignof(TVMFFIAny)) &&
      ...);
-static_assert(
-    all_optional_layouts_uniform_v<bool, int8_t, int16_t, int32_t, int64_t, 
uint8_t, uint16_t,
-                                   uint32_t, uint64_t, float, double, String, 
Bytes>);
+static_assert(all_non_object_optional_layouts_uniform_v<bool, int8_t, int16_t, 
int32_t, int64_t,
+                                                        uint8_t, uint16_t, 
uint32_t, uint64_t,
+                                                        float, double, String, 
Bytes>);
 }  // namespace
diff --git a/tests/python/test_dataclass_gen_abi_cpp.py 
b/tests/python/test_dataclass_gen_abi_cpp.py
index 98345c51..56ee7e65 100644
--- a/tests/python/test_dataclass_gen_abi_cpp.py
+++ b/tests/python/test_dataclass_gen_abi_cpp.py
@@ -728,10 +728,10 @@ struct alignas(8) ObjectContainersObj : public 
::tvm::ffi::Object {
   ::tvm::ffi::Dict<::tvm::ffi::String, 
::tvm::ffi::Arc<::testing::gen_abi_cpp::native::RawTensorObj>> dictionary;  // 
offset=48, size=8, align=8
   ::tvm::ffi::Arc<::testing::gen_abi_cpp::native::RawTensorObj> item;  // 
offset=56, size=8, align=8
   ::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::native::RawTensorObj> 
nullable_item;  // offset=64, size=8, align=8
-  
::tvm::ffi::Optional<::tvm::ffi::Arc<::testing::gen_abi_cpp::native::RawTensorObj>>
 optional_item;  // offset=72, size=16, align=8
+  ::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::native::RawTensorObj> 
optional_item;  // offset=72, size=8, align=8
 };
 
-static_assert(sizeof(ObjectContainersObj) == 88);
+static_assert(sizeof(ObjectContainersObj) == 80);
 static_assert(alignof(ObjectContainersObj) == 8);
 static_assert(sizeof(decltype(ObjectContainersObj::array_items)) == 8);
 static_assert(alignof(decltype(ObjectContainersObj::array_items)) == 8);
@@ -751,7 +751,7 @@ static_assert(offsetof(ObjectContainersObj, item) == 56);
 static_assert(sizeof(decltype(ObjectContainersObj::nullable_item)) == 8);
 static_assert(alignof(decltype(ObjectContainersObj::nullable_item)) == 8);
 static_assert(offsetof(ObjectContainersObj, nullable_item) == 64);
-static_assert(sizeof(decltype(ObjectContainersObj::optional_item)) == 16);
+static_assert(sizeof(decltype(ObjectContainersObj::optional_item)) == 8);
 static_assert(alignof(decltype(ObjectContainersObj::optional_item)) == 8);
 static_assert(offsetof(ObjectContainersObj, optional_item) == 72);
 

Reply via email to