From 24261729a49feb4cf0376c6f9326374ab28ec0a5 Mon Sep 17 00:00:00 2001 From: Jessica Paquette Date: Wed, 27 Jan 2021 12:09:05 -0800 Subject: [PATCH] [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 --- llvm/docs/GlobalISel/GenericOpcode.rst | 34 ++++++++++++++ .../CodeGen/GlobalISel/MachineIRBuilder.h | 6 +++ llvm/include/llvm/CodeGen/TargetOpcodes.h | 8 ++++ llvm/include/llvm/Support/TargetOpcodes.def | 8 ++++ llvm/include/llvm/Target/GenericOpcodes.td | 12 +++++ .../CodeGen/GlobalISel/MachineIRBuilder.cpp | 6 +++ llvm/lib/CodeGen/MachineVerifier.cpp | 38 +++++++++++++++- .../GlobalISel/legalize-ignore-hint.mir | 21 +++++++++ .../MachineVerifier/test_g_assert_zext.mir | 44 +++++++++++++++++++ ...test_g_assert_zext_register_bank_class.mir | 35 +++++++++++++++ 10 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 llvm/test/CodeGen/AArch64/GlobalISel/legalize-ignore-hint.mir create mode 100644 llvm/test/MachineVerifier/test_g_assert_zext.mir create mode 100644 llvm/test/MachineVerifier/test_g_assert_zext_register_bank_class.mir diff --git a/llvm/docs/GlobalISel/GenericOpcode.rst b/llvm/docs/GlobalISel/GenericOpcode.rst index 9b9076b126c28e..51adb6316d9d62 100644 --- a/llvm/docs/GlobalISel/GenericOpcode.rst +++ b/llvm/docs/GlobalISel/GenericOpcode.rst @@ -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 diff --git a/llvm/include/llvm/CodeGen/GlobalISel/MachineIRBuilder.h b/llvm/include/llvm/CodeGen/GlobalISel/MachineIRBuilder.h index 1ab4cd7048240c..93fd1cce29fd3d 100644 --- a/llvm/include/llvm/CodeGen/GlobalISel/MachineIRBuilder.h +++ b/llvm/include/llvm/CodeGen/GlobalISel/MachineIRBuilder.h @@ -810,6 +810,12 @@ class MachineIRBuilder { /// \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. diff --git a/llvm/include/llvm/CodeGen/TargetOpcodes.h b/llvm/include/llvm/CodeGen/TargetOpcodes.h index 080a244f6f6964..169caf30bb2222 100644 --- a/llvm/include/llvm/CodeGen/TargetOpcodes.h +++ b/llvm/include/llvm/CodeGen/TargetOpcodes.h @@ -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 diff --git a/llvm/include/llvm/Support/TargetOpcodes.def b/llvm/include/llvm/Support/TargetOpcodes.def index a63d40484089f6..ca78dbcb33fc7c 100644 --- a/llvm/include/llvm/Support/TargetOpcodes.def +++ b/llvm/include/llvm/Support/TargetOpcodes.def @@ -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) diff --git a/llvm/include/llvm/Target/GenericOpcodes.td b/llvm/include/llvm/Target/GenericOpcodes.td index 209925969df3ff..77f7752e8317f5 100644 --- a/llvm/include/llvm/Target/GenericOpcodes.td +++ b/llvm/include/llvm/Target/GenericOpcodes.td @@ -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; +} diff --git a/llvm/lib/CodeGen/GlobalISel/MachineIRBuilder.cpp b/llvm/lib/CodeGen/GlobalISel/MachineIRBuilder.cpp index 67ef02a4e7b2e7..3ff331af53c345 100644 --- a/llvm/lib/CodeGen/GlobalISel/MachineIRBuilder.cpp +++ b/llvm/lib/CodeGen/GlobalISel/MachineIRBuilder.cpp @@ -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()); diff --git a/llvm/lib/CodeGen/MachineVerifier.cpp b/llvm/lib/CodeGen/MachineVerifier.cpp index 0f6d9b888f47bd..2691dd8fd89f93 100644 --- a/llvm/lib/CodeGen/MachineVerifier.cpp +++ b/llvm/lib/CodeGen/MachineVerifier.cpp @@ -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; } diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/legalize-ignore-hint.mir b/llvm/test/CodeGen/AArch64/GlobalISel/legalize-ignore-hint.mir new file mode 100644 index 00000000000000..8cf3e70b293557 --- /dev/null +++ b/llvm/test/CodeGen/AArch64/GlobalISel/legalize-ignore-hint.mir @@ -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 +... diff --git a/llvm/test/MachineVerifier/test_g_assert_zext.mir b/llvm/test/MachineVerifier/test_g_assert_zext.mir new file mode 100644 index 00000000000000..f39be8bf935ebb --- /dev/null +++ b/llvm/test/MachineVerifier/test_g_assert_zext.mir @@ -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 diff --git a/llvm/test/MachineVerifier/test_g_assert_zext_register_bank_class.mir b/llvm/test/MachineVerifier/test_g_assert_zext_register_bank_class.mir new file mode 100644 index 00000000000000..b4ed7162fff307 --- /dev/null +++ b/llvm/test/MachineVerifier/test_g_assert_zext_register_bank_class.mir @@ -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