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

daisytuner / docc / 23944546040

03 Apr 2026 11:24AM UTC coverage: 64.733% (+0.01%) from 64.723%
23944546040

Pull #584

github

web-flow
Merge f56d5e7cb into dab37c229
Pull Request #584: adds diamond tiling test

18 of 20 new or added lines in 1 file covered. (90.0%)

79 existing lines in 1 file now uncovered.

28763 of 44433 relevant lines covered (64.73%)

471.63 hits per line

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

72.0
/opt/src/transformations/tile_fusion.cpp
1
#include "sdfg/transformations/tile_fusion.h"
2

3
#include <cmath>
4
#include <stdexcept>
5

6
#include <symengine/solve.h>
7

8
#include "sdfg/analysis/arguments_analysis.h"
9
#include "sdfg/analysis/assumptions_analysis.h"
10
#include "sdfg/analysis/loop_analysis.h"
11
#include "sdfg/analysis/scope_analysis.h"
12
#include "sdfg/analysis/users.h"
13
#include "sdfg/builder/structured_sdfg_builder.h"
14
#include "sdfg/data_flow/data_flow_graph.h"
15
#include "sdfg/structured_control_flow/block.h"
16
#include "sdfg/structured_control_flow/for.h"
17
#include "sdfg/structured_control_flow/map.h"
18
#include "sdfg/symbolic/symbolic.h"
19
#include "sdfg/symbolic/utils.h"
20

