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

daisytuner / docc / 22023884668

14 Feb 2026 08:36PM UTC coverage: 64.903% (-1.4%) from 66.315%
22023884668

Pull #525

github

web-flow
Merge 1d47f8bf2 into 9d01cacd5
Pull Request #525: Step 3 (Native Tensor Support): Refactor Python Frontend

2522 of 3435 new or added lines in 32 files covered. (73.42%)

320 existing lines in 15 files now uncovered.

23204 of 35752 relevant lines covered (64.9%)

370.03 hits per line

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

70.47
/sdfg/src/data_flow/library_nodes/math/tensor/elementwise_node.cpp
1
#include "sdfg/data_flow/library_nodes/math/tensor/elementwise_node.h"
2

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

6
#include "sdfg/analysis/scope_analysis.h"
7

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

12
ElementWiseUnaryNode::ElementWiseUnaryNode(
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
)
20
    : TensorNode(element_id, debug_info, vertex, parent, code, {"Y"}, {"X"}, data_flow::ImplementationType_NONE),
76✔
21
      shape_(shape) {}
76✔
22

23
void ElementWiseUnaryNode::validate(const Function& function) const {
49✔
24
    TensorNode::validate(function);
49✔
25

26
    auto& graph = this->get_parent();
49✔
27

28
    auto& oedge = *graph.out_edges(*this).begin();
49✔
29
    auto& tensor_output = static_cast<const types::Tensor&>(oedge.base_type());
49✔
30
    if (tensor_output.shape().size() != this->shape_.size()) {
49✔
NEW
31
        throw InvalidSDFGException(
×
NEW
32
            "Library Node: Output tensor shape must match node shape. Output shape: " +
×
NEW
33
            std::to_string(tensor_output.shape().size()) + " Node shape: " + std::to_string(this->shape_.size())
×
NEW
34
        );
×
NEW
35
    }
×
36
    for (size_t i = 0; i < this->shape_.size(); ++i) {
162✔
37
        if (!symbolic::eq(tensor_output.shape().at(i), this->shape_.at(i))) {
113✔
NEW
38
            throw InvalidSDFGException(
×
NEW
39
                "Library Node: Output tensor shape does not match expected shape. Output shape: " +
×
NEW
40
                tensor_output.shape().at(i)->__str__() + " Expected shape: " + this->shape_.at(i)->__str__()
×
NEW
41
            );
×
NEW
42
        }
×
43
    }
113✔
44

45
    for (auto& iedge : graph.in_edges(*this)) {
49✔
46
        auto& tensor_input = static_cast<const types::Tensor&>(iedge.base_type());
48✔
47
        // Case 1: Scalar input is allowed as secondary input
48
        if (tensor_input.is_scalar()) {
48✔
49
            continue;
1✔
50
        }
1✔
51

52
        // Case 2: Tensor input
53
        if (tensor_input.shape().size() != this->shape_.size()) {
47✔
NEW
54
            throw InvalidSDFGException(
×
NEW
55
                "Library Node: Input tensor shape must match node shape. Input shape: " +
×
NEW
56
                std::to_string(tensor_input.shape().size()) + " Node shape: " + std::to_string(this->shape_.size())
×
NEW
57
            );
×
NEW
58
        }
×
59
        for (size_t i = 0; i < this->shape_.size(); ++i) {
160✔
60
            if (!symbolic::eq(tensor_input.shape().at(i), this->shape_.at(i))) {
113✔
NEW
61
                throw InvalidSDFGException(
×
NEW
62
                    "Library Node: Input tensor shape does not match expected shape. Input shape: " +
×
NEW
63
                    tensor_input.shape().at(i)->__str__() + " Expected shape: " + this->shape_.at(i)->__str__()
×
NEW
64
                );
×
NEW
65
            }
×
66
        }
113✔
67
    }
47✔
68
}
49✔
69

70
symbolic::SymbolSet ElementWiseUnaryNode::symbols() const {
2✔
71
    symbolic::SymbolSet syms;
2✔
72
    for (const auto& dim : shape_) {
4✔
73
        for (auto& atom : symbolic::atoms(dim)) {
4✔
74
            syms.insert(atom);
×
75
        }
×
76
    }
4✔
77
    return syms;
2✔
78
}
2✔
79

