Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to generate an executable from an llvm::Module?

Tags:

c++

llvm

clang

I want to write a C++ function that takes an llvm::Module, which is already linked, and output it to an executable file. It should use the llvm/clang API rather than forking a process and invoking the command-line clang.

After looking through the llvm/clang source code, the closest thing I've found is to output a Module to a .o file. For example, the llc tool (tools/llc/llc.cpp) accomplishes this by calling TargetMachine::addPassesToEmitFile(...).

An alternative would be to write the Module to a .bc file, then create a CompilerInstance and call ExecuteCompilerInvocation (as in tools/clang/tools/driver/cc1_main.cpp). But then there's the overhead of file I/O.

So, am I asking for something possible, or must I fall back on the alternative?

Edit: (Of course this is possible. It happens somewhere in the clang source code, I just can't find it.)

like image 300
jlstrecker Avatar asked Jul 25 '12 19:07

jlstrecker


2 Answers

Not possible! clang does not create the executable itself. It invokes ld.

Found it in tools/clang/lib/Driver/Tools.cpp. In the ConstructJob functions for the various platforms (darwin::Link::ConstructJob, solaris::Link::ConstructJob, etc.), it does this:

const char *Exec =
    Args.MakeArgString(getToolChain().GetProgramPath("ld"));
  C.addCommand(new Command(JA, *this, Exec, CmdArgs));

(For visualstudio::Link::ConstructJob, it instead invokes link.exe.)

Edit: In retrospect, it would have been faster to find this out by tracing clang's system calls with dtruss (Mac) or strace (Linux).

Edit: I ended up using the Clang driver API for building and linking. I wrote up some example code.

like image 131
jlstrecker Avatar answered Nov 19 '22 10:11

jlstrecker


Here's my compile function, which compiles the Module to an object file and then invokes the Clang++ compilation API to generate an executable.

I don't know if it correctly answers your question, as the object file exists momentarily and is just deleted afterwards (last line) An optimization is of course to do it all in-memory. The constructor of clang::driver::Driver does have a "Virtual File System" optional argument, which can probably be used.

#include "clang/Driver/Driver.h"
#include "clang/Driver/Compilation.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "llvm/IR/Module.h"
#include "llvm/Target/TargetOptions.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/TargetRegistry.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Support/Host.h"
#include "llvm/Target/TargetMachine.h"

using namespace llvm;
using namespace llvm::sys;


void writeModuleToFile(Module *module)
{
    auto TargetTriple = getDefaultTargetTriple();
    InitializeAllTargetInfos();
    InitializeAllTargets();
    InitializeAllTargetMCs();
    InitializeAllAsmParsers();
    InitializeAllAsmPrinters();

    std::string Error;
    auto Target = TargetRegistry::lookupTarget(TargetTriple, Error);
    auto CPU = "generic";
    auto Features = "";

    TargetOptions opt;
    auto RM = Optional<Reloc::Model>();
    auto TargetMachine = Target->createTargetMachine(TargetTriple, CPU, Features, opt, RM);

    module->setDataLayout(TargetMachine->createDataLayout());
    module->setTargetTriple(TargetTriple);

    auto Filename = "output.o";
    std::error_code EC;
    raw_fd_ostream dest(Filename, EC, sys::fs::OF_None);

    legacy::PassManager pass;
    auto FileType = CGFT_ObjectFile;

    if (TargetMachine->addPassesToEmitFile(pass, dest, nullptr, FileType))
    {
        errs() << "TargetMachine can't emit a file of this type";
        return;
    }
    pass.run(*module);
    dest.flush();

    IntrusiveRefCntPtr<clang::DiagnosticOptions> DiagOpts = new clang::DiagnosticOptions;
    clang::TextDiagnosticPrinter *DiagClient = new clang::TextDiagnosticPrinter(errs(), &*DiagOpts);
    IntrusiveRefCntPtr<clang::DiagnosticIDs> DiagID(new clang::DiagnosticIDs());
    clang::DiagnosticsEngine Diags(DiagID, &*DiagOpts, DiagClient);
    clang::driver::Driver TheDriver("/usr/bin/clang++-12", TargetTriple, Diags);

    auto args = ArrayRef<const char *>{"-g", "output.o", "-o", "main"};

    std::unique_ptr<clang::driver::Compilation> C(TheDriver.BuildCompilation(args));

    if (C && !C->containsError())
    {
        SmallVector<std::pair<int, const clang::driver::Command *>, 4> FailingCommands;
        TheDriver.ExecuteCompilation(*C, FailingCommands);
    }

    remove(Filename);
}
like image 1
Jeppe Avatar answered Nov 19 '22 08:11

Jeppe