Skip to content

Add kernel selftest equivalent roadmap tests - #105

Merged
r41k0u merged 26 commits into
masterfrom
claude/pythonbpf-state-replication-g23gij
Sep 25, 2026
Merged

r41k0u merged 26 commits into
masterfrom
claude/pythonbpf-state-replication-g23gij

Conversation

@r41k0u

@r41k0u r41k0u commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

varun-r-mallya and others added 11 commits September 1, 2026 12:47
Ports that import from vmlinux need the same treatment as
tests/passing_tests/vmlinux/ -- skipped, not failed, when no vmlinux.py has been
generated for the running kernel. Without this they fail outright wherever one
is absent, CI included.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ports of programs from tools/testing/selftests/bpf/progs/ in the Linux tree,
each naming its upstream original in a header comment.

    tracing/tracepoint_sched_switch.py   test_tracepoint.c
    tracing/get_cgroup_id.py             get_cgroup_id_kern.c
    tracing/autoattach.py                test_autoattach.c

Deliberately a small spike rather than bulk coverage. The three sample different
global-variable shapes, since substituting for globals is what porting the rest
of the corpus will mostly consist of: none at all (the control case), a scalar
read plus a scalar write, and two flags written from two programs on two
different attach points.

They also widen the range of program types under test.
tracepoint/sched/sched_switch and raw_tp/sys_enter were previously unexercised;
@section writes its string straight into the ELF with no allowlist, so that
pass-through had only ever been tested against a handful of types.

Only the BPF half of each selftest is ported -- upstream pairs every program
with a userspace driver in prog_tests/ that loads and asserts, whereas this
framework compiles and verifies but never runs. tests/README.md now says so
explicitly, along with the WORKAROUND(globals) convention marking each map
substituted for a global.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ported faithfully from test_perf_skip.c, which reads the sampled instruction
pointer out of a perf_event context: ctx.regs.ip. That is two levels of struct
field access and PythonBPF supports one, so this lands as a strict expected
failure describing the gap.

_allocate_for_attribute in allocation_pass.py declines to allocate unless the
attribute's base is a plain Name, so the nested form is skipped. One level on
the same context works today -- ctx.sample_period compiles and llc's fine.

Worth noting for whoever picks this up: the failure surfaces as
'SyntaxError: Undefined variable actual', naming the assignment target rather
than the nested access responsible. The allocation pass declines quietly and the
expression pass then trips over the missing symbol.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The four ports were the experiment; this is the result. Covers whether
LLM-assisted porting is worth continuing (yes, in feature-sized increments), what
a passing port actually proves (a compiler assertion, not a behavioural one), and
the four distinct global-variable shapes the spike turned up.

Includes the finding that libbpf implements globals as single-element
BPF_MAP_TYPE_ARRAY maps, so a one-element ArrayMap -- not a HashMap -- is the
structurally faithful stand-in, and that the ELF half of global support already
works today via the machinery behind @bpfglobal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ate-replication-g23gij

# Conflicts:
#	tests/test_config.toml
…scalars

The selftest spike substituted a one-entry HashMap for every upstream global
because PythonBPF had no global variable support. Integer-scalar @bpfglobal
support has since landed, so the three affected ports now declare the upstream
globals directly: get_cgroup_id reads expected_pid and writes cg_id, autoattach
shares prog1_called/prog2_called between two programs, and perf_skip reads ip.
perf_skip stays a strict xfail on the nested ctx.regs.ip access.

Update the tests README and porting notes accordingly; the WORKAROUND tag no
longer exists in the tree.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLMW3X7opqAPH6bjmTVkr7
A sub-register context field is loaded zero-extended to i64, and the
assignment path accepted it only into a 64-bit slot or one whose type was
exactly the field's declared type. The second case never worked in practice:
`data_end = skb.data_end` into a c_uint32 global died in llvmlite with
"cannot store i64 to i32*" because the value was still i64.

