Skip to content

Commit ccc805f

Browse files
authored
Add helpers for compile-time operator properties (#1114)
* Add helpers for compile-time operator properties For some operators, we would like to extend or alter the default behavior of the operator without modifying the operator's base API, such as by adding new arguments or template parameters. Further, for modifications known at compile time, we would prefer to avoid increasing the size of the operators by storing additional metadata. This PR adds helpers to allow the addition of compile-time properties to operators. As an initial use case, the channelize_poly operator now supports the following two properties: - PropAccum: Sets the accumulator type of the operator. This can be used to perform fp64 accumulation with fp32 inputs. - PropOutput: Directly set the output type (::value_type) of the operator. We typically deduce output types from the inputs and the context. For example, with a simple (a = b).run() usage, the type of the output a is known in the operator's Exec function. However, if we consider (a = b + c).run(), then if b is a transform, we will need to write its intermediate results to a temporary object and the type of that object will now depend on b's inputs. Using PropOutput, we can explicitly set the type of b's output, even for intermediates. Currently, only the channelize_poly operator has been extended to support properties. An example usage is as follows, from the ChannelizePoly.cu unit tests: ``` auto chan_poly = channelize_poly(ac32, f32, num_channels, decimation_factor) .props<PropAccum<double>, PropOutput<cuda::std::complex<double>>>(); ``` Alternatively, the properties can be chained: ``` auto chan_poly = channelize_poly(ac32, f32, num_channels, decimation_factor) .props<PropAccum<double>>() .props<PropOutput<cuda::std::complex<double>>>(); Above, we modify chan_poly to use fp64 accumulators and to have a double-precision complex output type. With the single-precision inputs, the output type written to temporary intermediates would have otherwise been single precision. Signed-off-by: Thomas Benson <tbenson@nvidia.com> * Move props support functions to matx::detail Also fix other issues highlighted in PR feedback * Address several PR comments Signed-off-by: Thomas Benson <tbenson@nvidia.com> * Add count property to prevent duplicate properties Prevent duplicate properties like PropAccum<float> and PropAccum<double>. That will now result in a static assertion failure. Signed-off-by: Thomas Benson <tbenson@nvidia.com> * Fix channelize_poly copyright header Signed-off-by: Thomas Benson <tbenson@nvidia.com> --------- Signed-off-by: Thomas Benson <tbenson@nvidia.com>
1 parent 7def4cc commit ccc805f

File tree

9 files changed

+691
-92
lines changed

9 files changed

+691
-92
lines changed

docs_input/Doxyfile.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2076,7 +2076,7 @@ SEARCH_INCLUDES = YES
20762076
# preprocessor.
20772077
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
20782078

2079-
INCLUDE_PATH =
2079+
INCLUDE_PATH = @PROJECT_SOURCE_DIR@/include
20802080

20812081
# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
20822082
# patterns (like *.h and *.hpp) to filter out the header-files in the

docs_input/api/signalimage/filtering/channelize_poly.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@ Examples
1616
:end-before: example-end channelize_poly-test-1
1717
:dedent:
1818

19+
.. literalinclude:: ../../../../test/00_transform/ChannelizePoly.cu
20+
:language: cpp
21+
:start-after: example-begin channelize_poly-test-2
22+
:end-before: example-end channelize_poly-test-2
23+
:dedent:
24+

include/matx/core/props.h

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
////////////////////////////////////////////////////////////////////////////////
2+
// BSD 3-Clause License
3+
//
4+
// Copyright (c) 2026, NVIDIA Corporation
5+
// All rights reserved.
6+
//
7+
// Redistribution and use in source and binary forms, with or without
8+
// modification, are permitted provided that the following conditions are met:
9+
//
10+
// 1. Redistributions of source code must retain the above copyright notice, this
11+
// list of conditions and the following disclaimer.
12+
//
13+
// 2. Redistributions in binary form must reproduce the above copyright notice,
14+
// this list of conditions and the following disclaimer in the documentation
15+
// and/or other materials provided with the distribution.
16+
//
17+
// 3. Neither the name of the copyright holder nor the names of its
18+
// contributors may be used to endorse or promote products derived from
19+
// this software without specific prior written permission.
20+
//
21+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24+
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25+
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26+
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27+
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28+
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29+
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
/////////////////////////////////////////////////////////////////////////////////
32+
33+
#pragma once
34+
35+
#include "matx/core/type_utils.h"
36+
#include <type_traits>
37+
#include <utility>
38+
39+
namespace matx
40+
{
41+
namespace detail
42+
{
43+
44+
template <typename... Ts>
45+
struct type_list {};
46+
47+
// Check if a type is contained in a type_list
48+
template <typename T, typename List>
49+
struct contains;
50+
51+
template <typename T>
52+
struct contains<T, type_list<>> : cuda::std::false_type {};
53+
54+
template <typename T, typename Head, typename... Tail>
55+
struct contains<T, type_list<Head, Tail...>>
56+
: cuda::std::conditional_t<cuda::std::is_same_v<T, Head>, cuda::std::true_type, contains<T, type_list<Tail...>>> {};
57+
58+
// Helper to append a type if not already in the list
59+
template <typename List, typename T>
60+
struct append_unique;
61+
62+
template <typename... Ts, typename T>
63+
struct append_unique<type_list<Ts...>, T> {
64+
using type = cuda::std::conditional_t<
65+
contains<T, type_list<Ts...>>::value,
66+
type_list<Ts...>, // already present -> keep list as is
67+
type_list<Ts..., T> // otherwise append
68+
>;
69+
};
70+
71+
// Recursively merge multiple types into a list
72+
template <typename List, typename... NewTs>
73+
struct merge_props_unique;
74+
75+
template <typename List>
76+
struct merge_props_unique<List> { using type = List; };
77+
78+
template <typename List, typename Head, typename... Tail>
79+
struct merge_props_unique<List, Head, Tail...> {
80+
using type = typename merge_props_unique<
81+
typename append_unique<List, Head>::type,
82+
Tail...
83+
>::type;
84+
};
85+
86+
// Tag property
87+
template <typename T>
88+
struct prop_tag {};
89+
90+
// Template category
91+
template <template <typename> class C>
92+
struct prop_category {};
93+
94+
template <typename Spec, typename... Props>
95+
struct has_property;
96+
97+
template <typename Tag, typename... Props>
98+
inline constexpr bool has_property_tag = (cuda::std::is_same_v<Tag, Props> || ...);
99+
100+
template <template <typename> class C, typename T>
101+
struct is_property_category : cuda::std::false_type {};
102+
103+
template <template <typename> class C, typename T>
104+
struct is_property_category<C, C<T>> : cuda::std::true_type {};
105+
106+
template <template <typename> class C, typename... Props>
107+
inline constexpr bool has_property_category = (is_property_category<C, Props>::value || ...);
108+
109+
template <template <typename> class Prop, typename... Props>
110+
struct count_property;
111+
112+
template <template <typename> class Prop>
113+
struct count_property<Prop> : cuda::std::integral_constant<int, 0> {};
114+
115+
template <template <typename> class Prop,
116+
typename T,
117+
typename... Tail>
118+
struct count_property<Prop, Prop<T>, Tail...>
119+
: cuda::std::integral_constant<
120+
int,
121+
1 + count_property<Prop, Tail...>::value> {};
122+
123+
template <template <typename> class Prop,
124+
typename Head,
125+
typename... Tail>
126+
struct count_property<Prop, Head, Tail...>
127+
: count_property<Prop, Tail...> {};
128+
129+
// The get_property_or helpers are used to extract a type from a category-style
130+
// property (i.e., a templated property) or return a default type if the property
131+
// is not found. Using the PropAccum property as an example, we can extract the accumulator
132+
// type or use the default output_t as follows:
133+
// using accum_type = get_property_or<PropAccum, output_t, CurrentProps...>::type;
134+
135+
// Base case: no properties, use the default type
136+
template <template <typename> class Prop, typename Default, typename... Props>
137+
struct get_property_or {
138+
using type = Default;
139+
};
140+
141+
// Case 1: Head matches Prop<T>
142+
template <template <typename> class Prop,
143+
typename Default,
144+
typename T,
145+
typename... Tail>
146+
struct get_property_or<Prop, Default, Prop<T>, Tail...> {
147+
static_assert(
148+
count_property<Prop, Tail...>::value == 0,
149+
"Duplicate property detected"
150+
);
151+
152+
using type = T;
153+
};
154+
155+
// Case 2: Head is something else
156+
template <template <typename> class Prop,
157+
typename Default,
158+
typename Head,
159+
typename... Tail>
160+
struct get_property_or<Prop, Default, Head, Tail...> {
161+
using type = typename get_property_or<Prop, Default, Tail...>::type;
162+
};
163+
164+
} // namespace detail
165+
166+
// Below are common properties meant to be reused by multiple operators. If a property only
167+
// makes sense for a single operator (such as a tag specificying a specific algorithm), then
168+
// the property should be defined directly in the operator.
169+
170+
// PropAccum is a property to set the type of an accumulator. This should be used for operations
171+
// where a single accumulation type is sufficient. For operations where this type would be
172+
// ambiguous (e.g., those requiring multiple accumulators), more fine-grained properties
173+
// could be added.
174+
template <typename T>
175+
struct PropAccum { using type = T; };
176+
177+
// PropOutput is a property to set the output type of an operator. By default, output types of
178+
// operators are deduced at compile time based on the input types and other operator properties.
179+
// The PropOutput property can be used to override the default output type. This can be useful,
180+
// for example, when an operator has single-precision inputs, but the user wishes to override
181+
// the accumulator type to double precision and avoid any implicit conversions to single
182+
// precision when writing the outputs.
183+
template <typename T>
184+
struct PropOutput { using type = T; };
185+
186+
} // namespace matx

0 commit comments

Comments
 (0)