Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 90 additions & 35 deletions cmake/Utils/Library.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ endfunction ()
# target_name
# [LINK_SHARED ON|OFF] [LINK_HEADER ON|OFF] [DEBUG_SYMBOL ON|OFF] [MSVC_FLAGS ON|OFF]
# [STUB_INIT ON|OFF] [STUB_DIR <dir>] [STUB_PKG <pkg>] [STUB_PREFIX <prefix>]
# [STUB_TARGET <backend>...] # one or more of: python rust
# [STUB_DIR_PYTHON <dir>] [STUB_DIR_RUST <dir>]
# )
# Configure a target to integrate with TVM-FFI CMake utilities:
# - Link against tvm_ffi::header and/or tvm_ffi::shared
Expand All @@ -194,6 +196,20 @@ endfunction ()
# STUB_INIT: Whether to allow generating new directives. Default: OFF (ON/OFF-style)
# STUB_PKG: Package name passed to stub generator (requires STUB_DIR and STUB_INIT=ON; default: ${SKBUILD_PROJECT_NAME} if set, otherwise target name)
# STUB_PREFIX: Module prefix passed to stub generator (requires STUB_DIR and STUB_INIT=ON; default: "<STUB_PKG>.")
# STUB_TARGET: Code generator backend(s): a list of one or more of "python" (default) and
# "rust" (e.g. STUB_TARGET python rust). Each listed backend is passed to the
# stub generator as --target. With "rust", object bindings are emitted into a
# Rust module tree under its STUB_DIR (global functions are not generated for Rust).
# STUB_TARGET with multiple backends (e.g. STUB_TARGET python rust):
# Generates stubs for each listed backend. Because each backend writes a
# different file tree, supply per-backend STUB_DIR_PYTHON / STUB_DIR_RUST. One
# post-build stub command is emitted per backend; the shared STUB_PKG / STUB_PREFIX
# apply to every backend.
# STUB_DIR_PYTHON / STUB_DIR_RUST:
# Per-backend output directories, for listing several backends that each write a
# different file tree. A listed backend with no directory (no STUB_DIR_<BACKEND>,
# and no STUB_DIR for a single backend) is skipped. Relative paths resolve against
# CMAKE_CURRENT_SOURCE_DIR, same as STUB_DIR.
# ~~~
function (tvm_ffi_configure_target target)
if (NOT target)
Expand All @@ -219,8 +235,11 @@ function (tvm_ffi_configure_target target)
STUB_DIR
STUB_PKG
STUB_PREFIX
STUB_DIR_PYTHON
STUB_DIR_RUST
)
set(tvm_ffi_arg_multiValueArgs)
# STUB_TARGET is a list: one or more of `python` / `rust`.
set(tvm_ffi_arg_multiValueArgs STUB_TARGET)

