diff options
Diffstat (limited to 'tools/driver/NativeCompiler.cc')
| -rw-r--r-- | tools/driver/NativeCompiler.cc | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/tools/driver/NativeCompiler.cc b/tools/driver/NativeCompiler.cc new file mode 100644 index 0000000..f2ea3dc --- /dev/null +++ b/tools/driver/NativeCompiler.cc @@ -0,0 +1,197 @@ +#include "NativeCompiler.h" +#include "cps/CodeGen.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Driver/Compilation.h" +#include "clang/Driver/Driver.h" +#include "clang/Driver/Job.h" +#include "lld/Common/Driver.h" +#include "llvm/IR/LegacyPassManager.h" +#include "llvm/InitializePasses.h" +#include "llvm/PassRegistry.h" +#include "llvm/MC/TargetRegistry.h" +#include "llvm/Support/Compression.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/FileUtilities.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Support/VirtualFileSystem.h" +#include "llvm/Target/TargetMachine.h" +#include "llvm/TargetParser/Triple.h" +#include "llvm/Transforms/Utils/Cloning.h" +#include <cstdlib> + +#ifdef _WIN32 +LLD_HAS_DRIVER(mingw) +LLD_HAS_DRIVER(coff) +#elif defined(__APPLE__) +LLD_HAS_DRIVER(macho) +#else +LLD_HAS_DRIVER(elf) +#endif + +namespace { + +#include "NativeRuntime.inc" + +struct TemporaryDirectory { + llvm::SmallString<256> Path; + ~TemporaryDirectory() { + if (!Path.empty()) llvm::sys::fs::remove_directories(Path); + } +}; + +bool fail(const llvm::Twine &Message) { + llvm::errs() << "cpsc: fatal error: " << Message << "\n"; + return false; +} + +class DriverDiagnostics : public clang::DiagnosticConsumer { + void HandleDiagnostic(clang::DiagnosticsEngine::Level Level, + const clang::Diagnostic &Info) override { + clang::DiagnosticConsumer::HandleDiagnostic(Level, Info); + llvm::SmallString<256> Message; + Info.FormatDiagnostic(Message); + llvm::errs() << "cpsc: " << (Level >= clang::DiagnosticsEngine::Error + ? "error: " : "warning: ") << Message << "\n"; + } +}; + +} // namespace + +bool compileNative(const cps::CodeGen &CG, llvm::StringRef OutputPath) { + TemporaryDirectory Runtime; + if (auto EC = llvm::sys::fs::createUniqueDirectory("cpsc-native", Runtime.Path)) + return fail("cannot create native compilation directory: " + EC.message()); + + auto Manifest = llvm::json::parse(NativeRuntimeManifest); + if (!Manifest) return fail(llvm::toString(Manifest.takeError())); + auto &Metadata = *Manifest->getAsObject(); + llvm::SmallVector<uint8_t, 0> Data; + if (auto Error = llvm::compression::zlib::decompress( + llvm::ArrayRef(reinterpret_cast<const uint8_t *>(NativeRuntimeData), + sizeof(NativeRuntimeData) - 1), Data, NativeRuntimeSize)) + return fail("cannot unpack native link resources: " + llvm::toString(std::move(Error))); + + auto relocate = [&](llvm::StringRef Text) { + std::string Result = Text.str(); + const std::string Marker = "@CPSC_RUNTIME@"; + size_t Position = 0; + while ((Position = Result.find(Marker, Position)) != std::string::npos) { + Result.replace(Position, Marker.size(), Runtime.Path.str()); + Position += Runtime.Path.size(); + } + return Result; + }; + for (const auto &Entry : *Metadata.getArray("files")) { + const auto &File = *Entry.getAsObject(); + llvm::SmallString<256> Path(Runtime.Path); + llvm::sys::path::append(Path, *File.getString("path")); + if (auto EC = llvm::sys::fs::create_directories(llvm::sys::path::parent_path(Path))) + return fail("cannot prepare native link resources: " + EC.message()); + std::error_code EC; + llvm::raw_fd_ostream Output(Path, EC); + if (EC) return fail("cannot open native link resource: " + EC.message()); + llvm::StringRef Contents(reinterpret_cast<const char *>(Data.data()) + + *File.getInteger("offset"), *File.getInteger("size")); + if (*File.getBoolean("script")) Output << relocate(Contents); + else Output << Contents; + Output.close(); + if (Output.has_error()) { + auto Error = Output.error(); + Output.clear_error(); + return fail("cannot write native link resource: " + Error.message()); + } + } + + if (llvm::InitializeNativeTarget() || llvm::InitializeNativeTargetAsmPrinter()) + return fail("cannot initialize the native LLVM target"); + auto &Registry = *llvm::PassRegistry::getPassRegistry(); + llvm::initializeCore(Registry); + llvm::initializeCodeGen(Registry); + llvm::initializeScalarOpts(Registry); + llvm::initializeVectorization(Registry); + llvm::initializeTransformUtils(Registry); + llvm::initializeInstCombine(Registry); + llvm::initializeTarget(Registry); + llvm::Triple Triple(*Metadata.getString("target")); + std::string Error; + const auto *Target = llvm::TargetRegistry::lookupTarget(Triple, Error); + if (!Target) return fail(Error); + std::unique_ptr<llvm::TargetMachine> Machine(Target->createTargetMachine( + Triple, "generic", "", llvm::TargetOptions(), llvm::Reloc::PIC_)); + if (!Machine) return fail("cannot create the native LLVM target machine"); + auto Module = llvm::CloneModule(CG.getModule()); + Module->setTargetTriple(Triple); + Module->setDataLayout(Machine->createDataLayout()); + llvm::SmallString<256> ObjectPath(Runtime.Path); + llvm::sys::path::append(ObjectPath, "program.o"); + std::error_code EC; + llvm::raw_fd_ostream ObjectOutput(ObjectPath, EC); + if (EC) return fail("cannot open native object file: " + EC.message()); + llvm::legacy::PassManager Passes; + if (Machine->addPassesToEmitFile(Passes, ObjectOutput, nullptr, + llvm::CodeGenFileType::ObjectFile)) + return fail("native target cannot emit object files"); + Passes.run(*Module); + ObjectOutput.close(); + if (ObjectOutput.has_error()) { + auto Error = ObjectOutput.error(); + ObjectOutput.clear_error(); + return fail("cannot write native object file: " + Error.message()); + } + + // Link beside the destination so the final rename stays on one filesystem. + llvm::SmallString<256> StagedPath; + int FD; + if (auto EC = llvm::sys::fs::createUniqueFile(OutputPath + ".tmp-%%%%%%", FD, StagedPath)) + return fail("cannot open native output file: " + EC.message()); + llvm::FileRemover StagedOutput(StagedPath); + llvm::raw_fd_ostream CloseFD(FD, true); + CloseFD.close(); + + // All link inputs are explicit. Clang's driver provides job validation and + // platform command construction; no compiler subprocess is executed. + clang::DiagnosticOptions Options; + DriverDiagnostics Printer; + clang::DiagnosticsEngine Diagnostics(new clang::DiagnosticIDs, Options, &Printer, false); + clang::driver::Driver Driver("cpsc", Triple.str(), Diagnostics); + // The standard linker flavor constructs a job without checking for an + // installed linker. Its platform arguments are passed to embedded LLD. + std::vector<std::string> Args = {"cpsc", "--driver-mode=g++", "-nostdlib", "-nostdlib++", + "-fuse-ld=ld", "-o", std::string(StagedPath), std::string(ObjectPath)}; + for (const auto &Arg : *Metadata.getArray("arguments")) { + Args.push_back("-Xlinker"); + Args.push_back(relocate(*Arg.getAsString())); + } + std::vector<const char *> Argv; + for (const auto &Arg : Args) Argv.push_back(Arg.c_str()); + Driver.setCheckInputsExist(false); + std::unique_ptr<clang::driver::Compilation> Compilation(Driver.BuildCompilation(Argv)); + if (!Compilation || Diagnostics.hasErrorOccurred()) return false; + if (Compilation->getJobs().size() != 1) + return fail("expected one embedded linker job"); + const auto &Job = *Compilation->getJobs().begin(); + llvm::SmallVector<const char *, 64> LinkArgs; +#ifdef _WIN32 + LinkArgs.push_back("ld.lld"); + const lld::DriverDef Linkers[] = {{lld::MinGW, &lld::mingw::link}, {lld::WinLink, &lld::coff::link}}; +#elif defined(__APPLE__) + LinkArgs.push_back("ld64.lld"); + const lld::DriverDef Linkers[] = {{lld::Darwin, &lld::macho::link}}; +#else + LinkArgs.push_back("ld.lld"); + const lld::DriverDef Linkers[] = {{lld::Gnu, &lld::elf::link}}; +#endif + LinkArgs.append(Job.getArguments().begin(), Job.getArguments().end()); + auto Result = lld::lldMain(LinkArgs, llvm::outs(), llvm::errs(), Linkers); + if (!Result.canRunAgain) std::exit(1); + if (Result.retCode != 0) return fail("native linking failed"); + if (auto EC = llvm::sys::fs::setPermissions(StagedPath, + llvm::sys::fs::all_read | llvm::sys::fs::owner_write | llvm::sys::fs::all_exe)) + return fail("cannot set executable permissions: " + EC.message()); + if (auto EC = llvm::sys::fs::rename(StagedPath, OutputPath)) + return fail("cannot save native output: " + EC.message()); + return true; +} |
