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

daisytuner / docc / 23249128422

18 Mar 2026 02:12PM UTC coverage: 63.938% (+0.3%) from 63.617%
23249128422

Pull #584

github

web-flow
Merge 0fcde60dc into 64d54d7de
Pull Request #584: adds diamond tiling test

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

1180 existing lines in 28 files now uncovered.

26122 of 40855 relevant lines covered (63.94%)

407.69 hits per line

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

74.4
/sdfg/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) {
6✔
28
    for (size_t i = 0; i < seq.size(); ++i) {
20✔
29
        auto& child = seq.at(i).first;
14✔
30
        if (auto* block = dynamic_cast<structured_control_flow::Block*>(&child)) {
14✔
31
            blocks.push_back(block);
14✔
32
        } else if (auto* loop = dynamic_cast<structured_control_flow::StructuredLoop*>(&child)) {
14✔
NEW
33
            collect_blocks(loop->root(), blocks);
×
NEW
34
        }
×
35
    }
14✔
36
}
6✔
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) {}
7✔
42

43
std::string TileFusion::name() const { return "TileFusion"; }
1✔
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
) {
3✔
53
    if (producer_loops.empty() || consumer_loops.empty()) {
3✔
54
        return -1;
×
55
    }
×
56

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

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

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

71
    int max_radius = 0;
3✔
72

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

76
        if (consumer_sub.size() != producer_sub.size()) {
7✔
77
            return -1;
×
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;
7✔
83
        producer_vars.push_back(SymEngine::rcp_static_cast<const SymEngine::Symbol>(producer_indvar));
7✔
84

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

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

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

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

111
        auto& sol = solution[0];
7✔
112
        if (SymEngine::is_a<SymEngine::NaN>(*sol) || SymEngine::is_a<SymEngine::Infty>(*sol)) {
7✔
113
            return -1;
×
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));
7✔
123

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

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

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

136
    return max_radius;
3✔
137
}
3✔
138

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

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

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

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

168
    // Criterion 2: Transition between them is empty
169
    auto& transition = parent_sequence->at(first_index).second;
6✔
170
    if (!transition.empty()) {
6✔
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) {
6✔
177
        return false;
×
178
    }
×
179
    auto* first_inner_map = dynamic_cast<structured_control_flow::Map*>(&first_map_.root().at(0).first);
6✔
180
    if (first_inner_map == nullptr) {
6✔
181
        return false;
×
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) {
6✔
188
        return false;
×
189
    }
×
190
    auto* second_inner_map = dynamic_cast<structured_control_flow::Map*>(&second_map_.root().at(0).first);
6✔
191
    if (second_inner_map == nullptr) {
6✔
192
        return false;
×
193
    }
×
194

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

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

212
    // Extract tile size as integer
213
    if (!SymEngine::is_a<SymEngine::Integer>(*first_stride)) {
5✔
214
        return false;
×
215
    }
×
216
    int tile_size = static_cast<int>(SymEngine::down_cast<const SymEngine::Integer&>(*first_stride).as_int());
5✔
217
    if (tile_size <= 0) {
5✔
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>();
5✔
224
    auto first_args = arguments_analysis.arguments(analysis_manager, first_map_);
5✔
225
    auto second_args = arguments_analysis.arguments(analysis_manager, second_map_);
5✔
226

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

234
    std::unordered_set<std::string> shared_containers;
5✔
235
    for (const auto& [name, arg] : second_args) {
13✔
236
        if (first_outputs.contains(name)) {
13✔
237
            if (arg.is_output) {
4✔
238
                return false; // Consumer also writes the shared container
1✔
239
            }
1✔
240
            if (arg.is_input) {
3✔
241
                shared_containers.insert(name);
3✔
242
            }
3✔
243
        }
3✔
244
    }
13✔
245
    if (shared_containers.empty()) {
4✔
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};
3✔
252
    std::vector<structured_control_flow::StructuredLoop*> consumer_loops = {&second_map_, second_inner_map};
3✔
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);
3✔
256
    auto& consumer_assumptions = assumptions_analysis.get(second_inner_map->root().at(0).first);
3✔
257

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

264
    int overall_radius = 0;
3✔
265

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

