Skip to content

Commit cd1f2be

Browse files
r41k0uclaude
andcommitted
Tests: Add the ir-first-feature skill and the globals C reference
Captures the development loop this project has always used for new compiler features: write a minimal C eBPF program exercising only the feature, compile it to LLVM IR and read that as the specification, diff against what PythonBPF currently emits, stop for a human decision on the Python syntax, implement against the reference, and test at the right tier. Recorded as a project skill so agents follow it too; the syntax step is an explicit hard stop. tests/c-form/global_vars.bpf.c is the loop's step 1 for global variables: the four C global classes and nothing else. Its IR established that every global is an independent symbol accessed by plain load/store, that llc manufactures the BTF VAR/DATASEC entries from DIGlobalVariable metadata, and that clang folds const but not const volatile -- the facts the globals design rests on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L1PX8EuP9C3o3veWGA84RF
1 parent 926ce3f commit cd1f2be

2 files changed

Lines changed: 94 additions & 0 deletions

File tree

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
name: ir-first-feature
3+
description: PythonBPF's development loop for implementing a new compiler feature — write a minimal C eBPF reference, compile it to LLVM IR and read that as the spec, stop for a human syntax decision, then implement against the reference. Use whenever adding or extending a PythonBPF language feature (new statement/expression support, map types, globals, helpers, program constructs).
4+
---
5+
6+
# The IR-first feature loop
7+
8+
PythonBPF targets LLVM IR via llvmlite. For any new feature, clang's output for the
9+
equivalent C is the specification — not documentation, not intuition. Follow the loop
10+
in order; do not skip steps because the feature "looks simple".
11+
12+
## 1. Write the C reference
13+
14+
A minimal `.bpf.c` in `tests/c-form/` exercising **only** the target feature. Small
15+
enough that every line of the resulting IR is attributable to the feature. Prefer no
16+
includes (define `SEC` and the `__u*` typedefs by hand) so nothing else pollutes the IR.
17+
Cover each variant of the feature in one file (e.g. for globals: zero-init, initialized,
18+
const, const volatile).
19+
20+
## 2. Compile and read the IR — this is the spec
21+
22+
```bash
23+
clang -target bpf -O2 -g -emit-llvm -S feature.bpf.c -o feature.ll
24+
llc -march=bpf -filetype=obj feature.ll -o feature.o
25+
bpftool btf dump file feature.o # what must come out the far end
26+
```
27+
28+
Read `feature.ll` and answer, in writing: What top-level symbols/globals appear? What
29+
do loads/stores/calls look like in the body? What `!DI*` debug metadata exists, and
30+
what BTF does llc manufacture from it? What did -O2 fold away, and does that folding
31+
carry semantics (it did for `const` globals)?
32+
33+
Version discipline: llvmlite ≥0.49 emits LLVM 21/22-era attribute spellings
34+
(`captures(none)`, not `nocapture`). Use a clang/llc generation that accepts them, and
35+
compare against what `pythonbpf` + the CI's LLVM actually use.
36+
37+
## 3. Diff against current PythonBPF output
38+
39+
Compile the nearest thing PythonBPF can already express and diff the `.ll`s. The delta
40+
is the actual work item — often smaller than expected (machinery like section placement
41+
and BTF generation frequently comes free from llc).
42+
43+
## 4. HARD STOP — syntax is a human decision
44+
45+
Present 2–3 Pythonic syntax candidates with trade-offs (declaration site, usage site,
46+
failure modes, precedents from FastAPI/typing/Triton-style DSLs). **Wait for a human to
47+
choose. Never proceed on your own judgment, and never treat silence as consent.** The
48+
maintainers own the language surface.
49+
50+
## 5. Implement against the reference
51+
52+
Emit IR through the existing passes (`globals_pass`, `expr_pass`, `assign_pass`,
53+
`allocation_pass`, `debuginfo/`). Verify by **diffing your emitted `.ll` against the
54+
clang reference for the same shapes** — "it compiles and llc accepts it" is not the
55+
bar; llc accepts plenty of subtly wrong IR.
56+
57+
## 6. Test at the right tier
58+
59+
- Works now → `tests/passing_tests/<category>/`.
60+
- Documents a gap → `tests/kernel_selftest_equivalent/` with a strict xfail in
61+
`tests/test_config.toml` (level `"ir"`, `"llc"`, or `"verifier"`).
62+
- Wrong-input behaviour → `tests/failing_tests/` with a config entry.
63+
- Kernel verifier level runs in CI; locally it needs the user's sudo — ask, don't
64+
assume.
65+
66+
## House guardrails (always)
67+
68+
- **Never read/cat/grep `vmlinux.py` or `vmlinux.h`** — generated, enormous, will
69+
exhaust context. Probe with one-liners:
70+
`.venv/bin/python -c "import vmlinux; print(vmlinux.struct_x._fields_[:3])"`
71+
- Use `.venv/bin/python`; the system python has no llvmlite.
72+
- Atomic commits, `Core:`/`Tests:` subject prefixes, one logical change each.

tests/c-form/global_vars.bpf.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/* Minimal reference: the four C global-variable classes, nothing else.
2+
* No headers, so every line of IR is attributable. */
3+
#define SEC(name) __attribute__((section(name), used))
4+
typedef unsigned int __u32;
5+
typedef unsigned long long __u64;
6+
7+
__u64 counter; /* zero-init -> .bss, mutable */
8+
__u64 total = 7; /* init -> .data, mutable */
9+
const __u32 version = 3; /* const -> .rodata, clang folds */
10+
const volatile __u32 filter_pid; /* cfg knob -> .rodata, never folded */
11+
12+
SEC("tracepoint/syscalls/sys_enter_nanosleep")
13+
int prog(void *ctx)
14+
{
15+
if (filter_pid == 0)
16+
return 0;
17+
counter += 1;
18+
total += version;
19+
return (int)counter;
20+
}
21+
22+
char _license[] SEC("license") = "GPL";

0 commit comments

Comments
 (0)