[MIPS GlobalISel] Register bank select for G_STORE. Select i64 store

Select gprb or fprb when stored value is defined by either:
 copy from physical register or
 instruction with only one mapping available for that def operand.

Store of integer s64 is handled with narrowScalar when mapping is applied,
produced artifacts are combined away. Manually set gprb to all register
operands of instructions created during narrowScalar.

Differential Revision: https://reviews.llvm.org/D64268

llvm-svn: 365322
This commit is contained in:
Petar Avramovic 2019-07-08 14:36:36 +00:00
parent ee510131f8
commit e165bef5cc
6 changed files with 569 additions and 5 deletions

View File

@ -39,9 +39,13 @@ MipsLegalizerInfo::MipsLegalizerInfo(const MipsSubtarget &ST) {
.legalForTypesWithMemDesc({{s32, p0, 8, 8},
{s32, p0, 16, 8},
{s32, p0, 32, 8},
{s64, p0, 64, 8},
{p0, p0, 32, 8}})
.minScalar(0, s32);
getActionDefinitionsBuilder(G_MERGE_VALUES)
.legalFor({{s64, s32}});
getActionDefinitionsBuilder({G_ZEXTLOAD, G_SEXTLOAD})
.legalForTypesWithMemDesc({{s32, p0, 8, 8},
{s32, p0, 16, 8}})

View File

@ -10,14 +10,15 @@
/// \todo This should be generated by TableGen.
//===----------------------------------------------------------------------===//
#include "MipsInstrInfo.h"
#include "MipsRegisterBankInfo.h"
#include "MipsInstrInfo.h"
#include "llvm/CodeGen/GlobalISel/GISelChangeObserver.h"
#include "llvm/CodeGen/GlobalISel/LegalizationArtifactCombiner.h"
#include "llvm/CodeGen/GlobalISel/LegalizerHelper.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#define GET_TARGET_REGBANK_IMPL
#define DEBUG_TYPE "registerbankinfo"
#include "MipsGenRegisterBank.inc"
namespace llvm {
@ -91,9 +92,169 @@ const RegisterBank &MipsRegisterBankInfo::getRegBankFromRegClass(
}
}
// Instructions where all register operands are floating point.
static bool isFloatingPointOpcode(unsigned Opc) {
switch (Opc) {
case TargetOpcode::G_FCONSTANT:
case TargetOpcode::G_FADD:
case TargetOpcode::G_FSUB:
case TargetOpcode::G_FMUL:
case TargetOpcode::G_FDIV:
case TargetOpcode::G_FABS:
case TargetOpcode::G_FSQRT:
case TargetOpcode::G_FCEIL:
case TargetOpcode::G_FFLOOR:
case TargetOpcode::G_FPEXT:
case TargetOpcode::G_FPTRUNC:
return true;
default:
return false;
}
}
// Instructions where def operands are floating point registers.
// Use operands are general purpose.
static bool isFloatingPointOpcodeDef(unsigned Opc) {
switch (Opc) {
case TargetOpcode::G_SITOFP:
case TargetOpcode::G_UITOFP:
case Mips::MTC1:
case Mips::BuildPairF64:
case Mips::BuildPairF64_64:
return true;
default:
return isFloatingPointOpcode(Opc);
}
}
static bool isAmbiguous(unsigned Opc) {
switch (Opc) {
case TargetOpcode::G_LOAD:
case TargetOpcode::G_STORE:
case TargetOpcode::G_PHI:
case TargetOpcode::G_SELECT:
return true;
default:
return false;
}
}
void MipsRegisterBankInfo::AmbiguousRegDefUseContainer::addUseDef(
Register Reg, const MachineRegisterInfo &MRI) {
assert(!MRI.getType(Reg).isPointer() &&
"Pointers are gprb, they should not be considered as ambiguous.\n");
MachineInstr *DefMI = MRI.getVRegDef(Reg);
if (DefMI->getOpcode() == TargetOpcode::COPY &&
!TargetRegisterInfo::isPhysicalRegister(DefMI->getOperand(1).getReg()))
// Copies from non-physical registers are not supported.
return;
UseDefs.push_back(DefMI);
}
MipsRegisterBankInfo::AmbiguousRegDefUseContainer::AmbiguousRegDefUseContainer(
const MachineInstr *MI) {
assert(isAmbiguous(MI->getOpcode()) &&
"Not implemented for non Ambiguous opcode.\n");
const MachineRegisterInfo &MRI = MI->getMF()->getRegInfo();
if (MI->getOpcode() == TargetOpcode::G_STORE)
addUseDef(MI->getOperand(0).getReg(), MRI);
}
bool MipsRegisterBankInfo::TypeInfoForMF::visit(const MachineInstr *MI) {
assert(isAmbiguous(MI->getOpcode()) && "Visiting non-Ambiguous opcode.\n");
startVisit(MI);
AmbiguousRegDefUseContainer DefUseContainer(MI);
// Visit instructions that DEFINE MI's USE operands.
if (visitAdjacentInstrs(MI, DefUseContainer.getUseDefs()))
return true;
return false;
}
bool MipsRegisterBankInfo::TypeInfoForMF::visitAdjacentInstrs(
const MachineInstr *MI, SmallVectorImpl<MachineInstr *> &AdjacentInstrs) {
while (!AdjacentInstrs.empty()) {
MachineInstr *AdjMI = AdjacentInstrs.pop_back_val();
if (isFloatingPointOpcodeDef(AdjMI->getOpcode())) {
setTypes(MI, InstType::FloatingPoint);
return true;
}
// Determine InstType from register bank of phys register that is
// use of this copy.
if (AdjMI->getOpcode() == TargetOpcode::COPY) {
setTypesAccordingToPhysicalRegister(MI, AdjMI, 1);
return true;
}
if (isAmbiguous(AdjMI->getOpcode())) {
// Chains of ambiguous instructions are not supported.
return false;
}
// Defaults to integer instruction. Includes G_MERGE_VALUES and
// G_UNMERGE_VALUES.
setTypes(MI, InstType::Integer);
return true;
}
return false;
}
void MipsRegisterBankInfo::TypeInfoForMF::setTypes(const MachineInstr *MI,
InstType InstTy) {
changeRecordedTypeForInstr(MI, InstTy);
}
void MipsRegisterBankInfo::TypeInfoForMF::setTypesAccordingToPhysicalRegister(
const MachineInstr *MI, const MachineInstr *CopyInst, unsigned Op) {
assert((TargetRegisterInfo::isPhysicalRegister(
CopyInst->getOperand(Op).getReg())) &&
"Copies of non physical registers should not be considered here.\n");
const MachineFunction &MF = *CopyInst->getMF();
const MachineRegisterInfo &MRI = MF.getRegInfo();
const TargetRegisterInfo &TRI = *MF.getSubtarget().getRegisterInfo();
const RegisterBankInfo &RBI =
*CopyInst->getMF()->getSubtarget().getRegBankInfo();
const RegisterBank *Bank =
RBI.getRegBank(CopyInst->getOperand(Op).getReg(), MRI, TRI);
if (Bank == &Mips::FPRBRegBank)
setTypes(MI, InstType::FloatingPoint);
else if (Bank == &Mips::GPRBRegBank)
setTypes(MI, InstType::Integer);
else
llvm_unreachable("Unsupported register bank.\n");
}
MipsRegisterBankInfo::InstType
MipsRegisterBankInfo::TypeInfoForMF::determineInstType(const MachineInstr *MI) {
visit(MI);
return getRecordedTypeForInstr(MI);
}
void MipsRegisterBankInfo::TypeInfoForMF::cleanupIfNewFunction(
llvm::StringRef FunctionName) {
if (MFName != FunctionName) {
MFName = FunctionName;
Types.clear();
}
}
const RegisterBankInfo::InstructionMapping &
MipsRegisterBankInfo::getInstrMapping(const MachineInstr &MI) const {
static TypeInfoForMF TI;
// Reset TI internal data when MF changes.
TI.cleanupIfNewFunction(MI.getMF()->getName());
unsigned Opc = MI.getOpcode();
const MachineFunction &MF = *MI.getParent()->getParent();
const MachineRegisterInfo &MRI = MF.getRegInfo();
@ -106,6 +267,8 @@ MipsRegisterBankInfo::getInstrMapping(const MachineInstr &MI) const {
unsigned NumOperands = MI.getNumOperands();
const ValueMapping *OperandsMapping = &Mips::ValueMappings[Mips::GPRIdx];
unsigned MappingID = DefaultMappingID;
const unsigned CustomMappingID = 1;
switch (Opc) {
case G_TRUNC:
@ -114,7 +277,6 @@ MipsRegisterBankInfo::getInstrMapping(const MachineInstr &MI) const {
case G_MUL:
case G_UMULH:
case G_LOAD:
case G_STORE:
case G_ZEXTLOAD:
case G_SEXTLOAD:
case G_GEP:
@ -130,6 +292,36 @@ MipsRegisterBankInfo::getInstrMapping(const MachineInstr &MI) const {
case G_UREM:
OperandsMapping = &Mips::ValueMappings[Mips::GPRIdx];
break;
case G_STORE: {
unsigned Size = MRI.getType(MI.getOperand(0).getReg()).getSizeInBits();
InstType InstTy = InstType::Integer;
if (!MRI.getType(MI.getOperand(0).getReg()).isPointer()) {
InstTy = TI.determineInstType(&MI);
}
if (InstTy == InstType::FloatingPoint) { // fprb
OperandsMapping =
getOperandsMapping({Size == 32 ? &Mips::ValueMappings[Mips::SPRIdx]
: &Mips::ValueMappings[Mips::DPRIdx],
&Mips::ValueMappings[Mips::GPRIdx]});
break;
} else { // gprb
OperandsMapping =
getOperandsMapping({Size <= 32 ? &Mips::ValueMappings[Mips::GPRIdx]
: &Mips::ValueMappings[Mips::DPRIdx],
&Mips::ValueMappings[Mips::GPRIdx]});
if (Size == 64)
MappingID = CustomMappingID;
}
break;
}
case G_MERGE_VALUES: {
OperandsMapping = getOperandsMapping({&Mips::ValueMappings[Mips::DPRIdx],
&Mips::ValueMappings[Mips::GPRIdx],
&Mips::ValueMappings[Mips::GPRIdx]});
MappingID = CustomMappingID;
break;
}
case G_FADD:
case G_FSUB:
case G_FMUL:
@ -218,6 +410,73 @@ MipsRegisterBankInfo::getInstrMapping(const MachineInstr &MI) const {
return getInvalidInstructionMapping();
}
return getInstructionMapping(DefaultMappingID, /*Cost=*/1, OperandsMapping,
return getInstructionMapping(MappingID, /*Cost=*/1, OperandsMapping,
NumOperands);
}
using InstListTy = GISelWorkList<4>;
namespace {
class InstManager : public GISelChangeObserver {
InstListTy &InstList;
public:
InstManager(InstListTy &Insts) : InstList(Insts) {}
void createdInstr(MachineInstr &MI) override { InstList.insert(&MI); }
void erasingInstr(MachineInstr &MI) override {}
void changingInstr(MachineInstr &MI) override {}
void changedInstr(MachineInstr &MI) override {}
};
} // end anonymous namespace
/// Here we have to narrowScalar s64 operands to s32, combine away
/// G_MERGE/G_UNMERGE and erase instructions that became dead in the process.
/// We manually assign 32 bit gprb to register operands of all new instructions
/// that got created in the process since they will not end up in RegBankSelect
/// loop. Careful not to delete instruction after MI i.e. MI.getIterator()++.
void MipsRegisterBankInfo::applyMappingImpl(
const OperandsMapper &OpdMapper) const {
MachineInstr &MI = OpdMapper.getMI();
InstListTy NewInstrs;
MachineIRBuilder B(MI);
MachineFunction *MF = MI.getMF();
MachineRegisterInfo &MRI = OpdMapper.getMRI();
InstManager NewInstrObserver(NewInstrs);
GISelObserverWrapper WrapperObserver(&NewInstrObserver);
LegalizerHelper Helper(*MF, WrapperObserver, B);
LegalizationArtifactCombiner ArtCombiner(
B, MF->getRegInfo(), *MF->getSubtarget().getLegalizerInfo());
switch (MI.getOpcode()) {
case TargetOpcode::G_STORE: {
Helper.narrowScalar(MI, 0, LLT::scalar(32));
// Handle new instructions.
while (!NewInstrs.empty()) {
MachineInstr *NewMI = NewInstrs.pop_back_val();
// This is new G_UNMERGE that was created during narrowScalar and will
// not be considered for regbank selection. RegBankSelect for mips
// visits/makes corresponding G_MERGE first. Combine them here.
if (NewMI->getOpcode() == TargetOpcode::G_UNMERGE_VALUES) {
SmallVector<MachineInstr *, 2> DeadInstrs;
ArtCombiner.tryCombineMerges(*NewMI, DeadInstrs);
for (MachineInstr *DeadMI : DeadInstrs)
DeadMI->eraseFromParent();
} else
// Manually set register banks for all register operands to 32 bit gprb.
for (auto Op : NewMI->operands()) {
if (Op.isReg()) {
assert(MRI.getType(Op.getReg()).getSizeInBits() == 32 &&
"Only 32 bit gprb is handled here.\n");
MRI.setRegBank(Op.getReg(), getRegBank(Mips::GPRBRegBankID));
}
}
}
return;
}
default:
break;
}
return applyDefaultMapping(OpdMapper);
}

View File

@ -37,6 +37,82 @@ public:
const InstructionMapping &
getInstrMapping(const MachineInstr &MI) const override;
void applyMappingImpl(const OperandsMapper &OpdMapper) const override;
private:
/// Some instructions are used with both floating point and integer operands.
/// We assign InstType to such instructions as it helps us to avoid cross bank
/// copies. InstType deppends on context.
enum InstType {
NotDetermined,
/// Connected with instruction that interprets 'bags of bits' as integers.
/// Select gprb to avoid cross bank copies.
Integer,
/// Connected with instruction that interprets 'bags of bits' as floating
/// point numbers. Select fprb to avoid cross bank copies.
FloatingPoint
};
/// Some generic instructions have operands that can be mapped to either fprb
/// or gprb e.g. for G_LOAD we consider only operand 0 as ambiguous, operand 1
/// is always gprb since it is a pointer.
/// This class provides container for MI's ambiguous:
/// UseDefs : MachineInstrs that define MI's ambiguous use operands.
class AmbiguousRegDefUseContainer {
SmallVector<MachineInstr *, 2> UseDefs;
void addUseDef(Register Reg, const MachineRegisterInfo &MRI);
public:
AmbiguousRegDefUseContainer(const MachineInstr *MI);
SmallVectorImpl<MachineInstr *> &getUseDefs() { return UseDefs; }
};
class TypeInfoForMF {
/// MachineFunction name is used to recognise when MF changes.
std::string MFName = "";
/// Recorded InstTypes for visited instructions.
DenseMap<const MachineInstr *, InstType> Types;
bool visit(const MachineInstr *MI);
/// Visit MI's adjacent UseDefs.
bool visitAdjacentInstrs(const MachineInstr *MI,
SmallVectorImpl<MachineInstr *> &AdjacentInstrs);
void setTypes(const MachineInstr *MI, InstType ITy);
/// InstType for MI is determined, set it to InstType that corresponds to
/// physical regisiter that is operand number Op in CopyInst.
void setTypesAccordingToPhysicalRegister(const MachineInstr *MI,
const MachineInstr *CopyInst,
unsigned Op);
/// Set default values for MI in order to start visit.
void startVisit(const MachineInstr *MI) {
Types.try_emplace(MI, InstType::NotDetermined);
}
bool wasVisited(const MachineInstr *MI) const { return Types.count(MI); };
/// Returns recorded type for instruction.
const InstType &getRecordedTypeForInstr(const MachineInstr *MI) const {
assert(wasVisited(MI) && "Instruction was not visited!");
return Types.find(MI)->getSecond();
};
/// Change recorded type for instruction.
void changeRecordedTypeForInstr(const MachineInstr *MI, InstType InstTy) {
assert(wasVisited(MI) && "Instruction was not visited!");
Types.find(MI)->getSecond() = InstTy;
};
public:
InstType determineInstType(const MachineInstr *MI);
void cleanupIfNewFunction(llvm::StringRef FunctionName);
};
};
} // end namespace llvm
#endif

View File

@ -0,0 +1,98 @@
# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py
# RUN: llc -O0 -mtriple=mipsel-linux-gnu -run-pass=legalizer -verify-machineinstrs %s -o - | FileCheck %s -check-prefixes=MIPS32
--- |
define void @store_i32(i32* %ptr) { entry: ret void }
define void @store_i64(i64* %ptr) { entry: ret void }
define void @store_float(float* %ptr) { entry: ret void }
define void @store_double(double* %ptr) { entry: ret void }
...
---
name: store_i32
alignment: 2
legalized: true
tracksRegLiveness: true
body: |
bb.1.entry:
liveins: $a0, $a1
; MIPS32-LABEL: name: store_i32
; MIPS32: liveins: $a0, $a1
; MIPS32: [[COPY:%[0-9]+]]:_(s32) = COPY $a0
; MIPS32: [[COPY1:%[0-9]+]]:_(p0) = COPY $a1
; MIPS32: G_STORE [[COPY]](s32), [[COPY1]](p0) :: (store 4 into %ir.ptr)
; MIPS32: RetRA
%0:_(s32) = COPY $a0
%1:_(p0) = COPY $a1
G_STORE %0(s32), %1(p0) :: (store 4 into %ir.ptr)
RetRA
...
---
name: store_i64
alignment: 2
legalized: true
tracksRegLiveness: true
body: |
bb.1.entry:
liveins: $a0, $a1, $a2
; MIPS32-LABEL: name: store_i64
; MIPS32: liveins: $a0, $a1, $a2
; MIPS32: [[COPY:%[0-9]+]]:_(s32) = COPY $a0
; MIPS32: [[COPY1:%[0-9]+]]:_(s32) = COPY $a1
; MIPS32: [[MV:%[0-9]+]]:_(s64) = G_MERGE_VALUES [[COPY]](s32), [[COPY1]](s32)
; MIPS32: [[COPY2:%[0-9]+]]:_(p0) = COPY $a2
; MIPS32: G_STORE [[MV]](s64), [[COPY2]](p0) :: (store 8 into %ir.ptr)
; MIPS32: RetRA
%2:_(s32) = COPY $a0
%3:_(s32) = COPY $a1
%0:_(s64) = G_MERGE_VALUES %2(s32), %3(s32)
%1:_(p0) = COPY $a2
G_STORE %0(s64), %1(p0) :: (store 8 into %ir.ptr)
RetRA
...
---
name: store_float
alignment: 2
legalized: true
tracksRegLiveness: true
body: |
bb.1.entry:
liveins: $a1, $f12
; MIPS32-LABEL: name: store_float
; MIPS32: liveins: $a1, $f12
; MIPS32: [[COPY:%[0-9]+]]:_(s32) = COPY $f12
; MIPS32: [[COPY1:%[0-9]+]]:_(p0) = COPY $a1
; MIPS32: G_STORE [[COPY]](s32), [[COPY1]](p0) :: (store 4 into %ir.ptr)
; MIPS32: RetRA
%0:_(s32) = COPY $f12
%1:_(p0) = COPY $a1
G_STORE %0(s32), %1(p0) :: (store 4 into %ir.ptr)
RetRA
...
---
name: store_double
alignment: 2
legalized: true
tracksRegLiveness: true
body: |
bb.1.entry:
liveins: $a2, $d6
; MIPS32-LABEL: name: store_double
; MIPS32: liveins: $a2, $d6
; MIPS32: [[COPY:%[0-9]+]]:_(s64) = COPY $d6
; MIPS32: [[COPY1:%[0-9]+]]:_(p0) = COPY $a2
; MIPS32: G_STORE [[COPY]](s64), [[COPY1]](p0) :: (store 8 into %ir.ptr)
; MIPS32: RetRA
%0:_(s64) = COPY $d6
%1:_(p0) = COPY $a2
G_STORE %0(s64), %1(p0) :: (store 8 into %ir.ptr)
RetRA
...

View File

@ -0,0 +1,27 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
; RUN: llc -O0 -mtriple=mipsel-linux-gnu -global-isel -verify-machineinstrs %s -o -| FileCheck %s -check-prefixes=MIPS32
define void @store_i32(i32 %val, i32* %ptr) {
; MIPS32-LABEL: store_i32:
; MIPS32: # %bb.0: # %entry
; MIPS32-NEXT: sw $4, 0($5)
; MIPS32-NEXT: jr $ra
; MIPS32-NEXT: nop
entry:
store i32 %val, i32* %ptr
ret void
}
define void @store_i64(i64 %val, i64* %ptr) {
; MIPS32-LABEL: store_i64:
; MIPS32: # %bb.0: # %entry
; MIPS32-NEXT: sw $4, 0($6)
; MIPS32-NEXT: ori $1, $zero, 4
; MIPS32-NEXT: addu $1, $6, $1
; MIPS32-NEXT: sw $5, 0($1)
; MIPS32-NEXT: jr $ra
; MIPS32-NEXT: nop
entry:
store i64 %val, i64* %ptr
ret void
}

View File

@ -0,0 +1,100 @@
# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py
# RUN: llc -O0 -mtriple=mipsel-linux-gnu -run-pass=regbankselect -verify-machineinstrs %s -o - | FileCheck %s -check-prefixes=MIPS32
--- |
define void @store_i32(i32* %ptr) { entry: ret void }
define void @store_i64(i64* %ptr) { entry: ret void }
define void @store_float(float* %ptr) { entry: ret void }
define void @store_double(double* %ptr) { entry: ret void }
...
---
name: store_i32
alignment: 2
legalized: true
tracksRegLiveness: true
body: |
bb.1.entry:
liveins: $a0, $a1
; MIPS32-LABEL: name: store_i32
; MIPS32: liveins: $a0, $a1
; MIPS32: [[COPY:%[0-9]+]]:gprb(s32) = COPY $a0
; MIPS32: [[COPY1:%[0-9]+]]:gprb(p0) = COPY $a1
; MIPS32: G_STORE [[COPY]](s32), [[COPY1]](p0) :: (store 4 into %ir.ptr)
; MIPS32: RetRA
%0:_(s32) = COPY $a0
%1:_(p0) = COPY $a1
G_STORE %0(s32), %1(p0) :: (store 4 into %ir.ptr)
RetRA
...
---
name: store_i64
alignment: 2
legalized: true
tracksRegLiveness: true
body: |
bb.1.entry:
liveins: $a0, $a1, $a2
; MIPS32-LABEL: name: store_i64
; MIPS32: liveins: $a0, $a1, $a2
; MIPS32: [[COPY:%[0-9]+]]:gprb(s32) = COPY $a0
; MIPS32: [[COPY1:%[0-9]+]]:gprb(s32) = COPY $a1
; MIPS32: [[COPY2:%[0-9]+]]:gprb(p0) = COPY $a2
; MIPS32: G_STORE [[COPY]](s32), [[COPY2]](p0) :: (store 4 into %ir.ptr, align 8)
; MIPS32: [[C:%[0-9]+]]:gprb(s32) = G_CONSTANT i32 4
; MIPS32: [[GEP:%[0-9]+]]:gprb(p0) = G_GEP [[COPY2]], [[C]](s32)
; MIPS32: G_STORE [[COPY1]](s32), [[GEP]](p0) :: (store 4 into %ir.ptr + 4, align 8)
; MIPS32: RetRA
%2:_(s32) = COPY $a0
%3:_(s32) = COPY $a1
%0:_(s64) = G_MERGE_VALUES %2(s32), %3(s32)
%1:_(p0) = COPY $a2
G_STORE %0(s64), %1(p0) :: (store 8 into %ir.ptr)
RetRA
...
---
name: store_float
alignment: 2
legalized: true
tracksRegLiveness: true
body: |
bb.1.entry:
liveins: $a1, $f12
; MIPS32-LABEL: name: store_float
; MIPS32: liveins: $a1, $f12
; MIPS32: [[COPY:%[0-9]+]]:fprb(s32) = COPY $f12
; MIPS32: [[COPY1:%[0-9]+]]:gprb(p0) = COPY $a1
; MIPS32: G_STORE [[COPY]](s32), [[COPY1]](p0) :: (store 4 into %ir.ptr)
; MIPS32: RetRA
%0:_(s32) = COPY $f12
%1:_(p0) = COPY $a1
G_STORE %0(s32), %1(p0) :: (store 4 into %ir.ptr)
RetRA
...
---
name: store_double
alignment: 2
legalized: true
tracksRegLiveness: true
body: |
bb.1.entry:
liveins: $a2, $d6
; MIPS32-LABEL: name: store_double
; MIPS32: liveins: $a2, $d6
; MIPS32: [[COPY:%[0-9]+]]:fprb(s64) = COPY $d6
; MIPS32: [[COPY1:%[0-9]+]]:gprb(p0) = COPY $a2
; MIPS32: G_STORE [[COPY]](s64), [[COPY1]](p0) :: (store 8 into %ir.ptr)
; MIPS32: RetRA
%0:_(s64) = COPY $d6
%1:_(p0) = COPY $a2
G_STORE %0(s64), %1(p0) :: (store 8 into %ir.ptr)
RetRA
...