80
void ElementWiseUnaryNode::replace(const symbolic::Expression old_expression, const symbolic::Expression new_expression) {
×
81
    for (auto& dim : shape_) {
×
82
        dim = symbolic::subs(dim, old_expression, new_expression);
×
83
    }
×
84
}
×
85

86
bool ElementWiseUnaryNode::expand(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
70✔
87
    auto& dataflow = this->get_parent();
70✔
88
    auto& block = static_cast<structured_control_flow::Block&>(*dataflow.get_parent());
70✔
89
    if (dataflow.in_degree(*this) != 1 || dataflow.out_degree(*this) != 1) {
70✔
90
        return false;
×
91
    }
×
92

93
    auto& scope_analysis = analysis_manager.get<analysis::ScopeAnalysis>();
70✔
94
    auto& parent = static_cast<structured_control_flow::Sequence&>(*scope_analysis.parent_scope(&block));
70✔
95
    int index = parent.index(block);
70✔
96
    auto& transition = parent.at(index).second;
70✔
97

98
    auto& input = this->inputs_.at(0);
70✔
99
    auto& output = this->outputs_.at(0);
70✔
100

101
    auto& iedge = *dataflow.in_edges(*this).begin();
70✔
102
    auto& oedge = *dataflow.out_edges(*this).begin();
70✔
103

104
    // Checks if legal
105
    auto& input_node = static_cast<data_flow::AccessNode&>(iedge.src());
70✔
106
    auto& output_node = static_cast<data_flow::AccessNode&>(oedge.dst());
70✔
107
    if (dataflow.in_degree(input_node) != 0 || dataflow.out_degree(output_node) != 0) {
70✔
108
        return false;
×
109
    }
×
110

111
    // Add new graph after the current block
112
    auto& new_sequence = builder.add_sequence_before(parent, block, transition.assignments(), block.debug_info());
70✔
113

114
    // Add maps
115
    data_flow::Subset new_subset;
70✔
116
    structured_control_flow::Sequence* last_scope = &new_sequence;
70✔
117
    structured_control_flow::Map* last_map = nullptr;
70✔
118
    std::vector<symbolic::Expression> loop_vars;
70✔
119

120
    for (size_t i = 0; i < this->shape_.size(); i++) {
236✔
121
        std::string indvar_str = builder.find_new_name("_i");
166✔
122
        builder.add_container(indvar_str, types::Scalar(types::PrimitiveType::UInt64));
166✔
123

124
        auto indvar = symbolic::symbol(indvar_str);
166✔
125
        auto init = symbolic::zero();
166✔
126
        auto update = symbolic::add(indvar, symbolic::one());
166✔
127
        auto condition = symbolic::Lt(indvar, this->shape_[i]);
166✔
128
        last_map = &builder.add_map(
166✔
129
            *last_scope,
166✔
130
            indvar,
166✔
131
            condition,
166✔
132
            init,
166✔
133
            update,
166✔
134
            structured_control_flow::ScheduleType_Sequential::create(),
166✔
135
            {},
166✔
136
            block.debug_info()
166✔
137
        );
166✔
138
        last_scope = &last_map->root();
166✔
139

140
        loop_vars.push_back(indvar);
166✔
141
    }
166✔
142

143
    bool success = this->expand_operation(
70✔
144
        builder,
70✔
145
        analysis_manager,
70✔
146
        *last_scope,
70✔
147
        input_node.data(),
70✔
148
        output_node.data(),
70✔
149
        static_cast<const types::Tensor&>(iedge.base_type()),
70✔
150
        static_cast<const types::Tensor&>(oedge.base_type()),
70✔
151
        loop_vars
70✔
152
    );
70✔
153
    if (!success) {
70✔
154
        return false;
×
155
    }
×
156

157
    // Clean up block
158
    builder.remove_memlet(block, iedge);
70✔
159
    builder.remove_memlet(block, oedge);
70✔
160
    builder.remove_node(block, input_node);
70✔
161
    builder.remove_node(block, output_node);
70✔
162
    builder.remove_node(block, *this);
70✔
163
    builder.remove_child(parent, index + 1);
70✔
164

165
    return true;
70✔
166
}
70✔
167

