• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

daisytuner / docc / 28806128926

06 Jul 2026 04:16PM UTC coverage: 62.96%. First build
28806128926

push

github

web-flow
New LibNodeExpansion pass (#740)

* Switched expansion "pipeline" to new LibNodeExpansionPass that can recursively expand using a LibNodeExpander impl.
- removed access to analysis_manager from expansion methods, as those would currently not be appropriately maintained between nodes
+ New expansion API handles more of the boilerplate code (checking for standalone, creating boundary access nodes, removing old elements)
* Also migrated sdfg-json-to-c.cpp to not using the expansion pipeline anymore
* toStr() for ReduceNodes and giving PyStructuredSDFG access to the output_dir for additional dumping
~ DotVisualizer : Fix on nested sequences.
+ DotVisualizer: visualize empty sequences
* DotVisualizer default-enabled show block/loop ids
 * while MathNodes still have an expand-method, the new infrastructure is based upon "Expander" classes. The MathNodeExpander just redirects to the method for now
 ~ Broadcast node still used old ptr-output semantics
 * updated tensor & blas node expand to new expand API, that handles more of the boilerplate code (checking, removing old nodes, creating standalone-replacement nodes)
 - removed Transpose Node. Was unused and on old ptr-semantics
 + StructuredSDFGBuilder.add_sequence_at, add_for_at, add_map_at
 * switched StructuredSDFGBuilder internally to use ptr of Assignments to express using default assignments when needed
 * updated tests to use the new expand_single_math_node helper function, instead of the method directly
 + pass and expand-single helper functions are ready to use other expanders
 + EinsumExpansionPass is basically the legacy ExpansionPass, only restricted to EinsumNodes. It still needs to run in a pipeline to ensure finding multiple EinsumNodes per block. This is temporary, because Einsum is the only node that already supported splitting a block, which the new expansion disallows, as it should handle it itself when needed (but that part is not yet implemented)

594 of 962 new or added lines in 31 files covered. (61.75%)

40554 of 64412 relevant lines covered (62.96%)

963.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.31
/sdfg/src/data_flow/library_nodes/math/tensor/reduce_node.cpp
1
#include "sdfg/data_flow/library_nodes/math/tensor/reduce_node.h"
2

3
#include "sdfg/analysis/analysis.h"
4
#include "sdfg/builder/structured_sdfg_builder.h"
5

6
#include <algorithm>
7

8
namespace sdfg {
9
namespace math {
10
namespace tensor {
11

12
ReduceNode::ReduceNode(
13
    size_t element_id,
14
    const DebugInfo& debug_info,
15
    const graph::Vertex vertex,
16
    data_flow::DataFlowGraph& parent,
17
    const data_flow::LibraryNodeCode& code,
18
    const std::vector<symbolic::Expression>& shape,
19
    const std::vector<int64_t>& axes,
20
    bool keepdims
21
)
22
    : TensorNode(element_id, debug_info, vertex, parent, code, {}, {"Y", "X"}, data_flow::ImplementationType_NONE),
34✔
23
      shape_(shape), axes_(axes), keepdims_(keepdims) {}
34✔
24

25
void ReduceNode::validate(const Function& function) const {
10✔
26
    TensorNode::validate(function);
10✔
27

28
    auto& graph = this->get_parent();
10✔
29

30
    auto* iedge = graph.in_edge_for_connector(*this, inputs_.at(1));
10✔
31
    auto& tensor_input = static_cast<const types::Tensor&>(iedge->base_type());
10✔
32
    validate_shape_matches(shape_, tensor_input.layout(), "input");
10✔
33

34
    // Calculate expected output shape based on axes and keepdims
35
    std::vector<int64_t> sorted_axes = axes_;
10✔
36
    // Normalize negative axes
37
    for (auto& axis : sorted_axes) {
11✔
38
        if (axis < 0) {
11✔
39
            axis = static_cast<int64_t>(shape_.size()) + axis;
1✔
40
        }
1✔
41
        // Validate axis is in bounds
42
        if (axis < 0 || axis >= static_cast<int64_t>(shape_.size())) {
11✔
43
            throw InvalidSDFGException(
×
44
                "Library Node: Axis value out of bounds. Axis: " + std::to_string(axis) +
×
45
                " Shape size: " + std::to_string(shape_.size())
×
46
            );
×
47
        }
×
48
    }
11✔
49
    std::sort(sorted_axes.begin(), sorted_axes.end());
10✔
50

51
    std::vector<symbolic::Expression> expected_output_shape;
10✔
52
    for (size_t i = 0; i < shape_.size(); ++i) {
27✔
53
        bool is_axis = false;
17✔
54
        for (auto axis : sorted_axes) {
19✔
55
            if (axis == (int64_t) i) {
19✔
56
                is_axis = true;
11✔
57
                break;
11✔
58
            }
11✔
59
        }
19✔
60

61
        if (is_axis) {
17✔
62
            if (keepdims_) {
11✔
63
                expected_output_shape.push_back(symbolic::one());
1✔
64
            }
1✔
65
        } else {
11✔
66
            expected_output_shape.push_back(shape_[i]);
6✔
67
        }
6✔
68
    }
17✔
69

70
    auto* iedge_result = graph.in_edge_for_connector(*this, inputs_.at(0));
10✔
71
    auto& tensor_output = static_cast<const types::Tensor&>(iedge_result->base_type());
10✔
72
    validate_shape_matches(expected_output_shape, tensor_output.layout(), "output");
10✔
73
}
10✔
74

75

76
symbolic::SymbolSet ReduceNode::symbols() const {
×
77
    symbolic::SymbolSet syms;
×
78
    for (const auto& dim : shape_) {
×
79
        for (auto& atom : symbolic::atoms(dim)) {
×
80
            syms.insert(atom);
×
81
        }
×
82
    }
×
83
    return syms;
×
84
}
×
85

86
void ReduceNode::replace(const symbolic::Expression old_expression, const symbolic::Expression new_expression) {
×
87
    for (auto& dim : shape_) {
×
88
        dim = symbolic::subs(dim, old_expression, new_expression);
×
89
    }
×
90
}
×
91

92
void ReduceNode::replace(const symbolic::ExpressionMapping& replacements) {
×
93
    for (auto& dim : shape_) {
×
94
        dim = symbolic::subs(dim, replacements);
×
95
    }
×
96
}
×
97

98
data_flow::PointerAccessType ReduceNode::pointer_access_type(int input_idx) const {
×
99
    if (input_idx == 0) {
×
100
        return data_flow::PointerAccessMeta::create_full_write_only(symbolic::__nullptr__(), true);
×
101
    } else if (input_idx == 1) {
×
102
        return data_flow::PointerAccessMeta::create_read_only(symbolic::__nullptr__(), true);
×
103
    } else {
×
104
        return TensorNode::pointer_access_type(input_idx);
×
105
    }
×
106
}
×
107

NEW
108
std::ostream& operator<<(std::ostream& os, const std::vector<int64_t>& list) {
×
NEW
109
    os << "[";
×
NEW
110
    for (size_t i = 0; i < list.size(); ++i) {
×
NEW
111
        if (i > 0) os << ", ";
×
NEW
112
        os << list[i];
×
NEW
113
    }
×
NEW
114
    os << "]";
×
NEW
115
    return os;
×
NEW
116
}
×
117

NEW
118
std::string ReduceNode::toStr() const {
×
NEW
119
    std::stringstream ss;
×
NEW
120
    ss << this->code_.value();
×
NEW
121
    ss << "(shape=";
×
NEW
122
    TensorLayout::emit_symbolic_list(ss, shape_);
×
NEW
123
    ss << ", axes=" << axes_;
×
NEW
124
    ss << ", keep=" << this->keepdims_;
×
NEW
125
    ss << ")";
×
NEW
126
    return ss.str();
×
NEW
127
}
×
128

129
passes::LibNodeExpander::ExpandOutcome ReduceNode::expand_inner(
130
    passes::LibNodeExpander::AccessNodeExpand& expansion,
131
    structured_control_flow::Block& block,
132
    const data_flow::Memlet* iedge_input,
133
    const data_flow::Memlet* iedge_result,
134
    const std::vector<symbolic::Expression>& output_shape,
135
    const std::vector<int64_t>& sorted_axes
136
) {
11✔
137
    sdfg::types::Scalar element_type(iedge_result->base_type().primitive_type());
11✔
138
    types::Tensor scalar_tensor(element_type.primitive_type(), {});
11✔
139

140
    // Add new sequence
141
    auto& new_sequence = expansion.replace_with_sequence();
11✔
142
    auto& builder = expansion.builder();
11✔
143

144
    // 1. Initialization Loop
145
    {
11✔
146
        data_flow::Subset init_subset;
11✔
147
        structured_control_flow::Sequence* last_scope = &new_sequence;
11✔
148
        structured_control_flow::Map* last_map = nullptr;
11✔
149

150
        for (size_t i = 0; i < output_shape.size(); ++i) {
18✔
151
            std::string indvar_str = builder.find_new_name("_i_init");
7✔
152
            builder.add_container(indvar_str, types::Scalar(types::PrimitiveType::Int64));
7✔
153

154
            auto indvar = symbolic::symbol(indvar_str);
7✔
155
            auto init = symbolic::zero();
7✔
156
            auto update = symbolic::add(indvar, symbolic::one());
7✔
157
            auto condition = symbolic::Lt(indvar, output_shape[i]);
7✔
158

159
            last_map = &builder.add_map(
7✔
160
                *last_scope,
7✔
161
                indvar,
7✔
162
                condition,
7✔
163
                init,
7✔
164
                update,
7✔
165
                structured_control_flow::ScheduleType_Sequential::create(),
7✔
166
                {},
7✔
167
                block.debug_info()
7✔
168
            );
7✔
169
            last_scope = &last_map->root();
7✔
170
            init_subset.push_back(indvar);
7✔
171
        }
7✔
172

173
        // Add initialization tasklet
174
        auto& init_block = builder.add_block(*last_scope, {}, block.debug_info());
11✔
175
        auto& init_tasklet =
11✔
176
            builder.add_tasklet(init_block, data_flow::TaskletCode::assign, {"_out"}, {"_in"}, block.debug_info());
11✔
177

178
        auto& const_node =
11✔
179
            builder
11✔
180
                .add_constant(init_block, this->identity(element_type.primitive_type()), element_type, block.debug_info());
11✔
181
        auto& out_access = expansion.add_indirect_write_access(init_block, RESULT_PTR_IDX);
11✔
182

183
        builder
11✔
184
            .add_computational_memlet(init_block, const_node, init_tasklet, "_in", {}, scalar_tensor, block.debug_info());
11✔
185
        builder.add_computational_memlet(
11✔
186
            init_block, init_tasklet, "_out", out_access, init_subset, iedge_result->base_type(), block.debug_info()
11✔
187
        );
11✔
188
    }
11✔
189

190
    // 2. Reduction Loop
191
    {
11✔
192
        data_flow::Subset input_subset;
11✔
193
        data_flow::Subset output_subset;
11✔
194

195
        structured_control_flow::Sequence* last_scope = &new_sequence;
11✔
196
        structured_control_flow::StructuredLoop* last_loop = nullptr;
11✔
197

198
        std::map<size_t, symbolic::Expression> loop_vars_map;
11✔
199
        std::vector<size_t> outer_dims;
11✔
200
        std::vector<size_t> inner_dims;
11✔
201

202
        // Partition dimensions
203
        for (size_t i = 0; i < shape_.size(); ++i) {
29✔
204
            bool is_axis = false;
18✔
205
            for (auto axis : sorted_axes) {
20✔
206
                if (axis == (int64_t) i) {
20✔
207
                    is_axis = true;
12✔
208
                    break;
12✔
209
                }
12✔
210
            }
20✔
211
            if (is_axis) {
18✔
212
                inner_dims.push_back(i);
12✔
213
            } else {
12✔
214
                outer_dims.push_back(i);
6✔
215
            }
6✔
216
        }
18✔
217

218
        // Generate outer parallel loops (Maps)
219
        for (size_t dim_idx : outer_dims) {
11✔
220
            std::string indvar_str = builder.find_new_name("_i");
6✔
221
            builder.add_container(indvar_str, types::Scalar(types::PrimitiveType::Int64));
6✔
222

223
            auto indvar = symbolic::symbol(indvar_str);
6✔
224
            auto init = symbolic::zero();
6✔
225
            auto update = symbolic::add(indvar, symbolic::one());
6✔
226
            auto condition = symbolic::Lt(indvar, shape_[dim_idx]);
6✔
227

228
            auto& map = builder.add_map(
6✔
229
                *last_scope,
6✔
230
                indvar,
6✔
231
                condition,
6✔
232
                init,
6✔
233
                update,
6✔
234
                structured_control_flow::ScheduleType_Sequential::create(),
6✔
235
                {},
6✔
236
                block.debug_info()
6✔
237
            );
6✔
238
            last_scope = &map.root();
6✔
239
            loop_vars_map[dim_idx] = indvar;
6✔
240
        }
6✔
241

242
        // Generate inner sequential loops (Fors)
243
        for (size_t dim_idx : inner_dims) {
12✔
244
            std::string indvar_str = builder.find_new_name("_k");
12✔
245
            builder.add_container(indvar_str, types::Scalar(types::PrimitiveType::Int64));
12✔
246

247
            auto indvar = symbolic::symbol(indvar_str);
12✔
248
            auto init = symbolic::zero();
12✔
249
            auto update = symbolic::add(indvar, symbolic::one());
12✔
250
            auto condition = symbolic::Lt(indvar, shape_[dim_idx]);
12✔
251

252
            last_loop = &builder.add_for(*last_scope, indvar, condition, init, update, {}, block.debug_info());
12✔
253
            last_scope = &last_loop->root();
12✔
254
            loop_vars_map[dim_idx] = indvar;
12✔
255
        }
12✔
256

257
        // Construct output indices
258
        std::vector<symbolic::Expression> input_indices;
11✔
259
        std::vector<symbolic::Expression> output_indices;
11✔
260
        for (size_t i = 0; i < shape_.size(); ++i) {
29✔
261
            input_indices.push_back(loop_vars_map[i]);
18✔
262
            bool is_axis = false;
18✔
263
            for (auto axis : sorted_axes) {
20✔
264
                if (axis == (int64_t) i) {
20✔
265
                    is_axis = true;
12✔
266
                    break;
12✔
267
                }
12✔
268
            }
20✔
269

270
            if (is_axis) {
18✔
271
                if (keepdims_) {
12✔
272
                    output_indices.push_back(symbolic::zero());
1✔
273
                }
1✔
274
            } else {
12✔
275
                output_indices.push_back(loop_vars_map[i]);
6✔
276
            }
6✔
277
        }
18✔
278

279
        this->expand_reduction(
11✔
280
            expansion,
11✔
281
            builder,
11✔
282
            *last_scope,
11✔
283
            static_cast<const types::Tensor&>(iedge_input->base_type()),
11✔
284
            static_cast<const types::Tensor&>(iedge_result->base_type()),
11✔
285
            input_indices,
11✔
286
            output_indices
11✔
287
        );
11✔
288
    }
11✔
289

290
    return expansion.successfully_expanded();
11✔
291
}
11✔
292

293
passes::LibNodeExpander::ExpandOutcome ReduceNode::expand(passes::LibNodeExpander::ExpandContext& context, Block& block) {
22✔
294
    auto& dataflow = this->get_parent();
22✔
295

296
    if (dataflow.in_degree(*this) != 2) {
22✔
NEW
297
        return context.unable();
×
298
    }
×
299

300
    auto* iedge_input = dataflow.in_edge_for_connector(*this, inputs_.at(1));
22✔
301
    auto* iedge_result = dataflow.in_edge_for_connector(*this, inputs_.at(0));
22✔
302

303
    // Calculate output shape
304
    std::vector<symbolic::Expression> output_shape;
22✔
305
    std::vector<int64_t> sorted_axes = axes_;
22✔
306
    // Normalize negative axes
307
    for (auto& axis : sorted_axes) {
23✔
308
        if (axis < 0) {
23✔
309
            axis = static_cast<int64_t>(shape_.size()) + axis;
3✔
310
        }
3✔
311
        // Validate axis is in bounds
312
        if (axis < 0 || axis >= static_cast<int64_t>(shape_.size())) {
23✔
313
            throw InvalidSDFGException(
×
314
                "Library Node: Axis value out of bounds. Axis: " + std::to_string(axis) +
×
315
                " Shape size: " + std::to_string(shape_.size())
×
316
            );
×
317
        }
×
318
    }
23✔
319
    std::sort(sorted_axes.begin(), sorted_axes.end());
22✔
320

321
    for (size_t i = 0; i < shape_.size(); ++i) {
53✔
322
        bool is_axis = false;
31✔
323
        for (auto axis : sorted_axes) {
33✔
324
            if (axis == (int64_t) i) {
33✔
325
                is_axis = true;
23✔
326
                break;
23✔
327
            }
23✔
328
        }
33✔
329

330
        if (is_axis) {
31✔
331
            if (keepdims_) {
23✔
332
                output_shape.push_back(symbolic::one());
1✔
333
            }
1✔
334
        } else {
23✔
335
            output_shape.push_back(shape_[i]);
8✔
336
        }
8✔
337
    }
31✔
338

339
    auto expansion = context.replacement_requires_access_nodes(
22✔
340
        {passes::LibNodeExpander::InputUse::IndirectReadWrite, passes::LibNodeExpander::InputUse::IndirectRead}
22✔
341
    );
22✔
342

343
    if (!expansion) {
22✔
344
        return context.unable();
1✔
345
    }
1✔
346

347
    return expand_inner(*expansion.get(), block, iedge_input, iedge_result, output_shape, sorted_axes);
21✔
348
}
22✔
349

350
} // namespace tensor
351
} // namespace math
352
} // namespace sdfg
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc