Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpf: Relax precision marking in open coded iters and may_goto loop. #7127

Closed

Conversation

kernel-patches-daemon-bpf[bot]
Copy link

Pull request for series with
subject: bpf: Relax precision marking in open coded iters and may_goto loop.
version: 4
url: https://patchwork.kernel.org/project/netdevbpf/list/?series=857895

@kernel-patches-daemon-bpf
Copy link
Author

Upstream branch: 531876c
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=857895
version: 4

@kernel-patches-daemon-bpf
Copy link
Author

Upstream branch: 96a27ee
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=857895
version: 4

@kernel-patches-daemon-bpf
Copy link
Author

Upstream branch: ce5249b
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=857895
version: 4

@kernel-patches-daemon-bpf
Copy link
Author

Upstream branch: ec1249d
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=857895
version: 4

@kernel-patches-daemon-bpf
Copy link
Author

Upstream branch: 49784c7
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=857895
version: 4

@kernel-patches-daemon-bpf
Copy link
Author

Upstream branch: 49df001
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=857895
version: 4

Alexei Starovoitov added 2 commits June 4, 2024 10:19
v1->v2->v3:
- Algorithm changed completely.
v3->v4:
- Fixed widening for Rx < Ry case and added more tests

Motivation for the patch
------------------------
Open coded iterators and may_goto is a great mechanism to implement loops,
but counted loops are problematic. For example:
  for (i = 0; i < 100 && can_loop; i++)
is verified as a bounded loop, since i < 100 condition forces the verifier to
mark 'i' as precise and loop states at different iterations are not equivalent.
That removes the benefit of open coded iterators and may_goto.
The workaround is to do:
  int zero = 0; /* global or volatile variable */
  for (i = zero; i < 100 && can_loop; i++)
to hide from the verifier the value of 'i'.
It's unnatural and so far users didn't learn such odd programming pattern.

This patch aims to improve the verifier to support
  for (i = 0; i < 100000 && can_loop; i++)
as open coded iter loop (when 'i' doesn't need to be precise).

Algorithm
---------
First of all:
   if (is_may_goto_insn_at(env, insn_idx)) {
+          update_loop_entry(cur, &sl->state);
           if (states_equal(env, &sl->state, cur, RANGE_WITHIN)) {
-                  update_loop_entry(cur, &sl->state);

It changes the definition of the verifier states loop.
Previously, we considered a state loop to be such a sequence of states
Si -> ... -> Sj -> ... -> Sk that states_equal(Si, Sk, RANGE_WITHIN)
is true.

With this change Si -> ... -> Sj -> ... Sk is a loop if call sites and
instruction pointers for Si and Sk match.

Whether or not Si and Sk are in the loop influences two things:
(a) if exact comparison is needed for states cache;
(b) if widening transformation could be applied to some scalars.

All pairs (Si, Sk) marked as a loop using old definition would be
marked as such using new definition (in a addition to some new pairs).

Hence it is safe to apply (a) and (b) in strictly more cases.

Note that update_loop_entry() relies on the following properties:
- every state in the current DFS path (except current)
  has branches > 0;
- states not in the DFS path are either:
  - in explored_states, are fully explored and have branches == 0;
  - in env->stack, are not yet explored and have branches == 0
    (and also not reachable from is_state_visited()).

With that the get_loop_entry() can be used to gate is_branch_taken() logic.
When the verifier sees 'r1 > 1000' inside the loop and it can predict it
instead of marking r1 as precise it widens both branches, so r1 becomes
[0, 1000] in fallthrough and [1001, UMAX] in other_branch.

Consider the loop:
    bpf_for_each(...) {
       if (r1 > 1000)
          break;

       arr[r1] = ..;
    }
At arr[r1] access the r1 is bounded and the loop can quickly converge.

Unfortunately compilers (both GCC and LLVM) often optimize loop exit
condition to equality, so
 for (i = 0; i < 100; i++) arr[i] = 1
becomes
 for (i = 0; i != 100; i++) arr[1] = 1

Hence treat != and == conditions specially in the verifier.
Widen only not-predicted branch and keep predict branch as is. Example:
  r1 = 0
  goto L1
L2:
  arr[r1] = 1
  r1++
L1:
  if r1 != 100 goto L2
  fallthrough: r1=100 after widening
  other_branch: r1 stays as-is (0, 1, 2, ..)

Also recognize the case where both LHS and RHS are constant and equal to each
other. In this case don't widen at all and take the predicted path.
This key heuristic allows the verifier detect loop end condition.
Such 'for (i = 0; i != 100; i++)' is validated just like bounded loop.

With that the users can use 'for (i = 0; ...' pattern everywhere
and many i = zero workarounds can be removed.

One tests has drastic improvement. The rest are noise.

File                  Insns (A)  Insns (B)  Insns     (DIFF)  States (A)  States (B)  States    (DIFF)
--------------------  ---------  ---------  ----------------  ----------  ----------  ----------------
iters_task_vma.bpf.o      22043        132  -21911 (-99.40%)        1006          10    -996 (-99.01%)

Few extra tests are added to iters_task_vma.bpf.o that demonstrate quick
convergence though they iterate over 100k elements.

Signed-off-by: Alexei Starovoitov <ast@kernel.org>
. remove i = zero workaround from various tests
. improve arena based tests
. add asm test for this_branch_reg->id == other_branch_reg->id condition
. add several loop inside open coded iter tests

Signed-off-by: Alexei Starovoitov <ast@kernel.org>
@kernel-patches-daemon-bpf
Copy link
Author

Upstream branch: 49df001
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=857895
version: 4

@kernel-patches-daemon-bpf
Copy link
Author

At least one diff in series https://patchwork.kernel.org/project/netdevbpf/list/?series=857895 expired. Closing PR.

@kernel-patches-daemon-bpf kernel-patches-daemon-bpf bot deleted the series/854891=>bpf-next branch June 7, 2024 00:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

0 participants