-
Notifications
You must be signed in to change notification settings - Fork 0
Add clang-linker-wrapper changes to call clang-sycl-linker for SYCL offloads #6
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
base: master
Are you sure you want to change the base?
Changes from all commits
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 |
---|---|---|
|
@@ -464,7 +464,8 @@ fatbinary(ArrayRef<std::pair<StringRef, StringRef>> InputFiles, | |
} // namespace amdgcn | ||
|
||
namespace generic { | ||
Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, const ArgList &Args) { | ||
Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, const ArgList &Args, | ||
bool HasSYCLOffloadKind = false) { | ||
llvm::TimeTraceScope TimeScope("Clang"); | ||
// Use `clang` to invoke the appropriate device tools. | ||
Expected<std::string> ClangPath = | ||
|
@@ -554,6 +555,17 @@ Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, const ArgList &Args) { | |
if (Args.hasArg(OPT_embed_bitcode)) | ||
CmdArgs.push_back("-Wl,--lto-emit-llvm"); | ||
|
||
// For linking device code with the SYCL offload kind, special handling is | ||
// required. Passing --sycl-link to clang results in a call to | ||
// clang-sycl-linker. Additional linker flags required by clang-sycl-linker | ||
// will be communicated via the -Xlinker option. | ||
if (HasSYCLOffloadKind) { | ||
CmdArgs.push_back("--sycl-link"); | ||
CmdArgs.append( | ||
{"-Xlinker", Args.MakeArgString("-triple=" + Triple.getTriple())}); | ||
CmdArgs.append({"-Xlinker", Args.MakeArgString("-arch=" + Arch)}); | ||
} | ||
|
||
for (StringRef Arg : Args.getAllArgValues(OPT_linker_arg_EQ)) | ||
CmdArgs.append({"-Xlinker", Args.MakeArgString(Arg)}); | ||
for (StringRef Arg : Args.getAllArgValues(OPT_compiler_arg_EQ)) | ||
|
@@ -567,7 +579,8 @@ Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, const ArgList &Args) { | |
} // namespace generic | ||
|
||
Expected<StringRef> linkDevice(ArrayRef<StringRef> InputFiles, | ||
const ArgList &Args) { | ||
const ArgList &Args, | ||
bool HasSYCLOffloadKind = false) { | ||
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. is there any way we can avoid this? omp/hip/cuda seem to work without special handing here, and it would be idea if SYCL did as well 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. As it exists today, we need to call clang tool with --sycl-link option for sycl offloads (in order to call clang-sycl-linker that includes device code splitting and other SYCL specific stuff). omp/cuda/hip do not have this requirement. They do not require any special handling (or require any special options to be passed to clang) during the linking stage. clang-linker-wrapper is designed in such a way that the offload kind does not matter during the device code linking stage. Only the targets matter. Unfortunately, for SYCL offload, it is not clear how we can get rid of 'special handling' during the device code linking stage. We can eventually try to align SYCL device code linking flow with community flow. But we need to have a SYCL device code linking in the first place for that. Please let me know if I am missing something here. Thanks 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. discussed offline, makes sense for now 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. @sarnex, Added a detailed PR description as we discussed offline. Thanks |
||
const llvm::Triple Triple(Args.getLastArgValue(OPT_triple_EQ)); | ||
switch (Triple.getArch()) { | ||
case Triple::nvptx: | ||
|
@@ -582,7 +595,7 @@ Expected<StringRef> linkDevice(ArrayRef<StringRef> InputFiles, | |
case Triple::spirv64: | ||
case Triple::systemz: | ||
case Triple::loongarch64: | ||
return generic::clang(InputFiles, Args); | ||
return generic::clang(InputFiles, Args, HasSYCLOffloadKind); | ||
default: | ||
return createStringError(Triple.getArchName() + | ||
" linking is not supported"); | ||
|
@@ -924,9 +937,20 @@ Expected<SmallVector<StringRef>> linkAndWrapDeviceFiles( | |
auto LinkerArgs = getLinkerArgs(Input, BaseArgs); | ||
|
||
DenseSet<OffloadKind> ActiveOffloadKinds; | ||
for (const auto &File : Input) | ||
// Currently, SYCL device code linking process differs from generic device | ||
// code linking. | ||
// TODO: Remove check for offload kind, once SYCL device code linking is | ||
// aligned with generic linking. | ||
bool HasSYCLOffloadKind = false; | ||
bool HasNonSYCLOffloadKind = false; | ||
for (const auto &File : Input) { | ||
if (File.getBinary()->getOffloadKind() != OFK_None) | ||
ActiveOffloadKinds.insert(File.getBinary()->getOffloadKind()); | ||
if (File.getBinary()->getOffloadKind() == OFK_SYCL) | ||
HasSYCLOffloadKind = true; | ||
else | ||
HasNonSYCLOffloadKind = true; | ||
} | ||
|
||
// Write any remaining device inputs to an output file. | ||
SmallVector<StringRef> InputFiles; | ||
|
@@ -937,13 +961,47 @@ Expected<SmallVector<StringRef>> linkAndWrapDeviceFiles( | |
InputFiles.emplace_back(*FileNameOrErr); | ||
} | ||
|
||
if (HasSYCLOffloadKind) { | ||
// Link the remaining device files using the device linker. | ||
auto OutputOrErr = linkDevice(InputFiles, LinkerArgs, HasSYCLOffloadKind); | ||
if (!OutputOrErr) | ||
return OutputOrErr.takeError(); | ||
// Output is a packaged object of device images. Unpackage the images and | ||
// copy them to Images[Kind] | ||
ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr = | ||
MemoryBuffer::getFileOrSTDIN(*OutputOrErr); | ||
if (std::error_code EC = BufferOrErr.getError()) | ||
return createFileError(*OutputOrErr, EC); | ||
|
||
MemoryBufferRef Buffer = **BufferOrErr; | ||
SmallVector<OffloadFile> Binaries; | ||
if (Error Err = extractOffloadBinaries(Buffer, Binaries)) | ||
return std::move(Err); | ||
for (auto &OffloadFile : Binaries) { | ||
auto TheBinary = OffloadFile.getBinary(); | ||
OffloadingImage TheImage{}; | ||
TheImage.TheImageKind = TheBinary->getImageKind(); | ||
TheImage.TheOffloadKind = TheBinary->getOffloadKind(); | ||
TheImage.StringData["triple"] = TheBinary->getTriple(); | ||
TheImage.StringData["arch"] = TheBinary->getArch(); | ||
TheImage.Image = MemoryBuffer::getMemBufferCopy(TheBinary->getImage()); | ||
Images[OFK_SYCL].emplace_back(std::move(TheImage)); | ||
} | ||
} | ||
|
||
if (!HasNonSYCLOffloadKind) | ||
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. I went back and forth on if this is necessary or if we can just check the |
||
return Error::success(); | ||
|
||
// Link the remaining device files using the device linker. | ||
auto OutputOrErr = linkDevice(InputFiles, LinkerArgs); | ||
if (!OutputOrErr) | ||
return OutputOrErr.takeError(); | ||
|
||
// Store the offloading image for each linked output file. | ||
for (OffloadKind Kind : ActiveOffloadKinds) { | ||
// For SYCL, Offloading images were created inside clang-sycl-linker | ||
if (Kind == OFK_SYCL) | ||
continue; | ||
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> FileOrErr = | ||
llvm::MemoryBuffer::getFileOrSTDIN(*OutputOrErr); | ||
if (std::error_code EC = FileOrErr.getError()) { | ||
|
@@ -986,6 +1044,11 @@ Expected<SmallVector<StringRef>> linkAndWrapDeviceFiles( | |
A.StringData["arch"] > B.StringData["arch"] || | ||
A.TheOffloadKind < B.TheOffloadKind; | ||
}); | ||
if (Kind == OFK_SYCL) { | ||
// TODO: Update once SYCL offload wrapping logic is available. | ||
reportError( | ||
createStringError("SYCL offload wrapping logic is not available")); | ||
} | ||
auto BundledImagesOrErr = bundleLinkedOutput(Input, Args, Kind); | ||
if (!BundledImagesOrErr) | ||
return BundledImagesOrErr.takeError(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -70,6 +70,8 @@ static StringRef OutputFile; | |
/// Directory to dump SPIR-V IR if requested by user. | ||
static SmallString<128> SPIRVDumpDir; | ||
|
||
using OffloadingImage = OffloadBinary::OffloadingImage; | ||
|
||
static void printVersion(raw_ostream &OS) { | ||
OS << clang::getClangToolFullVersion("clang-sycl-linker") << '\n'; | ||
} | ||
|
@@ -168,10 +170,10 @@ Expected<SmallVector<std::string>> getInput(const ArgList &Args) { | |
/// are LLVM IR bitcode files. | ||
// TODO: Support SPIR-V IR files. | ||
Expected<std::unique_ptr<Module>> getBitcodeModule(StringRef File, | ||
LLVMContext &C) { | ||
LLVMContext &Ctx) { | ||
SMDiagnostic Err; | ||
|
||
auto M = getLazyIRFileModule(File, Err, C); | ||
auto M = getLazyIRFileModule(File, Err, Ctx); | ||
if (M) | ||
return std::move(M); | ||
return createStringError(Err.getMessage()); | ||
|
@@ -211,16 +213,16 @@ Expected<SmallVector<std::string>> getSYCLDeviceLibs(const ArgList &Args) { | |
/// 3. Link all the images gathered in Step 2 with the output of Step 1 using | ||
/// linkInModule API. LinkOnlyNeeded flag is used. | ||
Expected<StringRef> linkDeviceCode(ArrayRef<std::string> InputFiles, | ||
const ArgList &Args, LLVMContext &C) { | ||
const ArgList &Args, LLVMContext &Ctx) { | ||
llvm::TimeTraceScope TimeScope("SYCL link device code"); | ||
|
||
assert(InputFiles.size() && "No inputs to link"); | ||
|
||
auto LinkerOutput = std::make_unique<Module>("sycl-device-link", C); | ||
auto LinkerOutput = std::make_unique<Module>("sycl-device-link", Ctx); | ||
Linker L(*LinkerOutput); | ||
// Link SYCL device input files. | ||
for (auto &File : InputFiles) { | ||
auto ModOrErr = getBitcodeModule(File, C); | ||
auto ModOrErr = getBitcodeModule(File, Ctx); | ||
if (!ModOrErr) | ||
return ModOrErr.takeError(); | ||
if (L.linkInModule(std::move(*ModOrErr))) | ||
|
@@ -235,7 +237,7 @@ Expected<StringRef> linkDeviceCode(ArrayRef<std::string> InputFiles, | |
// Link in SYCL device library files. | ||
const llvm::Triple Triple(Args.getLastArgValue(OPT_triple_EQ)); | ||
for (auto &File : *SYCLDeviceLibFiles) { | ||
auto LibMod = getBitcodeModule(File, C); | ||
auto LibMod = getBitcodeModule(File, Ctx); | ||
if (!LibMod) | ||
return LibMod.takeError(); | ||
if ((*LibMod)->getTargetTriple() == Triple) { | ||
|
@@ -278,18 +280,18 @@ Expected<StringRef> linkDeviceCode(ArrayRef<std::string> InputFiles, | |
/// Converts 'File' from LLVM bitcode to SPIR-V format using SPIR-V backend. | ||
/// 'Args' encompasses all arguments required for linking device code and will | ||
/// be parsed to generate options required to be passed into the backend. | ||
static Expected<StringRef> runSPIRVCodeGen(StringRef File, const ArgList &Args, | ||
LLVMContext &C) { | ||
static Error runSPIRVCodeGen(StringRef File, const ArgList &Args, | ||
asudarsa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
StringRef OutputFile, LLVMContext &Ctx) { | ||
llvm::TimeTraceScope TimeScope("SPIR-V code generation"); | ||
|
||
// Parse input module. | ||
SMDiagnostic Err; | ||
std::unique_ptr<Module> M = parseIRFile(File, Err, C); | ||
SMDiagnostic E; | ||
std::unique_ptr<Module> M = parseIRFile(File, E, Ctx); | ||
if (!M) | ||
return createStringError(Err.getMessage()); | ||
return createStringError(E.getMessage()); | ||
|
||
if (Error Err = M->materializeAll()) | ||
return std::move(Err); | ||
return Err; | ||
|
||
Triple TargetTriple(Args.getLastArgValue(OPT_triple_EQ)); | ||
M->setTargetTriple(TargetTriple); | ||
|
@@ -333,7 +335,7 @@ static Expected<StringRef> runSPIRVCodeGen(StringRef File, const ArgList &Args, | |
errs() << formatv("SPIR-V Backend: input: {0}, output: {1}\n", File, | ||
OutputFile); | ||
|
||
return OutputFile; | ||
return Error::success(); | ||
} | ||
|
||
/// Performs the following steps: | ||
|
@@ -342,17 +344,61 @@ static Expected<StringRef> runSPIRVCodeGen(StringRef File, const ArgList &Args, | |
Error runSYCLLink(ArrayRef<std::string> Files, const ArgList &Args) { | ||
llvm::TimeTraceScope TimeScope("SYCL device linking"); | ||
|
||
LLVMContext C; | ||
LLVMContext Ctx; | ||
|
||
// Link all input bitcode files and SYCL device library files, if any. | ||
auto LinkedFile = linkDeviceCode(Files, Args, C); | ||
auto LinkedFile = linkDeviceCode(Files, Args, Ctx); | ||
if (!LinkedFile) | ||
reportError(LinkedFile.takeError()); | ||
|
||
// TODO: SYCL post link functionality involves device code splitting and will | ||
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. code splitting PR is under review here: llvm#131347 Thanks |
||
// result in multiple bitcode codes. | ||
// The following lines are placeholders to represent multiple files and will | ||
// be refactored once SYCL post link support is available. | ||
SmallVector<std::string> SplitModules; | ||
SplitModules.emplace_back(*LinkedFile); | ||
|
||
// SPIR-V code generation step. | ||
auto SPVFile = runSPIRVCodeGen(*LinkedFile, Args, C); | ||
if (!SPVFile) | ||
return SPVFile.takeError(); | ||
for (size_t I = 0, E = SplitModules.size(); I != E; ++I) { | ||
auto Stem = OutputFile.rsplit('.').first; | ||
std::string SPVFile(Stem); | ||
SPVFile.append("_" + utostr(I) + ".spv"); | ||
auto Err = runSPIRVCodeGen(SplitModules[I], Args, SPVFile, Ctx); | ||
if (Err) | ||
return std::move(Err); | ||
SplitModules[I] = SPVFile; | ||
} | ||
|
||
// Write the final output into file. | ||
int FD = -1; | ||
if (std::error_code EC = sys::fs::openFileForWrite(OutputFile, FD)) | ||
return errorCodeToError(EC); | ||
llvm::raw_fd_ostream FS(FD, /*shouldClose=*/true); | ||
|
||
for (size_t I = 0, E = SplitModules.size(); I != E; ++I) { | ||
auto File = SplitModules[I]; | ||
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> FileOrErr = | ||
llvm::MemoryBuffer::getFileOrSTDIN(File); | ||
if (std::error_code EC = FileOrErr.getError()) { | ||
if (DryRun) | ||
FileOrErr = MemoryBuffer::getMemBuffer(""); | ||
else | ||
return createFileError(File, EC); | ||
} | ||
OffloadingImage TheImage{}; | ||
TheImage.TheImageKind = IMG_Object; | ||
TheImage.TheOffloadKind = OFK_SYCL; | ||
TheImage.StringData["triple"] = | ||
Args.MakeArgString(Args.getLastArgValue(OPT_triple_EQ)); | ||
TheImage.StringData["arch"] = | ||
Args.MakeArgString(Args.getLastArgValue(OPT_arch_EQ)); | ||
TheImage.Image = std::move(*FileOrErr); | ||
|
||
llvm::SmallString<0> Buffer = OffloadBinary::write(TheImage); | ||
if (Buffer.size() % OffloadBinary::getAlignment() != 0) | ||
return createStringError("Offload binary has invalid size alignment"); | ||
FS << Buffer; | ||
} | ||
return Error::success(); | ||
} | ||
|
||
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. nit: remove whitespace changes |
||
|
@@ -394,7 +440,7 @@ int main(int argc, char **argv) { | |
DryRun = Args.hasArg(OPT_dry_run); | ||
SaveTemps = Args.hasArg(OPT_save_temps); | ||
|
||
OutputFile = "a.spv"; | ||
OutputFile = "a.out"; | ||
asudarsa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (Args.hasArg(OPT_o)) | ||
OutputFile = Args.getLastArgValue(OPT_o); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,7 @@ enum OffloadKind : uint16_t { | |
OFK_OpenMP, | ||
OFK_Cuda, | ||
OFK_HIP, | ||
OFK_SYCL, | ||
OFK_LAST, | ||
}; | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.