Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ba39703
add failing examples to work on
varun-r-mallya Oct 21, 2025
4179fbf
move around examples
varun-r-mallya Oct 21, 2025
d872934
add bpf_passthrough generation
varun-r-mallya Oct 21, 2025
21cea97
add return None statements
varun-r-mallya Oct 21, 2025
adf3256
bpf passthrough gen in codegen
varun-r-mallya Oct 21, 2025
b3921c4
parse context from first function argument to local symbol table
varun-r-mallya Oct 22, 2025
f2bc7f1
pass context to memory allocation
varun-r-mallya Oct 22, 2025
36a1a09
Merge branch 'master' into vmlinux-handler
varun-r-mallya Oct 22, 2025
64674cf
add alloc for only i64
varun-r-mallya Oct 23, 2025
4e01df7
complete part of expr passing for attribute of i64 type
varun-r-mallya Oct 23, 2025
f18a439
format chore
varun-r-mallya Oct 23, 2025
30bcfcb
remove compile error on normal c_void_p in arg and separate localsymb…
varun-r-mallya Oct 23, 2025
c6b5ecb
find global variable ir and field data from metadata
varun-r-mallya Oct 23, 2025
028d9c2
generate IR partly
varun-r-mallya Oct 24, 2025
96216d4
Consistently use Dataclass syntac for AssignmentInfo and related classes
r41k0u Oct 24, 2025
1ea44dd
Use pointer arithmetic to resolve vmlinux struct fields
r41k0u Oct 25, 2025
93285db
geenrate gep IR
varun-r-mallya Oct 25, 2025
a1fe2ed
change to 64 bit pointers. May be an issue. revert this commit if iss…
varun-r-mallya Oct 26, 2025
7bf6f9c
add function_debug_info.py and format
varun-r-mallya Oct 26, 2025
8bd210c
add debug info storage on assignment_info.py dataclass
varun-r-mallya Oct 26, 2025
2386974
create debug info to subroutine type
varun-r-mallya Oct 26, 2025
73f7c80
add scope field separately to subroutine type remove circular dependency
varun-r-mallya Oct 26, 2025
3bf85e7
add DI subprogram to make CO-RE work fully.
varun-r-mallya Oct 26, 2025
ac74b03
Add TODO to specify flags and DISubprogram.
varun-r-mallya Oct 26, 2025
07580da
revert struct reference pointer sizes to i8 to ensure that compiler d…
varun-r-mallya Oct 26, 2025
c3fc790
remove fixed TODOs
varun-r-mallya Nov 1, 2025
85a62d6
add example and support unsigned i64
varun-r-mallya Nov 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 36 additions & 26 deletions pythonbpf/allocation_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,15 @@
import logging

from llvmlite import ir
from dataclasses import dataclass
from typing import Any
from .local_symbol import LocalSymbol
from pythonbpf.helper import HelperHandlerRegistry
from pythonbpf.vmlinux_parser.dependency_node import Field
from .expr import VmlinuxHandlerRegistry
from pythonbpf.type_deducer import ctypes_to_ir

logger = logging.getLogger(__name__)


@dataclass
class LocalSymbol:
var: ir.AllocaInstr
ir_type: ir.Type
metadata: Any = None

def __iter__(self):
yield self.var
yield self.ir_type
yield self.metadata


def create_targets_and_rvals(stmt):
"""Create lists of targets and right-hand values from an assignment statement."""
if isinstance(stmt.targets[0], ast.Tuple):
Expand Down Expand Up @@ -60,21 +48,11 @@ def handle_assign_allocation(builder, stmt, local_sym_tab, structs_sym_tab):
continue

var_name = target.id

# Skip if already allocated
if var_name in local_sym_tab:
logger.debug(f"Variable {var_name} already allocated, skipping")
continue

# When allocating a variable, check if it's a vmlinux struct type
if isinstance(
stmt.value, ast.Name
) and VmlinuxHandlerRegistry.is_vmlinux_struct(stmt.value.id):
# Handle vmlinux struct allocation
# This requires more implementation
print(stmt.value)
pass

