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

tqchen 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 c4847d4f [FIX] Decay the function type in 
TVM_FFI_DLL_EXPORT_TYPED_FUNC (#830)
c4847d4f is described below

commit c4847d4f25c5e1ae5b53154dca8e43e454f5c223
Author: YevheniiKotyrlo <[email protected]>
AuthorDate: Tue Sep 29 21:28:32 2026 +0300

    [FIX] Decay the function type in TVM_FFI_DLL_EXPORT_TYPED_FUNC (#830)
    
    ## Problem
    
    Exporting a static member function through its parenthesized address
    does not compile under MSVC:
    
    ```cpp
    struct BatchMemcpy { static void run(int64_t n); };
    TVM_FFI_DLL_EXPORT_TYPED_FUNC(batch_memcpy, (&BatchMemcpy::run));
    ```
    
    ```
    function_details.h(127): error C2825: 'T': must be a class or namespace 
when followed by '::'
    note: see reference to class template instantiation 
'tvm::ffi::details::FunctionInfo<void (__cdecl *&)(int64_t),void>' being 
compiled
    ```
    
    MSVC 19.51 deduces `decltype((&S::run))` as `void (*&)(int64_t)`, a
    reference, where the standard gives the prvalue's own type.
    `decltype((&free_function))` and the unparenthesized `decltype(&S::run)`
    come out right, so it takes exactly this spelling, in C++17 and C++20
    alike. No `FunctionInfo` specialization matches a reference to a
    function pointer, so the primary template tries `&T::operator()`.
    
    **The spelling is what generated exports look like**: a library that
    emits one export per kernel instantiation wraps the function in
    parentheses so the commas of its template arguments stay one macro
    argument. FreeToken generates `TVM_FFI_DLL_EXPORT_TYPED_FUNC(launch,
    (&MultiIndexCopyKernel<...>::run))`, and those kernels do not build on
    Windows.
    
    ## Solution
    
    Both export macros instantiate
    `FunctionInfo<std::decay_t<decltype(Function)>>`, as the callable paths
    of `TypedFunction` in the same header already do. Decay turns a function
    type, a function reference and a reference to a function pointer into
    the function pointer `FunctionInfo` is specialized for, and leaves a
    closure type as it is.
    
    ## Tests
    
    `Func.FromExternCStaticMemberAddress` in `tests/cpp/test_function.cc`
    exports `(&TestingAdd2::Run)` and calls it through
    `Function::FromExternC`. The Windows CI job builds the C++ tests with
    MSVC; on `main` this test fails to compile there, with `FunctionInfo<int
    (__cdecl *&)(int),void>`.
    
    ## Verification
    
    Windows 11, MSVC 19.51: `tvm_ffi_tests` 540 passed (2 disabled) ·
    `clang-format` 21.1.7 clean. With this and #829, FreeToken's 82 JIT
    kernels compile, link and load on Windows.
    
    No existing issue or PR covers it — searched the tracker for
    `TVM_FFI_DLL_EXPORT_TYPED_FUNC`, `FunctionInfo`, `decay` and `MSVC`.
    
    ## Base
    
    `c96f9e3`, and it merges cleanly with `main`.
---
 include/tvm/ffi/function.h | 28 ++++++++++++++--------------
 tests/cpp/test_function.cc | 13 +++++++++++++
 2 files changed, 27 insertions(+), 14 deletions(-)

diff --git a/include/tvm/ffi/function.h b/include/tvm/ffi/function.h
index a7a02959..eb741aaf 100644
--- a/include/tvm/ffi/function.h
+++ b/include/tvm/ffi/function.h
@@ -958,19 +958,19 @@ inline int32_t TypeKeyToIndex(std::string_view type_key) {
 // These should not be used directly; use the public macros instead.
 
 // Internal implementation macro that generates the C ABI wrapper function
-#define TVM_FFI_DLL_EXPORT_TYPED_FUNC_IMPL_(ExportName, Function)              
        \
-  extern "C" {                                                                 
        \
-  TVM_FFI_DLL_EXPORT int __tvm_ffi_##ExportName(void* self, const TVMFFIAny* 
args,     \
-                                                int32_t num_args, TVMFFIAny* 
result) { \
-    TVM_FFI_SAFE_CALL_BEGIN();                                                 
        \
-    using FuncInfo = ::tvm::ffi::details::FunctionInfo<decltype(Function)>;    
        \
-    static std::string name = #ExportName;                                     
        \
-    ::tvm::ffi::details::unpack_call<typename FuncInfo::RetType>(              
        \
-        std::make_index_sequence<FuncInfo::num_args>{}, &name, Function,       
        \
-        reinterpret_cast<const ::tvm::ffi::AnyView*>(args), num_args,          
        \
-        reinterpret_cast<::tvm::ffi::Any*>(result));                           
        \
-    TVM_FFI_SAFE_CALL_END();                                                   
        \
-  }                                                                            
        \
+#define TVM_FFI_DLL_EXPORT_TYPED_FUNC_IMPL_(ExportName, Function)              
           \
+  extern "C" {                                                                 
           \
+  TVM_FFI_DLL_EXPORT int __tvm_ffi_##ExportName(void* self, const TVMFFIAny* 
args,        \
+                                                int32_t num_args, TVMFFIAny* 
result) {    \
+    TVM_FFI_SAFE_CALL_BEGIN();                                                 
           \
+    using FuncInfo = 
::tvm::ffi::details::FunctionInfo<std::decay_t<decltype(Function)>>; \
+    static std::string name = #ExportName;                                     
           \
+    ::tvm::ffi::details::unpack_call<typename FuncInfo::RetType>(              
           \
+        std::make_index_sequence<FuncInfo::num_args>{}, &name, Function,       
           \
+        reinterpret_cast<const ::tvm::ffi::AnyView*>(args), num_args,          
           \
+        reinterpret_cast<::tvm::ffi::Any*>(result));                           
           \
+    TVM_FFI_SAFE_CALL_END();                                                   
           \
+  }                                                                            
           \
   }
 /// \endcond
 
@@ -1011,7 +1011,7 @@ inline int32_t TypeKeyToIndex(std::string_view type_key) {
   TVM_FFI_DLL_EXPORT int __tvm_ffi__metadata_##ExportName(void* self, const 
TVMFFIAny* args,     \
                                                           int32_t num_args, 
TVMFFIAny* result) { \
     TVM_FFI_SAFE_CALL_BEGIN();                                                 
                  \
-    using FuncInfo = ::tvm::ffi::details::FunctionInfo<decltype(Function)>;    
                  \
+    using FuncInfo = 
::tvm::ffi::details::FunctionInfo<std::decay_t<decltype(Function)>>;        \
     std::ostringstream os;                                                     
                  \
     os << R"({"type_schema":)"                                                 
                  \
        << 
::tvm::ffi::EscapeStringJSON(::tvm::ffi::String(FuncInfo::TypeSchema())) << 
R"(})";    \
diff --git a/tests/cpp/test_function.cc b/tests/cpp/test_function.cc
index 1a915539..0bcc8e0c 100644
--- a/tests/cpp/test_function.cc
+++ b/tests/cpp/test_function.cc
@@ -284,6 +284,19 @@ TEST(Func, FromExternC) {
   EXPECT_EQ(fadd1(1).cast<int>(), 2);
 }
 
+struct TestingAdd2 {
+  static int Run(int x) { return x + 2; }
+};
+
+// A library that generates its exports parenthesizes the function it names, 
and MSVC
+// deduces decltype((&TestingAdd2::Run)) as a reference to the function 
pointer.
+TVM_FFI_DLL_EXPORT_TYPED_FUNC(testing_add2, (&TestingAdd2::Run))
+
+TEST(Func, FromExternCStaticMemberAddress) {
+  Function fadd2 = Function::FromExternC(nullptr, __tvm_ffi_testing_add2, 
nullptr);
+  EXPECT_EQ(fadd2(1).cast<int>(), 3);
+}
+
 int invoke_testing_add1(int x) {
   return Function::InvokeExternC(nullptr, __tvm_ffi_testing_add1, 
x).cast<int>();
 }

Reply via email to