On Thu, 21 Dec 2023 at 04:39, Alexei Starovoitov <alexei.starovoitov@xxxxxxxxx> wrote: > > From: Alexei Starovoitov <ast@xxxxxxxxxx> > > Compilers optimize conditional operators at will, but often bpf programmers > want to force compilers to keep the same operator in asm as it's written in C. > Introduce bpf_cmp(var1, conditional_op, var2) macro that can be used as: > > - if (seen >= 1000) > + if (bpf_cmp(seen, >=, 1000)) > > The macro takes advantage of BPF assembly that is C like. > > The macro checks the sign of variable 'seen' and emits either > signed or unsigned compare. > > For example: > int a; > bpf_cmp(a, >, 0) will be translated to 'if rX s> 0 goto' in BPF assembly. > > unsigned int a; > bpf_cmp(a, >, 0) will be translated to 'if rX > 0 goto' in BPF assembly. > > C type conversions coupled with comparison operator are tricky. > int i = -1; > unsigned int j = 1; > if (i < j) // this is false. > > long i = -1; > unsigned int j = 1; > if (i < j) // this is true. > > Make sure BPF program is compiled with -Wsign-compare then the macro will catch > the mistake. > > The macro checks LHS (left hand side) only to figure out the sign of compare. > > 'if 0 < rX goto' is not allowed in the assembly, so the users > have to use a variable on LHS anyway. > > The patch updates few tests to demonstrate the use of the macro. > > The macro allows to use BPF_JSET in C code, since LLVM doesn't generate it at > present. For example: > > if (i & j) compiles into r0 &= r1; if r0 == 0 goto > > while > > if (bpf_cmp(i, &, j)) compiles into if r0 & r1 goto > > Note that the macro has to be careful with RHS assembly predicate. > Since: > u64 __rhs = 1ull << 42; > asm goto("if r0 < %[rhs] goto +1" :: [rhs] "ri" (__rhs)); > LLVM will silently truncate 64-bit constant into s32 imm. > > Acked-by: Daniel Borkmann <daniel@xxxxxxxxxxxxx> > Signed-off-by: Alexei Starovoitov <ast@xxxxxxxxxx> > --- Sorry about the delayed feedback on this, I'm knocked out due to flu. The macro looks very useful and I like the simplification for the assertion macros, +1, but I would just like to make sure one important point about this is not missed. When I was writing the _eq/lt/... variants I noticed that unless I use LHS to be a full 8-byte register the compiler can still play shenanigans even with inline assembly, like choosing a different input register for LHS than the one already allocated for a variable before the assembly is emitted, doing left/right shifts to mask upper bits before the inline assembly logic, and thus since the scalar id association is broken on that, the cmp does not propagate to what are logical copies. I noticed it often when say a 8-byte value (known to be small) was saved as int len = something, asserted upon, and then used later in the program. One of the contracts of the bpf_assert_eq* macros compared to just plain bpf_assert was that the former was supposed to guarantee the assertion resulted in the verifier preserving the new knowledge for the LHS variable. So maybe in addition to using bpf_cmp, we should instead do a bpf_assert_op as a replacement that enforces this rule of LHS being 8 byte (basically just perform __bpf_assert_check) before dispatching to the bpf_cmp. WDYT? Or just enforce that in bpf_cmp (but that might be too strict for general use of bpf_cmp on its own, so might be better to have a separate macro). Apart from that, consider Acked-by: Kumar Kartikeya Dwivedi <memor@xxxxxxxxx> for the whole set.