Skip to content

Commit 4e8f767

Browse files
r41k0uclaude
andcommitted
Core: Size and describe integers by their real width
Two places took an integer's width as 32 or 64 bits. Ten sites computed byte sizes as width // 8 (alignment, struct layout, globals), which is zero for a 1-bit type; they now share byte_size(), which rounds up to whole bytes. And debug info described every map key or value, and every struct field, that was not 32 bits wide as unsigned long long: a c_uint8 or c_uint16 map value was declared 8 bytes wide in BTF, so the kernel copied 8 bytes from a smaller slot. get_int_type() now gives the real width and sign everywhere, globals included. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent d3fa7d7 commit 4e8f767

7 files changed

Lines changed: 49 additions & 34 deletions

File tree

‎pythonbpf/allocation_pass.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from pythonbpf.helper import HelperHandlerRegistry
77
from pythonbpf.vmlinux_parser.dependency_node import Field
88
from .expr import VmlinuxHandlerRegistry
9-
from pythonbpf.type_deducer import ctypes_to_ir, is_ctypes, IntTy, signedness
9+
from pythonbpf.type_deducer import ctypes_to_ir, is_ctypes, IntTy, signedness, byte_size
1010
from pythonbpf.expr.type_inference import infer_int_type
1111
from pythonbpf.maps import BPFMapType
1212