Route the store through convert(), sizing from the physical value and signing
from the field, like every other integer store since the signedness work: a
no-op into an i64 slot, a trunc into anything narrower. Found by porting
cgroup_skb_direct_packet_access.c, which is that exact statement.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLMW3X7opqAPH6bjmTVkr7
Every program on the selftest audit's Tier 1 and Tier 2 lists that the compiler
can express today: xdp_dummy, priv_prog, test_xdp_link, xdp_tx, tc_dummy,
test_tc_bpf, cgroup_mprog, cgroup_skb_direct_packet_access, test_signed_loader,
test_signed_loader_data, test_netfilter_link_attach, kprobe_multi_empty,
uprobe_multi_bench, uprobe_multi_usdt, test_link_pinning and
test_xdp_devmap_helpers. Fifteen pass at every level, bringing tc, tcx,
cgroup/getsockopt, cgroup_skb, socket, netfilter, kprobe.multi, uprobe.multi,
usdt and tp_btf program types under test for the first time.

test_xdp_devmap_helpers is a negative fixture upstream: reading egress_ifindex
is only legal with expected_attach_type = BPF_XDP_DEVMAP and the driver asserts
the plain load fails. It is a strict verifier-level xfail here for the same
reason, until expected_attach_type can be expressed.

The porting notes record the batch and why each remaining program on the two
lists is still out of reach.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLMW3X7opqAPH6bjmTVkr7
The porting assessment was produced by a one-off script that scored every
program under tools/testing/selftests/bpf/progs against the compiler's
envelope; the notes recommended keeping it so the numbers can be regenerated
after each feature lands. This is that script, rebuilt: it strips comments,
skips include-shims and header-only fixtures, and reports per program the
hard blockers (language gaps) and soft flags (porting costs), with a
histogram and a near-miss listing. The helper, map and construct lists at
the top are the envelope and are maintained by hand.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLMW3X7opqAPH6bjmTVkr7
veristat_foo (three socket programs) is clean. test_perf_link,
test_enable_stats and test_cgroup_link count with __sync_fetch_and_add, which
becomes a plain `x += 1` tagged WORKAROUND(atomics) for a mechanical sweep
once atomics exist. connect4_dropper compares against bpf_htons(port), written
out as shifts on the low 16 bits. All five pass at IR, llc and verifier level,
and bring perf_event, raw_tracepoint/sys_enter, cgroup_skb/egress and
cgroup/connect4 under test.

The porting notes gain the third batch, the atomics tag, the current blocker
histogram from the checked-in audit tool, and how to re-run it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLMW3X7opqAPH6bjmTVkr7
Copilot AI lite review requested due to automatic review settings September 24, 2026 17:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Four unresolved moderate review findings affect audit correctness and vmlinux-test failure handling.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 3 Medium severity

Open (3)
What changed in this PR

Adds kernel selftest-equivalent PythonBPF fixtures, roadmap coverage, audit tooling, documentation, and related compiler/test integration.

Changes:

  • Adds selftest ports across XDP, vmlinux, tracing, socket, cgroup, TC, and map areas.
  • Adds expected-failure configuration, discovery support, and porting documentation.
  • Adds ArrayMap scaffolding and context-field narrowing support.
