blob: c69f3c906ffe7bcffa5f8d601639e3c00c83f13a [file] [log] [blame]
//===-- Driver.cpp --------------------------------------------------------===//
//
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
//===----------------------------------------------------------------------===//
//
// Gollvm driver helper class Driver methods.
//
//===----------------------------------------------------------------------===//
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Host.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Program.h"
#include "Action.h"
#include "Compilation.h"
#include "Driver.h"
#include "LinuxToolChain.h"
#include "ToolChain.h"
#include "GollvmConfig.h"
using namespace llvm;
namespace gollvm {
namespace driver {
Driver::Driver(opt::InputArgList &args,
opt::OptTable *optTable,
const char *argv0,
bool using_splitstack)
: args_(args),
opts_(optTable),
progname_(argv0),
usingSplitStack_(using_splitstack)
{
if (const opt::Arg *arg = args.getLastArg(gollvm::options::OPT_sysroot_EQ))
sysroot_ = arg->getValue();
if (const opt::Arg *arg = args.getLastArg(gollvm::options::OPT_gcc_toolchain_EQ))
gccToolchainDir_ = arg->getValue();
// Establish executable path and installation dir.
executablePath_ = argv0;
// Do a PATH lookup if argv0 is not a valid path.
if (!llvm::sys::fs::exists(executablePath_)) {
if (llvm::ErrorOr<std::string> path =
llvm::sys::findProgramByName(executablePath_))
executablePath_ = *path;
}
SmallString<128> abspath(executablePath_);
llvm::sys::fs::make_absolute(abspath);
installDir_ = llvm::sys::path::parent_path(abspath).str();
prefixes_ = args.getAllArgValues(gollvm::options::OPT_B);
}
Driver::~Driver()
{
}
std::string Driver::installedLibDir()
{
llvm::SmallString<256> ldir(installDir_);
const llvm::Triple::ArchType arch = triple().getArch();
switch (arch) {
// multilib is not supported on major aarch64/arm64 linux distributions
// (subject to change when more scenarios to be taken into account)
// NOTE: set aarch64's lib dir to "lib64" temporarily, until necessary
// change is made to cmake module files
case llvm::Triple::aarch64:
llvm::sys::path::append(ldir, "../lib64");
break;
default:
llvm::sys::path::append(ldir, "../lib64");
break;
}
return std::string(ldir.str());
}
// TODO: create a mechanism for capturing release tag/branch, and/or
// git/svn revision for LLVM, gollvm, and so on.
void Driver::emitVersion()
{
// NB: the go build tool keys off the presence of the "experimental"
// keyword (hashes compiler binary if detected).
llvm::errs() << "gollvm version 1 (experimental) [LLVM version "
<< GOLLVM_COMPILERVERSION << "]\n";
}
std::string Driver::getFilePath(llvm::StringRef name,
ToolChain &toolchain)
{
// Include -Bprefixed name in search.
SmallVector<std::string, 2> candidates;
for (auto p : prefixes_)
candidates.push_back((p + name).str());
for (auto &cand : candidates) {
if (llvm::sys::fs::exists(llvm::Twine(cand)))
return cand;
}
// Examine install dir
llvm::SmallString<256> installed(installedLibDir());
llvm::sys::path::append(installed, name);
if (llvm::sys::fs::exists(llvm::Twine(installed)))
return std::string(installed);
// Examine toolchain file paths.
for (const auto &dir : toolchain.filePaths()) {
llvm::SmallString<256> candidate(dir);
llvm::sys::path::append(candidate, name);
if (llvm::sys::fs::exists(llvm::Twine(candidate)))
return std::string(candidate);
}
return name.str();
}
std::string Driver::getProgramPath(llvm::StringRef name,
ToolChain &toolchain)
{
// Include -Bprefixed and target-prefixed name in search.
SmallVector<std::string, 3> candidates;
for (auto p : prefixes_)
candidates.push_back((p + name).str());
candidates.push_back((triple_.str() + "-" + name).str());
candidates.push_back(name.str());
// Examine toolchain program paths.
for (auto &dir : toolchain.programPaths()) {
for (auto &cand : candidates) {
llvm::SmallString<256> candidate(dir);
llvm::sys::path::append(candidate, cand);
if (llvm::sys::fs::can_execute(llvm::Twine(candidate)))
return std::string(candidate);
}
}
// Search path.
for (auto &cand : candidates) {
llvm::ErrorOr<std::string> pcand =
llvm::sys::findProgramByName(cand);
if (pcand)
return *pcand;
}
return name.str();
}
// FIXME: some platforms have PIE enabled by default; we don't
// yet support auto-detection of such platforms.
bool Driver::isPIE()
{
// Treat these options as trumping -pie.
// FIXME: also handle -r here when supported
if (args_.hasArg(gollvm::options::OPT_shared) ||
args_.hasArg(gollvm::options::OPT_static))
return false;
opt::Arg *arg = args_.getLastArg(gollvm::options::OPT_pie,
gollvm::options::OPT_no_pie,
gollvm::options::OPT_nopie);
return (arg ? arg->getOption().matches(options::OPT_pie) : false);
}
// Return any settings from the -fPIC/-fpic/-fPIE/-fpie 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" will give you large PIC.
// Similarly the presence of a "-fno-pic" to the right of "-fPIE"
// will disable use of the PIE code model.
PICLevel::Level Driver::getPicLevel()
{
opt::Arg *arg = args_.getLastArg(gollvm::options::OPT_fpic,
gollvm::options::OPT_fno_pic,
gollvm::options::OPT_fPIC,
gollvm::options::OPT_fno_PIC,
gollvm::options::OPT_fpie,
gollvm::options::OPT_fno_pie,
gollvm::options::OPT_fPIE,
gollvm::options::OPT_fno_PIE);
if (arg == nullptr)
return PICLevel::NotPIC;
if (arg->getOption().matches(gollvm::options::OPT_fpic) ||
arg->getOption().matches(gollvm::options::OPT_fpie))
return PICLevel::SmallPIC;
else if (arg->getOption().matches(gollvm::options::OPT_fPIC) ||
arg->getOption().matches(gollvm::options::OPT_fPIE))
return PICLevel::BigPIC;
return PICLevel::NotPIC;
}
// Similar to the routine above, but for -fPIE/-fpie etc.
PIELevel::Level Driver::getPieLevel()
{
opt::Arg *arg = args_.getLastArg(gollvm::options::OPT_fpie,
gollvm::options::OPT_fno_pie,
gollvm::options::OPT_fPIE,
gollvm::options::OPT_fno_PIE);
if (arg == nullptr)
return PIELevel::Default;
if (arg->getOption().matches(gollvm::options::OPT_fpie))
return PIELevel::Small;
else if (arg->getOption().matches(gollvm::options::OPT_fPIE))
return PIELevel::Large;
return PIELevel::Default;
}
// Returns TRUE if the rightmost enable -fpic/-fpie command line option is
// PIE as opposed to PIC.
bool Driver::picIsPIE()
{
opt::Arg *lpa = args_.getLastArg(gollvm::options::OPT_fPIC,
gollvm::options::OPT_fno_PIC,
gollvm::options::OPT_fpic,
gollvm::options::OPT_fno_pic,
gollvm::options::OPT_fPIE,
gollvm::options::OPT_fno_PIE,
gollvm::options::OPT_fpie,
gollvm::options::OPT_fno_pie);
if (!lpa)
return false;
opt::Option opt = lpa->getOption();
return (opt.matches(gollvm::options::OPT_fPIE) ||
opt.matches(gollvm::options::OPT_fpie));
}
// 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
Driver::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;
}
Optional<Reloc::Model>
Driver::reconcileRelocModel()
{
auto picLevel = getPicLevel();
if (picLevel != PICLevel::NotPIC) {
Reloc::Model R = Reloc::PIC_;
return R;
}
return None;
}
Optional<FPOpFusion::FPOpFusionMode>
Driver::getFPOpFusionMode()
{
opt::Arg *arg = args_.getLastArg(gollvm::options::OPT_ffp_contract_EQ);
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::unique_ptr<Compilation> Driver::buildCompilation(ToolChain &tc)
{
return std::unique_ptr<Compilation>(new Compilation(*this, tc));
}
ToolChain *Driver::setup()
{
bool inputseen = false;
if (args_.hasArg(gollvm::options::OPT_v) ||
args_.hasArg(gollvm::options::OPT__HASH_HASH_HASH))
emitVersion();
// 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());
// Honor -dumpmachine
if (args_.hasArg(gollvm::options::OPT_dumpmachine)) {
llvm::outs() << triple_.str() << "\n";
exit(0);
}
// Look up toolchain.
auto &tc = toolchains_[triple_.str()];
if (!tc) {
switch (triple_.getOS()) {
case Triple::Linux:
tc = std::make_unique<toolchains::Linux>(*this, triple_);
break;
default:
errs() << progname_ << ": error: unsupported target "
<< triple_.str() << ", unable to create toolchain\n";
return nullptr;
}
}
// Honor -print-file-name=...
opt::Arg *pfnarg = args_.getLastArg(gollvm::options::OPT_print_file_name_EQ);
if (pfnarg) {
llvm::outs() << getFilePath(pfnarg->getValue(), *tc) << "\n";
exit(0);
}
// Honor -print-prog-name=...
opt::Arg *ppnarg = args_.getLastArg(gollvm::options::OPT_print_prog_name_EQ);
if (ppnarg) {
llvm::outs() << getProgramPath(ppnarg->getValue(), *tc) << "\n";
exit(0);
}
// Check for existence of input files.
for (opt::Arg *arg : args_) {
if (arg->getOption().getKind() == opt::Option::InputClass) {
// Special case for "-" (always assumed to exist)
if (strcmp(arg->getValue(), "-")) {
std::string fn(arg->getValue());
// Check for existence of input file.
if (!sys::fs::exists(fn)) {
errs() << progname_ << ": error: input file '"
<< fn << "' does not exist\n";
return nullptr;
}
}
inputseen = true;
}
}
// Issue an error if no inputs.
if (! inputseen) {
if (args_.hasArg(gollvm::options::OPT_v) ||
args_.hasArg(gollvm::options::OPT__HASH_HASH_HASH))
exit(0);
errs() << progname_ << ": error: no inputs\n";
return nullptr;
}
// FIXME: add code to weed out unknown architectures (ex:
// SomethingWeird-unknown-linux-gnu).
return tc.get();
}
void Driver::appendInputActions(const inarglist &ifargs,
ActionList &result,
Compilation &compilation)
{
for (auto ifarg : ifargs) {
bool schedAction = false;
Action *act = nullptr;
if (!strcmp(ifarg->getValue(), "-")) {
opt::Arg *xarg = args_.getLastArg(gollvm::options::OPT_x);
assert(xarg);
const char *suf =
(llvm::StringRef(xarg->getValue()).equals("c") ? "c" :
(llvm::StringRef(xarg->getValue()).equals("go") ? "go" : "?"));
act = new ReadStdinAction(suf);
schedAction = true;
} else {
act = new InputAction(compilation.newArgArtifact(ifarg));
}
compilation.recordAction(act);
if (schedAction)
compilation.addAction(act);
result.push_back(act);
}
}
bool Driver::buildActions(Compilation &compilation)
{
inarglist gofiles;
inarglist asmfiles;
inarglist linkerinputfiles;
ActionList linkerInputActions;
// Examine inputs to see what sort of actions we need.
for (opt::Arg *arg : args_) {
if (arg->getOption().getKind() == opt::Option::InputClass) {
std::string fn(arg->getValue());
// At the moment the canonical "-" input (stdin) is assumed
// to be Go source.
if (!strcmp(arg->getValue(), "-")) {
gofiles.push_back(arg);
continue;
}
size_t dotindex = fn.find_last_of(".");
if (dotindex != std::string::npos) {
std::string extension = fn.substr(dotindex);
if (extension.compare(".s") == 0) {
asmfiles.push_back(arg);
continue;
} else if (extension.compare(".go") == 0) {
gofiles.push_back(arg);
continue;
} else if (extension.compare(".S") == 0) {
errs() << progname_ << ": warning: " << arg->getValue()
<< ": .S files (preprocessed assembler) not supported; "
<< "treating as linker input.\n";
}
}
// everything else assumed to be a linker input
linkerinputfiles.push_back(arg);
continue;
}
}
bool OPT_c = args_.hasArg(gollvm::options::OPT_c);
bool OPT_S = args_.hasArg(gollvm::options::OPT_S);
// For -c/-S compiles, a mix of Go and assembly currently not allowed.
if ((OPT_c || OPT_S) && !gofiles.empty() && !asmfiles.empty()) {
errs() << progname_ << ": error: cannot specify mix of "
<< "Go and assembly inputs with -c/-S\n";
return false;
}
// Handle Go compilation action if needed.
if (!gofiles.empty()) {
// Build a list of input actions for the go files.
ActionList inacts;
appendInputActions(gofiles, inacts, compilation);
// Create action
Action *gocompact =
new Action(Action::A_Compile, inacts);
compilation.recordAction(gocompact);
compilation.addAction(gocompact);
// Schedule assemble action now if no -S.
if (!OPT_S && !args_.hasArg(gollvm::options::OPT_emit_llvm)) {
// Create action
Action *asmact =
new Action(Action::A_Assemble, gocompact);
compilation.recordAction(asmact);
compilation.addAction(asmact);
if (!OPT_c)
linkerInputActions.push_back(asmact);
}
}
// Create actions to assemble any asm files appearing on the cmd line.
if (gofiles.empty() && !asmfiles.empty()) {
// Issue an error if -c in combination with multiple files.
if (OPT_c && asmfiles.size() > 1) {
errs() << progname_ << ": error: cannot specify multiple inputs "
<< "in combination with -c\n";
return false;
}
for (auto asmf : asmfiles) {
// Input action.
InputAction *ia = new InputAction(compilation.newArgArtifact(asmf));
compilation.recordAction(ia);
// Assemble action.
Action *asmact =
new Action(Action::A_Assemble, ia);
compilation.recordAction(asmact);
compilation.addAction(asmact);
if (!OPT_c)
linkerInputActions.push_back(asmact);
}
}
// If -S or -c, we are done at this point.
if (OPT_c || OPT_S)
return true;
// Create a linker action.
appendInputActions(linkerinputfiles, linkerInputActions, compilation);
Action *linkact =
new Action(Action::A_Link, linkerInputActions);
compilation.recordAction(linkact);
compilation.addAction(linkact);
return true;
}
ArtifactList Driver::collectInputArtifacts(Action *act, InternalTool *it)
{
ArtifactList result;
for (auto &input : act->inputs()) {
InputAction *inact = input->castToInputAction();
if (inact != nullptr) {
result.push_back(inact->input());
continue;
}
// It is an error if an internal-tool action is receiving a result
// from an external tool (in the current model all internal-tool actions
// have to take place before any external-tool actions).
assert(it == nullptr || input->castToReadStdinAction() != nullptr);
auto it = artmap_.find(input);
assert(it != artmap_.end());
result.push_back(it->second);
}
return result;
}
bool Driver::processAction(Action *act, Compilation &compilation, bool lastAct)
{
// Select the result file for this action.
Artifact *result = nullptr;
if (!lastAct) {
auto tfa = compilation.createTemporaryFileArtifact(act);
if (!tfa)
return false;
result = *tfa;
artmap_[act] = result;
} else {
result = compilation.createOutputFileArtifact(act);
}
// Select tool to process the action.
Tool *tool = compilation.toolchain().getTool(act);
assert(tool != nullptr);
InternalTool *it = tool->castToInternalTool();
// Collect input artifacts for this
ArtifactList actionInputs = collectInputArtifacts(act, it);
// If internal tool, perform now.
if (it != nullptr) {
if (! it->performAction(compilation, *act, actionInputs, *result))
return false;
return true;
}
// External tool: build command
ExternalTool *et = tool->castToExternalTool();
if (! et->constructCommand(compilation, *act, actionInputs, *result))
return false;
return true;
}
bool Driver::processActions(Compilation &compilation)
{
SmallVector<Action*, 8> actv;
for (Action *action : compilation.actions())
actv.push_back(action);
for (unsigned ai = 0; ai < actv.size(); ++ai) {
Action *act = actv[ai];
bool lastAction = (ai == actv.size()-1);
if (!processAction(act, compilation, lastAction))
return false;
}
return true;
}
} // end namespace driver
} // end namespace gollvm