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

daisytuner / docc / 28814257460

06 Jul 2026 06:31PM UTC coverage: 62.928% (-0.03%) from 62.96%
28814257460

Pull #843

github

web-flow
Merge b6b7f08d9 into 95ea95f1f
Pull Request #843: adds type_ids for Element classes and a human-readable element_type

457 of 599 new or added lines in 121 files covered. (76.29%)

5 existing lines in 3 files now uncovered.

40598 of 64515 relevant lines covered (62.93%)

977.42 hits per line

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

29.44
/opt/src/passes/offloading/data_transfer_minimization_pass.cpp
1
#include "sdfg/passes/offloading/data_transfer_minimization_pass.h"
2

3
#include <cstddef>
4
#include <string>
5
#include <unordered_set>
6
#include <utility>
7

8
#include "sdfg/analysis/analysis.h"
9
#include "sdfg/analysis/data_transfer_elimination_analysis.h"
10
#include "sdfg/analysis/users.h"
11
#include "sdfg/data_flow/access_node.h"
12
#include "sdfg/data_flow/code_node.h"
13
#include "sdfg/data_flow/data_flow_graph.h"
14
#include "sdfg/data_flow/library_node.h"
15
#include "sdfg/data_flow/memlet.h"
16
#include "sdfg/data_flow/tasklet.h"
17
#include "sdfg/element.h"
18
#include "sdfg/exceptions.h"
19
#include "sdfg/helpers/helpers.h"
20
#include "sdfg/structured_control_flow/block.h"
21
#include "sdfg/structured_control_flow/control_flow_node.h"
22
#include "sdfg/structured_control_flow/sequence.h"
23
#include "sdfg/symbolic/symbolic.h"
24
#include "sdfg/targets/cuda/cuda_data_offloading_node.h"
25
#include "sdfg/targets/offloading/data_offloading_node.h"
26
#include "sdfg/targets/rocm/rocm_data_offloading_node.h"
27
#include "sdfg/types/pointer.h"
28
#include "sdfg/visitor/structured_sdfg_visitor.h"
29