File Summary
tools/​selftest-audit.py Selftest corpus audit tooling
tests/​test_config.toml Roadmap expected failures
tests/​README.md Selftest-equivalent documentation
tests/​passing_tests/​vmlinux/​ctx_field_narrow_store.py Context-field narrowing regression test
tests/​kernel_selftest_equivalent/​xdp/​xdp_link.py XDP and TC link fixture
tests/​kernel_selftest_equivalent/​xdp/​xdp_dummy.py XDP dummy programs
tests/​kernel_selftest_equivalent/​xdp/​priv_prog.py Privileged XDP fixture
tests/​kernel_selftest_equivalent/​vmlinux/​xdp_tx.py XDP transmit fixture
tests/​kernel_selftest_equivalent/​vmlinux/​xdp_devmap_helpers.py Negative verifier fixture
tests/​kernel_selftest_equivalent/​vmlinux/​tc_bpf.py TC packet-access fixture
tests/​kernel_selftest_equivalent/​vmlinux/​perf_skip.py Nested-context roadmap fixture
tests/​kernel_selftest_equivalent/​vmlinux/​connect4_dropper.py Connect4 fixture
tests/​kernel_selftest_equivalent/​vmlinux/​cgroup_skb_direct_packet_access.py Direct packet-access fixture
tests/​kernel_selftest_equivalent/​tracing/​uprobe_multi_usdt.py USDT tracing fixture
tests/​kernel_selftest_equivalent/​tracing/​uprobe_multi_bench.py Uprobe benchmark fixture
tests/​kernel_selftest_equivalent/​tracing/​tracepoint_sched_switch.py Tracepoint fixture
tests/​kernel_selftest_equivalent/​tracing/​perf_link.py Perf-link fixture
tests/​kernel_selftest_equivalent/​tracing/​link_pinning.py Link-pinning fixtures
tests/​kernel_selftest_equivalent/​tracing/​kprobe_multi_empty.py Empty kprobe-multi fixture
tests/​kernel_selftest_equivalent/​tracing/​get_cgroup_id.py Cgroup ID tracing fixture
tests/​kernel_selftest_equivalent/​tracing/​enable_stats.py Runtime-statistics fixture
tests/​kernel_selftest_equivalent/​tracing/​autoattach.py Autoattach fixtures
tests/​kernel_selftest_equivalent/​tc/​tc_dummy.py TC dummy classifier
tests/​kernel_selftest_equivalent/​socket/​veristat_foo.py Socket naming fixture
tests/​kernel_selftest_equivalent/​socket/​signed_loader.py Signed-loader fixture
tests/​kernel_selftest_equivalent/​socket/​signed_loader_data.py Initialized-global loader fixture
tests/​kernel_selftest_equivalent/​ringbuf/​reserve_submit_discard.py Ring-buffer roadmap fixture
tests/​kernel_selftest_equivalent/​PORTING-NOTES.md Porting results and roadmap
tests/​kernel_selftest_equivalent/​netfilter/​netfilter_link_attach.py Netfilter fixture
tests/​kernel_selftest_equivalent/​maps/​array_map_lookup_update.py ArrayMap roadmap fixture
tests/​kernel_selftest_equivalent/​cgroup/​cgroup_mprog.py Multi-program cgroup fixture
tests/​kernel_selftest_equivalent/​cgroup/​cgroup_link.py Cgroup link fixture
tests/​framework/​collector.py New test-directory discovery
tests/​conftest.py vmlinux fixture handling
pythonbpf/​maps/​maps.py ArrayMap type stub
pythonbpf/​maps/​maps_pass.py Explicit ArrayMap failure
pythonbpf/​maps/​__init__.py ArrayMap export
pythonbpf/​assign_pass.py Context-field assignment support

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/conftest.py Outdated
Comment on lines +35 to +37
except Exception as exc:
VMLINUX_AVAILABLE = False
VMLINUX_SKIP_REASON = f"vmlinux.py not usable for current kernel: {exc}"
Comment thread tools/selftest-audit.py Outdated
Comment thread tools/selftest-audit.py
if args.histogram:
hist = Counter(b for r in results for b in r["hard"])
for label, n in hist.most_common():
print(f" {label:28s} {n:4d} {100 * n / real:4.0f}%")

@varun-r-mallya varun-r-mallya left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HI! THIS IS AI SLOP REVIEW. PLSSSSSSS READ ME SO That YOU DONT MESS UP

def prog(ctx: struct_xdp_md) -> c_int64:
global ifindex
ifindex = ctx.ingress_ifindex
queue = c_uint16(0)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

queue = c_uint16(0) never makes a 16-bit local. _allocate_for_call only handles c_int32/c_int64/c_uint32/c_uint64/c_void_p, so queue becomes an i64 alloca, and storing ctx.rx_queue_index into it never truncates.

The IR is zext i32 -> i64; store i64 %queue with no trunc to i16. If rx_queue_index is 0x10001, this program returns 65537, but C (__u16 queue = ctx->rx_queue_index) returns 1. Only the ifindex global actually exercises the new trunc path, so this test doesn't pin narrowing into the local's declared width, which is what its header says it does.

