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

daisytuner / docc / 28793626242

06 Jul 2026 01:04PM UTC coverage: 62.962% (+0.2%) from 62.801%
28793626242

Pull #740

github

web-flow
Merge b7e03d389 into 23e67a4ab
Pull Request #740: Expand pass

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

36 existing lines in 10 files now uncovered.

40557 of 64415 relevant lines covered (62.96%)

972.09 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