Skip to content

Commit 35c2e00

Browse files
[SYCL][sycl-post-link] Implement transformation of sycl-properties annotations (#5940)
This commit adds the transformation from llvm.ptr.annotations calls with "sycl-properties" as the annotation string into llvm.ptr.annotations calls with decoration IDs and values in a format the SPIR-V translator is able to translate into SPIR-V decorations. Signed-off-by: Steffen Larsen <[email protected]>
1 parent 863383b commit 35c2e00

File tree

3 files changed

+242
-0
lines changed

3 files changed

+242
-0
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
; RUN: sycl-post-link --device-globals --ir-output-only -S %s -o %t.ll
2+
; RUN: FileCheck %s -input-file=%t.ll
3+
;
4+
; TODO: Remove --device-globals once other features start using compile-time
5+
; properties.
6+
;
7+
; Tests the translation of "sycl-properties" pointer annotations to pointer
8+
; annotations the SPIR-V translator will produce decorations from.
9+
; NOTE: These use SYCL property meta-names that are currently only intended for
10+
; use in attributes-to-metadata translations, but sycl-post-link does not
11+
; currently make the distinction so we will use them for the purpose of
12+
; testing the transformations.
13+
14+
target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64"
15+
target triple = "spir64-unknown-unknown"
16+
17+
%struct.foo = type { i32 addrspace(4)*, i32 addrspace(4)*, i32 addrspace(4)*, i32 addrspace(4)*, i32 addrspace(4)*, i32 addrspace(4)*, i32 addrspace(4)* }
18+
19+
$_ZTSZ4mainEUlvE_ = comdat any
20+
21+
@.str = private unnamed_addr constant [16 x i8] c"sycl-properties\00", section "llvm.metadata"
22+
@.str.1 = private unnamed_addr constant [36 x i8] c"sycl-properties-ptr-annotations.cpp\00", section "llvm.metadata"
23+
@.str.2 = private unnamed_addr constant [15 x i8] c"sycl-init-mode\00", section "llvm.metadata"
24+
@.str.3 = private unnamed_addr constant [2 x i8] c"1\00", section "llvm.metadata"
25+
@.str.4 = private unnamed_addr constant [22 x i8] c"sycl-implement-in-csr\00", section "llvm.metadata"
26+
@.str.5 = private unnamed_addr constant [5 x i8] c"true\00", section "llvm.metadata"
27+
@.args = private unnamed_addr constant { [15 x i8]*, [2 x i8]*, [22 x i8]*, [5 x i8]* } { [15 x i8]* @.str.2, [2 x i8]*@.str.3, [22 x i8]* @.str.4, [5 x i8]* @.str.5 }, section "llvm.metadata"
28+
@.args.6 = private unnamed_addr constant { [15 x i8]*, [2 x i8]* } { [15 x i8]* @.str.2, [2 x i8]* @.str.3 }, section "llvm.metadata"
29+
@.args.7 = private unnamed_addr constant { [22 x i8]*, [5 x i8]* } { [22 x i8]* @.str.4, [5 x i8]* @.str.5 }, section "llvm.metadata"
30+
@.args.8 = private unnamed_addr constant { [15 x i8]*, [2 x i8]*, [22 x i8]*, [5 x i8]* } { [15 x i8]* @.str.2, [2 x i8]* @.str.3, [22 x i8]* @.str.4, [5 x i8]* @.str.5 }, section "llvm.metadata"
31+
@.str.9 = private unnamed_addr constant [18 x i8] c"sycl-unrecognized\00", section "llvm.metadata"
32+
@.args.10 = private unnamed_addr constant { [15 x i8]*, [2 x i8]*, [18 x i8]*, i8* } { [15 x i8]* @.str.2, [2 x i8]* @.str.3, [18 x i8]* @.str.9, i8* null }, section "llvm.metadata"
33+
@.args.11 = private unnamed_addr constant { [18 x i8]*, i8* } { [18 x i8]* @.str.9, i8* null }, section "llvm.metadata"
34+
35+
;CHECK: @[[NewAnnotStr1:.*]] = private unnamed_addr constant [24 x i8] c"{6148:\221\22}{6149:\22true\22}\00", section "llvm.metadata"
36+
;CHECK: @[[NewAnnotStr2:.*]] = private unnamed_addr constant [11 x i8] c"{6148:\221\22}\00", section "llvm.metadata"
37+
;CHECK: @[[NewAnnotStr3:.*]] = private unnamed_addr constant [14 x i8] c"{6149:\22true\22}\00", section "llvm.metadata"
38+
39+
; Function Attrs: mustprogress norecurse
40+
define weak_odr dso_local spir_kernel void @_ZTSZ4mainEUlvE_() local_unnamed_addr #0 comdat !kernel_arg_buffer_location !7 !sycl_kernel_omit_args !7 {
41+
entry:
42+
%x.i = alloca %struct.foo, align 8
43+
%x.ascast.i = addrspacecast %struct.foo* %x.i to %struct.foo addrspace(4)*
44+
%0 = bitcast %struct.foo* %x.i to i8*
45+
%1 = addrspacecast i8* %0 to i8 addrspace(4)*
46+
%2 = call i8 addrspace(4)* @llvm.ptr.annotation.p4i8(i8 addrspace(4)* %1, i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str, i64 0, i64 0), i8* getelementptr inbounds ([36 x i8], [36 x i8]* @.str.1, i64 0, i64 0), i32 5, i8* bitcast ({ [15 x i8]*, [2 x i8]*, [22 x i8]*, [5 x i8]* }* @.args to i8*)) #2
47+
; CHECK: %{{.*}} = call i8 addrspace(4)* @llvm.ptr.annotation.p4i8(i8 addrspace(4)* %1, i8* getelementptr inbounds ([24 x i8], [24 x i8]* @[[NewAnnotStr1]], i32 0, i32 0), i8* getelementptr inbounds ([36 x i8], [36 x i8]* @.str.1, i64 0, i64 0), i32 5, i8* null)
48+
%b.i = getelementptr inbounds %struct.foo, %struct.foo addrspace(4)* %x.ascast.i, i64 0, i32 1
49+
%3 = bitcast i32 addrspace(4)* addrspace(4)* %b.i to i8 addrspace(4)*
50+
%4 = call i8 addrspace(4)* @llvm.ptr.annotation.p4i8(i8 addrspace(4)* %3, i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str, i64 0, i64 0), i8* getelementptr inbounds ([36 x i8], [36 x i8]* @.str.1, i64 0, i64 0), i32 6, i8* bitcast ({ [15 x i8]*, [2 x i8]* }* @.args.6 to i8*)) #2
51+
; CHECK: %{{.*}} = call i8 addrspace(4)* @llvm.ptr.annotation.p4i8(i8 addrspace(4)* %3, i8* getelementptr inbounds ([11 x i8], [11 x i8]* @[[NewAnnotStr2]], i32 0, i32 0), i8* getelementptr inbounds ([36 x i8], [36 x i8]* @.str.1, i64 0, i64 0), i32 6, i8* null)
52+
%c.i = getelementptr inbounds %struct.foo, %struct.foo addrspace(4)* %x.ascast.i, i64 0, i32 2
53+
%5 = bitcast i32 addrspace(4)* addrspace(4)* %c.i to i8 addrspace(4)*
54+
%6 = call i8 addrspace(4)* @llvm.ptr.annotation.p4i8(i8 addrspace(4)* %5, i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str, i64 0, i64 0), i8* getelementptr inbounds ([36 x i8], [36 x i8]* @.str.1, i64 0, i64 0), i32 7, i8* bitcast ({ [22 x i8]*, [5 x i8]* }* @.args.7 to i8*)) #2
55+
; CHECK: %{{.*}} = call i8 addrspace(4)* @llvm.ptr.annotation.p4i8(i8 addrspace(4)* %5, i8* getelementptr inbounds ([14 x i8], [14 x i8]* @[[NewAnnotStr3]], i32 0, i32 0), i8* getelementptr inbounds ([36 x i8], [36 x i8]* @.str.1, i64 0, i64 0), i32 7, i8* null)
56+
%d.i = getelementptr inbounds %struct.foo, %struct.foo addrspace(4)* %x.ascast.i, i64 0, i32 3
57+
%7 = bitcast i32 addrspace(4)* addrspace(4)* %d.i to i8 addrspace(4)*
58+
%8 = call i8 addrspace(4)* @llvm.ptr.annotation.p4i8(i8 addrspace(4)* %7, i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str, i64 0, i64 0), i8* getelementptr inbounds ([36 x i8], [36 x i8]* @.str.1, i64 0, i64 0), i32 8, i8* bitcast ({ [15 x i8]*, [2 x i8]*, [22 x i8]*, [5 x i8]* }* @.args.8 to i8*)) #2
59+
; CHECK: %{{.*}} = call i8 addrspace(4)* @llvm.ptr.annotation.p4i8(i8 addrspace(4)* %7, i8* getelementptr inbounds ([24 x i8], [24 x i8]* @[[NewAnnotStr1]], i32 0, i32 0), i8* getelementptr inbounds ([36 x i8], [36 x i8]* @.str.1, i64 0, i64 0), i32 8, i8* null)
60+
%e.i = getelementptr inbounds %struct.foo, %struct.foo addrspace(4)* %x.ascast.i, i64 0, i32 4
61+
%9 = bitcast i32 addrspace(4)* addrspace(4)* %e.i to i8 addrspace(4)*
62+
%10 = call i8 addrspace(4)* @llvm.ptr.annotation.p4i8(i8 addrspace(4)* %9, i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str, i64 0, i64 0), i8* getelementptr inbounds ([36 x i8], [36 x i8]* @.str.1, i64 0, i64 0), i32 9, i8* bitcast ({ [15 x i8]*, [2 x i8]*, [18 x i8]*, i8* }* @.args.10 to i8*)) #2
63+
; CHECK: %{{.*}} = call i8 addrspace(4)* @llvm.ptr.annotation.p4i8(i8 addrspace(4)* %9, i8* getelementptr inbounds ([11 x i8], [11 x i8]* @[[NewAnnotStr2]], i32 0, i32 0), i8* getelementptr inbounds ([36 x i8], [36 x i8]* @.str.1, i64 0, i64 0), i32 9, i8* null)
64+
%f.i = getelementptr inbounds %struct.foo, %struct.foo addrspace(4)* %x.ascast.i, i64 0, i32 5
65+
%11 = bitcast i32 addrspace(4)* addrspace(4)* %f.i to i8 addrspace(4)*
66+
%12 = call i8 addrspace(4)* @llvm.ptr.annotation.p4i8(i8 addrspace(4)* %11, i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str, i64 0, i64 0), i8* getelementptr inbounds ([36 x i8], [36 x i8]* @.str.1, i64 0, i64 0), i32 10, i8* bitcast ({ [18 x i8]*, i8* }* @.args.11 to i8*)) #2
67+
; CHECK-NOT: %{{.*}} = call i8 addrspace(4)* @llvm.ptr.annotation.
68+
%g.i = getelementptr inbounds %struct.foo, %struct.foo addrspace(4)* %x.ascast.i, i64 0, i32 6
69+
%13 = bitcast i32 addrspace(4)* addrspace(4)* %g.i to i8 addrspace(4)*
70+
%14 = call i8 addrspace(4)* @llvm.ptr.annotation.p4i8(i8 addrspace(4)* %13, i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str, i64 0, i64 0), i8* getelementptr inbounds ([36 x i8], [36 x i8]* @.str.1, i64 0, i64 0), i32 11, i8* null) #2
71+
ret void
72+
}
73+
74+
; Function Attrs: inaccessiblememonly nofree nosync nounwind willreturn
75+
declare i8 addrspace(4)* @llvm.ptr.annotation.p4i8(i8 addrspace(4)*, i8*, i8*, i32, i8*) #1
76+
77+
attributes #0 = { mustprogress norecurse "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "sycl-module-id"="sycl-properties-ptr-annotations.cpp" "uniform-work-group-size"="true" }
78+
attributes #1 = { inaccessiblememonly nofree nosync nounwind willreturn }
79+
attributes #2 = { nounwind }
80+
81+
!opencl.spir.version = !{!0, !0, !0, !0, !0, !0}
82+
!spirv.Source = !{!1, !1, !1, !1, !1, !1}
83+
!llvm.ident = !{!2, !3, !3, !3, !4, !3}
84+
!llvm.module.flags = !{!5, !6}
85+
86+
!0 = !{i32 1, i32 2}
87+
!1 = !{i32 4, i32 100000}
88+
!2 = !{!"clang version 15.0.0"}
89+
!3 = !{!"clang version 15.0.0"}
90+
!4 = !{!"clang version 15.0.0"}
91+
!5 = !{i32 1, !"wchar_size", i32 4}
92+
!6 = !{i32 7, !"frame-pointer", i32 2}
93+
!7 = !{}