Comment thread tools/selftest-audit.py Outdated
r"task_acquire|task_release|cgroup_acquire|cgroup_release|cpumask_\w+|rbtree_\w+|list_\w+|"
r"rcu_read_lock|rcu_read_unlock|arena_\w+|key_put|lookup_user_key|dynptr_\w+|iter_\w+|"
r"wq_\w+|timer_\w+|throw|percpu_obj_\w+|res_spin_\w+|preempt_\w+|local_irq_\w+|"
r"session_\w+|get_dentry_xattr|get_file_xattr|kptr_xchg|sk_assign|xdp_\w+|skb_\w+)\s*\(",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The kfunc regex includes bpf_(...|xdp_\w+|skb_\w+)\s*\(, so it also matches ordinary helpers, including bpf_skb_store_bytes, which is in SUPPORTED_HELPERS.

Any program that calls bpf_skb_store_bytes(...) gets hard=['kfunc'] and drops off the portable list. All bpf_skb_* and bpf_xdp_* helper users are counted as kfunc users, which inflates the kfunc row in PORTING-NOTES.

Comment thread tools/selftest-audit.py Outdated
r"jited|xlated|caps_unpriv|load_if_JITed|not_msg|failure_unpriv|success_unpriv)\b",
),
# functions
("subprog_call", "hard", r"\b__noinline\b|\b__weak\b|\bSEC\s*\(\s*\"\?"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hard subprog_call pattern also matches SEC("?..."), so every non-autoloaded program counts as a hard BPF-to-BPF call blocker. That contradicts the soft sleepable_or_special_sec rule for the same syntax.

SEC("?tc") int prog(...) { return 0; } gets both hard=['subprog_call'] and soft=['sleepable_or_special_sec'], is wrongly excluded from the candidates, and inflates the BPF-to-BPF calls histogram row.

Comment thread tools/selftest-audit.py Outdated
hard.add("legacy_map_def")

helpers = set(HELPER_CALL.findall(src)) - NOT_HELPERS
helpers = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This filter drops every bpf_map_* call that isn't in SUPPORTED_HELPERS instead of flagging it as unsupported, so unsupported map helpers are never counted as blockers.

A program that uses bpf_map_push_elem, bpf_map_peek_elem or bpf_map_lookup_percpu_elem on a supported map type is reported as having no hard blocker and listed as portable. A port attempt then fails because PythonBPF can't emit those helpers.

Comment thread tools/selftest-audit.py Outdated

def strip_comments(src: str) -> str:
src = re.sub(r"/\*.*?\*/", "", src, flags=re.S)
return re.sub(r"//[^\n]*", "", src)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strip_comments also removes //... inside string literals, which truncates common section names such as SEC("uprobe//proc/self/exe:func").

That line becomes SEC("uprobe, so SEC_RE doesn't match and the section is missing from sections. A file whose programs all use sections like this is classified as a shim (classify returns None) and silently left out of the real-program count.

Comment thread tests/conftest.py Outdated
VMLINUX_AVAILABLE = True
except ImportError:
VMLINUX_SKIP_REASON = ""
except Exception as exc:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Widening except ImportError to except Exception means a broken vmlinux.py quietly skips every vmlinux test instead of failing.

If a vmlinux.py regeneration bug causes a SyntaxError, NameError or ctypes TypeError at import time, every test under passing_tests/vmlinux, failing_tests/* and kernel_selftest_equivalent/vmlinux is marked SKIPPED and CI passes with no vmlinux coverage.

Comment thread tests/test_config.toml Outdated
# prog_tests driver asserts that a plain load *fails*. The kernel rejects it
# here with "invalid bpf_context access off=20 size=4", which is the pass
# condition upstream. PythonBPF has no way to set expected_attach_type yet.
"kernel_selftest_equivalent/vmlinux/xdp_devmap_helpers.py" = {reason = "Negative fixture: egress_ifindex needs expected_attach_type=BPF_XDP_DEVMAP, which cannot be set yet; the verifier rejection is upstream's pass condition", level = "verifier"}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This negative fixture is a strict xfail at verifier level with raises=Exception, so any verifier failure counts as the expected rejection, not only the documented invalid bpf_context access off=20.

If a codegen regression makes the verifier reject the program for an unrelated reason (for example, bad packet-pointer arithmetic in ctx.data_end - ctx.data), or if bpftool or sudo fails, the test still reports XFAIL. The regression is hidden, and the claim that the rejection is upstream's pass condition is never checked.

Comment thread pythonbpf/assign_pass.py Outdated
@@ -197,22 +197,14 @@ def handle_variable_assignment(
logger.info("Handling assignment to struct field")
field_ir_type = ctypes_to_ir(val_type.type.__name__)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch gets the type from ctypes_to_ir(val_type.type.__name__) and duplicates the integer-convert path. type_deducer.field_int_type(val_type) already returns the signed IntTy, or None for non-integer fields.

For a vmlinux Field whose ctype is a pointer, array or struct class (for example an LP_struct_* name), ctypes_to_ir raises NotImplementedError instead of reaching the logged "Failed to assign" error. Resolving the Field with field_int_type before the top-level isinstance(val_type, ir.IntType) check would remove this special case, and ctx fields would go through the same convert() call as everything else.



@MapProcessorRegistry.register("ArrayMap")
def process_array_map(map_name, rval, compilation_context):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

process_array_map is a stub that raises NotImplementedError, but ArrayMap is now exported from pythonbpf.maps as if it were usable. A working version is process_hash_map with BPFMapType.ARRAY (the enum value already exists).

A user who imports the exported pythonbpf.maps.ArrayMap gets a NotImplementedError traceback at compile time. Implementing it (a copy of process_hash_map with a different type constant, plus lookup/update dispatch) is less work than keeping the stub and the strict xfail in step.

# and XDP_DROP; that is why this lives under vmlinux/.

from pythonbpf import bpf, section, bpfglobal, compile
from vmlinux import XDP_TX

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XDP_TX is imported from vmlinux, and the test sits under vmlinux/, because pythonbpf.helper doesn't export it. But pythonbpf/helper/helpers.py already defines XDP_TX and return_utils.py maps it.

Adding XDP_TX to __all__ in pythonbpf/helper/__init__.py would let this simple port run on hosts without a generated vmlinux.py. As written, it is skipped there only because of a missing one-line export.

r41k0u and others added 11 commits September 25, 2026 01:15
except Exception turned every import-time defect in the generated module
into a skip of every vmlinux test, so CI could pass with no vmlinux
coverage. ImportError (no module) skips; anything else propagates.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- The subprog_call pattern also matched SEC("?..."), so every
  non-autoloaded program counted as a BPF-to-BPF call blocker, in
  contradiction with the soft rule for the same syntax.
- The kfunc pattern matched any bpf_xdp_* or bpf_skb_* call, which are
  ordinary helpers (bpf_skb_store_bytes is in SUPPORTED_HELPERS). Only
  the kfunc families keep those prefixes; an unsupported helper is still
  a hard blocker, classified as one.
- strip_comments removed // inside string literals, truncating section
  names like SEC("uprobe//proc/self/exe:func") and classifying the file
  as a shim. Strings are matched first and kept.
- Every bpf_map_* helper not in SUPPORTED_HELPERS was dropped before the
  unsupported-helper test, so bpf_map_push_elem and friends never showed
  up as blockers. The membership test classifies them now.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
_allocate_for_call recognised only c_int32, c_int64, c_uint32, c_uint64
and c_void_p, so queue = c_uint16(0) fell through to an i64 slot and a
later store of a u32 field into it never narrowed. Any integer ctypes
constructor now declares a slot of its width, which is what the
ctx_field_narrow_store test claimed to pin; the IR assertion pins it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Field branch of handle_variable_assignment rebuilt the field's type
by name and duplicated the integer convert() path, and ctypes_to_ir
raises NotImplementedError for a pointer or array ctype before the
logged error is reached. An integer Field is now normalised to its
declared IntTy (field_int_type) before the integer path, in both
variable and struct-field assignment, so ctx fields go through the same
convert() call as everything else; the Field branch keeps only the
non-integer error.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
process_array_map was a stub raising NotImplementedError while ArrayMap
was exported from pythonbpf.maps as if usable. The lowering is the hash
map's with a different type constant: the same lookup/update/delete
helpers, and the kernel fixes the key at a 4-byte index. The lookup
allocation path accepts ARRAY alongside HASH, the audit tool lists
ARRAY as supported, and the array_map_lookup_update case is a passing
test now instead of a strict xfail.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every xfail is strict with raises=Exception, so a verifier-level
negative fixture counted any rejection as the expected one, including
one caused by a codegen regression, or by bpftool failing. A verifier
entry may now carry match = "..."; the harness attaches it as a
verifier_match marker and test_kernel_verifier fails, through
pytest.fail (not swallowed by raises=Exception), when the program is
rejected for any other reason. xdp_devmap_helpers pins its documented
"invalid bpf_context access".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
helpers.py defined all five and return_utils mapped them, but only
XDP_DROP and XDP_PASS were exported, so the xdp_tx port had to import
XDP_TX from vmlinux and live under vmlinux/, skipped on any host without
a generated module. It imports from pythonbpf.helper and lives under
xdp/ now, returning XDP_TX bare like its siblings and the C original,
which is the shape the return fast path resolves without vmlinux.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
get_typed_operand fell through its Name branch for a name that is not a
local, a global or a vmlinux constant, and raised the generic
"Unsupported operand type" meant for unknown node kinds. Since return
goes through it, forgetting `from vmlinux import XDP_PASS` produced that
message; it says "Undefined variable XDP_PASS" now, like every other
read.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait what is this? XDP_TX, XDP_ABORTED and XDP_PASS (everything XDP_..) is from the vmlinux.py. Why is this being included from helpers?


# C type constructors
if call_type in ("c_int32", "c_int64", "c_uint32", "c_uint64", "c_void_p"):
if is_ctypes(call_type) and isinstance(ctypes_to_ir(call_type), ir.IntType):

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is bool also accepted here?

Comment thread pythonbpf/assign_pass.py
)
return False
if isinstance(val_type, Field):
logger.info("Handling assignment to struct field")

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure this change is fine? Can you comprehensively test this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine though

is_expected_fail: bool = False
xfail_reason: str = ""
xfail_level: str = "ir" # one of LEVELS
xfail_match: str = "" # verifier level: substring the rejection must contain

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

is_expected_fail = xfail_entry is not None
xfail_reason = xfail_entry.get("reason", "") if xfail_entry else ""
xfail_level = xfail_entry.get("level", "ir") if xfail_entry else "ir"
xfail_match = xfail_entry.get("match", "") if xfail_entry else ""

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

r41k0u and others added 3 commits September 25, 2026 22:20
The printk formatter accepted 64- and 32-bit integers only and raised for
anything else. That went unnoticed while c_uint16(0) and friends silently
made 64-bit slots; with those slots now their declared width, printing
any 8- or 16-bit local failed. Every integer argument is already widened
to 64 bits per its sign before the call, so the 64-bit format fits every
width, and the sign now chooses %lld or %llu (an unsigned value above
INT_MAX printed as negative before).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
c_bool was missing from the ctypes table, so c_bool(5) was an unknown
call, its slot was never allocated, and the error read "Undefined
variable". It is C's _Bool: one bit, like the True/False locals, never
negative, and a value narrows to it by comparing with zero, which the
signedness rules already apply to 1-bit types. It works as a local, a
struct field, a map value and a global; in BTF it is a one-byte _Bool.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ctx_field_into_slots.py stores 8-, 32- and 64-bit sk_buff fields into
locals declared at every width and into struct fields of every width,
with IR assertions on the slot widths and the truncation before each
store. It is the compact form of a 52-case matrix (four field widths,
nine local and four struct-field destinations) compiled on master and
here: every case compiles, nothing sign-extends a field on the way in,
and where master compiled the IR is identical except where master put a
narrow local in a 64-bit slot. c_bool.py covers the local, struct field,
map value and global.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@r41k0u

r41k0u commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator Author

LGTM

@r41k0u
r41k0u merged commit 4662fe6 into master Sep 25, 2026
2 checks passed
r41k0u added a commit that referenced this pull request Sep 25, 2026
Four keep-both conflicts: PktPtrTy and byte_size added at the same spot
in type_deducer, the allocation pass's type_deducer import, and
neighbouring entries in test_config.toml and test_signedness_ir.py.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
r41k0u added a commit that referenced this pull request Sep 25, 2026
convert() refused to narrow a packet pointer below 64 bits, on the
premise that the verifier only accepts one at 64 bits. That is true of
arithmetic, not of stores: upstream's cgroup_skb_direct_packet_access
stores skb->data_end into a __u32 global, and CI's verifier level
accepted exactly that on #105. Merging master brought that selftest in
and the refusal rejected it. Narrowing now truncates, and the result is
an ordinary integer. ctx_data_narrow_store.py moves to the passing
tests. Keeping the pointer when a local is declared narrower belongs to
allocation (widest type ever stored), noted for later.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants