Currently, the utility supports lowering of non atomic memory transfer routines only. This patch adds support for atomic version of memcopy. This may be useful for targets not supporting atomic memcopy. Reviewed By: arsenm Differential Revision: https://reviews.llvm.org/D118443
268 lines
10 KiB
C++
268 lines
10 KiB
C++
//=========- MemTransferLowerTest.cpp - MemTransferLower unit tests -=========//
|
|
//
|
|
// 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/Analysis/CGSCCPassManager.h"
|
|
#include "llvm/Analysis/ScalarEvolution.h"
|
|
#include "llvm/Analysis/TargetTransformInfo.h"
|
|
#include "llvm/AsmParser/Parser.h"
|
|
#include "llvm/IR/BasicBlock.h"
|
|
#include "llvm/IR/Function.h"
|
|
#include "llvm/IR/Instructions.h"
|
|
#include "llvm/IR/IntrinsicInst.h"
|
|
#include "llvm/IR/LLVMContext.h"
|
|
#include "llvm/IR/Module.h"
|
|
#include "llvm/IR/PassManager.h"
|
|
#include "llvm/InitializePasses.h"
|
|
#include "llvm/Passes/PassBuilder.h"
|
|
#include "llvm/Support/Debug.h"
|
|
#include "llvm/Support/SourceMgr.h"
|
|
#include "llvm/Testing/Support/Error.h"
|
|
#include "llvm/Transforms/Utils/LowerMemIntrinsics.h"
|
|
#include "llvm/Transforms/Vectorize/LoopVectorize.h"
|
|
|
|
#include "gtest/gtest-spi.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
using namespace llvm;
|
|
|
|
namespace {
|
|
struct ForwardingPass : public PassInfoMixin<ForwardingPass> {
|
|
template <typename T> ForwardingPass(T &&Arg) : Func(std::forward<T>(Arg)) {}
|
|
|
|
PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM) {
|
|
return Func(F, FAM);
|
|
}
|
|
|
|
std::function<PreservedAnalyses(Function &, FunctionAnalysisManager &)> Func;
|
|
};
|
|
|
|
struct MemTransferLowerTest : public testing::Test {
|
|
PassBuilder PB;
|
|
LoopAnalysisManager LAM;
|
|
FunctionAnalysisManager FAM;
|
|
CGSCCAnalysisManager CGAM;
|
|
ModuleAnalysisManager MAM;
|
|
ModulePassManager MPM;
|
|
LLVMContext Context;
|
|
std::unique_ptr<Module> M;
|
|
|
|
MemTransferLowerTest() {
|
|
// Register all the basic analyses with the managers.
|
|
PB.registerModuleAnalyses(MAM);
|
|
PB.registerCGSCCAnalyses(CGAM);
|
|
PB.registerFunctionAnalyses(FAM);
|
|
PB.registerLoopAnalyses(LAM);
|
|
PB.crossRegisterProxies(LAM, FAM, CGAM, MAM);
|
|
}
|
|
|
|
BasicBlock *getBasicBlockByName(Function &F, StringRef Name) const {
|
|
for (BasicBlock &BB : F) {
|
|
if (BB.getName() == Name)
|
|
return &BB;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Instruction *getInstructionByOpcode(BasicBlock &BB, unsigned Opcode,
|
|
unsigned Number) const {
|
|
unsigned CurrNumber = 0;
|
|
for (Instruction &I : BB)
|
|
if (I.getOpcode() == Opcode) {
|
|
++CurrNumber;
|
|
if (CurrNumber == Number)
|
|
return &I;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void ParseAssembly(const char *IR) {
|
|
SMDiagnostic Error;
|
|
M = parseAssemblyString(IR, Error, Context);
|
|
std::string errMsg;
|
|
raw_string_ostream os(errMsg);
|
|
Error.print("", os);
|
|
|
|
// A failure here means that the test itself is buggy.
|
|
if (!M)
|
|
report_fatal_error(os.str().c_str());
|
|
}
|
|
};
|
|
|
|
// By semantics source and destination of llvm.memcpy.* intrinsic
|
|
// are either equal or don't overlap. Once the intrinsic is lowered
|
|
// to a loop it can be hard or impossible to reason about these facts.
|
|
// For that reason expandMemCpyAsLoop is expected to explicitly mark
|
|
// loads from source and stores to destination as not aliasing.
|
|
TEST_F(MemTransferLowerTest, MemCpyKnownLength) {
|
|
ParseAssembly("declare void @llvm.memcpy.p0i8.p0i8.i64(i8*, i8 *, i64, i1)\n"
|
|
"define void @foo(i8* %dst, i8* %src, i64 %n) optsize {\n"
|
|
"entry:\n"
|
|
" %is_not_equal = icmp ne i8* %dst, %src\n"
|
|
" br i1 %is_not_equal, label %memcpy, label %exit\n"
|
|
"memcpy:\n"
|
|
" call void @llvm.memcpy.p0i8.p0i8.i64(i8* %dst, i8* %src, "
|
|
"i64 1024, i1 false)\n"
|
|
" br label %exit\n"
|
|
"exit:\n"
|
|
" ret void\n"
|
|
"}\n");
|
|
|
|
FunctionPassManager FPM;
|
|
FPM.addPass(ForwardingPass(
|
|
[=](Function &F, FunctionAnalysisManager &FAM) -> PreservedAnalyses {
|
|
TargetTransformInfo TTI(M->getDataLayout());
|
|
auto *MemCpyBB = getBasicBlockByName(F, "memcpy");
|
|
Instruction *Inst = &MemCpyBB->front();
|
|
MemCpyInst *MemCpyI = cast<MemCpyInst>(Inst);
|
|
auto &SE = FAM.getResult<ScalarEvolutionAnalysis>(F);
|
|
expandMemCpyAsLoop(MemCpyI, TTI, &SE);
|
|
auto *CopyLoopBB = getBasicBlockByName(F, "load-store-loop");
|
|
Instruction *LoadInst =
|
|
getInstructionByOpcode(*CopyLoopBB, Instruction::Load, 1);
|
|
EXPECT_NE(nullptr, LoadInst->getMetadata(LLVMContext::MD_alias_scope));
|
|
Instruction *StoreInst =
|
|
getInstructionByOpcode(*CopyLoopBB, Instruction::Store, 1);
|
|
EXPECT_NE(nullptr, StoreInst->getMetadata(LLVMContext::MD_noalias));
|
|
return PreservedAnalyses::none();
|
|
}));
|
|
MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));
|
|
|
|
MPM.run(*M, MAM);
|
|
}
|
|
|
|
// This test indirectly checks that loads and stores (generated as a result of
|
|
// llvm.memcpy lowering) doesn't alias by making sure the loop can be
|
|
// successfully vectorized without additional runtime checks.
|
|
TEST_F(MemTransferLowerTest, VecMemCpyKnownLength) {
|
|
ParseAssembly("declare void @llvm.memcpy.p0i8.p0i8.i64(i8*, i8 *, i64, i1)\n"
|
|
"define void @foo(i8* %dst, i8* %src, i64 %n) optsize {\n"
|
|
"entry:\n"
|
|
" %is_not_equal = icmp ne i8* %dst, %src\n"
|
|
" br i1 %is_not_equal, label %memcpy, label %exit\n"
|
|
"memcpy:\n"
|
|
" call void @llvm.memcpy.p0i8.p0i8.i64(i8* %dst, i8* %src, "
|
|
"i64 1024, i1 false)\n"
|
|
" br label %exit\n"
|
|
"exit:\n"
|
|
" ret void\n"
|
|
"}\n");
|
|
|
|
FunctionPassManager FPM;
|
|
FPM.addPass(ForwardingPass(
|
|
[=](Function &F, FunctionAnalysisManager &FAM) -> PreservedAnalyses {
|
|
TargetTransformInfo TTI(M->getDataLayout());
|
|
auto *MemCpyBB = getBasicBlockByName(F, "memcpy");
|
|
Instruction *Inst = &MemCpyBB->front();
|
|
MemCpyInst *MemCpyI = cast<MemCpyInst>(Inst);
|
|
auto &SE = FAM.getResult<ScalarEvolutionAnalysis>(F);
|
|
expandMemCpyAsLoop(MemCpyI, TTI, &SE);
|
|
return PreservedAnalyses::none();
|
|
}));
|
|
FPM.addPass(LoopVectorizePass(LoopVectorizeOptions()));
|
|
FPM.addPass(ForwardingPass(
|
|
[=](Function &F, FunctionAnalysisManager &FAM) -> PreservedAnalyses {
|
|
auto *TargetBB = getBasicBlockByName(F, "vector.body");
|
|
EXPECT_NE(nullptr, TargetBB);
|
|
return PreservedAnalyses::all();
|
|
}));
|
|
MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));
|
|
|
|
MPM.run(*M, MAM);
|
|
}
|
|
|
|
TEST_F(MemTransferLowerTest, AtomicMemCpyKnownLength) {
|
|
ParseAssembly("declare void "
|
|
"@llvm.memcpy.element.unordered.atomic.p0i32.p0i32.i64(i32*, "
|
|
"i32 *, i64, i32)\n"
|
|
"define void @foo(i32* %dst, i32* %src, i64 %n) optsize {\n"
|
|
"entry:\n"
|
|
" %is_not_equal = icmp ne i32* %dst, %src\n"
|
|
" br i1 %is_not_equal, label %memcpy, label %exit\n"
|
|
"memcpy:\n"
|
|
" call void "
|
|
"@llvm.memcpy.element.unordered.atomic.p0i32.p0i32.i64(i32* "
|
|
"%dst, i32* %src, "
|
|
"i64 1024, i32 4)\n"
|
|
" br label %exit\n"
|
|
"exit:\n"
|
|
" ret void\n"
|
|
"}\n");
|
|
|
|
FunctionPassManager FPM;
|
|
FPM.addPass(ForwardingPass(
|
|
[=](Function &F, FunctionAnalysisManager &FAM) -> PreservedAnalyses {
|
|
TargetTransformInfo TTI(M->getDataLayout());
|
|
auto *MemCpyBB = getBasicBlockByName(F, "memcpy");
|
|
Instruction *Inst = &MemCpyBB->front();
|
|
assert(isa<AtomicMemCpyInst>(Inst) &&
|
|
"Expecting llvm.memcpy.p0i8.i64 instructon");
|
|
AtomicMemCpyInst *MemCpyI = cast<AtomicMemCpyInst>(Inst);
|
|
auto &SE = FAM.getResult<ScalarEvolutionAnalysis>(F);
|
|
expandAtomicMemCpyAsLoop(MemCpyI, TTI, &SE);
|
|
auto *CopyLoopBB = getBasicBlockByName(F, "load-store-loop");
|
|
Instruction *LoadInst =
|
|
getInstructionByOpcode(*CopyLoopBB, Instruction::Load, 1);
|
|
EXPECT_TRUE(LoadInst->isAtomic());
|
|
EXPECT_NE(LoadInst->getMetadata(LLVMContext::MD_alias_scope), nullptr);
|
|
Instruction *StoreInst =
|
|
getInstructionByOpcode(*CopyLoopBB, Instruction::Store, 1);
|
|
EXPECT_TRUE(StoreInst->isAtomic());
|
|
EXPECT_NE(StoreInst->getMetadata(LLVMContext::MD_noalias), nullptr);
|
|
return PreservedAnalyses::none();
|
|
}));
|
|
MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));
|
|
|
|
MPM.run(*M, MAM);
|
|
}
|
|
|
|
TEST_F(MemTransferLowerTest, AtomicMemCpyUnKnownLength) {
|
|
ParseAssembly("declare void "
|
|
"@llvm.memcpy.element.unordered.atomic.p0i32.p0i32.i64(i32*, "
|
|
"i32 *, i64, i32)\n"
|
|
"define void @foo(i32* %dst, i32* %src, i64 %n) optsize {\n"
|
|
"entry:\n"
|
|
" %is_not_equal = icmp ne i32* %dst, %src\n"
|
|
" br i1 %is_not_equal, label %memcpy, label %exit\n"
|
|
"memcpy:\n"
|
|
" call void "
|
|
"@llvm.memcpy.element.unordered.atomic.p0i32.p0i32.i64(i32* "
|
|
"%dst, i32* %src, "
|
|
"i64 %n, i32 4)\n"
|
|
" br label %exit\n"
|
|
"exit:\n"
|
|
" ret void\n"
|
|
"}\n");
|
|
|
|
FunctionPassManager FPM;
|
|
FPM.addPass(ForwardingPass(
|
|
[=](Function &F, FunctionAnalysisManager &FAM) -> PreservedAnalyses {
|
|
TargetTransformInfo TTI(M->getDataLayout());
|
|
auto *MemCpyBB = getBasicBlockByName(F, "memcpy");
|
|
Instruction *Inst = &MemCpyBB->front();
|
|
assert(isa<AtomicMemCpyInst>(Inst) &&
|
|
"Expecting llvm.memcpy.p0i8.i64 instructon");
|
|
AtomicMemCpyInst *MemCpyI = cast<AtomicMemCpyInst>(Inst);
|
|
auto &SE = FAM.getResult<ScalarEvolutionAnalysis>(F);
|
|
expandAtomicMemCpyAsLoop(MemCpyI, TTI, &SE);
|
|
auto *CopyLoopBB = getBasicBlockByName(F, "loop-memcpy-expansion");
|
|
Instruction *LoadInst =
|
|
getInstructionByOpcode(*CopyLoopBB, Instruction::Load, 1);
|
|
EXPECT_TRUE(LoadInst->isAtomic());
|
|
EXPECT_NE(LoadInst->getMetadata(LLVMContext::MD_alias_scope), nullptr);
|
|
Instruction *StoreInst =
|
|
getInstructionByOpcode(*CopyLoopBB, Instruction::Store, 1);
|
|
EXPECT_TRUE(StoreInst->isAtomic());
|
|
EXPECT_NE(StoreInst->getMetadata(LLVMContext::MD_noalias), nullptr);
|
|
return PreservedAnalyses::none();
|
|
}));
|
|
MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));
|
|
|
|
MPM.run(*M, MAM);
|
|
}
|
|
} // namespace
|