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

daisytuner / docc / 26831349290

02 Jun 2026 03:50PM UTC coverage: 61.29% (-0.01%) from 61.302%
26831349290

Pull #725

github

web-flow
Merge a7e2175c0 into 887730e20
Pull Request #725: Tensor node backport

932 of 1642 new or added lines in 52 files covered. (56.76%)

92 existing lines in 33 files now uncovered.

35584 of 58058 relevant lines covered (61.29%)

11020.18 hits per line

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

85.6
/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/analysis/scope_analysis.h"
5
#include "sdfg/builder/structured_sdfg_builder.h"
6

7
#include <algorithm>
8

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

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

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

29
    auto& graph = this->get_parent();
12✔
30

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

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

52
    std::vector<symbolic::Expression> expected_output_shape;
12✔
53
    for (size_t i = 0; i < shape_.size(); ++i) {
32✔
54
        bool is_axis = false;
20✔
55
        for (auto axis : sorted_axes) {
22✔
56
            if (axis == (int64_t) i) {
22✔
57
                is_axis = true;
13✔
58
                break;
13✔
59
            }
13✔
60
        }
22✔
61

62
        if (is_axis) {
20✔
63
            if (keepdims_) {
13✔
64
                expected_output_shape.push_back(symbolic::one());
1✔
65
            }
1✔
66
        } else {
13✔
67
            expected_output_shape.push_back(shape_[i]);
7✔
68
        }
7✔
69
    }
20✔
70

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

76

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

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

NEW
93
data_flow::PointerAccessType ReduceNode::pointer_access_type(int input_idx) const {
×
NEW
94
    if (input_idx == 0) {
×
NEW
95
        return data_flow::PointerAccessMeta::create_full_write_only(symbolic::__nullptr__(), true);
×
NEW
96
    } else if (input_idx == 1) {
×
NEW
97
        return data_flow::PointerAccessMeta::create_read_only(symbolic::__nullptr__(), true);
×
NEW
98
    } else {
×
NEW
99
        return TensorNode::pointer_access_type(input_idx);
×
UNCOV
100
    }
×
NEW
101
}
×
102

