-
Notifications
You must be signed in to change notification settings - Fork 733
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SYCL] PoC implementation of kernel compiler extension with libtooling and sycl-jit #15701
base: sycl
Are you sure you want to change the base?
Changes from all commits
6e892f9
246bc6d
46e7127
7e06ae1
ef90b4b
bfe9b43
2c2ac32
f5871cc
c60a528
ce333dc
0ad693d
7a928b3
c2eb448
407124b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
//==---------------------- DeviceCompilation.cpp ---------------------------==// | ||
// | ||
// 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 "DeviceCompilation.h" | ||
|
||
#include <clang/Basic/Version.h> | ||
#include <clang/CodeGen/CodeGenAction.h> | ||
#include <clang/Driver/Compilation.h> | ||
#include <clang/Frontend/CompilerInstance.h> | ||
#include <clang/Tooling/CompilationDatabase.h> | ||
#include <clang/Tooling/Tooling.h> | ||
|
||
#include <llvm/IR/Module.h> | ||
|
||
#ifdef _GNU_SOURCE | ||
#include <dlfcn.h> | ||
static char X; // Dummy symbol, used as an anchor for `dlinfo` below. | ||
#endif // _GNU_SOURCE | ||
|
||
static constexpr auto InvalidDPCPPRoot = "<invalid>"; | ||
static constexpr auto JITLibraryPathSuffix = "/lib/libsycl-jit.so"; | ||
|
||
static const std::string &getDPCPPRoot() { | ||
thread_local std::string DPCPPRoot; | ||
|
||
if (!DPCPPRoot.empty()) { | ||
return DPCPPRoot; | ||
} | ||
DPCPPRoot = InvalidDPCPPRoot; | ||
|
||
#ifdef _GNU_SOURCE | ||
Dl_info Info; | ||
if (dladdr(&X, &Info)) { | ||
std::string LoadedLibraryPath = Info.dli_fname; | ||
auto Pos = LoadedLibraryPath.rfind(JITLibraryPathSuffix); | ||
if (Pos != std::string::npos) { | ||
DPCPPRoot = LoadedLibraryPath.substr(0, Pos); | ||
} | ||
} | ||
#endif // _GNU_SOURCE | ||
|
||
// TODO: Implemenent other means of determining the DPCPP root, e.g. | ||
// evaluating the `CMPLR_ROOT` env. | ||
|
||
return DPCPPRoot; | ||
} | ||
|
||
namespace { | ||
using namespace clang; | ||
using namespace clang::tooling; | ||
using namespace clang::driver; | ||
|
||
struct GetLLVMModuleAction : public ToolAction { | ||
// Code adapted from `FrontendActionFactory::runInvocation`. | ||
bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation, | ||
FileManager *Files, | ||
std::shared_ptr<PCHContainerOperations> PCHContainerOps, | ||
DiagnosticConsumer *DiagConsumer) override { | ||
assert(!Module && "Action should only be invoked on a single file"); | ||
|
||
// Create a compiler instance to handle the actual work. | ||
CompilerInstance Compiler(std::move(PCHContainerOps)); | ||
Compiler.setInvocation(std::move(Invocation)); | ||
Compiler.setFileManager(Files); | ||
|
||
// Create the compiler's actual diagnostics engine. | ||
Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false); | ||
if (!Compiler.hasDiagnostics()) { | ||
return false; | ||
} | ||
|
||
Compiler.createSourceManager(*Files); | ||
|
||
// Ignore `Compiler.getFrontendOpts().ProgramAction` (would be `EmitBC`) and | ||
// create/execute an `EmitLLVMOnlyAction` (= codegen to LLVM module without | ||
// emitting anything) instead. | ||
EmitLLVMOnlyAction ELOA; | ||
const bool Success = Compiler.ExecuteAction(ELOA); | ||
Files->clearStatCache(); | ||
if (!Success) { | ||
return false; | ||
} | ||
|
||
// Take the module and its context to extend the objects' lifetime. | ||
Module = ELOA.takeModule(); | ||
ELOA.takeLLVMContext(); | ||
|
||
return true; | ||
} | ||
|
||
std::unique_ptr<llvm::Module> Module; | ||
}; | ||
|
||
} // anonymous namespace | ||
|
||
llvm::Expected<std::unique_ptr<llvm::Module>> | ||
jit_compiler::compileDeviceCode(InMemoryFile SourceFile, | ||
View<InMemoryFile> IncludeFiles, | ||
View<const char *> UserArgs) { | ||
const std::string &DPCPPRoot = getDPCPPRoot(); | ||
if (DPCPPRoot == InvalidDPCPPRoot) { | ||
return llvm::createStringError("Could not locate DPCPP root directory"); | ||
} | ||
|
||
SmallVector<std::string> CommandLine = {"-fsycl-device-only"}; | ||
// TODO: Allow instrumentation again when device library linking is | ||
// implemented. | ||
CommandLine.push_back("-fno-sycl-instrument-device-code"); | ||
CommandLine.append(UserArgs.begin(), UserArgs.end()); | ||
clang::tooling::FixedCompilationDatabase DB{".", CommandLine}; | ||
|
||
clang::tooling::ClangTool Tool{DB, {SourceFile.Path}}; | ||
|
||
// Set up in-memory filesystem. | ||
Tool.mapVirtualFile(SourceFile.Path, SourceFile.Contents); | ||
for (const auto &IF : IncludeFiles) { | ||
Tool.mapVirtualFile(IF.Path, IF.Contents); | ||
} | ||
|
||
// Reset argument adjusters to drop the `-fsyntax-only` flag which is added by | ||
// default by this API. | ||
Tool.clearArgumentsAdjusters(); | ||
// Then, modify argv[0] and set the resource directory so that the driver | ||
// picks up the correct SYCL environment. | ||
Tool.appendArgumentsAdjuster( | ||
[&DPCPPRoot](const CommandLineArguments &Args, | ||
StringRef Filename) -> CommandLineArguments { | ||
(void)Filename; | ||
CommandLineArguments NewArgs = Args; | ||
NewArgs[0] = (Twine(DPCPPRoot) + "/bin/clang++").str(); | ||
NewArgs.push_back((Twine("-resource-dir=") + DPCPPRoot + "/lib/clang/" + | ||
Twine(CLANG_VERSION_MAJOR)) | ||
.str()); | ||
Comment on lines
+136
to
+138
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do these paths also apply in a packaged release? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, a release |
||
return NewArgs; | ||
}); | ||
|
||
GetLLVMModuleAction Action; | ||
if (!Tool.run(&Action)) { | ||
return std::move(Action.Module); | ||
} | ||
|
||
// TODO: Capture compiler errors from the ClangTool. | ||
return llvm::createStringError("Unable to obtain LLVM module"); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
//==---- DeviceCompilation.h - Compile SYCL device code with libtooling ----==// | ||
// | ||
// 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 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#ifndef SYCL_JIT_COMPILER_RTC_DEVICE_COMPILATION_H | ||
#define SYCL_JIT_COMPILER_RTC_DEVICE_COMPILATION_H | ||
|
||
#include "Kernel.h" | ||
#include "View.h" | ||
|
||
#include <llvm/Support/Error.h> | ||
|
||
#include <memory> | ||
|
||
namespace llvm { | ||
class Module; | ||
} // namespace llvm | ||
|
||
namespace jit_compiler { | ||
|
||
llvm::Expected<std::unique_ptr<llvm::Module>> | ||
compileDeviceCode(InMemoryFile SourceFile, View<InMemoryFile> IncludeFiles, | ||
View<const char *> UserArgs); | ||
|
||
} // namespace jit_compiler | ||
|
||
#endif // SYCL_JIT_COMPILER_RTC_DEVICE_COMPILATION_H |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -74,6 +74,14 @@ jit_compiler::jit_compiler() { | |
return false; | ||
} | ||
|
||
this->CompileSYCLHandle = reinterpret_cast<CompileSYCLFuncT>( | ||
sycl::detail::ur::getOsLibraryFuncAddress(LibraryPtr, "compileSYCL")); | ||
if (!this->CompileSYCLHandle) { | ||
printPerformanceWarning( | ||
"Cannot resolve JIT library function entry point"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sounds more serious than a mere performance warning :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
return false; | ||
} | ||
|
||
return true; | ||
}; | ||
Available = checkJITLibrary(); | ||
|
@@ -1143,6 +1151,52 @@ std::vector<uint8_t> jit_compiler::encodeReqdWorkGroupSize( | |
return Encoded; | ||
} | ||
|
||
std::vector<uint8_t> jit_compiler::compileSYCL( | ||
const std::string &Id, const std::string &SYCLSource, | ||
const std::vector<std::pair<std::string, std::string>> &IncludePairs, | ||
const std::vector<std::string> &UserArgs, std::string *LogPtr, | ||
const std::vector<std::string> &RegisteredKernelNames) { | ||
|
||
// TODO: Handle template instantiation. | ||
if (!RegisteredKernelNames.empty()) { | ||
throw sycl::exception( | ||
sycl::errc::build, | ||
"Property `sycl::ext::oneapi::experimental::registered_kernel_names` " | ||
"is not yet supported for the `sycl_jit` source language"); | ||
} | ||
|
||
std::string SYCLFileName = Id + ".cpp"; | ||
::jit_compiler::InMemoryFile SourceFile{SYCLFileName.c_str(), | ||
SYCLSource.c_str()}; | ||
|
||
std::vector<::jit_compiler::InMemoryFile> IncludeFilesView; | ||
IncludeFilesView.reserve(IncludePairs.size()); | ||
std::transform(IncludePairs.begin(), IncludePairs.end(), | ||
std::back_inserter(IncludeFilesView), [](const auto &Pair) { | ||
return ::jit_compiler::InMemoryFile{Pair.first.c_str(), | ||
Pair.second.c_str()}; | ||
}); | ||
std::vector<const char *> UserArgsView; | ||
UserArgsView.reserve(UserArgs.size()); | ||
std::transform(UserArgs.begin(), UserArgs.end(), | ||
std::back_inserter(UserArgsView), | ||
[](const auto &Arg) { return Arg.c_str(); }); | ||
|
||
auto Result = CompileSYCLHandle(SourceFile, IncludeFilesView, UserArgsView); | ||
|
||
if (Result.failed()) { | ||
throw sycl::exception(sycl::errc::build, Result.getErrorMessage()); | ||
} | ||
|
||
// TODO: We currently don't have a meaningful build log. | ||
(void)LogPtr; | ||
|
||
const auto &BI = Result.getKernelInfo().BinaryInfo; | ||
assert(BI.Format == ::jit_compiler::BinaryFormat::SPIRV); | ||
std::vector<uint8_t> SPV(BI.BinaryStart, BI.BinaryStart + BI.BinarySize); | ||
return SPV; | ||
} | ||
|
||
} // namespace detail | ||
} // namespace _V1 | ||
} // namespace sycl | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How expensive is it to set up and destroy the
LLVMContext
on every call to RTC? Would it be an alternative to use the context fromJITContext
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm not mistaken, certain things like constants and metadata are stored within
LLVMContext
and won't be deallocated even if a module which references them is deallocated. Therefore, keepingLLVMContext
between RTC call invocations could result in some memory build-up.At least that is the behavior we discovered a few years ago when we were debugging exceptionally huge memory footprint of
sycl-post-link
where we had a huge codebase compiled with per-kernel device code split. I don't know if anything has changed since that in the upstream LLVM, but we hadn't proposed any patches back then.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's certainly possible to pass an existing context into the ToolAction, but that also raises questions of thread safety.
For the performance implications, a) yes, looks like setting up a context does involve a non-trivial amount of work, and b) still seems true that types, constants and metadata are allocated in the context and not freed when the module is destroyed. I'd propose to keep the simple implementation for now, and will look out for the context setup overhead once we start benchmarking the RTC.