aboutsummaryrefslogtreecommitdiff
path: root/tools/driver/NativeCompiler.cc
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-16 07:54:06 +0000
committer_ov4 <ov4@ov4.org>2026-10-16 07:54:06 +0000
commit9226e87baacece10ea4971a9588d46e30cc23492 (patch)
treefe2729d2bf6c07366e71a6d39421ff05b239b713 /tools/driver/NativeCompiler.cc
parentc4565386553ea86308a9487650d2435804893ed0 (diff)
feat(driver): embed native compilation and link resources
Link LLVM, the Clang driver and LLD statically, and embed native link inputs so executable generation needs no installed development toolchain. Keep glibc dynamically linked on Linux. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat (limited to 'tools/driver/NativeCompiler.cc')
-rw-r--r--tools/driver/NativeCompiler.cc197
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;
+}