Skip to content

Commit 46bd3f6

Browse files
committed
Add PropagateAspectsUsage pass
Added a pass which is a part of optional kernel features design: it uses information provieded by FE & Headers about aspects used in device code to propagate it through the call graph to mark all kernels and functions with list of aspects they use.
1 parent 443971c commit 46bd3f6

File tree

11 files changed

+702
-0
lines changed

11 files changed

+702
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//===---- PropagateAspectsUsage.h - PropagateAspectsUsage Pass ------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// Pass propagates metadata corresponding to usage of optional device
10+
// features.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
//
14+
#ifndef LLVM_SYCLPROPAGATE_ASPECTS_USAGE_H
15+
#define LLVM_SYCLPROPAGATE_ASPECTS_USAGE_H
16+
17+
#include "llvm/IR/PassManager.h"
18+
19+
namespace llvm {
20+
21+
class PropagateAspectsUsagePass
22+
: public PassInfoMixin<PropagateAspectsUsagePass> {
23+
public:
24+
PreservedAnalyses run(Module &M, ModuleAnalysisManager &);
25+
};
26+
27+
} // namespace llvm
28+
29+
#endif // LLVM_SYCLPROPAGATE_ASPECTS_USAGE_H

llvm/lib/Passes/PassBuilder.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
#include "llvm/SYCLLowerIR/LowerWGLocalMemory.h"
8585
#include "llvm/SYCLLowerIR/LowerWGScope.h"
8686
#include "llvm/SYCLLowerIR/MutatePrintfAddrspace.h"
87+
#include "llvm/SYCLLowerIR/PropagateAspectsUsage.h"
8788
#include "llvm/Support/CommandLine.h"
8889
#include "llvm/Support/Debug.h"
8990
#include "llvm/Support/ErrorHandling.h"

llvm/lib/Passes/PassRegistry.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ MODULE_PASS("SPIRITTAnnotations", SPIRITTAnnotationsPass())
136136
MODULE_PASS("deadargelim-sycl", DeadArgumentEliminationSYCLPass())
137137
MODULE_PASS("sycllowerwglocalmemory", SYCLLowerWGLocalMemoryPass())
138138
MODULE_PASS("lower-esimd-kernel-attrs", SYCLFixupESIMDKernelWrapperMDPass())
139+
MODULE_PASS("sycl-propagate-aspects-usage", PropagateAspectsUsagePass())
139140
#undef MODULE_PASS
140141

141142
#ifndef MODULE_PASS_WITH_PARAMS

