Subject: bug#46670: 28.0.50; [feature/native-comp] possible miscompilation affecting lsp-mode

From: Andrea Corallo <[email-masked]>
Date: Mon, 22 Feb 2021 11:16:37 +0000

Pip Cet <[email-masked]> writes:

> On Mon, Feb 22, 2021 at 9:38 AM Andrea Corallo <[email-masked]> wrote:
>> Pip Cet <[email-masked]> writes:
>> > On Sun, Feb 21, 2021 at 9:03 PM Andrea Corallo <[email-masked]> wrote:
>> >> Pip Cet <[email-masked]> writes:
>> >>
>> >> > On Sun, Feb 21, 2021 at 11:51 AM Pip Cet <[email-masked]> wrote:
>> >> >> Does the attached patch help? Andrea, is my analysis correct?
>> >> >
>> >> > CCing Andrea.
>> >> >
>> >> > In more detail, some debugging showed we were trying to intersect a
>> >> > "nil or t" constraint with a "sequence" constraint, the result being a
>> >> > null constraint (t is not a sequence). So if (assume (and a b)) was
>> >> > meant to imply the intersection of a and b, we're emitting it
>> >> > incorrectly.
>> >>
>> >> Hi Pip,
>> >>
>> >> thanks for looking into this.
>> >
>> > Thanks for your explanation!
>> >
>> >> 'and' is meant to imply intersection, so yeah... as you guess there's
>> >> some problem in the LIMPLE we generate :)
>> >
>> > Thanks, I was on the wrong track there.
>> >
>> >> The correct fix is to have `comp-add-cond-cstrs' rewrite the comparison
>> >> sequence as something like:
>> >>
>> >> (set #(mvar nil X t) #(mvar 42082358 1 t))
>> >> (set #(mvar 41121566 1 boolean) (call equal #(mvar 42082358 1 t) #(mvar 41665638 2 sequence)))
>> >> (cond-jump #(mvar 41121566 1 boolean) #(mvar nil nil null) bb_2_cstrs_0 bb_1)
>> >>
>> >> Where X is a new slot we add to the frame.  We will reference this slot
>> >> number in the assume instead of 1 so it does not get clobbered.
>> >
>> > Okay, I think I understand the problem (we don't do classical SSA and
>> > throw away the slot numbers), and your solution would avoid it, but it
>> > seems needlessly complicated to me.
>>
>> Correct, ATM the assumption is that we keep LIMPLE always as
>> "conventional SSA form".  This for a number of reasons but mainly it
>> greatly helps in maintaining the compiler simple.
>
> "Conventional" meaning "not quite SSA"? I'm just trying to understand,
> the decision seems correct to me, since we already ran stack slot
> allocation in the byte compiler and we want to reuse those
> assignments.

The SSA book [1] and others discuss conventional and transformed SSA
forms.  IIRC you should find references of these in 2.5 and where copy
propagation is discussed.

>> > Why create a new slot that isn't used anywhere? The value of the stack
>> > slot is clobbered by the (set ...), so we cannot emit any assumptions
>> > about that stack slot based on previous values it held.
>>
>> Yes but in this case (and probably others) we *have* to emit this
>> assumption.
>
> Why? Are you saying the compiler requires (assume ...) insns for
> correctness? If it does, I'm afraid that's a serious issue.

It requires that assume if we want to infer precisely here.  If we
give-up emitting this assume it will just works perfectly but we'll miss
to predict the return value as we should.

>> The best option is to decide that negative slot numbers are not rendered
>> into libgccjit IR and we consider these virtual to solve these kind of
>> cases.  IIRC we already do something similar for the -1 index so this
>> concept has just to be generalized a bit.
>
> Again, that does seem very complicated, and if it improves
> optimization we could probably improve it much more by modifying the
> byte compiler to pop arguments in the caller rather than the callee.

To me this sounds considerably more invasive, probably is because I'm
much more used to work with the native compiler code that with the byte
compiler one :)

I like the idea of the native compiler patch being less invasive as
possible, this was the line I tried to follow and I think so far it
paid.  I guess a number of readers would'd agree with this kind of
approach to begin with.

I think I should be able to work it out as discussed in one two evenings
and it might be useful for other cases in the future too, so it does not
sound as a big deal to me.

>> > In fact, all we need to do is tell comp-cond-cstrs-target-mvar to
>> > return nil more often, isn't it?
>>
>> Nope, the target mvar identified is the correct one, we just have ATM no
>> way to reference it reliably into the assume.
>
> We don't have to, let's just not emit an assume about a variable that
> we just introduced and that's never read?

We disagree :)

We don't need that mvar to render functional code that's agreed, but
still we still need to reference it in the assume (assumes are not
functional code as they are not rendered in final).

As the byte compiler does not care about propagating types and values it
can just clobber the variable, here we aim for more so we need it to
keep it live till the assume.

>> BTW applying your patch
>> is breaking quite some of the comp-tests-ret-type-spec-* tests :)
>
> Where do you keep those?
>
> I see the same number of test failures with and without the patch,
> running "make check". The failures seem unrelated, too...

They are in "test/src/comp-tests.el", those are the first I suggest to
run after having modified the compiler.  Isn't "make check" picking-up
those?

Regards

  Andrea

[1] <http://ssabook.gforge.inria.fr/latest/book.pdf>