blob: 6cf717b9b0cb8d788d0e86e0ce1f1d284db4e5b3 [file]
# 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.
import pathlib
import numpy
import pytest
import tvm_ffi
import tvm_ffi.cpp
from tvm_ffi.core import TypeSchema
from tvm_ffi.module import Module
def test_build_cpp() -> None:
cpp_path = pathlib.Path(__file__).parent.resolve() / "test_build.cc"
output_lib_path = tvm_ffi.cpp.build(
name="hello",
sources=[str(cpp_path)],
)
mod: Module = tvm_ffi.load_module(output_lib_path)
metadata = mod.get_function_metadata("add_one_cpu")
assert metadata is not None, "add_one_cpu should have metadata"
assert "type_schema" in metadata, f"{'add_one_cpu'}: {metadata}"
schema = TypeSchema.from_json_str(metadata["type_schema"])
assert str(schema) == "Callable[[Tensor, Tensor], None]", f"{'add_one_cpu'}: {schema}"
doc = mod.get_function_doc("add_one_cpu")
assert doc is None
x = numpy.array([1, 2, 3, 4, 5], dtype=numpy.float32)
y = numpy.empty_like(x)
mod.add_one_cpu(x, y)
numpy.testing.assert_equal(x + 1, y)
def test_build_inline_with_metadata() -> None: # noqa: PLR0915
"""Test functions with various input and output types."""
# Keep module alive until all returned objects are destroyed
mod: Module = tvm_ffi.cpp.load_inline(
name="test_io_types",
cpp_sources=r"""
// int input -> int output
int square(int x) {
return x * x;
}
// float input -> float output
float reciprocal(float x) {
return 1.0f / x;
}
// bool input -> bool output
bool negate(bool x) {
return !x;
}
// String input -> String output
tvm::ffi::String uppercase_first(tvm::ffi::String s) {
std::string result(s.c_str());
if (!result.empty()) {
result[0] = std::toupper(result[0]);
}
return tvm::ffi::String(result);
}
// Multiple inputs: int, float -> float
float weighted_sum(int count, float weight) {
return static_cast<float>(count) * weight;
}
// Multiple inputs: String, int -> String
tvm::ffi::String repeat_string(tvm::ffi::String s, int times) {
std::string result;
for (int i = 0; i < times; ++i) {
result += s.c_str();
}
return tvm::ffi::String(result);
}
// Mixed types: bool, int, float, String -> String
tvm::ffi::String format_data(bool flag, int count, float value, tvm::ffi::String label) {
std::ostringstream oss;
oss << label.c_str() << ": flag=" << (flag ? "true" : "false")
<< ", count=" << count << ", value=" << value;
return tvm::ffi::String(oss.str());
}
// Tensor input/output
void double_tensor(tvm::ffi::TensorView input, tvm::ffi::TensorView output) {
TVM_FFI_ICHECK(input.ndim() == 1);
TVM_FFI_ICHECK(output.ndim() == 1);
TVM_FFI_ICHECK(input.size(0) == output.size(0));
DLDataType f32_dtype{kDLFloat, 32, 1};
TVM_FFI_ICHECK(input.dtype() == f32_dtype);
TVM_FFI_ICHECK(output.dtype() == f32_dtype);
for (int i = 0; i < input.size(0); ++i) {
static_cast<float*>(output.data_ptr())[i] =
static_cast<const float*>(input.data_ptr())[i] * 2.0f;
}
}
""",
functions=[
"square",
"reciprocal",
"negate",
"uppercase_first",
"weighted_sum",
"repeat_string",
"format_data",
"double_tensor",
],
extra_cflags=["-DTVM_FFI_DLL_EXPORT_INCLUDE_METADATA=1"],
)
# Test square: int -> int
assert mod.square(5) == 25
metadata = mod.get_function_metadata("square")
assert metadata is not None
schema = TypeSchema.from_json_str(metadata["type_schema"])
assert str(schema) == "Callable[[int], int]"
# Test reciprocal: float -> float
result = mod.reciprocal(2.0)
assert abs(result - 0.5) < 0.001
metadata = mod.get_function_metadata("reciprocal")
assert metadata is not None
schema = TypeSchema.from_json_str(metadata["type_schema"])
assert str(schema) == "Callable[[float], float]"
# Test negate: bool -> bool
assert mod.negate(True) is False
assert mod.negate(False) is True
metadata = mod.get_function_metadata("negate")
assert metadata is not None
schema = TypeSchema.from_json_str(metadata["type_schema"])
assert str(schema) == "Callable[[bool], bool]"
# Test uppercase_first: String -> String
result = mod.uppercase_first("hello")
assert result == "Hello"
metadata = mod.get_function_metadata("uppercase_first")
assert metadata is not None
schema = TypeSchema.from_json_str(metadata["type_schema"])
assert str(schema) == "Callable[[str], str]"
# Test weighted_sum: int, float -> float
result = mod.weighted_sum(10, 2.5)
assert abs(result - 25.0) < 0.001
metadata = mod.get_function_metadata("weighted_sum")
assert metadata is not None
schema = TypeSchema.from_json_str(metadata["type_schema"])
assert str(schema) == "Callable[[int, float], float]"
# Test repeat_string: String, int -> String
result = mod.repeat_string("ab", 3)
assert result == "ababab"
metadata = mod.get_function_metadata("repeat_string")
assert metadata is not None
schema = TypeSchema.from_json_str(metadata["type_schema"])
assert str(schema) == "Callable[[str, int], str]"
# Test format_data: bool, int, float, String -> String
result = mod.format_data(True, 42, 3.14, "test")
assert "test:" in result
assert "flag=true" in result
assert "count=42" in result
assert "value=3.14" in result
metadata = mod.get_function_metadata("format_data")
assert metadata is not None
schema = TypeSchema.from_json_str(metadata["type_schema"])
assert str(schema) == "Callable[[bool, int, float, str], str]"
# Test double_tensor: Tensor, Tensor -> None
x = numpy.array([1.0, 2.0, 3.0], dtype=numpy.float32)
y = numpy.empty_like(x)
mod.double_tensor(x, y)
numpy.testing.assert_allclose(y, x * 2.0)
metadata = mod.get_function_metadata("double_tensor")
assert metadata is not None
schema = TypeSchema.from_json_str(metadata["type_schema"])
assert str(schema) == "Callable[[Tensor, Tensor], None]"
def test_build_inline_with_docstrings() -> None:
"""Test building functions with documentation using the functions dict."""
# Keep module alive until all returned objects are destroyed
add_docstring = (
"Add two integers and return the sum.\n"
"\n"
"Parameters\n"
"----------\n"
"a : int\n"
" First integer\n"
"b : int\n"
" Second integer\n"
"\n"
"Returns\n"
"-------\n"
"result : int\n"
" Sum of a and b"
)
divide_docstring = "Divides two floats. Returns a/b."
mod: Module = tvm_ffi.cpp.load_inline(
name="test_docs",
cpp_sources=r"""
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
float divide(float a, float b) {
TVM_FFI_ICHECK(b != 0.0f) << "Division by zero";
return a / b;
}
""",
functions={
"add": add_docstring,
"subtract": "", # No documentation
"divide": divide_docstring,
},
extra_cflags=["-DTVM_FFI_DLL_EXPORT_INCLUDE_METADATA=1"],
)
# Test add function with full documentation
assert mod.add(10, 5) == 15
metadata = mod.get_function_metadata("add")
assert metadata is not None
schema = TypeSchema.from_json_str(metadata["type_schema"])
assert str(schema) == "Callable[[int, int], int]"
doc = mod.get_function_doc("add")
assert doc is not None, "add should have documentation"
assert doc == add_docstring
# Test subtract function without documentation
assert mod.subtract(10, 5) == 5
metadata = mod.get_function_metadata("subtract")
assert metadata is not None
schema = TypeSchema.from_json_str(metadata["type_schema"])
assert str(schema) == "Callable[[int, int], int]"
doc = mod.get_function_doc("subtract")
assert doc is None, "subtract should not have documentation"
# Test divide function with short documentation
result = mod.divide(10.0, 2.0)
assert abs(result - 5.0) < 0.001
metadata = mod.get_function_metadata("divide")
assert metadata is not None
schema = TypeSchema.from_json_str(metadata["type_schema"])
assert str(schema) == "Callable[[float, float], float]"
doc = mod.get_function_doc("divide")
assert doc is not None, "divide should have documentation"
assert doc == divide_docstring
def test_build_without_metadata() -> None:
"""Test building without metadata export."""
mod: Module = tvm_ffi.cpp.load_inline(
name="test_no_meta",
cpp_sources=r"""
// Note: NOT defining TVM_FFI_DLL_EXPORT_INCLUDE_METADATA
int simple_add(int a, int b) {
return a + b;
}
""",
functions=["simple_add"],
)
# Function should still work
result = mod.simple_add(10, 20)
assert result == 30
# But metadata should not be available
metadata = mod.get_function_metadata("simple_add")
assert metadata is None, (
"Metadata should not be available without TVM_FFI_DLL_EXPORT_INCLUDE_METADATA"
)
# Doc should also not be available
doc = mod.get_function_doc("simple_add")
assert doc is None
if __name__ == "__main__":
pytest.main([__file__])