Skip to content

Commit 5f9cead

Browse files
r41k0uclaude
andcommitted
Core: Choose signed or unsigned operators and predicates from the promoted type
The operator table now carries both variants for the sign-sensitive operations: / and // -> sdiv/udiv, % -> srem/urem, >> -> ashr/lshr. The ring operations are unchanged since two's complement makes their low bits sign-blind. apply_binop picks the variant from the promoted type computed by the usual arithmetic conversions, so uint32(10) / int32(-2) is a udiv in u32 (0, as C) rather than a signed division (-5). Comparisons between two integers now go through the same promotion and pick icmp_signed or icmp_unsigned from the promoted type, so uint64(10) > int64(-1) is icmp ugt (false, as C). Pointer and struct comparisons keep the depth-normalising path unchanged. Corpus: only an XDP test comparing c_uint context fields changes, to an unsigned predicate. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BSDVsZH5NtoASyxB8FCtGU
1 parent 29d7aaa commit 5f9cead

3 files changed

Lines changed: 48 additions & 27 deletions

File tree

pythonbpf/expr/expr_pass.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,13 @@ def _handle_deref_call(expr: ast.Call, local_sym_tab: Dict, builder: ir.IRBuilde
182182
def _descriptor(val, ty):
183183
"""IntTy descriptor for an evaluated integer value: width from the physical
184184
value unless the descriptor is itself an integer type, sign from the
185-
descriptor (an IntTy, a vmlinux Field, or plain -> signed)."""
186-
width = ty.width if isinstance(ty, ir.IntType) else val.type.width
187-
return IntTy(width, signedness(ty))
185+
descriptor (an IntTy, a vmlinux Field, or plain -> signed). None when the
186+
value is not an integer at all."""
187+
if isinstance(ty, ir.IntType):
188+
return IntTy(ty.width, signedness(ty))
189+
if val is not None and isinstance(val.type, ir.IntType):
190+
return IntTy(val.type.width, signedness(ty))
191+
return None
188192

189193

190194
def get_typed_operand(func, compilation_context, operand, builder, local_sym_tab):
@@ -260,7 +264,7 @@ def _handle_binary_op_impl(func, compilation_context, rval, builder, local_sym_t
260264
)
261265
left = to_promoted(builder, left, left_ty, result_ty)
262266
right = to_promoted(builder, right, right_ty, result_ty)
263-
result = apply_binop(builder, op, left, right)
267+
result = apply_binop(builder, op, left, right, signedness(result_ty))
264268
return canonicalise(builder, result, result_ty), result_ty
265269

266270

@@ -370,8 +374,19 @@ def _handle_compare(func, compilation_context, builder, cond, local_sym_tab):
370374
logger.error("Failed to evaluate comparison operands")
371375
return None
372376

373-
lhs, _ = lhs
374-
rhs, _ = rhs
377+
lhs, lhs_ty = lhs
378+
rhs, rhs_ty = rhs
379+
lhs_desc, rhs_desc = _descriptor(lhs, lhs_ty), _descriptor(rhs, rhs_ty)
380+
if lhs_desc is not None and rhs_desc is not None:
381+
# Both integers: compare in the promoted type, which also picks the
382+
# signed or unsigned predicate (u64 > s64 is an unsigned compare in C).
383+
cmp_ty = usual_arithmetic_conversions(lhs_desc, rhs_desc)
384+
lhs = to_promoted(builder, lhs, lhs_desc, cmp_ty)
385+
rhs = to_promoted(builder, rhs, rhs_desc, cmp_ty)
386+
return handle_comparator(
387+
func, builder, cond.ops[0], lhs, rhs, signed=signedness(cmp_ty)
388+
)
389+
# Pointers and struct values: the depth-normalising path
375390
return handle_comparator(func, builder, cond.ops[0], lhs, rhs)
376391

377392

pythonbpf/expr/operators.py

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,24 @@
1111

1212
from pythonbpf.type_deducer import IntTy, signedness
1313

14-
# ast.BinOp.op class -> llvmlite IRBuilder method name.
15-
# Shared by binary-op evaluation and augmented assignment.
14+
# ast.BinOp.op class -> (signed IRBuilder method, unsigned IRBuilder method).
15+
# Shared by binary-op evaluation and augmented assignment. The ring operations
16+
# are sign-blind (two's complement gives identical low bits); division,
17+
# remainder and right shift are not, and the operation's type decides. `/` and
18+
# `//` are the same C truncating division -- Python's floor semantics for `//`
19+
# and `%` on negatives are a documented divergence.
1620
BINOP_METHODS = {
17-
ast.Add: "add",
18-
ast.Sub: "sub",
19-
ast.Mult: "mul",
20-
ast.Div: "sdiv",
21-
ast.Mod: "srem",
22-
ast.LShift: "shl",
23-
ast.RShift: "lshr",
24-
ast.BitOr: "or_",
25-
ast.BitXor: "xor",
26-
ast.BitAnd: "and_",
27-
ast.FloorDiv: "udiv",
21+
ast.Add: ("add", "add"),
22+
ast.Sub: ("sub", "sub"),
23+
ast.Mult: ("mul", "mul"),
24+
ast.Div: ("sdiv", "udiv"),
25+
ast.FloorDiv: ("sdiv", "udiv"),
26+
ast.Mod: ("srem", "urem"),
27+
ast.LShift: ("shl", "shl"),
28+
ast.RShift: ("ashr", "lshr"),
29+
ast.BitOr: ("or_", "or_"),
30+
ast.BitXor: ("xor", "xor"),
31+
ast.BitAnd: ("and_", "and_"),
2832
}
2933

3034
# ast.Compare op class -> icmp predicate string.
@@ -44,12 +48,13 @@
4448
BOOL_OPS = (ast.And, ast.Or)
4549

4650

47-
def apply_binop(builder, op, left, right):
48-
"""Emit the LLVM instruction for a Python binary operator."""
49-
method = BINOP_METHODS.get(type(op))
50-
if method is None:
51+
def apply_binop(builder, op, left, right, signed=True):
52+
"""Emit the LLVM instruction for a Python binary operator, in the signed or
53+
unsigned form the operation's type calls for."""
54+
methods = BINOP_METHODS.get(type(op))
55+
if methods is None:
5156
raise SyntaxError(f"Unsupported binary operation: {type(op).__name__}")
52-
return getattr(builder, method)(left, right)
57+
return getattr(builder, methods[0] if signed else methods[1])(left, right)
5358

5459

5560
def comparison_predicate(op):

pythonbpf/expr/type_normalization.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ def convert_to_bool(builder, val):
111111
return builder.icmp_signed("!=", val, zero)
112112

113113

114-
def handle_comparator(func, builder, op, lhs, rhs):
115-
"""Handle comparison operations."""
114+
def handle_comparator(func, builder, op, lhs, rhs, signed=True):
115+
"""Handle comparison operations, signed or unsigned per the compared type."""
116116

117117
if lhs.type != rhs.type:
118118
lhs, rhs = _normalize_types(func, builder, lhs, rhs)
@@ -125,6 +125,7 @@ def handle_comparator(func, builder, op, lhs, rhs):
125125
return None
126126

127127
predicate = COMPARISON_OPS[type(op)]
128-
result = builder.icmp_signed(predicate, lhs, rhs)
128+
icmp = builder.icmp_signed if signed else builder.icmp_unsigned
129+
result = icmp(predicate, lhs, rhs)
129130
logger.debug(f"Comparison result: {result}")
130131
return result, ir.IntType(1)

0 commit comments

Comments
 (0)