From d1055e4d411ed8279239c81692fb034f990f9018 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 6 Oct 2025 22:20:54 +0530 Subject: [PATCH 01/43] Reduce a condition from handle_cond --- pythonbpf/functions/functions_pass.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pythonbpf/functions/functions_pass.py b/pythonbpf/functions/functions_pass.py index 18904eca..f31d48e1 100644 --- a/pythonbpf/functions/functions_pass.py +++ b/pythonbpf/functions/functions_pass.py @@ -242,9 +242,7 @@ def handle_assign( def handle_cond(func, module, builder, cond, local_sym_tab, map_sym_tab): if isinstance(cond, ast.Constant): - if isinstance(cond.value, bool): - return ir.Constant(ir.IntType(1), int(cond.value)) - elif isinstance(cond.value, int): + if isinstance(cond.value, bool) or isinstance(cond.value, int): return ir.Constant(ir.IntType(1), int(bool(cond.value))) else: logger.info("Unsupported constant type in condition") From f11a43010df363e4e3a4f4cb91dd831e0647de51 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 6 Oct 2025 22:33:03 +0530 Subject: [PATCH 02/43] Add _handle_cond to expr_pass --- pythonbpf/expr_pass.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pythonbpf/expr_pass.py b/pythonbpf/expr_pass.py index 56d047e6..78987cdc 100644 --- a/pythonbpf/expr_pass.py +++ b/pythonbpf/expr_pass.py @@ -132,6 +132,12 @@ def _handle_ctypes_call( return val +def _handle_compare( + func, module, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab=None +): + pass + + def eval_expr( func, module, @@ -212,6 +218,10 @@ def eval_expr( from pythonbpf.binary_ops import handle_binary_op return handle_binary_op(expr, builder, None, local_sym_tab) + elif isinstance(expr, ast.Compare): + return _handle_compare( + func, module, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab + ) logger.info("Unsupported expression evaluation") return None From 6cf5115ea95efe8741e84f78e4c0cfc7d0a9054f Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 6 Oct 2025 22:38:43 +0530 Subject: [PATCH 03/43] Eval LHS and RHS in _handle_compare --- pythonbpf/expr_pass.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/pythonbpf/expr_pass.py b/pythonbpf/expr_pass.py index 78987cdc..fd673df0 100644 --- a/pythonbpf/expr_pass.py +++ b/pythonbpf/expr_pass.py @@ -133,9 +133,37 @@ def _handle_ctypes_call( def _handle_compare( - func, module, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab=None + func, module, builder, cond, local_sym_tab, map_sym_tab, structs_sym_tab=None ): - pass + if len(cond.ops) != 1 or len(cond.comparators) != 1: + logger.error("Only single comparisons are supported") + return None + lhs = eval_expr( + func, + module, + builder, + cond.left, + local_sym_tab, + map_sym_tab, + structs_sym_tab, + ) + rhs = eval_expr( + func, + module, + builder, + cond.comparators[0], + local_sym_tab, + map_sym_tab, + structs_sym_tab, + ) + + if lhs is None or rhs is None: + logger.error("Failed to evaluate comparison operands") + return None + + lhs, _ = lhs + rhs, _ = rhs + return None def eval_expr( From 4f433d00cc849db94be530f3c991cf07b8849f9c Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 6 Oct 2025 23:04:45 +0530 Subject: [PATCH 04/43] Add Boolean return support --- pythonbpf/expr_pass.py | 9 ++++----- tests/passing_tests/return/bool.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 tests/passing_tests/return/bool.py diff --git a/pythonbpf/expr_pass.py b/pythonbpf/expr_pass.py index fd673df0..b037cc3a 100644 --- a/pythonbpf/expr_pass.py +++ b/pythonbpf/expr_pass.py @@ -22,12 +22,11 @@ def _handle_name_expr(expr: ast.Name, local_sym_tab: Dict, builder: ir.IRBuilder def _handle_constant_expr(expr: ast.Constant): """Handle ast.Constant expressions.""" - if isinstance(expr.value, int): - return ir.Constant(ir.IntType(64), expr.value), ir.IntType(64) - elif isinstance(expr.value, bool): - return ir.Constant(ir.IntType(1), int(expr.value)), ir.IntType(1) + logger.info("We the best") + if isinstance(expr.value, int) or isinstance(expr.value, bool): + return ir.Constant(ir.IntType(64), int(expr.value)), ir.IntType(64) else: - logger.info("Unsupported constant type") + logger.error("Unsupported constant type") return None diff --git a/tests/passing_tests/return/bool.py b/tests/passing_tests/return/bool.py new file mode 100644 index 00000000..b5627a64 --- /dev/null +++ b/tests/passing_tests/return/bool.py @@ -0,0 +1,18 @@ +from pythonbpf import bpf, section, bpfglobal, compile +from ctypes import c_void_p, c_int64 + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def hello_world(ctx: c_void_p) -> c_int64: + print("Hello, World!") + return True + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From fb63dbd698d2d5b464887250c15bd73f3263f826 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 7 Oct 2025 03:11:23 +0530 Subject: [PATCH 05/43] Move conditional logic to eval_expr, add _conver_to_bool, add passing bool test --- pythonbpf/expr_pass.py | 28 +++++++++++++++++++++++- pythonbpf/functions/functions_pass.py | 17 ++++++++++++-- tests/passing_tests/conditionals/bool.py | 21 ++++++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 tests/passing_tests/conditionals/bool.py diff --git a/pythonbpf/expr_pass.py b/pythonbpf/expr_pass.py index b037cc3a..79ff4bbc 100644 --- a/pythonbpf/expr_pass.py +++ b/pythonbpf/expr_pass.py @@ -131,9 +131,35 @@ def _handle_ctypes_call( return val +def _handle_comparator(builder, op, lhs, rhs): + """Handle comparison operations.""" + + # NOTE: For now assume same types + + comparison_ops = { + ast.Eq: "==", + ast.NotEq: "!=", + ast.Lt: "<", + ast.LtE: "<=", + ast.Gt: ">", + ast.GtE: ">=", + } + + if type(op) not in comparison_ops: + logger.error(f"Unsupported comparison operator: {type(op)}") + return None + + predicate = comparison_ops[type(op)] + result = builder.icmp_signed(predicate, lhs, rhs) + logger.debug(f"Comparison result: {result}") + return result, ir.IntType(1) + + def _handle_compare( func, module, builder, cond, local_sym_tab, map_sym_tab, structs_sym_tab=None ): + """Handle ast.Compare expressions.""" + if len(cond.ops) != 1 or len(cond.comparators) != 1: logger.error("Only single comparisons are supported") return None @@ -162,7 +188,7 @@ def _handle_compare( lhs, _ = lhs rhs, _ = rhs - return None + return _handle_comparator(builder, cond.ops[0], lhs, rhs) def eval_expr( diff --git a/pythonbpf/functions/functions_pass.py b/pythonbpf/functions/functions_pass.py index f31d48e1..7463f547 100644 --- a/pythonbpf/functions/functions_pass.py +++ b/pythonbpf/functions/functions_pass.py @@ -240,12 +240,25 @@ def handle_assign( logger.info("Unsupported assignment value type") +def _convert_to_bool(builder, val): + if val.type == ir.IntType(1): + return val + if isinstance(val.type, ir.PointerType): + zero = ir.Constant(val.type, None) + else: + zero = ir.Constant(val.type, 0) + return builder.icmp_signed("!=", val, zero) + + def handle_cond(func, module, builder, cond, local_sym_tab, map_sym_tab): + if True: + val = eval_expr(func, module, builder, cond, local_sym_tab, map_sym_tab)[0] + return _convert_to_bool(builder, val) if isinstance(cond, ast.Constant): if isinstance(cond.value, bool) or isinstance(cond.value, int): - return ir.Constant(ir.IntType(1), int(bool(cond.value))) + return ir.Constant(ir.IntType(1), int(cond.value)) else: - logger.info("Unsupported constant type in condition") + raise ValueError("Unsupported constant type in condition") return None elif isinstance(cond, ast.Name): if cond.id in local_sym_tab: diff --git a/tests/passing_tests/conditionals/bool.py b/tests/passing_tests/conditionals/bool.py new file mode 100644 index 00000000..341fa46f --- /dev/null +++ b/tests/passing_tests/conditionals/bool.py @@ -0,0 +1,21 @@ +from pythonbpf import bpf, section, bpfglobal, compile +from ctypes import c_void_p, c_int64 + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def hello_world(ctx: c_void_p) -> c_int64: + if True: + print("Hello, World!") + else: + print("Goodbye, World!") + return + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From 682a7e6566066d930ba095850d808447c374cdf4 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 7 Oct 2025 03:15:34 +0530 Subject: [PATCH 06/43] Add const_int test for conditionals --- tests/passing_tests/conditionals/const_int.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/passing_tests/conditionals/const_int.py diff --git a/tests/passing_tests/conditionals/const_int.py b/tests/passing_tests/conditionals/const_int.py new file mode 100644 index 00000000..47589c87 --- /dev/null +++ b/tests/passing_tests/conditionals/const_int.py @@ -0,0 +1,21 @@ +from pythonbpf import bpf, section, bpfglobal, compile +from ctypes import c_void_p, c_int64 + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def hello_world(ctx: c_void_p) -> c_int64: + if 0: + print("Hello, World!") + else: + print("Goodbye, World!") + return + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From 1cce49f5e01db66aca9c59913db71dc4dbabc001 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 7 Oct 2025 03:24:11 +0530 Subject: [PATCH 07/43] Add const_binop test for conditionals --- .../passing_tests/conditionals/const_binop.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/passing_tests/conditionals/const_binop.py diff --git a/tests/passing_tests/conditionals/const_binop.py b/tests/passing_tests/conditionals/const_binop.py new file mode 100644 index 00000000..9dffd30b --- /dev/null +++ b/tests/passing_tests/conditionals/const_binop.py @@ -0,0 +1,21 @@ +from pythonbpf import bpf, section, bpfglobal, compile +from ctypes import c_void_p, c_int64 + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def hello_world(ctx: c_void_p) -> c_int64: + if (0 + 1) * 0: + print("Hello, World!") + else: + print("Goodbye, World!") + return + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From 2de280915a59e5ade39a3ccfa89d802c5a747e1e Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 7 Oct 2025 03:37:13 +0530 Subject: [PATCH 08/43] Add var test for conditionals --- tests/passing_tests/conditionals/var.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/passing_tests/conditionals/var.py diff --git a/tests/passing_tests/conditionals/var.py b/tests/passing_tests/conditionals/var.py new file mode 100644 index 00000000..449501e9 --- /dev/null +++ b/tests/passing_tests/conditionals/var.py @@ -0,0 +1,22 @@ +from pythonbpf import bpf, section, bpfglobal, compile +from ctypes import c_void_p, c_int64 + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def hello_world(ctx: c_void_p) -> c_int64: + x = 0 + if x: + print("Hello, World!") + else: + print("Goodbye, World!") + return + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From 12b712c21783eb9d2a8f7188c2045cab7f2beb77 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 7 Oct 2025 03:43:36 +0530 Subject: [PATCH 09/43] Add var_binop test for conditionals --- tests/passing_tests/conditionals/var_binop.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/passing_tests/conditionals/var_binop.py diff --git a/tests/passing_tests/conditionals/var_binop.py b/tests/passing_tests/conditionals/var_binop.py new file mode 100644 index 00000000..75a2262d --- /dev/null +++ b/tests/passing_tests/conditionals/var_binop.py @@ -0,0 +1,22 @@ +from pythonbpf import bpf, section, bpfglobal, compile +from ctypes import c_void_p, c_int64 + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def hello_world(ctx: c_void_p) -> c_int64: + x = 0 + if x * 1: + print("Hello, World!") + else: + print("Goodbye, World!") + return + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From 1d6226d8293000b5ea907358a74b9adcbf380027 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 7 Oct 2025 04:06:16 +0530 Subject: [PATCH 10/43] Add map test to conditionals --- tests/passing_tests/conditionals/map.py | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tests/passing_tests/conditionals/map.py diff --git a/tests/passing_tests/conditionals/map.py b/tests/passing_tests/conditionals/map.py new file mode 100644 index 00000000..fa490a7b --- /dev/null +++ b/tests/passing_tests/conditionals/map.py @@ -0,0 +1,30 @@ +from pythonbpf import bpf, map, section, bpfglobal, compile +from ctypes import c_void_p, c_int64, c_uint64 +from pythonbpf.maps import HashMap + + +@bpf +@map +def last() -> HashMap: + return HashMap(key=c_uint64, value=c_uint64, max_entries=3) + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def hello_world(ctx: c_void_p) -> c_int64: + # last.update(0, 1) + tsp = last.lookup(0) + if tsp: + print("Hello, World!") + else: + print("Goodbye, World!") + return + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From 176673017cc75159a31caf1a2a246d72616929f7 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 7 Oct 2025 04:17:26 +0530 Subject: [PATCH 11/43] Add failing tests struct and not for conditionals --- tests/failing_tests/conditionals/not.py | 30 ++++++++++++++++++++++ tests/failing_tests/conditionals/struct.py | 29 +++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 tests/failing_tests/conditionals/not.py create mode 100644 tests/failing_tests/conditionals/struct.py diff --git a/tests/failing_tests/conditionals/not.py b/tests/failing_tests/conditionals/not.py new file mode 100644 index 00000000..773291ee --- /dev/null +++ b/tests/failing_tests/conditionals/not.py @@ -0,0 +1,30 @@ +from pythonbpf import bpf, map, section, bpfglobal, compile +from ctypes import c_void_p, c_int64, c_uint64 +from pythonbpf.maps import HashMap + + +@bpf +@map +def last() -> HashMap: + return HashMap(key=c_uint64, value=c_uint64, max_entries=3) + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def hello_world(ctx: c_void_p) -> c_int64: + # last.update(0, 1) + tsp = last.lookup(0) + if not tsp: + print("Hello, World!") + else: + print("Goodbye, World!") + return + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() diff --git a/tests/failing_tests/conditionals/struct.py b/tests/failing_tests/conditionals/struct.py new file mode 100644 index 00000000..bbfbba41 --- /dev/null +++ b/tests/failing_tests/conditionals/struct.py @@ -0,0 +1,29 @@ +from pythonbpf import bpf, struct, section, bpfglobal, compile +from ctypes import c_void_p, c_int64, c_uint64 + + +@bpf +@struct +class data_t: + pid: c_uint64 + ts: c_uint64 + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def hello_world(ctx: c_void_p) -> c_int64: + dat = data_t() + if dat.pid: + print("Hello, World!") + else: + print("Goodbye, World!") + return + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From a574527891bc08bb14a42bf55b29b9e699e2cfc3 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 7 Oct 2025 04:49:45 +0530 Subject: [PATCH 12/43] Add support for unary op 'not' in eval_expr, move not test to passing --- pythonbpf/expr_pass.py | 42 ++++++++++++++++++- pythonbpf/functions/functions_pass.py | 14 +------ .../{struct.py => struct_access.py} | 0 .../conditionals/not.py | 0 4 files changed, 43 insertions(+), 13 deletions(-) rename tests/failing_tests/conditionals/{struct.py => struct_access.py} (100%) rename tests/{failing_tests => passing_tests}/conditionals/not.py (100%) diff --git a/pythonbpf/expr_pass.py b/pythonbpf/expr_pass.py index 79ff4bbc..ca0fbf7f 100644 --- a/pythonbpf/expr_pass.py +++ b/pythonbpf/expr_pass.py @@ -22,7 +22,6 @@ def _handle_name_expr(expr: ast.Name, local_sym_tab: Dict, builder: ir.IRBuilder def _handle_constant_expr(expr: ast.Constant): """Handle ast.Constant expressions.""" - logger.info("We the best") if isinstance(expr.value, int) or isinstance(expr.value, bool): return ir.Constant(ir.IntType(64), int(expr.value)), ir.IntType(64) else: @@ -191,6 +190,43 @@ def _handle_compare( return _handle_comparator(builder, cond.ops[0], lhs, rhs) +def convert_to_bool(builder, val): + if val.type == ir.IntType(1): + return val + if isinstance(val.type, ir.PointerType): + zero = ir.Constant(val.type, None) + else: + zero = ir.Constant(val.type, 0) + return builder.icmp_signed("!=", val, zero) + + +def _handle_unary_op( + func, + module, + builder, + expr: ast.UnaryOp, + local_sym_tab, + map_sym_tab, + structs_sym_tab=None, +): + """Handle ast.UnaryOp expressions.""" + if not isinstance(expr.op, ast.Not): + logger.error("Only 'not' unary operator is supported") + return None + + operand = eval_expr( + func, module, builder, expr.operand, local_sym_tab, map_sym_tab, structs_sym_tab + ) + if operand is None: + logger.error("Failed to evaluate operand for unary operation") + return None + + operand_val, operand_type = operand + true_const = ir.Constant(ir.IntType(1), 1) + result = builder.xor(convert_to_bool(builder, operand_val), true_const) + return result, ir.IntType(1) + + def eval_expr( func, module, @@ -275,6 +311,10 @@ def eval_expr( return _handle_compare( func, module, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab ) + elif isinstance(expr, ast.UnaryOp): + return _handle_unary_op( + func, module, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab + ) logger.info("Unsupported expression evaluation") return None diff --git a/pythonbpf/functions/functions_pass.py b/pythonbpf/functions/functions_pass.py index 7463f547..00cbe75c 100644 --- a/pythonbpf/functions/functions_pass.py +++ b/pythonbpf/functions/functions_pass.py @@ -7,7 +7,7 @@ from pythonbpf.helper import HelperHandlerRegistry, handle_helper_call from pythonbpf.type_deducer import ctypes_to_ir from pythonbpf.binary_ops import handle_binary_op -from pythonbpf.expr_pass import eval_expr, handle_expr +from pythonbpf.expr_pass import eval_expr, handle_expr, convert_to_bool from .return_utils import _handle_none_return, _handle_xdp_return, _is_xdp_name @@ -240,20 +240,10 @@ def handle_assign( logger.info("Unsupported assignment value type") -def _convert_to_bool(builder, val): - if val.type == ir.IntType(1): - return val - if isinstance(val.type, ir.PointerType): - zero = ir.Constant(val.type, None) - else: - zero = ir.Constant(val.type, 0) - return builder.icmp_signed("!=", val, zero) - - def handle_cond(func, module, builder, cond, local_sym_tab, map_sym_tab): if True: val = eval_expr(func, module, builder, cond, local_sym_tab, map_sym_tab)[0] - return _convert_to_bool(builder, val) + return convert_to_bool(builder, val) if isinstance(cond, ast.Constant): if isinstance(cond.value, bool) or isinstance(cond.value, int): return ir.Constant(ir.IntType(1), int(cond.value)) diff --git a/tests/failing_tests/conditionals/struct.py b/tests/failing_tests/conditionals/struct_access.py similarity index 100% rename from tests/failing_tests/conditionals/struct.py rename to tests/failing_tests/conditionals/struct_access.py diff --git a/tests/failing_tests/conditionals/not.py b/tests/passing_tests/conditionals/not.py similarity index 100% rename from tests/failing_tests/conditionals/not.py rename to tests/passing_tests/conditionals/not.py From 0e7dcafbab6443aa63bdba7009781a9fb26c6a97 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 7 Oct 2025 05:02:26 +0530 Subject: [PATCH 13/43] Add var_comp test for conditionals --- tests/passing_tests/conditionals/var_comp.py | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/passing_tests/conditionals/var_comp.py diff --git a/tests/passing_tests/conditionals/var_comp.py b/tests/passing_tests/conditionals/var_comp.py new file mode 100644 index 00000000..4f12f15a --- /dev/null +++ b/tests/passing_tests/conditionals/var_comp.py @@ -0,0 +1,22 @@ +from pythonbpf import bpf, section, bpfglobal, compile +from ctypes import c_void_p, c_int64 + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def hello_world(ctx: c_void_p) -> c_int64: + x = 2 + if x > 3: + print("Hello, World!") + else: + print("Goodbye, World!") + return + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From b7092fa362ee66d1546c03967b34cc73a3059c68 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 7 Oct 2025 05:20:43 +0530 Subject: [PATCH 14/43] Add failing test map_comp for conditionals --- tests/failing_tests/conditionals/map_comp.py | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tests/failing_tests/conditionals/map_comp.py diff --git a/tests/failing_tests/conditionals/map_comp.py b/tests/failing_tests/conditionals/map_comp.py new file mode 100644 index 00000000..f5df5bc9 --- /dev/null +++ b/tests/failing_tests/conditionals/map_comp.py @@ -0,0 +1,30 @@ +from pythonbpf import bpf, map, section, bpfglobal, compile +from ctypes import c_void_p, c_int64, c_uint64 +from pythonbpf.maps import HashMap + + +@bpf +@map +def last() -> HashMap: + return HashMap(key=c_uint64, value=c_uint64, max_entries=3) + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def hello_world(ctx: c_void_p) -> c_int64: + last.update(0, 1) + tsp = last.lookup(0) + if not (tsp > 0): + print("Hello, World!") + else: + print("Goodbye, World!") + return + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From f41693bc6d0eef8300925c25a5636ce523db6c79 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 7 Oct 2025 05:27:31 +0530 Subject: [PATCH 15/43] Add 'and' and 'or' BoolOps as future deliverables --- TODO.md | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO.md b/TODO.md index 6be41121..7019d916 100644 --- a/TODO.md +++ b/TODO.md @@ -5,6 +5,7 @@ - XDP support in pylibbpf - ringbuf support - recursive expression resolution +- Add supoprt for BoolOp and short circuiting in conditions ## Long term From caa5d92c32de634f6dd57190842aa6a7195267f9 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 7 Oct 2025 13:35:31 +0530 Subject: [PATCH 16/43] Fix struct_access in eval_expr, move struct_access conditional test to passing --- pythonbpf/expr_pass.py | 1 - pythonbpf/functions/functions_pass.py | 12 +++++++++--- .../conditionals/struct_access.py | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) rename tests/{failing_tests => passing_tests}/conditionals/struct_access.py (96%) diff --git a/pythonbpf/expr_pass.py b/pythonbpf/expr_pass.py index ca0fbf7f..0bccc5bb 100644 --- a/pythonbpf/expr_pass.py +++ b/pythonbpf/expr_pass.py @@ -43,7 +43,6 @@ def _handle_attribute_expr( var_ptr, var_type, var_metadata = local_sym_tab[var_name] logger.info(f"Loading attribute {attr_name} from variable {var_name}") logger.info(f"Variable type: {var_type}, Variable ptr: {var_ptr}") - metadata = structs_sym_tab[var_metadata] if attr_name in metadata.fields: gep = metadata.gep(builder, var_ptr, attr_name) diff --git a/pythonbpf/functions/functions_pass.py b/pythonbpf/functions/functions_pass.py index 00cbe75c..80fa5204 100644 --- a/pythonbpf/functions/functions_pass.py +++ b/pythonbpf/functions/functions_pass.py @@ -240,9 +240,13 @@ def handle_assign( logger.info("Unsupported assignment value type") -def handle_cond(func, module, builder, cond, local_sym_tab, map_sym_tab): +def handle_cond( + func, module, builder, cond, local_sym_tab, map_sym_tab, structs_sym_tab=None +): if True: - val = eval_expr(func, module, builder, cond, local_sym_tab, map_sym_tab)[0] + val = eval_expr( + func, module, builder, cond, local_sym_tab, map_sym_tab, structs_sym_tab + )[0] return convert_to_bool(builder, val) if isinstance(cond, ast.Constant): if isinstance(cond.value, bool) or isinstance(cond.value, int): @@ -321,7 +325,9 @@ def handle_if( else: else_block = None - cond = handle_cond(func, module, builder, stmt.test, local_sym_tab, map_sym_tab) + cond = handle_cond( + func, module, builder, stmt.test, local_sym_tab, map_sym_tab, structs_sym_tab + ) if else_block: builder.cbranch(cond, then_block, else_block) else: diff --git a/tests/failing_tests/conditionals/struct_access.py b/tests/passing_tests/conditionals/struct_access.py similarity index 96% rename from tests/failing_tests/conditionals/struct_access.py rename to tests/passing_tests/conditionals/struct_access.py index bbfbba41..52672902 100644 --- a/tests/failing_tests/conditionals/struct_access.py +++ b/tests/passing_tests/conditionals/struct_access.py @@ -13,7 +13,7 @@ class data_t: @section("tracepoint/syscalls/sys_enter_execve") def hello_world(ctx: c_void_p) -> c_int64: dat = data_t() - if dat.pid: + if dat.ts: print("Hello, World!") else: print("Goodbye, World!") From 1843ca6c53bb5c45828cc7ec4c88727edca5d7d8 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 7 Oct 2025 13:42:58 +0530 Subject: [PATCH 17/43] Add failing struct_ptr test for conditionals --- .../failing_tests/conditionals/struct_ptr.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/failing_tests/conditionals/struct_ptr.py diff --git a/tests/failing_tests/conditionals/struct_ptr.py b/tests/failing_tests/conditionals/struct_ptr.py new file mode 100644 index 00000000..baee3a8d --- /dev/null +++ b/tests/failing_tests/conditionals/struct_ptr.py @@ -0,0 +1,29 @@ +from pythonbpf import bpf, struct, section, bpfglobal, compile +from ctypes import c_void_p, c_int64, c_uint64 + + +@bpf +@struct +class data_t: + pid: c_uint64 + ts: c_uint64 + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def hello_world(ctx: c_void_p) -> c_int64: + dat = data_t() + if dat: + print("Hello, World!") + else: + print("Goodbye, World!") + return + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From 9e1142bf053629738c7d0c10e191f138b0c7a246 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 7 Oct 2025 14:02:09 +0530 Subject: [PATCH 18/43] Add type_mismatch failing test for conditionals --- .../conditionals/type_mismatch.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tests/failing_tests/conditionals/type_mismatch.py diff --git a/tests/failing_tests/conditionals/type_mismatch.py b/tests/failing_tests/conditionals/type_mismatch.py new file mode 100644 index 00000000..1efc5e25 --- /dev/null +++ b/tests/failing_tests/conditionals/type_mismatch.py @@ -0,0 +1,23 @@ +from pythonbpf import bpf, section, bpfglobal, compile +from ctypes import c_void_p, c_int64, c_int32 + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def hello_world(ctx: c_void_p) -> c_int64: + x = 0 + y = c_int32(0) + if x == y: + print("Hello, World!") + else: + print("Goodbye, World!") + return + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From 2d850f457f37b4861043d73299dfaaa60e3a2f1f Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 02:22:41 +0530 Subject: [PATCH 19/43] Add _normalize_types to handle mismatched ints, move type_mismatch test to passing --- pythonbpf/expr_pass.py | 19 +++++++++++++++++++ .../conditionals/type_mismatch.py | 0 2 files changed, 19 insertions(+) rename tests/{failing_tests => passing_tests}/conditionals/type_mismatch.py (100%) diff --git a/pythonbpf/expr_pass.py b/pythonbpf/expr_pass.py index 0bccc5bb..fa22c2e2 100644 --- a/pythonbpf/expr_pass.py +++ b/pythonbpf/expr_pass.py @@ -129,10 +129,29 @@ def _handle_ctypes_call( return val +def _normalize_types(builder, lhs, rhs): + """Normalize types for comparison.""" + + if isinstance(lhs.type, ir.IntType) and isinstance(rhs.type, ir.IntType): + if lhs.type.width < rhs.type.width: + lhs = builder.sext(lhs, rhs.type) + else: + rhs = builder.sext(rhs, lhs.type) + return lhs, rhs + + logger.error(f"Type mismatch: {lhs.type} vs {rhs.type}") + return None, None + + def _handle_comparator(builder, op, lhs, rhs): """Handle comparison operations.""" # NOTE: For now assume same types + if lhs.type != rhs.type: + lhs, rhs = _normalize_types(builder, lhs, rhs) + + if lhs is None or rhs is None: + return None comparison_ops = { ast.Eq: "==", diff --git a/tests/failing_tests/conditionals/type_mismatch.py b/tests/passing_tests/conditionals/type_mismatch.py similarity index 100% rename from tests/failing_tests/conditionals/type_mismatch.py rename to tests/passing_tests/conditionals/type_mismatch.py From ab7127556605c59a31d3ea3faea9811adff57ce8 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 03:00:52 +0530 Subject: [PATCH 20/43] Add _get_base_type to expr_pass --- pythonbpf/expr_pass.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pythonbpf/expr_pass.py b/pythonbpf/expr_pass.py index fa22c2e2..3ba21253 100644 --- a/pythonbpf/expr_pass.py +++ b/pythonbpf/expr_pass.py @@ -129,6 +129,14 @@ def _handle_ctypes_call( return val +def _get_base_type(ir_type): + """Get the base type for pointer types.""" + cur_type = ir_type + while isinstance(cur_type, ir.PointerType): + cur_type = cur_type.pointee + return cur_type + + def _normalize_types(builder, lhs, rhs): """Normalize types for comparison.""" From 480afd1341eb6024bdbd3963e6565974e8ae2c82 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 03:02:31 +0530 Subject: [PATCH 21/43] Move _get_base_type to _get_base_type_and_depth --- pythonbpf/expr_pass.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pythonbpf/expr_pass.py b/pythonbpf/expr_pass.py index 3ba21253..fe04b683 100644 --- a/pythonbpf/expr_pass.py +++ b/pythonbpf/expr_pass.py @@ -129,12 +129,14 @@ def _handle_ctypes_call( return val -def _get_base_type(ir_type): +def _get_base_type_and_depth(ir_type): """Get the base type for pointer types.""" cur_type = ir_type + depth = 0 while isinstance(cur_type, ir.PointerType): + depth += 1 cur_type = cur_type.pointee - return cur_type + return cur_type, depth def _normalize_types(builder, lhs, rhs): From 3f9604a370710aff18c7bce20c1f1d053ea73491 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 03:12:17 +0530 Subject: [PATCH 22/43] Add _deref_to_depth in expr_pass --- pythonbpf/expr_pass.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pythonbpf/expr_pass.py b/pythonbpf/expr_pass.py index fe04b683..834d9247 100644 --- a/pythonbpf/expr_pass.py +++ b/pythonbpf/expr_pass.py @@ -139,6 +139,18 @@ def _get_base_type_and_depth(ir_type): return cur_type, depth +def _deref_to_depth(builder, val, target_depth): + """Dereference a pointer to a certain depth.""" + + cur_val = val + for _ in range(target_depth): + if not isinstance(val.type, ir.PointerType): + logger.error("Cannot dereference further, non-pointer type") + return None + cur_val = builder.load(cur_val) + return cur_val + + def _normalize_types(builder, lhs, rhs): """Normalize types for comparison.""" From 6b599808740d5fe774041eff42ad1f8d2afc0a2a Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 05:53:12 +0530 Subject: [PATCH 23/43] Add null checks for pointer derefs to avoid map_value_or_null verifier errors --- pythonbpf/expr_pass.py | 66 +++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/pythonbpf/expr_pass.py b/pythonbpf/expr_pass.py index 834d9247..aee2ab13 100644 --- a/pythonbpf/expr_pass.py +++ b/pythonbpf/expr_pass.py @@ -139,38 +139,84 @@ def _get_base_type_and_depth(ir_type): return cur_type, depth -def _deref_to_depth(builder, val, target_depth): +def _deref_to_depth(func, builder, val, target_depth): """Dereference a pointer to a certain depth.""" cur_val = val - for _ in range(target_depth): + cur_type = val.type + + for depth in range(target_depth): if not isinstance(val.type, ir.PointerType): logger.error("Cannot dereference further, non-pointer type") return None - cur_val = builder.load(cur_val) + + # dereference with null check + pointee_type = cur_type.pointee + null_check_block = builder.block + not_null_block = func.append_basic_block(name=f"deref_not_null_{depth}") + merge_block = func.append_basic_block(name=f"deref_merge_{depth}") + + null_ptr = ir.Constant(cur_type, None) + is_not_null = builder.icmp_signed("!=", cur_val, null_ptr) + logger.debug(f"Inserted null check for pointer at depth {depth}") + + builder.cbranch(is_not_null, not_null_block, merge_block) + + builder.position_at_end(not_null_block) + dereferenced_val = builder.load(cur_val) + logger.debug(f"Dereferenced to depth {depth - 1}, type: {pointee_type}") + builder.branch(merge_block) + + builder.position_at_end(merge_block) + phi = builder.phi(pointee_type, name=f"deref_result_{depth}") + + zero_value = ( + ir.Constant(pointee_type, 0) + if isinstance(pointee_type, ir.IntType) + else ir.Constant(pointee_type, None) + ) + phi.add_incoming(zero_value, null_check_block) + + phi.add_incoming(dereferenced_val, not_null_block) + + # Continue with phi result + cur_val = phi + cur_type = pointee_type return cur_val -def _normalize_types(builder, lhs, rhs): +def _normalize_types(func, builder, lhs, rhs): """Normalize types for comparison.""" + logger.info(f"Normalizing types: {lhs.type} vs {rhs.type}") if isinstance(lhs.type, ir.IntType) and isinstance(rhs.type, ir.IntType): if lhs.type.width < rhs.type.width: lhs = builder.sext(lhs, rhs.type) else: rhs = builder.sext(rhs, lhs.type) return lhs, rhs - - logger.error(f"Type mismatch: {lhs.type} vs {rhs.type}") - return None, None + elif not isinstance(lhs.type, ir.PointerType) and not isinstance( + rhs.type, ir.PointerType + ): + logger.error(f"Type mismatch: {lhs.type} vs {rhs.type}") + return None, None + else: + lhs_base, lhs_depth = _get_base_type_and_depth(lhs.type) + rhs_base, rhs_depth = _get_base_type_and_depth(rhs.type) + if lhs_base == rhs_base: + if lhs_depth < rhs_depth: + rhs = _deref_to_depth(func, builder, rhs, rhs_depth - lhs_depth) + elif rhs_depth < lhs_depth: + lhs = _deref_to_depth(func, builder, lhs, lhs_depth - rhs_depth) + return _normalize_types(func, builder, lhs, rhs) -def _handle_comparator(builder, op, lhs, rhs): +def _handle_comparator(func, builder, op, lhs, rhs): """Handle comparison operations.""" # NOTE: For now assume same types if lhs.type != rhs.type: - lhs, rhs = _normalize_types(builder, lhs, rhs) + lhs, rhs = _normalize_types(func, builder, lhs, rhs) if lhs is None or rhs is None: return None @@ -227,7 +273,7 @@ def _handle_compare( lhs, _ = lhs rhs, _ = rhs - return _handle_comparator(builder, cond.ops[0], lhs, rhs) + return _handle_comparator(func, builder, cond.ops[0], lhs, rhs) def convert_to_bool(builder, val): From 95a196a91fdfb1591262e9babd5ddad5a003384a Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 05:53:52 +0530 Subject: [PATCH 24/43] Move map_comp test to passing --- tests/{failing_tests => passing_tests}/conditionals/map_comp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/{failing_tests => passing_tests}/conditionals/map_comp.py (96%) diff --git a/tests/failing_tests/conditionals/map_comp.py b/tests/passing_tests/conditionals/map_comp.py similarity index 96% rename from tests/failing_tests/conditionals/map_comp.py rename to tests/passing_tests/conditionals/map_comp.py index f5df5bc9..2350f06a 100644 --- a/tests/failing_tests/conditionals/map_comp.py +++ b/tests/passing_tests/conditionals/map_comp.py @@ -14,7 +14,7 @@ def last() -> HashMap: def hello_world(ctx: c_void_p) -> c_int64: last.update(0, 1) tsp = last.lookup(0) - if not (tsp > 0): + if tsp > 0: print("Hello, World!") else: print("Goodbye, World!") From a764b095f8607d7550e602c972ccd00cfabc1661 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 05:54:49 +0530 Subject: [PATCH 25/43] Add helper_cond failing test for conditionals --- .../failing_tests/conditionals/helper_cond.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/failing_tests/conditionals/helper_cond.py diff --git a/tests/failing_tests/conditionals/helper_cond.py b/tests/failing_tests/conditionals/helper_cond.py new file mode 100644 index 00000000..fb13e3af --- /dev/null +++ b/tests/failing_tests/conditionals/helper_cond.py @@ -0,0 +1,26 @@ +from pythonbpf import bpf, map, section, bpfglobal +from ctypes import c_void_p, c_int64, c_uint64 +from pythonbpf.maps import HashMap + + +@bpf +@map +def last() -> HashMap: + return HashMap(key=c_uint64, value=c_uint64, max_entries=3) + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def hello_world(ctx: c_void_p) -> c_int64: + last.update(0, 1) + if last.lookup(0) > 0: + print("Hello, World!") + else: + print("Goodbye, World!") + return + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" From ecac24c1d2d00fb4caace445ec6cbc7554140eb4 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 05:57:17 +0530 Subject: [PATCH 26/43] Add explanation notes to failing conditionals tests --- tests/failing_tests/conditionals/helper_cond.py | 5 +++++ tests/failing_tests/conditionals/struct_ptr.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/tests/failing_tests/conditionals/helper_cond.py b/tests/failing_tests/conditionals/helper_cond.py index fb13e3af..32473c39 100644 --- a/tests/failing_tests/conditionals/helper_cond.py +++ b/tests/failing_tests/conditionals/helper_cond.py @@ -2,6 +2,11 @@ from ctypes import c_void_p, c_int64, c_uint64 from pythonbpf.maps import HashMap +# NOTE: Decided against fixing this +# as a workaround is assigning the result of lookup to a variable +# and then using that variable in the if statement. +# Might fix in future. + @bpf @map diff --git a/tests/failing_tests/conditionals/struct_ptr.py b/tests/failing_tests/conditionals/struct_ptr.py index baee3a8d..7085f819 100644 --- a/tests/failing_tests/conditionals/struct_ptr.py +++ b/tests/failing_tests/conditionals/struct_ptr.py @@ -1,6 +1,11 @@ from pythonbpf import bpf, struct, section, bpfglobal, compile from ctypes import c_void_p, c_int64, c_uint64 +# NOTE: Decided against fixing this +# as one workaround is to just check any field of the struct +# in the if statement. Ugly but works. +# Might fix in future. + @bpf @struct From d2ff53052caecded4666aacc1de6eb1cf9cc9cd0 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 06:04:29 +0530 Subject: [PATCH 27/43] Add support for is and is not keywords --- pythonbpf/expr_pass.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pythonbpf/expr_pass.py b/pythonbpf/expr_pass.py index aee2ab13..2c734ea8 100644 --- a/pythonbpf/expr_pass.py +++ b/pythonbpf/expr_pass.py @@ -228,6 +228,8 @@ def _handle_comparator(func, builder, op, lhs, rhs): ast.LtE: "<=", ast.Gt: ">", ast.GtE: ">=", + ast.Is: "==", + ast.IsNot: "!=", } if type(op) not in comparison_ops: From 98f262ae226b40a538a82ff4553303ac6e2b3d53 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 06:11:59 +0530 Subject: [PATCH 28/43] Add BoolOp handling stub in eval_expr --- pythonbpf/expr_pass.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pythonbpf/expr_pass.py b/pythonbpf/expr_pass.py index 2c734ea8..98e208ae 100644 --- a/pythonbpf/expr_pass.py +++ b/pythonbpf/expr_pass.py @@ -315,6 +315,18 @@ def _handle_unary_op( return result, ir.IntType(1) +def _handle_boolean_op( + func, + module, + builder, + expr: ast.BoolOp, + local_sym_tab, + map_sym_tab, + structs_sym_tab=None, +): + pass + + def eval_expr( func, module, @@ -403,6 +415,10 @@ def eval_expr( return _handle_unary_op( func, module, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab ) + elif isinstance(expr, ast.BoolOp): + return _handle_boolean_op( + func, module, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab + ) logger.info("Unsupported expression evaluation") return None From f98491f3bd5b3068d258ef90bcc98bd7a67d30fd Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 06:14:32 +0530 Subject: [PATCH 29/43] Add handle_and and handle_or handling stub in eval_expr --- pythonbpf/expr_pass.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/pythonbpf/expr_pass.py b/pythonbpf/expr_pass.py index 98e208ae..17d95006 100644 --- a/pythonbpf/expr_pass.py +++ b/pythonbpf/expr_pass.py @@ -315,6 +315,14 @@ def _handle_unary_op( return result, ir.IntType(1) +def _handle_and_op(func, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab): + pass + + +def _handle_or_op(func, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab): + pass + + def _handle_boolean_op( func, module, @@ -324,7 +332,19 @@ def _handle_boolean_op( map_sym_tab, structs_sym_tab=None, ): - pass + """Handle `and` and `or` boolean operations.""" + + if isinstance(expr.op, ast.And): + return _handle_and_op( + func, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab + ) + elif isinstance(expr.op, ast.Or): + return _handle_or_op( + func, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab + ) + else: + logger.error(f"Unsupported boolean operator: {type(expr.op).__name__}") + return None def eval_expr( From 1f96bab94404bc86634f2eed3e5706afbb81b67a Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 06:24:13 +0530 Subject: [PATCH 30/43] Add _handle_and_op in expr_pass --- pythonbpf/expr_pass.py | 51 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/pythonbpf/expr_pass.py b/pythonbpf/expr_pass.py index 17d95006..7accf6e2 100644 --- a/pythonbpf/expr_pass.py +++ b/pythonbpf/expr_pass.py @@ -316,7 +316,56 @@ def _handle_unary_op( def _handle_and_op(func, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab): - pass + """Handle `and` boolean operations.""" + + logger.debug(f"Handling 'and' operator with {len(expr.values)} operands") + + merge_block = func.append_basic_block(name="and.merge") + false_block = func.append_basic_block(name="and.false") + + incoming_values = [] + + for i, value in enumerate(expr.values): + is_last = i == len(expr.values) - 1 + + # Evaluate current operand + operand_result = eval_expr( + func, None, builder, value, local_sym_tab, map_sym_tab, structs_sym_tab + ) + if operand_result is None: + logger.error(f"Failed to evaluate operand {i} in 'and' expression") + return None + + operand_val, operand_type = operand_result + + # Convert to boolean if needed + operand_bool = convert_to_bool(builder, operand_val) + current_block = builder.block + + if is_last: + # Last operand: result is this value + builder.branch(merge_block) + incoming_values.append((operand_bool, current_block)) + else: + # Not last: check if true, continue or short-circuit + next_check = func.append_basic_block(name=f"and.check_{i + 1}") + builder.cbranch(operand_bool, next_check, false_block) + builder.position_at_end(next_check) + + # False block: short-circuit with false + builder.position_at_end(false_block) + builder.branch(merge_block) + false_value = ir.Constant(ir.IntType(1), 0) + incoming_values.append((false_value, false_block)) + + # Merge block: phi node + builder.position_at_end(merge_block) + phi = builder.phi(ir.IntType(1), name="and.result") + for val, block in incoming_values: + phi.add_incoming(val, block) + + logger.debug(f"Generated 'and' with {len(incoming_values)} incoming values") + return phi, ir.IntType(1) def _handle_or_op(func, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab): From 95d63d969e5f743ecd229ddc2d6dfce4887232ed Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 06:24:57 +0530 Subject: [PATCH 31/43] Add _handle_or_or in expr_pass --- pythonbpf/expr_pass.py | 51 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/pythonbpf/expr_pass.py b/pythonbpf/expr_pass.py index 7accf6e2..fb4cfe6b 100644 --- a/pythonbpf/expr_pass.py +++ b/pythonbpf/expr_pass.py @@ -369,7 +369,56 @@ def _handle_and_op(func, builder, expr, local_sym_tab, map_sym_tab, structs_sym_ def _handle_or_op(func, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab): - pass + """Handle `or` boolean operations.""" + + logger.debug(f"Handling 'or' operator with {len(expr.values)} operands") + + merge_block = func.append_basic_block(name="or.merge") + true_block = func.append_basic_block(name="or.true") + + incoming_values = [] + + for i, value in enumerate(expr.values): + is_last = i == len(expr.values) - 1 + + # Evaluate current operand + operand_result = eval_expr( + func, None, builder, value, local_sym_tab, map_sym_tab, structs_sym_tab + ) + if operand_result is None: + logger.error(f"Failed to evaluate operand {i} in 'or' expression") + return None + + operand_val, operand_type = operand_result + + # Convert to boolean if needed + operand_bool = convert_to_bool(builder, operand_val) + current_block = builder.block + + if is_last: + # Last operand: result is this value + builder.branch(merge_block) + incoming_values.append((operand_bool, current_block)) + else: + # Not last: check if false, continue or short-circuit + next_check = func.append_basic_block(name=f"or.check_{i + 1}") + builder.cbranch(operand_bool, true_block, next_check) + builder.position_at_end(next_check) + + # True block: short-circuit with true + builder.position_at_end(true_block) + builder.branch(merge_block) + true_value = ir.Constant(ir.IntType(1), 1) + incoming_values.append((true_value, true_block)) + + # Merge block: phi node + builder.position_at_end(merge_block) + phi = builder.phi(ir.IntType(1), name="or.result") + for val, block in incoming_values: + phi.add_incoming(val, block) + + logger.debug(f"Generated 'or' with {len(incoming_values)} incoming values") + return phi, ir.IntType(1) def _handle_boolean_op( From e7912a088fc2cfc1d47cee2d677545b94719984f Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 06:27:18 +0530 Subject: [PATCH 32/43] Add passing or.py test for conditionals --- tests/passing_tests/conditionals/or.py | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/passing_tests/conditionals/or.py diff --git a/tests/passing_tests/conditionals/or.py b/tests/passing_tests/conditionals/or.py new file mode 100644 index 00000000..5626179b --- /dev/null +++ b/tests/passing_tests/conditionals/or.py @@ -0,0 +1,32 @@ +from pythonbpf import bpf, map, section, bpfglobal, compile +from ctypes import c_void_p, c_int64, c_uint64 +from pythonbpf.maps import HashMap + + +@bpf +@map +def last() -> HashMap: + return HashMap(key=c_uint64, value=c_uint64, max_entries=3) + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def hello_world(ctx: c_void_p) -> c_int64: + last.update(0, 1) + # last.update(1, 2) + x = last.lookup(0) + y = last.lookup(1) + if x or y: + print("Hello, World!") + else: + print("Goodbye, World!") + return + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From 3bb4b099c188ee6b02948245f3b58b6015403255 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 06:28:03 +0530 Subject: [PATCH 33/43] Add passing and.py test for conditionals --- tests/passing_tests/conditionals/and.py | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/passing_tests/conditionals/and.py diff --git a/tests/passing_tests/conditionals/and.py b/tests/passing_tests/conditionals/and.py new file mode 100644 index 00000000..5cb4824e --- /dev/null +++ b/tests/passing_tests/conditionals/and.py @@ -0,0 +1,32 @@ +from pythonbpf import bpf, map, section, bpfglobal, compile +from ctypes import c_void_p, c_int64, c_uint64 +from pythonbpf.maps import HashMap + + +@bpf +@map +def last() -> HashMap: + return HashMap(key=c_uint64, value=c_uint64, max_entries=3) + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def hello_world(ctx: c_void_p) -> c_int64: + last.update(0, 1) + last.update(1, 2) + x = last.lookup(0) + y = last.lookup(1) + if x and y: + print("Hello, World!") + else: + print("Goodbye, World!") + return + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From 4857739eece640d7e9bc98c952f12e276b17f6ba Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 06:42:34 +0530 Subject: [PATCH 34/43] cleanup handle_cond in functions_pass --- pythonbpf/functions/functions_pass.py | 71 ++------------------------- 1 file changed, 4 insertions(+), 67 deletions(-) diff --git a/pythonbpf/functions/functions_pass.py b/pythonbpf/functions/functions_pass.py index 80fa5204..5b04311a 100644 --- a/pythonbpf/functions/functions_pass.py +++ b/pythonbpf/functions/functions_pass.py @@ -243,73 +243,10 @@ def handle_assign( def handle_cond( func, module, builder, cond, local_sym_tab, map_sym_tab, structs_sym_tab=None ): - if True: - val = eval_expr( - func, module, builder, cond, local_sym_tab, map_sym_tab, structs_sym_tab - )[0] - return convert_to_bool(builder, val) - if isinstance(cond, ast.Constant): - if isinstance(cond.value, bool) or isinstance(cond.value, int): - return ir.Constant(ir.IntType(1), int(cond.value)) - else: - raise ValueError("Unsupported constant type in condition") - return None - elif isinstance(cond, ast.Name): - if cond.id in local_sym_tab: - var = local_sym_tab[cond.id].var - val = builder.load(var) - if val.type != ir.IntType(1): - # Convert nonzero values to true, zero to false - if isinstance(val.type, ir.PointerType): - # For pointer types, compare with null pointer - zero = ir.Constant(val.type, None) - else: - # For integer types, compare with zero - zero = ir.Constant(val.type, 0) - val = builder.icmp_signed("!=", val, zero) - return val - else: - logger.info(f"Undefined variable {cond.id} in condition") - return None - elif isinstance(cond, ast.Compare): - lhs = eval_expr(func, module, builder, cond.left, local_sym_tab, map_sym_tab)[0] - if len(cond.ops) != 1 or len(cond.comparators) != 1: - logger.info("Unsupported complex comparison") - return None - rhs = eval_expr( - func, module, builder, cond.comparators[0], local_sym_tab, map_sym_tab - )[0] - op = cond.ops[0] - - if lhs.type != rhs.type: - if isinstance(lhs.type, ir.IntType) and isinstance(rhs.type, ir.IntType): - # Extend the smaller type to the larger type - if lhs.type.width < rhs.type.width: - lhs = builder.sext(lhs, rhs.type) - elif lhs.type.width > rhs.type.width: - rhs = builder.sext(rhs, lhs.type) - else: - logger.info("Type mismatch in comparison") - return None - - if isinstance(op, ast.Eq): - return builder.icmp_signed("==", lhs, rhs) - elif isinstance(op, ast.NotEq): - return builder.icmp_signed("!=", lhs, rhs) - elif isinstance(op, ast.Lt): - return builder.icmp_signed("<", lhs, rhs) - elif isinstance(op, ast.LtE): - return builder.icmp_signed("<=", lhs, rhs) - elif isinstance(op, ast.Gt): - return builder.icmp_signed(">", lhs, rhs) - elif isinstance(op, ast.GtE): - return builder.icmp_signed(">=", lhs, rhs) - else: - logger.info("Unsupported comparison operator") - return None - else: - logger.info("Unsupported condition expression") - return None + val = eval_expr( + func, module, builder, cond, local_sym_tab, map_sym_tab, structs_sym_tab + )[0] + return convert_to_bool(builder, val) def handle_if( From b86341ce7a91dc766cd86fe5b7e91718147e1252 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 06:45:52 +0530 Subject: [PATCH 35/43] Rework dir structure for expr --- pythonbpf/expr/__init__.py | 3 +++ pythonbpf/{ => expr}/expr_pass.py | 0 2 files changed, 3 insertions(+) create mode 100644 pythonbpf/expr/__init__.py rename pythonbpf/{ => expr}/expr_pass.py (100%) diff --git a/pythonbpf/expr/__init__.py b/pythonbpf/expr/__init__.py new file mode 100644 index 00000000..55eae5e7 --- /dev/null +++ b/pythonbpf/expr/__init__.py @@ -0,0 +1,3 @@ +from .expr_pass import eval_expr, handle_expr, convert_to_bool + +__all__ = ["eval_expr", "handle_expr", "convert_to_bool"] diff --git a/pythonbpf/expr_pass.py b/pythonbpf/expr/expr_pass.py similarity index 100% rename from pythonbpf/expr_pass.py rename to pythonbpf/expr/expr_pass.py From 5f9eaff59c2c079839ff1aede7d256310e6ddb68 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 06:49:34 +0530 Subject: [PATCH 36/43] Fix expr imports --- pythonbpf/functions/functions_pass.py | 2 +- pythonbpf/helper/helper_utils.py | 2 +- pythonbpf/maps/maps_pass.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pythonbpf/functions/functions_pass.py b/pythonbpf/functions/functions_pass.py index 5b04311a..7fc3febc 100644 --- a/pythonbpf/functions/functions_pass.py +++ b/pythonbpf/functions/functions_pass.py @@ -7,7 +7,7 @@ from pythonbpf.helper import HelperHandlerRegistry, handle_helper_call from pythonbpf.type_deducer import ctypes_to_ir from pythonbpf.binary_ops import handle_binary_op -from pythonbpf.expr_pass import eval_expr, handle_expr, convert_to_bool +from pythonbpf.expr import eval_expr, handle_expr, convert_to_bool from .return_utils import _handle_none_return, _handle_xdp_return, _is_xdp_name diff --git a/pythonbpf/helper/helper_utils.py b/pythonbpf/helper/helper_utils.py index 0da1e5ea..68ab52cd 100644 --- a/pythonbpf/helper/helper_utils.py +++ b/pythonbpf/helper/helper_utils.py @@ -3,7 +3,7 @@ from collections.abc import Callable from llvmlite import ir -from pythonbpf.expr_pass import eval_expr +from pythonbpf.expr import eval_expr logger = logging.getLogger(__name__) diff --git a/pythonbpf/maps/maps_pass.py b/pythonbpf/maps/maps_pass.py index cc8dfa61..95748a80 100644 --- a/pythonbpf/maps/maps_pass.py +++ b/pythonbpf/maps/maps_pass.py @@ -3,7 +3,7 @@ from llvmlite import ir from enum import Enum from .maps_utils import MapProcessorRegistry -from ..debuginfo import DebugInfoGenerator +from pythonbpf.debuginfo import DebugInfoGenerator import logging logger: Logger = logging.getLogger(__name__) From ee90ee93925add1f54973d8b0f5a1a551a6c8ef7 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 06:50:53 +0530 Subject: [PATCH 37/43] Fix type_deducer import in expr --- pythonbpf/expr/expr_pass.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonbpf/expr/expr_pass.py b/pythonbpf/expr/expr_pass.py index fb4cfe6b..b1cf39ef 100644 --- a/pythonbpf/expr/expr_pass.py +++ b/pythonbpf/expr/expr_pass.py @@ -4,7 +4,7 @@ import logging from typing import Dict -from .type_deducer import ctypes_to_ir, is_ctypes +from pythonbpf.type_deducer import ctypes_to_ir, is_ctypes logger: Logger = logging.getLogger(__name__) From e62557bd1da0ac92e79a79b97ea0225724a5cfe1 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 06:59:32 +0530 Subject: [PATCH 38/43] Seperate type_normalization from expr_pass --- pythonbpf/expr/expr_pass.py | 85 +-------------------------- pythonbpf/expr/type_normalization.py | 86 ++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 83 deletions(-) create mode 100644 pythonbpf/expr/type_normalization.py diff --git a/pythonbpf/expr/expr_pass.py b/pythonbpf/expr/expr_pass.py index b1cf39ef..f0c58a5e 100644 --- a/pythonbpf/expr/expr_pass.py +++ b/pythonbpf/expr/expr_pass.py @@ -5,6 +5,7 @@ from typing import Dict from pythonbpf.type_deducer import ctypes_to_ir, is_ctypes +from .type_normalization import normalize_types logger: Logger = logging.getLogger(__name__) @@ -129,94 +130,12 @@ def _handle_ctypes_call( return val -def _get_base_type_and_depth(ir_type): - """Get the base type for pointer types.""" - cur_type = ir_type - depth = 0 - while isinstance(cur_type, ir.PointerType): - depth += 1 - cur_type = cur_type.pointee - return cur_type, depth - - -def _deref_to_depth(func, builder, val, target_depth): - """Dereference a pointer to a certain depth.""" - - cur_val = val - cur_type = val.type - - for depth in range(target_depth): - if not isinstance(val.type, ir.PointerType): - logger.error("Cannot dereference further, non-pointer type") - return None - - # dereference with null check - pointee_type = cur_type.pointee - null_check_block = builder.block - not_null_block = func.append_basic_block(name=f"deref_not_null_{depth}") - merge_block = func.append_basic_block(name=f"deref_merge_{depth}") - - null_ptr = ir.Constant(cur_type, None) - is_not_null = builder.icmp_signed("!=", cur_val, null_ptr) - logger.debug(f"Inserted null check for pointer at depth {depth}") - - builder.cbranch(is_not_null, not_null_block, merge_block) - - builder.position_at_end(not_null_block) - dereferenced_val = builder.load(cur_val) - logger.debug(f"Dereferenced to depth {depth - 1}, type: {pointee_type}") - builder.branch(merge_block) - - builder.position_at_end(merge_block) - phi = builder.phi(pointee_type, name=f"deref_result_{depth}") - - zero_value = ( - ir.Constant(pointee_type, 0) - if isinstance(pointee_type, ir.IntType) - else ir.Constant(pointee_type, None) - ) - phi.add_incoming(zero_value, null_check_block) - - phi.add_incoming(dereferenced_val, not_null_block) - - # Continue with phi result - cur_val = phi - cur_type = pointee_type - return cur_val - - -def _normalize_types(func, builder, lhs, rhs): - """Normalize types for comparison.""" - - logger.info(f"Normalizing types: {lhs.type} vs {rhs.type}") - if isinstance(lhs.type, ir.IntType) and isinstance(rhs.type, ir.IntType): - if lhs.type.width < rhs.type.width: - lhs = builder.sext(lhs, rhs.type) - else: - rhs = builder.sext(rhs, lhs.type) - return lhs, rhs - elif not isinstance(lhs.type, ir.PointerType) and not isinstance( - rhs.type, ir.PointerType - ): - logger.error(f"Type mismatch: {lhs.type} vs {rhs.type}") - return None, None - else: - lhs_base, lhs_depth = _get_base_type_and_depth(lhs.type) - rhs_base, rhs_depth = _get_base_type_and_depth(rhs.type) - if lhs_base == rhs_base: - if lhs_depth < rhs_depth: - rhs = _deref_to_depth(func, builder, rhs, rhs_depth - lhs_depth) - elif rhs_depth < lhs_depth: - lhs = _deref_to_depth(func, builder, lhs, lhs_depth - rhs_depth) - return _normalize_types(func, builder, lhs, rhs) - - def _handle_comparator(func, builder, op, lhs, rhs): """Handle comparison operations.""" # NOTE: For now assume same types if lhs.type != rhs.type: - lhs, rhs = _normalize_types(func, builder, lhs, rhs) + lhs, rhs = normalize_types(func, builder, lhs, rhs) if lhs is None or rhs is None: return None diff --git a/pythonbpf/expr/type_normalization.py b/pythonbpf/expr/type_normalization.py new file mode 100644 index 00000000..a1b3fadd --- /dev/null +++ b/pythonbpf/expr/type_normalization.py @@ -0,0 +1,86 @@ +from llvmlite import ir +import logging + +logger = logging.getLogger(__name__) + + +def _get_base_type_and_depth(ir_type): + """Get the base type for pointer types.""" + cur_type = ir_type + depth = 0 + while isinstance(cur_type, ir.PointerType): + depth += 1 + cur_type = cur_type.pointee + return cur_type, depth + + +def _deref_to_depth(func, builder, val, target_depth): + """Dereference a pointer to a certain depth.""" + + cur_val = val + cur_type = val.type + + for depth in range(target_depth): + if not isinstance(val.type, ir.PointerType): + logger.error("Cannot dereference further, non-pointer type") + return None + + # dereference with null check + pointee_type = cur_type.pointee + null_check_block = builder.block + not_null_block = func.append_basic_block(name=f"deref_not_null_{depth}") + merge_block = func.append_basic_block(name=f"deref_merge_{depth}") + + null_ptr = ir.Constant(cur_type, None) + is_not_null = builder.icmp_signed("!=", cur_val, null_ptr) + logger.debug(f"Inserted null check for pointer at depth {depth}") + + builder.cbranch(is_not_null, not_null_block, merge_block) + + builder.position_at_end(not_null_block) + dereferenced_val = builder.load(cur_val) + logger.debug(f"Dereferenced to depth {depth - 1}, type: {pointee_type}") + builder.branch(merge_block) + + builder.position_at_end(merge_block) + phi = builder.phi(pointee_type, name=f"deref_result_{depth}") + + zero_value = ( + ir.Constant(pointee_type, 0) + if isinstance(pointee_type, ir.IntType) + else ir.Constant(pointee_type, None) + ) + phi.add_incoming(zero_value, null_check_block) + + phi.add_incoming(dereferenced_val, not_null_block) + + # Continue with phi result + cur_val = phi + cur_type = pointee_type + return cur_val + + +def normalize_types(func, builder, lhs, rhs): + """Normalize types for comparison.""" + + logger.info(f"Normalizing types: {lhs.type} vs {rhs.type}") + if isinstance(lhs.type, ir.IntType) and isinstance(rhs.type, ir.IntType): + if lhs.type.width < rhs.type.width: + lhs = builder.sext(lhs, rhs.type) + else: + rhs = builder.sext(rhs, lhs.type) + return lhs, rhs + elif not isinstance(lhs.type, ir.PointerType) and not isinstance( + rhs.type, ir.PointerType + ): + logger.error(f"Type mismatch: {lhs.type} vs {rhs.type}") + return None, None + else: + lhs_base, lhs_depth = _get_base_type_and_depth(lhs.type) + rhs_base, rhs_depth = _get_base_type_and_depth(rhs.type) + if lhs_base == rhs_base: + if lhs_depth < rhs_depth: + rhs = _deref_to_depth(func, builder, rhs, rhs_depth - lhs_depth) + elif rhs_depth < lhs_depth: + lhs = _deref_to_depth(func, builder, lhs, lhs_depth - rhs_depth) + return normalize_types(func, builder, lhs, rhs) From 0a6571726a459567eff87ad00ba7af282904dadb Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 07:14:42 +0530 Subject: [PATCH 39/43] Move convert_to_bool to type_normalization --- pythonbpf/expr/expr_pass.py | 12 +----------- pythonbpf/expr/type_normalization.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/pythonbpf/expr/expr_pass.py b/pythonbpf/expr/expr_pass.py index f0c58a5e..a72a4bbe 100644 --- a/pythonbpf/expr/expr_pass.py +++ b/pythonbpf/expr/expr_pass.py @@ -5,7 +5,7 @@ from typing import Dict from pythonbpf.type_deducer import ctypes_to_ir, is_ctypes -from .type_normalization import normalize_types +from .type_normalization import normalize_types, convert_to_bool logger: Logger = logging.getLogger(__name__) @@ -197,16 +197,6 @@ def _handle_compare( return _handle_comparator(func, builder, cond.ops[0], lhs, rhs) -def convert_to_bool(builder, val): - if val.type == ir.IntType(1): - return val - if isinstance(val.type, ir.PointerType): - zero = ir.Constant(val.type, None) - else: - zero = ir.Constant(val.type, 0) - return builder.icmp_signed("!=", val, zero) - - def _handle_unary_op( func, module, diff --git a/pythonbpf/expr/type_normalization.py b/pythonbpf/expr/type_normalization.py index a1b3fadd..2715e1f6 100644 --- a/pythonbpf/expr/type_normalization.py +++ b/pythonbpf/expr/type_normalization.py @@ -84,3 +84,14 @@ def normalize_types(func, builder, lhs, rhs): elif rhs_depth < lhs_depth: lhs = _deref_to_depth(func, builder, lhs, lhs_depth - rhs_depth) return normalize_types(func, builder, lhs, rhs) + + +def convert_to_bool(builder, val): + """Convert a value to boolean.""" + if val.type == ir.IntType(1): + return val + if isinstance(val.type, ir.PointerType): + zero = ir.Constant(val.type, None) + else: + zero = ir.Constant(val.type, 0) + return builder.icmp_signed("!=", val, zero) From d38d73d5c66c1ff2cf5c96eea242ff1360b51b3d Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 07:20:04 +0530 Subject: [PATCH 40/43] Move handle_comparator to type_normalization --- pythonbpf/expr/expr_pass.py | 35 ++-------------------------- pythonbpf/expr/type_normalization.py | 35 ++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/pythonbpf/expr/expr_pass.py b/pythonbpf/expr/expr_pass.py index a72a4bbe..21be1961 100644 --- a/pythonbpf/expr/expr_pass.py +++ b/pythonbpf/expr/expr_pass.py @@ -5,7 +5,7 @@ from typing import Dict from pythonbpf.type_deducer import ctypes_to_ir, is_ctypes -from .type_normalization import normalize_types, convert_to_bool +from .type_normalization import convert_to_bool, handle_comparator logger: Logger = logging.getLogger(__name__) @@ -130,37 +130,6 @@ def _handle_ctypes_call( return val -def _handle_comparator(func, builder, op, lhs, rhs): - """Handle comparison operations.""" - - # NOTE: For now assume same types - if lhs.type != rhs.type: - lhs, rhs = normalize_types(func, builder, lhs, rhs) - - if lhs is None or rhs is None: - return None - - comparison_ops = { - ast.Eq: "==", - ast.NotEq: "!=", - ast.Lt: "<", - ast.LtE: "<=", - ast.Gt: ">", - ast.GtE: ">=", - ast.Is: "==", - ast.IsNot: "!=", - } - - if type(op) not in comparison_ops: - logger.error(f"Unsupported comparison operator: {type(op)}") - return None - - predicate = comparison_ops[type(op)] - result = builder.icmp_signed(predicate, lhs, rhs) - logger.debug(f"Comparison result: {result}") - return result, ir.IntType(1) - - def _handle_compare( func, module, builder, cond, local_sym_tab, map_sym_tab, structs_sym_tab=None ): @@ -194,7 +163,7 @@ def _handle_compare( lhs, _ = lhs rhs, _ = rhs - return _handle_comparator(func, builder, cond.ops[0], lhs, rhs) + return handle_comparator(func, builder, cond.ops[0], lhs, rhs) def _handle_unary_op( diff --git a/pythonbpf/expr/type_normalization.py b/pythonbpf/expr/type_normalization.py index 2715e1f6..7a2fb574 100644 --- a/pythonbpf/expr/type_normalization.py +++ b/pythonbpf/expr/type_normalization.py @@ -1,8 +1,20 @@ from llvmlite import ir import logging +import ast logger = logging.getLogger(__name__) +COMPARISON_OPS = { + ast.Eq: "==", + ast.NotEq: "!=", + ast.Lt: "<", + ast.LtE: "<=", + ast.Gt: ">", + ast.GtE: ">=", + ast.Is: "==", + ast.IsNot: "!=", +} + def _get_base_type_and_depth(ir_type): """Get the base type for pointer types.""" @@ -60,7 +72,7 @@ def _deref_to_depth(func, builder, val, target_depth): return cur_val -def normalize_types(func, builder, lhs, rhs): +def _normalize_types(func, builder, lhs, rhs): """Normalize types for comparison.""" logger.info(f"Normalizing types: {lhs.type} vs {rhs.type}") @@ -83,7 +95,7 @@ def normalize_types(func, builder, lhs, rhs): rhs = _deref_to_depth(func, builder, rhs, rhs_depth - lhs_depth) elif rhs_depth < lhs_depth: lhs = _deref_to_depth(func, builder, lhs, lhs_depth - rhs_depth) - return normalize_types(func, builder, lhs, rhs) + return _normalize_types(func, builder, lhs, rhs) def convert_to_bool(builder, val): @@ -95,3 +107,22 @@ def convert_to_bool(builder, val): else: zero = ir.Constant(val.type, 0) return builder.icmp_signed("!=", val, zero) + + +def handle_comparator(func, builder, op, lhs, rhs): + """Handle comparison operations.""" + + if lhs.type != rhs.type: + lhs, rhs = _normalize_types(func, builder, lhs, rhs) + + if lhs is None or rhs is None: + return None + + if type(op) not in COMPARISON_OPS: + logger.error(f"Unsupported comparison operator: {type(op)}") + return None + + predicate = COMPARISON_OPS[type(op)] + result = builder.icmp_signed(predicate, lhs, rhs) + logger.debug(f"Comparison result: {result}") + return result, ir.IntType(1) From 6362a5e665026b0380efa4be548b3ce7b2959b49 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 07:24:14 +0530 Subject: [PATCH 41/43] Fix expr imports --- pythonbpf/expr/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pythonbpf/expr/__init__.py b/pythonbpf/expr/__init__.py index 55eae5e7..d58c543a 100644 --- a/pythonbpf/expr/__init__.py +++ b/pythonbpf/expr/__init__.py @@ -1,3 +1,4 @@ -from .expr_pass import eval_expr, handle_expr, convert_to_bool +from .expr_pass import eval_expr, handle_expr +from .type_normalization import convert_to_bool __all__ = ["eval_expr", "handle_expr", "convert_to_bool"] From 17004d58df777b0c3c925ab9daade1559e37376a Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 07:25:14 +0530 Subject: [PATCH 42/43] Remove completed short term goal from TODO.md --- TODO.md | 1 - 1 file changed, 1 deletion(-) diff --git a/TODO.md b/TODO.md index 7019d916..6be41121 100644 --- a/TODO.md +++ b/TODO.md @@ -5,7 +5,6 @@ - XDP support in pylibbpf - ringbuf support - recursive expression resolution -- Add supoprt for BoolOp and short circuiting in conditions ## Long term From 9fdc6fa3ed28a386dfb1f18c3be319ead2623ba4 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 8 Oct 2025 07:26:41 +0530 Subject: [PATCH 43/43] Add compile to tests/failing_tests/conditionals/helper_cond.py --- tests/failing_tests/conditionals/helper_cond.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/failing_tests/conditionals/helper_cond.py b/tests/failing_tests/conditionals/helper_cond.py index 32473c39..8cf5bdb9 100644 --- a/tests/failing_tests/conditionals/helper_cond.py +++ b/tests/failing_tests/conditionals/helper_cond.py @@ -1,4 +1,4 @@ -from pythonbpf import bpf, map, section, bpfglobal +from pythonbpf import bpf, map, section, bpfglobal, compile from ctypes import c_void_p, c_int64, c_uint64 from pythonbpf.maps import HashMap @@ -29,3 +29,6 @@ def hello_world(ctx: c_void_p) -> c_int64: @bpfglobal def LICENSE() -> str: return "GPL" + + +compile()