Skip to content

Commit ec4a685

Browse files
committed
PythonBPF: Add Compilation Context to allow parallel compilation of multiple bpf programs
1 parent 45d85c4 commit ec4a685

File tree

14 files changed

+455
-497
lines changed

14 files changed

+455
-497
lines changed

pythonbpf/allocation_pass.py

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ def create_targets_and_rvals(stmt):
2626
return stmt.targets, [stmt.value]
2727

2828

29-
def handle_assign_allocation(
30-
builder, stmt, local_sym_tab, map_sym_tab, structs_sym_tab
31-
):
29+
def handle_assign_allocation(compilation_context, builder, stmt, local_sym_tab):
3230
"""Handle memory allocation for assignment statements."""
3331

3432
logger.info(f"Handling assignment for allocation: {ast.dump(stmt)}")
@@ -59,7 +57,7 @@ def handle_assign_allocation(
5957
# Determine type and allocate based on rval
6058
if isinstance(rval, ast.Call):
6159
_allocate_for_call(
62-
builder, var_name, rval, local_sym_tab, map_sym_tab, structs_sym_tab
60+
builder, var_name, rval, local_sym_tab, compilation_context
6361
)
6462
elif isinstance(rval, ast.Constant):
6563
_allocate_for_constant(builder, var_name, rval, local_sym_tab)
@@ -71,18 +69,17 @@ def handle_assign_allocation(
7169
elif isinstance(rval, ast.Attribute):
7270
# Struct field-to-variable assignment (a = dat.fld)
7371
_allocate_for_attribute(
74-
builder, var_name, rval, local_sym_tab, structs_sym_tab
72+
builder, var_name, rval, local_sym_tab, compilation_context
7573
)
7674
else:
7775
logger.warning(
7876
f"Unsupported assignment value type for {var_name}: {type(rval).__name__}"
7977
)
8078

8179

82-
def _allocate_for_call(
83-
builder, var_name, rval, local_sym_tab, map_sym_tab, structs_sym_tab
84-
):
80+
def _allocate_for_call(builder, var_name, rval, local_sym_tab, compilation_context):
8581
"""Allocate memory for variable assigned from a call."""
82+
structs_sym_tab = compilation_context.structs_sym_tab
8683

8784
if isinstance(rval.func, ast.Name):
8885
call_type = rval.func.id
@@ -149,17 +146,19 @@ def _allocate_for_call(
149146
elif isinstance(rval.func, ast.Attribute):
150147
# Map method calls - need double allocation for ptr handling
151148
_allocate_for_map_method(
152-
builder, var_name, rval, local_sym_tab, map_sym_tab, structs_sym_tab
149+
builder, var_name, rval, local_sym_tab, compilation_context
153150
)
154151

155152
else:
156153
logger.warning(f"Unsupported call function type for {var_name}")
157154

158155

159156
def _allocate_for_map_method(
160-
builder, var_name, rval, local_sym_tab, map_sym_tab, structs_sym_tab
157+
builder, var_name, rval, local_sym_tab, compilation_context
161158
):
162159
"""Allocate memory for variable assigned from map method (double alloc)."""
160+
map_sym_tab = compilation_context.map_sym_tab
161+
structs_sym_tab = compilation_context.structs_sym_tab
163162

164163
map_name = rval.func.value.id
165164
method_name = rval.func.attr
@@ -299,6 +298,15 @@ def allocate_temp_pool(builder, max_temps, local_sym_tab):
299298
logger.debug(f"Allocated temp variable: {temp_name}")
300299

301300

301+
def _get_alignment(tmp_type):
302+
"""Return alignment for a given type."""
303+
if isinstance(tmp_type, ir.PointerType):
304+
return 8
305+
elif isinstance(tmp_type, ir.IntType):
306+
return tmp_type.width // 8
307+
return 8
308+
309+
302310
def _allocate_for_name(builder, var_name, rval, local_sym_tab):
303311
"""Allocate memory for variable-to-variable assignment (b = a)."""
304312
source_var = rval.id
@@ -321,8 +329,22 @@ def _allocate_for_name(builder, var_name, rval, local_sym_tab):
321329
)
322330

323331

324-
def _allocate_for_attribute(builder, var_name, rval, local_sym_tab, structs_sym_tab):
332+
def _allocate_with_type(builder, var_name, ir_type):
333+
"""Allocate memory for a variable with a specific type."""
334+
var = builder.alloca(ir_type, name=var_name)
335+
if isinstance(ir_type, ir.IntType):
336+
var.align = ir_type.width // 8
337+
elif isinstance(ir_type, ir.PointerType):
338+
var.align = 8
339+
return var
340+
341+
342+
def _allocate_for_attribute(
343+
builder, var_name, rval, local_sym_tab, compilation_context
344+
):
325345
"""Allocate memory for struct field-to-variable assignment (a = dat.fld)."""
346+
structs_sym_tab = compilation_context.structs_sym_tab
347+
326348
if not isinstance(rval.value, ast.Name):
327349
logger.warning(f"Complex attribute access not supported for {var_name}")
328350
return
@@ -455,20 +477,3 @@ def _allocate_for_attribute(builder, var_name, rval, local_sym_tab, structs_sym_
455477
logger.info(
456478
f"Pre-allocated {var_name} from {struct_var}.{field_name} with type {alloc_type}"
457479
)
458-
459-
460-
def _allocate_with_type(builder, var_name, ir_type):
461-
"""Allocate variable with appropriate alignment for type."""
462-
var = builder.alloca(ir_type, name=var_name)
463-
var.align = _get_alignment(ir_type)
464-
return var
465-
466-
467-
def _get_alignment(ir_type):
468-
"""Get appropriate alignment for IR type."""
469-
if isinstance(ir_type, ir.IntType):
470-
return ir_type.width // 8
471-
elif isinstance(ir_type, ir.ArrayType) and isinstance(ir_type.element, ir.IntType):
472-
return ir_type.element.width // 8
473-
else:
474-
return 8 # Default: pointer size

pythonbpf/assign_pass.py

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313

1414
def handle_struct_field_assignment(
15-
func, module, builder, target, rval, local_sym_tab, map_sym_tab, structs_sym_tab
15+
func, compilation_context, builder, target, rval, local_sym_tab
1616
):
1717
"""Handle struct field assignment (obj.field = value)."""
1818

@@ -24,7 +24,7 @@ def handle_struct_field_assignment(
2424
return
2525

2626
struct_type = local_sym_tab[var_name].metadata
27-
struct_info = structs_sym_tab[struct_type]
27+
struct_info = compilation_context.structs_sym_tab[struct_type]
2828

2929
if field_name not in struct_info.fields:
3030
logger.error(f"Field '{field_name}' not found in struct '{struct_type}'")
@@ -33,9 +33,7 @@ def handle_struct_field_assignment(
3333
# Get field pointer and evaluate value
3434
field_ptr = struct_info.gep(builder, local_sym_tab[var_name].var, field_name)
3535
field_type = struct_info.field_type(field_name)
36-
val_result = eval_expr(
37-
func, module, builder, rval, local_sym_tab, map_sym_tab, structs_sym_tab
38-
)
36+
val_result = eval_expr(func, compilation_context, builder, rval, local_sym_tab)
3937

4038
if val_result is None:
4139
logger.error(f"Failed to evaluate value for {var_name}.{field_name}")
@@ -47,14 +45,12 @@ def handle_struct_field_assignment(
4745
if _is_char_array(field_type) and _is_i8_ptr(val_type):
4846
_copy_string_to_char_array(
4947
func,
50-
module,
48+
compilation_context,
5149
builder,
5250
val,
5351
field_ptr,
5452
field_type,
5553
local_sym_tab,
56-
map_sym_tab,
57-
structs_sym_tab,
5854
)
5955
logger.info(f"Copied string to char array {var_name}.{field_name}")
6056
return
@@ -66,14 +62,12 @@ def handle_struct_field_assignment(
6662

6763
def _copy_string_to_char_array(
6864
func,
69-
module,
65+
compilation_context,
7066
builder,
7167
src_ptr,
7268
dst_ptr,
7369
array_type,
7470
local_sym_tab,
75-
map_sym_tab,
76-
struct_sym_tab,
7771
):
7872
"""Copy string (i8*) to char array ([N x i8]) using bpf_probe_read_kernel_str"""
7973

@@ -109,7 +103,7 @@ def _is_i8_ptr(ir_type):
109103

110104

111105
def handle_variable_assignment(
112-
func, module, builder, var_name, rval, local_sym_tab, map_sym_tab, structs_sym_tab
106+
func, compilation_context, builder, var_name, rval, local_sym_tab
113107
):
114108
"""Handle single named variable assignment."""
115109

@@ -120,6 +114,8 @@ def handle_variable_assignment(
120114
var_ptr = local_sym_tab[var_name].var
121115
var_type = local_sym_tab[var_name].ir_type
122116

117+
structs_sym_tab = compilation_context.structs_sym_tab
118+
123119
# NOTE: Special case for struct initialization
124120
if isinstance(rval, ast.Call) and isinstance(rval.func, ast.Name):
125121
struct_name = rval.func.id
@@ -142,9 +138,7 @@ def handle_variable_assignment(
142138
logger.info(f"Assigned char array pointer to {var_name}")
143139
return True
144140

145-
val_result = eval_expr(
146-
func, module, builder, rval, local_sym_tab, map_sym_tab, structs_sym_tab
147-
)
141+
val_result = eval_expr(func, compilation_context, builder, rval, local_sym_tab)
148142
if val_result is None:
149143
logger.error(f"Failed to evaluate value for {var_name}")
150144
return False

pythonbpf/codegen.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ast
22
from llvmlite import ir
3+
from .context import CompilationContext
34
from .license_pass import license_processing
45
from .functions import func_proc
56
from .maps import maps_proc
@@ -67,9 +68,10 @@ def find_bpf_chunks(tree):
6768
return bpf_functions
6869

6970

70-
def processor(source_code, filename, module):
71+
def processor(source_code, filename, compilation_context):
7172
tree = ast.parse(source_code, filename)
7273
logger.debug(ast.dump(tree, indent=4))
74+
module = compilation_context.module
7375

7476
bpf_chunks = find_bpf_chunks(tree)
7577
for func_node in bpf_chunks:
@@ -81,15 +83,18 @@ def processor(source_code, filename, module):
8183
if vmlinux_symtab:
8284
handler = VmlinuxHandler.initialize(vmlinux_symtab)
8385
VmlinuxHandlerRegistry.set_handler(handler)
86+
compilation_context.vmlinux_handler = handler
8487

85-
populate_global_symbol_table(tree, module)
86-
license_processing(tree, module)
87-
globals_processing(tree, module)
88-
structs_sym_tab = structs_proc(tree, module, bpf_chunks)
89-
map_sym_tab = maps_proc(tree, module, bpf_chunks, structs_sym_tab)
90-
func_proc(tree, module, bpf_chunks, map_sym_tab, structs_sym_tab)
88+
populate_global_symbol_table(tree, compilation_context)
89+
license_processing(tree, compilation_context)
90+
globals_processing(tree, compilation_context)
91+
structs_sym_tab = structs_proc(tree, compilation_context, bpf_chunks)
9192

92-
globals_list_creation(tree, module)
93+
map_sym_tab = maps_proc(tree, compilation_context, bpf_chunks)
94+
95+
func_proc(tree, compilation_context, bpf_chunks)
96+
97+
globals_list_creation(tree, compilation_context)
9398
return structs_sym_tab, map_sym_tab
9499

95100

@@ -104,6 +109,8 @@ def compile_to_ir(filename: str, output: str, loglevel=logging.INFO):
104109
module.data_layout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
105110
module.triple = "bpf"
106111

112+
compilation_context = CompilationContext(module)
113+
107114
if not hasattr(module, "_debug_compile_unit"):
108115
debug_generator = DebugInfoGenerator(module)
109116
debug_generator.generate_file_metadata(filename, os.path.dirname(filename))
@@ -116,7 +123,7 @@ def compile_to_ir(filename: str, output: str, loglevel=logging.INFO):
116123
True,
117124
)
118125

119-
structs_sym_tab, maps_sym_tab = processor(source, filename, module)
126+
structs_sym_tab, maps_sym_tab = processor(source, filename, compilation_context)
120127

121128
wchar_size = module.add_metadata(
122129
[

pythonbpf/context.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from llvmlite import ir
2+
import logging
3+
from typing import TYPE_CHECKING
4+
5+
if TYPE_CHECKING:
6+
from pythonbpf.structs.struct_type import StructType
7+
from pythonbpf.maps.maps_utils import MapSymbol
8+
9+
logger = logging.getLogger(__name__)
10+
11+
12+
class ScratchPoolManager:
13+
"""Manage the temporary helper variables in local_sym_tab"""
14+
15+
def __init__(self):
16+
self._counters = {}
17+
18+
@property
19+
def counter(self):
20+
return sum(self._counters.values())
21+
22+
def reset(self):
23+
self._counters.clear()
24+
logger.debug("Scratch pool counter reset to 0")
25+
26+
def _get_type_name(self, ir_type):
27+
if isinstance(ir_type, ir.PointerType):
28+
return "ptr"
29+
elif isinstance(ir_type, ir.IntType):
30+
return f"i{ir_type.width}"
31+
elif isinstance(ir_type, ir.ArrayType):
32+
return f"[{ir_type.count}x{self._get_type_name(ir_type.element)}]"
33+
else:
34+
return str(ir_type).replace(" ", "")
35+
36+
def get_next_temp(self, local_sym_tab, expected_type=None):
37+
# Default to i64 if no expected type provided
38+
type_name = self._get_type_name(expected_type) if expected_type else "i64"
39+
if type_name not in self._counters:
40+
self._counters[type_name] = 0
41+
42+
counter = self._counters[type_name]
43+
temp_name = f"__helper_temp_{type_name}_{counter}"
44+
self._counters[type_name] += 1
45+
46+
if temp_name not in local_sym_tab:
47+
raise ValueError(
48+
f"Scratch pool exhausted or inadequate: {temp_name}. "
49+
f"Type: {type_name} Counter: {counter}"
50+
)
51+
52+
logger.debug(f"Using {temp_name} for type {type_name}")
53+
return local_sym_tab[temp_name].var, temp_name
54+
55+
56+
class CompilationContext:
57+
"""
58+
Holds the state for a single compilation run.
59+
This replaces global mutable state modules.
60+
"""
61+
62+
def __init__(self, module: ir.Module):
63+
self.module = module
64+
65+
# Symbol tables
66+
self.global_sym_tab: list[ir.GlobalVariable] = []
67+
self.structs_sym_tab: dict[str, "StructType"] = {}
68+
self.map_sym_tab: dict[str, "MapSymbol"] = {}
69+
70+
# Helper management
71+
self.scratch_pool = ScratchPoolManager()
72+
73+
# Vmlinux handling (optional, specialized)
74+
self.vmlinux_handler = None # Can be VmlinuxHandler instance
75+
76+
# Current function context (optional, if needed globally during function processing)
77+
self.current_func = None
78+
79+
def reset(self):
80+
"""Reset state between functions if necessary, though new context per compile is preferred."""
81+
self.scratch_pool.reset()
82+
self.current_func = None

pythonbpf/expr/call_registry.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,8 @@ def set_handler(cls, handler):
99
cls._handler = handler
1010

1111
@classmethod
12-
def handle_call(
13-
cls, call, module, builder, func, local_sym_tab, map_sym_tab, structs_sym_tab
14-
):
12+
def handle_call(cls, call, compilation_context, builder, func, local_sym_tab):
1513
"""Handle a call using the registered handler"""
1614
if cls._handler is None:
1715
return None
18-
return cls._handler(
19-
call, module, builder, func, local_sym_tab, map_sym_tab, structs_sym_tab
20-
)
16+
return cls._handler(call, compilation_context, builder, func, local_sym_tab)

0 commit comments

Comments
 (0)