# Determine type and allocate based on rval
if isinstance(rval, ast.Call):
_allocate_for_call(builder, var_name, rval, local_sym_tab, structs_sym_tab)
Expand Down Expand Up @@ -248,9 +226,41 @@ def _allocate_for_attribute(builder, var_name, rval, local_sym_tab, structs_sym_
logger.error(f"Struct variable '{struct_var}' not found")
return

struct_type = local_sym_tab[struct_var].metadata
struct_type: type = local_sym_tab[struct_var].metadata
if not struct_type or struct_type not in structs_sym_tab:
logger.error(f"Struct type '{struct_type}' not found")
if VmlinuxHandlerRegistry.is_vmlinux_struct(struct_type.__name__):
# Handle vmlinux struct field access
vmlinux_struct_name = struct_type.__name__
if not VmlinuxHandlerRegistry.has_field(vmlinux_struct_name, field_name):
logger.error(
f"Field '{field_name}' not found in vmlinux struct '{vmlinux_struct_name}'"
)
return

field_type: tuple[ir.GlobalVariable, Field] = (
VmlinuxHandlerRegistry.get_field_type(vmlinux_struct_name, field_name)
)
field_ir, field = field_type
# TODO: For now, we only support integer type allocations.
# This always assumes first argument of function to be the context struct
base_ptr = builder.function.args[0]
local_sym_tab[
struct_var
].var = base_ptr # This is repurposing of var to store the pointer of the base type
local_sym_tab[struct_var].ir_type = field_ir

actual_ir_type = ir.IntType(64)

# Allocate with the actual IR type, not the GlobalVariable
var = _allocate_with_type(builder, var_name, actual_ir_type)
local_sym_tab[var_name] = LocalSymbol(var, actual_ir_type, field)

logger.info(
f"Pre-allocated {var_name} from vmlinux struct {vmlinux_struct_name}.{field_name}"
)
return
else:
logger.error(f"Struct type '{struct_type}' not found")
return

struct_info = structs_sym_tab[struct_type]
Expand Down
15 changes: 14 additions & 1 deletion pythonbpf/assign_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from llvmlite import ir
from pythonbpf.expr import eval_expr
from pythonbpf.helper import emit_probe_read_kernel_str_call
from pythonbpf.type_deducer import ctypes_to_ir
from pythonbpf.vmlinux_parser.dependency_node import Field

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -148,7 +150,18 @@ def handle_variable_assignment(
val, val_type = val_result
logger.info(f"Evaluated value for {var_name}: {val} of type {val_type}, {var_type}")
if val_type != var_type:
if isinstance(val_type, ir.IntType) and isinstance(var_type, ir.IntType):
if isinstance(val_type, Field):
logger.info("Handling assignment to struct field")
# TODO: handling only ctype struct fields for now. Handle other stuff too later.
if var_type == ctypes_to_ir(val_type.type.__name__):
builder.store(val, var_ptr)
logger.info(f"Assigned ctype struct field to {var_name}")
return True
logger.error(
f"Failed to assign ctype struct field to {var_name}: {val_type} != {var_type}"
)
return False
elif isinstance(val_type, ir.IntType) and isinstance(var_type, ir.IntType):
# Allow implicit int widening
if val_type.width < var_type.width:
val = builder.sext(val, var_type)
Expand Down
22 changes: 21 additions & 1 deletion pythonbpf/codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,24 @@ def finalize_module(original_str):
return re.sub(pattern, replacement, original_str)


def bpf_passthrough_gen(module):
i32_ty = ir.IntType(32)
ptr_ty = ir.PointerType(ir.IntType(8))
fnty = ir.FunctionType(ptr_ty, [i32_ty, ptr_ty])

# Declare the intrinsic
passthrough = ir.Function(module, fnty, "llvm.bpf.passthrough.p0.p0")

# Set function attributes
# TODO: the ones commented are supposed to be there but cannot be added due to llvmlite limitations at the moment
# passthrough.attributes.add("nofree")
# passthrough.attributes.add("nosync")
passthrough.attributes.add("nounwind")
# passthrough.attributes.add("memory(none)")

return passthrough


def find_bpf_chunks(tree):
"""Find all functions decorated with @bpf in the AST."""
bpf_functions = []
Expand All @@ -57,6 +75,8 @@ def processor(source_code, filename, module):
for func_node in bpf_chunks:
logger.info(f"Found BPF function/struct: {func_node.name}")

bpf_passthrough_gen(module)

vmlinux_symtab = vmlinux_proc(tree, module)
if vmlinux_symtab:
handler = VmlinuxHandler.initialize(vmlinux_symtab)
Expand Down Expand Up @@ -137,7 +157,7 @@ def compile_to_ir(filename: str, output: str, loglevel=logging.INFO):

module.add_named_metadata("llvm.ident", [f"PythonBPF {VERSION}"])

module_string = finalize_module(str(module))
module_string: str = finalize_module(str(module))

logger.info(f"IR written to {output}")
with open(output, "w") as f:
Expand Down
80 changes: 80 additions & 0 deletions pythonbpf/debuginfo/debug_info_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,83 @@ def create_global_var_debug_info(
"DIGlobalVariableExpression",
{"var": global_var, "expr": self.module.add_debug_info("DIExpression", {})},
)

def get_int64_type(self):
return self.get_basic_type("long", 64, dc.DW_ATE_signed)

def create_subroutine_type(self, return_type, param_types):
"""
Create a DISubroutineType given return type and list of parameter types.
Equivalent to: !DISubroutineType(types: !{ret, args...})
"""
type_array = [return_type]
if isinstance(param_types, (list, tuple)):
type_array.extend(param_types)
else:
type_array.append(param_types)
return self.module.add_debug_info("DISubroutineType", {"types": type_array})

def create_local_variable_debug_info(
self, name: str, arg: int, var_type: Any
) -> Any:
"""
Create debug info for a local variable (DILocalVariable) without scope.
Example:
!DILocalVariable(name: "ctx", arg: 1, file: !3, line: 20, type: !7)
"""
return self.module.add_debug_info(
"DILocalVariable",
{
"name": name,
"arg": arg,
"file": self.module._file_metadata,
"type": var_type,
},
)

def add_scope_to_local_variable(self, local_variable_debug_info, scope_value):
"""
Add scope information to an existing local variable debug info object.
"""
# TODO: this is a workaround a flaw in the debug info generation. Fix this if possible in the future.
# We should not be touching llvmlite's internals like this.
if hasattr(local_variable_debug_info, "operands"):
# LLVM metadata operands is a tuple, so we need to rebuild it
existing_operands = local_variable_debug_info.operands

# Convert tuple to list, add scope, convert back to tuple
operands_list = list(existing_operands)
operands_list.append(("scope", scope_value))

# Reassign the new tuple
local_variable_debug_info.operands = tuple(operands_list)

def create_subprogram(
self, name: str, subroutine_type: Any, retained_nodes: List[Any]
) -> Any:
"""
Create a DISubprogram for a function.

Args:
name: Function name
subroutine_type: DISubroutineType for the function signature
retained_nodes: List of DILocalVariable nodes for function parameters/variables

Returns:
DISubprogram metadata
"""
return self.module.add_debug_info(
"DISubprogram",
{
"name": name,
"scope": self.module._file_metadata,
"file": self.module._file_metadata,
"type": subroutine_type,
# TODO: the following flags do not exist at the moment in our dwarf constants file. We need to add them.
# "flags": dc.DW_FLAG_Prototyped | dc.DW_FLAG_AllCallsDescribed,
# "spFlags": dc.DW_SPFLAG_Definition | dc.DW_SPFLAG_Optimized,
"unit": self.module._debug_compile_unit,
"retainedNodes": retained_nodes,
},
is_distinct=True,
)
24 changes: 16 additions & 8 deletions pythonbpf/expr/expr_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,20 +72,28 @@ def _handle_attribute_expr(
if var_name in local_sym_tab:
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}")
logger.info(
f"Variable type: {var_type}, Variable ptr: {var_ptr}, Variable Metadata: {var_metadata}"
)
if (
hasattr(var_metadata, "__module__")
and var_metadata.__module__ == "vmlinux"
):
# Try vmlinux handler when var_metadata is not a string, but has a module attribute.
# This has been done to keep everything separate in vmlinux struct handling.
vmlinux_result = VmlinuxHandlerRegistry.handle_attribute(
expr, local_sym_tab, None, builder
)
if vmlinux_result is not None:
return vmlinux_result
else:
raise RuntimeError("Vmlinux struct did not process successfully")
metadata = structs_sym_tab[var_metadata]
if attr_name in metadata.fields:
gep = metadata.gep(builder, var_ptr, attr_name)
val = builder.load(gep)
field_type = metadata.field_type(attr_name)
return val, field_type

# Try vmlinux handler as fallback
vmlinux_result = VmlinuxHandlerRegistry.handle_attribute(
expr, local_sym_tab, None, builder
)
if vmlinux_result is not None:
return vmlinux_result
return None


Expand Down
32 changes: 31 additions & 1 deletion pythonbpf/expr/vmlinux_registry.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import ast

from pythonbpf.vmlinux_parser.vmlinux_exports_handler import VmlinuxHandler


class VmlinuxHandlerRegistry:
"""Registry for vmlinux handler operations"""

_handler = None

@classmethod
def set_handler(cls, handler):
def set_handler(cls, handler: VmlinuxHandler):
"""Set the vmlinux handler"""
cls._handler = handler

Expand Down Expand Up @@ -37,9 +39,37 @@ def handle_attribute(cls, expr, local_sym_tab, module, builder):
)
return None

@classmethod
def get_struct_debug_info(cls, name):
if cls._handler is None:
return False
return cls._handler.get_struct_debug_info(name)

@classmethod
def is_vmlinux_struct(cls, name):
"""Check if a name refers to a vmlinux struct"""
if cls._handler is None:
return False
return cls._handler.is_vmlinux_struct(name)

@classmethod
def get_struct_type(cls, name):
"""Try to handle a struct name as vmlinux struct"""
if cls._handler is None:
return None
return cls._handler.get_vmlinux_struct_type(name)

@classmethod
def has_field(cls, vmlinux_struct_name, field_name):
"""Check if a vmlinux struct has a specific field"""
if cls._handler is None:
return False
return cls._handler.has_field(vmlinux_struct_name, field_name)

@classmethod
def get_field_type(cls, vmlinux_struct_name, field_name):
"""Get the type of a field in a vmlinux struct"""
if cls._handler is None:
return None
assert isinstance(cls._handler, VmlinuxHandler)
return cls._handler.get_field_type(vmlinux_struct_name, field_name)
Loading