Changeset View
Changeset View
Standalone View
Standalone View
clang/tools/clang-offload-wrapper/ClangOffloadWrapper.cpp
Show All 11 Lines | |||||
/// packaged as data. Wrapper bitcode also includes initialization code which | /// packaged as data. Wrapper bitcode also includes initialization code which | ||||
/// registers target binaries in offloading runtime at program startup. | /// registers target binaries in offloading runtime at program startup. | ||||
/// | /// | ||||
//===----------------------------------------------------------------------===// | //===----------------------------------------------------------------------===// | ||||
#include "clang/Basic/Version.h" | #include "clang/Basic/Version.h" | ||||
#include "llvm/ADT/ArrayRef.h" | #include "llvm/ADT/ArrayRef.h" | ||||
#include "llvm/ADT/Triple.h" | #include "llvm/ADT/Triple.h" | ||||
#include "llvm/BinaryFormat/ELF.h" | |||||
#include "llvm/Bitcode/BitcodeWriter.h" | #include "llvm/Bitcode/BitcodeWriter.h" | ||||
#include "llvm/IR/Constants.h" | #include "llvm/IR/Constants.h" | ||||
#include "llvm/IR/GlobalVariable.h" | #include "llvm/IR/GlobalVariable.h" | ||||
#include "llvm/IR/IRBuilder.h" | #include "llvm/IR/IRBuilder.h" | ||||
#include "llvm/IR/LLVMContext.h" | #include "llvm/IR/LLVMContext.h" | ||||
#include "llvm/IR/Module.h" | #include "llvm/IR/Module.h" | ||||
#include "llvm/Object/ELFObjectFile.h" | |||||
#include "llvm/Object/ObjectFile.h" | |||||
#include "llvm/Support/CommandLine.h" | #include "llvm/Support/CommandLine.h" | ||||
#include "llvm/Support/EndianStream.h" | |||||
#include "llvm/Support/Errc.h" | #include "llvm/Support/Errc.h" | ||||
#include "llvm/Support/Error.h" | #include "llvm/Support/Error.h" | ||||
#include "llvm/Support/ErrorOr.h" | #include "llvm/Support/ErrorOr.h" | ||||
#include "llvm/Support/FileSystem.h" | #include "llvm/Support/FileSystem.h" | ||||
#include "llvm/Support/MemoryBuffer.h" | #include "llvm/Support/MemoryBuffer.h" | ||||
#include "llvm/Support/Path.h" | |||||
#include "llvm/Support/Program.h" | |||||
#include "llvm/Support/Signals.h" | #include "llvm/Support/Signals.h" | ||||
#include "llvm/Support/ToolOutputFile.h" | #include "llvm/Support/ToolOutputFile.h" | ||||
#include "llvm/Support/VCSRevision.h" | |||||
#include "llvm/Support/WithColor.h" | #include "llvm/Support/WithColor.h" | ||||
#include "llvm/Support/raw_ostream.h" | #include "llvm/Support/raw_ostream.h" | ||||
#include "llvm/Transforms/Utils/ModuleUtils.h" | #include "llvm/Transforms/Utils/ModuleUtils.h" | ||||
#include <cassert> | #include <cassert> | ||||
#include <cstdint> | #include <cstdint> | ||||
#define OPENMP_OFFLOAD_IMAGE_VERSION "1.0" | |||||
using namespace llvm; | using namespace llvm; | ||||
using namespace llvm::object; | |||||
static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden); | static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden); | ||||
// Mark all our options with this category, everything else (except for -version | // Mark all our options with this category, everything else (except for -version | ||||
// and -help) will be hidden. | // and -help) will be hidden. | ||||
static cl::OptionCategory | static cl::OptionCategory | ||||
ClangOffloadWrapperCategory("clang-offload-wrapper options"); | ClangOffloadWrapperCategory("clang-offload-wrapper options"); | ||||
static cl::opt<std::string> Output("o", cl::Required, | static cl::opt<std::string> Output("o", cl::Required, | ||||
cl::desc("Output filename"), | cl::desc("Output filename"), | ||||
cl::value_desc("filename"), | cl::value_desc("filename"), | ||||
cl::cat(ClangOffloadWrapperCategory)); | cl::cat(ClangOffloadWrapperCategory)); | ||||
static cl::list<std::string> Inputs(cl::Positional, cl::OneOrMore, | static cl::list<std::string> Inputs(cl::Positional, cl::OneOrMore, | ||||
cl::desc("<input files>"), | cl::desc("<input files>"), | ||||
cl::cat(ClangOffloadWrapperCategory)); | cl::cat(ClangOffloadWrapperCategory)); | ||||
static cl::opt<std::string> | static cl::opt<std::string> | ||||
Target("target", cl::Required, | Target("target", cl::Required, | ||||
cl::desc("Target triple for the output module"), | cl::desc("Target triple for the output module"), | ||||
cl::value_desc("triple"), cl::cat(ClangOffloadWrapperCategory)); | cl::value_desc("triple"), cl::cat(ClangOffloadWrapperCategory)); | ||||
static cl::opt<bool> SaveTemps( | |||||
"save-temps", | |||||
cl::desc("Save temporary files that may be produced by the tool. " | |||||
"This option forces print-out of the temporary files' names."), | |||||
cl::Hidden); | |||||
namespace { | namespace { | ||||
class BinaryWrapper { | class BinaryWrapper { | ||||
LLVMContext C; | LLVMContext C; | ||||
Module M; | Module M; | ||||
StructType *EntryTy = nullptr; | StructType *EntryTy = nullptr; | ||||
StructType *ImageTy = nullptr; | StructType *ImageTy = nullptr; | ||||
StructType *DescTy = nullptr; | StructType *DescTy = nullptr; | ||||
std::string ToolName; | |||||
std::string ObjcopyPath; | |||||
// Temporary file names that may be created during adding notes | |||||
// to ELF offload images. Use -save-temps to keep them and also | |||||
// see their names. A temporary file's name includes the name | |||||
// of the original input ELF image, so you can easily match | |||||
// them, if you have multiple inputs. | |||||
std::vector<std::string> TempFiles; | |||||
private: | private: | ||||
IntegerType *getSizeTTy() { | IntegerType *getSizeTTy() { | ||||
switch (M.getDataLayout().getPointerTypeSize(Type::getInt8PtrTy(C))) { | switch (M.getDataLayout().getPointerTypeSize(Type::getInt8PtrTy(C))) { | ||||
case 4u: | case 4u: | ||||
return Type::getInt32Ty(C); | return Type::getInt32Ty(C); | ||||
case 8u: | case 8u: | ||||
return Type::getInt64Ty(C); | return Type::getInt64Ty(C); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 208 Lines • ▼ Show 20 Lines | void createUnregisterFunction(GlobalVariable *BinDesc) { | ||||
Builder.CreateRetVoid(); | Builder.CreateRetVoid(); | ||||
// Add this function to global destructors. | // Add this function to global destructors. | ||||
// Match priority of __tgt_register_lib | // Match priority of __tgt_register_lib | ||||
appendToGlobalDtors(M, Func, /*Priority*/ 1); | appendToGlobalDtors(M, Func, /*Priority*/ 1); | ||||
} | } | ||||
public: | public: | ||||
BinaryWrapper(StringRef Target) : M("offload.wrapper.object", C) { | BinaryWrapper(StringRef Target, StringRef ToolName) | ||||
: M("offload.wrapper.object", C), ToolName(ToolName) { | |||||
M.setTargetTriple(Target); | M.setTargetTriple(Target); | ||||
// Look for llvm-objcopy in the same directory, from which | |||||
// clang-offload-wrapper is invoked. This helps OpenMP offload | |||||
// LIT tests. | |||||
// This just needs to be some symbol in the binary; C++ doesn't | |||||
// allow taking the address of ::main however. | |||||
void *P = (void *)(intptr_t)&Help; | |||||
std::string COWPath = sys::fs::getMainExecutable(ToolName.str().c_str(), P); | |||||
if (!COWPath.empty()) { | |||||
auto COWDir = sys::path::parent_path(COWPath); | |||||
ErrorOr<std::string> ObjcopyPathOrErr = | |||||
sys::findProgramByName("llvm-objcopy", {COWDir}); | |||||
if (ObjcopyPathOrErr) { | |||||
ObjcopyPath = *ObjcopyPathOrErr; | |||||
return; | |||||
} | |||||
// Otherwise, look through PATH environment. | |||||
} | |||||
ErrorOr<std::string> ObjcopyPathOrErr = | |||||
sys::findProgramByName("llvm-objcopy"); | |||||
if (!ObjcopyPathOrErr) { | |||||
WithColor::warning(errs(), ToolName) | |||||
<< "cannot find llvm-objcopy[.exe] in PATH; ELF notes cannot be " | |||||
"added.\n"; | |||||
return; | |||||
} | |||||
ObjcopyPath = *ObjcopyPathOrErr; | |||||
} | |||||
~BinaryWrapper() { | |||||
if (TempFiles.empty()) | |||||
return; | |||||
StringRef ToolNameRef(ToolName); | |||||
auto warningOS = [ToolNameRef]() -> raw_ostream & { | |||||
return WithColor::warning(errs(), ToolNameRef); | |||||
}; | |||||
for (auto &F : TempFiles) { | |||||
if (SaveTemps) { | |||||
warningOS() << "keeping temporary file " << F << "\n"; | |||||
continue; | |||||
} | |||||
auto EC = sys::fs::remove(F, false); | |||||
if (EC) | |||||
warningOS() << "cannot remove temporary file " << F << ": " | |||||
<< EC.message().c_str() << "\n"; | |||||
} | |||||
} | } | ||||
const Module &wrapBinaries(ArrayRef<ArrayRef<char>> Binaries) { | const Module &wrapBinaries(ArrayRef<ArrayRef<char>> Binaries) { | ||||
GlobalVariable *Desc = createBinDesc(Binaries); | GlobalVariable *Desc = createBinDesc(Binaries); | ||||
assert(Desc && "no binary descriptor"); | assert(Desc && "no binary descriptor"); | ||||
createRegisterFunction(Desc); | createRegisterFunction(Desc); | ||||
createUnregisterFunction(Desc); | createUnregisterFunction(Desc); | ||||
return M; | return M; | ||||
} | } | ||||
std::unique_ptr<MemoryBuffer> addELFNotes(std::unique_ptr<MemoryBuffer> Buf, | |||||
StringRef OriginalFileName) { | |||||
// Cannot add notes, if llvm-objcopy is not available. | |||||
// | |||||
// I did not find a clean way to add a new notes section into an existing | |||||
// ELF file. llvm-objcopy seems to recreate a new ELF from scratch, | |||||
// and we just try to use llvm-objcopy here. | |||||
if (ObjcopyPath.empty()) | |||||
return Buf; | |||||
StringRef ToolNameRef(ToolName); | |||||
// Helpers to emit warnings. | |||||
auto warningOS = [ToolNameRef]() -> raw_ostream & { | |||||
return WithColor::warning(errs(), ToolNameRef); | |||||
}; | |||||
auto handleErrorAsWarning = [&warningOS](Error E) { | |||||
logAllUnhandledErrors(std::move(E), warningOS()); | |||||
}; | |||||
Expected<std::unique_ptr<ObjectFile>> BinOrErr = | |||||
ObjectFile::createELFObjectFile(Buf->getMemBufferRef(), | |||||
/*InitContent=*/false); | |||||
if (Error E = BinOrErr.takeError()) { | |||||
consumeError(std::move(E)); | |||||
// This warning is questionable, but let it be here, | |||||
// assuming that most OpenMP offload models use ELF offload images. | |||||
warningOS() << OriginalFileName | |||||
<< " is not an ELF image, so notes cannot be added to it.\n"; | |||||
return Buf; | |||||
} | |||||
// If we fail to add the note section, we just pass through the original | |||||
// ELF image for wrapping. At some point we should enforce the note section | |||||
// and start emitting errors vs warnings. | |||||
support::endianness Endianness; | |||||
if (isa<ELF64LEObjectFile>(BinOrErr->get()) || | |||||
isa<ELF32LEObjectFile>(BinOrErr->get())) { | |||||
Endianness = support::little; | |||||
} else if (isa<ELF64BEObjectFile>(BinOrErr->get()) || | |||||
isa<ELF32BEObjectFile>(BinOrErr->get())) { | |||||
Endianness = support::big; | |||||
} else { | |||||
warningOS() << OriginalFileName | |||||
<< " is an ELF image of unrecognized format.\n"; | |||||
return Buf; | |||||
} | |||||
// Create temporary file for the data of a new SHT_NOTE section. | |||||
// We fill it in with data and then pass to llvm-objcopy invocation | |||||
// for reading. | |||||
Twine NotesFileModel = OriginalFileName + Twine(".elfnotes.%%%%%%%.tmp"); | |||||
Lint: Pre-merge checks: clang-tidy: warning: twine variables are prone to use-after-free bugs [llvm-twine-local]… | |||||
Expected<sys::fs::TempFile> NotesTemp = | |||||
sys::fs::TempFile::create(NotesFileModel); | |||||
if (Error E = NotesTemp.takeError()) { | |||||
handleErrorAsWarning(createFileError(NotesFileModel, std::move(E))); | |||||
return Buf; | |||||
} | |||||
TempFiles.push_back(NotesTemp->TmpName); | |||||
// Create temporary file for the updated ELF image. | |||||
// This is an empty file that we pass to llvm-objcopy invocation | |||||
// for writing. | |||||
Twine ELFFileModel = OriginalFileName + Twine(".elfwithnotes.%%%%%%%.tmp"); | |||||
Lint: Pre-merge checks clang-tidy: warning: twine variables are prone to use-after-free bugs [llvm-twine-local] Lint: Pre-merge checks: clang-tidy: warning: twine variables are prone to use-after-free bugs [llvm-twine-local]… | |||||
Expected<sys::fs::TempFile> ELFTemp = | |||||
sys::fs::TempFile::create(ELFFileModel); | |||||
if (Error E = ELFTemp.takeError()) { | |||||
handleErrorAsWarning(createFileError(ELFFileModel, std::move(E))); | |||||
return Buf; | |||||
} | |||||
TempFiles.push_back(ELFTemp->TmpName); | |||||
// Keep the new ELF image file to reserve the name for the future | |||||
// llvm-objcopy invocation. | |||||
std::string ELFTmpFileName = ELFTemp->TmpName; | |||||
if (Error E = ELFTemp->keep(ELFTmpFileName)) { | |||||
handleErrorAsWarning(createFileError(ELFTmpFileName, std::move(E))); | |||||
return Buf; | |||||
} | |||||
// Write notes to the *elfnotes*.tmp file. | |||||
raw_fd_ostream NotesOS(NotesTemp->FD, false); | |||||
struct NoteTy { | |||||
// Note name is a null-terminated "LLVMOMPOFFLOAD". | |||||
std::string Name; | |||||
// Note type defined in llvm/include/llvm/BinaryFormat/ELF.h. | |||||
uint32_t Type = 0; | |||||
// Each note has type-specific associated data. | |||||
std::string Desc; | |||||
NoteTy(std::string &&Name, uint32_t Type, std::string &&Desc) | |||||
: Name(std::move(Name)), Type(Type), Desc(std::move(Desc)) {} | |||||
}; | |||||
// So far we emit just three notes. | |||||
SmallVector<NoteTy, 3> Notes; | |||||
// Version of the offload image identifying the structure of the ELF image. | |||||
// Version 1.0 does not have any specific requirements. | |||||
// We may come up with some structure that has to be honored by all | |||||
// offload implementations in future (e.g. to let libomptarget | |||||
// get some information from the offload image). | |||||
Notes.emplace_back("LLVMOMPOFFLOAD", ELF::NT_LLVM_OPENMP_OFFLOAD_VERSION, | |||||
OPENMP_OFFLOAD_IMAGE_VERSION); | |||||
// This is a producer identification string. We are LLVM! | |||||
Notes.emplace_back("LLVMOMPOFFLOAD", ELF::NT_LLVM_OPENMP_OFFLOAD_PRODUCER, | |||||
"LLVM"); | |||||
// This is a producer version. Use the same format that is used | |||||
// by clang to report the LLVM version. | |||||
Notes.emplace_back("LLVMOMPOFFLOAD", | |||||
ELF::NT_LLVM_OPENMP_OFFLOAD_PRODUCER_VERSION, | |||||
LLVM_VERSION_STRING | |||||
#ifdef LLVM_REVISION | |||||
" " LLVM_REVISION | |||||
#endif | |||||
); | |||||
// Return the amount of padding required for a blob of N bytes | |||||
// to be aligned to Alignment bytes. | |||||
auto getPadAmount = [](uint32_t N, uint32_t Alignment) -> uint32_t { | |||||
uint32_t Mod = (N % Alignment); | |||||
if (Mod == 0) | |||||
return 0; | |||||
return Alignment - Mod; | |||||
}; | |||||
auto emitPadding = [&getPadAmount](raw_ostream &OS, uint32_t Size) { | |||||
for (uint32_t I = 0; I < getPadAmount(Size, 4); ++I) | |||||
OS << '\0'; | |||||
}; | |||||
// Put notes into the file. | |||||
for (auto &N : Notes) { | |||||
assert(!N.Name.empty() && "We should not create notes with empty names."); | |||||
// Name must be null-terminated. | |||||
if (N.Name.back() != '\0') | |||||
N.Name += '\0'; | |||||
uint32_t NameSz = N.Name.size(); | |||||
uint32_t DescSz = N.Desc.size(); | |||||
// A note starts with three 4-byte values: | |||||
// NameSz | |||||
// DescSz | |||||
// Type | |||||
// These three fields are endian-sensitive. | |||||
support::endian::write<uint32_t>(NotesOS, NameSz, Endianness); | |||||
support::endian::write<uint32_t>(NotesOS, DescSz, Endianness); | |||||
support::endian::write<uint32_t>(NotesOS, N.Type, Endianness); | |||||
// Next, we have a null-terminated Name padded to a 4-byte boundary. | |||||
NotesOS << N.Name; | |||||
emitPadding(NotesOS, NameSz); | |||||
if (DescSz == 0) | |||||
continue; | |||||
// Finally, we have a descriptor, which is an arbitrary flow of bytes. | |||||
NotesOS << N.Desc; | |||||
emitPadding(NotesOS, DescSz); | |||||
} | |||||
NotesOS.flush(); | |||||
// Keep the notes file. | |||||
std::string NotesTmpFileName = NotesTemp->TmpName; | |||||
if (Error E = NotesTemp->keep(NotesTmpFileName)) { | |||||
handleErrorAsWarning(createFileError(NotesTmpFileName, std::move(E))); | |||||
return Buf; | |||||
} | |||||
// Run llvm-objcopy like this: | |||||
// llvm-objcopy --add-section=.note.openmp=<notes-tmp-file-name> \ | |||||
// <orig-file-name> <elf-tmp-file-name> | |||||
// | |||||
// This will add a SHT_NOTE section on top of the original ELF. | |||||
std::vector<StringRef> Args; | |||||
Args.push_back(ObjcopyPath); | |||||
std::string Option("--add-section=.note.openmp=" + NotesTmpFileName); | |||||
Args.push_back(Option); | |||||
Args.push_back(OriginalFileName); | |||||
Args.push_back(ELFTmpFileName); | |||||
bool ExecutionFailed = false; | |||||
std::string ErrMsg; | |||||
(void)sys::ExecuteAndWait(ObjcopyPath, Args, | |||||
/*Env=*/llvm::None, /*Redirects=*/{}, | |||||
/*SecondsToWait=*/0, | |||||
/*MemoryLimit=*/0, &ErrMsg, &ExecutionFailed); | |||||
if (ExecutionFailed) { | |||||
warningOS() << ErrMsg << "\n"; | |||||
return Buf; | |||||
} | |||||
// Substitute the original ELF with new one. | |||||
ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr = | |||||
MemoryBuffer::getFile(ELFTmpFileName); | |||||
if (!BufOrErr) { | |||||
handleErrorAsWarning( | |||||
createFileError(ELFTmpFileName, BufOrErr.getError())); | |||||
return Buf; | |||||
} | |||||
return std::move(*BufOrErr); | |||||
} | |||||
}; | }; | ||||
} // anonymous namespace | } // anonymous namespace | ||||
int main(int argc, const char **argv) { | int main(int argc, const char **argv) { | ||||
sys::PrintStackTraceOnErrorSignal(argv[0]); | sys::PrintStackTraceOnErrorSignal(argv[0]); | ||||
cl::HideUnrelatedOptions(ClangOffloadWrapperCategory); | cl::HideUnrelatedOptions(ClangOffloadWrapperCategory); | ||||
Show All 17 Lines | int main(int argc, const char **argv) { | ||||
}; | }; | ||||
if (Triple(Target).getArch() == Triple::UnknownArch) { | if (Triple(Target).getArch() == Triple::UnknownArch) { | ||||
reportError(createStringError( | reportError(createStringError( | ||||
errc::invalid_argument, "'" + Target + "': unsupported target triple")); | errc::invalid_argument, "'" + Target + "': unsupported target triple")); | ||||
return 1; | return 1; | ||||
} | } | ||||
BinaryWrapper Wrapper(Target, argv[0]); | |||||
// Read device binaries. | // Read device binaries. | ||||
SmallVector<std::unique_ptr<MemoryBuffer>, 4u> Buffers; | SmallVector<std::unique_ptr<MemoryBuffer>, 4u> Buffers; | ||||
SmallVector<ArrayRef<char>, 4u> Images; | SmallVector<ArrayRef<char>, 4u> Images; | ||||
Buffers.reserve(Inputs.size()); | Buffers.reserve(Inputs.size()); | ||||
Images.reserve(Inputs.size()); | Images.reserve(Inputs.size()); | ||||
for (const std::string &File : Inputs) { | for (const std::string &File : Inputs) { | ||||
ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr = | ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr = | ||||
MemoryBuffer::getFileOrSTDIN(File); | MemoryBuffer::getFileOrSTDIN(File); | ||||
if (!BufOrErr) { | if (!BufOrErr) { | ||||
reportError(createFileError(File, BufOrErr.getError())); | reportError(createFileError(File, BufOrErr.getError())); | ||||
return 1; | return 1; | ||||
} | } | ||||
std::unique_ptr<MemoryBuffer> Buffer(std::move(*BufOrErr)); | |||||
if (File != "-") { | |||||
// Adding ELF notes for STDIN is not supported yet. | |||||
Buffer = Wrapper.addELFNotes(std::move(Buffer), File); | |||||
} | |||||
const std::unique_ptr<MemoryBuffer> &Buf = | const std::unique_ptr<MemoryBuffer> &Buf = | ||||
Buffers.emplace_back(std::move(*BufOrErr)); | Buffers.emplace_back(std::move(Buffer)); | ||||
Images.emplace_back(Buf->getBufferStart(), Buf->getBufferSize()); | Images.emplace_back(Buf->getBufferStart(), Buf->getBufferSize()); | ||||
} | } | ||||
// Create the output file to write the resulting bitcode to. | // Create the output file to write the resulting bitcode to. | ||||
std::error_code EC; | std::error_code EC; | ||||
ToolOutputFile Out(Output, EC, sys::fs::OF_None); | ToolOutputFile Out(Output, EC, sys::fs::OF_None); | ||||
if (EC) { | if (EC) { | ||||
reportError(createFileError(Output, EC)); | reportError(createFileError(Output, EC)); | ||||
return 1; | return 1; | ||||
} | } | ||||
// Create a wrapper for device binaries and write its bitcode to the file. | // Create a wrapper for device binaries and write its bitcode to the file. | ||||
WriteBitcodeToFile(BinaryWrapper(Target).wrapBinaries( | WriteBitcodeToFile( | ||||
makeArrayRef(Images.data(), Images.size())), | Wrapper.wrapBinaries(makeArrayRef(Images.data(), Images.size())), | ||||
Out.os()); | Out.os()); | ||||
if (Out.os().has_error()) { | if (Out.os().has_error()) { | ||||
reportError(createFileError(Output, Out.os().error())); | reportError(createFileError(Output, Out.os().error())); | ||||
return 1; | return 1; | ||||
} | } | ||||
// Success. | // Success. | ||||
Out.keep(); | Out.keep(); | ||||
return 0; | return 0; | ||||
} | } |
clang-tidy: warning: twine variables are prone to use-after-free bugs [llvm-twine-local]
not useful