llvm/tools/sycl-post-link/CompileTimePropertiesPass.cpp

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
#include "llvm/ADT/APInt.h"
1515
#include "llvm/ADT/StringMap.h"
1616
#include "llvm/ADT/StringRef.h"
17+
#include "llvm/IR/InstIterator.h"
18+
#include "llvm/IR/IntrinsicInst.h"
1719
#include "llvm/IR/Module.h"
20+
#include "llvm/IR/Operator.h"
1821

1922
using namespace llvm;
2023

@@ -90,6 +93,15 @@ MDNode *buildSpirvDecorMetadata(LLVMContext &Ctx, uint32_t OpCode,
9093
return MDNode::get(Ctx, MD);
9194
}
9295

96+
Optional<StringRef> getGlobalVariableString(const Value *StringV) {
97+
if (const auto *StringGV = dyn_cast<GlobalVariable>(StringV))
98+
if (const auto *StringData =
99+
dyn_cast<ConstantDataSequential>(StringGV->getInitializer()))
100+
if (StringData->isCString())
101+
return StringData->getAsCString();
102+
return {};
103+
}
104+
93105
} // anonymous namespace
94106

95107
PreservedAnalyses CompileTimePropertiesPass::run(Module &M,
@@ -142,9 +154,128 @@ PreservedAnalyses CompileTimePropertiesPass::run(Module &M,
142154
}
143155
}
144156

