Skip to content

Commit 1adf7d7

Browse files
authored
Merge pull request #5 from pythonbpf/struct_refactor
Struct refactor
2 parents ea5a1ab + 3ded17b commit 1adf7d7

7 files changed

Lines changed: 161 additions & 102 deletions

File tree

pythonbpf/bpf_helper_handler.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ def bpf_printk_emitter(call, map_ptr, module, builder, func, local_sym_tab=None,
113113
var_type = local_var_metadata[var_name]
114114
if var_type in struct_sym_tab:
115115
struct_info = struct_sym_tab[var_type]
116-
if field_name in struct_info["fields"]:
117-
field_index = struct_info["fields"][field_name]
118-
field_type = struct_info["field_types"][field_index]
116+
if field_name in struct_info.fields:
117+
field_type = struct_info.field_type(
118+
field_name)
119119
if isinstance(field_type, ir.IntType):
120120
fmt_parts.append("%lld")
121121
exprs.append(value.value)
@@ -408,7 +408,7 @@ def bpf_perf_event_output_handler(call, map_ptr, module, builder, func, local_sy
408408
data_type = local_var_metadata[data_name]
409409
if data_type in struct_sym_tab:
410410
struct_info = struct_sym_tab[data_type]
411-
size_val = ir.Constant(ir.IntType(64), struct_info["size"])
411+
size_val = ir.Constant(ir.IntType(64), struct_info.size)
412412
else:
413413
raise ValueError(
414414
f"Struct type {data_type} for variable {data_name} not found in struct symbol table.")

pythonbpf/codegen.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from .license_pass import license_processing
44
from .functions_pass import func_proc
55
from .maps_pass import maps_proc
6-
from .structs_pass import structs_proc
6+
from .structs.structs_pass import structs_proc
77
from .globals_pass import globals_processing
88
import os
99
import subprocess

pythonbpf/expr_pass.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,10 @@ def eval_expr(func, module, builder, expr, local_sym_tab, map_sym_tab, structs_s
7979
print(local_var_metadata)
8080
if local_var_metadata and var_name in local_var_metadata:
8181
metadata = structs_sym_tab[local_var_metadata[var_name]]
82-
if attr_name in metadata["fields"]:
83-
field_idx = metadata["fields"][attr_name]
84-
gep = builder.gep(var_ptr, [ir.Constant(ir.IntType(32), 0),
85-
ir.Constant(ir.IntType(32), field_idx)])
82+
if attr_name in metadata.fields:
83+
gep = metadata.gep(builder, var_ptr, attr_name)
8684
val = builder.load(gep)
87-
field_type = metadata["field_types"][field_idx]
85+
field_type = metadata.field_type(attr_name)
8886
return val, field_type
8987
print("Unsupported expression evaluation")
9088
return None

pythonbpf/functions_pass.py

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,17 @@ def handle_assign(func, module, builder, stmt, map_sym_tab, local_sym_tab, struc
4949
struct_type = local_var_metadata[var_name]
5050
struct_info = structs_sym_tab[struct_type]
5151

52-
if field_name in struct_info["fields"]:
53-
field_idx = struct_info["fields"][field_name]
54-
struct_ptr = local_sym_tab[var_name][0]
55-
field_ptr = builder.gep(
56-
struct_ptr, [ir.Constant(ir.IntType(32), 0),
57-
ir.Constant(ir.IntType(32), field_idx)],
58-
inbounds=True)
52+
if field_name in struct_info.fields:
53+
field_ptr = struct_info.gep(
54+
builder, local_sym_tab[var_name][0], field_name)
5955
val = eval_expr(func, module, builder, rval,
6056
local_sym_tab, map_sym_tab, structs_sym_tab)
61-
if isinstance(struct_info["field_types"][field_idx], ir.ArrayType) and val[1] == ir.PointerType(ir.IntType(8)):
57+
if isinstance(struct_info.field_type(field_name), ir.ArrayType) and val[1] == ir.PointerType(ir.IntType(8)):
6258
# TODO: Figure it out, not a priority rn
6359
# Special case for string assignment to char array
64-
#str_len = struct_info["field_types"][field_idx].count
65-
#assign_string_to_array(builder, field_ptr, val[0], str_len)
66-
#print(f"Assigned to struct field {var_name}.{field_name}")
60+
# str_len = struct_info["field_types"][field_idx].count
61+
# assign_string_to_array(builder, field_ptr, val[0], str_len)
62+
# print(f"Assigned to struct field {var_name}.{field_name}")
6763
pass
6864
if val is None:
6965
print("Failed to evaluate struct field assignment")
@@ -138,7 +134,7 @@ def handle_assign(func, module, builder, stmt, map_sym_tab, local_sym_tab, struc
138134
print(f"Dereferenced and assigned to {var_name}")
139135
elif call_type in structs_sym_tab and len(rval.args) == 0:
140136
struct_info = structs_sym_tab[call_type]
141-
ir_type = struct_info["type"]
137+
ir_type = struct_info.ir_type
142138
# var = builder.alloca(ir_type, name=var_name)
143139
# Null init
144140
builder.store(ir.Constant(ir_type, None),
@@ -364,7 +360,7 @@ def allocate_mem(module, builder, body, func, ret_type, map_sym_tab, local_sym_t
364360
f"Pre-allocated variable {var_name} for deref")
365361
elif call_type in structs_sym_tab:
366362
struct_info = structs_sym_tab[call_type]
367-
ir_type = struct_info["type"]
363+
ir_type = struct_info.ir_type
368364
var = builder.alloca(ir_type, name=var_name)
369365
local_var_metadata[var_name] = call_type
370366
print(
@@ -548,6 +544,8 @@ def _expr_type(e):
548544
return found_type or "None"
549545

550546
# For string assignment to fixed-size arrays
547+
548+
551549
def assign_string_to_array(builder, target_array_ptr, source_string_ptr, array_length):
552550
"""
553551
Copy a string (i8*) to a fixed-size array ([N x i8]*)
@@ -556,36 +554,39 @@ def assign_string_to_array(builder, target_array_ptr, source_string_ptr, array_l
556554
entry_block = builder.block
557555
copy_block = builder.append_basic_block("copy_char")
558556
end_block = builder.append_basic_block("copy_end")
559-
557+
560558
# Create loop counter
561559
i = builder.alloca(ir.IntType(32))
562560
builder.store(ir.Constant(ir.IntType(32), 0), i)
563-
561+
564562
# Start the loop
565563
builder.branch(copy_block)
566-
564+
567565
# Copy loop
568566
builder.position_at_end(copy_block)
569567
idx = builder.load(i)
570-
in_bounds = builder.icmp_unsigned('<', idx, ir.Constant(ir.IntType(32), array_length))
568+
in_bounds = builder.icmp_unsigned(
569+
'<', idx, ir.Constant(ir.IntType(32), array_length))
571570
builder.cbranch(in_bounds, copy_block, end_block)
572-
571+
573572
with builder.if_then(in_bounds):
574573
# Load character from source
575574
src_ptr = builder.gep(source_string_ptr, [idx])
576575
char = builder.load(src_ptr)
577-
576+
578577
# Store character in target
579-
dst_ptr = builder.gep(target_array_ptr, [ir.Constant(ir.IntType(32), 0), idx])
578+
dst_ptr = builder.gep(
579+
target_array_ptr, [ir.Constant(ir.IntType(32), 0), idx])
580580
builder.store(char, dst_ptr)
581-
581+
582582
# Increment counter
583583
next_idx = builder.add(idx, ir.Constant(ir.IntType(32), 1))
584584
builder.store(next_idx, i)
585-
585+
586586
builder.position_at_end(end_block)
587-
587+
588588
# Ensure null termination
589589
last_idx = ir.Constant(ir.IntType(32), array_length - 1)
590-
null_ptr = builder.gep(target_array_ptr, [ir.Constant(ir.IntType(32), 0), last_idx])
590+
null_ptr = builder.gep(
591+
target_array_ptr, [ir.Constant(ir.IntType(32), 0), last_idx])
591592
builder.store(ir.Constant(ir.IntType(8), 0), null_ptr)

pythonbpf/structs/struct_type.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from llvmlite import ir
2+
3+
4+
class StructType:
5+
def __init__(self, ir_type, fields, size):
6+
self.ir_type = ir_type
7+
self.fields = fields
8+
self.size = size
9+
10+
def field_idx(self, field_name):
11+
return list(self.fields.keys()).index(field_name)
12+
13+
def field_type(self, field_name):
14+
return self.fields[field_name]
15+
16+
def gep(self, builder, ptr, field_name):
17+
idx = self.field_idx(field_name)
18+
return builder.gep(ptr, [ir.Constant(ir.IntType(32), 0),
19+
ir.Constant(ir.IntType(32), idx)],
20+
inbounds=True)
21+
22+
def field_size(self, field_name):
23+
fld = self.fields[field_name]
24+
if isinstance(fld, ir.ArrayType):
25+
return fld.count * (fld.element.width // 8)
26+
elif isinstance(fld, ir.IntType):
27+
return fld.width // 8
28+
elif isinstance(fld, ir.PointerType):
29+
return 8
30+
31+
raise TypeError(f"Unsupported field type: {fld}")

pythonbpf/structs/structs_pass.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import ast
2+
import logging
3+
from llvmlite import ir
4+
from pythonbpf.type_deducer import ctypes_to_ir
5+
from .struct_type import StructType
6+
7+
logger = logging.getLogger(__name__)
8+
9+
# TODO: Shall we allow the following syntax:
10+
# struct MyStruct:
11+
# field1: int
12+
# field2: str(32)
13+
# Where int is mapped to c_uint64?
14+
# Shall we just int64, int32 and uint32 similarly?
15+
16+
17+
def structs_proc(tree, module, chunks):
18+
""" Process all class definitions to find BPF structs """
19+
structs_sym_tab = {}
20+
for cls_node in chunks:
21+
if is_bpf_struct(cls_node):
22+
print(f"Found BPF struct: {cls_node.name}")
23+
struct_info = process_bpf_struct(cls_node, module)
24+
structs_sym_tab[cls_node.name] = struct_info
25+
return structs_sym_tab
26+
27+
28+
def is_bpf_struct(cls_node):
29+
return any(
30+
isinstance(decorator, ast.Name) and decorator.id == "struct"
31+
for decorator in cls_node.decorator_list
32+
)
33+
34+
35+
def process_bpf_struct(cls_node, module):
36+
""" Process a single BPF struct definition """
37+
38+
fields = parse_struct_fields(cls_node)
39+
field_types = list(fields.values())
40+
total_size = calc_struct_size(field_types)
41+
struct_type = ir.LiteralStructType(field_types)
42+
logger.info(f"Created struct {cls_node.name} with fields {fields.keys()}")
43+
return StructType(struct_type, fields, total_size)
44+
45+
46+
def parse_struct_fields(cls_node):
47+
""" Parse fields of a struct class node """
48+
fields = {}
49+
50+
for item in cls_node.body:
51+
if isinstance(item, ast.AnnAssign) and \
52+
isinstance(item.target, ast.Name):
53+
fields[item.target.id] = get_type_from_ann(item.annotation)
54+
else:
55+
logger.error(f"Unsupported struct field: {ast.dump(item)}")
56+
raise TypeError(f"Unsupported field in {ast.dump(cls_node)}")
57+
return fields
58+
59+
60+
def get_type_from_ann(annotation):
61+
""" Convert an AST annotation node to an LLVM IR type for struct fields"""
62+
if isinstance(annotation, ast.Call) and \
63+
isinstance(annotation.func, ast.Name):
64+
if annotation.func.id == "str":
65+
# Char array
66+
# Assumes constant integer argument
67+
length = annotation.args[0].value
68+
return ir.ArrayType(ir.IntType(8), length)
69+
elif isinstance(annotation, ast.Name):
70+
# Int type, written as c_int64, c_uint32, etc.
71+
return ctypes_to_ir(annotation.id)
72+
73+
raise TypeError(f"Unsupported annotation type: {ast.dump(annotation)}")
74+
75+
76+
def calc_struct_size(field_types):
77+
""" Calculate total size of the struct with alignment and padding """
78+
curr_offset = 0
79+
for ftype in field_types:
80+
if isinstance(ftype, ir.IntType):
81+
fsize = ftype.width // 8
82+
alignment = fsize
83+
elif isinstance(ftype, ir.ArrayType):
84+
fsize = ftype.count * (ftype.element.width // 8)
85+
alignment = ftype.element.width // 8
86+
elif isinstance(ftype, ir.PointerType):
87+
# We won't encounter this rn, but for the future
88+
fsize = 8
89+
alignment = 8
90+
else:
91+
raise TypeError(f"Unsupported field type: {ftype}")
92+
93+
padding = (alignment - (curr_offset % alignment)) % alignment
94+
curr_offset += padding + fsize
95+
96+
final_padding = (8 - (curr_offset % 8)) % 8
97+
return curr_offset + final_padding

pythonbpf/structs_pass.py

Lines changed: 0 additions & 68 deletions
This file was deleted.

0 commit comments

Comments
 (0)