mirror of
https://github.com/ldc-developers/ldc.git
synced 2025-05-07 03:16:05 +03:00

For invoking MS tools such as link.exe and lib.exe, which require compatible PATH, LIB, LIBPATH etc. We previously wrapped the invokation of the tool with a batch file taking care of setting up the environment and then calling the actual tool. I changed this to first invoke another batch file setting up its environment and then dumping all environment variables to a file. LDC then parses this file and updates its own environment variables. Later spawned child processes then inherit the updated environment, and the tools will be located by LLVM in updated PATH. This allows LDC to keep track of the used Visual C++ installation (and could so set up additional runtime libs required since VS 2015, something we would have needed some time back when we still supported VS 2013). It's also required as a prerequisite for integrating LLD as in-process linker on Windows. I quickly looked into the option of setting up the environment manually, but the MS batch files are full of quirks, mixtures of registry and file-system lookups, checking for existence of misc. headers to filter out bogus installations etc. With VS 2017, there's nothing in the registry at all anymore (at least for the Build Tools); MS expose a COM interface for querying the VS installer... But at least they still provide the batch files.
561 lines
17 KiB
C++
561 lines
17 KiB
C++
//===-- toobj.cpp ---------------------------------------------------------===//
|
||
//
|
||
// LDC – the LLVM D compiler
|
||
//
|
||
// This file is distributed under the BSD-style LDC license. See the LICENSE
|
||
// file for details.
|
||
//
|
||
//===----------------------------------------------------------------------===//
|
||
|
||
#include "driver/toobj.h"
|
||
|
||
#include "driver/cl_options.h"
|
||
#include "driver/cache.h"
|
||
#include "driver/targetmachine.h"
|
||
#include "driver/tool.h"
|
||
#include "gen/irstate.h"
|
||
#include "gen/logger.h"
|
||
#include "gen/optimizer.h"
|
||
#include "gen/programs.h"
|
||
#include "llvm/IR/AssemblyAnnotationWriter.h"
|
||
#include "llvm/IR/Verifier.h"
|
||
#if LDC_LLVM_VER >= 309
|
||
#include "llvm/Analysis/ModuleSummaryAnalysis.h"
|
||
#endif
|
||
#if LDC_LLVM_VER >= 400
|
||
#include "llvm/Analysis/ProfileSummaryInfo.h"
|
||
#include "llvm/Bitcode/BitcodeWriter.h"
|
||
#else
|
||
#include "llvm/Bitcode/ReaderWriter.h"
|
||
#endif
|
||
#if LDC_LLVM_VER >= 307
|
||
#include "llvm/IR/LegacyPassManager.h"
|
||
#else
|
||
#include "llvm/PassManager.h"
|
||
#endif
|
||
#include "llvm/Support/CommandLine.h"
|
||
#include "llvm/Support/FileSystem.h"
|
||
#include "llvm/Support/FormattedStream.h"
|
||
#include "llvm/Support/Program.h"
|
||
#if LDC_LLVM_VER >= 307
|
||
#include "llvm/Support/Path.h"
|
||
#endif
|
||
#include "llvm/Target/TargetMachine.h"
|
||
#if LDC_LLVM_VER >= 307
|
||
#include "llvm/Analysis/TargetTransformInfo.h"
|
||
#endif
|
||
#if LDC_LLVM_VER >= 306
|
||
#include "llvm/Target/TargetSubtargetInfo.h"
|
||
#endif
|
||
#include "llvm/IR/Module.h"
|
||
#include <cstddef>
|
||
#include <fstream>
|
||
|
||
#if LDC_LLVM_VER >= 306
|
||
using LLErrorInfo = std::error_code;
|
||
#define ERRORINFO_STRING(errinfo) errinfo.message().c_str()
|
||
#else
|
||
using LLErrorInfo = std::string;
|
||
#define ERRORINFO_STRING(errinfo) errinfo.c_str()
|
||
#endif
|
||
|
||
static llvm::cl::opt<bool>
|
||
NoIntegratedAssembler("no-integrated-as", llvm::cl::Hidden,
|
||
llvm::cl::desc("Disable integrated assembler"));
|
||
|
||
// based on llc code, University of Illinois Open Source License
|
||
static void codegenModule(llvm::TargetMachine &Target, llvm::Module &m,
|
||
llvm::raw_fd_ostream &out,
|
||
llvm::TargetMachine::CodeGenFileType fileType) {
|
||
using namespace llvm;
|
||
|
||
// Create a PassManager to hold and optimize the collection of passes we are
|
||
// about to build.
|
||
#if LDC_LLVM_VER >= 307
|
||
legacy::
|
||
#endif
|
||
PassManager Passes;
|
||
|
||
#if LDC_LLVM_VER >= 307
|
||
// The DataLayout is already set at the module (in module.cpp,
|
||
// method Module::genLLVMModule())
|
||
// FIXME: Introduce new command line switch default-data-layout to
|
||
// override the module data layout
|
||
#elif LDC_LLVM_VER == 306
|
||
Passes.add(new DataLayoutPass());
|
||
#else
|
||
if (const DataLayout *DL = Target.getDataLayout())
|
||
Passes.add(new DataLayoutPass(*DL));
|
||
else
|
||
Passes.add(new DataLayoutPass(&m));
|
||
#endif
|
||
|
||
#if LDC_LLVM_VER >= 307
|
||
// Add internal analysis passes from the target machine.
|
||
Passes.add(
|
||
createTargetTransformInfoWrapperPass(Target.getTargetIRAnalysis()));
|
||
#else
|
||
Target.addAnalysisPasses(Passes);
|
||
#endif
|
||
|
||
#if LDC_LLVM_VER < 307
|
||
llvm::formatted_raw_ostream fout(out);
|
||
#endif
|
||
if (Target.addPassesToEmitFile(Passes,
|
||
#if LDC_LLVM_VER >= 307
|
||
out,
|
||
#else
|
||
fout,
|
||
#endif
|
||
fileType, codeGenOptLevel())) {
|
||
llvm_unreachable("no support for asm output");
|
||
}
|
||
|
||
Passes.run(m);
|
||
}
|
||
|
||
static void assemble(const std::string &asmpath, const std::string &objpath) {
|
||
std::vector<std::string> args;
|
||
args.push_back("-O3");
|
||
args.push_back("-c");
|
||
args.push_back("-xassembler");
|
||
args.push_back(asmpath);
|
||
args.push_back("-o");
|
||
args.push_back(objpath);
|
||
|
||
// Only specify -m32/-m64 for architectures where the two variants actually
|
||
// exist (as e.g. the GCC ARM toolchain doesn't recognize the switches).
|
||
// MIPS does not have -m32/-m64 but requires -mabi=.
|
||
if (global.params.targetTriple->get64BitArchVariant().getArch() !=
|
||
llvm::Triple::UnknownArch &&
|
||
global.params.targetTriple->get32BitArchVariant().getArch() !=
|
||
llvm::Triple::UnknownArch) {
|
||
if (global.params.targetTriple->get64BitArchVariant().getArch() ==
|
||
llvm::Triple::mips64 ||
|
||
global.params.targetTriple->get64BitArchVariant().getArch() ==
|
||
llvm::Triple::mips64el) {
|
||
switch (getMipsABI()) {
|
||
case MipsABI::EABI:
|
||
args.push_back("-mabi=eabi");
|
||
args.push_back("-march=mips32r2");
|
||
break;
|
||
case MipsABI::O32:
|
||
args.push_back("-mabi=32");
|
||
args.push_back("-march=mips32r2");
|
||
break;
|
||
case MipsABI::N32:
|
||
args.push_back("-mabi=n32");
|
||
args.push_back("-march=mips64r2");
|
||
break;
|
||
case MipsABI::N64:
|
||
args.push_back("-mabi=64");
|
||
args.push_back("-march=mips64r2");
|
||
break;
|
||
case MipsABI::Unknown:
|
||
break;
|
||
}
|
||
} else {
|
||
if (global.params.is64bit) {
|
||
args.push_back("-m64");
|
||
} else {
|
||
args.push_back("-m32");
|
||
}
|
||
}
|
||
}
|
||
|
||
// Run the compiler to assembly the program.
|
||
int R = executeToolAndWait(getGcc(), args, global.params.verbose);
|
||
if (R) {
|
||
error(Loc(), "Error while invoking external assembler.");
|
||
fatal();
|
||
}
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
namespace {
|
||
using namespace llvm;
|
||
static void printDebugLoc(const DebugLoc &debugLoc, formatted_raw_ostream &os) {
|
||
os << debugLoc.getLine() << ":" << debugLoc.getCol();
|
||
#if LDC_LLVM_VER >= 307
|
||
if (DILocation *IDL = debugLoc.getInlinedAt()) {
|
||
os << "@";
|
||
printDebugLoc(IDL, os);
|
||
}
|
||
#else
|
||
if (MDNode *N = debugLoc.getInlinedAt(getGlobalContext())) {
|
||
DebugLoc IDL = DebugLoc::getFromDILocation(N);
|
||
if (!IDL.isUnknown()) {
|
||
os << "@";
|
||
printDebugLoc(IDL, os);
|
||
}
|
||
}
|
||
#endif
|
||
}
|
||
|
||
class AssemblyAnnotator : public AssemblyAnnotationWriter {
|
||
// Find the MDNode which corresponds to the DISubprogram data that described F.
|
||
#if LDC_LLVM_VER >= 307
|
||
static DISubprogram *FindSubprogram(const Function *F,
|
||
DebugInfoFinder &Finder)
|
||
#else
|
||
static MDNode *FindSubprogram(const Function *F, DebugInfoFinder &Finder)
|
||
#endif
|
||
{
|
||
#if LDC_LLVM_VER >= 307
|
||
for (DISubprogram *Subprogram : Finder.subprograms())
|
||
if (Subprogram->describes(F))
|
||
return Subprogram;
|
||
return nullptr;
|
||
#else
|
||
for (DISubprogram Subprogram : Finder.subprograms()) {
|
||
if (Subprogram.describes(F)) {
|
||
return Subprogram;
|
||
}
|
||
}
|
||
return nullptr;
|
||
#endif
|
||
}
|
||
|
||
static llvm::StringRef GetDisplayName(const Function *F) {
|
||
llvm::DebugInfoFinder Finder;
|
||
Finder.processModule(*F->getParent());
|
||
#if LDC_LLVM_VER >= 307
|
||
if (DISubprogram *N = FindSubprogram(F, Finder))
|
||
#else
|
||
if (MDNode *N = FindSubprogram(F, Finder))
|
||
#endif
|
||
{
|
||
#if LDC_LLVM_VER >= 307
|
||
return N->getDisplayName();
|
||
#else
|
||
llvm::DISubprogram sub(N);
|
||
return sub.getDisplayName();
|
||
#endif
|
||
}
|
||
return "";
|
||
}
|
||
|
||
public:
|
||
void emitFunctionAnnot(const Function *F,
|
||
formatted_raw_ostream &os) LLVM_OVERRIDE {
|
||
os << "; [#uses = " << F->getNumUses() << ']';
|
||
|
||
// show demangled name
|
||
llvm::StringRef funcName = GetDisplayName(F);
|
||
if (!funcName.empty()) {
|
||
os << " [display name = " << funcName << ']';
|
||
}
|
||
os << '\n';
|
||
}
|
||
|
||
void printInfoComment(const Value &val,
|
||
formatted_raw_ostream &os) LLVM_OVERRIDE {
|
||
bool padding = false;
|
||
if (!val.getType()->isVoidTy()) {
|
||
os.PadToColumn(50);
|
||
padding = true;
|
||
os << "; [#uses = " << val.getNumUses();
|
||
if (isa<GetElementPtrInst>(&val) || isa<PHINode>(&val)) {
|
||
// Only print type for instructions where it is not obvious
|
||
// from being repeated in its parameters. Might need to be
|
||
// extended, but GEPs/PHIs are the most common ones.
|
||
os << ", type = " << *val.getType();
|
||
} else if (isa<AllocaInst>(&val)) {
|
||
os << ", size/byte = "
|
||
<< gDataLayout->getTypeAllocSize(val.getType()->getContainedType(0));
|
||
}
|
||
os << ']';
|
||
}
|
||
|
||
const Instruction *instr = dyn_cast<Instruction>(&val);
|
||
if (!instr) {
|
||
return;
|
||
}
|
||
|
||
#if LDC_LLVM_VER >= 307
|
||
if (const DebugLoc &debugLoc = instr->getDebugLoc())
|
||
#else
|
||
const DebugLoc &debugLoc = instr->getDebugLoc();
|
||
if (!debugLoc.isUnknown())
|
||
#endif
|
||
{
|
||
if (!padding) {
|
||
os.PadToColumn(50);
|
||
padding = true;
|
||
os << ';';
|
||
}
|
||
os << " [debug line = ";
|
||
printDebugLoc(debugLoc, os);
|
||
os << ']';
|
||
}
|
||
if (const DbgDeclareInst *DDI = dyn_cast<DbgDeclareInst>(instr)) {
|
||
#if LDC_LLVM_VER >= 307
|
||
DILocalVariable *Var(DDI->getVariable());
|
||
#else
|
||
DIVariable Var(DDI->getVariable());
|
||
#endif
|
||
if (!padding) {
|
||
os.PadToColumn(50);
|
||
os << ";";
|
||
}
|
||
#if LDC_LLVM_VER >= 307
|
||
os << " [debug variable = " << Var->getName() << ']';
|
||
#else
|
||
os << " [debug variable = " << Var.getName() << ']';
|
||
#endif
|
||
} else if (const DbgValueInst *DVI = dyn_cast<DbgValueInst>(instr)) {
|
||
#if LDC_LLVM_VER >= 307
|
||
DILocalVariable *Var(DVI->getVariable());
|
||
#else
|
||
DIVariable Var(DVI->getVariable());
|
||
#endif
|
||
if (!padding) {
|
||
os.PadToColumn(50);
|
||
os << ";";
|
||
}
|
||
#if LDC_LLVM_VER >= 307
|
||
os << " [debug variable = " << Var->getName() << ']';
|
||
#else
|
||
os << " [debug variable = " << Var.getName() << ']';
|
||
#endif
|
||
} else if (const CallInst *callinstr = dyn_cast<CallInst>(instr)) {
|
||
const Function *F = callinstr->getCalledFunction();
|
||
if (!F) {
|
||
return;
|
||
}
|
||
|
||
StringRef funcName = GetDisplayName(F);
|
||
if (!funcName.empty()) {
|
||
if (!padding) {
|
||
os.PadToColumn(50);
|
||
os << ";";
|
||
}
|
||
os << " [display name = " << funcName << ']';
|
||
}
|
||
} else if (const InvokeInst *invokeinstr = dyn_cast<InvokeInst>(instr)) {
|
||
const Function *F = invokeinstr->getCalledFunction();
|
||
if (!F) {
|
||
return;
|
||
}
|
||
|
||
StringRef funcName = GetDisplayName(F);
|
||
if (!funcName.empty()) {
|
||
if (!padding) {
|
||
os.PadToColumn(50);
|
||
os << ";";
|
||
}
|
||
os << " [display name = " << funcName << ']';
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
void writeObjectFile(llvm::Module *m, const char *filename) {
|
||
IF_LOG Logger::println("Writing object file to: %s", filename);
|
||
LLErrorInfo errinfo;
|
||
{
|
||
llvm::raw_fd_ostream out(filename, errinfo, llvm::sys::fs::F_None);
|
||
#if LDC_LLVM_VER >= 306
|
||
if (!errinfo)
|
||
#else
|
||
if (errinfo.empty())
|
||
#endif
|
||
{
|
||
codegenModule(*gTargetMachine, *m, out,
|
||
llvm::TargetMachine::CGFT_ObjectFile);
|
||
} else {
|
||
error(Loc(), "cannot write object file '%s': %s", filename,
|
||
ERRORINFO_STRING(errinfo));
|
||
fatal();
|
||
}
|
||
}
|
||
}
|
||
|
||
bool shouldAssembleExternally() {
|
||
// There is no integrated assembler on AIX because XCOFF is not supported.
|
||
// Starting with LLVM 3.5 the integrated assembler can be used with MinGW.
|
||
return global.params.output_o &&
|
||
(NoIntegratedAssembler ||
|
||
global.params.targetTriple->getOS() == llvm::Triple::AIX);
|
||
}
|
||
|
||
bool shouldOutputObjectFile() {
|
||
return global.params.output_o && !shouldAssembleExternally();
|
||
}
|
||
|
||
bool shouldDoLTO(llvm::Module *m) {
|
||
#if LDC_LLVM_VER < 309
|
||
return false;
|
||
#else
|
||
#if LDC_LLVM_VER == 309
|
||
// LLVM 3.9 bug: can't do ThinLTO with modules that have module-scope inline
|
||
// assembly blocks (duplicate definitions upon importing from such a module).
|
||
// https://llvm.org/bugs/show_bug.cgi?id=30610
|
||
if (opts::isUsingThinLTO() && !m->getModuleInlineAsm().empty())
|
||
return false;
|
||
#endif
|
||
return opts::isUsingLTO();
|
||
#endif
|
||
}
|
||
} // end of anonymous namespace
|
||
|
||
void writeModule(llvm::Module *m, const char *filename) {
|
||
const bool doLTO = shouldDoLTO(m);
|
||
const bool outputObj = shouldOutputObjectFile();
|
||
const bool assembleExternally = shouldAssembleExternally();
|
||
|
||
// Use cached object code if possible.
|
||
// TODO: combine LDC's cache and LTO (the advantage is skipping the IR
|
||
// optimization).
|
||
const bool useIR2ObjCache =
|
||
!opts::cacheDir.empty() && outputObj && !doLTO;
|
||
llvm::SmallString<32> moduleHash;
|
||
if (useIR2ObjCache) {
|
||
llvm::SmallString<128> cacheDir(opts::cacheDir.c_str());
|
||
llvm::sys::fs::make_absolute(cacheDir);
|
||
opts::cacheDir = cacheDir.c_str();
|
||
|
||
IF_LOG Logger::println("Use IR-to-Object cache in %s",
|
||
opts::cacheDir.c_str());
|
||
LOG_SCOPE
|
||
|
||
cache::calculateModuleHash(m, moduleHash);
|
||
std::string cacheFile = cache::cacheLookup(moduleHash);
|
||
if (!cacheFile.empty()) {
|
||
cache::recoverObjectFile(moduleHash, filename);
|
||
return;
|
||
}
|
||
}
|
||
|
||
// run optimizer
|
||
ldc_optimize_module(m);
|
||
|
||
// make sure the output directory exists
|
||
const auto directory = llvm::sys::path::parent_path(filename);
|
||
if (!directory.empty()) {
|
||
if (auto ec = llvm::sys::fs::create_directories(directory)) {
|
||
error(Loc(), "failed to create output directory: %s\n%s",
|
||
directory.data(), ec.message().c_str());
|
||
fatal();
|
||
}
|
||
}
|
||
|
||
const auto outputFlags = {global.params.output_o, global.params.output_bc,
|
||
global.params.output_ll, global.params.output_s};
|
||
const auto numOutputFiles =
|
||
std::count_if(outputFlags.begin(), outputFlags.end(),
|
||
[](OUTPUTFLAG flag) { return flag != 0; });
|
||
|
||
const auto replaceExtensionWith = [=](const char *ext) -> std::string {
|
||
if (numOutputFiles == 1)
|
||
return filename;
|
||
llvm::SmallString<128> buffer(filename);
|
||
llvm::sys::path::replace_extension(buffer, ext);
|
||
return buffer.str();
|
||
};
|
||
|
||
// write LLVM bitcode
|
||
if (global.params.output_bc || (doLTO && outputObj)) {
|
||
std::string bcpath =
|
||
(doLTO && outputObj) ? filename : replaceExtensionWith(global.bc_ext);
|
||
Logger::println("Writing LLVM bitcode to: %s\n", bcpath.c_str());
|
||
LLErrorInfo errinfo;
|
||
llvm::raw_fd_ostream bos(bcpath.c_str(), errinfo, llvm::sys::fs::F_None);
|
||
if (bos.has_error()) {
|
||
error(Loc(), "cannot write LLVM bitcode file '%s': %s", bcpath.c_str(),
|
||
ERRORINFO_STRING(errinfo));
|
||
fatal();
|
||
}
|
||
if (opts::isUsingThinLTO()) {
|
||
#if LDC_LLVM_VER >= 309
|
||
Logger::println("Creating module summary for ThinLTO");
|
||
#if LDC_LLVM_VER == 309
|
||
// When the function freq info callback is set to nullptr, LLVM will
|
||
// calculate it automatically for us.
|
||
llvm::ModuleSummaryIndexBuilder indexBuilder(
|
||
m, /* function freq callback */ nullptr);
|
||
auto &moduleSummaryIndex = indexBuilder.getIndex();
|
||
#else
|
||
llvm::ProfileSummaryInfo PSI(*m);
|
||
// Set PSIptr to nullptr when there is no PGO information available, such
|
||
// that LLVM will not try to find PGO information for each function inside
|
||
// `buildModuleSummaryIndex`. (micro-optimization)
|
||
auto PSIptr = m->getProfileSummary() ? &PSI : nullptr;
|
||
|
||
// When the function freq info callback is set to nullptr, LLVM will
|
||
// calculate it automatically for us.
|
||
auto moduleSummaryIndex = buildModuleSummaryIndex(
|
||
*m, /* function freq callback */ nullptr, PSIptr);
|
||
#endif
|
||
|
||
llvm::WriteBitcodeToFile(m, bos, true, &moduleSummaryIndex,
|
||
/* generate ThinLTO hash */ true);
|
||
#endif
|
||
} else {
|
||
llvm::WriteBitcodeToFile(m, bos);
|
||
}
|
||
}
|
||
|
||
// write LLVM IR
|
||
if (global.params.output_ll) {
|
||
const auto llpath = replaceExtensionWith(global.ll_ext);
|
||
Logger::println("Writing LLVM IR to: %s\n", llpath.c_str());
|
||
LLErrorInfo errinfo;
|
||
llvm::raw_fd_ostream aos(llpath.c_str(), errinfo, llvm::sys::fs::F_None);
|
||
if (aos.has_error()) {
|
||
error(Loc(), "cannot write LLVM IR file '%s': %s", llpath.c_str(),
|
||
ERRORINFO_STRING(errinfo));
|
||
fatal();
|
||
}
|
||
AssemblyAnnotator annotator;
|
||
m->print(aos, &annotator);
|
||
}
|
||
|
||
// write native assembly
|
||
if (global.params.output_s || assembleExternally) {
|
||
std::string spath;
|
||
if (!global.params.output_s) {
|
||
llvm::SmallString<16> buffer;
|
||
llvm::sys::fs::createUniqueFile("ldc-%%%%%%%.s", buffer);
|
||
spath = buffer.str();
|
||
} else {
|
||
spath = replaceExtensionWith(global.s_ext);
|
||
}
|
||
|
||
Logger::println("Writing asm to: %s\n", spath.c_str());
|
||
LLErrorInfo errinfo;
|
||
{
|
||
llvm::raw_fd_ostream out(spath.c_str(), errinfo, llvm::sys::fs::F_None);
|
||
#if LDC_LLVM_VER >= 306
|
||
if (!errinfo)
|
||
#else
|
||
if (errinfo.empty())
|
||
#endif
|
||
{
|
||
codegenModule(*gTargetMachine, *m, out,
|
||
llvm::TargetMachine::CGFT_AssemblyFile);
|
||
} else {
|
||
error(Loc(), "cannot write asm: %s", ERRORINFO_STRING(errinfo));
|
||
fatal();
|
||
}
|
||
}
|
||
|
||
if (assembleExternally) {
|
||
assemble(spath, filename);
|
||
}
|
||
|
||
if (!global.params.output_s) {
|
||
llvm::sys::fs::remove(spath);
|
||
}
|
||
}
|
||
|
||
if (outputObj && !doLTO) {
|
||
writeObjectFile(m, filename);
|
||
if (useIR2ObjCache) {
|
||
cache::cacheObjectFile(filename, moduleHash);
|
||
}
|
||
}
|
||
}
|
||
|
||
#undef ERRORINFO_STRING
|