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

daisytuner / docc / 23956972413

03 Apr 2026 06:17PM UTC coverage: 64.815% (+0.1%) from 64.689%
23956972413

push

github

web-flow
Merge pull request #584 from daisytuner/diamond-tiling

adds diamond tiling test

48 of 65 new or added lines in 1 file covered. (73.85%)

1 existing line in 1 file now uncovered.

29141 of 44960 relevant lines covered (64.82%)

561.4 hits per line

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

73.32
/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) {
30✔
28
    for (size_t i = 0; i < seq.size(); ++i) {
98✔
29
        auto& child = seq.at(i).first;
68✔
30
        if (auto* block = dynamic_cast<structured_control_flow::Block*>(&child)) {
68✔
31
            blocks.push_back(block);
56✔
32
        } else if (auto* loop = dynamic_cast<structured_control_flow::StructuredLoop*>(&child)) {
56✔
33
            collect_blocks(loop->root(), blocks);
12✔
34
        }
12✔
35
    }
68✔
36
}
30✔
37

38
/// Get the first block from a sequence, recursively descending into loops.
39
structured_control_flow::Block* get_first_block(structured_control_flow::Sequence& seq) {
30✔
40
    for (size_t i = 0; i < seq.size(); ++i) {
30✔
41
        auto& child = seq.at(i).first;
30✔
42
        if (auto* block = dynamic_cast<structured_control_flow::Block*>(&child)) {
30✔
43
            return block;
18✔
44
        } else if (auto* loop = dynamic_cast<structured_control_flow::StructuredLoop*>(&child)) {
18✔
45
            auto* result = get_first_block(loop->root());
12✔
46
            if (result) return result;
12✔
47
        }
12✔
48
    }
30✔
NEW
49
    return nullptr;
×
50
}
30✔
51

52
} // namespace
53

54
TileFusion::TileFusion(structured_control_flow::Map& first_map, structured_control_flow::Map& second_map)
55
    : first_map_(first_map), second_map_(second_map) {}
13✔
56

57
std::string TileFusion::name() const { return "TileFusion"; }
7✔
58

