diff mbox series

[RFC,bpf-next] libbpf: add resizable array helpers

Message ID 20240921011712.83355-1-inwardvessel@gmail.com (mailing list archive)
State RFC
Delegated to: BPF
Headers show
Series [RFC,bpf-next] libbpf: add resizable array helpers | expand

Checks

Context Check Description
netdev/tree_selection success Clearly marked for bpf-next, async
netdev/apply fail Patch does not apply to bpf-next-0
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Unittests
bpf/vmtest-bpf-next-VM_Test-5 success Logs for aarch64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-3 success Logs for Validate matrix.py
bpf/vmtest-bpf-next-VM_Test-13 success Logs for s390x-gcc / test (test_progs, false, 360) / test_progs on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-4 success Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-9 success Logs for aarch64-gcc / test (test_verifier, false, 360) / test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-PR success PR summary
bpf/vmtest-bpf-next-VM_Test-7 success Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-10 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-14 success Logs for s390x-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-18 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-6 success Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-15 success Logs for s390x-gcc / test (test_verifier, false, 360) / test_verifier on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-8 success Logs for aarch64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-21 success Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-12 success Logs for s390x-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-17 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-11 success Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-20 success Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-16 success Logs for s390x-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-23 success Logs for x86_64-gcc / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-27 success Logs for x86_64-llvm-17 / build / build for x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-26 success Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-19 success Logs for x86_64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-25 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-22 success Logs for x86_64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-30 success Logs for x86_64-llvm-17 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-24 success Logs for x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-32 success Logs for x86_64-llvm-17 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-28 success Logs for x86_64-llvm-17 / build-release / build for x86_64 with llvm-17-O2
bpf/vmtest-bpf-next-VM_Test-35 success Logs for x86_64-llvm-18 / build-release / build for x86_64 with llvm-18-O2
bpf/vmtest-bpf-next-VM_Test-29 success Logs for x86_64-llvm-17 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-31 success Logs for x86_64-llvm-17 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-38 success Logs for x86_64-llvm-18 / test (test_progs_cpuv4, false, 360) / test_progs_cpuv4 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-37 success Logs for x86_64-llvm-18 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-36 success Logs for x86_64-llvm-18 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-40 success Logs for x86_64-llvm-18 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-41 success Logs for x86_64-llvm-18 / veristat
bpf/vmtest-bpf-next-VM_Test-33 success Logs for x86_64-llvm-17 / veristat
bpf/vmtest-bpf-next-VM_Test-39 success Logs for x86_64-llvm-18 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-34 success Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18

Commit Message

JP Kobryn Sept. 21, 2024, 1:17 a.m. UTC
Arrays in custom data sections can be resized via bpf_map__set_value().
While working with these types of arrays in some sched_ext programs, there
was some feedback that the manual operations involved could use helpers.
The macros in the potential patch are intended to make resizing bpf arrays
easier.

To illustrate, declaring an array that will be resized looks like this:
__u32 my_map[1] SEC(".data.my_map");

Instead, using a macro to help with the declaration:
__u32 BPF_RESIZABLE_ARRAY(data, my_map);

To allow access to the post-resized array in the bpf program, this helper
can be used which maintains verifier safety:
u32 *val = (u32 *)ARRAY_ELEM_PTR(my_map, ctx->cpu, nr_cpus);

Meanwhile in the userspace program, instead of doing:
size_t sz = bpf_map__set_value_size(skel->maps.data_my_map, sizeof(skel->data_my_map->my_map[0]) * nr_cpus);
skel->data_my_map = bpf_map__initial_value(skel->maps.data_my_map, &sz);

The resizing macro can be used:
BPF_RESIZE_ARRAY(data, my_map, nr_cpus);

Signed-off-by: JP Kobryn <inwardvessel@gmail.com>
---
 include/uapi/linux/bpf.h    | 23 ++++++++++++++++++
 tools/lib/bpf/bpf_helpers.h | 48 +++++++++++++++++++++++++++++++++++++
 2 files changed, 71 insertions(+)

Comments

