Skip to content

Commit a111e4b

Browse files
authored
[SYCL][NewOffload][NFC] Move post-link processing to library for later reuse in NewOffload Model. (#17357)
This patch extracts post-link/pre-split processing into the dedicated library function for later reuse in clang-sycl-linker/clang-linker-wrapper. The functions `removeSYCLKernelsConstRefArray` and `removeDeviceGlobalFromCompilerUsed` have been transformed into LLVM Passes. Passes that were previously run individually are now executed together by the PassManager. This change allows to introduce the use of PassPipeline's debugging capabilities (e.g., -print-passes) in NewOffload Model later. In cases where the `invoke_simd` builtin is used in conjunction with `-split-esimd`, a straightforward check has been introduced instead of the previous post-processing check in sycl-post-link. This scenario should also be checked in the NewOffload Model.
1 parent d6214ad commit a111e4b

File tree

7 files changed

+193
-157
lines changed

7 files changed

+193
-157
lines changed

llvm/include/llvm/SYCLLowerIR/CleanupSYCLMetadata.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,33 @@ class CleanupSYCLMetadataPass : public PassInfoMixin<CleanupSYCLMetadataPass> {
2323
PreservedAnalyses run(Module &M, ModuleAnalysisManager &);
2424
};
2525

26+
/// Removes the global variable "llvm.used".
27+
/// "llvm.used" is a global constant array containing references to kernels
28+
/// available in the module and callable from host code. The elements of
29+
/// the array are ConstantExpr bitcast to i8*.
30+
/// The variable must be removed because it has done the job to the moment
31+
/// of a compilation stage and the references to the kernels callable from
32+
/// host must not have users.
33+
class CleanupSYCLMetadataFromLLVMUsed
34+
: public PassInfoMixin<CleanupSYCLMetadataFromLLVMUsed> {
35+
public:
36+
PreservedAnalyses run(Module &M, ModuleAnalysisManager &);
37+
};
38+
39+
/// Removes all device_global variables from the llvm.compiler.used global
40+
/// variable. A device_global with internal linkage will be in
41+
/// llvm.compiler.used to avoid the compiler wrongfully removing it during
42+
/// optimizations. However, as an effect the device_global variables will also
43+
/// be distributed across binaries, even if llvm.compiler.used has served its
44+
/// purpose. To avoid polluting other binaries with unused device_global
45+
/// variables, we remove them from llvm.compiler.used and erase them if they
46+
/// have no further uses.
47+
class RemoveDeviceGlobalFromLLVMCompilerUsed
48+
: public PassInfoMixin<RemoveDeviceGlobalFromLLVMCompilerUsed> {
49+
public:
50+
PreservedAnalyses run(Module &M, ModuleAnalysisManager &);
51+
};
52+
2653
} // namespace llvm
2754

2855
#endif // LLVM_CLEANUP_SYCL_METADATA

llvm/include/llvm/SYCLLowerIR/ESIMD/ESIMDUtils.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ bool isESIMDKernel(const Function &F);
4646
bool isESIMD(const Function &F);
4747
// Tells whether given function is a kernel.
4848
bool isKernel(const Function &F);
49+
// Tells whether a given Module contains an invoke_simd builtin.
50+
bool moduleContainsInvokeSimdBuiltin(Module &M);
4951

5052
/// Reports and error with the message \p Msg concatenated with the optional
5153
/// \p OptMsg if \p Condition is false.

llvm/include/llvm/SYCLLowerIR/ModuleSplitter.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,11 @@ struct ModuleSplitterSettings {
333333
/// Parses the output table file from sycl-post-link tool.
334334
Expected<std::vector<SplitModule>> parseSplitModulesFromFile(StringRef File);
335335

336+
/// PreSplitProcessingPipeline maintains correctness.
337+
/// Note: After migration from sycl-post-link to NewOffload Model this
338+
/// functionality should move to clang-sycl-linker or clang-linker-wrapper.
339+
bool runPreSplitProcessingPipeline(Module &M);
340+
336341
/// Splits the given module \p M according to the given \p Settings.
337342
Expected<std::vector<SplitModule>>
338343
splitSYCLModule(std::unique_ptr<Module> M, ModuleSplitterSettings Settings);

llvm/lib/SYCLLowerIR/CleanupSYCLMetadata.cpp

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
//===----------------------------------------------------------------------===//
1313

1414
#include "llvm/SYCLLowerIR/CleanupSYCLMetadata.h"
15+
#include "llvm/ADT/SmallVector.h"
16+
#include "llvm/ADT/StringRef.h"
17+
#include "llvm/IR/Constants.h"
18+
#include "llvm/SYCLLowerIR/DeviceGlobals.h"
19+
#include "llvm/Transforms/Utils/GlobalStatus.h"
1520

1621
#include "llvm/IR/Module.h"
1722
#include "llvm/Pass.h"
@@ -29,17 +34,113 @@ void cleanupSYCLCompilerMetadata(const Module &M, llvm::StringRef MD) {
2934
Node->eraseFromParent();
3035
}
3136

37+
// GV is supposed to be either llvm.compiler.used or llvm.used.
38+
SmallVector<Constant *>
39+
eraseGlobalVariableAndReturnOperands(GlobalVariable *GV) {
40+
assert(GV->user_empty() && "Users aren't expected");
41+
Constant *Initializer = GV->getInitializer();
42+
GV->setInitializer(nullptr);
43+
GV->eraseFromParent();
44+
45+
// Destroy the initializer and save operands.
46+
SmallVector<Constant *> Operands;
47+
Operands.resize(0);
48+
for (auto &Op : Initializer->operands())
49+
Operands.push_back(cast<Constant>(Op));
50+
51+
assert(isSafeToDestroyConstant(Initializer) &&
52+
"Cannot remove initializer of the given GV");
53+
54+
Initializer->destroyConstant();
55+
return Operands;
56+
}
57+
3258
} // anonymous namespace
3359

3460
PreservedAnalyses CleanupSYCLMetadataPass::run(Module &M,
3561
ModuleAnalysisManager &MAM) {
3662
// Remove SYCL module-level metadata that will never be used again to avoid
3763
// duplication of their operands during llvm-link hence preventing
3864
// increase of the module size
39-
llvm::SmallVector<llvm::StringRef, 2> ModuleMDToRemove = {
40-
"sycl_aspects", "sycl_types_that_use_aspects"};
65+
SmallVector<StringRef, 2> ModuleMDToRemove = {"sycl_aspects",
66+
"sycl_types_that_use_aspects"};
4167
for (const auto &MD : ModuleMDToRemove)
4268
cleanupSYCLCompilerMetadata(M, MD);
4369

4470
return PreservedAnalyses::all();
4571
}
72+
73+
PreservedAnalyses
74+
CleanupSYCLMetadataFromLLVMUsed::run(Module &M, ModuleAnalysisManager &) {
75+
GlobalVariable *GV = M.getGlobalVariable("llvm.used");
76+
if (!GV)
77+
return PreservedAnalyses::all();
78+
79+
SmallVector<Constant *, 8> IOperands =
80+
eraseGlobalVariableAndReturnOperands(GV);
81+
// Erase all operands.
82+
for (auto *Op : IOperands) {
83+
auto StrippedOp = Op->stripPointerCasts();
84+
auto *F = dyn_cast<Function>(StrippedOp);
85+
if (isSafeToDestroyConstant(Op))
86+
(Op)->destroyConstant();
87+
else if (F && F->getCallingConv() == CallingConv::SPIR_KERNEL &&
88+
!F->use_empty()) {
89+
// The element in "llvm.used" array has other users. That is Ok for
90+
// specialization constants, but is wrong for kernels.
91+
report_fatal_error("Unexpected usage of SYCL kernel");
92+
}
93+
94+
// Remove unused kernel declarations to avoid LLVM IR check fails.
95+
if (F && F->isDeclaration() && F->use_empty())
96+
F->eraseFromParent();
97+
}
98+
99+
return PreservedAnalyses::none();
100+
}
101+
102+
PreservedAnalyses
103+
RemoveDeviceGlobalFromLLVMCompilerUsed::run(Module &M,
104+
ModuleAnalysisManager &) {
105+
GlobalVariable *GV = M.getGlobalVariable("llvm.compiler.used");
106+
if (!GV)
107+
return PreservedAnalyses::all();
108+
109+
const auto *VAT = cast<ArrayType>(GV->getValueType());
110+
// Destroy the initializer. Keep the operands so we keep the ones we need.
111+
SmallVector<Constant *> IOperands = eraseGlobalVariableAndReturnOperands(GV);
112+
113+
// Iterate through all operands. If they are device_global then we drop them
114+
// and erase them if they have no uses afterwards. All other values are kept.
115+
SmallVector<Constant *> NewOperands;
116+
for (auto *Op : IOperands) {
117+
auto *DG = dyn_cast<GlobalVariable>(Op->stripPointerCasts());
118+
119+
// If it is not a device_global we keep it.
120+
if (!DG || !isDeviceGlobalVariable(*DG)) {
121+
NewOperands.push_back(Op);
122+
continue;
123+
}
124+
125+
// Destroy the device_global operand.
126+
if (isSafeToDestroyConstant(Op))
127+
Op->destroyConstant();
128+
129+
// Remove device_global if it no longer has any uses.
130+
if (!DG->isConstantUsed())
131+
DG->eraseFromParent();
132+
}
133+
134+
// If we have any operands left from the original llvm.compiler.used we create
135+
// a new one with the new size.
136+
if (!NewOperands.empty()) {
137+
ArrayType *ATy = ArrayType::get(VAT->getElementType(), NewOperands.size());
138+
GlobalVariable *NGV =
139+
new GlobalVariable(M, ATy, false, GlobalValue::AppendingLinkage,
140+
ConstantArray::get(ATy, NewOperands), "");
141+
NGV->setName("llvm.compiler.used");
142+
NGV->setSection("llvm.metadata");
143+
}
144+
145+
return PreservedAnalyses::none();
146+
}

llvm/lib/SYCLLowerIR/ESIMD/ESIMDUtils.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ bool isKernel(const Function &F) {
8282

8383
bool isESIMDKernel(const Function &F) { return isKernel(F) && isESIMD(F); }
8484

85+
bool moduleContainsInvokeSimdBuiltin(Module &M) {
86+
return std::any_of(M.begin(), M.end(), [](Function &F) {
87+
return !F.isDeclaration() && F.getName().starts_with(INVOKE_SIMD_PREF);
88+
});
89+
}
90+
8591
Type *getVectorTyOrNull(StructType *STy) {
8692
Type *Res = nullptr;
8793
while (STy && (STy->getStructNumElements() == 1)) {

llvm/lib/SYCLLowerIR/ModuleSplitter.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,15 @@
2323
#include "llvm/IR/PassManager.h"
2424
#include "llvm/IR/PassManagerImpl.h"
2525
#include "llvm/IRPrinter/IRPrintingPasses.h"
26+
#include "llvm/SYCLLowerIR/CleanupSYCLMetadata.h"
27+
#include "llvm/SYCLLowerIR/ComputeModuleRuntimeInfo.h"
2628
#include "llvm/SYCLLowerIR/DeviceGlobals.h"
29+
#include "llvm/SYCLLowerIR/ESIMD/LowerESIMD.h"
2730
#include "llvm/SYCLLowerIR/LowerInvokeSimd.h"
2831
#include "llvm/SYCLLowerIR/SYCLDeviceLibReqMask.h"
32+
#include "llvm/SYCLLowerIR/SYCLJointMatrixTransform.h"
2933
#include "llvm/SYCLLowerIR/SYCLUtils.h"
34+
#include "llvm/SYCLLowerIR/SanitizerKernelMetadata.h"
3035
#include "llvm/SYCLLowerIR/SpecConstants.h"
3136
#include "llvm/Support/CommandLine.h"
3237
#include "llvm/Support/Error.h"
@@ -1420,6 +1425,47 @@ Expected<std::vector<SplitModule>> parseSplitModulesFromFile(StringRef File) {
14201425
return Modules;
14211426
}
14221427

1428+
bool runPreSplitProcessingPipeline(Module &M) {
1429+
ModulePassManager MPM;
1430+
ModuleAnalysisManager MAM;
1431+
MAM.registerPass([&] { return PassInstrumentationAnalysis(); });
1432+
1433+
// Propagate ESIMD attribute to wrapper functions to prevent
1434+
// spurious splits and kernel link errors.
1435+
MPM.addPass(SYCLFixupESIMDKernelWrapperMDPass());
1436+
1437+
// After linking device bitcode "llvm.used" holds references to the kernels
1438+
// that are defined in the device image. But after splitting device image into
1439+
// separate kernels we may end up with having references to kernel declaration
1440+
// originating from "llvm.used" in the IR that is passed to llvm-spirv tool,
1441+
// and these declarations cause an assertion in llvm-spirv. To workaround this
1442+
// issue remove "llvm.used" from the input module before performing any other
1443+
// actions.
1444+
MPM.addPass(CleanupSYCLMetadataFromLLVMUsed());
1445+
1446+
// There may be device_global variables kept alive in "llvm.compiler.used"
1447+
// to keep the optimizer from wrongfully removing them. llvm.compiler.used
1448+
// symbols are usually removed at backend lowering, but this is handled here
1449+
// for SPIR-V since SYCL compilation uses llvm-spirv, not the SPIR-V backend.
1450+
if (M.getTargetTriple().find("spir") != std::string::npos)
1451+
MPM.addPass(RemoveDeviceGlobalFromLLVMCompilerUsed());
1452+
1453+
// Sanitizer specific passes.
1454+
if (sycl::isModuleUsingAsan(M) || sycl::isModuleUsingMsan(M) ||
1455+
sycl::isModuleUsingTsan(M))
1456+
MPM.addPass(SanitizerKernelMetadataPass());
1457+
1458+
// Transform Joint Matrix builtin calls to align them with SPIR-V friendly
1459+
// LLVM IR specification.
1460+
MPM.addPass(SYCLJointMatrixTransformPass());
1461+
1462+
// Do invoke_simd processing before splitting because this:
1463+
// - saves processing time (the pass is run once, even though on larger IR)
1464+
// - doing it before SYCL/ESIMD splitting is required for correctness
1465+
MPM.addPass(SYCLLowerInvokeSimdPass());
1466+
return !MPM.run(M, MAM).areAllPreserved();
1467+
}
1468+
14231469
Expected<std::vector<SplitModule>>
14241470
splitSYCLModule(std::unique_ptr<Module> M, ModuleSplitterSettings Settings) {
14251471
ModuleDesc MD = std::move(M); // makeModuleDesc() ?

0 commit comments

Comments
 (0)