This is an archive of the discontinued LLVM Phabricator instance.

GlobalISel: Enable the legalization of G_MERGE_VALUES and G_UNMERGE_VALUES
ClosedPublic

Authored by volkan on Nov 8 2017, 4:26 PM.

Details

Summary

LegalizerInfo assumes all G_MERGE_VALUES and G_UNMERGE_VALUES instructions are legal, so it is not possible to legalize vector operations on illegal vector types. This patch fixes the problem by removing the related check and adding default actions for G_MERGE_VALUES and G_UNMERGE_VALUES.

Diff Detail

Event Timeline

volkan created this revision.Nov 8 2017, 4:26 PM
kristof.beyls added inline comments.Nov 9 2017, 1:25 AM
lib/CodeGen/GlobalISel/LegalizerInfo.cpp
67–88

This is in the class that contains target-independent info on how to legalize operations and types.
Therefore, I don't understand how this could be the right place to specify that specific vector widths are legal and others aren't.
I think this must go in target-specific code.

207–208

This is very generic code, and having explicit checks for specific Gopcodes feels wrong.
Should the if test instead of reading

if ((MI.getOpcode() == TargetOpcode::G_MERGE_VALUES ||
         MI.getOpcode() == TargetOpcode::G_UNMERGE_VALUES) &&
        TypeIdx == 1)

be more something like the following?
With some of the helper functions maybe still needing to be written:

