The idea here is that without any kind of loop, it's quite tricky to do some basic packet processing. For instance, if you want to clamp the MSS of a TCP connection, you have to grovel through TCP options, which do not occur at fixed offsets or in a particular order; you want to write a "for" loop over the (inherently limited) range of bytes at the computed offset of the TCP options. You can trivially bound that loop by the MTU of the link your program is loaded on (and also the limited possible size of TCP options), the loop won't iterate that many times, and it won't do much inside the loop.
But it's very much not the case that bounded loops give you general-purpose programming, like to implement your own data structures. BPF programs rely on kernel helpers and userland programs that maintain maps and read perf to do that stuff.
int nested_loops(volatile struct pt_regs* ctx)
{
int i, j, sum = 0, m;
for (j = 0; j < 300; j++)
for (i = 0; i < j; i++) {
if (j & 1)
m = ctx->rax;
else
m = j;
sum += i * m;
}
return sum;
}
Or for example another one that is also part of selftests with induction variable i: int while_true(volatile struct pt_regs* ctx)
{
int i = 0;
while (true) {
if (ctx->rax & 1)
i += 3;
else
i += 7;
if (i > 40)
break;
}
return i;
}
Overall this is very useful to avoid unrolling loops & keeping the code dense and icache friendly, and to parse (e.g.) IPv6 extension headers and such.For example, can it handle something like the following, where there's no bound, but the loop necessarily always terminates? (I assumed this loop would be called "unbounded", but maybe I'm confused by the terminology?)
int test(unsigned i, unsigned j) {
while (true) {
i ^= j; j ^= i; i ^= j;
if (i <= j) { return 0; }
i ^= j; j ^= i; i ^= j;
if (j <= i) { return 1; }
i ^= j; j ^= i; i ^= j;
}
return 2;
}