This is an archive of the discontinued LLVM Phabricator instance.

[GlobalISel]: Fix bug where we can report GISelFailure on erased instructions
ClosedPublic

Authored by aditya_nandakumar on Mar 30 2017, 11:06 AM.

Details

Summary

The original instruction might get legalized and erased and expanded

into intermediate insts. An intermediate instruction might fail
legalization and we would report failure on an erased inst.
Instead now report failure on the first failing instruction

Diff Detail

Event Timeline

qcolombet edited edge metadata.Mar 30 2017, 12:01 PM

Hi Aditya,

If I understand correctly, the problem with the current reporting scheme is the MI we use to print the report may have been deleted, therefore we may access invalid memory.
I would suggest a less invasive change, pretty similar to what we do when we know an iterator is going to be invalidated:
What do you think of printing the MI in a string before calling legalization, then use that string in the error message?

Cheers,
-Quentin

include/llvm/CodeGen/GlobalISel/LegalizerHelper.h
27

Use a forward declaration at this point.

49

I don't particularly like the fact that the Helper needs to do reporting.
At least, I would expect TPC to be optional.

lib/CodeGen/GlobalISel/LegalizerHelper.cpp
63

It feels wrong to me to have reporting in here.
My concerns are:

  • We conflict reporting and doing the transformation
  • We may report failure on product of legalization. Hence, it may actually be difficult to understand where it comes from.

Thanks Quentin. Seems like printing the MI to a string and printing that instead might address all the concerns raised above.

ab edited edge metadata.Mar 30 2017, 12:56 PM

I considered and dismissed speculatively printing: it's a pretty expensive operation (especially when it needs to print an IR value, and it needs to recompute slot numbers).

Here's another idea I considered: have legalizeInstr return the instruction it failed on, in a std::pair<LegalizeResult, MachineInstr*>.

In practice, the external users of the LegalizeHelper API shouldn't care about LegalizeResult: we either return UnableToLegalize, or we return a success value (Legalized/AlreadyLegal. So we could simply return a MachineInstr*, which is nullptr if we succeeded. It would be weird to read though, so we could go extra fancy and have an Error that either contains a MachineInstr*, or is Error::success().

I considered and dismissed speculatively printing: it's a pretty expensive operation

I was expecting something like that, but I thought we could mitigate this by only pre-printing stuff in debug build. Non-debug would just fail with unable to legalize.

Here's another idea I considered: have legalizeInstr return the instruction it failed on, in a std::pair<LegalizeResult, MachineInstr*>.

How would that work with a deleted instruction?

Here's another idea I considered: have legalizeInstr return the instruction it failed on, in a std::pair<LegalizeResult, MachineInstr*>.

How would that work with a deleted instruction?

Talked with Ahmed offline. He was thinking in terms of the current instruction being legalized.
Also given this is going to be used only for our own debugging purposes, the "it may be difficult to understand where it comes from" argument I made is pointless.

Therefore, Aditya, please go with Ahmed's idea. I would just suggest to print (in a DEBUG statement) the instruction we process or have some optimization remarks just for us (i.e., not emitted if !NDEBUG).

In D31503#714507, @ab wrote:

I considered and dismissed speculatively printing: it's a pretty expensive operation (especially when it needs to print an IR value, and it needs to recompute slot numbers).

Here's another idea I considered: have legalizeInstr return the instruction it failed on, in a std::pair<LegalizeResult, MachineInstr*>.

In practice, the external users of the LegalizeHelper API shouldn't care about LegalizeResult: we either return UnableToLegalize, or we return a success value (Legalized/AlreadyLegal. So we could simply return a MachineInstr*, which is nullptr if we succeeded. It would be weird to read though, so we could go extra fancy and have an Error that either contains a MachineInstr*, or is Error::success().

Just to clarify here, (IIUC) returning either an Error (with MachineInstr*) or Error::success() would mean that we'd not be able to differentiate b/w Legalized and AlreadyLegal. Did you mean Expected<LegalizeResult>?

kristof.beyls added inline comments.Mar 31 2017, 12:08 AM
lib/CodeGen/GlobalISel/LegalizerHelper.cpp
59–60

This is probably a moot point now, now that it shows that using the "std::pair<LegalizeResult, MachineInstr*>"-approach looks a lot more promising, but I think this code probably still suffers the same problem as before:
After legalizeInstrStep, TmpMI can be removed from the MachineBasicBlock, and TmpMI->getParent() will then probably return a nullptr, causing a segmentation fault.

I'm afraid I don't have a good insight on whether we'd still want to be able to differentiate between "AlreadyLegal" and "Legalized" in the "std::pair<LegalizeResult, MachineInstr*>"-approach.

t.p.northover edited edge metadata.Mar 31 2017, 2:05 PM

I'm not entirely keen on the pair solution. It seems to be designing the API around the (temporary) situation where legalization can routinely fail and need to fall back. In the long term most of these will be assertion failures.

Actually, I'm still unclear why the legalization doesn't know it'll fail before getting bogged down in erasing instructions. Normal instructions created while doing a single-step legalization would be added to the worklist to be seen by the top-level Legalizer wouldn't they?

Tim also suggested that we move the implementation of LegalizerHelper::LegalizeInstr (with the worklist) into Legalizer.cpp. This would work fine too (except we would need to make LegalizeHelper::MachineIRBuilder public member so we can start and stop recording insertions).

Sounds good to me.

Update for feedback from Tim/Quentin.
Move the legalizeInstr to Legalizer and report failure on first instruction that fails.
Also this needs MIRBuilder to be accessible outside of LegalizeHelper (to set/unset RecordInsertions).

ab accepted this revision.Apr 6 2017, 4:35 PM

I'm not a big fan of this approach (because the iterative logic seems like it does belong in the helper), but it's mostly fine (we don't have users other than Legalizer), and it sounds like Tim and Quentin are on board. So, LGTM.

Can you add a test, say in arm64-fallback.ll? I was able to hit the same problem with:

define i16 @f(half* %p) {
  %tmp0 = load half, half* %p
  %tmp1 = fptoui half %tmp0 to i16
  ret i16 %tmp1
}
This revision is now accepted and ready to land.Apr 6 2017, 4:35 PM

Committed in 299802