Skip to content

Commit 5fb3c8d

Browse files
Tests: Loops pass now, plus the cases the lowering promises
- passing_tests/loops/: the seven range/while/break/continue/nested tests move over from failing_tests/, joined by rebinding the loop variable, a negative step, loop else-branches, and a helper-bounded loop over a @bpfglobal that survives -O2 as a real loop for the verifier. - failing_tests/loops/: break outside a loop, and a non-literal range step. - for_map_items stays xfail: map iteration needs its own design. - c-form/loops.bpf.c: the clang reference the lowering was checked against.
1 parent 24e7feb commit 5fb3c8d

15 files changed

Lines changed: 254 additions & 20 deletions

‎tests/c-form/loops.bpf.c‎

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/* Reference for loops. One program per shape the Python tests use, written
2+
* the way PythonBPF lowers them: a `for i in range(...)` keeps a hidden
3+
* induction counter separate from `i`, so rebinding `i` in the body cannot
4+
* change the trip count. The constant-bound cases fold to a `ret` at -O2;
5+
* `dyn_helper` reads its bound from a .data global and calls a helper per
6+
* iteration, so a real loop (and the verifier's bounded-loop check) survives.
7+
* (A helper-free sum over a runtime bound folds to a closed form that needs
8+
* the __multi3 libcall, which BPF lacks -- so it is not a useful reference.) */
9+
#define SEC(name) __attribute__((section(name), used))
10+
typedef unsigned int __u32;
11+
typedef unsigned long long __u64;
12+
typedef long long __s64;
13+
14+
char LICENSE[] SEC("license") = "GPL";
15+
16+
__s64 n = 10;
17+
18+
/* for i in range(10): total = total + 1 */
19+
SEC("tracepoint/syscalls/sys_enter_execve")
20+
__s64 range_sum(void *ctx)
21+
{
22+
__s64 total = 0;
23+
for (__s64 idx = 0; idx < 10; idx += 1) {
24+
__s64 i = idx;
25+
total = total + 1;
26+
}
27+
return total;
28+
}
29+
30+
/* for i in range(0, 10, 2): total = total + i */
31+
SEC("tracepoint/syscalls/sys_enter_execve")
32+
__s64 range_step(void *ctx)
33+
{
34+
__s64 total = 0;
35+
for (__s64 idx = 0; idx < 10; idx += 2) {
36+
__s64 i = idx;
37+
total = total + i;
38+
}
39+
return total;
40+
}
41+
42+
/* while i < 10: i = i + 1 */
43+
SEC("tracepoint/syscalls/sys_enter_execve")
44+
__s64 while_basic(void *ctx)
45+
{
46+
__s64 i = 0;
47+
while (i < 10)
48+
i = i + 1;
49+
return i;
50+
}
51+
52+
/* while True: total += 1; if total == 10: break */
53+
SEC("tracepoint/syscalls/sys_enter_execve")
54+
__s64 while_true_break(void *ctx)
55+
{
56+
__s64 total = 0;
57+
while (1) {
58+
total = total + 1;
59+
if (total == 10)
60+
break;
61+
}
62+
return total;
63+
}
64+
65+
/* for i in range(10): if i % 2 == 0: continue; total = total + i */
66+
SEC("tracepoint/syscalls/sys_enter_execve")
67+
__s64 for_continue(void *ctx)
68+
{
69+
__s64 total = 0;
70+
for (__s64 idx = 0; idx < 10; idx += 1) {
71+
__s64 i = idx;
72+
if (i % 2 == 0)
73+
continue;
74+
total = total + i;
75+
}
76+
return total;
77+
}
78+
79+
static __u32 (*bpf_get_prandom_u32)(void) = (void *)7;
80+
81+
/* for i in range(stop): total = total + random() -- a helper call per
82+
* iteration keeps the loop from folding, so the verifier sees a real loop.
83+
* The clamp is what makes it bounded: n is writable from userspace, so
84+
* unclamped the verifier walks iterations until E2BIG. */
85+
SEC("tracepoint/syscalls/sys_enter_execve")
86+
__s64 dyn_helper(void *ctx)
87+
{
88+
__s64 total = 0;
89+
__s64 stop = n;
90+
if (stop > 64)
91+
stop = 64;
92+
for (__s64 idx = 0; idx < stop; idx += 1) {
93+
__s64 i = idx;
94+
total = total + bpf_get_prandom_u32();
95+
}
96+
return total;
97+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from pythonbpf import bpf, section, bpfglobal, compile
2+
from ctypes import c_void_p, c_int64
3+
4+
5+
@bpf
6+
@section("tracepoint/syscalls/sys_enter_execve")
7+
def hello(ctx: c_void_p) -> c_int64:
8+
total: c_int64 = 0
9+
if total == 0:
10+
break # noqa: F701 -- the point of the test
11+
return total
12+
13+
14+
@bpf
15+
@bpfglobal
16+
def LICENSE() -> str:
17+
return "GPL"
18+
19+
20+
compile()
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from pythonbpf import bpf, section, bpfglobal, compile
2+
from ctypes import c_void_p, c_int64
3+
4+
5+
@bpf
6+
@section("tracepoint/syscalls/sys_enter_execve")
7+
def hello(ctx: c_void_p) -> c_int64:
8+
total: c_int64 = 0
9+
step: c_int64 = 2
10+
for i in range(0, 10, step):
11+
total = total + i
12+
return total
13+
14+
15+
@bpf
16+
@bpfglobal
17+
def LICENSE() -> str:
18+
return "GPL"
19+
20+
21+
compile()

tests/failing_tests/loops/for_range_start_stop_step.py renamed to tests/passing_tests/loops/for_range_start_stop_step.py

File renamed without changes.
File renamed without changes.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# A loop's else-branch runs when the loop ends without a break: the for loop
2+
# never breaks, so its else adds 100; the while loop breaks, so its else is
3+
# skipped. Returns 3 + 100 + 1 = 104.
4+
from pythonbpf import bpf, section, bpfglobal, compile
5+
from ctypes import c_void_p, c_int64
6+
7+
8+
@bpf
9+
@section("tracepoint/syscalls/sys_enter_execve")
10+
def hello(ctx: c_void_p) -> c_int64:
11+
total: c_int64 = 0
12+
for i in range(3):
13+
if i == 7:
14+
break
15+
total = total + 1
16+
else:
17+
total = total + 100
18+
while total < 1000:
19+
total = total + 1
20+
break
21+
else:
22+
total = total + 1000
23+
return total
24+
25+
26+
@bpf
27+
@bpfglobal
28+
def LICENSE() -> str:
29+
return "GPL"
30+
31+
32+
compile()
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# The bound is a @bpfglobal and the body calls a helper, so opt cannot fold
2+
# the loop away: this is the case that puts a real bounded loop in front of
3+
# the verifier. Reference: dyn_helper in tests/c-form/loops.bpf.c.
4+
#
5+
# The clamp is required, in C as here: userspace can write any value to a
6+
# .data global, so without it the verifier assumes a bound up to 2**63 and
7+
# walks iterations until it hits its 1M-instruction limit (E2BIG).
8+
from pythonbpf import bpf, section, bpfglobal, compile
9+
from pythonbpf.helper import random
10+
from ctypes import c_void_p, c_int64
11+
12+
13+
@bpf
14+
@bpfglobal
15+
def n() -> c_int64:
16+
return c_int64(10)
17+
18+
19+
@bpf
20+
@section("tracepoint/syscalls/sys_enter_execve")
21+
def hello(ctx: c_void_p) -> c_int64:
22+
total: c_int64 = 0
23+
stop = n
24+
if stop > 64:
25+
stop = 64
26+
for i in range(stop):
27+
total = total + random()
28+
return total
29+
30+
31+
@bpf
32+
@bpfglobal
33+
def LICENSE() -> str:
34+
return "GPL"
35+
36+
37+
compile()

0 commit comments

Comments
 (0)