Alexei Starovoitov Sept. 27, 2024, 1:28 p.m. UTC | #1
On Sat, Sep 21, 2024 at 3:17 AM JP Kobryn <inwardvessel@gmail.com> wrote:
>
> Arrays in custom data sections can be resized via bpf_map__set_value().
> While working with these types of arrays in some sched_ext programs, there
> was some feedback that the manual operations involved could use helpers.
> The macros in the potential patch are intended to make resizing bpf arrays
> easier.
>
> To illustrate, declaring an array that will be resized looks like this:
> __u32 my_map[1] SEC(".data.my_map");
>
> Instead, using a macro to help with the declaration:
> __u32 BPF_RESIZABLE_ARRAY(data, my_map);

I don't like hiding things in a macro.
SEC() isn't great, but that's what we got and users
used to it.

> To allow access to the post-resized array in the bpf program, this helper
> can be used which maintains verifier safety:
> u32 *val = (u32 *)ARRAY_ELEM_PTR(my_map, ctx->cpu, nr_cpus);

I don't like this one either.
We have bpf_cmp_likely/unlikely that can be used
to guard array access against the limit.

> Meanwhile in the userspace program, instead of doing:
> size_t sz = bpf_map__set_value_size(skel->maps.data_my_map, sizeof(skel->data_my_map->my_map[0]) * nr_cpus);
> skel->data_my_map = bpf_map__initial_value(skel->maps.data_my_map, &sz);
>
> The resizing macro can be used:
> BPF_RESIZE_ARRAY(data, my_map, nr_cpus);

Open code of libbpf api is much more readable. Macros are not.



>
> Signed-off-by: JP Kobryn <inwardvessel@gmail.com>
> ---
>  include/uapi/linux/bpf.h    | 23 ++++++++++++++++++
>  tools/lib/bpf/bpf_helpers.h | 48 +++++++++++++++++++++++++++++++++++++
>  2 files changed, 71 insertions(+)
>
> diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
> index e05b39e39c3f..92e93c9fc056 100644
> --- a/include/uapi/linux/bpf.h
> +++ b/include/uapi/linux/bpf.h
> @@ -7513,4 +7513,27 @@ struct bpf_iter_num {
>         __u64 __opaque[1];
>  } __attribute__((aligned(8)));
>
> +/*
> + * BPF_RESIZE_ARRAY - Convenience macro for resizing a BPF array
> + * @elfsec: the data section of the BPF program in which to the array exists
> + * @arr: the name of the array
> + * @n: the desired array element count
> + *
> + * For BPF arrays declared with RESIZABLE_ARRAY(), this macro performs two
> + * operations. It resizes the map which corresponds to the custom data
> + * section that contains the target array. As a side effect, the BTF info for
> + * the array is adjusted so that the array length is sized to cover the new
> + * data section size. The second operation is reassigning the skeleton pointer
> + * for that custom data section so that it points to the newly memory mapped
> + * region.
> + */
> +#define BPF_RESIZE_ARRAY(elfsec, arr, n)                                         \
> +       do {                                                                      \
> +               size_t __sz;                                                      \
> +               bpf_map__set_value_size(skel->maps.elfsec##_##arr,                \
> +                               sizeof(skel->elfsec##_##arr->arr[0]) * (n));      \
> +               skel->elfsec##_##arr =                                            \
> +                       bpf_map__initial_value(skel->maps.elfsec##_##arr, &__sz); \
> +       } while (0)
> +
>  #endif /* _UAPI__LINUX_BPF_H__ */
> diff --git a/tools/lib/bpf/bpf_helpers.h b/tools/lib/bpf/bpf_helpers.h
> index 305c62817dd3..b0d496b0f0d6 100644
> --- a/tools/lib/bpf/bpf_helpers.h
> +++ b/tools/lib/bpf/bpf_helpers.h
> @@ -420,4 +420,52 @@ extern void bpf_iter_num_destroy(struct bpf_iter_num *it) __weak __ksym;
>  )
>  #endif /* bpf_repeat */
>
> +/**
> + * RESIZABLE_ARRAY - Generates annotations for an array that may be resized
> + * @elfsec: the data section of the BPF program in which to place the array
> + * @arr: the name of the array
> + *
> + * libbpf has an API for setting map value sizes. Since data sections (i.e.
> + * bss, data, rodata) themselves are maps, a data section can be resized. If
> + * a data section has an array as its last element, the BTF info for that
> + * array will be adjusted so that length of the array is extended to meet the
> + * new length of the data section. This macro annotates an array to have an
> + * element count of one with the assumption that this array can be resized
> + * within the userspace program. It also annotates the section specifier so
> + * this array exists in a custom sub data section which can be resized
> + * independently.
> + *
> + * See BPF_RESIZE_ARRAY() for the userspace convenience macro for resizing an
> + * array declared with BPF_RESIZABLE_ARRAY().
> + */
> +#define BPF_RESIZABLE_ARRAY(elfsec, arr) arr[1] SEC("."#elfsec"."#arr)
> +
> +/*
> + * BPF_ARRAY_ELEM_PTR - Obtain the verified pointer to an array element
> + * @arr: array to index into
> + * @i: array index
> + * @n: number of elements in array
> + *
> + * Similar to MEMBER_VPTR() but is intended for use with arrays where the
> + * element count needs to be explicit.
> + * It can be used in cases where a global array is defined with an initial
> + * size but is intended to be be resized before loading the BPF program.
> + * Without this version of the macro, MEMBER_VPTR() will use the compile time
> + * size of the array to compute the max, which will result in rejection by
> + * the verifier.
> + */
> +#define BPF_ARRAY_ELEM_PTR(arr, i, n) (typeof(arr[i]) *)({       \
> +       u64 __base = (u64)arr;                                    \
> +       u64 __addr = (u64)&(arr[i]) - __base;                     \
> +       asm volatile (                                            \
> +               "if %0 <= %[max] goto +2\n"                       \
> +               "%0 = 0\n"                                        \
> +               "goto +1\n"                                       \
> +               "%0 += %1\n"                                      \
> +               : "+r"(__addr)                                    \
> +               : "r"(__base),                                    \
> +                 [max]"r"(sizeof(arr[0]) * ((n) - 1)));  \
> +       __addr;                                           \
> +})
> +
>  #endif
> --
> 2.46.0
>
Andrii Nakryiko Sept. 27, 2024, 8:49 p.m. UTC | #2
On Fri, Sep 27, 2024 at 6:28 AM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Sat, Sep 21, 2024 at 3:17 AM JP Kobryn <inwardvessel@gmail.com> wrote:
> >
> > Arrays in custom data sections can be resized via bpf_map__set_value().
> > While working with these types of arrays in some sched_ext programs, there
> > was some feedback that the manual operations involved could use helpers.
> > The macros in the potential patch are intended to make resizing bpf arrays
> > easier.
> >
> > To illustrate, declaring an array that will be resized looks like this:
> > __u32 my_map[1] SEC(".data.my_map");
> >
> > Instead, using a macro to help with the declaration:
> > __u32 BPF_RESIZABLE_ARRAY(data, my_map);
>
> I don't like hiding things in a macro.
> SEC() isn't great, but that's what we got and users
> used to it.
>

