Skip to content

Commit 2f6e90a

Browse files
authored
Add some string conversion functions (#463)
* New string functions for slangpy types * Add more to_string functions * Clean up a bit and use full name * Fix duplicate include
1 parent c4a7ea8 commit 2f6e90a

File tree

14 files changed

+291
-10
lines changed

14 files changed

+291
-10
lines changed

.github/copilot-instructions.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,23 @@ Directory structure:
1212
- The python code is in #slangpy
1313
- The python tests are in #slangpy/tests
1414
- The c++ tests are in #tests
15+
- General utility scripts are in #tools
16+
- CI workflows are in #.github/workflows
17+
- Example code is in #examples, #samples/examples, #samples/experiments
18+
- Documentation is in #docs
1519

1620
Code structure:
1721
- Any new python api must have tests added in #slangpy/tests.
1822
- The project is mainly divided into the pure native code (sgl), the python extension (slangpy_ext) and the python code (slangpy).
1923
- The C++ code is responsible for the low-level graphics API interactions, and most types directly map to a slang-rhi counterpart. i.e. Device wraps the slang-rhi rhi::IDevice type
2024

2125
Building:
22-
- To build the project, run "cmake --build ./build --config Debug"
26+
- On windows, build using: 'cmake --build .\build\windows-msvc --config Debug'
27+
- On linux, build using: 'cmake --build ./build/linux-gcc --config Debug'
2328

2429
Testing:
2530
- Python tests are in #slangpy/tests and C++ tests are in #tests
26-
- The Python testing system uses pytest
31+
- The Python testing system uses PYTEST
2732
- The C++ testing system uses doctest
2833
- Always build before running tests.
2934
- To run all Python tests, run "pytest slangpy/tests"
@@ -41,6 +46,12 @@ Python code style:
4146
- Member variables start with "m_" and are in snake_case.
4247
- All arguments should have type annotations.
4348

49+
CI
50+
- Our CI system uses github, and the main ci job is in #.github/workflows/ci.yml
51+
- It works by calling #tools/ci.py multiple times with different arguments to perform different steps
52+
- For example, "python tools/ci.py configure" runs the cmake configure process in ci
53+
- For all ci commands, run "python tools/ci.py --help"
54+
4455
Additional tools:
4556
- Once a task is complete, to fix any formatting errors, run "pre-commit run --all-files".
4657
- If changes are made, pre-commit will modify the files in place and return an error. Re-running the command should then succeed.

slangpy/core/module.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,9 @@ def __getitem__(self, name: str):
246246
with the specified item name (by calling __getattr__).
247247
"""
248248
return self.__getattr__(name)
249+
250+
def __repr__(self) -> str:
251+
"""
252+
Return a string representation of the Module for debugging.
253+
"""
254+
return f"Module(name='{self.device_module.name}', linked_modules={len(self.link)})"

slangpy/reflection/reflectiontypes.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,12 @@ def make_field(
329329
self._cached_fields = {name: make_field(name, value) for name, value in fields.items()}
330330
return self._cached_fields
331331

332+
def __repr__(self) -> str:
333+
"""
334+
Return a detailed string representation of the SlangType for debugging.
335+
"""
336+
return f"SlangType(name='{self.full_name}', kind={self.type_reflection.kind}, shape={self.shape})"
337+
332338

333339
class VoidType(SlangType):
334340
"""
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2+
3+
import pytest
4+
from slangpy import DeviceType, NDBuffer
5+
from slangpy.types import Tensor
6+
from slangpy.testing import helpers
7+
8+
MODULE = r"""
9+
struct Foo {
10+
int x;
11+
}
12+
"""
13+
14+
15+
@pytest.mark.parametrize("device_type", helpers.DEFAULT_DEVICE_TYPES)
16+
def test_buffer_to_string(device_type: DeviceType):
17+
device = helpers.get_device(device_type)
18+
buffer = NDBuffer.zeros(device, shape=(10, 20), dtype="float")
19+
20+
# Test that repr() returns a meaningful string
21+
repr_str = repr(buffer)
22+
print(f"NDBuffer: {repr_str}")
23+
24+
# Verify the repr contains expected information
25+
assert "NativeNDBuffer" in repr_str
26+
assert "shape" in repr_str
27+
assert "strides" in repr_str
28+
assert "dtype" in repr_str
29+
30+
31+
@pytest.mark.parametrize("device_type", helpers.DEFAULT_DEVICE_TYPES)
32+
def test_slangtype_struct_to_string(device_type: DeviceType):
33+
device = helpers.get_device(device_type)
34+
module = helpers.create_module(device, MODULE)
35+
foo_struct = module["Foo"].as_struct()
36+
37+
# Access the underlying SlangType
38+
foo_type = foo_struct.struct
39+
40+
# Test that repr() returns a meaningful string
41+
repr_str = repr(foo_type)
42+
print(f"SlangType: {repr_str}")
43+
44+
# Verify the repr contains expected information
45+
assert "SlangType" in repr_str
46+
assert "name" in repr_str
47+
assert "Foo" in repr_str
48+
assert "shape" in repr_str
49+
50+
51+
@pytest.mark.parametrize("device_type", helpers.DEFAULT_DEVICE_TYPES)
52+
def test_slangtype_vector_to_string(device_type: DeviceType):
53+
device = helpers.get_device(device_type)
54+
module = helpers.create_module(device, MODULE)
55+
foo_struct = module["float3"].as_struct()
56+
57+
# Access the underlying SlangType
58+
foo_type = foo_struct.struct
59+
60+
# Test that repr() returns a meaningful string
61+
repr_str = repr(foo_type)
62+
print(f"SlangType: {repr_str}")
63+
64+
# Verify the repr contains expected information
65+
assert "SlangType" in repr_str
66+
assert "name" in repr_str
67+
assert "vector<float,3>" in repr_str
68+
assert "shape" in repr_str
69+
70+
71+
@pytest.mark.parametrize("device_type", helpers.DEFAULT_DEVICE_TYPES)
72+
def test_tensor_to_string(device_type: DeviceType):
73+
device = helpers.get_device(device_type)
74+
75+
# Create a tensor without gradients
76+
tensor = Tensor.empty(device, dtype="float", shape=(3, 4))
77+
78+
# Test that repr() returns a meaningful string - Tensor inherits from NativeTensor
79+
repr_str = repr(tensor)
80+
print(f"Tensor: {repr_str}")
81+
82+
# Verify the repr contains expected information
83+
assert "NativeTensor" in repr_str
84+
assert "dtype" in repr_str
85+
assert "shape" in repr_str
86+
assert "has_grad_in=false" in repr_str
87+
assert "has_grad_out=false" in repr_str
88+
89+
# Test with gradients
90+
grad_in = Tensor.zeros(device, dtype="float", shape=(3, 4))
91+
grad_out = Tensor.zeros(device, dtype="float", shape=(3, 4))
92+
tensor_with_grads = tensor.with_grads(grad_in, grad_out)
93+
94+
repr_str_grads = repr(tensor_with_grads)
95+
print(f"Tensor with grads: {repr_str_grads}")
96+
assert "has_grad_in=true" in repr_str_grads
97+
assert "has_grad_out=true" in repr_str_grads
98+
99+
100+
@pytest.mark.parametrize("device_type", helpers.DEFAULT_DEVICE_TYPES)
101+
def test_module_to_string(device_type: DeviceType):
102+
device = helpers.get_device(device_type)
103+
module = helpers.create_module(device, MODULE)
104+
105+
# Test that repr() returns a meaningful string for Module
106+
repr_str = repr(module)
107+
print(f"Module: {repr_str}")
108+
109+
# Verify the repr contains expected information
110+
assert "Module" in repr_str
111+
assert "name=" in repr_str
112+
assert "linked_modules=" in repr_str
113+
114+
115+
@pytest.mark.parametrize("device_type", helpers.DEFAULT_DEVICE_TYPES)
116+
def test_slangtype_reflection_to_string(device_type: DeviceType):
117+
device = helpers.get_device(device_type)
118+
module = helpers.create_module(device, MODULE)
119+
foo_struct = module["Foo"].as_struct()
120+
121+
# Access the SlangType reflection
122+
foo_type_reflection = foo_struct.struct
123+
124+
# Test that repr() returns a meaningful string for SlangType reflection
125+
repr_str = repr(foo_type_reflection)
126+
print(f"SlangType reflection: {repr_str}")
127+
128+
# Verify the repr contains expected information - should get the Python SlangType repr
129+
assert "SlangType" in repr_str
130+
assert "name=" in repr_str
131+
assert "shape=" in repr_str
132+
133+
134+
if __name__ == "__main__":
135+
pytest.main([__file__, "-v", "-s"])

src/slangpy_ext/utils/slangpy.cpp

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include "utils/slangpypackedarg.h"
2020
#include "utils/slangpyfunction.h"
2121

22+
#include <fmt/format.h>
23+
2224
namespace sgl {
2325
extern void write_shader_cursor(ShaderCursor& cursor, nb::object value);
2426
extern nb::ndarray<nb::numpy> buffer_to_numpy(Buffer* self);
@@ -30,6 +32,31 @@ buffer_to_torch(Buffer* self, DataType type, std::vector<size_t> shape, std::vec
3032

3133
namespace sgl::slangpy {
3234

35+
// Implementation of to_string methods
36+
std::string NativeSlangType::to_string() const
37+
{
38+
if (m_type_reflection) {
39+
return fmt::format(
40+
"NativeSlangType(\n"
41+
" name = \"{}\",\n"
42+
" shape = {},\n"
43+
" kind = {}\n"
44+
")",
45+
m_type_reflection->full_name(),
46+
m_shape.to_string(),
47+
m_type_reflection->kind()
48+
);
49+
} else {
50+
return fmt::format(
51+
"NativeSlangType(\n"
52+
" shape = {},\n"
53+
" type_reflection = None\n"
54+
")",
55+
m_shape.to_string()
56+
);
57+
}
58+
}
59+
3360
static constexpr std::array<char, 16> HEX_CHARS
3461
= {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
3562

@@ -1048,7 +1075,8 @@ SGL_PY_EXPORT(utils_slangpy)
10481075
.def("_py_has_derivative", &NativeSlangType::_py_has_derivative)
10491076
.def("_py_derivative", &NativeSlangType::_py_derivative)
10501077
.def("_py_uniform_type_layout", &NativeSlangType::_py_uniform_type_layout)
1051-
.def("_py_buffer_type_layout", &NativeSlangType::_py_buffer_type_layout);
1078+
.def("_py_buffer_type_layout", &NativeSlangType::_py_buffer_type_layout)
1079+
.def("__repr__", &NativeSlangType::to_string);
10521080

10531081
nb::class_<NativeMarshall, PyNativeMarshall, Object>(slangpy, "NativeMarshall") //
10541082
.def(

src/slangpy_ext/utils/slangpy.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ class NativeSlangType : public Object {
170170
/// Get the buffer type layout of the type (call into Python).
171171
ref<TypeLayoutReflection> buffer_type_layout() const { return _py_buffer_type_layout(); }
172172

173+
/// Get string representation of the type.
174+
std::string to_string() const;
175+
173176
/// Virtual accessors to give native system access to python defined reflection properties
174177
virtual ref<NativeSlangType> _py_element_type() const { return nullptr; }
175178
virtual bool _py_has_derivative() const { return false; }

src/slangpy_ext/utils/slangpybuffer.cpp

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
#include "utils/slangpybuffer.h"
1010

11+
#include <fmt/format.h>
12+
1113
namespace sgl {
1214

1315
extern void write_shader_cursor(ShaderCursor& cursor, nb::object value);
@@ -42,6 +44,26 @@ ref<NativeNDBuffer> NativeNDBuffer::index(nb::object index_arg) const
4244
return result;
4345
}
4446

47+
std::string NativeNDBuffer::to_string() const
48+
{
49+
return fmt::format(
50+
"NativeNDBuffer(\n"
51+
" shape = {},\n"
52+
" strides = {},\n"
53+
" offset = {},\n"
54+
" dtype = \"{}\",\n"
55+
" memory_type = {},\n"
56+
" usage = {}\n"
57+
")",
58+
shape().to_string(),
59+
strides().to_string(),
60+
offset(),
61+
m_desc.dtype->to_string(),
62+
static_cast<int>(m_desc.memory_type),
63+
static_cast<int>(m_desc.usage)
64+
);
65+
}
66+
4567
Shape NativeNDBufferMarshall::get_shape(nb::object data) const
4668
{
4769
auto buffer = nb::cast<NativeNDBuffer*>(data);
@@ -274,7 +296,8 @@ SGL_PY_EXPORT(utils_slangpy_buffer)
274296
.def(nb::init<ref<Device>, NativeNDBufferDesc, ref<Buffer>>(), "device"_a, "desc"_a, "buffer"_a = nullptr)
275297
.def("broadcast_to", &NativeNDBuffer::broadcast_to, "shape"_a)
276298
.def("view", &NativeNDBuffer::view, "shape"_a, "strides"_a = Shape(), "offset"_a = 0)
277-
.def("__getitem__", &NativeNDBuffer::index);
299+
.def("__getitem__", &NativeNDBuffer::index)
300+
.def("__repr__", &NativeNDBuffer::to_string);
278301

279302

280303
nb::class_<NativeNDBufferMarshall, NativeMarshall>(slangpy, "NativeNDBufferMarshall") //

src/slangpy_ext/utils/slangpybuffer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ class NativeNDBuffer : public StridedBufferView {
3434
ref<NativeNDBuffer> broadcast_to(const Shape& shape) const;
3535
ref<NativeNDBuffer> index(nb::object index_arg) const;
3636

37+
/// Get string representation of the NDBuffer.
38+
std::string to_string() const;
39+
3740
private:
3841
NativeNDBufferDesc m_desc;
3942
};

src/slangpy_ext/utils/slangpyfunction.cpp

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
22

33
#include "nanobind.h"
4-
5-
#include "sgl/device/device.h"
64
#include "sgl/device/command.h"
7-
85
#include "utils/slangpyfunction.h"
6+
#include <fmt/format.h>
97

108
namespace sgl {
119

@@ -100,6 +98,25 @@ void NativeFunctionNode::append_to(
10098
}
10199
}
102100

101+
std::string NativeFunctionNode::to_string() const
102+
{
103+
std::string data_type_name = "None";
104+
if (!m_data.is_none()) {
105+
data_type_name = nb::cast<std::string>(m_data.type().attr("__name__"));
106+
}
107+
108+
return fmt::format(
109+
"NativeFunctionNode(\n"
110+
" type = {},\n"
111+
" parent = {},\n"
112+
" data_type = \"{}\"\n"
113+
")",
114+
static_cast<int>(m_type),
115+
m_parent ? "present" : "None",
116+
data_type_name
117+
);
118+
}
119+
103120
} // namespace sgl::slangpy
104121

105122
SGL_PY_EXPORT(utils_slangpy_function)
@@ -163,5 +180,6 @@ SGL_PY_EXPORT(utils_slangpy_function)
163180
&NativeFunctionNode::gather_runtime_options,
164181
"options"_a,
165182
D_NA(NativeFunctionNode, gather_runtime_options)
166-
);
183+
)
184+
.def("__repr__", &NativeFunctionNode::to_string);
167185
}

src/slangpy_ext/utils/slangpyfunction.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ class NativeFunctionNode : NativeObject {
9898

9999
void append_to(NativeCallDataCache* cache, CommandEncoder* command_encoder, nb::args args, nb::kwargs kwargs);
100100

101+
/// Get string representation of the function node.
102+
std::string to_string() const;
103+
101104
virtual ref<NativeCallData> generate_call_data(nb::args args, nb::kwargs kwargs)
102105
{
103106
SGL_UNUSED(args);

0 commit comments

Comments
 (0)