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

daisytuner / docc / 31009380971

05 Aug 2026 01:15PM UTC coverage: 65.103% (+0.1%) from 65.005%
31009380971

Pull #814

github

web-flow
Merge 268a080ae into 7d5b198bd
Pull Request #814: Adds GPU reduce dispatchers

409 of 663 new or added lines in 18 files covered. (61.69%)

145 existing lines in 6 files now uncovered.

46693 of 71722 relevant lines covered (65.1%)

713.1 hits per line

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

66.86
/opt/src/targets/gpu/gpu_reduce_dispatcher.cpp
1
#include "sdfg/targets/gpu/gpu_reduce_dispatcher.h"
2

3
#include <algorithm>
4
#include <cstddef>
5
#include <list>
6
#include <memory>
7
#include <string>
8
#include <vector>
9

10
#include <sdfg/analysis/analysis.h>
11
#include <sdfg/analysis/arguments_analysis.h>
12
#include <sdfg/analysis/loop_analysis.h>
13
#include <sdfg/codegen/dispatchers/sequence_dispatcher.h>
14
#include <sdfg/data_flow/access_node.h>
15
#include <sdfg/data_flow/memlet.h>
16
#include <sdfg/exceptions.h>
17
#include <sdfg/helpers/helpers.h>
18
#include <sdfg/structured_control_flow/block.h>
19
#include <sdfg/structured_control_flow/map.h>
20
#include <sdfg/structured_control_flow/reduce.h>
21
#include <sdfg/structured_control_flow/structured_loop.h>
22
#include <sdfg/symbolic/symbolic.h>
23
#include <sdfg/types/scalar.h>
24
#include <sdfg/types/type.h>
25

26
#include "sdfg/targets/gpu/gpu_schedule_type.h"
27

28
namespace sdfg {
29
namespace gpu {
30

31
namespace {
32

33
using structured_control_flow::ReductionOperation;
34

35
std::string op_tag(ReductionOperation op) {
2✔
36
    switch (op) {
2✔
NEW
37
        case ReductionOperation::Add:
×
NEW
38
            return "add";
×
39
        case ReductionOperation::Mul:
2✔
40
            return "mul";
2✔
NEW
41
        case ReductionOperation::Min:
×
NEW
42
            return "min";
×
NEW
43
        case ReductionOperation::Max:
×
NEW
44
            return "max";
×
45
    }
2✔
NEW
46
    throw InvalidSDFGException("GPUReduceDispatcher: unknown reduction operation");
×
47
}
2✔
48

49
// Identity element of the operator for the given primitive type, as a C literal.
50
std::string identity_literal(ReductionOperation op, types::PrimitiveType prim) {
8✔
51
    if (op == ReductionOperation::Add) {
8✔
52
        return "0";
6✔
53
    }
6✔
54
    if (op == ReductionOperation::Mul) {
2✔
55
        return "1";
2✔
56
    }
2✔
57

58
    // Min / Max: need the most-extreme element so the first real value wins.
NEW
59
    if (types::is_floating_point(prim)) {
×
60
        // INFINITY / -INFINITY are provided by the CUDA/HIP math headers.
NEW
61
        return op == ReductionOperation::Min ? "INFINITY" : "-INFINITY";
×
NEW
62
    }
×
63

64
    // Integers: use the <cstdint> limit macros instead of hand-written literals.
NEW
65
    const size_t width = types::bit_width(prim);
×
NEW
66
    const bool is_unsigned = types::is_unsigned(prim);
×
67

NEW
68
    if (op == ReductionOperation::Min) {
×
69
        // identity for Min is the type maximum
NEW
70
        if (is_unsigned) {
×
NEW
71
            if (width == 32) return "UINT32_MAX";
×
NEW
72
            if (width == 64) return "UINT64_MAX";
×
NEW
73
        } else {
×
NEW
74
            if (width == 32) return "INT32_MAX";
×
NEW
75
            if (width == 64) return "INT64_MAX";
×
NEW
76
        }
×
NEW
77
    } else { // Max -> identity is the type minimum
×
NEW
78
        if (is_unsigned) {
×
NEW
79
            return "0";
×
NEW
80
        }
×
NEW
81
        if (width == 32) return "INT32_MIN";
×
NEW
82
        if (width == 64) return "INT64_MIN";
×
NEW
83
    }
×
84

NEW
85
    throw InvalidSDFGException("GPUReduceDispatcher: unsupported integer width for min/max reduction");
×
NEW
86
}
×
87

88
// Resolve the scalar element type of a reduction accumulator container.
89
// The accumulator must be a device-resident pointer to a scalar (the offloaded
90
// form of the host accumulator).
91
types::PrimitiveType accumulator_primitive(const StructuredSDFG& sdfg, const std::string& container) {
24✔
92
    auto& type = sdfg.type(container);
24✔
93
    if (auto* ptr = dynamic_cast<const types::Pointer*>(&type)) {
24✔
94
        if (!ptr->has_pointee_type()) {
24✔
NEW
95
            throw InvalidSDFGException(
×
NEW
96
                "GPUReduceDispatcher: reduction accumulator '" + container + "' has no pointee type"
×
NEW
97
            );
×
NEW
98
        }
×
99
        if (auto* scalar = dynamic_cast<const types::Scalar*>(&ptr->pointee_type())) {
24✔
100
            return scalar->primitive_type();
24✔
101
        }
24✔
NEW
102
        throw InvalidSDFGException(
×
NEW
103
            "GPUReduceDispatcher: reduction accumulator '" + container + "' must point to a scalar"
×
NEW
104
        );
×
105
    }
24✔
NEW
106
    throw InvalidSDFGException(
×
NEW
107
        "GPUReduceDispatcher: reduction accumulator '" + container +
×
NEW
108
        "' must be a device-resident pointer (offload it before scheduling)"
×
NEW
109
    );
×
110
}
24✔
111

112
// Find the single index expression with which `container` is accessed in the
113
// reduce body. The accumulator is a flat pointer (Pointer->Scalar), so its
114
// memlet subset has exactly one element; e.g. `acc[0]` for a scalar reduction
115
// or `acc[i]` for a reduction whose output slot is selected by an enclosing
116
// (data-parallel) map's induction variable.
117
//
118
// All accesses to the accumulator must use the same index, and that index must
119
// be invariant in the reduction induction variable `indvar` -- otherwise the
120
// body scatters across distinct slots per iteration and is not a reduction into
121
// a single accumulator element (that requires OutLocalStorage / privatization
122
// analysis, which is out of scope for this baseline).
123
symbolic::Expression accumulator_index(
124
    structured_control_flow::Sequence& root, const std::string& container, const symbolic::Symbol& indvar
125
) {
16✔
126
    symbolic::Expression index = SymEngine::null;
16✔
127
    bool found = false;
16✔
128

129
    std::list<structured_control_flow::ControlFlowNode*> queue = {&root};
16✔
130
    while (!queue.empty()) {
48✔
131
        auto* current = queue.front();
32✔
132
        queue.pop_front();
32✔
133

134
        if (auto* block = dynamic_cast<structured_control_flow::Block*>(current)) {
32✔
135
            auto& dfg = block->dataflow();
16✔
136
            for (auto& memlet : dfg.edges()) {
48✔
137
                const auto* src = dynamic_cast<const data_flow::AccessNode*>(&memlet.src());
48✔
138
                const auto* dst = dynamic_cast<const data_flow::AccessNode*>(&memlet.dst());
48✔
139
                const data_flow::AccessNode* access = nullptr;
48✔
140
                if (src != nullptr && src->data() == container) {
48✔
141
                    access = src;
16✔
142
                } else if (dst != nullptr && dst->data() == container) {
32✔
143
                    access = dst;
16✔
144
                }
16✔
145
                if (access == nullptr || memlet.subset().size() != 1) {
48✔
146
                    continue;
16✔
147
                }
16✔
148
                auto candidate = memlet.subset()[0];
32✔
149
                if (!found) {
32✔
150
                    index = candidate;
16✔
151
                    found = true;
16✔
152
                } else if (!symbolic::eq(index, candidate)) {
16✔
NEW
153
                    throw InvalidSDFGException(
×
NEW
154
                        "GPUReduceDispatcher: accumulator '" + container +
×
NEW
155
                        "' is accessed with inconsistent indices in the reduce body"
×
NEW
156
                    );
×
NEW
157
                }
×
158
            }
32✔
159
        } else if (auto* seq = dynamic_cast<structured_control_flow::Sequence*>(current)) {
16✔
160
            for (size_t i = 0; i < seq->size(); ++i) {
32✔
161
                queue.push_back(&seq->at(i));
16✔
162
            }
16✔
163
        } else if (auto* loop = dynamic_cast<structured_control_flow::StructuredLoop*>(current)) {
16✔
NEW
164
            queue.push_back(&loop->root());
×
NEW
165
        }
×
166
    }
32✔
167

168
    if (!found) {
16✔
NEW
169
        throw InvalidSDFGException(
×
NEW
170
            "GPUReduceDispatcher: accumulator '" + container + "' is not accessed in the reduce body"
×
NEW
171
        );
×
NEW
172
    }
×
173
    if (symbolic::uses(index, indvar)) {
16✔
NEW
174
        throw InvalidSDFGException(
×
NEW
175
            "GPUReduceDispatcher: accumulator '" + container + "' index depends on the reduction variable '" +
×
NEW
176
            indvar->get_name() + "'; this is a scatter, not a reduction into a single slot"
×
NEW
177
        );
×
NEW
178
    }
×
179
    return index;
16✔
180
}
16✔
181

182
// Per-dimension GPU built-in symbols (identical intrinsics on CUDA and HIP).
183
struct DimSymbols {
184
    symbolic::Symbol thread_idx;
185
    symbolic::Symbol block_idx;
186
    symbolic::Symbol block_dim;
187
    symbolic::Symbol grid_dim;
188
};
189

190
DimSymbols dim_symbols(GPUDimension dim) {
8✔
191
    switch (dim) {
8✔
192
        case GPUDimension::X:
4✔
193
            return {symbolic::threadIdx_x(), symbolic::blockIdx_x(), symbolic::blockDim_x(), symbolic::gridDim_x()};
4✔
194
        case GPUDimension::Y:
4✔
195
            return {symbolic::threadIdx_y(), symbolic::blockIdx_y(), symbolic::blockDim_y(), symbolic::gridDim_y()};
4✔
NEW
196
        case GPUDimension::Z:
×
NEW
197
            return {symbolic::threadIdx_z(), symbolic::blockIdx_z(), symbolic::blockDim_z(), symbolic::gridDim_z()};
×
198
    }
8✔
NEW
199
    throw InvalidSDFGException("GPUReduceDispatcher: invalid GPU dimension");
×
200
}
8✔
201

202
// Whether the runtime provides a native atomicAdd overload for this primitive.
203
bool has_native_atomic_add(types::PrimitiveType prim) {
6✔
204
    const size_t width = types::bit_width(prim);
6✔
205
    if (types::is_floating_point(prim)) {
6✔
206
        return width == 32 || width == 64; // float, double (double needs sm_60+ on CUDA)
6✔
207
    }
6✔
NEW
208
    if (width == 32) {
×
NEW
209
        return true; // int / unsigned int
×
NEW
210
    }
×
NEW
211
    if (width == 64 && types::is_unsigned(prim)) {
×
NEW
212
        return true; // unsigned long long
×
NEW
213
    }
×
NEW
214
    return false; // signed 64-bit -> CAS fallback
×
NEW
215
}
×
216

217
} // namespace
218

219
GPUReduceDispatcher::GPUReduceDispatcher(
220
    codegen::LanguageExtension& language_extension,
221
    StructuredSDFG& sdfg,
222
    analysis::AnalysisManager& analysis_manager,
223
    structured_control_flow::Reduce& node,
224
    codegen::InstrumentationPlan& instrumentation_plan,
225
    codegen::ArgCapturePlan& arg_capture_plan
226
)
227
    : codegen::NodeDispatcher(language_extension, sdfg, analysis_manager, node, instrumentation_plan, arg_capture_plan),
8✔
228
      node_(node) {};
8✔
229

230
bool GPUReduceDispatcher::is_nested_in_gpu_kernel() {
8✔
231
    auto& loop_analysis = analysis_manager_.get<analysis::LoopAnalysis>();
8✔
232
    auto& loop_tree = loop_analysis.loop_tree();
8✔
233
    structured_control_flow::ControlFlowNode* ancestor = loop_tree.at(&node_);
8✔
234
    while (ancestor != nullptr) {
8✔
235
        if (auto* map = dynamic_cast<structured_control_flow::Map*>(ancestor)) {
4✔
236
            if (map->schedule_type().value() == this->schedule_value()) {
4✔
237
                return true;
4✔
238
            }
4✔
239
        }
4✔
NEW
240
        ancestor = loop_tree.at(ancestor);
×
NEW
241
    }
×
242
    return false;
4✔
243
}
8✔
244

245
void GPUReduceDispatcher::dispatch_node(
246
    codegen::PrettyPrinter& main_stream,
247
    codegen::PrettyPrinter& globals_stream,
248
    codegen::CodeSnippetFactory& library_snippet_factory
249
) {
8✔
250
    if (node_.reductions().empty()) {
8✔
NEW
251
        throw InvalidSDFGException("GPUReduceDispatcher: Reduce node carries no reductions");
×
NEW
252
    }
×
253

254
    // Nested inside an enclosing GPU kernel: inline the privatize + grid-stride
255
    // reduce + atomic merge on this Reduce's own GPU dimension. No kernel launch.
256
    if (this->is_nested_in_gpu_kernel()) {
8✔
257
        GPUDimension reduce_dim = gpu_dimension(node_.schedule_type());
4✔
258
        std::vector<std::string> no_scope_variables;
4✔
259
        this->dispatch_reduce_core(
4✔
260
            library_snippet_factory, main_stream, reduce_dim, /*declare_scope_variables=*/false, no_scope_variables
4✔
261
        );
4✔
262
        return;
4✔
263
    }
4✔
264

265
    // Top-level: emit a standalone kernel parallelized on X.
266
    analysis::ArgumentsAnalysis& arguments_analysis = analysis_manager_.get<analysis::ArgumentsAnalysis>();
4✔
267
    auto& used_arguments = arguments_analysis.arguments(analysis_manager_, node_);
4✔
268
    auto& locals = arguments_analysis.locals(analysis_manager_, node_);
4✔
269

270
    auto indvar = node_.indvar();
4✔
271

272
    // Scope variables: loop-local temporaries except the induction variable.
273
    std::vector<std::string> scope_variables;
4✔
274
    for (auto& local : locals) {
12✔
275
        if (local == indvar->get_name()) {
12✔
276
            continue;
4✔
277
        }
4✔
278
        scope_variables.push_back(local);
8✔
279
    }
8✔
280
    std::sort(scope_variables.begin(), scope_variables.end());
4✔
281

282
    // Kernel arguments (device pointers and scalars), excluding NV symbols.
283
    std::vector<std::string> arguments;
4✔
284
    for (auto& argument : used_arguments) {
4✔
NEW
285
        if (!sdfg_.type(argument.first).storage_type().is_nv_symbol()) {
×
NEW
286
            arguments.push_back(argument.first);
×
NEW
287
        }
×
NEW
288
    }
×
289
    std::sort(arguments.begin(), arguments.end());
4✔
290

291
    std::vector<std::string> arguments_device;
4✔
292
    for (auto& argument : arguments) {
4✔
NEW
293
        auto& arg_type = sdfg_.type(argument);
×
NEW
294
        if (this->is_device_pointer_storage(arg_type.storage_type())) {
×
NEW
295
            arguments_device.push_back(argument);
×
NEW
296
        } else if (arg_type.type_id() == types::TypeID::Scalar) {
×
NEW
297
            arguments_device.push_back(argument);
×
NEW
298
        } else {
×
NEW
299
            throw InvalidSDFGException("Argument " + argument + " is not a scalar or device pointer");
×
NEW
300
        }
×
NEW
301
    }
×
302

303
    std::vector<std::string> arguments_declaration;
4✔
304
    for (auto& container : arguments) {
4✔
NEW
305
        arguments_declaration.push_back(this->language_extension_.declaration(container, sdfg_.type(container)));
×
NEW
306
    }
×
307

308
    // Grid geometry: flat X-dimension mapping, one thread per iteration.
309
    symbolic::Integer block_size = gpu_block_size(node_.schedule_type());
4✔
310
    symbolic::Expression num_iters = node_.num_iterations();
4✔
311
    symbolic::Expression block_size_expr = block_size;
4✔
312
    symbolic::Expression num_blocks = symbolic::max(symbolic::divide_ceil(num_iters, block_size_expr), symbolic::one());
4✔
313

314
    std::string kernel_name = "kernel_" + sdfg_.name() + "_" + std::to_string(node_.element_id());
4✔
315

316
    this->emit_kernel_call(main_stream, kernel_name, num_blocks, block_size_expr, arguments_device);
4✔
317

318
    this->emit_kernel_includes(library_snippet_factory);
4✔
319

320
    this->dispatch_header(globals_stream, kernel_name, arguments_declaration);
4✔
321
    globals_stream << ";" << std::endl;
4✔
322

323
    auto& library_stream = library_snippet_factory.require(kernel_name, this->kernel_file_extension(), true).stream();
4✔
324
    library_stream << "#include " << library_snippet_factory.header_path().filename() << std::endl << std::endl;
4✔
325
    this->emit_library_preamble(library_stream);
4✔
326

327
    this->dispatch_header(library_stream, kernel_name, arguments_declaration);
4✔
328
    library_stream << "{" << std::endl;
4✔
329
    library_stream.setIndent(library_stream.indent() + 4);
4✔
330

331
    this->dispatch_reduce_core(
4✔
332
        library_snippet_factory, library_stream, GPUDimension::X, /*declare_scope_variables=*/true, scope_variables
4✔
333
    );
4✔
334

335
    library_stream.setIndent(library_stream.indent() - 4);
4✔
336
    library_stream << "}" << std::endl;
4✔
337
}
4✔
338

339
void GPUReduceDispatcher::dispatch_header(
340
    codegen::PrettyPrinter& stream, const std::string& kernel_name, std::vector<std::string>& arguments_declaration
341
) {
8✔
342
    stream << "__global__ void " << kernel_name << "(";
8✔
343
    stream << helpers::join(arguments_declaration, ", ");
8✔
344
    stream << ")";
8✔
345
}
8✔
346

347
void GPUReduceDispatcher::dispatch_reduce_core(
348
    codegen::CodeSnippetFactory& library_snippet_factory,
349
    codegen::PrettyPrinter& library_stream,
350
    GPUDimension dimension,
351
    bool declare_scope_variables,
352
    std::vector<std::string>& scope_variables
353
) {
8✔
354
    std::unique_ptr<codegen::LanguageExtension> device_language_extension = this->create_device_language_extension();
8✔
355
    codegen::LanguageExtension& language_extension = *device_language_extension;
8✔
356
    DimSymbols dim = dim_symbols(dimension);
8✔
357

358
    // <cstdint> supplies the integer identity macros (INT32_MAX, ...).
359
    library_snippet_factory.add_global("#include <cstdint>");
8✔
360

361
    // Grid-stride parameters on the chosen dimension.
362
    symbolic::Expression flat_id = symbolic::add(dim.thread_idx, symbolic::mul(dim.block_idx, dim.block_dim));
8✔
363
    symbolic::Expression num_threads = symbolic::mul(dim.block_dim, dim.grid_dim);
8✔
364

365
    std::string start_var = "__daisy_reduce_tid";
8✔
366
    std::string step_var = "__daisy_reduce_nthreads";
8✔
367

368
    library_stream << "{" << std::endl;
8✔
369
    library_stream.setIndent(library_stream.indent() + 4);
8✔
370

371
    library_stream << "int " << start_var << " = " << language_extension.expression(flat_id) << ";" << std::endl;
8✔
372
    library_stream << "int " << step_var << " = " << language_extension.expression(num_threads) << ";" << std::endl;
8✔
373

374
    // Thread-private partials, one per reduction.
375
    for (auto& reduction : node_.reductions()) {
8✔
376
        auto prim = accumulator_primitive(sdfg_, reduction.container);
8✔
377
        std::string ctype = language_extension.primitive_type(prim);
8✔
378
        library_stream << ctype << " __daisy_reduce_" << reduction.container << " = "
8✔
379
                       << identity_literal(reduction.operation, prim) << ";" << std::endl;
8✔
380
    }
8✔
381

382
    // Body scope: shadow each accumulator pointer so the in-body combine writes
383
    // the thread-private partial instead of the shared device accumulator. For
384
    // an indexed accumulator `acc[index]` the shadow base is offset by `-index`
385
    // so the body's `acc[index]` resolves to the single private register; the
386
    // body re-adds `index`, so the net access is the register itself.
387
    library_stream << "{" << std::endl;
8✔
388
    library_stream.setIndent(library_stream.indent() + 4);
8✔
389

390
    for (auto& reduction : node_.reductions()) {
8✔
391
        auto prim = accumulator_primitive(sdfg_, reduction.container);
8✔
392
        std::string ctype = language_extension.primitive_type(prim);
8✔
393
        auto index = accumulator_index(node_.root(), reduction.container, node_.indvar());
8✔
394
        if (symbolic::eq(index, symbolic::zero())) {
8✔
395
            library_stream << ctype << " *" << reduction.container << " = &__daisy_reduce_" << reduction.container
6✔
396
                           << ";" << std::endl;
6✔
397
        } else {
6✔
398
            library_stream << ctype << " *" << reduction.container << " = &__daisy_reduce_" << reduction.container
2✔
399
                           << " - (" << language_extension.expression(index) << ");" << std::endl;
2✔
400
        }
2✔
401
    }
8✔
402

403
    // Declare loop-local scope variables (top-level only; nested kernels have
404
    // them declared by the enclosing map dispatcher).
405
    if (declare_scope_variables) {
8✔
406
        for (auto& local : scope_variables) {
8✔
407
            std::string val = language_extension.declaration(local, sdfg_.type(local), false, true);
8✔
408
            if (!val.empty()) {
8✔
409
                library_stream << val << ";" << std::endl;
8✔
410
            }
8✔
411
        }
8✔
412
    }
4✔
413

414
    // Grid-stride reduce loop on the reduction dimension:
415
    //   for (j = init + tid*stride; <cond>; j += nthreads*stride)
416
    auto indvar = node_.indvar();
8✔
417
    auto stride = node_.stride();
8✔
418
    symbolic::Expression strided_start = symbolic::symbol(start_var);
8✔
419
    symbolic::Expression strided_step = symbolic::symbol(step_var);
8✔
420
    if (!stride.is_null() && !symbolic::eq(stride, symbolic::one())) {
8✔
NEW
421
        strided_start = symbolic::mul(strided_start, stride);
×
NEW
422
        strided_step = symbolic::mul(strided_step, stride);
×
NEW
423
    }
×
424
    symbolic::Expression loop_init = strided_start;
8✔
425
    auto init = node_.init();
8✔
426
    if (!symbolic::eq(init, symbolic::zero())) {
8✔
NEW
427
        loop_init = symbolic::add(init, strided_start);
×
NEW
428
    }
×
429
    symbolic::Expression loop_update = symbolic::add(indvar, strided_step);
8✔
430

431
    library_stream << "for (int " << indvar->get_name() << " = " << language_extension.expression(loop_init) << "; "
8✔
432
                   << language_extension.expression(node_.condition()) << "; " << indvar->get_name() << " = "
8✔
433
                   << language_extension.expression(loop_update) << ") {" << std::endl;
8✔
434
    library_stream.setIndent(library_stream.indent() + 4);
8✔
435

436
    codegen::SequenceDispatcher
8✔
437
        dispatcher(language_extension, sdfg_, analysis_manager_, node_.root(), instrumentation_plan_, arg_capture_plan_);
8✔
438
    dispatcher.dispatch(library_stream, library_stream, library_snippet_factory);
8✔
439

440
    library_stream.setIndent(library_stream.indent() - 4);
8✔
441
    library_stream << "}" << std::endl; // for
8✔
442

443
    library_stream.setIndent(library_stream.indent() - 4);
8✔
444
    library_stream << "}" << std::endl; // body scope
8✔
445

446
    // Atomic merge of each private partial into the shared device accumulator.
447
    for (auto& reduction : node_.reductions()) {
8✔
448
        this->dispatch_atomic_merge(library_stream, library_snippet_factory, reduction);
8✔
449
    }
8✔
450

451
    library_stream.setIndent(library_stream.indent() - 4);
8✔
452
    library_stream << "}" << std::endl; // outer scope
8✔
453
}
8✔
454

455
void GPUReduceDispatcher::dispatch_atomic_merge(
456
    codegen::PrettyPrinter& library_stream,
457
    codegen::CodeSnippetFactory& library_snippet_factory,
458
    const structured_control_flow::ReductionInfo& reduction
459
) {
8✔
460
    std::unique_ptr<codegen::LanguageExtension> device_language_extension = this->create_device_language_extension();
8✔
461
    codegen::LanguageExtension& language_extension = *device_language_extension;
8✔
462
    auto prim = accumulator_primitive(sdfg_, reduction.container);
8✔
463
    std::string ctype = language_extension.primitive_type(prim);
8✔
464

465
    // Merge into the accumulator's real slot. Outside the shadow scope the
466
    // container name again refers to the device pointer parameter, so the
467
    // address is `&acc[index]` -- distinct per enclosing-map thread, shared
468
    // across the parallel reduction threads (hence the atomic).
469
    auto index = accumulator_index(node_.root(), reduction.container, node_.indvar());
8✔
470
    std::string index_str = language_extension.expression(index);
8✔
471
    std::string target = "&(reinterpret_cast<" + ctype + " *>(" + reduction.container + "))[" + index_str + "]";
8✔
472
    std::string value = "__daisy_reduce_" + reduction.container;
8✔
473

474
    // Fast path: native atomicAdd where the runtime provides an overload.
475
    if (reduction.operation == ReductionOperation::Add && has_native_atomic_add(prim)) {
8✔
476
        library_stream << "atomicAdd(" << target << ", " << value << ");" << std::endl;
6✔
477
        return;
6✔
478
    }
6✔
479

480
    std::string type_tag = ctype;
2✔
481
    std::replace(type_tag.begin(), type_tag.end(), ' ', '_');
2✔
482
    std::string helper_name = "__daisy_reduce_combine_" + op_tag(reduction.operation) + "_" + type_tag;
2✔
483

484
    // Every other operator/type uses a device-side compare-and-swap combine
485
    // helper. These live in daisy_rtl.h (guarded by __CUDACC__/__HIPCC__) so
486
    // they are visible to every kernel translation unit; we only emit the call.
487
    // The CAS baseline supports 32/64-bit accumulators only.
488
    const size_t width = types::bit_width(prim);
2✔
489
    if (width != 32 && width != 64) {
2✔
NEW
490
        throw InvalidSDFGException(
×
NEW
491
            "GPUReduceDispatcher: only 32/64-bit reduction accumulators are supported in the atomics baseline"
×
NEW
492
        );
×
NEW
493
    }
×
494

495
    // The accumulator container is the real device pointer again outside the
496
    // shadow scope; merge into its (loop-invariant) element 0.
497
    library_stream << helper_name << "(" << target << ", " << value << ");" << std::endl;
2✔
498
}
2✔
499

NEW
500
codegen::InstrumentationInfo GPUReduceDispatcher::instrumentation_info() const {
×
NEW
501
    auto& loop_analysis = analysis_manager_.get<analysis::LoopAnalysis>();
×
NEW
502
    analysis::LoopInfo loop_info = loop_analysis.loop_info(&node_);
×
503

NEW
504
    std::unordered_map<std::string, std::string> metrics;
×
NEW
505
    auto& flop_analysis = analysis_manager_.get<analysis::FlopAnalysis>();
×
NEW
506
    auto flop = flop_analysis.get_if_available_for_codegen(&node_);
×
NEW
507
    if (!flop.is_null()) {
×
NEW
508
        metrics.insert({"flop", language_extension_.expression(flop)});
×
NEW
509
    }
×
510

NEW
511
    return codegen::InstrumentationInfo(
×
NEW
512
        node_.element_id(),
×
NEW
513
        node_.element_type(),
×
NEW
514
        this->target_type(),
×
NEW
515
        codegen::InstrumentationEventType::CUDA,
×
NEW
516
        loop_info,
×
NEW
517
        metrics
×
NEW
518
    );
×
NEW
519
}
×
520

521
} // namespace gpu
522
} // namespace sdfg
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc