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

Alan-Jowett / ebpf-verifier / 21993000823

13 Feb 2026 01:02PM UTC coverage: 86.313% (-0.5%) from 86.783%
21993000823

push

github

web-flow
ISA feature support matrix and precise rejection semantics (#999)

* ISA feature support matrix and precise rejection semantics

Signed-off-by: Elazar Gershuni <elazarg@gmail.com>

282 of 380 new or added lines in 14 files covered. (74.21%)

3 existing lines in 3 files now uncovered.

9535 of 11047 relevant lines covered (86.31%)

3060772.25 hits per line

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

67.33
/src/ir/cfg_builder.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3
#include <algorithm>
4
#include <cassert>
5
#include <map>
6
#include <optional>
7
#include <string>
8
#include <vector>
9

10
#include "cfg/cfg.hpp"
11
#include "cfg/wto.hpp"
12
#include "config.hpp"
13
#include "ir/program.hpp"
14
#include "ir/syntax.hpp"
15
#include "platform.hpp"
16

17
using std::optional;
18
using std::set;
19
using std::string;
20
using std::to_string;
21
using std::vector;
22

23
namespace prevail {
24
struct CfgBuilder final {
25
    Program prog;
26

27
    // TODO: ins should be inserted elsewhere
28
    void insert_after(const Label& prev_label, const Label& new_label, const Instruction& ins) {
32✔
29
        if (prev_label == new_label) {
32✔
30
            CRAB_ERROR("Cannot insert after the same label ", to_string(new_label));
×
31
        }
32
        std::set<Label> prev_children;
32✔
33
        std::swap(prev_children, prog.m_cfg.get_node(prev_label).children);
32✔
34

35
        for (const Label& next_label : prev_children) {
72✔
36
            prog.m_cfg.get_node(next_label).parents.erase(prev_label);
40✔
37
        }
38

39
        insert(new_label, ins);
32✔
40
        for (const Label& next_label : prev_children) {
72✔
41
            add_child(prev_label, new_label);
40✔
42
            add_child(new_label, next_label);
40✔
43
        }
44
    }
32✔
45

46
    // TODO: ins should be inserted elsewhere
47
    void insert(const Label& _label, const Instruction& ins) {
384,930✔
48
        if (const auto it = prog.m_cfg.neighbours.find(_label); it != prog.m_cfg.neighbours.end()) {
384,930✔
49
            CRAB_ERROR("Label ", to_string(_label), " already exists");
×
50
        }
51
        prog.m_cfg.neighbours.emplace(_label, Cfg::Adjacent{});
384,930✔
52
        prog.m_instructions.emplace(_label, ins);
384,930✔
53
    }
384,930✔
54

55
    // TODO: ins should be inserted elsewhere
56
    Label insert_jump(const Label& from, const Label& to, const Instruction& ins) {
60,056✔
57
        const Label jump_label = Label::make_jump(from, to);
60,056✔
58
        if (prog.m_cfg.contains(jump_label)) {
60,056✔
59
            CRAB_ERROR("Jump label ", to_string(jump_label), " already exists");
×
60
        }
61
        insert(jump_label, ins);
60,056✔
62
        add_child(from, jump_label);
60,056✔
63
        add_child(jump_label, to);
60,056✔
64
        return jump_label;
60,056✔
65
    }
×
66

67
    void add_child(const Label& a, const Label& b) {
417,930✔
68
        assert(b != Label::entry);
417,930✔
69
        assert(a != Label::exit);
417,930✔
70
        prog.m_cfg.neighbours.at(a).children.insert(b);
417,930✔
71
        prog.m_cfg.neighbours.at(b).parents.insert(a);
417,930✔
72
    }
417,930✔
73

74
    void remove_child(const Label& a, const Label& b) {
136✔
75
        prog.m_cfg.get_node(a).children.erase(b);
136✔
76
        prog.m_cfg.get_node(b).parents.erase(a);
136✔
77
    }
136✔
78

79
    void set_assertions(const Label& label, const std::vector<Assertion>& assertions) {
390,276✔
80
        if (!prog.m_cfg.contains(label)) {
390,276✔
81
            CRAB_ERROR("Label ", to_string(label), " not found in the CFG: ");
×
82
        }
83
        prog.m_assertions.insert_or_assign(label, assertions);
390,276✔
84
    }
390,276✔
85
};
86

87
/// Get the inverse of a given comparison operation.
88
static Condition::Op reverse(const Condition::Op op) {
30,028✔
89
    switch (op) {
30,028✔
90
    case Condition::Op::EQ: return Condition::Op::NE;
6,301✔
91
    case Condition::Op::NE: return Condition::Op::EQ;
5,036✔
92

93
    case Condition::Op::GE: return Condition::Op::LT;
175✔
94
    case Condition::Op::LT: return Condition::Op::GE;
383✔
95

96
    case Condition::Op::SGE: return Condition::Op::SLT;
21✔
97
    case Condition::Op::SLT: return Condition::Op::SGE;
825✔
98

99
    case Condition::Op::LE: return Condition::Op::GT;
47✔
100
    case Condition::Op::GT: return Condition::Op::LE;
1,183✔
101

102
    case Condition::Op::SLE: return Condition::Op::SGT;
21✔
103
    case Condition::Op::SGT: return Condition::Op::SLE;
1,003✔
104

105
    case Condition::Op::SET: return Condition::Op::NSET;
17✔
106
    case Condition::Op::NSET: return Condition::Op::SET;
2✔
107
    }
108
    assert(false);
109
    return {};
110
}
111

112
/// Get the inverse of a given comparison condition.
113
static Condition reverse(const Condition& cond) {
30,028✔
114
    return {.op = reverse(cond.op), .left = cond.left, .right = cond.right, .is64 = cond.is64};
30,028✔
115
}
116

117
static bool has_fall(const Instruction& ins) {
291,424✔
118
    if (std::holds_alternative<Exit>(ins)) {
291,424✔
119
        return false;
1,593✔
120
    }
121

122
    if (const auto pins = std::get_if<Jmp>(&ins)) {
288,238✔
123
        if (!pins->cond) {
144✔
124
            return false;
72✔
125
        }
126
    }
127

128
    return true;
144,047✔
129
}
130

131
enum class RejectKind {
132
    NotImplemented,
133
    Capability,
134
};
135

136
struct RejectionReason {
46✔
137
    RejectKind kind{};
138
    std::string detail;
139
};
140

141
static bool supports(const ebpf_platform_t& platform, const bpf_conformance_groups_t group) {
494,101✔
142
    return platform.supports_group(group);
492,250✔
143
}
144

145
static bool un_requires_base64(const Un& un) {
4,465✔
146
    switch (un.op) {
4,465✔
147
    case Un::Op::BE64:
35✔
148
    case Un::Op::LE64:
149
    case Un::Op::SWAP64: return true;
35✔
150
    default: return false;
2,941✔
151
    }
152
}
153

154
static std::optional<RejectionReason> check_instruction_feature_support(const Instruction& ins,
490,314✔
155
                                                                        const ebpf_platform_t& platform) {
156
    auto reject_not_implemented = [](std::string detail) {
164,092✔
157
        return RejectionReason{.kind = RejectKind::NotImplemented, .detail = std::move(detail)};
4✔
158
    };
159
    auto reject_capability = [](std::string detail) {
164,130✔
160
        return RejectionReason{.kind = RejectKind::Capability, .detail = std::move(detail)};
42✔
161
    };
162

163
    if (std::holds_alternative<CallBtf>(ins)) {
490,314✔
164
        return reject_not_implemented("call helper by BTF id");
4✔
165
    }
166
    if (const auto p = std::get_if<Call>(&ins)) {
490,312✔
167
        if (!p->is_supported) {
32,385✔
168
            return reject_capability(p->unsupported_reason);
3✔
169
        }
170
    }
171
    if (std::holds_alternative<Callx>(ins) && !supports(platform, bpf_conformance_groups_t::callx)) {
490,310✔
NEW
172
        return reject_capability("requires conformance group callx");
×
173
    }
174
    if ((std::holds_alternative<Call>(ins) || std::holds_alternative<CallLocal>(ins) ||
1,099,550✔
175
         std::holds_alternative<Callx>(ins) || std::holds_alternative<Exit>(ins)) &&
1,276,157✔
176
        !supports(platform, bpf_conformance_groups_t::base32)) {
35,300✔
NEW
177
        return reject_capability("requires conformance group base32");
×
178
    }
179
    if (const auto p = std::get_if<Bin>(&ins)) {
576,387✔
180
        if (!supports(platform, p->is64 ? bpf_conformance_groups_t::base64 : bpf_conformance_groups_t::base32)) {
282,435✔
NEW
181
            return reject_capability(p->is64 ? "requires conformance group base64"
×
NEW
182
                                             : "requires conformance group base32");
×
183
        }
184
        if ((p->op == Bin::Op::MUL || p->op == Bin::Op::UDIV || p->op == Bin::Op::UMOD || p->op == Bin::Op::SDIV ||
255,758✔
185
             p->op == Bin::Op::SMOD) &&
428,615✔
186
            !supports(platform, p->is64 ? bpf_conformance_groups_t::divmul64 : bpf_conformance_groups_t::divmul32)) {
5,330✔
NEW
187
            return reject_capability(p->is64 ? "requires conformance group divmul64"
×
NEW
188
                                             : "requires conformance group divmul32");
×
189
        }
190
    }
191
    if (const auto p = std::get_if<Un>(&ins)) {
490,309✔
192
        const bool need_base64 = p->is64 || un_requires_base64(*p);
6,321✔
193
        if (!supports(platform, need_base64 ? bpf_conformance_groups_t::base64 : bpf_conformance_groups_t::base32)) {
4,740✔
194
            return reject_capability(need_base64 ? "requires conformance group base64"
6✔
195
                                                 : "requires conformance group base32");
2✔
196
        }
197
    }
198
    if (const auto p = std::get_if<Jmp>(&ins)) {
490,308✔
199
        if (!supports(platform,
84,152✔
200
                      p->cond ? (p->cond->is64 ? bpf_conformance_groups_t::base64 : bpf_conformance_groups_t::base32)
84,152✔
201
                              : bpf_conformance_groups_t::base32)) {
NEW
202
            return reject_capability((p->cond && p->cond->is64) ? "requires conformance group base64"
×
NEW
203
                                                                : "requires conformance group base32");
×
204
        }
205
    }
206
    if (const auto p = std::get_if<LoadPseudo>(&ins)) {
490,308✔
207
        if (!supports(platform, bpf_conformance_groups_t::base64)) {
2✔
NEW
208
            return reject_capability("requires conformance group base64");
×
209
        }
210
        switch (p->addr.kind) {
2✔
211
        case PseudoAddress::Kind::VARIABLE_ADDR: return reject_not_implemented("lddw variable_addr pseudo");
5✔
NEW
212
        case PseudoAddress::Kind::CODE_ADDR: return reject_not_implemented("lddw code_addr pseudo");
×
NEW
213
        case PseudoAddress::Kind::MAP_BY_IDX: return reject_not_implemented("lddw map_by_idx pseudo");
×
NEW
214
        case PseudoAddress::Kind::MAP_VALUE_BY_IDX: return reject_not_implemented("lddw map_value_by_idx pseudo");
×
NEW
215
        default: return reject_not_implemented("lddw unknown pseudo");
×
216
        }
217
    }
218
    if ((std::holds_alternative<LoadMapFd>(ins) || std::holds_alternative<LoadMapAddress>(ins)) &&
504,389✔
219
        !supports(platform, bpf_conformance_groups_t::base64)) {
21,138✔
NEW
220
        return reject_capability("requires conformance group base64");
×
221
    }
222
    if (const auto p = std::get_if<Mem>(&ins)) {
490,306✔
223
        if (!supports(platform,
116,219✔
224
                      (p->access.width == 8) ? bpf_conformance_groups_t::base64 : bpf_conformance_groups_t::base32)) {
116,219✔
NEW
225
            return reject_capability((p->access.width == 8) ? "requires conformance group base64"
×
NEW
226
                                                            : "requires conformance group base32");
×
227
        }
228
        if (p->is_signed && !supports(platform, bpf_conformance_groups_t::base64)) {
116,219✔
NEW
229
            return reject_capability("requires conformance group base64");
×
230
        }
231
    }
232
    if (std::holds_alternative<Packet>(ins) && !supports(platform, bpf_conformance_groups_t::packet)) {
490,306✔
233
        return reject_capability("requires conformance group packet");
76✔
234
    }
235
    if (const auto p = std::get_if<Atomic>(&ins)) {
490,268✔
236
        const auto group =
1,076✔
237
            (p->access.width == 8) ? bpf_conformance_groups_t::atomic64 : bpf_conformance_groups_t::atomic32;
807✔
238
        if (!supports(platform, group)) {
807✔
NEW
239
            return reject_capability((group == bpf_conformance_groups_t::atomic64)
×
240
                                         ? "requires conformance group atomic64"
NEW
241
                                         : "requires conformance group atomic32");
×
242
        }
243
    }
244
    return {};
490,268✔
245
}
246

247
// Validate instruction-level feature support before CFG construction.
248
// This is the user-facing rejection point for unsupported or unavailable features.
249
static void validate_instruction_feature_support(const InstructionSeq& insts, const ebpf_platform_t& platform) {
2,808✔
250
    for (const auto& [label, inst, _] : insts) {
330,938✔
251
        if (const auto reason = check_instruction_feature_support(inst, platform)) {
328,176✔
252
            if (reason->kind == RejectKind::NotImplemented) {
46✔
253
                throw InvalidControlFlow{"not implemented: " + reason->detail + " (at " + to_string(label) + ")"};
8✔
254
            }
255
            throw InvalidControlFlow{"rejected: " + reason->detail + " (at " + to_string(label) + ")"};
84✔
256
        }
328,153✔
257
    }
258
}
2,762✔
259

260
/// Update a control-flow graph to inline function macros.
261
static void add_cfg_nodes(CfgBuilder& builder, const Label& caller_label, const Label& entry_label) {
140✔
262
    bool first = true;
140✔
263

264
    // Get the label of the node to go to on returning from the macro.
265
    Label exit_to_label = builder.prog.cfg().get_child(caller_label);
140✔
266

267
    // Construct the variable prefix to use for the new stack frame
268
    // and store a copy in the CallLocal instruction since the instruction-specific
269
    // labels may only exist until the CFG is simplified.
270
    const std::string stack_frame_prefix = to_string(caller_label);
140✔
271
    if (const auto pcall = std::get_if<CallLocal>(&builder.prog.instruction_at(caller_label))) {
210✔
272
        pcall->stack_frame_prefix = stack_frame_prefix;
140✔
273
    }
274

275
    // Walk the transitive closure of CFG nodes starting at entry_label and ending at
276
    // any exit instruction.
277
    std::set macro_labels{entry_label};
367✔
278
    std::set seen_labels{entry_label};
367✔
279
    while (!macro_labels.empty()) {
672✔
280
        Label macro_label = *macro_labels.begin();
536✔
281
        macro_labels.erase(macro_label);
536✔
282

283
        if (stack_frame_prefix == macro_label.stack_frame_prefix) {
536✔
284
            throw InvalidControlFlow{stack_frame_prefix + ": illegal recursion"};
6✔
285
        }
286

287
        // Clone the macro block into a new block with the new stack frame prefix.
288
        const Label label{macro_label.from, macro_label.to, stack_frame_prefix};
800✔
289
        auto inst = builder.prog.instruction_at(macro_label);
532✔
290
        if (const auto pexit = std::get_if<Exit>(&inst)) {
532✔
291
            pexit->stack_frame_prefix = label.stack_frame_prefix;
142✔
292
        } else if (const auto pcall = std::get_if<Call>(&inst)) {
462✔
293
            pcall->stack_frame_prefix = label.stack_frame_prefix;
2✔
294
        }
295
        builder.insert(label, inst);
532✔
296

297
        if (first) {
532✔
298
            // Add an edge from the caller to the new block.
299
            first = false;
140✔
300
            builder.add_child(caller_label, label);
140✔
301
        }
302

303
        // Add an edge from any other predecessors.
304
        for (const auto& prev_macro_nodes = builder.prog.cfg().parents_of(macro_label);
798✔
305
             const auto& prev_macro_label : prev_macro_nodes) {
1,198✔
306
            const Label prev_label(prev_macro_label.from, prev_macro_label.to, to_string(caller_label));
400✔
307
            if (const auto& labels = builder.prog.cfg().labels();
400✔
308
                std::ranges::find(labels, prev_label) != labels.end()) {
400✔
309
                builder.add_child(prev_label, label);
394✔
310
            }
311
        }
400✔
312

313
        // Walk all successor nodes.
314
        for (const auto& next_macro_nodes = builder.prog.cfg().children_of(macro_label);
1,067✔
315
             const auto& next_macro_label : next_macro_nodes) {
1,336✔
316
            if (next_macro_label == builder.prog.cfg().exit_label()) {
807✔
317
                // This is an exit transition, so add edge to the block to execute
318
                // upon returning from the macro.
319
                builder.add_child(label, exit_to_label);
140✔
320
            } else if (!seen_labels.contains(next_macro_label)) {
398✔
321
                // Push any other unprocessed successor label onto the list to be processed.
322
                if (!macro_labels.contains(next_macro_label)) {
396✔
323
                    macro_labels.insert(next_macro_label);
396✔
324
                }
325
                seen_labels.insert(next_macro_label);
396✔
326
            }
327
        }
328
    }
1,068✔
329

330
    // Remove the original edge from the caller node to its successor,
331
    // since processing now goes through the function macro instead.
332
    builder.remove_child(caller_label, exit_to_label);
136✔
333

334
    // Finally, recurse to replace any nested function macros.
335
    string caller_label_str = to_string(caller_label);
136✔
336
    const long stack_frame_depth = std::ranges::count(caller_label_str, STACK_FRAME_DELIMITER) + 2;
136✔
337
    for (const auto& macro_label : seen_labels) {
596✔
338
        const Label label{macro_label.from, macro_label.to, caller_label_str};
750✔
339
        if (const auto pins = std::get_if<CallLocal>(&builder.prog.instruction_at(label))) {
720✔
340
            if (stack_frame_depth >= MAX_CALL_STACK_FRAMES) {
82✔
341
                throw InvalidControlFlow{"too many call stack frames"};
10✔
342
            }
343
            add_cfg_nodes(builder, label, pins->target);
78✔
344
        }
345
    }
490✔
346
}
588✔
347

348
/// Convert an instruction sequence to a control-flow graph (CFG).
349
static CfgBuilder instruction_seq_to_cfg(const InstructionSeq& insts, const bool must_have_exit) {
2,762✔
350
    CfgBuilder builder;
2,762✔
351
    assert(thread_local_program_info->platform != nullptr && "platform must be set before CFG construction");
2,762✔
352

353
    // First, add all instructions to the CFG without connecting
354
    for (const auto& [label, inst, _] : insts) {
327,038✔
355
        assert(!check_instruction_feature_support(inst, *thread_local_program_info->platform).has_value() &&
324,276✔
356
               "instruction support must be validated before CFG construction");
357
        if (std::holds_alternative<Undefined>(inst)) {
324,276✔
358
            continue;
×
359
        }
360
        builder.insert(label, inst);
324,276✔
361
    }
362

363
    if (insts.empty()) {
2,762✔
364
        throw InvalidControlFlow{"empty instruction sequence"};
×
365
    } else {
366
        const auto& [label, inst, _0] = insts[0];
2,762✔
367
        builder.add_child(builder.prog.cfg().entry_label(), label);
2,766✔
368
    }
369

370
    // Do a first pass ignoring all function macro calls.
371
    for (size_t i = 0; i < insts.size(); i++) {
327,038✔
372
        const auto& [label, inst, _0] = insts[i];
324,276✔
373

374
        if (std::holds_alternative<Undefined>(inst)) {
324,276✔
375
            continue;
18✔
376
        }
377
        assert(!std::holds_alternative<CallBtf>(inst) && "CallBtf must be rejected before CFG edge construction");
324,276✔
378

379
        Label fallthrough{builder.prog.cfg().exit_label()};
324,276✔
380
        if (i + 1 < insts.size()) {
324,276✔
381
            fallthrough = std::get<0>(insts[i + 1]);
321,514✔
382
        } else {
383
            if (has_fall(inst) && must_have_exit) {
3,457✔
384
                throw InvalidControlFlow{"fallthrough in last instruction"};
×
385
            }
386
        }
387
        if (const auto jmp = std::get_if<Jmp>(&inst)) {
324,276✔
388
            if (const auto cond = jmp->cond) {
35,614✔
389
                Label target_label = jmp->target;
30,046✔
390
                if (target_label == fallthrough) {
30,046✔
391
                    builder.add_child(label, fallthrough);
18✔
392
                    continue;
18✔
393
                }
394
                if (!builder.prog.cfg().contains(target_label)) {
30,028✔
395
                    throw InvalidControlFlow{"jump to undefined label " + to_string(target_label)};
×
396
                }
397
                builder.insert_jump(label, target_label, Assume{.cond = *cond, .is_implicit = true});
45,042✔
398
                builder.insert_jump(label, fallthrough, Assume{.cond = reverse(*cond), .is_implicit = true});
60,056✔
399
            } else {
30,046✔
400
                builder.add_child(label, jmp->target);
5,568✔
401
            }
402
        } else {
403
            if (has_fall(inst)) {
288,662✔
404
                builder.add_child(label, fallthrough);
286,848✔
405
            }
406
        }
407
        if (std::holds_alternative<Exit>(inst)) {
324,258✔
408
            builder.add_child(label, builder.prog.cfg().exit_label());
2,721✔
409
        }
410
    }
324,276✔
411

412
    // Now replace macros. We have to do this as a second pass so that
413
    // we only add new nodes that are actually reachable, based on the
414
    // results of the first pass.
415
    for (const auto& [label, inst, _] : insts) {
326,982✔
416
        if (const auto pins = std::get_if<CallLocal>(&inst)) {
324,255✔
417
            add_cfg_nodes(builder, label, pins->target);
62✔
418
        }
419
    }
420

421
    return builder;
2,754✔
422
}
8✔
423

424
Program Program::from_sequence(const InstructionSeq& inst_seq, const ProgramInfo& info,
2,808✔
425
                               const ebpf_verifier_options_t& options) {
426
    thread_local_program_info.set(info);
4,212✔
427
    thread_local_options = options;
2,808✔
428
    assert(info.platform != nullptr && "platform must be set before instruction feature validation");
2,808✔
429
    validate_instruction_feature_support(inst_seq, *info.platform);
2,808✔
430

431
    // Convert the instruction sequence to a deterministic control-flow graph.
432
    CfgBuilder builder = instruction_seq_to_cfg(inst_seq, options.cfg_opts.must_have_exit);
2,762✔
433

434
    // Detect loops using Weak Topological Ordering (WTO) and insert counters at loop entry points. WTO provides a
435
    // hierarchical decomposition of the CFG that identifies all strongly connected components (cycles) and their entry
436
    // points. These entry points serve as natural locations for loop counters that help verify program termination.
437
    if (options.cfg_opts.check_for_termination) {
2,754✔
438
        const Wto wto{builder.prog.cfg()};
34✔
439
        wto.for_each_loop_head([&](const Label& label) -> void {
34✔
440
            builder.insert_after(label, Label::make_increment_counter(label), IncrementLoopCounter{label});
64✔
441
        });
32✔
442
    }
34✔
443

444
    // Annotate the CFG by explicitly adding in assertions before every memory instruction.
445
    for (const auto& label : builder.prog.labels()) {
393,030✔
446
        builder.set_assertions(label, get_assertions(builder.prog.instruction_at(label), info, label));
585,414✔
447
    }
448
    return std::move(builder.prog);
4,131✔
449
}
2,754✔
450

451
std::set<BasicBlock> BasicBlock::collect_basic_blocks(const Cfg& cfg, const bool simplify) {
×
452
    if (!simplify) {
×
453
        std::set<BasicBlock> res;
×
454
        for (const Label& label : cfg.labels()) {
×
455
            if (label != cfg.entry_label() && label != cfg.exit_label()) {
×
456
                res.insert(BasicBlock{label});
×
457
            }
458
        }
459
        return res;
×
460
    }
×
461

462
    std::set<BasicBlock> res;
×
463
    std::set<Label> worklist;
×
464
    for (const Label& label : cfg.labels()) {
×
465
        worklist.insert(label);
×
466
    }
467
    std::set<Label> seen;
×
468
    while (!worklist.empty()) {
×
469
        Label label = *worklist.begin();
×
470
        worklist.erase(label);
×
471
        if (seen.contains(label)) {
×
472
            continue;
×
473
        }
474
        seen.insert(label);
×
475

476
        if (cfg.in_degree(label) == 1 && cfg.num_siblings(label) == 1) {
×
477
            continue;
×
478
        }
479
        BasicBlock bb{label};
×
480
        while (cfg.out_degree(label) == 1) {
×
481
            const Label& next_label = cfg.get_child(bb.last_label());
×
482

483
            if (seen.contains(next_label) || next_label == cfg.exit_label() || cfg.in_degree(next_label) != 1) {
×
484
                break;
485
            }
486

487
            if (bb.first_label() == cfg.entry_label()) {
×
488
                // Entry instruction is Undefined. We want to start with 0
489
                bb.m_ts.clear();
×
490
            }
491
            bb.m_ts.push_back(next_label);
×
492

493
            worklist.erase(next_label);
×
494
            seen.insert(next_label);
×
495

496
            label = next_label;
×
497
        }
×
498
        res.emplace(std::move(bb));
×
499
    }
×
500
    return res;
×
501
}
×
502

503
/// Get the type of given Instruction.
504
/// Most of these type names are also statistics header labels.
505
static std::string instype(Instruction ins) {
×
506
    if (const auto pcall = std::get_if<Call>(&ins)) {
×
507
        if (pcall->is_map_lookup) {
×
508
            return "call_1";
×
509
        }
510
        if (pcall->pairs.empty()) {
×
511
            if (std::ranges::all_of(pcall->singles,
×
512
                                    [](const ArgSingle kr) { return kr.kind == ArgSingle::Kind::ANYTHING; })) {
513
                return "call_nomem";
×
514
            }
515
        }
516
        return "call_mem";
×
517
    } else if (std::holds_alternative<Callx>(ins)) {
518
        return "callx";
×
519
    } else if (std::holds_alternative<CallBtf>(ins)) {
NEW
520
        return "call_mem";
×
521
    } else if (const auto pimm = std::get_if<Mem>(&ins)) {
×
522
        return pimm->is_load ? "load" : "store";
×
523
    } else if (std::holds_alternative<Atomic>(ins)) {
524
        return "load_store";
×
525
    } else if (std::holds_alternative<Packet>(ins)) {
526
        return "packet_access";
×
527
    } else if (const auto pins = std::get_if<Bin>(&ins)) {
×
528
        switch (pins->op) {
×
529
        case Bin::Op::MOV:
×
530
        case Bin::Op::MOVSX8:
531
        case Bin::Op::MOVSX16:
532
        case Bin::Op::MOVSX32: return "assign";
×
533
        default: return "arith";
×
534
        }
535
    } else if (std::holds_alternative<Un>(ins)) {
536
        return "arith";
×
537
    } else if (std::holds_alternative<LoadMapFd>(ins)) {
538
        return "assign";
×
539
    } else if (std::holds_alternative<LoadMapAddress>(ins)) {
540
        return "assign";
×
541
    } else if (std::holds_alternative<LoadPseudo>(ins)) {
NEW
542
        return "assign";
×
543
    } else if (std::holds_alternative<Assume>(ins)) {
544
        return "assume";
×
545
    } else {
546
        return "other";
×
547
    }
548
}
549

550
std::vector<std::string> stats_headers() {
×
551
    return {
552
        "instructions", "joins",      "other",      "jumps",         "assign",  "arith",
553
        "load",         "store",      "load_store", "packet_access", "call_1",  "call_mem",
554
        "call_nomem",   "reallocate", "map_in_map", "arith64",       "arith32",
555
    };
×
556
}
557

558
std::map<std::string, int> collect_stats(const Program& prog) {
×
559
    std::map<std::string, int> res;
×
560
    for (const auto& h : stats_headers()) {
×
561
        res[h] = 0;
×
562
    }
×
563
    for (const auto& label : prog.labels()) {
×
564
        res["instructions"]++;
×
565
        const auto cmd = prog.instruction_at(label);
×
566
        if (const auto pins = std::get_if<LoadMapFd>(&cmd)) {
×
567
            if (pins->mapfd == -1) {
×
568
                res["map_in_map"] = 1;
×
569
            }
570
        }
571
        if (const auto pins = std::get_if<Call>(&cmd)) {
×
572
            if (pins->reallocate_packet) {
×
573
                res["reallocate"] = 1;
×
574
            }
575
        }
576
        if (const auto pins = std::get_if<Bin>(&cmd)) {
×
577
            res[pins->is64 ? "arith64" : "arith32"]++;
×
578
        }
579
        res[instype(cmd)]++;
×
580
        if (prog.cfg().in_degree(label) > 1) {
×
581
            res["joins"]++;
×
582
        }
583
        if (prog.cfg().out_degree(label) > 1) {
×
584
            res["jumps"]++;
×
585
        }
586
    }
×
587
    return res;
×
588
}
×
589

590
Cfg cfg_from_adjacency_list(const std::map<Label, std::vector<Label>>& AdjList) {
6✔
591
    CfgBuilder builder;
6✔
592
    for (const auto& label : std::views::keys(AdjList)) {
46✔
593
        if (label == Label::entry || label == Label::exit) {
40✔
594
            continue;
6✔
595
        }
596
        builder.insert(label, Undefined{});
51✔
597
    }
598
    for (const auto& [label, children] : AdjList) {
46✔
599
        for (const auto& child : children) {
94✔
600
            builder.add_child(label, child);
54✔
601
        }
602
    }
603
    return builder.prog.cfg();
12✔
604
}
6✔
605
} // namespace prevail
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