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>();
}