168
ElementWiseBinaryNode::ElementWiseBinaryNode(
169
    size_t element_id,
170
    const DebugInfo& debug_info,
171
    const graph::Vertex vertex,
172
    data_flow::DataFlowGraph& parent,
173
    const data_flow::LibraryNodeCode& code,
174
    const std::vector<symbolic::Expression>& shape
175
)
176
    : TensorNode(element_id, debug_info, vertex, parent, code, {"C"}, {"A", "B"}, data_flow::ImplementationType_NONE),
42✔
177
      shape_(shape) {}
42✔
178

179
void ElementWiseBinaryNode::validate(const Function& function) const {
30✔
180
    TensorNode::validate(function);
30✔
181

182
    auto& graph = this->get_parent();
30✔
183

184
    auto& oedge = *graph.out_edges(*this).begin();
30✔
185
    auto& tensor_output = static_cast<const types::Tensor&>(oedge.base_type());
30✔
186
    if (tensor_output.shape().size() != this->shape_.size()) {
30✔
NEW
187
        throw InvalidSDFGException(
×
NEW
188
            "Library Node: Output tensor shape must match node shape. Output shape: " +
×
NEW
189
            std::to_string(tensor_output.shape().size()) + " Node shape: " + std::to_string(this->shape_.size())
×
NEW
190
        );
×
NEW
191
    }
×
192
    for (size_t i = 0; i < this->shape_.size(); ++i) {
87✔
193
        if (!symbolic::eq(tensor_output.shape().at(i), this->shape_.at(i))) {
57✔
NEW
194
            throw InvalidSDFGException(
×
NEW
195
                "Library Node: Output tensor shape does not match expected shape. Output shape: " +
×
NEW
196
                tensor_output.shape().at(i)->__str__() + " Expected shape: " + this->shape_.at(i)->__str__()
×
NEW
197
            );
×
NEW
198
        }
×
199
    }
57✔
200

201
    for (auto& iedge : graph.in_edges(*this)) {
58✔
202
        auto& tensor_input = static_cast<const types::Tensor&>(iedge.base_type());
58✔
203
        // Case 1: Scalar input is allowed as secondary input
204
        if (tensor_input.is_scalar()) {
58✔
205
            continue;
6✔
206
        }
6✔
207

208
        // Case 2: Tensor input
209
        if (tensor_input.shape().size() != this->shape_.size()) {
52✔
NEW
210
            throw InvalidSDFGException(
×
NEW
211
                "Library Node: Input tensor shape must match node shape. Input shape: " +
×
NEW
212
                std::to_string(tensor_input.shape().size()) + " Node shape: " + std::to_string(this->shape_.size())
×
NEW
213
            );
×
NEW
214
        }
×
215
        for (size_t i = 0; i < this->shape_.size(); ++i) {
164✔
216
            if (!symbolic::eq(tensor_input.shape().at(i), this->shape_.at(i))) {
112✔
NEW
217
                throw InvalidSDFGException(
×
NEW
218
                    "Library Node: Input tensor shape does not match expected shape. Input shape: " +
×
NEW
219
                    tensor_input.shape().at(i)->__str__() + " Expected shape: " + this->shape_.at(i)->__str__()
×
NEW
220
                );
×
NEW
221
            }
×
222
        }
112✔
223
    }
52✔
224
}
30✔
225

226
symbolic::SymbolSet ElementWiseBinaryNode::symbols() const {
×
227
    symbolic::SymbolSet syms;
×
228
    for (const auto& dim : shape_) {
×
229
        for (auto& atom : symbolic::atoms(dim)) {
×
230
            syms.insert(atom);
×
231
        }
×
232
    }
×
233
    return syms;
×
234
}
×
235

236
void ElementWiseBinaryNode::replace(const symbolic::Expression old_expression, const symbolic::Expression new_expression) {
×
237
    for (auto& dim : shape_) {
×
238
        dim = symbolic::subs(dim, old_expression, new_expression);
×
239
    }
×
240
}
×
241

242
bool ElementWiseBinaryNode::expand(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
31✔
243
    auto& dataflow = this->get_parent();
31✔
244
    auto& block = static_cast<structured_control_flow::Block&>(*dataflow.get_parent());
31✔
245
    if (dataflow.in_degree(*this) != 2 || dataflow.out_degree(*this) != 1) {
31✔
246
        return false;
×
247
    }
×
248
    auto& scope_analysis = analysis_manager.get<analysis::ScopeAnalysis>();
31✔
249
    auto& parent = static_cast<structured_control_flow::Sequence&>(*scope_analysis.parent_scope(&block));
31✔
250
    int index = parent.index(block);
31✔
251
    auto& transition = parent.at(index).second;
31✔
252

253
    auto& input_a = this->inputs_.at(0);
31✔
254
    auto& input_b = this->inputs_.at(1);
31✔
255
    auto& output = this->outputs_.at(0);
31✔
256

257
    auto iedge_a = &(*dataflow.in_edges(*this).begin());
31✔
258
    auto iedge_b = &(*(++dataflow.in_edges(*this).begin()));
31✔
259
    if (iedge_a->dst_conn() != "A") {
31✔
260
        std::swap(iedge_a, iedge_b);
×
261
    }
×
262
    auto& oedge = *dataflow.out_edges(*this).begin();
31✔
263

264
    // Checks if legal
265
    auto& input_node_a = static_cast<data_flow::AccessNode&>(iedge_a->src());
31✔
266
    auto& input_node_b = static_cast<data_flow::AccessNode&>(iedge_b->src());
31✔
267
    auto& output_node = static_cast<data_flow::AccessNode&>(oedge.dst());
31✔
268
    if (dataflow.in_degree(input_node_a) != 0 || dataflow.in_degree(input_node_b) != 0 ||
31✔
269
        dataflow.out_degree(output_node) != 0) {
31✔
270
        return false;
×
271
    }
×
272

273
    // Add new graph after the current block
274
    auto& new_sequence = builder.add_sequence_before(parent, block, transition.assignments(), block.debug_info());
31✔
275

276
    // Add maps
277
    structured_control_flow::Sequence* last_scope = &new_sequence;
31✔
278
    structured_control_flow::Map* last_map = nullptr;
31✔
279
    std::vector<symbolic::Expression> loop_vars;
31✔
280

281
    for (size_t i = 0; i < this->shape_.size(); i++) {
92✔
282
        std::string indvar_str = builder.find_new_name("_i");
61✔
283
        builder.add_container(indvar_str, types::Scalar(types::PrimitiveType::UInt64));
61✔
284

285
        auto indvar = symbolic::symbol(indvar_str);
61✔
286
        auto init = symbolic::zero();
61✔
287
        auto update = symbolic::add(indvar, symbolic::one());
61✔
288
        auto condition = symbolic::Lt(indvar, this->shape_[i]);
61✔
289
        last_map = &builder.add_map(
61✔
290
            *last_scope,
61✔
291
            indvar,
61✔
292
            condition,
61✔
293
            init,
61✔
294
            update,
61✔
295
            structured_control_flow::ScheduleType_Sequential::create(),
61✔
296
            {},
61✔
297
            block.debug_info()
61✔
298
        );
61✔
299
        last_scope = &last_map->root();
61✔
300

301
        loop_vars.push_back(indvar);
61✔
302
    }
61✔
303

304
    // Add tasklet block
305
    bool success = this->expand_operation(
31✔
306
        builder,
31✔
307
        analysis_manager,
31✔
308
        *last_scope,
31✔
309
        input_node_a.data(),
31✔
310
        input_node_b.data(),
31✔
311
        output_node.data(),
31✔
312
        static_cast<const types::Tensor&>(iedge_a->base_type()),
31✔
313
        static_cast<const types::Tensor&>(iedge_b->base_type()),
31✔
314
        static_cast<const types::Tensor&>(oedge.base_type()),
31✔
315
        loop_vars
31✔
316
    );
31✔
317
    if (!success) {
31✔
318
        return false;
×
319
    }
×
320

321
    // Clean up block
322
    builder.remove_memlet(block, *iedge_a);
31✔
323
    builder.remove_memlet(block, *iedge_b);
31✔
324
    builder.remove_memlet(block, oedge);
31✔
325
    builder.remove_node(block, input_node_a);
31✔
326
    // Only remove input_node_b if it's different from input_node_a
327
    if (&input_node_b != &input_node_a) {
31✔
328
        builder.remove_node(block, input_node_b);
31✔
329
    }
31✔
330
    builder.remove_node(block, output_node);
31✔
331
    builder.remove_node(block, *this);
31✔
332
    builder.remove_child(parent, index + 1);
31✔
333

334
    return true;
31✔
335
}
31✔
336

337
} // namespace tensor
338
} // namespace math
339
} // 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