59
int TileFusion::compute_radius(
60
    const data_flow::Subset& producer_write_subset,
61
    const std::vector<data_flow::Subset>& consumer_read_subsets,
62
    const std::vector<structured_control_flow::StructuredLoop*>& producer_loops,
63
    const std::vector<structured_control_flow::StructuredLoop*>& consumer_loops,
64
    const symbolic::Assumptions& producer_assumptions,
65
    const symbolic::Assumptions& consumer_assumptions
66
) {
9✔
67
    if (producer_loops.empty() || consumer_loops.empty()) {
9✔
68
        return -1;
×
69
    }
×
70

71
    // Delinearize the producer write subset
72
    auto producer_sub = symbolic::delinearize(producer_write_subset, producer_assumptions);
9✔
73
    if (producer_sub.empty()) {
9✔
74
        return -1;
×
75
    }
×
76

77
    // Extract the innermost producer loop indvar (the spatial iteration variable)
78
    auto* producer_inner = producer_loops.back();
9✔
79
    auto producer_indvar = producer_inner->indvar();
9✔
80

81
    // Extract the innermost consumer loop indvar
82
    auto* consumer_inner = consumer_loops.back();
9✔
83
    auto consumer_indvar = consumer_inner->indvar();
9✔
84

85
    int max_radius = 0;
9✔
86

87
    for (const auto& consumer_read_subset : consumer_read_subsets) {
28✔
88
        auto consumer_sub = symbolic::delinearize(consumer_read_subset, consumer_assumptions);
28✔
89

90
        if (consumer_sub.size() != producer_sub.size()) {
28✔
91
            return -1;
×
92
        }
×
93

94
        // Solve: producer_sub[d] = consumer_sub[d] for producer_indvar
95
        // This gives us: producer_indvar = f(consumer_indvar)
96
        SymEngine::vec_sym producer_vars;
28✔
97
        producer_vars.push_back(SymEngine::rcp_static_cast<const SymEngine::Symbol>(producer_indvar));
28✔
98

99
        SymEngine::vec_basic equations;
28✔
100
        for (size_t d = 0; d < producer_sub.size(); ++d) {
77✔
101
            auto diff = symbolic::sub(producer_sub.at(d), consumer_sub.at(d));
49✔
102
            if (symbolic::uses(diff, producer_indvar->get_name())) {
49✔
103
                // This dimension depends on the producer indvar — include in system
104
                equations.push_back(diff);
28✔
105
            }
28✔
106
            // Dimensions not involving the producer indvar are independent
107
            // (e.g., inner loop variables in 2D stencils) — skip them.
108
        }
49✔
109

110
        if (equations.size() != producer_vars.size()) {
28✔
111
            return -1;
×
112
        }
×
113

114
        SymEngine::vec_basic solution;
28✔
115
        try {
28✔
116
            solution = SymEngine::linsolve(equations, producer_vars);
28✔
117
        } catch (...) {
28✔
118
            return -1;
×
119
        }
×
120

121
        if (solution.size() != 1) {
28✔
122
            return -1;
×
123
        }
×
124

125
        auto& sol = solution[0];
28✔
126
        if (SymEngine::is_a<SymEngine::NaN>(*sol) || SymEngine::is_a<SymEngine::Infty>(*sol)) {
28✔
127
            return -1;
×
128
        }
×
129

130
        // The solution is: producer_indvar = f(consumer_indvar)
131
        // The offset is: f(consumer_indvar) - consumer_indvar
132
        // For a stencil B[1+i] and consumer reads B[j], B[1+j], B[2+j]:
133
        //   solve 1+i=j   -> i = j-1, offset = -1
134
        //   solve 1+i=1+j -> i = j,   offset = 0
135
        //   solve 1+i=2+j -> i = j+1, offset = +1
136
        auto offset_expr = symbolic::expand(symbolic::sub(sol, consumer_indvar));
28✔
137

138
        // The offset should be a constant integer
139
        if (!SymEngine::is_a<SymEngine::Integer>(*offset_expr)) {
28✔
140
            return -1;
×
141
        }
×
142

143
        int offset = std::abs(static_cast<int>(SymEngine::down_cast<const SymEngine::Integer&>(*offset_expr).as_int()));
28✔
144

145
        if (offset > max_radius) {
28✔
146
            max_radius = offset;
5✔
147
        }
5✔
148
    }
28✔
149

150
    return max_radius;
9✔
151
}
9✔
152

