This is an archive of the discontinued LLVM Phabricator instance.

[RISCV] Fix an inconsistency with compatible load/store handling
ClosedPublic

Authored by reames on May 27 2022, 3:25 PM.

Details

Summary

Once we've computed the incoming predecessor state, we can use the same compatibility check to decide if we need to insert a vsetvli before it. We in fact did this during the data flow (phase 1 and 2), but skipped doing when using the result (phase 3).

The test changes show minor improvements, but the actual motivation is to fix a case where strict-asserts fail. I haven't yet managed to reduce a test case down to anything sensible, will update if I manage.

Diff Detail

Event Timeline

reames created this revision.May 27 2022, 3:25 PM
Herald added a project: Restricted Project. · View Herald TranscriptMay 27 2022, 3:25 PM
reames requested review of this revision.May 27 2022, 3:25 PM
Herald added a project: Restricted Project. · View Herald TranscriptMay 27 2022, 3:25 PM
reames updated this revision to Diff 433141.May 31 2022, 11:03 AM

Add a reduced test which violates strict asserts without this change. It can't be precommitted as strict asserts are currently enabled in tree, and thus it crashes by default.

I suppose the test could be added as a separate file with strict assertions disabled. But I only think that's valuable it if there's a visible codegen bug in that test that we're fixing here. If it's just that the strict assertions trip but the codegen happens to come out okay then adding the test in this patch is best, if you ask me.

As for the change itself, I'm afraid don't understand the precise issue. In phase 1 and 2 if the current info isn't valid we don't do any compatibility checks at all, do we? We just assign the NewInfo to BBInfo.Change. So I'm a bit confused where you say "We can use the same compatibility check [...] We in fact did this during the data flow (phase 1 and 2)". Also I think the use of "can" suggests this is option we have available to us, where in fact we're fixing a bug? So isn't it required?

I suppose the test could be added as a separate file with strict assertions disabled. But I only think that's valuable it if there's a visible codegen bug in that test that we're fixing here. If it's just that the strict assertions trip but the codegen happens to come out okay then adding the test in this patch is best, if you ask me.

There is a small codegen diff without small asserts, both versions are "correct". (i.e. it is not fixing an active miscompile, just the assertion failure.) I do not plan to precommit unless a reviewer wants it.

As for the change itself, I'm afraid don't understand the precise issue. In phase 1 and 2 if the current info isn't valid we don't do any compatibility checks at all, do we? We just assign the NewInfo to BBInfo.Change. So I'm a bit confused where you say "We can use the same compatibility check [...] We in fact did this during the data flow (phase 1 and 2)". Also I think the use of "can" suggests this is option we have available to us, where in fact we're fixing a bug? So isn't it required?

Let me lay it out for you.

In phase 1, we have no block predecessor state, and thus start with invalid. We hit the store and use the state of the store. That's fine as a stating exit state, but may get further refined in phase 2.

In phase 2, we have predecessor states and merge them. This state may be compatible with the store. If so, we use the incoming state and do *not* change the state at the store. Thus, if the store is the only instruction in the block, the output state is the input state.

In phase 3, we have the same predecessor states. Currently, we don't consider the fact the store may be compatible (i.e. we don't pass MI), and thus don't apply the store compat rule. As such, we select the state of the store (different from phase 2) and propagate that forward. We get to the end of block with a different state in this case.

In my description, my wording may be a bit sloppy. The bug is that we not consistent between phase 2 and phase 3. (phase 1 is somewhat irrelevant since we don't have the predecessor states). I can adjust the submit comment if you have suggestions on how to clarify.

Honestly, this isn't really an "interesting" bug. We just have two copies of the same code which are supposed to be the same, and they aren't. We should definitely common up this code, but I wanted to do that post bugfix.

frasercrmck accepted this revision.Jun 2 2022, 1:22 AM

There is a small codegen diff without small asserts, both versions are "correct". (i.e. it is not fixing an active miscompile, just the assertion failure.) I do not plan to precommit unless a reviewer wants it.

No, that's fine. Cheers.

Let me lay it out for you.

In phase 1, we have no block predecessor state, and thus start with invalid. We hit the store and use the state of the store. That's fine as a stating exit state, but may get further refined in phase 2.

In phase 2, we have predecessor states and merge them. This state may be compatible with the store. If so, we use the incoming state and do *not* change the state at the store. Thus, if the store is the only instruction in the block, the output state is the input state.

In phase 3, we have the same predecessor states. Currently, we don't consider the fact the store may be compatible (i.e. we don't pass MI), and thus don't apply the store compat rule. As such, we select the state of the store (different from phase 2) and propagate that forward. We get to the end of block with a different state in this case.

Thanks for the explanation! I think my confusion arose because I saw phase 2 starting with predecessor state (presumably valid) and phase 3 starting with invalid state. I missed the fact that if the phase 3 state is invalid we take the predecessor state before checking compatibility. So yeah I see how the compatibility checks are different between phases 2 and 3.

In my description, my wording may be a bit sloppy. The bug is that we not consistent between phase 2 and phase 3. (phase 1 is somewhat irrelevant since we don't have the predecessor states). I can adjust the submit comment if you have suggestions on how to clarify.

Maybe something along the lines of "Once we've computed the incoming predecessor state, we should use the same compatibility check with knowledge of MI as we did in phase 2 in order to be consistent across all phases." ? It's really just the "can" that throws me off the most.

We just have two copies of the same code which are supposed to be the same, and they aren't. We should definitely common up this code, but I wanted to do that post bugfix.

SGTM.

Anyway, this patch LGTM. I also ran our internal testing before and after this patch, and everything passes with this patch applied. I saw lots of assertions before.

This revision is now accepted and ready to land.Jun 2 2022, 1:22 AM
This revision was landed with ongoing or failed builds.Jun 2 2022, 8:04 AM
This revision was automatically updated to reflect the committed changes.