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

Alan-Jowett / bpf_conformance / 21268647438

22 Jan 2026 11:29PM UTC coverage: 95.041% (-0.2%) from 95.284%
21268647438

push

github

web-flow
Add RFC 9669-focused test corpus (#435)

* Add RFC 9669-focused test corpus

Signed-off-by: Alan Jowett <alan.jowett@microsoft.com>

* Add Copilot agent instructions

Signed-off-by: Alan Jowett <alan.jowett@microsoft.com>

* Add MEMSX load mnemonics

Signed-off-by: Alan Jowett <alan.jowett@microsoft.com>

* Fix RFC9669 tests and assembler

Signed-off-by: Alan Jowett <alanjo@users.noreply.github.com>

* Remove dead assembler comment parsing

Signed-off-by: Alan Jowett <alanjo@users.noreply.github.com>

* Remove dead code in assembler

Signed-off-by: Alan Jowett <alanjo@users.noreply.github.com>

---------

Signed-off-by: Alan Jowett <alan.jowett@microsoft.com>
Signed-off-by: Alan Jowett <alanjo@users.noreply.github.com>
Co-authored-by: Alan Jowett <alan.jowett@microsoft.com>
Co-authored-by: Alan Jowett <alanjo@users.noreply.github.com>

11 of 12 new or added lines in 1 file covered. (91.67%)

1054 of 1109 relevant lines covered (95.04%)

6207.59 hits per line

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

97.9
/src/bpf_assembler.cc
1
// Copyright (c) Microsoft Corporation
2
// SPDX-License-Identifier: MIT
3

4
#include <array>
5
#include <functional>
6
#include <optional>
7
#include <unordered_map>
8
#include <variant>
9
#include <sstream>
10

11
#include "bpf_assembler.h"
12

13
// The _bpf_assembler class is a helper class for the bpf_assembler.
14
typedef class _bpf_assembler
15
{
16

17
  private:
18
    typedef std::variant<ebpf_inst, std::array<ebpf_inst, 2>> bpf_encode_result_t;
19

20
    typedef bpf_encode_result_t (_bpf_assembler::*bpf_encode_t)(
21
        const std::string& mnemonic, const std::vector<std::string>& operands);
22

23
    const std::unordered_map<std::string, int> _bpf_encode_register_map{
24
        {"%r0", 0},
1,432✔
25
        {"%r1", 1},
1,432✔
26
        {"%r2", 2},
1,432✔
27
        {"%r3", 3},
1,432✔
28
        {"%r4", 4},
1,432✔
29
        {"%r5", 5},
1,432✔
30
        {"%r6", 6},
1,432✔
31
        {"%r7", 7},
1,432✔
32
        {"%r8", 8},
1,432✔
33
        {"%r9", 9},
1,432✔
34
        {"%r10", 10},
1,432✔
35
        // Add fake registers to support negative tests.
36
        {"%r11", 11},
1,432✔
37
        {"%r12", 12},
1,432✔
38
        {"%r13", 13},
1,432✔
39
        {"%r14", 14},
1,432✔
40
        {"%r15", 15},
1,432✔
41
    };
42

43
    const std::unordered_map<std::string, int> _bpf_encode_alu_ops{
44
        {"add", 0x0},
1,432✔
45
        {"sub", 0x1},
1,432✔
46
        {"mul", 0x2},
1,432✔
47
        {"div", 0x3},
1,432✔
48
        {"sdiv", 0x3},
1,432✔
49
        {"or", 0x4},
1,432✔
50
        {"and", 0x5},
1,432✔
51
        {"lsh", 0x6},
1,432✔
52
        {"rsh", 0x7},
1,432✔
53
        {"neg", 0x8},
1,432✔
54
        {"mod", 0x9},
1,432✔
55
        {"smod", 0x9},
1,432✔
56
        {"xor", 0xa},
1,432✔
57
        {"mov", 0xb},
1,432✔
58
        {"movsx", 0xb},
1,432✔
59
        {"arsh", 0xc},
1,432✔
60
        {"le", 0xd},
1,432✔
61
        {"be", 0xd},
1,432✔
62
        {"swap", 0xd},
1,432✔
63
    };
64

65
    const std::unordered_map<std::string, int> _bpf_encode_jmp_ops{
66
        {"jeq", 0x1},
1,432✔
67
        {"jgt", 0x2},
1,432✔
68
        {"jge", 0x3},
1,432✔
69
        {"jset", 0x4},
1,432✔
70
        {"jne", 0x5},
1,432✔
71
        {"jsgt", 0x6},
1,432✔
72
        {"jsge", 0x7},
1,432✔
73
        {"jlt", 0xa},
1,432✔
74
        {"jle", 0xb},
1,432✔
75
        {"jslt", 0xc},
1,432✔
76
        {"jsle", 0xd},
1,432✔
77
    };
78

79
    // Labels discovered while parsing the assembly code.
80
    std::unordered_map<std::string, size_t> _labels{};
81

82
    // Vector of the same size as the assembly code, containing the label to
83
    // jump to.
84
    std::vector<std::optional<std::string>> _jump_instructions{};
85

86
    uint64_t
87
    _decode_imm64(const std::string& str)
1,230✔
88
    {
89
        if (str.find("0x") == std::string::npos) {
1,230✔
90
            return std::stoull(str);
18✔
91
        } else {
92
            return std::stoull(str, nullptr, 16);
1,212✔
93
        }
94
    }
95

96
    uint32_t
97
    _decode_imm32(const std::string& str)
10,502✔
98
    {
99
        if (str.find("0x") == std::string::npos) {
10,502✔
100
            return static_cast<uint32_t>(std::stoull(str));
7,748✔
101
        } else {
102
            return static_cast<uint32_t>(std::stoull(str, nullptr, 16));
2,754✔
103
        }
104
    }
105

106
    uint16_t
107
    _decode_offset(const std::string& str)
2,890✔
108
    {
109
        if (str.find("0x") == std::string::npos) {
2,890✔
110
            return static_cast<uint16_t>(std::stoull(str));
2,866✔
111
        } else {
112
            return static_cast<uint16_t>(std::stoull(str, nullptr, 16));
24✔
113
        }
114
    }
115

116
    uint16_t
117
    _decode_jump_target(const std::string& str)
2,884✔
118
    {
119
        if (str.starts_with("+") || str.starts_with("-")) {
3,978✔
120
            return _decode_offset(str);
696✔
121
        } else {
122
            _jump_instructions.back() = {str};
2,188✔
123
            return 0;
2,188✔
124
        }
125
    }
126

127
    uint8_t
128
    _decode_register(const std::string& register_name)
23,678✔
129
    {
130
        auto reg = _bpf_encode_register_map.find(register_name);
11,839✔
131
        if (reg == _bpf_encode_register_map.end()) {
23,678✔
132
            throw std::runtime_error(std::string("Invalid register: ") + register_name);
3✔
133
        }
134
        return static_cast<uint8_t>(reg->second);
23,676✔
135
    }
136

137
    std::tuple<uint8_t, uint16_t>
138
    _decode_register_and_offset(const std::string& operand)
2,414✔
139
    {
140
        auto reg_start = operand.find('[');
2,414✔
141
        auto reg_end = operand.find('+');
2,414✔
142
        reg_end = (reg_end != std::string::npos) ? reg_end : operand.find('-');
2,414✔
143
        reg_end = (reg_end != std::string::npos) ? reg_end : operand.find(']');
2,414✔
144

145
        if (reg_start == std::string::npos || reg_end == std::string::npos) {
2,414✔
146
            throw std::runtime_error(std::string("Failed to decode register and offset: ") + operand);
3✔
147
        }
148

149
        if (operand.substr(reg_end).starts_with(']')) {
3,618✔
150
            return std::make_tuple<uint8_t, uint16_t>(
151
                _decode_register(operand.substr(reg_start + 1, reg_end - reg_start - 1)), 0);
576✔
152
        } else {
153
            return std::make_tuple<uint8_t, uint16_t>(
154
                _decode_register(operand.substr(reg_start + 1, reg_end - reg_start - 1)),
4,248✔
155
                _decode_offset(operand.substr(reg_end)));
5,310✔
156
        }
157
    }
158

159
    bpf_encode_result_t
160
    _encode_ld([[maybe_unused]] const std::string& mnemonic, const std::vector<std::string>& operands)
1,230✔
161
    {
162
        std::array<ebpf_inst, 2> inst{};
1,230✔
163
        // Issue: https://github.com/Alan-Jowett/bpf_conformance/issues/59
164
        // Add support for other 64-bit immediate values.
165
        inst[0].opcode = EBPF_OP_LDDW;
615✔
166
        inst[0].dst = _decode_register(operands[0]);
1,230✔
167
        uint64_t immediate = _decode_imm64(operands[1]);
1,230✔
168
        inst[0].imm = static_cast<uint32_t>(immediate);
1,230✔
169
        inst[1].imm = static_cast<uint32_t>(immediate >> 32);
1,230✔
170

171
        return inst;
1,845✔
172
    }
173

174
    bpf_encode_result_t
175
    _encode_ldx(const std::string& mnemonic, const std::vector<std::string>& operands)
1,394✔
176
    {
177
        ebpf_inst inst{};
1,394✔
178
        inst.dst = _decode_register(operands[0]);
1,394✔
179
        auto [src, offset] = _decode_register_and_offset(operands[1]);
1,394✔
180
        inst.src = src;
1,392✔
181
        inst.offset = offset;
1,392✔
182
        if (mnemonic == "ldxb") {
1,392✔
183
            inst.opcode = EBPF_OP_LDXB;
141✔
184
        } else if (mnemonic == "ldxdw") {
1,110✔
185
            inst.opcode = EBPF_OP_LDXDW;
268✔
186
        } else if (mnemonic == "ldxh") {
574✔
187
            inst.opcode = EBPF_OP_LDXH;
177✔
188
        } else if (mnemonic == "ldxw") {
220✔
189
            inst.opcode = EBPF_OP_LDXW;
107✔
190
        } else if (mnemonic == "ldxsb") {
6✔
191
            inst.opcode = EBPF_OP_LDXSB;
1✔
192
        } else if (mnemonic == "ldxsh") {
4✔
193
            inst.opcode = EBPF_OP_LDXSH;
1✔
194
        } else if (mnemonic == "ldxsw") {
2✔
195
            inst.opcode = EBPF_OP_LDXSW;
1✔
196
        } else {
NEW
197
            throw std::runtime_error(std::string("Invalid mnemonic: ") + mnemonic);
×
198
        }
199

200
        return inst;
2,088✔
201
    }
202

203
    bpf_encode_result_t
204
    _encode_st(const std::string& mnemonic, const std::vector<std::string>& operands)
120✔
205
    {
206
        ebpf_inst inst{};
120✔
207
        auto [dst, offset] = _decode_register_and_offset(operands[0]);
120✔
208
        inst.dst = dst;
120✔
209
        inst.offset = offset;
120✔
210
        if (mnemonic == "stb") {
120✔
211
            inst.opcode = EBPF_OP_STB;
9✔
212
        } else if (mnemonic == "stdw") {
102✔
213
            inst.opcode = EBPF_OP_STDW;
19✔
214
        } else if (mnemonic == "sth") {
64✔
215
            inst.opcode = EBPF_OP_STH;
15✔
216
        } else if (mnemonic == "stw") {
34✔
217
            inst.opcode = EBPF_OP_STW;
17✔
218
        }
219
        inst.imm = _decode_imm32(operands[1]);
120✔
220
        return inst;
180✔
221
    }
222

223
    bpf_encode_result_t
224
    _encode_stx(const std::string& mnemonic, const std::vector<std::string>& operands)
584✔
225
    {
226
        ebpf_inst inst{};
584✔
227
        auto [dst, offset] = _decode_register_and_offset(operands[0]);
584✔
228
        inst.dst = dst;
584✔
229
        inst.offset = offset;
584✔
230
        inst.src = _decode_register(operands[1]);
584✔
231
        if (mnemonic == "stxb") {
584✔
232
            inst.opcode = EBPF_OP_STXB;
121✔
233
        } else if (mnemonic == "stxdw") {
342✔
234
            inst.opcode = EBPF_OP_STXDW;
157✔
235
        } else if (mnemonic == "stxh") {
28✔
236
            inst.opcode = EBPF_OP_STXH;
7✔
237
        } else if (mnemonic == "stxw") {
14✔
238
            inst.opcode = EBPF_OP_STXW;
7✔
239
        }
240

241
        return inst;
876✔
242
    }
243

244
    bpf_encode_result_t
245
    _encode_alu(const std::string& mnemonic, const std::vector<std::string>& operands)
11,374✔
246
    {
247
        ebpf_inst inst{};
5,687✔
248
        std::string alu_op;
5,687✔
249
        if (mnemonic.starts_with("be")) {
11,374✔
250
            inst.opcode = EBPF_OP_BE;
225✔
251
            inst.dst = _decode_register(operands[0]);
450✔
252
            inst.imm = _decode_imm32(mnemonic.substr(2));
450✔
253
            return inst;
225✔
254
        } else if (mnemonic.starts_with("le")) {
10,924✔
255
            inst.opcode = EBPF_OP_LE;
33✔
256
            inst.dst = _decode_register(operands[0]);
66✔
257
            inst.imm = _decode_imm32(mnemonic.substr(2));
66✔
258
            return inst;
33✔
259
        } else if (mnemonic.starts_with("swap")) {
10,858✔
260
            inst.opcode = EBPF_OP_SWAP;
21✔
261
            inst.dst = _decode_register(operands[0]);
42✔
262
            inst.imm = _decode_imm32(mnemonic.substr(4));
42✔
263
            return inst;
21✔
264
        }
265
        if (mnemonic.starts_with("sdiv") || mnemonic.starts_with("smod")) {
16,148✔
266
            inst.offset = 1;
200✔
267
        }
268

269
        if (mnemonic.ends_with("32")) {
10,816✔
270
            inst.opcode |= EBPF_CLS_ALU;
2,175✔
271
            alu_op = mnemonic.substr(0, mnemonic.size() - 2);
6,525✔
272
        } else if (mnemonic.ends_with("64")) {
6,466✔
273
            inst.opcode |= EBPF_CLS_ALU64;
21✔
274
            alu_op = mnemonic.substr(0, mnemonic.size() - 2);
63✔
275
        } else {
276
            inst.opcode |= EBPF_CLS_ALU64;
3,212✔
277
            alu_op = mnemonic;
3,212✔
278
        }
279
        if (alu_op.starts_with("movsx")) {
10,816✔
280
            inst.offset = _decode_offset(alu_op.substr(5));
71✔
281
            alu_op = "movsx";
35✔
282
        }
283
        auto iter = _bpf_encode_alu_ops.find(alu_op);
5,408✔
284
        // It is not possible to reach here with no match.
285
        inst.opcode |= iter->second << 4;
10,816✔
286

287
        inst.dst = _decode_register(operands[0]);
10,816✔
288

289
        if (operands.size() == 2) {
10,816✔
290
            if (operands[1].starts_with('%')) {
5,368✔
291
                inst.opcode |= EBPF_SRC_REG;
2,252✔
292
                inst.src = _decode_register(operands[1]);
2,252✔
293
            } else {
294
                inst.opcode |= EBPF_SRC_IMM;
4,242✔
295
                inst.imm = _decode_imm32(operands[1]);
8,484✔
296
            }
297
        }
298

299
        return inst;
5,407✔
300
    }
5,687✔
301

302
    bpf_encode_result_t
303
    _encode_jmp(const std::string& mnemonic, const std::vector<std::string>& operands)
6,388✔
304
    {
305
        ebpf_inst inst{};
3,194✔
306
        if (mnemonic == "ja") {
6,388✔
307
            inst.opcode = EBPF_CLS_JMP;
64✔
308
            inst.offset = _decode_jump_target(operands[0]);
128✔
309
        } else if (mnemonic == "ja32") {
6,260✔
310
            inst.opcode = EBPF_CLS_JMP32;
13✔
311
            inst.imm = _decode_jump_target(operands[0]);
26✔
312
        } else if (mnemonic == "exit") {
6,234✔
313
            inst.opcode = EBPF_OP_EXIT;
1,740✔
314
        } else if (mnemonic == "call") {
2,754✔
315
            inst.opcode = EBPF_OP_CALL;
19✔
316
            auto mode = operands[0];
19✔
317
            auto target = operands[1];
19✔
318
            // Mode determines if this is a helper function, a local call, or a call to a runtime function.
319
            if (mode == "helper") {
38✔
320
                if (target.starts_with('%')) {
12✔
321
                    inst.opcode |= EBPF_SRC_REG;
6✔
322
                    inst.dst = _decode_register(target);
12✔
323
                } else {
324
                    inst.opcode |= EBPF_SRC_IMM;
6✔
325
                    inst.imm = _decode_imm32(target);
12✔
326
                }
327
                inst.src = 0;
12✔
328
            } else if (mode == "local") {
14✔
329
                inst.imm = _decode_jump_target(target);
14✔
330
                inst.src = 1;
7✔
331
            } else if (mode == "runtime") {
×
332
                inst.imm = _decode_imm32(target);
×
333
                inst.src = 2;
334
            } else {
335
                throw std::runtime_error("Invalid call mode");
×
336
            }
337
        } else {
19✔
338
            mnemonic.ends_with("32") ? inst.opcode = EBPF_CLS_JMP32 : inst.opcode = EBPF_CLS_JMP;
2,716✔
339
            auto iter =
340
                _bpf_encode_jmp_ops.find(mnemonic.ends_with("32") ? mnemonic.substr(0, mnemonic.size() - 2) : mnemonic);
2,716✔
341
            // It is not possible to reach here with no match.
342
            inst.opcode |= iter->second << 4;
2,716✔
343
            inst.dst = _decode_register(operands[0]);
2,716✔
344
            if (operands[1].starts_with('%')) {
1,358✔
345
                inst.opcode |= EBPF_SRC_REG;
1,388✔
346
                inst.src = _decode_register(operands[1]);
1,388✔
347
            } else {
348
                inst.opcode |= EBPF_SRC_IMM;
664✔
349
                inst.imm = _decode_imm32(operands[1]);
1,328✔
350
            }
351
            inst.offset = _decode_jump_target(operands[2]);
2,716✔
352
        }
353
        return inst;
9,582✔
354
    }
355

356
    bpf_encode_result_t
357
    _encode_atomic_add(const std::string& mnemonic, const std::vector<std::string>& operands)
64✔
358
    {
359
        ebpf_inst inst{};
32✔
360
        if (mnemonic.ends_with("32")) {
64✔
361
            inst.opcode = EBPF_OP_ATOMIC32_STORE;
14✔
362
        } else {
363
            inst.opcode = EBPF_OP_ATOMIC_STORE;
18✔
364
        }
365
        auto [dst, offset] = _decode_register_and_offset(operands[1]);
64✔
366
        inst.dst = dst;
64✔
367
        inst.offset = offset;
64✔
368
        inst.src = _decode_register(operands[2]);
64✔
369
        inst.imm = EBPF_ALU_OP_ADD;
32✔
370
        // Set fetch bit if fetch is the first operand.
371
        if (operands[0] == "fetch") {
64✔
372
            inst.imm |= EBPF_ATOMIC_OP_FETCH;
14✔
373
        }
374
        return inst;
96✔
375
    }
376

377
    bpf_encode_result_t
378
    _encode_atomic_and(const std::string& mnemonic, const std::vector<std::string>& operands)
56✔
379
    {
380
        ebpf_inst inst{};
28✔
381
        if (mnemonic.ends_with("32")) {
56✔
382
            inst.opcode = EBPF_OP_ATOMIC32_STORE;
14✔
383
        } else {
384
            inst.opcode = EBPF_OP_ATOMIC_STORE;
14✔
385
        }
386
        auto [dst, offset] = _decode_register_and_offset(operands[1]);
56✔
387
        inst.dst = dst;
56✔
388
        inst.offset = offset;
56✔
389
        inst.src = _decode_register(operands[2]);
56✔
390
        inst.imm = EBPF_ALU_OP_AND;
28✔
391
        // Set fetch bit if fetch is the first operand.
392
        if (operands[0] == "fetch") {
56✔
393
            inst.imm |= EBPF_ATOMIC_OP_FETCH;
14✔
394
        }
395
        return inst;
84✔
396
    }
397

398
    bpf_encode_result_t
399
    _encode_atomic_or(const std::string& mnemonic, const std::vector<std::string>& operands)
56✔
400
    {
401
        ebpf_inst inst{};
28✔
402
        if (mnemonic.ends_with("32")) {
56✔
403
            inst.opcode = EBPF_OP_ATOMIC32_STORE;
14✔
404
        } else {
405
            inst.opcode = EBPF_OP_ATOMIC_STORE;
14✔
406
        }
407
        auto [dst, offset] = _decode_register_and_offset(operands[1]);
56✔
408
        inst.dst = dst;
56✔
409
        inst.offset = offset;
56✔
410
        inst.src = _decode_register(operands[2]);
56✔
411
        inst.imm = EBPF_ALU_OP_OR;
28✔
412
        // Set fetch bit if fetch is the first operand.
413
        if (operands[0] == "fetch") {
56✔
414
            inst.imm |= EBPF_ATOMIC_OP_FETCH;
14✔
415
        }
416
        return inst;
84✔
417
    }
418

419
    bpf_encode_result_t
420
    _encode_atomic_xor(const std::string& mnemonic, const std::vector<std::string>& operands)
56✔
421
    {
422
        ebpf_inst inst{};
28✔
423
        if (mnemonic.ends_with("32")) {
56✔
424
            inst.opcode = EBPF_OP_ATOMIC32_STORE;
14✔
425
        } else {
426
            inst.opcode = EBPF_OP_ATOMIC_STORE;
14✔
427
        }
428
        auto [dst, offset] = _decode_register_and_offset(operands[1]);
56✔
429
        inst.dst = dst;
56✔
430
        inst.offset = offset;
56✔
431
        inst.src = _decode_register(operands[2]);
56✔
432
        inst.imm = EBPF_ALU_OP_XOR;
28✔
433
        // Set fetch bit if fetch is the first operand.
434
        if (operands[0] == "fetch") {
56✔
435
            inst.imm |= EBPF_ATOMIC_OP_FETCH;
14✔
436
        }
437
        return inst;
84✔
438
    }
439

440
    bpf_encode_result_t
441
    _encode_atomic_xchg(const std::string& mnemonic, const std::vector<std::string>& operands)
28✔
442
    {
443
        ebpf_inst inst{};
14✔
444
        if (mnemonic.ends_with("32")) {
28✔
445
            inst.opcode = EBPF_OP_ATOMIC32_STORE;
7✔
446
        } else {
447
            inst.opcode = EBPF_OP_ATOMIC_STORE;
7✔
448
        }
449
        auto [dst, offset] = _decode_register_and_offset(operands[1]);
28✔
450
        inst.dst = dst;
28✔
451
        inst.offset = offset;
28✔
452
        inst.src = _decode_register(operands[2]);
28✔
453
        // EPBF_ATOMIC_OP_XCHG always has the fetch bit set.
454
        inst.imm = EPBF_ATOMIC_OP_XCHG;
14✔
455
        return inst;
42✔
456
    }
457

458
    bpf_encode_result_t
459
    _encode_atomic_cmpxchg(const std::string& mnemonic, const std::vector<std::string>& operands)
56✔
460
    {
461
        ebpf_inst inst{};
28✔
462
        if (mnemonic.ends_with("32")) {
56✔
463
            inst.opcode = EBPF_OP_ATOMIC32_STORE;
14✔
464
        } else {
465
            inst.opcode = EBPF_OP_ATOMIC_STORE;
14✔
466
        }
467
        auto [dst, offset] = _decode_register_and_offset(operands[1]);
56✔
468
        inst.dst = dst;
56✔
469
        inst.offset = offset;
56✔
470
        inst.src = _decode_register(operands[2]);
56✔
471
        // EBPF_ATOMIC_OP_CMPXCHG always has the fetch bit set.
472
        inst.imm = EBPF_ATOMIC_OP_CMPXCHG;
28✔
473
        return inst;
84✔
474
    }
475

476
    const std::unordered_map<std::string, std::tuple<bpf_encode_t, size_t>> _bpf_mnemonic_map{
477
        {"add", {&_bpf_assembler::_encode_alu, 2}},   {"add32", {&_bpf_assembler::_encode_alu, 2}},
2,864✔
478
        {"and", {&_bpf_assembler::_encode_alu, 2}},   {"and32", {&_bpf_assembler::_encode_alu, 2}},
2,864✔
479
        {"arsh", {&_bpf_assembler::_encode_alu, 2}},  {"arsh32", {&_bpf_assembler::_encode_alu, 2}},
2,864✔
480
        {"be16", {&_bpf_assembler::_encode_alu, 1}},  {"be32", {&_bpf_assembler::_encode_alu, 1}},
2,864✔
481
        {"be64", {&_bpf_assembler::_encode_alu, 1}},  {"call", {&_bpf_assembler::_encode_jmp, 2}},
2,864✔
482
        {"div", {&_bpf_assembler::_encode_alu, 2}},   {"div32", {&_bpf_assembler::_encode_alu, 2}},
2,864✔
483
        {"exit", {&_bpf_assembler::_encode_jmp, 0}},  {"ja", {&_bpf_assembler::_encode_jmp, 1}},
2,864✔
484
        {"ja32", {&_bpf_assembler::_encode_jmp, 1}},
1,432✔
485
        {"jeq", {&_bpf_assembler::_encode_jmp, 3}},   {"jeq32", {&_bpf_assembler::_encode_jmp, 3}},
2,864✔
486
        {"jge", {&_bpf_assembler::_encode_jmp, 3}},   {"jge32", {&_bpf_assembler::_encode_jmp, 3}},
2,864✔
487
        {"jgt", {&_bpf_assembler::_encode_jmp, 3}},   {"jgt32", {&_bpf_assembler::_encode_jmp, 3}},
2,864✔
488
        {"jle", {&_bpf_assembler::_encode_jmp, 3}},   {"jle32", {&_bpf_assembler::_encode_jmp, 3}},
2,864✔
489
        {"jlt", {&_bpf_assembler::_encode_jmp, 3}},   {"jlt32", {&_bpf_assembler::_encode_jmp, 3}},
2,864✔
490
        {"jne", {&_bpf_assembler::_encode_jmp, 3}},   {"jne32", {&_bpf_assembler::_encode_jmp, 3}},
2,864✔
491
        {"jset", {&_bpf_assembler::_encode_jmp, 3}},  {"jset32", {&_bpf_assembler::_encode_jmp, 3}},
2,864✔
492
        {"jsge", {&_bpf_assembler::_encode_jmp, 3}},  {"jsge32", {&_bpf_assembler::_encode_jmp, 3}},
2,864✔
493
        {"jsgt", {&_bpf_assembler::_encode_jmp, 3}},  {"jsgt32", {&_bpf_assembler::_encode_jmp, 3}},
2,864✔
494
        {"jsle", {&_bpf_assembler::_encode_jmp, 3}},  {"jsle32", {&_bpf_assembler::_encode_jmp, 3}},
2,864✔
495
        {"jslt", {&_bpf_assembler::_encode_jmp, 3}},  {"jslt32", {&_bpf_assembler::_encode_jmp, 3}},
2,864✔
496
        {"lddw", {&_bpf_assembler::_encode_ld, 2}},   {"ldxb", {&_bpf_assembler::_encode_ldx, 2}},
2,864✔
497
        {"ldxdw", {&_bpf_assembler::_encode_ldx, 2}}, {"ldxh", {&_bpf_assembler::_encode_ldx, 2}},
2,864✔
498
        {"ldxw", {&_bpf_assembler::_encode_ldx, 2}},  {"ldxsb", {&_bpf_assembler::_encode_ldx, 2}},
2,864✔
499
        {"ldxsh", {&_bpf_assembler::_encode_ldx, 2}}, {"ldxsw", {&_bpf_assembler::_encode_ldx, 2}},
2,864✔
500
        {"le16", {&_bpf_assembler::_encode_alu, 1}},
1,432✔
501
        {"le32", {&_bpf_assembler::_encode_alu, 1}},  {"le64", {&_bpf_assembler::_encode_alu, 1}},
2,864✔
502
        {"lsh", {&_bpf_assembler::_encode_alu, 2}},   {"lsh32", {&_bpf_assembler::_encode_alu, 2}},
2,864✔
503
        {"mod", {&_bpf_assembler::_encode_alu, 2}},   {"mod32", {&_bpf_assembler::_encode_alu, 2}},
2,864✔
504
        {"mov", {&_bpf_assembler::_encode_alu, 2}},   {"mov32", {&_bpf_assembler::_encode_alu, 2}},
2,864✔
505
        {"movsx864", {&_bpf_assembler::_encode_alu, 2}}, {"movsx832", {&_bpf_assembler::_encode_alu, 2}},
2,864✔
506
        {"movsx1664", {&_bpf_assembler::_encode_alu, 2}}, {"movsx1632", {&_bpf_assembler::_encode_alu, 2}},
2,864✔
507
        {"movsx3264", {&_bpf_assembler::_encode_alu, 2}},
1,432✔
508
        {"mul", {&_bpf_assembler::_encode_alu, 2}},   {"mul32", {&_bpf_assembler::_encode_alu, 2}},
2,864✔
509
        {"neg", {&_bpf_assembler::_encode_alu, 1}},   {"neg32", {&_bpf_assembler::_encode_alu, 1}},
2,864✔
510
        {"or", {&_bpf_assembler::_encode_alu, 2}},    {"or32", {&_bpf_assembler::_encode_alu, 2}},
2,864✔
511
        {"rsh", {&_bpf_assembler::_encode_alu, 2}},   {"rsh32", {&_bpf_assembler::_encode_alu, 2}},
2,864✔
512
        {"sdiv", {&_bpf_assembler::_encode_alu, 2}},  {"sdiv32", {&_bpf_assembler::_encode_alu, 2}},
2,864✔
513
        {"smod", {&_bpf_assembler::_encode_alu, 2}},  {"smod32", {&_bpf_assembler::_encode_alu, 2}},
2,864✔
514
        {"stb", {&_bpf_assembler::_encode_st, 2}},    {"stdw", {&_bpf_assembler::_encode_st, 2}},
2,864✔
515
        {"sth", {&_bpf_assembler::_encode_st, 2}},    {"stw", {&_bpf_assembler::_encode_st, 2}},
2,864✔
516
        {"stxb", {&_bpf_assembler::_encode_stx, 2}},  {"stxdw", {&_bpf_assembler::_encode_stx, 2}},
2,864✔
517
        {"stxh", {&_bpf_assembler::_encode_stx, 2}},  {"stxw", {&_bpf_assembler::_encode_stx, 2}},
2,864✔
518
        {"sub", {&_bpf_assembler::_encode_alu, 2}},   {"sub32", {&_bpf_assembler::_encode_alu, 2}},
2,864✔
519
        {"swap16", {&_bpf_assembler::_encode_alu, 1}},{"swap32", {&_bpf_assembler::_encode_alu, 1}},
2,864✔
520
        {"swap64", {&_bpf_assembler::_encode_alu, 1}},{"xor", {&_bpf_assembler::_encode_alu, 2}},
2,864✔
521
        {"xor32", {&_bpf_assembler::_encode_alu, 2}},
1,432✔
522
    };
523

524
    const std::unordered_map<std::string, std::tuple<bpf_encode_t, size_t>> _bpf_encode_atomic_ops{
525
        {"add", {&_bpf_assembler::_encode_atomic_add, 3}},
1,432✔
526
        {"add32", {&_bpf_assembler::_encode_atomic_add, 3}},
1,432✔
527
        {"and", {&_bpf_assembler::_encode_atomic_and, 3}},
1,432✔
528
        {"and32", {&_bpf_assembler::_encode_atomic_and, 3}},
1,432✔
529
        {"or", {&_bpf_assembler::_encode_atomic_or, 3}},
1,432✔
530
        {"or32", {&_bpf_assembler::_encode_atomic_or, 3}},
1,432✔
531
        {"xor", {&_bpf_assembler::_encode_atomic_xor, 3}},
1,432✔
532
        {"xor32", {&_bpf_assembler::_encode_atomic_xor, 3}},
1,432✔
533
        {"xchg", {&_bpf_assembler::_encode_atomic_xchg, 3}},
1,432✔
534
        {"xchg32", {&_bpf_assembler::_encode_atomic_xchg, 3}},
1,432✔
535
        {"cmpxchg", {&_bpf_assembler::_encode_atomic_cmpxchg, 3}},
1,432✔
536
        {"cmpxchg32", {&_bpf_assembler::_encode_atomic_cmpxchg, 3}},
1,432✔
537
    };
538

539
  public:
540
    _bpf_assembler() = default;
422,440✔
541
    ~_bpf_assembler() = default;
4,296✔
542

543
    std::vector<ebpf_inst>
544
    assemble(std::istream& input)
2,864✔
545
    {
546
        size_t exit_count = 0;
1,432✔
547
        _jump_instructions.clear();
2,864✔
548
        _labels.clear();
1,432✔
549
        std::vector<ebpf_inst> output;
1,432✔
550
        std::string line;
1,432✔
551
        // Parse the input stream one line at a time.
552
        while (std::getline(input, line)) {
24,696✔
553
            std::istringstream line_stream(line);
21,844✔
554
            std::string mnemonic;
10,922✔
555
            std::string operand;
10,922✔
556
            std::vector<std::string> operands;
10,922✔
557
            // Check for empty lines.
558
            if (!(line_stream >> mnemonic)) {
21,844✔
559
                continue;
×
560
            }
561
            // Use operator>> to skip repeated whitespace and avoid generating empty operands.
562
            while (line_stream >> operand) {
60,034✔
563
                if (operand.ends_with(',')) {
19,095✔
564
                    operand = operand.substr(0, operand.length() - 1);
29,721✔
565
                }
566
                operands.emplace_back(operand);
38,190✔
567
            }
568

569
            // Check for labels.
570
            if (mnemonic.ends_with(':')) {
10,922✔
571
                auto label =  mnemonic.substr(0, mnemonic.length() - 1);
430✔
572
                if (_labels.contains(label)) {
430✔
573
                    std::stringstream ss{};
×
574
                    ss << "Duplicate label (" + label + ") detected at line " << output.size() << " (previous declaration at line " << _labels[label] << ")";
×
575
                    throw std::runtime_error(ss.str());
×
576
                }
×
577
                _labels[label] = output.size();
430✔
578
                continue;
215✔
579
            }
215✔
580

581
            // Add a default exit label for the first exit statement.
582
            if (mnemonic == "exit" && exit_count == 0) {
21,414✔
583
                _labels[mnemonic] = output.size();
2,852✔
584
                exit_count++;
1,426✔
585
            }
586

587
            // Assume not a jump instruction.
588
            _jump_instructions.push_back({});
21,420✔
589

590
            bpf_encode_t encode = nullptr;
21,414✔
591
            size_t operand_count = 0;
21,414✔
592

593
            // If this is a call instruction and it doesn't specify a mode, add the default mode (helper).
594
            if (mnemonic == "call") {
21,414✔
595
                if (operands.size() == 1) {
38✔
596
                    operands.insert(operands.begin(), "helper");
36✔
597
                }
598
            }
599

600
            if (mnemonic == "lock") {
21,414✔
601
                // Find the handler for this atomic operation.
602
                if (operands.size() == 0) {
320✔
603
                    throw std::runtime_error("Invalid number of operands for lock");
2✔
604
                }
605

606
                // Format of interlocked operations is:
607
                // lock [fetch] <op> <dst>, <src>
608
                // where fetch is optional.
609

610
                // For simpler processing, insert a "no_fetch" operand if it is missing.
611
                if (operands.size() > 1 && operands[0] != "fetch") {
318✔
612
                    operands.insert(operands.begin(), "no_fetch");
309✔
613
                }
614

615
                // Swap fetch and op.
616
                std::swap(operands[0], operands[1]);
159✔
617

618
                // Format of interlocked operations is now:
619
                // lock <op> <fetch/no_fetch> <dst>, <src>
620

621
                auto iter = _bpf_encode_atomic_ops.find(operands[0]);
159✔
622
                if (iter != _bpf_encode_atomic_ops.end()) {
318✔
623
                    mnemonic = operands[0];
159✔
624
                    operands.erase(operands.begin());
159✔
625
                    std::tie(encode, operand_count) = iter->second;
159✔
626
                }
627
            } else {
628
                // Find the handler for this mnemonic.
629
                auto iter = _bpf_mnemonic_map.find(mnemonic);
10,547✔
630
                if (iter != _bpf_mnemonic_map.end()) {
21,094✔
631
                    std::tie(encode, operand_count) = iter->second;
10,546✔
632
                }
633
            }
634

635
            // Check if the mnemonic is valid.
636
            if (encode == nullptr) {
21,412✔
637
                throw std::runtime_error(std::string("Invalid mnemonic: ") + mnemonic);
3✔
638
            }
639

640
            // Check if the number of operands is valid.
641
            if (operands.size() != operand_count) {
21,410✔
642
                throw std::runtime_error(std::string("Invalid number of operands for mnemonic: ") + mnemonic);
6✔
643
            }
644

645
            // Invoke handler and store result.
646
            auto result = (this->*encode)(mnemonic, operands);
21,406✔
647
            if (std::holds_alternative<ebpf_inst>(result)) {
21,402✔
648
                output.emplace_back(std::get<ebpf_inst>(result));
20,172✔
649
            } else {
650
                // Instruction is 2 slots wide.
651
                _jump_instructions.push_back({});
1,230✔
652
                for (const auto& inst : std::get<std::array<ebpf_inst, 2>>(result)) {
3,690✔
653
                    output.emplace_back(inst);
2,460✔
654
                }
655
            }
656
        }
33,429✔
657

658
        // Fixup jump instructions.
659
        for (size_t i = 0; i < _jump_instructions.size(); i++) {
25,480✔
660
            if (!_jump_instructions[i].has_value()) {
22,630✔
661
                continue;
10,221✔
662
            }
663
            auto iter = _labels.find(_jump_instructions[i].value());
1,094✔
664
            if (iter == _labels.end()) {
2,188✔
665
                throw std::runtime_error(std::string("Invalid label: ") + _jump_instructions[i].value());
4✔
666
            }
667
            if (output[i].opcode == EBPF_OP_CALL || output[i].opcode == EBPF_OP_JA32) {
2,186✔
668
                output[i].imm = static_cast<uint32_t>(iter->second - i - 1);
40✔
669
            } else {
670
                output[i].offset = static_cast<uint16_t>(iter->second - i - 1);
2,146✔
671
            }
672
        }
673
        return output;
4,275✔
674
    }
1,439✔
675
} bpf_assembler_t;
676

677
std::vector<ebpf_inst>
678
bpf_assembler(std::istream& input)
2,864✔
679
{
680
    bpf_assembler_t assembler;
2,864✔
681
    return assembler.assemble(input);
5,714✔
682
}
2,864✔
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