if (hasVariableNumberOfOperands(MI.getOpcode() && TypeIdx == numberOfTypeIndices(MI.getOpcode())

Also, I fail to understand the need for this logic here with the little documentation that exists about G_MERGE_VALUES and G_UNMERGE_VALUES in include/llvm/Target/GenericOpcodes.td.
Are all of the operands in the variable-number-of-operands in G_MERGE_VALUES of the same type?
If so why do you need to get the Type of the last operand, instead of just any other operand?
If not, how can picking an action based on the type of the last operand by correct for the other operands that are of a different type?
It probably shows that I'm pretty confused here. I think I'd need quite a bit of explanation starting from exactly what constraints exist on G_(UN)MERGE_VALUES operands, the relationship with the TypeIdx, and going from there, maybe an example of what issue this code is trying to solve?

volkan added inline comments.Nov 9 2017, 3:23 PM
lib/CodeGen/GlobalISel/LegalizerInfo.cpp
67–88

I thought about this, but then I decided to handle the common types here. My concern was targets use these operations in order to legalize some other operations. For example, in test/CodeGen/X86/GlobalISel/legalize-add.mir, there is a function called test_add_i64 as below.

bb.1 (%ir-block.0):
  %0(s64) = IMPLICIT_DEF
  %1(s64) = IMPLICIT_DEF
  %2(s64) = G_ADD %0, %1
  RET 0

Here, Legalizer builds G_MERGE_VALUES and G_UNMERGE_VALUES to extract 32-bit values, but they are not actually legal. The patch Aditya proposed (https://reviews.llvm.org/D39267) should fix this problem by processing the legalizer artifacts after legalizing all other instructions.

I'll move this to target-specific code and create another diff.

207–208

Are all of the operands in the variable-number-of-operands in G_MERGE_VALUES of the same type?

Yes, G_MERGE_VALUES concatenates multiple registers of the same size into a wider register and G_UNMERGE_VALUES extracts multiple registers with the specified size. These instructions has variable number of operands, but there is only one source type and one destination type as all sources for G_MERGE_VALUES and all destination for G_UNMERGE_VALUES must be the same type.

Should the if test instead of reading be more something like the following?

That would be incorrect because other instruction with variable number of operands may have other operands with different types. For example:

def G_OPCODE : Instruction {
  let OutOperandList = (outs type0:$dst);
  let InOperandList = (ins type1:$src0, type2:src1, ..., variable_ops);
}

So, this check is okay only for these instructions because they have size constraints.

I think I'd need quite a bit of explanation starting from exactly what constraints exist on G_(UN)MERGE_VALUES operands, the relationship with the TypeIdx, and going from there, maybe an example of what issue this code is trying to solve?

Simply, I'm trying to set legalizer actions for these instruction, but not for every operand. The existing definition for G_MERGE_VALUES is:

def G_MERGE_VALUES : Instruction {
  let OutOperandList = (outs type0:$dst);
  let InOperandList = (ins variable_ops);
  let hasSideEffects = 0;
}

In this case, there is no way to legalize source operands because MI.getDesc().getNumOperands() returns 1. We can fix this by replacing MI.getDesc().getNumOperands() with MI.getNumOperands(), but this means we need to set actions for every possible case.

I'll add a comment to explain why we are doing this.

volkan updated this revision to Diff 122353.Nov 9 2017, 4:08 PM

Updated based on Kristof's comments.

dsanders added inline comments.Nov 9 2017, 4:19 PM
lib/CodeGen/GlobalISel/LegalizerInfo.cpp
77

Isn't this one redundant? We've already said 2<=Size<8 are unsupported

207–208

Do we really need to treat G_MERGE_VALUE's specially here? G_UNMERGE_VALUES needs it because type1 is only on the last operand, but for G_MERGE_VALUES, type1 is on operands 1 and higher.

Should the if test instead of reading be more something like the following?

That would be incorrect because other instruction with variable number of operands
may have other operands with different types. For example:

def G_OPCODE : Instruction {

let OutOperandList = (outs type0:$dst);
let InOperandList = (ins type1:$src0, type2:src1, ..., variable_ops);

}
So, this check is okay only for these instructions because they have size constraints.

So long as variable_ops is equivalent to specifying typeN (where N is 1 higher than the highest number used elsewhere in the definition) a variable number of times, I think Kristof's if-statement would work for your example. Either way, I agree with the main point that we should be driving this with a generic mechanism rather than a special case.
Also, it seems odd to me that we have type constraints that aren't described.

Maybe, we should have a variable_ops_typeN that indicates the type constraint and leaves some information in the MCInstrDesc we can inspect during this code.

volkan added inline comments.Nov 10 2017, 12:57 PM
lib/CodeGen/GlobalISel/LegalizerInfo.cpp
207–208

Do we really need to treat G_MERGE_VALUE's specially here? G_UNMERGE_VALUES needs it because type1 is only on the last operand, but for G_MERGE_VALUES, type1 is on operands 1 and higher.

Right, it seems like I was trying to get the last operand for G_MERGE_VALUES for no reason. I'll remove the special case for G_MERGE_VALUES.

So long as variable_ops is equivalent to specifying typeN (where N is 1 higher than the highest number used elsewhere in the definition) a variable number of times, I think Kristof's if-statement would work for your example.

I think it would not work because $src1 doesn't have to be the same type with variable_ops. MRI.getType(MI.getOperand(LastTypeIdx).getReg()) must return type2.

volkan updated this revision to Diff 122513.Nov 10 2017, 1:09 PM

Removed the special case for G_MERGE_VALUES.

kristof.beyls added inline comments.Nov 13 2017, 8:50 AM
lib/CodeGen/GlobalISel/LegalizerInfo.cpp
207–208

To make this easier to read, I think it may make sense to factor this out into a separate function, e.g. with a name like "getTyFromTypeIdx(MI, TypeIdx)". I'm not sure on the name, but doing it this way would make this code a lot easier to understand, I think.

lib/Target/AArch64/AArch64LegalizerInfo.cpp
355

I haven't thought this through at all, but I'm a bit surprised v8s64 also isn't legal if v6s64 is?
Similarly for S256 if s192 is legal?

volkan updated this revision to Diff 122941.Nov 14 2017, 4:07 PM
  • Handled missing cases for AArch64.
  • Rebased.
volkan marked 2 inline comments as done.Nov 14 2017, 4:08 PM
volkan added inline comments.
lib/Target/AArch64/AArch64LegalizerInfo.cpp
355

Added missing cases.

dsanders added inline comments.Nov 29 2017, 10:48 AM
include/llvm/Target/GenericOpcodes.td
612

I know this isn't from your patch but Concatenante -> Concatenate

lib/CodeGen/GlobalISel/LegalizerInfo.cpp
185–189

This is a nitpick but we should early-return here

test/CodeGen/AArch64/GlobalISel/legalize-merge-values.mir
2

Could you add a comment explaining the intent of this test?

test/CodeGen/AArch64/GlobalISel/legalize-nonpowerof2eltsvec.mir
21–34

I'm not sure why this test has changed from s32's to s64's. Could you explain?

Also, the name is inconsistent with the types used

volkan updated this revision to Diff 124875.Nov 29 2017, 10:39 PM

Updated based on Daniel's comments.

volkan marked 3 inline comments as done.Nov 29 2017, 10:49 PM
volkan added inline comments.
test/CodeGen/AArch64/GlobalISel/legalize-nonpowerof2eltsvec.mir
21–34

I'm not sure why this test has changed from s32's to s64's. Could you explain?

It's because <3 x s32> is not legal.

dsanders accepted this revision.Nov 30 2017, 11:30 AM

LGTM

test/CodeGen/AArch64/GlobalISel/legalize-nonpowerof2eltsvec.mir
21–34

That makes sense. In that case, changing the name is enough

This revision is now accepted and ready to land.Nov 30 2017, 11:30 AM
volkan closed this revision.Dec 1 2017, 12:19 AM
volkan marked an inline comment as done.