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

junrushao 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 8ccfe1b  [CORE] Make AnyView trivially copyable — match C-ABI struct 
passing (#602)
8ccfe1b is described below

commit 8ccfe1b9d05fb7e067f8d5b98ac60159c23bb35e
Author: Tianqi Chen <[email protected]>
AuthorDate: Sat May 30 16:52:38 2026 -0400

    [CORE] Make AnyView trivially copyable — match C-ABI struct passing (#602)
    
    [CORE] Make AnyView trivially copyable — match C-ABI struct passing
    
    Default AnyView's move constructor and move assignment so the type
    is std::is_trivially_copyable_v<AnyView>. AnyView owns nothing, so
    the prior "reset source to None after move" body was dead semantics.
    
    Motivation
    ----------
    A trivially-copyable C++ aggregate is passed by the C++ compiler
    using the same calling convention as the equivalent C struct of POD
    types. That makes function signatures like `void fn(AnyView)`
    ABI-stable across every language that interoperates via the C ABI —
    Rust, Go, Python ctypes, etc. can call C++ functions that take
    AnyView by value using their native FFI, with no wrapper or shim.
    The 16-byte AnyView is treated identically to a C
    `struct { int32_t type_index; uint32_t zero_padding; int64_t v_int64; }`
    or a Rust `#[repr(C)] struct { i32, u32, i64 }`.
    
    A user-defined move ctor disqualifies that classification, forcing
    the compiler to fall back to passing AnyView by hidden reference (a
    C++-specific calling convention) — silently breaking cross-language
    ABI compatibility for any signature that takes AnyView by value.
    
    As a follow-on benefit, on x86-64 SysV / AAPCS AArch64 the trivially-
    copyable AnyView is now passed in two integer registers instead of
    via a stack pointer, eliminating a memory dereference per dispatch.
    
    Result
    ------
    Codegen for `int fn(AnyView x) { return x.type_index(); }` on
    x86-64 SysV (-O2, gcc 11.4):
    
      BEFORE:  mov eax, DWORD PTR [rdi]   ; deref of caller stack slot
      AFTER :  mov eax, edi               ; type_index lives directly in rdi
    
    A `static_assert(std::is_trivially_copyable_v<AnyView>, ...)` is
    added next to the existing size asserts so any future user-defined
    copy/move/destructor that regresses this property fails at compile
    time.
    
    Notes
    -----
    - Any (the owning sibling) is a separate class — not a subclass —
      and remains non-trivially-copyable via its refcount-decrementing
      destructor. The static_assert is fully local to AnyView.
    - No test churn required: no existing test asserted the moved-from
      AnyView reset-to-None semantics.
    - No ABI break: of the 21 TVM_FFI_EXTRA_CXX_API declarations in
      include/tvm/ffi/extra/*, none currently takes AnyView by value
      (all take `const Any&` or project-specific types), and the C ABI
      in c_api.h uses `const TVMFFIAny*` pointers. So this calling-
      convention flip does not break any existing exported API.
---
 include/tvm/ffi/any.h | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)

diff --git a/include/tvm/ffi/any.h b/include/tvm/ffi/any.h
index 47a4472..9d7ccee 100644
--- a/include/tvm/ffi/any.h
+++ b/include/tvm/ffi/any.h
@@ -83,16 +83,8 @@ class AnyView {
   /*! \brief Copy assignment operator */
   AnyView& operator=(const AnyView&) = default;
   /*! \brief Move constructor */
-  AnyView(AnyView&& other) noexcept : data_(other.data_) {
-    other.data_.type_index = TypeIndex::kTVMFFINone;
-    other.data_.zero_padding = 0;
-    other.data_.v_int64 = 0;
-  }
-  TVM_FFI_INLINE AnyView& operator=(AnyView&& other) noexcept {
-    // copy-and-swap idiom
-    AnyView(std::move(other)).swap(*this);  // NOLINT(*)
-    return *this;
-  }
+  AnyView(AnyView&& other) noexcept = default;
+  AnyView& operator=(AnyView&& other) noexcept = default;
   /*!
    * \brief Constructor from a general type.
    * \tparam T The type to convert from.
@@ -498,6 +490,12 @@ class Any {
 // layout assert to ensure we can freely cast between the two types
 static_assert(sizeof(AnyView) == sizeof(TVMFFIAny));
 static_assert(sizeof(Any) == sizeof(TVMFFIAny));
+// AnyView is a non-owning, POD-shaped view of TVMFFIAny. Keeping it
+// trivially copyable lets the C++ ABI pass it through registers and
+// match the C ABI for struct passing. Any user-defined copy/move ctor
+// or non-trivial destructor on AnyView would silently regress this
+// calling convention — catch it here at compile time.
+static_assert(std::is_trivially_copyable_v<AnyView>, "AnyView must be 
trivially copyable.");
 
 namespace details {
 

Reply via email to