I agree, macros are sometimes a necessary evil, but they do obscure
things and shouldn't be proliferated unnecessarily.

JP, instead of these patches, I'd suggest adding a new file under
Documentation/bpf/libbpf to describe common libbpf coding patterns,
and this resizable array use case would be a perfect starter for this.

> > To allow access to the post-resized array in the bpf program, this helper
> > can be used which maintains verifier safety:
> > u32 *val = (u32 *)ARRAY_ELEM_PTR(my_map, ctx->cpu, nr_cpus);
>
> I don't like this one either.
> We have bpf_cmp_likely/unlikely that can be used
> to guard array access against the limit.
>
> > Meanwhile in the userspace program, instead of doing:
> > size_t sz = bpf_map__set_value_size(skel->maps.data_my_map, sizeof(skel->data_my_map->my_map[0]) * nr_cpus);
> > skel->data_my_map = bpf_map__initial_value(skel->maps.data_my_map, &sz);
> >
> > The resizing macro can be used:
> > BPF_RESIZE_ARRAY(data, my_map, nr_cpus);
>
> Open code of libbpf api is much more readable. Macros are not.
>
>
>
> >
> > Signed-off-by: JP Kobryn <inwardvessel@gmail.com>
> > ---
> >  include/uapi/linux/bpf.h    | 23 ++++++++++++++++++
> >  tools/lib/bpf/bpf_helpers.h | 48 +++++++++++++++++++++++++++++++++++++
> >  2 files changed, 71 insertions(+)
> >
> > diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
> > index e05b39e39c3f..92e93c9fc056 100644
> > --- a/include/uapi/linux/bpf.h
> > +++ b/include/uapi/linux/bpf.h
> > @@ -7513,4 +7513,27 @@ struct bpf_iter_num {
> >         __u64 __opaque[1];
> >  } __attribute__((aligned(8)));
> >
> > +/*
> > + * BPF_RESIZE_ARRAY - Convenience macro for resizing a BPF array
> > + * @elfsec: the data section of the BPF program in which to the array exists
> > + * @arr: the name of the array
> > + * @n: the desired array element count
> > + *
> > + * For BPF arrays declared with RESIZABLE_ARRAY(), this macro performs two
> > + * operations. It resizes the map which corresponds to the custom data
> > + * section that contains the target array. As a side effect, the BTF info for
> > + * the array is adjusted so that the array length is sized to cover the new
> > + * data section size. The second operation is reassigning the skeleton pointer
> > + * for that custom data section so that it points to the newly memory mapped
> > + * region.
> > + */
> > +#define BPF_RESIZE_ARRAY(elfsec, arr, n)                                         \
> > +       do {                                                                      \
> > +               size_t __sz;                                                      \
> > +               bpf_map__set_value_size(skel->maps.elfsec##_##arr,                \
> > +                               sizeof(skel->elfsec##_##arr->arr[0]) * (n));      \
> > +               skel->elfsec##_##arr =                                            \
> > +                       bpf_map__initial_value(skel->maps.elfsec##_##arr, &__sz); \
> > +       } while (0)
> > +
> >  #endif /* _UAPI__LINUX_BPF_H__ */
> > diff --git a/tools/lib/bpf/bpf_helpers.h b/tools/lib/bpf/bpf_helpers.h
> > index 305c62817dd3..b0d496b0f0d6 100644
> > --- a/tools/lib/bpf/bpf_helpers.h
> > +++ b/tools/lib/bpf/bpf_helpers.h
> > @@ -420,4 +420,52 @@ extern void bpf_iter_num_destroy(struct bpf_iter_num *it) __weak __ksym;
> >  )
> >  #endif /* bpf_repeat */
> >
> > +/**
> > + * RESIZABLE_ARRAY - Generates annotations for an array that may be resized
> > + * @elfsec: the data section of the BPF program in which to place the array
> > + * @arr: the name of the array
> > + *
> > + * libbpf has an API for setting map value sizes. Since data sections (i.e.
> > + * bss, data, rodata) themselves are maps, a data section can be resized. If
> > + * a data section has an array as its last element, the BTF info for that
> > + * array will be adjusted so that length of the array is extended to meet the
> > + * new length of the data section. This macro annotates an array to have an
> > + * element count of one with the assumption that this array can be resized
> > + * within the userspace program. It also annotates the section specifier so
> > + * this array exists in a custom sub data section which can be resized
> > + * independently.
> > + *
> > + * See BPF_RESIZE_ARRAY() for the userspace convenience macro for resizing an
> > + * array declared with BPF_RESIZABLE_ARRAY().
> > + */
> > +#define BPF_RESIZABLE_ARRAY(elfsec, arr) arr[1] SEC("."#elfsec"."#arr)
> > +
> > +/*
> > + * BPF_ARRAY_ELEM_PTR - Obtain the verified pointer to an array element
> > + * @arr: array to index into
> > + * @i: array index
> > + * @n: number of elements in array
> > + *
> > + * Similar to MEMBER_VPTR() but is intended for use with arrays where the
> > + * element count needs to be explicit.
> > + * It can be used in cases where a global array is defined with an initial
> > + * size but is intended to be be resized before loading the BPF program.
> > + * Without this version of the macro, MEMBER_VPTR() will use the compile time
> > + * size of the array to compute the max, which will result in rejection by
> > + * the verifier.
> > + */
> > +#define BPF_ARRAY_ELEM_PTR(arr, i, n) (typeof(arr[i]) *)({       \
> > +       u64 __base = (u64)arr;                                    \
> > +       u64 __addr = (u64)&(arr[i]) - __base;                     \
> > +       asm volatile (                                            \
> > +               "if %0 <= %[max] goto +2\n"                       \
> > +               "%0 = 0\n"                                        \
> > +               "goto +1\n"                                       \
> > +               "%0 += %1\n"                                      \
> > +               : "+r"(__addr)                                    \
> > +               : "r"(__base),                                    \
> > +                 [max]"r"(sizeof(arr[0]) * ((n) - 1)));  \
> > +       __addr;                                           \
> > +})
> > +
> >  #endif
> > --
> > 2.46.0
> >
diff mbox series

