[GlobalISel] Add G_ASSERT_ZEXT

This adds a generic opcode which communicates that a type has already been
zero-extended from a narrower type.

This is intended to be similar to AssertZext in SelectionDAG.

For example,

```
%x_was_extended:_(s64) = G_ASSERT_ZEXT %x, 16
```

Signifies that the top 48 bits of %x are known to be 0.

This is useful in cases like this:

```
define i1 @zeroext_param(i8 zeroext %x) {
  %cmp = icmp ult i8 %x, -20
  ret i1 %cmp
}
```

In AArch64, `%x` must use a 32-bit register, which is then truncated to a 8-bit
value.

If we know that `%x` is already zero-ed out in the relevant high bits, we can
avoid the truncate.

Currently, in GISel, this looks like this:

```
_zeroext_param:
  and w8, w0, #0xff ; We don't actually need this!
  cmp w8, #236
  cset w0, lo
  ret
```

While SDAG does not produce the truncation, since it knows that it's
unnecessary:

```
_zeroext_param:
  cmp w0, #236
  cset w0, lo
  ret
```

This patch

- Adds G_ASSERT_ZEXT
- Adds MIRBuilder support for it
- Adds MachineVerifier support for it
- Documents it

It also puts G_ASSERT_ZEXT into its own class of "hint instruction." (There
should be a G_ASSERT_SEXT in the future, maybe a G_ASSERT_ALIGN as well.)

This allows us to skip over hints in the legalizer etc. These can then later
be selected like COPY instructions or removed.

Differential Revision: https://reviews.llvm.org/D95564
This commit is contained in:
Jessica Paquette 2021-01-27 12:09:05 -08:00
parent ac70a53653
commit 24261729a4
10 changed files with 211 additions and 1 deletions

View File

@ -735,3 +735,37 @@ An alignment value of `0` or `1` mean no specific alignment.
.. code-block:: none
%8:_(p0) = G_DYN_STACKALLOC %7(s64), 32
Optimization Hints
------------------
These instructions do not correspond to any target instructions. They act as
hints for various combines.
G_ASSERT_ZEXT
^^^^^^^^^^^^^
Signifies that the contents of a register were previously zero-extended from a
smaller type.
The smaller type is denoted using an immediate operand. For scalars, this is the
width of the entire smaller type. For vectors, this is the width of the smaller
element type.
.. code-block:: none
%x_assert:_(s32) = G_ASSERT_ZEXT %x(s32), 16
%y_assert:_(<2 x s32>) = G_ASSERT_ZEXT %y(<2 x s32>), 16
G_ASSERT_ZEXT acts like a restricted form of copy.
The source and destination registers must
- Be virtual
- Belong to the same register class
- Belong to the same register bank
It should always be safe to
- Look through the source register
- Replace the destination register with the source register

View File

@ -810,6 +810,12 @@ public:
/// \return a MachineInstrBuilder for the newly created instruction.
MachineInstrBuilder buildCopy(const DstOp &Res, const SrcOp &Op);
/// Build and insert \p Res = G_ASSERT_ZEXT Op, Size
///
/// \return a MachineInstrBuilder for the newly created instruction.
MachineInstrBuilder buildAssertZExt(const DstOp &Res, const SrcOp &Op,
unsigned Size);
/// Build and insert `Res = G_LOAD Addr, MMO`.
///
/// Loads the value stored at \p Addr. Puts the result in \p Res.

View File

@ -36,6 +36,14 @@ inline bool isPreISelGenericOpcode(unsigned Opcode) {
inline bool isTargetSpecificOpcode(unsigned Opcode) {
return Opcode > TargetOpcode::PRE_ISEL_GENERIC_OPCODE_END;
}
/// \returns true if \p Opcode is an optimization hint opcode which is not
/// supposed to appear after ISel.
inline bool isPreISelGenericOptimizationHint(unsigned Opcode) {
return Opcode >= TargetOpcode::PRE_ISEL_GENERIC_OPTIMIZATION_HINT_START &&
Opcode <= TargetOpcode::PRE_ISEL_GENERIC_OPTIMIZATION_HINT_END;
}
} // end namespace llvm
#endif

View File

@ -213,6 +213,14 @@ HANDLE_TARGET_OPCODE(ICALL_BRANCH_FUNNEL)
/// This is something we might want to relax, but for now, this is convenient
/// to produce diagnostics.
/// Instructions which should not exist past instruction selection, but do not
/// generate code. These instructions only act as optimization hints.
HANDLE_TARGET_OPCODE(G_ASSERT_ZEXT)
HANDLE_TARGET_OPCODE_MARKER(PRE_ISEL_GENERIC_OPTIMIZATION_HINT_START,
G_ASSERT_ZEXT)
HANDLE_TARGET_OPCODE_MARKER(PRE_ISEL_GENERIC_OPTIMIZATION_HINT_END,
G_ASSERT_ZEXT)
/// Generic ADD instruction. This is an integer add.
HANDLE_TARGET_OPCODE(G_ADD)
HANDLE_TARGET_OPCODE_MARKER(PRE_ISEL_GENERIC_OPCODE_START, G_ADD)

