Skip to content

Commit 1327939

Browse files
committed
[SYCL] Add e2e test for optional kernel features and virtual functions
1 parent 3b4de41 commit 1327939

File tree

8 files changed

+227
-38
lines changed

8 files changed

+227
-38
lines changed

llvm/include/llvm/SYCLLowerIR/ModuleSplitter.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ class ModuleDesc {
130130
EntryPointGroup EntryPoints;
131131
bool IsTopLevel = false;
132132
mutable std::optional<SYCLDeviceRequirements> Reqs;
133+
bool IsDummyImage = false;
133134

134135
public:
135136
struct Properties {
@@ -225,6 +226,9 @@ class ModuleDesc {
225226

226227
void saveSplitInformationAsMetadata();
227228

229+
ModuleDesc makeDummy() const;
230+
bool isDummyImage() { return IsDummyImage; }
231+
228232
#ifndef NDEBUG
229233
void verifyESIMDProperty() const;
230234
void dump() const;

llvm/lib/SYCLLowerIR/ModuleSplitter.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,15 @@ void ModuleDesc::saveSplitInformationAsMetadata() {
816816
SpecConstantsPass::SPEC_CONST_DEFAULT_VAL_MODULE_MD_STRING);
817817
}
818818

819+
ModuleDesc ModuleDesc::makeDummy() const {
820+
ModuleDesc MD(CloneModule(getModule()));
821+
MD.EntryPoints = EntryPoints;
822+
MD.IsTopLevel = IsTopLevel;
823+
MD.Reqs = Reqs;
824+
MD.IsDummyImage = true;
825+
return MD;
826+
}
827+
819828
void EntryPointGroup::saveNames(std::vector<std::string> &Dest) const {
820829
Dest.reserve(Dest.size() + Functions.size());
821830
std::transform(Functions.begin(), Functions.end(),

llvm/lib/SYCLLowerIR/SYCLPropagateAspectsUsage.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,22 @@ void processDeclaredVirtualFunctionSets(
665665
StringMap<SmallVector<Function *, 4>> &VirtualFunctionSets) {
666666
if (!F->hasFnAttribute("calls-indirectly"))
667667
return;
668+
669+
// "Construction" kernels which reference the vtable
670+
// can be marked with calls-indirectly attribute by SYCLVirtualFunctionAnalysis pass.
671+
bool hasVirtualCall = false;
672+
for (const Instruction &I : instructions(F)) {
673+
const auto *CI = dyn_cast<CallInst>(&I);
674+
if (!CI)
675+
continue;
676+
if (CI->isIndirectCall() && CI->hasFnAttr("virtual-call")) {
677+
hasVirtualCall = true;
678+
break;
679+
}
680+
}
681+
if (!hasVirtualCall)
682+
return;
683+
668684
Attribute CallsIndirectlyAttr = F->getFnAttribute("calls-indirectly");
669685
SmallVector<StringRef, 4> DeclaredVirtualFunctionSetNames;
670686
CallsIndirectlyAttr.getValueAsString().split(DeclaredVirtualFunctionSetNames,

llvm/test/tools/sycl-post-link/virtual-functions/dummy.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
; CHECK-FP64-DUMMY-NEXT: entry:
1717
; CHECK-FP64-DUMMY-NEXT: ret void
1818

19-
; CHECK-FP64-DUMMY-PROPS: dummy=1
19+
; CHECK-FP64-DUMMY-PROPS: dummy-image=1
2020

2121
define spir_func void @foo() #1 {
2222
%x = alloca float

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

Lines changed: 79 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
#include "llvm/Transforms/Scalar/DCE.h"
5959
#include "llvm/Transforms/Scalar/EarlyCSE.h"
6060
#include "llvm/Transforms/Scalar/SROA.h"
61+
#include "llvm/Transforms/Utils/Cloning.h"
6162
#include "llvm/Transforms/Utils/GlobalStatus.h"
6263

6364
#include <algorithm>
@@ -295,18 +296,41 @@ void saveModuleIR(Module &M, StringRef OutFilename) {
295296
MPM.run(M, MAM);
296297
}
297298

298-
std::string saveModuleIR(Module &M, int I, StringRef Suff) {
299-
DUMP_ENTRY_POINTS(M, EmitOnlyKernelsAsEntryPoints, "saving IR");
299+
std::unique_ptr<Module> makeDummyImageIR(const Module &M) {
300+
auto MCopy = CloneModule(M);
301+
for (Function &F : MCopy->functions()) {
302+
if (!F.hasFnAttribute("indirectly-callable"))
303+
continue;
304+
305+
F.erase(F.begin(), F.end());
306+
BasicBlock *newBB = BasicBlock::Create(F.getContext(), "entry", &F);
307+
IRBuilder<> builder(newBB);
308+
if (F.getReturnType()->isVoidTy())
309+
builder.CreateRetVoid();
310+
else
311+
builder.CreateRet(UndefValue::get(F.getReturnType()));
312+
}
313+
return MCopy;
314+
}
315+
316+
std::string saveModuleIR(module_split::ModuleDesc &MD, int I, StringRef Suff) {
317+
std::unique_ptr<Module> Storage;
318+
Module *M = &MD.getModule();
319+
if (MD.isDummyImage()) {
320+
Storage = makeDummyImageIR(MD.getModule());
321+
M = Storage.get();
322+
}
323+
324+
DUMP_ENTRY_POINTS(*M, EmitOnlyKernelsAsEntryPoints, "saving IR");
300325
StringRef FileExt = (OutputAssembly) ? ".ll" : ".bc";
301326
std::string OutFilename = makeResultFileName(FileExt, I, Suff);
302-
saveModuleIR(M, OutFilename);
327+
saveModuleIR(*M, OutFilename);
303328
return OutFilename;
304329
}
305330

306331
std::string saveModuleProperties(module_split::ModuleDesc &MD,
307332
const GlobalBinImageProps &GlobProps, int I,
308-
StringRef Suff, StringRef Target = "",
309-
bool IsDummy = false) {
333+
StringRef Suff, StringRef Target = "") {
310334
auto PropSet =
311335
computeModuleProperties(MD.getModule(), MD.entries(), GlobProps);
312336

@@ -318,9 +342,8 @@ std::string saveModuleProperties(module_split::ModuleDesc &MD,
318342
NewSuff += Target;
319343
}
320344

321-
if (IsDummy) {
322-
PropSet.add(PropSetRegTy::SYCL_VIRTUAL_FUNCTIONS, "dummy", 1);
323-
}
345+
if (MD.isDummyImage())
346+
PropSet.add(PropSetRegTy::SYCL_VIRTUAL_FUNCTIONS, "dummy-image", 1);
324347

325348
std::error_code EC;
326349
std::string SCFile = makeResultFileName(".prop", I, NewSuff);
@@ -420,8 +443,7 @@ void addTableRow(util::SimpleTable &Table,
420443
// IR component saving is skipped, and this file name is recorded as such in
421444
// the result.
422445
void saveModule(std::vector<std::unique_ptr<util::SimpleTable>> &OutTables,
423-
module_split::ModuleDesc &MD, int I, StringRef IRFilename,
424-
bool IsDummy = false) {
446+
module_split::ModuleDesc &MD, int I, StringRef IRFilename) {
425447
IrPropSymFilenameTriple BaseTriple;
426448
StringRef Suffix = getModuleSuffix(MD);
427449
MD.saveSplitInformationAsMetadata();
@@ -430,7 +452,7 @@ void saveModule(std::vector<std::unique_ptr<util::SimpleTable>> &OutTables,
430452
BaseTriple.Ir = IRFilename.str();
431453
} else {
432454
MD.cleanup();
433-
BaseTriple.Ir = saveModuleIR(MD.getModule(), I, Suffix);
455+
BaseTriple.Ir = saveModuleIR(MD, I, Suffix);
434456
}
435457
if (DoSymGen) {
436458
// save the names of the entry points - the symbol table
@@ -445,8 +467,8 @@ void saveModule(std::vector<std::unique_ptr<util::SimpleTable>> &OutTables,
445467
GlobalBinImageProps Props = {EmitKernelParamInfo, EmitProgramMetadata,
446468
EmitExportedSymbols, EmitImportedSymbols,
447469
DeviceGlobals};
448-
CopyTriple.Prop = saveModuleProperties(MD, Props, I, Suffix,
449-
OutputFile.Target, IsDummy);
470+
CopyTriple.Prop =
471+
saveModuleProperties(MD, Props, I, Suffix, OutputFile.Target);
450472
}
451473
addTableRow(*Table, CopyTriple);
452474
}
@@ -746,36 +768,53 @@ bool isTargetCompatibleWithModule(const std::string &Target,
746768
return true;
747769
}
748770

749-
std::optional<module_split::ModuleDesc>
750-
makeDummy(module_split::ModuleDesc &MD) {
771+
bool hasVirtualFunctionsAndOptionalKernelFeatures(const Module &M) {
751772
bool hasVirtualFunctions = false;
752773
bool hasOptionalKernelFeatures = false;
753-
for (Function &F : MD.getModule().functions()) {
774+
for (const Function &F : M.functions()) {
754775
if (F.hasFnAttribute("indirectly-callable"))
755776
hasVirtualFunctions = true;
756777
if (F.getMetadata("sycl_used_aspects"))
757778
hasOptionalKernelFeatures = true;
758779
if (hasVirtualFunctions && hasOptionalKernelFeatures)
759780
break;
760781
}
761-
if (!hasVirtualFunctions || !hasOptionalKernelFeatures)
762-
return {};
763-
764-
auto MDCopy = MD.clone();
765-
766-
for (Function &F : MDCopy.getModule().functions()) {
767-
if (!F.hasFnAttribute("indirectly-callable"))
768-
continue;
769-
770-
F.erase(F.begin(), F.end());
771-
BasicBlock *newBB = BasicBlock::Create(F.getContext(), "entry", &F);
772-
IRBuilder<> builder(newBB);
773-
builder.CreateRetVoid();
774-
}
775-
776-
return MDCopy;
782+
return hasVirtualFunctions && hasOptionalKernelFeatures;
777783
}
778784

785+
// std::optional<module_split::ModuleDesc>
786+
// makeDummy(module_split::ModuleDesc &MD) {
787+
// bool hasVirtualFunctions = false;
788+
// bool hasOptionalKernelFeatures = false;
789+
// for (Function &F : M.functions()) {
790+
// if (F.hasFnAttribute("indirectly-callable"))
791+
// hasVirtualFunctions = true;
792+
// if (F.getMetadata("sycl_used_aspects"))
793+
// hasOptionalKernelFeatures = true;
794+
// if (hasVirtualFunctions && hasOptionalKernelFeatures)
795+
// break;
796+
// }
797+
// if (!hasVirtualFunctions || !hasOptionalKernelFeatures)
798+
// return {};
799+
800+
// auto MDCopy = MD.clone();
801+
802+
// for (Function &F : MDCopy.getModule().functions()) {
803+
// if (!F.hasFnAttribute("indirectly-callable"))
804+
// continue;
805+
806+
// F.erase(F.begin(), F.end());
807+
// BasicBlock *newBB = BasicBlock::Create(F.getContext(), "entry", &F);
808+
// IRBuilder<> builder(newBB);
809+
// if (F.getReturnType()->isVoidTy())
810+
// builder.CreateRetVoid();
811+
// else
812+
// builder.CreateRet(UndefValue::get(F.getReturnType()));
813+
// }
814+
815+
// return MDCopy;
816+
// }
817+
779818
std::vector<std::unique_ptr<util::SimpleTable>>
780819
processInputModule(std::unique_ptr<Module> M) {
781820
// Construct the resulting table which will accumulate all the outputs.
@@ -924,11 +963,16 @@ processInputModule(std::unique_ptr<Module> M) {
924963
++ID;
925964
}
926965

966+
// For kernels with virtual functions and optional kernel features, generate
967+
// a dummy image to avoid link errors. A dummy image for a set of virtual
968+
// functions is a module with the same set of virtual functions, but with
969+
// those function bodies replaced with just a return.
927970
bool dummyEmitted = false;
928971
for (module_split::ModuleDesc &IrMD : MMs) {
929-
if (auto Dummy = makeDummy(IrMD)) {
930-
saveModule(Tables, *Dummy, ID, OutIRFileName, /*IsDummy*/ true);
931-
dummyEmitted = true;
972+
if ((dummyEmitted = hasVirtualFunctionsAndOptionalKernelFeatures(
973+
IrMD.getModule()))) {
974+
auto DummyImage = IrMD.makeDummy();
975+
saveModule(Tables, DummyImage, ID, OutIRFileName);
932976
}
933977
}
934978
if (dummyEmitted)

sycl/source/detail/device_binary_image.cpp

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,35 @@ namespace sycl {
2020
inline namespace _V1 {
2121
namespace detail {
2222

23+
void printAspects(std::ostream &Out, ByteArray BA) {
24+
BA.dropBytes(8);
25+
Out << "[";
26+
for (int i = 0; !BA.empty(); ++i) {
27+
auto Aspect = BA.consume<sycl::aspect>();
28+
switch (Aspect) {
29+
#define __SYCL_ASPECT(ASPECT, ID) \
30+
case sycl::aspect::ASPECT: \
31+
Out << #ASPECT; \
32+
break;
33+
#define __SYCL_ASPECT_DEPRECATED(ASPECT, ID, MESSAGE) \
34+
case sycl::aspect::ASPECT: \
35+
Out << #ASPECT; \
36+
break;
37+
#include <sycl/info/aspects.def>
38+
#include <sycl/info/aspects_deprecated.def>
39+
#undef __SYCL_ASPECT
40+
#undef __SYCL_ASPECT_DEPRECATED
41+
default:
42+
Out << "unknown (" << static_cast<int>(Aspect) << ")";
43+
break;
44+
}
45+
if (i != 0)
46+
Out << ", ";
47+
}
48+
Out << "]";
49+
return;
50+
}
51+
2352
std::ostream &operator<<(std::ostream &Out, const DeviceBinaryProperty &P) {
2453
switch (P.Prop->Type) {
2554
case SYCL_PROPERTY_TYPE_UINT32:
@@ -42,6 +71,20 @@ std::ostream &operator<<(std::ostream &Out, const DeviceBinaryProperty &P) {
4271
Out << P.asUint32();
4372
break;
4473
case SYCL_PROPERTY_TYPE_BYTE_ARRAY: {
74+
// Special case for aspects, print out the aspect names
75+
if (P.Prop->Name == std::string_view("aspects")) {
76+
printAspects(Out, P.asByteArray());
77+
break;
78+
}
79+
80+
// Special case for these properties, print out the value as a string
81+
if (P.Prop->Name == std::string_view("virtual-functions-set") ||
82+
P.Prop->Name == std::string_view("uses-virtual-functions-set")) {
83+
Out << P.asStringView();
84+
break;
85+
}
86+
87+
// Otherwise, print out the byte array as hex
4588
ByteArray BA = P.asByteArray();
4689
std::ios_base::fmtflags FlagsBackup = Out.flags();
4790
Out << std::hex;
@@ -84,7 +127,7 @@ std::string_view DeviceBinaryProperty::asStringView() const {
84127
assert(Prop->ValSize > 0 && "property size mismatch");
85128
// Byte array stores its size in first 8 bytes
86129
size_t Shift = Prop->Type == SYCL_PROPERTY_TYPE_BYTE_ARRAY ? 8 : 0;
87-
return {ur::cast<const char *>(Prop->ValAddr) + Shift, Prop->ValSize};
130+
return {ur::cast<const char *>(Prop->ValAddr) + Shift, Prop->ValSize - Shift};
88131
}
89132

90133
void RTDeviceBinaryImage::PropertyRange::init(sycl_device_binary Bin,

sycl/source/detail/program_manager/program_manager.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,8 +732,10 @@ ProgramManager::collectDependentDeviceImagesForVirtualFunctions(
732732
// when the dummy image does not support the device requirements, we
733733
// know the corresponding image providing virtual functions was not
734734
// linked and we must link the dummy image.
735-
if (doesDevSupportDeviceRequirements(Dev, *BinImage) + isDummyImage == 1)
735+
if (doesDevSupportDeviceRequirements(Dev, *BinImage) + isDummyImage ==
736+
1) {
736737
DeviceImagesToLink.insert(BinImage);
738+
}
737739
}
738740
}
739741

@@ -1765,6 +1767,9 @@ void ProgramManager::addImages(sycl_device_binaries DeviceBinary) {
17651767
else
17661768
Img = std::make_unique<RTDeviceBinaryImage>(RawImg);
17671769

1770+
if (std::getenv("SYCL_PRINT_IMAGES"))
1771+
Img->print();
1772+
17681773
static uint32_t SequenceID = 0;
17691774

17701775
// Fill the kernel argument mask map

0 commit comments

Comments
 (0)