llvm/lib/SYCLLowerIR/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ add_llvm_component_library(LLVMSYCLLowerIR
5858
LowerWGScope.cpp
5959
LowerWGLocalMemory.cpp
6060
MutatePrintfAddrspace.cpp
61+
PropagateAspectsUsage.cpp
6162

6263
LocalAccessorToSharedMemory.cpp
6364
GlobalOffset.cpp
Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
//===---- PropagateAspectsUsage.cpp - PropagateAspectsUsage Pass ----------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// Pass propagates metadata corresponding to usage of optional device
10+
// features.
11+
//
12+
// The pass consists of a four main steps:
13+
//
14+
// I. It builds Type -> set of aspects mapping for usage in step II
15+
// II. It builds Function -> set of aspects mapping to use in further steps
16+
// III. FIXME: this step is not yet implemented
17+
// Analyzes aspects usage and emit warnings if necessary
18+
// IV. Generates metadata with information about aspects used by each function
19+
//
20+
// Note: step I is not strictly necessary, because we can simply check if a
21+
// function actually uses one or another type to say whether or not it uses any
22+
// aspects. However, from customers point of view it could be more transparent
23+
// that if a function is declared accepting an optional type, then it means that
24+
// it uses an associated aspect, regardless of whether or not compiler was able
25+
// to optimize out that variable.
26+
//
27+
//===----------------------------------------------------------------------===//
28+
29+
#include "llvm/SYCLLowerIR/PropagateAspectsUsage.h"
30+
31+
#include "llvm/ADT/SetVector.h"
32+
#include "llvm/ADT/SmallPtrSet.h"
33+
#include "llvm/ADT/SmallSet.h"
34+
#include "llvm/IR/Constants.h"
35+
#include "llvm/IR/InstIterator.h"
36+
#include "llvm/IR/Instructions.h"
37+
#include "llvm/IR/IntrinsicInst.h"
38+
#include "llvm/IR/Module.h"
39+
#include "llvm/Pass.h"
40+
#include "llvm/Support/Path.h"
41+
42+
#include <queue>
43+
#include <unordered_map>
44+
#include <unordered_set>
45+
46+
using namespace llvm;
47+
48+
namespace {
49+
50+
using AspectsSetTy = SmallSet<int, 4>;
51+
using TypeToAspectsMapTy = std::unordered_map<const Type *, AspectsSetTy>;
52+
53+
/// Retrieves from metadata (intel_types_that_use_aspects) types
54+
/// and aspects these types depend on.
55+
TypeToAspectsMapTy getTypesThatUseAspectsFromMetadata(const Module &M) {
56+
const NamedMDNode *Node = M.getNamedMetadata("intel_types_that_use_aspects");
57+
TypeToAspectsMapTy Result;
58+
if (!Node)
59+
return Result;
60+
61+
LLVMContext &C = M.getContext();
62+
for (const auto OperandIt : Node->operands()) {
63+
const MDNode &N = *OperandIt;
64+
assert(N.getNumOperands() > 1 && "intel_types_that_use_aspect metadata "
65+
"shouldn't contain empty metadata nodes");
66+
67+
const auto *TypeName = cast<MDString>(N.getOperand(0));
68+
const Type *T = StructType::getTypeByName(C, TypeName->getString());
69+
assert(T &&
70+
"invalid type referenced by intel_types_that_use_aspect metadata");
71+
72+
AspectsSetTy &Aspects = Result[T];
73+
for (size_t I = 1; I != N.getNumOperands(); ++I) {
74+
const auto *CAM = cast<ConstantAsMetadata>(N.getOperand(I));
75+
const Constant *C = CAM->getValue();
76+
Aspects.insert(cast<ConstantInt>(C)->getSExtValue());
77+
}
78+
}
79+
80+
return Result;
81+
}
82+
83+
using TypesEdgesTy =
84+
std::unordered_map<const Type *, std::vector<const Type *>>;
85+
86+
/// Propagates aspects from type @Start to all types which
87+
/// are reachable by edges @Edges by BFS algorithm.
88+
/// Result is recorded in @Aspects.
89+
void propagateAspectsThroughTypes(const TypesEdgesTy &Edges, const Type *Start,
90+
TypeToAspectsMapTy &Aspects) {
91+
const AspectsSetTy &AspectsToPropagate = Aspects[Start];
92+
SmallSetVector<const Type *, 16> TypesToPropagate;
93+
TypesToPropagate.insert(Start);
94+
for (size_t I = 0; I < TypesToPropagate.size(); ++I) {
95+
const Type *T = TypesToPropagate[I];
96+
Aspects[T].insert(AspectsToPropagate.begin(), AspectsToPropagate.end());
97+
const auto It = Edges.find(T);
98+
if (It != Edges.end())
99+
TypesToPropagate.insert(It->second.begin(), It->second.end());
100+
}
101+
}
102+
103+
/// Propagates given aspects to all types in module @M. Function accepts
104+
/// aspects in @TypesWithAspects reference and writes a result in this
105+
/// reference.
106+
/// Type T in the result uses an aspect A if Type T is a composite
107+
/// type (array, struct, vector) which contains elements/fields of
108+
/// another type TT, which in turn uses the aspect A.
109+
/// @TypesWithAspects argument consist of known types with aspects
110+
/// from metadata information.
111+
///
112+
/// The algorithm is the following:
113+
/// 1) Make a list of all structure types from module @M. The list also
114+
/// contains DoubleTy since it is optional as well.
115+
/// 2) Make from list a type graph which consists of nodes corresponding to
116+
/// types and directed edges between nodes. An edge from type A to type B
117+
/// corresponds to the fact that A is contained within B.
118+
/// Examples: B is a pointer to A, B is a struct containing field of type A.
119+
/// 3) For every known type with aspects propagate it's aspects over graph.
120+
/// Every propagation is a separate run of BFS algorithm.
121+
///
122+
/// Time complexity: O((V + E) * T) where T is the number of input types
123+
/// containing aspects.
124+
void propagateAspectsToOtherTypesInModule(
125+
const Module &M, TypeToAspectsMapTy &TypesWithAspects) {
126+
std::unordered_set<const Type *> TypesToProcess;
127+
const Type *DoubleTy = Type::getDoubleTy(M.getContext());
128+
129+
// 6 is taken from sycl/include/CL/sycl/aspects.hpp
130+
// Note: that magic number must strictly correspond to the one assigned to
131+
// 'fp64' value of 'aspect' enum.
132+
// FIXME: we should develop some kind of mechanism which will allow us to
133+
// avoid hardcoding this number here and having a build dependency between
134+
// the compiler and the runtime. See intel/llvm#5892
135+
static constexpr int AspectFP64 = 6;
136+
TypesWithAspects[DoubleTy].insert(AspectFP64);
137+
138+
TypesToProcess.insert(DoubleTy);
139+
for (const Type *T : M.getIdentifiedStructTypes())
140+
TypesToProcess.insert(T);
141+
142+
TypesEdgesTy Edges;
143+
for (const Type *T : TypesToProcess) {
144+
for (const Type *TT : T->subtypes()) {
145+
if (TT->isPointerTy())
146+
// We don't know the pointee type in opaque pointers world
147+
continue;
148+
149+
// If TT = [4 x [4 x [4 x %A]]] then we want to get TT = %A
150+
// The same with vectors
151+
while (TT->isArrayTy() || TT->isVectorTy()) {
152+
TT = TT->getContainedType(0);
153+
}
154+
155+
// We are not interested in some types. For example, IntTy.
156+
if (TypesToProcess.count(TT))
157+
Edges[TT].push_back(T);
158+
}
159+
}
160+
161+
TypeToAspectsMapTy Result;
162+
for (const Type *T : TypesToProcess)
163+
propagateAspectsThroughTypes(Edges, T, TypesWithAspects);
164+
}
165+
166+
/// Returns all aspects which might be reached from type @T.
167+
/// It encompases composite structures and pointers.
168+
/// NB! This function inserts new records in @Types map for new discovered
169+
/// types. For the best perfomance pass this map in the next invocations.
170+
const AspectsSetTy &getAspectsFromType(const Type *T,
171+
TypeToAspectsMapTy &Types) {
172+
const auto It = Types.find(T);
173+
if (It != Types.end())
174+
return It->second;
175+
176+
// Empty value is inserted for absent key T.
177+
// This is essential to no get into infinite recursive loops.
178+
AspectsSetTy &Result = Types[T];
179+
180+
for (const Type *TT : T->subtypes()) {
181+
const AspectsSetTy &Aspects = getAspectsFromType(TT, Types);
182+
Result.insert(Aspects.begin(), Aspects.end());
183+
}
184+
185+
return Result;
186+
}
187+
188+
/// Returns aspects which might be used in instruction @I.
189+
/// Function inspects return type and all operand's types.
190+
/// NB! This function inserts new records in @Types map for new discovered
191+
/// types. For the best perfomance pass this map in the next invocations.
192+
AspectsSetTy getAspectsUsedByInstruction(const Instruction &I,
193+
TypeToAspectsMapTy &Types) {
194+
const Type *ReturnType = I.getType();
195+
AspectsSetTy Result = getAspectsFromType(ReturnType, Types);
196+
for (const auto &OperandIt : I.operands()) {
197+
const AspectsSetTy &Aspects =
198+
getAspectsFromType(OperandIt->getType(), Types);
199+
Result.insert(Aspects.begin(), Aspects.end());
200+
}
201+
202+
return Result;
203+
}
204+
205+
using FunctionToAspectsMapTy = DenseMap<Function *, AspectsSetTy>;
206+
using CallGraphTy = DenseMap<Function *, SmallPtrSet<Function *, 8>>;
207+
208+
void createUsedAspectsMetadataForFunctions(FunctionToAspectsMapTy &Map) {
209+
for (auto &It : Map) {
210+
AspectsSetTy &Aspects = It.second;
211+
if (Aspects.empty())
212+
continue;
213+
214+
Function *F = It.first;
215+
LLVMContext &C = F->getContext();
216+
217+
SmallVector<Metadata *, 16> AspectsMetadata;
218+
for (const auto &A : Aspects)
219+
AspectsMetadata.push_back(ConstantAsMetadata::get(
220+
ConstantInt::getSigned(Type::getInt32Ty(C), A)));
221+
222+
MDNode *MDN = MDNode::get(C, AspectsMetadata);
223+
F->setMetadata("intel_used_aspects", MDN);
224+
}
225+
}
226+
227+
/// Propagates aspects from leaves up to the top of call graph.
228+
/// NB! Call graph corresponds to call graph of SYCL code which
229+
/// can't contain recursive calls. So there can't be loops in
230+
/// a call graph. But there can be path's intersections.
231+
void propagateAspectsThroughCG(Function *F, CallGraphTy &CG,
232+
FunctionToAspectsMapTy &AspectsMap,
233+
SmallPtrSet<const Function *, 16> &Visited) {
234+
const auto It = CG.find(F);
235+
if (It == CG.end())
236+
return;
237+
238+
AspectsSetTy LocalAspects;
239+
for (Function *Callee : It->second) {
240+
if (Visited.insert(Callee).second)
241+
propagateAspectsThroughCG(Callee, CG, AspectsMap, Visited);
242+
243+
const auto &CalleeAspects = AspectsMap[Callee];
244+
LocalAspects.insert(CalleeAspects.begin(), CalleeAspects.end());
245+
}
246+
247+
AspectsMap[F].insert(LocalAspects.begin(), LocalAspects.end());
248+
}
249+
250+
/// Processes a function:
251+
/// - checks if return and argument types are using any aspects
252+
/// - checks if instructions are using any aspects
253+
/// - updates call graph information
254+
///
255+
void processFunction(Function &F, FunctionToAspectsMapTy &FunctionToAspects,
256+
TypeToAspectsMapTy &TypesWithAspects, CallGraphTy &CG) {
257+
const AspectsSetTy RetTyAspects =
258+
getAspectsFromType(F.getReturnType(), TypesWithAspects);
259+
FunctionToAspects[&F].insert(RetTyAspects.begin(), RetTyAspects.end());
260+
for (Argument &Arg : F.args()) {
261+
const AspectsSetTy ArgAspects =
262+
getAspectsFromType(Arg.getType(), TypesWithAspects);
263+
FunctionToAspects[&F].insert(ArgAspects.begin(), ArgAspects.end());
264+
}
265+
266+
for (Instruction &I : instructions(F)) {
267+
const AspectsSetTy Aspects =
268+
getAspectsUsedByInstruction(I, TypesWithAspects);
269+
FunctionToAspects[&F].insert(Aspects.begin(), Aspects.end());
270+
271+
if (const auto *CI = dyn_cast<CallInst>(&I)) {
272+
if (!CI->isIndirectCall() && CI->getCalledFunction())
273+
CG[&F].insert(CI->getCalledFunction());
274+
}
275+
}
276+
}
277+
278+
// Return true if the function is a SPIRV or SYCL builtin, e.g.
279+
// _Z28__spirv_GlobalInvocationId_xv
280+
bool isSpirvSyclBuiltin(StringRef FName) {
281+
if (!FName.consume_front("_Z"))
282+
return false;
283+
// now skip the digits
284+
FName = FName.drop_while([](char C) { return std::isdigit(C); });
285+
286+
return FName.startswith("__spirv_") || FName.startswith("__sycl_");
287+
}
288+
289+
bool isEntryPoint(const Function &F) {
290+
// Skip declarations, we can't analyze them
291+
if (F.isDeclaration())
292+
return false;
293+
294+
// Kernels are always considered to be entry points
295+
if (CallingConv::SPIR_KERNEL == F.getCallingConv())
296+
return true;
297+
298+
// FIXME: sycl-post-link allows to disable treating SYCL_EXTERNAL's as entry
299+
// points - do we need similar flag here?
300+
// SYCL_EXTERNAL functions with sycl-module-id attribute
301+
// are also considered as entry points (except __spirv_* and __sycl_*
302+
// functions)
303+
return F.hasFnAttribute("sycl-module-id") && !isSpirvSyclBuiltin(F.getName());
304+
}
305+
306+
/// Returns a map of functions with corresponding used aspects.
307+
FunctionToAspectsMapTy
308+
buildFunctionsToAspectsMap(Module &M, TypeToAspectsMapTy &TypesWithAspects) {
309+
FunctionToAspectsMapTy FunctionToAspects;
310+
CallGraphTy CG;
311+
std::vector<Function *> EntryPoints;
312+
for (Function &F : M.functions()) {
313+
if (F.isDeclaration())
314+
continue;
315+
316+
if (isEntryPoint(F))
317+
EntryPoints.push_back(&F);
318+
319+
processFunction(F, FunctionToAspects, TypesWithAspects, CG);
320+
}
321+
322+
SmallPtrSet<const Function *, 16> Visited;
323+
for (Function *F : EntryPoints)
324+
propagateAspectsThroughCG(F, CG, FunctionToAspects, Visited);
325+
326+
return FunctionToAspects;
327+
}
328+
329+
} // anonymous namespace
330+
331+
PreservedAnalyses PropagateAspectsUsagePass::run(Module &M,
332+
ModuleAnalysisManager &MAM) {
333+
TypeToAspectsMapTy TypesWithAspects = getTypesThatUseAspectsFromMetadata(M);
334+
propagateAspectsToOtherTypesInModule(M, TypesWithAspects);
335+
336+
FunctionToAspectsMapTy FunctionToAspects =
337+
buildFunctionsToAspectsMap(M, TypesWithAspects);
338+
339+
createUsedAspectsMetadataForFunctions(FunctionToAspects);
340+
// FIXME: check and diagnose if a function uses an aspect which was not
341+
// declared through [[sycl::device_has()]] attribute
342+
343+
return PreservedAnalyses::all();
344+
}

0 commit comments

Comments
 (0)