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 {