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

daisytuner / docc / 24408765437

14 Apr 2026 03:47PM UTC coverage: 64.417% (-0.05%) from 64.469%
24408765437

push

github

web-flow
Add support for DDE to remove mallocs and frees as dead-code when DataOffloadingNodes operate on them (#678)

 + generic modeling for libNodes to declare that an input pointer does not actually escape through them
 + draft of generic modeling of input-pointer access ranges and access types (read-only, full overwrite etc.)
 + DDE: Offload-Transfer-specific handling to reinterpret their fake output edges instead as only indirect writes vie the target pointer
 + DDE: option to disable all the legacy code that requires full UserAnalysis and DataDependencyAnalysis.
 * the new malloc/heap removal in DDE can now remove containers by itself, not relying on DataDependencyAnalysis for that
 * improved builder.clear_node, node can report how to deal with a dead edge, including calling an update method on the node to make it validate again after edge removal.
 + Used for OffloadTransfer nodes to support removing the output edge via dead-code and drop the transfer and maybe the entire node if it does nothing anymore.
 * Offloading: option to quickly disable omitting d2h for read-only data

92 of 162 new or added lines in 11 files covered. (56.79%)

4 existing lines in 3 files now uncovered.

30598 of 47500 relevant lines covered (64.42%)

582.72 hits per line

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

37.04
/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 = false;
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(
85✔
32
          element_id, debug_info, vertex, parent, code, outputs, inputs, true, data_flow::ImplementationType_NONE
85✔
33
      ),
85✔
34
      transfer_direction_(transfer_direction), buffer_lifecycle_(buffer_lifecycle), size_(std::move(size)) {}
85✔
35

36
DataTransferDirection DataOffloadingNode::transfer_direction() const { return this->transfer_direction_; }
215✔
37

38
BufferLifecycle DataOffloadingNode::buffer_lifecycle() const { return this->buffer_lifecycle_; }
96✔
39

40
const symbolic::Expression DataOffloadingNode::size() const { return this->size_; }
109✔
41

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

44
symbolic::SymbolSet DataOffloadingNode::symbols() const {
32✔
45
    if (this->size().is_null()) {
32✔
46
        return {};
×
47
    } else {
32✔
48
        return symbolic::atoms(this->size());
32✔
49
    }
32✔
50
}
32✔
51

52
void DataOffloadingNode::replace(const symbolic::Expression old_expression, const symbolic::Expression new_expression) {
15✔
53
    if (!this->size_.is_null()) {
15✔
54
        this->size_ = symbolic::subs(this->size_, old_expression, new_expression);
15✔
55
    }
15✔
56
}
15✔
57

58
std::string DataOffloadingNode::toStr() const {
×
59
    std::string direction, lifecycle;
×
60
    switch (this->transfer_direction()) {
×
61
        case DataTransferDirection::D2H:
×
62
            direction = " D2H";
×
63
            break;
×
64
        case DataTransferDirection::H2D:
×
65
            direction = " H2D";
×
66
            break;
×
67
        default:
×
68
            direction = " NONE";
×
69
            break;
×
70
    }
×
71
    switch (this->buffer_lifecycle()) {
×
72
        case BufferLifecycle::FREE:
×
73
            lifecycle = " FREE";
×
74
            break;
×
75
        case BufferLifecycle::ALLOC:
×
76
            lifecycle = " ALLOC";
×
77
            break;
×
78
        default:
×
79
            lifecycle = " NO_CHANGE";
×
80
            break;
×
81
    }
×
82
    std::string res = std::string(this->code_.value());
×
83
    if (dump_offload_node_ids) {
×
84
        res += " #" + std::to_string(element_id_);
×
85
    }
×
86
    res += direction + lifecycle;
×
87
    return res;
×
88
}
×
89

90
symbolic::Expression DataOffloadingNode::flop() const { return symbolic::zero(); }
×
91

92
bool DataOffloadingNode::redundant_with(const DataOffloadingNode& other) const {
2✔
93
    if (code() != other.code()) {
2✔
94
        return false;
×
95
    }
×
96
    if ((static_cast<int8_t>(transfer_direction()) + static_cast<int8_t>(other.transfer_direction())) != 0) {
2✔
97
        return false; // not the inverse
×
98
    }
×
99
    if ((static_cast<int8_t>(buffer_lifecycle()) + static_cast<int8_t>(other.buffer_lifecycle())) != 0) {
2✔
100
        return false;
×
101
    }
×
102

103
    if (!symbolic::null_safe_eq(size(), other.size())) {
2✔
104
        return false;
×
105
    }
×
106

107
    return true; // add more checks in sub-classes
2✔
108
}
2✔
109

110
bool DataOffloadingNode::equal_with(const DataOffloadingNode& other) const {
8✔
111
    if (code() != other.code()) {
8✔
112
        return false;
×
113
    }
×
114
    if (this->transfer_direction() != other.transfer_direction()) {
8✔
115
        return false;
×
116
    }
×
117
    if (this->buffer_lifecycle() != other.buffer_lifecycle()) {
8✔
118
        return false;
×
119
    }
×
120

121
    if (!symbolic::null_safe_eq(size(), other.size())) {
8✔
122
        return false;
×
123
    }
×
124

125
    return true; // add more checks in sub-classes
8✔
126
}
8✔
127

128
bool DataOffloadingNode::is_d2h() const { return is_D2H(this->transfer_direction()); }
94✔
129

130
bool DataOffloadingNode::is_h2d() const { return is_H2D(this->transfer_direction()); }
83✔
131

132
bool DataOffloadingNode::has_transfer() const { return this->is_d2h() || this->is_h2d(); }
13✔
133

134
bool DataOffloadingNode::is_free() const { return is_FREE(this->buffer_lifecycle()); }
32✔
135

136
bool DataOffloadingNode::is_alloc() const { return is_ALLOC(this->buffer_lifecycle()); }
36✔
137

138
void DataOffloadingNode::remove_h2d() {
1✔
139
    if (this->is_h2d()) {
1✔
140
        if (!this->is_alloc()) {
1✔
141
            throw InvalidSDFGException("DataOffloadingNode: Tried removing h2d but node has no other purpose");
×
142
        }
×
143
        this->transfer_direction_ = DataTransferDirection::NONE;
1✔
144
        this->inputs_.erase(this->inputs_.begin()); // Standard nodes only have one, others need to override
1✔
145
    }
1✔
146
}
1✔
147

NEW
148
data_flow::PointerAccessType DataOffloadingNode::pointer_access_type(int input_idx) const {
×
NEW
149
    if (is_h2d() && input_idx == 0) {
×
NEW
150
        return data_flow::PointerReadOnly(size_, true);
×
NEW
151
    } else {
×
NEW
152
        return LibraryNode::pointer_access_type(input_idx);
×
NEW
153
    }
×
NEW
154
}
×
155

156
void DataOffloadingNode::remove_free() {
1✔
157
    if (this->is_free()) {
1✔
158
        if (!this->has_transfer()) {
1✔
159
            throw InvalidSDFGException("DataOffloadingNode: Tried removing free but no data transfer direction present"
×
160
            );
×
161
        }
×
162
        this->buffer_lifecycle_ = BufferLifecycle::NO_CHANGE;
1✔
163
    }
1✔
164
}
1✔
165

166
data_flow::EdgeRemoveOption DataOffloadingNode::
NEW
167
    can_remove_out_edge(const data_flow::DataFlowGraph& graph, const data_flow::Memlet* memlet) const {
×
NEW
168
    if (graph.out_edges_for_connector(*this, memlet->src_conn()).size() > 1) {
×
NEW
169
        return data_flow::EdgeRemoveOption::Trivially;
×
NEW
170
    } else if (transfer_direction_ != DataTransferDirection::NONE && outputs_.size() == 1 &&
×
NEW
171
               memlet->src_conn() == outputs_.at(0)) {
×
172
        // the node represents a transfer, whose output is dead.
NEW
173
        if (buffer_lifecycle_ != BufferLifecycle::NO_CHANGE) {
×
174
            // the node still has remaining purpose without the transfer
NEW
175
            return data_flow::EdgeRemoveOption::RequiresUpdate;
×
NEW
176
        } else {
×
177
            // the node in its entirety is dead if it the transfer is not needed
NEW
178
            return data_flow::EdgeRemoveOption::RemoveNodeAfter;
×
NEW
179
        }
×
NEW
180
    } else {
×
NEW
181
        return data_flow::EdgeRemoveOption::NotRemovable;
×
NEW
182
    }
×
NEW
183
}
×
184

NEW
185
bool DataOffloadingNode::update_edge_removed(const std::string& out_conn) {
×
NEW
186
    if (transfer_direction_ != DataTransferDirection::NONE && outputs_.size() == 1 && out_conn == outputs_.at(0)) {
×
NEW
187
        transfer_direction_ = DataTransferDirection::NONE;
×
NEW
188
        outputs_.erase(outputs_.begin());
×
NEW
189
        return true;
×
NEW
190
    } else {
×
NEW
191
        return false;
×
NEW
192
    }
×
NEW
193
}
×
194

195
} // namespace offloading
196
} // 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