Skip to content

Commit 532dc0f

Browse files
r41k0uclaude
andcommitted
Core: Rank ctx fields and map values by their declared types
Two descriptors were built from the physical value instead of the declaration. A sub-register vmlinux field is widened to i64 by load_ctx_field, and its descriptor took that width, so ctx.data - k with a c_int32 k was a 64-bit subtraction where C makes it a u32 one. A map-lookup local dereferenced past its slot took the physical pointee (i64, signed by default) and dropped the map's declared value ctype kept in the symbol's metadata, so p >> 63 on a c_uint64 value was an ashr. Both now read the declaration; the physical width still comes from the value, as everywhere else. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 26604b9 commit 532dc0f

2 files changed

Lines changed: 35 additions & 14 deletions

File tree

‎pythonbpf/expr/expr_pass.py‎

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from pythonbpf.type_deducer import (
88
ctypes_to_ir,
9+
field_int_type,
910
is_ctypes,
1011
IntTy,
1112
int_literal_type,
@@ -200,6 +201,11 @@ def _descriptor(val, ty):
200201
value is not an integer at all."""
201202
if isinstance(ty, ir.IntType):
202203
return IntTy(ty.width, signedness(ty))
204+
field = field_int_type(ty)
205+
if field is not None:
206+
# A vmlinux field: load_ctx_field already widened the value, but C
207+
# ranks it by its declared width (a c_uint32 field is unsigned int).
208+
return field
203209
if val is not None and isinstance(val.type, ir.IntType):
204210
return IntTy(val.type.width, signedness(ty))
205211
return None
@@ -214,12 +220,21 @@ def get_typed_operand(func, compilation_context, operand, builder, local_sym_tab
214220
sym = local_sym_tab[operand.id]
215221
var = sym.var
216222
base_type, depth = get_base_type_and_depth(var.type)
217-
val = (
218-
builder.load(var)
219-
if depth == 1
220-
else deref_to_depth(func, builder, var, depth)
223+
if depth == 1:
224+
val = builder.load(var)
225+
return val, _descriptor(val, sym.ir_type)
226+
val = deref_to_depth(func, builder, var, depth)
227+
# A map-lookup local: the slot points at the value, and the map's
228+
# declared value ctype is the symbol's metadata. That, not the
229+
# physical pointee, carries the sign (a c_uint64 value is unsigned).
230+
declared = (
231+
ctypes_to_ir(sym.metadata)
232+
if isinstance(sym.metadata, str) and is_ctypes(sym.metadata)
233+
else None
234+
)
235+
return val, _descriptor(
236+
val, declared if declared is not None else base_type
221237
)
222-
return val, _descriptor(val, sym.ir_type if depth == 1 else base_type)
223238
elif operand.id in compilation_context.bpf_globals:
224239
sym = compilation_context.bpf_globals[operand.id]
225240
return builder.load(sym.var), _descriptor(None, sym.ir_type)

‎pythonbpf/type_deducer.py‎

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,24 @@ def int_literal_type(value: int) -> IntTy:
4949
return IntTy(32, True) if -(1 << 31) <= value < (1 << 31) else IntTy(64, True)
5050

5151

52+
def field_int_type(ty) -> "IntTy | None":
53+
"""The declared integer type behind a vmlinux Field descriptor (its ctypes
54+
class), or None if the descriptor is not a Field with an integer ctype.
55+
The loaded value may already be wider; C ranks it by the declared width."""
56+
ctype = getattr(getattr(ty, "type", None), "__name__", None)
57+
if ctype in _INT_CTYPE_WIDTHS:
58+
return IntTy(_INT_CTYPE_WIDTHS[ctype], is_signed_ctype(ctype))
59+
return None
60+
61+
5262
def signedness(ty) -> bool:
53-
"""Sign of a descriptor: an IntTy says; a vmlinux Field says through its
54-
ctype; a 1-bit integer is a bool and never negative, whatever it is
55-
wrapped in; anything else is signed, the pre-signedness default."""
63+
"""Sign of a descriptor. An IntTy carries it directly; a vmlinux Field
64+
carries a ctypes class in .type, whose name decides; a 1-bit integer is a
65+
bool and never negative, whatever it is wrapped in; a plain ir.IntType, a
66+
site not yet taught to carry a sign, reads as signed, the compiler's
67+
historical behaviour."""
5668
if isinstance(ty, ir.IntType) and ty.width == 1:
5769
return False
58-
"""Sign of an integer type descriptor.
59-
60-
IntTy carries it directly. A vmlinux Field carries a ctypes class in .type,
61-
whose name decides. A plain ir.IntType -- a site that has not been taught to
62-
carry a sign yet -- reads as signed, the compiler's historical behaviour.
63-
"""
6470
if hasattr(ty, "signed"):
6571
return ty.signed
6672
ctype = getattr(getattr(ty, "type", None), "__name__", None)

0 commit comments

Comments
 (0)