Chandler Carruth dd647e3e60
Rework the Option library to reduce dynamic relocations (#119198)
Apologies for the large change, I looked for ways to break this up and
all of the ones I saw added real complexity. This change focuses on the
option's prefixed names and the array of prefixes. These are present in
every option and the dominant source of dynamic relocations for PIE or
PIC users of LLVM and Clang tooling. In some cases, 100s or 1000s of
them for the Clang driver which has a huge number of options.

This PR addresses this by building a string table and a prefixes table
that can be referenced with indices rather than pointers that require
dynamic relocations. This removes almost 7k dynmaic relocations from the
`clang` binary, roughly 8% of the remaining dynmaic relocations outside
of vtables. For busy-boxing use cases where many different option tables
are linked into the same binary, the savings add up a bit more.

The string table is a straightforward mechanism, but the prefixes
required some subtlety. They are encoded in a Pascal-string fashion with
a size followed by a sequence of offsets. This works relatively well for
the small realistic prefixes arrays in use.

Lots of code has to change in order to land this though: both all the
option library code has to be updated to use the string table and
prefixes table, and all the users of the options library have to be
updated to correctly instantiate the objects.

Some follow-up patches in the works to provide an abstraction for this
style of code, and to start using the same technique for some of the
other strings here now that the infrastructure is in place.
2024-12-11 15:44:44 -08:00

635 lines
22 KiB
C++

//===- llvm-ifs.cpp -------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===-----------------------------------------------------------------------===/
#include "ErrorCollector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/InterfaceStub/ELFObjHandler.h"
#include "llvm/InterfaceStub/IFSHandler.h"
#include "llvm/InterfaceStub/IFSStub.h"
#include "llvm/ObjectYAML/yaml2obj.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/Option.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileOutputBuffer.h"
#include "llvm/Support/LLVMDriver.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/VersionTuple.h"
#include "llvm/Support/WithColor.h"
#include "llvm/Support/YAMLTraits.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/TargetParser/Triple.h"
#include "llvm/TextAPI/InterfaceFile.h"
#include "llvm/TextAPI/TextAPIReader.h"
#include "llvm/TextAPI/TextAPIWriter.h"
#include <optional>
#include <set>
#include <string>
#include <vector>
using namespace llvm;
using namespace llvm::yaml;
using namespace llvm::MachO;
using namespace llvm::ifs;
#define DEBUG_TYPE "llvm-ifs"
namespace {
const VersionTuple IfsVersionCurrent(3, 0);
enum class FileFormat { IFS, ELF, TBD };
} // end anonymous namespace
using namespace llvm::opt;
enum ID {
OPT_INVALID = 0, // This is not an option ID.
#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
#include "Opts.inc"
#undef OPTION
};
#define OPTTABLE_STR_TABLE_CODE
#include "Opts.inc"
#undef OPTTABLE_STR_TABLE_CODE
#define OPTTABLE_PREFIXES_TABLE_CODE
#include "Opts.inc"
#undef OPTTABLE_PREFIXES_TABLE_CODE
static constexpr opt::OptTable::Info InfoTable[] = {
#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
#include "Opts.inc"
#undef OPTION
};
class IFSOptTable : public opt::GenericOptTable {
public:
IFSOptTable()
: opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {
setGroupedShortOptions(true);
}
};
struct DriverConfig {
std::vector<std::string> InputFilePaths;
std::optional<FileFormat> InputFormat;
std::optional<FileFormat> OutputFormat;
std::optional<std::string> HintIfsTarget;
std::optional<std::string> OptTargetTriple;
std::optional<IFSArch> OverrideArch;
std::optional<IFSBitWidthType> OverrideBitWidth;
std::optional<IFSEndiannessType> OverrideEndianness;
bool StripIfsArch = false;
bool StripIfsBitwidth = false;
bool StripIfsEndianness = false;
bool StripIfsTarget = false;
bool StripNeeded = false;
bool StripSize = false;
bool StripUndefined = false;
std::vector<std::string> Exclude;
std::optional<std::string> SoName;
std::optional<std::string> Output;
std::optional<std::string> OutputElf;
std::optional<std::string> OutputIfs;
std::optional<std::string> OutputTbd;
bool WriteIfChanged = false;
};
static std::string getTypeName(IFSSymbolType Type) {
switch (Type) {
case IFSSymbolType::NoType:
return "NoType";
case IFSSymbolType::Func:
return "Func";
case IFSSymbolType::Object:
return "Object";
case IFSSymbolType::TLS:
return "TLS";
case IFSSymbolType::Unknown:
return "Unknown";
}
llvm_unreachable("Unexpected ifs symbol type.");
}
static Expected<std::unique_ptr<IFSStub>>
readInputFile(std::optional<FileFormat> &InputFormat, StringRef FilePath) {
// Read in file.
ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrError =
MemoryBuffer::getFileOrSTDIN(FilePath, /*IsText=*/true);
if (!BufOrError)
return createStringError(BufOrError.getError(), "Could not open `%s`",
FilePath.data());
std::unique_ptr<MemoryBuffer> FileReadBuffer = std::move(*BufOrError);
ErrorCollector EC(/*UseFatalErrors=*/false);
// First try to read as a binary (fails fast if not binary).
if (!InputFormat || *InputFormat == FileFormat::ELF) {
Expected<std::unique_ptr<IFSStub>> StubFromELF =
readELFFile(FileReadBuffer->getMemBufferRef());
if (StubFromELF) {
InputFormat = FileFormat::ELF;
(*StubFromELF)->IfsVersion = IfsVersionCurrent;
return std::move(*StubFromELF);
}
EC.addError(StubFromELF.takeError(), "BinaryRead");
}
// Fall back to reading as a ifs.
if (!InputFormat || *InputFormat == FileFormat::IFS) {
Expected<std::unique_ptr<IFSStub>> StubFromIFS =
readIFSFromBuffer(FileReadBuffer->getBuffer());
if (StubFromIFS) {
InputFormat = FileFormat::IFS;
if ((*StubFromIFS)->IfsVersion > IfsVersionCurrent)
EC.addError(
createStringError(errc::not_supported,
"IFS version " +
(*StubFromIFS)->IfsVersion.getAsString() +
" is unsupported."),
"ReadInputFile");
else
return std::move(*StubFromIFS);
} else {
EC.addError(StubFromIFS.takeError(), "YamlParse");
}
}
// If both readers fail, build a new error that includes all information.
EC.addError(createStringError(errc::not_supported,
"No file readers succeeded reading `%s` "
"(unsupported/malformed file?)",
FilePath.data()),
"ReadInputFile");
EC.escalateToFatal();
return EC.makeError();
}
static int writeTbdStub(const Triple &T, const std::vector<IFSSymbol> &Symbols,
const StringRef Format, raw_ostream &Out) {
auto PlatformTypeOrError =
[](const llvm::Triple &T) -> llvm::Expected<llvm::MachO::PlatformType> {
if (T.isMacOSX())
return llvm::MachO::PLATFORM_MACOS;
if (T.isTvOS())
return llvm::MachO::PLATFORM_TVOS;
if (T.isWatchOS())
return llvm::MachO::PLATFORM_WATCHOS;
// Note: put isiOS last because tvOS and watchOS are also iOS according
// to the Triple.
if (T.isiOS())
return llvm::MachO::PLATFORM_IOS;
return createStringError(errc::not_supported, "Invalid Platform.\n");
}(T);
if (!PlatformTypeOrError)
return -1;
PlatformType Plat = PlatformTypeOrError.get();
TargetList Targets({Target(llvm::MachO::mapToArchitecture(T), Plat)});
InterfaceFile File;
File.setFileType(FileType::TBD_V3); // Only supporting v3 for now.
File.addTargets(Targets);
for (const auto &Symbol : Symbols) {
auto Name = Symbol.Name;
auto Kind = EncodeKind::GlobalSymbol;
switch (Symbol.Type) {
default:
case IFSSymbolType::NoType:
Kind = EncodeKind::GlobalSymbol;
break;
case IFSSymbolType::Object:
Kind = EncodeKind::GlobalSymbol;
break;
case IFSSymbolType::Func:
Kind = EncodeKind::GlobalSymbol;
break;
}
if (Symbol.Weak)
File.addSymbol(Kind, Name, Targets, SymbolFlags::WeakDefined);
else
File.addSymbol(Kind, Name, Targets);
}
SmallString<4096> Buffer;
raw_svector_ostream OS(Buffer);
if (Error Result = TextAPIWriter::writeToStream(OS, File))
return -1;
Out << OS.str();
return 0;
}
static void fatalError(Error Err) {
WithColor::defaultErrorHandler(std::move(Err));
exit(1);
}
static void fatalError(Twine T) {
WithColor::error() << T.str() << '\n';
exit(1);
}
/// writeIFS() writes a Text-Based ELF stub to a file using the latest version
/// of the YAML parser.
static Error writeIFS(StringRef FilePath, IFSStub &Stub, bool WriteIfChanged) {
// Write IFS to memory first.
std::string IFSStr;
raw_string_ostream OutStr(IFSStr);
Error YAMLErr = writeIFSToOutputStream(OutStr, Stub);
if (YAMLErr)
return YAMLErr;
OutStr.flush();
if (WriteIfChanged) {
if (ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrError =
MemoryBuffer::getFile(FilePath)) {
// Compare IFS output with the existing IFS file. If unchanged, avoid
// changing the file.
if ((*BufOrError)->getBuffer() == IFSStr)
return Error::success();
}
}
// Open IFS file for writing.
std::error_code SysErr;
raw_fd_ostream Out(FilePath, SysErr);
if (SysErr)
return createStringError(SysErr, "Couldn't open `%s` for writing",
FilePath.data());
Out << IFSStr;
return Error::success();
}
static DriverConfig parseArgs(int argc, char *const *argv) {
BumpPtrAllocator A;
StringSaver Saver(A);
IFSOptTable Tbl;
StringRef ToolName = argv[0];
llvm::opt::InputArgList Args = Tbl.parseArgs(
argc, argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { fatalError(Msg); });
if (Args.hasArg(OPT_help)) {
Tbl.printHelp(llvm::outs(),
(Twine(ToolName) + " <input_file> <output_file> [options]")
.str()
.c_str(),
"shared object stubbing tool");
std::exit(0);
}
if (Args.hasArg(OPT_version)) {
llvm::outs() << ToolName << '\n';
cl::PrintVersionMessage();
std::exit(0);
}
DriverConfig Config;
for (const opt::Arg *A : Args.filtered(OPT_INPUT))
Config.InputFilePaths.push_back(A->getValue());
if (const opt::Arg *A = Args.getLastArg(OPT_input_format_EQ)) {
Config.InputFormat = StringSwitch<std::optional<FileFormat>>(A->getValue())
.Case("IFS", FileFormat::IFS)
.Case("ELF", FileFormat::ELF)
.Default(std::nullopt);
if (!Config.InputFormat)
fatalError(Twine("invalid argument '") + A->getValue());
}
auto OptionNotFound = [ToolName](StringRef FlagName, StringRef OptionName) {
fatalError(Twine(ToolName) + ": for the " + FlagName +
" option: Cannot find option named '" + OptionName + "'!");
};
if (const opt::Arg *A = Args.getLastArg(OPT_output_format_EQ)) {
Config.OutputFormat = StringSwitch<std::optional<FileFormat>>(A->getValue())
.Case("IFS", FileFormat::IFS)
.Case("ELF", FileFormat::ELF)
.Case("TBD", FileFormat::TBD)
.Default(std::nullopt);
if (!Config.OutputFormat)
OptionNotFound("--output-format", A->getValue());
}
if (const opt::Arg *A = Args.getLastArg(OPT_arch_EQ)) {
uint16_t eMachine = ELF::convertArchNameToEMachine(A->getValue());
if (eMachine == ELF::EM_NONE) {
fatalError(Twine("unknown arch '") + A->getValue() + "'");
}
Config.OverrideArch = eMachine;
}
if (const opt::Arg *A = Args.getLastArg(OPT_bitwidth_EQ)) {
size_t Width;
llvm::StringRef S(A->getValue());
if (!S.getAsInteger<size_t>(10, Width) || Width == 64 || Width == 32)
Config.OverrideBitWidth =
Width == 64 ? IFSBitWidthType::IFS64 : IFSBitWidthType::IFS32;
else
OptionNotFound("--bitwidth", A->getValue());
}
if (const opt::Arg *A = Args.getLastArg(OPT_endianness_EQ)) {
Config.OverrideEndianness =
StringSwitch<std::optional<IFSEndiannessType>>(A->getValue())
.Case("little", IFSEndiannessType::Little)
.Case("big", IFSEndiannessType::Big)
.Default(std::nullopt);
if (!Config.OverrideEndianness)
OptionNotFound("--endianness", A->getValue());
}
if (const opt::Arg *A = Args.getLastArg(OPT_target_EQ))
Config.OptTargetTriple = A->getValue();
if (const opt::Arg *A = Args.getLastArg(OPT_hint_ifs_target_EQ))
Config.HintIfsTarget = A->getValue();
Config.StripIfsArch = Args.hasArg(OPT_strip_ifs_arch);
Config.StripIfsBitwidth = Args.hasArg(OPT_strip_ifs_bitwidth);
Config.StripIfsEndianness = Args.hasArg(OPT_strip_ifs_endianness);
Config.StripIfsTarget = Args.hasArg(OPT_strip_ifs_target);
Config.StripUndefined = Args.hasArg(OPT_strip_undefined);
Config.StripNeeded = Args.hasArg(OPT_strip_needed);
Config.StripSize = Args.hasArg(OPT_strip_size);
for (const opt::Arg *A : Args.filtered(OPT_exclude_EQ))
Config.Exclude.push_back(A->getValue());
if (const opt::Arg *A = Args.getLastArg(OPT_soname_EQ))
Config.SoName = A->getValue();
if (const opt::Arg *A = Args.getLastArg(OPT_output_EQ))
Config.Output = A->getValue();
if (const opt::Arg *A = Args.getLastArg(OPT_output_elf_EQ))
Config.OutputElf = A->getValue();
if (const opt::Arg *A = Args.getLastArg(OPT_output_ifs_EQ))
Config.OutputIfs = A->getValue();
if (const opt::Arg *A = Args.getLastArg(OPT_output_tbd_EQ))
Config.OutputTbd = A->getValue();
Config.WriteIfChanged = Args.hasArg(OPT_write_if_changed);
return Config;
}
int llvm_ifs_main(int argc, char **argv, const llvm::ToolContext &) {
DriverConfig Config = parseArgs(argc, argv);
if (Config.InputFilePaths.empty())
Config.InputFilePaths.push_back("-");
// If input files are more than one, they can only be IFS files.
if (Config.InputFilePaths.size() > 1)
Config.InputFormat = FileFormat::IFS;
// Attempt to merge input.
IFSStub Stub;
std::map<std::string, IFSSymbol> SymbolMap;
std::string PreviousInputFilePath;
for (const std::string &InputFilePath : Config.InputFilePaths) {
Expected<std::unique_ptr<IFSStub>> StubOrErr =
readInputFile(Config.InputFormat, InputFilePath);
if (!StubOrErr)
fatalError(StubOrErr.takeError());
std::unique_ptr<IFSStub> TargetStub = std::move(StubOrErr.get());
if (PreviousInputFilePath.empty()) {
Stub.IfsVersion = TargetStub->IfsVersion;
Stub.Target = TargetStub->Target;
Stub.SoName = TargetStub->SoName;
Stub.NeededLibs = TargetStub->NeededLibs;
} else {
if (Stub.IfsVersion != TargetStub->IfsVersion) {
if (Stub.IfsVersion.getMajor() != IfsVersionCurrent.getMajor()) {
WithColor::error()
<< "Interface Stub: IfsVersion Mismatch."
<< "\nFilenames: " << PreviousInputFilePath << " "
<< InputFilePath << "\nIfsVersion Values: " << Stub.IfsVersion
<< " " << TargetStub->IfsVersion << "\n";
return -1;
}
if (TargetStub->IfsVersion > Stub.IfsVersion)
Stub.IfsVersion = TargetStub->IfsVersion;
}
if (Stub.Target != TargetStub->Target && !TargetStub->Target.empty()) {
WithColor::error() << "Interface Stub: Target Mismatch."
<< "\nFilenames: " << PreviousInputFilePath << " "
<< InputFilePath;
return -1;
}
if (Stub.SoName != TargetStub->SoName) {
WithColor::error() << "Interface Stub: SoName Mismatch."
<< "\nFilenames: " << PreviousInputFilePath << " "
<< InputFilePath
<< "\nSoName Values: " << Stub.SoName << " "
<< TargetStub->SoName << "\n";
return -1;
}
if (Stub.NeededLibs != TargetStub->NeededLibs) {
WithColor::error() << "Interface Stub: NeededLibs Mismatch."
<< "\nFilenames: " << PreviousInputFilePath << " "
<< InputFilePath << "\n";
return -1;
}
}
for (auto Symbol : TargetStub->Symbols) {
auto [SI, Inserted] = SymbolMap.try_emplace(Symbol.Name, Symbol);
if (Inserted)
continue;
assert(Symbol.Name == SI->second.Name && "Symbol Names Must Match.");
// Check conflicts:
if (Symbol.Type != SI->second.Type) {
WithColor::error() << "Interface Stub: Type Mismatch for "
<< Symbol.Name << ".\nFilename: " << InputFilePath
<< "\nType Values: " << getTypeName(SI->second.Type)
<< " " << getTypeName(Symbol.Type) << "\n";
return -1;
}
if (Symbol.Size != SI->second.Size) {
WithColor::error() << "Interface Stub: Size Mismatch for "
<< Symbol.Name << ".\nFilename: " << InputFilePath
<< "\nSize Values: " << SI->second.Size << " "
<< Symbol.Size << "\n";
return -1;
}
if (Symbol.Weak != SI->second.Weak) {
Symbol.Weak = false;
continue;
}
// TODO: Not checking Warning. Will be dropped.
}
PreviousInputFilePath = InputFilePath;
}
if (Stub.IfsVersion != IfsVersionCurrent)
if (Stub.IfsVersion.getMajor() != IfsVersionCurrent.getMajor()) {
WithColor::error() << "Interface Stub: Bad IfsVersion: "
<< Stub.IfsVersion << ", llvm-ifs supported version: "
<< IfsVersionCurrent << ".\n";
return -1;
}
for (auto &Entry : SymbolMap)
Stub.Symbols.push_back(Entry.second);
// Change SoName before emitting stubs.
if (Config.SoName)
Stub.SoName = *Config.SoName;
Error OverrideError =
overrideIFSTarget(Stub, Config.OverrideArch, Config.OverrideEndianness,
Config.OverrideBitWidth, Config.OptTargetTriple);
if (OverrideError)
fatalError(std::move(OverrideError));
if (Config.StripNeeded)
Stub.NeededLibs.clear();
if (Error E = filterIFSSyms(Stub, Config.StripUndefined, Config.Exclude))
fatalError(std::move(E));
if (Config.StripSize)
for (IFSSymbol &Sym : Stub.Symbols)
Sym.Size.reset();
if (!Config.OutputElf && !Config.OutputIfs && !Config.OutputTbd) {
if (!Config.OutputFormat) {
WithColor::error() << "at least one output should be specified.";
return -1;
}
} else if (Config.OutputFormat) {
WithColor::error() << "'--output-format' cannot be used with "
"'--output-{FILE_FORMAT}' options at the same time";
return -1;
}
if (Config.OutputFormat) {
// TODO: Remove OutputFormat flag in the next revision.
WithColor::warning() << "--output-format option is deprecated, please use "
"--output-{FILE_FORMAT} options instead\n";
switch (*Config.OutputFormat) {
case FileFormat::TBD: {
std::error_code SysErr;
raw_fd_ostream Out(*Config.Output, SysErr);
if (SysErr) {
WithColor::error() << "Couldn't open " << *Config.Output
<< " for writing.\n";
return -1;
}
if (!Stub.Target.Triple) {
WithColor::error()
<< "Triple should be defined when output format is TBD";
return -1;
}
return writeTbdStub(llvm::Triple(*Stub.Target.Triple), Stub.Symbols,
"TBD", Out);
}
case FileFormat::IFS: {
Stub.IfsVersion = IfsVersionCurrent;
if (*Config.InputFormat == FileFormat::ELF && Config.HintIfsTarget) {
std::error_code HintEC(1, std::generic_category());
IFSTarget HintTarget = parseTriple(*Config.HintIfsTarget);
if (*Stub.Target.Arch != *HintTarget.Arch)
fatalError(make_error<StringError>(
"Triple hint does not match the actual architecture", HintEC));
if (*Stub.Target.Endianness != *HintTarget.Endianness)
fatalError(make_error<StringError>(
"Triple hint does not match the actual endianness", HintEC));
if (*Stub.Target.BitWidth != *HintTarget.BitWidth)
fatalError(make_error<StringError>(
"Triple hint does not match the actual bit width", HintEC));
stripIFSTarget(Stub, true, false, false, false);
Stub.Target.Triple = *Config.HintIfsTarget;
} else {
stripIFSTarget(Stub, Config.StripIfsTarget, Config.StripIfsArch,
Config.StripIfsEndianness, Config.StripIfsBitwidth);
}
Error IFSWriteError =
writeIFS(*Config.Output, Stub, Config.WriteIfChanged);
if (IFSWriteError)
fatalError(std::move(IFSWriteError));
break;
}
case FileFormat::ELF: {
Error TargetError = validateIFSTarget(Stub, true);
if (TargetError)
fatalError(std::move(TargetError));
Error BinaryWriteError =
writeBinaryStub(*Config.Output, Stub, Config.WriteIfChanged);
if (BinaryWriteError)
fatalError(std::move(BinaryWriteError));
break;
}
}
} else {
// Check if output path for individual format.
if (Config.OutputElf) {
Error TargetError = validateIFSTarget(Stub, true);
if (TargetError)
fatalError(std::move(TargetError));
Error BinaryWriteError =
writeBinaryStub(*Config.OutputElf, Stub, Config.WriteIfChanged);
if (BinaryWriteError)
fatalError(std::move(BinaryWriteError));
}
if (Config.OutputIfs) {
Stub.IfsVersion = IfsVersionCurrent;
if (*Config.InputFormat == FileFormat::ELF && Config.HintIfsTarget) {
std::error_code HintEC(1, std::generic_category());
IFSTarget HintTarget = parseTriple(*Config.HintIfsTarget);
if (*Stub.Target.Arch != *HintTarget.Arch)
fatalError(make_error<StringError>(
"Triple hint does not match the actual architecture", HintEC));
if (*Stub.Target.Endianness != *HintTarget.Endianness)
fatalError(make_error<StringError>(
"Triple hint does not match the actual endianness", HintEC));
if (*Stub.Target.BitWidth != *HintTarget.BitWidth)
fatalError(make_error<StringError>(
"Triple hint does not match the actual bit width", HintEC));
stripIFSTarget(Stub, true, false, false, false);
Stub.Target.Triple = *Config.HintIfsTarget;
} else {
stripIFSTarget(Stub, Config.StripIfsTarget, Config.StripIfsArch,
Config.StripIfsEndianness, Config.StripIfsBitwidth);
}
Error IFSWriteError =
writeIFS(*Config.OutputIfs, Stub, Config.WriteIfChanged);
if (IFSWriteError)
fatalError(std::move(IFSWriteError));
}
if (Config.OutputTbd) {
std::error_code SysErr;
raw_fd_ostream Out(*Config.OutputTbd, SysErr);
if (SysErr) {
WithColor::error() << "Couldn't open " << *Config.OutputTbd
<< " for writing.\n";
return -1;
}
if (!Stub.Target.Triple) {
WithColor::error()
<< "Triple should be defined when output format is TBD";
return -1;
}
return writeTbdStub(llvm::Triple(*Stub.Target.Triple), Stub.Symbols,
"TBD", Out);
}
}
return 0;
}