vporpo a2b5fd7f6e
[SandboxIR][Region] Auxiliary vector metadata now requires a region (#137443)
The auxiliary vector is represented by the !sandboxaux metadata. But
until now adding an instruction to the aux vector would not
automatically add it to the region (i.e., it would not mark it with
!sandboxvec). This behavior was problematic when generating regions from
metadata, because you wouldn't know which region owns the auxiliary
vector.

This patch fixes this issue: We now require that an instruction with
!sandboxaux also defines its region with !sandboxvec.
2025-05-27 16:11:13 -07:00

513 lines
16 KiB
C++

//===- RegionTest.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/SandboxIR/Region.h"
#include "llvm/Analysis/TargetTransformInfo.h"
#include "llvm/AsmParser/Parser.h"
#include "llvm/SandboxIR/Context.h"
#include "llvm/SandboxIR/Function.h"
#include "llvm/SandboxIR/Instruction.h"
#include "llvm/Support/SourceMgr.h"
#include "gmock/gmock-matchers.h"
#include "gtest/gtest.h"
using namespace llvm;
struct RegionTest : public testing::Test {
LLVMContext C;
std::unique_ptr<Module> M;
std::unique_ptr<TargetTransformInfo> TTI;
void parseIR(LLVMContext &C, const char *IR) {
SMDiagnostic Err;
M = parseAssemblyString(IR, Err, C);
TTI = std::make_unique<TargetTransformInfo>(M->getDataLayout());
if (!M)
Err.print("RegionTest", errs());
}
};
TEST_F(RegionTest, Basic) {
parseIR(C, R"IR(
define i8 @foo(i8 %v0, i8 %v1) {
%t0 = add i8 %v0, 1
%t1 = add i8 %t0, %v1
ret i8 %t1
}
)IR");
llvm::Function *LLVMF = &*M->getFunction("foo");
sandboxir::Context Ctx(C);
auto *F = Ctx.createFunction(LLVMF);
auto *BB = &*F->begin();
auto It = BB->begin();
auto *T0 = cast<sandboxir::Instruction>(&*It++);
auto *T1 = cast<sandboxir::Instruction>(&*It++);
auto *Ret = cast<sandboxir::Instruction>(&*It++);
sandboxir::Region Rgn(Ctx, *TTI);
// Check getContext.
EXPECT_EQ(&Ctx, &Rgn.getContext());
// Check add / remove / empty.
EXPECT_TRUE(Rgn.empty());
sandboxir::RegionInternalsAttorney::add(Rgn, T0);
EXPECT_FALSE(Rgn.empty());
sandboxir::RegionInternalsAttorney::remove(Rgn, T0);
EXPECT_TRUE(Rgn.empty());
// Check iteration.
sandboxir::RegionInternalsAttorney::add(Rgn, T0);
sandboxir::RegionInternalsAttorney::add(Rgn, T1);
sandboxir::RegionInternalsAttorney::add(Rgn, Ret);
// Use an ordered matcher because we're supposed to preserve the insertion
// order for determinism.
EXPECT_THAT(Rgn.insts(), testing::ElementsAre(T0, T1, Ret));
// Check contains
EXPECT_TRUE(Rgn.contains(T0));
sandboxir::RegionInternalsAttorney::remove(Rgn, T0);
EXPECT_FALSE(Rgn.contains(T0));
#ifndef NDEBUG
// Check equality comparison. Insert in reverse order into `Other` to check
// that comparison is order-independent.
sandboxir::Region Other(Ctx, *TTI);
sandboxir::RegionInternalsAttorney::add(Other, Ret);
EXPECT_NE(Rgn, Other);
sandboxir::RegionInternalsAttorney::add(Other, T1);
EXPECT_EQ(Rgn, Other);
#endif
}
TEST_F(RegionTest, CallbackUpdates) {
parseIR(C, R"IR(
define i8 @foo(i8 %v0, i8 %v1, ptr %ptr) {
%t0 = add i8 %v0, 1
%t1 = add i8 %t0, %v1
ret i8 %t0
}
)IR");
llvm::Function *LLVMF = &*M->getFunction("foo");
sandboxir::Context Ctx(C);
auto *F = Ctx.createFunction(LLVMF);
auto *Ptr = F->getArg(2);
auto *BB = &*F->begin();
auto It = BB->begin();
auto *T0 = cast<sandboxir::Instruction>(&*It++);
auto *T1 = cast<sandboxir::Instruction>(&*It++);
auto *Ret = cast<sandboxir::Instruction>(&*It++);
sandboxir::Region Rgn(Ctx, *TTI);
sandboxir::RegionInternalsAttorney::add(Rgn, T0);
sandboxir::RegionInternalsAttorney::add(Rgn, T1);
// Test creation.
auto *NewI = sandboxir::StoreInst::create(T0, Ptr, /*Align=*/std::nullopt,
Ret->getIterator(), Ctx);
EXPECT_THAT(Rgn.insts(), testing::ElementsAre(T0, T1, NewI));
// Test deletion.
T1->eraseFromParent();
EXPECT_THAT(Rgn.insts(), testing::ElementsAre(T0, NewI));
}
TEST_F(RegionTest, MetadataFromIR) {
parseIR(C, R"IR(
define i8 @foo(i8 %v0, i8 %v1) {
%t0 = add i8 %v0, 1, !sandboxvec !0
%t1 = add i8 %t0, %v1, !sandboxvec !1
%t2 = add i8 %t1, %v1, !sandboxvec !1
ret i8 %t2
}
!0 = distinct !{!"sandboxregion"}
!1 = distinct !{!"sandboxregion"}
)IR");
llvm::Function *LLVMF = &*M->getFunction("foo");
sandboxir::Context Ctx(C);
auto *F = Ctx.createFunction(LLVMF);
auto *BB = &*F->begin();
auto It = BB->begin();
auto *T0 = cast<sandboxir::Instruction>(&*It++);
auto *T1 = cast<sandboxir::Instruction>(&*It++);
auto *T2 = cast<sandboxir::Instruction>(&*It++);
SmallVector<std::unique_ptr<sandboxir::Region>> Regions =
sandboxir::Region::createRegionsFromMD(*F, *TTI);
EXPECT_THAT(Regions[0]->insts(), testing::UnorderedElementsAre(T0));
EXPECT_THAT(Regions[1]->insts(), testing::UnorderedElementsAre(T1, T2));
}
TEST_F(RegionTest, NonContiguousRegion) {
parseIR(C, R"IR(
define i8 @foo(i8 %v0, i8 %v1) {
%t0 = add i8 %v0, 1, !sandboxvec !0
%t1 = add i8 %t0, %v1
%t2 = add i8 %t1, %v1, !sandboxvec !0
ret i8 %t2
}
!0 = distinct !{!"sandboxregion"}
)IR");
llvm::Function *LLVMF = &*M->getFunction("foo");
sandboxir::Context Ctx(C);
auto *F = Ctx.createFunction(LLVMF);
auto *BB = &*F->begin();
auto It = BB->begin();
auto *T0 = cast<sandboxir::Instruction>(&*It++);
[[maybe_unused]] auto *T1 = cast<sandboxir::Instruction>(&*It++);
auto *T2 = cast<sandboxir::Instruction>(&*It++);
SmallVector<std::unique_ptr<sandboxir::Region>> Regions =
sandboxir::Region::createRegionsFromMD(*F, *TTI);
EXPECT_THAT(Regions[0]->insts(), testing::UnorderedElementsAre(T0, T2));
}
TEST_F(RegionTest, DumpedMetadata) {
parseIR(C, R"IR(
define i8 @foo(i8 %v0, i8 %v1) {
%t0 = add i8 %v0, 1
%t1 = add i8 %t0, %v1
%t2 = add i8 %t1, %v1
ret i8 %t1
}
)IR");
llvm::Function *LLVMF = &*M->getFunction("foo");
sandboxir::Context Ctx(C);
auto *F = Ctx.createFunction(LLVMF);
auto *BB = &*F->begin();
auto It = BB->begin();
auto *T0 = cast<sandboxir::Instruction>(&*It++);
[[maybe_unused]] auto *T1 = cast<sandboxir::Instruction>(&*It++);
auto *T2 = cast<sandboxir::Instruction>(&*It++);
[[maybe_unused]] auto *Ret = cast<sandboxir::Instruction>(&*It++);
sandboxir::Region Rgn(Ctx, *TTI);
sandboxir::RegionInternalsAttorney::add(Rgn, T0);
sandboxir::Region Rgn2(Ctx, *TTI);
sandboxir::RegionInternalsAttorney::add(Rgn2, T2);
std::string output;
llvm::raw_string_ostream RSO(output);
M->print(RSO, nullptr, /*ShouldPreserveUseListOrder=*/true,
/*IsForDebug=*/true);
// TODO: Replace this with a lit test, which is more suitable for this kind
// of IR comparison.
std::string expected = R"(; ModuleID = '<string>'
source_filename = "<string>"
define i8 @foo(i8 %v0, i8 %v1) {
%t0 = add i8 %v0, 1, !sandboxvec !0
%t1 = add i8 %t0, %v1
%t2 = add i8 %t1, %v1, !sandboxvec !1
ret i8 %t1
}
!0 = distinct !{!"sandboxregion"}
!1 = distinct !{!"sandboxregion"}
)";
EXPECT_EQ(expected, output);
}
TEST_F(RegionTest, MetadataRoundTrip) {
parseIR(C, R"IR(
define i8 @foo(i8 %v0, i8 %v1) {
%t0 = add i8 %v0, 1
%t1 = add i8 %t0, %v1
ret i8 %t1
}
)IR");
llvm::Function *LLVMF = &*M->getFunction("foo");
sandboxir::Context Ctx(C);
auto *F = Ctx.createFunction(LLVMF);
auto *BB = &*F->begin();
auto It = BB->begin();
auto *T0 = cast<sandboxir::Instruction>(&*It++);
auto *T1 = cast<sandboxir::Instruction>(&*It++);
sandboxir::Region Rgn(Ctx, *TTI);
sandboxir::RegionInternalsAttorney::add(Rgn, T0);
sandboxir::RegionInternalsAttorney::add(Rgn, T1);
SmallVector<std::unique_ptr<sandboxir::Region>> Regions =
sandboxir::Region::createRegionsFromMD(*F, *TTI);
ASSERT_EQ(1U, Regions.size());
#ifndef NDEBUG
EXPECT_EQ(Rgn, *Regions[0].get());
#endif
}
TEST_F(RegionTest, RegionCost) {
parseIR(C, R"IR(
define void @foo(i8 %v0, i8 %v1, i8 %v2) {
%add0 = add i8 %v0, 1
%add1 = add i8 %v1, 2
%add2 = add i8 %v2, 3
ret void
}
)IR");
llvm::Function *LLVMF = &*M->getFunction("foo");
auto *LLVMBB = &*LLVMF->begin();
auto LLVMIt = LLVMBB->begin();
auto *LLVMAdd0 = &*LLVMIt++;
auto *LLVMAdd1 = &*LLVMIt++;
auto *LLVMAdd2 = &*LLVMIt++;
sandboxir::Context Ctx(C);
auto *F = Ctx.createFunction(LLVMF);
auto *BB = &*F->begin();
auto It = BB->begin();
auto *Add0 = cast<sandboxir::Instruction>(&*It++);
auto *Add1 = cast<sandboxir::Instruction>(&*It++);
auto *Add2 = cast<sandboxir::Instruction>(&*It++);
sandboxir::Region Rgn(Ctx, *TTI);
const auto &SB = Rgn.getScoreboard();
EXPECT_EQ(SB.getAfterCost(), 0);
EXPECT_EQ(SB.getBeforeCost(), 0);
auto GetCost = [this](llvm::Instruction *LLVMI) {
constexpr static TTI::TargetCostKind CostKind = TTI::TCK_RecipThroughput;
SmallVector<const llvm::Value *> Operands(LLVMI->operands());
return TTI->getInstructionCost(LLVMI, Operands, CostKind);
};
// Add `Add0` to the region, should be counted in "After".
sandboxir::RegionInternalsAttorney::add(Rgn, Add0);
EXPECT_EQ(SB.getBeforeCost(), 0);
EXPECT_EQ(SB.getAfterCost(), GetCost(LLVMAdd0));
// Same for `Add1`.
sandboxir::RegionInternalsAttorney::add(Rgn, Add1);
EXPECT_EQ(SB.getBeforeCost(), 0);
EXPECT_EQ(SB.getAfterCost(), GetCost(LLVMAdd0) + GetCost(LLVMAdd1));
// Remove `Add0`, should be subtracted from "After".
sandboxir::RegionInternalsAttorney::remove(Rgn, Add0);
EXPECT_EQ(SB.getBeforeCost(), 0);
EXPECT_EQ(SB.getAfterCost(), GetCost(LLVMAdd1));
// Remove `Add2` which was never in the region, should counted in "Before".
sandboxir::RegionInternalsAttorney::remove(Rgn, Add2);
EXPECT_EQ(SB.getBeforeCost(), GetCost(LLVMAdd2));
EXPECT_EQ(SB.getAfterCost(), GetCost(LLVMAdd1));
}
TEST_F(RegionTest, Aux) {
parseIR(C, R"IR(
define void @foo(i8 %v) {
%t0 = add i8 %v, 0, !sandboxvec !0, !sandboxaux !2
%t1 = add i8 %v, 1, !sandboxvec !0, !sandboxaux !3
%t2 = add i8 %v, 2, !sandboxvec !1
%t3 = add i8 %v, 3, !sandboxvec !1, !sandboxaux !2
%t4 = add i8 %v, 4, !sandboxvec !1, !sandboxaux !4
%t5 = add i8 %v, 5, !sandboxvec !1, !sandboxaux !3
ret void
}
!0 = distinct !{!"sandboxregion"}
!1 = distinct !{!"sandboxregion"}
!2 = !{i32 0}
!3 = !{i32 1}
!4 = !{i32 2}
)IR");
llvm::Function *LLVMF = &*M->getFunction("foo");
auto *LLVMBB = &*LLVMF->begin();
auto LLVMIt = LLVMBB->begin();
auto *LLVMI0 = &*LLVMIt++;
auto *LLVMI1 = &*LLVMIt++;
sandboxir::Context Ctx(C);
auto *F = Ctx.createFunction(LLVMF);
auto *BB = &*F->begin();
auto It = BB->begin();
auto *T0 = cast<sandboxir::Instruction>(&*It++);
auto *T1 = cast<sandboxir::Instruction>(&*It++);
auto *T2 = cast<sandboxir::Instruction>(&*It++);
auto *T3 = cast<sandboxir::Instruction>(&*It++);
auto *T4 = cast<sandboxir::Instruction>(&*It++);
auto *T5 = cast<sandboxir::Instruction>(&*It++);
SmallVector<std::unique_ptr<sandboxir::Region>> Regions =
sandboxir::Region::createRegionsFromMD(*F, *TTI);
// Check that the regions are correct.
EXPECT_THAT(Regions[0]->insts(), testing::UnorderedElementsAre(T0, T1));
EXPECT_THAT(Regions[1]->insts(),
testing::UnorderedElementsAre(T2, T3, T4, T5));
// Check aux.
EXPECT_THAT(Regions[0]->getAux(), testing::ElementsAre(T0, T1));
EXPECT_THAT(Regions[1]->getAux(), testing::ElementsAre(T3, T5, T4));
// Check clearAux().
EXPECT_TRUE(LLVMI0->getMetadata("sandboxaux"));
EXPECT_TRUE(LLVMI1->getMetadata("sandboxaux"));
Regions[0]->clearAux();
EXPECT_TRUE(Regions[0]->getAux().empty());
EXPECT_FALSE(LLVMI0->getMetadata("sandboxaux"));
EXPECT_FALSE(LLVMI1->getMetadata("sandboxaux"));
}
// Check that Aux is well-formed.
TEST_F(RegionTest, AuxVerify) {
parseIR(C, R"IR(
define void @foo(i8 %v) {
%t0 = add i8 %v, 0, !sandboxvec !0, !sandboxaux !2
%t1 = add i8 %v, 1, !sandboxvec !0, !sandboxaux !3
ret void
}
!0 = distinct !{!"sandboxregion"}
!2 = !{i32 0}
!3 = !{i32 2}
)IR");
llvm::Function *LLVMF = &*M->getFunction("foo");
sandboxir::Context Ctx(C);
auto *F = Ctx.createFunction(LLVMF);
EXPECT_DEBUG_DEATH(sandboxir::Region::createRegionsFromMD(*F, *TTI),
".*Gap*");
}
// Check that we get an assertion failure if we try to set the same index more
// than once.
TEST_F(RegionTest, AuxSameIndex) {
parseIR(C, R"IR(
define void @foo(i8 %v) {
%t0 = add i8 %v, 0, !sandboxvec !0, !sandboxaux !2
%t1 = add i8 %v, 1, !sandboxvec !0, !sandboxaux !2
ret void
}
!0 = distinct !{!"sandboxregion"}
!2 = !{i32 0}
)IR");
llvm::Function *LLVMF = &*M->getFunction("foo");
sandboxir::Context Ctx(C);
auto *F = Ctx.createFunction(LLVMF);
EXPECT_DEBUG_DEATH(sandboxir::Region::createRegionsFromMD(*F, *TTI),
".*already.*");
}
// Check that Aux automatically drops instructions that get deleted.
TEST_F(RegionTest, AuxDeleteInstr) {
parseIR(C, R"IR(
define void @foo(i8 %v) {
%Add0 = add i8 %v, 0, !sandboxvec !0, !sandboxaux !1
%Add1 = add i8 %v, 1, !sandboxvec !0, !sandboxaux !2
%Add2 = add i8 %v, 2, !sandboxvec !0, !sandboxaux !3
%Add3 = add i8 %v, 2, !sandboxvec !0, !sandboxaux !4
ret void
}
!0 = distinct !{!"sandboxregion"}
!1 = !{i32 0}
!2 = !{i32 1}
!3 = !{i32 2}
!4 = !{i32 3}
)IR");
llvm::Function *LLVMF = &*M->getFunction("foo");
sandboxir::Context Ctx(C);
auto *F = Ctx.createFunction(LLVMF);
auto *BB = &*F->begin();
auto It = BB->begin();
auto *Add0 = &*It++;
auto *Add1 = &*It++;
auto *Add2 = &*It++;
auto *Add3 = &*It++;
SmallVector<std::unique_ptr<sandboxir::Region>> Regions =
sandboxir::Region::createRegionsFromMD(*F, *TTI);
auto &R = *Regions[0];
EXPECT_THAT(R.getAux(), testing::ElementsAre(Add0, Add1, Add2, Add3));
// Now delete Add1 and check that Aux contains nullptr instead of Add1.
Add2->eraseFromParent();
EXPECT_THAT(R.getAux(), testing::ElementsAre(Add0, Add1, Add3));
{
// Check that metadata have also been updated.
// But first drop Add3 to create a legal Aux vector with no gaps.
Add3->eraseFromParent();
SmallVector<std::unique_ptr<sandboxir::Region>> Regions =
sandboxir::Region::createRegionsFromMD(*F, *TTI);
EXPECT_THAT(Regions[0]->getAux(), testing::ElementsAre(Add0, Add1));
}
}
TEST_F(RegionTest, AuxWithoutRegion) {
parseIR(C, R"IR(
define void @foo(i8 %v) {
%Add0 = add i8 %v, 0, !sandboxaux !0
ret void
}
!0 = !{i32 0}
)IR");
llvm::Function *LLVMF = &*M->getFunction("foo");
sandboxir::Context Ctx(C);
auto *F = Ctx.createFunction(LLVMF);
#ifndef NDEBUG
EXPECT_DEATH(sandboxir::Region::createRegionsFromMD(*F, *TTI), "No region.*");
#endif
}
TEST_F(RegionTest, AuxRoundTrip) {
parseIR(C, R"IR(
define i8 @foo(i8 %v0, i8 %v1) {
%t0 = add i8 %v0, 1
%t1 = add i8 %t0, %v1
ret i8 %t1
}
)IR");
llvm::Function *LLVMF = &*M->getFunction("foo");
sandboxir::Context Ctx(C);
auto *F = Ctx.createFunction(LLVMF);
auto *BB = &*F->begin();
auto It = BB->begin();
auto *T0 = cast<sandboxir::Instruction>(&*It++);
auto *T1 = cast<sandboxir::Instruction>(&*It++);
sandboxir::Region Rgn(Ctx, *TTI);
#ifndef NDEBUG
EXPECT_DEATH(Rgn.setAux({T0, T0}), ".*already.*");
#endif
sandboxir::RegionInternalsAttorney::add(Rgn, T0);
sandboxir::RegionInternalsAttorney::add(Rgn, T1);
Rgn.setAux({T1, T0});
SmallVector<std::unique_ptr<sandboxir::Region>> Regions =
sandboxir::Region::createRegionsFromMD(*F, *TTI);
ASSERT_EQ(1U, Regions.size());
#ifndef NDEBUG
EXPECT_EQ(Rgn, *Regions[0].get());
#endif
EXPECT_THAT(Rgn.getAux(), testing::ElementsAre(T1, T0));
}
// Same as before but only add instructions to aux. They should get added too
// the region too automatically.
TEST_F(RegionTest, AuxOnlyRoundTrip) {
parseIR(C, R"IR(
define void @foo(i8 %v) {
%add0 = add i8 %v, 0
%add1 = add i8 %v, 1
ret void
}
)IR");
llvm::Function *LLVMF = &*M->getFunction("foo");
sandboxir::Context Ctx(C);
auto *F = Ctx.createFunction(LLVMF);
auto *BB = &*F->begin();
auto It = BB->begin();
auto *Add0 = cast<sandboxir::Instruction>(&*It++);
auto *Add1 = cast<sandboxir::Instruction>(&*It++);
sandboxir::Region Rgn(Ctx, *TTI);
#ifndef NDEBUG
EXPECT_DEATH(Rgn.setAux({Add0, Add0}), ".*already.*");
#endif
Rgn.setAux({Add1, Add0});
SmallVector<std::unique_ptr<sandboxir::Region>> Regions =
sandboxir::Region::createRegionsFromMD(*F, *TTI);
ASSERT_EQ(1U, Regions.size());
#ifndef NDEBUG
EXPECT_EQ(Rgn, *Regions[0].get());
#endif
EXPECT_THAT(Rgn.getAux(), testing::ElementsAre(Add1, Add0));
}