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

daisytuner / docc / 29726336683

20 Jul 2026 07:59AM UTC coverage: 63.437% (+0.3%) from 63.18%
29726336683

Pull #857

github

web-flow
Merge 129cc05f2 into b57f184e7
Pull Request #857: Added PyTorch frontend

419 of 544 new or added lines in 14 files covered. (77.02%)

1 existing line in 1 file now uncovered.

41006 of 64641 relevant lines covered (63.44%)

977.69 hits per line

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

77.98
/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

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

118
std::string ReduceNode::toStr() const {
×
119
    std::stringstream ss;
×
120
    ss << this->code_.value();
×
121
    ss << "(shape=";
×
122
    TensorLayout::emit_symbolic_list(ss, shape_);
×
123
    ss << ", axes=" << axes_;
×
124
    ss << ", keep=" << this->keepdims_;
×
125
    ss << ")";
×
126
    return ss.str();
×
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
    std::string accum_container;
11✔
144

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

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

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

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

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

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

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

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

197
        structured_control_flow::Sequence* last_scope = &new_sequence;
11✔
198
        structured_control_flow::StructuredLoop* last_loop = nullptr;
11✔
199

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

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

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

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

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

244
        // Generate inner sequential loops (Fors / Reduces)
245
        auto reduction_operation = this->reduction_operation();
11✔
246
        for (size_t dim_idx : inner_dims) {
12✔
247
            std::string indvar_str = builder.find_new_name("_k");
12✔
248
            builder.add_container(indvar_str, types::Scalar(types::PrimitiveType::Int64));
12✔
249

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

255
            if (reduction_operation.has_value()) {
12✔
256
                last_loop = &builder.add_reduce(
12✔
257
                    *last_scope,
12✔
258
                    indvar,
12✔
259
                    condition,
12✔
260
                    init,
12✔
261
                    update,
12✔
262
                    {{.operation = reduction_operation.value(), .container = accum_container}},
12✔
263
                    structured_control_flow::ScheduleType_Sequential::create(),
12✔
264
                    {},
12✔
265
                    block.debug_info()
12✔
266
                );
12✔
267
            } else {
12✔
NEW
268
                last_loop = &builder.add_for(*last_scope, indvar, condition, init, update, {}, block.debug_info());
×
NEW
269
            }
×
270
            last_scope = &last_loop->root();
12✔
271
            loop_vars_map[dim_idx] = indvar;
12✔
272
        }
12✔
273

274
        // Construct output indices
275
        std::vector<symbolic::Expression> input_indices;
11✔
276
        std::vector<symbolic::Expression> output_indices;
11✔
277
        for (size_t i = 0; i < shape_.size(); ++i) {
29✔
278
            input_indices.push_back(loop_vars_map[i]);
18✔
279
            bool is_axis = false;
18✔
280
            for (auto axis : sorted_axes) {
20✔
281
                if (axis == (int64_t) i) {
20✔
282
                    is_axis = true;
12✔
283
                    break;
12✔
284
                }
12✔
285
            }
20✔
286

287
            if (is_axis) {
18✔
288
                if (keepdims_) {
12✔
289
                    output_indices.push_back(symbolic::zero());
1✔
290
                }
1✔
291
            } else {
12✔
292
                output_indices.push_back(loop_vars_map[i]);
6✔
293
            }
6✔
294
        }
18✔
295

296
        this->expand_reduction(
11✔
297
            expansion,
11✔
298
            builder,
11✔
299
            *last_scope,
11✔
300
            static_cast<const types::Tensor&>(iedge_input->base_type()),
11✔
301
            static_cast<const types::Tensor&>(iedge_result->base_type()),
11✔
302
            input_indices,
11✔
303
            output_indices
11✔
304
        );
11✔
305
    }
11✔
306

307
    return expansion.successfully_expanded();
11✔
308
}
11✔
309

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

313
    if (dataflow.in_degree(*this) != 2) {
22✔
314
        return context.unable();
×
315
    }
×
316

317
    auto* iedge_input = dataflow.in_edge_for_connector(*this, inputs_.at(1));
22✔
318
    auto* iedge_result = dataflow.in_edge_for_connector(*this, inputs_.at(0));
22✔
319

320
    // Calculate output shape
321
    std::vector<symbolic::Expression> output_shape;
22✔
322
    std::vector<int64_t> sorted_axes = axes_;
22✔
323
    // Normalize negative axes
324
    for (auto& axis : sorted_axes) {
23✔
325
        if (axis < 0) {
23✔
326
            axis = static_cast<int64_t>(shape_.size()) + axis;
3✔
327
        }
3✔
328
        // Validate axis is in bounds
329
        if (axis < 0 || axis >= static_cast<int64_t>(shape_.size())) {
23✔
330
            throw InvalidSDFGException(
×
331
                "Library Node: Axis value out of bounds. Axis: " + std::to_string(axis) +
×
332
                " Shape size: " + std::to_string(shape_.size())
×
333
            );
×
334
        }
×
335
    }
23✔
336
    std::sort(sorted_axes.begin(), sorted_axes.end());
22✔
337

338
    for (size_t i = 0; i < shape_.size(); ++i) {
53✔
339
        bool is_axis = false;
31✔
340
        for (auto axis : sorted_axes) {
33✔
341
            if (axis == (int64_t) i) {
33✔
342
                is_axis = true;
23✔
343
                break;
23✔
344
            }
23✔
345
        }
33✔
346

347
        if (is_axis) {
31✔
348
            if (keepdims_) {
23✔
349
                output_shape.push_back(symbolic::one());
1✔
350
            }
1✔
351
        } else {
23✔
352
            output_shape.push_back(shape_[i]);
8✔
353
        }
8✔
354
    }
31✔
355

356
    auto expansion = context.replacement_requires_access_nodes(
22✔
357
        {passes::LibNodeExpander::InputUse::IndirectReadWrite, passes::LibNodeExpander::InputUse::IndirectRead}
22✔
358
    );
22✔
359

360
    if (!expansion) {
22✔
361
        return context.unable();
1✔
362
    }
1✔
363

364
    return expand_inner(*expansion.get(), block, iedge_input, iedge_result, output_shape, sorted_axes);
21✔
365
}
22✔
366

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

© 2026 Coveralls, Inc