View File

@ -1337,3 +1337,15 @@ def G_MEMSET : GenericInstruction {
let hasSideEffects = false;
let mayStore = true;
}
//------------------------------------------------------------------------------
// Optimization hints
//------------------------------------------------------------------------------
// Asserts that an operation has already been zero-extended from a specific
// type.
def G_ASSERT_ZEXT : GenericInstruction {
let OutOperandList = (outs type0:$dst);
let InOperandList = (ins type0:$src, untyped_imm_0:$sz);
let hasSideEffects = false;
}

View File

@ -240,6 +240,12 @@ MachineInstrBuilder MachineIRBuilder::buildCopy(const DstOp &Res,
return buildInstr(TargetOpcode::COPY, Res, Op);
}
MachineInstrBuilder MachineIRBuilder::buildAssertZExt(const DstOp &Res,
const SrcOp &Op,
unsigned Size) {
return buildInstr(TargetOpcode::G_ASSERT_ZEXT, Res, Op).addImm(Size);
}
MachineInstrBuilder MachineIRBuilder::buildConstant(const DstOp &Res,
const ConstantInt &Val) {
LLT Ty = Res.getLLTTy(*getMRI());

View File

@ -941,6 +941,41 @@ void MachineVerifier::verifyPreISelGenericInstruction(const MachineInstr *MI) {
// Verify properties of various specific instruction types
switch (MI->getOpcode()) {
case TargetOpcode::G_ASSERT_ZEXT: {
if (!MI->getOperand(2).isImm()) {
report("G_ASSERT_ZEXT expects an immediate operand #2", MI);
break;
}
Register Dst = MI->getOperand(0).getReg();
Register Src = MI->getOperand(1).getReg();
LLT DstTy = MRI->getType(Dst);
LLT SrcTy = MRI->getType(Src);
verifyVectorElementMatch(DstTy, SrcTy, MI);
int64_t Imm = MI->getOperand(2).getImm();
if (Imm <= 0) {
report("G_ASSERT_ZEXT size must be >= 1", MI);
break;
}
if (Imm >= SrcTy.getScalarSizeInBits()) {
report("G_ASSERT_ZEXT size must be less than source bit width", MI);
break;
}
if (MRI->getRegBankOrNull(Src) != MRI->getRegBankOrNull(Dst)) {
report("G_ASSERT_ZEXT source and destination register banks must match",
MI);
break;
}
if (MRI->getRegClassOrNull(Src) != MRI->getRegClassOrNull(Dst))
report("G_ASSERT_ZEXT source and destination register classes must match",
MI);
break;
}
case TargetOpcode::G_CONSTANT:
case TargetOpcode::G_FCONSTANT: {
LLT DstTy = MRI->getType(MI->getOperand(0).getReg());
@ -1594,7 +1629,8 @@ void MachineVerifier::visitMachineInstrBefore(const MachineInstr *MI) {
}
}
if (isPreISelGenericOpcode(MCID.getOpcode())) {
unsigned Opc = MCID.getOpcode();
if (isPreISelGenericOpcode(Opc) || isPreISelGenericOptimizationHint(Opc)) {
verifyPreISelGenericInstruction(MI);
return;
}

View File

@ -0,0 +1,21 @@
# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py
# RUN: llc -mtriple=aarch64 -run-pass=legalizer -verify-machineinstrs %s -o - | FileCheck %s
#
# Verify that hint instructions are ignored by the legalizer.
---
name: assert_zext
tracksRegLiveness: true
body: |
bb.0:
liveins: $w0, $w1
; CHECK-LABEL: name: assert_zext
; CHECK: %copy:_(s32) = COPY $w1
; CHECK: %hint:_(s32) = G_ASSERT_ZEXT %copy, 16
; CHECK: $w0 = COPY %hint(s32)
; CHECK: RET_ReallyLR implicit $w0
%copy:_(s32) = COPY $w1
%hint:_(s32) = G_ASSERT_ZEXT %copy, 16
$w0 = COPY %hint
RET_ReallyLR implicit $w0
...

View File

@ -0,0 +1,44 @@
# REQUIRES: aarch64-registered-target
# RUN: not --crash llc -verify-machineinstrs -mtriple aarch64 -run-pass none -o /dev/null %s 2>&1 | FileCheck %s
name: test
body: |
bb.0:
liveins: $x0, $w0
%0:_(s64) = COPY $x0
%1:_(<4 x s16>) = COPY $x0
%2:_(s32) = COPY $w0
; CHECK: *** Bad machine code: G_ASSERT_ZEXT expects an immediate operand #2 ***
; CHECK: instruction: %assert_zext_1:_(s64) = G_ASSERT_ZEXT
%assert_zext_1:_(s64) = G_ASSERT_ZEXT %0, %0
; CHECK: *** Bad machine code: G_ASSERT_ZEXT expects an immediate operand #2 ***
; CHECK: instruction: %assert_zext_2:_(s64) = G_ASSERT_ZEXT
%assert_zext_2:_(s64) = G_ASSERT_ZEXT %0, i8 8
; CHECK: *** Bad machine code: Type mismatch in generic instruction ***
; CHECK: instruction: %assert_zext_3:_(<2 x s32>) = G_ASSERT_ZEXT
; CHECK: *** Bad machine code: operand types must be all-vector or all-scalar ***
; CHECK: instruction: %assert_zext_3:_(<2 x s32>) = G_ASSERT_ZEXT
%assert_zext_3:_(<2 x s32>) = G_ASSERT_ZEXT %0, 8
; CHECK: *** Bad machine code: operand types must preserve number of vector elements ***
; CHECK: instruction: %assert_zext_4:_(<2 x s32>) = G_ASSERT_ZEXT
%assert_zext_4:_(<2 x s32>) = G_ASSERT_ZEXT %1, 8
; CHECK: *** Bad machine code: G_ASSERT_ZEXT size must be >= 1 ***
; CHECK: instruction: %assert_zext_5:_(s64) = G_ASSERT_ZEXT
%assert_zext_5:_(s64) = G_ASSERT_ZEXT %0, 0
; CHECK: *** Bad machine code: G_ASSERT_ZEXT size must be less than source bit width ***
; CHECK: instruction: %assert_zext_6:_(s64) = G_ASSERT_ZEXT
%assert_zext_6:_(s64) = G_ASSERT_ZEXT %0, 128
; CHECK: *** Bad machine code: Type mismatch in generic instruction ***
; CHECK: instruction: %assert_zext_7:_(s64) = G_ASSERT_ZEXT %2:_, 8
%assert_zext_7:_(s64) = G_ASSERT_ZEXT %2, 8
; CHECK: *** Bad machine code: Generic instruction cannot have physical register ***
; CHECK: instruction: %assert_zext_8:_(s64) = G_ASSERT_ZEXT $x0, 8
%assert_zext_8:_(s64) = G_ASSERT_ZEXT $x0, 8

View File

@ -0,0 +1,35 @@
# REQUIRES: aarch64-registered-target
# RUN: not --crash llc -verify-machineinstrs -mtriple aarch64 -run-pass none -o /dev/null %s 2>&1 | FileCheck %s
name: test
legalized: true
regBankSelected: true
body: |
bb.0:
liveins: $w0, $w1
%bank:gpr(s32) = COPY $w0
%class:gpr32(s32) = COPY $w1
; CHECK: *** Bad machine code: G_ASSERT_ZEXT source and destination register banks must match ***
; CHECK: instruction: %bank_mismatch:fpr(s32) = G_ASSERT_ZEXT %bank:gpr, 16
%bank_mismatch:fpr(s32) = G_ASSERT_ZEXT %bank, 16
; CHECK: *** Bad machine code: G_ASSERT_ZEXT source and destination register classes must match ***
; CHECK: instruction: %class_mismatch_gpr:gpr32all(s32) = G_ASSERT_ZEXT %class:gpr32, 16
%class_mismatch_gpr:gpr32all(s32) = G_ASSERT_ZEXT %class, 16
; CHECK: *** Bad machine code: G_ASSERT_ZEXT source and destination register classes must match ***
; CHECK: instruction: %class_mismatch_fpr:fpr32(s32) = G_ASSERT_ZEXT %class:gpr32, 16
%class_mismatch_fpr:fpr32(s32) = G_ASSERT_ZEXT %class, 16
; CHECK: *** Bad machine code: G_ASSERT_ZEXT source and destination register banks must match ***
; CHECK: instruction: %dst_has_class_src_has_bank:gpr32all(s32) = G_ASSERT_ZEXT %bank:gpr, 16
%dst_has_class_src_has_bank:gpr32all(s32) = G_ASSERT_ZEXT %bank, 16
; CHECK: *** Bad machine code: G_ASSERT_ZEXT source and destination register banks must match ***
; CHECK: instruction: %dst_has_bank_src_has_class:gpr(s32) = G_ASSERT_ZEXT %class:gpr32, 16
%dst_has_bank_src_has_class:gpr(s32) = G_ASSERT_ZEXT %class, 16
; CHECK: *** Bad machine code: Generic instruction cannot have physical register ***
; CHECK: instruction: %implicit_physreg:gpr(s32) = G_ASSERT_ZEXT %class:gpr32, 16, implicit-def $w0
%implicit_physreg:gpr(s32) = G_ASSERT_ZEXT %class, 16, implicit-def $w0