blob: 42d8f1e8063438386446763dc394504d449863eb [file] [log] [blame]
//===-- llvm-goc.cpp - compiler driver for gollvm ------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// Compiler driver for gollvm. Invokes frontend / backend to compile
// Go code into assembly and/or object files, and orchestrates process
// of assembling and linking if needed.
//
//===----------------------------------------------------------------------===//
#include "go-llvm-linemap.h"
#include "go-llvm-diagnostics.h"
#include "go-llvm.h"
#include "go-c.h"
#include "mpfr.h"
#include "GollvmOptions.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/Triple.h"
#include "llvm/Analysis/TargetLibraryInfo.h"
#include "llvm/Bitcode/BitcodeWriterPass.h"
#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/DiagnosticPrinter.h"
#include "llvm/IR/IRPrintingPasses.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/IR/Verifier.h"
#include "llvm/MC/SubtargetFeature.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/OptTable.h"
#include "llvm/Option/Option.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/TargetRegistry.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/ToolOutputFile.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Target/TargetMachine.h"
#include "llvm/Transforms/IPO.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#include <algorithm>
#include <cstring>
#include <string>
#include <system_error>
#include <sys/types.h>
#include <unistd.h>
using namespace llvm;
static std::unique_ptr<ToolOutputFile>
GetOutputStream(std::string outFileName, bool binary)
{
// Open the file.
std::error_code EC;
sys::fs::OpenFlags OpenFlags = sys::fs::F_None;
if (!binary)
OpenFlags |= sys::fs::F_Text;
auto FDOut = llvm::make_unique<ToolOutputFile>(outFileName, EC,
OpenFlags);
if (EC) {
errs() << "error opening " << outFileName << ": "
<< EC.message() << '\n';
return nullptr;
}
return FDOut;
}
class BEDiagnosticHandler : public DiagnosticHandler {
bool *error_;
public:
BEDiagnosticHandler(bool *errorPtr)
: error_(errorPtr) {}
bool handleDiagnostics(const DiagnosticInfo &DI) override {
if (DI.getSeverity() == DS_Error)
*error_ = true;
if (auto *Remark = dyn_cast<DiagnosticInfoOptimizationBase>(&DI))
if (!Remark->isEnabled())
return true;
DiagnosticPrinterRawOStream DP(errs());
errs() << LLVMContext::getDiagnosticMessagePrefix(DI.getSeverity()) << ": ";
DI.print(DP);
errs() << "\n";
return true;
}
};
// Are we in "assemble" mode (all inputs are .s files) or "compile" mode
// (all inputs are *.go files)?
enum CompileMode {
UnknownCompileMode,
CompileAssemblyMode,
CompileGoMode
};
// Helper class for managing the overall Go compilation process.
class CompilationOrchestrator {
public:
CompilationOrchestrator(const char *argvZero);
~CompilationOrchestrator();
// Various stages in the setup/execution of the compilation. The
// convention here is to return false if there was a fatal error, true
// otherwise.
bool parseCommandLine(int argc, char **argv);
bool preamble();
bool initBridge();
bool resolveInputOutput();
bool invokeFrontEnd();
bool invokeBridge();
bool invokeBackEnd();
bool invokeAssembler();
// Exit code to return if there was an error in one of the steps above.
int errorReturnCode() const { return errorReturnCode_; }
private:
Triple triple_;
llvm::LLVMContext context_;
const char *progname_;
CodeGenOpt::Level cgolvl_;
unsigned olvl_;
int errorReturnCode_;
std::unique_ptr<opt::OptTable> opts_;
SmallVector<const char *, 256> argvsmv_;
SpecificBumpPtrAllocator<char> argallocator_;
opt::InputArgList args_;
std::unique_ptr<Llvm_backend> bridge_;
std::unique_ptr<TargetMachine> target_;
std::unique_ptr<Llvm_linemap> linemap_;
std::unique_ptr<llvm::Module> module_;
std::vector<std::string> inputFileNames_;
std::string outFileName_;
std::string asmOutFileName_;
std::unique_ptr<ToolOutputFile> asmout_;
std::unique_ptr<ToolOutputFile> out_;
std::unique_ptr<TargetLibraryInfoImpl> tlii_;
CompileMode compileMode_;
bool hasError_;
bool tmpCreated_;
CompileMode compileMode() const { return compileMode_; }
bool setCompileMode(CompileMode mode) {
bool mixedModeError = false;
if (compileMode_ == UnknownCompileMode)
compileMode_ = mode;
else if (compileMode_ != mode)
mixedModeError = true;
return mixedModeError;
}
bool phaseSuccessful() { errorReturnCode_++; return true; }
void createPasses(legacy::PassManager &MPM,
legacy::FunctionPassManager &FPM);
TargetMachine::CodeGenFileType getOutputFileType();
template<typename IT>
llvm::Optional<IT> getLastArgAsInteger(gollvm::options::ID id,
IT defaultValue);
PICLevel::Level getPicLevel();
llvm::Optional<llvm::Reloc::Model> reconcileRelocModel();
bool reconcileOptionPair(gollvm::options::ID yesOption,
gollvm::options::ID noOption,
bool defaultVal);
llvm::Optional<llvm::FPOpFusion::FPOpFusionMode> getFPOpFusionMode();
std::string firstFileBase();
};
CompilationOrchestrator::CompilationOrchestrator(const char *argvZero)
: progname_(argvZero), cgolvl_(CodeGenOpt::Default),
olvl_(2), errorReturnCode_(1),
opts_(gollvm::options::createGollvmDriverOptTable()),
compileMode_(UnknownCompileMode),
hasError_(false), tmpCreated_(false)
{
InitializeAllTargets();
InitializeAllTargetMCs();
InitializeAllAsmPrinters();
InitializeAllAsmParsers();
}
CompilationOrchestrator::~CompilationOrchestrator()
{
if (tmpCreated_)
sys::fs::remove(asmOutFileName_);
}
TargetMachine::CodeGenFileType CompilationOrchestrator::getOutputFileType()
{
TargetMachine::CodeGenFileType ft;
if (args_.hasArg(gollvm::options::OPT_S) ||
args_.hasArg(gollvm::options::OPT_emit_llvm))
ft = TargetMachine::CGFT_AssemblyFile;
else
ft = TargetMachine::CGFT_ObjectFile;
return ft;
}
template<typename IT>
llvm::Optional<IT>
CompilationOrchestrator::getLastArgAsInteger(gollvm::options::ID id,
IT defaultValue)
{
IT result = defaultValue;
opt::Arg *arg = args_.getLastArg(id);
if (arg != nullptr) {
if (llvm::StringRef(arg->getValue()).getAsInteger(10, result)) {
errs() << progname_ << ": invalid argument '"
<< arg->getValue() << "' to '"
<< arg->getAsString(args_) << "' option\n";
return None;
}
}
return result;
}
// Return any settings from the -fPIC/-fpic options, if present. The
// intent of the code below is to support "rightmost on the command
// line wins" (compatible with clang and other compilers), so if you
// specify "-fPIC -fpic" you get small PIC, whereas "-fPIC -fpic
// -fPIC" this will give you large PIC.
PICLevel::Level CompilationOrchestrator::getPicLevel()
{
opt::Arg *arg = args_.getLastArg(gollvm::options::OPT_fpic,
gollvm::options::OPT_fno_pic,
gollvm::options::OPT_fPIC,
gollvm::options::OPT_fno_PIC);
if (arg == nullptr)
return PICLevel::NotPIC;
if (arg->getOption().matches(gollvm::options::OPT_fpic))
return PICLevel::SmallPIC;
else if (arg->getOption().matches(gollvm::options::OPT_fPIC))
return PICLevel::BigPIC;
return PICLevel::NotPIC;
}
// Given a pair of llvm::opt options (presumably corresponding to
// -fXXX and -fno-XXX boolean flags), select the correct value for the
// option depending on the relative position of the options on the
// command line (rightmost wins). For example, given -fblah -fno-blah
// -fblah, we want the same semantics as a single -fblah.
bool
CompilationOrchestrator::reconcileOptionPair(gollvm::options::ID yesOption,
gollvm::options::ID noOption,
bool defaultVal)
{
opt::Arg *arg = args_.getLastArg(yesOption, noOption);
if (arg == nullptr)
return defaultVal;
if (arg->getOption().matches(yesOption))
return true;
assert(arg->getOption().matches(noOption));
return false;
}
llvm::Optional<llvm::Reloc::Model>
CompilationOrchestrator::reconcileRelocModel()
{
auto picLevel = getPicLevel();
if (picLevel != llvm::PICLevel::NotPIC) {
Reloc::Model R = Reloc::PIC_;
return R;
}
return None;
}
llvm::Optional<llvm::FPOpFusion::FPOpFusionMode>
CompilationOrchestrator::getFPOpFusionMode()
{
opt::Arg *arg = args_.getLastArg(gollvm::options::OPT_ffp_contract_EQ);
llvm::FPOpFusion::FPOpFusionMode res = FPOpFusion::Standard;
if (arg != nullptr) {
std::string val(arg->getValue());
if (val == "off")
res = FPOpFusion::Strict;
else if (val == "on")
res = FPOpFusion::Standard;
else if (val == "fast")
res = FPOpFusion::Fast;
else {
errs() << progname_ << ": invalid argument '"
<< arg->getValue() << "' to '"
<< arg->getAsString(args_) << "' option\n";
return None;
}
}
return res;
}
std::string CompilationOrchestrator::firstFileBase()
{
std::string firstFile = inputFileNames_[0];
size_t dotindex = firstFile.find_last_of(".");
assert(dotindex != std::string::npos);
return firstFile.substr(0, dotindex);
}
bool CompilationOrchestrator::parseCommandLine(int argc, char **argv)
{
unsigned missingArgIndex, missingArgCount;
ArrayRef<const char *> argvv = makeArrayRef(argv, argc);
this->args_ =
opts_->ParseArgs(argvv.slice(1), missingArgIndex, missingArgCount);
// Honor --help first
if (args_.hasArg(gollvm::options::OPT_help)) {
opts_->PrintHelp(errs(), progname_, "Gollvm (LLVM-based Go compiler)",
0, 0, false);
exit(0);
}
// Complain about missing arguments.
if (missingArgIndex != 0) {
errs() << "error: argument to '"
<< args_.getArgString(missingArgIndex)
<< "' option missing, expected "
<< missingArgCount << " value(s)\n";
return false;
}
// Check for unsupported options.
for (const opt::Arg *arg : args_) {
if (arg->getOption().hasFlag(gollvm::options::Unsupported)) {
errs() << "error: unsupported command line option '"
<< arg->getAsString(args_) << "'\n";
return false;
}
}
// Check for unknown options.
bool foundUnknown = false;
for (const opt::Arg *arg : args_.filtered(gollvm::options::OPT_UNKNOWN)) {
errs() << "error: unrecognized command line option '"
<< arg->getAsString(args_) << "'\n";
foundUnknown = true;
}
if (foundUnknown)
return false;
// Honor -mllvm
auto llvmargs = args_.getAllArgValues(gollvm::options::OPT_mllvm);
if (! llvmargs.empty()) {
unsigned nargs = llvmargs.size();
auto args = llvm::make_unique<const char*[]>(nargs + 2);
args[0] = "gollvm (LLVM option parsing)";
for (unsigned i = 0; i != nargs; ++i)
args[i + 1] = llvmargs[i].c_str();
args[nargs + 1] = nullptr;
llvm::cl::ParseCommandLineOptions(nargs + 1, args.get());
}
// Collect input file names
for (opt::Arg *arg : args_) {
if (arg->getOption().getKind() == opt::Option::InputClass) {
const char *val = arg->getValue();
inputFileNames_.push_back(val);
}
}
// Set triple.
if (const opt::Arg *arg = args_.getLastArg(gollvm::options::OPT_target_EQ))
triple_ = Triple(Triple::normalize(arg->getValue()));
else
triple_ = Triple(sys::getDefaultTargetTriple());
// Support -march
std::string archStr;
opt::Arg *archarg = args_.getLastArg(gollvm::options::OPT_march_EQ);
if (archarg != nullptr) {
std::string val(archarg->getValue());
if (val == "native")
archStr = sys::getHostCPUName();
else
archStr = archarg->getValue();
triple_.setArchName(archStr);
}
return phaseSuccessful();
}
bool CompilationOrchestrator::preamble()
{
// Get the target specific parser.
std::string Error;
const Target *TheTarget =
TargetRegistry::lookupTarget("", triple_, Error);
if (!TheTarget) {
errs() << progname_ << ": " << Error;
return false;
}
// optimization level
opt::Arg *oarg = args_.getLastArg(gollvm::options::OPT_O_Group);
if (oarg != nullptr) {
StringRef lev(oarg->getValue());
if (lev.size() != 1) {
errs() << progname_ << ": invalid argument to -O flag: "
<< lev << "\n";
return false;
}
switch (lev[0]) {
case '0':
olvl_ = 0;
cgolvl_ = CodeGenOpt::None;
break;
case '1':
olvl_ = 1;
cgolvl_ = CodeGenOpt::Less;
break;
case 's': // TODO: -Os same as -O for now.
case '2':
olvl_ = 2;
cgolvl_ = CodeGenOpt::Default;
break;
case '3':
olvl_ = 3;
cgolvl_ = CodeGenOpt::Aggressive;
break;
default:
errs() << progname_ << ": invalid optimization level.\n";
return false;
}
}
go_no_warn = args_.hasArg(gollvm::options::OPT_w);
TargetOptions Options;
// FIXME: turn off integrated assembler for now.
Options.DisableIntegratedAS = true;
// FIXME: this hard-wires on the equivalent of -ffunction-sections
// and -fdata-sections, since there doesn't seem to be a high-level
// hook for selecting a separate section for a specific variable or
// function (other than forcing it into a comdat, which is not
// always what we want).
Options.FunctionSections = true;
Options.DataSections = true;
Options.UniqueSectionNames = true;
// FIXME: this needs to be dependent on target triple
Options.EABIVersion = llvm::EABI::Default;
// init array use
Options.UseInitArray =
reconcileOptionPair(gollvm::options::OPT_fuse_init_array,
gollvm::options::OPT_fno_use_init_array,
true);
// FP trapping mode
Options.NoTrappingFPMath =
reconcileOptionPair(gollvm::options::OPT_ftrapping_math,
gollvm::options::OPT_fno_trapping_math,
true);
// The -fno-math-errno option is essentially a no-op when compiling
// Go code, but -fmath-errno does not make sense, since 'errno' is
// not exposed in any meaningful way as part of the math package.
// Allow users to set -fno-math-errno for compatibility reasons, but
// issue an error if -fmath-errno is set.
bool mathErrno = reconcileOptionPair(gollvm::options::OPT_fmath_errno,
gollvm::options::OPT_fno_math_errno,
false);
if (mathErrno) {
errs() << "error: -fmath-errno unsupported for Go code\n";
return false;
}
// FP contract settings.
auto dofuse = getFPOpFusionMode();
if (!dofuse)
return false;
Options.AllowFPOpFusion = *dofuse;
// Support -mcpu
std::string cpuStr;
opt::Arg *cpuarg = args_.getLastArg(gollvm::options::OPT_mcpu_EQ);
if (cpuarg != nullptr) {
std::string val(cpuarg->getValue());
if (val == "native")
cpuStr = sys::getHostCPUName();
else
cpuStr = cpuarg->getValue();
}
// Features
SubtargetFeatures features;
features.getDefaultSubtargetFeatures(triple_);
std::string featStr = features.getString();
// Create target machine
Optional<llvm::CodeModel::Model> CM = None;
target_.reset(
TheTarget->createTargetMachine(triple_.getTriple(), cpuStr, featStr,
Options, reconcileRelocModel(),
CM, cgolvl_));
assert(target_.get() && "Could not allocate target machine!");
return phaseSuccessful();
}
// This helper performs the various initial steps needed to set up the
// compilation, including prepping the LLVM context, creating an LLVM
// module, creating the bridge itself (Llvm_backend object) and
// setting up the Go frontend via a call to go_create_gogo(). At the
// end of this routine things should be ready to kick off the front end.
bool CompilationOrchestrator::initBridge()
{
// Set up the LLVM context
context_.setDiagnosticHandler(
llvm::make_unique<BEDiagnosticHandler>(&this->hasError_));
// Construct linemap and module
linemap_.reset(new Llvm_linemap());
module_.reset(new llvm::Module("gomodule", context_));
// Add the target data from the target machine, if it exists
module_->setTargetTriple(triple_.getTriple());
module_->setDataLayout(target_->createDataLayout());
module_->setPICLevel(getPicLevel());
// Now construct Llvm_backend helper.
bridge_.reset(new Llvm_backend(context_, module_.get(), linemap_.get()));
// Honor inline, tracelevel cmd line options
llvm::Optional<unsigned> tl =
getLastArgAsInteger(gollvm::options::OPT_tracelevel_EQ, 0u);
if (!tl)
return false;
bridge_->setTraceLevel(*tl);
bridge_->setNoInline(args_.hasArg(gollvm::options::OPT_fno_inline));
// Support -fgo-dump-ast
if (args_.hasArg(gollvm::options::OPT_fgo_dump_ast))
go_enable_dump("ast");
// Populate 'args' struct with various bits of information needed by
// the front end, then pass it to the front end via go_create_gogo().
struct go_create_gogo_args args;
unsigned bpi = target_->getPointerSize(0) * 8;
args.int_type_size = bpi;
args.pointer_size = bpi;
opt::Arg *pkpa = args_.getLastArg(gollvm::options::OPT_fgo_pkgpath_EQ);
args.pkgpath = (pkpa == nullptr ? NULL : pkpa->getValue());
opt::Arg *pkpp = args_.getLastArg(gollvm::options::OPT_fgo_prefix_EQ);
args.prefix = (pkpp == nullptr ? NULL : pkpp->getValue());
opt::Arg *relimp =
args_.getLastArg(gollvm::options::OPT_fgo_relative_import_path_EQ);
args.relative_import_path =
(relimp == nullptr ? NULL : relimp->getValue());
opt::Arg *chdr =
args_.getLastArg(gollvm::options::OPT_fgo_c_header_EQ);
args.c_header = (chdr == nullptr ? NULL : chdr->getValue());
args.check_divide_by_zero =
reconcileOptionPair(gollvm::options::OPT_fgo_check_divide_zero,
gollvm::options::OPT_fno_go_check_divide_zero,
true);
args.check_divide_overflow =
reconcileOptionPair(gollvm::options::OPT_fgo_check_divide_overflow,
gollvm::options::OPT_fno_go_check_divide_overflow,
true);
args.compiling_runtime =
args_.hasArg(gollvm::options::OPT_fgo_compiling_runtime);
llvm::Optional<int> del =
getLastArgAsInteger(gollvm::options::OPT_fgo_debug_escape_EQ, 0);
if (!del)
return false;
args.debug_escape_level = *del;
opt::Arg *hasharg =
args_.getLastArg(gollvm::options::OPT_fgo_debug_escape_hash_EQ);
args.debug_escape_hash = (hasharg != nullptr ? hasharg->getValue() : NULL);
args.nil_check_size_threshold = -1;
args.linemap = linemap_.get();
args.backend = bridge_.get();
go_create_gogo (&args);
/* The default precision for floating point numbers. This is used
for floating point constants with abstract type. This may
eventually be controllable by a command line option. */
mpfr_set_default_prec (256);
// Escape analysis
bool enableEscapeAnalysis =
reconcileOptionPair(gollvm::options::OPT_fgo_optimize_allocs,
gollvm::options::OPT_fno_go_optimize_allocs,
true);
go_enable_optimize("allocs", enableEscapeAnalysis ? 1 : 0);
// Include dirs
std::vector<std::string> incargs =
args_.getAllArgValues(gollvm::options::OPT_I);
for (auto dir : incargs) {
if (sys::fs::is_directory(dir))
go_add_search_path(dir.c_str());
}
// Library dirs
// TODO: add version, architecture dirs
std::vector<std::string> libargs =
args_.getAllArgValues(gollvm::options::OPT_L);
for (auto dir : libargs)
if (sys::fs::is_directory(dir))
go_add_search_path(dir.c_str());
return phaseSuccessful();
}
bool CompilationOrchestrator::resolveInputOutput()
{
// What 'mode' are we operating in? If all inputs are *.s files,
// then we're in assemble mode; if all inputs are *.go files, we're
// in compile mode. No support for a mix of *.s and *.go files.
bool mixedModeError = false;
if (inputFileNames_.size() == 0) {
errs() << "error: no input files supplied.\n";
return false;
}
for (auto &fn : inputFileNames_) {
// Check for existence of input file.
if (!sys::fs::exists(fn)) {
errs() << "error: input file '"
<< fn << "' does not exist\n";
return false;
}
size_t dotindex = fn.find_last_of(".");
if (dotindex == std::string::npos) {
errs() << "error: malformed input file '"
<< fn << "' (no extension)\n";
return false;
}
std::string extension = fn.substr(dotindex);
if (extension.compare(".s") == 0) {
mixedModeError = setCompileMode(CompileAssemblyMode);
if (mixedModeError)
break;
} else if (extension.compare(".go") == 0) {
mixedModeError = setCompileMode(CompileGoMode);
if (mixedModeError)
break;
} else {
errs() << "error: unknown input file '"
<< fn << "' (extension must be '.s' or '.go')\n";
return false;
}
}
if (mixedModeError) {
errs() << "error: mixing of assembler (*.s) and Go (*.go) "
<< "source files not supported.\n";
return false;
}
if (args_.hasArg(gollvm::options::OPT_S) &&
compileMode_ == CompileAssemblyMode) {
errs() << "error: -S option not valid if input files "
<< "are assembly source.\n";
return false;
}
// Decide where we're going to send the output for this compilation.
// If the "-o" option was not specified, use the basename of the
// first input argument.
TargetMachine::CodeGenFileType ft = getOutputFileType();
opt::Arg *outFileArg = args_.getLastArg(gollvm::options::OPT_o);
if (outFileArg) {
outFileName_ = outFileArg->getValue();
} else {
outFileName_ = firstFileBase();
if (ft == TargetMachine::CGFT_AssemblyFile) {
if (args_.hasArg(gollvm::options::OPT_emit_llvm)) {
if (args_.hasArg(gollvm::options::OPT_S)) {
outFileName_ += ".ll";
} else {
outFileName_ += ".bc";
}
} else {
outFileName_ += ".s";
}
} else {
outFileName_ += ".o";
}
}
// If we're not compiling to assembly, then we need an intermediate
// output file into which we'll emit assembly code.
if (ft != TargetMachine::CGFT_AssemblyFile) {
if (args_.hasArg(gollvm::options::OPT_save_temps)) {
asmOutFileName_ = firstFileBase();
asmOutFileName_ += ".s";
} else {
llvm::SmallString<128> tempFileName;
std::error_code tfcEC =
llvm::sys::fs::createTemporaryFile("asm", "s", tempFileName);
if (tfcEC) {
errs() << tfcEC.message() << "\n";
return false;
}
tmpCreated_ = true;
sys::RemoveFileOnSignal(tempFileName);
asmOutFileName_ = std::string(tempFileName.c_str());
}
} else {
asmOutFileName_ = outFileName_;
}
// Open the assembler output file
asmout_ = GetOutputStream(asmOutFileName_, /*text*/ false);
if (!asmout_)
return false;
// Open object file as well if needed
if (ft != TargetMachine::CGFT_AssemblyFile) {
out_ = GetOutputStream(outFileName_, /*binary*/ true);
if (!out_)
return false;
}
return phaseSuccessful();
}
bool CompilationOrchestrator::invokeFrontEnd()
{
// If we're in "assemble" mode, no need to invoke the compiler
if (compileMode_ == CompileAssemblyMode)
return true;
// Collect the input files and kick off the front end
// Kick off the front end
unsigned nfiles = inputFileNames_.size();
std::unique_ptr<const char *> filenames(new const char *[nfiles]);
const char **fns = filenames.get();
unsigned idx = 0;
for (auto &fn : inputFileNames_)
fns[idx++] = fn.c_str();
go_parse_input_files(fns, nfiles, false, true);
if (!args_.hasArg(gollvm::options::OPT_nobackend))
go_write_globals();
if (args_.hasArg(gollvm::options::OPT_dump_ir))
bridge_->dumpModule();
if (!args_.hasArg(gollvm::options::OPT_noverify) && !go_be_saw_errors())
bridge_->verifyModule();
llvm::Optional<unsigned> tl =
getLastArgAsInteger(gollvm::options::OPT_tracelevel_EQ, 0u);
if (*tl)
std::cerr << "linemap stats:" << linemap_->statistics() << "\n";
// Delete the bridge at this point. In the case that there were
// errors, this will help clean up any unreachable llvm Instructions
// (which would otherwise trigger asserts); in the non-error case it
// will help to free up bridge-related memory prior to kicking off
// the pass manager.
bridge_.reset(nullptr);
// Early exit at this point if we've seen errors
if (go_be_saw_errors())
return false;
return phaseSuccessful();
}
void CompilationOrchestrator::createPasses(legacy::PassManager &MPM,
legacy::FunctionPassManager &FPM)
{
if (args_.hasArg(gollvm::options::OPT_disable_llvm_passes))
return;
// FIXME: support LTO, ThinLTO, PGO
PassManagerBuilder pmb;
// Configure the inliner
if (args_.hasArg(gollvm::options::OPT_fno_inline) || olvl_ == 0) {
// Nothing here at the moment. There is go:noinline, but no equivalent
// of go:alwaysinline.
} else {
bool disableInlineHotCallSite = false; // for autofdo, not yet supported
pmb.Inliner =
createFunctionInliningPass(olvl_, 2, disableInlineHotCallSite);
}
pmb.OptLevel = olvl_;
pmb.SizeLevel = 0; // TODO: decide on right value here
pmb.PrepareForThinLTO = false;
pmb.PrepareForLTO = false;
FPM.add(new TargetLibraryInfoWrapperPass(*tlii_));
if (! args_.hasArg(gollvm::options::OPT_noverify))
FPM.add(createVerifierPass());
pmb.populateFunctionPassManager(FPM);
pmb.populateModulePassManager(MPM);
}
bool CompilationOrchestrator::invokeBackEnd()
{
// If we're in "assemble" mode, no need to invoke the compiler
if (compileMode_ == CompileAssemblyMode)
return true;
tlii_.reset(new TargetLibraryInfoImpl(triple_));
legacy::PassManager modulePasses;
legacy::FunctionPassManager functionPasses(module_.get());
// Set up module and function passes
if (!args_.hasArg(gollvm::options::OPT_disable_llvm_passes)) {
modulePasses.add(
createTargetTransformInfoWrapperPass(target_->getTargetIRAnalysis()));
functionPasses.add(
createTargetTransformInfoWrapperPass(target_->getTargetIRAnalysis()));
createPasses(modulePasses, functionPasses);
}
// Add passes to emit bitcode or LLVM IR as appropriate. Here we mimic
// clang behavior, which is to emit bitcode when "-emit-llvm" is specified
// but an LLVM IR dump of "-S -emit-llvm" is used.
raw_pwrite_stream *OS = &asmout_->os();
if (args_.hasArg(gollvm::options::OPT_emit_llvm)) {
bool bitcode = !args_.hasArg(gollvm::options::OPT_S);
bool preserveUseLists =
reconcileOptionPair(gollvm::options::OPT_emit_llvm_uselists,
gollvm::options::OPT_no_emit_llvm_uselists,
false);
modulePasses.add(bitcode ?
createBitcodeWriterPass(*OS, preserveUseLists) :
createPrintModulePass(*OS, "", preserveUseLists));
}
// Set up codegen passes
legacy::PassManager codeGenPasses;
if (!args_.hasArg(gollvm::options::OPT_disable_llvm_passes)) {
codeGenPasses.add(
createTargetTransformInfoWrapperPass(target_->getTargetIRAnalysis()));
// Codegen setup
codeGenPasses.add(new TargetLibraryInfoWrapperPass(*tlii_));
bool noverify = args_.hasArg(gollvm::options::OPT_noverify);
TargetMachine::CodeGenFileType ft = TargetMachine::CGFT_AssemblyFile;
if (target_->addPassesToEmitFile(codeGenPasses, *OS, ft,
/*DisableVerify=*/ noverify)) {
errs() << "error: unable to interface with target\n";
return false;
}
}
// Here we go... first function passes
functionPasses.doInitialization();
for (Function &F : *module_.get())
if (!F.isDeclaration())
functionPasses.run(F);
functionPasses.doFinalization();
// ... then module passes
modulePasses.run(*module_.get());
// ... and finally code generation
if (args_.hasArg(gollvm::options::OPT_disable_llvm_passes))
codeGenPasses.run(*module_.get());
if (hasError_)
return false;
// If -v is in effect, print something to show the effect of the
// compilation. This is in some sense a fiction, because the top
// level driver is not invoking a tool to perform the compile, but
// there is an expectation with compilers that if you take the
// "-v" output and then execute each command shown by hand, you'll
// get the same effect as the original command that produced the
// "-v" output.
if (args_.hasArg(gollvm::options::OPT_v)) {
errs() << progname_ << " -S";
for (auto arg : args_) {
if (arg->getOption().matches(gollvm::options::OPT_v) ||
arg->getOption().matches(gollvm::options::OPT_c) ||
arg->getOption().matches(gollvm::options::OPT_o) ||
arg->getOption().matches(gollvm::options::OPT_save_temps))
continue;
errs() << " " << arg->getAsString(args_);
}
errs() << " -o " << asmOutFileName_ << "\n";
}
// Keep the resulting output file if -S or -save-temps are in effect.
if (getOutputFileType() == TargetMachine::CGFT_AssemblyFile ||
args_.hasArg(gollvm::options::OPT_save_temps))
asmout_->keep();
return phaseSuccessful();
}
bool CompilationOrchestrator::invokeAssembler()
{
if (getOutputFileType() == TargetMachine::CGFT_AssemblyFile)
return true;
ArrayRef<StringRef> searchpaths;
auto aspath = sys::findProgramByName("as", searchpaths);
if (! aspath ) {
errs() << "error: unable to locate path for 'as'" << "\n";
return false;
}
// Note: ArgStringList is effectively a vector of "const char *".
opt::ArgStringList asmcmd;
asmcmd.push_back("as");
if (compileMode_ == CompileAssemblyMode) {
for (auto &fn : inputFileNames_)
asmcmd.push_back(fn.c_str());
} else {
asmcmd.push_back(asmOutFileName_.c_str());
}
asmcmd.push_back("-o");
asmcmd.push_back(outFileName_.c_str());
args_.AddAllArgValues(asmcmd,
gollvm::options::OPT_Wa_COMMA,
gollvm::options::OPT_Xassembler);
opt::Arg *gzarg = args_.getLastArg(gollvm::options::OPT_gz,
gollvm::options::OPT_gz_EQ);
std::string cds;
if (gzarg != nullptr) {
if (gzarg->getOption().matches(gollvm::options::OPT_gz)) {
asmcmd.push_back("-compress-debug-sections");
} else {
cds = "-compress-debug-sections=";
cds += gzarg->getValue();
asmcmd.push_back(cds.c_str());
}
}
asmcmd.push_back(nullptr);
if (args_.hasArg(gollvm::options::OPT_v)) {
bool first = true;
for (auto arg : asmcmd) {
errs() << (first ? "" : " ") << arg;
first = false;
}
errs() << "\n";
}
std::string errMsg;
bool rval = true;
int rc = sys::ExecuteAndWait(*aspath, asmcmd.data(),
/*env=*/nullptr, /*Redirects*/{},
/*secondsToWait=*/0,
/*memoryLimit=*/0, &errMsg);
if (rc != 0) {
errs() << errMsg << "\n";
rval = false;
} else {
out_->keep();
}
return (rval ? phaseSuccessful() : false);
}
int main(int argc, char **argv)
{
CompilationOrchestrator orchestrator(argv[0]);
// Print a stack trace if we signal out.
sys::PrintStackTraceOnErrorSignal(argv[0]);
PrettyStackTraceProgram X(argc, argv);
llvm_shutdown_obj Y; // Call llvm_shutdown() on exit.
// Parse command line.
if (!orchestrator.parseCommandLine(argc, argv))
return orchestrator.errorReturnCode();
// Initialize target and sort out selected command line options.
if (!orchestrator.preamble())
return orchestrator.errorReturnCode();
// Set up the bridge
if (!orchestrator.initBridge())
return orchestrator.errorReturnCode();
// Determine output file
if (!orchestrator.resolveInputOutput())
return orchestrator.errorReturnCode();
// Invoke front end
if (! orchestrator.invokeFrontEnd())
return orchestrator.errorReturnCode();
// Invoke back end
if (! orchestrator.invokeBackEnd())
return orchestrator.errorReturnCode();
// Invoke assembler if needed.
if (! orchestrator.invokeAssembler())
return orchestrator.errorReturnCode();
// We're done.
return 0;
}