• 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

85.65
/src/ir/marshal.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3
#include <cassert>
4
#include <map>
5
#include <variant>
6
#include <vector>
7

8
#include "crab_utils/num_safety.hpp"
9
#include "ir/marshal.hpp"
10

11
using std::vector;
12

13
namespace prevail {
14

15
static uint8_t op(const Condition::Op op) {
88✔
16
    using Op = Condition::Op;
44✔
17
    switch (op) {
88✔
18
    case Op::EQ: return 0x1;
4✔
19
    case Op::GT: return 0x2;
4✔
20
    case Op::GE: return 0x3;
4✔
21
    case Op::SET: return 0x4;
4✔
22
    case Op::NSET: assert(false);
23
    case Op::NE: return 0x5;
4✔
24
    case Op::SGT: return 0x6;
4✔
25
    case Op::SGE: return 0x7;
4✔
26
    case Op::LT: return 0xa;
4✔
27
    case Op::LE: return 0xb;
4✔
28
    case Op::SLT: return 0xc;
4✔
29
    case Op::SLE: return 0xd;
4✔
30
    }
31
    assert(false);
32
    return {};
33
}
34

35
static uint8_t op(const Bin::Op op) {
186✔
36
    using Op = Bin::Op;
93✔
37
    switch (op) {
186✔
38
    case Op::ADD: return 0x0;
7✔
39
    case Op::SUB: return 0x1;
6✔
40
    case Op::MUL: return 0x2;
6✔
41
    case Op::SDIV:
12✔
42
    case Op::UDIV: return 0x3;
12✔
43
    case Op::OR: return 0x4;
6✔
44
    case Op::AND: return 0x5;
6✔
45
    case Op::LSH: return 0x6;
6✔
46
    case Op::RSH: return 0x7;
6✔
47
    case Op::SMOD:
12✔
48
    case Op::UMOD: return 0x9;
12✔
49
    case Op::XOR: return 0xa;
6✔
50
    case Op::MOV:
14✔
51
    case Op::MOVSX8:
52
    case Op::MOVSX16:
53
    case Op::MOVSX32: return 0xb;
14✔
54
    case Op::ARSH: return 0xc;
6✔
55
    }
56
    assert(false);
57
    return {};
58
}
59

60
static int16_t offset(const Bin::Op op) {
186✔
61
    using Op = Bin::Op;
93✔
62
    switch (op) {
186✔
63
    case Op::SDIV:
12✔
64
    case Op::SMOD: return 1;
12✔
65
    case Op::MOVSX8: return 8;
2✔
66
    case Op::MOVSX16: return 16;
2✔
67
    case Op::MOVSX32: return 32;
3✔
68
    default: return 0;
74✔
69
    }
70
}
71

72
static uint8_t imm_endian(const Un::Op op) {
18✔
73
    using Op = Un::Op;
9✔
74
    switch (op) {
18✔
75
    case Op::NEG: assert(false); return 0;
76
    case Op::BE16:
6✔
77
    case Op::LE16:
3✔
78
    case Op::SWAP16: return 16;
6✔
79
    case Op::BE32:
6✔
80
    case Op::LE32:
3✔
81
    case Op::SWAP32: return 32;
6✔
82
    case Op::BE64:
6✔
83
    case Op::LE64:
3✔
84
    case Op::SWAP64: return 64;
6✔
85
    }
86
    assert(false);
87
    return {};
88
}
89

90
struct MarshalVisitor {
219✔
91
  private:
92
    static vector<EbpfInst> makeLddw(const Reg dst, const uint8_t type, const int32_t imm, const int32_t next_imm) {
18✔
93
        return {EbpfInst{.opcode = gsl::narrow<uint8_t>(INST_CLS_LD | width_to_opcode(8)),
18✔
94
                         .dst = dst.v,
18✔
95
                         .src = type,
18✔
96
                         .offset = 0,
97
                         .imm = imm},
98
                EbpfInst{.opcode = 0, .dst = 0, .src = 0, .offset = 0, .imm = next_imm}};
36✔
99
    }
100

101
  public:
102
    std::function<auto(Label)->int16_t> label_to_offset16;
103
    std::function<auto(Label)->int32_t> label_to_offset32;
104

105
    vector<EbpfInst> operator()(Undefined const& a) const {
×
106
        assert(false);
×
107
        return {};
108
    }
109

110
    vector<EbpfInst> operator()(LoadMapFd const& b) const { return makeLddw(b.dst, INST_LD_MODE_MAP_FD, b.mapfd, 0); }
2✔
111

112
    vector<EbpfInst> operator()(LoadMapAddress const& b) const {
2✔
113
        return makeLddw(b.dst, INST_LD_MODE_MAP_VALUE, b.mapfd, b.offset);
2✔
114
    }
115

116
    vector<EbpfInst> operator()(LoadPseudo const& b) const {
8✔
117
        uint8_t src{};
8✔
118
        switch (b.addr.kind) {
8✔
119
        case PseudoAddress::Kind::VARIABLE_ADDR: src = INST_LD_MODE_VARIABLE_ADDR; break;
1✔
120
        case PseudoAddress::Kind::CODE_ADDR: src = INST_LD_MODE_CODE_ADDR; break;
1✔
121
        case PseudoAddress::Kind::MAP_BY_IDX: src = INST_LD_MODE_MAP_BY_IDX; break;
1✔
122
        case PseudoAddress::Kind::MAP_VALUE_BY_IDX: src = INST_LD_MODE_MAP_VALUE_BY_IDX; break;
1✔
123
        }
124
        return makeLddw(b.dst, src, b.addr.imm, b.addr.next_imm);
8✔
125
    }
126

127
    vector<EbpfInst> operator()(Bin const& b) const {
192✔
128
        if (b.lddw) {
192✔
129
            const auto pimm = std::get_if<Imm>(&b.v);
6✔
130
            assert(pimm != nullptr);
6✔
131
            auto [imm, next_imm] = split(pimm->v);
6✔
132
            return makeLddw(b.dst, INST_LD_MODE_IMM, imm, next_imm);
6✔
133
        }
134

135
        EbpfInst res{.opcode = gsl::narrow<uint8_t>((b.is64 ? INST_CLS_ALU64 : INST_CLS_ALU) | (op(b.op) << 4)),
325✔
136
                     .dst = b.dst.v,
186✔
137
                     .src = 0,
138
                     .offset = offset(b.op),
279✔
139
                     .imm = 0};
186✔
140
        std::visit(Overloaded{[&](const Reg right) {
222✔
141
                                  res.opcode |= INST_SRC_REG;
72✔
142
                                  res.src = right.v;
72✔
143
                              },
36✔
144
                              [&](const Imm right) { res.imm = gsl::narrow<int32_t>(right.v); }},
114✔
145
                   b.v);
186✔
146
        return {res};
372✔
147
    }
148

149
    vector<EbpfInst> operator()(Un const& b) const {
22✔
150
        switch (b.op) {
22✔
151
        case Un::Op::NEG:
4✔
152
            return {EbpfInst{
2✔
153
                .opcode = gsl::narrow<uint8_t>((b.is64 ? INST_CLS_ALU64 : INST_CLS_ALU) | INST_ALU_OP_NEG),
5✔
154
                .dst = b.dst.v,
4✔
155
                .src = 0,
156
                .offset = 0,
157
                .imm = 0,
158
            }};
8✔
159
        case Un::Op::LE16:
6✔
160
        case Un::Op::LE32:
3✔
161
        case Un::Op::LE64:
3✔
162
            return {EbpfInst{
3✔
163
                .opcode = gsl::narrow<uint8_t>(INST_CLS_ALU | INST_END_LE | INST_ALU_OP_END),
164
                .dst = b.dst.v,
6✔
165
                .src = 0,
166
                .offset = 0,
167
                .imm = imm_endian(b.op),
9✔
168
            }};
12✔
169
        case Un::Op::BE16:
6✔
170
        case Un::Op::BE32:
3✔
171
        case Un::Op::BE64:
3✔
172
            return {EbpfInst{
3✔
173
                .opcode = gsl::narrow<uint8_t>(INST_CLS_ALU | INST_END_BE | INST_ALU_OP_END),
174
                .dst = b.dst.v,
6✔
175
                .src = 0,
176
                .offset = 0,
177
                .imm = imm_endian(b.op),
9✔
178
            }};
12✔
179
        case Un::Op::SWAP16:
6✔
180
        case Un::Op::SWAP32:
3✔
181
        case Un::Op::SWAP64:
3✔
182
            return {EbpfInst{
3✔
183
                .opcode = gsl::narrow<uint8_t>(INST_CLS_ALU64 | INST_ALU_OP_END),
184
                .dst = b.dst.v,
6✔
185
                .src = 0,
186
                .offset = 0,
187
                .imm = imm_endian(b.op),
9✔
188
            }};
12✔
189
        }
190
        assert(false);
×
191
        return {};
192
    }
193

194
    vector<EbpfInst> operator()(Call const& b) const {
4✔
195
        return {EbpfInst{.opcode = gsl::narrow<uint8_t>(INST_OP_CALL),
2✔
196
                         .dst = 0,
197
                         .src = INST_CALL_STATIC_HELPER,
198
                         .offset = 0,
199
                         .imm = b.func}};
8✔
200
    }
201

202
    vector<EbpfInst> operator()(CallLocal const& b) const {
×
203
        return {EbpfInst{.opcode = gsl::narrow<uint8_t>(INST_OP_CALL),
204
                         .dst = 0,
205
                         .src = INST_CALL_LOCAL,
206
                         .offset = 0,
207
                         .imm = label_to_offset32(b.target)}};
×
208
    }
209

210
    vector<EbpfInst> operator()(Callx const& b) const {
6✔
211
        // callx is defined to have the register in 'dst' not in 'src'.
212
        return {EbpfInst{.opcode = gsl::narrow<uint8_t>(INST_OP_CALLX),
3✔
213
                         .dst = b.func.v,
6✔
214
                         .src = INST_CALL_STATIC_HELPER,
215
                         .offset = 0}};
12✔
216
    }
217

218
    vector<EbpfInst> operator()(CallBtf const& b) const {
2✔
219
        return {EbpfInst{.opcode = gsl::narrow<uint8_t>(INST_OP_CALL),
1✔
220
                         .dst = 0,
221
                         .src = INST_CALL_BTF_HELPER,
222
                         .offset = 0,
223
                         .imm = b.btf_id}};
4✔
224
    }
225

226
    vector<EbpfInst> operator()(Exit const& b) const {
2✔
227
        return {EbpfInst{.opcode = INST_OP_EXIT, .dst = 0, .src = 0, .offset = 0, .imm = 0}};
4✔
228
    }
229

230
    vector<EbpfInst> operator()(Assume const&) const { throw std::invalid_argument("Cannot marshal assumptions"); }
×
231

232
    vector<EbpfInst> operator()(Jmp const& b) const {
92✔
233
        if (b.cond) {
92✔
234
            EbpfInst res{
88✔
235
                .opcode = gsl::narrow<uint8_t>(INST_CLS_JMP | (op(b.cond->op) << 4)),
132✔
236
                .dst = b.cond->left.v,
132✔
237
                .src = 0,
238
                .offset = label_to_offset16(b.target),
88✔
239
            };
176✔
240
            visit(Overloaded{[&](const Reg right) {
110✔
241
                                 res.opcode |= INST_SRC_REG;
44✔
242
                                 res.src = right.v;
44✔
243
                             },
22✔
244
                             [&](const Imm right) { res.imm = gsl::narrow<int32_t>(right.v); }},
44✔
245
                  b.cond->right);
88✔
246
            return {res};
176✔
247
        } else {
248
            const int32_t imm = label_to_offset32(b.target);
4✔
249
            if (imm != 0) {
4✔
250
                return {EbpfInst{.opcode = INST_OP_JA32, .imm = imm}};
×
251
            } else {
252
                return {EbpfInst{.opcode = INST_OP_JA16, .offset = label_to_offset16(b.target)}};
10✔
253
            }
254
        }
255
    }
256

257
    vector<EbpfInst> operator()(Mem const& b) const {
34✔
258
        const Deref access = b.access;
34✔
259
        if (b.is_signed && (!b.is_load || access.width == 8)) {
34✔
NEW
260
            throw std::runtime_error(std::string("Invalid MEMSX form: ") + to_string(b));
×
261
        }
262
        EbpfInst res{
34✔
263
            .opcode =
264
                gsl::narrow<uint8_t>((b.is_signed ? INST_MODE_MEMSX : INST_MODE_MEM) | width_to_opcode(access.width)),
51✔
265
            .dst = 0,
266
            .src = 0,
267
            .offset = gsl::narrow<int16_t>(access.offset),
51✔
268
        };
34✔
269
        if (b.is_load) {
34✔
270
            if (!std::holds_alternative<Reg>(b.value)) {
14✔
271
                throw std::runtime_error(std::string("LD IMM: ") + to_string(b));
5✔
272
            }
273
            res.opcode |= INST_CLS_LDX;
12✔
274
            res.dst = gsl::narrow<uint8_t>(std::get<Reg>(b.value).v);
12✔
275
            res.src = access.basereg.v;
12✔
276
        } else {
277
            if (b.is_signed) {
20✔
NEW
278
                throw std::runtime_error(std::string("ST MEMSX: ") + to_string(b));
×
279
            }
280
            res.opcode |= INST_CLS_ST;
20✔
281
            res.dst = access.basereg.v;
20✔
282
            if (const auto preg = std::get_if<Reg>(&b.value)) {
20✔
283
                res.opcode |= 0x1;
10✔
284
                res.src = preg->v;
10✔
285
            } else {
286
                res.opcode |= 0x0;
10✔
287
                res.imm = gsl::narrow<int32_t>(std::get<Imm>(b.value).v);
10✔
288
            }
289
        }
290
        return {res};
64✔
291
    }
292

293
    vector<EbpfInst> operator()(Packet const& b) const {
24✔
294
        EbpfInst res{
24✔
295
            .opcode = gsl::narrow<uint8_t>(INST_CLS_LD | width_to_opcode(b.width)),
24✔
296
            .dst = 0,
297
            .src = 0,
298
            .offset = 0,
299
            .imm = gsl::narrow<int32_t>(b.offset),
36✔
300
        };
24✔
301
        if (b.regoffset) {
24✔
302
            res.opcode |= INST_MODE_IND;
12✔
303
            res.src = b.regoffset->v;
12✔
304
        } else {
305
            res.opcode |= INST_MODE_ABS;
12✔
306
        }
307
        return {res};
48✔
308
    }
309

310
    vector<EbpfInst> operator()(Atomic const& b) const {
48✔
311
        auto imm = gsl::narrow<int32_t>(b.op);
48✔
312
        if (b.fetch) {
48✔
313
            imm |= INST_FETCH;
24✔
314
        }
315
        return {
24✔
316
            EbpfInst{.opcode = gsl::narrow<uint8_t>(INST_CLS_STX | INST_MODE_ATOMIC | width_to_opcode(b.access.width)),
48✔
317
                     .dst = b.access.basereg.v,
48✔
318
                     .src = b.valreg.v,
48✔
319
                     .offset = gsl::narrow<int16_t>(b.access.offset),
72✔
320
                     .imm = imm}};
96✔
321
    }
322

323
    vector<EbpfInst> operator()(IncrementLoopCounter const&) const { return {}; }
×
324
};
325

326
vector<EbpfInst> marshal(const Instruction& ins, const Pc pc) {
438✔
327
    return std::visit(MarshalVisitor{label_to_offset16(pc), label_to_offset32(pc)}, ins);
657✔
328
}
329

330
int size(const Instruction& inst) {
1,340✔
331
    if (const auto pins = std::get_if<Bin>(&inst)) {
1,340✔
332
        if (pins->lddw) {
556✔
333
            return 2;
2✔
334
        }
335
    }
336
    if (std::holds_alternative<LoadMapFd>(inst)) {
1,336✔
337
        return 2;
24✔
338
    }
339
    if (std::holds_alternative<LoadMapAddress>(inst)) {
1,288✔
340
        return 2;
341
    }
342
    if (std::holds_alternative<LoadPseudo>(inst)) {
1,288✔
NEW
343
        return 2;
×
344
    }
345
    return 1;
644✔
346
}
347

348
static auto get_labels(const InstructionSeq& insts) {
×
349
    Pc pc = 0;
×
350
    std::map<Label, Pc> pc_of_label;
×
351
    for (const auto& [label, inst, _] : insts) {
×
352
        pc_of_label[label] = pc;
×
353
        pc += size(inst);
×
354
    }
355
    return pc_of_label;
×
356
}
×
357

358
vector<EbpfInst> marshal(const InstructionSeq& insts) {
×
359
    vector<EbpfInst> res;
×
360
    const auto pc_of_label = get_labels(insts);
×
361
    Pc pc = 0;
×
362
    for (auto [label, ins, _] : insts) {
×
363
        (void)label; // unused
364
        if (const auto pins = std::get_if<Jmp>(&ins)) {
×
365
            pins->target = Label{gsl::narrow<int>(pc_of_label.at(pins->target))};
×
366
        }
367
        for (const auto e : marshal(ins, pc)) {
×
368
            pc++;
×
369
            res.push_back(e);
×
370
        }
×
371
    }
×
372
    return res;
×
373
}
×
374
} // 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