157+
// Check pointer annotations.
158+
SmallVector<IntrinsicInst *, 4> RemovableAnnots;
159+
for (Function &F : M)
160+
for (inst_iterator I = inst_begin(&F), E = inst_end(&F); I != E; ++I)
161+
if (auto *IntrInst = dyn_cast<IntrinsicInst>(&*I))
162+
if (IntrInst->getIntrinsicID() == Intrinsic::ptr_annotation &&
163+
transformSYCLPropertiesAnnotation(M, IntrInst, RemovableAnnots))
164+
CompileTimePropertiesMet = true;
165+
166+
// Remove irrelevant "sycl-properties" annotations after the transformations.
167+
for (IntrinsicInst *IntrInst : RemovableAnnots) {
168+
assert(IntrInst->getNumUses() == 0);
169+
IntrInst->eraseFromParent();
170+
}
171+
145172
// The pass just adds some metadata to the module, it should not ruin
146173
// any analysis, but we need return PreservedAnalyses::none() to inform
147174
// the caller that at least one compile-time property was met.
148175
return CompileTimePropertiesMet ? PreservedAnalyses::none()
149176
: PreservedAnalyses::all();
150177
}
178+
179+
// Returns true if the transformation changed IntrInst.
180+
bool CompileTimePropertiesPass::transformSYCLPropertiesAnnotation(
181+
Module &M, IntrinsicInst *IntrInst,
182+
SmallVectorImpl<IntrinsicInst *> &RemovableAnnotations) {
183+
assert(IntrInst->getIntrinsicID() == Intrinsic::ptr_annotation &&
184+
"Intrinsic is not a pointer annotation.");
185+
assert(IntrInst->arg_size() == 5 &&
186+
"Unexpected number of arguments in annotation intrinsic.");
187+
188+
// Get the global variable with the annotation string.
189+
const GlobalVariable *AnnotStrArgGV = nullptr;
190+
const Value *IntrAnnotStringArg = IntrInst->getArgOperand(1);
191+
if (auto *GEP = dyn_cast<GEPOperator>(IntrAnnotStringArg))
192+
if (auto *C = dyn_cast<Constant>(GEP->getOperand(0)))
193+
AnnotStrArgGV = dyn_cast<GlobalVariable>(C);
194+
if (!AnnotStrArgGV)
195+
return false;
196+
197+
// We only need to consider annotations with "sycl-properties" annotation
198+
// string.
199+
Optional<StringRef> AnnotStr = getGlobalVariableString(AnnotStrArgGV);
200+
if (!AnnotStr || AnnotStr->str() != "sycl-properties")
201+
return false;
202+
203+
// Read the annotation values and create the new annotation string.
204+
std::string NewAnnotString = "";
205+
if (const auto *Cast =
206+
dyn_cast<BitCastOperator>(IntrInst->getArgOperand(4))) {
207+
if (const auto *AnnotValsGV =
208+
dyn_cast<GlobalVariable>(Cast->getOperand(0))) {
209+
if (const auto *AnnotValsAggr =
210+
dyn_cast<ConstantAggregate>(AnnotValsGV->getInitializer())) {
211+
assert(
212+
(AnnotValsAggr->getNumOperands() & 1) == 0 &&
213+
"sycl-properties annotation must have an even number of annotation "
214+
"values.");
215+
216+
// Iterate over the pairs of property meta-names and meta-values.
217+
for (size_t I = 0; I < AnnotValsAggr->getNumOperands(); I += 2) {
218+
Optional<StringRef> PropMetaName =
219+
getGlobalVariableString(AnnotValsAggr->getOperand(I));
220+
Optional<StringRef> PropMetaValue =
221+
getGlobalVariableString(AnnotValsAggr->getOperand(I + 1));
222+
223+
assert(PropMetaName &&
224+
"Unexpected format for property name in annotation.");
225+
226+
auto DecorIt = SpirvDecorMap.find(*PropMetaName);
227+
if (DecorIt == SpirvDecorMap.end())
228+
continue;
229+
uint32_t DecorCode = DecorIt->second.Code;
230+
231+
// Expected format is '{X}' or '{X:Y}' where X is decoration ID and
232+
// Y is the value if present. It encloses Y in " to ensure that
233+
// string values are handled correctly. Note that " around values are
234+
// always valid, even if the decoration parameters are not strings.
235+
NewAnnotString += "{" + std::to_string(DecorCode);
236+
if (PropMetaValue)
237+
NewAnnotString += ":\"" + PropMetaValue->str() + "\"";
238+
NewAnnotString += "}";
239+
}
240+
}
241+
}
242+
}
243+
244+
// If the new annotation string is empty there is no reason to keep it, so
245+
// replace it with the first operand and mark it for removal.
246+
if (NewAnnotString.empty()) {
247+
IntrInst->replaceAllUsesWith(IntrInst->getOperand(0));
248+
RemovableAnnotations.push_back(IntrInst);
249+
return true;
250+
}
251+
252+
// Either reuse a previously generated one or create a new global variable
253+
// with the new annotation string.
254+
GlobalVariable *NewAnnotStringGV = nullptr;
255+
auto ExistingNewAnnotStringIt = ReusableAnnotStrings.find(NewAnnotString);
256+
if (ExistingNewAnnotStringIt != ReusableAnnotStrings.end()) {
257+
NewAnnotStringGV = ExistingNewAnnotStringIt->second;
258+
} else {
259+
Constant *NewAnnotStringData =
260+
ConstantDataArray::getString(M.getContext(), NewAnnotString);
261+
NewAnnotStringGV = new GlobalVariable(M, NewAnnotStringData->getType(),
262+
true, GlobalValue::PrivateLinkage,
263+
NewAnnotStringData, ".str");
264+
NewAnnotStringGV->setSection(AnnotStrArgGV->getSection());
265+
NewAnnotStringGV->setUnnamedAddr(GlobalValue::UnnamedAddr::Global);
266+
ReusableAnnotStrings.insert({NewAnnotString, NewAnnotStringGV});
267+
}
268+
269+
// Replace the annotation string with a bitcast of the new global variable.
270+
IntrInst->setArgOperand(
271+
1, ConstantExpr::getBitCast(NewAnnotStringGV,
272+
IntrAnnotStringArg->getType()));
273+
274+
// The values are not in the annotation string, so we can remove the original
275+
// annotation value.
276+
unsigned DefaultAS = M.getDataLayout().getDefaultGlobalsAddressSpace();
277+
Type *Int8Ty = IntegerType::getInt8Ty(M.getContext());
278+
PointerType *Int8DefaultASPtrTy = Int8Ty->getPointerTo(DefaultAS);
279+
IntrInst->setArgOperand(4, ConstantPointerNull::get(Int8DefaultASPtrTy));
280+
return true;
281+
}

