Commit 8c2b5e90 authored by Andrii Nakryiko's avatar Andrii Nakryiko Committed by Alexei Starovoitov

selftests/bpf: add bpf_for_each(), bpf_for(), and bpf_repeat() macros

Add bpf_for_each(), bpf_for(), and bpf_repeat() macros that make writing
open-coded iterator-based loops much more convenient and natural. These
macros utilize cleanup attribute to ensure proper destruction of the
iterator and thanks to that manage to provide the ergonomics that is
very close to C language's for() construct. Typical loop would look like:

  int i;
  int arr[N];

  bpf_for(i, 0, N) {
      /* verifier will know that i >= 0 && i < N, so could be used to
       * directly access array elements with no extra checks
       */
       arr[i] = i;
  }

bpf_repeat() is very similar, but it doesn't expose iteration number and
is meant as a simple "repeat action N times" loop:

  bpf_repeat(N) { /* whatever, N times */ }

Note that `break` and `continue` statements inside the {} block work as
expected.

bpf_for_each() is a generalization over any kind of BPF open-coded
iterator allowing to use for-each-like approach instead of calling
low-level bpf_iter_<type>_{new,next,destroy}() APIs explicitly. E.g.:

  struct cgroup *cg;

  bpf_for_each(cgroup, cg, some, input, args) {
      /* do something with each cg */
  }

would call (not-yet-implemented) bpf_iter_cgroup_{new,next,destroy}()
functions to form a loop over cgroups, where `some, input, args` are
passed verbatim into constructor as

  bpf_iter_cgroup_new(&it, some, input, args).

As a first demonstration, add pyperf variant based on the bpf_for() loop.