153
bool TileFusion::can_be_applied(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
13✔
154
    candidates_.clear();
13✔
155
    radius_ = 0;
13✔
156

157
    // Get analyses upfront
158
    auto& scope_analysis = analysis_manager.get<analysis::ScopeAnalysis>();
13✔
159
    auto& loop_analysis = analysis_manager.get<analysis::LoopAnalysis>();
13✔
160
    auto& assumptions_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
13✔
161
    auto& arguments_analysis = analysis_manager.get<analysis::ArgumentsAnalysis>();
13✔
162

163
    // ==========================================================================
164
    // Criterion 1: Both maps are in the same parent sequence, consecutive
165
    // ==========================================================================
166
    auto* first_parent = scope_analysis.parent_scope(&first_map_);
13✔
167
    auto* second_parent = scope_analysis.parent_scope(&second_map_);
13✔
168
    if (first_parent == nullptr || second_parent == nullptr) {
13✔
169
        return false;
×
170
    }
×
171
    if (first_parent != second_parent) {
13✔
172
        return false;
×
173
    }
×
174

175
    auto* parent_sequence = dynamic_cast<structured_control_flow::Sequence*>(first_parent);
13✔
176
    if (parent_sequence == nullptr) {
13✔
177
        return false;
×
178
    }
×
179

180
    int first_index = parent_sequence->index(first_map_);
13✔
181
    int second_index = parent_sequence->index(second_map_);
13✔
182
    if (first_index == -1 || second_index == -1) {
13✔
183
        return false;
×
184
    }
×
185
    if (second_index != first_index + 1) {
13✔
186
        return false;
1✔
187
    }
1✔
188

189
    // ==========================================================================
190
    // Criterion 2: Transition between them is empty
191
    // ==========================================================================
192
    auto& transition = parent_sequence->at(first_index).second;
12✔
193
    if (!transition.empty()) {
12✔
194
        return false;
×
195
    }
×
196

197
    // ==========================================================================
198
    // Criterion 3: Both are perfectly nested tile structures with Maps
199
    // Structure required: outer tile Map -> inner iteration Map(s) -> Block(s)
200
    // ==========================================================================
201
    auto first_loop_info = loop_analysis.loop_info(&first_map_);
12✔
202
    auto second_loop_info = loop_analysis.loop_info(&second_map_);
12✔
203

204
    // Must be perfectly nested (no side code between loop levels)
205
    if (!first_loop_info.is_perfectly_nested) {
12✔
NEW
206
        return false;
×
NEW
207
    }
×
208
    if (!second_loop_info.is_perfectly_nested) {
12✔
NEW
209
        return false;
×
NEW
210
    }
×
211

212
    // Must be all Maps (perfectly parallel) - this is a tile fusion on parallel maps
213
    if (!first_loop_info.is_perfectly_parallel) {
12✔
NEW
214
        return false;
×
NEW
215
    }
×
216
    if (!second_loop_info.is_perfectly_parallel) {
12✔
NEW
217
        return false;
×
NEW
218
    }
×
219

220
    // Must have at least depth 2 (outer tile + inner iteration)
221
    if (first_loop_info.max_depth < 2) {
12✔
NEW
222
        return false;
×
NEW
223
    }
×
224
    if (second_loop_info.max_depth < 2) {
12✔
NEW
225
        return false;
×
NEW
226
    }
×
227

228
    // The immediate child must be a Map (the inner iteration map)
229
    if (first_map_.root().size() != 1) {
12✔
230
        return false;
×
231
    }
×
232
    auto* first_inner_map = dynamic_cast<structured_control_flow::Map*>(&first_map_.root().at(0).first);
12✔
233
    if (first_inner_map == nullptr) {
12✔
234
        return false;
×
235
    }
×
236

237
    if (second_map_.root().size() != 1) {
12✔
UNCOV
238
        return false;
×
239
    }
×
240
    auto* second_inner_map = dynamic_cast<structured_control_flow::Map*>(&second_map_.root().at(0).first);
12✔
241
    if (second_inner_map == nullptr) {
12✔
242
        return false;
×
243
    }
×
244

245
    // ==========================================================================
246
    // Criterion 4: Compatible tile bounds (same init and stride)
247
    // ==========================================================================
248
    if (!symbolic::eq(first_map_.init(), second_map_.init())) {
12✔
249
        return false;
×
250
    }
×
251

252
    auto first_stride = analysis::LoopAnalysis::stride(&first_map_);
12✔
253
    auto second_stride = analysis::LoopAnalysis::stride(&second_map_);
12✔
254
    if (first_stride.is_null() || second_stride.is_null()) {
12✔
255
        return false;
×
256
    }
×
257
    if (!symbolic::eq(first_stride, second_stride)) {
12✔
258
        return false;
1✔
259
    }
1✔
260

261
    // Extract tile size as integer
262
    if (!SymEngine::is_a<SymEngine::Integer>(*first_stride)) {
11✔
263
        return false;
×
264
    }
×
265
    int tile_size = static_cast<int>(SymEngine::down_cast<const SymEngine::Integer&>(*first_stride).as_int());
11✔
266
    if (tile_size <= 0) {
11✔
267
        return false;
×
268
    }
×
269

270
    // ==========================================================================
271
    // Criterion 5: Shared intermediate container exists
272
    // First writes C, second reads C, second does NOT write C
273
    // ==========================================================================
274
    auto first_args = arguments_analysis.arguments(analysis_manager, first_map_);
11✔
275
    auto second_args = arguments_analysis.arguments(analysis_manager, second_map_);
11✔
276

277
    std::unordered_set<std::string> first_outputs;
11✔
278
    for (const auto& [name, arg] : first_args) {
40✔
279
        if (arg.is_output) {
40✔
280
            first_outputs.insert(name);
11✔
281
        }
11✔
282
    }
40✔
283

284
    std::unordered_set<std::string> shared_containers;
11✔
285
    for (const auto& [name, arg] : second_args) {
38✔
286
        if (first_outputs.contains(name)) {
38✔
287
            if (arg.is_output) {
10✔
288
                return false; // Consumer also writes the shared container
1✔
289
            }
1✔
290
            if (arg.is_input) {
9✔
291
                shared_containers.insert(name);
9✔
292
            }
9✔
293
        }
9✔
294
    }
38✔
295
    if (shared_containers.empty()) {
10✔
296
        return false;
1✔
297
    }
1✔
298

299
    // ==========================================================================
300
    // Criterion 6: Compute radius for each shared container
301
    // ==========================================================================
302
    // Collect the loop hierarchy for producer and consumer
303
    std::vector<structured_control_flow::StructuredLoop*> producer_loops = {&first_map_, first_inner_map};
9✔
304
    std::vector<structured_control_flow::StructuredLoop*> consumer_loops = {&second_map_, second_inner_map};
9✔
305

306
    // Get assumptions from the innermost block (recursively find it)
307
    auto* first_block = get_first_block(first_inner_map->root());
9✔
308
    if (first_block == nullptr) {
9✔
NEW
309
        return false;
×
NEW
310
    }
×
311
    auto& producer_assumptions = assumptions_analysis.get(*first_block);
9✔
312

313
    auto* second_block = get_first_block(second_inner_map->root());
9✔
314
    if (second_block == nullptr) {
9✔
NEW
315
        return false;
×
NEW
316
    }
×
317
    auto& consumer_assumptions = assumptions_analysis.get(*second_block);
9✔
318

319
    // Collect all blocks in producer and consumer
320
    std::vector<structured_control_flow::Block*> producer_blocks;
9✔
321
    collect_blocks(first_inner_map->root(), producer_blocks);
9✔
322
    std::vector<structured_control_flow::Block*> consumer_blocks;
9✔
323
    collect_blocks(second_inner_map->root(), consumer_blocks);
9✔
324

325
    int overall_radius = 0;
9✔
326

327
    for (const auto& container : shared_containers) {
9✔
328
        // Find the producer write subset for this container
329
        data_flow::Subset producer_write_subset;
9✔
330
        bool found_producer = false;
9✔
331

332
        for (auto* block : producer_blocks) {
28✔
333
            auto& dataflow = block->dataflow();
28✔
334
            for (auto& node : dataflow.nodes()) {
103✔
335
                auto* access = dynamic_cast<data_flow::AccessNode*>(&node);
103✔
336
                if (access == nullptr || access->data() != container) {
103✔
337
                    continue;
91✔
338
                }
91✔
339
                // It's a write if it has incoming edges and no outgoing edges
340
                if (dataflow.in_degree(*access) > 0 && dataflow.out_degree(*access) == 0) {
12✔
341
                    auto& iedge = *dataflow.in_edges(*access).begin();
9✔
342
                    if (iedge.type() != data_flow::MemletType::Computational) {
9✔
343
                        continue;
×
344
                    }
×
345
                    if (found_producer) {
9✔
346
                        return false; // Multiple writes to same container
×
347
                    }
×
348
                    producer_write_subset = iedge.subset();
9✔
349
                    found_producer = true;
9✔
350
                }
9✔
351
            }
12✔
352
        }
28✔
353
        if (!found_producer || producer_write_subset.empty()) {
9✔
354
            return false;
×
355
        }
×
356

357
        // Collect all consumer read subsets for this container
358
        std::vector<data_flow::Subset> consumer_read_subsets;
9✔
359
        for (auto* block : consumer_blocks) {
28✔
360
            auto& dataflow = block->dataflow();
28✔
361
            for (auto& node : dataflow.nodes()) {
103✔
362
                auto* access = dynamic_cast<data_flow::AccessNode*>(&node);
103✔
363
                if (access == nullptr || access->data() != container) {
103✔
364
                    continue;
83✔
365
                }
83✔
366
                // It's a read if it has outgoing edges
367
                if (dataflow.out_degree(*access) > 0) {
20✔
368
                    for (auto& memlet : dataflow.out_edges(*access)) {
28✔
369
                        if (memlet.type() != data_flow::MemletType::Computational) {
28✔
370
                            continue;
×
371
                        }
×
372
                        auto& subset = memlet.subset();
28✔
373
                        // Deduplicate
374
                        bool found = false;
28✔
375
                        for (const auto& existing : consumer_read_subsets) {
39✔
376
                            if (existing.size() != subset.size()) continue;
39✔
377
                            bool match = true;
39✔
378
                            for (size_t d = 0; d < existing.size(); ++d) {
51✔
379
                                if (!symbolic::eq(existing[d], subset[d])) {
51✔
380
                                    match = false;
39✔
381
                                    break;
39✔
382
                                }
39✔
383
                            }
51✔
384
                            if (match) {
39✔
385
                                found = true;
×
386
                                break;
×
387
                            }
×
388
                        }
39✔
389
                        if (!found) {
28✔
390
                            consumer_read_subsets.push_back(subset);
28✔
391
                        }
28✔
392
                    }
28✔
393
                }
20✔
394
            }
20✔
395
        }
28✔
396
        if (consumer_read_subsets.empty()) {
9✔
397
            return false;
×
398
        }
×
399

400
        int radius = compute_radius(
9✔
401
            producer_write_subset,
9✔
402
            consumer_read_subsets,
9✔
403
            producer_loops,
9✔
404
            consumer_loops,
9✔
405
            producer_assumptions,
9✔
406
            consumer_assumptions
9✔
407
        );
9✔
408
        if (radius < 0) {
9✔
409
            return false;
×
410
        }
×
411

412
        // ==========================================================================
413
        // Criterion 7: Radius must be less than tile size
414
        // ==========================================================================
415
        if (radius >= tile_size) {
9✔
416
            return false;
×
417
        }
×
418

419
        if (radius > overall_radius) {
9✔
420
            overall_radius = radius;
5✔
421
        }
5✔
422

423
        TileFusionCandidate candidate;
9✔
424
        candidate.container = container;
9✔
425
        candidate.radius = radius;
9✔
426
        candidates_.push_back(candidate);
9✔
427
    }
9✔
428

429
    radius_ = overall_radius;
9✔
430
    return true;
9✔
431
}
9✔
432