271
        for (auto* block : producer_blocks) {
7✔
272
            auto& dataflow = block->dataflow();
7✔
273
            for (auto& node : dataflow.nodes()) {
25✔
274
                auto* access = dynamic_cast<data_flow::AccessNode*>(&node);
25✔
275
                if (access == nullptr || access->data() != container) {
25✔
276
                    continue;
22✔
277
                }
22✔
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) {
3✔
280
                    auto& iedge = *dataflow.in_edges(*access).begin();
3✔
281
                    if (iedge.type() != data_flow::MemletType::Computational) {
3✔
282
                        continue;
×
283
                    }
×
284
                    if (found_producer) {
3✔
285
                        return false; // Multiple writes to same container
×
286
                    }
×
287
                    producer_write_subset = iedge.subset();
3✔
288
                    found_producer = true;
3✔
289
                }
3✔
290
            }
3✔
291
        }
7✔
292
        if (!found_producer || producer_write_subset.empty()) {
3✔
293
            return false;
×
294
        }
×
295

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

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

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

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

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

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

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

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

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

385
    // Extract tile size
386
    auto stride = analysis::LoopAnalysis::stride(&first_map_);
3✔
387
    auto tile_size_expr = stride;
3✔
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);
3✔
391
    auto* second_inner_map = dynamic_cast<structured_control_flow::Map*>(&second_map_.root().at(0).first);
3✔
392

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

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

406
    // Get the old tile indvars to substitute later
407
    auto first_tile_indvar = first_map_.indvar();
3✔
408
    auto second_tile_indvar = second_map_.indvar();
3✔
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());
3✔
414
    builder.add_container(new_tile_indvar_name, sdfg.type(tile_indvar->get_name()));
3✔
415
    auto new_tile_indvar = symbolic::symbol(new_tile_indvar_name);
3✔
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);
3✔
419

420
    auto& fused_for = builder.add_for_before(
3✔
421
        *parent,
3✔
422
        first_map_,
3✔
423
        new_tile_indvar,
3✔
424
        new_tile_condition,
3✔
425
        tile_init,
3✔
426
        symbolic::subs(tile_update, tile_indvar, new_tile_indvar),
3✔
427
        {},
3✔
428
        first_map_.debug_info()
3✔
429
    );
3✔
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);
3✔
434
    auto new_first_inner_condition = symbolic::subs(first_inner_condition, first_tile_indvar, new_tile_indvar);
3✔
435

436
    // Extend the producer's range by the radius
437
    if (radius_ > 0) {
3✔
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_);
2✔
442
        auto extended_init = symbolic::max(symbolic::integer(0), symbolic::sub(new_first_inner_init, radius_expr));
2✔
443
        new_first_inner_init = extended_init;
2✔
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 =
2✔
452
            symbolic::Lt(first_inner_indvar, symbolic::add(new_tile_indvar, symbolic::add(tile_size_expr, radius_expr)));
2✔
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);
2✔
456
        if (!canonical.is_null()) {
2✔
457
            auto original_bound = symbolic::Lt(first_inner_indvar, canonical);
2✔
458
            new_first_inner_condition = symbolic::And(extended_tile_bound, original_bound);
2✔
459
        } else {
2✔
460
            new_first_inner_condition = extended_tile_bound;
×
461
        }
×
462
    }
2✔
463

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

473
    // Move the producer body into the new inner map
474
    builder.move_children(first_inner_map->root(), new_first_inner.root());
3✔
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);
3✔
478
    auto new_second_inner_condition = symbolic::subs(second_inner_condition, second_tile_indvar, new_tile_indvar);
3✔
479

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

489
    // Move the consumer body into the new inner map
490
    builder.move_children(second_inner_map->root(), new_second_inner.root());
3✔
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_);
3✔
498
    size_t current_second_index = parent->index(second_map_);
3✔
499
    builder.remove_child(*parent, current_second_index);
3✔
500
    builder.remove_child(*parent, current_first_index);
3✔
501

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

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

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

520
    auto first_element = builder.find_element_by_id(first_map_id);
×
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.");
×
525
    }
×
526
    if (second_element == nullptr) {
×
527
        throw InvalidTransformationDescriptionException(
×
528
            "Element with ID " + std::to_string(second_map_id) + " not found."
×
529
        );
×
530
    }
×
531

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

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

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

549
structured_control_flow::StructuredLoop* TileFusion::fused_loop() const {
×
550
    if (!applied_) {
×
551
        throw InvalidTransformationException("Accessing fused loop before apply.");
×
552
    }
×
553
    return fused_loop_;
×
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