cmake_parse_arguments(
tvm_ffi_arg_ "${tvm_ffi_arg_options}" "${tvm_ffi_arg_oneValueArgs}"
Expand All @@ -236,16 +255,35 @@ function (tvm_ffi_configure_target target)
if (NOT DEFINED tvm_ffi_arg__STUB_INIT)
set(tvm_ffi_arg__STUB_INIT OFF)
endif ()
if (NOT DEFINED tvm_ffi_arg__STUB_TARGET OR NOT tvm_ffi_arg__STUB_TARGET)
set(tvm_ffi_arg__STUB_TARGET "python")
endif ()

# Validation
if ((NOT DEFINED tvm_ffi_arg__STUB_DIR) OR (NOT tvm_ffi_arg__STUB_DIR))
if (DEFINED tvm_ffi_arg__STUB_PKG OR DEFINED tvm_ffi_arg__STUB_PREFIX)
list(LENGTH tvm_ffi_arg__STUB_TARGET tvm_ffi_stub_target_count)

# Validation: every requested backend must be 'python' or 'rust'.
foreach (tvm_ffi_b IN LISTS tvm_ffi_arg__STUB_TARGET)
if (NOT tvm_ffi_b MATCHES "^(python|rust)$")
message(
FATAL_ERROR
"tvm_ffi_configure_target(${target}): STUB_PKG/STUB_PREFIX require STUB_DIR to be set."
"tvm_ffi_configure_target(${target}): STUB_TARGET entries must be 'python' or 'rust', got '${tvm_ffi_b}'."
)
endif ()
endforeach ()

# A stub directory may be given globally (STUB_DIR) or, when STUB_TARGET lists several backends
# that each write a different file tree, per backend (STUB_DIR_PYTHON / STUB_DIR_RUST).
set(tvm_ffi_has_stub_dir OFF)
if (DEFINED tvm_ffi_arg__STUB_DIR AND tvm_ffi_arg__STUB_DIR)
set(tvm_ffi_has_stub_dir ON)
endif ()
foreach (tvm_ffi_B IN ITEMS PYTHON RUST)
if (DEFINED tvm_ffi_arg__STUB_DIR_${tvm_ffi_B} AND tvm_ffi_arg__STUB_DIR_${tvm_ffi_B})
set(tvm_ffi_has_stub_dir ON)
endif ()
endforeach ()

# Validation
if (NOT tvm_ffi_arg__STUB_INIT)
if (DEFINED tvm_ffi_arg__STUB_PKG OR DEFINED tvm_ffi_arg__STUB_PREFIX)
message(
Expand All @@ -254,15 +292,16 @@ function (tvm_ffi_configure_target target)
)
endif ()
else ()
if (NOT DEFINED tvm_ffi_arg__STUB_DIR OR NOT tvm_ffi_arg__STUB_DIR)
if (NOT tvm_ffi_has_stub_dir)
message(
FATAL_ERROR "tvm_ffi_configure_target(${target}): STUB_INIT=ON requires STUB_DIR to be set."
FATAL_ERROR
"tvm_ffi_configure_target(${target}): STUB_INIT=ON requires a stub directory (STUB_DIR, or STUB_DIR_PYTHON/STUB_DIR_RUST)."
)
endif ()
endif ()

# STUB_PKG and STUB_PREFIX defaults
if (tvm_ffi_arg__STUB_INIT AND tvm_ffi_arg__STUB_DIR)
# STUB_PKG and STUB_PREFIX defaults (shared across all listed backends)
if (tvm_ffi_arg__STUB_INIT AND tvm_ffi_has_stub_dir)
if (NOT DEFINED tvm_ffi_arg__STUB_PKG)
if (DEFINED SKBUILD_PROJECT_NAME AND SKBUILD_PROJECT_NAME)
set(tvm_ffi_arg__STUB_PKG "${SKBUILD_PROJECT_NAME}")
Expand Down Expand Up @@ -336,37 +375,53 @@ function (tvm_ffi_configure_target target)
endif ()
endif ()

if (DEFINED tvm_ffi_arg__STUB_DIR AND tvm_ffi_arg__STUB_DIR)
get_filename_component(
tvm_ffi_arg__STUB_DIR_ABS "${tvm_ffi_arg__STUB_DIR}" ABSOLUTE BASE_DIR
"${CMAKE_CURRENT_SOURCE_DIR}"
)
if (tvm_ffi_has_stub_dir)
find_package(
Python3
COMPONENTS Interpreter
REQUIRED
)
set(tvm_ffi_stub_cli_args "${tvm_ffi_arg__STUB_DIR_ABS}" --dlls $<TARGET_FILE:${target}>)
if (tvm_ffi_arg__STUB_INIT)
list(
APPEND
tvm_ffi_stub_cli_args
--init-lib
${target}
--init-pypkg
"${tvm_ffi_arg__STUB_PKG}"
--init-prefix
"${tvm_ffi_arg__STUB_PREFIX}"
)
endif ()
add_custom_command(
TARGET ${target}
POST_BUILD
COMMAND ${Python3_EXECUTABLE} -m tvm_ffi.stub.cli ${tvm_ffi_stub_cli_args}
COMMENT
"[COMMAND] Running: ${Python3_EXECUTABLE} -m tvm_ffi.stub.cli ${tvm_ffi_stub_cli_args}"
VERBATIM
)
# One post-build stub command per backend. Each backend writes to its own STUB_DIR_<BACKEND>; a
# single backend may instead use the shared STUB_DIR. A listed backend with no directory is
# skipped.
foreach (tvm_ffi_b IN LISTS tvm_ffi_arg__STUB_TARGET)
string(TOUPPER "${tvm_ffi_b}" tvm_ffi_B)
set(tvm_ffi_stub_dir "")
if (DEFINED tvm_ffi_arg__STUB_DIR_${tvm_ffi_B} AND tvm_ffi_arg__STUB_DIR_${tvm_ffi_B})
set(tvm_ffi_stub_dir "${tvm_ffi_arg__STUB_DIR_${tvm_ffi_B}}")
elseif (tvm_ffi_stub_target_count EQUAL 1)
set(tvm_ffi_stub_dir "${tvm_ffi_arg__STUB_DIR}")
endif ()
if (tvm_ffi_stub_dir)
get_filename_component(
tvm_ffi_arg__STUB_DIR_ABS "${tvm_ffi_stub_dir}" ABSOLUTE BASE_DIR
"${CMAKE_CURRENT_SOURCE_DIR}"
)
set(tvm_ffi_stub_cli_args "${tvm_ffi_arg__STUB_DIR_ABS}" --dlls $<TARGET_FILE:${target}>
--target "${tvm_ffi_b}"
)
if (tvm_ffi_arg__STUB_INIT)
list(
APPEND
tvm_ffi_stub_cli_args
--init-lib
${target}
--init-pypkg
"${tvm_ffi_arg__STUB_PKG}"
--init-prefix
"${tvm_ffi_arg__STUB_PREFIX}"
)
endif ()
add_custom_command(
TARGET ${target}
POST_BUILD
COMMAND ${Python3_EXECUTABLE} -m tvm_ffi.stub.cli ${tvm_ffi_stub_cli_args}
COMMENT
"[COMMAND] Running: ${Python3_EXECUTABLE} -m tvm_ffi.stub.cli ${tvm_ffi_stub_cli_args}"
VERBATIM
)
endif ()
endforeach ()
endif ()
endfunction ()

Expand Down
17 changes: 17 additions & 0 deletions docs/guides/rust_lang_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,22 @@ fn may_fail(value: i32) -> Result<()> {
}
```

### Generating Bindings with stubgen

For registered C++ classes, `tvm-ffi-stubgen --target rust` generates typed Rust
bindings whose memory layout mirrors C++ exactly, so you can construct and call
objects without hand-written glue:

```rust
// generated for a C++ class `my_ext.IntPair` (fields `a`/`b`, defaulted `scale`, method `sum`)
let mut pair = IntPair::ffi_new().a(1).b(2).build()?; // builder; `scale` defaults to 1
println!("sum = {}", pair.sum()?); // call a C++ method
pair.a = 10; // write a field through DerefMut
```

See {ref}`sec-stubgen-rust` for the full reference and the runnable
[`examples/rust_stubgen`](https://github.com/apache/tvm-ffi/tree/main/examples/rust_stubgen).

## Examples

The repository includes a complete example in `rust/tvm-ffi/examples/load_library.rs`.
Expand Down Expand Up @@ -213,5 +229,6 @@ For detailed API documentation, see the [Rust API Reference](../reference/rust/i
## Related Resources

- [Quick Start Guide](../get_started/quickstart.rst) - General TVM FFI introduction
- [Stub Generation](../packaging/stubgen.rst) - Full `tvm-ffi-stubgen` reference (Python and Rust targets)
- [C++ Guide](./cpp_lang_guide.md) - C++ API usage
- [Python Guide](./python_lang_guide.md) - Python API usage
9 changes: 9 additions & 0 deletions docs/packaging/cpp_tooling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ and optionally runs :ref:`stub generation <sec-stubgen>` as a post-build step.
[STUB_DIR <dir> ]
[STUB_PKG <pkg> ]
[STUB_PREFIX <prefix>]
[STUB_TARGET python rust] # one or more backends
# When STUB_TARGET lists multiple backends, use these instead of STUB_DIR:
[STUB_DIR_PYTHON <dir>] [STUB_DIR_RUST <dir>]
)

:LINK_SHARED: (default: ON) Link against the TVM-FFI shared library
Expand All @@ -115,6 +118,12 @@ and optionally runs :ref:`stub generation <sec-stubgen>` as a post-build step.
and ``STUB_INIT=ON``.
:STUB_PREFIX: (default: "") Module prefix passed to the stub generator. Requires
``STUB_DIR`` and ``STUB_INIT=ON``.
:STUB_TARGET: (default: ``python``) Code generator backend(s): a list of one or more of
``python`` and ``rust`` (e.g. ``STUB_TARGET python rust``). When more than one backend is
listed, stubs are generated for each; because each backend writes a different file tree,
give the output directories per backend via ``STUB_DIR_PYTHON`` / ``STUB_DIR_RUST``
(instead of ``STUB_DIR``). The shared ``STUB_PKG`` / ``STUB_PREFIX`` apply to every
listed backend.

See :ref:`sec-stubgen-cmake` for a detailed explanation of each ``STUB_*`` option
and the generation modes they control.
Expand Down
Loading
Loading