433
void TileFusion::apply(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
9✔
434
    auto& sdfg = builder.subject();
9✔
435
    auto& scope_analysis = analysis_manager.get<analysis::ScopeAnalysis>();
9✔
436
    auto& assumptions_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
9✔
437

438
    // Get parent sequence
439
    auto* parent = static_cast<structured_control_flow::Sequence*>(scope_analysis.parent_scope(&first_map_));
9✔
440
    size_t first_index = parent->index(first_map_);
9✔
441

442
    // Extract tile loop properties from first map (representative)
443
    auto tile_indvar = first_map_.indvar();
9✔
444
    auto tile_init = first_map_.init();
9✔
445
    auto tile_condition = first_map_.condition();
9✔
446
    auto tile_update = first_map_.update();
9✔
447

448
    // Extract tile size
449
    auto stride = analysis::LoopAnalysis::stride(&first_map_);
9✔
450
    auto tile_size_expr = stride;
9✔
451

452
    // Get references to inner maps before moving
453
    auto* first_inner_map = dynamic_cast<structured_control_flow::Map*>(&first_map_.root().at(0).first);
9✔
454
    auto* second_inner_map = dynamic_cast<structured_control_flow::Map*>(&second_map_.root().at(0).first);
9✔
455

456
    // Extract inner map properties before they get moved
457
    auto first_inner_indvar = first_inner_map->indvar();
9✔
458
    auto first_inner_init = first_inner_map->init();
9✔
459
    auto first_inner_condition = first_inner_map->condition();
9✔
460
    auto first_inner_update = first_inner_map->update();
9✔
461
    auto first_inner_schedule = first_inner_map->schedule_type();
9✔
462

463
    auto second_inner_indvar = second_inner_map->indvar();
9✔
464
    auto second_inner_init = second_inner_map->init();
9✔
465
    auto second_inner_condition = second_inner_map->condition();
9✔
466
    auto second_inner_update = second_inner_map->update();
9✔
467
    auto second_inner_schedule = second_inner_map->schedule_type();
9✔
468

469
    // Get the old tile indvars to substitute later
470
    auto first_tile_indvar = first_map_.indvar();
9✔
471
    auto second_tile_indvar = second_map_.indvar();
9✔
472

473
    // Step 1: Create a new For tile loop with the same bounds as the first tile Map
474
    // Use the condition from the first map (which uses first_tile_indvar)
475
    // We need a fresh indvar for the new tile loop
476
    auto new_tile_indvar_name = builder.find_new_name(tile_indvar->get_name());
9✔
477
    builder.add_container(new_tile_indvar_name, sdfg.type(tile_indvar->get_name()));
9✔
478
    auto new_tile_indvar = symbolic::symbol(new_tile_indvar_name);
9✔
479

480
    // Substitute the old tile indvar in the condition with the new one
481
    auto new_tile_condition = symbolic::subs(tile_condition, tile_indvar, new_tile_indvar);
9✔
482

483
    auto& fused_for = builder.add_for_before(
9✔
484
        *parent,
9✔
485
        first_map_,
9✔
486
        new_tile_indvar,
9✔
487
        new_tile_condition,
9✔
488
        tile_init,
9✔
489
        symbolic::subs(tile_update, tile_indvar, new_tile_indvar),
9✔
490
        {},
9✔
491
        first_map_.debug_info()
9✔
492
    );
9✔
493

494
    // Step 2: Create the producer inner Map inside the fused For
495
    // Its init and condition need to reference the new tile indvar
496
    auto new_first_inner_init = symbolic::subs(first_inner_init, first_tile_indvar, new_tile_indvar);
9✔
497
    auto new_first_inner_condition = symbolic::subs(first_inner_condition, first_tile_indvar, new_tile_indvar);
9✔
498

499
    // Extend the producer's range by the radius
500
    if (radius_ > 0) {
9✔
501
        // Extend init: max(0, tile - radius) -> take the original init and subtract radius
502
        // Original init is typically: tile_indvar (i.e., the iteration starts at the tile boundary)
503
        // New init: max(original_lower_bound, new_init - radius)
504
        auto radius_expr = symbolic::integer(radius_);
5✔
505
        auto extended_init = symbolic::max(symbolic::integer(0), symbolic::sub(new_first_inner_init, radius_expr));
5✔
506
        new_first_inner_init = extended_init;
5✔
507

508
        // Extend condition: the condition has form like And(i < tile + S, i < N)
509
        // We need to adjust the tile-relative bound: i < tile + S becomes i < tile + S + radius
510
        // We do this by substituting new_tile_indvar with (new_tile_indvar + radius) in the
511
        // tile-relative part. Since the condition is a conjunction, we can reconstruct it:
512
        // Original: And(inner_indvar < new_tile_indvar + S, inner_indvar < N)
513
        // Extended: And(inner_indvar < new_tile_indvar + S + radius, inner_indvar < N)
514
        auto extended_tile_bound =
5✔
515
            symbolic::Lt(first_inner_indvar, symbolic::add(new_tile_indvar, symbolic::add(tile_size_expr, radius_expr)));
5✔
516

517
        // Get the original non-tile bound from the canonical bound of the original inner map
518
        auto canonical = analysis::LoopAnalysis::canonical_bound(first_inner_map, assumptions_analysis);
5✔
519
        if (!canonical.is_null()) {
5✔
520
            auto original_bound = symbolic::Lt(first_inner_indvar, canonical);
5✔
521
            new_first_inner_condition = symbolic::And(extended_tile_bound, original_bound);
5✔
522
        } else {
5✔
523
            new_first_inner_condition = extended_tile_bound;
×
524
        }
×
525
    }
5✔
526

527
    auto& new_first_inner = builder.add_map(
9✔
528
        fused_for.root(),
9✔
529
        first_inner_indvar,
9✔
530
        new_first_inner_condition,
9✔
531
        new_first_inner_init,
9✔
532
        first_inner_update,
9✔
533
        first_inner_schedule
9✔
534
    );
9✔
535

536
    // Move the producer body into the new inner map
537
    builder.move_children(first_inner_map->root(), new_first_inner.root());
9✔
538

539
    // Step 3: Create the consumer inner Map inside the fused For, after the producer
540
    auto new_second_inner_init = symbolic::subs(second_inner_init, second_tile_indvar, new_tile_indvar);
9✔
541
    auto new_second_inner_condition = symbolic::subs(second_inner_condition, second_tile_indvar, new_tile_indvar);
9✔
542

543
    auto& new_second_inner = builder.add_map(
9✔
544
        fused_for.root(),
9✔
545
        second_inner_indvar,
9✔
546
        new_second_inner_condition,
9✔
547
        new_second_inner_init,
9✔
548
        second_inner_update,
9✔
549
        second_inner_schedule
9✔
550
    );
9✔
551

552
    // Move the consumer body into the new inner map
553
    builder.move_children(second_inner_map->root(), new_second_inner.root());
9✔
554

555
    // Step 4: Remove the original two tile Map nests
556
    // After we've moved the children out, remove the old maps from the parent
557
    // The fused_for was inserted before first_map_, so first_map_ is now at first_index + 1
558
    // and second_map_ is at first_index + 2
559
    // Remove second first (higher index) to avoid invalidating first's index
560
    size_t current_first_index = parent->index(first_map_);
9✔
561
    size_t current_second_index = parent->index(second_map_);
9✔
562
    builder.remove_child(*parent, current_second_index);
9✔
563
    builder.remove_child(*parent, current_first_index);
9✔
564

565
    analysis_manager.invalidate_all();
9✔
566
    applied_ = true;
9✔
567
    fused_loop_ = &fused_for;
9✔
568
}
9✔
569