21
namespace sdfg {
22
namespace transformations {
23

24
namespace {
25

26
/// Collect all Block nodes reachable from a Sequence, including through nested For/Map loops.
27
void collect_blocks(structured_control_flow::Sequence& seq, std::vector<structured_control_flow::Block*>& blocks) {
4✔
28
    for (size_t i = 0; i < seq.size(); ++i) {
12✔
29
        auto& child = seq.at(i).first;
8✔
30
        if (auto* block = dynamic_cast<structured_control_flow::Block*>(&child)) {
8✔
31
            blocks.push_back(block);
8✔
32
        } else if (auto* loop = dynamic_cast<structured_control_flow::StructuredLoop*>(&child)) {
8✔
NEW
33
            collect_blocks(loop->root(), blocks);
×
NEW
34
        }
×
35
    }
8✔
36
}
4✔
37

38
} // namespace
39

40
TileFusion::TileFusion(structured_control_flow::Map& first_map, structured_control_flow::Map& second_map)
41
    : first_map_(first_map), second_map_(second_map) {}
6✔
42

43
std::string TileFusion::name() const { return "TileFusion"; }
×
44

45
int TileFusion::compute_radius(
46
    const data_flow::Subset& producer_write_subset,
47
    const std::vector<data_flow::Subset>& consumer_read_subsets,
48
    const std::vector<structured_control_flow::StructuredLoop*>& producer_loops,
49
    const std::vector<structured_control_flow::StructuredLoop*>& consumer_loops,
50
    const symbolic::Assumptions& producer_assumptions,
51
    const symbolic::Assumptions& consumer_assumptions
52
) {
2✔
53
    if (producer_loops.empty() || consumer_loops.empty()) {
2✔
UNCOV
54
        return -1;
×
UNCOV
55
    }
×
56

57
    // Delinearize the producer write subset
58
    auto producer_sub = symbolic::delinearize(producer_write_subset, producer_assumptions);
2✔
59
    if (producer_sub.empty()) {
2✔
UNCOV
60
        return -1;
×
UNCOV
61
    }
×
62

63
    // Extract the innermost producer loop indvar (the spatial iteration variable)
64
    auto* producer_inner = producer_loops.back();
2✔
65
    auto producer_indvar = producer_inner->indvar();
2✔
66

67
    // Extract the innermost consumer loop indvar
68
    auto* consumer_inner = consumer_loops.back();
2✔
69
    auto consumer_indvar = consumer_inner->indvar();
2✔
70

71
    int max_radius = 0;
2✔
72

73
    for (const auto& consumer_read_subset : consumer_read_subsets) {
4✔
74
        auto consumer_sub = symbolic::delinearize(consumer_read_subset, consumer_assumptions);
4✔
75

76
        if (consumer_sub.size() != producer_sub.size()) {
4✔
UNCOV
77
            return -1;
×
UNCOV
78
        }
×
79

80
        // Solve: producer_sub[d] = consumer_sub[d] for producer_indvar
81
        // This gives us: producer_indvar = f(consumer_indvar)
82
        SymEngine::vec_sym producer_vars;
4✔
83
        producer_vars.push_back(SymEngine::rcp_static_cast<const SymEngine::Symbol>(producer_indvar));
4✔
84

85
        SymEngine::vec_basic equations;
4✔
86
        for (size_t d = 0; d < producer_sub.size(); ++d) {
8✔
87
            auto diff = symbolic::sub(producer_sub.at(d), consumer_sub.at(d));
4✔
88
            if (symbolic::uses(diff, producer_indvar->get_name())) {
4✔
89
                // This dimension depends on the producer indvar — include in system
90
                equations.push_back(diff);
4✔
91
            }
4✔
92
            // Dimensions not involving the producer indvar are independent
93
            // (e.g., inner loop variables in 2D stencils) — skip them.
94
        }
4✔
95

96
        if (equations.size() != producer_vars.size()) {
4✔
UNCOV
97
            return -1;
×
UNCOV
98
        }
×
99

100
        SymEngine::vec_basic solution;
4✔
101
        try {
4✔
102
            solution = SymEngine::linsolve(equations, producer_vars);
4✔
103
        } catch (...) {
4✔
104
            return -1;
×
105
        }
×
106

107
        if (solution.size() != 1) {
4✔
UNCOV
108
            return -1;
×
UNCOV
109
        }
×
110

111
        auto& sol = solution[0];
4✔
112
        if (SymEngine::is_a<SymEngine::NaN>(*sol) || SymEngine::is_a<SymEngine::Infty>(*sol)) {
4✔
UNCOV
113
            return -1;
×
UNCOV
114
        }
×
115

116
        // The solution is: producer_indvar = f(consumer_indvar)
117
        // The offset is: f(consumer_indvar) - consumer_indvar
118
        // For a stencil B[1+i] and consumer reads B[j], B[1+j], B[2+j]:
119
        //   solve 1+i=j   -> i = j-1, offset = -1
120
        //   solve 1+i=1+j -> i = j,   offset = 0
121
        //   solve 1+i=2+j -> i = j+1, offset = +1
122
        auto offset_expr = symbolic::expand(symbolic::sub(sol, consumer_indvar));
4✔
123

124
        // The offset should be a constant integer
125
        if (!SymEngine::is_a<SymEngine::Integer>(*offset_expr)) {
4✔
UNCOV
126
            return -1;
×
127
        }
×
128

129
        int offset = std::abs(static_cast<int>(SymEngine::down_cast<const SymEngine::Integer&>(*offset_expr).as_int()));
4✔
130

131
        if (offset > max_radius) {
4✔
132
            max_radius = offset;
1✔
133
        }
1✔
134
    }
4✔
135

136
    return max_radius;
2✔
137
}
2✔
138

139
bool TileFusion::can_be_applied(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
6✔
140
    candidates_.clear();
6✔
141
    radius_ = 0;
6✔
142

143
    // Criterion 1: Both maps are in the same parent sequence, consecutive
144
    auto& scope_analysis = analysis_manager.get<analysis::ScopeAnalysis>();
6✔
145
    auto* first_parent = scope_analysis.parent_scope(&first_map_);
6✔
146
    auto* second_parent = scope_analysis.parent_scope(&second_map_);
6✔
147
    if (first_parent == nullptr || second_parent == nullptr) {
6✔
UNCOV
148
        return false;
×
UNCOV
149
    }
×
150
    if (first_parent != second_parent) {
6✔
UNCOV
151
        return false;
×
UNCOV
152
    }
×
153

154
    auto* parent_sequence = dynamic_cast<structured_control_flow::Sequence*>(first_parent);
6✔
155
    if (parent_sequence == nullptr) {
6✔
UNCOV
156
        return false;
×
157
    }
×
158

159
    int first_index = parent_sequence->index(first_map_);
6✔
160
    int second_index = parent_sequence->index(second_map_);
6✔
161
    if (first_index == -1 || second_index == -1) {
6✔
162
        return false;
×
163
    }
×
164
    if (second_index != first_index + 1) {
6✔
165
        return false;
1✔
166
    }
1✔
167

168
    // Criterion 2: Transition between them is empty
169
    auto& transition = parent_sequence->at(first_index).second;
5✔
170
    if (!transition.empty()) {
5✔
UNCOV
171
        return false;
×
172
    }
×
173

174
    // Criterion 3: Both are tiled Maps — perfectly nested with exactly one child that is a Map
175
    // First map: outer tile Map -> inner iteration Map
176
    if (first_map_.root().size() != 1) {
5✔
177
        return false;
×
178
    }
×
179
    auto* first_inner_map = dynamic_cast<structured_control_flow::Map*>(&first_map_.root().at(0).first);
5✔
180
    if (first_inner_map == nullptr) {
5✔
UNCOV
181
        return false;
×
UNCOV
182
    }
×
183
    // Inner map should not have another map nested inside (depth exactly 2)
184
    // The inner map body should contain blocks (the actual computation)
185

186
    // Second map: outer tile Map -> inner iteration Map
187
    if (second_map_.root().size() != 1) {
5✔
UNCOV
188
        return false;
×
189
    }
×
190
    auto* second_inner_map = dynamic_cast<structured_control_flow::Map*>(&second_map_.root().at(0).first);
5✔
191
    if (second_inner_map == nullptr) {
5✔
UNCOV
192
        return false;
×
UNCOV
193
    }
×
194

195
    // Criterion 4: Compatible tile bounds
196
    // Same init
197
    if (!symbolic::eq(first_map_.init(), second_map_.init())) {
5✔
198
        return false;
×
199
    }
×
200

201
    // Same tile step (stride of the outer loops)
202
    auto& assumptions_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
5✔
203
    auto first_stride = analysis::LoopAnalysis::stride(&first_map_);
5✔
204
    auto second_stride = analysis::LoopAnalysis::stride(&second_map_);
5✔
205
    if (first_stride.is_null() || second_stride.is_null()) {
5✔
206
        return false;
×
207
    }
×
208
    if (!symbolic::eq(first_stride, second_stride)) {
5✔
209
        return false;
1✔
210
    }
1✔
211

212
    // Extract tile size as integer
213
    if (!SymEngine::is_a<SymEngine::Integer>(*first_stride)) {
4✔
214
        return false;
×
215
    }
×
216
    int tile_size = static_cast<int>(SymEngine::down_cast<const SymEngine::Integer&>(*first_stride).as_int());
4✔
217
    if (tile_size <= 0) {
4✔
218
        return false;
×
219
    }
×
220

221
    // Criterion 5: Shared intermediate container
222
    // First writes C, second reads C, second does NOT write C
223
    auto& arguments_analysis = analysis_manager.get<analysis::ArgumentsAnalysis>();
4✔
224
    auto first_args = arguments_analysis.arguments(analysis_manager, first_map_);
4✔
225
    auto second_args = arguments_analysis.arguments(analysis_manager, second_map_);
4✔
226

227
    std::unordered_set<std::string> first_outputs;
4✔
228
    for (const auto& [name, arg] : first_args) {
12✔
229
        if (arg.is_output) {
12✔
230
            first_outputs.insert(name);
4✔
231
        }
4✔
232
    }
12✔
233

234
    std::unordered_set<std::string> shared_containers;
4✔
235
    for (const auto& [name, arg] : second_args) {
10✔
236
        if (first_outputs.contains(name)) {
10✔
237
            if (arg.is_output) {
3✔
238
                return false; // Consumer also writes the shared container
1✔
239
            }
1✔
240
            if (arg.is_input) {
2✔
241
                shared_containers.insert(name);
2✔
242
            }
2✔
243
        }
2✔
244
    }
10✔
245
    if (shared_containers.empty()) {
3✔
246
        return false;
1✔
247
    }
1✔
248

249
    // Criterion 6: Compute radius for each shared container
250
    // Navigate to the innermost block of each map for access analysis
251
    std::vector<structured_control_flow::StructuredLoop*> producer_loops = {&first_map_, first_inner_map};
2✔
252
    std::vector<structured_control_flow::StructuredLoop*> consumer_loops = {&second_map_, second_inner_map};
2✔
253

254
    // Get the innermost body of each map for assumptions
255
    auto& producer_assumptions = assumptions_analysis.get(first_inner_map->root().at(0).first);
2✔
256
    auto& consumer_assumptions = assumptions_analysis.get(second_inner_map->root().at(0).first);
2✔
257

258
    // Find the innermost block(s) of the producer
259
    std::vector<structured_control_flow::Block*> producer_blocks;
2✔
260
    collect_blocks(first_inner_map->root(), producer_blocks);
2✔
261
    std::vector<structured_control_flow::Block*> consumer_blocks;
2✔
262
    collect_blocks(second_inner_map->root(), consumer_blocks);
2✔
263

264
    int overall_radius = 0;
2✔
265

266
    for (const auto& container : shared_containers) {
2✔
267
        // Find the producer write subset for this container
268
        data_flow::Subset producer_write_subset;
2✔
269
        bool found_producer = false;
2✔
270

271
        for (auto* block : producer_blocks) {
4✔
272
            auto& dataflow = block->dataflow();
4✔
273
            for (auto& node : dataflow.nodes()) {
14✔
274
                auto* access = dynamic_cast<data_flow::AccessNode*>(&node);
14✔
275
                if (access == nullptr || access->data() != container) {
14✔
276
                    continue;
12✔
277
                }
12✔
278
                // It's a write if it has incoming edges and no outgoing edges
279
                if (dataflow.in_degree(*access) > 0 && dataflow.out_degree(*access) == 0) {
2✔
280
                    auto& iedge = *dataflow.in_edges(*access).begin();
2✔
281
                    if (iedge.type() != data_flow::MemletType::Computational) {
2✔
UNCOV
282
                        continue;
×
UNCOV
283
                    }
×
284
                    if (found_producer) {
2✔
UNCOV
285
                        return false; // Multiple writes to same container
×
UNCOV
286
                    }
×
287
                    producer_write_subset = iedge.subset();
2✔
288
                    found_producer = true;
2✔
289
                }
2✔
290
            }
2✔
291
        }
4✔
292
        if (!found_producer || producer_write_subset.empty()) {
2✔
UNCOV
293
            return false;
×
UNCOV
294
        }
×
295

296
        // Collect all consumer read subsets for this container
297
        std::vector<data_flow::Subset> consumer_read_subsets;
2✔
298
        for (auto* block : consumer_blocks) {
4✔
299
            auto& dataflow = block->dataflow();
4✔
300
            for (auto& node : dataflow.nodes()) {
14✔
301
                auto* access = dynamic_cast<data_flow::AccessNode*>(&node);
14✔
302
                if (access == nullptr || access->data() != container) {
14✔
303
                    continue;
11✔
304
                }
11✔
305
                // It's a read if it has outgoing edges
306
                if (dataflow.out_degree(*access) > 0) {
3✔
307
                    for (auto& memlet : dataflow.out_edges(*access)) {
4✔
308
                        if (memlet.type() != data_flow::MemletType::Computational) {
4✔
309
                            continue;
×
310
                        }
×
311
                        auto& subset = memlet.subset();
4✔
312
                        // Deduplicate
313
                        bool found = false;
4✔
314
                        for (const auto& existing : consumer_read_subsets) {
4✔
315
                            if (existing.size() != subset.size()) continue;
3✔
316
                            bool match = true;
3✔
317
                            for (size_t d = 0; d < existing.size(); ++d) {
3✔
318
                                if (!symbolic::eq(existing[d], subset[d])) {
3✔
319
                                    match = false;
3✔
320
                                    break;
3✔
321
                                }
3✔
322
                            }
3✔
323
                            if (match) {
3✔
UNCOV
324
                                found = true;
×
UNCOV
325
                                break;
×
UNCOV
326
                            }
×
327
                        }
3✔
328
                        if (!found) {
4✔
329
                            consumer_read_subsets.push_back(subset);
4✔
330
                        }
4✔
331
                    }
4✔
332
                }
3✔
333
            }
3✔
334
        }
4✔
335
        if (consumer_read_subsets.empty()) {
2✔
UNCOV
336
            return false;
×
UNCOV
337
        }
×
338

339
        int radius = compute_radius(
2✔
340
            producer_write_subset,
2✔
341
            consumer_read_subsets,
2✔
342
            producer_loops,
2✔
343
            consumer_loops,
2✔
344
            producer_assumptions,
2✔
345
            consumer_assumptions
2✔
346
        );
2✔
347
        if (radius < 0) {
2✔
UNCOV
348
            return false;
×
UNCOV
349
        }
×
350

351
        // Criterion 7: Radius must be less than tile size
352
        if (radius >= tile_size) {
2✔
UNCOV
353
            return false;
×
354
        }
×
355

356
        if (radius > overall_radius) {
2✔
357
            overall_radius = radius;
1✔
358
        }
1✔
359

360
        TileFusionCandidate candidate;
2✔
361
        candidate.container = container;
2✔
362
        candidate.radius = radius;
2✔
363
        candidates_.push_back(candidate);
2✔
364
    }
2✔
365

366
    radius_ = overall_radius;
2✔
367
    return true;
2✔
368
}
2✔
369

370
void TileFusion::apply(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
2✔
371
    auto& sdfg = builder.subject();
2✔
372
    auto& scope_analysis = analysis_manager.get<analysis::ScopeAnalysis>();
2✔
373
    auto& assumptions_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
2✔
374

375
    // Get parent sequence
376
    auto* parent = static_cast<structured_control_flow::Sequence*>(scope_analysis.parent_scope(&first_map_));
2✔
377
    size_t first_index = parent->index(first_map_);
2✔
378

379
    // Extract tile loop properties from first map (representative)
380
    auto tile_indvar = first_map_.indvar();
2✔
381
    auto tile_init = first_map_.init();
2✔
382
    auto tile_condition = first_map_.condition();
2✔
383
    auto tile_update = first_map_.update();
2✔
384

385
    // Extract tile size
386
    auto stride = analysis::LoopAnalysis::stride(&first_map_);
2✔
387
    auto tile_size_expr = stride;
2✔
388

389
    // Get references to inner maps before moving
390
    auto* first_inner_map = dynamic_cast<structured_control_flow::Map*>(&first_map_.root().at(0).first);
2✔
391
    auto* second_inner_map = dynamic_cast<structured_control_flow::Map*>(&second_map_.root().at(0).first);
2✔
392

393
    // Extract inner map properties before they get moved
394
    auto first_inner_indvar = first_inner_map->indvar();
2✔
395
    auto first_inner_init = first_inner_map->init();
2✔
396
    auto first_inner_condition = first_inner_map->condition();
2✔
397
    auto first_inner_update = first_inner_map->update();
2✔
398
    auto first_inner_schedule = first_inner_map->schedule_type();
2✔
399

400
    auto second_inner_indvar = second_inner_map->indvar();
2✔
401
    auto second_inner_init = second_inner_map->init();
2✔
402
    auto second_inner_condition = second_inner_map->condition();
2✔
403
    auto second_inner_update = second_inner_map->update();
2✔
404
    auto second_inner_schedule = second_inner_map->schedule_type();
2✔
405

406
    // Get the old tile indvars to substitute later
407
    auto first_tile_indvar = first_map_.indvar();
2✔
408
    auto second_tile_indvar = second_map_.indvar();
2✔
409

410
    // Step 1: Create a new For tile loop with the same bounds as the first tile Map
411
    // Use the condition from the first map (which uses first_tile_indvar)
412
    // We need a fresh indvar for the new tile loop
413
    auto new_tile_indvar_name = builder.find_new_name(tile_indvar->get_name());
2✔
414
    builder.add_container(new_tile_indvar_name, sdfg.type(tile_indvar->get_name()));
2✔
415
    auto new_tile_indvar = symbolic::symbol(new_tile_indvar_name);
2✔
416

417
    // Substitute the old tile indvar in the condition with the new one
418
    auto new_tile_condition = symbolic::subs(tile_condition, tile_indvar, new_tile_indvar);
2✔
419

420
    auto& fused_for = builder.add_for_before(
2✔
421
        *parent,
2✔
422
        first_map_,
2✔
423
        new_tile_indvar,
2✔
424
        new_tile_condition,
2✔
425
        tile_init,
2✔
426
        symbolic::subs(tile_update, tile_indvar, new_tile_indvar),
2✔
427
        {},
2✔
428
        first_map_.debug_info()
2✔
429
    );
2✔
430

431
    // Step 2: Create the producer inner Map inside the fused For
432
    // Its init and condition need to reference the new tile indvar
433
    auto new_first_inner_init = symbolic::subs(first_inner_init, first_tile_indvar, new_tile_indvar);
2✔
434
    auto new_first_inner_condition = symbolic::subs(first_inner_condition, first_tile_indvar, new_tile_indvar);
2✔
435

436
    // Extend the producer's range by the radius
437
    if (radius_ > 0) {
2✔
438
        // Extend init: max(0, tile - radius) -> take the original init and subtract radius
439
        // Original init is typically: tile_indvar (i.e., the iteration starts at the tile boundary)
440
        // New init: max(original_lower_bound, new_init - radius)
441
        auto radius_expr = symbolic::integer(radius_);
1✔
442
        auto extended_init = symbolic::max(symbolic::integer(0), symbolic::sub(new_first_inner_init, radius_expr));
1✔
443
        new_first_inner_init = extended_init;
1✔
444

445
        // Extend condition: the condition has form like And(i < tile + S, i < N)
446
        // We need to adjust the tile-relative bound: i < tile + S becomes i < tile + S + radius
447
        // We do this by substituting new_tile_indvar with (new_tile_indvar + radius) in the
448
        // tile-relative part. Since the condition is a conjunction, we can reconstruct it:
449
        // Original: And(inner_indvar < new_tile_indvar + S, inner_indvar < N)
450
        // Extended: And(inner_indvar < new_tile_indvar + S + radius, inner_indvar < N)
451
        auto extended_tile_bound =
1✔
452
            symbolic::Lt(first_inner_indvar, symbolic::add(new_tile_indvar, symbolic::add(tile_size_expr, radius_expr)));
1✔
453

454
        // Get the original non-tile bound from the canonical bound of the original inner map
455
        auto canonical = analysis::LoopAnalysis::canonical_bound(first_inner_map, assumptions_analysis);
1✔
456
        if (!canonical.is_null()) {
1✔
457
            auto original_bound = symbolic::Lt(first_inner_indvar, canonical);
1✔
458
            new_first_inner_condition = symbolic::And(extended_tile_bound, original_bound);
1✔
459
        } else {
1✔
UNCOV
460
            new_first_inner_condition = extended_tile_bound;
×
UNCOV
461
        }
×
462
    }
1✔
463

464
    auto& new_first_inner = builder.add_map(
2✔
465
        fused_for.root(),
2✔
466
        first_inner_indvar,
2✔
467
        new_first_inner_condition,
2✔
468
        new_first_inner_init,
2✔
469
        first_inner_update,
2✔
470
        first_inner_schedule
2✔
471
    );
2✔
472

473
    // Move the producer body into the new inner map
474
    builder.move_children(first_inner_map->root(), new_first_inner.root());
2✔
475

476
    // Step 3: Create the consumer inner Map inside the fused For, after the producer
477
    auto new_second_inner_init = symbolic::subs(second_inner_init, second_tile_indvar, new_tile_indvar);
2✔
478
    auto new_second_inner_condition = symbolic::subs(second_inner_condition, second_tile_indvar, new_tile_indvar);
2✔
479

480
    auto& new_second_inner = builder.add_map(
2✔
481
        fused_for.root(),
2✔
482
        second_inner_indvar,
2✔
483
        new_second_inner_condition,
2✔
484
        new_second_inner_init,
2✔
485
        second_inner_update,
2✔
486
        second_inner_schedule
2✔
487
    );
2✔
488

489
    // Move the consumer body into the new inner map
490
    builder.move_children(second_inner_map->root(), new_second_inner.root());
2✔
491

492
    // Step 4: Remove the original two tile Map nests
493
    // After we've moved the children out, remove the old maps from the parent
494
    // The fused_for was inserted before first_map_, so first_map_ is now at first_index + 1
495
    // and second_map_ is at first_index + 2
496
    // Remove second first (higher index) to avoid invalidating first's index
497
    size_t current_first_index = parent->index(first_map_);
2✔
498
    size_t current_second_index = parent->index(second_map_);
2✔
499
    builder.remove_child(*parent, current_second_index);
2✔
500
    builder.remove_child(*parent, current_first_index);
2✔
501

502
    analysis_manager.invalidate_all();
2✔
503
    applied_ = true;
2✔
504
    fused_loop_ = &fused_for;
2✔
505
}
2✔
506

UNCOV
507
void TileFusion::to_json(nlohmann::json& j) const {
×
UNCOV
508
    j["transformation_type"] = this->name();
×
UNCOV
509
    j["subgraph"] = {
×
UNCOV
510
        {"0", {{"element_id", first_map_.element_id()}, {"type", "map"}}},
×
UNCOV
511
        {"1", {{"element_id", second_map_.element_id()}, {"type", "map"}}}
×
UNCOV
512
    };
×
UNCOV
513
    j["parameters"] = {{"radius", radius_}};
×
UNCOV
514
}
×
515

UNCOV
516
TileFusion TileFusion::from_json(builder::StructuredSDFGBuilder& builder, const nlohmann::json& desc) {
×
UNCOV
517
    auto first_map_id = desc["subgraph"]["0"]["element_id"].get<size_t>();
×
UNCOV
518
    auto second_map_id = desc["subgraph"]["1"]["element_id"].get<size_t>();
×
519

UNCOV
520
    auto first_element = builder.find_element_by_id(first_map_id);
×
UNCOV
521
    auto second_element = builder.find_element_by_id(second_map_id);
×
522

523
    if (first_element == nullptr) {
×
524
        throw InvalidTransformationDescriptionException("Element with ID " + std::to_string(first_map_id) + " not found.");
×
UNCOV
525
    }
×
UNCOV
526
    if (second_element == nullptr) {
×
UNCOV
527
        throw InvalidTransformationDescriptionException(
×
UNCOV
528
            "Element with ID " + std::to_string(second_map_id) + " not found."
×
UNCOV
529
        );
×
UNCOV
530
    }
×
531

UNCOV
532
    auto* first_map = dynamic_cast<structured_control_flow::Map*>(first_element);
×
UNCOV
533
    auto* second_map = dynamic_cast<structured_control_flow::Map*>(second_element);
×
534

UNCOV
535
    if (first_map == nullptr) {
×
UNCOV
536
        throw InvalidTransformationDescriptionException(
×
UNCOV
537
            "Element with ID " + std::to_string(first_map_id) + " is not a Map."
×
UNCOV
538
        );
×
UNCOV
539
    }
×
UNCOV
540
    if (second_map == nullptr) {
×
UNCOV
541
        throw InvalidTransformationDescriptionException(
×
UNCOV
542
            "Element with ID " + std::to_string(second_map_id) + " is not a Map."
×
UNCOV
543
        );
×
UNCOV
544
    }
×
545

UNCOV
546
    return TileFusion(*first_map, *second_map);
×
UNCOV
547
}
×
548

UNCOV
549
structured_control_flow::StructuredLoop* TileFusion::fused_loop() const {
×
UNCOV
550
    if (!applied_) {
×
UNCOV
551
        throw InvalidTransformationException("Accessing fused loop before apply.");
×
UNCOV
552
    }
×
UNCOV
553
    return fused_loop_;
×
UNCOV
554
}
×
555

556
int TileFusion::radius() const { return radius_; }
2✔
557

558
} // namespace transformations
559
} // 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