llvm/tools/sycl-post-link/CompileTimePropertiesPass.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,33 @@
1717
#include "llvm/IR/PassManager.h"
1818

1919
#include <cassert>
20+
#include <string>
21+
#include <unordered_map>
2022

2123
namespace llvm {
2224

25+
// Forward declaration.
26+
class IntrinsicInst;
27+
2328
class CompileTimePropertiesPass
2429
: public PassInfoMixin<CompileTimePropertiesPass> {
2530
public:
2631
// Enriches the module with metadata that describes the found variables for
2732
// the SPIRV-LLVM Translator.
2833
PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM);
34+
35+
private:
36+
// Transforms llvm.ptr.annotations with the "sycl-properties" annotation
37+
// string into another llvm.ptr.annotations call in a format consumable by
38+
// the SPIR-V translator.
39+
bool transformSYCLPropertiesAnnotation(
40+
Module &M, IntrinsicInst *IntrInst,
41+
SmallVectorImpl<IntrinsicInst *> &RemovableAnnotations);
42+
43+
// Map for keeping track of global variables generated for annotation strings.
44+
// This allows reuse for annotations with the same generated annotation
45+
// strings.
46+
std::unordered_map<std::string, GlobalVariable *> ReusableAnnotStrings;
2947
};
3048

3149
namespace detail {

0 commit comments

Comments
 (0)