103
bool ReduceNode::expand_inner(
104
    builder::StructuredSDFGBuilder& builder,
105
    analysis::AnalysisManager& analysis_manager,
106
    structured_control_flow::Block& block,
107
    data_flow::DataFlowGraph& dataflow,
108
    structured_control_flow::Sequence& parent,
109
    Transition& transition,
110
    const data_flow::Memlet* iedge_input,
111
    const data_flow::Memlet* iedge_result,
112
    const data_flow::AccessNode* input_node,
113
    const data_flow::AccessNode* output_node,
114
    const std::vector<symbolic::Expression>& output_shape,
115
    const std::vector<int64_t>& sorted_axes
116
) {
6✔
117
    auto org_idx = parent.index(block);
6✔
118

119
    sdfg::types::Scalar element_type(iedge_result->base_type().primitive_type());
6✔
120
    types::Tensor scalar_tensor(element_type.primitive_type(), {});
6✔
121

122
    // Add new sequence
123
    auto& new_sequence = builder.add_sequence_before(parent, block, transition.assignments(), block.debug_info());
6✔
124

125
    // 1. Initialization Loop
126
    {
6✔
127
        data_flow::Subset init_subset;
6✔
128
        structured_control_flow::Sequence* last_scope = &new_sequence;
6✔
129
        structured_control_flow::Map* last_map = nullptr;
6✔
130

131
        for (size_t i = 0; i < output_shape.size(); ++i) {
12✔
132
            std::string indvar_str = builder.find_new_name("_i_init");
6✔
133
            builder.add_container(indvar_str, types::Scalar(types::PrimitiveType::Int64));
6✔
134

135
            auto indvar = symbolic::symbol(indvar_str);
6✔
136
            auto init = symbolic::zero();
6✔
137
            auto update = symbolic::add(indvar, symbolic::one());
6✔
138
            auto condition = symbolic::Lt(indvar, output_shape[i]);
6✔
139

140
            last_map = &builder.add_map(
6✔
141
                *last_scope,
6✔
142
                indvar,
6✔
143
                condition,
6✔
144
                init,
6✔
145
                update,
6✔
146
                structured_control_flow::ScheduleType_Sequential::create(),
6✔
147
                {},
6✔
148
                block.debug_info()
6✔
149
            );
6✔
150
            last_scope = &last_map->root();
6✔
151
            init_subset.push_back(indvar);
6✔
152
        }
6✔
153

154
        // Add initialization tasklet
155
        auto& init_block = builder.add_block(*last_scope, {}, block.debug_info());
6✔
156
        auto& init_tasklet =
6✔
157
            builder.add_tasklet(init_block, data_flow::TaskletCode::assign, {"_out"}, {"_in"}, block.debug_info());
6✔
158

159
        auto& const_node =
6✔
160
            builder
6✔
161
                .add_constant(init_block, this->identity(element_type.primitive_type()), element_type, block.debug_info());
6✔
162
        auto& out_access = builder.add_access(init_block, output_node->data(), block.debug_info());
6✔
163

164
        builder
6✔
165
            .add_computational_memlet(init_block, const_node, init_tasklet, "_in", {}, scalar_tensor, block.debug_info());
6✔
166
        builder.add_computational_memlet(
6✔
167
            init_block, init_tasklet, "_out", out_access, init_subset, iedge_result->base_type(), block.debug_info()
6✔
168
        );
6✔
169
    }
6✔
170

171
    // 2. Reduction Loop
172
    {
6✔
173
        data_flow::Subset input_subset;
6✔
174
        data_flow::Subset output_subset;
6✔
175

176
        structured_control_flow::Sequence* last_scope = &new_sequence;
6✔
177
        structured_control_flow::StructuredLoop* last_loop = nullptr;
6✔
178

179
        std::map<size_t, symbolic::Expression> loop_vars_map;
6✔
180
        std::vector<size_t> outer_dims;
6✔
181
        std::vector<size_t> inner_dims;
6✔
182

183
        // Partition dimensions
184
        for (size_t i = 0; i < shape_.size(); ++i) {
18✔
185
            bool is_axis = false;
12✔
186
            for (auto axis : sorted_axes) {
14✔
187
                if (axis == (int64_t) i) {
14✔
188
                    is_axis = true;
7✔
189
                    break;
7✔
190
                }
7✔
191
            }
14✔
192
            if (is_axis) {
12✔
193
                inner_dims.push_back(i);
7✔
194
            } else {
7✔
195
                outer_dims.push_back(i);
5✔
196
            }
5✔
197
        }
12✔
198

199
        // Generate outer parallel loops (Maps)
200
        for (size_t dim_idx : outer_dims) {
6✔
201
            std::string indvar_str = builder.find_new_name("_i");
5✔
202
            builder.add_container(indvar_str, types::Scalar(types::PrimitiveType::Int64));
5✔
203

204
            auto indvar = symbolic::symbol(indvar_str);
5✔
205
            auto init = symbolic::zero();
5✔
206
            auto update = symbolic::add(indvar, symbolic::one());
5✔
207
            auto condition = symbolic::Lt(indvar, shape_[dim_idx]);
5✔
208

209
            auto& map = builder.add_map(
5✔
210
                *last_scope,
5✔
211
                indvar,
5✔
212
                condition,
5✔
213
                init,
5✔
214
                update,
5✔
215
                structured_control_flow::ScheduleType_Sequential::create(),
5✔
216
                {},
5✔
217
                block.debug_info()
5✔
218
            );
5✔
219
            last_scope = &map.root();
5✔
220
            loop_vars_map[dim_idx] = indvar;
5✔
221
        }
5✔
222

223
        // Generate inner sequential loops (Fors)
224
        for (size_t dim_idx : inner_dims) {
7✔
225
            std::string indvar_str = builder.find_new_name("_k");
7✔
226
            builder.add_container(indvar_str, types::Scalar(types::PrimitiveType::Int64));
7✔
227

228
            auto indvar = symbolic::symbol(indvar_str);
7✔
229
            auto init = symbolic::zero();
7✔
230
            auto update = symbolic::add(indvar, symbolic::one());
7✔
231
            auto condition = symbolic::Lt(indvar, shape_[dim_idx]);
7✔
232

233
            last_loop = &builder.add_for(*last_scope, indvar, condition, init, update, {}, block.debug_info());
7✔
234
            last_scope = &last_loop->root();
7✔
235
            loop_vars_map[dim_idx] = indvar;
7✔
236
        }
7✔
237

238
        // Construct output indices
239
        std::vector<symbolic::Expression> input_indices;
6✔
240
        std::vector<symbolic::Expression> output_indices;
6✔
241
        for (size_t i = 0; i < shape_.size(); ++i) {
18✔
242
            input_indices.push_back(loop_vars_map[i]);
12✔
243
            bool is_axis = false;
12✔
244
            for (auto axis : sorted_axes) {
14✔
245
                if (axis == (int64_t) i) {
14✔
246
                    is_axis = true;
7✔
247
                    break;
7✔
248
                }
7✔
249
            }
14✔
250

251
            if (is_axis) {
12✔
252
                if (keepdims_) {
7✔
253
                    output_indices.push_back(symbolic::zero());
1✔
254
                }
1✔
255
            } else {
7✔
256
                output_indices.push_back(loop_vars_map[i]);
5✔
257
            }
5✔
258
        }
12✔
259

260
        this->expand_reduction(
6✔
261
            builder,
6✔
262
            analysis_manager,
6✔
263
            *last_scope,
6✔
264
            input_node->data(),
6✔
265
            output_node->data(),
6✔
266
            static_cast<const types::Tensor&>(iedge_input->base_type()),
6✔
267
            static_cast<const types::Tensor&>(iedge_result->base_type()),
6✔
268
            input_indices,
6✔
269
            output_indices
6✔
270
        );
6✔
271
    }
6✔
272

273
    // Clean up block
274
    builder.clear_code_node_legacy(block, *this);
6✔
275
    // WARNING: this has been deallocated at this point!!
276
    builder.remove_child(parent, org_idx + 1);
6✔
277

278
    return true;
6✔
279
}
6✔
280

