Add kernel selftest equivalent roadmap tests - #105
Conversation
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
There was a problem hiding this comment.
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
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.
| except Exception as exc: | ||
| VMLINUX_AVAILABLE = False | ||
| VMLINUX_SKIP_REASON = f"vmlinux.py not usable for current kernel: {exc}" |
| 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
left a comment
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
| 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*\(", |
There was a problem hiding this comment.
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.
| 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*\"\?"), |
There was a problem hiding this comment.
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.
| hard.add("legacy_map_def") | ||
|
|
||
| helpers = set(HELPER_CALL.findall(src)) - NOT_HELPERS | ||
| helpers = { |
There was a problem hiding this comment.
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.
|
|
||
| def strip_comments(src: str) -> str: | ||
| src = re.sub(r"/\*.*?\*/", "", src, flags=re.S) | ||
| return re.sub(r"//[^\n]*", "", src) |
There was a problem hiding this comment.
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.
| VMLINUX_AVAILABLE = True | ||
| except ImportError: | ||
| VMLINUX_SKIP_REASON = "" | ||
| except Exception as exc: |
There was a problem hiding this comment.
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.
| # 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"} |
There was a problem hiding this comment.
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.
| @@ -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__) | |||
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
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>
This reverts commit 55fb6d6.
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>
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
Is bool also accepted here?
| ) | ||
| return False | ||
| if isinstance(val_type, Field): | ||
| logger.info("Handling assignment to struct field") |
There was a problem hiding this comment.
Are you sure this change is fine? Can you comprehensively test this?
| 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 |
| 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 "" |
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>
|
LGTM |
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>
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>

No description provided.