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

daisytuner / docc / 26556322966

27 May 2026 03:45PM UTC coverage: 60.869% (-0.02%) from 60.886%
26556322966

push

github

web-flow
Libnode ptr edges (#719)

Migrating SDFGs to treat pointers as inputs to libNodes / Calls as scalars.
A pointer will only appear in an output edge if its actually returned from the function (like malloc).

* Stdlib, Blas and Tensor Matmul nodes were migrated to this new format. Other, currently transitory Tensor Nodes are not yet migrated.
* DOCC version was bumped to incorporate previous docc-llvm versions (up to 0.4.0) that had been counted separately.
! Until all passes consider the use / leak of pointers as uncertainty / hiding potential writes, TensorNodes are declared as general side-effect.
* Lots of utility functions to centralize the creation (and edges) of various libNodes that needed to be changed.
* Fixed & unified docc paths across python and llvm front-ends.
* Skip BlockFusion test that fails to its libNodes currently having side effects
~ Prevent a crash in DotViz when using symbolic offsets into structs
* Removing old ConstProp pass, it is not safe for the new pointer representation and should not be all too critical

961 of 1749 new or added lines in 52 files covered. (54.95%)

87 existing lines in 28 files now uncovered.

35225 of 57870 relevant lines covered (60.87%)

11046.32 hits per line

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

63.1
/llvm/src/lifting/functions/function_lifting.cpp
1
#include "docc/lifting/functions/function_lifting.h"
2

3
#include "docc/lifting/functions/blas_lifting.h"
4
#include "docc/lifting/functions/intrinsic_lifting.h"
5
#include "docc/lifting/functions/libfunc_lifting.h"
6

7
#include <sdfg/data_flow/library_nodes/call_node.h>
8
#include <sdfg/data_flow/library_nodes/invoke_node.h>
9

10
namespace docc {
11
namespace lifting {
12

13
static const std::unordered_set<std::string> BLACKLISTED_FUNCTIONS = {};
14

15
bool FunctionLifting::is_supported(llvm::TargetLibraryInfo& TLI, const llvm::CallBase* instruction) {
×
16
    if (!llvm::isa<llvm::CallInst>(instruction) && !llvm::isa<llvm::InvokeInst>(instruction)) {
×
17
        LiftingReport::add_failed_lift(
×
18
            docc::utils::get_debug_info(*instruction),
×
19
            "Unsupported call base type",
×
20
            docc::utils::toIRString(*instruction)
×
21
        );
×
22
        LLVM_DEBUG_PRINTLN("Unsupported call base type: " << docc::utils::toIRString(*instruction));
×
23
        return false;
×
24
    }
×
25

26
    // Intrinsic functions
27
    if (auto intrinsic_inst = llvm::dyn_cast<const llvm::IntrinsicInst>(instruction)) {
×
28
        auto iid = intrinsic_inst->getIntrinsicID();
×
29
        if (IntrinsicLifting::is_supported(intrinsic_inst)) {
×
30
            return true;
×
31
        } else {
×
32
            LiftingReport::add_failed_lift(
×
33
                docc::utils::get_debug_info(*instruction),
×
34
                "Unsupported intrinsic: " + llvm::Intrinsic::getName(iid).str(),
×
35
                docc::utils::toIRString(*instruction)
×
36
            );
×
37
            LLVM_DEBUG_PRINTLN("Unsupported intrinsic: " << docc::utils::toIRString(*instruction));
×
38
            return false;
×
39
        }
×
40
    }
×
41

42
    // Operand must be supported
43
    auto operand = instruction->getCalledOperand();
×
44
    if (!operand) {
×
45
        LiftingReport::add_failed_lift(
×
46
            docc::utils::get_debug_info(*instruction),
×
47
            "Unsupported call with no called operand",
×
48
            docc::utils::toIRString(*instruction)
×
49
        );
×
50
        LLVM_DEBUG_PRINTLN("Unsupported call with no called operand: " << docc::utils::toIRString(*instruction));
×
51
        return false;
×
52
    }
×
53

54
    return true;
×
55
}
×
56

57
sdfg::control_flow::State& FunctionLifting::
58
    visit(const llvm::BasicBlock* block, const llvm::CallBase* instruction, sdfg::control_flow::State& current_state) {
29✔
59
    // Intrinsic functions
60
    if (llvm::isa<const llvm::IntrinsicInst>(instruction)) {
29✔
61
        IntrinsicLifting lifter(
17✔
62
            TLI_,
17✔
63
            DL_,
17✔
64
            function_,
17✔
65
            target_type_,
17✔
66
            builder_,
17✔
67
            state_mapping_,
17✔
68
            pred_mapping_,
17✔
69
            constants_mapping_,
17✔
70
            anonymous_types_mapping_
17✔
71
        );
17✔
72
        return lifter.visit(block, instruction, current_state);
17✔
73
    }
17✔
74

75
    // Match known library functions
76
    llvm::Function* called_func = instruction->getCalledFunction();
12✔
77
    if (called_func) {
12✔
78
        // LLVM Library functions
79
        llvm::LibFunc lf;
12✔
80
        if (TLI_.getLibFunc(called_func->getName(), lf) && LibFuncLifting::is_supported(lf)) {
12✔
81
            LibFuncLifting lifter(
4✔
82
                TLI_,
4✔
83
                DL_,
4✔
84
                function_,
4✔
85
                target_type_,
4✔
86
                builder_,
4✔
87
                state_mapping_,
4✔
88
                pred_mapping_,
4✔
89
                constants_mapping_,
4✔
90
                anonymous_types_mapping_
4✔
91
            );
4✔
92
            return lifter.visit(block, instruction, current_state);
4✔
93
        }
4✔
94

95
        // BLAS functions
96
        if (BLASLifting::is_supported(*called_func)) {
8✔
97
            BLASLifting lifter(
4✔
98
                TLI_,
4✔
99
                DL_,
4✔
100
                function_,
4✔
101
                target_type_,
4✔
102
                builder_,
4✔
103
                state_mapping_,
4✔
104
                pred_mapping_,
4✔
105
                constants_mapping_,
4✔
106
                anonymous_types_mapping_
4✔
107
            );
4✔
108
            return lifter.visit(block, instruction, current_state);
4✔
109
        }
4✔
110
    }
8✔
111

112
    if (auto call_inst = llvm::dyn_cast<const llvm::CallInst>(instruction)) {
4✔
113
        return this->visit_call(block, call_inst, current_state);
3✔
114
    } else if (auto invoke_inst = llvm::dyn_cast<const llvm::InvokeInst>(instruction)) {
3✔
115
        return this->visit_invoke(block, invoke_inst, current_state);
1✔
116
    } else {
1✔
117
        throw NotImplementedException(
×
118
            "Unsupported CallBase type in FunctionLifting::visit",
×
119
            docc::utils::get_debug_info(*instruction),
×
120
            docc::utils::toIRString(*instruction)
×
121
        );
×
122
    }
×
123
}
4✔
124

125
sdfg::control_flow::State& FunctionLifting::visit_call(
126
    const llvm::BasicBlock* block, const llvm::CallInst* instruction, sdfg::control_flow::State& current_state
127
) {
3✔
128
    // Define Debug
129
    auto dbg_info = docc::utils::get_debug_info(*instruction);
3✔
130

131
    // Define function name
132
    std::string callee_name;
3✔
133
    llvm::Function* called_func = instruction->getCalledFunction();
3✔
134
    if (called_func) {
3✔
135
        callee_name = called_func->getName().str();
3✔
136
    } else if (instruction->getCalledOperand()) {
3✔
137
        callee_name = utils::get_name(instruction->getCalledOperand());
×
138
    } else {
×
139
        throw NotImplementedException(
×
140
            "Unsupported call instruction with no function or operand",
×
141
            docc::utils::get_debug_info(*instruction),
×
142
            docc::utils::toIRString(*instruction)
×
143
        );
×
144
    }
×
145
    assert(!callee_name.empty() && "Function name is empty");
3✔
146

147
    // Define return type
148
    std::vector<std::string> outputs;
3✔
149
    std::unordered_map<std::string, sdfg::data_flow::AccessNode*> out_conns;
3✔
150
    std::unordered_map<std::string, sdfg::data_flow::AccessNode*> unique_nodes_out;
3✔
151
    std::unordered_map<std::string, std::unique_ptr<sdfg::types::IType>> types;
3✔
152
    if (!instruction->getType()->isVoidTy()) {
3✔
153
        std::string output = utils::get_name(instruction);
2✔
154
        if (!this->builder_.subject().exists(output)) {
2✔
155
            auto output_type = utils::get_type(
2✔
156
                this->builder_,
2✔
157
                this->anonymous_types_mapping_,
2✔
158
                this->DL_,
2✔
159
                instruction->getType(),
2✔
160
                utils::get_storage_type(this->target_type_, 0)
2✔
161
            );
2✔
162
            this->builder_.add_container(output, *output_type);
2✔
163
        }
2✔
164
        auto output_node = &this->builder_.add_access(current_state, output, dbg_info);
2✔
165

166
        outputs.push_back("_ret");
2✔
167
        out_conns.insert({"_ret", output_node});
2✔
168
        unique_nodes_out.insert({output, output_node});
2✔
169
        types.insert({"_ret", this->builder_.subject().type(output).clone()});
2✔
170
    }
2✔
171

172
    // Define arguments
173
    std::vector<std::string> inputs;
3✔
174
    std::unordered_map<std::string, sdfg::data_flow::AccessNode*> in_conns;
3✔
175
    std::unordered_map<std::string, sdfg::data_flow::AccessNode*> unique_nodes_in;
3✔
176
    std::vector<sdfg::data_flow::PointerAccessType> ptr_meta;
3✔
177
    size_t i = 0;
3✔
178
    for (auto& op : instruction->args()) {
3✔
179
        std::string conn = "_arg" + std::to_string(i);
3✔
180

181
        auto op_type = utils::get_type(
3✔
182
            this->builder_,
3✔
183
            this->anonymous_types_mapping_,
3✔
184
            this->DL_,
3✔
185
            op->getType(),
3✔
186
            utils::get_storage_type(this->target_type_, 0)
3✔
187
        );
3✔
188
        if (utils::is_literal(op)) {
3✔
189
            std::string value = utils::as_initializer(llvm::dyn_cast<const llvm::ConstantData>(op));
×
190
            auto& const_node = this->builder_.add_constant(current_state, value, *op_type, dbg_info);
×
191

192
            inputs.push_back(conn);
×
193
            in_conns.insert({conn, &const_node});
×
194
            types.insert({conn, std::move(op_type)});
×
195
        } else {
3✔
196
            std::string data = utils::find_const_name_to_sdfg_name(this->constants_mapping_, op);
3✔
197

198
            // Add type
199
            types.insert({conn, op_type->clone()});
3✔
200

201
            // Add to inputs
202
            if (unique_nodes_in.find(data) == unique_nodes_in.end()) {
3✔
203
                auto& data_node_in = this->builder_.add_access(current_state, data, dbg_info);
3✔
204
                unique_nodes_in.insert({data, &data_node_in});
3✔
205
            }
3✔
206
            inputs.push_back(conn);
3✔
207
            in_conns.insert({conn, unique_nodes_in[data]});
3✔
208

209
            sdfg::data_flow::PointerAccessType meta;
3✔
210
            if (op_type->type_id() == sdfg::types::TypeID::Pointer) {
3✔
211
                if (called_func && i < called_func->arg_size()) {
2✔
212
                    auto* arg = called_func->getArg(i);
2✔
213
                    bool onlyReadsMemory = arg->onlyReadsMemory();
2✔
214
                    bool noCapture = arg->hasNoCaptureAttr();
2✔
215

216
                    if (onlyReadsMemory) {
2✔
NEW
217
                        meta = sdfg::data_flow::PointerAccessMeta::create_read_only(SymEngine::null, noCapture);
×
218
                    } else if (noCapture) {
2✔
NEW
219
                        meta = sdfg::data_flow::PointerAccessMeta::create_generic(nullptr, nullptr, noCapture);
×
UNCOV
220
                    }
×
221
                }
2✔
222
            }
2✔
223
            if (meta) {
3✔
NEW
224
                ptr_meta.push_back(std::move(meta));
×
UNCOV
225
            }
×
226
        }
3✔
227

228
        i++;
3✔
229
    }
3✔
230

231
    // Define library node
232
    auto& lib_node =
3✔
233
        this->builder_
3✔
234
            .add_library_node<sdfg::data_flow::CallNode>(current_state, dbg_info, callee_name, outputs, inputs);
3✔
235

236
    // Define in connectors
237
    for (auto& [in_conn, in_node] : in_conns) {
3✔
238
        auto& conn_type = types.at(in_conn);
3✔
239
        this->builder_.add_computational_memlet(current_state, *in_node, lib_node, in_conn, {}, *conn_type, dbg_info);
3✔
240
    }
3✔
241

242
    // Define out connectors
243
    for (auto& [out_conn, out_node] : out_conns) {
3✔
244
        auto& conn_type = types.at(out_conn);
2✔
245
        this->builder_.add_computational_memlet(current_state, lib_node, out_conn, *out_node, {}, *conn_type, dbg_info);
2✔
246
    }
2✔
247

248
    return current_state;
3✔
249
}
3✔
250

251
sdfg::control_flow::State& FunctionLifting::visit_invoke(
252
    const llvm::BasicBlock* block, const llvm::InvokeInst* instruction, sdfg::control_flow::State& current_state
253
) {
1✔
254
    // Define Debug
255
    auto dbg_info = docc::utils::get_debug_info(*instruction);
1✔
256

257
    // Define function name
258
    std::string callee_name;
1✔
259
    llvm::Function* called_func = instruction->getCalledFunction();
1✔
260
    if (called_func) {
1✔
261
        callee_name = called_func->getName().str();
1✔
262
    } else if (instruction->getCalledOperand()) {
1✔
263
        callee_name = utils::get_name(instruction->getCalledOperand());
×
264
    } else {
×
265
        throw NotImplementedException(
×
266
            "Unsupported call instruction with no function or operand",
×
267
            docc::utils::get_debug_info(*instruction),
×
268
            docc::utils::toIRString(*instruction)
×
269
        );
×
270
    }
×
271
    assert(!callee_name.empty() && "Function name is empty");
1✔
272

273
    // Define return type
274
    std::vector<std::string> outputs;
1✔
275
    std::unordered_map<std::string, sdfg::data_flow::AccessNode*> out_conns;
1✔
276
    std::unordered_map<std::string, sdfg::data_flow::AccessNode*> unique_nodes_out;
1✔
277
    std::unordered_map<std::string, std::unique_ptr<sdfg::types::IType>> types;
1✔
278
    if (!instruction->getType()->isVoidTy()) {
1✔
279
        std::string output = utils::get_name(instruction);
1✔
280
        if (!this->builder_.subject().exists(output)) {
1✔
281
            auto output_type = utils::get_type(
1✔
282
                this->builder_,
1✔
283
                this->anonymous_types_mapping_,
1✔
284
                this->DL_,
1✔
285
                instruction->getType(),
1✔
286
                utils::get_storage_type(this->target_type_, 0)
1✔
287
            );
1✔
288
            this->builder_.add_container(output, *output_type);
1✔
289
        }
1✔
290
        auto output_node = &this->builder_.add_access(current_state, output, dbg_info);
1✔
291

292
        outputs.push_back("_ret");
1✔
293
        out_conns.insert({"_ret", output_node});
1✔
294
        unique_nodes_out.insert({output, output_node});
1✔
295
        types.insert({"_ret", this->builder_.subject().type(output).clone()});
1✔
296
    }
1✔
297

298
    // Define arguments
299
    std::vector<std::string> inputs;
1✔
300
    std::unordered_map<std::string, sdfg::data_flow::AccessNode*> in_conns;
1✔
301
    std::unordered_map<std::string, sdfg::data_flow::AccessNode*> unique_nodes_in;
1✔
302
    size_t i = 0;
1✔
303
    for (auto& op : instruction->args()) {
1✔
304
        std::string conn = "_arg" + std::to_string(i);
×
305

306
        auto op_type = utils::get_type(
×
307
            this->builder_,
×
308
            this->anonymous_types_mapping_,
×
309
            this->DL_,
×
310
            op->getType(),
×
311
            utils::get_storage_type(this->target_type_, 0)
×
312
        );
×
313
        if (utils::is_literal(op)) {
×
314
            std::string value = utils::as_initializer(llvm::dyn_cast<const llvm::ConstantData>(op));
×
315
            auto& const_node = this->builder_.add_constant(current_state, value, *op_type, dbg_info);
×
316

317
            inputs.push_back(conn);
×
318
            in_conns.insert({conn, &const_node});
×
319
            types.insert({conn, std::move(op_type)});
×
320
        } else {
×
321
            std::string data = utils::find_const_name_to_sdfg_name(this->constants_mapping_, op);
×
322

323
            // Add type
324
            types.insert({conn, std::move(op_type)});
×
325

326
            if (unique_nodes_in.find(data) == unique_nodes_in.end()) {
×
327
                auto& data_node_in = this->builder_.add_access(current_state, data, dbg_info);
×
328
                unique_nodes_in.insert({data, &data_node_in});
×
329

330
                if (called_func && !called_func->isVarArg() && called_func->getArg(i)->onlyReadsMemory()) {
×
331
                    // No output node needed
332
                } else {
×
333
                    auto& data_node_out = this->builder_.add_access(current_state, data, dbg_info);
×
334
                    unique_nodes_out.insert({data, &data_node_out});
×
335
                }
×
336
            }
×
337

338
            inputs.push_back(conn);
×
339
            in_conns.insert({conn, unique_nodes_in[data]});
×
340

341
            if (called_func && !called_func->isVarArg() && called_func->getArg(i)->onlyReadsMemory()) {
×
342
                // No output connector needed
343
            } else {
×
344
                outputs.push_back(conn);
×
345
                out_conns.insert({conn, unique_nodes_out[data]});
×
346
            }
×
347
        }
×
348

349
        i++;
×
350
    }
×
351

352
    // Define library node
353
    auto& lib_node =
1✔
354
        this->builder_
1✔
355
            .add_library_node<sdfg::data_flow::InvokeNode>(current_state, dbg_info, callee_name, outputs, inputs);
1✔
356

357
    // Define in connectors
358
    for (auto& [in_conn, in_node] : in_conns) {
1✔
359
        auto& conn_type = types.at(in_conn);
×
360
        this->builder_.add_computational_memlet(current_state, *in_node, lib_node, in_conn, {}, *conn_type, dbg_info);
×
361
    }
×
362

363
    // Define out connectors
364
    for (auto& [out_conn, out_node] : out_conns) {
1✔
365
        auto& conn_type = types.at(out_conn);
1✔
366
        this->builder_.add_computational_memlet(current_state, lib_node, out_conn, *out_node, {}, *conn_type, dbg_info);
1✔
367
    }
1✔
368

369
    // Add unwind handling
370
    std::string unwind_container = "__unwind_" + utils::get_name(instruction);
1✔
371
    sdfg::types::Scalar unwind_type(sdfg::types::PrimitiveType::Bool);
1✔
372
    auto& unwind_node = this->builder_.add_access(current_state, unwind_container, dbg_info);
1✔
373
    this->builder_.add_computational_memlet(current_state, lib_node, "_unwind", unwind_node, {}, unwind_type, dbg_info);
1✔
374

375
    return current_state;
1✔
376
}
1✔
377

378
} // namespace lifting
379
} // namespace docc
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