30
namespace sdfg {
31
namespace passes {
32

33
DataTransferMinimizationPass::DataTransferMinimizationPass() {}
8✔
34

35
bool DataTransferMinimizationPass::eliminate_malloc_first_transfer(
36
    builder::StructuredSDFGBuilder& builder, analysis::OffloadHolder& malloc_holder, analysis::OffloadHolder& copy_in
37
) {
3✔
38
    // Get all relevant information
39
    std::string copy_in_device_container = copy_in.dev_data->data();
3✔
40
    DebugInfo copy_in_dst_debinfo = copy_in.dev_data->debug_info();
3✔
41

42
    // leave the malloc itself, because we have not proven yet that there is no more d2H transfer that needs it
43
    // DDE needs to be able to find it
44

45
    auto* h2d_block = dyn_cast<structured_control_flow::Block*>(copy_in.offload_node->get_parent().get_parent());
3✔
46
    builder.remove_memlet(*h2d_block, *copy_in.host_access);
3✔
47
    builder.remove_node(*h2d_block, *copy_in.host_data);
3✔
48
    copy_in.remove_h2d_parts();
3✔
49
    copy_in.offload_node->remove_h2d();
3✔
50

51
    return true;
3✔
52
}
3✔
53

54
bool DataTransferMinimizationPass::eliminate_redundant_d2h(
55
    builder::StructuredSDFGBuilder& builder, analysis::OffloadHolder& h2d, analysis::OffloadHolder& d2h
56
) {
4✔
57
    // Get all relevant information
58
    std::string copy_out_device_container = d2h.dev_data->data();
4✔
59
    DebugInfo copy_out_dst_debinfo = d2h.dev_data->debug_info();
4✔
60

61
    // leave the malloc itself, because we have not proven yet that there is no more d2H transfer that needs it
62
    // DDE needs to be able to find it
63

64
    auto* d2h_block = dyn_cast<structured_control_flow::Block*>(d2h.offload_node->get_parent().get_parent());
4✔
65
    if (d2h.offload_node->is_d2h()) {
4✔
66
        if (d2h.offload_node->is_free()) {
4✔
67
            std::string out_conn = d2h.host_access->src_conn();
4✔
68
            auto access_type = d2h.host_access->base_type().clone();
4✔
69
            builder.remove_memlet(*d2h_block, *d2h.host_access);
4✔
70
            builder.remove_node(*d2h_block, *d2h.host_data);
4✔
71
            d2h.offload_node->remove_d2h();
4✔
72
        } else { // remove it entirely
4✔
73
            builder.clear_code_node_legacy(*d2h_block, *d2h.offload_node);
×
74
        }
×
75
        d2h.remove_d2h_parts();
4✔
76
        return true;
4✔
77
    } else {
4✔
78
        return false;
×
79
    }
×
80
}
4✔
81

82
bool DataTransferMinimizationPass::eliminate_transfer_pair(
83
    builder::StructuredSDFGBuilder& builder,
84
    analysis::OffloadHolder& copy_out,
85
    analysis::OffloadHolder& copy_in,
86
    const analysis::DataTransferEliminationAnalysis& transfer_analysis,
87
    analysis::ControlFlowAnalysis& cf_analysis,
88
    bool remove_d2h
89
) {
7✔
90
    // Get all relevant information
91
    std::string copy_out_device_container = copy_out.dev_data->data();
7✔
92
    std::string copy_in_device_container = copy_in.dev_data->data();
7✔
93
    DebugInfo copy_out_src_debinfo = copy_out.dev_data->debug_info();
7✔
94
    DebugInfo copy_in_dst_debinfo = copy_in.dev_data->debug_info();
7✔
95
    DebugInfo copy_out_off_debinfo = copy_out.offload_node->debug_info();
7✔
96
    DebugInfo copy_in_off_debinfo = copy_in.offload_node->debug_info();
7✔
97

98
    const bool aliases = copy_out_device_container != copy_in_device_container;
7✔
99

100
    // When the two device buffers differ we will alias `T = S` (T == copy_in's buffer, S == copy_out's). After that
101
    // both buffers name the same storage, so any deallocation of S and of T would free it twice. Reconcile the two
102
    // device frees up front: keep the one that post-dominates the other (it outlives every use of the shared storage)
103
    // and drop the dominated one. If the lifetimes are not ordered we cannot prove a single safe free, so we bail out
104
    // before mutating anything. OffloadTransform emits these frees locally and never reconciles them, which is the
105
    // double-free this guards against.
106
    const analysis::DeviceFreeSite* free_to_drop = nullptr;
7✔
107
    bool drop_free_via_copy_out = false; // the dominated free is fused into copy_out's D2H node
7✔
108
    if (aliases) {
7✔
109
        const auto* frees_s = transfer_analysis.device_frees(copy_out_device_container);
×
110
        const auto* frees_t = transfer_analysis.device_frees(copy_in_device_container);
×
111
        const bool s_freed = frees_s != nullptr && !frees_s->empty();
×
112
        const bool t_freed = frees_t != nullptr && !frees_t->empty();
×
113
        if (s_freed && t_freed) {
×
114
            // Only the unambiguous single-free-each topology is reconcilable here.
115
            if (frees_s->size() != 1 || frees_t->size() != 1) {
×
116
                return false;
×
117
            }
×
118
            const analysis::DeviceFreeSite& free_s = frees_s->front();
×
119
            const analysis::DeviceFreeSite& free_t = frees_t->front();
×
120
            if (cf_analysis.post_dominates(*free_t.block, *free_s.block)) {
×
121
                free_to_drop = &free_s; // keep T's (later) free
×
122
            } else if (cf_analysis.post_dominates(*free_s.block, *free_t.block)) {
×
123
                free_to_drop = &free_t; // keep S's (later) free
×
124
            } else {
×
125
                return false; // frees on incomparable paths: cannot prove a single safe deallocation
×
126
            }
×
127
            // If the dominated free is fused into copy_out's D2H node we must strip only the free part (the D2H itself
128
            // may still feed a live host buffer), handled below via remove_free().
129
            drop_free_via_copy_out = free_to_drop->node == copy_out.offload_node;
×
130
        }
×
131
        // If at most one of the buffers is freed there is no double free to reconcile.
132
    }
×
133

134
    bool remove_entirely = false;
7✔
135
    // Remove what you can remove
136
    if (!remove_d2h && copy_out.offload_node->is_free()) {
7✔
137
        if (copy_out.offload_node->is_d2h()) {
7✔
138
            // Only drop copy_out's fused free if it is the deallocation we decided to remove (same-container case keeps
139
            // the historical behaviour of always dropping it).
140
            if (!aliases || drop_free_via_copy_out) {
3✔
141
                copy_out.offload_node->remove_free();
3✔
142
            }
3✔
143
        } else {
4✔
144
            remove_entirely = true;
4✔
145
        }
4✔
146
    } else if (remove_d2h) {
7✔
147
        remove_entirely = true;
×
148
    }
×
149
    if (remove_entirely) {
7✔
150
        auto* copy_out_block =
4✔
151
            dyn_cast<structured_control_flow::Block*>(copy_out.offload_node->get_parent().get_parent());
4✔
152
        builder.clear_code_node_legacy(*copy_out_block, *copy_out.offload_node);
4✔
153
    }
4✔
154

155
    auto* copy_in_block = dyn_cast<structured_control_flow::Block*>(copy_in.offload_node->get_parent().get_parent());
7✔
156
    builder.clear_code_node_legacy(*copy_in_block, *copy_in.offload_node);
7✔
157

158
    // Maps the device pointers if necessary
159
    if (aliases) {
7✔
160
        auto& container_type = builder.subject().type(copy_out_device_container);
×
161
        auto ref_type = container_type.clone();
×
162
        auto& in_access = builder.add_access(*copy_in_block, copy_out_device_container, copy_out_src_debinfo);
×
163
        auto& out_access = builder.add_access(*copy_in_block, copy_in_device_container, copy_in_dst_debinfo);
×
164
        builder.add_reference_memlet(
×
165
            *copy_in_block,
×
166
            in_access,
×
167
            out_access,
×
168
            {symbolic::zero()},
×
169
            *ref_type,
×
170
            DebugInfo::merge(copy_out_off_debinfo, copy_in_off_debinfo)
×
171
        );
×
172

173
        // Drop the dominated device free of the now-shared storage (unless it was copy_out's fused free, already
174
        // stripped above).
175
        if (free_to_drop != nullptr && !drop_free_via_copy_out) {
×
176
            builder.clear_code_node_legacy(*free_to_drop->block, *free_to_drop->node);
×
177
        }
×
178
    }
×
179

180
    return true;
7✔
181
}
7✔
182

183
bool DataTransferMinimizationPass::
184
    run_pass(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
8✔
185
    analysis::DataTransferEliminationAnalysis transfer_analysis(builder.subject(), analysis_manager);
8✔
186
    transfer_analysis.run();
8✔
187

188
    auto& cf_analysis = analysis_manager.get<analysis::ControlFlowAnalysis>();
8✔
189

190
    int removed = 0;
8✔
191

192
    for (auto& [malloc_cand, first_h2d] : transfer_analysis.empty_malloc_candidates()) {
8✔
193
        auto& malloc_holder = *malloc_cand.offload;
3✔
194

195
        DEBUG_PRINTLN(
3✔
196
            "  Elim malloc: " << "#" << malloc_holder.malloc_node->element_id() << " -> "
3✔
197
                              << (malloc_holder.host_data ? malloc_holder.host_data->data() : "-") << " / "
3✔
198
                              << "h2d+malloc: #" << first_h2d.offload_node->element_id() << " "
3✔
199
                              << (first_h2d.host_data ? first_h2d.host_data->data() : "-") << " -> "
3✔
200
                              << first_h2d.dev_data->data()
3✔
201
        );
3✔
202

203
        bool success = eliminate_malloc_first_transfer(builder, malloc_holder, first_h2d);
3✔
204

205
        if (success) {
3✔
206
            ++removed;
3✔
207
        }
3✔
208
    }
3✔
209

210
    for (auto& [h2d_cand, redundant_d2h] : transfer_analysis.redundant_d2h_candidates()) {
8✔
211
        auto& h2d_holder = *h2d_cand.offload;
6✔
212

213
        if (h2d_holder.updates_on_dev) {
6✔
214
            DEBUG_PRINTLN(
4✔
215
                "  Elim h2d: " << "#" << h2d_holder.offload_node->element_id() << " -> " << h2d_holder.host_data->data()
4✔
216
                               << " -> " << h2d_holder.dev_data->data() << " / "
4✔
217
                               << "clean d2h: #" << redundant_d2h.offload_node->element_id() << " "
4✔
218
                               << redundant_d2h.dev_data->data() << " -> " << redundant_d2h.host_data->data()
4✔
219
            );
4✔
220
            bool success = eliminate_redundant_d2h(builder, h2d_holder, redundant_d2h);
4✔
221

222
            if (success) {
4✔
223
                ++removed;
4✔
224
            }
4✔
225
        }
4✔
226
    }
6✔
227

228
    for (auto& candidate : transfer_analysis.transfer_reuse_candidates()) {
8✔
229
        auto reads = candidate.first.read_count;
7✔
230
        auto& copy_out = *candidate.first.offload;
7✔
231
        auto& copy_in = candidate.second;
7✔
232
        auto& copy_in_container = copy_in.host_data->data();
7✔
233

234
        DEBUG_PRINTLN(
7✔
235
            "  Elim hd2: #" << copy_out.offload_node->element_id() << " " << copy_out.dev_data->data() << " -> "
7✔
236
                            << (copy_out.host_data ? copy_out.host_data->data() : "-") << " / "
7✔
237
                            << "d2h: #" << copy_in.offload_node->element_id() << " "
7✔
238
                            << (copy_in.host_data ? copy_in.host_data->data() : "-") << " -> "
7✔
239
                            << copy_in.dev_data->data()
7✔
240
        );
7✔
241

242
        bool success = eliminate_transfer_pair(builder, copy_out, copy_in, transfer_analysis, cf_analysis, false);
7✔
243

244
        if (success) {
7✔
245
            ++removed;
7✔
246
        }
7✔
247
    }
7✔
248

249
    return removed > 0;
8✔
250
}
8✔
251

252
DataTransferMinimizationLegacy::
253
    DataTransferMinimizationLegacy(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager)
254
    : visitor::NonStoppingStructuredSDFGVisitor(builder, analysis_manager) {}
×
255

256
bool DataTransferMinimizationLegacy::visit() {
×
257
    DEBUG_PRINTLN("Running DataTransferMinimizationPass on " << this->builder_.subject().name());
×
258
    return visitor::NonStoppingStructuredSDFGVisitor::visit();
×
259
}
×
260

261
bool DataTransferMinimizationLegacy::accept(structured_control_flow::Sequence& sequence) {
×
262
    bool applied = false;
×
263
    offloading::DataOffloadingNode* copy_out = nullptr;
×
264
    structured_control_flow::Block* copy_out_block = nullptr;
×
265
    size_t copy_out_index = 0;
×
266

267
    // While a copy-out can be found:
268
    while (copy_out_index < sequence.size()) {
×
269
        // Find a new copy-out
270
        for (; copy_out_index < sequence.size(); copy_out_index++) {
×
NEW
271
            if (auto* block = dyn_cast<structured_control_flow::Block*>(&sequence.at(copy_out_index).first)) {
×
272
                if (block->dataflow().library_nodes().size() == 1 && block->dataflow().tasklets().size() == 0) {
×
273
                    auto* libnode = *block->dataflow().library_nodes().begin();
×
274
                    if (auto* offloading_node = dynamic_cast<offloading::DataOffloadingNode*>(libnode)) {
×
275
                        if (offloading_node->is_d2h()) {
×
276
                            copy_out = offloading_node;
×
277
                            copy_out_block = block;
×
278
                            break;
×
279
                        }
×
280
                    }
×
281
                }
×
282
            }
×
283
        }
×
284

285
        // Find a matching copy-in
286
        size_t i;
×
287
        for (i = copy_out_index; i < sequence.size(); i++) {
×
288
            // Child must be a block
NEW
289
            auto* copy_in_block = dyn_cast<structured_control_flow::Block*>(&sequence.at(i).first);
×
290
            if (!copy_in_block) {
×
291
                continue;
×
292
            }
×
293

294
            // Block must contain exactly one library node
295
            if (copy_in_block->dataflow().library_nodes().size() != 1 ||
×
296
                copy_in_block->dataflow().tasklets().size() != 0) {
×
297
                continue;
×
298
            }
×
299

300
            // Library node must be an offloading node
301
            auto* copy_in =
×
302
                dynamic_cast<offloading::DataOffloadingNode*>(*copy_in_block->dataflow().library_nodes().begin());
×
303
            if (!copy_in) {
×
304
                continue;
×
305
            }
×
306

307
            // Offloading node must be a copy-in
308
            if (!copy_in->is_h2d()) {
×
309
                continue;
×
310
            }
×
311

312
            // Copy-in and copy-out must be redundant
313
            if (!copy_out->redundant_with(*copy_in)) {
×
314
                continue;
×
315
            }
×
316

317
            // Get src and dst access nodes for copy-in & -out
318
            auto [copy_out_src, copy_out_dst] = this->get_src_and_dst(copy_out_block->dataflow(), copy_out);
×
319
            auto [copy_in_src, copy_in_dst] = this->get_src_and_dst(copy_in_block->dataflow(), copy_in);
×
320

321
            // Get the write and read users
322
            auto& users = this->analysis_manager_.get<analysis::Users>();
×
323
            analysis::User* write = users.get_user(copy_out_dst->data(), copy_out_dst, analysis::Use::WRITE);
×
324
            if (!write) {
×
325
                continue;
×
326
            }
×
327
            analysis::User* read = users.get_user(copy_in_src->data(), copy_in_src, analysis::Use::READ);
×
328
            if (!read) {
×
329
                continue;
×
330
            }
×
331

332
            if (copy_out_dst->data() == copy_in_src->data()) {
×
333
                // Ensure that the container is not written between the data transfer nodes
334
                bool used_between = false;
×
335
                for (auto* user : users.all_uses_between(*write, *read)) {
×
336
                    if (user->container() == copy_out_dst->data() && user->use() != analysis::Use::READ) {
×
337
                        used_between = true;
×
338
                        break;
×
339
                    }
×
340
                }
×
341
                if (used_between) {
×
342
                    continue;
×
343
                }
×
344
            } else {
×
345
                if (!this->check_container_dependency(
×
346
                        copy_out_block, copy_out_dst->data(), copy_in_block, copy_in_src->data()
×
347
                    )) {
×
348
                    continue;
×
349
                }
×
350
            }
×
351

352
            // Check that the container is not written after the data transfer nodes
353
            bool read_after = false;
×
354
            for (auto* user : users.all_uses_after(*write)) {
×
355
                if (user->container() == copy_out_dst->data() && user->use() == analysis::Use::READ && user != read) {
×
356
                    read_after = true;
×
357
                    break;
×
358
                }
×
359
            }
×
360

361
            // Debug output
362
            DEBUG_PRINTLN(
×
363
                "  Eliminating " << (read_after ? "(" : "") << "copy-out: #" << copy_out->element_id() << " "
×
364
                                 << copy_out_src->data() << " -> " << copy_out_dst->data() << (read_after ? ")" : "")
×
365
                                 << " / copy-in: #" << copy_in->element_id() << " " << copy_in_src->data() << " -> "
×
366
                                 << copy_in_dst->data()
×
367
            );
×
368

369
            // Get all relevant information
370
            std::string copy_out_device_container = copy_out_src->data();
×
371
            std::string copy_in_device_container = copy_in_dst->data();
×
372
            DebugInfo copy_out_src_debinfo = copy_out_src->debug_info();
×
373
            DebugInfo copy_in_dst_debinfo = copy_in_dst->debug_info();
×
374

375
            // Remove the data tranfers
376
            if (read_after && copy_out->is_free()) {
×
377
                copy_out->remove_free();
×
378
            } else if (!read_after) {
×
379
                this->builder_.clear_code_node_legacy(*copy_out_block, *copy_out);
×
380
            }
×
381
            this->builder_.clear_code_node_legacy(*copy_in_block, *copy_in);
×
382

383
            // Maps the device pointers if necessary
384
            if (copy_out_device_container != copy_in_device_container) {
×
385
                auto& container_type = this->builder_.subject().type(copy_out_device_container);
×
386
                auto ref_type = container_type.clone();
×
387
                auto& in_access =
×
388
                    this->builder_.add_access(*copy_in_block, copy_out_device_container, copy_out_src_debinfo);
×
389
                auto& out_access =
×
390
                    this->builder_.add_access(*copy_in_block, copy_in_device_container, copy_in_dst_debinfo);
×
391
                this->builder_.add_reference_memlet(
×
392
                    *copy_in_block,
×
393
                    in_access,
×
394
                    out_access,
×
395
                    {symbolic::zero()},
×
396
                    *ref_type,
×
397
                    DebugInfo::merge(copy_out->debug_info(), copy_in->debug_info())
×
398
                );
×
399
            }
×
400

401
            // Invalidate users analysis
402
            this->analysis_manager_.invalidate<analysis::Users>();
×
403
            applied = true;
×
404
            break;
×
405
        }
×
406

407
        // Skip if no matching copy-in was found
408
        if (i >= sequence.size()) {
×
409
            copy_out_index++;
×
410
        }
×
411
    }
×
412

413
    return applied;
×
414
}
×
415

416
std::pair<data_flow::AccessNode*, data_flow::AccessNode*> DataTransferMinimizationLegacy::
417
    get_src_and_dst(data_flow::DataFlowGraph& dfg, offloading::DataOffloadingNode* offloading_node) {
×
418
    if (!offloading_node->has_transfer()) {
×
419
        throw InvalidSDFGException(
×
420
            "DataTransferMinimization: Cannot get copy access nodes for offloading node without data transfers"
×
421
        );
×
422
    }
×
423
    data_flow::AccessNode *src, *dst;
×
424
    if (dynamic_cast<cuda::CUDADataOffloadingNode*>(offloading_node)) {
×
425
        src = this->get_in_access(offloading_node, "_src");
×
426
        dst = this->get_out_access(offloading_node, "_dst");
×
427
    } else if (dynamic_cast<rocm::ROCMDataOffloadingNode*>(offloading_node)) {
×
428
        src = this->get_in_access(offloading_node, "_src");
×
429
        dst = this->get_out_access(offloading_node, "_dst");
×
430
    } else {
×
431
        throw InvalidSDFGException(
×
432
            "DataTransferMinimization: Unknown offloading node encountered: " + offloading_node->code().value()
×
433
        );
×
434
    }
×
435
    return {src, dst};
×
436
}
×
437

438
data_flow::AccessNode* DataTransferMinimizationLegacy::
439
    get_in_access(data_flow::CodeNode* node, const std::string& dst_conn) {
×
440
    auto& dfg = node->get_parent();
×
441
    for (auto& iedge : dfg.in_edges(*node)) {
×
442
        if (iedge.dst_conn() == dst_conn) {
×
443
            return dynamic_cast<data_flow::AccessNode*>(&iedge.src());
×
444
        }
×
445
    }
×
446
    return nullptr;
×
447
}
×
448

449
data_flow::AccessNode* DataTransferMinimizationLegacy::
450
    get_out_access(data_flow::CodeNode* node, const std::string& src_conn) {
×
451
    auto& dfg = node->get_parent();
×
452
    for (auto& oedge : dfg.out_edges(*node)) {
×
453
        if (oedge.src_conn() == src_conn) {
×
454
            return static_cast<data_flow::AccessNode*>(&oedge.dst());
×
455
        }
×
456
    }
×
457
    return nullptr;
×
458
}
×
459

460
bool DataTransferMinimizationLegacy::check_container_dependency(
461
    structured_control_flow::Block* copy_out_block,
462
    const std::string& copy_out_container,
463
    structured_control_flow::Block* copy_in_block,
464
    const std::string& copy_in_container
465
) {
×
466
    // Simplification: Assume blocks are in the same sequence
467
    auto* copy_out_block_parent = copy_out_block->get_parent();
×
468
    auto* copy_in_block_parent = copy_in_block->get_parent();
×
NEW
469
    auto* sequence = dyn_cast<structured_control_flow::Sequence*>(copy_out_block_parent);
×
470
    if (copy_out_block_parent != copy_in_block_parent || !sequence) {
×
471
        return false;
×
472
    }
×
473

474
    std::unordered_set<std::string> copy_out_container_captures, copy_in_container_parts;
×
475
    size_t start = sequence->index(*copy_out_block);
×
476
    size_t stop = sequence->index(*copy_in_block);
×
477
    for (size_t i = start + 1; i < stop; i++) {
×
NEW
478
        auto* block = dyn_cast<structured_control_flow::Block*>(&sequence->at(i).first);
×
479
        if (!block) {
×
480
            continue;
×
481
        }
×
482

483
        auto& dfg = block->dataflow();
×
484
        for (auto* access_node : dfg.data_nodes()) {
×
485
            if (access_node->data() == copy_in_container) {
×
486
                // Only allow constant assignments
487
                for (auto& iedge : dfg.in_edges(*access_node)) {
×
488
                    auto* tasklet = dynamic_cast<data_flow::Tasklet*>(&iedge.src());
×
489
                    if (!tasklet || tasklet->code() != data_flow::TaskletCode::assign) {
×
490
                        continue;
×
491
                    }
×
492

493
                    auto& iedge2 = *dfg.in_edges(*tasklet).begin();
×
494
                    if (!dynamic_cast<data_flow::ConstantNode*>(&iedge2.src())) {
×
495
                        return false;
×
496
                    }
×
497
                }
×
498

499
                // Collect H2D container parts
500
                for (auto& oedge : dfg.out_edges(*access_node)) {
×
501
                    if (oedge.type() != data_flow::MemletType::Reference) {
×
502
                        continue;
×
503
                    }
×
504

505
                    auto* access_node2 = dynamic_cast<data_flow::AccessNode*>(&oedge.dst());
×
506
                    if (!access_node2) {
×
507
                        continue;
×
508
                    }
×
509

510
                    copy_in_container_parts.insert(access_node2->data());
×
511
                }
×
512
            } else if (access_node->data() == copy_out_container) {
×
513
                // Collect D2H container captures
514
                for (auto& oedge : dfg.out_edges(*access_node)) {
×
515
                    if (oedge.type() != data_flow::MemletType::Dereference_Dst) {
×
516
                        continue;
×
517
                    }
×
518

519
                    auto* access_node2 = dynamic_cast<data_flow::AccessNode*>(&oedge.dst());
×
520
                    if (!access_node2) {
×
521
                        continue;
×
522
                    }
×
523

524
                    copy_out_container_captures.insert(access_node2->data());
×
525
                }
×
526
            }
×
527
        }
×
528
    }
×
529

530
    // Find all matches between captures and parts
531
    size_t matches = 0;
×
532
    for (auto& capture : copy_out_container_captures) {
×
533
        for (auto& part : copy_in_container_parts) {
×
534
            if (capture == part) {
×
535
                matches++;
×
536
            }
×
537
        }
×
538
    }
×
539

540
    return (matches == 1);
×
541
}
×
542

543
} // namespace passes
544
} // 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