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

From: Luc Van Oostenryck
Date: Thu Jan 02 2020 - 21:20:39 EST

On Thu, Jan 02, 2020 at 05:35:34PM -0800, Linus Torvalds wrote:
> 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.

Yes, sure. I was, in fact, speaking for for all the cases where
'test' is more complex than an argument or local var. When I looked
at these false warnings about context imbalance, maybe 18 months ago,
my vague impression was that in most cases the test contained a pointer
dereference or worse. But I didn't look much.

> 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!

Absolutely. I'm more than willing to look at this but I just fear
that in most cases the conditional is more complex. I'll make
some investigations.

-- Luc