diff --git a/README.md b/README.md index 99b81626..c7a04db2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1 @@ -# eBPF-sure-project -eBPF SURE research project +# ePass diff --git a/core/.clang-tidy b/core/.clang-tidy new file mode 100644 index 00000000..cdc2136e --- /dev/null +++ b/core/.clang-tidy @@ -0,0 +1,7 @@ +--- +Checks: 'clang-diagnostic-*,-*,misc-*,performance-*,clang-analyzer-*,readability-function-size,readability-identifier-naming,readability-redundant-*,readability-mis*,readability-string-compare,readability-non-const-parameter,-clang-analyzer-security*,-misc-no-recursion,bugprone-assignment-in-if-condition,bugprone-infinite-loop,bugprone-integer-division,bugprone-suspicious-string-compare,llvm-header-guard,google-global-names-in-headers,cppcoreguidelines-no-malloc,bugprone-use-after-move' +WarningsAsErrors: '*,-misc-non-private-member-variables-in-classes' +CheckOptions: + - key: readability-function-size.LineThreshold + value: '200' +... diff --git a/core/.vscode/launch.json b/core/.vscode/launch.json index 9cef546b..e28133d2 100644 --- a/core/.vscode/launch.json +++ b/core/.vscode/launch.json @@ -8,8 +8,13 @@ "type": "lldb", "request": "launch", "name": "Debug", - "program": "${workspaceFolder}/build/read", - "args": ["tests/output/empty.o", "prog"], + "program": "${workspaceFolder}/build/epasstool/epass", + "args": [ + "-m", + "read", + "-p", + "bpftests/output/empty.o" + ], "cwd": "${workspaceFolder}" } ] diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 838fe925..8a83635e 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -8,6 +8,8 @@ ENDIF(EPASS_LIBBPF) add_library(epass STATIC bpf_ir.c array.c + hashtbl.c + ptrset.c ir_helper.c ir_value.c ir_bb.c @@ -25,9 +27,16 @@ add_library(epass STATIC aux/disasm.c aux/kern_utils.c ir_code_gen.c - lii.c + lli.c ) +add_executable(test_list tests/test_list.c) +add_executable(test_hashtable tests/test_hashtable.c) +add_executable(test_ptrset tests/test_ptrset.c) + +target_link_libraries(test_hashtable epass) +target_link_libraries(test_ptrset epass) + include_directories(include) set_target_properties(epass PROPERTIES PUBLIC_HEADER "include/linux/bpf_ir.h") diff --git a/core/Makefile b/core/Makefile index 83c95d41..b3c7b915 100644 --- a/core/Makefile +++ b/core/Makefile @@ -21,4 +21,9 @@ read: build readlog: build epasstool -m readlog -p tests/log/${NAME}.txt --gopt verbose=3 -.PHONY: build format kernel readlog read genctor +test: build + ./build/test_list + ./build/test_hashtable + ./build/test_ptrset + +.PHONY: build format kernel readlog read genctor test diff --git a/core/Readme.md b/core/Readme.md index 6e20d7cf..bee24333 100644 --- a/core/Readme.md +++ b/core/Readme.md @@ -28,42 +28,28 @@ After building, run: sudo cmake --install build ``` -# Verifier - -We design a form of constraint that could describe all types of ebpf verifier rules. The verifier will generate a "constraint set" based on static analysis information (e.g. BTF) and that doesn't need any simulation. - -Then this constraint set will be passed to our IR and we will add check for those constraints. Since currently our IR is typeless so we can only use some raw constraint generated from the verifier. - -To start with, a simple constraint would be "range constraint", meaning a register (at a specific position) must be within a range. - -One opinion, one benefit of designing the raw constraint from is that our runtime-check system will not depend heavily on the current linux verifier and will be portable to other verifiers. - -## Future work - -Rewrite Normalization. Plain the IR. - -Just store the allocated position in value. Not track users. No references. - -All VRs are changed to Real Registers. - -Automatically connect BB based on JMP instructions. - ## Bugs -### SplitBB operation may not work properly if it's at the top of a BB - -Resolved. - ### Coalesce has some bugs Found root cause: you may not directly remove instructions like r1 = r1. +Note. this should be refactored to the last step in the new pipeline. + ## Errors Reproduce: `ringbuf.c` enable coalesce will cause some error in CG Raw libbpf library loader doesn't change the "imm" value when calling the `callback_fn`. It doesn't support calling it after changing the resources. +## ALLOCARRAY? + +Why pre-colored? + # TODO -- bpf-to-bpf calls +- [ ] Rewrite Normalization, flatten the IR. +- [ ] Refactor CG to use the simpler pipeline described in "Register Allocation via Coloring of Chordal Graphs" +- [ ] bpf-to-bpf calls + +Move CG builtin passes to `ir_code_gen`. diff --git a/core/aux/disasm.c b/core/aux/disasm.c index 00171f8c..944033e3 100644 --- a/core/aux/disasm.c +++ b/core/aux/disasm.c @@ -1,6 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only // Modified from kernel/bpf/disasm.c - #include static const char *const bpf_class_string[8] = { diff --git a/core/aux/prog_check.c b/core/aux/prog_check.c index b6d0a01e..5dae2100 100644 --- a/core/aux/prog_check.c +++ b/core/aux/prog_check.c @@ -408,6 +408,8 @@ static void check_err_and_print(struct bpf_ir_env *env, struct ir_function *fun) // Check that the program is valid and able to be compiled void bpf_ir_prog_check(struct bpf_ir_env *env, struct ir_function *fun) { + print_ir_err_init(fun); + check_insn(env, fun); CHECK_DUMP(); diff --git a/core/bpf_ir.c b/core/bpf_ir.c index cc670cf4..30697903 100644 --- a/core/bpf_ir.c +++ b/core/bpf_ir.c @@ -1,6 +1,4 @@ // SPDX-License-Identifier: GPL-2.0-only - -#include #include static const s8 helper_func_arg_num[] = { @@ -625,7 +623,6 @@ static struct ir_insn *add_phi_operands(struct bpf_ir_env *env, env, tenv, reg, (struct pre_ir_basic_block *)pred->user_data); add_user(env, insn, phi.value); - bpf_ir_array_push(env, &pred->users, &insn); bpf_ir_array_push(env, &insn->phi, &phi); } return insn; @@ -961,8 +958,6 @@ static void create_cond_jmp(struct bpf_ir_env *env, size_t pos = insn.pos + insn.off + 1; new_insn->bb1 = get_ir_bb_from_position(tenv, insn.pos + 1); new_insn->bb2 = get_ir_bb_from_position(tenv, pos); - bpf_ir_array_push(env, &new_insn->bb1->users, &new_insn); - bpf_ir_array_push(env, &new_insn->bb2->users, &new_insn); set_insn_raw_pos(new_insn, insn.pos); set_value_raw_pos(&new_insn->values[0], insn.pos, IR_RAW_POS_DST); @@ -1224,8 +1219,6 @@ static void transform_bb(struct bpf_ir_env *env, struct ssa_transform_env *tenv, new_insn->bb1 = get_ir_bb_from_position(tenv, pos); set_insn_raw_pos(new_insn, insn.pos); - bpf_ir_array_push(env, &new_insn->bb1->users, - &new_insn); } else if (BPF_OP(code) == BPF_EXIT) { // Exit struct ir_insn *new_insn = @@ -1383,22 +1376,21 @@ struct ir_insn *bpf_ir_find_ir_insn_by_rawpos(struct ir_function *fun, void bpf_ir_free_function(struct ir_function *fun) { - for (size_t i = 0; i < fun->all_bbs.num_elem; ++i) { - struct ir_basic_block *bb = - ((struct ir_basic_block **)(fun->all_bbs.data))[i]; - + struct ir_basic_block **pos; + array_for(pos, fun->reachable_bbs) + { + struct ir_basic_block *bb = *pos; bpf_ir_array_free(&bb->preds); bpf_ir_array_free(&bb->succs); - bpf_ir_array_free(&bb->users); // Free the instructions - struct ir_insn *pos = NULL, *n = NULL; - list_for_each_entry_safe(pos, n, &bb->ir_insn_head, list_ptr) { - list_del(&pos->list_ptr); - bpf_ir_array_free(&pos->users); - if (pos->op == IR_INSN_PHI) { - bpf_ir_array_free(&pos->phi); + struct ir_insn *pos2 = NULL, *n = NULL; + list_for_each_entry_safe(pos2, n, &bb->ir_insn_head, list_ptr) { + list_del(&pos2->list_ptr); + bpf_ir_array_free(&pos2->users); + if (pos2->op == IR_INSN_PHI) { + bpf_ir_array_free(&pos2->phi); } - free_proto(pos); + free_proto(pos2); } free_proto(bb); } diff --git a/core/tests/.gitignore b/core/bpftests/.gitignore similarity index 100% rename from core/tests/.gitignore rename to core/bpftests/.gitignore diff --git a/core/tests/1mloop.c b/core/bpftests/1mloop.c similarity index 100% rename from core/tests/1mloop.c rename to core/bpftests/1mloop.c diff --git a/core/tests/Makefile b/core/bpftests/Makefile similarity index 100% rename from core/tests/Makefile rename to core/bpftests/Makefile diff --git a/core/tests/alu64.c b/core/bpftests/alu64.c similarity index 100% rename from core/tests/alu64.c rename to core/bpftests/alu64.c diff --git a/core/tests/compact_opt.c b/core/bpftests/compact_opt.c similarity index 100% rename from core/tests/compact_opt.c rename to core/bpftests/compact_opt.c diff --git a/core/tests/complex.c b/core/bpftests/complex.c similarity index 100% rename from core/tests/complex.c rename to core/bpftests/complex.c diff --git a/core/tests/empty.c b/core/bpftests/empty.c similarity index 100% rename from core/tests/empty.c rename to core/bpftests/empty.c diff --git a/core/tests/log/bpftrace1.txt b/core/bpftests/log/bpftrace1.txt similarity index 100% rename from core/tests/log/bpftrace1.txt rename to core/bpftests/log/bpftrace1.txt diff --git a/core/tests/log/loop1.txt b/core/bpftests/log/loop1.txt similarity index 100% rename from core/tests/log/loop1.txt rename to core/bpftests/log/loop1.txt diff --git a/core/tests/log/loop1_gen.txt b/core/bpftests/log/loop1_gen.txt similarity index 100% rename from core/tests/log/loop1_gen.txt rename to core/bpftests/log/loop1_gen.txt diff --git a/core/tests/log/loop1_ker.txt b/core/bpftests/log/loop1_ker.txt similarity index 100% rename from core/tests/log/loop1_ker.txt rename to core/bpftests/log/loop1_ker.txt diff --git a/core/tests/log/mask_ker.txt b/core/bpftests/log/mask_ker.txt similarity index 100% rename from core/tests/log/mask_ker.txt rename to core/bpftests/log/mask_ker.txt diff --git a/core/tests/log/simple1.txt b/core/bpftests/log/simple1.txt similarity index 100% rename from core/tests/log/simple1.txt rename to core/bpftests/log/simple1.txt diff --git a/core/tests/log/simple1_v.txt b/core/bpftests/log/simple1_v.txt similarity index 100% rename from core/tests/log/simple1_v.txt rename to core/bpftests/log/simple1_v.txt diff --git a/core/tests/loop1.c b/core/bpftests/loop1.c similarity index 100% rename from core/tests/loop1.c rename to core/bpftests/loop1.c diff --git a/core/tests/loop2.c b/core/bpftests/loop2.c similarity index 100% rename from core/tests/loop2.c rename to core/bpftests/loop2.c diff --git a/core/tests/loop3.c b/core/bpftests/loop3.c similarity index 100% rename from core/tests/loop3.c rename to core/bpftests/loop3.c diff --git a/core/tests/loop3r.c b/core/bpftests/loop3r.c similarity index 100% rename from core/tests/loop3r.c rename to core/bpftests/loop3r.c diff --git a/core/tests/lsm.c b/core/bpftests/lsm.c similarity index 100% rename from core/tests/lsm.c rename to core/bpftests/lsm.c diff --git a/core/tests/map1.c b/core/bpftests/map1.c similarity index 100% rename from core/tests/map1.c rename to core/bpftests/map1.c diff --git a/core/tests/mask.c b/core/bpftests/mask.c similarity index 100% rename from core/tests/mask.c rename to core/bpftests/mask.c diff --git a/core/tests/mem1.c b/core/bpftests/mem1.c similarity index 100% rename from core/tests/mem1.c rename to core/bpftests/mem1.c diff --git a/core/tests/neg.c b/core/bpftests/neg.c similarity index 100% rename from core/tests/neg.c rename to core/bpftests/neg.c diff --git a/core/tests/output/.gitignore b/core/bpftests/output/.gitignore similarity index 100% rename from core/tests/output/.gitignore rename to core/bpftests/output/.gitignore diff --git a/core/tests/ringbuf.c b/core/bpftests/ringbuf.c similarity index 100% rename from core/tests/ringbuf.c rename to core/bpftests/ringbuf.c diff --git a/core/tests/simple1.c b/core/bpftests/simple1.c similarity index 100% rename from core/tests/simple1.c rename to core/bpftests/simple1.c diff --git a/core/tests/simple2.c b/core/bpftests/simple2.c similarity index 100% rename from core/tests/simple2.c rename to core/bpftests/simple2.c diff --git a/core/tests/str.c b/core/bpftests/str.c similarity index 100% rename from core/tests/str.c rename to core/bpftests/str.c diff --git a/core/tests/test_asm.c b/core/bpftests/test_asm.c similarity index 100% rename from core/tests/test_asm.c rename to core/bpftests/test_asm.c diff --git a/core/tests/uninit1.c b/core/bpftests/uninit1.c similarity index 100% rename from core/tests/uninit1.c rename to core/bpftests/uninit1.c diff --git a/core/epasstool/epasstool.c b/core/epasstool/epasstool.c index edfeca91..de2076e8 100644 --- a/core/epasstool/epasstool.c +++ b/core/epasstool/epasstool.c @@ -72,6 +72,7 @@ int main(int argc, char **argv) struct user_opts uopts; uopts.gopt[0] = 0; uopts.popt[0] = 0; + uopts.prog[0] = 0; uopts.no_compile = false; uopts.auto_sec = true; static struct option long_options[] = { @@ -142,6 +143,10 @@ int main(int argc, char **argv) if (mode == MODE_PRINT) { return print(uopts); } + if (uopts.prog[0] == 0) { + printf("Program not specified\n"); + usage(argv[0]); + } // Initialize common options common_opts = bpf_ir_default_opts(); diff --git a/core/epasstool/print.c b/core/epasstool/print.c index 56d69632..1e321c44 100644 --- a/core/epasstool/print.c +++ b/core/epasstool/print.c @@ -18,8 +18,14 @@ static void print_bpf_prog(struct bpf_ir_env *env, const struct bpf_insn *insns, int print(struct user_opts uopts) { struct bpf_object *obj = bpf_object__open(uopts.prog); - struct bpf_program *prog = - bpf_object__find_program_by_name(obj, uopts.sec); + + struct bpf_program *prog = NULL; + if (uopts.auto_sec) { + prog = bpf_object__next_program(obj, NULL); + } else { + prog = bpf_object__find_program_by_name(obj, uopts.sec); + } + if (!prog) { return 1; } diff --git a/core/hashtbl.c b/core/hashtbl.c new file mode 100644 index 00000000..e77e395f --- /dev/null +++ b/core/hashtbl.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include + +// An open-addressing hashtable + +#define STEP 31 + +// Make sure size > 0 +void bpf_ir_hashtbl_init(struct bpf_ir_env *env, struct hashtbl *res, + size_t size) +{ + SAFE_MALLOC(res->table, size * sizeof(struct hashtbl_entry)); + res->size = size; + res->cnt = 0; +} + +static void bpf_ir_hashtbl_insert_raw(struct hashtbl *tbl, void *key, + u32 key_hash, void *data) +{ + u32 index = __hash_32(key_hash) % tbl->size; + for (u32 i = 0; i < tbl->size; ++i) { + if (tbl->table[index].occupy <= 0) { + // Found an empty slot + tbl->table[index].key = key; + tbl->table[index].data = data; + tbl->table[index].key_hash = key_hash; + tbl->table[index].occupy = 1; + tbl->cnt++; + return; + } + index = (index + STEP) % tbl->size; + } + CRITICAL("Impossible"); +} + +static void bpf_ir_hashtbl_insert_raw_cpy(struct bpf_ir_env *env, + struct hashtbl *tbl, void *key, + size_t key_size, u32 key_hash, + void *data, size_t data_size) +{ + u32 index = __hash_32(key_hash) % tbl->size; + for (u32 i = 0; i < tbl->size; ++i) { + if (tbl->table[index].occupy <= 0) { + // Found an empty slot + SAFE_MALLOC(tbl->table[index].key, key_size); + SAFE_MALLOC(tbl->table[index].data, data_size); + memcpy(tbl->table[index].key, key, key_size); + memcpy(tbl->table[index].data, data, data_size); + tbl->table[index].key_hash = key_hash; + tbl->table[index].occupy = 1; + tbl->cnt++; + return; + } else { + if (tbl->table[index].key && + memcmp(tbl->table[index].key, key, key_size) == 0) { + // Found, overwrite + free_proto(tbl->table[index].data); + SAFE_MALLOC(tbl->table[index].data, data_size); + memcpy(tbl->table[index].data, data, data_size); + return; + } + } + index = (index + STEP) % tbl->size; + } + CRITICAL("Impossible"); +} + +void bpf_ir_hashtbl_insert(struct bpf_ir_env *env, struct hashtbl *tbl, + void *key, size_t key_size, u32 key_hash, void *data, + size_t data_size) +{ + if (tbl->cnt >= tbl->size) { + // Table is full, grow it + size_t new_size = tbl->size * 2; + struct hashtbl new_table; + bpf_ir_hashtbl_init(env, &new_table, new_size); + for (size_t i = 0; i < tbl->size; ++i) { + if (tbl->table[i].occupy > 0) { + bpf_ir_hashtbl_insert_raw( + &new_table, tbl->table[i].key, + tbl->table[i].key_hash, + tbl->table[i].data); + } + } + // This free does not free the data & key + free_proto(tbl->table); + tbl->table = new_table.table; + tbl->size = new_table.size; + } + bpf_ir_hashtbl_insert_raw_cpy(env, tbl, key, key_size, key_hash, data, + data_size); +} + +int bpf_ir_hashtbl_delete(struct hashtbl *tbl, void *key, size_t key_size, + u32 key_hash) +{ + u32 index = __hash_32(key_hash) % tbl->size; + for (u32 i = 0; i < tbl->size; ++i) { + if (tbl->table[index].occupy == 0) { + // Not found + return -1; + } + if (tbl->table[index].occupy == 1) { + if (tbl->table[index].key && + memcmp(tbl->table[index].key, key, key_size) == 0) { + // Found + tbl->table[index].occupy = -1; + tbl->cnt--; + free_proto(tbl->table[index].key); + free_proto(tbl->table[index].data); + return 0; + } + } + index = (index + STEP) % tbl->size; + } + return -1; +} + +void *bpf_ir_hashtbl_get(struct hashtbl *tbl, void *key, size_t key_size, + u32 key_hash) +{ + u32 index = __hash_32(key_hash) % tbl->size; + for (u32 i = 0; i < tbl->size; ++i) { + if (tbl->table[index].occupy == 0) { + // Not found + return NULL; + } + if (tbl->table[index].occupy == 1) { + if (tbl->table[index].key && + memcmp(tbl->table[index].key, key, key_size) == 0) { + // Found + return tbl->table[index].data; + } + } + index = (index + STEP) % tbl->size; + } + return NULL; +} + +void bpf_ir_hashtbl_print_dbg(struct bpf_ir_env *env, struct hashtbl *tbl, + void (*print_key)(struct bpf_ir_env *env, void *), + void (*print_data)(struct bpf_ir_env *env, + void *)) +{ + for (size_t i = 0; i < tbl->size; ++i) { + if (tbl->table[i].occupy > 0) { + PRINT_LOG_DEBUG(env, "Key: "); + print_key(env, tbl->table[i].key); + PRINT_LOG_DEBUG(env, ", Data: "); + print_data(env, tbl->table[i].data); + PRINT_LOG_DEBUG(env, "\n"); + } + } +} + +void bpf_ir_hashtbl_clean(struct hashtbl *tbl) +{ + for (size_t i = 0; i < tbl->size; ++i) { + if (tbl->table[i].occupy > 0) { + free_proto(tbl->table[i].key); + free_proto(tbl->table[i].data); + tbl->table[i].key = 0; + tbl->table[i].data = 0; + tbl->table[i].occupy = 0; + } + } + tbl->cnt = 0; +} + +void bpf_ir_hashtbl_free(struct hashtbl *tbl) +{ + bpf_ir_hashtbl_clean(tbl); + free_proto(tbl->table); + tbl->size = 0; + tbl->table = NULL; +} diff --git a/core/include/linux/bpf_ir.h b/core/include/linux/bpf_ir.h index 5277afb9..5cc094db 100644 --- a/core/include/linux/bpf_ir.h +++ b/core/include/linux/bpf_ir.h @@ -8,14 +8,16 @@ #include #include #include -#include "list.h" #include #include - #include #include #include +// Used to simulate kernel functions +#include "list.h" +#include "hash.h" + typedef __s8 s8; typedef __u8 u8; typedef __s16 s16; @@ -32,6 +34,7 @@ typedef __u64 u64; #include #include #include +#include #define SIZET_MAX ULONG_MAX @@ -159,12 +162,12 @@ void bpf_ir_print_log_dbg(struct bpf_ir_env *env); /* Array Start */ -struct array { +typedef struct array { void *data; size_t num_elem; // Current length size_t max_elem; // Maximum length size_t elem_size; -}; +} array; void bpf_ir_array_init(struct array *res, size_t size); @@ -199,6 +202,103 @@ void bpf_ir_array_clone(struct bpf_ir_env *env, struct array *res, /* Array End */ +/* Hashtable Start */ + +struct hashtbl_entry { + u32 key_hash; // Used for growing + void *key; + void *data; + s8 occupy; // 0: Empty, 1: Occupied, -1: Deleted +}; + +struct hashtbl { + struct hashtbl_entry *table; + size_t size; + size_t cnt; +}; + +void bpf_ir_hashtbl_init(struct bpf_ir_env *env, struct hashtbl *res, + size_t size); + +#define hashtbl_insert(env, tbl, key, keyhash, data) \ + bpf_ir_hashtbl_insert(env, tbl, &(key), sizeof(key), keyhash, &(data), \ + sizeof(data)) + +void bpf_ir_hashtbl_insert(struct bpf_ir_env *env, struct hashtbl *tbl, + void *key, size_t key_size, u32 key_hash, void *data, + size_t data_size); + +#define hashtbl_delete(env, tbl, key, keyhash) \ + bpf_ir_hashtbl_delete(tbl, &(key), sizeof(key), keyhash) + +int bpf_ir_hashtbl_delete(struct hashtbl *tbl, void *key, size_t key_size, + u32 key_hash); + +#define hashtbl_get(env, tbl, key, keyhash, type) \ + (type *)bpf_ir_hashtbl_get(tbl, &(key), sizeof(key), keyhash) + +void *bpf_ir_hashtbl_get(struct hashtbl *tbl, void *key, size_t key_size, + u32 key_hash); + +void bpf_ir_hashtbl_print_dbg(struct bpf_ir_env *env, struct hashtbl *tbl, + void (*print_key)(struct bpf_ir_env *env, void *), + void (*print_data)(struct bpf_ir_env *env, + void *)); + +void bpf_ir_hashtbl_clean(struct hashtbl *tbl); + +void bpf_ir_hashtbl_free(struct hashtbl *tbl); + +/* Hashtable End */ + +/* Ptrset Start */ + +struct ptrset_entry { + void *key; + s8 occupy; // 0: Empty, 1: Occupied, -1: Deleted +}; + +struct ptrset { + struct ptrset_entry *set; + size_t size; + size_t cnt; +}; + +void bpf_ir_ptrset_init(struct bpf_ir_env *env, struct ptrset *res, + size_t size); + +void bpf_ir_ptrset_insert(struct bpf_ir_env *env, struct ptrset *set, + void *key); + +int bpf_ir_ptrset_delete(struct ptrset *set, void *key); + +bool bpf_ir_ptrset_exists(struct ptrset *set, void *key); + +void bpf_ir_ptrset_print_dbg(struct bpf_ir_env *env, struct ptrset *set, + void (*print_key)(struct bpf_ir_env *env, void *)); + +void bpf_ir_ptrset_clean(struct ptrset *set); + +void bpf_ir_ptrset_free(struct ptrset *set); + +struct ptrset bpf_ir_ptrset_union(struct bpf_ir_env *env, struct ptrset *set1, + struct ptrset *set2); + +struct ptrset bpf_ir_ptrset_intersec(struct bpf_ir_env *env, + struct ptrset *set1, struct ptrset *set2); + +void bpf_ir_ptrset_move(struct ptrset *set1, struct ptrset *set2); + +void bpf_ir_ptrset_clone(struct bpf_ir_env *env, struct ptrset *set1, + struct ptrset *set2); + +void bpf_ir_ptrset_add(struct bpf_ir_env *env, struct ptrset *set1, + struct ptrset *set2); + +void bpf_ir_ptrset_minus(struct ptrset *set1, struct ptrset *set2); + +/* Ptrset End */ + /* DBG Macro Start */ #ifndef __KERNEL__ @@ -331,6 +431,7 @@ enum ir_value_type { IR_VALUE_CONSTANT_RAWOFF, IR_VALUE_CONSTANT_RAWOFF_REV, IR_VALUE_INSN, + IR_VALUE_FLATTEN_DST, // Used only in code generation IR_VALUE_UNDEF, }; @@ -348,6 +449,15 @@ struct ir_raw_pos { enum ir_raw_pos_type pos_t; }; +/* Actual position of a VR, used after RA in cg */ +struct ir_vr_pos { + // If this VR needs to be allocated (insn like store does not) + bool allocated; + u32 spilled_size; // Spilled + u8 alloc_reg; // Not spilled + s32 spilled; +}; + /* * VALUE = CONSTANT | INSN * @@ -357,6 +467,7 @@ struct ir_value { union { s64 constant_d; struct ir_insn *insn_d; + struct ir_vr_pos vr_pos; } data; enum ir_value_type type; enum ir_alu_op_type const_type; // Used when type is a constant @@ -596,11 +707,8 @@ struct ir_basic_block { size_t _id; void *user_data; - // Flag + // Flag (experimental, may be removed in the future) u32 flag; - - // Array of struct ir_insn * - struct array users; }; /** @@ -1138,6 +1246,8 @@ void print_ir_dst(struct bpf_ir_env *env, struct ir_insn *insn); void print_ir_alloc(struct bpf_ir_env *env, struct ir_insn *insn); +void print_ir_flatten(struct bpf_ir_env *env, struct ir_insn *insn); + void bpf_ir_clean_visited(struct ir_function *); // Tag the instruction and BB @@ -1289,6 +1399,17 @@ struct ir_bb_cg_extra { size_t pos; }; +/* Instruction data used after RA (e.g. normalization) */ +struct ir_insn_norm_extra { + struct ir_vr_pos pos; + + // Translated pre_ir_insn + struct pre_ir_insn translated[2]; + + // Translated number + u8 translated_num; +}; + struct ir_insn_cg_extra { // Destination (Not in SSA form anymore) struct ir_value dst; @@ -1303,12 +1424,6 @@ struct ir_insn_cg_extra { // Array of struct ir_insn* struct array adj; - // Translated pre_ir_insn - struct pre_ir_insn translated[2]; - - // Translated number - u8 translated_num; - // Whether the VR is allocated with a real register // If it's a pre-colored register, it's also 1 bool allocated; @@ -1326,6 +1441,8 @@ struct ir_insn_cg_extra { // Valid number: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 u8 alloc_reg; + struct ir_vr_pos vr_pos; + // Whether this instruction is a non-VR instruction, like a pre-colored register bool nonvr; }; @@ -1340,8 +1457,14 @@ enum val_type { #define insn_cg(insn) ((struct ir_insn_cg_extra *)(insn)->user_data) +/* Dst of a instruction + +Note. This could be only applied to an instruction with return value. +*/ #define insn_dst(insn) insn_cg(insn)->dst.data.insn_d +#define insn_norm(insn) ((struct ir_insn_norm_extra *)(insn)->user_data) + /* Code Gen End */ /* IR Value Start */ diff --git a/core/include/linux/hash.h b/core/include/linux/hash.h new file mode 100644 index 00000000..2e429ae7 --- /dev/null +++ b/core/include/linux/hash.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _LINUX_HASHTABLE_H +#define _LINUX_HASHTABLE_H + +#include + +#define GOLDEN_RATIO_32 0x61C88647 + +static inline __u32 __hash_32(__u32 val) +{ + return val * GOLDEN_RATIO_32; +} + +static inline __u32 hash32_ptr(const void *ptr) +{ + unsigned long val = (unsigned long)ptr; + + val ^= (val >> 32); + return (__u32)val; +} + +#endif diff --git a/core/include/linux/list.h b/core/include/linux/list.h index c561c7d8..72a70523 100644 --- a/core/include/linux/list.h +++ b/core/include/linux/list.h @@ -1,45 +1,20 @@ -/** - * - * I grub it from linux kernel source code and fix it for user space - * program. Of course, this is a GPL licensed header file. - * - * Here is a recipe to cook list.h for user space program - * - * 1. copy list.h from linux/include/list.h - * 2. remove - * - #ifdef __KERNEL__ and its #endif - * - all #include line - * - prefetch() and rcu related functions - * 3. add macro offsetof() and container_of - * - * - kazutomo@mcs.anl.gov - */ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _LINUX_LIST_H #define _LINUX_LIST_H /** - * @name from other kernel headers - */ -/*@{*/ - -/** - * Get offset of a member - */ -// #define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER) - -/** - * Casts a member of a structure out to the containing structure - * @param ptr the pointer to the member. - * @param type the type of the container struct this is embedded in. - * @param member the name of the member within the struct. + * container_of - cast a member of a structure out to the containing structure + * @ptr: the pointer to the member. + * @type: the type of the container struct this is embedded in. + * @member: the name of the member within the struct. * + * WARNING: any const qualifier of @ptr is lost. */ -#define container_of(ptr, type, member) \ - ({ \ - const typeof(((type *)0)->member) *__mptr = (ptr); \ - (type *)((char *)__mptr - offsetof(type, member)); \ +#define container_of(ptr, type, member) \ + ({ \ + void *__mptr = (void *)(ptr); \ + ((type *)(__mptr - offsetof(type, member))); \ }) -/*@}*/ /* * These are non-NULL pointers that will result in page faults @@ -373,7 +348,11 @@ struct hlist_node { #define HLIST_HEAD_INIT { .first = NULL } #define HLIST_HEAD(name) struct hlist_head name = { .first = NULL } #define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) -#define INIT_HLIST_NODE(ptr) ((ptr)->next = NULL, (ptr)->pprev = NULL) +static inline void INIT_HLIST_NODE(struct hlist_node *h) +{ + h->next = NULL; + h->pprev = NULL; +} static inline int hlist_unhashed(const struct hlist_node *h) { @@ -442,12 +421,8 @@ static inline void hlist_add_after(struct hlist_node *n, #define hlist_entry(ptr, type, member) container_of(ptr, type, member) -#define hlist_for_each(pos, head) \ - for (pos = (head)->first; pos && ({ \ - prefetch(pos->next); \ - 1; \ - }); \ - pos = pos->next) +#define hlist_for_each(pos, head) \ + for (pos = (head)->first; pos; pos = pos->next) #define hlist_for_each_safe(pos, n, head) \ for (pos = (head)->first; pos && ({ \ @@ -456,78 +431,56 @@ static inline void hlist_add_after(struct hlist_node *n, }); \ pos = n) +#define hlist_entry_safe(ptr, type, member) \ + ({ \ + typeof(ptr) ____ptr = (ptr); \ + ____ptr ? hlist_entry(____ptr, type, member) : NULL; \ + }) + /** * hlist_for_each_entry - iterate over list of given type - * @tpos: the type * to use as a loop counter. - * @pos: the &struct hlist_node to use as a loop counter. + * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the hlist_node within the struct. */ -#define hlist_for_each_entry(tpos, pos, head, member) \ - for (pos = (head)->first; \ - pos && ({ \ - prefetch(pos->next); \ - 1; \ - }) && \ - ({ \ - tpos = hlist_entry(pos, typeof(*tpos), member); \ - 1; \ - }); \ - pos = pos->next) +#define hlist_for_each_entry(pos, head, member) \ + for (pos = hlist_entry_safe((head)->first, typeof(*(pos)), member); \ + pos; pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), \ + member)) /** - * hlist_for_each_entry_continue - iterate over a hlist continuing after existing point - * @tpos: the type * to use as a loop counter. - * @pos: the &struct hlist_node to use as a loop counter. - * @member: the name of the hlist_node within the struct. - */ -#define hlist_for_each_entry_continue(tpos, pos, member) \ - for (pos = (pos)->next; \ - pos && ({ \ - prefetch(pos->next); \ - 1; \ - }) && \ - ({ \ - tpos = hlist_entry(pos, typeof(*tpos), member); \ - 1; \ - }); \ - pos = pos->next) +* hlist_for_each_entry_continue - iterate over a hlist continuing after current point +* @pos: the type * to use as a loop cursor. +* @member: the name of the hlist_node within the struct. +*/ +#define hlist_for_each_entry_continue(pos, member) \ + for (pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), \ + member); \ + pos; pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), \ + member)) /** - * hlist_for_each_entry_from - iterate over a hlist continuing from existing point - * @tpos: the type * to use as a loop counter. - * @pos: the &struct hlist_node to use as a loop counter. - * @member: the name of the hlist_node within the struct. - */ -#define hlist_for_each_entry_from(tpos, pos, member) \ - for (; pos && ({ \ - prefetch(pos->next); \ - 1; \ - }) && \ - ({ \ - tpos = hlist_entry(pos, typeof(*tpos), member); \ - 1; \ - }); \ - pos = pos->next) +* hlist_for_each_entry_from - iterate over a hlist continuing from current point +* @pos: the type * to use as a loop cursor. +* @member: the name of the hlist_node within the struct. +*/ +#define hlist_for_each_entry_from(pos, member) \ + for (; pos; pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), \ + member)) /** - * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry - * @tpos: the type * to use as a loop counter. - * @pos: the &struct hlist_node to use as a loop counter. - * @n: another &struct hlist_node to use as temporary storage - * @head: the head for your list. - * @member: the name of the hlist_node within the struct. - */ -#define hlist_for_each_entry_safe(tpos, pos, n, head, member) \ - for (pos = (head)->first; \ - pos && ({ \ - n = pos->next; \ - 1; \ - }) && \ - ({ \ - tpos = hlist_entry(pos, typeof(*tpos), member); \ - 1; \ - }); \ - pos = n) +* hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry +* @pos: the type * to use as a loop cursor. +* @n: a &struct hlist_node to use as temporary storage +* @head: the head for your list. +* @member: the name of the hlist_node within the struct. +*/ +#define hlist_for_each_entry_safe(pos, n, head, member) \ + for (pos = hlist_entry_safe((head)->first, typeof(*pos), member); \ + pos && ({ \ + n = pos->member.next; \ + 1; \ + }); \ + pos = hlist_entry_safe(n, typeof(*pos), member)) #endif diff --git a/core/ir_bb.c b/core/ir_bb.c index 5f051b71..62863507 100644 --- a/core/ir_bb.c +++ b/core/ir_bb.c @@ -28,7 +28,6 @@ struct ir_basic_block *bpf_ir_init_bb_raw(void) new_bb->user_data = NULL; INIT_ARRAY(&new_bb->preds, struct ir_basic_block *); INIT_ARRAY(&new_bb->succs, struct ir_basic_block *); - INIT_ARRAY(&new_bb->users, struct ir_insn *); new_bb->flag = 0; return new_bb; } diff --git a/core/ir_code_gen.c b/core/ir_code_gen.c index 625ed0c9..9bfefdf9 100644 --- a/core/ir_code_gen.c +++ b/core/ir_code_gen.c @@ -1,5 +1,4 @@ // SPDX-License-Identifier: GPL-2.0-only - #include static void set_insn_dst(struct bpf_ir_env *env, struct ir_insn *insn, @@ -16,6 +15,28 @@ static void set_insn_dst(struct bpf_ir_env *env, struct ir_insn *insn, insn_cg(insn)->dst = v; } +void bpf_ir_init_insn_cg(struct bpf_ir_env *env, struct ir_insn *insn) +{ + struct ir_insn_cg_extra *extra = NULL; + SAFE_MALLOC(extra, sizeof(struct ir_insn_cg_extra)); + insn->user_data = extra; + // When init, the destination is itself + extra->dst = bpf_ir_value_undef(); + if (!bpf_ir_is_void(insn)) { + set_insn_dst(env, insn, insn); + } + + INIT_ARRAY(&extra->adj, struct ir_insn *); + extra->allocated = false; + extra->spilled = 0; + extra->alloc_reg = 0; + INIT_ARRAY(&extra->gen, struct ir_insn *); + INIT_ARRAY(&extra->kill, struct ir_insn *); + INIT_ARRAY(&extra->in, struct ir_insn *); + INIT_ARRAY(&extra->out, struct ir_insn *); + extra->nonvr = false; +} + static void init_cg(struct bpf_ir_env *env, struct ir_function *fun) { struct ir_basic_block **pos = NULL; @@ -68,8 +89,10 @@ void bpf_ir_free_insn_cg(struct ir_insn *insn) insn->user_data = NULL; } -static void free_cg_res(struct ir_function *fun) +static void free_cg_final(struct ir_function *fun) { + // Free CG resources (after flattening) + struct ir_basic_block **pos = NULL; array_for(pos, fun->reachable_bbs) { @@ -77,9 +100,50 @@ static void free_cg_res(struct ir_function *fun) struct ir_bb_cg_extra *bb_cg = bb->user_data; free_proto(bb_cg); bb->user_data = NULL; + struct ir_insn *insn = NULL; list_for_each_entry(insn, &bb->ir_insn_head, list_ptr) { + struct ir_insn_cg_extra *extra = insn_cg(insn); + free_proto(extra); + insn->user_data = NULL; + } + } +} + +// Free CG resources, create a new extra data for flattening +static void cg_to_flatten(struct bpf_ir_env *env, struct ir_function *fun) +{ + struct ir_basic_block **pos = NULL; + array_for(pos, fun->reachable_bbs) + { + struct ir_basic_block *bb = *pos; + struct ir_insn *insn = NULL; + list_for_each_entry(insn, &bb->ir_insn_head, list_ptr) { + struct ir_vr_pos pos; + if (bpf_ir_is_void(insn)) { + pos.allocated = false; + } else { + struct ir_insn_cg_extra *dst_extra = + insn_cg(insn_dst(insn)); + DBGASSERT(dst_extra->allocated); + pos.spilled = dst_extra->spilled; + pos.alloc_reg = dst_extra->alloc_reg; + pos.allocated = dst_extra->allocated; + pos.spilled_size = dst_extra->spilled_size; + } + insn_cg(insn)->vr_pos = pos; + } + } + array_for(pos, fun->reachable_bbs) + { + struct ir_basic_block *bb = *pos; + struct ir_insn *insn = NULL; + list_for_each_entry(insn, &bb->ir_insn_head, list_ptr) { + struct ir_insn_cg_extra *extra = insn_cg(insn); + struct ir_vr_pos pos = extra->vr_pos; bpf_ir_free_insn_cg(insn); + SAFE_MALLOC(insn->user_data, sizeof(struct ir_vr_pos)); + insn_norm(insn)->pos = pos; } } @@ -141,6 +205,13 @@ static void print_ir_prog_cg_alloc(struct bpf_ir_env *env, print_ir_prog_advanced(env, fun, NULL, NULL, print_ir_alloc); } +static void print_ir_prog_cg_flatten(struct bpf_ir_env *env, + struct ir_function *fun, char *msg) +{ + PRINT_LOG_DEBUG(env, "\x1B[32m----- CG: %s -----\x1B[0m\n", msg); + print_ir_prog_advanced(env, fun, NULL, NULL, print_ir_flatten); +} + static void synthesize(struct bpf_ir_env *env, struct ir_function *fun) { // The last step, synthesizes the program @@ -151,7 +222,7 @@ static void synthesize(struct bpf_ir_env *env, struct ir_function *fun) struct ir_basic_block *bb = *pos; struct ir_insn *insn = NULL; list_for_each_entry(insn, &bb->ir_insn_head, list_ptr) { - struct ir_insn_cg_extra *extra = insn_cg(insn); + struct ir_insn_norm_extra *extra = insn_norm(insn); for (u8 i = 0; i < extra->translated_num; ++i) { struct pre_ir_insn translated_insn = extra->translated[i]; @@ -949,6 +1020,17 @@ static enum val_type vtype(struct ir_value val) val.type == IR_VALUE_CONSTANT_RAWOFF || val.type == IR_VALUE_CONSTANT_RAWOFF_REV) { return CONST; + } else if (val.type == IR_VALUE_FLATTEN_DST) { + if (val.data.vr_pos.allocated) { + if (val.data.vr_pos.spilled) { + // WARNING: cannot determine whether it's a stackoff + return STACK; + } else { + return REG; + } + } else { + return UNDEF; + } } else { CRITICAL("No such value type for dst"); } @@ -987,7 +1069,7 @@ static void calc_pos(struct bpf_ir_env *env, struct ir_function *fun) bb_extra->pos = ipos; struct ir_insn *insn; list_for_each_entry(insn, &bb->ir_insn_head, list_ptr) { - struct ir_insn_cg_extra *insn_extra = insn_cg(insn); + struct ir_insn_norm_extra *insn_extra = insn_norm(insn); for (u8 i = 0; i < insn_extra->translated_num; ++i) { struct pre_ir_insn *translated_insn = &insn_extra->translated[i]; @@ -1013,7 +1095,7 @@ static void relocate(struct bpf_ir_env *env, struct ir_function *fun) struct ir_basic_block *bb = *pos; struct ir_insn *insn; list_for_each_entry(insn, &bb->ir_insn_head, list_ptr) { - struct ir_insn_cg_extra *insn_extra = insn_cg(insn); + struct ir_insn_norm_extra *insn_extra = insn_norm(insn); if (insn->op == IR_INSN_JA) { DBGASSERT(insn_extra->translated_num == 1); size_t target = bpf_ir_bb_cg(insn->bb1)->pos; @@ -1155,6 +1237,105 @@ static void spill_callee(struct bpf_ir_env *env, struct ir_function *fun) // Normalization +static void remove_all_users(struct ir_function *fun) +{ + struct ir_basic_block **pos; + array_for(pos, fun->reachable_bbs) + { + struct ir_basic_block *bb = *pos; + struct ir_insn *insn; + list_for_each_entry(insn, &bb->ir_insn_head, list_ptr) { + bpf_ir_array_free(&insn->users); + } + } + for (u8 i = 0; i < MAX_FUNC_ARG; ++i) { + bpf_ir_array_free(&fun->function_arg[i]->users); + } + if (fun->sp) { + bpf_ir_array_free(&fun->sp->users); + } + for (u8 i = 0; i < BPF_REG_10; ++i) { + struct ir_insn *insn = fun->cg_info.regs[i]; + bpf_ir_array_free(&insn->users); + } +} + +// To flatten IR, we first need to change all the values to ir_pos +static void change_all_value_to_ir_pos(struct bpf_ir_env *env, + struct ir_function *fun) +{ + struct ir_basic_block **pos; + array_for(pos, fun->reachable_bbs) + { + struct ir_basic_block *bb = *pos; + struct ir_insn *insn; + list_for_each_entry(insn, &bb->ir_insn_head, list_ptr) { + struct array operands = bpf_ir_get_operands(env, insn); + struct ir_value **pos2; + array_for(pos2, operands) + { + struct ir_value *v = *pos2; + if (v->type == IR_VALUE_INSN) { + struct ir_insn *insn_d = v->data.insn_d; + // The value should be the final value (not proxy) + DBGASSERT(insn_d == insn_dst(insn_d)); + + struct ir_insn_cg_extra *extra = + insn_cg(insn_d); + struct ir_vr_pos pos; + pos.spilled = extra->spilled; + pos.alloc_reg = extra->alloc_reg; + pos.allocated = extra->allocated; + pos.spilled_size = extra->spilled_size; + v->type = IR_VALUE_FLATTEN_DST; + v->data.vr_pos = pos; + } + } + } + } +} + +static void test(struct bpf_ir_env *env, struct ir_function *fun) +{ + struct ir_basic_block **pos = NULL; + array_for(pos, fun->reachable_bbs) + { + struct ir_basic_block *bb = *pos; + // struct ir_bb_cg_extra *bb_cg = bb->user_data; + // free_proto(bb_cg); + // bb->user_data = NULL; + struct ir_insn *insn = NULL; + list_for_each_entry(insn, &bb->ir_insn_head, list_ptr) { + struct ir_vr_pos pos; + if (bpf_ir_is_void(insn)) { + pos.allocated = false; + } else { + struct ir_insn_cg_extra *extra = + insn_cg(insn_dst(insn)); + DBGASSERT(extra->allocated); + pos.spilled = extra->spilled; + pos.alloc_reg = extra->alloc_reg; + pos.allocated = extra->allocated; + pos.spilled_size = extra->spilled_size; + } + bpf_ir_free_insn_cg(insn); + SAFE_MALLOC(insn->user_data, sizeof(struct ir_vr_pos)); + insn_norm(insn)->pos = pos; + } + } +} + +/* Flatten IR */ +static void flatten_ir(struct bpf_ir_env *env, struct ir_function *fun) +{ + // Make sure no users + remove_all_users(fun); + change_all_value_to_ir_pos(env, fun); + CHECK_ERR(); + cg_to_flatten(env, fun); + CHECK_ERR(); +} + /* Loading constant used in normalization */ static struct ir_insn *normalize_load_const(struct bpf_ir_env *env, struct ir_insn *insn, @@ -1333,9 +1514,8 @@ static void normalize_getelemptr(struct bpf_ir_env *env, struct ir_value *v1 = &insn->values[1]; enum val_type t0 = insn->value_num >= 1 ? vtype(*v0) : UNDEF; enum val_type t1 = insn->value_num >= 2 ? vtype(*v1) : UNDEF; - enum val_type tdst = vtype_insn(insn); struct ir_insn *dst_insn = insn_dst(insn); - DBGASSERT(tdst == REG); + DBGASSERT(t1 == STACKOFF); DBGASSERT(v1->type == IR_VALUE_INSN && v1->data.insn_d->op == IR_INSN_ALLOCARRAY); @@ -2572,7 +2752,7 @@ static u8 get_alloc_reg(struct ir_insn *insn) static void translate_loadraw(struct ir_insn *insn) { enum val_type tdst = vtype_insn(insn); - struct ir_insn_cg_extra *extra = insn_cg(insn); + struct ir_insn_norm_extra *extra = insn_norm(insn); struct ir_insn *dst_insn = insn_dst(insn); DBGASSERT(tdst == REG); extra->translated[0] = load_addr_to_reg(get_alloc_reg(dst_insn), @@ -2582,7 +2762,7 @@ static void translate_loadraw(struct ir_insn *insn) static void translate_loadimm_extra(struct ir_insn *insn) { enum val_type tdst = vtype_insn(insn); - struct ir_insn_cg_extra *extra = insn_cg(insn); + struct ir_insn_norm_extra *extra = insn_norm(insn); struct ir_insn *dst_insn = insn_dst(insn); DBGASSERT(tdst == REG); extra->translated[0].opcode = BPF_IMM | BPF_LD | BPF_DW; @@ -2598,7 +2778,7 @@ static void translate_storeraw(struct ir_insn *insn) { struct ir_value v0 = insn->values[0]; enum val_type t0 = insn->value_num >= 1 ? vtype(v0) : UNDEF; - struct ir_insn_cg_extra *extra = insn_cg(insn); + struct ir_insn_norm_extra *extra = insn_norm(insn); // storeraw if (insn->addr_val.value.type == IR_VALUE_INSN) { // Store value in (address in the value) @@ -2629,7 +2809,7 @@ static void translate_alu(struct ir_insn *insn) enum val_type t0 = insn->value_num >= 1 ? vtype(v0) : UNDEF; enum val_type t1 = insn->value_num >= 2 ? vtype(v1) : UNDEF; enum val_type tdst = vtype_insn(insn); - struct ir_insn_cg_extra *extra = insn_cg(insn); + struct ir_insn_norm_extra *extra = insn_norm(insn); struct ir_insn *dst_insn = insn_dst(insn); DBGASSERT(tdst == REG); DBGASSERT(t0 == REG); @@ -2658,7 +2838,7 @@ static void translate_assign(struct ir_insn *insn) struct ir_value v0 = insn->values[0]; enum val_type t0 = insn->value_num >= 1 ? vtype(v0) : UNDEF; enum val_type tdst = vtype_insn(insn); - struct ir_insn_cg_extra *extra = insn_cg(insn); + struct ir_insn_norm_extra *extra = insn_norm(insn); struct ir_insn *dst_insn = insn_dst(insn); // reg = const (alu) @@ -2682,13 +2862,13 @@ static void translate_assign(struct ir_insn *insn) static void translate_ret(struct ir_insn *insn) { - struct ir_insn_cg_extra *extra = insn_cg(insn); + struct ir_insn_norm_extra *extra = insn_norm(insn); extra->translated[0].opcode = BPF_EXIT | BPF_JMP; } static void translate_call(struct ir_insn *insn) { - struct ir_insn_cg_extra *extra = insn_cg(insn); + struct ir_insn_norm_extra *extra = insn_norm(insn); // Currently only support local helper functions extra->translated[0].opcode = BPF_CALL | BPF_JMP; extra->translated[0].it = IMM; @@ -2697,7 +2877,7 @@ static void translate_call(struct ir_insn *insn) static void translate_ja(struct ir_insn *insn) { - struct ir_insn_cg_extra *extra = insn_cg(insn); + struct ir_insn_norm_extra *extra = insn_norm(insn); extra->translated[0].opcode = BPF_JMP | BPF_JA; } @@ -2706,7 +2886,7 @@ static void translate_neg(struct ir_insn *insn) struct ir_value v0 = insn->values[0]; enum val_type t0 = insn->value_num >= 1 ? vtype(v0) : UNDEF; enum val_type tdst = vtype_insn(insn); - struct ir_insn_cg_extra *extra = insn_cg(insn); + struct ir_insn_norm_extra *extra = insn_norm(insn); struct ir_insn *dst_insn = insn_dst(insn); DBGASSERT(tdst == REG && t0 == REG); DBGASSERT(get_alloc_reg(dst_insn) == get_alloc_reg(v0.data.insn_d)); @@ -2718,7 +2898,7 @@ static void translate_end(struct ir_insn *insn) struct ir_value v0 = insn->values[0]; enum val_type t0 = insn->value_num >= 1 ? vtype(v0) : UNDEF; enum val_type tdst = vtype_insn(insn); - struct ir_insn_cg_extra *extra = insn_cg(insn); + struct ir_insn_norm_extra *extra = insn_norm(insn); struct ir_insn *dst_insn = insn_dst(insn); DBGASSERT(tdst == REG); DBGASSERT(t0 == REG); @@ -2733,7 +2913,7 @@ static void translate_cond_jmp(struct ir_insn *insn) struct ir_value v1 = insn->values[1]; enum val_type t0 = insn->value_num >= 1 ? vtype(v0) : UNDEF; enum val_type t1 = insn->value_num >= 2 ? vtype(v1) : UNDEF; - struct ir_insn_cg_extra *extra = insn_cg(insn); + struct ir_insn_norm_extra *extra = insn_norm(insn); DBGASSERT(t0 == REG || t1 == REG); if (t0 == REG) { if (t1 == REG) { @@ -2840,7 +3020,7 @@ static void check_total_insn(struct bpf_ir_env *env, struct ir_function *fun) struct ir_insn *insn, *tmp; list_for_each_entry_safe(insn, tmp, &bb->ir_insn_head, list_ptr) { - struct ir_insn_cg_extra *extra = insn_cg(insn); + struct ir_insn_norm_extra *extra = insn_norm(insn); cnt += extra->translated_num; } } @@ -2858,7 +3038,7 @@ static void translate(struct bpf_ir_env *env, struct ir_function *fun) struct ir_insn *insn, *tmp; list_for_each_entry_safe(insn, tmp, &bb->ir_insn_head, list_ptr) { - struct ir_insn_cg_extra *extra = insn_cg(insn); + struct ir_insn_norm_extra *extra = insn_norm(insn); extra->translated_num = 1; // Default: 1 instruction if (insn->op == IR_INSN_ALLOC) { // Nothing to do @@ -3075,12 +3255,16 @@ void bpf_ir_compile(struct bpf_ir_env *env, struct ir_function *fun) prog_check_cg(env, fun); CHECK_ERR(); } + + flatten_ir(env, fun); + CHECK_ERR(); + + print_ir_prog_cg_flatten(env, fun, "Flattening"); + // Step 12: Normalize normalize(env, fun); CHECK_ERR(); print_ir_prog_cg_alloc(env, fun, "Normalization"); - // prog_check_cg(env, fun); - // CHECK_ERR(); replace_builtin_const(env, fun); CHECK_ERR(); @@ -3101,29 +3285,6 @@ void bpf_ir_compile(struct bpf_ir_env *env, struct ir_function *fun) CHECK_ERR(); // Free CG resources - free_cg_res(fun); + free_cg_final(fun); env->cg_time += get_cur_time_ns() - starttime; } - -void bpf_ir_init_insn_cg(struct bpf_ir_env *env, struct ir_insn *insn) -{ - struct ir_insn_cg_extra *extra = NULL; - SAFE_MALLOC(extra, sizeof(struct ir_insn_cg_extra)); - insn->user_data = extra; - // When init, the destination is itself - extra->dst = bpf_ir_value_undef(); - if (!bpf_ir_is_void(insn)) { - set_insn_dst(env, insn, insn); - } - - INIT_ARRAY(&extra->adj, struct ir_insn *); - extra->allocated = false; - extra->spilled = 0; - extra->alloc_reg = 0; - INIT_ARRAY(&extra->gen, struct ir_insn *); - INIT_ARRAY(&extra->kill, struct ir_insn *); - INIT_ARRAY(&extra->in, struct ir_insn *); - INIT_ARRAY(&extra->out, struct ir_insn *); - extra->translated_num = 0; - extra->nonvr = false; -} diff --git a/core/ir_helper.c b/core/ir_helper.c index 8b7eca70..f35b9426 100644 --- a/core/ir_helper.c +++ b/core/ir_helper.c @@ -152,6 +152,18 @@ static void print_ir_rawpos(struct bpf_ir_env *env, struct ir_raw_pos pos) } } +static void print_vr_pos(struct bpf_ir_env *env, struct ir_vr_pos *pos) +{ + if (pos->allocated) { + if (pos->spilled) { + PRINT_LOG_DEBUG(env, "sp+%d", pos->spilled); + } else { + PRINT_LOG_DEBUG(env, "r%u", pos->alloc_reg); + } + } else { + RAISE_ERROR("Not allocated"); + } +} static void print_ir_value_full(struct bpf_ir_env *env, struct ir_value v, void (*print_ir)(struct bpf_ir_env *env, struct ir_insn *)) @@ -176,6 +188,9 @@ static void print_ir_value_full(struct bpf_ir_env *env, struct ir_value v, case IR_VALUE_UNDEF: PRINT_LOG_DEBUG(env, "undef"); break; + case IR_VALUE_FLATTEN_DST: + print_vr_pos(env, &v.data.vr_pos); + break; default: RAISE_ERROR("Unknown IR value type"); } @@ -659,6 +674,13 @@ void print_ir_alloc(struct bpf_ir_env *env, struct ir_insn *insn) } } +void print_ir_flatten(struct bpf_ir_env *env, struct ir_insn *insn) +{ + struct ir_insn_norm_extra *norm = insn_norm(insn); + struct ir_vr_pos *pos = &norm->pos; + print_vr_pos(env, pos); +} + void print_ir_prog_advanced( struct bpf_ir_env *env, struct ir_function *fun, void (*post_bb)(struct bpf_ir_env *env, struct ir_basic_block *), diff --git a/core/ir_insn.c b/core/ir_insn.c index 1d4b42cd..4962484e 100644 --- a/core/ir_insn.c +++ b/core/ir_insn.c @@ -125,6 +125,7 @@ void bpf_ir_replace_all_usage_except(struct bpf_ir_env *env, bpf_ir_array_free(&users); } +// Get all operands of an instruction struct array bpf_ir_get_operands(struct bpf_ir_env *env, struct ir_insn *insn) { struct array uses; @@ -624,7 +625,6 @@ static struct ir_insn *create_ja_insn_base(struct bpf_ir_env *env, struct ir_insn *new_insn = bpf_ir_create_insn_base(env, bb); new_insn->op = IR_INSN_JA; new_insn->bb1 = to_bb; - bpf_ir_array_push(env, &to_bb->users, &new_insn); return new_insn; } @@ -644,8 +644,6 @@ create_jbin_insn_base(struct bpf_ir_env *env, struct ir_basic_block *bb, new_insn->alu_op = alu_type; bpf_ir_val_add_user(env, val1, new_insn); bpf_ir_val_add_user(env, val2, new_insn); - bpf_ir_array_push(env, &to_bb1->users, &new_insn); - bpf_ir_array_push(env, &to_bb2->users, &new_insn); new_insn->value_num = 2; return new_insn; } diff --git a/core/lii.c b/core/lli.c similarity index 92% rename from core/lii.c rename to core/lli.c index 64276fd0..7de9e88d 100644 --- a/core/lii.c +++ b/core/lli.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only #include -// Kernel-side Low-level Interface Implementation +// Low-level Interface implemented for both kernel and user-space #ifdef __KERNEL__ diff --git a/core/ptrset.c b/core/ptrset.c new file mode 100644 index 00000000..dbdf75c6 --- /dev/null +++ b/core/ptrset.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include + +// An efficient pointer hashset data structure + +#define STEP 31 + +// Make sure size > 0 +void bpf_ir_ptrset_init(struct bpf_ir_env *env, struct ptrset *res, size_t size) +{ + SAFE_MALLOC(res->set, size * sizeof(struct ptrset_entry)); + res->size = size; + res->cnt = 0; +} + +static void bpf_ir_ptrset_insert_raw(struct ptrset *set, void *key) +{ + u32 index = hash32_ptr(key) % set->size; + for (u32 i = 0; i < set->size; ++i) { + if (set->set[index].occupy <= 0) { + // Found an empty slot + set->set[index].key = key; + set->set[index].occupy = 1; + set->cnt++; + return; + } else if (set->set[index].key == key) { + // Found + return; + } + index = (index + STEP) % set->size; + } + CRITICAL("Impossible"); +} + +void bpf_ir_ptrset_insert(struct bpf_ir_env *env, struct ptrset *set, void *key) +{ + if (set->cnt >= set->size) { + // Table is full, grow it + size_t new_size = set->size * 2; + struct ptrset new_table; + bpf_ir_ptrset_init(env, &new_table, new_size); + for (size_t i = 0; i < set->size; ++i) { + if (set->set[i].occupy > 0) { + bpf_ir_ptrset_insert_raw(&new_table, + set->set[i].key); + } + } + free_proto(set->set); + set->set = new_table.set; + set->size = new_table.size; + } + bpf_ir_ptrset_insert_raw(set, key); +} + +int bpf_ir_ptrset_delete(struct ptrset *set, void *key) +{ + u32 index = hash32_ptr(key) % set->size; + for (u32 i = 0; i < set->size; ++i) { + if (set->set[index].occupy == 0) { + // Already deleted + return -1; + } + if (set->set[index].occupy == 1) { + if (set->set[index].key == key) { + // Found + set->set[index].occupy = -1; + set->cnt--; + return 0; + } + } + index = (index + STEP) % set->size; + } + return -1; +} + +bool bpf_ir_ptrset_exists(struct ptrset *set, void *key) +{ + u32 index = hash32_ptr(key) % set->size; + for (u32 i = 0; i < set->size; ++i) { + if (set->set[index].occupy == 0) { + // Not found + return false; + } + if (set->set[index].occupy == 1) { + if (set->set[index].key == key) { + // Found + return true; + } + } + index = (index + STEP) % set->size; + } + return NULL; +} + +void bpf_ir_ptrset_print_dbg(struct bpf_ir_env *env, struct ptrset *set, + void (*print_key)(struct bpf_ir_env *env, void *)) +{ + for (size_t i = 0; i < set->size; ++i) { + if (set->set[i].occupy > 0) { + print_key(env, set->set[i].key); + PRINT_LOG_DEBUG(env, " "); + } + } + PRINT_LOG_DEBUG(env, "\n"); +} + +void bpf_ir_ptrset_clean(struct ptrset *set) +{ + for (size_t i = 0; i < set->size; ++i) { + set->set[i].key = NULL; + set->set[i].occupy = 0; + } + set->cnt = 0; +} + +void bpf_ir_ptrset_free(struct ptrset *set) +{ + bpf_ir_ptrset_clean(set); + free_proto(set->set); + set->size = 0; + set->set = NULL; +} + +struct ptrset bpf_ir_ptrset_union(struct bpf_ir_env *env, struct ptrset *set1, + struct ptrset *set2) +{ + struct ptrset res; + bpf_ir_ptrset_init(env, &res, set1->cnt + set2->cnt); + for (size_t i = 0; i < set1->size; ++i) { + if (set1->set[i].occupy > 0) { + bpf_ir_ptrset_insert(env, &res, set1->set[i].key); + } + } + for (size_t i = 0; i < set2->size; ++i) { + if (set2->set[i].occupy > 0) { + bpf_ir_ptrset_insert(env, &res, set2->set[i].key); + } + } + return res; +} + +struct ptrset bpf_ir_ptrset_intersec(struct bpf_ir_env *env, + struct ptrset *set1, struct ptrset *set2) +{ + struct ptrset res; + bpf_ir_ptrset_init(env, &res, set1->cnt); + for (size_t i = 0; i < set1->size; ++i) { + if (set1->set[i].occupy > 0 && + bpf_ir_ptrset_exists(set2, set1->set[i].key)) { + bpf_ir_ptrset_insert(env, &res, set1->set[i].key); + } + } + return res; +} + +// Move set2 to set1 +void bpf_ir_ptrset_move(struct ptrset *set1, struct ptrset *set2) +{ + bpf_ir_ptrset_free(set1); + *set1 = *set2; + set2->set = NULL; + set2->cnt = 0; + set2->size = 0; +} + +// Clone set2 to set1 +void bpf_ir_ptrset_clone(struct bpf_ir_env *env, struct ptrset *set1, + struct ptrset *set2) +{ + bpf_ir_ptrset_free(set1); + bpf_ir_ptrset_init(env, set1, set2->size); + for (size_t i = 0; i < set2->size; ++i) { + if (set2->set[i].occupy > 0) { + bpf_ir_ptrset_insert(env, set1, set2->set[i].key); + } + } +} + +// set1 += set2 +void bpf_ir_ptrset_add(struct bpf_ir_env *env, struct ptrset *set1, + struct ptrset *set2) +{ + for (size_t i = 0; i < set2->size; ++i) { + if (set2->set[i].occupy > 0) { + bpf_ir_ptrset_insert(env, set1, set2->set[i].key); + } + } +} + +// set1 -= set2 +void bpf_ir_ptrset_minus(struct ptrset *set1, struct ptrset *set2) +{ + for (size_t i = 0; i < set2->size; ++i) { + if (set2->set[i].occupy > 0) { + bpf_ir_ptrset_delete(set1, set2->set[i].key); + } + } +} diff --git a/core/tests/test_hashtable.c b/core/tests/test_hashtable.c new file mode 100644 index 00000000..0bca5ec2 --- /dev/null +++ b/core/tests/test_hashtable.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include + +static const struct function_pass pre_passes_def[] = {}; + +static struct function_pass post_passes_def[] = {}; + +const struct function_pass *pre_passes = pre_passes_def; +const struct function_pass *post_passes = post_passes_def; + +const size_t post_passes_cnt = + sizeof(post_passes_def) / sizeof(post_passes_def[0]); +const size_t pre_passes_cnt = + sizeof(pre_passes_def) / sizeof(pre_passes_def[0]); + +static u32 hash(const char *s) +{ + u32 key = 0; + char c; + + while ((c = *s++)) + key += c; + + return key % 7; +} + +void print_key(struct bpf_ir_env *env, void *key) +{ + char *s = (char *)key; + PRINT_LOG_DEBUG(env, "%s", s); +} + +void print_data(struct bpf_ir_env *env, void *data) +{ + int *i = (int *)data; + PRINT_LOG_DEBUG(env, "%d", *i); +} + +void test(int tb_init_size) +{ + struct bpf_ir_opts opts = bpf_ir_default_opts(); + opts.verbose = 5; + struct bpf_ir_env *env = bpf_ir_init_env(opts, NULL, 0); + + struct hashtbl tbl; + bpf_ir_hashtbl_init(env, &tbl, tb_init_size); + + for (int i = 0; i < 5; i++) { + char name[32]; + sprintf(name, "node%d", i); + hashtbl_insert(env, &tbl, name, hash(name), i); + } + + // bpf_ir_hashtbl_print_dbg(env, &tbl, print_key, print_data); + DBGASSERT(tbl.cnt == 5); + + PRINT_LOG_DEBUG(env, "Delete\n"); + + for (int i = 0; i < 3; i++) { + char name[32]; + sprintf(name, "node%d", i); + hashtbl_delete(env, &tbl, name, hash(name)); + } + DBGASSERT(tbl.cnt == 2); + + // bpf_ir_hashtbl_print_dbg(env, &tbl, print_key, print_data); + + PRINT_LOG_DEBUG(env, "Rehash\n"); + + for (int i = 0; i < 8; i++) { + char name[32]; + sprintf(name, "node%d", i); + int tmp = 8 - i; + hashtbl_insert(env, &tbl, name, hash(name), tmp); + } + + DBGASSERT(tbl.cnt == 8); + + // bpf_ir_hashtbl_print_dbg(env, &tbl, print_key, print_data); + + PRINT_LOG_DEBUG(env, "Get\n"); + + for (int i = 0; i < 10; i++) { + char name[32]; + sprintf(name, "node%d", i); + int *tmp = hashtbl_get(env, &tbl, name, hash(name), int); + if (tmp) { + PRINT_LOG_DEBUG(env, "GET %s %d\n", name, *tmp); + } else { + PRINT_LOG_DEBUG(env, "GET %s NULL\n", name); + } + } + + PRINT_LOG_DEBUG(env, "Clean\n"); + + bpf_ir_hashtbl_clean(&tbl); + + DBGASSERT(tbl.cnt == 0); + + // bpf_ir_hashtbl_print_dbg(env, &tbl, print_key, print_data); + + PRINT_LOG_DEBUG(env, "Extend\n"); + + for (int i = 0; i < 100; i++) { + char name[32]; + sprintf(name, "node%d", i); + hashtbl_insert(env, &tbl, name, hash(name), i); + } + + DBGASSERT(tbl.cnt == 100); + + // bpf_ir_hashtbl_print_dbg(env, &tbl, print_key, print_data); + + bpf_ir_hashtbl_free(&tbl); + + bpf_ir_free_env(env); +} + +int main(void) +{ + for (int i = 1; i < 10; i++) { + test(i); + } + return 0; +} diff --git a/core/tests/test_list.c b/core/tests/test_list.c new file mode 100644 index 00000000..9741fc49 --- /dev/null +++ b/core/tests/test_list.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include + +struct my_data { + int val; + struct list_head list; +}; + +void static_test(void) +{ + printf("Test: static list\n"); + // Initialize head element + struct list_head list_head; + INIT_LIST_HEAD(&list_head); + + // Add some elements + struct my_data node1; + node1.val = 1; + list_add_tail(&node1.list, &list_head); + + struct my_data node2; + node2.val = 2; + list_add_tail(&node2.list, &list_head); + + struct my_data node3; + node3.val = 3; + list_add(&node3.list, &node1.list); + + // Iterate over the list + // Use `list_for_each_safe` if need to manipulate the linked list in the loop + struct list_head *p = NULL; + list_for_each(p, &list_head) { + struct my_data *entry = list_entry(p, struct my_data, list); + printf("List element: %d\n", entry->val); + } + + // Result: 1 3 2 +} + +void dyn_test(void) +{ + printf("Test: dynamic list\n"); + + // Initialize head element + struct list_head *p = NULL; + + // Dynamic version + + struct list_head *dlist_head = malloc(sizeof(struct list_head)); + INIT_LIST_HEAD(dlist_head); + for (int i = 0; i < 10; ++i) { + struct my_data *new_node = malloc(sizeof(struct my_data)); + new_node->val = i; + list_add(&new_node->list, dlist_head); + } + list_for_each(p, dlist_head) { + struct my_data *entry = list_entry(p, struct my_data, list); + printf("List element: %d\n", entry->val); + } + // Free + struct list_head *tmp = NULL; + list_for_each_safe(p, tmp, dlist_head) { + struct my_data *entry = list_entry(p, struct my_data, list); + printf("Removing element: %d\n", entry->val); + list_del(p); + free(entry); + } + free(dlist_head); + // Result: 9 8 7 6 5 4 3 2 1 0 +} + +int main(void) +{ + static_test(); + dyn_test(); + return 0; +} diff --git a/core/tests/test_ptrset.c b/core/tests/test_ptrset.c new file mode 100644 index 00000000..6e9accd5 --- /dev/null +++ b/core/tests/test_ptrset.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include + +static const struct function_pass pre_passes_def[] = {}; + +static struct function_pass post_passes_def[] = {}; + +const struct function_pass *pre_passes = pre_passes_def; +const struct function_pass *post_passes = post_passes_def; + +const size_t post_passes_cnt = + sizeof(post_passes_def) / sizeof(post_passes_def[0]); +const size_t pre_passes_cnt = + sizeof(pre_passes_def) / sizeof(pre_passes_def[0]); + +void print_key(struct bpf_ir_env *env, void *key) +{ + char *s = (char *)key; + PRINT_LOG_DEBUG(env, "%s", s); +} + +void test(int initsize) +{ + struct bpf_ir_opts opts = bpf_ir_default_opts(); + opts.verbose = 5; + struct bpf_ir_env *env = bpf_ir_init_env(opts, NULL, 0); + + struct ptrset set; + bpf_ir_ptrset_init(env, &set, initsize); + + char strmap[10][10] = { "hello", "world", "foo", "bar", "baz", + "qux", "quux", "corge", "grault", "garply" }; + + for (int i = 0; i < 10; i++) { + bpf_ir_ptrset_insert(env, &set, strmap[i]); + } + + DBGASSERT(set.cnt == 10); + + bpf_ir_ptrset_print_dbg(env, &set, print_key); + + for (int j = 0; j < 20; ++j) { + for (int i = 0; i < 10; i++) { + bpf_ir_ptrset_insert(env, &set, strmap[i]); + } + } + + DBGASSERT(set.cnt == 10); + + // Should output the same as the previous one + bpf_ir_ptrset_print_dbg(env, &set, print_key); + + for (int i = 0; i < 10; i++) { + int ret = bpf_ir_ptrset_delete(&set, strmap[i]); + if (ret != 0) { + PRINT_LOG_DEBUG(env, "Failed to delete %s\n", + strmap[i]); + } + } + + // for (int i = 0; i < 10; i++) { + // u32 index = hash32_ptr(strmap[i]) % set.size; + // PRINT_LOG_DEBUG(env, "hash32_ptr(%s) = %u\n", strmap[i], index); + // } + + bpf_ir_ptrset_print_dbg(env, &set, print_key); + DBGASSERT(set.cnt == 0); + + struct ptrset set2; + bpf_ir_ptrset_init(env, &set2, initsize); + + for (int i = 0; i < 3; i++) { + bpf_ir_ptrset_insert(env, &set, strmap[i]); + } + + for (int i = 5; i < 9; i++) { + bpf_ir_ptrset_insert(env, &set2, strmap[i]); + } + + struct ptrset set3 = bpf_ir_ptrset_union(env, &set, &set2); + + CRITICAL_ASSERT(env, set3.cnt == 7); + + for (int i = 7; i < 10; i++) { + bpf_ir_ptrset_insert(env, &set, strmap[i]); + } + + struct ptrset set4 = bpf_ir_ptrset_union(env, &set, &set2); + + CRITICAL_ASSERT(env, set4.cnt == 8); + + struct ptrset set5 = bpf_ir_ptrset_intersec(env, &set, &set2); + + CRITICAL_ASSERT(env, set5.cnt == 2); + + // bpf_ir_ptrset_print_dbg(env, &set3, print_key); + + bpf_ir_ptrset_free(&set); + bpf_ir_ptrset_free(&set2); + bpf_ir_ptrset_free(&set3); + bpf_ir_ptrset_free(&set4); + bpf_ir_ptrset_free(&set5); + + bpf_ir_free_env(env); +} + +int main(void) +{ + for (int i = 1; i < 10; i++) { + test(i); + } + return 0; +}