281
bool ReduceNode::expand(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
11✔
282
    auto& dataflow = this->get_parent();
11✔
283
    auto& block = static_cast<structured_control_flow::Block&>(*dataflow.get_parent());
11✔
284

285
    if (dataflow.in_degree(*this) != 2) {
11✔
NEW
286
        return false;
×
NEW
287
    }
×
288

289
    auto& scope_analysis = analysis_manager.get<analysis::ScopeAnalysis>();
11✔
290
    auto& parent = static_cast<structured_control_flow::Sequence&>(*scope_analysis.parent_scope(&block));
11✔
291
    int index = parent.index(block);
11✔
292
    auto& transition = parent.at(index).second;
11✔
293

294
    auto* iedge_input = dataflow.in_edge_for_connector(*this, inputs_.at(1));
11✔
295
    auto* iedge_result = dataflow.in_edge_for_connector(*this, inputs_.at(0));
11✔
296

297

298
    auto* input_node = dataflow.find_standalone_entry(iedge_input);
11✔
299
    auto* output_node = dataflow.find_standalone_entry(iedge_result);
11✔
300

301
    if (!input_node || !output_node) {
11✔
NEW
302
        return false;
×
NEW
303
    }
×
304

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

323
    for (size_t i = 0; i < shape_.size(); ++i) {
30✔
324
        bool is_axis = false;
19✔
325
        for (auto axis : sorted_axes) {
21✔
326
            if (axis == (int64_t) i) {
21✔
327
                is_axis = true;
12✔
328
                break;
12✔
329
            }
12✔
330
        }
21✔
331

332
        if (is_axis) {
19✔
333
            if (keepdims_) {
12✔
334
                output_shape.push_back(symbolic::one());
1✔
335
            }
1✔
336
        } else {
12✔
337
            output_shape.push_back(shape_[i]);
7✔
338
        }
7✔
339
    }
19✔
340

341

342
    return expand_inner(
11✔
343
        builder,
11✔
344
        analysis_manager,
11✔
345
        block,
11✔
346
        dataflow,
11✔
347
        parent,
11✔
348
        transition,
11✔
349
        iedge_input,
11✔
350
        iedge_result,
11✔
351
        input_node,
11✔
352
        output_node,
11✔
353
        output_shape,
11✔
354
        sorted_axes
11✔
355
    );
11✔
356
}
11✔
357

358
} // namespace tensor
359
} // namespace math
360
} // 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