blob: cd235060db7d760ac602e5d17189afe2e0890208 [file] [edit]
//===- RematerializerTest.cpp ---------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "llvm/CodeGen/Rematerializer.h"
#include "CodeGenTestBase.h"
#include "llvm/CodeGen/LiveIntervals.h"
#include "llvm/CodeGen/RegisterPressure.h"
#include "llvm/Config/Targets.h"
#include "llvm/Support/TargetSelect.h"
using namespace llvm;
using RegisterIdx = Rematerializer::RegisterIdx;
namespace {
/// Wraps a rematerializer (with pointer-like access semantics through ->) next
/// to other members generally used by unit tests.
struct RematerializerWrapper {
MachineFunction &MF;
LiveIntervals &LIS;
Rematerializer Remater;
/// Region sizes for regions passed to the rematerializer. Initialized at
/// construction to the correct value, can then be modified to track expected
/// changes.
SmallVector<unsigned> RegionSizes;
/// Number of rematerializable registers identified by the rematerializer.
/// Initialized at construction to the correct value, can then be modified to
/// track expected changes.
unsigned NumRematRegs;
using RegionBoundaries = Rematerializer::RegionBoundaries;
RematerializerWrapper(MachineFunction &MF,
SmallVectorImpl<RegionBoundaries> &Regions,
LiveIntervals &LIS)
: MF(MF), LIS(LIS), Remater(MF, Regions, LIS) {
for (const RegionBoundaries &Region : Regions)
RegionSizes.push_back(std::distance(Region.first, Region.second));
Remater.analyze();
NumRematRegs = Remater.getNumRegs();
}
Rematerializer *operator->() { return &Remater; }
const Rematerializer *operator->() const { return &Remater; }
Rematerializer &operator*() { return Remater; }
const Rematerializer &operator*() const { return Remater; }
/// Returns the number of users of rematerializable register \p RegIdx.
unsigned getNumUsers(RegisterIdx RegIdx) const {
unsigned NumUsers = 0;
for (const auto &[_, RegionUses] : Remater.getReg(RegIdx).Uses)
NumUsers += RegionUses.size();
return NumUsers;
}
/// Returns the number of MIs in region \p RegionIdx.
unsigned getRegionSize(unsigned RegionIdx) const {
const RegionBoundaries &Region = Remater.getRegion(RegionIdx);
return std::distance(Region.first, Region.second);
}
/// Expects that \p NumMIs were added to region \p RegionIdx.
RematerializerWrapper &addMIs(unsigned RegionIdx, unsigned NumMIs) {
RegionSizes[RegionIdx] += NumMIs;
return *this;
}
/// Expects that \p NumMIs were removed from region \p RegionIdx.
RematerializerWrapper &removeMIs(unsigned RegionIdx, unsigned NumMIs) {
RegionSizes[RegionIdx] -= NumMIs;
return *this;
}
/// Expects that \p NumMIs were move from region \p FromRegionIdx to region \p
/// ToRegionIdx.
RematerializerWrapper &moveMIs(unsigned FromRegionIdx, unsigned ToRegionIdx,
unsigned NumMIs) {
return removeMIs(FromRegionIdx, NumMIs).addMIs(ToRegionIdx, NumMIs);
}
/// Expects that \p NumRegs rematerializable registers were added to the
/// rematerializer.
RematerializerWrapper &addRematRegs(unsigned NumRegs) {
NumRematRegs += NumRegs;
return *this;
}
};
class RematerializerTest : public CodeGenTestBase {
public:
static void SetUpTestCase() {
#if LLVM_HAS_AMDGPU_TARGET
LLVMInitializeAMDGPUTargetInfo();
LLVMInitializeAMDGPUTarget();
LLVMInitializeAMDGPUTargetMC();
#else
GTEST_SKIP();
#endif
}
void SetUp() override { setUpImpl("amdgpu9.50--", "", ""); }
using RematerializerTestFn = std::function<void(RematerializerWrapper &RW)>;
using ProcessMIRFn =
std::function<void(MachineFunction &MF, LiveIntervals &LIS)>;
static void doNothing(MachineFunction &MF, LiveIntervals &LIS) {};
void rematerializerTest(StringRef MIRBody, RematerializerTestFn Test,
ProcessMIRFn PreRemat = doNothing) {
SmallString<512> S;
StringRef MIRString = (Twine(R"MIR(
---
name: func
tracksRegLiveness: true
machineFunctionInfo:
isEntryFunction: true
body: |
)MIR") + Twine(MIRBody) + Twine("...\n"))
.toNullTerminatedStringRef(S);
ASSERT_TRUE(parseMIR(MIRString));
MachineFunction &MF = getMF("func");
LiveIntervals &LIS = MFAM.getResult<LiveIntervalsAnalysis>(MF);
PreRemat(MF, LIS);
SmallVector<Rematerializer::RegionBoundaries> Regions;
MachineInstr *FirstMI = nullptr;
for (MachineBasicBlock &MBB : MF) {
for (MachineInstr &MI : MBB) {
if (!FirstMI)
FirstMI = &MI;
if (MI.isTerminator()) {
if (FirstMI != &MI)
Regions.push_back({FirstMI, MI});
FirstMI = nullptr;
}
}
if (FirstMI) {
Regions.push_back({FirstMI, MBB.end()});
FirstMI = nullptr;
}
}
RematerializerWrapper RW(MF, Regions, LIS);
Test(RW);
EXPECT_TRUE(MF.verify());
}
/// Replicates the scheduler's effect on \p LIS on an intra-block move of \p
/// MI right before \p MoveBefore, which must be in the same block as \p MI.
void moveMIAndAdjustLiveness(MachineBasicBlock::iterator MoveBefore,
MachineInstr &MI, LiveIntervals &LIS) {
MachineBasicBlock &MBB = *MI.getParent();
const MachineFunction &MF = *MBB.getParent();
const MachineRegisterInfo &MRI = MF.getRegInfo();
const TargetRegisterInfo &TRI = *MF.getSubtarget().getRegisterInfo();
MBB.splice(MoveBefore, &MBB, MI.getIterator());
LIS.handleMove(MI);
RegisterOperands RegOpers;
RegOpers.collect(MI, TRI, MRI, true, /*IgnoreDead=*/false);
RegOpers.adjustLaneLiveness(LIS, MRI, MI);
};
};
} // namespace
/// All custon asserts/expects assume that a RematerializerWrapper is in scope
/// and named RW.
/// Asserts that the number of expected rematerializable registers indeed tracks
/// the actual number correctly.
#define ASSERT_NUM_REMAT_REGS() ASSERT_EQ(RW->getNumRegs(), RW.NumRematRegs)
/// Asserts that all regions match expected sizes from the test rematerializer.
#define ASSERT_REGION_SIZES() \
{ \
for (const auto [RegionIdx, ExpectedSize] : enumerate(RW.RegionSizes)) \
ASSERT_EQ(RW.getRegionSize(RegionIdx), ExpectedSize); \
}
/// Expects that register RegIdx in the rematerializer has a total of N users.
#define EXPECT_NUM_USERS(RegIdx, N) \
EXPECT_EQ(RW.getNumUsers(RegIdx), static_cast<unsigned>(N))
/// Expects that register RegIdx in the rematerializer has a total of N
/// dependencies.
#define EXPECT_NUM_DEPENDENCIES(RegIdx, N) \
EXPECT_EQ(RW->getReg(RegIdx).Dependencies.size(), static_cast<unsigned>(N))
/// Expects that register RegIdx in the rematerializer has no users.
#define EXPECT_NO_USERS(RegIdx) EXPECT_NUM_USERS(RegIdx, 0)
/// Expects that rematerialized register RegIdx has origin OriginIdx, is defined
/// in region DefRegionIdx, and has a total of NumUsers users.
#define EXPECT_REMAT(RegIdx, OriginIdx, DefRegionIdx, NumUsers) \
{ \
const Rematerializer::Reg &RematReg = RW->getReg(RegIdx); \
EXPECT_EQ(RW->getOriginOf(RegIdx), OriginIdx); \
EXPECT_EQ(RematReg.DefRegion, DefRegionIdx); \
EXPECT_NUM_USERS(RegIdx, NumUsers); \
}
/// Rematerializes a tree of registers to a single user in different ways using
/// the dependency reuse mechanics and the coarse-grained or more fine-grained
/// API. Rollback rematerializations in-between each different wave of
/// rematerializations.
TEST_F(RematerializerTest, TreeRematRollback) {
StringRef MIRBody = R"MIR(
bb.0:
%0:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 0, implicit $exec, implicit $mode
%1:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 1, implicit $exec, implicit $mode
%2:vgpr_32 = V_ADD_U32_e32 %0, %1, implicit $exec
%3:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 3, implicit $exec, implicit $mode
%4:vgpr_32 = V_ADD_U32_e32 %2, %3, implicit $exec
bb.1:
S_NOP 0, implicit %4
S_ENDPGM 0
)MIR";
rematerializerTest(MIRBody, [](RematerializerWrapper &RW) {
Rematerializer::DependencyReuseInfo DRI;
Rollbacker Rollbacker;
RW->addListener(&Rollbacker);
const unsigned MBB0 = 0, MBB1 = 1;
const RegisterIdx Cst0 = 0, Cst1 = 1, Add01 = 2, Cst3 = 3, Add23 = 4;
// Rematerialize Add23 with all transitive dependencies.
RW->rematerializeToRegion(Add23, MBB1, DRI);
EXPECT_NO_USERS(Cst0);
EXPECT_NO_USERS(Cst1);
EXPECT_NO_USERS(Add01);
EXPECT_NO_USERS(Cst3);
EXPECT_NO_USERS(Add23);
RW.moveMIs(MBB0, MBB1, 5).addRematRegs(5);
ASSERT_REGION_SIZES();
ASSERT_NUM_REMAT_REGS();
// After rollback all rematerializations are removed from the MIR.
Rollbacker.rollback(*RW);
RW.moveMIs(MBB1, MBB0, 5);
ASSERT_REGION_SIZES();
// Rematerialize Add23 only with its direct dependencies, reuse the rest.
DRI.clear().reuse(Cst0).reuse(Cst1);
RW->rematerializeToRegion(Add23, MBB1, DRI);
EXPECT_NUM_USERS(Cst0, 1);
EXPECT_NUM_USERS(Cst1, 1);
EXPECT_NO_USERS(Add01);
EXPECT_NO_USERS(Cst3);
EXPECT_NO_USERS(Add23);
RW.moveMIs(MBB0, MBB1, 3).addRematRegs(3);
ASSERT_REGION_SIZES();
ASSERT_NUM_REMAT_REGS();
// After rollback all rematerializations are removed from the MIR.
Rollbacker.rollback(*RW);
RW.moveMIs(MBB1, MBB0, 3);
ASSERT_REGION_SIZES();
// Rematerialize Add23 only with its direct dependencies as before, but
// with as fine-grained operations as possible.
MachineInstr *NopMI = &*RW->getRegion(MBB1).first;
DRI.clear().reuse(Cst0).reuse(Cst1);
const RegisterIdx RematAdd01 =
RW->rematerializeToPos(Add01, MBB1, NopMI, DRI);
EXPECT_NO_USERS(RematAdd01);
EXPECT_NUM_USERS(Add01, 1);
EXPECT_NUM_USERS(Cst0, 2);
EXPECT_NUM_USERS(Cst1, 2);
DRI.clear();
const RegisterIdx RematCst3 =
RW->rematerializeToPos(Cst3, MBB1, NopMI, DRI);
EXPECT_NO_USERS(RematCst3);
EXPECT_NUM_USERS(Cst3, 1);
DRI.clear().useRemat(Add01, RematAdd01).useRemat(Cst3, RematCst3);
const RegisterIdx RematAdd23 =
RW->rematerializeToPos(Add23, MBB1, NopMI, DRI);
EXPECT_NO_USERS(RematAdd23);
EXPECT_NUM_USERS(Add23, 1);
EXPECT_NUM_USERS(RematAdd01, 1);
EXPECT_NUM_USERS(RematCst3, 1);
RW->transferUser(Add23, RematAdd23, MBB1, *NopMI);
EXPECT_NO_USERS(Add23);
EXPECT_NUM_USERS(RematAdd23, 1);
RW.moveMIs(MBB0, MBB1, 3).addRematRegs(3);
ASSERT_REGION_SIZES();
ASSERT_NUM_REMAT_REGS();
});
}
/// To rematerialize %3 along with all its dependencies before its only use in
/// bb.1, we must first rematerialize %0 and %1 (in any order), then %2, and
/// finally %3. The rematerializer had a rematerialization order bug wherein,
/// because %0 is also used directly in the MI defining %3, it was
/// rematerialized after %2, breaking the invariant that dependencies of a
/// register must always be rematerialized before the register itself.
TEST_F(RematerializerTest, MultiplePathsRematOrder) {
StringRef MIRBody = R"MIR(
bb.0:
%0:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 0, implicit $exec, implicit $mode
%1:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 1, implicit $exec, implicit $mode
%2:vgpr_32 = V_ADD_U32_e32 %0, %1, implicit $exec
%3:vgpr_32 = V_ADD_U32_e32 %0, %2, implicit $exec
bb.1:
S_NOP 0, implicit %3
S_ENDPGM 0
)MIR";
rematerializerTest(MIRBody, [](RematerializerWrapper &RW) {
Rematerializer::DependencyReuseInfo DRI;
const unsigned MBB1 = 1;
const RegisterIdx Add02 = 3;
RW->rematerializeToRegion(Add02, MBB1, DRI);
});
}
/// Rematerializes a single register to multiple regions, tracking that
/// rematerializations are linked correctly and making sure that the original
/// register is deleted automatically when it no longer has any uses.
TEST_F(RematerializerTest, MultiRegionsRemat) {
StringRef MIRBody = R"MIR(
bb.0:
%0:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 0, implicit $exec, implicit $mode
bb.1:
S_NOP 0, implicit %0, implicit %0
bb.2:
S_NOP 0, implicit %0
S_NOP 0, implicit %0
bb.3:
S_NOP 0, implicit %0
S_ENDPGM 0
)MIR";
rematerializerTest(MIRBody, [](RematerializerWrapper &RW) {
Rematerializer::DependencyReuseInfo DRI;
const unsigned MBB0 = 0, MBB1 = 1, MBB2 = 2, MBB3 = 3;
const RegisterIdx Cst0 = 0;
// Rematerialization to MBB1.
const RegisterIdx RematBB1 = RW->rematerializeToRegion(Cst0, MBB1, DRI);
RW.addMIs(MBB1, 1);
ASSERT_REGION_SIZES();
EXPECT_REMAT(RematBB1, Cst0, MBB1, 1);
// Rematerialization to MBB2.
DRI.clear();
const RegisterIdx RematBB2 = RW->rematerializeToRegion(Cst0, MBB2, DRI);
RW.addMIs(MBB2, 1);
ASSERT_REGION_SIZES();
EXPECT_REMAT(RematBB2, Cst0, MBB2, 2);
// Rematerialization to MBB3. Rematerializing to the last original user
// deletes the original register.
DRI.clear();
const RegisterIdx RematBB3 = RW->rematerializeToRegion(Cst0, MBB3, DRI);
RW.moveMIs(MBB0, MBB3, 1);
ASSERT_REGION_SIZES();
EXPECT_REMAT(RematBB3, Cst0, MBB3, 1);
});
}
/// Rematerializes a tree of register with some unrematerializable operands to a
/// final destination in two steps, creating rematerializations of
/// rematerializations in the process. Make sure that origins of
/// rematerializations are always original registers.
TEST_F(RematerializerTest, MultiStep) {
StringRef MIRBody = R"MIR(
bb.0:
%0:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 0, implicit $exec, implicit $mode
%1:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 1, implicit $exec, implicit $mode, implicit-def $m0
%2:vgpr_32 = V_ADD_U32_e32 %0, %1, implicit $exec
S_NOP 0, implicit %0
bb.1:
%3:vgpr_32 = V_ADD_U32_e32 %2, %2, implicit $exec
bb.2:
S_NOP 0, implicit %3
S_ENDPGM 0
)MIR";
rematerializerTest(MIRBody, [](RematerializerWrapper &RW) {
Rematerializer::DependencyReuseInfo DRI;
const unsigned MBB0 = 0, MBB1 = 1, MBB2 = 2;
const RegisterIdx Cst0 = 0, Add01 = 1, Add22 = 2, RematCst0 = 3,
RematAdd01 = 4, RematRematAdd01 = 5, RematAdd22 = 6;
// Rematerialize Add01 from the first to the second block along with its
// single rematerializable dependency (constant 0). The constant 1 has an
// implicit def that is non-ignorable so it cannot be rematerialized. The
// constant 0 remains in the first block because it has a user there, but
// the add is deleted.
RW->rematerializeToRegion(Add01, MBB1, DRI);
RW.removeMIs(MBB0, 1).addMIs(MBB1, 2);
ASSERT_REGION_SIZES();
EXPECT_REMAT(RematCst0, Cst0, MBB1, 1);
EXPECT_REMAT(RematAdd01, Add01, MBB1, 1);
// Rematerialize Add22 from the second to the third block, which will also
// indirectly rematerialize RematAdd01; make sure the latter's
// rematerialization's origin is the original register, not RematAdd01.
DRI.clear().reuse(RematCst0);
RW->rematerializeToRegion(Add22, MBB2, DRI);
RW.moveMIs(MBB1, MBB2, 2);
ASSERT_REGION_SIZES();
EXPECT_REMAT(RematRematAdd01, Add01, MBB2, 1);
EXPECT_REMAT(RematAdd22, Add22, MBB2, 1);
});
}
/// Checks that it is possible to rematerialize inside a region that was
/// rendered empty by previous rematerializations (as long as the region ends
/// with a terminator).
TEST_F(RematerializerTest, EmptyRegion) {
StringRef MIRBody = R"MIR(
bb.0:
%0:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 0, implicit $exec, implicit $mode
%1:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 1, implicit $exec, implicit $mode
bb.1:
%2:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 2, implicit $exec, implicit $mode
bb.2:
%3:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 3, implicit $exec, implicit $mode
S_BRANCH %bb.3
bb.3:
S_NOP 0, implicit %0, implicit %1
S_NOP 0, implicit %2, implicit %3
S_ENDPGM 0
)MIR";
rematerializerTest(MIRBody, [](RematerializerWrapper &RW) {
Rematerializer::DependencyReuseInfo DRI;
const unsigned MBB0 = 0, MBB1 = 1, MBB2 = 2, MBB3 = 3;
const RegisterIdx Cst0 = 0, Cst1 = 1, Cst2 = 2, Cst3 = 3;
// After rematerializing %2 and %3 to bb.3, their respective original
// defining regions are empty. %2's region ends at the end of its parent
// block, whereas %3's region ends at a terminator MI (S_BRANCH).
RW->rematerializeToRegion(Cst2, MBB3, DRI);
RW->rematerializeToRegion(Cst3, MBB3, DRI.clear());
RW.removeMIs(MBB1, 1).removeMIs(MBB2, 1).addMIs(MBB3, 2);
ASSERT_REGION_SIZES();
// Move %0 to the empty MBB1 block/region.
const RegisterIdx RematCst0 =
RW->rematerializeToRegion(Cst0, MBB1, DRI.clear());
RW->transferRegionUsers(Cst0, RematCst0, MBB3);
// Move %1 to the empty MBB2 region, right before the S_BRANCH terminator.
const RegisterIdx RematCst1 = RW->rematerializeToPos(
Cst1, MBB2, RW->getRegion(MBB2).first, DRI.clear());
RW->transferRegionUsers(Cst1, RematCst1, MBB3);
RW.removeMIs(MBB0, 2).addMIs(MBB1, 1).addMIs(MBB2, 1);
ASSERT_REGION_SIZES();
});
}
/// Checks that only registers with a single definition are rematerializable,
/// even when registers are made up of multiple sub-registers each with their
/// own definition.
TEST_F(RematerializerTest, SubRegRematSupport) {
StringRef MIRBody = R"MIR(
bb.0:
undef %01.sub0:vreg_64_align2 = nofpexcept V_CVT_I32_F64_e32 0, implicit $exec, implicit $mode
%01.sub1:vreg_64_align2 = nofpexcept V_CVT_I32_F64_e32 1, implicit $exec, implicit $mode
undef %2.sub0:vreg_64_align2 = nofpexcept V_CVT_I32_F64_e32 2, implicit $exec, implicit $mode
undef %34.sub0:vreg_64_align2 = nofpexcept V_CVT_I32_F64_e32 3, implicit $exec, implicit $mode
undef %56.sub0:sreg_64 = S_MOV_B32 5
%56.sub1:sreg_64 = S_MOV_B32 6, implicit-def $m0
undef %78.sub0:sreg_64 = S_MOV_B32 7
S_NOP 0, implicit %78.sub0
%78.sub1:sreg_64 = S_MOV_B32 8
undef %99.sub0:sreg_64 = S_MOV_B32 9
%99.sub1:sreg_64 = S_MOV_B32 %99.sub0
bb.1:
%34.sub1:vreg_64_align2 = nofpexcept V_CVT_I32_F64_e32 4, implicit $exec, implicit $mode
S_NOP 0, implicit %01, implicit %2, implicit %34, implicit %56, implicit %78, implicit %99
S_ENDPGM 0
)MIR";
rematerializerTest(MIRBody, [](RematerializerWrapper &RW) {
Rematerializer::DependencyReuseInfo DRI;
const unsigned MBB0 = 0, MBB1 = 1;
const RegisterIdx Cst01 = 0, Cst2 = 1, Cst99 = 2;
// - %34 is not rematerializable because it is defined over multiple
// regions.
// - %56 is not rematerializable because the second defining MI is
// unrematerializable due to the implicit def.
// - %78 is not rematerializable because it is read by an MI not defining it
// before its last definition.
EXPECT_EQ(RW->getNumRegs(), 3U);
auto CheckBasicRemat = [&](RegisterIdx RegIdx,
unsigned NumExpectDefs) -> void {
Rematerializer::DependencyReuseInfo DRI;
EXPECT_EQ(RW->getReg(RegIdx).Defs.size(), NumExpectDefs);
const RegisterIdx Remat = RW->rematerializeToRegion(RegIdx, MBB1, DRI);
RW.moveMIs(MBB0, MBB1, NumExpectDefs);
ASSERT_REGION_SIZES();
EXPECT_REMAT(Remat, RegIdx, MBB1, 1);
};
CheckBasicRemat(Cst01, 2);
CheckBasicRemat(Cst2, 1);
CheckBasicRemat(Cst99, 2);
});
}
/// Checks that the user transfer logic works correctly when different defining
/// MIs of the same rematerializable register start dependening on different
/// versions (original and rematerialized) of the same register.
TEST_F(RematerializerTest, SubRegUserTransfer) {
StringRef MIRBody = R"MIR(
bb.0:
undef %01.sub0:sreg_64 = S_MOV_B32 0
%01.sub1:sreg_64 = S_MOV_B32 1
bb.1:
undef %23.sub0:sreg_64 = S_MOV_B32 %01.sub0
%23.sub1:sreg_64 = S_MOV_B32 %01.sub1
S_NOP 0, implicit %23
S_ENDPGM 0
)MIR";
rematerializerTest(MIRBody, [](RematerializerWrapper &RW) {
Rematerializer::DependencyReuseInfo DRI;
Rollbacker Rollback;
RW->addListener(&Rollback);
const unsigned MBB1 = 1;
const RegisterIdx Cst01 = 0, Cst23 = 1;
EXPECT_EQ(RW->getReg(Cst01).Defs.size(), 2U);
EXPECT_EQ(RW->getReg(Cst23).Defs.size(), 2U);
MachineInstr *Cst23FirstDef = RW->getReg(Cst23).Defs[0];
MachineInstr *Cst23SecondDef = RW->getReg(Cst23).Defs[1];
// Create a rematerialization of %01 just before %23.
const RegisterIdx RematCst01 =
RW->rematerializeToPos(Cst01, MBB1, Cst23FirstDef, DRI);
EXPECT_NUM_USERS(Cst01, 2);
EXPECT_NUM_USERS(RematCst01, 0);
EXPECT_NUM_USERS(Cst23, 1);
EXPECT_NUM_DEPENDENCIES(Cst23, 1);
// Have the first def of %23 use the rematerialization of %01 (the second
// def still uses %01). This transfers a user to the rematerialization of
// %01 and adds the rematerialization of %01 as a rematerializable
// dependency to %23.
RW->transferUser(Cst01, RematCst01, MBB1, *Cst23FirstDef);
EXPECT_NUM_USERS(Cst01, 1);
EXPECT_NUM_USERS(RematCst01, 1);
EXPECT_NUM_USERS(Cst23, 1);
EXPECT_NUM_DEPENDENCIES(Cst23, 2);
// Have the second def of %23 use the rematerialization of %01 as well. This
// transfers a user to the rematerialization of %01 and removes %01 as a
// rematerializable dependency of %23.
RW->transferUser(Cst01, RematCst01, MBB1, *Cst23SecondDef);
EXPECT_NUM_USERS(Cst01, 0);
EXPECT_NUM_USERS(RematCst01, 2);
EXPECT_NUM_DEPENDENCIES(Cst23, 1);
// Rollback should restore everything to its original state.
Rollback.rollback(*RW);
EXPECT_NUM_USERS(Cst01, 2);
EXPECT_NUM_USERS(RematCst01, 0);
EXPECT_NUM_USERS(Cst23, 1);
EXPECT_NUM_DEPENDENCIES(Cst23, 1);
});
}
TEST_F(RematerializerTest, SubRegRollback) {
StringRef MIRBody = R"MIR(
bb.0:
undef %01.sub0:sreg_64 = S_MOV_B32 0
%unremat0:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 0, implicit $exec, implicit $mode, implicit-def $m0
%01.sub1:sreg_64 = S_MOV_B32 1
%unremat1:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 1, implicit $exec, implicit $mode, implicit-def $m0
bb.1:
undef %23.sub0:sreg_64 = S_MOV_B32 2
%23.sub1:sreg_64 = S_MOV_B32 3
bb.2:
undef %45.sub0:sreg_64 = S_MOV_B32 4
undef %67.sub0:sreg_64 = S_MOV_B32 6
%45.sub1:sreg_64 = S_MOV_B32 5
%67.sub1:sreg_64 = S_MOV_B32 7
bb.3:
S_NOP 0, implicit %01, implicit %23, implicit %45, implicit %67
S_NOP 0, implicit %unremat0, implicit %unremat1
S_ENDPGM 0
)MIR";
rematerializerTest(MIRBody, [](RematerializerWrapper &RW) {
Rematerializer::DependencyReuseInfo DRI;
Rollbacker Rollback;
RW->addListener(&Rollback);
const unsigned MBB0 = 0, MBB1 = 1, MBB2 = 2, MBB3 = 3;
const RegisterIdx Cst01 = 0, Cst23 = 1, Cst45 = 2, Cst67 = 3;
EXPECT_EQ(RW->getReg(Cst01).Defs.size(), 2U);
EXPECT_EQ(RW->getReg(Cst23).Defs.size(), 2U);
EXPECT_EQ(RW->getReg(Cst45).Defs.size(), 2U);
EXPECT_EQ(RW->getReg(Cst67).Defs.size(), 2U);
auto GetNextMI = [&](MachineInstr *MI) -> MachineInstr * {
return &*std::next(MI->getIterator());
};
auto GetDefMI = [&](RegisterIdx RegIdx, unsigned DefIdx) -> MachineInstr * {
return RW->getReg(RegIdx).Defs[DefIdx];
};
// Rematerialize and rollback %01.
MachineInstr *Unremat0 = GetNextMI(GetDefMI(Cst01, 0));
MachineInstr *Unremat1 = GetNextMI(GetDefMI(Cst01, 1));
const RegisterIdx RematCst01 =
RW->rematerializeToRegion(Cst01, MBB3, DRI.clear());
RW.moveMIs(MBB0, MBB3, 2);
ASSERT_REGION_SIZES();
EXPECT_REMAT(RematCst01, Cst01, MBB3, 1);
// Rollback must re-create MIs in the same order.
Rollback.rollback(*RW);
RW.moveMIs(MBB3, MBB0, 2);
ASSERT_REGION_SIZES();
EXPECT_EQ(Unremat0, GetNextMI(GetDefMI(Cst01, 0)));
EXPECT_EQ(Unremat1, GetNextMI(GetDefMI(Cst01, 1)));
// Rematerialize and rollback %23.
MachineBasicBlock::iterator EndOfMBB1 =
std::next(GetDefMI(Cst23, 1)->getIterator());
const RegisterIdx RematCst23 =
RW->rematerializeToRegion(Cst23, MBB3, DRI.clear());
RW.moveMIs(MBB1, MBB3, 2);
ASSERT_REGION_SIZES();
EXPECT_REMAT(RematCst23, Cst23, MBB3, 1);
// Rollback must re-create MIs in the same order.
Rollback.rollback(*RW);
RW.moveMIs(MBB3, MBB1, 2);
ASSERT_REGION_SIZES();
MachineInstr *Cst23Def0 = GetDefMI(Cst23, 0);
MachineInstr *Cst23Def1 = GetDefMI(Cst23, 1);
EXPECT_EQ(Cst23Def1, GetNextMI(Cst23Def0));
EXPECT_EQ(EndOfMBB1, std::next(Cst23Def1->getIterator()));
// Rematerialize and rollback %45 and %67.
MachineBasicBlock::iterator EndOfMBB2 =
std::next(GetDefMI(Cst67, 1)->getIterator());
const RegisterIdx RematCst45 =
RW->rematerializeToRegion(Cst45, MBB3, DRI.clear());
const RegisterIdx RematCst67 =
RW->rematerializeToRegion(Cst67, MBB3, DRI.clear());
RW.moveMIs(MBB2, MBB3, 4);
ASSERT_REGION_SIZES();
EXPECT_REMAT(RematCst45, Cst45, MBB3, 1);
EXPECT_REMAT(RematCst67, Cst67, MBB3, 1);
// Rollback must re-create MIs in the same order.
Rollback.rollback(*RW);
RW.moveMIs(MBB3, MBB2, 4);
ASSERT_REGION_SIZES();
MachineInstr *Cst45Def0 = GetDefMI(Cst45, 0);
MachineInstr *Cst67Def0 = GetDefMI(Cst67, 0);
MachineInstr *Cst45Def1 = GetDefMI(Cst45, 1);
MachineInstr *Cst67Def1 = GetDefMI(Cst67, 1);
EXPECT_EQ(Cst67Def0, GetNextMI(Cst45Def0));
EXPECT_EQ(Cst45Def1, GetNextMI(Cst67Def0));
EXPECT_EQ(Cst67Def1, GetNextMI(Cst45Def1));
EXPECT_EQ(EndOfMBB2, std::next(Cst67Def1->getIterator()));
});
}
/// Checks that instructions which use a rematerializable register as their
/// first operand (here the KILL pseudo) are not treated as defining
/// instructions for that register.
TEST_F(RematerializerTest, FirstOperandNotDef) {
StringRef MIRBody = R"MIR(
bb.0:
undef %0.sub0:sgpr_64 = S_MOV_B32 0
KILL %0
S_ENDPGM 0
)MIR";
rematerializerTest(MIRBody, [](RematerializerWrapper &RW) {
Rematerializer::DependencyReuseInfo DRI;
const RegisterIdx Cst0 = 0;
EXPECT_EQ(RW->getNumRegs(), 1U);
EXPECT_EQ(RW->getReg(Cst0).Defs.size(), 1U);
EXPECT_NUM_USERS(Cst0, 1);
});
}
/// When updating a subregister-tracked interval, extendToNewUsers must create
/// subranges even when the transferred user reads the full register. A previous
/// version only did so for users reading a partial lane mask, so processing a
/// full-register user first left the interval without subranges; a later
/// subregister use then tripped the `SubRegIdx != 0 && LI.hasSubRanges()`
/// assertion in VirtRegRewriter.
TEST_F(RematerializerTest, ExtendToNewUsersFullMaskCreatesSubRanges) {
StringRef MIRBody = R"MIR(
bb.0:
%0:vreg_64_align2 = V_MOV_B64_PSEUDO 0, implicit $exec
bb.1:
S_NOP 0, implicit %0.sub0
S_NOP 0, implicit %0
S_ENDPGM 0
)MIR";
rematerializerTest(MIRBody, [](RematerializerWrapper &RW) {
Rematerializer::DependencyReuseInfo DRI;
const unsigned MBB1 = 1;
const RegisterIdx Cst0 = 0;
ASSERT_EQ(RW->getNumRegs(), 1U);
MachineBasicBlock &BB1 = *RW.MF.getBlockNumbered(1);
MachineInstr *NopSub0 = &*BB1.begin();
MachineInstr *NopFull = &*std::next(BB1.begin());
// Rematerialize %0 into bb.1. The fresh remat register is defined by a
// full-register def, so its interval starts out without subranges; this
// guards against the test silently degrading into a tautology should remat
// ever create subranges eagerly.
const RegisterIdx RematCst0 =
RW->rematerializeToPos(Cst0, MBB1, NopSub0, DRI);
const Register DefReg = RW->getReg(RematCst0).getDefReg();
ASSERT_FALSE(RW.LIS.getInterval(DefReg).hasSubRanges());
// Transferring the full-register user first must still create subranges for
// the subregister-tracked interval; without them the subsequent .sub0 user
// would leave VirtRegRewriter asserting.
RW->transferUser(Cst0, RematCst0, MBB1, *NopFull);
ASSERT_TRUE(RW.LIS.getInterval(DefReg).hasSubRanges());
RW->transferUser(Cst0, RematCst0, MBB1, *NopSub0);
EXPECT_TRUE(RW.LIS.getInterval(DefReg).hasSubRanges());
});
}
/// The rematerializer had a bug where re-creating the interval of a
/// super-register defined over multiple MIs, some of which defining entirely
/// dead subregisters, could cause a crash when changing the order of
/// sub-definitions (for example during scheduling) because the re-created
/// interval could end up with multiple connected components, which is illegal.
/// The solution is to elimimate dead definitions in such cases.
TEST_F(RematerializerTest, SplitSubRegDeadDef) {
StringRef MIRBody = R"MIR(
bb.0:
undef %0.sub0:vreg_64 = IMPLICIT_DEF
%0.sub1:vreg_64 = IMPLICIT_DEF
%1:vgpr_32 = V_ADD_U32_e32 %0.sub0, %0.sub0, implicit $exec
bb.1:
S_NOP 0, implicit %1
S_ENDPGM 0
)MIR";
ProcessMIRFn PreRemat = [this](MachineFunction &MF, LiveIntervals &LIS) {
MachineBasicBlock &MBB0 = *MF.getBlockNumbered(0);
MachineInstr &Sub0Def = *MBB0.begin();
MachineInstr &Sub1Def = *std::next(Sub0Def.getIterator());
// Flip %0's subdefinition order. After the move, the definitions look
// like:
// undef %0.sub1:vreg_64 = IMPLICIT_DEF
// undef %0.sub0:vreg_64 = IMPLICIT_DEF
moveMIAndAdjustLiveness(Sub0Def.getIterator(), Sub1Def, LIS);
};
rematerializerTest(
MIRBody,
[](RematerializerWrapper &RW) {
// Only %1 should be rematerializable.
ASSERT_EQ(RW->getNumRegs(), 1U);
// Rematerialize %1 to bb.1. This triggers a live-interval update of %0,
// during which the sub1 def is identified as dead and sub-sequently
// removed.
Rematerializer::DependencyReuseInfo DRI;
const unsigned MBB0 = 0, MBB1 = 1;
const RegisterIdx Add = 0;
RW->rematerializeToRegion(Add, MBB1, DRI);
// The add is moved to another region.
RW.moveMIs(MBB0, MBB1, 1);
// The sub1 def is dead and deleted.
RW.removeMIs(MBB0, 1);
ASSERT_REGION_SIZES();
},
PreRemat);
}
/// Uses of undefined lanes may create empty sub-ranges during live-interval
/// refinement. Empty sub-ranges are illegal and are only allowed to exist
/// temporarily. The rematerializer now automatically deletes these empty
/// sub-ranges.
TEST_F(RematerializerTest, RemoveEmptySubRanges) {
StringRef MIRBody = R"MIR(
bb.0:
undef %fullUndefUse.sub0:vreg_64 = IMPLICIT_DEF
undef %partialUndefUse.sub0_sub1_sub2:vreg_128 = IMPLICIT_DEF
bb.1:
S_NOP 0, implicit %fullUndefUse.sub1
S_NOP 0, implicit %partialUndefUse.sub2_sub3
S_ENDPGM 0
)MIR";
rematerializerTest(MIRBody, [](RematerializerWrapper &RW) {
// Both registers in bb.0 should be rematerializable.
ASSERT_EQ(RW->getNumRegs(), 2U);
// Rematerialize both registers to bb.1. When the new registers intervals
// are created and extended to their users in bb.1, an empty sub-range will
// be temporarily created then removed immediately.
Rematerializer::DependencyReuseInfo DRI;
const unsigned MBB1 = 1;
const RegisterIdx FullUndefUse = 0, PartialUndefUse = 1;
RW->rematerializeToRegion(FullUndefUse, MBB1, DRI);
RW->rematerializeToRegion(PartialUndefUse, MBB1, DRI.clear());
});
}
/// Checks that dead-def elimination successfully deletes all unrematerializable
/// MIs and rematerializable registers that become dead after shrinking the
/// interval of an unrematerializable register reveals a dead definition.
TEST_F(RematerializerTest, DeadDefCascadeDeletion) {
StringRef MIRBody = R"MIR(
bb.0:
%cst0Die:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 0, implicit $exec, implicit $mode
%cst1Die:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 1, implicit $exec, implicit $mode
%addDie:vgpr_32 = V_ADD_U32_e32 %cst0Die, %cst1Die, implicit $exec
undef %multidefDontDie.sub0:vreg_64 = IMPLICIT_DEF
%multidefDontDie.sub1:vreg_64 = IMPLICIT_DEF
bb.1:
%cst2:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 2, implicit $exec, implicit $mode
undef %multidef.sub0:vreg_64 = IMPLICIT_DEF
%multidef.sub1:vreg_64 = V_ADD_U32_e32 %addDie, %multidefDontDie.sub1, implicit $exec
%add:vgpr_32 = V_ADD_U32_e32 %multidef.sub0, %multidefDontDie.sub0, implicit $exec
bb.2:
S_NOP 0, implicit %cst2, implicit %add
S_ENDPGM 0
)MIR";
ProcessMIRFn PreRemat = [this](MachineFunction &MF, LiveIntervals &LIS) {
MachineBasicBlock &MBB0 = *MF.getBlockNumbered(1);
MachineInstr &Sub0Def = *std::next(MBB0.begin());
MachineInstr &Sub1Def = *std::next(Sub0Def.getIterator());
// Flip %multidef's subdefinition order. After the move, the definitions
// look like:
// undef %multidef.sub1:vreg_64 = ...
// undef %multidef.sub0:vreg_64 = ...
moveMIAndAdjustLiveness(Sub0Def.getIterator(), Sub1Def, LIS);
};
rematerializerTest(
MIRBody,
[](RematerializerWrapper &RW) {
Rollbacker Rollback;
RW->addListener(&Rollback);
Rematerializer::DependencyReuseInfo DRI;
const unsigned MBB0 = 0, MBB1 = 1, MBB2 = 2;
const RegisterIdx Cst1Die = 1, AddDie = 2, MultidefDontDie = 3,
Cst2 = 4, Add = 5;
ASSERT_EQ(RW->getNumRegs(), 6U);
// Rematerialize %addDie along with %cst0Die right after %cst2.
RW->rematerializeToRegion(AddDie, MBB1, DRI.reuse(Cst1Die));
RW.moveMIs(MBB0, MBB1, 2);
// %cst2 and %add are moved to their using region.
RW->rematerializeToRegion(Cst2, MBB2, DRI.clear());
RW->rematerializeToRegion(Add, MBB2,
DRI.clear().reuse(MultidefDontDie));
RW.moveMIs(MBB1, MBB2, 2);
// The rematerialization of %add makes %multidef.sub1 become a dead def.
// It is deleted along with %addDie, %cst1Die, and %cst0Die, which in
// turn no longer have any uses. These are rematerializable registers
// that become "permanently dead" in the rematerializer's nomenclature.
RW.removeMIs(MBB1, 3);
RW.removeMIs(MBB0, 1);
ASSERT_REGION_SIZES();
// We are mostly interested in %cst2 being re-created correctly. When
// it was rematerialized it was followed by rematerializations that have
// now been permanently deleted (which cannot therefore be rolled back),
// and by an unrematerializable MI that has also been permanently
// deleted. It should be re-created at the beginning of its block, as it
// was initially.
Rollback.rollback(*RW);
EXPECT_EQ(RW->getReg(Cst2).getFirstDef(),
&*RW.MF.getBlockNumbered(1)->begin());
RW.moveMIs(MBB2, MBB1, 2);
ASSERT_REGION_SIZES();
},
PreRemat);
}
/// Checks that rollback works as expected when the rollback listener is added
/// mid-rematerializations.
TEST_F(RematerializerTest, Rollback) {
StringRef MIRBody = R"MIR(
bb.0:
%0:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 0, implicit $exec, implicit $mode
%1:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 1, implicit $exec, implicit $mode
bb.1:
S_NOP 0, implicit %0, implicit %1
bb.2:
S_NOP 0, implicit %0, implicit %1
S_ENDPGM 0
)MIR";
rematerializerTest(MIRBody, [](RematerializerWrapper &RW) {
Rematerializer::DependencyReuseInfo DRI;
const unsigned MBB0 = 0, MBB1 = 1, MBB2 = 2;
const RegisterIdx Cst0 = 0, Cst1 = 1;
// Rematerialize %0 to MBB1, taking one user from the original register.
RegisterIdx RematCst0MBB1 = RW->rematerializeToRegion(Cst0, MBB1, DRI);
RW.addMIs(MBB1, 1).addRematRegs(1);
ASSERT_REGION_SIZES();
ASSERT_NUM_REMAT_REGS();
Rollbacker Rollback;
RW->addListener(&Rollback);
// Rematerialize %0 to MBB2 and %1 to MBB1/MBB2; each rematerialization ends
// up with a single user and both original registers are deleted.
RegisterIdx RematCst0MBB2 =
RW->rematerializeToRegion(Cst0, MBB2, DRI.clear());
RegisterIdx RematCst1MBB1 =
RW->rematerializeToRegion(Cst1, MBB1, DRI.clear());
RegisterIdx RematCst1MBB2 =
RW->rematerializeToRegion(Cst1, MBB2, DRI.clear());
RW.removeMIs(MBB0, 2).addMIs(MBB1, 1).addMIs(MBB2, 2).addRematRegs(3);
ASSERT_REGION_SIZES();
ASSERT_NUM_REMAT_REGS();
EXPECT_NO_USERS(Cst0);
EXPECT_NO_USERS(Cst1);
EXPECT_NUM_USERS(RematCst0MBB1, 1);
EXPECT_NUM_USERS(RematCst0MBB2, 1);
EXPECT_NUM_USERS(RematCst1MBB1, 1);
EXPECT_NUM_USERS(RematCst1MBB2, 1);
// Rollback all changes since the rollbacker was added. The first
// rematerialization of %0 to MBB1 happened before so it is not rolled back.
// However %0 is re-created because it was deleted after.
Rollback.rollback(*RW);
RW.addMIs(MBB0, 2).removeMIs(MBB1, 1).removeMIs(MBB2, 2);
ASSERT_REGION_SIZES();
ASSERT_NUM_REMAT_REGS();
EXPECT_NUM_USERS(Cst0, 1);
EXPECT_NUM_USERS(Cst1, 2);
EXPECT_NUM_USERS(RematCst0MBB1, 1);
EXPECT_NO_USERS(RematCst0MBB2);
EXPECT_NO_USERS(RematCst1MBB1);
EXPECT_NO_USERS(RematCst1MBB2);
});
}
/// Checks that rollback re-creates MIs at correct positions when the order of
/// register deletions forces the re-creation logic to iterate through multiple
/// deleted registers' respective insert position to find a valid one.
TEST_F(RematerializerTest, RollbackInvalidInsertPos) {
StringRef MIRBody = R"MIR(
bb.0:
%0:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 0, implicit $exec, implicit $mode
%1:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 1, implicit $exec, implicit $mode
%2:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 2, implicit $exec, implicit $mode
%3:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 3, implicit $exec, implicit $mode
bb.1:
S_NOP 0, implicit %0, implicit %1, implicit %2, implicit %3
S_ENDPGM 0
)MIR";
rematerializerTest(MIRBody, [](RematerializerWrapper &RW) {
Rematerializer::DependencyReuseInfo DRI;
Rollbacker Rollback;
RW->addListener(&Rollback);
const unsigned MBB0 = 0, MBB1 = 1;
const RegisterIdx Cst0 = 0, Cst1 = 1, Cst2 = 2, Cst3 = 3;
auto RematToMBB1 = [&](RegisterIdx RegIdx) -> void {
// Rematerialize %RegIdx to MBB1, deleting the original register.
RW->rematerializeToRegion(RegIdx, MBB1, DRI.clear());
RW.moveMIs(MBB0, MBB1, 1);
ASSERT_REGION_SIZES();
};
auto GetNextMI = [&](MachineInstr *MI) -> MachineInstr * {
return &*std::next(MI->getIterator());
};
auto RollbackAndCheckOriginalOrder = [&]() -> void {
// Rollback and check for correct instruction order in the original
// defining region. The asserts on region sizes ensure that all original
// registers were indeed deleted and will be re-created in the original
// region.
Rollback.rollback(*RW);
RW.moveMIs(MBB1, MBB0, 3);
ASSERT_REGION_SIZES();
MachineInstr *DefCst0 = RW->getReg(Cst0).getFirstDef();
MachineInstr *DefCst1 = RW->getReg(Cst1).getFirstDef();
MachineInstr *DefCst2 = RW->getReg(Cst2).getFirstDef();
MachineInstr *DefCst3 = RW->getReg(Cst3).getFirstDef();
EXPECT_EQ(GetNextMI(DefCst0), DefCst1);
EXPECT_EQ(GetNextMI(DefCst1), DefCst2);
EXPECT_EQ(GetNextMI(DefCst2), DefCst3);
};
// Test every possible rematerialization order.
RematToMBB1(Cst0);
RematToMBB1(Cst1);
RematToMBB1(Cst2);
RollbackAndCheckOriginalOrder();
RematToMBB1(Cst0);
RematToMBB1(Cst2);
RematToMBB1(Cst1);
RollbackAndCheckOriginalOrder();
RematToMBB1(Cst1);
RematToMBB1(Cst0);
RematToMBB1(Cst2);
RollbackAndCheckOriginalOrder();
RematToMBB1(Cst1);
RematToMBB1(Cst2);
RematToMBB1(Cst0);
RollbackAndCheckOriginalOrder();
RematToMBB1(Cst2);
RematToMBB1(Cst0);
RematToMBB1(Cst1);
RollbackAndCheckOriginalOrder();
RematToMBB1(Cst2);
RematToMBB1(Cst1);
RematToMBB1(Cst0);
RollbackAndCheckOriginalOrder();
});
}
/// Checks that rollback re-creates MIs in the correct order when the next MI
/// after a deleted one is a rematerialization of another MI.
TEST_F(RematerializerTest, RollbackNextPosIsRemat) {
StringRef MIRBody = R"MIR(
bb.0:
%0:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 0, implicit $exec, implicit $mode
%1:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 1, implicit $exec, implicit $mode
bb.1:
%2:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 2, implicit $exec, implicit $mode
S_NOP 0, implicit %0
bb.2:
%3:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 3, implicit $exec, implicit $mode
S_NOP 0, implicit %1
bb.3:
S_NOP 0, implicit %2, implicit %3
S_ENDPGM 0
)MIR";
rematerializerTest(MIRBody, [](RematerializerWrapper &RW) {
Rematerializer::DependencyReuseInfo DRI;
Rollbacker Rollback;
const unsigned MBB1 = 1, MBB2 = 2, MBB3 = 3;
const RegisterIdx Cst0 = 0, Cst1 = 1, Cst2 = 2, Cst3 = 3;
MachineInstr *Nop1 = &*std::prev(RW.MF.getBlockNumbered(1)->end());
MachineInstr *Nop2 = &*std::prev(RW.MF.getBlockNumbered(2)->end());
MachineInstr *Nop3 =
&*std::prev(std::prev(RW.MF.getBlockNumbered(3)->end()));
auto ExpectSeq = [](MachineInstr *MI, MachineInstr *ExpectedNext) {
MachineInstr *ActualNext = &*std::next(MI->getIterator());
EXPECT_EQ(ActualNext, ExpectedNext);
};
// This rematerialization is created right after %2, which is later
// rematerialized. It is *not* recorded by the rollbacker.
RegisterIdx RematCst0 = RW->rematerializeToRegion(Cst0, MBB1, DRI.clear());
ExpectSeq(RW->getReg(Cst2).getFirstDef(),
RW->getReg(RematCst0).getFirstDef());
ExpectSeq(RW->getReg(RematCst0).getFirstDef(), Nop1);
RW->addListener(&Rollback);
// This rematerialization is created right after %3, which is later
// rematerialized. It is recorded by the rollbacker.
RegisterIdx RematCst1 = RW->rematerializeToRegion(Cst1, MBB2, DRI.clear());
ExpectSeq(RW->getReg(Cst3).getFirstDef(),
RW->getReg(RematCst1).getFirstDef());
ExpectSeq(RW->getReg(RematCst1).getFirstDef(), Nop2);
RegisterIdx RematCst2 = RW->rematerializeToRegion(Cst2, MBB3, DRI.clear());
RegisterIdx RematCst3 = RW->rematerializeToRegion(Cst3, MBB3, DRI.clear());
ExpectSeq(RW->getReg(RematCst2).getFirstDef(),
RW->getReg(RematCst3).getFirstDef());
ExpectSeq(RW->getReg(RematCst3).getFirstDef(), Nop3);
// After rollback, %2 and %3 should be re-created at the beginning of their
// respective original region.
Rollback.rollback(*RW);
// The rematerialization of %0 was not recorded so isn't rolled back, %2 is
// re-created right before it.
ExpectSeq(RW->getReg(Cst2).getFirstDef(),
RW->getReg(RematCst0).getFirstDef());
ExpectSeq(RW->getReg(RematCst0).getFirstDef(), Nop1);
// The rematerialization of %1 was recorded so is rolled back, %3 is
// re-created before the S_NOP in its region.
ExpectSeq(RW->getReg(Cst3).getFirstDef(), Nop2);
});
}