Re: [PATCH] locking/refcount: add sparse annotations to dec-and-lock functions

From: Linus Torvalds
Date: Thu Jan 02 2020 - 20:36:13 EST


On Mon, Dec 30, 2019 at 3:38 PM Luc Van Oostenryck
<luc.vanoostenryck@xxxxxxxxx> wrote:
>
> One of the simplest situation with these conditional locks is:
>
> if (test)
> lock();
>
> do_stuff();
>
> if (test)
> unlock();
>
> No program can check that the second test gives the same result than
> the first one, it's undecidable. I mean, it's undecidable even on
> if single threaded and without interrupts. The best you can do is
> to simulate the whole thing (and be sure your simulation will halt).

No, no.

It's undecidable in the general case, but it's usually actually
trivially decidable in most real-world kernel cases.

Because "test" tends to be an argument to the function (or one bit of
an argument), and after it has been turned into SSA form, it's
literally using the same exact register for the conditional thanks to
CSE and simplification.

Perhaps not every time, but I bet it would be most times.

So I guess sparse could in theory notice that certain basic blocks are
conditional on the same thing, so if one is done, then the other is
always done (assuming there is conditional branch out in between, of
course).

IOW, the context tracking *could* do check son a bigger state than
just one basic block. It doesn't, and it would probably be painful to
do, but it's certainly not impossible.

So to make a trivial example for sparse:

extern int testfn(int);
extern int do_something(void);

int testfn(int flag)
{
if (flag & 1)
__context__(1);
do_something();
if (flag & 1)
__context__(-1);
}

this causes a warning:

c.c:4:5: warning: context imbalance in 'testfn' - different lock
contexts for basic block

because "do_something()" is called with different lock contexts. And
that's definitely a real issue. But if we were to want to extend the
"make sure we enter/exit with the same lock context", we _could_ do
it, because look at the linearization:

testfn:
.L0:
<entry-point>
and.32 %r2 <- %arg1, $1
cbr %r2, .L1, .L2
.L1:
context 1
br .L2
.L2:
call.32 %r4 <- do_something
cbr %r2, .L3, .L5
.L3:
context -1
br .L5
.L5:
ret.32 UNDEF

becasue the conditional branch always uses "%r2" as the conditional.
Notice? Not at all undecideable, and it would not be *impossible* to
say that "we can see that all context changes are conditional on %r2
not being true".

So sparse has already done all the real work to know that the two "if
(test)" conditionals test the exact same thing. We _know_ that the
second test has the same result as the first test, we're using the
same SSA register for both of them!

Linus