Also clean up a few tests that either included bpf_misc.h header
unnecessarily from the user-space, which is unsupported, or included it
before any common types are defined (and thus leading to unnecessary
compilation warnings, potentially).
Signed-off-by: default avatarAndrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/r/20230308184121.1165081-6-andrii@kernel.orgSigned-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parent 6018e1f4
...@@ -144,6 +144,12 @@ void test_verif_scale_pyperf600_nounroll() ...@@ -144,6 +144,12 @@ void test_verif_scale_pyperf600_nounroll()
scale_test("pyperf600_nounroll.bpf.o", BPF_PROG_TYPE_RAW_TRACEPOINT, false); scale_test("pyperf600_nounroll.bpf.o", BPF_PROG_TYPE_RAW_TRACEPOINT, false);
} }
void test_verif_scale_pyperf600_iter()
{
/* open-coded BPF iterator version */
scale_test("pyperf600_iter.bpf.o", BPF_PROG_TYPE_RAW_TRACEPOINT, false);
}
void test_verif_scale_loop1() void test_verif_scale_loop1()
{ {
scale_test("loop1.bpf.o", BPF_PROG_TYPE_RAW_TRACEPOINT, false); scale_test("loop1.bpf.o", BPF_PROG_TYPE_RAW_TRACEPOINT, false);
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
#include <test_progs.h> #include <test_progs.h>
#include "test_uprobe_autoattach.skel.h" #include "test_uprobe_autoattach.skel.h"
#include "progs/bpf_misc.h"
/* uprobe attach point */ /* uprobe attach point */
static noinline int autoattach_trigger_func(int arg1, int arg2, int arg3, static noinline int autoattach_trigger_func(int arg1, int arg2, int arg3,
......
...@@ -75,5 +75,104 @@ ...@@ -75,5 +75,104 @@
#define FUNC_REG_ARG_CNT 5 #define FUNC_REG_ARG_CNT 5
#endif #endif
struct bpf_iter_num;
extern int bpf_iter_num_new(struct bpf_iter_num *it, int start, int end) __ksym;
extern int *bpf_iter_num_next(struct bpf_iter_num *it) __ksym;
extern void bpf_iter_num_destroy(struct bpf_iter_num *it) __ksym;
#ifndef bpf_for_each
/* bpf_for_each(iter_type, cur_elem, args...) provides generic construct for
* using BPF open-coded iterators without having to write mundane explicit
* low-level loop logic. Instead, it provides for()-like generic construct
* that can be used pretty naturally. E.g., for some hypothetical cgroup
* iterator, you'd write:
*
* struct cgroup *cg, *parent_cg = <...>;
*
* bpf_for_each(cgroup, cg, parent_cg, CG_ITER_CHILDREN) {
* bpf_printk("Child cgroup id = %d", cg->cgroup_id);
* if (cg->cgroup_id == 123)
* break;
* }
*
* I.e., it looks almost like high-level for each loop in other languages,
* supports continue/break, and is verifiable by BPF verifier.
*
* For iterating integers, the difference betwen bpf_for_each(num, i, N, M)
* and bpf_for(i, N, M) is in that bpf_for() provides additional proof to
* verifier that i is in [N, M) range, and in bpf_for_each() case i is `int
* *`, not just `int`. So for integers bpf_for() is more convenient.
*
* Note: this macro relies on C99 feature of allowing to declare variables
* inside for() loop, bound to for() loop lifetime. It also utilizes GCC
* extension: __attribute__((cleanup(<func>))), supported by both GCC and
* Clang.
*/
#define bpf_for_each(type, cur, args...) for ( \
/* initialize and define destructor */ \
struct bpf_iter_##type ___it __attribute__((aligned(8), /* enforce, just in case */, \
cleanup(bpf_iter_##type##_destroy))), \
/* ___p pointer is just to call bpf_iter_##type##_new() *once* to init ___it */ \
*___p = (bpf_iter_##type##_new(&___it, ##args), \
/* this is a workaround for Clang bug: it currently doesn't emit BTF */ \
/* for bpf_iter_##type##_destroy() when used from cleanup() attribute */ \
(void)bpf_iter_##type##_destroy, (void *)0); \
/* iteration and termination check */ \
(((cur) = bpf_iter_##type##_next(&___it))); \
)
#endif /* bpf_for_each */
#ifndef bpf_for
/* bpf_for(i, start, end) implements a for()-like looping construct that sets
* provided integer variable *i* to values starting from *start* through,
* but not including, *end*. It also proves to BPF verifier that *i* belongs
* to range [start, end), so this can be used for accessing arrays without
* extra checks.
*
* Note: *start* and *end* are assumed to be expressions with no side effects
* and whose values do not change throughout bpf_for() loop execution. They do
* not have to be statically known or constant, though.
*
* Note: similarly to bpf_for_each(), it relies on C99 feature of declaring for()
* loop bound variables and cleanup attribute, supported by GCC and Clang.
*/
#define bpf_for(i, start, end) for ( \
/* initialize and define destructor */ \
struct bpf_iter_num ___it __attribute__((aligned(8), /* enforce, just in case */ \
cleanup(bpf_iter_num_destroy))), \
/* ___p pointer is necessary to call bpf_iter_num_new() *once* to init ___it */ \
*___p = (bpf_iter_num_new(&___it, (start), (end)), \
/* this is a workaround for Clang bug: it currently doesn't emit BTF */ \
/* for bpf_iter_num_destroy() when used from cleanup() attribute */ \
(void)bpf_iter_num_destroy, (void *)0); \
({ \
/* iteration step */ \
int *___t = bpf_iter_num_next(&___it); \
/* termination and bounds check */ \
(___t && ((i) = *___t, (i) >= (start) && (i) < (end))); \
}); \
)
#endif /* bpf_for */
#ifndef bpf_repeat
/* bpf_repeat(N) performs N iterations without exposing iteration number
*
* Note: similarly to bpf_for_each(), it relies on C99 feature of declaring for()
* loop bound variables and cleanup attribute, supported by GCC and Clang.
*/
#define bpf_repeat(N) for ( \
/* initialize and define destructor */ \
struct bpf_iter_num ___it __attribute__((aligned(8), /* enforce, just in case */ \
cleanup(bpf_iter_num_destroy))), \
/* ___p pointer is necessary to call bpf_iter_num_new() *once* to init ___it */ \
*___p = (bpf_iter_num_new(&___it, 0, (N)), \
/* this is a workaround for Clang bug: it currently doesn't emit BTF */ \
/* for bpf_iter_num_destroy() when used from cleanup() attribute */ \
(void)bpf_iter_num_destroy, (void *)0); \
bpf_iter_num_next(&___it); \
/* nothing here */ \
)
#endif /* bpf_repeat */
#endif #endif
...@@ -4,12 +4,12 @@ ...@@ -4,12 +4,12 @@
* Copyright 2020 Google LLC. * Copyright 2020 Google LLC.
*/ */
#include "bpf_misc.h"
#include "vmlinux.h" #include "vmlinux.h"
#include <errno.h>
#include <bpf/bpf_core_read.h> #include <bpf/bpf_core_read.h>
#include <bpf/bpf_helpers.h> #include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h> #include <bpf/bpf_tracing.h>
#include <errno.h> #include "bpf_misc.h"
struct { struct {
__uint(type, BPF_MAP_TYPE_ARRAY); __uint(type, BPF_MAP_TYPE_ARRAY);
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <linux/bpf.h> #include <linux/bpf.h>
#include <bpf/bpf_helpers.h> #include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
#define FUNCTION_NAME_LEN 64 #define FUNCTION_NAME_LEN 64
#define FILE_NAME_LEN 128 #define FILE_NAME_LEN 128
...@@ -294,17 +295,22 @@ int __on_event(struct bpf_raw_tracepoint_args *ctx) ...@@ -294,17 +295,22 @@ int __on_event(struct bpf_raw_tracepoint_args *ctx)
if (ctx.done) if (ctx.done)
return 0; return 0;
#else #else
#ifdef NO_UNROLL #if defined(USE_ITER)
/* no for loop, no unrolling */
#elif defined(NO_UNROLL)
#pragma clang loop unroll(disable) #pragma clang loop unroll(disable)
#else #elif defined(UNROLL_COUNT)
#ifdef UNROLL_COUNT
#pragma clang loop unroll_count(UNROLL_COUNT) #pragma clang loop unroll_count(UNROLL_COUNT)
#else #else
#pragma clang loop unroll(full) #pragma clang loop unroll(full)
#endif
#endif /* NO_UNROLL */ #endif /* NO_UNROLL */
/* Unwind python stack */ /* Unwind python stack */
#ifdef USE_ITER
int i;
bpf_for(i, 0, STACK_MAX_LEN) {
#else /* !USE_ITER */
for (int i = 0; i < STACK_MAX_LEN; ++i) { for (int i = 0; i < STACK_MAX_LEN; ++i) {
#endif
if (frame_ptr && get_frame_data(frame_ptr, pidData, &frame, &sym)) { if (frame_ptr && get_frame_data(frame_ptr, pidData, &frame, &sym)) {
int32_t new_symbol_id = *symbol_counter * 64 + cur_cpu; int32_t new_symbol_id = *symbol_counter * 64 + cur_cpu;
int32_t *symbol_id = bpf_map_lookup_elem(&symbolmap, &sym); int32_t *symbol_id = bpf_map_lookup_elem(&symbolmap, &sym);
......
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2023 Meta Platforms, Inc. and affiliates.
#define STACK_MAX_LEN 600
#define SUBPROGS
#define NO_UNROLL
#define USE_ITER
#include "pyperf.h"
...@@ -2,7 +2,4 @@ ...@@ -2,7 +2,4 @@
// Copyright (c) 2019 Facebook // Copyright (c) 2019 Facebook
#define STACK_MAX_LEN 600 #define STACK_MAX_LEN 600
#define NO_UNROLL #define NO_UNROLL
/* clang will not unroll at all.
* Total program size is around 2k insns
*/
#include "pyperf.h" #include "pyperf.h"
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment