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

daisytuner / docc / 26556322966

27 May 2026 03:45PM UTC coverage: 60.869% (-0.02%) from 60.886%
26556322966

push

github

web-flow
Libnode ptr edges (#719)

Migrating SDFGs to treat pointers as inputs to libNodes / Calls as scalars.
A pointer will only appear in an output edge if its actually returned from the function (like malloc).

* Stdlib, Blas and Tensor Matmul nodes were migrated to this new format. Other, currently transitory Tensor Nodes are not yet migrated.
* DOCC version was bumped to incorporate previous docc-llvm versions (up to 0.4.0) that had been counted separately.
! Until all passes consider the use / leak of pointers as uncertainty / hiding potential writes, TensorNodes are declared as general side-effect.
* Lots of utility functions to centralize the creation (and edges) of various libNodes that needed to be changed.
* Fixed & unified docc paths across python and llvm front-ends.
* Skip BlockFusion test that fails to its libNodes currently having side effects
~ Prevent a crash in DotViz when using symbolic offsets into structs
* Removing old ConstProp pass, it is not safe for the new pointer representation and should not be all too critical

961 of 1749 new or added lines in 52 files covered. (54.95%)

87 existing lines in 28 files now uncovered.

35225 of 57870 relevant lines covered (60.87%)

11046.32 hits per line

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

60.43
/sdfg/src/data_flow/memlet.cpp
1
#include <sdfg/data_flow/memlet.h>
2

3
#include "sdfg/data_flow/library_node.h"
4
#include "sdfg/data_flow/tasklet.h"
5
#include "sdfg/function.h"
6
#include "sdfg/symbolic/symbolic.h"
7
#include "sdfg/types/type.h"
8
#include "sdfg/types/utils.h"
9

10
namespace sdfg {
11
namespace data_flow {
12

13
Memlet::Memlet(
14
    size_t element_id,
15
    const DebugInfo& debug_info,
16
    const graph::Edge& edge,
17
    DataFlowGraph& parent,
18
    DataFlowNode& src,
19
    const std::string& src_conn,
20
    DataFlowNode& dst,
21
    const std::string& dst_conn,
22
    const Subset& subset,
23
    const types::IType& base_type
24
)
25
    : Element(element_id, debug_info), edge_(edge), parent_(&parent), src_(src), dst_(dst), src_conn_(src_conn),
7,382✔
26
      dst_conn_(dst_conn), subset_(subset), base_type_(base_type.clone()) {
7,382✔
27

28
      };
7,382✔
29

30
void Memlet::validate(const Function& function) const {
19,459✔
31
    // Validate subset
32
    for (const auto& dim : this->subset_) {
19,459✔
33
        // Null ptr check
34
        if (dim.is_null()) {
16,545✔
35
            throw InvalidSDFGException("Memlet: Subset dimensions cannot be null");
×
36
        }
×
37
    }
16,545✔
38

39
    // Validate connections
40
    switch (this->type()) {
19,459✔
41
        case MemletType::Computational: {
19,010✔
42
            // Criterion: Must connect a code node and an access node with void connector at access node
43
            const AccessNode* data_node = nullptr;
19,010✔
44
            const CodeNode* code_node = nullptr;
19,010✔
45
            if (this->src_conn_ == "void") {
19,010✔
46
                data_node = dynamic_cast<const AccessNode*>(&this->src_);
12,201✔
47
                code_node = dynamic_cast<const CodeNode*>(&this->dst_);
12,201✔
48
                if (!data_node || !code_node) {
12,201✔
49
                    throw InvalidSDFGException("Memlet: Computation memlets must connect a code node and an access node"
×
50
                    );
×
51
                }
×
52

53
                // Criterion: Non-void connector must be an input of the code node
54
                if (std::find(code_node->inputs().begin(), code_node->inputs().end(), this->dst_conn_) ==
12,201✔
55
                    code_node->inputs().end()) {
12,201✔
56
                    throw InvalidSDFGException("Memlet: Computation memlets must have an input in the code node");
×
57
                }
×
58
            } else if (this->dst_conn_ == "void") {
12,201✔
59
                data_node = dynamic_cast<const AccessNode*>(&this->dst_);
6,809✔
60
                code_node = dynamic_cast<const CodeNode*>(&this->src_);
6,809✔
61
                if (!data_node || !code_node) {
6,809✔
62
                    throw InvalidSDFGException("Memlet: Computation memlets must connect a code node and an access node"
×
63
                    );
×
64
                }
×
65

66
                // Criterion: Non-void connector must be an output of the code node
67
                if (std::find(code_node->outputs().begin(), code_node->outputs().end(), this->src_conn_) ==
6,809✔
68
                    code_node->outputs().end()) {
6,809✔
NEW
69
                    throw InvalidSDFGException(
×
NEW
70
                        "Memlet " + std::to_string(element_id_) + " attached to non-existent " + this->src_conn_ +
×
NEW
71
                        " connector on #" + std::to_string(code_node->element_id())
×
NEW
72
                    );
×
UNCOV
73
                }
×
74
            } else {
6,809✔
75
                throw InvalidSDFGException(
×
76
                    "Memlet: Computation memlets must have void connector at source or destination"
×
77
                );
×
78
            }
×
79

80
            // If tensor, check that the type is consistenly defined
81
            if (this->base_type_->type_id() == types::TypeID::Tensor) {
19,010✔
82
                auto& tensor_type = dynamic_cast<const types::Tensor&>(*this->base_type_);
6,699✔
83
                if (tensor_type.is_scalar()) {
6,699✔
84
                    if (auto const_node = dynamic_cast<const data_flow::ConstantNode*>(data_node)) {
78✔
85
                        if (const_node->type().type_id() != types::TypeID::Scalar) {
30✔
86
                            throw InvalidSDFGException(
×
87
                                "Memlet: Scalar tensors must reference scalar buffers. Base type: " +
×
88
                                this->base_type_->print() + " Buffer type: " + const_node->type().print()
×
89
                            );
×
90
                        }
×
91
                    } else {
48✔
92
                        auto& buffer_type = function.type(data_node->data());
48✔
93
                        if (buffer_type.type_id() != types::TypeID::Scalar) {
48✔
94
                            throw InvalidSDFGException(
×
95
                                "Memlet: Scalar tensors must reference scalar buffers. Base type: " +
×
96
                                this->base_type_->print() + " Buffer type: " + buffer_type.print()
×
97
                            );
×
98
                        }
×
99
                    }
48✔
100
                } else {
6,621✔
101
                    auto& buffer_type = function.type(data_node->data());
6,621✔
102
                    if (buffer_type.type_id() != types::TypeID::Pointer) {
6,621✔
103
                        throw InvalidSDFGException(
×
104
                            "Memlet: Non-scalar tensors must reference pointer buffers. Base type: " +
×
105
                            this->base_type_->print() + " Buffer type: " + buffer_type.print()
×
106
                        );
×
107
                    }
×
108
                    if (this->subset_.size() > tensor_type.shape().size()) {
6,621✔
109
                        throw InvalidSDFGException(
×
110
                            "Memlet: Subset dimensions must match base type dimensions. Base type: " +
×
111
                            this->base_type_->print() + " Subset Dim: " + std::to_string(this->subset_.size())
×
112
                        );
×
113
                    }
×
114
                    if (tensor_type.shape().size() != tensor_type.strides().size()) {
6,621✔
115
                        throw InvalidSDFGException(
×
116
                            "Memlet: Tensor types must have the same number of shape and stride dimensions. Base "
×
117
                            "type: " +
×
118
                            this->base_type_->print()
×
119
                        );
×
120
                    }
×
121
                }
6,621✔
122
            }
6,699✔
123
            break;
19,010✔
124
        }
19,010✔
125
        case MemletType::Reference: {
19,010✔
126
            // Criterion: Destination must be an access node with a pointer type
127
            auto dst_node = dynamic_cast<const AccessNode*>(&this->dst_);
404✔
128
            if (!dst_node) {
404✔
129
                throw InvalidSDFGException("Memlet: Reference memlets must have an access node destination");
×
130
            }
×
131
            auto dst_data = dst_node->data();
404✔
132
            // Criterion: Destination must be non-constant
133
            if (helpers::is_number(dst_data) || symbolic::is_nullptr(symbolic::symbol(dst_data))) {
404✔
134
                throw InvalidSDFGException("Memlet: Reference memlets must have a non-constant destination");
×
135
            }
×
136

137
            // Criterion: Destination must be a pointer
138
            auto& dst_type = function.type(dst_data);
404✔
139
            if (dst_type.type_id() != types::TypeID::Pointer) {
404✔
140
                throw InvalidSDFGException("Memlet: Reference memlets must have a pointer destination");
×
141
            }
×
142

143
            // Criterion: Source must be an access node
144
            if (this->src_conn_ != "void") {
404✔
145
                throw InvalidSDFGException("Memlet: Reference memlets must have a void source");
×
146
            }
×
147
            auto src_node = dynamic_cast<const AccessNode*>(&this->src_);
404✔
148
            if (!src_node) {
404✔
149
                throw InvalidSDFGException("Memlet: Reference memlets must have an access node source");
×
150
            }
×
151

152
            // Case: Constant
153
            if (helpers::is_number(src_node->data()) || symbolic::is_nullptr(symbolic::symbol(src_node->data()))) {
404✔
154
                if (!this->subset_.empty()) {
4✔
155
                    throw InvalidSDFGException("Memlet: Reference memlets for raw addresses must not have a subset");
×
156
                }
×
157
                return;
4✔
158
            }
4✔
159

160
            // Case: Container
161
            // Criterion: Must be contiguous memory reference
162
            // Throws exception if not contiguous
163
            types::infer_type(function, *this->base_type_, this->subset_);
400✔
164
            break;
400✔
165
        }
404✔
166
        case MemletType::Dereference_Src: {
27✔
167
            if (this->src_conn_ != "void") {
27✔
168
                throw InvalidSDFGException("Memlet: Dereference memlets must have a void destination");
×
169
            }
×
170

171
            auto src_node = dynamic_cast<const AccessNode*>(&this->src_);
27✔
172
            if (!src_node) {
27✔
173
                throw InvalidSDFGException("Memlet: Dereference memlets must have an access node source");
×
174
            }
×
175
            auto dst_node = dynamic_cast<const AccessNode*>(&this->dst_);
27✔
176
            if (!dst_node) {
27✔
177
                throw InvalidSDFGException("Memlet: Dereference memlets must have an access node destination");
×
178
            }
×
179

180
            // Criterion: Dereference memlets must have '0' as the only dimension
181
            if (this->subset_.size() != 1) {
27✔
182
                throw InvalidSDFGException("Memlet: Dereference memlets must have '0' as the only dimension");
×
183
            }
×
184
            if (!symbolic::eq(this->subset_[0], symbolic::zero())) {
27✔
185
                throw InvalidSDFGException("Memlet: Dereference memlets must have '0' as the only dimension");
×
186
            }
×
187

188
            // Criterion: Source must be a pointer
189
            if (auto const_node = dynamic_cast<const ConstantNode*>(src_node)) {
27✔
190
                if (const_node->type().type_id() != types::TypeID::Pointer &&
×
191
                    const_node->type().type_id() != types::TypeID::Scalar) {
×
192
                    throw InvalidSDFGException("Memlet: Dereference memlets must have a pointer source");
×
193
                }
×
194
            } else {
27✔
195
                auto src_data = src_node->data();
27✔
196
                auto& src_type = function.type(src_data);
27✔
197
                if (src_type.type_id() != types::TypeID::Pointer) {
27✔
198
                    throw InvalidSDFGException("Memlet: Dereference memlets must have a pointer source");
×
199
                }
×
200
            }
27✔
201

202
            // Criterion: Must be typed pointer
203
            auto base_pointer_type = dynamic_cast<const types::Pointer*>(this->base_type_.get());
27✔
204
            if (!base_pointer_type) {
27✔
205
                throw InvalidSDFGException("Memlet: Dereference memlets must have a typed pointer base type");
×
206
            }
×
207
            if (!base_pointer_type->has_pointee_type()) {
27✔
208
                throw InvalidSDFGException("Memlet: Dereference memlets must have a pointee type");
×
209
            }
×
210

211
            break;
27✔
212
        }
27✔
213
        case MemletType::Dereference_Dst: {
27✔
214
            if (this->dst_conn_ != "void") {
18✔
215
                throw InvalidSDFGException("Memlet: Dereference memlets must have a void source");
×
216
            }
×
217

218
            auto src_node = dynamic_cast<const AccessNode*>(&this->src_);
18✔
219
            if (!src_node) {
18✔
220
                throw InvalidSDFGException("Memlet: Dereference memlets must have an access node source");
×
221
            }
×
222
            auto dst_node = dynamic_cast<const AccessNode*>(&this->dst_);
18✔
223
            if (!dst_node) {
18✔
224
                throw InvalidSDFGException("Memlet: Dereference memlets must have an access node destination");
×
225
            }
×
226

227
            // Criterion: Dereference memlets must have '0' as the only dimension
228
            if (this->subset_.size() != 1) {
18✔
229
                throw InvalidSDFGException("Memlet: Dereference memlets must have '0' as the only dimension");
×
230
            }
×
231
            if (!symbolic::eq(this->subset_[0], symbolic::zero())) {
18✔
232
                throw InvalidSDFGException("Memlet: Dereference memlets must have '0' as the only dimension");
×
233
            }
×
234

235
            // Criterion: src type cannot be a function
236
            const sdfg::types::IType* src_type;
18✔
237
            if (auto const_node = dynamic_cast<const data_flow::ConstantNode*>(src_node)) {
18✔
238
                src_type = &const_node->type();
2✔
239
            } else {
16✔
240
                src_type = &function.type(src_node->data());
16✔
241
            }
16✔
242
            if (src_type->type_id() == types::TypeID::Function) {
18✔
243
                throw InvalidSDFGException("Memlet: Dereference memlets cannot have source of type Function");
×
244
            }
×
245

246
            // Criterion: Destination must be a pointer
247
            if (auto const_node = dynamic_cast<const ConstantNode*>(dst_node)) {
18✔
248
                throw InvalidSDFGException("Memlet: Dereference memlets must have a non-constant destination");
×
249
            }
×
250
            auto dst_data = dst_node->data();
18✔
251
            auto& dst_type = function.type(dst_data);
18✔
252
            if (dst_type.type_id() != types::TypeID::Pointer) {
18✔
253
                throw InvalidSDFGException("Memlet: Dereference memlets must have a pointer destination");
×
254
            }
×
255

256
            // Criterion: Must be typed pointer
257
            auto base_pointer_type = dynamic_cast<const types::Pointer*>(this->base_type_.get());
18✔
258
            if (!base_pointer_type) {
18✔
259
                throw InvalidSDFGException("Memlet: Dereference memlets must have a typed pointer base type");
×
260
            }
×
261
            if (!base_pointer_type->has_pointee_type()) {
18✔
262
                throw InvalidSDFGException("Memlet: Dereference memlets must have a pointee type");
×
263
            }
×
264

265
            break;
18✔
266
        }
18✔
267
        default:
18✔
268
            throw InvalidSDFGException("Memlet: Invalid memlet type");
×
269
    }
19,459✔
270
};
19,459✔
271

272
const graph::Edge Memlet::edge() const { return this->edge_; };
1,747✔
273

274
const DataFlowGraph& Memlet::get_parent() const { return *this->parent_; };
×
275

276
DataFlowGraph& Memlet::get_parent() { return *this->parent_; };
979✔
277

278
MemletType Memlet::type() const {
34,171✔
279
    if (this->dst_conn_ == "ref") {
34,171✔
280
        return Reference;
626✔
281
    } else if (this->dst_conn_ == "deref") {
33,545✔
282
        return Dereference_Src;
92✔
283
    } else if (this->src_conn_ == "deref") {
33,453✔
284
        return Dereference_Dst;
61✔
285
    } else {
33,392✔
286
        return Computational;
33,392✔
287
    }
33,392✔
288
}
34,171✔
289

290
bool Memlet::is_src_read() const {
6✔
291
    if (src_conn_ == "void" || src_conn_ == "deref") { // anything else is not an access node on the input
6✔
292
        auto t = type();
6✔
293
        if (t == Computational) {
6✔
294
            return true;
6✔
295
        }
6✔
296
        if (t == Dereference_Dst || t == Dereference_Src) {
×
297
            return true;
×
298
        }
×
299
        if (t == Reference && !subset_.empty() && base_type_ && base_type_->type_id() == types::TypeID::Pointer) {
×
300
            return true; // we hide the read of src for pointer types
×
301
        }
×
302
    }
×
303
    return false;
×
304
}
6✔
305

306
bool Memlet::is_src_direct_read() const {
×
307
    if (src_conn_ == "void" || src_conn_ == "deref") { // anything else is not an access node on the input
×
308
        auto t = type();
×
309
        if (t == Computational && base_type_ && (base_type_->type_id() != types::TypeID::Pointer || subset_.empty())) {
×
310
            return true;
×
311
        }
×
312
        if (t == Dereference_Dst) {
×
313
            return true;
×
314
        }
×
315
    }
×
316
    return false;
×
317
}
×
318

319
bool Memlet::is_src_pointed_to_read() const {
50✔
320
    if (src_conn_ == "void" || src_conn_ == "deref") {
50✔
321
        auto t = type();
50✔
322
        if (t == Dereference_Src) {
50✔
323
            return true;
×
324
        }
×
325
        if (t == Computational && base_type_->type_id() == types::TypeID::Pointer && !subset_.empty()) {
50✔
326
            return true; // implicitly reads src, because we are crazy
4✔
327
        }
4✔
328
    }
50✔
329
    return false;
46✔
330
}
50✔
331

332
bool Memlet::is_src_address_leak() const {
536✔
333
    if (src_conn_ == "void" || src_conn_ == "deref") {
536✔
334
        auto t = type();
536✔
335
        if (t == Reference) {
536✔
336
            if (subset_.empty()) {
×
337
                return true;
×
338
            }
×
339
            if (!subset_.empty() && base_type_ && base_type_->type_id() != types::TypeID::Pointer) {
×
340
                return true;
×
341
            }
×
342
        }
×
343
    }
536✔
344
    return false;
536✔
345
}
536✔
346

347
bool Memlet::is_src_pointed_to_address_leak(const types::IType& src_type) const {
1,360✔
348
    if (src_conn_ == "void" || src_conn_ == "deref") {
1,360✔
349
        auto t = type();
1,360✔
350
        if (src_type.type_id() == types::TypeID::Pointer) { // even if we use it as integer
1,360✔
351
            if (t == Computational && base_type_ && base_type_->type_id() != types::TypeID::Pointer) { // reinterpret as
1,069✔
352
                                                                                                       // not pointer,
353
                                                                                                       // but the
354
                                                                                                       // pointer is
355
                                                                                                       // still read
356
                return true;
2✔
357
            }
2✔
358
            if (t == Dereference_Dst) {
1,067✔
359
                return true;
2✔
360
            }
2✔
361
        }
1,067✔
362
        if (base_type_ && base_type_->type_id() == types::TypeID::Pointer) { // read as pointer, so more hidden things
1,356✔
363
                                                                             // possible
364
            if (t == Reference && !subset_.empty()) { // = address calc of ptr + subsets
1,085✔
365
                return true;
2✔
366
            }
2✔
367
            if (t == Dereference_Dst) { // straight reads the contents of the ptr
1,083✔
368
                return true;
×
369
            }
×
370
            if (t == Computational && subset().empty()) {
1,083✔
371
                return true;
76✔
372
            }
76✔
373
        }
1,083✔
374
    }
1,356✔
375
    return false;
1,278✔
376
}
1,360✔
377

378
bool Memlet::is_dst_write() const {
348✔
379
    if (dst_conn_ == "void" || dst_conn_ == "deref" || dst_conn_ == "ref") { // everyting else is not an access node at
348✔
380
                                                                             // dst
381
        auto t = type();
348✔
382
        if (t == Reference) {
348✔
383
            return true;
1✔
384
        }
1✔
385
        if (t == Computational) { // we already checked that dst is access node. So subset & type must be for that
347✔
386
            if (base_type_ && (base_type_->type_id() != types::TypeID::Pointer || subset_.empty())) {
346✔
387
                return true; // we either write to dst contents or if its a pointer, not to sth. indirect
221✔
388
            }
221✔
389
        }
346✔
390
        if (t == Dereference_Src) {
126✔
391
            return true;
×
392
        }
×
393
    }
126✔
394
    return false;
126✔
395
}
348✔
396

397
bool Memlet::is_dst_read() const {
×
398
    if (dst_conn_ == "void" || dst_conn_ == "deref" || dst_conn_ == "ref") {
×
399
        // everyting else is not an access node at dst
400
        auto t = type();
×
401
        if (t == Dereference_Dst) {
×
402
            return true;
×
403
        }
×
404
        if (t == Computational) { // we already checked that dst is access node. So subset & type must be for that
×
405
            if (base_type_ && base_type_->type_id() == types::TypeID::Pointer && !subset_.empty()) {
×
406
                return true; // we use dst only as base address for the actual write
×
407
            }
×
408
        }
×
409
    }
×
410
    return false;
×
411
}
×
412

413
bool Memlet::is_dst_pointed_to_write() const {
30✔
414
    if (dst_conn_ == "void" || dst_conn_ == "deref" || dst_conn_ == "ref") {
30✔
415
        auto t = type();
30✔
416
        if (t == Dereference_Dst) {
30✔
417
            return true;
×
418
        }
×
419
        if (t == Computational) { // we already checked that dst is access node. So subset & type must be for that
30✔
420
            if (!subset_.empty() && base_type_ && base_type_->type_id() == types::TypeID::Pointer) {
30✔
421
                return true; // we use dst only as base address for the actual write
7✔
422
            }
7✔
423
        }
30✔
424
    }
30✔
425
    return false;
23✔
426
}
30✔
427

428
const DataFlowNode& Memlet::src() const { return this->src_; };
3,853✔
429

430
DataFlowNode& Memlet::src() { return this->src_; };
11,189✔
431

432
const DataFlowNode& Memlet::dst() const { return this->dst_; };
1,888✔
433

434
DataFlowNode& Memlet::dst() { return this->dst_; };
3,376✔
435

436
const std::string& Memlet::src_conn() const { return this->src_conn_; };
612✔
437

438
const std::string& Memlet::dst_conn() const { return this->dst_conn_; };
9,657✔
439

440
const Subset& Memlet::subset() const { return this->subset_; };
22,071✔
441

442
void Memlet::set_subset(const Subset& subset) { this->subset_ = subset; };
153✔
443

444
const types::IType& Memlet::base_type() const { return *this->base_type_; };
10,460✔
445

446
void Memlet::set_base_type(const types::IType& base_type) { this->base_type_ = base_type.clone(); };
126✔
447

448
std::unique_ptr<types::IType> Memlet::result_type(const Function& function) const {
5,986✔
449
    return types::infer_type(function, *this->base_type_, this->subset_);
5,986✔
450
};
5,986✔
451

452
std::unique_ptr<Memlet> Memlet::clone(
453
    size_t element_id, const graph::Edge& edge, DataFlowGraph& parent, DataFlowNode& src, DataFlowNode& dst
454
) const {
14✔
455
    return std::unique_ptr<Memlet>(new Memlet(
14✔
456
        element_id,
14✔
457
        this->debug_info_,
14✔
458
        edge,
14✔
459
        parent,
14✔
460
        src,
14✔
461
        this->src_conn_,
14✔
462
        dst,
14✔
463
        this->dst_conn_,
14✔
464
        this->subset_,
14✔
465
        *this->base_type_
14✔
466
    ));
14✔
467
};
14✔
468

469
void Memlet::replace(const symbolic::Expression old_expression, const symbolic::Expression new_expression) {
605✔
470
    Subset new_subset;
605✔
471
    for (auto& dim : this->subset_) {
623✔
472
        new_subset.push_back(symbolic::subs(dim, old_expression, new_expression));
623✔
473
    }
623✔
474
    this->subset_ = new_subset;
605✔
475
};
605✔
476

477
} // namespace data_flow
478
} // 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