• 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

62.39
/sdfg/src/offloading/data_offloading_node.cpp
1
#include "sdfg/targets/offloading/data_offloading_node.h"
2

3
#include <cstddef>
4
#include <string>
5
#include <vector>
6

7
#include "sdfg/data_flow/data_flow_graph.h"
8
#include "sdfg/data_flow/library_node.h"
9
#include "sdfg/element.h"
10
#include "sdfg/exceptions.h"
11
#include "sdfg/graph/graph.h"
12
#include "sdfg/symbolic/symbolic.h"
13

14
namespace sdfg {
15
namespace offloading {
16

17
constexpr bool dump_offload_node_ids = true;
18

19
DataOffloadingNode::DataOffloadingNode(
20
    size_t element_id,
21
    const DebugInfo& debug_info,
22
    const graph::Vertex vertex,
23
    data_flow::DataFlowGraph& parent,
24
    const data_flow::LibraryNodeCode code,
25
    const std::vector<std::string>& outputs,
26
    const std::vector<std::string>& inputs,
27
    DataTransferDirection transfer_direction,
28
    BufferLifecycle buffer_lifecycle,
29
    symbolic::Expression size
30
)
31
    : data_flow::LibraryNode(
100✔
32
          element_id, debug_info, vertex, parent, code, outputs, inputs, true, data_flow::ImplementationType_NONE
100✔
33
      ),
100✔
34
      transfer_direction_(transfer_direction), buffer_lifecycle_(buffer_lifecycle), size_(std::move(size)) {}
100✔
35

36
DataOffloadingNode::DataOffloadingNode(
37
    size_t element_id,
38
    const DebugInfo& debug_info,
39
    const graph::Vertex vertex,
40
    data_flow::DataFlowGraph& parent,
41
    const data_flow::LibraryNodeCode code,
42
    DataTransferDirection transfer_direction,
43
    BufferLifecycle buffer_lifecycle,
44
    symbolic::Expression size
45
)
46
    : DataOffloadingNode(
100✔
47
          element_id,
100✔
48
          debug_info,
100✔
49
          vertex,
100✔
50
          parent,
100✔
51
          code,
100✔
52
          output_conns(transfer_direction, buffer_lifecycle),
100✔
53
          input_conns(transfer_direction, buffer_lifecycle),
100✔
54
          transfer_direction,
100✔
55
          buffer_lifecycle,
100✔
56
          size
100✔
57
      ) {}
100✔
58

59
std::vector<std::string> DataOffloadingNode::
60
    output_conns(DataTransferDirection transfer_direction, BufferLifecycle buffer_lifecycle) {
100✔
61
    if (is_ALLOC(buffer_lifecycle)) {
100✔
62
        return {"_dev"};
44✔
63
    } else {
56✔
64
        return {};
56✔
65
    }
56✔
66
}
100✔
67

68
std::vector<std::string> DataOffloadingNode::
69
    input_conns(DataTransferDirection transfer_direction, BufferLifecycle buffer_lifecycle) {
100✔
70
    if (is_H2D(transfer_direction) && is_ALLOC(buffer_lifecycle)) {
100✔
71
        return {"_hst"};
33✔
72
    } else if (!is_NONE(transfer_direction)) {
67✔
73
        return {"_hst", "_dev"};
39✔
74
    } else if (is_FREE(buffer_lifecycle)) {
39✔
75
        return {"_dev"};
17✔
76
    } else {
17✔
77
        return {};
11✔
78
    }
11✔
79
}
100✔
80

81
int DataOffloadingNode::dev_ptr_input_idx() const {
48✔
82
    if (transfer_direction_ == DataTransferDirection::NONE && buffer_lifecycle_ == BufferLifecycle::FREE) {
48✔
83
        return 0;
5✔
84
    } else if (transfer_direction_ == DataTransferDirection::D2H) {
43✔
85
        return 1;
27✔
86
    } else if (transfer_direction_ == DataTransferDirection::H2D && buffer_lifecycle_ != BufferLifecycle::ALLOC) {
27✔
87
        return 1;
1✔
88
    } else {
15✔
89
        return -1;
15✔
90
    }
15✔
91
}
48✔
92

93
int DataOffloadingNode::host_ptr_input_idx() const {
54✔
94
    if (transfer_direction_ != DataTransferDirection::NONE) {
54✔
95
        return 0;
50✔
96
    } else {
50✔
97
        return -1;
4✔
98
    }
4✔
99
}
54✔
100

101
int DataOffloadingNode::dev_ptr_output_idx() const {
7✔
102
    if (buffer_lifecycle_ == BufferLifecycle::ALLOC) {
7✔
103
        return 0;
6✔
104
    } else {
6✔
105
        return -1;
1✔
106
    }
1✔
107
}
7✔
108

109
const std::string& DataOffloadingNode::dev_in_conn() const { return inputs_.at(dev_ptr_input_idx()); }
2✔
110

111
const std::string& DataOffloadingNode::dev_out_conn() const { return outputs_.at(dev_ptr_output_idx()); }
4✔
112

NEW
113
const std::string& DataOffloadingNode::host_in_conn() const { return inputs_.at(host_ptr_input_idx()); }
×
114

115
DataTransferDirection DataOffloadingNode::transfer_direction() const { return this->transfer_direction_; }
266✔
116

117
BufferLifecycle DataOffloadingNode::buffer_lifecycle() const { return this->buffer_lifecycle_; }
158✔
118

119
const symbolic::Expression DataOffloadingNode::size() const { return this->size_; }
181✔
120

121
const symbolic::Expression DataOffloadingNode::alloc_size() const { return this->size(); }
×
122

123
symbolic::SymbolSet DataOffloadingNode::symbols() const {
56✔
124
    if (this->size().is_null()) {
56✔
125
        return {};
×
126
    } else {
56✔
127
        return symbolic::atoms(this->size());
56✔
128
    }
56✔
129
}
56✔
130

131
void DataOffloadingNode::replace(const symbolic::Expression old_expression, const symbolic::Expression new_expression) {
15✔
132
    if (!this->size_.is_null()) {
15✔
133
        this->size_ = symbolic::subs(this->size_, old_expression, new_expression);
15✔
134
    }
15✔
135
}
15✔
136

137
std::string DataOffloadingNode::toStr() const {
×
138
    std::string direction, lifecycle;
×
139
    switch (this->transfer_direction()) {
×
140
        case DataTransferDirection::D2H:
×
141
            direction = " D2H";
×
142
            break;
×
143
        case DataTransferDirection::H2D:
×
144
            direction = " H2D";
×
145
            break;
×
146
        default:
×
147
            direction = " NONE";
×
148
            break;
×
149
    }
×
150
    switch (this->buffer_lifecycle()) {
×
151
        case BufferLifecycle::FREE:
×
152
            lifecycle = " FREE";
×
153
            break;
×
154
        case BufferLifecycle::ALLOC:
×
155
            lifecycle = " ALLOC";
×
156
            break;
×
157
        default:
×
158
            lifecycle = " NO_CHANGE";
×
159
            break;
×
160
    }
×
161
    std::string res = std::string(this->code_.value());
×
162
    if (dump_offload_node_ids) {
×
163
        res += " #" + std::to_string(element_id_);
×
164
    }
×
165
    res += direction + lifecycle;
×
166
    return res;
×
167
}
×
168

169
symbolic::Expression DataOffloadingNode::flop() const { return symbolic::zero(); }
×
170

171
bool DataOffloadingNode::is_compatible_with(const DataOffloadingNode& other) const {
22✔
172
    if (code() != other.code()) {
22✔
173
        return false;
×
174
    }
×
175
    if (!symbolic::null_safe_eq(size(), other.size())) {
22✔
176
        return false;
×
177
    }
×
178
    return true;
22✔
179
}
22✔
180

181
bool DataOffloadingNode::redundant_with(const DataOffloadingNode& other) const {
×
182
    if (!is_compatible_with(other)) {
×
183
        return false;
×
184
    }
×
185
    if ((static_cast<int8_t>(transfer_direction()) + static_cast<int8_t>(other.transfer_direction())) != 0) {
×
186
        return false; // not the inverse
×
187
    }
×
188
    if ((static_cast<int8_t>(buffer_lifecycle()) + static_cast<int8_t>(other.buffer_lifecycle())) != 0) {
×
189
        return false;
×
190
    }
×
191

192
    return true; // add more checks in sub-classes
×
193
}
×
194

195
bool DataOffloadingNode::equal_with(const DataOffloadingNode& other) const {
6✔
196
    if (!is_compatible_with(other)) {
6✔
197
        return false;
×
198
    }
×
199
    if (this->transfer_direction() != other.transfer_direction()) {
6✔
200
        return false;
×
201
    }
×
202
    if (this->buffer_lifecycle() != other.buffer_lifecycle()) {
6✔
203
        return false;
×
204
    }
×
205

206
    return true; // add more checks in sub-classes
6✔
207
}
6✔
208

209
bool DataOffloadingNode::is_d2h() const { return is_D2H(this->transfer_direction()); }
106✔
210

211
bool DataOffloadingNode::is_h2d() const { return is_H2D(this->transfer_direction()); }
98✔
212

213
bool DataOffloadingNode::has_transfer() const { return this->is_d2h() || this->is_h2d(); }
3✔
214

215
bool DataOffloadingNode::is_free() const { return is_FREE(this->buffer_lifecycle()); }
53✔
216

217
bool DataOffloadingNode::is_alloc() const { return is_ALLOC(this->buffer_lifecycle()); }
43✔
218

219
void DataOffloadingNode::remove_h2d() {
3✔
220
    if (this->is_h2d()) {
3✔
221
        if (!this->is_alloc()) {
3✔
222
            throw InvalidSDFGException("DataOffloadingNode: Tried removing h2d but node has no other purpose");
×
223
        }
×
224
        this->transfer_direction_ = DataTransferDirection::NONE;
3✔
225
        this->inputs_.erase(this->inputs_.begin()); // Standard nodes only have one, others need to override
3✔
226
    }
3✔
227
}
3✔
228

229
data_flow::PointerAccessType DataOffloadingNode::pointer_access_type(int input_idx) const {
5✔
230
    if (is_h2d() && input_idx == host_ptr_input_idx()) {
5✔
231
        return data_flow::PointerAccessMeta::create_read_only(size_, true);
3✔
232
    } else if (is_h2d() && !is_alloc() && input_idx == dev_ptr_input_idx()) {
3✔
NEW
233
        return data_flow::PointerAccessMeta::create_full_write_only(size_, true);
×
234
    } else if (is_d2h() && input_idx == dev_ptr_input_idx()) {
2✔
NEW
235
        return data_flow::PointerAccessMeta::create_read_only(size_, true);
×
236
    } else if (is_d2h() && input_idx == host_ptr_input_idx()) {
2✔
237
        return data_flow::PointerAccessMeta::create_full_write_only(size_, true);
2✔
238
    } else if (is_d2h() && is_free() && input_idx == dev_ptr_input_idx()) {
2✔
NEW
239
        return data_flow::PointerAccessMeta::create_invalidate();
×
NEW
240
    } else if (is_d2h() && !is_free() && input_idx == 1) {
×
NEW
241
        return data_flow::PointerAccessMeta::create_read_only(size_, true);
×
242
    } else {
×
243
        return LibraryNode::pointer_access_type(input_idx);
×
244
    }
×
245
}
5✔
246

247
void DataOffloadingNode::remove_free() {
3✔
248
    if (this->is_free()) {
3✔
249
        if (!this->has_transfer()) {
3✔
250
            throw InvalidSDFGException("DataOffloadingNode: Tried removing free but no data transfer direction present"
×
251
            );
×
252
        }
×
253
        this->buffer_lifecycle_ = BufferLifecycle::NO_CHANGE;
3✔
254
    }
3✔
255
}
3✔
256

257
void DataOffloadingNode::remove_d2h() {
4✔
258
    if (this->is_d2h()) {
4✔
259
        if (!this->is_free()) {
4✔
260
            throw InvalidSDFGException("DataOffloadingNode: Tried removing d2h but node has no other purpose");
×
261
        }
×
262
        this->transfer_direction_ = DataTransferDirection::NONE;
4✔
263
        this->inputs_.erase(this->inputs_.begin());
4✔
264
    }
4✔
265
}
4✔
266

267
data_flow::EdgeRemoveOption DataOffloadingNode::
UNCOV
268
    can_remove_out_edge(const data_flow::DataFlowGraph& graph, const data_flow::Memlet* memlet) const {
×
UNCOV
269
    if (graph.out_edges_for_connector(*this, memlet->src_conn()).size() > 1) {
×
270
        return data_flow::EdgeRemoveOption::Trivially;
×
NEW
271
    } else if (is_alloc() && outputs_.size() == 1 && memlet->src_conn() == outputs_.at(0)) {
×
272
        // the node in its entirety is dead if it the alloc is not needed
NEW
273
        return data_flow::EdgeRemoveOption::RemoveNodeAfter;
×
274
    } else {
×
275
        return data_flow::EdgeRemoveOption::NotRemovable;
×
276
    }
×
UNCOV
277
}
×
278

NEW
279
bool DataOffloadingNode::update_edge_removed(const std::string& out_conn) { return false; }
×
280

281
data_flow::EdgeRemoveOption DataOffloadingNode::
282
    can_remove_in_edge(const data_flow::DataFlowGraph& graph, const data_flow::Memlet* memlet) const {
1✔
283
    if (is_h2d() && is_alloc() && memlet->dst_conn() == inputs_.at(host_ptr_input_idx())) {
1✔
NEW
284
        return data_flow::EdgeRemoveOption::RequiresUpdate;
×
285
    } else if (is_d2h() && is_NO_CHANGE(this->buffer_lifecycle_) &&
1✔
286
               memlet->dst_conn() == inputs_.at(host_ptr_input_idx())) {
1✔
287
        return data_flow::EdgeRemoveOption::RemoveNodeAfter;
1✔
288
    } else {
1✔
NEW
289
        return data_flow::EdgeRemoveOption::NotRemovable;
×
NEW
290
    }
×
291
}
1✔
292

293
nlohmann::json DataOffloadingNodeSerializer::serialize(const sdfg::data_flow::LibraryNode& library_node) {
4✔
294
    const auto& node = static_cast<const DataOffloadingNode&>(library_node);
4✔
295
    nlohmann::json j;
4✔
296

297
    // Library node properties
298
    j["code"] = std::string(library_node.code().value());
4✔
299

300
    // Offloading node properties
301
    sdfg::serializer::JSONSerializer serializer;
4✔
302
    if (node.size().is_null()) {
4✔
303
        j["size"] = nlohmann::json::value_t::null;
1✔
304
    } else {
3✔
305
        j["size"] = serializer.expression(node.size());
3✔
306
    }
3✔
307
    j["transfer_direction"] = static_cast<int8_t>(node.transfer_direction());
4✔
308
    j["buffer_lifecycle"] = static_cast<int8_t>(node.buffer_lifecycle());
4✔
309

310
    return j;
4✔
311
}
4✔
312

313
} // namespace offloading
314
} // 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