Skip to content

Commit f8db6f9

Browse files
r41k0uclaude
andcommitted
Core: Route every integer widening and narrowing through convert()
The same widen-to-i64 / truncate-to-slot logic was written out by hand in six places: local assignment, struct-field assignment, augmented assignment, helper-argument temporaries, binary-operation operands, and the comparison normaliser. Each now calls type_normalization.convert, which is the single point where the sign decides between zext and sext. Across the 67-program corpus this changes exactly one emitted instruction, and it is a correction rather than a regression: augmented assignment on a c_uint32 struct field now widens the field's current value with zext instead of sext, because that slot's descriptor already carries its sign from ctypes_to_ir. Every other site still sees plain (signed-by-default) descriptors and is byte-identical. The remaining hand-written widenings -- the printk formatter, the vmlinux field loader, return handling -- have their own rules and are migrated together with the sign sources. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BSDVsZH5NtoASyxB8FCtGU
1 parent b16009d commit f8db6f9

6 files changed

Lines changed: 23 additions & 31 deletions

File tree

pythonbpf/assign_pass.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from inspect import isclass
44

55
from llvmlite import ir
6-
from pythonbpf.expr import eval_expr
6+
from pythonbpf.expr import eval_expr, convert
77
from pythonbpf.helper import emit_probe_read_kernel_str_call
88
from pythonbpf.type_deducer import ctypes_to_ir
99
from pythonbpf.vmlinux_parser.dependency_node import Field
@@ -57,10 +57,7 @@ def handle_struct_field_assignment(
5757
# Same implicit widening/truncation as assignment to a local: expressions
5858
# evaluate in i64, but a field may be narrower.
5959
if isinstance(val_type, ir.IntType) and isinstance(field_type, ir.IntType):
60-
if val_type.width < field_type.width:
61-
val = builder.sext(val, field_type)
62-
elif val_type.width > field_type.width:
63-
val = builder.trunc(val, field_type)
60+
val = convert(builder, val, val_type, field_type)
6461

6562
# Regular assignment
6663
builder.store(val, field_ptr)
@@ -220,13 +217,7 @@ def handle_variable_assignment(
220217
)
221218
return False
222219
elif isinstance(val_type, ir.IntType) and isinstance(var_type, ir.IntType):
223-
# Allow implicit int widening
224-
if val_type.width < var_type.width:
225-
val = builder.sext(val, var_type)
226-
logger.info(f"Implicitly widened int for variable {var_name}")
227-
elif val_type.width > var_type.width:
228-
val = builder.trunc(val, var_type)
229-
logger.info(f"Implicitly truncated int for variable {var_name}")
220+
val = convert(builder, val, val_type, var_type)
230221
elif isinstance(val_type, ir.IntType) and isinstance(var_type, ir.PointerType):
231222
# NOTE: This is assignment to a PTR_TO_MAP_VALUE_OR_NULL
232223
logger.info(

pythonbpf/expr/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
from .expr_pass import eval_expr, handle_expr, get_operand_value
2-
from .type_normalization import convert_to_bool, get_base_type_and_depth
2+
from .type_normalization import (
3+
convert_to_bool,
4+
get_base_type_and_depth,
5+
convert,
6+
canonicalise,
7+
)
38
from .ir_ops import deref_to_depth, access_struct_field
49
from .operators import apply_binop
510
from .call_registry import CallHandlerRegistry
@@ -9,6 +14,8 @@
914
"eval_expr",
1015
"handle_expr",
1116
"convert_to_bool",
17+
"convert",
18+
"canonicalise",
1219
"get_base_type_and_depth",
1320
"deref_to_depth",
1421
"apply_binop",

pythonbpf/expr/expr_pass.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .ir_ops import deref_to_depth, access_struct_field
1010
from .operators import apply_binop, UNARY_OPS, BOOL_OPS
1111
from .type_normalization import (
12+
convert,
1213
convert_to_bool,
1314
handle_comparator,
1415
get_base_type_and_depth,
@@ -232,10 +233,8 @@ def _handle_binary_op_impl(func, compilation_context, rval, builder, local_sym_t
232233
# NOTE: Before doing the operation, if the operands are integers
233234
# we always extend them to i64. The assignment to LHS will take
234235
# care of truncation if needed.
235-
if isinstance(left.type, ir.IntType) and left.type.width < 64:
236-
left = builder.sext(left, ir.IntType(64))
237-
if isinstance(right.type, ir.IntType) and right.type.width < 64:
238-
right = builder.sext(right, ir.IntType(64))
236+
left = convert(builder, left, left.type, ir.IntType(64))
237+
right = convert(builder, right, right.type, ir.IntType(64))
239238

240239
# Map AST operation nodes to LLVM IR builder methods
241240
return apply_binop(builder, op, left, right)

pythonbpf/expr/type_normalization.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ def _normalize_types(func, builder, lhs, rhs):
2323
logger.info(f"Normalizing types: {lhs.type} vs {rhs.type}")
2424
if isinstance(lhs.type, ir.IntType) and isinstance(rhs.type, ir.IntType):
2525
if lhs.type.width < rhs.type.width:
26-
lhs = builder.sext(lhs, rhs.type)
26+
lhs = convert(builder, lhs, lhs.type, rhs.type)
2727
else:
28-
rhs = builder.sext(rhs, lhs.type)
28+
rhs = convert(builder, rhs, rhs.type, lhs.type)
2929
return lhs, rhs
3030
elif not isinstance(lhs.type, ir.PointerType) and not isinstance(
3131
rhs.type, ir.PointerType

pythonbpf/functions/functions_pass.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
convert_to_bool,
1414
get_operand_value,
1515
apply_binop,
16+
convert,
1617
VmlinuxHandlerRegistry,
1718
)
1819
from pythonbpf.assign_pass import (
@@ -262,13 +263,10 @@ def handle_aug_assign(func, compilation_context, builder, stmt, local_sym_tab):
262263
)
263264
# Same width discipline as binary-op evaluation: compute in i64, narrow
264265
# back to the slot's width on the way out.
265-
if current.type.width < 64:
266-
current = builder.sext(current, ir.IntType(64))
267-
if isinstance(rhs.type, ir.IntType) and rhs.type.width < 64:
268-
rhs = builder.sext(rhs, ir.IntType(64))
266+
current = convert(builder, current, slot_type, ir.IntType(64))
267+
rhs = convert(builder, rhs, rhs.type, ir.IntType(64))
269268
result = apply_binop(builder, stmt.op, current, rhs)
270-
if result.type.width > slot_type.width:
271-
result = builder.trunc(result, slot_type)
269+
result = convert(builder, result, result.type, slot_type)
272270
builder.store(result, slot)
273271

274272

pythonbpf/helper/helper_utils.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from llvmlite import ir
55
from pythonbpf.expr import (
6+
convert,
67
eval_expr,
78
access_struct_field,
89
)
@@ -134,12 +135,8 @@ def get_or_create_ptr_from_arg(
134135
local_sym_tab, expected_type
135136
)
136137
logger.info(f"Using temp variable '{temp_name}' for expression result")
137-
if (
138-
isinstance(val.type, ir.IntType)
139-
and expected_type
140-
and val.type.width > expected_type.width
141-
):
142-
val = builder.trunc(val, expected_type)
138+
if expected_type is not None and isinstance(expected_type, ir.IntType):
139+
val = convert(builder, val, val.type, expected_type)
143140
builder.store(val, ptr)
144141

145142
# NOTE: For char arrays, also return size

0 commit comments

Comments
 (0)