[llvm-dev] [cfe-dev] CFG simplification question, and preservation of branching in the original code

Joan Lluch via llvm-dev llvm-dev at lists.llvm.org
Fri Oct 4 03:46:27 PDT 2019


Hi all,

As a continuation of this thread, I was about to fill a bug report requesting the modification of DAGTypeLegalizer::ExpandIntRes_SIGN_EXTEND, in order to avoid the creation of Shifts for targets with no native support. For example by generating a ‘select' equivalent to   a<0 ? -1 : 0   instead of an arithmetic shift right. For targets with no multiple shifts or word extension instructions, the select is much cheaper.

However, I found that the early InstCombine pass spoils such optimisation by creating shifts on their own as a transform of (supposedly) equivalent code. 

Consider this:

int word_extend( int a )
{
  return a<0 ? -1 : 0;
}

This is converted into this IR-Code (It’s 16 bits because it’s the MSP430 target)

; Function Attrs: norecurse nounwind readnone
define dso_local i16 @word_extend(i16 %a) local_unnamed_addr #0 {
entry:
  %a.lobit = ashr i16 %a, 15
  ret i16 %a.lobit
}

Which the backend then turns into this

	.globl	word_extend
	.p2align	1
	.type	word_extend, at function
word_extend:
	swpb	r12
	sxt	r12
	rra	r12
	rra	r12
	rra	r12
	rra	r12
	rra	r12
	rra	r12
	rra	r12
	ret
.Lfunc_end2:

For this particular target, a simple select would be much cheaper. Ideally, the frontend should have generated this:

; Function Attrs: norecurse nounwind readnone
define dso_local i16 @word_extend(i16 %a) local_unnamed_addr #0 {
entry:
  %cmp = icmp slt i16 %a, 0 
  %cond = select i1 %cmp, i16 -1, i16 0
  ret i16 %cond
}

Which the backend would convert into basically a ‘move', and a ‘branch' skipping an instruction, which would be much cheaper.

Unfortunately, the above IR code is combined back into a SignExtend by the backend, thus recreating the undesired shift, and producing again bad code for the MSP430.

The following IR code also causes sub-optimal code on the MSP430 due to combines into ZeroExtend and then into shifts in DAGCombiner::SimplifySelectC, DAGCombiner::foldSelectCCToShiftAnd and static SDValue foldExtendedSignBitTest :

; Function Attrs: norecurse nounwind readnone
define dso_local i16 @word_extend(i16 %a) local_unnamed_addr #0 {
entry:
  %cmp = icmp slt i16 %a, 0 
  %cond = select i1 %cmp, i16 1, i16 0
  ret i16 %cond
}

Variations like this also cause the same issue:

; Function Attrs: norecurse nounwind readnone
define dso_local i16 @word_extend(i16 %a) local_unnamed_addr #0 {
entry:
  %cmp = icmp slt i16 %a, 0 
  %cond = select i1 %cmp, i16 2, i16 0
  ret i16 %cond
}

So for now, we would need to fix these:

DAGCombiner::SimplifySelectCC
DAGCombiner::foldSelectCCToShiftAnd
static SDValue foldExtendedSignBitTest

I think this can be fixed in a similar way than https://reviews.llvm.org/D68397 <https://reviews.llvm.org/D68397>
I posted a bug report for that https://bugs.llvm.org/show_bug.cgi?id=43559

I strongly suggest the above gets fixed. HOWEVER, even after the DAG combiner is fixed, the issues will remain due to InstCombine doing essentially the same thing much earlier. Consider code like this:

int test0( int a )
{
  return a<0 ? -1 : 0;
}

int test1( int a )
{
  return a<0 ? 1 : 0;
}

int test2( int a )
{
  return a<0 ? 2 : 0;
}

int test3( int a )
{
  return a<0 ? 3 : 0;
}

In all cases, InstCombine converts the above into Shifts, right into the IR, which the backend can’t do anything about. This is why I suggest that something must be done at the IR Optimize level to deal with such undesired situations. I think it should not be that difficult for someone with experience on it, because incidentally, the -O0 option almost produces the desired result, except for all that stack storage of variables and registers.

I hope that I was able to explain the nature of the problem in an effective way.

Thanks.

John