570
void TileFusion::to_json(nlohmann::json& j) const {
7✔
571
    j["transformation_type"] = this->name();
7✔
572
    j["subgraph"] = {
7✔
573
        {"0", {{"element_id", first_map_.element_id()}, {"type", "map"}}},
7✔
574
        {"1", {{"element_id", second_map_.element_id()}, {"type", "map"}}}
7✔
575
    };
7✔
576
    j["parameters"] = {{"radius", radius_}};
7✔
577
}
7✔
578

579
TileFusion TileFusion::from_json(builder::StructuredSDFGBuilder& builder, const nlohmann::json& desc) {
×
580
    auto first_map_id = desc["subgraph"]["0"]["element_id"].get<size_t>();
×
581
    auto second_map_id = desc["subgraph"]["1"]["element_id"].get<size_t>();
×
582

583
    auto first_element = builder.find_element_by_id(first_map_id);
×
584
    auto second_element = builder.find_element_by_id(second_map_id);
×
585

586
    if (first_element == nullptr) {
×
587
        throw InvalidTransformationDescriptionException("Element with ID " + std::to_string(first_map_id) + " not found.");
×
588
    }
×
589
    if (second_element == nullptr) {
×
590
        throw InvalidTransformationDescriptionException(
×
591
            "Element with ID " + std::to_string(second_map_id) + " not found."
×
592
        );
×
593
    }
×
594

595
    auto* first_map = dynamic_cast<structured_control_flow::Map*>(first_element);
×
596
    auto* second_map = dynamic_cast<structured_control_flow::Map*>(second_element);
×
597

598
    if (first_map == nullptr) {
×
599
        throw InvalidTransformationDescriptionException(
×
600
            "Element with ID " + std::to_string(first_map_id) + " is not a Map."
×
601
        );
×
602
    }
×
603
    if (second_map == nullptr) {
×
604
        throw InvalidTransformationDescriptionException(
×
605
            "Element with ID " + std::to_string(second_map_id) + " is not a Map."
×
606
        );
×
607
    }
×
608

609
    return TileFusion(*first_map, *second_map);
×
610
}
×
611

612
structured_control_flow::StructuredLoop* TileFusion::fused_loop() const {
×
613
    if (!applied_) {
×
614
        throw InvalidTransformationException("Accessing fused loop before apply.");
×
615
    }
×
616
    return fused_loop_;
×
617
}
×
618

619
int TileFusion::radius() const { return radius_; }
2✔
620

621
} // namespace transformations
622
} // 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