Patch

diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index e05b39e39c3f..92e93c9fc056 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -7513,4 +7513,27 @@  struct bpf_iter_num {
 	__u64 __opaque[1];
 } __attribute__((aligned(8)));
 
+/*
+ * BPF_RESIZE_ARRAY - Convenience macro for resizing a BPF array
+ * @elfsec: the data section of the BPF program in which to the array exists
+ * @arr: the name of the array
+ * @n: the desired array element count
+ *
+ * For BPF arrays declared with RESIZABLE_ARRAY(), this macro performs two
+ * operations. It resizes the map which corresponds to the custom data
+ * section that contains the target array. As a side effect, the BTF info for
+ * the array is adjusted so that the array length is sized to cover the new
+ * data section size. The second operation is reassigning the skeleton pointer
+ * for that custom data section so that it points to the newly memory mapped
+ * region.
+ */
+#define BPF_RESIZE_ARRAY(elfsec, arr, n)					  \
+	do {									  \
+		size_t __sz;							  \
+		bpf_map__set_value_size(skel->maps.elfsec##_##arr,		  \
+				sizeof(skel->elfsec##_##arr->arr[0]) * (n));	  \
+		skel->elfsec##_##arr =						  \
+			bpf_map__initial_value(skel->maps.elfsec##_##arr, &__sz); \
+	} while (0)
+
 #endif /* _UAPI__LINUX_BPF_H__ */
diff --git a/tools/lib/bpf/bpf_helpers.h b/tools/lib/bpf/bpf_helpers.h
index 305c62817dd3..b0d496b0f0d6 100644
--- a/tools/lib/bpf/bpf_helpers.h
+++ b/tools/lib/bpf/bpf_helpers.h
@@ -420,4 +420,52 @@  extern void bpf_iter_num_destroy(struct bpf_iter_num *it) __weak __ksym;
 )
 #endif /* bpf_repeat */
 
+/**
+ * RESIZABLE_ARRAY - Generates annotations for an array that may be resized
+ * @elfsec: the data section of the BPF program in which to place the array
+ * @arr: the name of the array
+ *
+ * libbpf has an API for setting map value sizes. Since data sections (i.e.
+ * bss, data, rodata) themselves are maps, a data section can be resized. If
+ * a data section has an array as its last element, the BTF info for that
+ * array will be adjusted so that length of the array is extended to meet the
+ * new length of the data section. This macro annotates an array to have an
+ * element count of one with the assumption that this array can be resized
+ * within the userspace program. It also annotates the section specifier so
+ * this array exists in a custom sub data section which can be resized
+ * independently.
+ *
+ * See BPF_RESIZE_ARRAY() for the userspace convenience macro for resizing an
+ * array declared with BPF_RESIZABLE_ARRAY().
+ */
+#define BPF_RESIZABLE_ARRAY(elfsec, arr) arr[1] SEC("."#elfsec"."#arr)
+
+/*
+ * BPF_ARRAY_ELEM_PTR - Obtain the verified pointer to an array element
+ * @arr: array to index into
+ * @i: array index
+ * @n: number of elements in array
+ *
+ * Similar to MEMBER_VPTR() but is intended for use with arrays where the
+ * element count needs to be explicit.
+ * It can be used in cases where a global array is defined with an initial
+ * size but is intended to be be resized before loading the BPF program.
+ * Without this version of the macro, MEMBER_VPTR() will use the compile time
+ * size of the array to compute the max, which will result in rejection by
+ * the verifier.
+ */
+#define BPF_ARRAY_ELEM_PTR(arr, i, n) (typeof(arr[i]) *)({	  \
+	u64 __base = (u64)arr;					  \
+	u64 __addr = (u64)&(arr[i]) - __base;			  \
+	asm volatile (						  \
+		"if %0 <= %[max] goto +2\n"		  	  \
+		"%0 = 0\n"					  \
+		"goto +1\n"					  \
+		"%0 += %1\n"					  \
+		: "+r"(__addr)					  \
+		: "r"(__base),					  \
+		  [max]"r"(sizeof(arr[0]) * ((n) - 1)));  \
+	__addr;						  \
+})
+
 #endif