> On 3 Oct 2019, at 11:26, Joan Lluch <joan.lluch at icloud.com <mailto:joan.lluch at icloud.com>> wrote:
> 
> Hi all,
> 
>> On 2 Oct 2019, at 14:34, Sanjay Patel <spatel at rotateright.com <mailto:spatel at rotateright.com>> wrote
>> Providing target options/overrides to code that is supposed to be target-independent sounds self-defeating to me. I doubt that proposal would gain much support.
>> Of course, if you're customizing LLVM for your own out-of-trunk backend, you can do anything you'd like if you're willing to maintain the custom patches.
>> I suggest that you file a bug report showing an exact problem caused by 1 of the icmp/select transforms. 
>> If it can be done using an in-trunk backend like AVR or MSP430, then we'll have a real example showing the harm and hopefully better ideas about how to solve the bug.
> 
> 
> I fully understand your statement about target-independent code. This has no possible discussion in a general way. However one of my points is that at least some of this code is not really that “target independent” because in fact it depends on cheap features available on ALL targets, which is not the case. That’s why I think that some flexibility must be given. I’m not advocating for “target options/overrides”, but general ones that can be optionally chosen by particular targets. I think that at the end of the day it’s just a matter of language, not a matter of fact.
> 
> If we look at the problem from some distance, we find that there are already options to alter IR code generation. For example the -phi-node-folding-threshold option is one of them. Ok, no target seems to use that particular one, but it’s there already, and it modifies LLVM-IR generation by telling how aggressively LLVM turns branches into selects. I found that this option produces better  LLVM-IR input for the target I’m working on, as well as both the MSP430 and AVR targets. 
> 
> I can of course try to apply my custom patches and maintain them, but although I was able to implement a backend, the complexity and extend of the whole thing is beyond my abilities and resources. Furthermore, I regard this as a LLVM wide problem, and that’s why I try to raise some awareness.
> 
> Following your suggestion, I just filed a bug with a very simple example that to make things easier is not attributable to the InstCombine pass in this case but to transformations happening during LLVM codegen. 
> https://bugs.llvm.org/show_bug.cgi?id=43542 <https://bugs.llvm.org/show_bug.cgi?id=43542>
> 
> John
> 
>> On 2 Oct 2019, at 14:34, Sanjay Patel <spatel at rotateright.com <mailto:spatel at rotateright.com>> wrote:
>> 
>> On Tue, Oct 1, 2019 at 5:51 PM Joan Lluch <joan.lluch at icloud.com <mailto:joan.lluch at icloud.com>> wrote:
>> In order to give better support to current and future implementations of small processor targets, I wonder if instead of attempting to implement a general solution, we could implement a set of compiler flags (or code hooks) that targets could use to optionally disable undesired IR optimiser actions, however not affecting anything of the current behaviour as long as these flags are not used. If we had that, nothing would need to be done on the existing targets, particularly the major ones, and yet, new backend developments and small processor (AVR, MSP430) could potentially benefit from that. My opinion is that the availability of such options will not defeat the “almost entirely target-independent” nature of the IR optimiser, as the transformations would still be target-agnostic, except that targets would be able to decide which ones are more convenient for them, or disable the non desirable ones. Does this sound ok or feasible?.
>> 
>> Providing target options/overrides to code that is supposed to be target-independent sounds self-defeating to me. I doubt that proposal would gain much support.
>> Of course, if you're customizing LLVM for your own out-of-trunk backend, you can do anything you'd like if you're willing to maintain the custom patches.
>> I suggest that you file a bug report showing an exact problem caused by 1 of the icmp/select transforms. 
>> If it can be done using an in-trunk backend like AVR or MSP430, then we'll have a real example showing the harm and hopefully better ideas about how to solve the bug.
>>  
>> 
>> On Tue, Oct 1, 2019 at 5:51 PM Joan Lluch <joan.lluch at icloud.com <mailto:joan.lluch at icloud.com>> wrote:
>> Hi Sanjay,
>> 
>> Thanks for your reply.
>> 
>>> So yes, the IR optimizer (instcombine is the specific pass) sometimes turns icmp (and select) sequences into ALU ops. Instcombine is almost entirely *target-independent* and should remain that way. The (sometimes unfortunate) decision to create shifts were made based on popular targets of the time (PowerPC and/or x86), and other targets may have suffered because of that.
>> 
>> Yes, that’s what I actually found that I didn’t anticipate.
>> 
>>> We've been trying to reverse those canonicalizations in IR over the past few years when the choice is clearly not always optimal, but it's not easy. To avoid perf regressions, you first have to make the backend/codegen aware of the alternate pattern that includes icmp/select and transform that to math/logic (translate instcombine code to DAGCombiner). Then, you have to remove the transform from instcombine and replace it with the reverse transform. This can uncover infinite loops and other problems within instcombine.
>> 
>> I understand that. In any case, I am glad that at least this is acknowledged as some kind of flaw of the LLVM system, particularly for the optimal implementation of small processor backends. As these targets generally have cheap branches and do not generally have selects or multi-bit shifts, they hardly benefit from transformations involving shifts or aggressively attempting to replace jumps.
>> 
>>> So to finally answer the question: If you can transform the shift into an alternate sequence with a "setcc" DAG node in your target's "ISelLowering" code, that's the easiest way forward. Otherwise, you have to weigh the impact of each target-independent transform on every target.
>> 
>> Yes, that’s what I have been doing all the time. My backend contains a lot of code only to reverse undesired LLVM transformations, and yet the resulting final assembly is not as good as it could be, because it is often hard or impossible to identify all sources of improvement. It’s ironical that some older, less sophisticated compilers (and GCC) produce /much/ better code than LLVM for simple architectures.
>> 
>> In order to give better support to current and future implementations of small processor targets, I wonder if instead of attempting to implement a general solution, we could implement a set of compiler flags (or code hooks) that targets could use to optionally disable undesired IR optimiser actions, however not affecting anything of the current behaviour as long as these flags are not used. If we had that, nothing would need to be done on the existing targets, particularly the major ones, and yet, new backend developments and small processor (AVR, MSP430) could potentially benefit from that. My opinion is that the availability of such options will not defeat the “almost entirely target-independent” nature of the IR optimiser, as the transformations would still be target-agnostic, except that targets would be able to decide which ones are more convenient for them, or disable the non desirable ones. Does this sound ok or feasible?.
>> 
>> John
>> 
> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20191004/a5d00201/attachment-0001.html>


More information about the llvm-dev mailing list