Executable codegen output for LLVM IR

Hi all,

I am very, very new to LLVM. I’ve been working for about one to two weeks on a little toy language and compiler in C++ using the Kaleidoscope tutorial as a general guide (w/ LLVM 15.0.7). It’s been great so far, and I think I am like 99% there in terms of something extremely basic that Just Works.

I can generate LLVM IR just fine. My issue is the next step. Chapter eight of the Kaleidoscope tutorial goes over generating object files (8. Kaleidoscope: Compiling to Object Code — LLVM 17.0.0git documentation), and copy pasting the code from there is basically all I needed to do save for some minor tweaks (ir::mod here is my llvm Module):

llvm::InitializeAllTargetInfos();
llvm::InitializeAllTargets();
llvm::InitializeAllTargetMCs();
llvm::InitializeAllAsmParsers();
llvm::InitializeAllAsmPrinters();

auto TargetTriple = llvm::sys::getDefaultTargetTriple();
ir::mod->setTargetTriple(TargetTriple);

std::string Error;
auto Target = llvm::TargetRegistry::lookupTarget(TargetTriple, Error);

if (!Target)
{
    llvm::errs() << Error;
    return 1;
}

auto CPU = "generic";
auto Features = "";

llvm::TargetOptions opt;
auto RM = llvm::Optional<llvm::Reloc::Model>();
auto TheTargetMachine =
    Target->createTargetMachine(TargetTriple, CPU, Features, opt, RM, llvm::None, llvm::CodeGenOpt::Aggressive);

ir::mod->setDataLayout(TheTargetMachine->createDataLayout());

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

if (EC)
{
    llvm::errs() << "Could not open file: " << EC.message();
    return 1;
}

llvm::legacy::PassManager pass;
auto FileType = llvm::CGFT_ObjectFile;

if (TheTargetMachine->addPassesToEmitFile(pass, dest, nullptr, FileType))
{
    llvm::errs() << "TheTargetMachine can't emit a file of this type";
    return 1;
}

pass.run(*ir::mod);
dest.flush();

llvm::outs() << "Wrote " << Filename << "\n";

This works fine, and running my compiler I can then compile the object file in a terminal with clang. However, I don’t want my user to deal with object files, I want my compiler to do all the linking behind the scenes.

Is there an API for doing effectively what clang++ out.o would accomplish? How do other compilers approach this issue?

Thanks in advance!

The simplest thing is just to make your compiler run system("clang++ out.o").

Other possibilities:

  • clang::driver::Driver is a public API, if you want to access the logic clang uses to invoke the linker more directly.
  • You can try to figure out how to invoke the linker yourself.

In any case, you end up invoking an external process.

I considered system("clang out.o") but I have two main issues with that approach:

  1. I don’t really have a way to control which clang a user has.
  2. The object file is still created and will exist in the directory. I could system("rm out.o") afterwards but this all just feels like a very ‘hacky’ solution.

Both of the other possibilities seem promising but I just don’t know where to get started learning how to implement them.
clang::driver::Driver seems like it’s maybe what I’m looking for but the documentation is daunting and I can’t find any guides or examples of it being used. Any help here would be immensely appreciated!

I’m also trying to find a way to implement that step without having to rely on a binary. Can’t seem to find anything at all online… @tsedan did you manage?

I would argue that it is the recommend way to invoke clang resp. cc to handle final steps. E.g. rust invokes cc for the final link step. gcc resp. clang know exactly the parameters it has to pass to the linker. It knows where standard c library is and more. And it works for a wide range of platforms without having to learn new flags.