@@ -115,7 +115,7 @@ def _allocate_for_call(builder, var_name, rval, local_sym_tab, compilation_conte
115115
# slot of that width; the value is converted into it at the store.
116116
ir_type = ctypes_to_ir(call_type)
117117
var = builder.alloca(ir_type, name=var_name)
118-
var.align = ir_type.width // 8
118+
var.align = byte_size(ir_type)
119119
local_sym_tab[var_name] = LocalSymbol(var, ir_type)
120120
logger.info(f"Pre-allocated {var_name} as {call_type}")
121121

@@ -472,7 +472,7 @@ def _allocate_for_attribute(
472472
tmp_name = f"{struct_var}_{field_name}_tmp"
473473
tmp_ir_type = ir.IntType(field_size_bits)
474474
tmp_var = builder.alloca(tmp_ir_type, name=tmp_name)
475-
tmp_var.align = tmp_ir_type.width // 8
475+
tmp_var.align = byte_size(tmp_ir_type)
476476
local_sym_tab[tmp_name] = LocalSymbol(tmp_var, tmp_ir_type)
477477
logger.info(
478478
f"Pre-allocated temp {tmp_name} (i{field_size_bits}) for vmlinux field read {vmlinux_struct_name}.{field_name}"
@@ -527,8 +527,8 @@ def _allocate_with_type(builder, var_name, ir_type):
527527
def _get_alignment(ir_type):
528528
"""Get appropriate alignment for IR type."""
529529
if isinstance(ir_type, ir.IntType):
530-
return ir_type.width // 8
530+
return byte_size(ir_type)
531531
elif isinstance(ir_type, ir.ArrayType) and isinstance(ir_type.element, ir.IntType):
532-
return ir_type.element.width // 8
532+
return byte_size(ir_type.element)
533533
else:
534534
return 8 # Default: pointer size

‎pythonbpf/debuginfo/debug_info_generator.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,27 @@ def get_basic_type(self, name: str, size: int, encoding: int) -> Any:
4949
)
5050
return self._type_cache[key]
5151

52+
def get_int_type(self, ty) -> Any:
53+
"""Debug type for an integer IR type, by width and sign (read from the
54+
IntTy descriptor, signed for a plain ir.IntType). A 1-bit type is C's
55+
_Bool, which occupies one byte. BTF, and so a map's key and value
56+
sizes, come from this, so the width must be the real one."""
57+
from pythonbpf.type_deducer import signedness
58+
59+
width, signed = ty.width, signedness(ty)
60+
if width == 1:
61+
return self.get_basic_type("_Bool", 8, dc.DW_ATE_boolean)
62+
base = {8: "char", 16: "short", 32: "int", 64: "long long"}.get(width)
63+
if base is None:
64+
raise ValueError(f"no debug type for a {width}-bit integer")
65+
if width == 8:
66+
encoding = dc.DW_ATE_signed_char if signed else dc.DW_ATE_unsigned_char
67+
else:
68+
encoding = dc.DW_ATE_signed if signed else dc.DW_ATE_unsigned
69+
return self.get_basic_type(
70+
base if signed else f"unsigned {base}", width, encoding
71+
)
72+
5273
def get_uint8_type(self) -> Any:
5374
"""Get debug info for signed 8-bit integer"""
5475
return self.get_basic_type("char", 8, dc.DW_ATE_unsigned)

‎pythonbpf/globals_pass.py‎

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,13 @@
33

44
from logging import Logger
55
import logging
6-
from .type_deducer import ctypes_to_ir, is_signed_ctype
6+
from .type_deducer import ctypes_to_ir, byte_size
77
from .symbols import BpfGlobalSymbol
88
from .debuginfo import DebugInfoGenerator
99
from .expr import VmlinuxHandlerRegistry
10-
from .debuginfo import dwarf_constants as dc
1110

1211
logger: Logger = logging.getLogger(__name__)
1312

14-
_C_NAME_BY_WIDTH = {8: "char", 16: "short", 32: "int", 64: "long long"}
15-
1613

1714
def populate_global_symbol_table(tree, compilation_context):
1815
"""
@@ -78,7 +75,7 @@ def _emit_global(module: ir.Module, node, name):
7875
# (align 4 for i32, align 8 for i64). llc derives the BTF DATASEC layout
7976
# from these symbols, so the alignment should mirror the C reference in
8077
# tests/c-form/global_vars.bpf.c.
81-
gvar.align = ty.width // 8 if isinstance(ty, ir.IntType) else 8
78+
gvar.align = byte_size(ty) if isinstance(ty, ir.IntType) else 8
8279
gvar.linkage = "dso_local"
8380
gvar.global_constant = False
8481
return gvar
@@ -93,15 +90,7 @@ def _emit_global_debug_info(compilation_context, gvar, name, ctype_name):
9390
future skeleton can tell which variable lives at which offset.
9491
"""
9592
generator = DebugInfoGenerator(compilation_context.module)
96-
width = gvar.value_type.width
97-
signed = is_signed_ctype(ctype_name)
98-
base = _C_NAME_BY_WIDTH[width]
99-
if width == 8:
100-
encoding = dc.DW_ATE_signed_char if signed else dc.DW_ATE_unsigned_char
101-
else:
102-
encoding = dc.DW_ATE_signed if signed else dc.DW_ATE_unsigned
103-
cname = base if signed else f"unsigned {base}"
104-
di_type = generator.get_basic_type(cname, width, encoding)
93+
di_type = generator.get_int_type(ctypes_to_ir(ctype_name))
10594
dv = generator.create_global_var_debug_info(name, di_type, is_local=False)
10695
gvar.set_metadata("dbg", dv)
10796

‎pythonbpf/maps/map_debug_info.py‎

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from pythonbpf.type_deducer import ctypes_to_ir, is_ctypes
12
import logging
23
from llvmlite import ir
34
from pythonbpf.debuginfo import DebugInfoGenerator
@@ -122,11 +123,12 @@ def _get_key_val_dbg_type(name, generator, structs_sym_tab):
122123
# Fallback to basic types
123124
logger.info(f"No struct named {name}, falling back to basic type")
124125

125-
# NOTE: Only handling int and long for now
126-
if name in ["c_int32", "c_uint32"]:
127-
return generator.get_uint32_type()
126+
# A ctypes integer: its real width and sign, so the map's key or value
127+
# size in BTF matches what the program stores (a c_uint8 value is 1 byte).
128+
if is_ctypes(name):
129+
return generator.get_int_type(ctypes_to_ir(name))
128130

129-
# Default fallback for now
131+
logger.warning(f"No debug type for map key/value {name}, defaulting to u64")
130132
return generator.get_uint64_type()
131133

132134

@@ -136,11 +138,7 @@ def _get_struct_debug_type(struct_obj, generator, structs_sym_tab):
136138
for fld in struct_obj.fields.keys():
137139
fld_type = struct_obj.field_type(fld)
138140
if isinstance(fld_type, ir.IntType):
139-
if fld_type.width == 32:
140-
fld_dbg_type = generator.get_uint32_type()
141-
else:
142-
# NOTE: Assuming 64-bit for all other int types
143-
fld_dbg_type = generator.get_uint64_type()
141+
fld_dbg_type = generator.get_int_type(fld_type)
144142
elif isinstance(fld_type, ir.ArrayType):
145143
# NOTE: Array types have u8 elements only for now
146144
# Debug info generation should fail for other types

‎pythonbpf/structs/struct_type.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from pythonbpf.type_deducer import byte_size
12
from llvmlite import ir
23

34

@@ -24,9 +25,9 @@ def gep(self, builder, ptr, field_name):
2425
def field_size(self, field_name):
2526
fld = self.fields[field_name]
2627
if isinstance(fld, ir.ArrayType):
27-
return fld.count * (fld.element.width // 8)
28+
return fld.count * byte_size(fld.element)
2829
elif isinstance(fld, ir.IntType):
29-
return fld.width // 8
30+
return byte_size(fld)
3031
elif isinstance(fld, ir.PointerType):
3132
return 8
3233

‎pythonbpf/structs/structs_pass.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import ast
22
import logging
33
from llvmlite import ir
4-
from pythonbpf.type_deducer import ctypes_to_ir
4+
from pythonbpf.type_deducer import ctypes_to_ir, byte_size
55
from .struct_type import StructType
66

77
logger = logging.getLogger(__name__)
@@ -79,11 +79,11 @@ def calc_struct_size(field_types):
7979
curr_offset = 0
8080
for ftype in field_types:
8181
if isinstance(ftype, ir.IntType):
82-
fsize = ftype.width // 8
82+
fsize = byte_size(ftype)
8383
alignment = fsize
8484
elif isinstance(ftype, ir.ArrayType):
85-
fsize = ftype.count * (ftype.element.width // 8)
86-
alignment = ftype.element.width // 8
85+
fsize = ftype.count * byte_size(ftype.element)
86+
alignment = byte_size(ftype.element)
8787
elif isinstance(ftype, ir.PointerType):
8888
# We won't encounter this rn, but for the future
8989
fsize = 8

‎pythonbpf/type_deducer.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ def describe(self) -> str:
4141
return f"{'i' if self.signed else 'u'}{self.width}"
4242

4343

44+
def byte_size(ty) -> int:
45+
"""Bytes an integer type occupies in memory: its width rounded up to whole
46+
bytes, so a 1-bit bool still takes one byte, as C's _Bool does."""
47+
return (ty.width + 7) // 8
48+
49+
4450
def int_literal_type(value: int) -> IntTy:
4551
"""C's type for an integer constant: `int` if the value fits, else
4652
`long long`. Literals and enum constants alike; an enum constant is an

0 commit comments

Comments
 (0)