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 41e9b7ee [FEAT][Rust] Read function metadata and doc strings from a 
Module (#833)
41e9b7ee is described below

commit 41e9b7eec7a424e382bd260385836b200f9de01f
Author: Gabriel Wu <[email protected]>
AuthorDate: Tue Oct 6 22:10:46 2026 +0800

    [FEAT][Rust] Read function metadata and doc strings from a Module (#833)
    
    ## Why
    
    C++ `ModuleObj` and Python `Module` can tell whether a module implements
    a function. They can also read the metadata and the doc string that a
    library exports for it (`__tvm_ffi__metadata_<name>` and
    `__tvm_ffi__doc_<name>`), and optionally look in the modules it imports.
    The Rust `Module` can only load a library and get a function, so a Rust
    host cannot check a library's exports or read their type schemas.
    
    ## What
    
    - `Module::implements_function(name, query_imports) -> Result<bool>`
    - `Module::get_function_metadata(name, query_imports) ->
    Result<Option<String>>` returns the JSON string the library wrote, such
    as `{"type_schema":"..."}`.
    - `Module::get_function_doc(name, query_imports) ->
    Result<Option<String>>`
    
    They call `ffi.ModuleImplementsFunction`,
    `ffi.ModuleGetFunctionMetadata` and `ffi.ModuleGetFunctionDoc`, the same
    functions that Python calls. As in the C++ methods, `query_imports`
    controls the search of imported modules. `None` means that the library
    exports no metadata or doc string for the function. The metadata stays a
    JSON string, since the crate has no JSON dependency.
    
    The Rust guide documents the accessors.
---
 docs/guides/rust_lang_guide.md    |  15 +++++
 rust/tvm-ffi/src/extra/module.rs  |  67 +++++++++++++++++++
 rust/tvm-ffi/tests/test_module.rs | 135 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 217 insertions(+)

diff --git a/docs/guides/rust_lang_guide.md b/docs/guides/rust_lang_guide.md
index 9c869121..02171630 100644
--- a/docs/guides/rust_lang_guide.md
+++ b/docs/guides/rust_lang_guide.md
@@ -90,6 +90,21 @@ fn main() -> Result<()> {
 }
 ```
 
+A module also reports whether it implements a function, and reads the
+metadata and doc string a function was exported with, as
+`Module.implements_function`, `Module.get_function_metadata` and
+`Module.get_function_doc` do in Python. The second argument says whether to
+also look in the modules it imports:
+
+```rust
+let module = Module::load_from_file("build/add_one_cpu.so")?;
+if module.implements_function("add_one_cpu", false)? {
+    // A JSON object such as {"type_schema": "..."}, if it was exported
+    let metadata = module.get_function_metadata("add_one_cpu", false)?;
+    let doc = module.get_function_doc("add_one_cpu", false)?;
+}
+```
+
 ### Working with Tensors
 
 Create and manipulate tensors:
diff --git a/rust/tvm-ffi/src/extra/module.rs b/rust/tvm-ffi/src/extra/module.rs
index 84cc4780..f4296747 100644
--- a/rust/tvm-ffi/src/extra/module.rs
+++ b/rust/tvm-ffi/src/extra/module.rs
@@ -20,6 +20,7 @@ use crate::derive::{Object, ObjectRef};
 use crate::error::Result;
 use crate::function::Function;
 use crate::object::{Object, ObjectArc};
+use crate::string::String;
 use tvm_ffi_sys::TVMFFITypeIndex as TypeIndex;
 
 //-----------------------------------------------------
@@ -71,4 +72,70 @@ impl Module {
             .call_tuple_with_len::<3, _>((self, name, true))?
             .try_into()
     }
+
+    /// Check whether the module implements a function.
+    ///
+    /// # Arguments
+    /// * `name` - The name of the function
+    /// * `query_imports` - Whether to also check the modules this module 
imports
+    ///
+    /// # Returns
+    /// * `Result<bool>` - Whether the function exists
+    pub fn implements_function<Str: AsRef<str>>(
+        &self,
+        name: Str,
+        query_imports: bool,
+    ) -> Result<bool> {
+        let name = String::from(name);
+        crate::cached_global_func!("ffi.ModuleImplementsFunction")
+            .call_tuple_with_len::<3, _>((self, name, query_imports))?
+            .try_into()
+    }
+
+    /// Get the metadata of a function the module exports.
+    ///
+    /// The metadata is a JSON object whose `type_schema` entry is the
+    /// function's type schema, as C++ `TVM_FFI_DLL_EXPORT_TYPED_FUNC` exports
+    /// it with `TVM_FFI_DLL_EXPORT_INCLUDE_METADATA`.
+    ///
+    /// # Arguments
+    /// * `name` - The name of the function
+    /// * `query_imports` - Whether to also check the modules this module 
imports
+    ///
+    /// # Returns
+    /// * `Result<Option<String>>` - The metadata as a JSON string, or `None`
+    ///   if the function has no metadata
+    pub fn get_function_metadata<Str: AsRef<str>>(
+        &self,
+        name: Str,
+        query_imports: bool,
+    ) -> Result<Option<String>> {
+        let name = String::from(name);
+        crate::cached_global_func!("ffi.ModuleGetFunctionMetadata")
+            .call_tuple_with_len::<3, _>((self, name, query_imports))?
+            .try_into()
+    }
+
+    /// Get the doc string of a function the module exports.
+    ///
+    /// The doc string is the one C++ `TVM_FFI_DLL_EXPORT_TYPED_FUNC_DOC`
+    /// exports.
+    ///
+    /// # Arguments
+    /// * `name` - The name of the function
+    /// * `query_imports` - Whether to also check the modules this module 
imports
+    ///
+    /// # Returns
+    /// * `Result<Option<String>>` - The doc string, or `None` if the function
+    ///   has none
+    pub fn get_function_doc<Str: AsRef<str>>(
+        &self,
+        name: Str,
+        query_imports: bool,
+    ) -> Result<Option<String>> {
+        let name = String::from(name);
+        crate::cached_global_func!("ffi.ModuleGetFunctionDoc")
+            .call_tuple_with_len::<3, _>((self, name, query_imports))?
+            .try_into()
+    }
 }
diff --git a/rust/tvm-ffi/tests/test_module.rs 
b/rust/tvm-ffi/tests/test_module.rs
new file mode 100644
index 00000000..9cf44962
--- /dev/null
+++ b/rust/tvm-ffi/tests/test_module.rs
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+//! Module accessors read the functions, metadata and doc strings that
+//! libtvm_ffi_testing exports with `TVM_FFI_DLL_EXPORT_TYPED_FUNC` and
+//! `TVM_FFI_DLL_EXPORT_TYPED_FUNC_DOC`.
+
+use std::env::consts::{DLL_PREFIX, DLL_SUFFIX};
+
+use tvm_ffi::tvm_ffi_sys::{TVMFFIAny, TVMFFISafeCallType};
+use tvm_ffi::*;
+
+// The metadata and doc getters libtvm_ffi_testing exports, read directly.
+extern "C" {
+    fn __tvm_ffi__metadata_testing_dll_schema_id_int(
+        handle: *mut std::ffi::c_void,
+        args: *const TVMFFIAny,
+        num_args: i32,
+        result: *mut TVMFFIAny,
+    ) -> i32;
+    fn __tvm_ffi__doc_testing_dll_test_add_with_docstring(
+        handle: *mut std::ffi::c_void,
+        args: *const TVMFFIAny,
+        num_args: i32,
+        result: *mut TVMFFIAny,
+    ) -> i32;
+}
+
+const SCHEMA_ID_INT: &str = "testing_dll_schema_id_int";
+const ADD_WITH_DOCSTRING: &str = "testing_dll_test_add_with_docstring";
+const MISSING: &str = "testing_dll_missing";
+
+/// libtvm_ffi_testing, which the test binary links, so that the loader finds
+/// it by name.
+fn testing_module() -> Module {
+    
Module::load_from_file(format!("{DLL_PREFIX}tvm_ffi_testing{DLL_SUFFIX}")).unwrap()
+}
+
+/// A module without functions of its own that imports libtvm_ffi_testing.
+fn importing_module() -> Module {
+    let module: Module = Function::get_global("ffi.SystemLib")
+        .unwrap()
+        .call_tuple_with_len::<1, 
_>((String::from("testing.rust.test_module."),))
+        .unwrap()
+        .try_into()
+        .unwrap();
+    Function::get_global("ffi.ModuleImportModule")
+        .unwrap()
+        .call_tuple_with_len::<2, _>((&module, &testing_module()))
+        .unwrap();
+    module
+}
+
+/// Calls an exported metadata or doc getter.
+fn call_getter(getter: TVMFFISafeCallType) -> String {
+    // SAFETY: the getters take no arguments and do not use the handle.
+    let getter = unsafe { Function::from_extern_c(std::ptr::null_mut(), 
getter, None) };
+    getter.call_packed(&[]).unwrap().try_into().unwrap()
+}
+
+#[test]
+fn test_module_implements_function() {
+    let module = testing_module();
+    assert!(module.implements_function(SCHEMA_ID_INT, false).unwrap());
+    assert!(!module.implements_function(MISSING, false).unwrap());
+}
+
+#[test]
+fn test_module_get_function_metadata() {
+    let module = testing_module();
+    let metadata = module
+        .get_function_metadata(SCHEMA_ID_INT, false)
+        .unwrap()
+        .unwrap();
+    let exported = call_getter(__tvm_ffi__metadata_testing_dll_schema_id_int);
+    assert_eq!(metadata.as_str(), exported.as_str());
+    assert!(module
+        .get_function_metadata(MISSING, false)
+        .unwrap()
+        .is_none());
+}
+
+#[test]
+fn test_module_get_function_doc() {
+    let module = testing_module();
+    let doc = module
+        .get_function_doc(ADD_WITH_DOCSTRING, false)
+        .unwrap()
+        .unwrap();
+    let exported = 
call_getter(__tvm_ffi__doc_testing_dll_test_add_with_docstring);
+    assert_eq!(doc.as_str(), exported.as_str());
+    // A function exported without a doc string has none.
+    assert!(module
+        .get_function_doc(SCHEMA_ID_INT, false)
+        .unwrap()
+        .is_none());
+}
+
+#[test]
+fn test_module_accessors_query_imports() {
+    let module = importing_module();
+    assert!(!module.implements_function(SCHEMA_ID_INT, false).unwrap());
+    assert!(module.implements_function(SCHEMA_ID_INT, true).unwrap());
+    assert!(module
+        .get_function_metadata(SCHEMA_ID_INT, false)
+        .unwrap()
+        .is_none());
+    assert!(module
+        .get_function_metadata(SCHEMA_ID_INT, true)
+        .unwrap()
+        .is_some());
+    assert!(module
+        .get_function_doc(ADD_WITH_DOCSTRING, false)
+        .unwrap()
+        .is_none());
+    assert!(module
+        .get_function_doc(ADD_WITH_DOCSTRING, true)
+        .unwrap()
+        .is_some());
+}

Reply via email to